mirror of
https://github.com/immich-app/immich.git
synced 2025-06-03 13:44:16 -04:00
feat(web): search by filename (#7624)
* Toggle to search by filename * wild card search and pr feedback * Pr feedback * naming * placeholder * Create index * pr feedback * pr feedback * Update web/src/lib/components/shared-components/search-bar/search-text-section.svelte Co-authored-by: Jason Rasmussen <jrasm91@gmail.com> * pr feedback * pr feedback * pr feedback * pr feedback --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
parent
ae46188753
commit
2f53f6a62c
@ -35,6 +35,7 @@ export const ASSET_CHECKSUM_CONSTRAINT = 'UQ_assets_owner_library_checksum';
|
|||||||
@Index('IDX_day_of_month', { synchronize: false })
|
@Index('IDX_day_of_month', { synchronize: false })
|
||||||
@Index('IDX_month', { synchronize: false })
|
@Index('IDX_month', { synchronize: false })
|
||||||
@Index('IDX_originalPath_libraryId', ['originalPath', 'libraryId'])
|
@Index('IDX_originalPath_libraryId', ['originalPath', 'libraryId'])
|
||||||
|
@Index('idx_originalpath_trigram', { synchronize: false })
|
||||||
// For all assets, each originalpath must be unique per user and library
|
// For all assets, each originalpath must be unique per user and library
|
||||||
export class AssetEntity {
|
export class AssetEntity {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
@ -160,9 +160,15 @@ export function searchAssetBuilder(
|
|||||||
builder.andWhere(`${builder.alias}.ownerId IN (:...userIds)`, { userIds: options.userIds });
|
builder.andWhere(`${builder.alias}.ownerId IN (:...userIds)`, { userIds: options.userIds });
|
||||||
}
|
}
|
||||||
|
|
||||||
const path = _.pick(options, ['encodedVideoPath', 'originalFileName', 'originalPath', 'resizePath', 'webpPath']);
|
const path = _.pick(options, ['encodedVideoPath', 'originalFileName', 'resizePath', 'webpPath']);
|
||||||
builder.andWhere(_.omitBy(path, _.isUndefined));
|
builder.andWhere(_.omitBy(path, _.isUndefined));
|
||||||
|
|
||||||
|
if (options.originalPath) {
|
||||||
|
builder.andWhere(`f_unaccent(${builder.alias}.originalPath) ILIKE f_unaccent(:originalPath)`, {
|
||||||
|
originalPath: `%${options.originalPath}%`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const status = _.pick(options, ['isExternal', 'isFavorite', 'isOffline', 'isReadOnly', 'isVisible', 'type']);
|
const status = _.pick(options, ['isExternal', 'isFavorite', 'isOffline', 'isReadOnly', 'isVisible', 'type']);
|
||||||
const {
|
const {
|
||||||
isArchived,
|
isArchived,
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddAssetOriginalPathTrigramIndex1709608140355 implements MigrationInterface {
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE INDEX idx_originalpath_trigram
|
||||||
|
ON assets
|
||||||
|
USING gin (f_unaccent("originalPath") gin_trgm_ops)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`DROP INDEX "idx_originalpath_trigram"`);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
export type SearchFilter = {
|
export type SearchFilter = {
|
||||||
context?: string;
|
context?: string;
|
||||||
|
filename?: string;
|
||||||
personIds: Set<string>;
|
personIds: Set<string>;
|
||||||
location: SearchLocationFilter;
|
location: SearchLocationFilter;
|
||||||
camera: SearchCameraFilter;
|
camera: SearchCameraFilter;
|
||||||
@ -32,6 +33,7 @@
|
|||||||
import SearchMediaSection from './search-media-section.svelte';
|
import SearchMediaSection from './search-media-section.svelte';
|
||||||
import { parseUtcDate } from '$lib/utils/date-time';
|
import { parseUtcDate } from '$lib/utils/date-time';
|
||||||
import SearchDisplaySection from './search-display-section.svelte';
|
import SearchDisplaySection from './search-display-section.svelte';
|
||||||
|
import SearchTextSection from './search-text-section.svelte';
|
||||||
|
|
||||||
export let searchQuery: MetadataSearchDto | SmartSearchDto;
|
export let searchQuery: MetadataSearchDto | SmartSearchDto;
|
||||||
|
|
||||||
@ -41,6 +43,7 @@
|
|||||||
|
|
||||||
let filter: SearchFilter = {
|
let filter: SearchFilter = {
|
||||||
context: 'query' in searchQuery ? searchQuery.query : '',
|
context: 'query' in searchQuery ? searchQuery.query : '',
|
||||||
|
filename: 'originalPath' in searchQuery ? searchQuery.originalPath : undefined,
|
||||||
personIds: new Set('personIds' in searchQuery ? searchQuery.personIds : []),
|
personIds: new Set('personIds' in searchQuery ? searchQuery.personIds : []),
|
||||||
location: {
|
location: {
|
||||||
country: searchQuery.country,
|
country: searchQuery.country,
|
||||||
@ -91,6 +94,7 @@
|
|||||||
|
|
||||||
let payload: SmartSearchDto | MetadataSearchDto = {
|
let payload: SmartSearchDto | MetadataSearchDto = {
|
||||||
query: filter.context || undefined,
|
query: filter.context || undefined,
|
||||||
|
originalPath: filter.filename,
|
||||||
country: filter.location.country,
|
country: filter.location.country,
|
||||||
state: filter.location.state,
|
state: filter.location.state,
|
||||||
city: filter.location.city,
|
city: filter.location.city,
|
||||||
@ -124,20 +128,8 @@
|
|||||||
<!-- PEOPLE -->
|
<!-- PEOPLE -->
|
||||||
<SearchPeopleSection width={filterBoxWidth} bind:selectedPeople={filter.personIds} />
|
<SearchPeopleSection width={filterBoxWidth} bind:selectedPeople={filter.personIds} />
|
||||||
|
|
||||||
<!-- CONTEXT -->
|
<!-- TEXT -->
|
||||||
<div>
|
<SearchTextSection bind:filename={filter.filename} bind:context={filter.context} />
|
||||||
<label class="immich-form-label" for="context">
|
|
||||||
<span>CONTEXT</span>
|
|
||||||
<input
|
|
||||||
class="immich-form-input hover:cursor-text w-full mt-1"
|
|
||||||
type="text"
|
|
||||||
id="context"
|
|
||||||
name="context"
|
|
||||||
placeholder="Sunrise on the beach"
|
|
||||||
bind:value={filter.context}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- LOCATION -->
|
<!-- LOCATION -->
|
||||||
<SearchLocationSection bind:filters={filter.location} />
|
<SearchLocationSection bind:filters={filter.location} />
|
||||||
|
@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let filename: string | undefined;
|
||||||
|
export let context: string | undefined;
|
||||||
|
|
||||||
|
enum TextSearchOptions {
|
||||||
|
Context = 'context',
|
||||||
|
Filename = 'filename',
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedOption = filename ? TextSearchOptions.Filename : TextSearchOptions.Context;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (selectedOption === TextSearchOptions.Context) {
|
||||||
|
filename = undefined;
|
||||||
|
} else {
|
||||||
|
context = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex gap-5">
|
||||||
|
<label class="immich-form-label" for="context">
|
||||||
|
<input type="radio" name="context" id="context" bind:group={selectedOption} value={TextSearchOptions.Context} />
|
||||||
|
<span>CONTEXT</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="immich-form-label" for="file-name">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="file-name"
|
||||||
|
id="file-name"
|
||||||
|
bind:group={selectedOption}
|
||||||
|
value={TextSearchOptions.Filename}
|
||||||
|
/>
|
||||||
|
<span>FILE NAME</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if selectedOption === TextSearchOptions.Context}
|
||||||
|
<input
|
||||||
|
class="immich-form-input hover:cursor-text w-full !mt-1"
|
||||||
|
type="text"
|
||||||
|
id="context"
|
||||||
|
name="context"
|
||||||
|
placeholder="Sunrise on the beach"
|
||||||
|
bind:value={context}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
class="immich-form-input hover:cursor-text w-full !mt-1"
|
||||||
|
type="text"
|
||||||
|
id="file-name"
|
||||||
|
name="file-name"
|
||||||
|
placeholder="File name or extension i.e. IMG_1234.JPG or PNG"
|
||||||
|
bind:value={filename}
|
||||||
|
/>
|
||||||
|
{/if}
|
@ -173,6 +173,7 @@
|
|||||||
make: 'Camera brand',
|
make: 'Camera brand',
|
||||||
model: 'Camera model',
|
model: 'Camera model',
|
||||||
personIds: 'People',
|
personIds: 'People',
|
||||||
|
originalPath: 'File name',
|
||||||
};
|
};
|
||||||
return keyMap[key] || key;
|
return keyMap[key] || key;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user