From 90a69e2ba63d4e535a1395547df47d30a683f5df Mon Sep 17 00:00:00 2001 From: mws-weekend-projects <255546191+mws-weekend-projects@users.noreply.github.com> Date: Wed, 6 May 2026 15:45:40 +0200 Subject: [PATCH] feat(web): add full-path search mode to UI (#26758) Co-authored-by: mws-weekend-projects Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com> Co-authored-by: Daniel Dietzler Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> --- docs/docs/features/searching.md | 7 +++++++ i18n/en.json | 3 +++ .../shared-components/search-bar/SearchBar.svelte | 9 +++++++++ .../search-bar/SearchTextSection.svelte | 13 ++++++++++++- web/src/lib/constants.ts | 9 ++++++++- web/src/lib/modals/SearchFilterModal.svelte | 5 +++++ web/src/lib/types.ts | 2 +- .../[[photos=photos]]/[[assetId=id]]/+page.svelte | 1 + 8 files changed, 46 insertions(+), 3 deletions(-) diff --git a/docs/docs/features/searching.md b/docs/docs/features/searching.md index 92eb01c39d..1bdfeca8ba 100644 --- a/docs/docs/features/searching.md +++ b/docs/docs/features/searching.md @@ -18,6 +18,7 @@ You can search the following types of content: | People | Faces that are recognized in your photos/videos. | | Contextual | Content of the photos and videos. | | File name or extension | Full or partial file's name, or file's extension | +| Full path or folder | Full or partial folder names from the original path. | | Description | Description added to assets. | | Optical Character Recognition (OCR) | Text in images | | Locations | Cities, states, and countries from reverse geocoding. | @@ -30,6 +31,12 @@ You can search the following types of content: +### Full path or folder + +Use this mode when you know a folder name or part of the original asset path. + +Example: for /John/Projects/3D_Printing/2026-07-01/IMG_0001.jpg, searches like Projects, 3D, Printing, or 2026 match the asset. + ## Configuration Navigating to `Administration > Settings > Machine Learning Settings > Smart Search` will show the options available. diff --git a/i18n/en.json b/i18n/en.json index cc30d9e350..7616c0a55a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -1240,6 +1240,7 @@ "free_up_space_description": "Move backed-up photos and videos to your device's trash to free up space. Your copies on the server remain safe.", "free_up_space_settings_subtitle": "Free up device storage", "full_path": "Full path: {path}", + "full_path_or_folder": "Full path or folder", "gcast_enabled": "Google Cast", "gcast_enabled_description": "This feature loads external resources from Google in order to work.", "general": "General", @@ -1943,6 +1944,8 @@ "search_by_description_example": "Hiking day in Sapa", "search_by_filename": "Search by file name or extension", "search_by_filename_example": "i.e. IMG_1234.JPG or PNG", + "search_by_full_path": "Search by full path or folder", + "search_by_full_path_example": "/John/Projects/3D_Printing/2026-07-01 - you can search for Projects, 3D, Printing, 2026 etc.", "search_by_ocr": "Search by OCR", "search_by_ocr_example": "Latte", "search_camera_lens_model": "Search lens model...", diff --git a/web/src/lib/components/shared-components/search-bar/SearchBar.svelte b/web/src/lib/components/shared-components/search-bar/SearchBar.svelte index da61081e59..339bfb64a2 100644 --- a/web/src/lib/components/shared-components/search-bar/SearchBar.svelte +++ b/web/src/lib/components/shared-components/search-bar/SearchBar.svelte @@ -88,6 +88,10 @@ case 'description': { return { description: term }; } + case 'fullPath': { + const normalizedTerm = term.trim(); + return normalizedTerm ? { originalPath: normalizedTerm } : {}; + } case 'ocr': { return { ocr: term }; } @@ -198,6 +202,7 @@ case 'smart': case 'metadata': case 'description': + case 'fullPath': case 'ocr': { currentSearchType = searchType; return searchType; @@ -220,6 +225,9 @@ case 'description': { return $t('description'); } + case 'fullPath': { + return $t('full_path_or_folder'); + } case 'ocr': { return $t('ocr'); } @@ -237,6 +245,7 @@ { value: 'smart', label: () => $t('context') }, { value: 'metadata', label: () => $t('filename') }, { value: 'description', label: () => $t('description') }, + { value: 'fullPath', label: () => $t('full_path_or_folder') }, { value: 'ocr', label: () => $t('ocr') }, ] as const; diff --git a/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte b/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte index 9e78531ac3..e63ab5bcf2 100644 --- a/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte +++ b/web/src/lib/components/shared-components/search-bar/SearchTextSection.svelte @@ -6,7 +6,7 @@ interface Props { query: string | undefined; - queryType?: 'smart' | 'metadata' | 'description' | 'ocr'; + queryType?: 'smart' | 'metadata' | 'description' | 'fullPath' | 'ocr'; } let { query = $bindable(), queryType = $bindable('smart') }: Props = $props(); @@ -33,6 +33,13 @@ bind:group={queryType} value="description" /> + {#if featureFlagsManager.value.ocr} {/if} @@ -51,6 +58,10 @@ + {:else if queryType === 'fullPath'} + + + {:else if queryType === 'ocr'} diff --git a/web/src/lib/constants.ts b/web/src/lib/constants.ts index 9212343c04..3bb1539e0c 100644 --- a/web/src/lib/constants.ts +++ b/web/src/lib/constants.ts @@ -87,10 +87,17 @@ export enum QueryType { SMART = 'smart', METADATA = 'metadata', DESCRIPTION = 'description', + FULL_PATH = 'fullPath', OCR = 'ocr', } -export const validQueryTypes = new Set([QueryType.SMART, QueryType.METADATA, QueryType.DESCRIPTION, QueryType.OCR]); +export const validQueryTypes = new Set([ + QueryType.SMART, + QueryType.METADATA, + QueryType.DESCRIPTION, + QueryType.FULL_PATH, + QueryType.OCR, +]); export const locales = [ { code: 'af-ZA', name: 'Afrikaans (South Africa)' }, diff --git a/web/src/lib/modals/SearchFilterModal.svelte b/web/src/lib/modals/SearchFilterModal.svelte index 0cb5276343..0145caacaf 100644 --- a/web/src/lib/modals/SearchFilterModal.svelte +++ b/web/src/lib/modals/SearchFilterModal.svelte @@ -54,6 +54,10 @@ query = searchQuery.originalFileName; } + if ('originalPath' in searchQuery && searchQuery.originalPath) { + query = searchQuery.originalPath; + } + return { query, ocr: searchQuery.ocr, @@ -133,6 +137,7 @@ ocr: filter.queryType === 'ocr' ? query : undefined, originalFileName: filter.queryType === 'metadata' ? query : undefined, description: filter.queryType === 'description' ? query : undefined, + originalPath: filter.queryType === 'fullPath' ? filter.query.trim() || undefined : undefined, country: filter.location.country, state: filter.location.state, city: filter.location.city, diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts index 835b6c151f..076847f65b 100644 --- a/web/src/lib/types.ts +++ b/web/src/lib/types.ts @@ -80,7 +80,7 @@ export type SearchLocationFilter = { export type SearchFilter = { query: string; ocr?: string; - queryType: 'smart' | 'metadata' | 'description' | 'ocr'; + queryType: 'smart' | 'metadata' | 'description' | 'fullPath' | 'ocr'; personIds: SvelteSet; tagIds: SvelteSet | null; location: SearchLocationFilter; diff --git a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte index 488d699368..f32b74efa2 100644 --- a/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte +++ b/web/src/routes/(user)/search/[[photos=photos]]/[[assetId=id]]/+page.svelte @@ -184,6 +184,7 @@ personIds: $t('people'), tagIds: $t('tags'), originalFileName: $t('file_name_text'), + originalPath: $t('full_path_or_folder'), description: $t('description'), queryAssetId: $t('query_asset_id'), ocr: $t('ocr'),