From 51cfe10c287e9bead16181d0ef0184f6407fcbb5 Mon Sep 17 00:00:00 2001 From: PyKen Date: Mon, 31 Jul 2023 10:31:57 +0900 Subject: [PATCH] feat(web/server): Search by panorama photos (#3470) * Add panorama filter * Add generated api changes * Fix naming --- cli/src/api/open-api/api.ts | 23 ++++++++++++---- mobile/openapi/doc/SearchApi.md | Bin 7801 -> 8012 bytes mobile/openapi/lib/api/search_api.dart | Bin 8563 -> 8960 bytes mobile/openapi/test/search_api_test.dart | Bin 1144 -> 1181 bytes server/immich-openapi-specs.json | 8 ++++++ server/src/domain/search/dto/search.dto.ts | 5 ++++ .../infra/typesense-schemas/asset.schema.ts | 3 +- web/src/api/open-api/api.ts | 26 ++++++++++++++---- web/src/routes/(user)/explore/+page.svelte | 10 +++++++ 9 files changed, 63 insertions(+), 12 deletions(-) diff --git a/cli/src/api/open-api/api.ts b/cli/src/api/open-api/api.ts index 384e33ca95..6e237d8640 100644 --- a/cli/src/api/open-api/api.ts +++ b/cli/src/api/open-api/api.ts @@ -9489,6 +9489,7 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {string} [exifInfoCountry] * @param {string} [exifInfoMake] * @param {string} [exifInfoModel] + * @param {string} [exifInfoProjectionType] * @param {Array} [smartInfoObjects] * @param {Array} [smartInfoTags] * @param {boolean} [recent] @@ -9496,7 +9497,7 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {*} [options] Override http request option. * @throws {RequiredError} */ - search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { + search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/search`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -9562,6 +9563,10 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio localVarQueryParameter['exifInfo.model'] = exifInfoModel; } + if (exifInfoProjectionType !== undefined) { + localVarQueryParameter['exifInfo.projectionType'] = exifInfoProjectionType; + } + if (smartInfoObjects) { localVarQueryParameter['smartInfo.objects'] = smartInfoObjects; } @@ -9630,6 +9635,7 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {string} [exifInfoCountry] * @param {string} [exifInfoMake] * @param {string} [exifInfoModel] + * @param {string} [exifInfoProjectionType] * @param {Array} [smartInfoObjects] * @param {Array} [smartInfoTags] * @param {boolean} [recent] @@ -9637,8 +9643,8 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options); + async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, exifInfoProjectionType, smartInfoObjects, smartInfoTags, recent, motion, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -9674,7 +9680,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * @throws {RequiredError} */ search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig): AxiosPromise { - return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath)); + return localVarFp.search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(axios, basePath)); }, }; }; @@ -9762,6 +9768,13 @@ export interface SearchApiSearchRequest { */ readonly exifInfoModel?: string + /** + * + * @type {string} + * @memberof SearchApiSearch + */ + readonly exifInfoProjectionType?: string + /** * * @type {Array} @@ -9826,7 +9839,7 @@ export class SearchApi extends BaseAPI { * @memberof SearchApi */ public search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig) { - return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); + return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); } } diff --git a/mobile/openapi/doc/SearchApi.md b/mobile/openapi/doc/SearchApi.md index 2ada86a47058fb9174ca06e5d4f1ee659ea1cc10..74840a73cda5b4e0c245fd509706e8cbd5a5f662 100644 GIT binary patch delta 172 zcmexqbH;AN3<25Hip(_6ytMp))S}G%lz^iAtkmR^%>2BN%7Rp#$$H}Ao7V|6vQ3T_ z41vkok~W(^2}H9^zAetbd5d@?<7RD1Vdl-XGUu5933m-w diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index 9df6c79d07e2a58653bd05eec86bf10d72b0e6e4..8178395e077478f806a303487d57c8c2d14e8ba2 100644 GIT binary patch delta 284 zcmezD)Zn(^8QW8cihU(Yz%Pyj=It$;jO zeysot+vIg3e7q?7Cm#@T)Ga_UQC&wNwIVakGcPSaAhjqnKLtfY>gM@kWh{6c0(4OM j<_*$p_#88Ntw6x!To&=ojQ! diff --git a/mobile/openapi/test/search_api_test.dart b/mobile/openapi/test/search_api_test.dart index a65075d1eb7caeae4afc80c77bcb18ad8fdde58f..a6bbacfa2a9bde4621ef5743abc56a66beca7070 100644 GIT binary patch delta 34 qcmeytF_&}0S?0-(%%Xe&Mfq8&$t9Wjc_Ebrsgpl2%T0dC+z$ZenhlZw delta 11 TcmbQs`GaG_S?0-~m{$V;ADjhR diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 1fc155c54a..56e96bd116 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -2924,6 +2924,14 @@ "type": "string" } }, + { + "name": "exifInfo.projectionType", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, { "name": "smartInfo.objects", "required": false, diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts index 27eeb52725..bb38a383b2 100644 --- a/server/src/domain/search/dto/search.dto.ts +++ b/server/src/domain/search/dto/search.dto.ts @@ -58,6 +58,11 @@ export class SearchDto { @IsOptional() 'exifInfo.model'?: string; + @IsString() + @IsNotEmpty() + @IsOptional() + 'exifInfo.projectionType'?: string; + @IsString({ each: true }) @IsArray() @IsOptional() diff --git a/server/src/infra/typesense-schemas/asset.schema.ts b/server/src/infra/typesense-schemas/asset.schema.ts index 5d31e67b2b..53729c0772 100644 --- a/server/src/infra/typesense-schemas/asset.schema.ts +++ b/server/src/infra/typesense-schemas/asset.schema.ts @@ -1,6 +1,6 @@ import { CollectionCreateSchema } from 'typesense/lib/Typesense/Collections'; -export const assetSchemaVersion = 7; +export const assetSchemaVersion = 8; export const assetSchema: CollectionCreateSchema = { name: `assets-v${assetSchemaVersion}`, fields: [ @@ -23,6 +23,7 @@ export const assetSchema: CollectionCreateSchema = { { name: 'exifInfo.make', type: 'string', facet: true, optional: true }, { name: 'exifInfo.model', type: 'string', facet: true, optional: true }, { name: 'exifInfo.orientation', type: 'string', optional: true }, + { name: 'exifInfo.projectionType', type: 'string', facet: true, optional: true }, // smart info { name: 'smartInfo.objects', type: 'string[]', facet: true, optional: true }, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 02d3b41404..e42b66ccbf 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -9535,6 +9535,7 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {string} [exifInfoCountry] * @param {string} [exifInfoMake] * @param {string} [exifInfoModel] + * @param {string} [exifInfoProjectionType] * @param {Array} [smartInfoObjects] * @param {Array} [smartInfoTags] * @param {boolean} [recent] @@ -9542,7 +9543,7 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {*} [options] Override http request option. * @throws {RequiredError} */ - search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { + search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/search`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -9608,6 +9609,10 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio localVarQueryParameter['exifInfo.model'] = exifInfoModel; } + if (exifInfoProjectionType !== undefined) { + localVarQueryParameter['exifInfo.projectionType'] = exifInfoProjectionType; + } + if (smartInfoObjects) { localVarQueryParameter['smartInfo.objects'] = smartInfoObjects; } @@ -9676,6 +9681,7 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {string} [exifInfoCountry] * @param {string} [exifInfoMake] * @param {string} [exifInfoModel] + * @param {string} [exifInfoProjectionType] * @param {Array} [smartInfoObjects] * @param {Array} [smartInfoTags] * @param {boolean} [recent] @@ -9683,8 +9689,8 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options); + async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, exifInfoProjectionType, smartInfoObjects, smartInfoTags, recent, motion, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -9726,6 +9732,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * @param {string} [exifInfoCountry] * @param {string} [exifInfoMake] * @param {string} [exifInfoModel] + * @param {string} [exifInfoProjectionType] * @param {Array} [smartInfoObjects] * @param {Array} [smartInfoTags] * @param {boolean} [recent] @@ -9733,8 +9740,8 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: any): AxiosPromise { - return localVarFp.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options).then((request) => request(axios, basePath)); + search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, exifInfoProjectionType?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: any): AxiosPromise { + return localVarFp.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, exifInfoProjectionType, smartInfoObjects, smartInfoTags, recent, motion, options).then((request) => request(axios, basePath)); }, }; }; @@ -9822,6 +9829,13 @@ export interface SearchApiSearchRequest { */ readonly exifInfoModel?: string + /** + * + * @type {string} + * @memberof SearchApiSearch + */ + readonly exifInfoProjectionType?: string + /** * * @type {Array} @@ -9886,7 +9900,7 @@ export class SearchApi extends BaseAPI { * @memberof SearchApi */ public search(requestParameters: SearchApiSearchRequest = {}, options?: AxiosRequestConfig) { - return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); + return SearchApiFp(this.configuration).search(requestParameters.q, requestParameters.query, requestParameters.clip, requestParameters.type, requestParameters.isFavorite, requestParameters.isArchived, requestParameters.exifInfoCity, requestParameters.exifInfoState, requestParameters.exifInfoCountry, requestParameters.exifInfoMake, requestParameters.exifInfoModel, requestParameters.exifInfoProjectionType, requestParameters.smartInfoObjects, requestParameters.smartInfoTags, requestParameters.recent, requestParameters.motion, options).then((request) => request(this.axios, this.basePath)); } } diff --git a/web/src/routes/(user)/explore/+page.svelte b/web/src/routes/(user)/explore/+page.svelte index 3972bbb41f..cdedb2d10e 100644 --- a/web/src/routes/(user)/explore/+page.svelte +++ b/web/src/routes/(user)/explore/+page.svelte @@ -8,6 +8,7 @@ import HeartMultipleOutline from 'svelte-material-icons/HeartMultipleOutline.svelte'; import MotionPlayOutline from 'svelte-material-icons/MotionPlayOutline.svelte'; import PlayCircleOutline from 'svelte-material-icons/PlayCircleOutline.svelte'; + import Rotate360Icon from 'svelte-material-icons/Rotate360.svelte'; import type { PageData } from './$types'; export let data: PageData; @@ -149,6 +150,15 @@ Motion photos +