1
0
forked from Cutlery/immich

Compare commits

...

38 Commits

Author SHA1 Message Date
Thomas Way 96a2725c3e feat: object storage 2024-02-18 23:16:45 +00:00
Alex 8e1c85fe4f fix(server): not in album filter show motion part of LivePhotos (#7175) 2024-02-18 12:17:53 -06:00
martyfuhry 88214a485b fix(mobile): Fixes bottom exif info sheet modal drag controls (#7165)
* WIPip

* Fixed show modal bottom sheet to use drag handle

* Fixes advanced bottom sheet scrolling

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-02-17 22:03:01 -06:00
Ben Basten 9c1dd373a5 feat(web): add skip link to top navigation (#7091)
* feat(web): add skip link to top nav

* Styling skip link with tailwind
2024-02-17 21:34:27 -06:00
martyfuhry 70e5febb72 refactor(mobile): Use widgets in Immich asset grid (#7140)
* Refactors to use widgets in immich asset grid

Adds comments

* Fixes asset thumbnail absoluteOffset

* feat(mobile): Uses gradient image placeholders in memory lane and in the asset grid (#7142)

* Uses gradient image placeholders in memory lane and in the asset grid

* Changes to create placeholders in immmich image.thumbnail

* removed unused import

* uses context.isDarkTheme and fixed function signature for allAssetsSelected
2024-02-17 21:31:34 -06:00
Michel Heusschen 0730b54ca9 fix(server): empty/restore trash (#7161)
* fix(server): empty/restore trash

* add e2e tests

* add e2e tests - part 2
2024-02-17 14:05:43 -06:00
Alex 69983ff83a feat: enhance search (#7127)
* feat: hybrid search

* fixing normal search

* building out the query

* okla

* filters

* date

* order by date

* Remove hybrid search endpoint

* remove search hybrid endpoint

* faces query

* search for person

* search and pagination

* with exif

* with exif

* justify gallery viewer

* memory view

* Fixed userId is null

* openapi and styling

* searchdto

* lint and format

* remove term

* generate sql

* fix test

* chips

* not showing true

* pr feedback

* pr feedback

* nit name

* linting

* pr feedback

* styling

* linting
2024-02-17 11:00:55 -06:00
Michel Heusschen 60ba37b3a7 fix(web): validation when editing asset date & time (#7160) 2024-02-17 09:28:34 -05:00
Ben McCann 3915867b1b chore: remove svelte-preprocess (#7159) 2024-02-17 08:35:51 -05:00
Michel Heusschen fab19a8583 fix(server): recognize faces when min. faces is set to 1 (#7144)
* fix(server): recognize face when min. faces is set to 1

* update logic

* clarified log

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2024-02-17 03:32:11 +00:00
Jason Rasmussen a24f3805c9 chore: web e2e improvements (#7155) 2024-02-16 16:31:22 -05:00
Jan 67b1675850 fix(web) display wrong apikey-name on edit (#7131)
* fix display wrong apikey-name on edit

* use apiKey property with fallback value

* remove null fallback

* chore: cleanup

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-02-16 16:01:44 -05:00
Michel Heusschen c84c0bae6c refactor(web): websocket events (#7152) 2024-02-16 15:43:40 -05:00
Daniel Dietzler bbf7a54c65 chore(web): upgrade to maplibre 4 (#7132)
upgrade to maplibre 4
2024-02-16 07:55:13 -06:00
waclaw66 0ca7adcdbf fix(mobile): bottom app bar icons (#7147) 2024-02-16 07:54:17 -06:00
Ben McCann dabbd63a02 fix(web): update unauthorized share link handling (#7126) 2024-02-14 22:01:01 -06:00
Alex 9b814354a4 fix(web): search params (#7123)
* fix(web): search params

* format
2024-02-14 20:43:48 -06:00
martyfuhry 31ae35e9a6 chore(mobile): Fixed exclude analyzer options (#7125)
Fixed exclude analyzer options
2024-02-15 01:28:45 +00:00
Ben McCann 5abbb335c3 chore(deps): upgrade tj-actions/verify-changed-files to v18 (#7121) 2024-02-14 11:53:42 -08:00
Mert 5ff68d4cdb feat(server): only transcode streams that require it (#7106) 2024-02-14 11:24:39 -05:00
Jason Rasmussen b823dfffdc chore: suppress abort errors (#7120)
* chore: suppress abort errors

* chore: return if aborted

* chore: linting
2024-02-14 10:24:18 -06:00
Jason Rasmussen 747df0ae86 chore(web): auto sort imports (#7118)
chore(web): auto sort impomrts
2024-02-14 10:54:32 -05:00
Ben McCann 2906950188 fix: update error handling to match new API (#7117) 2024-02-14 07:25:15 -08:00
Torbjorn Tyridal 6adff50f0a feat(server, web): Include partner's photos on map (#7065)
* feat(server): Include partner's photos on map - if included in timeline

* depend on query parameter withPartners

instead of partners.inTimeline

* web: map option to include partners images

* make open-api
2024-02-14 10:07:00 -05:00
Jason Rasmussen 7d59900662 chore: web shutdown signal (#7114) 2024-02-14 08:57:02 -06:00
Jason Rasmussen 69166fa520 chore(cli): auto-sort imports (#7116) 2024-02-14 09:55:40 -05:00
Ben McCann 87ae0be081 chore: enum support for new API (#7110)
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-02-14 06:38:57 -08:00
martin 6f5648569a fix(web): always show asset owner when viewing an asset from a shared album (#7104)
fix: always show album owner when viewing a shared album
2024-02-14 09:08:33 -05:00
Jason Rasmussen d8631a00bb refactor(web) open api client (#7103)
* refactor: person api

* refactor: shared link and others
2024-02-14 08:09:49 -05:00
ItsJustRuby 5fc1d43012 chore(web,mobile): Fix reoccurring typo (#7111) 2024-02-14 08:48:59 +00:00
Łukasz Wawrzyk e9f3360f02 chore(mobile): Put the real delete button before other delete variants (#6895)
fix: put the real delete button before other delete variants
2024-02-13 20:17:21 -06:00
renovate[bot] b2181ee6f1 chore(deps): update dependency @types/node to v20.11.17 (#7102)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-13 22:13:38 +00:00
renovate[bot] 021867fe6c chore(deps): update dependency @types/node to v20.11.17 (#7099)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-13 17:08:38 -05:00
renovate[bot] caa9673f3a chore(deps): update dependency @types/node to v20.11.17 (#7098)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-13 17:08:25 -05:00
Jason Rasmussen 8fd94211c0 refactor(web): use new open api client (#7097)
* refactor(web): use new open api client

* refactor: remove activity api

* refactor: trash, oauth, and partner apis

* refactor: job api

* refactor: face, library, system config

* refactor: user api

* refactor: album api
2024-02-13 17:07:37 -05:00
martyfuhry 9b4a770b9d refactor(mobile): Immich image provider (#7016)
* Adds image provider

* uses image provider

* wip load preview

* wip everything but activity asset thumbnail needs some help with a remote id

* Immich provider used in gallery

* First draft of the immich image provider, working nicely!

* Removed OriginalImageProvider

* Fixes for thumbnails

* feat(mobile): thumbhash support (#7028)

* feat(mobile): thumbhash support

* perf(mobile): store bmp thumbhash bytes in Isar

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>

* Uses octoimage for fade in and placeholders

* fixes thumbnails, removes unused values, adds better thumbnail size

* removes thumbhash support for now

* Forgot one thumbhash removal

* Use big thumbnail for local image on ios

* fix(mobile): Multipart image loading for iOS double swipe (#7064)

* uses local thumb first

* Multipart thumbnail

* Clean up file delete

* await file delete

* Fynn's comments, made thumbnail smaller and doesn't crash on erroring out on thumbnail

* lint

---------

Co-authored-by: Marty Fuhry <marty@fuhry.farm>
Co-authored-by: Alex <alex.tran1502@gmail.com>

* Moves http client to global private place for reuse

* Got rid of usePreview for local image providers since we always show a thumbnail anyway first

* linter

---------

Co-authored-by: shenlong <139912620+shenlong-tanwen@users.noreply.github.com>
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Marty Fuhry <marty@fuhry.farm>
2024-02-13 15:30:32 -06:00
Alex 4b3f8d1946 feat: Search filtering logic (#6968)
* commit

* controller/service/repository logic

* use enum

* openapi

* suggest people

* suggest place/camera

* cursor hover

* refactor

* Add try catch

* Remove get people with name service

* Remove deadcode

* people selection

* People placement

* sort people

* Update server/src/domain/repositories/metadata.repository.ts

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>

* pr feedback

* styling

* done

* open api

* fix test

* use string type

* remmove bad merge

* use correct type

* fix test

* fix lint

* remove unused code

* remove unused code

* pr feedback

* pr feedback

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-02-13 19:54:58 +00:00
Jason Rasmussen 0c45f51a29 fix(deps): bump oazapfts to v6 (#7093)
chore: bump to v6
2024-02-13 13:37:57 -06:00
307 changed files with 10837 additions and 6137 deletions
+3 -3
View File
@@ -285,7 +285,7 @@ jobs:
- name: Run API generation
run: make open-api
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
uses: tj-actions/verify-changed-files@v18
id: verify-changed-files
with:
files: |
@@ -340,7 +340,7 @@ jobs:
run: npm run typeorm:migrations:generate ./src/infra/migrations/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
uses: tj-actions/verify-changed-files@v18
id: verify-changed-files
with:
files: |
@@ -358,7 +358,7 @@ jobs:
DB_URL: postgres://postgres:postgres@localhost:5432/immich
- name: Find file changes
uses: tj-actions/verify-changed-files@v13.1
uses: tj-actions/verify-changed-files@v18
id: verify-changed-sql-files
with:
files: |
+3 -1
View File
@@ -2,5 +2,7 @@
"singleQuote": true,
"trailingComma": "all",
"printWidth": 120,
"semi": true
"semi": true,
"plugins": ["prettier-plugin-organize-imports"],
"organizeImportsSkipDestructiveCodeActions": true
}
+44 -17
View File
@@ -31,6 +31,8 @@
"glob": "^10.3.1",
"immich": "file:../server",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"typescript": "^5.3.3",
"vite": "^5.0.12",
"vitest": "^1.2.2",
@@ -46,8 +48,8 @@
"dev": true,
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@oazapfts/runtime": "^1.0.0",
"@types/node": "^20.11.0",
"oazapfts": "^5.1.4",
"typescript": "^5.3.3"
},
"peerDependencies": {
@@ -1305,9 +1307,9 @@
}
},
"node_modules/@types/node": {
"version": "20.11.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
"integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
"version": "20.11.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
"integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -4068,11 +4070,10 @@
}
},
"node_modules/prettier": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true,
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -4095,6 +4096,26 @@
"node": ">=6.0.0"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz",
"integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==",
"dev": true,
"peerDependencies": {
"@volar/vue-language-plugin-pug": "^1.0.4",
"@volar/vue-typescript": "^1.0.4",
"prettier": ">=2.0",
"typescript": ">=2.9"
},
"peerDependenciesMeta": {
"@volar/vue-language-plugin-pug": {
"optional": true
},
"@volar/vue-typescript": {
"optional": true
}
}
},
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -6111,8 +6132,8 @@
"@immich/sdk": {
"version": "file:../open-api/typescript-sdk",
"requires": {
"@oazapfts/runtime": "^1.0.0",
"@types/node": "^20.11.0",
"oazapfts": "^5.1.4",
"typescript": "^5.3.3"
}
},
@@ -6426,9 +6447,9 @@
}
},
"@types/node": {
"version": "20.11.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
"integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
"version": "20.11.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
"integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
"dev": true,
"requires": {
"undici-types": "~5.26.4"
@@ -8543,11 +8564,10 @@
"dev": true
},
"prettier": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
"dev": true,
"peer": true
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
"dev": true
},
"prettier-linter-helpers": {
"version": "1.0.0",
@@ -8558,6 +8578,13 @@
"fast-diff": "^1.1.2"
}
},
"prettier-plugin-organize-imports": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz",
"integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==",
"dev": true,
"requires": {}
},
"pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+2
View File
@@ -32,6 +32,8 @@
"glob": "^10.3.1",
"immich": "file:../server",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"typescript": "^5.3.3",
"vite": "^5.0.12",
"vitest": "^1.2.2",
+1 -1
View File
@@ -1,6 +1,6 @@
import { ServerVersionResponseDto, UserResponseDto } from '@immich/sdk';
import { SessionService } from '../services/session.service';
import { ImmichApi } from 'src/services/api.service';
import { SessionService } from '../services/session.service';
export abstract class BaseCommand {
protected sessionService!: SessionService;
+5 -5
View File
@@ -1,13 +1,13 @@
import byteSize from 'byte-size';
import cliProgress from 'cli-progress';
import { createHash } from 'node:crypto';
import fs, { createReadStream } from 'node:fs';
import { access, constants, stat, unlink } from 'node:fs/promises';
import os from 'node:os';
import { basename } from 'node:path';
import { ImmichApi } from 'src/services/api.service';
import { CrawlService } from '../services/crawl.service';
import { BaseCommand } from './base-command';
import { basename } from 'node:path';
import { access, constants, stat, unlink } from 'node:fs/promises';
import { createHash } from 'node:crypto';
import os from 'node:os';
import { ImmichApi } from 'src/services/api.service';
class Asset {
readonly path: string;
+1 -1
View File
@@ -1,7 +1,7 @@
#! /usr/bin/env node
import { Command, Option } from 'commander';
import path from 'node:path';
import os from 'node:os';
import path from 'node:path';
import { version } from '../package.json';
import { LoginCommand } from './commands/login.command';
import { LogoutCommand } from './commands/logout.command';
+7 -7
View File
@@ -1,4 +1,11 @@
import {
ApiKeyCreateDto,
AssetBulkUploadCheckDto,
BulkIdsDto,
CreateAlbumDto,
CreateAssetDto,
LoginCredentialDto,
SignUpDto,
addAssetsToAlbum,
checkBulkUpload,
createAlbum,
@@ -13,13 +20,6 @@ import {
pingServer,
signUpAdmin,
uploadFile,
ApiKeyCreateDto,
AssetBulkUploadCheckDto,
BulkIdsDto,
CreateAlbumDto,
CreateAssetDto,
LoginCredentialDto,
SignUpDto,
} from '@immich/sdk';
/**
+1 -1
View File
@@ -1,5 +1,5 @@
import mockfs from 'mock-fs';
import { CrawlService, CrawlOptions } from './crawl.service';
import { CrawlOptions, CrawlService } from './crawl.service';
interface Test {
test: string;
+1 -1
View File
@@ -1,4 +1,3 @@
import { SessionService } from './session.service';
import fs from 'node:fs';
import yaml from 'yaml';
import {
@@ -11,6 +10,7 @@ import {
readTestAuthFile,
spyOnConsole,
} from '../../test/cli-test-utils';
import { SessionService } from './session.service';
const mocks = vi.hoisted(() => {
return {
+2 -2
View File
@@ -1,8 +1,8 @@
import { restoreTempFolder, testApp } from '@test-utils';
import { CLI_BASE_OPTIONS, TEST_AUTH_FILE, deleteAuthFile, setup, spyOnConsole } from 'test/cli-test-utils';
import { readFile, stat } from 'node:fs/promises';
import { LoginCommand } from '../../src/commands/login.command';
import { CLI_BASE_OPTIONS, TEST_AUTH_FILE, deleteAuthFile, setup, spyOnConsole } from 'test/cli-test-utils';
import yaml from 'yaml';
import { LoginCommand } from '../../src/commands/login.command';
describe(`login-key (e2e)`, () => {
let apiKey: string;
+1 -1
View File
@@ -1,6 +1,6 @@
import path from 'node:path';
import { PostgreSqlContainer } from '@testcontainers/postgresql';
import { access } from 'node:fs/promises';
import path from 'node:path';
export const directoryExists = (directory: string) =>
access(directory)
+1 -1
View File
@@ -1,7 +1,7 @@
import { IMMICH_TEST_ASSET_PATH, restoreTempFolder, testApp } from '@test-utils';
import { ImmichApi } from 'src/services/api.service';
import { CLI_BASE_OPTIONS, setup, spyOnConsole } from 'test/cli-test-utils';
import { UploadCommand } from '../../src/commands/upload.command';
import { ImmichApi } from 'src/services/api.service';
describe(`upload (e2e)`, () => {
let api: ImmichApi;
+1 -1
View File
@@ -57,7 +57,7 @@ services:
image: immich-web-dev:latest
build:
context: ../web
command: "/usr/src/app/bin/immich-web"
command: [ "/usr/src/app/bin/immich-web" ]
env_file:
- .env
ports:
+1 -1
View File
@@ -23,8 +23,8 @@
"dev": true,
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@oazapfts/runtime": "^1.0.0",
"@types/node": "^20.11.0",
"oazapfts": "^5.1.4",
"typescript": "^5.3.3"
},
"peerDependencies": {
+1 -1
View File
@@ -2,7 +2,7 @@ import { test, expect } from '@playwright/test';
import { app } from '../test-utils';
test.describe('Registration', () => {
test.beforeAll(async () => {
test.beforeEach(async () => {
await app.reset();
});
+16 -7
View File
@@ -63,17 +63,26 @@ export const app = {
return response;
},
reset: async () => {
if (!connected) {
await client.connect();
}
try {
if (!connected) {
await client.connect();
connected = true;
}
for (const table of ['users', 'system_metadata']) {
await client.query(`DELETE FROM ${table} CASCADE;`);
for (const table of ['user_token', 'users', 'system_metadata']) {
await client.query(`DELETE FROM ${table} CASCADE;`);
}
} catch (error) {
console.error('Failed to reset database', error);
}
},
teardown: async () => {
if (connected) {
await client.end();
try {
if (connected) {
await client.end();
}
} catch (error) {
console.error('Failed to teardown database', error);
}
},
};
+1 -1
View File
@@ -59,7 +59,7 @@ start_docker_compose() {
}
show_friendly_message() {
echo "Succesfully deployed Immich!"
echo "Successfully deployed Immich!"
echo "You can access the website at http://$ip_address:2283 and the server URL for the mobile app is http://$ip_address:2283/api"
echo "The library location is $upload_location"
echo "---------------------------------------------------"
+3 -3
View File
@@ -33,10 +33,10 @@ linter:
# https://dart.dev/guides/language/analysis-options
analyzer:
exclude:
- openapi/
- openapi/test/
- openapi/**
- openapi/test/**
- lib/generated_plugin_registrant.dart
plugins:
- custom_lint
+1 -1
View File
@@ -180,4 +180,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
COCOAPODS: 1.11.3
COCOAPODS: 1.12.1
@@ -3,8 +3,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/datetime_extensions.dart';
import 'package:immich_mobile/modules/activities/models/activity.model.dart';
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/current_asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class ActivityTile extends HookConsumerWidget {
@@ -106,7 +106,10 @@ class _ActivityAssetThumbnail extends StatelessWidget {
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage(
image: ImmichImage.remoteThumbnailProviderForId(assetId),
image: ImmichRemoteImageProvider(
assetId: assetId,
isThumbnail: true,
),
fit: BoxFit.cover,
),
),
@@ -45,7 +45,7 @@ class AlbumThumbnailCard extends StatelessWidget {
);
}
buildAlbumThumbnail() => ImmichImage(
buildAlbumThumbnail() => ImmichImage.thumbnail(
album.thumbnail.value,
width: cardSize,
height: cardSize,
@@ -16,7 +16,11 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
},
child: Stack(
children: [
ImmichImage(asset, width: 500, height: 500),
ImmichImage.thumbnail(
asset,
width: 500,
height: 500,
),
],
),
);
@@ -72,7 +72,7 @@ class SharingPage extends HookConsumerWidget {
contentPadding: const EdgeInsets.symmetric(horizontal: 12),
leading: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: ImmichImage(
child: ImmichImage.thumbnail(
album.thumbnail.value,
width: 60,
height: 60,
@@ -0,0 +1,106 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:photo_manager/photo_manager.dart';
/// The local image provider for an asset
/// Only viable
class ImmichLocalImageProvider extends ImageProvider<Asset> {
final Asset asset;
ImmichLocalImageProvider({
required this.asset,
}) : assert(asset.local != null, 'Only usable when asset.local is set');
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
/// that describes the precise image to load.
@override
Future<Asset> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture(asset);
}
@override
ImageStreamCompleter loadImage(Asset key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _codec(key, decode, chunkEvents),
scale: 1.0,
chunkEvents: chunkEvents.stream,
informationCollector: () sync* {
yield ErrorDescription(asset.fileName);
},
);
}
// Streams in each stage of the image as we ask for it
Stream<ui.Codec> _codec(
Asset key,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async* {
// Load a small thumbnail
final thumbBytes = await asset.local?.thumbnailDataWithSize(
const ThumbnailSize.square(256),
quality: 80,
);
if (thumbBytes != null) {
final buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
final codec = await decode(buffer);
yield codec;
} else {
debugPrint("Loading thumb for ${asset.fileName} failed");
}
if (asset.isImage) {
/// Using 2K thumbnail for local iOS image to avoid double swiping issue
if (Platform.isIOS) {
final largeImageBytes = await asset.local
?.thumbnailDataWithSize(const ThumbnailSize(3840, 2160));
if (largeImageBytes == null) {
throw StateError(
"Loading thumb for local photo ${asset.fileName} failed",
);
}
final buffer = await ui.ImmutableBuffer.fromUint8List(largeImageBytes);
final codec = await decode(buffer);
yield codec;
} else {
// Use the original file for Android
final File? file = await asset.local?.originFile;
if (file == null) {
throw StateError("Opening file for asset ${asset.fileName} failed");
}
try {
final buffer = await ui.ImmutableBuffer.fromFilePath(file.path);
final codec = await decode(buffer);
yield codec;
} catch (error) {
throw StateError("Loading asset ${asset.fileName} failed");
} finally {
if (Platform.isIOS) {
// Clean up this file
await file.delete();
}
}
}
}
chunkEvents.close();
}
@override
bool operator ==(Object other) {
if (other is! ImmichLocalImageProvider) return false;
if (identical(this, other)) return true;
return asset == other.asset;
}
@override
int get hashCode => asset.hashCode;
}
@@ -0,0 +1,145 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:openapi/api.dart' as api;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
/// Our Image Provider HTTP client to make the request
final _httpClient = HttpClient()..autoUncompress = false;
/// The remote image provider
class ImmichRemoteImageProvider extends ImageProvider<String> {
/// The [Asset.remoteId] of the asset to fetch
final String assetId;
// If this is a thumbnail, we stop at loading the
// smallest version of the remote image
final bool isThumbnail;
ImmichRemoteImageProvider({
required this.assetId,
this.isThumbnail = false,
});
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
/// that describes the precise image to load.
@override
Future<String> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture('$assetId,$isThumbnail');
}
@override
ImageStreamCompleter loadImage(String key, ImageDecoderCallback decode) {
final id = key.split(',').first;
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _codec(id, decode, chunkEvents),
scale: 1.0,
chunkEvents: chunkEvents.stream,
);
}
/// Whether to show the original file or load a compressed version
bool get _useOriginal => Store.get(
AppSettingsEnum.loadOriginal.storeKey,
AppSettingsEnum.loadOriginal.defaultValue,
);
/// Whether to load the preview thumbnail first or not
bool get _loadPreview => Store.get(
AppSettingsEnum.loadPreview.storeKey,
AppSettingsEnum.loadPreview.defaultValue,
);
// Streams in each stage of the image as we ask for it
Stream<ui.Codec> _codec(
String key,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async* {
// Load a preview to the chunk events
if (_loadPreview || isThumbnail) {
final preview = getThumbnailUrlForRemoteId(
assetId,
type: api.ThumbnailFormat.WEBP,
);
yield await _loadFromUri(
Uri.parse(preview),
decode,
chunkEvents,
);
}
// Guard thumnbail rendering
if (isThumbnail) {
await chunkEvents.close();
return;
}
// Load the higher resolution version of the image
final url = getThumbnailUrlForRemoteId(
assetId,
type: api.ThumbnailFormat.JPEG,
);
final codec = await _loadFromUri(Uri.parse(url), decode, chunkEvents);
yield codec;
// Load the final remote image
if (_useOriginal) {
// Load the original image
final url = getImageUrlFromId(assetId);
final codec = await _loadFromUri(Uri.parse(url), decode, chunkEvents);
yield codec;
}
await chunkEvents.close();
}
// Loads the codec from the URI and sends the events to the [chunkEvents] stream
Future<ui.Codec> _loadFromUri(
Uri uri,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async {
final request = await _httpClient.getUrl(uri);
request.headers.add(
'x-immich-user-token',
Store.get(StoreKey.accessToken),
);
final response = await request.close();
// Chunks of the completed image can be shown
final data = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (cumulative, total) {
chunkEvents.add(
ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
),
);
},
);
// Decode the response
final buffer = await ui.ImmutableBuffer.fromUint8List(data);
return decode(buffer);
}
@override
bool operator ==(Object other) {
if (other is! ImmichRemoteImageProvider) return false;
if (identical(this, other)) return true;
return assetId == other.assetId;
}
@override
int get hashCode => assetId.hashCode;
}
@@ -0,0 +1,104 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
import 'package:openapi/api.dart' as api;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
/// The remote image provider
class ImmichRemoteThumbnailProvider extends ImageProvider<String> {
/// The [Asset.remoteId] of the asset to fetch
final String assetId;
/// Our HTTP client to make the request
final _httpClient = HttpClient()..autoUncompress = false;
ImmichRemoteThumbnailProvider({
required this.assetId,
});
/// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
/// that describes the precise image to load.
@override
Future<String> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture(assetId);
}
@override
ImageStreamCompleter loadImage(String key, ImageDecoderCallback decode) {
final chunkEvents = StreamController<ImageChunkEvent>();
return MultiImageStreamCompleter(
codec: _codec(key, decode, chunkEvents),
scale: 1.0,
chunkEvents: chunkEvents.stream,
);
}
// Streams in each stage of the image as we ask for it
Stream<ui.Codec> _codec(
String key,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async* {
// Load a preview to the chunk events
final preview = getThumbnailUrlForRemoteId(
assetId,
type: api.ThumbnailFormat.WEBP,
);
yield await _loadFromUri(
Uri.parse(preview),
decode,
chunkEvents,
);
await chunkEvents.close();
}
// Loads the codec from the URI and sends the events to the [chunkEvents] stream
Future<ui.Codec> _loadFromUri(
Uri uri,
ImageDecoderCallback decode,
StreamController<ImageChunkEvent> chunkEvents,
) async {
final request = await _httpClient.getUrl(uri);
request.headers.add(
'x-immich-user-token',
Store.get(StoreKey.accessToken),
);
final response = await request.close();
// Chunks of the completed image can be shown
final data = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (cumulative, total) {
chunkEvents.add(
ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
),
);
},
);
// Decode the response
final buffer = await ui.ImmutableBuffer.fromUint8List(data);
return decode(buffer);
}
@override
bool operator ==(Object other) {
if (other is! ImmichRemoteImageProvider) return false;
if (identical(this, other)) return true;
return assetId == other.assetId;
}
@override
int get hashCode => assetId.hashCode;
}
@@ -12,92 +12,85 @@ class AdvancedBottomSheet extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
return SingleChildScrollView(
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
margin: const EdgeInsets.all(0),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: LayoutBuilder(
builder: (context, constraints) {
// One column
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 32.0),
const Align(
child: Text(
"ADVANCED INFO",
style: TextStyle(fontSize: 12.0),
),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: LayoutBuilder(
builder: (context, constraints) {
// One column
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Align(
child: Text(
"ADVANCED INFO",
style: TextStyle(fontSize: 12.0),
),
const SizedBox(height: 32.0),
Container(
decoration: BoxDecoration(
color: context.isDarkTheme
? Colors.grey[900]
: Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
const SizedBox(height: 32.0),
Container(
decoration: BoxDecoration(
color: context.isDarkTheme
? Colors.grey[900]
: Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 16,
top: 8,
bottom: 16,
),
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 16,
top: 8,
bottom: 16,
),
child: ListView(
shrinkWrap: true,
children: [
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () {
Clipboard.setData(
ClipboardData(text: assetDetail.toString()),
).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Copied to clipboard",
style: context.textTheme.bodyLarge
?.copyWith(
color: context.primaryColor,
),
child: ListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
Align(
alignment: Alignment.centerRight,
child: IconButton(
onPressed: () {
Clipboard.setData(
ClipboardData(
text: assetDetail.toString(),
),
).then((_) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Copied to clipboard",
style:
context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
),
);
});
},
icon: Icon(
Icons.copy,
size: 16.0,
color: context.primaryColor,
),
),
);
});
},
icon: Icon(
Icons.copy,
size: 16.0,
color: context.primaryColor,
),
),
SelectableText(
assetDetail.toString(),
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
showCursor: true,
),
SelectableText(
assetDetail.toString(),
style: const TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontFamily: "Inconsolata",
),
],
),
showCursor: true,
),
],
),
),
const SizedBox(height: 32.0),
],
);
},
),
),
const SizedBox(height: 32.0),
],
);
},
),
),
);
@@ -10,7 +10,6 @@ import 'package:immich_mobile/modules/asset_viewer/ui/description_input.dart';
import 'package:immich_mobile/modules/map/widgets/map_thumbnail.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
import 'package:immich_mobile/utils/selection_handlers.dart';
import 'package:immich_mobile/utils/bytes_units.dart';
import 'package:maplibre_gl/maplibre_gl.dart';
@@ -126,20 +125,6 @@ class ExifBottomSheet extends HookConsumerWidget {
return text.isNotEmpty ? text : null;
}
buildDragHeader() {
return const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12),
Align(
alignment: Alignment.center,
child: CustomDraggingHandle(),
),
SizedBox(height: 12),
],
);
}
buildLocation() {
// Guard no lat/lng
if (!hasCoordinates()) {
@@ -341,86 +326,69 @@ class ExifBottomSheet extends HookConsumerWidget {
);
}
return GestureDetector(
onTap: () {
// FocusScope.of(context).unfocus();
},
child: SingleChildScrollView(
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
margin: const EdgeInsets.all(0),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
// Two column
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildDragHeader(),
buildDate(),
if (asset.isRemote) DescriptionInput(asset: asset),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: hasCoordinates() ? 5 : 0,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: buildLocation(),
),
),
Flexible(
flex: 5,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: buildDetail(),
),
),
],
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16.0),
child: LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 600) {
// Two column
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildDate(),
if (asset.isRemote) DescriptionInput(asset: asset),
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: buildLocation(),
),
const SizedBox(height: 50),
],
),
);
}
),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300),
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: buildDetail(),
),
),
],
),
const SizedBox(height: 50),
],
);
}
// One column
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildDragHeader(),
buildDate(),
assetWithExif.when(
data: (data) => DescriptionInput(asset: data),
error: (error, stackTrace) => Icon(
Icons.image_not_supported_outlined,
color: context.primaryColor,
),
loading: () => const SizedBox(
width: 75,
height: 75,
child: CircularProgressIndicator.adaptive(),
),
),
buildLocation(),
SizedBox(height: hasCoordinates() ? 16.0 : 6.0),
buildDetail(),
const SizedBox(height: 50),
],
);
},
),
),
// One column
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
buildDate(),
assetWithExif.when(
data: (data) => DescriptionInput(asset: data),
error: (error, stackTrace) => Icon(
Icons.image_not_supported_outlined,
color: context.primaryColor,
),
loading: () => const SizedBox(
width: 75,
height: 75,
child: CircularProgressIndicator.adaptive(),
),
),
buildLocation(),
SizedBox(height: hasCoordinates() ? 16.0 : 6.0),
buildDetail(),
const SizedBox(height: 50),
],
);
},
),
),
);
@@ -25,7 +25,6 @@ import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart'
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
import 'package:immich_mobile/shared/cache/original_image_provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
@@ -41,8 +40,6 @@ import 'package:immich_mobile/shared/ui/photo_view/src/photo_view_scale_state.da
import 'package:immich_mobile/shared/ui/photo_view/src/utils/photo_view_hero_attributes.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:isar/isar.dart';
import 'package:openapi/api.dart' show ThumbnailFormat;
@@ -78,7 +75,6 @@ class GalleryViewerPage extends HookConsumerWidget {
final isPlayingMotionVideo = useState(false);
final isPlayingVideo = useState(false);
Offset? localPosition;
final header = {"x-immich-user-token": Store.get(StoreKey.accessToken)};
final currentIndex = useState(initialIndex);
final currentAsset = loadAsset(currentIndex.value);
final isTrashEnabled =
@@ -135,53 +131,19 @@ class GalleryViewerPage extends HookConsumerWidget {
void toggleFavorite(Asset asset) =>
ref.read(assetProvider.notifier).toggleFavorite([asset]);
/// Original (large) image of a remote asset. Required asset.isRemote
ImageProvider remoteOriginalProvider(Asset asset) =>
CachedNetworkImageProvider(
getImageUrl(asset),
cacheKey: getImageCacheKey(asset),
headers: header,
);
/// Original (large) image of a local asset. Required asset.isLocal
ImageProvider localOriginalProvider(Asset asset) =>
OriginalImageProvider(asset);
ImageProvider finalImageProvider(Asset asset) {
if (ImmichImage.useLocal(asset)) {
return localOriginalProvider(asset);
} else if (isLoadOriginal.value) {
return remoteOriginalProvider(asset);
} else if (isLoadPreview.value) {
return ImmichImage.remoteThumbnailProvider(asset, jpeg, header);
}
return ImmichImage.remoteThumbnailProvider(asset, webp, header);
}
Iterable<ImageProvider> allImageProviders(Asset asset) sync* {
if (ImmichImage.useLocal(asset)) {
yield ImmichImage.localImageProvider(asset);
yield localOriginalProvider(asset);
} else {
yield ImmichImage.remoteThumbnailProvider(asset, webp, header);
if (isLoadPreview.value) {
yield ImmichImage.remoteThumbnailProvider(asset, jpeg, header);
}
if (isLoadOriginal.value) {
yield remoteOriginalProvider(asset);
}
}
}
void precacheNextImage(int index) {
void onError(Object exception, StackTrace? stackTrace) {
// swallow error silently
debugPrint('Error precaching next image: $exception, $stackTrace');
}
if (index < totalAssets && index >= 0) {
final asset = loadAsset(index);
for (final imageProvider in allImageProviders(asset)) {
precacheImage(imageProvider, context, onError: onError);
}
precacheImage(
ImmichImage.imageProvider(asset: asset),
context,
onError: onError,
);
}
}
@@ -191,10 +153,11 @@ class GalleryViewerPage extends HookConsumerWidget {
borderRadius: BorderRadius.all(Radius.circular(15.0)),
),
barrierColor: Colors.transparent,
backgroundColor: Colors.transparent,
isScrollControlled: true,
useSafeArea: true,
showDragHandle: true,
enableDrag: true,
context: context,
useSafeArea: true,
builder: (context) {
return Padding(
padding: EdgeInsets.only(
@@ -765,6 +728,10 @@ class GalleryViewerPage extends HookConsumerWidget {
isZoomed.value = state != PhotoViewScaleState.initial;
ref.read(showControlsProvider.notifier).show = !isZoomed.value;
},
loadingBuilder: (context, event, index) => ImmichImage.thumbnail(
asset(),
fit: BoxFit.contain,
),
pageController: controller,
scrollPhysics: isZoomed.value
? const NeverScrollableScrollPhysics() // Don't allow paging while scrolled in
@@ -781,47 +748,11 @@ class GalleryViewerPage extends HookConsumerWidget {
stackIndex.value = -1;
HapticFeedback.selectionClick();
},
loadingBuilder: (context, event, index) {
final a = loadAsset(index);
if (ImmichImage.useLocal(a)) {
return Image(
image: ImmichImage.localImageProvider(a),
fit: BoxFit.contain,
);
}
// Use the WEBP Thumbnail as a placeholder for the JPEG thumbnail to achieve
// Three-Stage Loading (WEBP -> JPEG -> Original)
final webPThumbnail = CachedNetworkImage(
imageUrl: getThumbnailUrl(a, type: webp),
cacheKey: getThumbnailCacheKey(a, type: webp),
httpHeaders: header,
progressIndicatorBuilder: (_, __, ___) => const Center(
child: ImmichLoadingIndicator(),
),
fadeInDuration: const Duration(milliseconds: 0),
fit: BoxFit.contain,
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
);
// loading the preview in the loadingBuilder only
// makes sense if the original is loaded in the builder
return isLoadPreview.value && isLoadOriginal.value
? CachedNetworkImage(
imageUrl: getThumbnailUrl(a, type: jpeg),
cacheKey: getThumbnailCacheKey(a, type: jpeg),
httpHeaders: header,
fit: BoxFit.contain,
fadeInDuration: const Duration(milliseconds: 0),
placeholder: (_, __) => webPThumbnail,
errorWidget: (_, __, ___) => webPThumbnail,
)
: webPThumbnail;
},
builder: (context, index) {
final a =
index == currentIndex.value ? asset() : loadAsset(index);
final ImageProvider provider = finalImageProvider(a);
final ImageProvider provider =
ImmichImage.imageProvider(asset: a);
if (a.isImage && !isPlayingMotionVideo.value) {
return PhotoViewGalleryPageOptions(
@@ -88,7 +88,7 @@ class UploadProfileImageNotifier
var res = await _userSErvice.uploadProfileImage(file);
if (res != null) {
debugPrint("Succesfully upload profile image");
debugPrint("Successfully upload profile image");
state = state.copyWith(
status: UploadProfileStatus.success,
profileImagePath: res.profileImagePath,
@@ -10,6 +10,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@@ -127,184 +128,6 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
assets.firstWhereOrNull((e) => !_selectedAssets.contains(e)) == null;
}
Widget _buildThumbnailOrPlaceholder(Asset asset, int index) {
return ThumbnailImage(
asset: asset,
index: index,
loadAsset: widget.renderList.loadAsset,
totalAssets: widget.renderList.totalAssets,
multiselectEnabled: widget.selectionActive,
isSelected: widget.selectionActive && _selectedAssets.contains(asset),
onSelect: () => _selectAssets([asset]),
onDeselect: widget.canDeselect ||
widget.preselectedAssets == null ||
!widget.preselectedAssets!.contains(asset)
? () => _deselectAssets([asset])
: null,
useGrayBoxPlaceholder: true,
showStorageIndicator: widget.showStorageIndicator,
heroOffset: widget.heroOffset,
showStack: widget.showStack,
);
}
Widget _buildAssetRow(
Key key,
BuildContext context,
List<Asset> assets,
int absoluteOffset,
double width,
) {
// Default: All assets have the same width
final widthDistribution = List.filled(assets.length, 1.0);
if (widget.dynamicLayout) {
final aspectRatios =
assets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList();
final meanAspectRatio = aspectRatios.sum / assets.length;
// 1: mean width
// 0.5: width < mean - threshold
// 1.5: width > mean + threshold
final arConfiguration = aspectRatios.map((e) {
if (e - meanAspectRatio > 0.3) return 1.5;
if (e - meanAspectRatio < -0.3) return 0.5;
return 1.0;
});
// Normalize:
final sum = arConfiguration.sum;
widthDistribution.setRange(
0,
widthDistribution.length,
arConfiguration.map((e) => (e * assets.length) / sum),
);
}
return Row(
key: key,
children: assets.mapIndexed((int index, Asset asset) {
final bool last = index + 1 == widget.assetsPerRow;
return Container(
key: ValueKey(index),
width: width * widthDistribution[index],
height: width,
margin: EdgeInsets.only(
bottom: widget.margin,
right: last ? 0.0 : widget.margin,
),
child: _buildThumbnailOrPlaceholder(asset, absoluteOffset + index),
);
}).toList(),
);
}
Widget _buildTitle(
BuildContext context,
String title,
List<Asset> assets,
) {
return GroupDividerTitle(
text: title,
multiselectEnabled: widget.selectionActive,
onSelect: () => _selectAssets(assets),
onDeselect: () => _deselectAssets(assets),
selected: _allAssetsSelected(assets),
);
}
Widget _buildMonthTitle(BuildContext context, DateTime date) {
final monthFormat = DateTime.now().year == date.year
? DateFormat.MMMM()
: DateFormat.yMMMM();
final String title = monthFormat.format(date);
return Padding(
key: Key("month-$title"),
padding: const EdgeInsets.only(left: 12.0, top: 24.0),
child: Text(
title,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.w500,
),
),
);
}
Widget _buildPlaceHolderRow(Key key, int num, double width, double height) {
return Row(
key: key,
children: [
for (int i = 0; i < num; i++)
Container(
key: ValueKey(i),
width: width,
height: height,
margin: EdgeInsets.only(
bottom: widget.margin,
right: i + 1 == num ? 0.0 : widget.margin,
),
color: Colors.grey,
),
],
);
}
Widget _buildSection(
BuildContext context,
RenderAssetGridElement section,
bool scrolling,
) {
return LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth / widget.assetsPerRow -
widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
final rows =
(section.count + widget.assetsPerRow - 1) ~/ widget.assetsPerRow;
final List<Asset> assetsToRender = scrolling
? []
: widget.renderList.loadAssets(section.offset, section.count);
return Column(
key: ValueKey(section.offset),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (section.type == RenderAssetGridElementType.monthTitle)
_buildMonthTitle(context, section.date),
if (section.type == RenderAssetGridElementType.groupDividerTitle ||
section.type == RenderAssetGridElementType.monthTitle)
_buildTitle(
context,
section.title!,
scrolling
? []
: widget.renderList
.loadAssets(section.offset, section.totalCount),
),
for (int i = 0; i < rows; i++)
scrolling
? _buildPlaceHolderRow(
ValueKey(i),
i + 1 == rows
? section.count - i * widget.assetsPerRow
: widget.assetsPerRow,
width,
width,
)
: _buildAssetRow(
ValueKey(i),
context,
assetsToRender.nestedSlice(
i * widget.assetsPerRow,
min((i + 1) * widget.assetsPerRow, section.count),
),
section.offset + i * widget.assetsPerRow,
width,
),
],
);
},
);
}
Widget _itemBuilder(BuildContext c, int position) {
int index = position;
if (widget.topWidget != null) {
@@ -314,8 +137,23 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
index--;
}
final item = widget.renderList.elements[index];
return _buildSection(c, item, _scrolling);
final section = widget.renderList.elements[index];
return _Section(
showStorageIndicator: widget.showStorageIndicator,
selectedAssets: _selectedAssets,
selectionActive: widget.selectionActive,
section: section,
margin: widget.margin,
renderList: widget.renderList,
assetsPerRow: widget.assetsPerRow,
scrolling: _scrolling,
dynamicLayout: widget.dynamicLayout,
selectAssets: _selectAssets,
deselectAssets: _deselectAssets,
allAssetsSelected: _allAssetsSelected,
showStack: widget.showStack,
heroOffset: widget.heroOffset,
);
}
Text _labelBuilder(int pos) {
@@ -485,3 +323,292 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
);
}
}
/// A single row of all placeholder widgets
class _PlaceholderRow extends StatelessWidget {
final int number;
final double width;
final double height;
final double margin;
const _PlaceholderRow({
super.key,
required this.number,
required this.width,
required this.height,
required this.margin,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
for (int i = 0; i < number; i++)
ThumbnailPlaceholder(
key: ValueKey(i),
width: width,
height: height,
margin: EdgeInsets.only(
bottom: margin,
right: i + 1 == number ? 0.0 : margin,
),
),
],
);
}
}
/// A section for the render grid
class _Section extends StatelessWidget {
final RenderAssetGridElement section;
final Set<Asset> selectedAssets;
final bool scrolling;
final double margin;
final int assetsPerRow;
final RenderList renderList;
final bool selectionActive;
final bool dynamicLayout;
final Function(List<Asset>) selectAssets;
final Function(List<Asset>) deselectAssets;
final bool Function(List<Asset>) allAssetsSelected;
final bool showStack;
final int heroOffset;
final bool showStorageIndicator;
const _Section({
required this.section,
required this.scrolling,
required this.margin,
required this.assetsPerRow,
required this.renderList,
required this.selectionActive,
required this.dynamicLayout,
required this.selectAssets,
required this.deselectAssets,
required this.allAssetsSelected,
required this.selectedAssets,
required this.showStack,
required this.heroOffset,
required this.showStorageIndicator,
});
@override
Widget build(
BuildContext context,
) {
return LayoutBuilder(
builder: (context, constraints) {
final width = constraints.maxWidth / assetsPerRow -
margin * (assetsPerRow - 1) / assetsPerRow;
final rows = (section.count + assetsPerRow - 1) ~/ assetsPerRow;
final List<Asset> assetsToRender = scrolling
? []
: renderList.loadAssets(section.offset, section.count);
return Column(
key: ValueKey(section.offset),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (section.type == RenderAssetGridElementType.monthTitle)
_MonthTitle(date: section.date),
if (section.type == RenderAssetGridElementType.groupDividerTitle ||
section.type == RenderAssetGridElementType.monthTitle)
_Title(
selectionActive: selectionActive,
title: section.title!,
assets: scrolling
? []
: renderList.loadAssets(section.offset, section.totalCount),
allAssetsSelected: allAssetsSelected,
selectAssets: selectAssets,
deselectAssets: deselectAssets,
),
for (int i = 0; i < rows; i++)
scrolling
? _PlaceholderRow(
key: ValueKey(i),
number: i + 1 == rows
? section.count - i * assetsPerRow
: assetsPerRow,
width: width,
height: width,
margin: margin,
)
: _AssetRow(
key: ValueKey(i),
assets: assetsToRender.nestedSlice(
i * assetsPerRow,
min((i + 1) * assetsPerRow, section.count),
),
absoluteOffset: section.offset + i * assetsPerRow,
width: width,
assetsPerRow: assetsPerRow,
margin: margin,
dynamicLayout: dynamicLayout,
renderList: renderList,
selectedAssets: selectedAssets,
isSelectionActive: selectionActive,
showStack: showStack,
heroOffset: heroOffset,
showStorageIndicator: showStorageIndicator,
selectionActive: selectionActive,
onSelect: (asset) => selectAssets([asset]),
onDeselect: (asset) => deselectAssets([asset]),
),
],
);
},
);
}
}
/// The month title row for a section
class _MonthTitle extends StatelessWidget {
final DateTime date;
const _MonthTitle({
required this.date,
});
@override
Widget build(BuildContext context) {
final monthFormat = DateTime.now().year == date.year
? DateFormat.MMMM()
: DateFormat.yMMMM();
final String title = monthFormat.format(date);
return Padding(
key: Key("month-$title"),
padding: const EdgeInsets.only(left: 12.0, top: 24.0),
child: Text(
title,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.w500,
),
),
);
}
}
/// A title row
class _Title extends StatelessWidget {
final String title;
final List<Asset> assets;
final bool selectionActive;
final Function(List<Asset>) selectAssets;
final Function(List<Asset>) deselectAssets;
final Function(List<Asset>) allAssetsSelected;
const _Title({
required this.title,
required this.assets,
required this.selectionActive,
required this.selectAssets,
required this.deselectAssets,
required this.allAssetsSelected,
});
@override
Widget build(BuildContext context) {
return GroupDividerTitle(
text: title,
multiselectEnabled: selectionActive,
onSelect: () => selectAssets(assets),
onDeselect: () => deselectAssets(assets),
selected: allAssetsSelected(assets),
);
}
}
/// The row of assets
class _AssetRow extends StatelessWidget {
final List<Asset> assets;
final Set<Asset> selectedAssets;
final int absoluteOffset;
final double width;
final bool dynamicLayout;
final double margin;
final int assetsPerRow;
final RenderList renderList;
final bool selectionActive;
final bool showStorageIndicator;
final int heroOffset;
final bool showStack;
final Function(Asset)? onSelect;
final Function(Asset)? onDeselect;
final bool isSelectionActive;
const _AssetRow({
super.key,
required this.assets,
required this.absoluteOffset,
required this.width,
required this.dynamicLayout,
required this.margin,
required this.assetsPerRow,
required this.renderList,
required this.selectionActive,
required this.showStorageIndicator,
required this.heroOffset,
required this.showStack,
required this.isSelectionActive,
required this.selectedAssets,
this.onSelect,
this.onDeselect,
});
@override
Widget build(BuildContext context) {
// Default: All assets have the same width
final widthDistribution = List.filled(assets.length, 1.0);
if (dynamicLayout) {
final aspectRatios =
assets.map((e) => (e.width ?? 1) / (e.height ?? 1)).toList();
final meanAspectRatio = aspectRatios.sum / assets.length;
// 1: mean width
// 0.5: width < mean - threshold
// 1.5: width > mean + threshold
final arConfiguration = aspectRatios.map((e) {
if (e - meanAspectRatio > 0.3) return 1.5;
if (e - meanAspectRatio < -0.3) return 0.5;
return 1.0;
});
// Normalize:
final sum = arConfiguration.sum;
widthDistribution.setRange(
0,
widthDistribution.length,
arConfiguration.map((e) => (e * assets.length) / sum),
);
}
return Row(
key: key,
children: assets.mapIndexed((int index, Asset asset) {
final bool last = index + 1 == assetsPerRow;
return Container(
width: width * widthDistribution[index],
height: width,
margin: EdgeInsets.only(
bottom: margin,
right: last ? 0.0 : margin,
),
child: ThumbnailImage(
asset: asset,
index: absoluteOffset + index,
loadAsset: renderList.loadAsset,
totalAssets: renderList.totalAssets,
multiselectEnabled: selectionActive,
isSelected: isSelectionActive && selectedAssets.contains(asset),
onSelect: () => onSelect?.call(asset),
onDeselect: () => onDeselect?.call(asset),
showStorageIndicator: showStorageIndicator,
heroOffset: heroOffset,
showStack: showStack,
),
);
}).toList(),
);
}
}
@@ -15,7 +15,6 @@ class ThumbnailImage extends StatelessWidget {
final int totalAssets;
final bool showStorageIndicator;
final bool showStack;
final bool useGrayBoxPlaceholder;
final bool isSelected;
final bool multiselectEnabled;
final Function? onSelect;
@@ -30,7 +29,6 @@ class ThumbnailImage extends StatelessWidget {
required this.totalAssets,
this.showStorageIndicator = true,
this.showStack = false,
this.useGrayBoxPlaceholder = false,
this.isSelected = false,
this.multiselectEnabled = false,
this.onDeselect,
@@ -136,10 +134,10 @@ class ThumbnailImage extends StatelessWidget {
tag: isFromDto
? '${asset.remoteId}-$heroOffset'
: asset.id + heroOffset,
child: ImmichImage(
child: ImmichImage.thumbnail(
asset,
useGrayBoxPlaceholder: useGrayBoxPlaceholder,
fit: BoxFit.cover,
height: 300,
width: 300,
),
),
);
@@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class ThumbnailPlaceholder extends StatelessWidget {
final EdgeInsets margin;
final double width;
final double height;
const ThumbnailPlaceholder({
super.key,
this.margin = EdgeInsets.zero,
this.width = 250,
this.height = 250,
});
static const _brightColors = [
Color(0xFFF1F3F4),
Color(0xFFB4B6B8),
];
static const _darkColors = [
Color(0xFF3B3F42),
Color(0xFF2B2F32),
];
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
margin: margin,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: context.isDarkTheme ? _darkColors : _brightColors,
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
);
}
}
@@ -125,6 +125,19 @@ class ControlBottomAppBar extends ConsumerWidget {
.tr(),
onPressed: enabled ? onFavorite : null,
),
if (hasLocal && hasRemote && onDelete != null)
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 90),
child: ControlBoxButton(
iconData: Icons.delete_sweep_outlined,
label: "control_bottom_app_bar_delete".tr(),
onPressed: enabled
? () => handleRemoteDelete(!trashEnabled, onDelete!)
: null,
onLongPressed:
enabled ? () => showForceDeleteDialog(onDelete!) : null,
),
),
if (hasRemote && onDeleteServer != null)
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 85),
@@ -172,44 +185,43 @@ class ControlBottomAppBar extends ConsumerWidget {
: null,
),
),
if (hasLocal && hasRemote && onDelete != null)
if (hasRemote && onEditTime != null)
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 95),
child: ControlBoxButton(
iconData: Icons.edit_calendar_outlined,
label: "control_bottom_app_bar_edit_time".tr(),
onPressed: enabled ? onEditTime : null,
),
),
if (hasRemote && onEditLocation != null)
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 90),
child: ControlBoxButton(
iconData: Icons.delete_sweep_outlined,
label: "control_bottom_app_bar_delete".tr(),
onPressed: enabled
? () => handleRemoteDelete(!trashEnabled, onDelete!)
: null,
onLongPressed:
enabled ? () => showForceDeleteDialog(onDelete!) : null,
iconData: Icons.edit_location_alt_outlined,
label: "control_bottom_app_bar_edit_location".tr(),
onPressed: enabled ? onEditLocation : null,
),
),
if (hasRemote && onEditTime != null)
ControlBoxButton(
iconData: Icons.edit_calendar_outlined,
label: "control_bottom_app_bar_edit_time".tr(),
onPressed: enabled ? onEditTime : null,
),
if (hasRemote && onEditLocation != null)
ControlBoxButton(
iconData: Icons.edit_location_alt_outlined,
label: "control_bottom_app_bar_edit_location".tr(),
onPressed: enabled ? onEditLocation : null,
),
if (!selectionAssetState.hasLocal &&
selectionAssetState.selectedCount > 1 &&
onStack != null)
ControlBoxButton(
iconData: Icons.filter_none_rounded,
label: "control_bottom_app_bar_stack".tr(),
onPressed: enabled ? onStack : null,
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 90),
child: ControlBoxButton(
iconData: Icons.filter_none_rounded,
label: "control_bottom_app_bar_stack".tr(),
onPressed: enabled ? onStack : null,
),
),
if (onRemoveFromAlbum != null)
ControlBoxButton(
iconData: Icons.delete_sweep_rounded,
label: 'album_viewer_appbar_share_remove'.tr(),
onPressed: enabled ? onRemoveFromAlbum : null,
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 90),
child: ControlBoxButton(
iconData: Icons.delete_sweep_rounded,
label: 'album_viewer_appbar_share_remove'.tr(),
onPressed: enabled ? onRemoveFromAlbum : null,
),
),
if (selectionAssetState.hasLocal)
ControlBoxButton(
@@ -230,9 +242,9 @@ class ControlBottomAppBar extends ConsumerWidget {
}
return DraggableScrollableSheet(
initialChildSize: hasRemote ? 0.30 : bottomPadding,
initialChildSize: hasRemote ? 0.35 : bottomPadding,
minChildSize: bottomPadding,
maxChildSize: hasRemote ? 0.60 : bottomPadding,
maxChildSize: hasRemote ? 0.65 : bottomPadding,
snap: true,
builder: (
BuildContext context,
@@ -257,9 +269,9 @@ class ControlBottomAppBar extends ConsumerWidget {
children: <Widget>[
const SizedBox(height: 12),
const CustomDraggingHandle(),
const SizedBox(height: 24),
const SizedBox(height: 12),
SizedBox(
height: 90,
height: 100,
child: ListView(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
@@ -8,7 +8,6 @@ import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
class MemoryCard extends StatelessWidget {
final Asset asset;
@@ -84,8 +83,6 @@ class MemoryCard extends StatelessWidget {
fit: fit,
height: double.infinity,
width: double.infinity,
type: ThumbnailFormat.JPEG,
preferredLocalAssetSize: 2048,
),
);
} else {
@@ -97,8 +94,6 @@ class MemoryCard extends StatelessWidget {
placeholder: ImmichImage(
asset,
fit: fit,
type: ThumbnailFormat.JPEG,
preferredLocalAssetSize: 2048,
),
hideControlsTimer: const Duration(seconds: 2),
onVideoEnded: onVideoEnded,
@@ -2,10 +2,10 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
import 'package:immich_mobile/modules/memories/providers/memory.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:openapi/api.dart';
class MemoryLane extends HookConsumerWidget {
const MemoryLane({super.key});
@@ -61,8 +61,10 @@ class MemoryLane extends HookConsumerWidget {
fit: BoxFit.cover,
width: 130,
height: 200,
useGrayBoxPlaceholder: true,
type: ThumbnailFormat.JPEG,
placeholder: const ThumbnailPlaceholder(
width: 130,
height: 200,
),
),
),
),
@@ -10,7 +10,6 @@ import 'package:immich_mobile/modules/memories/ui/memory_epilogue.dart';
import 'package:immich_mobile/modules/memories/ui/memory_progress_indicator.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:openapi/api.dart' as api;
@RoutePage()
class MemoryPage extends HookConsumerWidget {
@@ -113,23 +112,21 @@ class MemoryPage extends HookConsumerWidget {
// Gets the thumbnail url and precaches it
final precaches = <Future<dynamic>>[];
precaches.add(
ImmichImage.precacheAsset(
asset,
precaches.addAll([
precacheImage(
ImmichImage.imageProvider(
asset: asset,
),
context,
type: api.ThumbnailFormat.WEBP,
size: 2048,
),
);
precaches.add(
ImmichImage.precacheAsset(
asset,
precacheImage(
ImmichImage.imageProvider(
asset: asset,
isThumbnail: true,
),
context,
type: api.ThumbnailFormat.JPEG,
size: 2048,
),
);
]);
await Future.wait(precaches);
}
+24 -6
View File
@@ -354,6 +354,9 @@ abstract class _$AppRouter extends RootStackRouter {
onPlaying: args.onPlaying,
onPaused: args.onPaused,
placeholder: args.placeholder,
showControls: args.showControls,
hideControlsTimer: args.hideControlsTimer,
showDownloadingIndicator: args.showDownloadingIndicator,
),
);
},
@@ -1384,11 +1387,14 @@ class VideoViewerRoute extends PageRouteInfo<VideoViewerRouteArgs> {
VideoViewerRoute({
Key? key,
required Asset asset,
required bool isMotionVideo,
required void Function() onVideoEnded,
bool isMotionVideo = false,
void Function()? onVideoEnded,
void Function()? onPlaying,
void Function()? onPaused,
Widget? placeholder,
bool showControls = true,
Duration hideControlsTimer = const Duration(seconds: 5),
bool showDownloadingIndicator = true,
List<PageRouteInfo>? children,
}) : super(
VideoViewerRoute.name,
@@ -1400,6 +1406,9 @@ class VideoViewerRoute extends PageRouteInfo<VideoViewerRouteArgs> {
onPlaying: onPlaying,
onPaused: onPaused,
placeholder: placeholder,
showControls: showControls,
hideControlsTimer: hideControlsTimer,
showDownloadingIndicator: showDownloadingIndicator,
),
initialChildren: children,
);
@@ -1414,11 +1423,14 @@ class VideoViewerRouteArgs {
const VideoViewerRouteArgs({
this.key,
required this.asset,
required this.isMotionVideo,
required this.onVideoEnded,
this.isMotionVideo = false,
this.onVideoEnded,
this.onPlaying,
this.onPaused,
this.placeholder,
this.showControls = true,
this.hideControlsTimer = const Duration(seconds: 5),
this.showDownloadingIndicator = true,
});
final Key? key;
@@ -1427,7 +1439,7 @@ class VideoViewerRouteArgs {
final bool isMotionVideo;
final void Function() onVideoEnded;
final void Function()? onVideoEnded;
final void Function()? onPlaying;
@@ -1435,8 +1447,14 @@ class VideoViewerRouteArgs {
final Widget? placeholder;
final bool showControls;
final Duration hideControlsTimer;
final bool showDownloadingIndicator;
@override
String toString() {
return 'VideoViewerRouteArgs{key: $key, asset: $asset, isMotionVideo: $isMotionVideo, onVideoEnded: $onVideoEnded, onPlaying: $onPlaying, onPaused: $onPaused, placeholder: $placeholder}';
return 'VideoViewerRouteArgs{key: $key, asset: $asset, isMotionVideo: $isMotionVideo, onVideoEnded: $onVideoEnded, onPlaying: $onPlaying, onPaused: $onPaused, placeholder: $placeholder, showControls: $showControls, hideControlsTimer: $hideControlsTimer, showDownloadingIndicator: $showDownloadingIndicator}';
}
}
+5 -6
View File
@@ -1,6 +1,5 @@
import 'package:flutter/painting.dart';
import 'original_image_provider.dart';
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_local_image_provider.dart';
/// [ImageCache] that uses two caches for small and large images
/// so that a single large image does not evict all small iamges
@@ -34,7 +33,7 @@ final class CustomImageCache implements ImageCache {
@override
bool containsKey(Object key) =>
(key is OriginalImageProvider ? _large : _small).containsKey(key);
(key is ImmichLocalImageProvider ? _large : _small).containsKey(key);
@override
int get currentSize => _small.currentSize + _large.currentSize;
@@ -44,7 +43,7 @@ final class CustomImageCache implements ImageCache {
@override
bool evict(Object key, {bool includeLive = true}) =>
(key is OriginalImageProvider ? _large : _small)
(key is ImmichLocalImageProvider ? _large : _small)
.evict(key, includeLive: includeLive);
@override
@@ -60,10 +59,10 @@ final class CustomImageCache implements ImageCache {
ImageStreamCompleter Function() loader, {
ImageErrorListener? onError,
}) =>
(key is OriginalImageProvider ? _large : _small)
(key is ImmichLocalImageProvider ? _large : _small)
.putIfAbsent(key, loader, onError: onError);
@override
ImageCacheStatus statusForKey(Object key) =>
(key is OriginalImageProvider ? _large : _small).statusForKey(key);
(key is ImmichLocalImageProvider ? _large : _small).statusForKey(key);
}
-73
View File
@@ -1,73 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:immich_mobile/shared/models/asset.dart';
/// Loads the original image for local assets
@immutable
final class OriginalImageProvider extends ImageProvider<OriginalImageProvider> {
final Asset asset;
const OriginalImageProvider(this.asset);
@override
Future<OriginalImageProvider> obtainKey(ImageConfiguration configuration) =>
SynchronousFuture<OriginalImageProvider>(this);
@override
ImageStreamCompleter loadImage(
OriginalImageProvider key,
ImageDecoderCallback decode,
) =>
MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode),
scale: 1.0,
informationCollector: () sync* {
yield ErrorDescription(asset.fileName);
},
);
Future<ui.Codec> _loadAsync(
OriginalImageProvider key,
ImageDecoderCallback decode,
) async {
final ui.ImmutableBuffer buffer;
if (asset.isImage) {
final File? file = await asset.local?.originFile;
if (file == null) {
throw StateError("Opening file for asset ${asset.fileName} failed");
}
try {
buffer = await ui.ImmutableBuffer.fromFilePath(file.path);
} catch (error) {
throw StateError("Loading asset ${asset.fileName} failed");
}
} else {
final thumbBytes = await asset.local?.thumbnailData;
if (thumbBytes == null) {
throw StateError("Loading thumb for video ${asset.fileName} failed");
}
buffer = await ui.ImmutableBuffer.fromUint8List(thumbBytes);
}
try {
final codec = await decode(buffer);
debugPrint("Decoded image ${asset.fileName}");
return codec;
} catch (error) {
throw StateError("Decoding asset ${asset.fileName} failed");
}
}
@override
bool operator ==(Object other) {
if (other is! OriginalImageProvider) return false;
if (identical(this, other)) return true;
return asset == other.asset;
}
@override
int get hashCode => asset.hashCode;
}
+6 -6
View File
@@ -437,17 +437,17 @@ class Asset {
"remoteId": "${remoteId ?? "N/A"}",
"localId": "${localId ?? "N/A"}",
"checksum": "$checksum",
"ownerId": $ownerId,
"ownerId": $ownerId,
"livePhotoVideoId": "${livePhotoVideoId ?? "N/A"}",
"stackCount": "$stackCount",
"stackParentId": "${stackParentId ?? "N/A"}",
"fileCreatedAt": "$fileCreatedAt",
"fileModifiedAt": "$fileModifiedAt",
"updatedAt": "$updatedAt",
"durationInSeconds": $durationInSeconds,
"fileModifiedAt": "$fileModifiedAt",
"updatedAt": "$updatedAt",
"durationInSeconds": $durationInSeconds,
"type": "$type",
"fileName": "$fileName",
"isFavorite": $isFavorite,
"fileName": "$fileName",
"isFavorite": $isFavorite,
"isRemote": $isRemote,
"storage": "$storage",
"width": ${width ?? "N/A"},
+108 -161
View File
@@ -1,40 +1,109 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_local_image_provider.dart';
import 'package:immich_mobile/modules/asset_viewer/image_providers/immich_remote_image_provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:octo_image/octo_image.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:openapi/api.dart' as api;
import 'package:photo_manager_image_provider/photo_manager_image_provider.dart';
/// Renders an Asset using local data if available, else remote data
class ImmichImage extends StatelessWidget {
const ImmichImage(
this.asset, {
this.width,
this.height,
this.fit = BoxFit.cover,
this.useGrayBoxPlaceholder = false,
this.useProgressIndicator = false,
this.type = api.ThumbnailFormat.WEBP,
this.preferredLocalAssetSize = 250,
this.placeholder = const ThumbnailPlaceholder(),
this.isThumbnail = false,
this.thumbnailSize = 250,
super.key,
});
final Asset? asset;
final bool useGrayBoxPlaceholder;
final bool useProgressIndicator;
final Widget? placeholder;
final double? width;
final double? height;
final BoxFit fit;
final api.ThumbnailFormat type;
final int preferredLocalAssetSize;
final bool isThumbnail;
final int thumbnailSize;
/// Factory constructor to use the thumbnail variant
factory ImmichImage.thumbnail(
Asset? asset, {
BoxFit fit = BoxFit.cover,
double? width,
double? height,
}) {
// Use the width and height to derive thumbnail size
final thumbnailSize = max(width ?? 250, height ?? 250).toInt();
return ImmichImage(
asset,
isThumbnail: true,
fit: fit,
width: width,
height: height,
placeholder: ThumbnailPlaceholder(
height: thumbnailSize.toDouble(),
width: thumbnailSize.toDouble(),
),
thumbnailSize: thumbnailSize,
);
}
// Helper function to return the image provider for the asset
// either by using the asset ID or the asset itself
/// [asset] is the Asset to request, or else use [assetId] to get a remote
/// image provider
/// Use [isThumbnail] and [thumbnailSize] if you'd like to request a thumbnail
/// The size of the square thumbnail to request. Ignored if isThumbnail
/// is not true
static ImageProvider imageProvider({
Asset? asset,
String? assetId,
bool isThumbnail = false,
int thumbnailSize = 250,
}) {
if (asset == null && assetId == null) {
throw Exception('Must supply either asset or assetId');
}
if (asset == null) {
return ImmichRemoteImageProvider(
assetId: assetId!,
isThumbnail: isThumbnail,
);
}
if (useLocal(asset) && isThumbnail) {
return AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: ThumbnailSize.square(thumbnailSize),
);
} else if (useLocal(asset) && !isThumbnail) {
return ImmichLocalImageProvider(
asset: asset,
);
} else {
return ImmichRemoteImageProvider(
assetId: asset.remoteId!,
isThumbnail: isThumbnail,
);
}
}
static bool useLocal(Asset asset) =>
!asset.isRemote ||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false);
@override
Widget build(BuildContext context) {
if (this.asset == null) {
if (asset == null) {
return Container(
decoration: const BoxDecoration(
color: Colors.grey,
@@ -48,96 +117,35 @@ class ImmichImage extends StatelessWidget {
),
);
}
final Asset asset = this.asset!;
if (useLocal(asset)) {
return Image(
image: localImageProvider(asset, size: preferredLocalAssetSize),
width: width,
height: height,
fit: fit,
frameBuilder: (context, child, frame, wasSynchronouslyLoaded) {
if (wasSynchronouslyLoaded || frame != null) {
return child;
}
// Show loading if desired
return Stack(
children: [
if (useGrayBoxPlaceholder)
const SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
),
),
if (useProgressIndicator)
const Center(
child: CircularProgressIndicator(),
),
],
);
},
errorBuilder: (context, error, stackTrace) {
if (error is PlatformException &&
error.code == "The asset not found!") {
debugPrint(
"Asset ${asset.localId} does not exist anymore on device!",
);
} else {
debugPrint(
"Error getting thumb for assetId=${asset.localId}: $error",
);
}
return Icon(
Icons.image_not_supported_outlined,
color: context.primaryColor,
);
},
);
}
final String? accessToken = Store.get(StoreKey.accessToken);
final String thumbnailRequestUrl = getThumbnailUrl(asset, type: type);
return CachedNetworkImage(
imageUrl: thumbnailRequestUrl,
httpHeaders: {"x-immich-user-token": accessToken ?? ""},
cacheKey: getThumbnailCacheKey(asset, type: type),
return OctoImage(
fadeInDuration: const Duration(milliseconds: 0),
fadeOutDuration: const Duration(milliseconds: 400),
placeholderBuilder: (context) {
if (placeholder != null) {
// Use the gray box placeholder
return placeholder!;
}
// No placeholder
return const SizedBox();
},
image: ImmichImage.imageProvider(
asset: asset,
isThumbnail: isThumbnail,
),
width: width,
height: height,
// keeping memCacheWidth, memCacheHeight, maxWidthDiskCache and
// maxHeightDiskCache = null allows to simply store the webp thumbnail
// from the server and use it for all rendered thumbnail sizes
fit: fit,
fadeInDuration: const Duration(milliseconds: 250),
progressIndicatorBuilder: (context, url, downloadProgress) {
// Show loading if desired
return Stack(
children: [
if (useGrayBoxPlaceholder)
const SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
),
),
if (useProgressIndicator)
Transform.scale(
scale: 2,
child: Center(
child: CircularProgressIndicator.adaptive(
strokeWidth: 1,
value: downloadProgress.progress,
),
),
),
],
);
},
errorWidget: (context, url, error) {
if (error is HttpExceptionWithStatus &&
error.statusCode >= 400 &&
error.statusCode < 500) {
debugPrint("Evicting thumbnail '$url' from cache: $error");
CachedNetworkImage.evictFromCache(url);
errorBuilder: (context, error, stackTrace) {
if (error is PlatformException &&
error.code == "The asset not found!") {
debugPrint(
"Asset ${asset?.localId} does not exist anymore on device!",
);
} else {
debugPrint(
"Error getting thumb for assetId=${asset?.localId}: $error",
);
}
return Icon(
Icons.image_not_supported_outlined,
@@ -146,65 +154,4 @@ class ImmichImage extends StatelessWidget {
},
);
}
static AssetEntityImageProvider localImageProvider(
Asset asset, {
int size = 250,
}) =>
AssetEntityImageProvider(
asset.local!,
isOriginal: false,
thumbnailSize: ThumbnailSize.square(size),
);
static CachedNetworkImageProvider remoteThumbnailProvider(
Asset asset,
api.ThumbnailFormat type,
Map<String, String> authHeader,
) =>
CachedNetworkImageProvider(
getThumbnailUrl(asset, type: type),
cacheKey: getThumbnailCacheKey(asset, type: type),
headers: authHeader,
);
/// TODO: refactor image providers to separate class
static CachedNetworkImageProvider remoteThumbnailProviderForId(
String assetId, {
api.ThumbnailFormat type = api.ThumbnailFormat.WEBP,
}) =>
CachedNetworkImageProvider(
getThumbnailUrlForRemoteId(assetId, type: type),
cacheKey: getThumbnailCacheKeyForRemoteId(assetId, type: type),
headers: {
"x-immich-user-token": Store.get(StoreKey.accessToken),
},
);
/// Precaches this asset for instant load the next time it is shown
static Future<void> precacheAsset(
Asset asset,
BuildContext context, {
type = api.ThumbnailFormat.WEBP,
size = 250,
}) {
if (useLocal(asset)) {
// Precache the local image
return precacheImage(
localImageProvider(asset, size: size),
context,
);
} else {
final accessToken = Store.get(StoreKey.accessToken);
// Precache the remote image since we are not using local images
return precacheImage(
remoteThumbnailProvider(asset, type, {"x-immich-user-token": accessToken}),
context,
);
}
}
static bool useLocal(Asset asset) =>
!asset.isRemote ||
asset.isLocal && !Store.get(StoreKey.preferRemoteImage, false);
}
+5 -1
View File
@@ -56,7 +56,11 @@ String getAlbumThumbNailCacheKey(
}
String getImageUrl(final Asset asset) {
return '${Store.get(StoreKey.serverEndpoint)}/asset/file/${asset.remoteId}?isThumb=false';
return getImageUrlFromId(asset.remoteId!);
}
String getImageUrlFromId(final String id) {
return '${Store.get(StoreKey.serverEndpoint)}/asset/file/$id?isThumb=false';
}
String getImageCacheKey(final Asset asset) {
+9
View File
@@ -90,6 +90,7 @@ doc/MapMarkerResponseDto.md
doc/MapTheme.md
doc/MemoryLaneResponseDto.md
doc/MergePersonDto.md
doc/MetadataSearchDto.md
doc/ModelType.md
doc/OAuthApi.md
doc/OAuthAuthorizeResponseDto.md
@@ -120,6 +121,7 @@ doc/SearchExploreResponseDto.md
doc/SearchFacetCountResponseDto.md
doc/SearchFacetResponseDto.md
doc/SearchResponseDto.md
doc/SearchSuggestionType.md
doc/ServerConfigDto.md
doc/ServerFeaturesDto.md
doc/ServerInfoApi.md
@@ -136,6 +138,7 @@ doc/SharedLinkResponseDto.md
doc/SharedLinkType.md
doc/SignUpDto.md
doc/SmartInfoResponseDto.md
doc/SmartSearchDto.md
doc/SystemConfigApi.md
doc/SystemConfigDto.md
doc/SystemConfigFFmpegDto.md
@@ -287,6 +290,7 @@ lib/model/map_marker_response_dto.dart
lib/model/map_theme.dart
lib/model/memory_lane_response_dto.dart
lib/model/merge_person_dto.dart
lib/model/metadata_search_dto.dart
lib/model/model_type.dart
lib/model/o_auth_authorize_response_dto.dart
lib/model/o_auth_callback_dto.dart
@@ -313,6 +317,7 @@ lib/model/search_explore_response_dto.dart
lib/model/search_facet_count_response_dto.dart
lib/model/search_facet_response_dto.dart
lib/model/search_response_dto.dart
lib/model/search_suggestion_type.dart
lib/model/server_config_dto.dart
lib/model/server_features_dto.dart
lib/model/server_info_response_dto.dart
@@ -327,6 +332,7 @@ lib/model/shared_link_response_dto.dart
lib/model/shared_link_type.dart
lib/model/sign_up_dto.dart
lib/model/smart_info_response_dto.dart
lib/model/smart_search_dto.dart
lib/model/system_config_dto.dart
lib/model/system_config_f_fmpeg_dto.dart
lib/model/system_config_job_dto.dart
@@ -455,6 +461,7 @@ test/map_marker_response_dto_test.dart
test/map_theme_test.dart
test/memory_lane_response_dto_test.dart
test/merge_person_dto_test.dart
test/metadata_search_dto_test.dart
test/model_type_test.dart
test/o_auth_api_test.dart
test/o_auth_authorize_response_dto_test.dart
@@ -485,6 +492,7 @@ test/search_explore_response_dto_test.dart
test/search_facet_count_response_dto_test.dart
test/search_facet_response_dto_test.dart
test/search_response_dto_test.dart
test/search_suggestion_type_test.dart
test/server_config_dto_test.dart
test/server_features_dto_test.dart
test/server_info_api_test.dart
@@ -501,6 +509,7 @@ test/shared_link_response_dto_test.dart
test/shared_link_type_test.dart
test/sign_up_dto_test.dart
test/smart_info_response_dto_test.dart
test/smart_search_dto_test.dart
test/system_config_api_test.dart
test/system_config_dto_test.dart
test/system_config_f_fmpeg_dto_test.dart
+6 -2
View File
@@ -161,10 +161,11 @@ Class | Method | HTTP request | Description
*PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person |
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
*SearchApi* | [**getSearchSuggestions**](doc//SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions |
*SearchApi* | [**search**](doc//SearchApi.md#search) | **GET** /search |
*SearchApi* | [**searchMetadata**](doc//SearchApi.md#searchmetadata) | **GET** /search/metadata |
*SearchApi* | [**searchMetadata**](doc//SearchApi.md#searchmetadata) | **POST** /search/metadata |
*SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person |
*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **GET** /search/smart |
*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **POST** /search/smart |
*ServerInfoApi* | [**getServerConfig**](doc//ServerInfoApi.md#getserverconfig) | **GET** /server-info/config |
*ServerInfoApi* | [**getServerFeatures**](doc//ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features |
*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |
@@ -289,6 +290,7 @@ Class | Method | HTTP request | Description
- [MapTheme](doc//MapTheme.md)
- [MemoryLaneResponseDto](doc//MemoryLaneResponseDto.md)
- [MergePersonDto](doc//MergePersonDto.md)
- [MetadataSearchDto](doc//MetadataSearchDto.md)
- [ModelType](doc//ModelType.md)
- [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md)
- [OAuthCallbackDto](doc//OAuthCallbackDto.md)
@@ -315,6 +317,7 @@ Class | Method | HTTP request | Description
- [SearchFacetCountResponseDto](doc//SearchFacetCountResponseDto.md)
- [SearchFacetResponseDto](doc//SearchFacetResponseDto.md)
- [SearchResponseDto](doc//SearchResponseDto.md)
- [SearchSuggestionType](doc//SearchSuggestionType.md)
- [ServerConfigDto](doc//ServerConfigDto.md)
- [ServerFeaturesDto](doc//ServerFeaturesDto.md)
- [ServerInfoResponseDto](doc//ServerInfoResponseDto.md)
@@ -329,6 +332,7 @@ Class | Method | HTTP request | Description
- [SharedLinkType](doc//SharedLinkType.md)
- [SignUpDto](doc//SignUpDto.md)
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
- [SmartSearchDto](doc//SmartSearchDto.md)
- [SystemConfigDto](doc//SystemConfigDto.md)
- [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md)
- [SystemConfigJobDto](doc//SystemConfigJobDto.md)
+10 -4
View File
@@ -659,7 +659,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getMapMarkers**
> List<MapMarkerResponseDto> getMapMarkers(fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite)
> List<MapMarkerResponseDto> getMapMarkers(fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite, withPartners)
@@ -686,9 +686,10 @@ final fileCreatedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final fileCreatedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final isArchived = true; // bool |
final isFavorite = true; // bool |
final withPartners = true; // bool |
try {
final result = api_instance.getMapMarkers(fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite);
final result = api_instance.getMapMarkers(fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite, withPartners);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getMapMarkers: $e\n');
@@ -703,6 +704,7 @@ Name | Type | Description | Notes
**fileCreatedBefore** | **DateTime**| | [optional]
**isArchived** | **bool**| | [optional]
**isFavorite** | **bool**| | [optional]
**withPartners** | **bool**| | [optional]
### Return type
@@ -1034,7 +1036,7 @@ void (empty response body)
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **searchAssets**
> List<AssetResponseDto> searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked)
> List<AssetResponseDto> searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked)
@@ -1071,6 +1073,7 @@ final isEncoded = true; // bool |
final isExternal = true; // bool |
final isFavorite = true; // bool |
final isMotion = true; // bool |
final isNotInAlbum = true; // bool |
final isOffline = true; // bool |
final isReadOnly = true; // bool |
final isVisible = true; // bool |
@@ -1082,6 +1085,7 @@ final order = ; // AssetOrder |
final originalFileName = originalFileName_example; // String |
final originalPath = originalPath_example; // String |
final page = 8.14; // num |
final personIds = []; // List<String> |
final resizePath = resizePath_example; // String |
final size = 8.14; // num |
final state = state_example; // String |
@@ -1100,7 +1104,7 @@ final withPeople = true; // bool |
final withStacked = true; // bool |
try {
final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked);
final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked);
print(result);
} catch (e) {
print('Exception when calling AssetApi->searchAssets: $e\n');
@@ -1125,6 +1129,7 @@ Name | Type | Description | Notes
**isExternal** | **bool**| | [optional]
**isFavorite** | **bool**| | [optional]
**isMotion** | **bool**| | [optional]
**isNotInAlbum** | **bool**| | [optional]
**isOffline** | **bool**| | [optional]
**isReadOnly** | **bool**| | [optional]
**isVisible** | **bool**| | [optional]
@@ -1136,6 +1141,7 @@ Name | Type | Description | Notes
**originalFileName** | **String**| | [optional]
**originalPath** | **String**| | [optional]
**page** | **num**| | [optional]
**personIds** | [**List<String>**](String.md)| | [optional] [default to const []]
**resizePath** | **String**| | [optional]
**size** | **num**| | [optional]
**state** | **String**| | [optional]
+57
View File
@@ -0,0 +1,57 @@
# openapi.model.MetadataSearchDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**checksum** | **String** | | [optional]
**city** | **String** | | [optional]
**country** | **String** | | [optional]
**createdAfter** | [**DateTime**](DateTime.md) | | [optional]
**createdBefore** | [**DateTime**](DateTime.md) | | [optional]
**deviceAssetId** | **String** | | [optional]
**deviceId** | **String** | | [optional]
**encodedVideoPath** | **String** | | [optional]
**id** | **String** | | [optional]
**isArchived** | **bool** | | [optional]
**isEncoded** | **bool** | | [optional]
**isExternal** | **bool** | | [optional]
**isFavorite** | **bool** | | [optional]
**isMotion** | **bool** | | [optional]
**isNotInAlbum** | **bool** | | [optional]
**isOffline** | **bool** | | [optional]
**isReadOnly** | **bool** | | [optional]
**isVisible** | **bool** | | [optional]
**lensModel** | **String** | | [optional]
**libraryId** | **String** | | [optional]
**make** | **String** | | [optional]
**model** | **String** | | [optional]
**order** | [**AssetOrder**](AssetOrder.md) | | [optional]
**originalFileName** | **String** | | [optional]
**originalPath** | **String** | | [optional]
**page** | **num** | | [optional]
**personIds** | **List<String>** | | [optional] [default to const []]
**resizePath** | **String** | | [optional]
**size** | **num** | | [optional]
**state** | **String** | | [optional]
**takenAfter** | [**DateTime**](DateTime.md) | | [optional]
**takenBefore** | [**DateTime**](DateTime.md) | | [optional]
**trashedAfter** | [**DateTime**](DateTime.md) | | [optional]
**trashedBefore** | [**DateTime**](DateTime.md) | | [optional]
**type** | [**AssetTypeEnum**](AssetTypeEnum.md) | | [optional]
**updatedAfter** | [**DateTime**](DateTime.md) | | [optional]
**updatedBefore** | [**DateTime**](DateTime.md) | | [optional]
**webpPath** | **String** | | [optional]
**withArchived** | **bool** | | [optional]
**withDeleted** | **bool** | | [optional]
**withExif** | **bool** | | [optional]
**withPeople** | **bool** | | [optional]
**withStacked** | **bool** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+78 -154
View File
@@ -10,10 +10,11 @@ All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**getExploreData**](SearchApi.md#getexploredata) | **GET** /search/explore |
[**getSearchSuggestions**](SearchApi.md#getsearchsuggestions) | **GET** /search/suggestions |
[**search**](SearchApi.md#search) | **GET** /search |
[**searchMetadata**](SearchApi.md#searchmetadata) | **GET** /search/metadata |
[**searchMetadata**](SearchApi.md#searchmetadata) | **POST** /search/metadata |
[**searchPerson**](SearchApi.md#searchperson) | **GET** /search/person |
[**searchSmart**](SearchApi.md#searchsmart) | **GET** /search/smart |
[**searchSmart**](SearchApi.md#searchsmart) | **POST** /search/smart |
# **getExploreData**
@@ -67,6 +68,69 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getSearchSuggestions**
> List<String> getSearchSuggestions(type, country, make, model, state)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = SearchApi();
final type = ; // SearchSuggestionType |
final country = country_example; // String |
final make = make_example; // String |
final model = model_example; // String |
final state = state_example; // String |
try {
final result = api_instance.getSearchSuggestions(type, country, make, model, state);
print(result);
} catch (e) {
print('Exception when calling SearchApi->getSearchSuggestions: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**type** | [**SearchSuggestionType**](.md)| |
**country** | **String**| | [optional]
**make** | **String**| | [optional]
**model** | **String**| | [optional]
**state** | **String**| | [optional]
### Return type
**List<String>**
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **search**
> SearchResponseDto search(clip, motion, page, q, query, recent, size, smart, type, withArchived)
@@ -91,7 +155,7 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = SearchApi();
final clip = true; // bool | @deprecated
final clip = true; // bool |
final motion = true; // bool |
final page = 8.14; // num |
final q = q_example; // String |
@@ -114,7 +178,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**clip** | **bool**| @deprecated | [optional]
**clip** | **bool**| | [optional]
**motion** | **bool**| | [optional]
**page** | **num**| | [optional]
**q** | **String**| | [optional]
@@ -141,7 +205,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **searchMetadata**
> SearchResponseDto searchMetadata(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked)
> SearchResponseDto searchMetadata(metadataSearchDto)
@@ -164,50 +228,10 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = SearchApi();
final checksum = checksum_example; // String |
final city = city_example; // String |
final country = country_example; // String |
final createdAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final createdBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final deviceAssetId = deviceAssetId_example; // String |
final deviceId = deviceId_example; // String |
final encodedVideoPath = encodedVideoPath_example; // String |
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final isArchived = true; // bool |
final isEncoded = true; // bool |
final isExternal = true; // bool |
final isFavorite = true; // bool |
final isMotion = true; // bool |
final isOffline = true; // bool |
final isReadOnly = true; // bool |
final isVisible = true; // bool |
final lensModel = lensModel_example; // String |
final libraryId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final make = make_example; // String |
final model = model_example; // String |
final order = ; // AssetOrder |
final originalFileName = originalFileName_example; // String |
final originalPath = originalPath_example; // String |
final page = 8.14; // num |
final resizePath = resizePath_example; // String |
final size = 8.14; // num |
final state = state_example; // String |
final takenAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final takenBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final trashedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final trashedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final type = ; // AssetTypeEnum |
final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final webpPath = webpPath_example; // String |
final withArchived = true; // bool |
final withDeleted = true; // bool |
final withExif = true; // bool |
final withPeople = true; // bool |
final withStacked = true; // bool |
final metadataSearchDto = MetadataSearchDto(); // MetadataSearchDto |
try {
final result = api_instance.searchMetadata(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked);
final result = api_instance.searchMetadata(metadataSearchDto);
print(result);
} catch (e) {
print('Exception when calling SearchApi->searchMetadata: $e\n');
@@ -218,47 +242,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**checksum** | **String**| | [optional]
**city** | **String**| | [optional]
**country** | **String**| | [optional]
**createdAfter** | **DateTime**| | [optional]
**createdBefore** | **DateTime**| | [optional]
**deviceAssetId** | **String**| | [optional]
**deviceId** | **String**| | [optional]
**encodedVideoPath** | **String**| | [optional]
**id** | **String**| | [optional]
**isArchived** | **bool**| | [optional]
**isEncoded** | **bool**| | [optional]
**isExternal** | **bool**| | [optional]
**isFavorite** | **bool**| | [optional]
**isMotion** | **bool**| | [optional]
**isOffline** | **bool**| | [optional]
**isReadOnly** | **bool**| | [optional]
**isVisible** | **bool**| | [optional]
**lensModel** | **String**| | [optional]
**libraryId** | **String**| | [optional]
**make** | **String**| | [optional]
**model** | **String**| | [optional]
**order** | [**AssetOrder**](.md)| | [optional]
**originalFileName** | **String**| | [optional]
**originalPath** | **String**| | [optional]
**page** | **num**| | [optional]
**resizePath** | **String**| | [optional]
**size** | **num**| | [optional]
**state** | **String**| | [optional]
**takenAfter** | **DateTime**| | [optional]
**takenBefore** | **DateTime**| | [optional]
**trashedAfter** | **DateTime**| | [optional]
**trashedBefore** | **DateTime**| | [optional]
**type** | [**AssetTypeEnum**](.md)| | [optional]
**updatedAfter** | **DateTime**| | [optional]
**updatedBefore** | **DateTime**| | [optional]
**webpPath** | **String**| | [optional]
**withArchived** | **bool**| | [optional]
**withDeleted** | **bool**| | [optional]
**withExif** | **bool**| | [optional]
**withPeople** | **bool**| | [optional]
**withStacked** | **bool**| | [optional]
**metadataSearchDto** | [**MetadataSearchDto**](MetadataSearchDto.md)| |
### Return type
@@ -270,7 +254,7 @@ Name | Type | Description | Notes
### HTTP request headers
- **Content-Type**: Not defined
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
@@ -333,7 +317,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **searchSmart**
> SearchResponseDto searchSmart(query, city, country, createdAfter, createdBefore, deviceId, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, page, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif)
> SearchResponseDto searchSmart(smartSearchDto)
@@ -356,40 +340,10 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = SearchApi();
final query = query_example; // String |
final city = city_example; // String |
final country = country_example; // String |
final createdAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final createdBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final deviceId = deviceId_example; // String |
final isArchived = true; // bool |
final isEncoded = true; // bool |
final isExternal = true; // bool |
final isFavorite = true; // bool |
final isMotion = true; // bool |
final isOffline = true; // bool |
final isReadOnly = true; // bool |
final isVisible = true; // bool |
final lensModel = lensModel_example; // String |
final libraryId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final make = make_example; // String |
final model = model_example; // String |
final page = 8.14; // num |
final size = 8.14; // num |
final state = state_example; // String |
final takenAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final takenBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final trashedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final trashedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final type = ; // AssetTypeEnum |
final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime |
final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime |
final withArchived = true; // bool |
final withDeleted = true; // bool |
final withExif = true; // bool |
final smartSearchDto = SmartSearchDto(); // SmartSearchDto |
try {
final result = api_instance.searchSmart(query, city, country, createdAfter, createdBefore, deviceId, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, page, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif);
final result = api_instance.searchSmart(smartSearchDto);
print(result);
} catch (e) {
print('Exception when calling SearchApi->searchSmart: $e\n');
@@ -400,37 +354,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**query** | **String**| |
**city** | **String**| | [optional]
**country** | **String**| | [optional]
**createdAfter** | **DateTime**| | [optional]
**createdBefore** | **DateTime**| | [optional]
**deviceId** | **String**| | [optional]
**isArchived** | **bool**| | [optional]
**isEncoded** | **bool**| | [optional]
**isExternal** | **bool**| | [optional]
**isFavorite** | **bool**| | [optional]
**isMotion** | **bool**| | [optional]
**isOffline** | **bool**| | [optional]
**isReadOnly** | **bool**| | [optional]
**isVisible** | **bool**| | [optional]
**lensModel** | **String**| | [optional]
**libraryId** | **String**| | [optional]
**make** | **String**| | [optional]
**model** | **String**| | [optional]
**page** | **num**| | [optional]
**size** | **num**| | [optional]
**state** | **String**| | [optional]
**takenAfter** | **DateTime**| | [optional]
**takenBefore** | **DateTime**| | [optional]
**trashedAfter** | **DateTime**| | [optional]
**trashedBefore** | **DateTime**| | [optional]
**type** | [**AssetTypeEnum**](.md)| | [optional]
**updatedAfter** | **DateTime**| | [optional]
**updatedBefore** | **DateTime**| | [optional]
**withArchived** | **bool**| | [optional]
**withDeleted** | **bool**| | [optional]
**withExif** | **bool**| | [optional]
**smartSearchDto** | [**SmartSearchDto**](SmartSearchDto.md)| |
### Return type
@@ -442,7 +366,7 @@ Name | Type | Description | Notes
### HTTP request headers
- **Content-Type**: Not defined
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+14
View File
@@ -0,0 +1,14 @@
# openapi.model.SearchSuggestionType
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+45
View File
@@ -0,0 +1,45 @@
# openapi.model.SmartSearchDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**city** | **String** | | [optional]
**country** | **String** | | [optional]
**createdAfter** | [**DateTime**](DateTime.md) | | [optional]
**createdBefore** | [**DateTime**](DateTime.md) | | [optional]
**deviceId** | **String** | | [optional]
**isArchived** | **bool** | | [optional]
**isEncoded** | **bool** | | [optional]
**isExternal** | **bool** | | [optional]
**isFavorite** | **bool** | | [optional]
**isMotion** | **bool** | | [optional]
**isOffline** | **bool** | | [optional]
**isReadOnly** | **bool** | | [optional]
**isVisible** | **bool** | | [optional]
**lensModel** | **String** | | [optional]
**libraryId** | **String** | | [optional]
**make** | **String** | | [optional]
**model** | **String** | | [optional]
**page** | **num** | | [optional]
**query** | **String** | |
**size** | **num** | | [optional]
**state** | **String** | | [optional]
**takenAfter** | [**DateTime**](DateTime.md) | | [optional]
**takenBefore** | [**DateTime**](DateTime.md) | | [optional]
**trashedAfter** | [**DateTime**](DateTime.md) | | [optional]
**trashedBefore** | [**DateTime**](DateTime.md) | | [optional]
**type** | [**AssetTypeEnum**](AssetTypeEnum.md) | | [optional]
**updatedAfter** | [**DateTime**](DateTime.md) | | [optional]
**updatedBefore** | [**DateTime**](DateTime.md) | | [optional]
**withArchived** | **bool** | | [optional]
**withDeleted** | **bool** | | [optional]
**withExif** | **bool** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+3
View File
@@ -127,6 +127,7 @@ part 'model/map_marker_response_dto.dart';
part 'model/map_theme.dart';
part 'model/memory_lane_response_dto.dart';
part 'model/merge_person_dto.dart';
part 'model/metadata_search_dto.dart';
part 'model/model_type.dart';
part 'model/o_auth_authorize_response_dto.dart';
part 'model/o_auth_callback_dto.dart';
@@ -153,6 +154,7 @@ part 'model/search_explore_response_dto.dart';
part 'model/search_facet_count_response_dto.dart';
part 'model/search_facet_response_dto.dart';
part 'model/search_response_dto.dart';
part 'model/search_suggestion_type.dart';
part 'model/server_config_dto.dart';
part 'model/server_features_dto.dart';
part 'model/server_info_response_dto.dart';
@@ -167,6 +169,7 @@ part 'model/shared_link_response_dto.dart';
part 'model/shared_link_type.dart';
part 'model/sign_up_dto.dart';
part 'model/smart_info_response_dto.dart';
part 'model/smart_search_dto.dart';
part 'model/system_config_dto.dart';
part 'model/system_config_f_fmpeg_dto.dart';
part 'model/system_config_job_dto.dart';
+27 -6
View File
@@ -652,7 +652,9 @@ class AssetApi {
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
Future<Response> getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, }) async {
///
/// * [bool] withPartners:
Future<Response> getMapMarkersWithHttpInfo({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/map-marker';
@@ -675,6 +677,9 @@ class AssetApi {
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (withPartners != null) {
queryParams.addAll(_queryParams('', 'withPartners', withPartners));
}
const contentTypes = <String>[];
@@ -699,8 +704,10 @@ class AssetApi {
/// * [bool] isArchived:
///
/// * [bool] isFavorite:
Future<List<MapMarkerResponseDto>?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, }) async {
final response = await getMapMarkersWithHttpInfo( fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, );
///
/// * [bool] withPartners:
Future<List<MapMarkerResponseDto>?> getMapMarkers({ DateTime? fileCreatedAfter, DateTime? fileCreatedBefore, bool? isArchived, bool? isFavorite, bool? withPartners, }) async {
final response = await getMapMarkersWithHttpInfo( fileCreatedAfter: fileCreatedAfter, fileCreatedBefore: fileCreatedBefore, isArchived: isArchived, isFavorite: isFavorite, withPartners: withPartners, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -1133,6 +1140,8 @@ class AssetApi {
///
/// * [bool] isMotion:
///
/// * [bool] isNotInAlbum:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
@@ -1155,6 +1164,8 @@ class AssetApi {
///
/// * [num] page:
///
/// * [List<String>] personIds:
///
/// * [String] resizePath:
///
/// * [num] size:
@@ -1186,7 +1197,7 @@ class AssetApi {
/// * [bool] withPeople:
///
/// * [bool] withStacked:
Future<Response> searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
Future<Response> searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List<String>? personIds, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
// ignore: prefer_const_declarations
final path = r'/assets';
@@ -1239,6 +1250,9 @@ class AssetApi {
if (isMotion != null) {
queryParams.addAll(_queryParams('', 'isMotion', isMotion));
}
if (isNotInAlbum != null) {
queryParams.addAll(_queryParams('', 'isNotInAlbum', isNotInAlbum));
}
if (isOffline != null) {
queryParams.addAll(_queryParams('', 'isOffline', isOffline));
}
@@ -1272,6 +1286,9 @@ class AssetApi {
if (page != null) {
queryParams.addAll(_queryParams('', 'page', page));
}
if (personIds != null) {
queryParams.addAll(_queryParams('multi', 'personIds', personIds));
}
if (resizePath != null) {
queryParams.addAll(_queryParams('', 'resizePath', resizePath));
}
@@ -1365,6 +1382,8 @@ class AssetApi {
///
/// * [bool] isMotion:
///
/// * [bool] isNotInAlbum:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
@@ -1387,6 +1406,8 @@ class AssetApi {
///
/// * [num] page:
///
/// * [List<String>] personIds:
///
/// * [String] resizePath:
///
/// * [num] size:
@@ -1418,8 +1439,8 @@ class AssetApi {
/// * [bool] withPeople:
///
/// * [bool] withStacked:
Future<List<AssetResponseDto>?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, );
Future<List<AssetResponseDto>?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, List<String>? personIds, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, personIds: personIds, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
+98 -516
View File
@@ -60,11 +60,90 @@ class SearchApi {
return null;
}
/// Performs an HTTP 'GET /search/suggestions' operation and returns the [Response].
/// Parameters:
///
/// * [SearchSuggestionType] type (required):
///
/// * [String] country:
///
/// * [String] make:
///
/// * [String] model:
///
/// * [String] state:
Future<Response> getSearchSuggestionsWithHttpInfo(SearchSuggestionType type, { String? country, String? make, String? model, String? state, }) async {
// ignore: prefer_const_declarations
final path = r'/search/suggestions';
// ignore: prefer_final_locals
Object? postBody;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (country != null) {
queryParams.addAll(_queryParams('', 'country', country));
}
if (make != null) {
queryParams.addAll(_queryParams('', 'make', make));
}
if (model != null) {
queryParams.addAll(_queryParams('', 'model', model));
}
if (state != null) {
queryParams.addAll(_queryParams('', 'state', state));
}
queryParams.addAll(_queryParams('', 'type', type));
const contentTypes = <String>[];
return apiClient.invokeAPI(
path,
'GET',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
/// Parameters:
///
/// * [SearchSuggestionType] type (required):
///
/// * [String] country:
///
/// * [String] make:
///
/// * [String] model:
///
/// * [String] state:
Future<List<String>?> getSearchSuggestions(SearchSuggestionType type, { String? country, String? make, String? model, String? state, }) async {
final response = await getSearchSuggestionsWithHttpInfo(type, country: country, make: make, model: model, state: state, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
final responseBody = await _decodeBodyBytes(response);
return (await apiClient.deserializeAsync(responseBody, 'List<String>') as List)
.cast<String>()
.toList(growable: false);
}
return null;
}
/// Performs an HTTP 'GET /search' operation and returns the [Response].
/// Parameters:
///
/// * [bool] clip:
/// @deprecated
///
/// * [bool] motion:
///
@@ -142,7 +221,6 @@ class SearchApi {
/// Parameters:
///
/// * [bool] clip:
/// @deprecated
///
/// * [bool] motion:
///
@@ -176,231 +254,27 @@ class SearchApi {
return null;
}
/// Performs an HTTP 'GET /search/metadata' operation and returns the [Response].
/// Performs an HTTP 'POST /search/metadata' operation and returns the [Response].
/// Parameters:
///
/// * [String] checksum:
///
/// * [String] city:
///
/// * [String] country:
///
/// * [DateTime] createdAfter:
///
/// * [DateTime] createdBefore:
///
/// * [String] deviceAssetId:
///
/// * [String] deviceId:
///
/// * [String] encodedVideoPath:
///
/// * [String] id:
///
/// * [bool] isArchived:
///
/// * [bool] isEncoded:
///
/// * [bool] isExternal:
///
/// * [bool] isFavorite:
///
/// * [bool] isMotion:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
///
/// * [bool] isVisible:
///
/// * [String] lensModel:
///
/// * [String] libraryId:
///
/// * [String] make:
///
/// * [String] model:
///
/// * [AssetOrder] order:
///
/// * [String] originalFileName:
///
/// * [String] originalPath:
///
/// * [num] page:
///
/// * [String] resizePath:
///
/// * [num] size:
///
/// * [String] state:
///
/// * [DateTime] takenAfter:
///
/// * [DateTime] takenBefore:
///
/// * [DateTime] trashedAfter:
///
/// * [DateTime] trashedBefore:
///
/// * [AssetTypeEnum] type:
///
/// * [DateTime] updatedAfter:
///
/// * [DateTime] updatedBefore:
///
/// * [String] webpPath:
///
/// * [bool] withArchived:
///
/// * [bool] withDeleted:
///
/// * [bool] withExif:
///
/// * [bool] withPeople:
///
/// * [bool] withStacked:
Future<Response> searchMetadataWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
/// * [MetadataSearchDto] metadataSearchDto (required):
Future<Response> searchMetadataWithHttpInfo(MetadataSearchDto metadataSearchDto,) async {
// ignore: prefer_const_declarations
final path = r'/search/metadata';
// ignore: prefer_final_locals
Object? postBody;
Object? postBody = metadataSearchDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (checksum != null) {
queryParams.addAll(_queryParams('', 'checksum', checksum));
}
if (city != null) {
queryParams.addAll(_queryParams('', 'city', city));
}
if (country != null) {
queryParams.addAll(_queryParams('', 'country', country));
}
if (createdAfter != null) {
queryParams.addAll(_queryParams('', 'createdAfter', createdAfter));
}
if (createdBefore != null) {
queryParams.addAll(_queryParams('', 'createdBefore', createdBefore));
}
if (deviceAssetId != null) {
queryParams.addAll(_queryParams('', 'deviceAssetId', deviceAssetId));
}
if (deviceId != null) {
queryParams.addAll(_queryParams('', 'deviceId', deviceId));
}
if (encodedVideoPath != null) {
queryParams.addAll(_queryParams('', 'encodedVideoPath', encodedVideoPath));
}
if (id != null) {
queryParams.addAll(_queryParams('', 'id', id));
}
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isEncoded != null) {
queryParams.addAll(_queryParams('', 'isEncoded', isEncoded));
}
if (isExternal != null) {
queryParams.addAll(_queryParams('', 'isExternal', isExternal));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (isMotion != null) {
queryParams.addAll(_queryParams('', 'isMotion', isMotion));
}
if (isOffline != null) {
queryParams.addAll(_queryParams('', 'isOffline', isOffline));
}
if (isReadOnly != null) {
queryParams.addAll(_queryParams('', 'isReadOnly', isReadOnly));
}
if (isVisible != null) {
queryParams.addAll(_queryParams('', 'isVisible', isVisible));
}
if (lensModel != null) {
queryParams.addAll(_queryParams('', 'lensModel', lensModel));
}
if (libraryId != null) {
queryParams.addAll(_queryParams('', 'libraryId', libraryId));
}
if (make != null) {
queryParams.addAll(_queryParams('', 'make', make));
}
if (model != null) {
queryParams.addAll(_queryParams('', 'model', model));
}
if (order != null) {
queryParams.addAll(_queryParams('', 'order', order));
}
if (originalFileName != null) {
queryParams.addAll(_queryParams('', 'originalFileName', originalFileName));
}
if (originalPath != null) {
queryParams.addAll(_queryParams('', 'originalPath', originalPath));
}
if (page != null) {
queryParams.addAll(_queryParams('', 'page', page));
}
if (resizePath != null) {
queryParams.addAll(_queryParams('', 'resizePath', resizePath));
}
if (size != null) {
queryParams.addAll(_queryParams('', 'size', size));
}
if (state != null) {
queryParams.addAll(_queryParams('', 'state', state));
}
if (takenAfter != null) {
queryParams.addAll(_queryParams('', 'takenAfter', takenAfter));
}
if (takenBefore != null) {
queryParams.addAll(_queryParams('', 'takenBefore', takenBefore));
}
if (trashedAfter != null) {
queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter));
}
if (trashedBefore != null) {
queryParams.addAll(_queryParams('', 'trashedBefore', trashedBefore));
}
if (type != null) {
queryParams.addAll(_queryParams('', 'type', type));
}
if (updatedAfter != null) {
queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter));
}
if (updatedBefore != null) {
queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore));
}
if (webpPath != null) {
queryParams.addAll(_queryParams('', 'webpPath', webpPath));
}
if (withArchived != null) {
queryParams.addAll(_queryParams('', 'withArchived', withArchived));
}
if (withDeleted != null) {
queryParams.addAll(_queryParams('', 'withDeleted', withDeleted));
}
if (withExif != null) {
queryParams.addAll(_queryParams('', 'withExif', withExif));
}
if (withPeople != null) {
queryParams.addAll(_queryParams('', 'withPeople', withPeople));
}
if (withStacked != null) {
queryParams.addAll(_queryParams('', 'withStacked', withStacked));
}
const contentTypes = <String>[];
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'GET',
'POST',
queryParams,
postBody,
headerParams,
@@ -411,89 +285,9 @@ class SearchApi {
/// Parameters:
///
/// * [String] checksum:
///
/// * [String] city:
///
/// * [String] country:
///
/// * [DateTime] createdAfter:
///
/// * [DateTime] createdBefore:
///
/// * [String] deviceAssetId:
///
/// * [String] deviceId:
///
/// * [String] encodedVideoPath:
///
/// * [String] id:
///
/// * [bool] isArchived:
///
/// * [bool] isEncoded:
///
/// * [bool] isExternal:
///
/// * [bool] isFavorite:
///
/// * [bool] isMotion:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
///
/// * [bool] isVisible:
///
/// * [String] lensModel:
///
/// * [String] libraryId:
///
/// * [String] make:
///
/// * [String] model:
///
/// * [AssetOrder] order:
///
/// * [String] originalFileName:
///
/// * [String] originalPath:
///
/// * [num] page:
///
/// * [String] resizePath:
///
/// * [num] size:
///
/// * [String] state:
///
/// * [DateTime] takenAfter:
///
/// * [DateTime] takenBefore:
///
/// * [DateTime] trashedAfter:
///
/// * [DateTime] trashedBefore:
///
/// * [AssetTypeEnum] type:
///
/// * [DateTime] updatedAfter:
///
/// * [DateTime] updatedBefore:
///
/// * [String] webpPath:
///
/// * [bool] withArchived:
///
/// * [bool] withDeleted:
///
/// * [bool] withExif:
///
/// * [bool] withPeople:
///
/// * [bool] withStacked:
Future<SearchResponseDto?> searchMetadata({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async {
final response = await searchMetadataWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, );
/// * [MetadataSearchDto] metadataSearchDto (required):
Future<SearchResponseDto?> searchMetadata(MetadataSearchDto metadataSearchDto,) async {
final response = await searchMetadataWithHttpInfo(metadataSearchDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -566,179 +360,27 @@ class SearchApi {
return null;
}
/// Performs an HTTP 'GET /search/smart' operation and returns the [Response].
/// Performs an HTTP 'POST /search/smart' operation and returns the [Response].
/// Parameters:
///
/// * [String] query (required):
///
/// * [String] city:
///
/// * [String] country:
///
/// * [DateTime] createdAfter:
///
/// * [DateTime] createdBefore:
///
/// * [String] deviceId:
///
/// * [bool] isArchived:
///
/// * [bool] isEncoded:
///
/// * [bool] isExternal:
///
/// * [bool] isFavorite:
///
/// * [bool] isMotion:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
///
/// * [bool] isVisible:
///
/// * [String] lensModel:
///
/// * [String] libraryId:
///
/// * [String] make:
///
/// * [String] model:
///
/// * [num] page:
///
/// * [num] size:
///
/// * [String] state:
///
/// * [DateTime] takenAfter:
///
/// * [DateTime] takenBefore:
///
/// * [DateTime] trashedAfter:
///
/// * [DateTime] trashedBefore:
///
/// * [AssetTypeEnum] type:
///
/// * [DateTime] updatedAfter:
///
/// * [DateTime] updatedBefore:
///
/// * [bool] withArchived:
///
/// * [bool] withDeleted:
///
/// * [bool] withExif:
Future<Response> searchSmartWithHttpInfo(String query, { String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, num? page, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, bool? withArchived, bool? withDeleted, bool? withExif, }) async {
/// * [SmartSearchDto] smartSearchDto (required):
Future<Response> searchSmartWithHttpInfo(SmartSearchDto smartSearchDto,) async {
// ignore: prefer_const_declarations
final path = r'/search/smart';
// ignore: prefer_final_locals
Object? postBody;
Object? postBody = smartSearchDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (city != null) {
queryParams.addAll(_queryParams('', 'city', city));
}
if (country != null) {
queryParams.addAll(_queryParams('', 'country', country));
}
if (createdAfter != null) {
queryParams.addAll(_queryParams('', 'createdAfter', createdAfter));
}
if (createdBefore != null) {
queryParams.addAll(_queryParams('', 'createdBefore', createdBefore));
}
if (deviceId != null) {
queryParams.addAll(_queryParams('', 'deviceId', deviceId));
}
if (isArchived != null) {
queryParams.addAll(_queryParams('', 'isArchived', isArchived));
}
if (isEncoded != null) {
queryParams.addAll(_queryParams('', 'isEncoded', isEncoded));
}
if (isExternal != null) {
queryParams.addAll(_queryParams('', 'isExternal', isExternal));
}
if (isFavorite != null) {
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
}
if (isMotion != null) {
queryParams.addAll(_queryParams('', 'isMotion', isMotion));
}
if (isOffline != null) {
queryParams.addAll(_queryParams('', 'isOffline', isOffline));
}
if (isReadOnly != null) {
queryParams.addAll(_queryParams('', 'isReadOnly', isReadOnly));
}
if (isVisible != null) {
queryParams.addAll(_queryParams('', 'isVisible', isVisible));
}
if (lensModel != null) {
queryParams.addAll(_queryParams('', 'lensModel', lensModel));
}
if (libraryId != null) {
queryParams.addAll(_queryParams('', 'libraryId', libraryId));
}
if (make != null) {
queryParams.addAll(_queryParams('', 'make', make));
}
if (model != null) {
queryParams.addAll(_queryParams('', 'model', model));
}
if (page != null) {
queryParams.addAll(_queryParams('', 'page', page));
}
queryParams.addAll(_queryParams('', 'query', query));
if (size != null) {
queryParams.addAll(_queryParams('', 'size', size));
}
if (state != null) {
queryParams.addAll(_queryParams('', 'state', state));
}
if (takenAfter != null) {
queryParams.addAll(_queryParams('', 'takenAfter', takenAfter));
}
if (takenBefore != null) {
queryParams.addAll(_queryParams('', 'takenBefore', takenBefore));
}
if (trashedAfter != null) {
queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter));
}
if (trashedBefore != null) {
queryParams.addAll(_queryParams('', 'trashedBefore', trashedBefore));
}
if (type != null) {
queryParams.addAll(_queryParams('', 'type', type));
}
if (updatedAfter != null) {
queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter));
}
if (updatedBefore != null) {
queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore));
}
if (withArchived != null) {
queryParams.addAll(_queryParams('', 'withArchived', withArchived));
}
if (withDeleted != null) {
queryParams.addAll(_queryParams('', 'withDeleted', withDeleted));
}
if (withExif != null) {
queryParams.addAll(_queryParams('', 'withExif', withExif));
}
const contentTypes = <String>[];
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'GET',
'POST',
queryParams,
postBody,
headerParams,
@@ -749,69 +391,9 @@ class SearchApi {
/// Parameters:
///
/// * [String] query (required):
///
/// * [String] city:
///
/// * [String] country:
///
/// * [DateTime] createdAfter:
///
/// * [DateTime] createdBefore:
///
/// * [String] deviceId:
///
/// * [bool] isArchived:
///
/// * [bool] isEncoded:
///
/// * [bool] isExternal:
///
/// * [bool] isFavorite:
///
/// * [bool] isMotion:
///
/// * [bool] isOffline:
///
/// * [bool] isReadOnly:
///
/// * [bool] isVisible:
///
/// * [String] lensModel:
///
/// * [String] libraryId:
///
/// * [String] make:
///
/// * [String] model:
///
/// * [num] page:
///
/// * [num] size:
///
/// * [String] state:
///
/// * [DateTime] takenAfter:
///
/// * [DateTime] takenBefore:
///
/// * [DateTime] trashedAfter:
///
/// * [DateTime] trashedBefore:
///
/// * [AssetTypeEnum] type:
///
/// * [DateTime] updatedAfter:
///
/// * [DateTime] updatedBefore:
///
/// * [bool] withArchived:
///
/// * [bool] withDeleted:
///
/// * [bool] withExif:
Future<SearchResponseDto?> searchSmart(String query, { String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, num? page, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, bool? withArchived, bool? withDeleted, bool? withExif, }) async {
final response = await searchSmartWithHttpInfo(query, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, page: page, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, );
/// * [SmartSearchDto] smartSearchDto (required):
Future<SearchResponseDto?> searchSmart(SmartSearchDto smartSearchDto,) async {
final response = await searchSmartWithHttpInfo(smartSearchDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
+6
View File
@@ -336,6 +336,8 @@ class ApiClient {
return MemoryLaneResponseDto.fromJson(value);
case 'MergePersonDto':
return MergePersonDto.fromJson(value);
case 'MetadataSearchDto':
return MetadataSearchDto.fromJson(value);
case 'ModelType':
return ModelTypeTypeTransformer().decode(value);
case 'OAuthAuthorizeResponseDto':
@@ -388,6 +390,8 @@ class ApiClient {
return SearchFacetResponseDto.fromJson(value);
case 'SearchResponseDto':
return SearchResponseDto.fromJson(value);
case 'SearchSuggestionType':
return SearchSuggestionTypeTypeTransformer().decode(value);
case 'ServerConfigDto':
return ServerConfigDto.fromJson(value);
case 'ServerFeaturesDto':
@@ -416,6 +420,8 @@ class ApiClient {
return SignUpDto.fromJson(value);
case 'SmartInfoResponseDto':
return SmartInfoResponseDto.fromJson(value);
case 'SmartSearchDto':
return SmartSearchDto.fromJson(value);
case 'SystemConfigDto':
return SystemConfigDto.fromJson(value);
case 'SystemConfigFFmpegDto':
+3
View File
@@ -109,6 +109,9 @@ String parameterToString(dynamic value) {
if (value is ReactionType) {
return ReactionTypeTypeTransformer().encode(value).toString();
}
if (value is SearchSuggestionType) {
return SearchSuggestionTypeTypeTransformer().encode(value).toString();
}
if (value is SharedLinkType) {
return SharedLinkTypeTypeTransformer().encode(value).toString();
}
+813
View File
@@ -0,0 +1,813 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class MetadataSearchDto {
/// Returns a new [MetadataSearchDto] instance.
MetadataSearchDto({
this.checksum,
this.city,
this.country,
this.createdAfter,
this.createdBefore,
this.deviceAssetId,
this.deviceId,
this.encodedVideoPath,
this.id,
this.isArchived,
this.isEncoded,
this.isExternal,
this.isFavorite,
this.isMotion,
this.isNotInAlbum,
this.isOffline,
this.isReadOnly,
this.isVisible,
this.lensModel,
this.libraryId,
this.make,
this.model,
this.order,
this.originalFileName,
this.originalPath,
this.page,
this.personIds = const [],
this.resizePath,
this.size,
this.state,
this.takenAfter,
this.takenBefore,
this.trashedAfter,
this.trashedBefore,
this.type,
this.updatedAfter,
this.updatedBefore,
this.webpPath,
this.withArchived,
this.withDeleted,
this.withExif,
this.withPeople,
this.withStacked,
});
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? checksum;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? city;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? country;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? createdAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? createdBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? deviceAssetId;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? deviceId;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? encodedVideoPath;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? id;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isArchived;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isEncoded;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isExternal;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isFavorite;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isMotion;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isNotInAlbum;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isOffline;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isReadOnly;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isVisible;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? lensModel;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? libraryId;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? make;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? model;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetOrder? order;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? originalFileName;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? originalPath;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? page;
List<String> personIds;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? resizePath;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? size;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? state;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? takenAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? takenBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? trashedAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? trashedBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetTypeEnum? type;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? updatedAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? updatedBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? webpPath;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withArchived;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withDeleted;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withExif;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withPeople;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withStacked;
@override
bool operator ==(Object other) => identical(this, other) || other is MetadataSearchDto &&
other.checksum == checksum &&
other.city == city &&
other.country == country &&
other.createdAfter == createdAfter &&
other.createdBefore == createdBefore &&
other.deviceAssetId == deviceAssetId &&
other.deviceId == deviceId &&
other.encodedVideoPath == encodedVideoPath &&
other.id == id &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded &&
other.isExternal == isExternal &&
other.isFavorite == isFavorite &&
other.isMotion == isMotion &&
other.isNotInAlbum == isNotInAlbum &&
other.isOffline == isOffline &&
other.isReadOnly == isReadOnly &&
other.isVisible == isVisible &&
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
other.model == model &&
other.order == order &&
other.originalFileName == originalFileName &&
other.originalPath == originalPath &&
other.page == page &&
_deepEquality.equals(other.personIds, personIds) &&
other.resizePath == resizePath &&
other.size == size &&
other.state == state &&
other.takenAfter == takenAfter &&
other.takenBefore == takenBefore &&
other.trashedAfter == trashedAfter &&
other.trashedBefore == trashedBefore &&
other.type == type &&
other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore &&
other.webpPath == webpPath &&
other.withArchived == withArchived &&
other.withDeleted == withDeleted &&
other.withExif == withExif &&
other.withPeople == withPeople &&
other.withStacked == withStacked;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(checksum == null ? 0 : checksum!.hashCode) +
(city == null ? 0 : city!.hashCode) +
(country == null ? 0 : country!.hashCode) +
(createdAfter == null ? 0 : createdAfter!.hashCode) +
(createdBefore == null ? 0 : createdBefore!.hashCode) +
(deviceAssetId == null ? 0 : deviceAssetId!.hashCode) +
(deviceId == null ? 0 : deviceId!.hashCode) +
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
(id == null ? 0 : id!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) +
(isExternal == null ? 0 : isExternal!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) +
(isNotInAlbum == null ? 0 : isNotInAlbum!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) +
(isReadOnly == null ? 0 : isReadOnly!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
(model == null ? 0 : model!.hashCode) +
(order == null ? 0 : order!.hashCode) +
(originalFileName == null ? 0 : originalFileName!.hashCode) +
(originalPath == null ? 0 : originalPath!.hashCode) +
(page == null ? 0 : page!.hashCode) +
(personIds.hashCode) +
(resizePath == null ? 0 : resizePath!.hashCode) +
(size == null ? 0 : size!.hashCode) +
(state == null ? 0 : state!.hashCode) +
(takenAfter == null ? 0 : takenAfter!.hashCode) +
(takenBefore == null ? 0 : takenBefore!.hashCode) +
(trashedAfter == null ? 0 : trashedAfter!.hashCode) +
(trashedBefore == null ? 0 : trashedBefore!.hashCode) +
(type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(webpPath == null ? 0 : webpPath!.hashCode) +
(withArchived == null ? 0 : withArchived!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode) +
(withPeople == null ? 0 : withPeople!.hashCode) +
(withStacked == null ? 0 : withStacked!.hashCode);
@override
String toString() => 'MetadataSearchDto[checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceAssetId=$deviceAssetId, deviceId=$deviceId, encodedVideoPath=$encodedVideoPath, id=$id, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, resizePath=$resizePath, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, webpPath=$webpPath, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.checksum != null) {
json[r'checksum'] = this.checksum;
} else {
// json[r'checksum'] = null;
}
if (this.city != null) {
json[r'city'] = this.city;
} else {
// json[r'city'] = null;
}
if (this.country != null) {
json[r'country'] = this.country;
} else {
// json[r'country'] = null;
}
if (this.createdAfter != null) {
json[r'createdAfter'] = this.createdAfter!.toUtc().toIso8601String();
} else {
// json[r'createdAfter'] = null;
}
if (this.createdBefore != null) {
json[r'createdBefore'] = this.createdBefore!.toUtc().toIso8601String();
} else {
// json[r'createdBefore'] = null;
}
if (this.deviceAssetId != null) {
json[r'deviceAssetId'] = this.deviceAssetId;
} else {
// json[r'deviceAssetId'] = null;
}
if (this.deviceId != null) {
json[r'deviceId'] = this.deviceId;
} else {
// json[r'deviceId'] = null;
}
if (this.encodedVideoPath != null) {
json[r'encodedVideoPath'] = this.encodedVideoPath;
} else {
// json[r'encodedVideoPath'] = null;
}
if (this.id != null) {
json[r'id'] = this.id;
} else {
// json[r'id'] = null;
}
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded;
} else {
// json[r'isEncoded'] = null;
}
if (this.isExternal != null) {
json[r'isExternal'] = this.isExternal;
} else {
// json[r'isExternal'] = null;
}
if (this.isFavorite != null) {
json[r'isFavorite'] = this.isFavorite;
} else {
// json[r'isFavorite'] = null;
}
if (this.isMotion != null) {
json[r'isMotion'] = this.isMotion;
} else {
// json[r'isMotion'] = null;
}
if (this.isNotInAlbum != null) {
json[r'isNotInAlbum'] = this.isNotInAlbum;
} else {
// json[r'isNotInAlbum'] = null;
}
if (this.isOffline != null) {
json[r'isOffline'] = this.isOffline;
} else {
// json[r'isOffline'] = null;
}
if (this.isReadOnly != null) {
json[r'isReadOnly'] = this.isReadOnly;
} else {
// json[r'isReadOnly'] = null;
}
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.lensModel != null) {
json[r'lensModel'] = this.lensModel;
} else {
// json[r'lensModel'] = null;
}
if (this.libraryId != null) {
json[r'libraryId'] = this.libraryId;
} else {
// json[r'libraryId'] = null;
}
if (this.make != null) {
json[r'make'] = this.make;
} else {
// json[r'make'] = null;
}
if (this.model != null) {
json[r'model'] = this.model;
} else {
// json[r'model'] = null;
}
if (this.order != null) {
json[r'order'] = this.order;
} else {
// json[r'order'] = null;
}
if (this.originalFileName != null) {
json[r'originalFileName'] = this.originalFileName;
} else {
// json[r'originalFileName'] = null;
}
if (this.originalPath != null) {
json[r'originalPath'] = this.originalPath;
} else {
// json[r'originalPath'] = null;
}
if (this.page != null) {
json[r'page'] = this.page;
} else {
// json[r'page'] = null;
}
json[r'personIds'] = this.personIds;
if (this.resizePath != null) {
json[r'resizePath'] = this.resizePath;
} else {
// json[r'resizePath'] = null;
}
if (this.size != null) {
json[r'size'] = this.size;
} else {
// json[r'size'] = null;
}
if (this.state != null) {
json[r'state'] = this.state;
} else {
// json[r'state'] = null;
}
if (this.takenAfter != null) {
json[r'takenAfter'] = this.takenAfter!.toUtc().toIso8601String();
} else {
// json[r'takenAfter'] = null;
}
if (this.takenBefore != null) {
json[r'takenBefore'] = this.takenBefore!.toUtc().toIso8601String();
} else {
// json[r'takenBefore'] = null;
}
if (this.trashedAfter != null) {
json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String();
} else {
// json[r'trashedAfter'] = null;
}
if (this.trashedBefore != null) {
json[r'trashedBefore'] = this.trashedBefore!.toUtc().toIso8601String();
} else {
// json[r'trashedBefore'] = null;
}
if (this.type != null) {
json[r'type'] = this.type;
} else {
// json[r'type'] = null;
}
if (this.updatedAfter != null) {
json[r'updatedAfter'] = this.updatedAfter!.toUtc().toIso8601String();
} else {
// json[r'updatedAfter'] = null;
}
if (this.updatedBefore != null) {
json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String();
} else {
// json[r'updatedBefore'] = null;
}
if (this.webpPath != null) {
json[r'webpPath'] = this.webpPath;
} else {
// json[r'webpPath'] = null;
}
if (this.withArchived != null) {
json[r'withArchived'] = this.withArchived;
} else {
// json[r'withArchived'] = null;
}
if (this.withDeleted != null) {
json[r'withDeleted'] = this.withDeleted;
} else {
// json[r'withDeleted'] = null;
}
if (this.withExif != null) {
json[r'withExif'] = this.withExif;
} else {
// json[r'withExif'] = null;
}
if (this.withPeople != null) {
json[r'withPeople'] = this.withPeople;
} else {
// json[r'withPeople'] = null;
}
if (this.withStacked != null) {
json[r'withStacked'] = this.withStacked;
} else {
// json[r'withStacked'] = null;
}
return json;
}
/// Returns a new [MetadataSearchDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static MetadataSearchDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return MetadataSearchDto(
checksum: mapValueOfType<String>(json, r'checksum'),
city: mapValueOfType<String>(json, r'city'),
country: mapValueOfType<String>(json, r'country'),
createdAfter: mapDateTime(json, r'createdAfter', r''),
createdBefore: mapDateTime(json, r'createdBefore', r''),
deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId'),
deviceId: mapValueOfType<String>(json, r'deviceId'),
encodedVideoPath: mapValueOfType<String>(json, r'encodedVideoPath'),
id: mapValueOfType<String>(json, r'id'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isExternal: mapValueOfType<bool>(json, r'isExternal'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'),
isNotInAlbum: mapValueOfType<bool>(json, r'isNotInAlbum'),
isOffline: mapValueOfType<bool>(json, r'isOffline'),
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
model: mapValueOfType<String>(json, r'model'),
order: AssetOrder.fromJson(json[r'order']),
originalFileName: mapValueOfType<String>(json, r'originalFileName'),
originalPath: mapValueOfType<String>(json, r'originalPath'),
page: num.parse('${json[r'page']}'),
personIds: json[r'personIds'] is Iterable
? (json[r'personIds'] as Iterable).cast<String>().toList(growable: false)
: const [],
resizePath: mapValueOfType<String>(json, r'resizePath'),
size: num.parse('${json[r'size']}'),
state: mapValueOfType<String>(json, r'state'),
takenAfter: mapDateTime(json, r'takenAfter', r''),
takenBefore: mapDateTime(json, r'takenBefore', r''),
trashedAfter: mapDateTime(json, r'trashedAfter', r''),
trashedBefore: mapDateTime(json, r'trashedBefore', r''),
type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', r''),
updatedBefore: mapDateTime(json, r'updatedBefore', r''),
webpPath: mapValueOfType<String>(json, r'webpPath'),
withArchived: mapValueOfType<bool>(json, r'withArchived'),
withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
withExif: mapValueOfType<bool>(json, r'withExif'),
withPeople: mapValueOfType<bool>(json, r'withPeople'),
withStacked: mapValueOfType<bool>(json, r'withStacked'),
);
}
return null;
}
static List<MetadataSearchDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <MetadataSearchDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = MetadataSearchDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, MetadataSearchDto> mapFromJson(dynamic json) {
final map = <String, MetadataSearchDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = MetadataSearchDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of MetadataSearchDto-objects as value to a dart map
static Map<String, List<MetadataSearchDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<MetadataSearchDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = MetadataSearchDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
};
}
+94
View File
@@ -0,0 +1,94 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SearchSuggestionType {
/// Instantiate a new enum with the provided [value].
const SearchSuggestionType._(this.value);
/// The underlying value of this enum member.
final String value;
@override
String toString() => value;
String toJson() => value;
static const country = SearchSuggestionType._(r'country');
static const state = SearchSuggestionType._(r'state');
static const city = SearchSuggestionType._(r'city');
static const cameraMake = SearchSuggestionType._(r'camera-make');
static const cameraModel = SearchSuggestionType._(r'camera-model');
/// List of all possible values in this [enum][SearchSuggestionType].
static const values = <SearchSuggestionType>[
country,
state,
city,
cameraMake,
cameraModel,
];
static SearchSuggestionType? fromJson(dynamic value) => SearchSuggestionTypeTypeTransformer().decode(value);
static List<SearchSuggestionType> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SearchSuggestionType>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SearchSuggestionType.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
}
/// Transformation class that can [encode] an instance of [SearchSuggestionType] to String,
/// and [decode] dynamic data back to [SearchSuggestionType].
class SearchSuggestionTypeTypeTransformer {
factory SearchSuggestionTypeTypeTransformer() => _instance ??= const SearchSuggestionTypeTypeTransformer._();
const SearchSuggestionTypeTypeTransformer._();
String encode(SearchSuggestionType data) => data.value;
/// Decodes a [dynamic value][data] to a SearchSuggestionType.
///
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
///
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
/// and users are still using an old app with the old code.
SearchSuggestionType? decode(dynamic data, {bool allowNull = true}) {
if (data != null) {
switch (data) {
case r'country': return SearchSuggestionType.country;
case r'state': return SearchSuggestionType.state;
case r'city': return SearchSuggestionType.city;
case r'camera-make': return SearchSuggestionType.cameraMake;
case r'camera-model': return SearchSuggestionType.cameraModel;
default:
if (!allowNull) {
throw ArgumentError('Unknown enum value to decode: $data');
}
}
}
return null;
}
/// Singleton [SearchSuggestionTypeTypeTransformer] instance.
static SearchSuggestionTypeTypeTransformer? _instance;
}
+608
View File
@@ -0,0 +1,608 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class SmartSearchDto {
/// Returns a new [SmartSearchDto] instance.
SmartSearchDto({
this.city,
this.country,
this.createdAfter,
this.createdBefore,
this.deviceId,
this.isArchived,
this.isEncoded,
this.isExternal,
this.isFavorite,
this.isMotion,
this.isOffline,
this.isReadOnly,
this.isVisible,
this.lensModel,
this.libraryId,
this.make,
this.model,
this.page,
required this.query,
this.size,
this.state,
this.takenAfter,
this.takenBefore,
this.trashedAfter,
this.trashedBefore,
this.type,
this.updatedAfter,
this.updatedBefore,
this.withArchived,
this.withDeleted,
this.withExif,
});
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? city;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? country;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? createdAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? createdBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? deviceId;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isArchived;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isEncoded;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isExternal;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isFavorite;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isMotion;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isOffline;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isReadOnly;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? isVisible;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? lensModel;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? libraryId;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? make;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? model;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? page;
String query;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
num? size;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? state;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? takenAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? takenBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? trashedAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? trashedBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
AssetTypeEnum? type;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? updatedAfter;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
DateTime? updatedBefore;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withArchived;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withDeleted;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? withExif;
@override
bool operator ==(Object other) => identical(this, other) || other is SmartSearchDto &&
other.city == city &&
other.country == country &&
other.createdAfter == createdAfter &&
other.createdBefore == createdBefore &&
other.deviceId == deviceId &&
other.isArchived == isArchived &&
other.isEncoded == isEncoded &&
other.isExternal == isExternal &&
other.isFavorite == isFavorite &&
other.isMotion == isMotion &&
other.isOffline == isOffline &&
other.isReadOnly == isReadOnly &&
other.isVisible == isVisible &&
other.lensModel == lensModel &&
other.libraryId == libraryId &&
other.make == make &&
other.model == model &&
other.page == page &&
other.query == query &&
other.size == size &&
other.state == state &&
other.takenAfter == takenAfter &&
other.takenBefore == takenBefore &&
other.trashedAfter == trashedAfter &&
other.trashedBefore == trashedBefore &&
other.type == type &&
other.updatedAfter == updatedAfter &&
other.updatedBefore == updatedBefore &&
other.withArchived == withArchived &&
other.withDeleted == withDeleted &&
other.withExif == withExif;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(city == null ? 0 : city!.hashCode) +
(country == null ? 0 : country!.hashCode) +
(createdAfter == null ? 0 : createdAfter!.hashCode) +
(createdBefore == null ? 0 : createdBefore!.hashCode) +
(deviceId == null ? 0 : deviceId!.hashCode) +
(isArchived == null ? 0 : isArchived!.hashCode) +
(isEncoded == null ? 0 : isEncoded!.hashCode) +
(isExternal == null ? 0 : isExternal!.hashCode) +
(isFavorite == null ? 0 : isFavorite!.hashCode) +
(isMotion == null ? 0 : isMotion!.hashCode) +
(isOffline == null ? 0 : isOffline!.hashCode) +
(isReadOnly == null ? 0 : isReadOnly!.hashCode) +
(isVisible == null ? 0 : isVisible!.hashCode) +
(lensModel == null ? 0 : lensModel!.hashCode) +
(libraryId == null ? 0 : libraryId!.hashCode) +
(make == null ? 0 : make!.hashCode) +
(model == null ? 0 : model!.hashCode) +
(page == null ? 0 : page!.hashCode) +
(query.hashCode) +
(size == null ? 0 : size!.hashCode) +
(state == null ? 0 : state!.hashCode) +
(takenAfter == null ? 0 : takenAfter!.hashCode) +
(takenBefore == null ? 0 : takenBefore!.hashCode) +
(trashedAfter == null ? 0 : trashedAfter!.hashCode) +
(trashedBefore == null ? 0 : trashedBefore!.hashCode) +
(type == null ? 0 : type!.hashCode) +
(updatedAfter == null ? 0 : updatedAfter!.hashCode) +
(updatedBefore == null ? 0 : updatedBefore!.hashCode) +
(withArchived == null ? 0 : withArchived!.hashCode) +
(withDeleted == null ? 0 : withDeleted!.hashCode) +
(withExif == null ? 0 : withExif!.hashCode);
@override
String toString() => 'SmartSearchDto[city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, deviceId=$deviceId, isArchived=$isArchived, isEncoded=$isEncoded, isExternal=$isExternal, isFavorite=$isFavorite, isMotion=$isMotion, isOffline=$isOffline, isReadOnly=$isReadOnly, isVisible=$isVisible, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, page=$page, query=$query, size=$size, state=$state, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, withArchived=$withArchived, withDeleted=$withDeleted, withExif=$withExif]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.city != null) {
json[r'city'] = this.city;
} else {
// json[r'city'] = null;
}
if (this.country != null) {
json[r'country'] = this.country;
} else {
// json[r'country'] = null;
}
if (this.createdAfter != null) {
json[r'createdAfter'] = this.createdAfter!.toUtc().toIso8601String();
} else {
// json[r'createdAfter'] = null;
}
if (this.createdBefore != null) {
json[r'createdBefore'] = this.createdBefore!.toUtc().toIso8601String();
} else {
// json[r'createdBefore'] = null;
}
if (this.deviceId != null) {
json[r'deviceId'] = this.deviceId;
} else {
// json[r'deviceId'] = null;
}
if (this.isArchived != null) {
json[r'isArchived'] = this.isArchived;
} else {
// json[r'isArchived'] = null;
}
if (this.isEncoded != null) {
json[r'isEncoded'] = this.isEncoded;
} else {
// json[r'isEncoded'] = null;
}
if (this.isExternal != null) {
json[r'isExternal'] = this.isExternal;
} else {
// json[r'isExternal'] = null;
}
if (this.isFavorite != null) {
json[r'isFavorite'] = this.isFavorite;
} else {
// json[r'isFavorite'] = null;
}
if (this.isMotion != null) {
json[r'isMotion'] = this.isMotion;
} else {
// json[r'isMotion'] = null;
}
if (this.isOffline != null) {
json[r'isOffline'] = this.isOffline;
} else {
// json[r'isOffline'] = null;
}
if (this.isReadOnly != null) {
json[r'isReadOnly'] = this.isReadOnly;
} else {
// json[r'isReadOnly'] = null;
}
if (this.isVisible != null) {
json[r'isVisible'] = this.isVisible;
} else {
// json[r'isVisible'] = null;
}
if (this.lensModel != null) {
json[r'lensModel'] = this.lensModel;
} else {
// json[r'lensModel'] = null;
}
if (this.libraryId != null) {
json[r'libraryId'] = this.libraryId;
} else {
// json[r'libraryId'] = null;
}
if (this.make != null) {
json[r'make'] = this.make;
} else {
// json[r'make'] = null;
}
if (this.model != null) {
json[r'model'] = this.model;
} else {
// json[r'model'] = null;
}
if (this.page != null) {
json[r'page'] = this.page;
} else {
// json[r'page'] = null;
}
json[r'query'] = this.query;
if (this.size != null) {
json[r'size'] = this.size;
} else {
// json[r'size'] = null;
}
if (this.state != null) {
json[r'state'] = this.state;
} else {
// json[r'state'] = null;
}
if (this.takenAfter != null) {
json[r'takenAfter'] = this.takenAfter!.toUtc().toIso8601String();
} else {
// json[r'takenAfter'] = null;
}
if (this.takenBefore != null) {
json[r'takenBefore'] = this.takenBefore!.toUtc().toIso8601String();
} else {
// json[r'takenBefore'] = null;
}
if (this.trashedAfter != null) {
json[r'trashedAfter'] = this.trashedAfter!.toUtc().toIso8601String();
} else {
// json[r'trashedAfter'] = null;
}
if (this.trashedBefore != null) {
json[r'trashedBefore'] = this.trashedBefore!.toUtc().toIso8601String();
} else {
// json[r'trashedBefore'] = null;
}
if (this.type != null) {
json[r'type'] = this.type;
} else {
// json[r'type'] = null;
}
if (this.updatedAfter != null) {
json[r'updatedAfter'] = this.updatedAfter!.toUtc().toIso8601String();
} else {
// json[r'updatedAfter'] = null;
}
if (this.updatedBefore != null) {
json[r'updatedBefore'] = this.updatedBefore!.toUtc().toIso8601String();
} else {
// json[r'updatedBefore'] = null;
}
if (this.withArchived != null) {
json[r'withArchived'] = this.withArchived;
} else {
// json[r'withArchived'] = null;
}
if (this.withDeleted != null) {
json[r'withDeleted'] = this.withDeleted;
} else {
// json[r'withDeleted'] = null;
}
if (this.withExif != null) {
json[r'withExif'] = this.withExif;
} else {
// json[r'withExif'] = null;
}
return json;
}
/// Returns a new [SmartSearchDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static SmartSearchDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
return SmartSearchDto(
city: mapValueOfType<String>(json, r'city'),
country: mapValueOfType<String>(json, r'country'),
createdAfter: mapDateTime(json, r'createdAfter', r''),
createdBefore: mapDateTime(json, r'createdBefore', r''),
deviceId: mapValueOfType<String>(json, r'deviceId'),
isArchived: mapValueOfType<bool>(json, r'isArchived'),
isEncoded: mapValueOfType<bool>(json, r'isEncoded'),
isExternal: mapValueOfType<bool>(json, r'isExternal'),
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
isMotion: mapValueOfType<bool>(json, r'isMotion'),
isOffline: mapValueOfType<bool>(json, r'isOffline'),
isReadOnly: mapValueOfType<bool>(json, r'isReadOnly'),
isVisible: mapValueOfType<bool>(json, r'isVisible'),
lensModel: mapValueOfType<String>(json, r'lensModel'),
libraryId: mapValueOfType<String>(json, r'libraryId'),
make: mapValueOfType<String>(json, r'make'),
model: mapValueOfType<String>(json, r'model'),
page: num.parse('${json[r'page']}'),
query: mapValueOfType<String>(json, r'query')!,
size: num.parse('${json[r'size']}'),
state: mapValueOfType<String>(json, r'state'),
takenAfter: mapDateTime(json, r'takenAfter', r''),
takenBefore: mapDateTime(json, r'takenBefore', r''),
trashedAfter: mapDateTime(json, r'trashedAfter', r''),
trashedBefore: mapDateTime(json, r'trashedBefore', r''),
type: AssetTypeEnum.fromJson(json[r'type']),
updatedAfter: mapDateTime(json, r'updatedAfter', r''),
updatedBefore: mapDateTime(json, r'updatedBefore', r''),
withArchived: mapValueOfType<bool>(json, r'withArchived'),
withDeleted: mapValueOfType<bool>(json, r'withDeleted'),
withExif: mapValueOfType<bool>(json, r'withExif'),
);
}
return null;
}
static List<SmartSearchDto> listFromJson(dynamic json, {bool growable = false,}) {
final result = <SmartSearchDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = SmartSearchDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, SmartSearchDto> mapFromJson(dynamic json) {
final map = <String, SmartSearchDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = SmartSearchDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of SmartSearchDto-objects as value to a dart map
static Map<String, List<SmartSearchDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<SmartSearchDto>>{};
if (json is Map && json.isNotEmpty) {
// ignore: parameter_assignments
json = json.cast<String, dynamic>();
for (final entry in json.entries) {
map[entry.key] = SmartSearchDto.listFromJson(entry.value, growable: growable,);
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'query',
};
}
+2 -2
View File
@@ -80,7 +80,7 @@ void main() {
// TODO
});
//Future<List<MapMarkerResponseDto>> getMapMarkers({ DateTime fileCreatedAfter, DateTime fileCreatedBefore, bool isArchived, bool isFavorite }) async
//Future<List<MapMarkerResponseDto>> getMapMarkers({ DateTime fileCreatedAfter, DateTime fileCreatedBefore, bool isArchived, bool isFavorite, bool withPartners }) async
test('test getMapMarkers', () async {
// TODO
});
@@ -110,7 +110,7 @@ void main() {
// TODO
});
//Future<List<AssetResponseDto>> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async
//Future<List<AssetResponseDto>> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isNotInAlbum, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, List<String> personIds, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async
test('test searchAssets', () async {
// TODO
});
+237
View File
@@ -0,0 +1,237 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for MetadataSearchDto
void main() {
// final instance = MetadataSearchDto();
group('test MetadataSearchDto', () {
// String checksum
test('to test the property `checksum`', () async {
// TODO
});
// String city
test('to test the property `city`', () async {
// TODO
});
// String country
test('to test the property `country`', () async {
// TODO
});
// DateTime createdAfter
test('to test the property `createdAfter`', () async {
// TODO
});
// DateTime createdBefore
test('to test the property `createdBefore`', () async {
// TODO
});
// String deviceAssetId
test('to test the property `deviceAssetId`', () async {
// TODO
});
// String deviceId
test('to test the property `deviceId`', () async {
// TODO
});
// String encodedVideoPath
test('to test the property `encodedVideoPath`', () async {
// TODO
});
// String id
test('to test the property `id`', () async {
// TODO
});
// bool isArchived
test('to test the property `isArchived`', () async {
// TODO
});
// bool isEncoded
test('to test the property `isEncoded`', () async {
// TODO
});
// bool isExternal
test('to test the property `isExternal`', () async {
// TODO
});
// bool isFavorite
test('to test the property `isFavorite`', () async {
// TODO
});
// bool isMotion
test('to test the property `isMotion`', () async {
// TODO
});
// bool isNotInAlbum
test('to test the property `isNotInAlbum`', () async {
// TODO
});
// bool isOffline
test('to test the property `isOffline`', () async {
// TODO
});
// bool isReadOnly
test('to test the property `isReadOnly`', () async {
// TODO
});
// bool isVisible
test('to test the property `isVisible`', () async {
// TODO
});
// String lensModel
test('to test the property `lensModel`', () async {
// TODO
});
// String libraryId
test('to test the property `libraryId`', () async {
// TODO
});
// String make
test('to test the property `make`', () async {
// TODO
});
// String model
test('to test the property `model`', () async {
// TODO
});
// AssetOrder order
test('to test the property `order`', () async {
// TODO
});
// String originalFileName
test('to test the property `originalFileName`', () async {
// TODO
});
// String originalPath
test('to test the property `originalPath`', () async {
// TODO
});
// num page
test('to test the property `page`', () async {
// TODO
});
// List<String> personIds (default value: const [])
test('to test the property `personIds`', () async {
// TODO
});
// String resizePath
test('to test the property `resizePath`', () async {
// TODO
});
// num size
test('to test the property `size`', () async {
// TODO
});
// String state
test('to test the property `state`', () async {
// TODO
});
// DateTime takenAfter
test('to test the property `takenAfter`', () async {
// TODO
});
// DateTime takenBefore
test('to test the property `takenBefore`', () async {
// TODO
});
// DateTime trashedAfter
test('to test the property `trashedAfter`', () async {
// TODO
});
// DateTime trashedBefore
test('to test the property `trashedBefore`', () async {
// TODO
});
// AssetTypeEnum type
test('to test the property `type`', () async {
// TODO
});
// DateTime updatedAfter
test('to test the property `updatedAfter`', () async {
// TODO
});
// DateTime updatedBefore
test('to test the property `updatedBefore`', () async {
// TODO
});
// String webpPath
test('to test the property `webpPath`', () async {
// TODO
});
// bool withArchived
test('to test the property `withArchived`', () async {
// TODO
});
// bool withDeleted
test('to test the property `withDeleted`', () async {
// TODO
});
// bool withExif
test('to test the property `withExif`', () async {
// TODO
});
// bool withPeople
test('to test the property `withPeople`', () async {
// TODO
});
// bool withStacked
test('to test the property `withStacked`', () async {
// TODO
});
});
}
+7 -2
View File
@@ -22,12 +22,17 @@ void main() {
// TODO
});
//Future<List<String>> getSearchSuggestions(SearchSuggestionType type, { String country, String make, String model, String state }) async
test('test getSearchSuggestions', () async {
// TODO
});
//Future<SearchResponseDto> search({ bool clip, bool motion, num page, String q, String query, bool recent, num size, bool smart, String type, bool withArchived }) async
test('test search', () async {
// TODO
});
//Future<SearchResponseDto> searchMetadata({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async
//Future<SearchResponseDto> searchMetadata(MetadataSearchDto metadataSearchDto) async
test('test searchMetadata', () async {
// TODO
});
@@ -37,7 +42,7 @@ void main() {
// TODO
});
//Future<SearchResponseDto> searchSmart(String query, { String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceId, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, num page, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, bool withArchived, bool withDeleted, bool withExif }) async
//Future<SearchResponseDto> searchSmart(SmartSearchDto smartSearchDto) async
test('test searchSmart', () async {
// TODO
});
+21
View File
@@ -0,0 +1,21 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for SearchSuggestionType
void main() {
group('test SearchSuggestionType', () {
});
}
+177
View File
@@ -0,0 +1,177 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for SmartSearchDto
void main() {
// final instance = SmartSearchDto();
group('test SmartSearchDto', () {
// String city
test('to test the property `city`', () async {
// TODO
});
// String country
test('to test the property `country`', () async {
// TODO
});
// DateTime createdAfter
test('to test the property `createdAfter`', () async {
// TODO
});
// DateTime createdBefore
test('to test the property `createdBefore`', () async {
// TODO
});
// String deviceId
test('to test the property `deviceId`', () async {
// TODO
});
// bool isArchived
test('to test the property `isArchived`', () async {
// TODO
});
// bool isEncoded
test('to test the property `isEncoded`', () async {
// TODO
});
// bool isExternal
test('to test the property `isExternal`', () async {
// TODO
});
// bool isFavorite
test('to test the property `isFavorite`', () async {
// TODO
});
// bool isMotion
test('to test the property `isMotion`', () async {
// TODO
});
// bool isOffline
test('to test the property `isOffline`', () async {
// TODO
});
// bool isReadOnly
test('to test the property `isReadOnly`', () async {
// TODO
});
// bool isVisible
test('to test the property `isVisible`', () async {
// TODO
});
// String lensModel
test('to test the property `lensModel`', () async {
// TODO
});
// String libraryId
test('to test the property `libraryId`', () async {
// TODO
});
// String make
test('to test the property `make`', () async {
// TODO
});
// String model
test('to test the property `model`', () async {
// TODO
});
// num page
test('to test the property `page`', () async {
// TODO
});
// String query
test('to test the property `query`', () async {
// TODO
});
// num size
test('to test the property `size`', () async {
// TODO
});
// String state
test('to test the property `state`', () async {
// TODO
});
// DateTime takenAfter
test('to test the property `takenAfter`', () async {
// TODO
});
// DateTime takenBefore
test('to test the property `takenBefore`', () async {
// TODO
});
// DateTime trashedAfter
test('to test the property `trashedAfter`', () async {
// TODO
});
// DateTime trashedBefore
test('to test the property `trashedBefore`', () async {
// TODO
});
// AssetTypeEnum type
test('to test the property `type`', () async {
// TODO
});
// DateTime updatedAfter
test('to test the property `updatedAfter`', () async {
// TODO
});
// DateTime updatedBefore
test('to test the property `updatedBefore`', () async {
// TODO
});
// bool withArchived
test('to test the property `withArchived`', () async {
// TODO
});
// bool withDeleted
test('to test the property `withDeleted`', () async {
// TODO
});
// bool withExif
test('to test the property `withExif`', () async {
// TODO
});
});
}
+1 -1
View File
@@ -960,7 +960,7 @@ packages:
source: hosted
version: "0.5.0"
octo_image:
dependency: transitive
dependency: "direct main"
description:
name: octo_image
sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d"
+1
View File
@@ -56,6 +56,7 @@ dependencies:
wakelock_plus: ^1.1.4
flutter_local_notifications: ^16.3.2
timezone: ^0.9.2
octo_image: ^2.0.0
openapi:
path: openapi
+1 -1
View File
@@ -19,7 +19,7 @@ function dart {
function typescript {
rm -rf ./typescript-sdk/client
npx --yes @openapitools/openapi-generator-cli generate -g typescript-axios -i ./immich-openapi-specs.json -o ./typescript-sdk/axios-client --additional-properties=useSingleRequestParameter=true,supportsES6=true
npx --yes oazapfts --optimistic --argumentStyle=object immich-openapi-specs.json typescript-sdk/fetch-client.ts
npx --yes oazapfts --optimistic --argumentStyle=object --useEnumType immich-openapi-specs.json typescript-sdk/fetch-client.ts
npm --prefix typescript-sdk ci && npm --prefix typescript-sdk run build
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+306 -185
View File
@@ -4,8 +4,8 @@
* DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts
*/
import * as Oazapfts from "oazapfts/lib/runtime";
import * as QS from "oazapfts/lib/runtime/query";
import * as Oazapfts from "@oazapfts/runtime";
import * as QS from "@oazapfts/runtime/query";
export const defaults: Oazapfts.Defaults<Oazapfts.CustomHeaders> = {
headers: {},
baseUrl: "/api",
@@ -14,9 +14,6 @@ const oazapfts = Oazapfts.runtime(defaults);
export const servers = {
server1: "/api"
};
export type ReactionLevel = "album" | "asset";
export type ReactionType = "comment" | "like";
export type UserAvatarColor = "primary" | "pink" | "red" | "yellow" | "blue" | "green" | "purple" | "orange" | "gray" | "amber";
export type UserDto = {
avatarColor: UserAvatarColor;
email: string;
@@ -29,7 +26,7 @@ export type ActivityResponseDto = {
comment?: string | null;
createdAt: string;
id: string;
"type": "comment" | "like";
"type": Type;
user: UserDto;
};
export type ActivityCreateDto = {
@@ -103,14 +100,12 @@ export type SmartInfoResponseDto = {
objects?: string[] | null;
tags?: string[] | null;
};
export type TagTypeEnum = "OBJECT" | "FACE" | "CUSTOM";
export type TagResponseDto = {
id: string;
name: string;
"type": TagTypeEnum;
userId: string;
};
export type AssetTypeEnum = "IMAGE" | "VIDEO" | "AUDIO" | "OTHER";
export type AssetResponseDto = {
/** base64 encoded sha1 hash */
checksum: string;
@@ -186,7 +181,7 @@ export type BulkIdsDto = {
ids: string[];
};
export type BulkIdResponseDto = {
error?: "duplicate" | "no_permission" | "not_found" | "unknown";
error?: Error;
id: string;
success: boolean;
};
@@ -232,10 +227,10 @@ export type AssetBulkUploadCheckDto = {
assets: AssetBulkUploadCheckItem[];
};
export type AssetBulkUploadCheckResult = {
action: "accept" | "reject";
action: Action;
assetId?: string;
id: string;
reason?: "duplicate" | "unsupported-format";
reason?: Reason;
};
export type AssetBulkUploadCheckResponseDto = {
results: AssetBulkUploadCheckResult[];
@@ -261,7 +256,6 @@ export type CheckExistingAssetsDto = {
export type CheckExistingAssetsResponseDto = {
existingIds: string[];
};
export type AssetJobName = "regenerate-thumbnail" | "refresh-metadata" | "transcode-video";
export type AssetJobsDto = {
assetIds: string[];
name: AssetJobName;
@@ -284,8 +278,6 @@ export type AssetStatsResponseDto = {
total: number;
videos: number;
};
export type ThumbnailFormat = "JPEG" | "WEBP";
export type TimeBucketSize = "DAY" | "MONTH";
export type TimeBucketResponseDto = {
count: number;
timeBucket: string;
@@ -319,14 +311,10 @@ export type UpdateAssetDto = {
latitude?: number;
longitude?: number;
};
export type AssetOrder = "asc" | "desc";
export type EntityType = "ASSET" | "ALBUM";
export type AuditDeletesResponseDto = {
ids: string[];
needsFullSync: boolean;
};
export type PathEntityType = "asset" | "person" | "user";
export type PathType = "original" | "jpeg_thumbnail" | "webp_thumbnail" | "encoded_video" | "sidecar" | "face" | "profile";
export type FileReportItemDto = {
checksum?: string;
entityId: string;
@@ -452,13 +440,10 @@ export type AllJobStatusResponseDto = {
thumbnailGeneration: JobStatusDto;
videoConversion: JobStatusDto;
};
export type JobName = "thumbnailGeneration" | "metadataExtraction" | "videoConversion" | "faceDetection" | "facialRecognition" | "smartSearch" | "backgroundTask" | "storageTemplateMigration" | "migration" | "search" | "sidecar" | "library";
export type JobCommand = "start" | "pause" | "resume" | "empty" | "clear-failed";
export type JobCommandDto = {
command: JobCommand;
force: boolean;
};
export type LibraryType = "UPLOAD" | "EXTERNAL";
export type LibraryResponseDto = {
assetCount: number;
createdAt: string;
@@ -603,6 +588,84 @@ export type SearchExploreResponseDto = {
fieldName: string;
items: SearchExploreItem[];
};
export type MetadataSearchDto = {
checksum?: string;
city?: string;
country?: string;
createdAfter?: string;
createdBefore?: string;
deviceAssetId?: string;
deviceId?: string;
encodedVideoPath?: string;
id?: string;
isArchived?: boolean;
isEncoded?: boolean;
isExternal?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isNotInAlbum?: boolean;
isOffline?: boolean;
isReadOnly?: boolean;
isVisible?: boolean;
lensModel?: string;
libraryId?: string;
make?: string;
model?: string;
order?: AssetOrder;
originalFileName?: string;
originalPath?: string;
page?: number;
personIds?: string[];
resizePath?: string;
size?: number;
state?: string;
takenAfter?: string;
takenBefore?: string;
trashedAfter?: string;
trashedBefore?: string;
"type"?: AssetTypeEnum;
updatedAfter?: string;
updatedBefore?: string;
webpPath?: string;
withArchived?: boolean;
withDeleted?: boolean;
withExif?: boolean;
withPeople?: boolean;
withStacked?: boolean;
};
export type SmartSearchDto = {
city?: string;
country?: string;
createdAfter?: string;
createdBefore?: string;
deviceId?: string;
isArchived?: boolean;
isEncoded?: boolean;
isExternal?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isOffline?: boolean;
isReadOnly?: boolean;
isVisible?: boolean;
lensModel?: string;
libraryId?: string;
make?: string;
model?: string;
page?: number;
query: string;
size?: number;
state?: string;
takenAfter?: string;
takenBefore?: string;
trashedAfter?: string;
trashedBefore?: string;
"type"?: AssetTypeEnum;
updatedAfter?: string;
updatedBefore?: string;
withArchived?: boolean;
withDeleted?: boolean;
withExif?: boolean;
};
export type ServerInfoResponseDto = {
diskAvailable: string;
diskAvailableRaw: number;
@@ -664,7 +727,6 @@ export type ServerVersionResponseDto = {
minor: number;
patch: number;
};
export type SharedLinkType = "ALBUM" | "INDIVIDUAL";
export type SharedLinkResponseDto = {
album?: AlbumResponseDto;
allowDownload: boolean;
@@ -706,21 +768,15 @@ export type SharedLinkEditDto = {
};
export type AssetIdsResponseDto = {
assetId: string;
error?: "duplicate" | "no_permission" | "not_found";
error?: Error2;
success: boolean;
};
export type TranscodeHwAccel = "nvenc" | "qsv" | "vaapi" | "rkmpp" | "disabled";
export type AudioCodec = "mp3" | "aac" | "libopus";
export type VideoCodec = "h264" | "hevc" | "vp9";
export type CqMode = "auto" | "cqp" | "icq";
export type ToneMapping = "hable" | "mobius" | "reinhard" | "disabled";
export type TranscodePolicy = "all" | "optimal" | "bitrate" | "required" | "disabled";
export type SystemConfigFFmpegDto = {
accel: TranscodeHwAccel;
accel: TranscodeHWAccel;
acceptedAudioCodecs: AudioCodec[];
acceptedVideoCodecs: VideoCodec[];
bframes: number;
cqMode: CqMode;
cqMode: CQMode;
crf: number;
gopSize: number;
maxBitrate: string;
@@ -765,16 +821,13 @@ export type SystemConfigLibraryDto = {
scan: SystemConfigLibraryScanDto;
watch: SystemConfigLibraryWatchDto;
};
export type LogLevel = "verbose" | "debug" | "log" | "warn" | "error" | "fatal";
export type SystemConfigLoggingDto = {
enabled: boolean;
level: LogLevel;
};
export type ClipMode = "vision" | "text";
export type ModelType = "facial-recognition" | "clip";
export type ClipConfig = {
enabled: boolean;
mode?: ClipMode;
mode?: CLIPMode;
modelName: string;
modelType?: ModelType;
};
@@ -832,7 +885,6 @@ export type SystemConfigStorageTemplateDto = {
export type SystemConfigThemeDto = {
customCss: string;
};
export type Colorspace = "srgb" | "p3";
export type SystemConfigThumbnailDto = {
colorspace: Colorspace;
jpegSize: number;
@@ -860,7 +912,6 @@ export type SystemConfigDto = {
thumbnail: SystemConfigThumbnailDto;
trash: SystemConfigTrashDto;
};
export type MapTheme = "light" | "dark";
export type SystemConfigTemplateStorageOptionDto = {
dayOptions: string[];
hourOptions: string[];
@@ -1267,11 +1318,12 @@ export function runAssetJobs({ assetJobsDto }: {
body: assetJobsDto
})));
}
export function getMapMarkers({ fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite }: {
export function getMapMarkers({ fileCreatedAfter, fileCreatedBefore, isArchived, isFavorite, withPartners }: {
fileCreatedAfter?: string;
fileCreatedBefore?: string;
isArchived?: boolean;
isFavorite?: boolean;
withPartners?: boolean;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
@@ -1280,7 +1332,8 @@ export function getMapMarkers({ fileCreatedAfter, fileCreatedBefore, isArchived,
fileCreatedAfter,
fileCreatedBefore,
isArchived,
isFavorite
isFavorite,
withPartners
}))}`, {
...opts
}));
@@ -1462,7 +1515,7 @@ export function updateAsset({ id, updateAssetDto }: {
body: updateAssetDto
})));
}
export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: {
export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isNotInAlbum, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, personIds, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: {
checksum?: string;
city?: string;
country?: string;
@@ -1477,6 +1530,7 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef
isExternal?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isNotInAlbum?: boolean;
isOffline?: boolean;
isReadOnly?: boolean;
isVisible?: boolean;
@@ -1488,6 +1542,7 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef
originalFileName?: string;
originalPath?: string;
page?: number;
personIds?: string[];
resizePath?: string;
size?: number;
state?: string;
@@ -1523,6 +1578,7 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef
isExternal,
isFavorite,
isMotion,
isNotInAlbum,
isOffline,
isReadOnly,
isVisible,
@@ -1534,6 +1590,7 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef
originalFileName,
originalPath,
page,
personIds,
resizePath,
size,
state,
@@ -2088,97 +2145,17 @@ export function getExploreData(opts?: Oazapfts.RequestOpts) {
...opts
}));
}
export function searchMetadata({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: {
checksum?: string;
city?: string;
country?: string;
createdAfter?: string;
createdBefore?: string;
deviceAssetId?: string;
deviceId?: string;
encodedVideoPath?: string;
id?: string;
isArchived?: boolean;
isEncoded?: boolean;
isExternal?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isOffline?: boolean;
isReadOnly?: boolean;
isVisible?: boolean;
lensModel?: string;
libraryId?: string;
make?: string;
model?: string;
order?: AssetOrder;
originalFileName?: string;
originalPath?: string;
page?: number;
resizePath?: string;
size?: number;
state?: string;
takenAfter?: string;
takenBefore?: string;
trashedAfter?: string;
trashedBefore?: string;
$type?: AssetTypeEnum;
updatedAfter?: string;
updatedBefore?: string;
webpPath?: string;
withArchived?: boolean;
withDeleted?: boolean;
withExif?: boolean;
withPeople?: boolean;
withStacked?: boolean;
export function searchMetadata({ metadataSearchDto }: {
metadataSearchDto: MetadataSearchDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
status: 201;
data: SearchResponseDto;
}>(`/search/metadata${QS.query(QS.explode({
checksum,
city,
country,
createdAfter,
createdBefore,
deviceAssetId,
deviceId,
encodedVideoPath,
id,
isArchived,
isEncoded,
isExternal,
isFavorite,
isMotion,
isOffline,
isReadOnly,
isVisible,
lensModel,
libraryId,
make,
model,
order,
originalFileName,
originalPath,
page,
resizePath,
size,
state,
takenAfter,
takenBefore,
trashedAfter,
trashedBefore,
"type": $type,
updatedAfter,
updatedBefore,
webpPath,
withArchived,
withDeleted,
withExif,
withPeople,
withStacked
}))}`, {
...opts
}));
}>("/search/metadata", oazapfts.json({
...opts,
method: "POST",
body: metadataSearchDto
})));
}
export function searchPerson({ name, withHidden }: {
name: string;
@@ -2194,74 +2171,34 @@ export function searchPerson({ name, withHidden }: {
...opts
}));
}
export function searchSmart({ city, country, createdAfter, createdBefore, deviceId, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, page, query, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif }: {
city?: string;
export function searchSmart({ smartSearchDto }: {
smartSearchDto: SmartSearchDto;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 201;
data: SearchResponseDto;
}>("/search/smart", oazapfts.json({
...opts,
method: "POST",
body: smartSearchDto
})));
}
export function getSearchSuggestions({ country, make, model, state, $type }: {
country?: string;
createdAfter?: string;
createdBefore?: string;
deviceId?: string;
isArchived?: boolean;
isEncoded?: boolean;
isExternal?: boolean;
isFavorite?: boolean;
isMotion?: boolean;
isOffline?: boolean;
isReadOnly?: boolean;
isVisible?: boolean;
lensModel?: string;
libraryId?: string;
make?: string;
model?: string;
page?: number;
query: string;
size?: number;
state?: string;
takenAfter?: string;
takenBefore?: string;
trashedAfter?: string;
trashedBefore?: string;
$type?: AssetTypeEnum;
updatedAfter?: string;
updatedBefore?: string;
withArchived?: boolean;
withDeleted?: boolean;
withExif?: boolean;
$type: SearchSuggestionType;
}, opts?: Oazapfts.RequestOpts) {
return oazapfts.ok(oazapfts.fetchJson<{
status: 200;
data: SearchResponseDto;
}>(`/search/smart${QS.query(QS.explode({
city,
data: string[];
}>(`/search/suggestions${QS.query(QS.explode({
country,
createdAfter,
createdBefore,
deviceId,
isArchived,
isEncoded,
isExternal,
isFavorite,
isMotion,
isOffline,
isReadOnly,
isVisible,
lensModel,
libraryId,
make,
model,
page,
query,
size,
state,
takenAfter,
takenBefore,
trashedAfter,
trashedBefore,
"type": $type,
updatedAfter,
updatedBefore,
withArchived,
withDeleted,
withExif
"type": $type
}))}`, {
...opts
}));
@@ -2695,3 +2632,187 @@ export function restoreUser({ id }: {
method: "POST"
}));
}
export enum ReactionLevel {
Album = "album",
Asset = "asset"
}
export enum ReactionType {
Comment = "comment",
Like = "like"
}
export enum Type {
Comment = "comment",
Like = "like"
}
export enum UserAvatarColor {
Primary = "primary",
Pink = "pink",
Red = "red",
Yellow = "yellow",
Blue = "blue",
Green = "green",
Purple = "purple",
Orange = "orange",
Gray = "gray",
Amber = "amber"
}
export enum TagTypeEnum {
Object = "OBJECT",
Face = "FACE",
Custom = "CUSTOM"
}
export enum AssetTypeEnum {
Image = "IMAGE",
Video = "VIDEO",
Audio = "AUDIO",
Other = "OTHER"
}
export enum Error {
Duplicate = "duplicate",
NoPermission = "no_permission",
NotFound = "not_found",
Unknown = "unknown"
}
export enum Action {
Accept = "accept",
Reject = "reject"
}
export enum Reason {
Duplicate = "duplicate",
UnsupportedFormat = "unsupported-format"
}
export enum AssetJobName {
RegenerateThumbnail = "regenerate-thumbnail",
RefreshMetadata = "refresh-metadata",
TranscodeVideo = "transcode-video"
}
export enum ThumbnailFormat {
Jpeg = "JPEG",
Webp = "WEBP"
}
export enum TimeBucketSize {
Day = "DAY",
Month = "MONTH"
}
export enum AssetOrder {
Asc = "asc",
Desc = "desc"
}
export enum EntityType {
Asset = "ASSET",
Album = "ALBUM"
}
export enum PathEntityType {
Asset = "asset",
Person = "person",
User = "user"
}
export enum PathType {
Original = "original",
JpegThumbnail = "jpeg_thumbnail",
WebpThumbnail = "webp_thumbnail",
EncodedVideo = "encoded_video",
Sidecar = "sidecar",
Face = "face",
Profile = "profile"
}
export enum JobName {
ThumbnailGeneration = "thumbnailGeneration",
MetadataExtraction = "metadataExtraction",
VideoConversion = "videoConversion",
FaceDetection = "faceDetection",
FacialRecognition = "facialRecognition",
SmartSearch = "smartSearch",
BackgroundTask = "backgroundTask",
StorageTemplateMigration = "storageTemplateMigration",
Migration = "migration",
Search = "search",
Sidecar = "sidecar",
Library = "library"
}
export enum JobCommand {
Start = "start",
Pause = "pause",
Resume = "resume",
Empty = "empty",
ClearFailed = "clear-failed"
}
export enum LibraryType {
Upload = "UPLOAD",
External = "EXTERNAL"
}
export enum SearchSuggestionType {
Country = "country",
State = "state",
City = "city",
CameraMake = "camera-make",
CameraModel = "camera-model"
}
export enum SharedLinkType {
Album = "ALBUM",
Individual = "INDIVIDUAL"
}
export enum Error2 {
Duplicate = "duplicate",
NoPermission = "no_permission",
NotFound = "not_found"
}
export enum TranscodeHWAccel {
Nvenc = "nvenc",
Qsv = "qsv",
Vaapi = "vaapi",
Rkmpp = "rkmpp",
Disabled = "disabled"
}
export enum AudioCodec {
Mp3 = "mp3",
Aac = "aac",
Libopus = "libopus"
}
export enum VideoCodec {
H264 = "h264",
Hevc = "hevc",
Vp9 = "vp9"
}
export enum CQMode {
Auto = "auto",
Cqp = "cqp",
Icq = "icq"
}
export enum ToneMapping {
Hable = "hable",
Mobius = "mobius",
Reinhard = "reinhard",
Disabled = "disabled"
}
export enum TranscodePolicy {
All = "all",
Optimal = "optimal",
Bitrate = "bitrate",
Required = "required",
Disabled = "disabled"
}
export enum LogLevel {
Verbose = "verbose",
Debug = "debug",
Log = "log",
Warn = "warn",
Error = "error",
Fatal = "fatal"
}
export enum CLIPMode {
Vision = "vision",
Text = "text"
}
export enum ModelType {
FacialRecognition = "facial-recognition",
Clip = "clip"
}
export enum Colorspace {
Srgb = "srgb",
P3 = "p3"
}
export enum MapTheme {
Light = "light",
Dark = "dark"
}
+8 -639
View File
@@ -9,8 +9,8 @@
"version": "1.92.1",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@oazapfts/runtime": "^1.0.0",
"@types/node": "^20.11.0",
"oazapfts": "^5.1.4",
"typescript": "^5.3.3"
},
"peerDependencies": {
@@ -22,134 +22,21 @@
}
}
},
"node_modules/@apidevtools/json-schema-ref-parser": {
"version": "9.0.6",
"resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.6.tgz",
"integrity": "sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==",
"dev": true,
"dependencies": {
"@jsdevtools/ono": "^7.1.3",
"call-me-maybe": "^1.0.1",
"js-yaml": "^3.13.1"
}
},
"node_modules/@apidevtools/openapi-schemas": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz",
"integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/@apidevtools/swagger-methods": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz",
"integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==",
"dev": true
},
"node_modules/@apidevtools/swagger-parser": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.1.0.tgz",
"integrity": "sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==",
"dev": true,
"dependencies": {
"@apidevtools/json-schema-ref-parser": "9.0.6",
"@apidevtools/openapi-schemas": "^2.1.0",
"@apidevtools/swagger-methods": "^3.0.2",
"@jsdevtools/ono": "^7.1.3",
"ajv": "^8.6.3",
"ajv-draft-04": "^1.0.0",
"call-me-maybe": "^1.0.1"
},
"peerDependencies": {
"openapi-types": ">=7"
}
},
"node_modules/@exodus/schemasafe": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@exodus/schemasafe/-/schemasafe-1.3.0.tgz",
"integrity": "sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==",
"dev": true
},
"node_modules/@jsdevtools/ono": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
"integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
"node_modules/@oazapfts/runtime": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@oazapfts/runtime/-/runtime-1.0.0.tgz",
"integrity": "sha512-1ovqeaeEvShbYge5/7ctJokpvqB0anBdfDNfU5jWstjV2/Gbe+vvcBM274Z0abM3IM0b9MmSNWYBXnJXYO8KCw==",
"dev": true
},
"node_modules/@types/node": {
"version": "20.11.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
"integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
"version": "20.11.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
"integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/ajv": {
"version": "8.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/ajv-draft-04": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz",
"integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==",
"dev": true,
"peerDependencies": {
"ajv": "^8.5.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -169,44 +56,6 @@
"proxy-from-env": "^1.1.0"
}
},
"node_modules/call-me-maybe": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz",
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
"dev": true
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"dev": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -230,52 +79,6 @@
"node": ">=0.4.0"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
"dev": true
},
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
@@ -312,55 +115,6 @@
"node": ">= 6"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"dev": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/http2-client": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz",
"integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==",
"dev": true
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-schema-traverse": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
"dev": true
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -384,149 +138,6 @@
"node": ">= 0.6"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dev": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-fetch-h2": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz",
"integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==",
"dev": true,
"dependencies": {
"http2-client": "^1.2.5"
},
"engines": {
"node": "4.x || >=6.0.0"
}
},
"node_modules/node-readfiles": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz",
"integrity": "sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==",
"dev": true,
"dependencies": {
"es6-promise": "^3.2.1"
}
},
"node_modules/oas-kit-common": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz",
"integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==",
"dev": true,
"dependencies": {
"fast-safe-stringify": "^2.0.7"
}
},
"node_modules/oas-linter": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.2.2.tgz",
"integrity": "sha512-KEGjPDVoU5K6swgo9hJVA/qYGlwfbFx+Kg2QB/kd7rzV5N8N5Mg6PlsoCMohVnQmo+pzJap/F610qTodKzecGQ==",
"dev": true,
"dependencies": {
"@exodus/schemasafe": "^1.0.0-rc.2",
"should": "^13.2.1",
"yaml": "^1.10.0"
},
"funding": {
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
}
},
"node_modules/oas-resolver": {
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.5.6.tgz",
"integrity": "sha512-Yx5PWQNZomfEhPPOphFbZKi9W93CocQj18NlD2Pa4GWZzdZpSJvYwoiuurRI7m3SpcChrnO08hkuQDL3FGsVFQ==",
"dev": true,
"dependencies": {
"node-fetch-h2": "^2.3.0",
"oas-kit-common": "^1.0.8",
"reftools": "^1.1.9",
"yaml": "^1.10.0",
"yargs": "^17.0.1"
},
"bin": {
"resolve": "resolve.js"
},
"funding": {
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
}
},
"node_modules/oas-schema-walker": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.5.tgz",
"integrity": "sha512-2yucenq1a9YPmeNExoUa9Qwrt9RFkjqaMAA1X+U7sbb0AqBeTIdMHky9SQQ6iN94bO5NW0W4TRYXerG+BdAvAQ==",
"dev": true,
"funding": {
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
}
},
"node_modules/oas-validator": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-5.0.8.tgz",
"integrity": "sha512-cu20/HE5N5HKqVygs3dt94eYJfBi0TsZvPVXDhbXQHiEityDN+RROTleefoKRKKJ9dFAF2JBkDHgvWj0sjKGmw==",
"dev": true,
"dependencies": {
"call-me-maybe": "^1.0.1",
"oas-kit-common": "^1.0.8",
"oas-linter": "^3.2.2",
"oas-resolver": "^2.5.6",
"oas-schema-walker": "^1.1.5",
"reftools": "^1.1.9",
"should": "^13.2.1",
"yaml": "^1.10.0"
},
"funding": {
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
}
},
"node_modules/oazapfts": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/oazapfts/-/oazapfts-5.1.5.tgz",
"integrity": "sha512-yEBYyX1xfUfCenL+G9wiDNzgPp8wxouuBLnS3F/yTA3lGoDlBtPIIHgC0kpP/aQo16raLLWDp2pJ+FzKNxpBLQ==",
"dev": true,
"dependencies": {
"@apidevtools/swagger-parser": "^10.1.0",
"lodash": "^4.17.21",
"minimist": "^1.2.8",
"swagger2openapi": "^7.0.8",
"typescript": "^5.3.3"
},
"bin": {
"oazapfts": "lib/codegen/cli.js"
}
},
"node_modules/openapi-types": {
"version": "12.1.3",
"resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz",
"integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==",
"dev": true,
"peer": true
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -534,161 +145,6 @@
"optional": true,
"peer": true
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/reftools": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.9.tgz",
"integrity": "sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==",
"dev": true,
"funding": {
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/should": {
"version": "13.2.3",
"resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz",
"integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==",
"dev": true,
"dependencies": {
"should-equal": "^2.0.0",
"should-format": "^3.0.3",
"should-type": "^1.4.0",
"should-type-adaptors": "^1.0.1",
"should-util": "^1.0.0"
}
},
"node_modules/should-equal": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz",
"integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==",
"dev": true,
"dependencies": {
"should-type": "^1.4.0"
}
},
"node_modules/should-format": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz",
"integrity": "sha512-hZ58adtulAk0gKtua7QxevgUaXTTXxIi8t41L3zo9AHvjXO1/7sdLECuHeIN2SRtYXpNkmhoUP2pdeWgricQ+Q==",
"dev": true,
"dependencies": {
"should-type": "^1.3.0",
"should-type-adaptors": "^1.0.1"
}
},
"node_modules/should-type": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz",
"integrity": "sha512-MdAsTu3n25yDbIe1NeN69G4n6mUnJGtSJHygX3+oN0ZbO3DTiATnf7XnYJdGT42JCXurTb1JI0qOBR65shvhPQ==",
"dev": true
},
"node_modules/should-type-adaptors": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz",
"integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==",
"dev": true,
"dependencies": {
"should-type": "^1.3.0",
"should-util": "^1.0.0"
}
},
"node_modules/should-util": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz",
"integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==",
"dev": true
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"dev": true
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/swagger2openapi": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz",
"integrity": "sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==",
"dev": true,
"dependencies": {
"call-me-maybe": "^1.0.1",
"node-fetch": "^2.6.1",
"node-fetch-h2": "^2.3.0",
"node-readfiles": "^0.2.0",
"oas-kit-common": "^1.0.8",
"oas-resolver": "^2.5.6",
"oas-schema-walker": "^1.1.5",
"oas-validator": "^5.0.8",
"reftools": "^1.1.9",
"yaml": "^1.10.0",
"yargs": "^17.0.1"
},
"bin": {
"boast": "boast.js",
"oas-validate": "oas-validate.js",
"swagger2openapi": "swagger2openapi.js"
},
"funding": {
"url": "https://github.com/Mermade/oas-kit?sponsor=1"
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
},
"node_modules/typescript": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
@@ -707,93 +163,6 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dev": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"dev": true,
"engines": {
"node": ">=10"
}
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"dev": true,
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true,
"engines": {
"node": ">=12"
}
}
}
}
+1 -1
View File
@@ -20,8 +20,8 @@
},
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@oazapfts/runtime": "^1.0.0",
"@types/node": "^20.11.0",
"oazapfts": "^5.1.4",
"typescript": "^5.3.3"
},
"peerDependencies": {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"include": ["fetch.ts"],
"include": ["fetch.ts", "fetch-client.ts"],
"compilerOptions": {
"target": "esnext",
"strict": true,
+5 -1
View File
@@ -1,4 +1,4 @@
import { AssetResponseDto } from '@app/domain';
import { AssetBulkDeleteDto, AssetResponseDto } from '@app/domain';
import { CreateAssetDto } from '@app/immich/api-v1/asset/dto/create-asset.dto';
import { AssetFileUploadResponseDto } from '@app/immich/api-v1/asset/response-dto/asset-file-upload-response.dto';
import { randomBytes } from 'node:crypto';
@@ -74,4 +74,8 @@ export const assetApi = {
expect(status).toBe(200);
return body;
},
delete: async (server: any, accessToken: string, dto: AssetBulkDeleteDto) => {
const { status } = await request(server).delete('/asset').set('Authorization', `Bearer ${accessToken}`).send(dto);
expect(status).toBe(204);
},
};
+2
View File
@@ -7,6 +7,7 @@ import { libraryApi } from './library-api';
import { partnerApi } from './partner-api';
import { serverInfoApi } from './server-info-api';
import { sharedLinkApi } from './shared-link-api';
import { trashApi } from './trash-api';
import { userApi } from './user-api';
export const api = {
@@ -17,6 +18,7 @@ export const api = {
libraryApi,
serverInfoApi,
sharedLinkApi,
trashApi,
albumApi,
userApi,
partnerApi,
+13
View File
@@ -0,0 +1,13 @@
import request from 'supertest';
import type { App } from 'supertest/types';
export const trashApi = {
async empty(server: App, accessToken: string) {
const { status } = await request(server).post('/trash/empty').set('Authorization', `Bearer ${accessToken}`);
expect(status).toBe(204);
},
async restore(server: App, accessToken: string) {
const { status } = await request(server).post('/trash/restore').set('Authorization', `Bearer ${accessToken}`);
expect(status).toBe(204);
},
};
+80
View File
@@ -0,0 +1,80 @@
import { LoginResponseDto } from '@app/domain';
import { api } from 'e2e/client';
import { readFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import type { App } from 'supertest/types';
import { IMMICH_TEST_ASSET_PATH, testApp } from '../../../src/test-utils/utils';
const assetFilePath = join(IMMICH_TEST_ASSET_PATH, 'formats/png/density_plot.png');
describe(`Trash (e2e)`, () => {
let server: App;
let admin: LoginResponseDto;
beforeAll(async () => {
const app = await testApp.create();
server = app.getHttpServer();
});
beforeEach(async () => {
await testApp.reset();
await api.authApi.adminSignUp(server);
admin = await api.authApi.adminLogin(server);
});
afterAll(async () => {
await testApp.teardown();
});
it('should move an asset to trash', async () => {
const content = await readFile(assetFilePath);
const { id: assetId } = await api.assetApi.upload(server, admin.accessToken, 'test-device-id', {
content,
filename: basename(assetFilePath),
});
const uploadedAsset = await api.assetApi.get(server, admin.accessToken, assetId);
expect(uploadedAsset.isTrashed).toBe(false);
await api.assetApi.delete(server, admin.accessToken, { ids: [assetId] });
const deletedAsset = await api.assetApi.get(server, admin.accessToken, assetId);
expect(deletedAsset.isTrashed).toBe(true);
});
it('should delete all trashed assets', async () => {
const content = await readFile(assetFilePath);
const { id: assetId } = await api.assetApi.upload(server, admin.accessToken, 'test-device-id', {
content,
filename: basename(assetFilePath),
});
await api.assetApi.delete(server, admin.accessToken, { ids: [assetId] });
const assetsBeforeEmpty = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assetsBeforeEmpty.length).toBe(1);
await api.trashApi.empty(server, admin.accessToken);
const assetsAfterEmpty = await api.assetApi.getAllAssets(server, admin.accessToken);
expect(assetsAfterEmpty.length).toBe(0);
});
it('should restore all trashed assets', async () => {
const content = await readFile(assetFilePath);
const { id: assetId } = await api.assetApi.upload(server, admin.accessToken, 'test-device-id', {
content,
filename: basename(assetFilePath),
});
await api.assetApi.delete(server, admin.accessToken, { ids: [assetId] });
const deletedAsset = await api.assetApi.get(server, admin.accessToken, assetId);
expect(deletedAsset.isTrashed).toBe(true);
await api.trashApi.restore(server, admin.accessToken);
const restoredAsset = await api.assetApi.get(server, admin.accessToken, assetId);
expect(restoredAsset.isTrashed).toBe(false);
});
});
+2802 -9
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -34,6 +34,8 @@
"sql:generate": "node ./dist/infra/sql-generator/"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.515.0",
"@aws-sdk/lib-storage": "^3.515.0",
"@babel/runtime": "^7.22.11",
"@immich/cli": "^2.0.7",
"@nestjs/bullmq": "^10.0.1",
@@ -145,7 +147,7 @@
"coverageDirectory": "./coverage",
"coverageThreshold": {
"./src/domain/": {
"branches": 80,
"branches": 79,
"functions": 80,
"lines": 90,
"statements": 90
@@ -286,6 +286,7 @@ describe(AssetService.name, () => {
describe('getMapMarkers', () => {
it('should get geo information of assets', async () => {
partnerMock.getAll.mockResolvedValue([]);
assetMock.getMapMarkers.mockResolvedValue(
[assetStub.withLocation].map((asset) => ({
id: asset.id,
+10 -2
View File
@@ -157,8 +157,16 @@ export class AssetService {
return folder;
}
getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
return this.assetRepository.getMapMarkers(auth.user.id, options);
async getMapMarkers(auth: AuthDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> {
const userIds: string[] = [auth.user.id];
if (options.withPartners) {
const partners = await this.partnerRepository.getAll(auth.user.id);
const partnersIds = partners
.filter((partner) => partner.sharedBy && partner.sharedWith && partner.sharedById != auth.user.id)
.map((partner) => partner.sharedById);
userIds.push(...partnersIds);
}
return this.assetRepository.getMapMarkers(userIds, options);
}
async getMemoryLane(auth: AuthDto, dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> {
@@ -25,4 +25,10 @@ export class MapMarkerDto {
@IsDate()
@Type(() => Date)
fileCreatedBefore?: Date;
@ApiProperty()
@Optional()
@IsBoolean()
@Transform(toBoolean)
withPartners?: boolean;
}
+27
View File
@@ -0,0 +1,27 @@
import { Readable, Writable } from "node:stream";
export interface FS {
// create creates an object with the given name.
create(name: string): Promise<Writable>;
// open opens the named object.
open(name: string): Promise<Readable>;
// remove removes the named object.
remove(name: string): Promise<void>;
}
// export interface FS {
// // create creates an object with the given name.
// create(name: string): Promise<WritableFile>;
// // open opens the object with the given name.
// open(name: string): Promise<ReadableFile>;
// // remove removes the named object.
// remove(name: string): Promise<void>;
// }
// export interface File {
// createReadableStream(): Promise<Readable>;
// }
+21
View File
@@ -0,0 +1,21 @@
import { constants, open, unlink } from "node:fs/promises";
import { join } from "node:path";
import { Readable, Writable } from "node:stream";
export class LocalFS {
constructor(private dir: string) { }
async create(name: string): Promise<Writable> {
const file = await open(join(this.dir, name), constants.O_WRONLY);
return file.createWriteStream();
}
async open(name: string): Promise<Readable> {
const file = await open(join(this.dir, name), constants.O_RDONLY);
return file.createReadStream();
}
async remove(name: string): Promise<void> {
await unlink(join(this.dir, name));
}
}
+65
View File
@@ -0,0 +1,65 @@
import { PassThrough, Readable, Writable } from "node:stream";
import { S3 } from "@aws-sdk/client-s3";
import { FS } from "./fs";
import { Upload } from "@aws-sdk/lib-storage";
export class S3FS implements FS {
s3: S3;
constructor(private bucket: string) {
this.s3 = new S3();
}
async create(name: string): Promise<Writable> {
const stream = new PassThrough();
const upload = new Upload({
client: this.s3,
params: {
Body: stream,
Bucket: this.bucket,
Key: name,
},
});
// Abort the upload if the stream has finished. Should be a
// no-op if the upload has already finished.
stream.on('close', () => upload.abort());
// Close the stream when the upload is finished, or if it
// failed.
//
// TODO: Find a way to bubble up this error.
upload.done().then(() => void stream.end(), error => {
console.log(`s3 upload failed: ${error}`);
stream.end();
});
return stream;
}
async open(name: string): Promise<Readable> {
const obj = await this.s3.getObject({
Bucket: this.bucket,
Key: name,
});
return obj.Body as Readable;
// const stream = obj.Body?.transformToWebStream();
// if (!stream) {
// throw new Error("no body");
// }
// return Readable.fromWeb(new ReadableStream(stream));
}
async remove(name: string): Promise<void> {
await this.s3.deleteObject({
Bucket: this.bucket,
Key: name,
})
}
}
// class ObjectReadable extends Readable {
// constructor(private s3: S3, private bucket: string) { }
// }
+36 -9
View File
@@ -704,8 +704,35 @@ describe(MediaService.name, () => {
);
});
it('should transcode when audio doesnt match target', async () => {
mediaMock.probe.mockResolvedValue(probeStub.audioStreamMp3);
it('should copy video stream when video matches target', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC }]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
expect(mediaMock.transcode).toHaveBeenCalledWith(
'/original/path.ext',
'upload/encoded-video/user-id/as/se/asset-id.mp4',
{
inputOptions: [],
outputOptions: [
'-c:v copy',
'-c:a aac',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
'-map 0:1',
'-tag:v hvc1',
'-v verbose',
'-preset ultrafast',
'-crf 23',
],
twoPass: false,
},
);
});
it('should copy audio stream when audio matches target', async () => {
mediaMock.probe.mockResolvedValue(probeStub.audioStreamAac);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: TranscodePolicy.OPTIMAL }]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
@@ -716,7 +743,7 @@ describe(MediaService.name, () => {
inputOptions: [],
outputOptions: [
'-c:v h264',
'-c:a aac',
'-c:a copy',
'-movflags faststart',
'-fps_mode passthrough',
'-map 0:0',
@@ -758,11 +785,11 @@ describe(MediaService.name, () => {
);
});
it('should not transcode an invalid transcode value', async () => {
it('should throw an exception if transcode value is invalid', async () => {
mediaMock.probe.mockResolvedValue(probeStub.videoStream2160p);
configMock.load.mockResolvedValue([{ key: SystemConfigKey.FFMPEG_TRANSCODE, value: 'invalid' }]);
assetMock.getByIds.mockResolvedValue([assetStub.video]);
await sut.handleVideoConversion({ id: assetStub.video.id });
await expect(sut.handleVideoConversion({ id: assetStub.video.id })).rejects.toThrow();
expect(mediaMock.transcode).not.toHaveBeenCalled();
});
@@ -1106,7 +1133,7 @@ describe(MediaService.name, () => {
});
it('should disable thread pooling for hevc if thread limit is above 0', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_THREADS, value: 2 },
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
@@ -1140,7 +1167,7 @@ describe(MediaService.name, () => {
});
it('should omit thread flags for hevc if thread limit is at or below 0', async () => {
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_THREADS, value: 0 },
{ key: SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC, value: VideoCodec.HEVC },
@@ -1756,7 +1783,7 @@ describe(MediaService.name, () => {
it('should set vbr options for rkmpp when max bitrate is enabled', async () => {
storageMock.readdir.mockResolvedValue(['renderD128']);
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVp9);
configMock.load.mockResolvedValue([
{ key: SystemConfigKey.FFMPEG_ACCEL, value: TranscodeHWAccel.RKMPP },
{ key: SystemConfigKey.FFMPEG_MAX_BITRATE, value: '10000k' },
+78 -48
View File
@@ -6,6 +6,7 @@ import {
Colorspace,
TranscodeHWAccel,
TranscodePolicy,
TranscodeTarget,
VideoCodec,
} from '@app/infra/entities';
import { ImmichLogger } from '@app/infra/logger';
@@ -197,7 +198,7 @@ export class MediaService {
}
const mainAudioStream = this.getMainStream(audioStreams);
const config = { ...ffmpeg, targetResolution: size.toString() };
const options = new ThumbnailConfig(config).getOptions(mainVideoStream, mainAudioStream);
const options = new ThumbnailConfig(config).getOptions(TranscodeTarget.VIDEO, mainVideoStream, mainAudioStream);
await this.mediaRepository.transcode(asset.originalPath, path, options);
break;
}
@@ -267,7 +268,6 @@ export class MediaService {
const mainVideoStream = this.getMainStream(videoStreams);
const mainAudioStream = this.getMainStream(audioStreams);
const containerExtension = format.formatName;
const bitrate = format.bitrate;
if (!mainVideoStream || !containerExtension) {
return false;
}
@@ -279,15 +279,8 @@ export class MediaService {
const { ffmpeg: config } = await this.configCore.getConfig();
const required = this.isTranscodeRequired(
asset,
mainVideoStream,
mainAudioStream,
containerExtension,
config,
bitrate,
);
if (!required) {
const target = this.getTranscodeTarget(config, mainVideoStream, mainAudioStream);
if (target === TranscodeTarget.NONE) {
if (asset.encodedVideoPath) {
this.logger.log(`Transcoded video exists for asset ${asset.id}, but is no longer required. Deleting...`);
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: [asset.encodedVideoPath] } });
@@ -299,13 +292,15 @@ export class MediaService {
let transcodeOptions;
try {
transcodeOptions = await this.getCodecConfig(config).then((c) => c.getOptions(mainVideoStream, mainAudioStream));
transcodeOptions = await this.getCodecConfig(config).then((c) =>
c.getOptions(target, mainVideoStream, mainAudioStream),
);
} catch (error) {
this.logger.error(`An error occurred while configuring transcoding options: ${error}`);
return false;
}
this.logger.log(`Start encoding video ${asset.id} ${JSON.stringify(transcodeOptions)}`);
this.logger.log(`Started encoding video ${asset.id} ${JSON.stringify(transcodeOptions)}`);
try {
await this.mediaRepository.transcode(input, output, transcodeOptions);
} catch (error) {
@@ -316,11 +311,13 @@ export class MediaService {
);
}
config.accel = TranscodeHWAccel.DISABLED;
transcodeOptions = await this.getCodecConfig(config).then((c) => c.getOptions(mainVideoStream, mainAudioStream));
transcodeOptions = await this.getCodecConfig(config).then((c) =>
c.getOptions(target, mainVideoStream, mainAudioStream),
);
await this.mediaRepository.transcode(input, output, transcodeOptions);
}
this.logger.log(`Encoding success ${asset.id}`);
this.logger.log(`Successfully encoded ${asset.id}`);
await this.assetRepository.save({ id: asset.id, encodedVideoPath: output });
@@ -331,55 +328,88 @@ export class MediaService {
return streams.sort((stream1, stream2) => stream2.frameCount - stream1.frameCount)[0];
}
private isTranscodeRequired(
asset: AssetEntity,
videoStream: VideoStreamInfo,
private getTranscodeTarget(
config: SystemConfigFFmpegDto,
videoStream: VideoStreamInfo | null,
audioStream: AudioStreamInfo | null,
containerExtension: string,
ffmpegConfig: SystemConfigFFmpegDto,
bitrate: number,
): boolean {
const isTargetVideoCodec = ffmpegConfig.acceptedVideoCodecs.includes(videoStream.codecName as VideoCodec);
const isTargetContainer = ['mov,mp4,m4a,3gp,3g2,mj2', 'mp4', 'mov'].includes(containerExtension);
const isTargetAudioCodec =
audioStream == null || ffmpegConfig.acceptedAudioCodecs.includes(audioStream.codecName as AudioCodec);
): TranscodeTarget {
if (videoStream == null && audioStream == null) {
return TranscodeTarget.NONE;
}
this.logger.verbose(
`${asset.id}: AudioCodecName ${audioStream?.codecName ?? 'None'}, AudioStreamCodecType ${
audioStream?.codecType ?? 'None'
}, containerExtension ${containerExtension}`,
);
const isAudioTranscodeRequired = this.isAudioTranscodeRequired(config, audioStream);
const isVideoTranscodeRequired = this.isVideoTranscodeRequired(config, videoStream);
const allTargetsMatching = isTargetVideoCodec && isTargetAudioCodec && isTargetContainer;
const scalingEnabled = ffmpegConfig.targetResolution !== 'original';
const targetRes = Number.parseInt(ffmpegConfig.targetResolution);
const isLargerThanTargetRes = scalingEnabled && Math.min(videoStream.height, videoStream.width) > targetRes;
const isLargerThanTargetBitrate = bitrate > this.parseBitrateToBps(ffmpegConfig.maxBitrate);
if (isAudioTranscodeRequired && isVideoTranscodeRequired) {
return TranscodeTarget.ALL;
}
if (isAudioTranscodeRequired) {
return TranscodeTarget.AUDIO;
}
if (isVideoTranscodeRequired) {
return TranscodeTarget.VIDEO;
}
return TranscodeTarget.NONE;
}
private isAudioTranscodeRequired(ffmpegConfig: SystemConfigFFmpegDto, stream: AudioStreamInfo | null): boolean {
if (stream == null) {
return false;
}
switch (ffmpegConfig.transcode) {
case TranscodePolicy.DISABLED: {
return false;
}
case TranscodePolicy.ALL: {
return true;
}
case TranscodePolicy.REQUIRED: {
return !allTargetsMatching || videoStream.isHDR;
}
case TranscodePolicy.OPTIMAL: {
return !allTargetsMatching || isLargerThanTargetRes || videoStream.isHDR;
}
case TranscodePolicy.REQUIRED:
case TranscodePolicy.OPTIMAL:
case TranscodePolicy.BITRATE: {
return !allTargetsMatching || isLargerThanTargetBitrate || videoStream.isHDR;
return !ffmpegConfig.acceptedAudioCodecs.includes(stream.codecName as AudioCodec);
}
default: {
throw new Error(`Unsupported transcode policy: ${ffmpegConfig.transcode}`);
}
}
}
private isVideoTranscodeRequired(ffmpegConfig: SystemConfigFFmpegDto, stream: VideoStreamInfo | null): boolean {
if (stream == null) {
return false;
}
const scalingEnabled = ffmpegConfig.targetResolution !== 'original';
const targetRes = Number.parseInt(ffmpegConfig.targetResolution);
const isLargerThanTargetRes = scalingEnabled && Math.min(stream.height, stream.width) > targetRes;
const isLargerThanTargetBitrate = stream.bitrate > this.parseBitrateToBps(ffmpegConfig.maxBitrate);
const isTargetVideoCodec = ffmpegConfig.acceptedVideoCodecs.includes(stream.codecName as VideoCodec);
const isRequired = !isTargetVideoCodec || stream.isHDR;
switch (ffmpegConfig.transcode) {
case TranscodePolicy.DISABLED: {
return false;
}
case TranscodePolicy.ALL: {
return true;
}
case TranscodePolicy.REQUIRED: {
return isRequired;
}
case TranscodePolicy.OPTIMAL: {
return isRequired || isLargerThanTargetRes;
}
case TranscodePolicy.BITRATE: {
return isRequired || isLargerThanTargetBitrate;
}
default: {
throw new Error(`Unsupported transcode policy: ${ffmpegConfig.transcode}`);
}
}
}
+21 -16
View File
@@ -1,4 +1,4 @@
import { CQMode, ToneMapping, TranscodeHWAccel, VideoCodec } from '@app/infra/entities';
import { CQMode, ToneMapping, TranscodeHWAccel, TranscodeTarget, VideoCodec } from '@app/infra/entities';
import {
AudioStreamInfo,
BitrateDistribution,
@@ -12,16 +12,19 @@ class BaseConfig implements VideoCodecSWConfig {
presets = ['veryslow', 'slower', 'slow', 'medium', 'fast', 'faster', 'veryfast', 'superfast', 'ultrafast'];
constructor(protected config: SystemConfigFFmpegDto) {}
getOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
getOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = {
inputOptions: this.getBaseInputOptions(),
outputOptions: [...this.getBaseOutputOptions(videoStream, audioStream), '-v verbose'],
outputOptions: [...this.getBaseOutputOptions(target, videoStream, audioStream), '-v verbose'],
twoPass: this.eligibleForTwoPass(),
} as TranscodeOptions;
const filters = this.getFilterOptions(videoStream);
if (filters.length > 0) {
options.outputOptions.push(`-vf ${filters.join(',')}`);
if ([TranscodeTarget.ALL, TranscodeTarget.VIDEO].includes(target)) {
const filters = this.getFilterOptions(videoStream);
if (filters.length > 0) {
options.outputOptions.push(`-vf ${filters.join(',')}`);
}
}
options.outputOptions.push(...this.getPresetOptions(), ...this.getThreadOptions(), ...this.getBitrateOptions());
return options;
@@ -31,10 +34,10 @@ class BaseConfig implements VideoCodecSWConfig {
return [];
}
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = [
`-c:v ${this.getVideoCodec()}`,
`-c:a ${this.getAudioCodec()}`,
`-c:v ${[TranscodeTarget.ALL, TranscodeTarget.VIDEO].includes(target) ? this.getVideoCodec() : 'copy'}`,
`-c:a ${[TranscodeTarget.ALL, TranscodeTarget.AUDIO].includes(target) ? this.getAudioCodec() : 'copy'}`,
// Makes a second pass moving the moov atom to the
// beginning of the file for improved playback speed.
'-movflags faststart',
@@ -398,14 +401,14 @@ export class NVENCConfig extends BaseHWConfig {
return ['-init_hw_device cuda=cuda:0', '-filter_hw_device cuda'];
}
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = [
// below settings recommended from https://docs.nvidia.com/video-technologies/video-codec-sdk/12.0/ffmpeg-with-nvidia-gpu/index.html#command-line-for-latency-tolerant-high-quality-transcoding
'-tune hq',
'-qmin 0',
'-rc-lookahead 20',
'-i_qfactor 0.75',
...super.getBaseOutputOptions(videoStream, audioStream),
...super.getBaseOutputOptions(target, videoStream, audioStream),
];
if (this.getBFrames() > 0) {
options.push('-b_ref_mode middle', '-b_qfactor 1.1');
@@ -483,8 +486,8 @@ export class QSVConfig extends BaseHWConfig {
return [`-init_hw_device qsv=hw${qsvString}`, '-filter_hw_device hw'];
}
getBaseOutputOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = super.getBaseOutputOptions(videoStream, audioStream);
getBaseOutputOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo) {
const options = super.getBaseOutputOptions(target, videoStream, audioStream);
// VP9 requires enabling low power mode https://git.ffmpeg.org/gitweb/ffmpeg.git/commit/33583803e107b6d532def0f9d949364b01b6ad5a
if (this.config.targetVideoCodec === VideoCodec.VP9) {
options.push('-low_power 1');
@@ -604,11 +607,13 @@ export class VAAPIConfig extends BaseHWConfig {
}
export class RKMPPConfig extends BaseHWConfig {
getOptions(videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo): TranscodeOptions {
const options = super.getOptions(videoStream, audioStream);
getOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream?: AudioStreamInfo): TranscodeOptions {
const options = super.getOptions(target, videoStream, audioStream);
options.ffmpegPath = 'ffmpeg_mpp';
options.ldLibraryPath = '/lib/aarch64-linux-gnu:/lib/ffmpeg-mpp';
options.outputOptions.push(...this.getSizeOptions(videoStream));
if ([TranscodeTarget.ALL, TranscodeTarget.VIDEO].includes(target)) {
options.outputOptions.push(...this.getSizeOptions(videoStream));
}
return options;
}
+2 -2
View File
@@ -410,8 +410,8 @@ export class PersonService {
});
// `matches` also includes the face itself
if (matches.length <= 1) {
this.logger.debug(`Face ${id} has no matches`);
if (machineLearning.facialRecognition.minFaces > 1 && matches.length <= 1) {
this.logger.debug(`Face ${id} only matched the face itself, skipping`);
return true;
}
@@ -140,7 +140,7 @@ export interface IAssetRepository {
softDeleteAll(ids: string[]): Promise<void>;
restoreAll(ids: string[]): Promise<void>;
findLivePhotoMatch(options: LivePhotoSearchOptions): Promise<AssetEntity | null>;
getMapMarkers(ownerId: string, options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
getMapMarkers(ownerIds: string[], options?: MapMarkerSearchOptions): Promise<MapMarker[]>;
getStatistics(ownerId: string, options: AssetStatsOptions): Promise<AssetStats>;
getTimeBuckets(options: TimeBucketOptions): Promise<TimeBucketItem[]>;
getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise<AssetEntity[]>;
@@ -1,4 +1,4 @@
import { VideoCodec } from '@app/infra/entities';
import { TranscodeTarget, VideoCodec } from '@app/infra/entities';
import { Writable } from 'node:stream';
export const IMediaRepository = 'IMediaRepository';
@@ -16,15 +16,14 @@ export interface VideoStreamInfo {
width: number;
rotation: number;
codecName?: string;
codecType?: string;
frameCount: number;
isHDR: boolean;
bitrate: number;
}
export interface AudioStreamInfo {
index: number;
codecName?: string;
codecType?: string;
frameCount: number;
}
@@ -64,7 +63,7 @@ export interface BitrateDistribution {
}
export interface VideoCodecSWConfig {
getOptions(videoStream: VideoStreamInfo, audioStream: AudioStreamInfo): TranscodeOptions;
getOptions(target: TranscodeTarget, videoStream: VideoStreamInfo, audioStream: AudioStreamInfo): TranscodeOptions;
}
export interface VideoCodecHWConfig extends VideoCodecSWConfig {
@@ -39,4 +39,9 @@ export interface IMetadataRepository {
readTags(path: string): Promise<ImmichTags | null>;
writeTags(path: string, tags: Partial<Tags>): Promise<void>;
extractBinaryTag(tagName: string, path: string): Promise<Buffer>;
getCountries(userId: string): Promise<string[]>;
getStates(userId: string, country?: string): Promise<string[]>;
getCities(userId: string, country?: string, state?: string): Promise<string[]>;
getCameraMakes(userId: string, model?: string): Promise<string[]>;
getCameraModels(userId: string, make?: string): Promise<string[]>;
}
@@ -66,13 +66,14 @@ export interface SearchAssetIDOptions {
id?: string;
}
export interface SearchUserIDOptions {
export interface SearchUserIdOptions {
deviceId?: string;
libraryId?: string;
ownerId?: string;
userIds?: string[];
}
export type SearchIDOptions = SearchAssetIDOptions & SearchUserIDOptions;
export type SearchIdOptions = SearchAssetIDOptions & SearchUserIdOptions;
export interface SearchStatusOptions {
isArchived?: boolean;
@@ -83,6 +84,7 @@ export interface SearchStatusOptions {
isOffline?: boolean;
isReadOnly?: boolean;
isVisible?: boolean;
isNotInAlbum?: boolean;
type?: AssetType;
withArchived?: boolean;
withDeleted?: boolean;
@@ -132,6 +134,10 @@ export interface SearchEmbeddingOptions {
userIds: string[];
}
export interface SearchPeopleOptions {
personIds?: string[];
}
export interface SearchOrderOptions {
orderDirection?: 'ASC' | 'DESC';
}
@@ -142,12 +148,14 @@ export interface SearchPaginationOptions {
}
export type AssetSearchOptions = SearchDateOptions &
SearchIDOptions &
SearchIdOptions &
SearchExifOptions &
SearchOrderOptions &
SearchPathOptions &
SearchRelationOptions &
SearchStatusOptions;
SearchStatusOptions &
SearchUserIdOptions &
SearchPeopleOptions;
export type AssetSearchBuilderOptions = Omit<AssetSearchOptions, 'orderDirection'>;
@@ -156,7 +164,8 @@ export type SmartSearchOptions = SearchDateOptions &
SearchExifOptions &
SearchOneToOneRelationOptions &
SearchStatusOptions &
SearchUserIDOptions;
SearchUserIdOptions &
SearchPeopleOptions;
export interface FaceEmbeddingSearch extends SearchEmbeddingOptions {
hasPerson?: boolean;
@@ -0,0 +1,33 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export enum SearchSuggestionType {
COUNTRY = 'country',
STATE = 'state',
CITY = 'city',
CAMERA_MAKE = 'camera-make',
CAMERA_MODEL = 'camera-model',
}
export class SearchSuggestionRequestDto {
@IsEnum(SearchSuggestionType)
@IsNotEmpty()
@ApiProperty({ enumName: 'SearchSuggestionType', enum: SearchSuggestionType })
type!: SearchSuggestionType;
@IsString()
@IsOptional()
country?: string;
@IsString()
@IsOptional()
state?: string;
@IsString()
@IsOptional()
make?: string;
@IsString()
@IsOptional()
model?: string;
}
@@ -169,6 +169,12 @@ export class MetadataSearchDto extends BaseSearchDto {
@Optional()
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
order?: AssetOrder;
@QueryBoolean({ optional: true })
isNotInAlbum?: boolean;
@Optional()
personIds?: string[];
}
export class SmartSearchDto extends BaseSearchDto {
@@ -4,6 +4,7 @@ import {
authStub,
newAssetRepositoryMock,
newMachineLearningRepositoryMock,
newMetadataRepositoryMock,
newPartnerRepositoryMock,
newPersonRepositoryMock,
newSearchRepositoryMock,
@@ -14,6 +15,7 @@ import { mapAsset } from '../asset';
import {
IAssetRepository,
IMachineLearningRepository,
IMetadataRepository,
IPartnerRepository,
IPersonRepository,
ISearchRepository,
@@ -32,6 +34,7 @@ describe(SearchService.name, () => {
let personMock: jest.Mocked<IPersonRepository>;
let searchMock: jest.Mocked<ISearchRepository>;
let partnerMock: jest.Mocked<IPartnerRepository>;
let metadataMock: jest.Mocked<IMetadataRepository>;
beforeEach(() => {
assetMock = newAssetRepositoryMock();
@@ -40,7 +43,9 @@ describe(SearchService.name, () => {
personMock = newPersonRepositoryMock();
searchMock = newSearchRepositoryMock();
partnerMock = newPartnerRepositoryMock();
sut = new SearchService(configMock, machineMock, personMock, searchMock, assetMock, partnerMock);
metadataMock = newMetadataRepositoryMock();
sut = new SearchService(configMock, machineMock, personMock, searchMock, assetMock, partnerMock, metadataMock);
});
it('should work', () => {
+29 -1
View File
@@ -7,6 +7,7 @@ import { PersonResponseDto } from '../person';
import {
IAssetRepository,
IMachineLearningRepository,
IMetadataRepository,
IPartnerRepository,
IPersonRepository,
ISearchRepository,
@@ -16,6 +17,7 @@ import {
} from '../repositories';
import { FeatureFlag, SystemConfigCore } from '../system-config';
import { MetadataSearchDto, SearchDto, SearchPeopleDto, SmartSearchDto } from './dto';
import { SearchSuggestionRequestDto, SearchSuggestionType } from './dto/search-suggestion.dto';
import { SearchResponseDto } from './response-dto';
@Injectable()
@@ -30,6 +32,7 @@ export class SearchService {
@Inject(ISearchRepository) private searchRepository: ISearchRepository,
@Inject(IAssetRepository) private assetRepository: IAssetRepository,
@Inject(IPartnerRepository) private partnerRepository: IPartnerRepository,
@Inject(IMetadataRepository) private metadataRepository: IMetadataRepository,
) {
this.configCore = SystemConfigCore.create(configRepository);
}
@@ -57,6 +60,7 @@ export class SearchService {
async searchMetadata(auth: AuthDto, dto: MetadataSearchDto): Promise<SearchResponseDto> {
let checksum: Buffer | undefined;
const userIds = await this.getUserIdsToSearch(auth);
if (dto.checksum) {
const encoding = dto.checksum.length === 28 ? 'base64' : 'hex';
@@ -71,7 +75,7 @@ export class SearchService {
{
...dto,
checksum,
ownerId: auth.user.id,
userIds,
orderDirection: dto.order ? enumToOrder[dto.order] : 'DESC',
},
);
@@ -176,4 +180,28 @@ export class SearchService {
},
};
}
async getSearchSuggestions(auth: AuthDto, dto: SearchSuggestionRequestDto): Promise<string[]> {
if (dto.type === SearchSuggestionType.COUNTRY) {
return this.metadataRepository.getCountries(auth.user.id);
}
if (dto.type === SearchSuggestionType.STATE) {
return this.metadataRepository.getStates(auth.user.id, dto.country);
}
if (dto.type === SearchSuggestionType.CITY) {
return this.metadataRepository.getCities(auth.user.id, dto.country, dto.state);
}
if (dto.type === SearchSuggestionType.CAMERA_MAKE) {
return this.metadataRepository.getCameraMakes(auth.user.id, dto.model);
}
if (dto.type === SearchSuggestionType.CAMERA_MODEL) {
return this.metadataRepository.getCameraModels(auth.user.id, dto.make);
}
return [];
}
}
@@ -140,6 +140,12 @@ export const defaults = Object.freeze<SystemConfig>({
externalDomain: '',
loginPageMessage: '',
},
storage: {
kind: 'local',
options: {
path: process.env.UPLOAD_LOCATION ?? '',
}
}
});
export enum FeatureFlag {
@@ -168,7 +174,7 @@ export class SystemConfigCore {
public config$ = new Subject<SystemConfig>();
private constructor(private repository: ISystemConfigRepository) {}
private constructor(private repository: ISystemConfigRepository) { }
static create(repository: ISystemConfigRepository) {
if (!instance) {
@@ -9,7 +9,8 @@ import {
SearchService,
SmartSearchDto,
} from '@app/domain';
import { Controller, Get, Query } from '@nestjs/common';
import { SearchSuggestionRequestDto } from '@app/domain/search/dto/search-suggestion.dto';
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Auth, Authenticated } from '../app.guard';
import { UseValidation } from '../app.utils';
@@ -21,13 +22,13 @@ import { UseValidation } from '../app.utils';
export class SearchController {
constructor(private service: SearchService) {}
@Get('metadata')
searchMetadata(@Auth() auth: AuthDto, @Query() dto: MetadataSearchDto): Promise<SearchResponseDto> {
@Post('metadata')
searchMetadata(@Auth() auth: AuthDto, @Body() dto: MetadataSearchDto): Promise<SearchResponseDto> {
return this.service.searchMetadata(auth, dto);
}
@Get('smart')
searchSmart(@Auth() auth: AuthDto, @Query() dto: SmartSearchDto): Promise<SearchResponseDto> {
@Post('smart')
searchSmart(@Auth() auth: AuthDto, @Body() dto: SmartSearchDto): Promise<SearchResponseDto> {
return this.service.searchSmart(auth, dto);
}
@@ -46,4 +47,9 @@ export class SearchController {
searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
return this.service.searchPerson(auth, dto);
}
@Get('suggestions')
getSearchSuggestions(@Auth() auth: AuthDto, @Query() dto: SearchSuggestionRequestDto): Promise<string[]> {
return this.service.getSearchSuggestions(auth, dto);
}
}
@@ -118,6 +118,13 @@ export enum TranscodePolicy {
DISABLED = 'disabled',
}
export enum TranscodeTarget {
NONE,
AUDIO,
VIDEO,
ALL,
}
export enum VideoCodec {
H264 = 'h264',
HEVC = 'hevc',
@@ -165,6 +172,18 @@ export enum LogLevel {
FATAL = 'fatal',
}
export interface StorageOptionsLocal {
path: string;
}
export interface StorageOptionsS3 {
bucket: string;
region: string;
endpoint: string;
accessKeyId: string;
secretAccessKey: string;
}
export interface SystemConfig {
ffmpeg: {
crf: number;
@@ -269,4 +288,10 @@ export interface SystemConfig {
externalDomain: string;
loginPageMessage: string;
};
// TODO(uhthomas): Is this definitely the approach we want to take for
// configuring storage?
storage: {
kind: string;
options: StorageOptionsLocal | StorageOptionsS3;
}
}

Some files were not shown because too many files have changed in this diff Show More