From 1071396a4a7a4b42db2599e5e97922e94f4f099a Mon Sep 17 00:00:00 2001 From: Ben <45583362+ben-basten@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:15:37 +0000 Subject: [PATCH 01/46] fix(web,a11y): remove autofocus from input fields (#8857) * fix(web,a11y): remove autofocus from input field The autofocus attribute can cause the keyboard to unexpectedly appear for mobile users, and override any other focus management that the application is doing programatically. * fix: always include people filter --- web/src/lib/components/elements/search-bar.svelte | 2 -- .../lib/components/faces-page/edit-name-input.svelte | 11 ++++++++--- .../search-bar/search-people-section.svelte | 11 +++++------ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/web/src/lib/components/elements/search-bar.svelte b/web/src/lib/components/elements/search-bar.svelte index fb301b65fe..9d8c8854be 100644 --- a/web/src/lib/components/elements/search-bar.svelte +++ b/web/src/lib/components/elements/search-bar.svelte @@ -28,9 +28,7 @@ - import { type PersonResponseDto } from '@immich/sdk'; - import { createEventDispatcher } from 'svelte'; + import { createEventDispatcher, onMount } from 'svelte'; import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte'; import Button from '../elements/buttons/button.svelte'; @@ -9,11 +9,17 @@ export let suggestedPeople = false; export let thumbnailData: string; + let inputElement: HTMLInputElement; + const dispatch = createEventDispatcher<{ change: string; cancel: void; input: void; }>(); + + onMount(() => { + inputElement.focus(); + });
dispatch('change', name)} > - dispatch('input')} /> diff --git a/web/src/lib/components/shared-components/search-bar/search-people-section.svelte b/web/src/lib/components/shared-components/search-bar/search-people-section.svelte index a8f0008450..1b7c8c2fad 100644 --- a/web/src/lib/components/shared-components/search-bar/search-people-section.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-people-section.svelte @@ -43,21 +43,20 @@ const filterPeople = (list: PersonResponseDto[], name: string) => { const nameLower = name.toLowerCase(); - return name ? list.filter((p) => p.name.toLowerCase().startsWith(nameLower)) : list; + return name ? list.filter((p) => p.name.toLowerCase().includes(nameLower)) : list; }; {#await peoplePromise then people} {#if people && people.length > 0} - {@const peopleList = showAllPeople ? filterPeople(people, name) : people.slice(0, numberOfPeople)} + {@const peopleList = showAllPeople + ? filterPeople(people, name) + : filterPeople(people, name).slice(0, numberOfPeople)}

PEOPLE

- - {#if showAllPeople} - - {/if} +
From b21566c2fc9ba82da2b6a8ec863086a72c47da1f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 07:52:29 -0400 Subject: [PATCH 02/46] chore(deps): update node.js to d328c7b (#8829) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- server/Dockerfile | 2 +- web/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index 8ed5344395..553084ed77 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -25,7 +25,7 @@ COPY --from=dev /usr/src/app/node_modules/@img ./node_modules/@img COPY --from=dev /usr/src/app/node_modules/exiftool-vendored.pl ./node_modules/exiftool-vendored.pl # web build -FROM node:iron-alpine3.18@sha256:3fb85a68652064ab109ed9730f45a3ede11f064afdd3ad9f96ef7e8a3c55f47e as web +FROM node:iron-alpine3.18@sha256:d328c7bc3305e1ab26491817936c8151a47a8861ad617c16c1eeaa9c8075c8f6 as web WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ diff --git a/web/Dockerfile b/web/Dockerfile index 8659c64277..a25ac2bfac 100644 --- a/web/Dockerfile +++ b/web/Dockerfile @@ -1,4 +1,4 @@ -FROM node:iron-alpine3.18@sha256:3fb85a68652064ab109ed9730f45a3ede11f064afdd3ad9f96ef7e8a3c55f47e +FROM node:iron-alpine3.18@sha256:d328c7bc3305e1ab26491817936c8151a47a8861ad617c16c1eeaa9c8075c8f6 RUN apk add --no-cache tini USER node From a3feca2580c72492d39199763a9b845c73e96660 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 07:53:00 -0400 Subject: [PATCH 03/46] chore(deps): update node.js to ec0c413 (#8833) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- cli/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/Dockerfile b/cli/Dockerfile index 18f38fb4ab..17799b8506 100644 --- a/cli/Dockerfile +++ b/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-alpine3.19@sha256:7e227295e96f5b00aa79555ae166f50610940d888fc2e321cf36304cbd17d7d6 as core +FROM node:20-alpine3.19@sha256:ec0c413b1d84f3f7f67ec986ba885930c57b5318d2eb3abc6960ee05d4f2eb28 as core WORKDIR /usr/src/open-api/typescript-sdk COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./ From c227f9893e74be22deb3eaaae5561d8689bee844 Mon Sep 17 00:00:00 2001 From: Ethan Margaillan Date: Wed, 17 Apr 2024 13:55:07 +0200 Subject: [PATCH 04/46] feat(web): un-stack from the photos page ; fix stack count (#8419) * feat(web): un-stack from the photos page ; fix stack count * move stuff outside of try-catch block * small optim --- .../asset-viewer/asset-viewer.svelte | 22 ++--- .../photos-page/actions/stack-action.svelte | 39 ++++++-- .../components/photos-page/asset-grid.svelte | 9 +- web/src/lib/utils/actions.ts | 3 +- web/src/lib/utils/asset-utils.ts | 99 +++++++++++++------ web/src/routes/(user)/photos/+page.svelte | 17 +++- 6 files changed, 129 insertions(+), 60 deletions(-) diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 4fa7d72a2f..46c95636d0 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -12,7 +12,7 @@ import { stackAssetsStore } from '$lib/stores/stacked-asset.store'; import { user } from '$lib/stores/user.store'; import { getAssetJobMessage, getSharedLink, handlePromiseError, isSharedLink } from '$lib/utils'; - import { addAssetsToAlbum, addAssetsToNewAlbum, downloadFile } from '$lib/utils/asset-utils'; + import { addAssetsToAlbum, addAssetsToNewAlbum, downloadFile, unstackAssets } from '$lib/utils/asset-utils'; import { handleError } from '$lib/utils/handle-error'; import { shortcuts } from '$lib/utils/shortcut'; import { SlideshowHistory } from '$lib/utils/slideshow-history'; @@ -28,7 +28,6 @@ getAllAlbums, runAssetJobs, updateAsset, - updateAssets, updateAlbumInfo, type ActivityResponseDto, type AlbumResponseDto, @@ -481,20 +480,15 @@ }; const handleUnstack = async () => { - try { - const ids = $stackAssetsStore.map(({ id }) => id); - await updateAssets({ assetBulkUpdateDto: { ids, removeParent: true } }); - for (const child of $stackAssetsStore) { - child.stackParentId = null; - child.stackCount = 0; - child.stack = []; - dispatch('action', { type: AssetAction.ADD, asset: child }); + const unstackedAssets = await unstackAssets($stackAssetsStore); + if (unstackedAssets) { + for (const asset of unstackedAssets) { + dispatch('action', { + type: AssetAction.ADD, + asset, + }); } - dispatch('close'); - notificationController.show({ type: NotificationType.Info, message: 'Un-stacked', timeout: 1500 }); - } catch (error) { - handleError(error, `Unable to unstack`); } }; diff --git a/web/src/lib/components/photos-page/actions/stack-action.svelte b/web/src/lib/components/photos-page/actions/stack-action.svelte index a857da1dd3..b6a034672b 100644 --- a/web/src/lib/components/photos-page/actions/stack-action.svelte +++ b/web/src/lib/components/photos-page/actions/stack-action.svelte @@ -1,20 +1,45 @@ - +{#if unstack} + +{:else} + +{/if} diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index 06b5627d1b..a84b9d4d73 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -89,11 +89,10 @@ }; const onStackAssets = async () => { - if ($selectedAssets.size > 1) { - await stackAssets(Array.from($selectedAssets), (ids) => { - assetStore.removeAssets(ids); - dispatch('escape'); - }); + const ids = await stackAssets(Array.from($selectedAssets)); + if (ids) { + assetStore.removeAssets(ids); + dispatch('escape'); } }; diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts index b6718e63a1..ecfd29a8fc 100644 --- a/web/src/lib/utils/actions.ts +++ b/web/src/lib/utils/actions.ts @@ -1,5 +1,5 @@ import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification'; -import { deleteAssets as deleteBulk } from '@immich/sdk'; +import { deleteAssets as deleteBulk, type AssetResponseDto } from '@immich/sdk'; import { handleError } from './handle-error'; export type OnDelete = (assetIds: string[]) => void; @@ -7,6 +7,7 @@ export type OnRestore = (ids: string[]) => void; export type OnArchive = (ids: string[], isArchived: boolean) => void; export type OnFavorite = (ids: string[], favorite: boolean) => void; export type OnStack = (ids: string[]) => void; +export type OnUnstack = (assets: AssetResponseDto[]) => void; export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => { try { diff --git a/web/src/lib/utils/asset-utils.ts b/web/src/lib/utils/asset-utils.ts index 72102d2634..50337fb06b 100644 --- a/web/src/lib/utils/asset-utils.ts +++ b/web/src/lib/utils/asset-utils.ts @@ -2,6 +2,7 @@ import { goto } from '$app/navigation'; import { NotificationType, notificationController } from '$lib/components/shared-components/notification/notification'; import { AppRoute } from '$lib/constants'; import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store'; +import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { BucketPosition, isSelectingAllAssets, type AssetStore } from '$lib/stores/assets.store'; import { downloadManager } from '$lib/stores/download'; import { downloadRequest, getKey } from '$lib/utils'; @@ -269,43 +270,81 @@ export const getSelectedAssets = (assets: Set, user: UserRespo return ids; }; -export async function stackAssets(assets: Array, onStack: (ds: string[]) => void) { +export const stackAssets = async (assets: AssetResponseDto[]) => { + if (assets.length < 2) { + return false; + } + + const parent = assets[0]; + const children = assets.slice(1); + const ids = children.map(({ id }) => id); + try { - const parent = assets.at(0); - if (!parent) { - return; - } + await updateAssets({ + assetBulkUpdateDto: { + ids, + stackParentId: parent.id, + }, + }); + } catch (error) { + handleError(error, 'Failed to stack assets'); + return false; + } - const children = assets.slice(1); - const ids = children.map(({ id }) => id); - - if (children.length > 0) { - await updateAssets({ assetBulkUpdateDto: { ids, stackParentId: parent.id } }); - } - - let childrenCount = parent.stackCount || 1; - for (const asset of children) { - asset.stackParentId = parent.id; - // Add grand-children's count to new parent - childrenCount += asset.stackCount || 1; + let grandChildren: AssetResponseDto[] = []; + for (const asset of children) { + asset.stackParentId = parent.id; + if (asset.stack) { + // Add grand-children to new parent + grandChildren = grandChildren.concat(asset.stack); // Reset children stack info asset.stackCount = null; asset.stack = []; } - - parent.stackCount = childrenCount; - - notificationController.show({ - message: `Stacked ${ids.length + 1} assets`, - type: NotificationType.Info, - timeout: 1500, - }); - - onStack(ids); - } catch (error) { - handleError(error, `Unable to stack`); } -} + + parent.stack ??= []; + parent.stack = parent.stack.concat(children, grandChildren); + parent.stackCount = parent.stack.length + 1; + + notificationController.show({ + message: `Stacked ${parent.stackCount} assets`, + type: NotificationType.Info, + button: { + text: 'View Stack', + onClick() { + return assetViewingStore.setAssetId(parent.id); + }, + }, + }); + + return ids; +}; + +export const unstackAssets = async (assets: AssetResponseDto[]) => { + const ids = assets.map(({ id }) => id); + try { + await updateAssets({ + assetBulkUpdateDto: { + ids, + removeParent: true, + }, + }); + } catch (error) { + handleError(error, 'Failed to un-stack assets'); + return; + } + for (const asset of assets) { + asset.stackParentId = null; + asset.stackCount = null; + asset.stack = []; + } + notificationController.show({ + type: NotificationType.Info, + message: `Un-stacked ${assets.length} assets`, + }); + return assets; +}; export const selectAllAssets = async (assetStore: AssetStore, assetInteractionStore: AssetInteractionStore) => { if (get(isSelectingAllAssets)) { diff --git a/web/src/routes/(user)/photos/+page.svelte b/web/src/routes/(user)/photos/+page.svelte index 00409cdc1b..f711b081d6 100644 --- a/web/src/routes/(user)/photos/+page.svelte +++ b/web/src/routes/(user)/photos/+page.svelte @@ -30,7 +30,14 @@ const assetInteractionStore = createAssetInteractionStore(); const { isMultiSelectState, selectedAssets } = assetInteractionStore; - $: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite); + let isAllFavorite: boolean; + let isAssetStackSelected: boolean; + + $: { + const selection = [...$selectedAssets]; + isAllFavorite = selection.every((asset) => asset.isFavorite); + isAssetStackSelected = selection.length === 1 && !!selection[0].stack; + } const handleEscape = () => { if ($showAssetViewer) { @@ -62,8 +69,12 @@ assetStore.triggerUpdate()} /> - {#if $selectedAssets.size > 1} - assetStore.removeAssets(assetIds)} /> + {#if $selectedAssets.size > 1 || isAssetStackSelected} + assetStore.removeAssets(assetIds)} + onUnstack={(assets) => assetStore.addAssets(assets)} + /> {/if} From 3a9df6dae8b1493cb79ea17a0938ef13e3dfc42f Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Wed, 17 Apr 2024 08:27:04 -0400 Subject: [PATCH 05/46] refactor(server): immich-admin list-users (#8862) --- .../specs/immich-admin.e2e-spec.ts | 19 +++++++++++++++++++ e2e/src/utils.ts | 18 ++++++++++-------- e2e/vitest.config.ts | 2 +- server/src/commands/list-users.command.ts | 12 +----------- server/src/services/user.service.ts | 5 +++++ 5 files changed, 36 insertions(+), 20 deletions(-) create mode 100644 e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts diff --git a/e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts b/e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts new file mode 100644 index 0000000000..707093ab25 --- /dev/null +++ b/e2e/src/immich-admin/specs/immich-admin.e2e-spec.ts @@ -0,0 +1,19 @@ +import { immichAdmin, utils } from 'src/utils'; +import { beforeAll, describe, expect, it } from 'vitest'; + +describe(`immich-admin`, () => { + beforeAll(async () => { + await utils.resetDatabase(); + await utils.adminSetup(); + }); + + describe('list-users', () => { + it('should list the admin user', async () => { + const { stdout, stderr, exitCode } = await immichAdmin(['list-users']); + expect(exitCode).toBe(0); + expect(stderr).toBe(''); + expect(stdout).toContain("email: 'admin@immich.cloud'"); + expect(stdout).toContain("name: 'Immich Admin'"); + }); + }); +}); diff --git a/e2e/src/utils.ts b/e2e/src/utils.ts index a46653eb11..617f2d62cc 100644 --- a/e2e/src/utils.ts +++ b/e2e/src/utils.ts @@ -43,7 +43,7 @@ import { loginDto, signupDto } from 'src/fixtures'; import { makeRandomImage } from 'src/generators'; import request from 'supertest'; -type CliResponse = { stdout: string; stderr: string; exitCode: number | null }; +type CommandResponse = { stdout: string; stderr: string; exitCode: number | null }; type EventType = 'assetUpload' | 'assetUpdate' | 'assetDelete' | 'userDelete'; type WaitOptions = { event: EventType; id?: string; total?: number; timeout?: number }; type AdminSetupOptions = { onboarding?: boolean }; @@ -59,13 +59,15 @@ export const testAssetDirInternal = '/data/assets'; export const tempDir = tmpdir(); export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` }); export const asKeyAuth = (key: string) => ({ 'x-api-key': key }); -export const immichCli = async (args: string[]) => { - let _resolve: (value: CliResponse) => void; - const deferred = new Promise((resolve) => (_resolve = resolve)); - const _args = ['node_modules/.bin/immich', '-d', `/${tempDir}/immich/`, ...args]; - const child = spawn('node', _args, { - stdio: 'pipe', - }); +export const immichCli = (args: string[]) => + executeCommand('node', ['node_modules/.bin/immich', '-d', `/${tempDir}/immich/`, ...args]); +export const immichAdmin = (args: string[]) => + executeCommand('docker', ['exec', '-i', 'immich-e2e-server', '/bin/bash', '-c', `immich-admin ${args.join(' ')}`]); + +const executeCommand = (command: string, args: string[]) => { + let _resolve: (value: CommandResponse) => void; + const deferred = new Promise((resolve) => (_resolve = resolve)); + const child = spawn(command, args, { stdio: 'pipe' }); let stdout = ''; let stderr = ''; diff --git a/e2e/vitest.config.ts b/e2e/vitest.config.ts index 9b9670c042..6b1db353c5 100644 --- a/e2e/vitest.config.ts +++ b/e2e/vitest.config.ts @@ -10,7 +10,7 @@ try { export default defineConfig({ test: { - include: ['src/{api,cli}/specs/*.e2e-spec.ts'], + include: ['src/{api,cli,immich-admin}/specs/*.e2e-spec.ts'], globalSetup, testTimeout: 15_000, poolOptions: { diff --git a/server/src/commands/list-users.command.ts b/server/src/commands/list-users.command.ts index 32bcc35d95..ea3e745463 100644 --- a/server/src/commands/list-users.command.ts +++ b/server/src/commands/list-users.command.ts @@ -1,5 +1,4 @@ import { Command, CommandRunner } from 'nest-commander'; -import { UserEntity } from 'src/entities/user.entity'; import { UserService } from 'src/services/user.service'; @Command({ @@ -13,16 +12,7 @@ export class ListUsersCommand extends CommandRunner { async run(): Promise { try { - const users = await this.userService.getAll( - { - user: { - id: 'cli', - email: 'cli@immich.app', - isAdmin: true, - } as UserEntity, - }, - true, - ); + const users = await this.userService.listUsers(); console.dir(users); } catch (error) { console.error(error); diff --git a/server/src/services/user.service.ts b/server/src/services/user.service.ts index cb9012d641..9d40a14e5a 100644 --- a/server/src/services/user.service.ts +++ b/server/src/services/user.service.ts @@ -37,6 +37,11 @@ export class UserService { this.configCore = SystemConfigCore.create(configRepository, this.logger); } + async listUsers(): Promise { + const users = await this.userRepository.getList({ withDeleted: true }); + return users.map((user) => mapUser(user)); + } + async getAll(auth: AuthDto, isAll: boolean): Promise { const users = await this.userRepository.getList({ withDeleted: !isAll }); return users.map((user) => mapUser(user)); From 7db07bbe615af186b55949cd4a58bc72cb8decba Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Apr 2024 11:23:24 -0400 Subject: [PATCH 06/46] fix(deps): update dependency gunicorn to v22 [security] (#8863) --- machine-learning/poetry.lock | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/machine-learning/poetry.lock b/machine-learning/poetry.lock index 6b98b8f521..ce3c8a180a 100644 --- a/machine-learning/poetry.lock +++ b/machine-learning/poetry.lock @@ -1150,22 +1150,23 @@ test = ["objgraph", "psutil"] [[package]] name = "gunicorn" -version = "21.2.0" +version = "22.0.0" description = "WSGI HTTP Server for UNIX" optional = false -python-versions = ">=3.5" +python-versions = ">=3.7" files = [ - {file = "gunicorn-21.2.0-py3-none-any.whl", hash = "sha256:3213aa5e8c24949e792bcacfc176fef362e7aac80b76c56f6b5122bf350722f0"}, - {file = "gunicorn-21.2.0.tar.gz", hash = "sha256:88ec8bff1d634f98e61b9f65bc4bf3cd918a90806c6f5c48bc5603849ec81033"}, + {file = "gunicorn-22.0.0-py3-none-any.whl", hash = "sha256:350679f91b24062c86e386e198a15438d53a7a8207235a78ba1b53df4c4378d9"}, + {file = "gunicorn-22.0.0.tar.gz", hash = "sha256:4a0b436239ff76fb33f11c07a16482c521a7e09c1ce3cc293c2330afe01bec63"}, ] [package.dependencies] packaging = "*" [package.extras] -eventlet = ["eventlet (>=0.24.1)"] +eventlet = ["eventlet (>=0.24.1,!=0.36.0)"] gevent = ["gevent (>=1.4.0)"] setproctitle = ["setproctitle"] +testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] [[package]] From a4f805e99ba86a8b3e196473888d593d3ae1acee Mon Sep 17 00:00:00 2001 From: Alessandro Vitali <98644809+alvitali@users.noreply.github.com> Date: Wed, 17 Apr 2024 20:59:09 +0200 Subject: [PATCH 07/46] Update oauth.md (#8794) Removed closing brackets from oauth redirect URIs. --- docs/docs/administration/oauth.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/administration/oauth.md b/docs/docs/administration/oauth.md index b273f27712..6fcc47d6a4 100644 --- a/docs/docs/administration/oauth.md +++ b/docs/docs/administration/oauth.md @@ -52,8 +52,8 @@ Before enabling OAuth in Immich, a new client application needs to be configured Hostname - - `https://immich.example.com/auth/login`) - - `https://immich.example.com/user-settings`) + - `https://immich.example.com/auth/login` + - `https://immich.example.com/user-settings` ## Enable OAuth From 8573c846056ed56a804848c5ddcf37bdfca54531 Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:47:24 -0400 Subject: [PATCH 08/46] fix(server): include archived images in face detection (#8892) --- server/src/services/person.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 77b7e552cc..2cd3cd88a9 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -292,7 +292,7 @@ export class PersonService { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force - ? this.assetRepository.getAll(pagination, { orderDirection: 'DESC', withFaces: true }) + ? this.assetRepository.getAll(pagination, { orderDirection: 'DESC', withFaces: true, withArchived: true }) : this.assetRepository.getWithout(pagination, WithoutProperty.FACES); }); @@ -424,7 +424,7 @@ export class PersonService { this.logger.debug(`Face ${id} has ${matches.length} matches`); - const isCore = matches.length >= machineLearning.facialRecognition.minFaces; + const isCore = matches.length >= machineLearning.facialRecognition.minFaces && !face.asset.isArchived; if (!isCore && !deferred) { this.logger.debug(`Deferring non-core face ${id} for later processing`); await this.jobRepository.queue({ name: JobName.FACIAL_RECOGNITION, data: { id, deferred: true } }); From b74f8273c2ad0745e18decb95aa59721b17253d3 Mon Sep 17 00:00:00 2001 From: martyfuhry Date: Thu, 18 Apr 2024 15:11:00 -0400 Subject: [PATCH 09/46] fix:(mobile): Updates old IMMICH text from the mobile settings modal (#8906) * fix: Removes old IMMICH text from the mobile settings modal Removed old Snowburst One font from the pubspec Removes SnowburstOne.ttf file * Uses immich text now --- mobile/fonts/SnowburstOne.ttf | Bin 66404 -> 0 bytes .../ui/app_bar_dialog/app_bar_dialog.dart | 33 ++++++++---------- mobile/pubspec.yaml | 3 -- 3 files changed, 15 insertions(+), 21 deletions(-) delete mode 100644 mobile/fonts/SnowburstOne.ttf diff --git a/mobile/fonts/SnowburstOne.ttf b/mobile/fonts/SnowburstOne.ttf deleted file mode 100644 index e29832085c1cd29dc552841c22c203d7a5e96b34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66404 zcmdSBXLwxIxhTBWuG4$d^ghxwjb=tO>KbX(d$DE7Ro#{?7u*43V=&m54#7a^{e6C_QJKSeRz5Ep%r^}Aw;X7?tA^imQ(_9P2jJSfVe`(t zfA78UGw3Uc5X;POotn67+Z&%F#N}X4hc-{_-A3Id4M6)Cw0CZq*gW&pFJ7oYNDI$% z^V_!W*!k!yC!Rz|Q3cN%w{4%<_W3dQ&*9m9(EeJ45iS&v63z&qKt9kF{5PR*As&4f zU`C-7YC#*J{-k&k(c}qO$VeW>kX%`~Hvf0=%fi#pVCfH@{_5WpT7%%=N(+DDjxm2n z(fkQ+Z{a2G7<(HjLt`>NM$MKNPm9+n>pn$dk{;+$aAvAG6nS*+2F9lhe8hYH7H+)u7~ms*p4uEU|5NAXPm1kI8mmWDy-e1whC*eMS{wAsWHYIg9H+Y?hmXc22}} z=Ux~ZJ%RBRV^4};c2mwA_Ov*2v}apR*Xne3=j4OkIc|96XfA2Vm5zQt=p%!ApeeC+jhXJi!I4Fc^bnr?E#)?{xzHXW%YI>);@BBy=T%w%4Fb(PeN=Lti22 zjf1-kT88S78+xIkRVi8v_cM!YVud;#>Vi4|Fnj|+!8b+$xPws)4$W8UHwg8nf2m&y zePdWde6e+LP7+w8WxL+%duxO>L;(%+u_i~{-~e;CG70WICWIGzdza0PC}19$|F z!+2X^yz^kZ&tbghs5hy%zZg&JG&!BZc-8+hp8AaOriJl}bV(IHEi4rNv+!2o&B9L$ zuN7V{oGLt0c(8Cs;r7DU3s)DeC>$vqDx6gix2W7nUsyo-UmJ{Pbt1 z|8e?Hr+_o!`In<9A;9<(%X!t`gSu|A)V= z=v@3J^)31e<~-&pR>5xMR&jsgJ^U{ICjOtIHKNDG8S#gbO;V5aCg~@#owB@KC-=+0 zrf5*~DSoZ|iRub zjn-4PwYK-{8|{Cw=N(%duQ>h)xI6Bubv@_Y{e=9N&c{92_`n~9Dln=yA70!x3C+Nf_$(rQ3l^rQt zsw{PP>hAP>`q?U3)mzoet6#6t*ZiP%zII>j>vie6*Xw>?pKml|1~U_x9hr+V*Jr+w z`DW&oX14i_<`0@b%gVEkY$%(~c4n7n*JS6i=VmX@-j+R{{cg)>D{7Usnp%Ucsn+J! zXWMqRUEFp<+x=}%xBaN?bo;v1^rj_f4%?Rf%69k2d4&i z4IUi4d`LeO7^)r?4L>#f-0+*j?+^cNxo>%4L^@&|@r;y@G>-I-tQ*-hvS;MNk?Th8 z8+mf%#T5^%_}1t@$5xI#vC_5jyp?yX{Pn7yRo`3PzPf*PVa;7@{=T+v?cuf8ul>>5 z(`!Fk$E|a&dvg4S@#n_hpZL>c`{eNCLsPFzzc};MZ29bsvv;i5tUq`CV;fW(4sUpC z!=L8v*cjPZz44-rk8S$q{5Lm0v-uxeTDR=pa@Ur3w$^ODcTERYm=9t&eQO<^run(fMnOU@jok0Vf1i9F3;eGyLB!jY?y%3qWYIF(xIIP2l zO6i;7%%Lt06+WWP=t@qDrkEkLj;)0AN}-Q)nD3)5b_jK{HRw3?9rPSMU$~YTL|f@~ zsDrx<-N+tAH^JJDFju1qCXP1JZ=lQQW#|^>S(IRV(Ur{0h0nPM3!iavlwv+bTGout zhx3zk8TyoY8r{L!Q7ysmBK)5K*i-B_bS`@xDg!uQM%vN4(T|v3^b&U;+QJ+|ck$<; z&-p*2+X04%)Y0EZU!$HyE$p|^F8Us1U|4iBtmk@I$DQnp=$i!QbU6yq?MTKPL@)6p zSjL;s5VsE<;x9zs1Na{yu>UWQ9Y_b`^a{8j_*gsu7u9gwPMs<|0A~*HMK9rk;N#2# z;Ch(-ec_)3A7>r}AFK)uF+W7r1or>c!8al=hj0||dCr*!y&0WDZ^jWgSHN)r^f4mf z;y=+f%>Q<9Z=pMwe?!A4P(c>!%FXJKp@M;Pl@h!*fq)>?@wVU6=JMvU5y z#IzWF#yo;9=R)WQ^e+H!*}}E#4jBJRw3Vp@nkhrWOcm;29xi;JF&BO;zyxSSpi!U` z5sfaAxY5OgPRT)E3a~1k2Z3$}=}h52mgXqXFVN5u-4fbis|&y90Vn(jswHDHYtbqu zjn=@?M?HZ43UqWS%Mt@Ng3$z zH1ipHgO#E;m~Ud8aJ1o&=+Ed)$$E60c?NyXJcDIy1&Xqtqk8d4v_f(v`i0QZ%KR?;6K1EFui=Y-$k_!IE$4P;!=7)tS5QJ2Vrx`4jd!aC-F zWW5tUzz6C1>bYe_l8Z(&19B5f0(6c01ie*@i z6XQIp~-bFwX=^qDrvX(x?hmqZ;7!I#iDu zP$SBqCe)0wpr5vauHBA0P$%fC-KYokqCV7*2GAfHLc^ftE=MD11sX+Tplhx|tHE|# z3mWn`n!qOXAi4`3LKlH`cntjkU5u^(P5Bn|4RkjyK}XRq1TFP1=&$H<(BO}tXVE+8 z&*)ZkKl&7Xg8qi?L=T}C(evnGGzs?LRcIRh5Y3<$&`ao5^fGz{{Rn*kcEL~4kI_%i zakL)&2f7;l9Q_Qv4m$6L=#S_UutVq2CbSvNgPwak+KSFX+t7Bj1MNaP(QdQ{{X5!= z&Ov9RbJ2NdA3BBZ0R4AAIv*WGAEA%YQ`n61*n+K~q1vz=>_I1Xp*OG_d$1S#upczm zQd|ah$$RKGIE2GEf}<#hPT+DJ16?+b6X<>PTbu;zF$MO+H_`9V@6o-WRafI0^d|Z> z`XzcB{R+K>-o>@J4%efb&?D$1dK^80zKr3*cO&`}x(6(fo6&vfO0ZzBLr>x++>EoJxwqmr+>SeNC+@=CxCi(0ySB{5 zVzG+FbA0ifSUe{e&y|bk)Z#h4c&?&4mQg)zbW2Yw-IA?RV{7N4wGe6nKk z$%?9C4;9ta&@i=pm~Lt9Db_T#@DrOSrnYb0BAuF=+dj2x^X&Z0UakddCU)|z#aqmV z$%*Y;=gztL=^1WLIPsmuTU2KkHMddP_2skDjbD~!-BVjPZ=RT#nwr_NQ?}_V7wfmr z%xsyT*fKpg#q>;X-O0?u7uPp2wQJ`Lw?#O~`#0>`vVLOwuFdlkyLQUAo_WO$2~A`} zXLh>%j0hLl8P+Cr58exW2RjFcqBwUMIg%%zYD zA^}xEyI|KcVkkeu{BE&~k(qgUu?%)Q^Xf;24lselq}ox+?o!W=e$#MlYa zqXuMWIY^KxkRx+&y%Wk&IOagcL_xxA2RT#@^05sh?S#@WlxCr{ z2TefR8K^0?n1}NeTyKH)Ga%`lAfvXxc?McJq5TG-?J!8;9=Ps@kxAP#d+zy44`H}B zB0NpzMn-pn#E%Mx^Ng8&b=;-dOn`Kpfam7ndlJ6MJomsgnTHeV$hb|g1Ca7gfQb|8 z|KH)W2m0ItbDD$MO~E&r>24T{j7NGSYu_WlbQ83ig3@N;3G&R2#d`AWPLSXP9!qV8 zp>8X*oCFvWX;0u@gff}=yl~Gc^hciE4r?g3A&@6norN>O0GYvF;pwFjW}z*?66ud% zY8#vh7D?+#n1vJi-Xh#5Sat%8wg}fn%ZcMkT^8E8l5)C{F5fIWdLxm%q75)G4cv467e zt*}3n=ZZ95#L_I(7P+N(Jt?#xFx(~dNJgK9u?amm;UMszgL~wP^!VS;?SI0@ze4g% zswvV5;YR|A?J(=5brFsx*Tpq0TckxN=uTS!8vojBKdfyAe3e7+y&h&=gyVn3t1&@K z`*P01usT9-OB6)-?qB;S(t(UX?#37OyswV+-#+} ze5qF=WB#YrlV_KB{D0!tGSF$ilrChhMNTBVKydw~^y~xJ6MQ>?d;WD5fgB3woa3{R z91c6Dow;WQa*Tgvj%i&r>hicOM@OBxfq_w1E;D9v=Bh}kYHZAzleA7u=Ss= zkwT2LcxGVK2_E*N6VPN}bR24&LL*X2ky2{hG7jEyOAdv`#ymMRFgi0f7RgazC-lSk zCSVF|c3?EedYW^br`h6ijpguoBu9rmFdygi33jsCN$xx%!Bo%%#jNwF^C)z8BF6fT z4vvlwSSE(XMm=M2FEcy}H!NiKi$g_nOgJZkzIiwhIkGuE3yytJHuMO%A+zJnIm3(< zX2gc!9+?!?It8JE$?=v*j$5ijA}Y- zh(V8{pvwc#E|mc%!t>CFaBmFe*ABC4AJ3gX`6V16mZbHx#|XllY5$-a@K@TaOA8FpWw+^7d{~(XMOmDn4Asa93T3>9NP$E8{ioej7`oZFg7`x zVQg}?z}Vz$g|W%m24j=69mXbS2aHY5P8ge$5{uLo}6aJ zN~kxIbBA-TP|g*MrW1`}10Tv<43 ztc&D=|HWNE{uHzb0sN4`=Zrer1zw_jokx$hd)hq{lMsY!ek6#oUIzn&fo%*xcknOV z&2g=vnWIsU(^+>Ex`}*w+Zh!afFvO7-x_k}#tD0521id)PTFZXN%?8>SaUOwvl#du zhJwL7ZR0s63p5JMfkH*$LuJROJvll%F%3jdWhX399v=f9fMydgFbFwM+eDSc1O2oS z%!#u?U(iLdD;NQo94-JHp&Sbw!IEd;IS_5Y`J{LF29aj*xW>Nh5fB$8>v6&utbcJm zo;p}&xlo-G1AjQ3Z5{ww2=E`t#f0U<%&0}^pk<>`XB`ONB1J7i1cv_td_I^=C){0( zVHV|+XAz(Nq8i}-Wp(n4 zYN7SgqhD!L3q$IQlmHS4Ft3f~N&)+I|4n^8&^Oj;b7jz`A)E`rsWO}khn713%Dg(m zk04YD6YPQlCWY?sBUq^Efr3zz3g;rBqd-)IdX55T{mc3Qoza{d9$rqmOF?0TbVmv+ zNOz<#O1dM3w15{vwM%Fu6banzi~{Qv!PQIpNkij4(hn*0lYU5Hfb>HORUj=u=6=Bf zOBne-(|G&;8(9-%5`j|Zsj|4vATih2;%uA9Y^z{O&1ANukR`Jvg%&bfQpkh}h2|Qe zSWU38L>R5)*=ne5BhQjTJ9(BAI>@u6P(u*lA{0i}wg{vzU_MBCsDWlfqz6(MCOweC zGSUMnG!mG%0_L1;ut6x2?%JuZ|;(}{2{_C?c4av?OG5=x}$v``{VXTrIPFPhGh3!&+Hp+uT)5K5%!TsV{y z&E#nBz+NJ>BOq-l^xcAyx`)T$S*%3qOi}`oYw%WKmZBi248A~yrkQTUFmykMX@<@Q z{0Nmt{FQ+OJmNEG)x11}1MYx7l}e}5+N3|=uS^<^hD0K6(Cavk=k%10Ga8Hr?5<3b zMsNeJJOTVdN1ry(VB=*>msb-@)yIdeZdY@;(pHCQR-Vqk>RaKFnj~tgPct@k1h40$ z#$KOHqo<$uCyn*tuvnwi>fGssA*}!Wz9FyDts7c6VKz<$71rvAVKWD@>H8Tw{SdcF zevP z@L*rBH>jyKxvRWhi$0`HRR)rYI00fJt*HbM1B?^KIDusm#ysy!C8G?1J=CXCOR(oT zJ`K%;hP=)I_<)B!JWZ;WDxn9c40vcA89>K*0-i-okwy$pb5O0=r}x+fSU^?+C& zuKZg)OcYMB@Qx)i>j*YTor0p+*GIc z(w6E-zGEBx>DNoABjvHM6!2f?2qjf3<_SYa#AK%uXL9N z4H0|kPbh;cYffy-)EZ2bQ=6a`p2C#Y-EK~7?nsF|j7?j?lL?Zei5CJO@u?;i?JE>X3@tEiEG#C+( zYa?2Lc%JEJF~g`E#X!JHl)-3bx+Pm%Qyz_ky&ktqAx9hWMu{wxCfH3H?UX=y8f}zH z64WRC{y@N=P606jO%giL@dlu}bV{$|c?Inz1PT=8_t1pXp^`Kqz3_z6cn+wDkY5_4 z2G4O=QwjHt2CY|}gr{DxYdtoXN#S*EU8~|RKdUMh8of;H*1Fvu^JHYLQCi~GnB&Y~ zlln44Qf8I%B89~uDY1!7noPU0Zhvyu+~jdaYtf1Q4nst-ys@`lRmSq%wl&EXqZi-G z-;$`^l)4KmZ;iyeDlT{;rBhc%^p(63QdH^&_MUUqtiM-#-b6$TijGb%k(vWidtJq? zlp-N1aVR}g*1EV;E-_{_5mvSEysI<5^W^O_>q>6OxEw}V+^Ci^I)f$dY}X}9+y}L6 zTIZx+!JUsfW_BNJ`urPZ*nY((hn?gI9WVTdyPo|y#C#+On0xd zef@#&T;7{mHu^ZPlbak~-H-<3kq&j^#v@<5F7uR~*Lcj09i}QrIIIg!FWkFMd)AOA zR@Y={xM6&-TjcP`Ox4sKx^(Qox3B2U438ZHymBQa-n~j+8MFS6EeA1FV_rQ^w2k-9{k83Y*xDL-K7e>8yn1( z`xaCeS*hRaS+&!WS^wkuQn!;brtEcB%r2By9lI!T*rG4?rdG5j@yfQdX02<7JiTRm zR%c3O0hhu?a(`|toaBDa-3T$SwV74RVNcw-gMvJ$U`{oO3SC81ORo2eZ z=E2~>gBhQCPpQknm>d?lSFU%RwRd#c-inRodbu{8(qKoM*67^1eebUFv-RSY=>tE( z`{(_*reasAPvg|vwKl#a=+PxBC~dsmclDFo`V)$fQ^~PGv02|(yRypUH}}Ed3T1c4{{8DpgFG(_1ftGm^`4Nn zMyY1Ck&ensUtN85**NCb?s9<)HLC57P-r4E5(^j`RP}*W{BGbTq8s4%AgXLa?uAXAxzQP+fn z5eoP@oea?a0Hp=Q1pJ9O5t}-KN}AGYFyL%ao&p-Fiy70;Hu~>)WzE%ghb&gIt|F-o z8cHLk*Nzc$@XBYNA4)fQ=~+L=G6Ypn|>Z`p{Sz<>SiQ>Aw|xB0zFr>TUOOV{T2 z=g-amB>zhOYOjTn$^Ef-SYsp8dLchd9}-o8kI?;8x1P50HO&>?dmd9sj)v{D{XZ4XmED!k^G+xMx%CmTM{x_ zO&w#)d#nxmvbxgLWv2YTGF!l{h`B;CUMkWW@b&n!pFtwyrxB(^XHa&Gl$1ENhgMy2 z?^BaM&A<|#gV@caY&_notOQ%_e-6_J-ECZhp1M{EQ} zBn(J)Ghoj&kOX1jMcs*Tp0II(fQ`~iiW#sPfPz3nFNozu{1Uz-wRUzSgT?qFxNzRjtoDLz?k?Y4^P zGK)s6t=Vjg=;M^fDK$_mEqZ8N;x+lo63%4OGh^KAXlb!cUOutB*-`G(lyjJODt)|! zm3^Y%y}Ak?ZA?tp`}@2UJzZI8#V3c<5|g{#DD&&JoX+DnZfqZ+uted|H+NXaqsC!V zjkCNYt7*Ayr9E!dNWj-3QTjEBa*aowD%;#~`SRMeA!m7|2J|3Sp&K#ANK4 ztU;^Nuf$qgY;8>a#F4LA%JpfDOQaI(70b$E%R+bc#>$O=;TsE|vHO|dLk>fGrX^BF z0q-_&EX9bJSsv38V0s#lAdwgYLa7zfqMk#c=BE0(%495B8UUBA!(>p%L_F%Ix@EGE zk9U)uQp*A^KzkLiL(CYKjxwo443uqL&A9QJ7mnm~wCznSeA zDv9y=D_k+H&k&I)n4yxm8lT5d-jI*-Cc8_t4yn9?&blZu-e(W_{Uv@*?$ZYi4UYT) z><{~?_%sxjLA<(9+M zN_BZjqc3D+MebdLmfr8Gyy=!M&+!(mQ{pvNRhCsbB?_5Y%o~X`cpLwTIY`?fL!~kk zw?p={Lj*FEXfO<>zhNT?-`ov(+13AmQ%%{+GQN3JHWe#m0)>*GgzB3Qc4h|9GxttV)3R#_Bf#zun|U$=3xtk>k=8I46_ z2*f%nnk#$~S-O96(^>PAr4wo<|Kk>`NvhNXFH1SKyNcfY{%a%mMCu}DY0_y4TT3*4 zhPH`JTkm@1y+6M9>hP=HfQs1shYJ@oYq@ohZB&k6??aHsw4Mb+pKN`w1laiu21B`_ z+-fSZajFo@5xCI)%1Tw5n3kZbYj_Hbevn$6S_NuJDy^YGOMy=e^h>&J$H{ZXAJF1U zn;!qxv4+FmlFHWn)=0QzsI<4xe{^5sa#zV{X3VA6D}BzDA^TY`;-9HiZoJ{ct>>*+ z`(l1aKL3mSZ`jM2w&6{$<^Qqp@;54}$<+Yl8?o=<{b?P;>HwdGzZ5>8HgT808Y~%; zPeS23Kn$KGd$$Z_%A6Ph5WZ@DvHxuCSwt~04kgaU6bOfWmSe#pH_WfBeb6r^ZTzy=fp z#u=vj5lu@Qfjbazhp+~Wl5w9`ua!wq7jEZdAtS6s3-%J|>>dI7fB<3tB^z!71M30g zfbl?<$#aP~3ri$4Msz~hrHLl#Ih`?UU&I6dY-yTXy>>ut;xMI_noX6KW~-qy?5@a^ zj@`C5w)t;^_pS*nH~3=D<7n3Qhb`qqMujF@1!x`$r!CcK!z&-rtwpVeq+?6Q$)P_Nv%eSjVcZN|#*8!pl1t zonQEX`4+bW@*Bo7BNjk1s9+#2XogsPI3Glt3K! zgGH<6G8F3T?QYLD)zzeuk#G!NzlJ~ zcheO$hp*goysWIJUmbSMV?}=Rv{g}S(AX$mX)~s^F>82uc3b7QcD2*m@}FL$q2I%2 z9r)dQ*B-CbRjGq2u}x*?rJJ=IYb;WqCRmf+|Ma(~2E}Tp)|&L6gF|MuB5Nogo3YfD zs{#zoaExLFqfh?fYX*VCmlf_|J^;(58gc-$nM^IFDFRpE4+c$(d5R|Tm&T(2TX`cV zq7dXCW@>7{>seP*SD6Tvc|9dYy-q4d)wl|j9TuPhtO81@uLz$s0{u=$3ueb6g$X;M z-{YYGqyjUM3J=T;Wh<2s`gV{M|$t)ukis}I<5;2Boa=SzDCUDdyo1Q zttOS7r9^Zq^}U4#LRIC3e=@go*FzTLmdvJf3QRRGrXl+qi)2y?fB+^OC*o+)G`ttY ziSQ&m!eKNCWJYlaNRIeOfD;7*WBY;Lz5vO z&e}>m2BZcqlcI|ZJa9ZweFf4|1^j|J25KM>8c>>GJQxxTan*q$0q#(+*F7$GfYJdm z0yPpNoM_KP?;~Kmd~LeQTnRdhiM5nx%2q#lZPmFyX}@J{ds5d|rW3K?Kay+9J5zXT zdvk+}6`B0jV3$At$;JyL3cfPrdFuFy-qMkdYu~uzlFF_4C(3m8mJ9mNyFOl5BbH)1))daT{9CHsINx-K+aRr}u6bn5XQvtu zuJ$yEz<{C9J%z{U|K$DvIjC7=&sfNg0yYguWAJ03H5fT;Jc|-I!4OM|tdDH*gl>dQ z!>`g1IZQr9g&1xL!iwV=8oYx==TVVGLGLwFnx@#~^@rwmUV7CNo5r`qN>+94`*o$l zEK-`KB_fSXRO=U8Eb_`~RsQ?4p0h00NUh_VtL9eBKe!#=JiOl9F*$$V3+w0Hy5*DK zn}z(!!0zYoFVlK_UGC;`y<)XW$59$Ntu#u1n%{X*{)te{q29W%qHVnG(ksR`mpfr$ z`wQ=YBtH&&k27Nvm`E^;$ZDm;ZY=fN&8QrQh4u1MkQc=b2394Q^63=j0NsKsOz`c3 zwO~l35{zke+1`&Xt<$JC?v>Hq>#pru;t_ z{*izAP|Dp9uhqHitIaB%QYM$0!~2;AJe-Q?8&g*WQht@6yv=s3aEe*QT?SbkY1n^Y z*KaQahc^fXviUDsbx4jgjrA4f8WmW)+Qo?yZy&L7mM{j$0J2`f9R{3Lf)WQyA(E65 zz7ybbCgRi|P*BH88wX0O2PEaIL$mLGgZwdRB~ihyLyOd*7R1i{BU>+jI5R zFSQyQBEuhg!sQztKRG|Ozva%v!TK(IVC29|pDkF%D)lXib1%E~$EzoDCvS|IWGY9j zHNO3})B7*5oA=LCZv>a0HGkEOcc!n{5go~|+jj9evH2}C*S$ENY%+EQTYy3*3ssDT zi-Ro-dC-}rDvWqoJov$QVgZsIZibx(VF1E`!z;ifq*;0x7HdK%m4sCbwj2?3L_QJ_sa4>Mg-_8fM|KWD1~gE#fe<*fBwiRo4fM4MG0#b8 zMbbC=-5XM8owM`4Z*AX^|K7ISN~2DLPDNXEA$NU+DmhthHCSJ}^Zv#;U$Z5=w)eiR zfo0Pp)P_&~O%5`q##B`qpL_hiP3v#JEC2CAKL4AUtY*GjZ*w*J2EzU33Qi=OdJN|w ziq^O1)}H!cVCdqj2D9W%x;G0CQz_<$ngC?rAo?{m2j_nv4TL{W(XZtKA$WWBRN)^` zfVP4q!ODwm86C9sfvPAbZ5bUjAyW70!ez`~xOv!hBAH+Us16`ZgI-LdY1n#*JXi+6 zR8znp(Qv>g5s}@6CF(c83m_?|%Or|HzzH@MSk)=wWr@RnLR^}_`y6jz39B$qRGMTB znvl+?v@5g{wP($N>$;xok+mJX_J-yo4N6u=Q%sA zn(MUXjit*$#r6ztJg@QAc3u|>t=V>VYHPDs-`S*cca094jUie7f_2S>1-C>^B;2J~ z#%yMGz^c2^zRcM+DeR$k2?WV_(3-Oqltk7Nqj+fqgEkMVW+7s>8cAeQsbuw{^A-|056jA5K}@TIdK=tWb%F_m&vmoZOxh5>Z(*S?)G|pF1O$9 zROrD}B7h?MNHHb^stMU+(kXyb(N+TR(f?PCu3UK5*&EQfWQ^Qui8dQ|e|&GFrKX&< zNxhuduMeA=?El9W^kE(Da5`FTwq4OgpPmw_n|M`u&*c?PZF=D|;a;2dU+(ErK!2Dn z9AT@u4QeH-WIts;c?K8hiTy;NCkpK?Jju|!3*KVrM>Ux=SdU<5!A@5+wqV0%5SaT6 zBka%`Hq+QZp|;kB{>EOr%~GP*qI#-c1UnAzCVp)}gCTnlvA+m0kzF_7=LFpuD9uP@ zFvvAx7#jo)Cg9gD8iuet2Z}*AYG|m;qpOPf23?tw8o#&ez;i1d<))xEFxcjH=o=%w ziCdmnw#ia|-gKh_ypcv(T0ZLROtqFs%{Ei1zDnX-h974AR&}`Xt+Pg!xf{KM_sw|P zj@JxFG(JXWGswN!RsLOG^>AS1>_^X?8d$zEd~Hlwr%(j6T>h^P<8VM7C@nWNt7T?* zH5=XzE|~eBbE_cR_7c>S>AaveOoLVf+8zs3NKX@QG=|6zsMDb7aFd`i&%k~F??nTx zv8*89)NJPBi_Y7(W%J}jduvU#!)A1e6(I~1Wv7YrQNi$x&r8ETE$rP+5Oc71lieGD zDx4P`fN^7r7#YO4BCu3&e2f6oKs zW?fl*%4hIKjOqBm<|9M-75QuP&rP{kT=#>mKmQm3YE3?f$pWf&8dBZN5KE#4Eg%I^L?x`nTKDktwc4=5DTCQwWZK@KQj5(Z z-^qnL@4iZ;2*XP{GORf|E*b9em4cJ9EF>4}IxoQ&@ugu;d#Zh?+rEXR4nG)IDEc4Y zoqzZgV^=$>McL*Zy-}sCwnnn>qm)`Dm#|u=EG9`ALk@e`JYZjmKh58se@ky$Mz3Y? zMOa~Zi$MPI!hYsg-2L#@)GX@EwCON+vV0ZfLMIo38YF40^3bC6a>?51SjIe2oTt{Y9w)?!;oQ1UQv}4~VrXIQ=26 zDyUjSPz3Chc6!M6w6s<)WFwWzUjj2&p!NA|CW=L%fnKwr@DJ| zvh=>dOA{;eq-Z?Yf=#90`QA@Uq|HV?uq>SrSANN zLqtWmpzsoVkShh5Kbl!?0Y8A#L9twI4aJK9;55AZNyE;`vP1<7SFB`r6*nzX78}Qyw@uB)EmBi4g)YI0Ibge7={I~DFy7H`d z9xHq}ruWzAJtAkSR;*zpoSu`$JLkLac^5x@>TIrDp>n$$riO#w!LU@}ZQs~_{SWi^ z-+l3jzAIHTRec}OUwh#v>^yfhyuZ?f8Zxyu&`v8cIH*Aa5aJ_o3y430stTfh6!;%p zcf@}rV=_&ds#Lr@SO#RI)u?%}O9hgv^b2k}BA|uz5uiG<;qLk)RcM16c|s&vbDic{4D&4g!rl^_w+rlpY_AjT&JyW+U}YB zdO{Dcz?D4HU%I^gqI@%}v3vdMGUYQkLRtS{GWVDa4yi<8HMrX=y@}Od8@zW#{^O61 z@AbDRH7iP$Zi?5jJ3f2$v2#zZ`RfyDVn%Mvf5>cu-&N^D)0v4DED~44oy?RAr8BNkm^esOF$IR# z9lq$|>hm2|XWJvVMeCg_@!D9*rE_V@smJ8TeEyD;HCC(J+8kQ5Hat-!8^SM5;*!5U zaiFwQO7(PRtFH9fy;Wtcv(3j&P%>;Ch#s=L196W5eg+7mJ>VJnE%zgU%KFT-4`K=M zP9(!tLJ~Mg^AKLlbzM0J3W3MU#RsZvDLfit9$c;>*} ziqXh$PiK8?I#nJ8-}CHxexaWS_^&Yl;dQNn!Yp3JM!^^>Ro7qpIry<2UVmxOzCnH2=#lYPV5b&6e#1bESZivjN@nJ(dLrPHv5s$genbA3zrTBfJ*!gT!M5Kq zOsFCyQyaZ2Iy2QRLAjBm2z3GU=HVBkTDd&DhYWc!L}TS?5yTlm%7M!gVttI@`331O z_~gb$2KurYjoMVEQQIZjqQC=xCGoX=DeXXS0o$8qe5!xF0ZJ|f6h<~&K>&h!Lt=g@ zEfb&!7=_AtQ)vX3up&VUR+eXLt4rl2D*Pfb4moC_Nf`RlSb>^?k>TrmiKa7%evhE!}yQ#*FX&x#^7hn4v6t+mAoqm6r^g zrA>Af5tK=d-CF0+t)n=LYT=14k4rZSwS@K_F8rM`a}MzLxH5L|O9@$>SlUQp+=O94 zaV$jv18B=~68o$Ka~q(q=K2<N!KHY0Hn7Q>%J@VT@Z`>M~y9cI&{R z2aRPrdlR<>-kAp-u`6k#dC<=}ke)ol^3&vXe_k{!h)iL3mNtsPZ9x3(kY2ki+f%W%8Yt$( zKR{$AxOI5qjEB!6Nk37!I!HCkI&{In{^~AcA~=EN>@DB<(+a&*?AANfNmsj76`iQS z;e{tXxB>5sZR>T(B;m4}5{*Y9PxFqv^@PzPlIYEWx*NRDzG( zV#r^*D^Zeg#nf&w1M5JWAX2s;e*K{t)n%$(pm&!6L&2_3!HzF@0l=pOo~3CJ3lRJm zCh;->pb*-J-5dfx&A3^Jmlaj*%A{5ZngL}4X%`_0f|#cbEC|jsH*D}e1q65z&HCv< ztJSOYDJ_jvbxrXd#tP?H=t10LH1@Nu;M z+6K1#cwY}>4aeLrlR>XhOXZ<7WGeulYg3CxI!P5K{z>31t=0hA4hv-UMKshy%vop)!NhNofk>6CDF0FgEr7?AaPYWZIE`EgGzZxW5(X4v1traJ0<4*_KGL| zVN#2ACX?6`IPyX!(-iBz$l>C2;aDuHqn$6dT5Wcv$K+tl4!tWI%HNProxeHKv*X&^ z@9z1n#xM*nLQZZmT0)izg}-KF zYM*iK2%ez0d~1GlSL;NdjsconQTPRS75f{|oT@XaGLMGD%K;@Iy}-Xoj6WfG4j2KC z6a;xWPQztdv&o8BIYa|&))Eh7%KLOgT@-j5EVdZPTOc!Gdm;jtfE%MkJOF9*P#Y%+ zw!qG0&k|BPqdYXH^x!;>8Spi0TKC!Nz!PmyYjt8rvsUkqUKwt!cMt2zAT;}cg{Fs+ z@%pydTz{LtOf8WslyX*z-72%bYUQ3ommb;^oe-_l3>ZqaN|W7gQ9B>I?$O7VV_yi& z8MPrKmRcODKz4lLj|-o2%oWzIW=)BVZS#ji6E{Ei`p;Iq)Zhwv^-Y^DyXC$+uBq8) z`utM8Iao4D?B=<`>ue3@1C|URU&akeIqruINZJUoZc2a>Fsn{$w`l{qKpEr!g}g;~ zInicZ#Ji;8Sp(6u)hb^xa;8_Y1}63H%Qqa|6JC=$4nL{F-+Jnid{@KvZ8L#o`CCWV z!!Hnq#<6-+>$cKmht|9RKk~8a7jND5Ony^-+lS|2Tg$fPPht}lTxTq)ndrRZ$>OwNkQjFlm zq=fQ@l}?~3kzB`j*qxyR-cqrz%xdN(*tPrEgTKydGgUjEym?>5Za1jA3{hF@0Jd$} zy|>Ehh-X~YR~-zm-c&wRBh&h|r6r!f;B%;Jx4IiWWopQll9b7#PMcV%dFz5@n>^+3 zWq$Bj;{_L#w=MJ?NE%n#K=jdHOGL}B>^!-(uR>vJt=xL(wrz2M+L3~RQ-OT;fv+Dh ztR+5vK|T?uKNw>KcEccuq$pzyRF^TTkPm|I0Y5=7kvLi-H~@G;-=Jm`H!Q*lUIVcd z)DSL(yPR4NSyhbZv3%jC-9LN!;eVcf;0J1aez5vf)p=DeQ=8wQbnp_1abn?nXI=Vm z>(?JT^^bkikJS%v7(N)c@B9h=Ic|IQ@dtj8zucSu+gqL08#$leYiJnI#g|vh}E!NTSM`2c^xA|mR zW9YzDF8x4~RAC%EZ*1$_kqfHrRjYQ@|AtapTvLX0c#TVA7e$qczRC4dWos<1OZ83%?P`Sc>+C##%l4QvqpWaAwo`vT;LHVTXuhQ+OIncWpT5 z`xUm&|7bMSJER#Af%TTc1bq)gj4~*eiC8cs#6YG3z!(fqcs~II2f!CZ2K=Bc(OK(j zt15#5x6@>Rpb^QLCLf~E3r`ZatPVYAX14B4V*WNu}C6izhWoR7bJW}SNwtX z6MH(hFCTQYngR-LOgQ@tZr1c-`=keMfJ(=jkU#yK%MNo8AnO3q*dc zu9Dje8V!kLkf;&3YspSdL$(i1!(usZsVF818ATnv88sPvz80USLoLyfWF{es0iHv* zNASviY05#WqTt0M3?|$nj`)C!TGRj`en!>Kxodi0mt+ncSmTR=$|-ndjUkuZpr+!R zj?_14Br5wC-q;eSLTk@oYW2vaC7OUW$cnAD%C-;$^<{nVj(f=6p6VXxtS^%pWSqDa zc9G`np8l9A6_P2r{E^RpR1!ACRW2X-RaaNx@9eAGcK9_avL7trcnQ5E=?s=zR~?Jm zZ5kC(_=!|t$UmSIyeNJ zx{IFt!M+>p)r?#>plVFs)LZV5nmEqYUNQd+c3yP3Lg}xros3vUxmHQsk{#Q3-8}=3 zUVYB_=TvOE_EEfrnh0skZDz<&x5@3g^74{&+n#}ZbN<7jlkiJSYp8R|bOv84!b+S# z%Z2v~e_)TX{{&whST%KkUrgS>fyEQ|6cG|2F2Jgfgn|L1-UVhCUVNtuws>%05~h!G zfU+nJ)C*Z4L`@~?mC-;PA3)4n1d(>8wNw`v4(#k|G`JMrWVWNrv%l4}Tc--WAe?CU;LLhcW{TI2EC>=u{Qu5&q6devNh$De0<$LweDn=|mP zAgG?On|D_k89*p(m#{}dhA>=^O#l>uFP5BbZfXpd*(@&dz6yzm6BQez90)XuSaXnD z7$60L>SC5!QBVr+GXMZI;O^FFf!B$qBsh}jla)w9~%^M>6 zjM^yAzrN5k7RMjpzv~SlmkZ16TAfL!QR#oO@Q+{mu?4)8soD}E@#KXca@E{M&?3QF z1h+QFbXPhg47}+B9$k{iMRW}cd`Xi4PzLfAh&rimgiQ2B6Q{MMp)M2zr?ye0Bpx={ zV~W{X0rK7eUVQxuHXY$bK-ggrA*cqLBqRyRNN;R= zP^>G_fBsBuKBi(QMa-hDKF?-}4AgFFIr@$KP$b(JS)cORg9_T~>Z`bBC5V8U;N zu!!OzaGbz!KyE3Zx5Z5aG;<<}faLT>qY%0zZ%I-PS5+4+%D-9NY^e6w74DgCbFI#< z75g=}y>EE$QG6wm8#r!wMX4ySjazXgx9cTt9ank&olib_qVq2A zLgG1pNNaK^)dnSPl!wjjzNQn@f6PZ}^8SUt%~D5xa9%@6m)_{r>M5&v0A6-oz!LDF z8@%$^TqjfZ>wLnda$rxnk z^59^*l%|aw&852TEOTp@>HQK~WmM}V-mWT-$Gow)(_1>*wNw#E5a_@HR#C9B~IZhm> zn2-+X2}vL!6<8n`vY{nQNq{Bv`mxjnK1v|==>NR;jubnSN%J^@#TSg10WJfZ2Bpq&31Zb+@aIRlf&3ll>RM_g8EoQ)Z|VD&5C9PO=%Uev^is|K4Wo7P1IkK-m0Lh zc8kAJm-7`wbbvZR-SG@{3r*`>5u3c&`cP{kmx(9!ak0!AJ-1YDJ^w8w`Fy=l1bzb* zUNr*n-NHC2Fb@=^ZHBQ0bV8QfWz(nxHB=fd2VrhDiFJ8cN8C`jT3kRr{b8=22f0<6 z4pZb)unaz_|LVFxz#r-w8jDN-4sVnFyam+))q(oL zpJJ@#-{3r(N_yO>4P?y*@BuVbsm4RA;zF?}&x$=^gQ%O@e2hBRS+4&;`8QJOI))*i z{$}wj^enp;wIA-11G}9jTYsg5i@+9wHbXq5L2RZcA04F@BpbkCg+8)pKat4WVDHQ( zrNWeQcH0vPUqE}SMZ>Id>+{X|P*q({6EqdnHd`j3ot5J!1qxT}#bT*V|{53c1bOaOb6Uhua!F?|<&$JsX6rhdq>ceADIkKU{pd zY2eIf?z^Q{Ek4^}lE;iHkH%W?h7VU+%ZLB`XuZ>>a9JIZnBPoEtFi%=t8u3OimUg{ ztJaQOwiPonxBNBXo6HB`_YM+|sWu2EG6^R6>>39OBin$4r&VT#kn_RX;@sXzcb`GXg%?p;uVpGgCZK8j? zZTFhSh$0ruy!68sLk-!R3j(?5=b}HuS1y$r{a$4FF)Jb(CPxfjwpc>qY$Ug7r21Kw z1CNmBA-R2=0c;<0)D-qnZha&gw4?aL3N)A8P^d|S_y&*gk}V+|fXNM(cW0+fZcA7V(Ujq(YS10CxQ4fO2|t7}KSn&{!{g7$Fs_WBRsn3!odyV3ull-Oo9cyuyL#5)maN|j$k zz3E%`c(fKaZDVVcR=Oikwny{!{%cPRbxm?Cki&wgSSk7=^f5`a%u)WZA!rJSzZm&_ zE;B7!A`ehq#$Qadpsl05xiMdxuJ(D|PN4q;Y$ZVknHoZo!670uz!7T@YvCvn)4@** z2NP%rBb!ij3FQM0GyjrUSwA1?sD9}4w;Z{#-rczA+V0nW^%M}1LRYXowR`_)!RhP_ zC&O})BxqaL*xFNm&+GTU|GH~_{f#XM^h{2;(2QE^dZeItD&3lbMcX~{@WfA_ziE?i zLv`Rt>4C-GwAukl4yD>*ZD{EV$g-QY>XxoCO7ZYRZ+z$Q!9n+E(y5d}{LYQuHGt zh&VW5)}u8BNDh%ZayW6EW90YZ4mF!#uu%j5*+fXWCQ#cdFs4jLod{t7CdSYp@w)-sV7gQP`05t}pe;)i%eR zE?^H*|9bY(dTr3RzYwUYiPuKt&_}*$OO;8cm3JGeZ`ogMSJkV$N*KO!a|bIASuF}@ zW8JnzjZv?RH=O%R`;b{5mf`oqx$G%lC;E%{0AjPPg5FYBH@U5l$7{x+gh#~fLXe0c zc#n9VLP}sGE@Ny$k%-%bA}Hj*gtaX#Os^du8|d%qtjz>`IHzDL5aUcVFmS;Vn4had z;prE-ZQkOBfkY@ z*m0{wA28~!aF}Jbv}f}CkEh$4+B@uxiBRXkPj%a>L|tt+kG5s>K1F6Y;d2Btp@|zm zy>+KuH5i^eo*YZ0hh05Edyh{Qa**`ij=h~NPab}vU0*V#?@3z6re3}z*g0O<>8sM2 z?(*8@v`AjQ!7nxWZS^+{Ta$aDJI1iLpB9)!zhJdM;&E4cG3&&2ARxSuIH9nLB*y}7 z4+=@)y~fx##=4PB*hrh78}gDXcoo%4%N^xyuy%sQRX zN!a+yuhWA}0=h;&ewEQOphBTD7P$ByW;nJ!dUKWNm)SH*Ki~>#S-gG;HmsBo`myvp zTYvZRzljEzYoPp_E+uyq(cz=Hs~GS5w- zS{+}`M0|e^u3=aqA&rtfO=b}F`y>_+z?&vH4`Ph5r%Bk&8Cp!Sl3A(h|vHGsMP<8v7c;_s2=Q>MoSH@>~f@a#y#M8WD~%@(BhCTgtbb1y!pf1eeZ=#t4VHU$bDfx4mceQjDs zq=@?UdB<#AJ*!V>DC_X#mtHic*Ks~dH~Mtk&pM&H8Yp=YGm-+tMeIN%UE#~}ghRR% zkM)D>8UKy)u>ia-V z*xJTw-dERT($wj#Hiug)b6dNu8rl#RwKN|Bs&?;d*?_}i^odwx>-CwIljo|-mzkt` z8S9qSPZ}MzNk2)x+_ij!(TOJny9J}Ap@os&hICYb;1Z{a^d4YI)th7q31w%aos7q+xDOVo_A0rg(ZE!wc5;Hgo3 z9Ek{j$b8dlG@bl# z`CKNhjey{%44bUJ@pG@=u_L}Ao$Wq>HQB!WU2z82^B7?>`Mp{p@AAUQ6>)r4HHa_7 zks%ul9&T?pm_;GG&1&*$SrrMiF}-;XmZ>FM3s#NG4U>C?n_oQ~f8zUdsYMb+!KmSS zAk{P+E10j!q#k!?pUX3W9COU~T;zV_SuT;s6mK)E}2 z>9uv?nr&BZKT-ba_^<9uPgw%mfHR?jsEaWjUre>cHf-H<^s@BM^6y`{uKY?dX_=Cf zZ0t$&HJOEDXNEHI-_abgfq$x8Nfuw|zF|`Z_$#rtk~}20Elq!ZIySXsN7dr7{imi5 z?c1;$WfPkYmp|Pb>6uxGuK(%NAF=AY;=yRk=%rGHzb${{b1%K;q}i_1bqzl_{=K_4 zH?}AxwsdU%UaEPzeS=b(-a7rvk*PZJn{Fsii9+Jzf_79cx0Omc9~j{{U~mB_Fv8)Z z!rpEq0Um>pn+x<%)(sFBQ8zBJ!PZi`8ng_s`ULG%8&D<^gJU6U!!RxHoFyC_KF}j+ zV!XV1fH?9W+)F++fYSr(Lu&CZ${ycdTkYrryHkvs$Ye4)SJh_fD)^-kYNFHIj@f^Td!Wn9ix|RAy z*r}{Z8a;AV$Xw-7T0JrQB@2aClj|!@_w63ZX`DiZSzp!l)Z67xZm81r*z|st@J|L; z$5R{HB63|JKD+r}TMC%NZRKwZUl)HC{N_$UYpK~R<>Ek`11|9nunwLUNO>r6KsKkU%N=^zfVd07+ z#Rtj6YSBjvnOc>RR<+-J>hhVWsVleh#8VrolcC8M8=MW*X1m9)_PfJ#ZRN|SbA^UF zbIto}F5BR1uPgu9>vuXWzDD}VA(OEyyQ}!*b;+3{y=^tqA)8d@jwo&OgKxK`YePoR z#cR}{D?emrbbe>^j@Rn-wUOax&}frFH;@70b>b=PE6hkX1?_aZ6cG@yu)K-Z>xiFDwiv+=cxfJ^sl}Ui=YJa0E>w~Ec;E8d^ryvpkr@V}M6O_5#2mo@>Hy@Jk#k1E zWd&WT%V%rsHn2xXn#q6wBorbagNs0TzZ>B{c?{Q(vz54Pa@?&&E+{fHm<0L`uOB*1 z$!yM8b8_3XtLvZ{NZ++y@?gLMijQvV?^C&)BX8Cub1L7xu zhM?9#ucHyHHG!7KMQ{q0VzY!2$w}BNf{j6LO-9Okz-3nwF1u2tQuYayO4X1+rBt=e z&5n-^BIF zO2;t@asrTCNQ^geswKGl$q7R<8nIDYXL5uS>KdEJ$(U9B5p`3mw%=N(&iId11sO{< z{B;$ToUV0Z*X!^$Q_c_r-d}!?!Q`|S4$mwhMwYqF%9OQETiD)~`o`UlP$%67e||Xy zwIHu?Z>lXY85VuF-a2A61{9zvMudt^V{Py`l{8}0vbvT-R`ZBQU!=9=-+ueH$V{u+ z*t)~KA(_@FysZ7w0nku@g$e?qU1AsS>*BO<+%3d}xbvu4%&mYC3Z{riCr8{;Go_wx zUm@WwL|9!sn1`o{edPDRuOqu>6@eqAf`l?`%G7`jxbhO&N4Sp=>%q!!0o%$M6h21T zYuf9w-oA7h3vfW!7d(3vbuo67$uGxfQfKDoWeS)zVK{f9rN1My90G58xyRo?Y2uMN8tJpnm{%wiVEXQePZ%jX2F=y~yB;I{sfCr$7{R7TTi zhs9fBE^QqS2EZd+Nn7(S8xq%CT8sm2Y2ddPP7d}Pm$-#xLW1{n#Fl9DuF17n^oi!a z@LV93P+K$7$3CsJ4q9rn;~T_Pb*i|u<6AZjHB~KExW(hakV6IKID=7@yYzui&i#6L z=XRr;`YydTY^c*&y_C{zN-85-2*f z>+?++Z$3SjsBhaTVlS=M_O6>TZi_gj8k0`g7q06_bk;jevMRT%Ut@P3m6wx*X6m1Pw@$Zch%hicMS=870DxxqQ3 z4wFJ5yhPa&sZzui#9xZk;2Wv-n9rh}uj_O)neL#1KGrzQmEqgX0Z=;h_6@_j+2?z)aDyG8A{TckS09q7X~ zlC{tZoi|&TaHOvYY6gE%N69aBT~cE=cr-z+6QWH)wtPqV&S|AltB!=TDQ|jRRHv6y z&z1MawN~zC(=UHh?1cw8MEu7pWZ&czyFxp}B#Eg_2+;_bS7H5xh^rpdATnXtUHTBW(Rk#8*dojY=kUNv4B&Ykf__t<(W@43M(pL78XEreIUt zDYw$7!P06Z&bW2F`_;$G{rzd0aYlmn`gJ{VpQj#}6Nw%^Rz4}3WA8-A8B_!oDKW9t z93!5CCuuRzG60l-DS?zDu}yLaTw=Sgx4Sc$h=M&Q7^OxDD#;x)a?~o@g?K}p8N)up z$0w7;=T*3*3CB(*ba0oD^PLD_4k(SVB}}4BDC$VSkUzMY-F|cJ;ejH+wZ^eM2I33Az3MSbl-_u@V%#APC)!AGMYI@g&tZ!}nA0TWJaTIWiZKg}u9MIRQ; zQF|76-8y_YrQK$zmNRdEt~|ROO=&$wFVW#RQ$8gsu=hX*;ehCOjQ?y!2Z8ZdkU9!*6o+Abm9wA&o7zs|Aof-lY9cp@dyD4ZVHgttp1{IwU){%=90c@&7eAI~a zs+l|gT;&~E_FB<@ChSVjdQbB<+tSkemj+9|q1j8iHF{Q}i#UZkwcAgM5Qb_J?(wI# z4%BCA@q*G}C9(rt$K5-ux90OvD1e!3YzdoA?hBggW2Iitb(=jN zMS^_R%0C(gJub=DA1&{t?`3B2*=@pg;(h#Qr#~v*$9;C(6w3k8I_4YbLb+K072XIG zH0IZq;CG1UQ1^hvg_zSu^xte0z;UKYJuIHWR05($*oagSs&Ghs9bhk-FSH}Y611dk zYGQn>u_0euorp!9Iz+y)LJl>qo=E2R@BKb?^jKO%dFp(+9=pY(3QE)-jZ+=9#om-;_1@6XX_v~M zkE$b3e=9GP_n(@#m*T37S7E8Deni_CRwPuOk(d1jgIo*6*t1lP{?M^6e1M##Bg>=W zTiI&CwSxDQZW-{=LS~aq4cla;XvtP3(oR<&qy^GLlw1HA6p)-*G30d^V8FtSpl7i{ zk~EQ^=y4+!0ZXzGVGT>mHVULN0Sa&z!Do`AHR?tZ=AwcMYRXc%tWO}5$%pZRyqyvp zzI5;Io!d6dcXu`v!RU$+9Z%GnUQ1o8Bq>o~Cm}x9mj@2OnJ$vRLc|9*J(t(#xIAEm z>cIQ;Jo19K5vYuqJt4VBQG?2@B)9BjeNlMMia0(o0c9%J1Kvc^20_L^J?_#3EXDdr zG;1l+Vz#r%fn1Zz;+0BjW=yJ$J|SgJv?b<$a&o5NwQ7RFTC=6G%Dh!%aY&uYaGP&% z`}W>(rT3*y# z?hYH%4zEKfW7V23Q%_2CHjmQn-hERp(x+96sKKg@S6{XzG?r}ZOU0q$P6vWwl{De9 z#8qkMV4EQ^vEk$E%g2R`NUBdbbyB}wZfj_*s%8Tsr#2~!lsk7W%qKNLi_+FP7L+*k z)poVSTD{nhAVCTZmz%_msD9f?DvD!X=pzWnhnnjnLA^#Ik*>uUq$c^aBm*sTf{r25 z715B)rXWO4j;fFm2%!=xfCjS^b=!PVEJqkY23*j#WBbMpqr(=HIczRKSz}ehf*ee8 zY2`zjJPs}gbnsC;@w-XZtTqSba}(@!1$TzPwt;nt3`D{Y=R6!H=Q9+>nj0&G30@SP z2(~fc=IDJ!g)uk|r3qub@A%ezTVkjByowsF-YL_YhEGv@uDVKZwHjrKT1_BU-{MFQ z?V76}ZfS@c)tx0%Sby-QZ*19B6&tQrootA-j6{1zd)6&R=YHP#p__*z9;?D+&Un;d zWCTthqmJFWHxabQZ3qmb16hN4{pH(lp*&YOb;@E`>5QmEQfp%3`d_}VzpYcws61w6 zp-=A(&Uvreoi=#pv)Z(<^NxprPC_`)nvQO`o9dacb>hU{xcroOtN3NX9fA|3t2dS) zQncWqMeHJU6+{Oqp_(KXZod@u^yI`!q>@bnDI?t^K%pDcCqoS@07O151!@gM>DAOEgemb)>DF6rK7IVyk;@M3-@R+cw#`fPb2F2@-S$Ax z??aLq;T&lf!V*91La|q%5&6}}??}L!;hezzCwCNg=13{3o_xhcn(#m1fuKIhgk#Aw zTq`b{FNlZleu8j}Yy|v;)#iv@&2*05Q(je-RoWcpgskDaEc1U?pYckw)}QQnL%Tbk z+c51cnhVxa;0vGHU#C)A!Nnep>={#bd$RLOOG&4r_7D-I4XSxT+UQ{UElT(2{TDtO zDgOcHl2v+SriM#8)9MXS%1sj&qM}=C5la>xx#(;5RSL;Omo~^q!%BZKQh@8J-lox(o>#9;YQLpV&I5j?rBBV7Q z+v6jl{vXR9;5?)NdPAeStqN<-`AATb0qQ6~7Zakm0pUoINFyo*A^jEX4t9|!Bpeb1 z1pc&BD|^Rv;_BLPyto23a^lGOBH14@@xqFKMJOu&XiUYmB5J3f@8%yU4QQt z;hVh6MZNTHcdOhGt0unIUw`~JBNPssAthrlA_C}cCy1a3S&GFYy#OKO3`$i6f2&c>(8!BCs8?SeVYA+tnIIE-^R4N!Ja+I=Mbj3KN@ABvr7D9@Rd{1_(}%pz_B)%e+!*O= z$ZmbG$ve@n^-$Mt-}%J`YvkI=u5bGl@g`4kuPxzSjMoHRgVD|7y`{SJ;8dvl$QnM*z&@G$Oxd+&GkjWngYX&VPH<4%M7tLl97J!?Hxjewb~(Th5$Gr#L1eIjxML5nk=(Mi zS(e*Ni10J;VCnlM{U`F?0oLJ@$}O6%yL^4pSY)`dBj0xubEnE@4U8(QO^n%Nn6bN3 zcK?vNwc)^e2Fh1pV0|4d{2~;PKmiDnA8k536j*~#jlfb9hv^2kdYCP=MJG4PdG6AtAJuDX?%WxHqjMsy! z1|LGl%Q66l$I4eK9EpxTt{f~YHwF|Na&4^!>DUfff8wElY7Fa1fF%-v;TzPzpqWj( z_TNjzU;ONg*L?fmK3aaM{F&G9Y;=F1EjM?(_xh*rf9CSk*7Df)Yi@b(Y2kujyVdKe z_S`nVe&}XOICF}S*JHZ0A!B0YmMrZu;V7HBTurCvH?3cD`5i+}1EWf5wX&2>*zkkr z|Ik|%@*ntgd2hM@yPrFI!+##WTS-m6QvTV`K6o(DCTd$)^D(NOj;|+0mgnNA{n1Dw zF30e#zo7=Nt(HRIU23XQo6?(|b$uCQm1EDrPkiOzmTp_8xwVQsV z0Xd<4WMx`PNjG-H&>*fWuF8Zjs3CbZqDUgb-;FqgtWXRMJs{|CF~$f~g6-7ZMG1Cq zT|YZHK04C1uX|sy4wc~rPSBR(_7!4T%rgG2B&DG@i1{b>iYx;;nfSw_deD~Re1kkW zdyvQBy+)wv+)9zfK*p715J*r!-W>v>KtKh0KBDp}l5GS2S+O~9Da48mYLBhSU|^*( zX`AQTJLkun?A}25*3KJ(?lG-tt8c#8SpH7e+L2CsjaGBZ;k^#6$j=C+DD{n+8l*4$ zTeOJ25+PcblZI!#@zPLqWNkv9Wp1p|g(lR!TMy@MVKo+oqfRSR>4KBPev?+aHT&(s z8mF<)I=^*KZBQmY>eL|3>2y}-&plGzGZpELT8vX8^M&*6aebAkRo$i41&noi5q?rX z4!o?S653mkN2lTN2aS0Wxt$Y?GJC`?Xu_0-{v^U^hVtMXP=foxF6|Y441DGGQVS$O zX<-mcMr0CKNK|0pBWy$VhEVumaybg$Fzy)|c`HG6+-5PTv9JwvJy?A37IL{# z;X;#1Ja2Vgc0Ah7cn}(mVv;Al8Io|DO2}efLDgXyh;ZYhJ3A7z1jPWq0+YyfZtal*)&SCr|w4 zO!rMb%3Ux8QL9Th-TTTG-`fa(6(*p(xIn`&#CY?B&-j( zdbHI#ufY+{xZ6i(+|3WKy~c@McrB9`YS>wHJ4S5~@DW_v*k;&cuqD6)0GL4FnG&_p zf}kJG1oV*Jh|*EA-&m3=BiA#D{JFzz#&nYN&HJFM5mrgL4n>Rj}Ccm4@0jXGdH0wl+)FC%Q?@8iMYwJ$t8inyn9~j!hK(qk5Ggwf#UY=&=`1 z_2b8FTmHUyLOdhbBsf$$kg4{dGR*=mq?w}OM5vavYz)MBUCJ6=T8bnljLQ08{ z)>evylM2kRdI$~g)Xf@v4rE>URHaIxDk0*Votgyqw7aXbt+iN(7DtI_$PLL(gHEMX zNClgyOHdBA>yzXGil3##HF5(3y4wX z8L4o)h&6e|r<7+V42q7o(5Qp&mLZqR-NxRq4xKuLMPrcSI(4`dtDQO=RgrK^N2_VR z@5U9>!NCoBe^oSUgF|ZwSD9N~wG(?UJCb`C28VHXbZ>62USj}xAw|&vH1&ulOUL{p zL7M81W(^^Hrwe;{&?u-Oy+ND_E8iL9{8xlRf5e;XzFB8?8*(g^HjuFtcf&uW1SVmv zP|eN=+Q`nvS`gQiYxKsaxezg<)ds1xfWZ~CH8&RXK94sGkp)yt<5uD@sS3`U&yo9% zyQPSKNo)g5Lj}d+Y>5FhBhH+l{K6=db@p48C$n{NhX#S zd6j5-67IUnlTO-S*tfki^_e~KU+P9(aid*E3%Av}dh}vh6WCY&?%XR^y3$cq${~@s zjOlQv$!`eO8DmP3TrX>0H~G;1-`yZr&`e9DP*|h4lh1vE{=IM$E5_$8lxiF5K~@5I zMO*@)xIz)g000_jY6N!>S2@m0y$MU)4ok<~8U_Ih?@g{;_#|0`gdC6_lGx@fBsoMZ z!$q8EPlt#Ui~$ZkBCiU=b@~@I$I$|}w%NV#=JRHu&%~y~4v2!;ZBmssaQVV?SI^pY z-E}7<+2oaxde>Mm%u3t0xI12Dj<|HD9|ydf8*ZFz49i0`GMd(Sdek_U(t8>HqMr%h{gAAYl4%G83a`il14K=5iv~qb45hR<&)Cf@S z12R7cWRtB<7I78P#K$CQGoyjdFK8?cj!uOmXAOr?zG);%fi``JS)-JP4AqE=)B%&} z5K~+XYbb7QO6-r;6-A6xrVVZW#jX_PRT)Da zV;O6E)FL%78i~HsmT+rStlu0^dYXdq+~;kXNP696Gg%$77|3TiZ6oE^!q$G3QzLwp z8V7|TWQ-^onL;)NOTIgz)!5BpA*1q}Qf{sPqd!>xhpxLbrjV^( zNh@R8##Xc5t`JgtPp%F3c3J~JJC#X@oW61celd{-cMrvT8*xH+2hQ=dZR~QTXa>EdP5< z=k$iOLG8`-Q0EVdAG>!;quOf`NhJNojs~@6m(AT7lPTVQqdcoLr5s~JQkfy_^pQ6R ze@^9oBvt(O-1>6)q*vQDBo%q6+?%&I-;uw1Ll0;b#5?^aouzZaLwZ&KZFBkTKcLE09#vbFw$_#M7Vhb7&Rd((=5Az_jr5D#O{ze&+E6mOrP?Y- zSC2*_lFa1-_p)#hCmWh6JKa&nd93gHv3083gTuWxzaf?J zxsrKnqtan=N?m%>dW+q2{`*ZwLpB4Em=}Ce@SyM~%qIj|K?oU4c*&6LM*beSBhi*b zrU2mp#4k|wg)Am3f@CM@(Kbf-Iz&N8DJ+5H)X~~f$Yl~1Q&cO}z#U?EY=BsCy$N09 zOgY?G#E=trh*%7bi5Cl5iO4t}6z5C)ZR{qrU2=GOywB0NYwMFg9(5JU0}Hbg{^2#g ze!$<2h+3;_==~Q5Ui!ZqoC!t!lsei|JofspZn&m=ey;r1zfSq6eBtsRd~$a(2IZ_` zF}_&-^;74k-xXW^-2e<~=}N>~(}=e=Qy$S-zKU1VMS0kX)rwvb=2Xf&jXe?pr)<06 zUct`Nwj&Th-3Ixfb}VRzh6X_uRO=~;NG6#i zIHT~RJTu-_LuB#-URY2E3uZ3pw)JG2eb!u!L1m8|2nSbH?5 zw@axb_3=7wK*va8OP1RGLZL(!sBWxZI~k7X#OUtuu~^7|Nvd8td;X1o_Na(ot{MPy zAFT>po(Io=LP`}*hb- zPqH^q*c0i|+36l=?2=#|$59B!isYVH3h5e%j7tds28~#37a5+4kcUPyTkg__lP{4} z{!?74irszZd(T{cWNBgJ+{kcCDK=d-?eNOshT@{C>=GfaY+_CScANaSY}WtYv3+Nk zeEYNa!(PyTZ=aOU{YtCB`Wd6O~QU-AP-?6~wnE#CjE)slj z`HO6bJyx+UPYbRr9s9szv_ifQ^+hl-(sm815Ic~Jhk&p|i9{0Gr^*#16)%$ufTP0N zK!_Q*kCY&(i-73l=vRXruRsa`d(PgdR_zVc^WxE`pL+C>2i|wX$zw-%?)V#vqyGQP z;`}ee2GNQCw+(U=8zgUs4T{xrHmF$jPuZYy@P8hXT)5BvYpbMOwMwyC-YR+Hyj8N) z|JPPY6yd|3O^v4~Kr0B=pbMqeE31S(v-u0d9+&yuVUL`6a@Uu?439n!hDbqlqxiRH zId%*fBEKcR9A4-8BS<=QA=FcoTDN+7Ju)|vXK3of8##Axbxhmpm?E8oItVwW+rRSIBLiJliq#UdgD zl|Z9fgdny^i<=x6*c3799Fbfh02VWb)D;9`WHOHTtZh~!Ttg<~f&1>g`<9zd-Ej2q zfqlDoZQHzYetK$rw6C|lttJ%>1$=~eh+Y72h!KJDxB=(+k^;*-w8@A~hm(~8?SfQ( zp%I}V56_dtc?dy_fO5dI<2(Q^Ige`La0u^VQIb!R8Wr*#3FkP6fDH>xqDnG^r{Lff z;`D_{1PDK&^7A-9gAgFYaC1x$P7;8HQzkeJXHQe@9|gLOpeVQ%NLsNEOPk zIS_Vfw6%s*OtURm<=;ILgr(59bdBXUiW*nw8`tcPX}#9QkVGS-&i^QQx7R6GXVT8G zcxiSllCx*y=Gv&*ta7q2vj&GZZY{N(SVLXH(B)5^qi>+8=Qq5Jh-jePBW}Sy*d^Fs z+LHvXRFg(M&OkqCI#x<9gWHJvA02gx7AD_?ffgue1=&KJY&o}&P#ejz@*#m-&IvZ~ z9rK!%JPNRD=eDiurY9%HM*4a>+5r4z5+DZ$(0KsR4Du<&5#)q*R)Q(imXZu931CGP z$O>5As@%|uG$`3&czGrHf(;3Wf^0!-lZ!((YR9|7u$29kr>?Je7dKu1zQ@OIdOmK5 zsckF096SlWm&5m;U;gEkiRc;wnj=ZD>jDz0Aye9esG!q^aYjO&EJ{uXuv zPCT)7)F5tA3F>-3tX#2C+!#q{bxe_Z2sbu2CUW7JwcGs{a|hLHwTg7tjskdN2pS!+p)^@v=X{xGwx` zmTIJWxaXYutR#i!-IY1&6Aa*I;64~w`E2|c)9RQal|>(TZj9yPG4a(gb%I9)_wZxx zxp+)AQqHR5iFTtW+FxVwwA>Fh6f zRD@|j*z$0~$)N-KLkl+|fdSG@AN~*0AUKG^w$Ap(hFBzI_7Z#(29^t*I8`^QBIg5R z%iutSQ-Uzz#aFlloc;wDS|!C75bVvU*QgaW#teQ#eXurt++i&LloD+4EUjtkQB_BE z5>@$6Bjwi}A{6UM?LLcTAZFjVwYoL7F!zn8-EEEWfsRZ_6P0Oa%2Y@#7AsO7ONYT# z&jf7c&%K4R2T{;gowJUHbhn!*X0ZH>ht&+mG~TLkz1*RSs5IVE)utU^y3HUFDK!y) zKJBkL=F`vrf$D428L$Wbx_qzrG3H4wKfH$UGa^Vmgdw~z($!WMaLNSe^McStgnT|4 z`T;nAzD}jN0w*I1@e9zEC=}Qh%+=8#qa_8+5To_t3JL=)0Cq_J3wIT?D=a8Ja;5j+Lcur2R;FWiu;L7(TCzyY4fr@kQ<@EYEs{oM~oY*1-dN#KA z=<-)ZPcyfH-`qj4t;S-^MUYKGxj~XtC|blxzz?hRwyCZ0y3v~!f)AjGT-4WwBriyH zBTpK^kX#00(p+vo2;iGX6tHqsNKDvBWPyPaJ!-MKl$G8#1J3Zqj?Kz&Y)bRFuTsrh z!s&^F?O!W@l|kJ{k?-U{pZ!t!i%M4f{V$vyi#iKpsJ$Z;M|YbnUG;y=rWPk!hgjWc zZWBgnQrE{jzIex;K67{VO9L)-vqR@Ydj`?VxqantKDSU`o4K{;sr+`>fV$;Qbhx<} zp%Rc^7imF>l24@8co{aN*2IXzi3TM74%+9ZC++Q13 zd*DY*w^?&*TAx3iTE|M1j7;fn^*+a7sLd6y)NMUE4Z`t>7p|VP4QIkzYVSR~reHP3 z@;#Y7k9}yz9(}-JvUxM^D)d#$LpwY)dhM5Y)-`*ytR`+?g^{$sfA6MTY+c`bzEb{C z+3-@v-V%i36!tTxO~&4gJ&C4jS{8iOZ=>Fgv-r6cYyR#rr|I^8FebC|eZXM-<3AWv zwK_(DIO6aAEapoqV+2J}@7 zG*=!!c69r;t()i8Pfv{u=duwgoPJKA0fvukBF;$W@-#7-JkXaT_@7y?Bd@G%I}A+< zE1QZGkeKiqc?U^XSEx(`gC?*aLH$t6PHwF5%5Eh;D#(}jSJ*cqv}-!HXYl^lJ~)=C z4}0dW_}Z4~n<6UL7T=nB%K@cV^RW0c+b+HOo(FS!Pfe3E7u~$RGi~!4>lqpAv#Kmg ztzx9hHXgL?Z~WK;O(zc?{PgQxuAs(jVHvrnN;M_i_uVhYB7=SQqP?Yhr_yfqp1aJj zZtu>`RkKOctN-#$%*q~h*uVbzn-71r*nR!(-DlP(w3KSnAP*UpE_+Zry2j?2w3k0d zDes?hqaXU^$wM=(36;ggh?MTQrCp_FJTA@6A~7us*=LTgjTi@8Z~pjG+qZQ&`b^qD z)EF&jLeW63LuI^Srxub$CZoMc<$c1DnLN;X^4h5TJ*RG4i;FA?pZ$60s4WS$mNvy> zG{c%HxdbUWu5gTD(O{O)tYy%jrHL{x8aZ+OBY4Lh9n>qHx>PC~LKLW2@zj?VH_T6r z4-NEmw>0NaA`u3A*X#K&JavN4hP2$B$z|)_3E|*)kd@|fc;cxeEyXb@Rw75NE<@6i zB#0wv73xFp_~=G8QYFSFo%urtZd(;*b47?So6Hnxo8WU63htp3WiGqNY7J)Hl*m$>{=)U zSp*l|1y)DAgs%ZfkV+<@Pz3(JNH|Q=Z|%^9-?_AaoMw~Bpp*+{s2QT}fO;F)lRUu< zD(QN7vVal<@0ba)(<<3c3=Ihh&;ZlJStkyBKzkF^;FA0Un!>MCMe@laZjVw+)h3-m zIO`3YfdcD67;jRAowEV8#z4u2>YM7#nf9%p-f`l{);%dpa(H{=15f%*{eMx#%qoe( zuQTZla*M%{-rQkSKy9lgXRZynP@1+&uMRe)Hb3;@zKvU5p2@FWy`h~=^*8m)7o34n zqL5sZRYsJwNGDfl%0FK$^-kpu-g@-Vi;X_3d0@>gy&u2h;H0zHB4%ePxytVAj?{a@ zc7;Qs(WT-_buW$%BbGUxrc#&t?Y--V=jWz;y*D1Y_tk5Hfk3=#hO8;4Kf}7QXEt+w zn*v%ZQL=i}hojtvz?d^oU)jVwg!SDbOr9GJpYbAdE_I<;$Wm~wy=8{d zE>ezGkH+8(Xv1=a+3(h;*4#8v9(%j7#@Oi*sv9>%5_Xv})1`@3f#96)e)j{RCfLz9 z67(yix?1oq@sCcSbp{ekvF2dVZOAClXhi(a zkXhC(KOp*-P?~UeaOOdWB0Nkj9A9wL=uKB&ny`x04vkl-@+N!IYqI*L8=wW0ufOF$w^oz1 zhK&t@e11ogX@2h|x9F!1HeJ?e9dg*K?XfSPxaq}@ci5X#_m}?~+1=jYiVrs1Z4!e* z@Ab){pQ-OX{%ax@q-|T&YA&tzS-Nx%tA`B=hi)wY<_m{z70y>T2W6wDuRqX^p10+f zCTeQ#Y5e*_>93X_CB3haoehY3*{#^yyS~HO=Oqn)8a3G1tFIH=jMq8a?Bi@R|Gw19 z`zUre!@nH13Qc7`|RXwpQhf6b>+@KXT3=rwh#T=8=04} z%6)=(DU$N6^f(eKAP+_k3SmTWJpI}%*Ws~_$|DFP6%Ls73zdsN$wOrh4fhWI9kFBN__kE|N4>t5i zo_y-E468GO$DO>3F1XCK&?)lVdij#i;*1@AU|+yoXm6w<)Cv0Wh=Z3YQcE=fxer1` zhA%zw;*EXd*1#`1o<2NP)O1ZZ?>qm;cy+w0RZ8D)^!0z9^tox8A3Q#@{;FIjStoAZ zn6ItQ+m(XPf!WO6dJUWj!WceoI0M_t#}+3ME5FO%r;-A>_<82nnlO|*U*$Z6)Aau3 z+hEP0$+vtP_pR{rD&zk6&Nz4g4=lgy?#5bG&@e$ev?{lkwxI7kJxWO>V^&B-OXoUt zTB%qd1J{@(xj0%%OGvt(0)D%ZxPug_YCs|YNe5(i1uQyzf@r~tr@$j4P5AhH|E4A4 z^>=r+HW%x3TB|t;flxEg62jdoA{AVGT_xxLbND`2$U-83f2OM}3q67r*p?`X!esiv zPvV=+O07{3GVOY|QeHc4R1_e}@&9f;`-0`7*0X=GT=&i3Y)I>Z)Ll_3jSX5opKTIW z`q3Vy{>m_kOf+9HSL7`2#Br1cl>+ z@s1PujzT#EgthDi`oW7+E{mL6Qy*2^jUKgF$Jf?1Cxy&An!*Y_b1G;=O`xYzE_Vaf ze}Ta82QLyB_J!5iv2Y=Dfxd93#q+KGH`(nM7SGXxgBR!x>s{}aIMjYeatB`!XR{T~ zFaDL&9=0vv~vJ zZD9dfxo)f*z(-!JODG1{A0!Dr?ItJLZsjD7AUgg?z^#%ut5piH_jo1K!-C6p?U-9P z06E^K27l0R^#%PFt;V>bvr`??LsjD?K5L8tutYU*M(y#5KFAzd)hVJd*s(NEc@0E&KRgJ`%w<~PD zy(0R5sEsNSO$a6J&eTG0$rT@-sJTEUwcl;IjxHE{b=#%(NRI`2T3XRx4)*=`R8yCC zAp0=KWei&RjKQ*syHit$Yu;e~1RIZ733V)rySY0PSo5FhYb8lMZ~Y#@E1Q=IUP%ew zMr^ex`d5LEaP{HhKu-eyBjTA{_LLIn)B+#n5%OmduAZKN?TCF>+q?_SC`BdwCMuaJ z)Cx^hI2r27HSNuS8Vs$pdV5_}Hl*q6G6?Hk=%;Mv8Z{ZKeM+P5+;V$-Jf!l|#LAp5 z-z>V1DF_0HxuA~{<9`>C0KLgbVwX@w<;~>R;t8XIQYbh%QECd#gw_UpMuP?|2~o(z z6GgM6*(munE3F7wu3Q$oD2&hxTT4i;a}sc&7!MdBJTX`U?5q6WwqJd%!V{Hu)J=9> z-4(9Jt0qjGPr+8IFXMQOmdURB4J~XUwU%Gmar+a&}wiATdLb?>a-z= z*&;6b>iZYl^)*vfyF;G7>fnLF*T}Xswk%yyIyL<6t$F_cHt}1N$rn#_wa=9dPF4Ak z%;B~NrmD(ohs*1q*qbQ-HWL~yvL4j5*L7yI+anHxrZuq@H(=(KP`$OP0b#_mt8ZUk zUj9z`1<^qXdf*A#z7Ee78HY2-#BrYMqZJQ~=p73`$=K61aVQIl>(8@a(r`O4S7`D(V7C?i|iR+N$7 z|LR?|uhlW(w3%XKVd`?~hZiX#myf^nWbo9ABC<|fKKqGVf}wZGAs@N_y7KQP^_jVp zhLKA1{M{*hg1fVJfuX2Nv;*8UXQDZjnzRZwkqjR>gB1b-C$39cnep|WA1B>{H34N^JfuW#3c0Jr zouGSW+2owLly5BY{ezvXTu48(TnjYZI2-N?T#0Pn#H= z-LyS(u=wu2!go(py+plKS#T{-(?DY<{vwUusO4joZ7V%8)|jwpMqQ zoBVQmrS)(OV$+aS5i5ij_8wk-1}k_Ds@Lm?c60{)H`yK~DX$@34YXX?MYyAcnoY`~ zk)wmCixkSAAcYFVQo%;tfQV$PJ&Z$YA*)?FgnEO-&p=b#q<0FIX!qUVHDI$iWmcS z1FTg^@FMqKC?|{K%%ga|g~Ruhg2$H!@LqzI@w5k8aP9I{>^5|xIVE^NaBu0ZTJU!Z zl$sevn`QO-b$X#pe$6gOu<(>J6jiaH9WbbQLu=+$N|}NJJ$xg0Wvb2aKh#oMO$1xy zGKqYuqD?x5<>#8(aAjEd7b*eyB0zFb@4VyK(Srx}?%B3!!`$@L#At6%S4VSGvML_2 zThR~b6m>mH|M<=(SYOiDhew2jp=@oXd90b|esTMRPxg{yjXp0Nlble9%zPS&gLJXW z@HL^1hE;h4kZr4=>T zT(SAe+jmxNP*aaQ{D+6HiTiL&bZSMQkjiI|EBqN7E2sW+Pm8(5n9wtZh{F`@%(jhh ztU1!fN~Ugj-MQ9atW_HrufgXFrKfYZ^d`_9(((07%G|7VdKyHsfG;IiUR(Z2KBTfF zZ!TYPczVp;QPr^~JaozY(tL2NoMLTyr{142edU@h8ylv2AEwL-i-NL$@)P%EO5X}r zSrsx%!04boK2tQHwR_Fh&T5xpe<14J)~$2u11hsi8!^81oM^!`vRLd1M^Z5nE0j>y z)SB|~*;YM7*Yw#1d)sciJheufj%%DT@lC0YrM5Fv>C;yanc~*`bjy3bT&}y^LVVE^ zf|&F*#*SO@pa8uMZ3uj%U_!uhAkPTr=^bOvWgr<53P%|MgCI#DShuu93ziNp9o)TZ z?Nnz6QRO1pZ0H(tL~vGw=D}FwFtHEhM&&}$Ts|Pi(sKEIV$#W0;h8LWnPimsB%=s# zBku@SbC^>xN-kWcQ<-wxvF>r~Lb~eLoxy z@B9|E>(E|H!6i}2g%Yn(s+DWdF{%{PzV)r%^-Hl0zaDt%mh6t7_CR&svbn1LuH(PB zdibbIwLW+Bb3c9Wlhu})bnkagy*gHXbMNExZ83#SCX#5a!ho)6vZuOrZ9xgxz}&m` z>BAYnW@+?K<$t;V=${+)t@>oEB{?WHD5Y9&z))kz1e8T0WIp#ks&UTMSv>rq@^99W zJ`!B7V~&y0$61pivf0GSTn1kWJ&<8d_~%*b4EKCpxhUK$p2GdU{Rbpc%09$JN|o>P zT4g-g3rG3qBT%#cPxcr;9(^h#f4Cs$fS)sh{=c7>ke|~JtA+Rd@(}N}9fG-o@Auo~ zw}m&cAH@52{F%Hic@L}&$yrQtuUAl1f@=PM?R{x@9Oad6RdrWY@72}S`@VOpSFPPz zyIZm)Yq2cLmMw4chAnT{7%v!uv9ZAzFc{1ZA*=xtvXH?sa0y8Wa5D)>ATWeX78q_w z!iAfMfy@Mk*lOMPt8U3QU@kNFx&Q7X9&EQ->ZOg5ix&>h9q20%HfAyw0=3ry*%O}H zN^doiZg_^Ieel7k)$?2v38Uag$=;FqPC)Po&9ZWolkbu;8~HAzTL^|U)pl5Qy+Qea zmwx~UJHD!~gRp{-H_9mz$HI~;9UiPYXracHUZ$okyOf;nOILZ1_3g!1hT zidX50{omNJA-+(f*Y=3<@x>d>f%@6j%-F_1)Bil$k0H=$>5Q2|wBn%~u4=p`tTpQj z#u*L07l#@HVpQCEdHFApj2Y-@8avo2PZY{&NvvGqZmZg{#`ns0(5mc1O@0P{6jiMosc%x(m99B_Z89NYBqb^)%r-<>G_B@{nju@j`AP#DBqTr(fk10R za5l{Y&Yv9ZsF=xKzhW@Nb6dC9RM-?i z4oH%L8bl$>!Z|>dYO|TN%Ym+5cmU+AWC^}37{TnV;0mk_xWXg553QcY6}mEA4K2nP zZwIarY9+V=yZ1j24&W-6vU$aG+ypvdqoq&^Bm(>gb}R@^6uA{jv5w>+TB?Ndx9C)Y z7YjuQ0-})2qQVp>wre9MjGH444hAN7n-g=Rn-!i=ud2<`Hk_NAUL)yT#^M5(JFr-Y zh8i3$o!4cd#p9~_^W6dVIeSttUnB%U+TlAR{hVoT(Ja{{)?jl&n3*Q7n4J?vX*>6(x+>zxz^D3 zK`q1CLv~%%IWwZ?n%H32NR;BzoS7W%qa2UrwMSjq}5U9x375=M>ERlTHY9f9YF=a;{x z$cRX<$&JWbOA4jM7VwJMOi#$rt#=tCH$1#PHt039uClf^*xF5Dr#_l+6|e3}c|-;G zk6VFxi&;w`B4`zyY4N7GuP_o(8A4i@+AYK*eeps8CH73KS4~jde`6hFh8C(SmXe9g z-XRu)W?40*qqV?pWz=&TlKbd11jhNN;VBaL9%dh9p5PxuED)f?k^#S2-bG^$I~i8< zS+y3>=a87d&VaAxbMz~+R*Su5c!D>>2c>R7RQ_L$jv zByoOUz2|r7cG2&!r0R#)nEm8FZex!rTey$Fo`p>jO&fw%5~)>;|6{QkFhm8LdaYc# zC6lYrkPsKG(S&FVsLT)r!P!n$V0vJ<92%3uoiG|mtES9%CL4pijhyus`al#-&z6Ng zpJ{>9;WLlSYKaT#Wan&+g7yerMF&!O@!+TDD%uu1pQQpscv4SO{ZBn!Wa6w6dRRaE!nVP_Wk!?fGVm4+rVSmk)aTco7 zfC(6JM_BYj=R6XqfGapeW0f=s9xhK2ULL|M8=2XSu?R?nNNfhEv06=vDDw?8#B>;T z;^cf$!30gbWb#UZR-LbDX}HKN^5 zRL=M%(T>~J=(Nl~(0ku)D&LA2d)4F^XW+(7936$t{(sGp|F@bW^yIH5A61%^yb_O~^N=SYZgnI$l0pQ@X%qU796nZFBDw7J%a&VxtSXYa@ArkaD z?UD!tXE8mht%9yBd7`_K18& zhhmq?Wep|Aa81+Ntd*KFN1s}{BM}V9Rb*>d+%j$~MZNkpS)IeCDUSD9{2H$sox+Qpu?e%earOnE>MBPh;5XtiDu_}$ioIaq zA`w&4Ly*!#3KkBJL_>a}*dCdBOtq(3gr}(sGP?Z0Y%l zM74&FyJrT8HfmK%*2bw==)YF(sCu1zuY8@TY52eppT1Tg@1&A(?rV)mYpT~81{y|I z^vp{Q1kMoPjYhd6ClB12MxaDW|J6&SN9n`#_v^cALjEYx$E{qf(|`J6dBz*|%Hp+c zK615hu|8&md!KSs<`P^J9v)z6_;<=35S0)Tfa9JDfbuy2LBx&A!~`fD8FmiWYBKJ| z(CBc&FNn1csQOsJaEYjekTJC?e|3W=D)}^FYinZ-M5T3lC&4}bS@~CHHuob@pv{Vu z_N`~DO8nZlPH2E3jC**IG{4*S(ijkj@|3W-8t(|;R@i-@8(hKSZO#uf;UjS zwR#x`E-+0^`Ft&UQ7S-$9G4J`0U?SE1S2(+g@#F*Vv!+WIGU<=k>eFfo-j#@?H|_% zYl6TfUGwWKc~e3osyyCEo4+mwITlG@ba&2A?#^6j9!$1b!cu_K#VlcSQ=QOG>pWs2 zu|0@?hrlGe@6_S9uVhRiQ>JES_jn}Pqr9SMwP@;GHou^Y1-f~FhpbzRuf zsw_V@@gJAQjL8yTd19|CzKbWei`refs52cw55{1a(Q$+-ty{4O>DVSXU8w7!;-X_Y zv~d%CeSu!1Av$|#Y*UWvX~s}}qBlN<2}y$bTlwvtGP3S*lwn_A0YA7s)SoPGC<-gQY)L@FWVtO@r*PN84xg9eg+||{iImu8Us~3}> zKK&=2@AEgzz9kj5wkYWE+mh{9)ktAo#Br@%Yc1Kd1})&G%>J{r9*^M=Qboi?Odyo+5uT6O>luz)qkk2idigrz$(} zJ6iH5T3Tt@Gx5}8lP{^CWM7A}QUv_Xaq3d^1Km$Gmg?rugGF_>wGv?{_;@+luS#5j z?q4P3Zdku!*}?_IjyNXPCnNA&1ZqJtC1?H!Ajv6AUXr~TmK{kl>D4$5#*@tlQVaYs z&TjlbL4_s{l0e8{AYr723{`g6^k3MzU9`^(_Y>bau$2-&AwF1oLDpQ@Cxa|d7;|&PFLZMmBRxnUqokb zVeU8QYxn)&$Y81RX?erhAJ=bgnc?!g*DhEh8oC>QlN)z0pBEae*|n?` zhaWH)bhBu+Qsu0TEk1J7=0k4twiS2ay%R$tqCf4`H|X?Xi{3Q%;J3c_%5dE1YSPh^{KJJ@i@WvTo&^2zj)o(n(q z*`6k1oY3f19A{4eDe;t?Ay_G31!NZZ7Ln5uXaGGHiNTViXhnjq%7S8{v2<0cwOO9= zR++E`vqqZ4+^*A?nqKYEyW6(a9#VzHptTfhPj~hO>JpEZXv22l!hxiBktsIoik()+ zoWkEmcqwA*-;=wob$vEq@3jQ8dQR(XuKRIs`I+tLdGMM7ro6MIqH(2o0UB4cPHSAr zc|d0J6}E|emP%5I5~iNQieb%Yi=Hy*GtevezFbVk8X{9;=y6*W{IH%hH&+5IGBE74 zQPNIcbT|y1DyueNORV>RqG1W}qQMc2&Wtztr!8gvh~UFK22RJBjF>8Ka48+mM54>! zkJi`tFr7|bIN?epp`f@=-p4+SPUTyv2TJ#L<1p;&Wwg3ROh2ioS(S@cDx8@4q{NIm z;>szj3NWvZ*J%OjTO}lF)EYglGD0m_#f}5h zB*^G!PECZ7$6{0RN4K?Yax~2A=nY3?w zWo>IREG9xOalW_35;AyozUBpOSANSE@%j=QQo2;8+&V55?>=;~1%- zpjG9Q`=O2*jrf;!foTSofI7>*joj=4OxyW3r7vl|a@GD_n^$$$DZ`kPM-JGEv>Ng( zS+^WUj!YjRXhjjlVx>(sjI5jrkl=3d{HxEL$1=;}kF{2M+TRlwTrH=fW=$4ehT%wg zlq{Wsd|uV9k)J;Ixy(P>HcELpR0?UK-dYK3X8(&x-)SKl_e zebch>l&!F0NA8X9Z3(I(9Rrb>vu7+=8Xf(ouRZBBj|9#X;d`#8Eha0ZH)*0{p7M)+ zd$_UD8kYpS*Xt4kkKgp2<6oa~GXl68gVkfOs0?DwYbJKNSJ$C<1t(VzO9kw_Vj@1fb;Q4#nFSEErv8(^~IWOO^ zvPCFtpFv-j*tTuBNpSJ@bae5Jv~Q$ql|en@BurK({YSqMUJsBAWQ24XZ6tEi}AH6s|e0Z16? zd$K&GjDTawu<)zDJ#E7CIDb@ip;+P6l zOO~GbvvW?U*ES=TjWdDqC7J$!-f2+!#dyT%wm)>ieH#~8oKlk&(mI0R(?t6J4&g72 zBWPpxh0_X8%4C#GDidS&goI#hW_G6`q^Pm+=6jYJS>EDH<-N@Xji){_bZgw@5@MXL zRcF<^GYuiPzug+Qgqu~h-4_IEl19#dMKCsCje8XItoS(w1+`GO|mRU2QCCH05&vXcx!<81`8{ zSJ`7SL4~kmkXo9s1hT6Y&4k{7;|w4I&uvhH@F4j*YHWGu%ez;tT`=bXv|Q2Jy} z9$pytq&aDRieTd}O#V)J1s9|0(VeF{QE0U4i7k^JIO20izCbcLqQ4>60c5fyc`08d zWXRp&n3*OUzkGJcd>zKHAm%CsT~dRyA-VDB&o`ZSY|iU{xFNmpYwNG<>V9QA{dm{D zx!!mhw4?vA<7eN!qQ{nUSl2ZEw}*?DD>vwP!R-iG^8NGUtN!KX@}uQL?`_%D*?Pgt zZ$7mnW4kXxA3IzhcZCPcu6{{xFR%IGH^yckz4buBzhvQKB;&54wZJIB9>;3>%WtuN z;ywUVf;;Y^v6^W6t7_?rBt*t-RumT4+*C}39ffBg!o!jvgbc*N?#(rciYBP{LRJ9< z5b&;PCq#6eE{N!wFK!UhL$w>zc8OOjx&dfJtR?S2jlq7vXPoVH<@>JB6?IMpcj|BG zdNXQs4I}0Eh+b|eVvLIHhsa2@p@?l;+HCHPXW6ye8^hzaW|vy8{kze;DCj{dtoB=n z#CWuUPAG4L9Kgw~<<}eB0b?)XM5?{KNBIzU1vLYA*k;5XN&- zSuT9)>Lbg{22DbXDQ=Da z_7PJs;S*ebYk6|{;(392P3@mt;B^agg3KH4NV7F*im&#l%HJwKt9XZA|6YUJXY4W= z-Cna=)JtwZE9%T4=fu_z{^vi9{QA0e^k07;v#O&a>HJ(mjqvv(Yp8+ZP9LRtaz<+| z;50deL=}b(;gB|M;iR zADHMW-nl(H)D@qdU4P`-k>Lkp1I4}HdwR!?@=34gwQk&WsQmF$KRPidp6Hqv51gkB z_*VDQ!);f!Ts3~dcRyIR{E-XGwf17&yrba*N&J5{mt zCY+2iK8;%dy&*@gANI^!s>*kADNiEgZL+EHN5rmW8pUXheaW0rMRC=x4p+WMu&C5t z*FZuFtjL*Tinz0F>)f>27s{0HEWXBSvo1|IAT$RfZz%uzz%{ctm1$<_wRCS-D>_26 zo;lfn_iq0zS}Xc$;D3zNW9*-lIa3S>5>*}tIk6t6c z15accbBhIIm^||7R~0vuzglk-HFaTs$<-fT7uR~%TGb_wqt`zeGX*f~(Cx}}*`k-; z{LHqqUP;j5rA#Q|vs#A&9sYEk6do`Nz7@WXEbGyRId#;MvUhqDH!f_YgMF=MzDVy8 z?jJ-DKjgIf4RPL*(HT-<{nIqv>-Y7zJv%cEb2$2O75QhkNB*Zs3!aN~F}q}dBEHGt zZT<+sFfi zrOP=Lo}3(M6&O;FBl8AG?t>?&1fQC05&Dc0iSkU`AH@}(!+VuaHraIrQ_>uPkg%i? zLu31H?SFi}u4n(vcXZv9beLF%QN|pF{yt~8xWDChRU3;X-m5A z`q`3q|Iw>8PTaC>zS)(Epv*vgUA1sR>JQ|c{9tl38|7<|%h$1gpnzSMa9rYfT{F2) zITNRelQ8T~pu3*L1Z*v7Xvrh&Az#G&5SyZf8@>cJOa`G2*tL?C$vyEv(JK6Tq1m3& z=xrJG_uo}$Oy#HQ;`;6|ZP>K$H%{>v4_^IK$7*^Jo!j5BlK$Bp+2m}O#aBL5&bnr1 zC!QM{v)n|#@e%E(?{4svkCb&P0y%|bwu62b?ew9|^Ojv5%t%iEF%8K8g zoBWDuHqTH;shy>5vuRE>SPOkcD+)mtKmb?LdM#TR$TBK)7jw|goo*tew2Yq7>erCz zdj?%4^~aPIia{sAD8{Nlc3BWml9TiO8*eyt)$WUt>Vw+1l!$hQvzWS=Fd5BBC1R{< z{-%hYuFIXK>@_qHhPBO_BjKi5_HXiG9TY8?l3n?3WrzUT=`mv1OpN!7mM}5wvzUCb zfZV-;7yxMozAB%KP~`N>^e>~Swh_Gx{FmE`C8_2#q0ey71zZ1Zq|Ug&kDVrQp# z)yGGU(DCKh?>j&%Mrw@<16oa^GjEKhgO&@+zoy#{G&pUBppcLxUS{~U$I5FI?U}&x zR$C~v93R4+dZqI30j=P4uP}vO5&DH^A8r#Pr%8T&bj_YUK8>jDm9iG~i|HBNDyCfQ zrnk|_OZEoH>*nm?Em@bL4ag73dFA3}UK@pMgQufq;~JU$*UC)9N<%g&ronwHS)D&a zKFgnD-lk9eedg3RaH{rCeuaC9dmCMQd%?!vQ`()SF=Y){&y+c=rnPEP0l@HcS)xf{ zU=?6@GFnEhT{GQQ3J6FIQh)&T*`P1#Rp16_=Nfg4Ry$=6m+u=}cFooM4qtxlmQBm{ zj_p~p7)>($DBD-e0;Pb{X3=yGv#_1hEIzyNp)(dfQpr=!-TWw>)%5n`v}_Mb=E6^z z!|pv7{rgCru<#6Xs4TfYbb85);p&o?6dTXgQhE7l4$l|Z0o_lx?73*#5MG-ygikMa z`>9W+m(==yYS!d_(sppx%#}rS#1S8e^;fMym(618TI%xBWoeofvKf#&r zRV&Aq!5@qgNvEz-0j=XLIoUCY;+L}kGR9DL4Are4Ma+P$Uv<|NSc%{Q(X%ZeI^FLh zL0XQP#u$iG5W%)5KR?A+qt9&tG_&QaJt<*&<9CD?mVf=PJL+cDyD*`ovgu&~_sN^Q z#CcD50~sWJA|-eJpx?>(=^mdiHiIMUQ&zQ+?E4$HSN6RKhrntT{Y}$b{!A+vGcx9w zva?7g>fZfx2>$!0I)oZJuRfLiY%dTjA;4Xr=;kj&$A(z0ZF@a6UusJ`G7+Q7c4_#=NKmBA?|DoI3W!{=0&1}9rSN8)uG z`Ja3Nh-!l|xO)6YE{ETSFo!)$QU|%fD!I}9yi4J__yUxFq0FW3~?lpmba>uid)Svp?&GR(_%w_rpH7Z zcb_Yr4u-{}b|d$uPs$hb(Ns&$7_l}BPG>_{T&AsX08irD|6S$)s!sTze1KK_zoY(5 z66&9`a3=Nd_h^pwO;i79YB@Ke9OnN3q>wuvkdX(Z;3WShStO9)DSU;*^MqBCYmk=~ zk-Jh}@(O$eI^P1P7#2(hUnuVo{as$4y?ta^Q50KS3bCM@cj%lZOfL6z)ffZL#Y^gE zhQ&-@O7QBq-4U1Dt#@k-4ug$WIAiAeA*;17QR*42h=)^slGF?eHh>trUYbw-uR zdJsD5MpcH2 context.pop(), - child: const Icon( - Icons.close, - size: 20, + Align( + alignment: Alignment.topLeft, + child: InkWell( + onTap: () => context.pop(), + child: const Icon( + Icons.close, + size: 20, + ), ), ), - Expanded( - child: Align( - alignment: Alignment.center, - child: Text( - 'IMMICH', - style: TextStyle( - fontFamily: 'SnowburstOne', - fontWeight: FontWeight.bold, - color: context.primaryColor, - fontSize: 16, - ), - ), + Center( + child: Image.asset( + context.isDarkTheme + ? 'assets/immich-text-dark.png' + : 'assets/immich-text-light.png', + height: 16, ), ), ], diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index b0dd2f6b61..8dcc892a06 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -105,9 +105,6 @@ flutter: - assets/ - assets/i18n/ fonts: - - family: SnowburstOne - fonts: - - asset: fonts/SnowburstOne.ttf - family: Inconsolata fonts: - asset: fonts/Inconsolata-Regular.ttf From c50241369ac3b16549f8f31a2f83a3c58e903e0a Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Thu, 18 Apr 2024 18:08:09 -0700 Subject: [PATCH 10/46] docs: link to storage label docs from storage template docs (#8911) * docs: link to storage label docs from storage template docs * docusaurus sucks --- docs/docs/partials/_storage-template.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/partials/_storage-template.md b/docs/docs/partials/_storage-template.md index b52fd4649a..6b733faa55 100644 --- a/docs/docs/partials/_storage-template.md +++ b/docs/docs/partials/_storage-template.md @@ -1,4 +1,4 @@ -Immich allows the admin user to set the uploaded filename pattern. Both at the directory and filename level. +Immich allows the admin user to set the uploaded filename pattern at the directory and filename level as well as the [storage label for a user](/docs/administration/user-management/#set-storage-label-for-user). :::note new version On new machines running version 1.92.0 storage template engine is off by default, for [more info](https://github.com/immich-app/immich/releases/tag/v1.92.0#:~:text=the%20partner%E2%80%99s%20assets.-,Hardening%20storage%20template,-We%20have%20further). From 112d6d60ec1aee1d6bf9ed7228cf18cf80fcbd6a Mon Sep 17 00:00:00 2001 From: martin <74269598+martabal@users.noreply.github.com> Date: Fri, 19 Apr 2024 03:11:54 +0200 Subject: [PATCH 11/46] feat(web): add page up and page down shortcuts (#8910) feat: add page up and page down shortcuts --- web/src/lib/components/photos-page/asset-grid.svelte | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/src/lib/components/photos-page/asset-grid.svelte b/web/src/lib/components/photos-page/asset-grid.svelte index a84b9d4d73..a3f4c51563 100644 --- a/web/src/lib/components/photos-page/asset-grid.svelte +++ b/web/src/lib/components/photos-page/asset-grid.svelte @@ -106,6 +106,8 @@ { shortcut: { key: '?', shift: true }, onShortcut: () => (showShortcuts = !showShortcuts) }, { shortcut: { key: '/' }, onShortcut: () => goto(AppRoute.EXPLORE) }, { shortcut: { key: 'A', ctrl: true }, onShortcut: () => selectAllAssets(assetStore, assetInteractionStore) }, + { shortcut: { key: 'PageUp' }, onShortcut: () => (element.scrollTop = 0) }, + { shortcut: { key: 'PageDown' }, onShortcut: () => (element.scrollTop = viewport.height) }, ]; if ($isMultiSelectState) { From 596c35dc00200d98ef71c9c6c05d1b7b9806eb3a Mon Sep 17 00:00:00 2001 From: Mert <101130780+mertalev@users.noreply.github.com> Date: Thu, 18 Apr 2024 21:37:55 -0400 Subject: [PATCH 12/46] fix(server): skip invisible assets for thumbnail generation and ml (#8891) * skip invisible assets for thumbnail generation and ml * no need to update job status * fix thumbhash check order * linting --- server/src/services/media.service.spec.ts | 27 ++++++++++++++++ server/src/services/media.service.ts | 20 ++++++++++-- server/src/services/person.service.ts | 11 ++++++- .../src/services/smart-info.service.spec.ts | 32 ++++++++++--------- server/src/services/smart-info.service.ts | 6 +++- 5 files changed, 77 insertions(+), 19 deletions(-) diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index 1b1adcd573..c6301c7c33 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -225,6 +225,15 @@ describe(MediaService.name, () => { expect(assetMock.update).not.toHaveBeenCalledWith(); }); + it('should skip invisible assets', async () => { + assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]); + + expect(await sut.handleGeneratePreview({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.SKIPPED); + + expect(mediaMock.resize).not.toHaveBeenCalled(); + expect(assetMock.update).not.toHaveBeenCalledWith(); + }); + it.each(Object.values(ImageFormat))('should generate a %s preview for an image when specified', async (format) => { configMock.load.mockResolvedValue([{ key: SystemConfigKey.IMAGE_PREVIEW_FORMAT, value: format }]); assetMock.getByIds.mockResolvedValue([assetStub.image]); @@ -353,6 +362,15 @@ describe(MediaService.name, () => { expect(assetMock.update).not.toHaveBeenCalledWith(); }); + it('should skip invisible assets', async () => { + assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]); + + expect(await sut.handleGenerateThumbnail({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.SKIPPED); + + expect(mediaMock.resize).not.toHaveBeenCalled(); + expect(assetMock.update).not.toHaveBeenCalledWith(); + }); + it.each(Object.values(ImageFormat))( 'should generate a %s thumbnail for an image when specified', async (format) => { @@ -410,6 +428,15 @@ describe(MediaService.name, () => { expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); }); + it('should skip invisible assets', async () => { + assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]); + + expect(await sut.handleGenerateThumbhash({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.SKIPPED); + + expect(mediaMock.generateThumbhash).not.toHaveBeenCalled(); + expect(assetMock.update).not.toHaveBeenCalledWith(); + }); + it('should generate a thumbhash', async () => { const thumbhashBuffer = Buffer.from('a thumbhash', 'utf8'); assetMock.getByIds.mockResolvedValue([assetStub.image]); diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 47fa31abcc..ca72b6cbdd 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -77,7 +77,7 @@ export class MediaService { async handleQueueGenerateThumbnails({ force }: IBaseJob): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force - ? this.assetRepository.getAll(pagination) + ? this.assetRepository.getAll(pagination, { isVisible: true }) : this.assetRepository.getWithout(pagination, WithoutProperty.THUMBNAIL); }); @@ -178,6 +178,10 @@ export class MediaService { return JobStatus.FAILED; } + if (!asset.isVisible) { + return JobStatus.SKIPPED; + } + const previewPath = await this.generateThumbnail(asset, AssetPathType.PREVIEW, image.previewFormat); await this.assetRepository.update({ id: asset.id, previewPath }); return JobStatus.SUCCESS; @@ -230,6 +234,10 @@ export class MediaService { return JobStatus.FAILED; } + if (!asset.isVisible) { + return JobStatus.SKIPPED; + } + const thumbnailPath = await this.generateThumbnail(asset, AssetPathType.THUMBNAIL, image.thumbnailFormat); await this.assetRepository.update({ id: asset.id, thumbnailPath }); return JobStatus.SUCCESS; @@ -237,7 +245,15 @@ export class MediaService { async handleGenerateThumbhash({ id }: IEntityJob): Promise { const [asset] = await this.assetRepository.getByIds([id]); - if (!asset?.previewPath) { + if (!asset) { + return JobStatus.FAILED; + } + + if (!asset.isVisible) { + return JobStatus.SKIPPED; + } + + if (!asset.previewPath) { return JobStatus.FAILED; } diff --git a/server/src/services/person.service.ts b/server/src/services/person.service.ts index 2cd3cd88a9..721f2586ee 100644 --- a/server/src/services/person.service.ts +++ b/server/src/services/person.service.ts @@ -292,7 +292,12 @@ export class PersonService { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force - ? this.assetRepository.getAll(pagination, { orderDirection: 'DESC', withFaces: true, withArchived: true }) + ? this.assetRepository.getAll(pagination, { + orderDirection: 'DESC', + withFaces: true, + withArchived: true, + isVisible: true, + }) : this.assetRepository.getWithout(pagination, WithoutProperty.FACES); }); @@ -322,6 +327,10 @@ export class PersonService { return JobStatus.FAILED; } + if (!asset.isVisible) { + return JobStatus.SKIPPED; + } + const faces = await this.machineLearningRepository.detectFaces( machineLearning.url, { imagePath: asset.previewPath }, diff --git a/server/src/services/smart-info.service.spec.ts b/server/src/services/smart-info.service.spec.ts index 4d85c00253..8dedcb5c5f 100644 --- a/server/src/services/smart-info.service.spec.ts +++ b/server/src/services/smart-info.service.spec.ts @@ -1,8 +1,7 @@ -import { AssetEntity } from 'src/entities/asset.entity'; import { SystemConfigKey } from 'src/entities/system-config.entity'; import { IAssetRepository, WithoutProperty } from 'src/interfaces/asset.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface'; -import { IJobRepository, JobName } from 'src/interfaces/job.interface'; +import { IJobRepository, JobName, JobStatus } from 'src/interfaces/job.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface'; import { ISearchRepository } from 'src/interfaces/search.interface'; @@ -19,11 +18,6 @@ import { newSearchRepositoryMock } from 'test/repositories/search.repository.moc import { newSystemConfigRepositoryMock } from 'test/repositories/system-config.repository.mock'; import { Mocked } from 'vitest'; -const asset = { - id: 'asset-1', - previewPath: 'path/to/resize.ext', -} as AssetEntity; - describe(SmartInfoService.name, () => { let sut: SmartInfoService; let assetMock: Mocked; @@ -44,7 +38,7 @@ describe(SmartInfoService.name, () => { loggerMock = newLoggerRepositoryMock(); sut = new SmartInfoService(assetMock, databaseMock, jobMock, machineMock, searchMock, configMock, loggerMock); - assetMock.getByIds.mockResolvedValue([asset]); + assetMock.getByIds.mockResolvedValue([assetStub.image]); }); it('should work', () => { @@ -92,17 +86,16 @@ describe(SmartInfoService.name, () => { it('should do nothing if machine learning is disabled', async () => { configMock.load.mockResolvedValue([{ key: SystemConfigKey.MACHINE_LEARNING_ENABLED, value: false }]); - await sut.handleEncodeClip({ id: '123' }); + expect(await sut.handleEncodeClip({ id: '123' })).toEqual(JobStatus.SKIPPED); expect(assetMock.getByIds).not.toHaveBeenCalled(); expect(machineMock.encodeImage).not.toHaveBeenCalled(); }); it('should skip assets without a resize path', async () => { - const asset = { previewPath: '' } as AssetEntity; - assetMock.getByIds.mockResolvedValue([asset]); + assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]); - await sut.handleEncodeClip({ id: asset.id }); + expect(await sut.handleEncodeClip({ id: assetStub.noResizePath.id })).toEqual(JobStatus.FAILED); expect(searchMock.upsert).not.toHaveBeenCalled(); expect(machineMock.encodeImage).not.toHaveBeenCalled(); @@ -111,14 +104,23 @@ describe(SmartInfoService.name, () => { it('should save the returned objects', async () => { machineMock.encodeImage.mockResolvedValue([0.01, 0.02, 0.03]); - await sut.handleEncodeClip({ id: asset.id }); + expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.SUCCESS); expect(machineMock.encodeImage).toHaveBeenCalledWith( 'http://immich-machine-learning:3003', - { imagePath: 'path/to/resize.ext' }, + { imagePath: assetStub.image.previewPath }, { enabled: true, modelName: 'ViT-B-32__openai' }, ); - expect(searchMock.upsert).toHaveBeenCalledWith('asset-1', [0.01, 0.02, 0.03]); + expect(searchMock.upsert).toHaveBeenCalledWith(assetStub.image.id, [0.01, 0.02, 0.03]); + }); + + it('should skip invisible assets', async () => { + assetMock.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]); + + expect(await sut.handleEncodeClip({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.SKIPPED); + + expect(machineMock.encodeImage).not.toHaveBeenCalled(); + expect(searchMock.upsert).not.toHaveBeenCalled(); }); }); diff --git a/server/src/services/smart-info.service.ts b/server/src/services/smart-info.service.ts index 9de5edbd88..929d15beca 100644 --- a/server/src/services/smart-info.service.ts +++ b/server/src/services/smart-info.service.ts @@ -60,7 +60,7 @@ export class SmartInfoService { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force - ? this.assetRepository.getAll(pagination) + ? this.assetRepository.getAll(pagination, { isVisible: true }) : this.assetRepository.getWithout(pagination, WithoutProperty.SMART_SEARCH); }); @@ -84,6 +84,10 @@ export class SmartInfoService { return JobStatus.FAILED; } + if (!asset.isVisible) { + return JobStatus.SKIPPED; + } + if (!asset.previewPath) { return JobStatus.FAILED; } From d2b5cc6a4abd2a8dde899e76294f38b473bbdef1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 22:44:37 -0400 Subject: [PATCH 13/46] chore(deps): update registry.hub.docker.com/library/redis:6.2-alpine docker digest to 84882e8 (#8913) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 9516ea9ca7..731bd4c90a 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -58,7 +58,7 @@ services: redis: container_name: immich_redis - image: registry.hub.docker.com/library/redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5 + image: registry.hub.docker.com/library/redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672 restart: always database: From efd8f0d6487082cb75ae94f04112155bf702462d Mon Sep 17 00:00:00 2001 From: martin <74269598+martabal@users.noreply.github.com> Date: Fri, 19 Apr 2024 04:55:11 +0200 Subject: [PATCH 14/46] fix(web): notification number of people when editing faces (#7352) * fix: notification number of people when editing faces * fix: lint * fix: use id instead of index * rename --- .../faces-page/assign-face-side-panel.svelte | 8 +- .../faces-page/person-side-panel.svelte | 147 ++++++++++-------- 2 files changed, 88 insertions(+), 67 deletions(-) diff --git a/web/src/lib/components/faces-page/assign-face-side-panel.svelte b/web/src/lib/components/faces-page/assign-face-side-panel.svelte index ce7212f950..7f48a9eae8 100644 --- a/web/src/lib/components/faces-page/assign-face-side-panel.svelte +++ b/web/src/lib/components/faces-page/assign-face-side-panel.svelte @@ -21,7 +21,7 @@ export let peopleWithFaces: AssetFaceResponseDto[]; export let allPeople: PersonResponseDto[]; - export let editedPersonIndex: number; + export let editedPerson: PersonResponseDto; export let assetType: AssetTypeEnum; export let assetId: string; @@ -106,7 +106,7 @@ const handleCreatePerson = async () => { const timeout = setTimeout(() => (isShowLoadingNewPerson = true), timeBeforeShowLoadingSpinner); - const personToUpdate = peopleWithFaces.find((person) => person.id === peopleWithFaces[editedPersonIndex].id); + const personToUpdate = peopleWithFaces.find((face) => face.person?.id === editedPerson.id); const newFeaturePhoto = personToUpdate ? await zoomImageToBase64(personToUpdate) : null; @@ -229,7 +229,7 @@
{#if searchName == ''} {#each allPeople as person (person.id)} - {#if person.id !== peopleWithFaces[editedPersonIndex].person?.id} + {#if person.id !== editedPerson.id}
{:else} -