mirror of
https://github.com/immich-app/immich.git
synced 2025-01-04 02:46:47 +01:00
refactor(web) open api client (#7103)
* refactor: person api * refactor: shared link and others
This commit is contained in:
parent
5fc1d43012
commit
d8631a00bb
81 changed files with 638 additions and 656 deletions
|
@ -1,135 +1,15 @@
|
||||||
import {
|
import { AssetApi, DownloadApi, configuration } from '@immich/sdk/axios';
|
||||||
AssetApi,
|
|
||||||
AssetApiFp,
|
|
||||||
AssetJobName,
|
|
||||||
DownloadApi,
|
|
||||||
JobName,
|
|
||||||
PersonApi,
|
|
||||||
SearchApi,
|
|
||||||
SharedLinkApi,
|
|
||||||
UserApiFp,
|
|
||||||
base,
|
|
||||||
common,
|
|
||||||
configuration,
|
|
||||||
} from '@immich/sdk/axios';
|
|
||||||
import type { ApiParams as ApiParameters } from './types';
|
|
||||||
|
|
||||||
class ImmichApi {
|
class ImmichApi {
|
||||||
public downloadApi: DownloadApi;
|
public downloadApi: DownloadApi;
|
||||||
public assetApi: AssetApi;
|
public assetApi: AssetApi;
|
||||||
public searchApi: SearchApi;
|
|
||||||
public sharedLinkApi: SharedLinkApi;
|
|
||||||
public personApi: PersonApi;
|
|
||||||
|
|
||||||
private config: configuration.Configuration;
|
private config: configuration.Configuration;
|
||||||
private key?: string;
|
|
||||||
|
|
||||||
get isSharedLink() {
|
|
||||||
return !!this.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(parameters: configuration.ConfigurationParameters) {
|
constructor(parameters: configuration.ConfigurationParameters) {
|
||||||
this.config = new configuration.Configuration(parameters);
|
this.config = new configuration.Configuration(parameters);
|
||||||
|
|
||||||
this.downloadApi = new DownloadApi(this.config);
|
this.downloadApi = new DownloadApi(this.config);
|
||||||
this.assetApi = new AssetApi(this.config);
|
this.assetApi = new AssetApi(this.config);
|
||||||
this.searchApi = new SearchApi(this.config);
|
|
||||||
this.sharedLinkApi = new SharedLinkApi(this.config);
|
|
||||||
this.personApi = new PersonApi(this.config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createUrl(path: string, parameters?: Record<string, unknown>) {
|
|
||||||
const searchParameters = new URLSearchParams();
|
|
||||||
for (const key in parameters) {
|
|
||||||
const value = parameters[key];
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
searchParameters.set(key, value.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(path, common.DUMMY_BASE_URL);
|
|
||||||
url.search = searchParameters.toString();
|
|
||||||
|
|
||||||
return (this.config.basePath || base.BASE_PATH) + common.toPathString(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
public setKey(key: string) {
|
|
||||||
this.key = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getKey(): string | undefined {
|
|
||||||
return this.key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setAccessToken(accessToken: string) {
|
|
||||||
this.config.accessToken = accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
public removeAccessToken() {
|
|
||||||
this.config.accessToken = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setBaseUrl(baseUrl: string) {
|
|
||||||
this.config.basePath = baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAssetFileUrl(...[assetId, isThumb, isWeb]: ApiParameters<typeof AssetApiFp, 'serveFile'>) {
|
|
||||||
const path = `/asset/file/${assetId}`;
|
|
||||||
return this.createUrl(path, { isThumb, isWeb, key: this.getKey() });
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAssetThumbnailUrl(...[assetId, format]: ApiParameters<typeof AssetApiFp, 'getAssetThumbnail'>) {
|
|
||||||
const path = `/asset/thumbnail/${assetId}`;
|
|
||||||
return this.createUrl(path, { format, key: this.getKey() });
|
|
||||||
}
|
|
||||||
|
|
||||||
public getProfileImageUrl(...[userId]: ApiParameters<typeof UserApiFp, 'getProfileImage'>) {
|
|
||||||
const path = `/user/profile-image/${userId}`;
|
|
||||||
return this.createUrl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPeopleThumbnailUrl(personId: string) {
|
|
||||||
const path = `/person/${personId}/thumbnail`;
|
|
||||||
return this.createUrl(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getJobName(jobName: JobName) {
|
|
||||||
const names: Record<JobName, string> = {
|
|
||||||
[JobName.ThumbnailGeneration]: 'Generate Thumbnails',
|
|
||||||
[JobName.MetadataExtraction]: 'Extract Metadata',
|
|
||||||
[JobName.Sidecar]: 'Sidecar Metadata',
|
|
||||||
[JobName.SmartSearch]: 'Smart Search',
|
|
||||||
[JobName.FaceDetection]: 'Face Detection',
|
|
||||||
[JobName.FacialRecognition]: 'Facial Recognition',
|
|
||||||
[JobName.VideoConversion]: 'Transcode Videos',
|
|
||||||
[JobName.StorageTemplateMigration]: 'Storage Template Migration',
|
|
||||||
[JobName.Migration]: 'Migration',
|
|
||||||
[JobName.BackgroundTask]: 'Background Tasks',
|
|
||||||
[JobName.Search]: 'Search',
|
|
||||||
[JobName.Library]: 'Library',
|
|
||||||
};
|
|
||||||
|
|
||||||
return names[jobName];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAssetJobName(job: AssetJobName) {
|
|
||||||
const names: Record<AssetJobName, string> = {
|
|
||||||
[AssetJobName.RefreshMetadata]: 'Refresh metadata',
|
|
||||||
[AssetJobName.RegenerateThumbnail]: 'Refresh thumbnails',
|
|
||||||
[AssetJobName.TranscodeVideo]: 'Refresh encoded videos',
|
|
||||||
};
|
|
||||||
|
|
||||||
return names[job];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAssetJobMessage(job: AssetJobName) {
|
|
||||||
const messages: Record<AssetJobName, string> = {
|
|
||||||
[AssetJobName.RefreshMetadata]: 'Refreshing metadata',
|
|
||||||
[AssetJobName.RegenerateThumbnail]: `Regenerating thumbnails`,
|
|
||||||
[AssetJobName.TranscodeVideo]: `Refreshing encoded video`,
|
|
||||||
};
|
|
||||||
|
|
||||||
return messages[job];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { finishOAuth, linkOAuthAccount, startOAuth, unlinkOAuthAccount } from '@immich/sdk';
|
import { finishOAuth, linkOAuthAccount, startOAuth, unlinkOAuthAccount } from '@immich/sdk';
|
||||||
import type { UserResponseDto } from '@immich/sdk/axios';
|
import { type UserResponseDto } from '@immich/sdk/axios';
|
||||||
import type { AxiosError } from 'axios';
|
import type { AxiosError } from 'axios';
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { JobCommand, type JobCommandDto, type JobCountsDto, type QueueStatusDto } from '@api';
|
|
||||||
import Badge from '$lib/components/elements/badge.svelte';
|
import Badge from '$lib/components/elements/badge.svelte';
|
||||||
import JobTileButton from './job-tile-button.svelte';
|
|
||||||
import JobTileStatus from './job-tile-status.svelte';
|
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
import { type JobCommandDto, type JobCountsDto, type QueueStatusDto } from '@immich/sdk';
|
||||||
|
import { JobCommand } from '@immich/sdk/axios';
|
||||||
import {
|
import {
|
||||||
mdiAlertCircle,
|
mdiAlertCircle,
|
||||||
mdiAllInclusive,
|
mdiAllInclusive,
|
||||||
|
@ -16,6 +14,9 @@
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiSelectionSearch,
|
mdiSelectionSearch,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import JobTileButton from './job-tile-button.svelte';
|
||||||
|
import JobTileStatus from './job-tile-status.svelte';
|
||||||
|
|
||||||
export let title: string;
|
export let title: string;
|
||||||
export let subtitle: string | undefined;
|
export let subtitle: string | undefined;
|
||||||
|
|
|
@ -4,9 +4,10 @@
|
||||||
NotificationType,
|
NotificationType,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
|
import { getJobName } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { type AllJobStatusResponseDto, api, JobCommand, type JobCommandDto, JobName } from '@api';
|
import { sendJobCommand, type AllJobStatusResponseDto, type JobCommandDto } from '@immich/sdk';
|
||||||
import type { ComponentType } from 'svelte';
|
import { JobCommand, JobName } from '@immich/sdk/axios';
|
||||||
import {
|
import {
|
||||||
mdiFaceRecognition,
|
mdiFaceRecognition,
|
||||||
mdiFileJpgBox,
|
mdiFileJpgBox,
|
||||||
|
@ -18,10 +19,10 @@
|
||||||
mdiTagFaces,
|
mdiTagFaces,
|
||||||
mdiVideo,
|
mdiVideo,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
|
import type { ComponentType } from 'svelte';
|
||||||
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
|
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
|
||||||
import JobTile from './job-tile.svelte';
|
import JobTile from './job-tile.svelte';
|
||||||
import StorageMigrationDescription from './storage-migration-description.svelte';
|
import StorageMigrationDescription from './storage-migration-description.svelte';
|
||||||
import { sendJobCommand } from '@immich/sdk';
|
|
||||||
|
|
||||||
export let jobs: AllJobStatusResponseDto;
|
export let jobs: AllJobStatusResponseDto;
|
||||||
|
|
||||||
|
@ -59,23 +60,23 @@
|
||||||
$: jobDetails = <Partial<Record<JobName, JobDetails>>>{
|
$: jobDetails = <Partial<Record<JobName, JobDetails>>>{
|
||||||
[JobName.ThumbnailGeneration]: {
|
[JobName.ThumbnailGeneration]: {
|
||||||
icon: mdiFileJpgBox,
|
icon: mdiFileJpgBox,
|
||||||
title: api.getJobName(JobName.ThumbnailGeneration),
|
title: getJobName(JobName.ThumbnailGeneration),
|
||||||
subtitle: 'Generate large, small and blurred thumbnails for each asset, as well as thumbnails for each person',
|
subtitle: 'Generate large, small and blurred thumbnails for each asset, as well as thumbnails for each person',
|
||||||
},
|
},
|
||||||
[JobName.MetadataExtraction]: {
|
[JobName.MetadataExtraction]: {
|
||||||
icon: mdiTable,
|
icon: mdiTable,
|
||||||
title: api.getJobName(JobName.MetadataExtraction),
|
title: getJobName(JobName.MetadataExtraction),
|
||||||
subtitle: 'Extract metadata information from each asset, such as GPS and resolution',
|
subtitle: 'Extract metadata information from each asset, such as GPS and resolution',
|
||||||
},
|
},
|
||||||
[JobName.Library]: {
|
[JobName.Library]: {
|
||||||
icon: mdiLibraryShelves,
|
icon: mdiLibraryShelves,
|
||||||
title: api.getJobName(JobName.Library),
|
title: getJobName(JobName.Library),
|
||||||
subtitle: 'Perform library tasks',
|
subtitle: 'Perform library tasks',
|
||||||
allText: 'ALL',
|
allText: 'ALL',
|
||||||
missingText: 'REFRESH',
|
missingText: 'REFRESH',
|
||||||
},
|
},
|
||||||
[JobName.Sidecar]: {
|
[JobName.Sidecar]: {
|
||||||
title: api.getJobName(JobName.Sidecar),
|
title: getJobName(JobName.Sidecar),
|
||||||
icon: mdiFileXmlBox,
|
icon: mdiFileXmlBox,
|
||||||
subtitle: 'Discover or synchronize sidecar metadata from the filesystem',
|
subtitle: 'Discover or synchronize sidecar metadata from the filesystem',
|
||||||
allText: 'SYNC',
|
allText: 'SYNC',
|
||||||
|
@ -84,13 +85,13 @@
|
||||||
},
|
},
|
||||||
[JobName.SmartSearch]: {
|
[JobName.SmartSearch]: {
|
||||||
icon: mdiImageSearch,
|
icon: mdiImageSearch,
|
||||||
title: api.getJobName(JobName.SmartSearch),
|
title: getJobName(JobName.SmartSearch),
|
||||||
subtitle: 'Run machine learning on assets to support smart search',
|
subtitle: 'Run machine learning on assets to support smart search',
|
||||||
disabled: !$featureFlags.smartSearch,
|
disabled: !$featureFlags.smartSearch,
|
||||||
},
|
},
|
||||||
[JobName.FaceDetection]: {
|
[JobName.FaceDetection]: {
|
||||||
icon: mdiFaceRecognition,
|
icon: mdiFaceRecognition,
|
||||||
title: api.getJobName(JobName.FaceDetection),
|
title: getJobName(JobName.FaceDetection),
|
||||||
subtitle:
|
subtitle:
|
||||||
'Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. "All" (re-)processes all assets. "Missing" queues assets that haven\'t been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.',
|
'Detect the faces in assets using machine learning. For videos, only the thumbnail is considered. "All" (re-)processes all assets. "Missing" queues assets that haven\'t been processed yet. Detected faces will be queued for Facial Recognition after Face Detection is complete, grouping them into existing or new people.',
|
||||||
handleCommand: handleConfirmCommand,
|
handleCommand: handleConfirmCommand,
|
||||||
|
@ -98,7 +99,7 @@
|
||||||
},
|
},
|
||||||
[JobName.FacialRecognition]: {
|
[JobName.FacialRecognition]: {
|
||||||
icon: mdiTagFaces,
|
icon: mdiTagFaces,
|
||||||
title: api.getJobName(JobName.FacialRecognition),
|
title: getJobName(JobName.FacialRecognition),
|
||||||
subtitle:
|
subtitle:
|
||||||
'Group detected faces into people. This step runs after Face Detection is complete. "All" (re-)clusters all faces. "Missing" queues faces that don\'t have a person assigned.',
|
'Group detected faces into people. This step runs after Face Detection is complete. "All" (re-)clusters all faces. "Missing" queues faces that don\'t have a person assigned.',
|
||||||
handleCommand: handleConfirmCommand,
|
handleCommand: handleConfirmCommand,
|
||||||
|
@ -106,18 +107,18 @@
|
||||||
},
|
},
|
||||||
[JobName.VideoConversion]: {
|
[JobName.VideoConversion]: {
|
||||||
icon: mdiVideo,
|
icon: mdiVideo,
|
||||||
title: api.getJobName(JobName.VideoConversion),
|
title: getJobName(JobName.VideoConversion),
|
||||||
subtitle: 'Transcode videos for wider compatibility with browsers and devices',
|
subtitle: 'Transcode videos for wider compatibility with browsers and devices',
|
||||||
},
|
},
|
||||||
[JobName.StorageTemplateMigration]: {
|
[JobName.StorageTemplateMigration]: {
|
||||||
icon: mdiFolderMove,
|
icon: mdiFolderMove,
|
||||||
title: api.getJobName(JobName.StorageTemplateMigration),
|
title: getJobName(JobName.StorageTemplateMigration),
|
||||||
allowForceCommand: false,
|
allowForceCommand: false,
|
||||||
component: StorageMigrationDescription,
|
component: StorageMigrationDescription,
|
||||||
},
|
},
|
||||||
[JobName.Migration]: {
|
[JobName.Migration]: {
|
||||||
icon: mdiFolderMove,
|
icon: mdiFolderMove,
|
||||||
title: api.getJobName(JobName.Migration),
|
title: getJobName(JobName.Migration),
|
||||||
subtitle: 'Migrate thumbnails for assets and faces to the latest folder structure',
|
subtitle: 'Migrate thumbnails for assets and faces to the latest folder structure',
|
||||||
allowForceCommand: false,
|
allowForceCommand: false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
|
||||||
import type { ServerStatsResponseDto } from '@api';
|
|
||||||
import { asByteUnitString, getBytesWithUnit } from '$lib/utils/byte-units';
|
|
||||||
import StatsCard from './stats-card.svelte';
|
|
||||||
import { mdiCameraIris, mdiChartPie, mdiPlayCircle } from '@mdi/js';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
import { asByteUnitString, getBytesWithUnit } from '$lib/utils/byte-units';
|
||||||
|
import type { ServerStatsResponseDto } from '@immich/sdk';
|
||||||
|
import { mdiCameraIris, mdiChartPie, mdiPlayCircle } from '@mdi/js';
|
||||||
|
import StatsCard from './stats-card.svelte';
|
||||||
|
|
||||||
export let stats: ServerStatsResponseDto = {
|
export let stats: ServerStatsResponseDto = {
|
||||||
photos: 0,
|
photos: 0,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { ResetOptions } from '$lib/utils/dipatch';
|
import type { ResetOptions } from '$lib/utils/dipatch';
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
|
|
||||||
export type SettingsEventType = {
|
export type SettingsEventType = {
|
||||||
reset: ResetOptions & { configKeys: Array<keyof SystemConfigDto> };
|
reset: ResetOptions & { configKeys: Array<keyof SystemConfigDto> };
|
||||||
|
|
|
@ -1,25 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
AudioCodec,
|
import { type SystemConfigDto } from '@immich/sdk';
|
||||||
CQMode,
|
import { AudioCodec, CQMode, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@immich/sdk/axios';
|
||||||
type SystemConfigDto,
|
import { mdiHelpCircleOutline } from '@mdi/js';
|
||||||
ToneMapping,
|
import { isEqual, sortBy } from 'lodash-es';
|
||||||
TranscodeHWAccel,
|
import { createEventDispatcher } from 'svelte';
|
||||||
TranscodePolicy,
|
import { fade } from 'svelte/transition';
|
||||||
VideoCodec,
|
import type { SettingsEventType } from '../admin-settings';
|
||||||
} from '@api';
|
import SettingAccordion from '../setting-accordion.svelte';
|
||||||
import SettingButtonsRow from '../setting-buttons-row.svelte';
|
import SettingButtonsRow from '../setting-buttons-row.svelte';
|
||||||
|
import SettingCheckboxes from '../setting-checkboxes.svelte';
|
||||||
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
|
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
|
||||||
import SettingSelect from '../setting-select.svelte';
|
import SettingSelect from '../setting-select.svelte';
|
||||||
import SettingSwitch from '../setting-switch.svelte';
|
import SettingSwitch from '../setting-switch.svelte';
|
||||||
import SettingCheckboxes from '../setting-checkboxes.svelte';
|
|
||||||
import { isEqual, sortBy } from 'lodash-es';
|
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import SettingAccordion from '../setting-accordion.svelte';
|
|
||||||
import { mdiHelpCircleOutline } from '@mdi/js';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import type { SettingsEventType } from '../admin-settings';
|
|
||||||
|
|
||||||
export let savedConfig: SystemConfigDto;
|
export let savedConfig: SystemConfigDto;
|
||||||
export let defaultConfig: SystemConfigDto;
|
export let defaultConfig: SystemConfigDto;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, JobName, type SystemConfigDto, type SystemConfigJobDto } from '@api';
|
import { getJobName } from '$lib/utils';
|
||||||
|
import { type SystemConfigDto, type SystemConfigJobDto } from '@immich/sdk';
|
||||||
|
import { JobName } from '@immich/sdk/axios';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
@ -41,7 +43,7 @@
|
||||||
<SettingInputField
|
<SettingInputField
|
||||||
inputType={SettingInputFieldType.NUMBER}
|
inputType={SettingInputFieldType.NUMBER}
|
||||||
{disabled}
|
{disabled}
|
||||||
label="{api.getJobName(jobName)} Concurrency"
|
label="{getJobName(jobName)} Concurrency"
|
||||||
desc=""
|
desc=""
|
||||||
bind:value={config.job[jobName].concurrency}
|
bind:value={config.job[jobName].concurrency}
|
||||||
required={true}
|
required={true}
|
||||||
|
@ -50,7 +52,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<SettingInputField
|
<SettingInputField
|
||||||
inputType={SettingInputFieldType.NUMBER}
|
inputType={SettingInputFieldType.NUMBER}
|
||||||
label="{api.getJobName(jobName)} Concurrency"
|
label="{getJobName(jobName)} Concurrency"
|
||||||
desc=""
|
desc=""
|
||||||
value="1"
|
value="1"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { LogLevel, type SystemConfigDto } from '@api';
|
import { type SystemConfigDto } from '@immich/sdk';
|
||||||
|
import { LogLevel } from '@immich/sdk/axios';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte';
|
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte';
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
import type { SettingsEventType } from '../admin-settings';
|
import type { SettingsEventType } from '../admin-settings';
|
||||||
|
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
|
||||||
|
|
||||||
export let savedConfig: SystemConfigDto;
|
export let savedConfig: SystemConfigDto;
|
||||||
export let defaultConfig: SystemConfigDto;
|
export let defaultConfig: SystemConfigDto;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigTemplateStorageOptionDto } from '@api';
|
import type { SystemConfigTemplateStorageOptionDto } from '@immich/sdk';
|
||||||
import * as luxon from 'luxon';
|
import * as luxon from 'luxon';
|
||||||
|
|
||||||
export let options: SystemConfigTemplateStorageOptionDto;
|
export let options: SystemConfigTemplateStorageOptionDto;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte';
|
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.svelte';
|
||||||
import SettingSelect from '$lib/components/admin-page/settings/setting-select.svelte';
|
import SettingSelect from '$lib/components/admin-page/settings/setting-select.svelte';
|
||||||
import { Colorspace, type SystemConfigDto } from '@api';
|
import { type SystemConfigDto } from '@immich/sdk';
|
||||||
|
import { Colorspace } from '@immich/sdk/axios';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SystemConfigDto } from '@api';
|
import type { SystemConfigDto } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { ThumbnailFormat, api, type AlbumResponseDto } from '@api';
|
import { ThumbnailFormat, api, type AlbumResponseDto } from '@api';
|
||||||
import { getUserById } from '@immich/sdk';
|
import { getUserById } from '@immich/sdk';
|
||||||
import { mdiDotsVertical } from '@mdi/js';
|
import { mdiDotsVertical } from '@mdi/js';
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
let showVerticalDots = false;
|
let showVerticalDots = false;
|
||||||
|
|
||||||
$: imageData = album.albumThumbnailAssetId
|
$: imageData = album.albumThumbnailAssetId
|
||||||
? api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
|
? getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
|
||||||
: noThumbnailUrl;
|
: noThumbnailUrl;
|
||||||
|
|
||||||
const dispatchClick = createEventDispatcher<OnClick>();
|
const dispatchClick = createEventDispatcher<OnClick>();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AlbumResponseDto } from '@api';
|
import type { AlbumResponseDto } from '@immich/sdk';
|
||||||
|
|
||||||
export type OnShowContextMenu = {
|
export type OnShowContextMenu = {
|
||||||
showalbumcontextmenu: OnShowContextMenuDetail;
|
showalbumcontextmenu: OnShowContextMenuDetail;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type AlbumResponseDto, type UserResponseDto } from '@api';
|
import { type AlbumResponseDto, type UserResponseDto } from '@immich/sdk';
|
||||||
import { getMyUserInfo, removeUserFromAlbum } from '@immich/sdk';
|
import { getMyUserInfo, removeUserFromAlbum } from '@immich/sdk';
|
||||||
import { mdiDotsVertical } from '@mdi/js';
|
import { mdiDotsVertical } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
|
|
@ -2,8 +2,13 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { api, type AlbumResponseDto, type SharedLinkResponseDto, type UserResponseDto } from '@api';
|
import {
|
||||||
import { getAllUsers } from '@immich/sdk';
|
getAllSharedLinks,
|
||||||
|
getAllUsers,
|
||||||
|
type AlbumResponseDto,
|
||||||
|
type SharedLinkResponseDto,
|
||||||
|
type UserResponseDto,
|
||||||
|
} from '@immich/sdk';
|
||||||
import { mdiCheck, mdiLink, mdiShareCircle } from '@mdi/js';
|
import { mdiCheck, mdiLink, mdiShareCircle } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
|
@ -35,8 +40,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSharedLinks = async () => {
|
const getSharedLinks = async () => {
|
||||||
const { data } = await api.sharedLinkApi.getAllSharedLinks();
|
const data = await getAllSharedLinks();
|
||||||
|
|
||||||
sharedLinks = data.filter((link) => link.album?.id === album.id);
|
sharedLinks = data.filter((link) => link.album?.id === album.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { getAssetType } from '$lib/utils/asset-utils';
|
import { getAssetType } from '$lib/utils/asset-utils';
|
||||||
import { autoGrowHeight } from '$lib/utils/autogrow';
|
import { autoGrowHeight } from '$lib/utils/autogrow';
|
||||||
import { clickOutside } from '$lib/utils/click-outside';
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { isTenMinutesApart } from '$lib/utils/timesince';
|
import { isTenMinutesApart } from '$lib/utils/timesince';
|
||||||
import {
|
import {
|
||||||
AssetTypeEnum,
|
createActivity,
|
||||||
ReactionType,
|
deleteActivity,
|
||||||
ThumbnailFormat,
|
getActivities,
|
||||||
api,
|
|
||||||
type ActivityResponseDto,
|
type ActivityResponseDto,
|
||||||
|
type AssetTypeEnum,
|
||||||
type UserResponseDto,
|
type UserResponseDto,
|
||||||
} from '@api';
|
} from '@immich/sdk';
|
||||||
import { createActivity, deleteActivity, getActivities } from '@immich/sdk';
|
import { ReactionType, ThumbnailFormat } from '@immich/sdk/axios';
|
||||||
import { mdiClose, mdiDotsVertical, mdiHeart, mdiSend } from '@mdi/js';
|
import { mdiClose, mdiDotsVertical, mdiHeart, mdiSend } from '@mdi/js';
|
||||||
import * as luxon from 'luxon';
|
import * as luxon from 'luxon';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
@ -191,7 +192,7 @@
|
||||||
<div class="aspect-square w-[75px] h-[75px]">
|
<div class="aspect-square w-[75px] h-[75px]">
|
||||||
<img
|
<img
|
||||||
class="rounded-lg w-[75px] h-[75px] object-cover"
|
class="rounded-lg w-[75px] h-[75px] object-cover"
|
||||||
src={api.getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
src={getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
||||||
alt="comment-thumbnail"
|
alt="comment-thumbnail"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -237,7 +238,7 @@
|
||||||
<div class="aspect-square w-[75px] h-[75px]">
|
<div class="aspect-square w-[75px] h-[75px]">
|
||||||
<img
|
<img
|
||||||
class="rounded-lg w-[75px] h-[75px] object-cover"
|
class="rounded-lg w-[75px] h-[75px] object-cover"
|
||||||
src={api.getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
src={getAssetThumbnailUrl(reaction.assetId, ThumbnailFormat.Webp)}
|
||||||
alt="like-thumbnail"
|
alt="like-thumbnail"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type AlbumResponseDto, ThumbnailFormat, api } from '@api';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
|
import { ThumbnailFormat, type AlbumResponseDto } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
<div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">
|
<div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">
|
||||||
{#if album.albumThumbnailAssetId}
|
{#if album.albumThumbnailAssetId}
|
||||||
<img
|
<img
|
||||||
src={api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
|
src={getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
|
||||||
alt={album.albumName}
|
alt={album.albumName}
|
||||||
class="z-0 h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg"
|
class="z-0 h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg"
|
||||||
data-testid="album-image"
|
data-testid="album-image"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||||
|
import { getAssetJobName } from '$lib/utils';
|
||||||
import { clickOutside } from '$lib/utils/click-outside';
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
import { getContextMenuPosition } from '$lib/utils/context-menu';
|
import { getContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
import { AssetJobName, type AssetResponseDto, AssetTypeEnum, api } from '@api';
|
import { AssetJobName, AssetTypeEnum, type AssetResponseDto } from '@api';
|
||||||
import {
|
import {
|
||||||
mdiAlertOutline,
|
mdiAlertOutline,
|
||||||
mdiArrowLeft,
|
mdiArrowLeft,
|
||||||
|
@ -22,7 +24,6 @@
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
|
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
|
||||||
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let showCopyButton: boolean;
|
export let showCopyButton: boolean;
|
||||||
|
@ -182,16 +183,16 @@
|
||||||
|
|
||||||
<MenuOption
|
<MenuOption
|
||||||
on:click={() => onJobClick(AssetJobName.RefreshMetadata)}
|
on:click={() => onJobClick(AssetJobName.RefreshMetadata)}
|
||||||
text={api.getAssetJobName(AssetJobName.RefreshMetadata)}
|
text={getAssetJobName(AssetJobName.RefreshMetadata)}
|
||||||
/>
|
/>
|
||||||
<MenuOption
|
<MenuOption
|
||||||
on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)}
|
on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)}
|
||||||
text={api.getAssetJobName(AssetJobName.RegenerateThumbnail)}
|
text={getAssetJobName(AssetJobName.RegenerateThumbnail)}
|
||||||
/>
|
/>
|
||||||
{#if asset.type === AssetTypeEnum.Video}
|
{#if asset.type === AssetTypeEnum.Video}
|
||||||
<MenuOption
|
<MenuOption
|
||||||
on:click={() => onJobClick(AssetJobName.TranscodeVideo)}
|
on:click={() => onJobClick(AssetJobName.TranscodeVideo)}
|
||||||
text={api.getAssetJobName(AssetJobName.TranscodeVideo)}
|
text={getAssetJobName(AssetJobName.TranscodeVideo)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
|
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { getAssetJobMessage, isSharedLink } from '$lib/utils';
|
||||||
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
|
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
|
@ -19,7 +20,6 @@
|
||||||
AssetJobName,
|
AssetJobName,
|
||||||
AssetTypeEnum,
|
AssetTypeEnum,
|
||||||
ReactionType,
|
ReactionType,
|
||||||
api,
|
|
||||||
type ActivityResponseDto,
|
type ActivityResponseDto,
|
||||||
type AlbumResponseDto,
|
type AlbumResponseDto,
|
||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
|
@ -29,9 +29,13 @@
|
||||||
createActivity,
|
createActivity,
|
||||||
createAlbum,
|
createAlbum,
|
||||||
deleteActivity,
|
deleteActivity,
|
||||||
|
deleteAssets,
|
||||||
getActivities,
|
getActivities,
|
||||||
getActivityStatistics,
|
getActivityStatistics,
|
||||||
getAllAlbums,
|
getAllAlbums,
|
||||||
|
runAssetJobs,
|
||||||
|
updateAsset,
|
||||||
|
updateAssets,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { mdiChevronLeft, mdiChevronRight, mdiImageBrokenVariant } from '@mdi/js';
|
import { mdiChevronLeft, mdiChevronRight, mdiImageBrokenVariant } from '@mdi/js';
|
||||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||||
|
@ -235,7 +239,7 @@
|
||||||
$: asset.id && !sharedLink && handleGetAllAlbums(); // Update the album information when the asset ID changes
|
$: asset.id && !sharedLink && handleGetAllAlbums(); // Update the album information when the asset ID changes
|
||||||
|
|
||||||
const handleGetAllAlbums = async () => {
|
const handleGetAllAlbums = async () => {
|
||||||
if (api.isSharedLink) {
|
if (isSharedLink()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +390,7 @@
|
||||||
|
|
||||||
const trashAsset = async () => {
|
const trashAsset = async () => {
|
||||||
try {
|
try {
|
||||||
await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } });
|
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } });
|
||||||
|
|
||||||
dispatch('action', { type: AssetAction.TRASH, asset });
|
dispatch('action', { type: AssetAction.TRASH, asset });
|
||||||
|
|
||||||
|
@ -401,7 +405,7 @@
|
||||||
|
|
||||||
const deleteAsset = async () => {
|
const deleteAsset = async () => {
|
||||||
try {
|
try {
|
||||||
await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
|
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
|
||||||
|
|
||||||
dispatch('action', { type: AssetAction.DELETE, asset });
|
dispatch('action', { type: AssetAction.DELETE, asset });
|
||||||
|
|
||||||
|
@ -418,7 +422,7 @@
|
||||||
|
|
||||||
const toggleFavorite = async () => {
|
const toggleFavorite = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.updateAsset({
|
const data = await updateAsset({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
updateAssetDto: {
|
updateAssetDto: {
|
||||||
isFavorite: !asset.isFavorite,
|
isFavorite: !asset.isFavorite,
|
||||||
|
@ -470,7 +474,7 @@
|
||||||
|
|
||||||
const toggleArchive = async () => {
|
const toggleArchive = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.assetApi.updateAsset({
|
const data = await updateAsset({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
updateAssetDto: {
|
updateAssetDto: {
|
||||||
isArchived: !asset.isArchived,
|
isArchived: !asset.isArchived,
|
||||||
|
@ -491,8 +495,8 @@
|
||||||
|
|
||||||
const handleRunJob = async (name: AssetJobName) => {
|
const handleRunJob = async (name: AssetJobName) => {
|
||||||
try {
|
try {
|
||||||
await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
|
await runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
|
||||||
notificationController.show({ type: NotificationType.Info, message: api.getAssetJobMessage(name) });
|
notificationController.show({ type: NotificationType.Info, message: getAssetJobMessage(name) });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await handleError(error, `Unable to submit job`);
|
await handleError(error, `Unable to submit job`);
|
||||||
}
|
}
|
||||||
|
@ -552,7 +556,7 @@
|
||||||
const handleUnstack = async () => {
|
const handleUnstack = async () => {
|
||||||
try {
|
try {
|
||||||
const ids = $stackAssetsStore.map(({ id }) => id);
|
const ids = $stackAssetsStore.map(({ id }) => id);
|
||||||
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, removeParent: true } });
|
await updateAssets({ assetBulkUpdateDto: { ids, removeParent: true } });
|
||||||
for (const child of $stackAssetsStore) {
|
for (const child of $stackAssetsStore) {
|
||||||
child.stackParentId = null;
|
child.stackParentId = null;
|
||||||
child.stackCount = 0;
|
child.stackCount = 0;
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||||
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { websocketStore } from '$lib/stores/websocket';
|
||||||
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||||
import { getAssetFilename } from '$lib/utils/asset-utils';
|
import { getAssetFilename } from '$lib/utils/asset-utils';
|
||||||
import { type AlbumResponseDto, type AssetResponseDto, ThumbnailFormat, api } from '@api';
|
import { autoGrowHeight } from '$lib/utils/autogrow';
|
||||||
import { DateTime } from 'luxon';
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
import { ThumbnailFormat, type AlbumResponseDto, type AssetResponseDto } from '@api';
|
||||||
import { slide } from 'svelte/transition';
|
import { getAssetInfo, updateAsset } from '@immich/sdk';
|
||||||
import { asByteUnitString } from '../../utils/byte-units';
|
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
|
||||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
|
||||||
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
|
||||||
import {
|
import {
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
mdiCameraIris,
|
mdiCameraIris,
|
||||||
|
@ -17,22 +20,21 @@
|
||||||
mdiEye,
|
mdiEye,
|
||||||
mdiEyeOff,
|
mdiEyeOff,
|
||||||
mdiImageOutline,
|
mdiImageOutline,
|
||||||
mdiMapMarkerOutline,
|
|
||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
|
mdiMapMarkerOutline,
|
||||||
mdiPencil,
|
mdiPencil,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import { DateTime } from 'luxon';
|
||||||
import PersonSidePanel from '../faces-page/person-side-panel.svelte';
|
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import { slide } from 'svelte/transition';
|
||||||
import Map from '../shared-components/map/map.svelte';
|
import { asByteUnitString } from '../../utils/byte-units';
|
||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
|
||||||
import { websocketStore } from '$lib/stores/websocket';
|
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
|
||||||
import ChangeLocation from '../shared-components/change-location.svelte';
|
|
||||||
import { handleError } from '../../utils/handle-error';
|
import { handleError } from '../../utils/handle-error';
|
||||||
import { user } from '$lib/stores/user.store';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import { autoGrowHeight } from '$lib/utils/autogrow';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import { clickOutside } from '$lib/utils/click-outside';
|
import PersonSidePanel from '../faces-page/person-side-panel.svelte';
|
||||||
|
import ChangeLocation from '../shared-components/change-location.svelte';
|
||||||
|
import Map from '../shared-components/map/map.svelte';
|
||||||
|
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let albums: AlbumResponseDto[] = [];
|
export let albums: AlbumResponseDto[] = [];
|
||||||
|
@ -61,8 +63,8 @@
|
||||||
description = newAsset?.exifInfo?.description || '';
|
description = newAsset?.exifInfo?.description || '';
|
||||||
|
|
||||||
// Get latest description from server
|
// Get latest description from server
|
||||||
if (newAsset.id && !api.isSharedLink) {
|
if (newAsset.id && !isSharedLink()) {
|
||||||
const { data } = await api.assetApi.getAssetInfo({ id: asset.id });
|
const data = await getAssetInfo({ id: asset.id });
|
||||||
people = data?.people || [];
|
people = data?.people || [];
|
||||||
|
|
||||||
description = data.exifInfo?.description || '';
|
description = data.exifInfo?.description || '';
|
||||||
|
@ -127,9 +129,9 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRefreshPeople = async () => {
|
const handleRefreshPeople = async () => {
|
||||||
await api.assetApi.getAssetInfo({ id: asset.id }).then((res) => {
|
await getAssetInfo({ id: asset.id }).then((data) => {
|
||||||
people = res.data?.people || [];
|
people = data?.people || [];
|
||||||
textArea.value = res.data?.exifInfo?.description || '';
|
textArea.value = data?.exifInfo?.description || '';
|
||||||
});
|
});
|
||||||
showEditFaces = false;
|
showEditFaces = false;
|
||||||
};
|
};
|
||||||
|
@ -146,10 +148,7 @@
|
||||||
originalDescription = description;
|
originalDescription = description;
|
||||||
dispatch('descriptionFocusOut');
|
dispatch('descriptionFocusOut');
|
||||||
try {
|
try {
|
||||||
await api.assetApi.updateAsset({
|
await updateAsset({ id: asset.id, updateAssetDto: { description } });
|
||||||
id: asset.id,
|
|
||||||
updateAssetDto: { description },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Cannot update the description');
|
handleError(error, 'Cannot update the description');
|
||||||
}
|
}
|
||||||
|
@ -162,7 +161,7 @@
|
||||||
async function handleConfirmChangeDate(dateTimeOriginal: string) {
|
async function handleConfirmChangeDate(dateTimeOriginal: string) {
|
||||||
isShowChangeDate = false;
|
isShowChangeDate = false;
|
||||||
try {
|
try {
|
||||||
await api.assetApi.updateAsset({ id: asset.id, updateAssetDto: { dateTimeOriginal } });
|
await updateAsset({ id: asset.id, updateAssetDto: { dateTimeOriginal } });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to change date');
|
handleError(error, 'Unable to change date');
|
||||||
}
|
}
|
||||||
|
@ -174,13 +173,7 @@
|
||||||
isShowChangeLocation = false;
|
isShowChangeLocation = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.assetApi.updateAsset({
|
await updateAsset({ id: asset.id, updateAssetDto: { latitude: gps.lat, longitude: gps.lng } });
|
||||||
id: asset.id,
|
|
||||||
updateAssetDto: {
|
|
||||||
latitude: gps.lat,
|
|
||||||
longitude: gps.lng,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to change location');
|
handleError(error, 'Unable to change location');
|
||||||
}
|
}
|
||||||
|
@ -219,7 +212,7 @@
|
||||||
<section class="px-4 mt-10">
|
<section class="px-4 mt-10">
|
||||||
{#key asset.id}
|
{#key asset.id}
|
||||||
<textarea
|
<textarea
|
||||||
disabled={!isOwner || api.isSharedLink}
|
disabled={!isOwner || isSharedLink()}
|
||||||
bind:this={textArea}
|
bind:this={textArea}
|
||||||
class="max-h-[500px]
|
class="max-h-[500px]
|
||||||
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
|
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
|
||||||
|
@ -238,7 +231,7 @@
|
||||||
<p class="px-4 break-words whitespace-pre-line w-full text-black dark:text-white text-base">{description}</p>
|
<p class="px-4 break-words whitespace-pre-line w-full text-black dark:text-white text-base">{description}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !api.isSharedLink && people.length > 0}
|
{#if !isSharedLink() && people.length > 0}
|
||||||
<section class="px-4 py-4 text-sm">
|
<section class="px-4 py-4 text-sm">
|
||||||
<div class="flex h-10 w-full items-center justify-between">
|
<div class="flex h-10 w-full items-center justify-between">
|
||||||
<h2>PEOPLE</h2>
|
<h2>PEOPLE</h2>
|
||||||
|
@ -284,7 +277,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
title={person.name}
|
title={person.name}
|
||||||
widthStyle="90px"
|
widthStyle="90px"
|
||||||
|
@ -670,7 +663,7 @@
|
||||||
alt={album.albumName}
|
alt={album.albumName}
|
||||||
class="h-[50px] w-[50px] rounded object-cover"
|
class="h-[50px] w-[50px] rounded object-cover"
|
||||||
src={album.albumThumbnailAssetId &&
|
src={album.albumThumbnailAssetId &&
|
||||||
api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
|
getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { api, type AssetResponseDto } from '@api';
|
import { api, type AssetResponseDto } from '@api';
|
||||||
|
import { getKey } from '$lib/utils';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
|
|
||||||
const loadAssetData = async () => {
|
const loadAssetData = async () => {
|
||||||
const { data } = await api.assetApi.serveFile(
|
const { data } = await api.assetApi.serveFile(
|
||||||
{ id: asset.id, isThumb: false, isWeb: false, key: api.getKey() },
|
{ id: asset.id, isThumb: false, isWeb: false, key: getKey() },
|
||||||
{ responseType: 'blob' },
|
{ responseType: 'blob' },
|
||||||
);
|
);
|
||||||
if (data instanceof Blob) {
|
if (data instanceof Blob) {
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
|
||||||
import { api, type AssetResponseDto } from '@api';
|
|
||||||
import { notificationController, NotificationType } from '../shared-components/notification/notification';
|
|
||||||
import { useZoomImageWheel } from '@zoom-image/svelte';
|
|
||||||
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
|
||||||
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
|
||||||
import { photoViewer } from '$lib/stores/assets.store';
|
import { photoViewer } from '$lib/stores/assets.store';
|
||||||
import { getBoundingBox } from '$lib/utils/people-utils';
|
|
||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
import { alwaysLoadOriginalFile } from '$lib/stores/preferences.store';
|
||||||
|
import { photoZoomState } from '$lib/stores/zoom-image.store';
|
||||||
|
import { getKey } from '$lib/utils';
|
||||||
|
import { isWebCompatibleImage } from '$lib/utils/asset-utils';
|
||||||
|
import { getBoundingBox } from '$lib/utils/people-utils';
|
||||||
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
|
import { api, type AssetResponseDto } from '@api';
|
||||||
|
import { useZoomImageWheel } from '@zoom-image/svelte';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let element: HTMLDivElement | undefined = undefined;
|
export let element: HTMLDivElement | undefined = undefined;
|
||||||
|
@ -50,7 +51,7 @@
|
||||||
abortController = new AbortController();
|
abortController = new AbortController();
|
||||||
|
|
||||||
const { data } = await api.assetApi.serveFile(
|
const { data } = await api.assetApi.serveFile(
|
||||||
{ id: asset.id, isThumb: false, isWeb: !loadOriginal, key: api.getKey() },
|
{ id: asset.id, isThumb: false, isWeb: !loadOriginal, key: getKey() },
|
||||||
{
|
{
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ThumbnailFormat, api } from '@api';
|
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { videoViewerVolume } from '$lib/stores/preferences.store';
|
import { videoViewerVolume } from '$lib/stores/preferences.store';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import { getAssetFileUrl, getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { ThumbnailFormat } from '@api';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
|
|
||||||
|
@ -35,9 +36,9 @@
|
||||||
on:canplay={handleCanPlay}
|
on:canplay={handleCanPlay}
|
||||||
on:ended={() => dispatch('onVideoEnded')}
|
on:ended={() => dispatch('onVideoEnded')}
|
||||||
bind:volume={$videoViewerVolume}
|
bind:volume={$videoViewerVolume}
|
||||||
poster={api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg)}
|
poster={getAssetThumbnailUrl(assetId, ThumbnailFormat.Jpeg)}
|
||||||
>
|
>
|
||||||
<source src={api.getAssetFileUrl(assetId, false, true)} type="video/mp4" />
|
<source src={getAssetFileUrl(assetId, false, true)} type="video/mp4" />
|
||||||
<track kind="captions" />
|
<track kind="captions" />
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ProjectionType } from '$lib/constants';
|
|
||||||
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { ProjectionType } from '$lib/constants';
|
||||||
|
import { getAssetFileUrl, getAssetThumbnailUrl, isSharedLink } from '$lib/utils';
|
||||||
import { timeToSeconds } from '$lib/utils/time-to-seconds';
|
import { timeToSeconds } from '$lib/utils/time-to-seconds';
|
||||||
import { api, type AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
|
import { AssetTypeEnum, ThumbnailFormat, type AssetResponseDto } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { fade } from 'svelte/transition';
|
|
||||||
import ImageThumbnail from './image-thumbnail.svelte';
|
|
||||||
import VideoThumbnail from './video-thumbnail.svelte';
|
|
||||||
import {
|
import {
|
||||||
mdiArchiveArrowDownOutline,
|
mdiArchiveArrowDownOutline,
|
||||||
mdiCameraBurst,
|
mdiCameraBurst,
|
||||||
|
@ -17,7 +15,10 @@
|
||||||
mdiMotionPlayOutline,
|
mdiMotionPlayOutline,
|
||||||
mdiRotate360,
|
mdiRotate360,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
import ImageThumbnail from './image-thumbnail.svelte';
|
||||||
|
import VideoThumbnail from './video-thumbnail.svelte';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
click: { asset: AssetResponseDto };
|
click: { asset: AssetResponseDto };
|
||||||
|
@ -138,13 +139,13 @@
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Favorite asset star -->
|
<!-- Favorite asset star -->
|
||||||
{#if !api.isSharedLink && asset.isFavorite}
|
{#if !isSharedLink() && asset.isFavorite}
|
||||||
<div class="absolute bottom-2 left-2 z-10">
|
<div class="absolute bottom-2 left-2 z-10">
|
||||||
<Icon path={mdiHeart} size="24" class="text-white" />
|
<Icon path={mdiHeart} size="24" class="text-white" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !api.isSharedLink && showArchiveIcon && asset.isArchived}
|
{#if !isSharedLink() && showArchiveIcon && asset.isArchived}
|
||||||
<div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
|
<div class="absolute {asset.isFavorite ? 'bottom-10' : 'bottom-2'} left-2 z-10">
|
||||||
<Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
<Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -175,7 +176,7 @@
|
||||||
|
|
||||||
{#if asset.resized}
|
{#if asset.resized}
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
url={api.getAssetThumbnailUrl(asset.id, format)}
|
url={getAssetThumbnailUrl(asset.id, format)}
|
||||||
altText={asset.originalFileName}
|
altText={asset.originalFileName}
|
||||||
widthStyle="{width}px"
|
widthStyle="{width}px"
|
||||||
heightStyle="{height}px"
|
heightStyle="{height}px"
|
||||||
|
@ -191,7 +192,7 @@
|
||||||
{#if asset.type === AssetTypeEnum.Video}
|
{#if asset.type === AssetTypeEnum.Video}
|
||||||
<div class="absolute top-0 h-full w-full">
|
<div class="absolute top-0 h-full w-full">
|
||||||
<VideoThumbnail
|
<VideoThumbnail
|
||||||
url={api.getAssetFileUrl(asset.id, false, true)}
|
url={getAssetFileUrl(asset.id, false, true)}
|
||||||
enablePlayback={mouseOver}
|
enablePlayback={mouseOver}
|
||||||
curve={selected}
|
curve={selected}
|
||||||
durationInSeconds={timeToSeconds(asset.duration)}
|
durationInSeconds={timeToSeconds(asset.duration)}
|
||||||
|
@ -202,7 +203,7 @@
|
||||||
{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
|
{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
|
||||||
<div class="absolute top-0 h-full w-full">
|
<div class="absolute top-0 h-full w-full">
|
||||||
<VideoThumbnail
|
<VideoThumbnail
|
||||||
url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true)}
|
url={getAssetFileUrl(asset.livePhotoVideoId, false, true)}
|
||||||
pauseIcon={mdiMotionPauseOutline}
|
pauseIcon={mdiMotionPauseOutline}
|
||||||
playIcon={mdiMotionPlayOutline}
|
playIcon={mdiMotionPlayOutline}
|
||||||
showTime={false}
|
showTime={false}
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto, ThumbnailFormat } from '@api';
|
import { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
|
import { photoViewer } from '$lib/stores/assets.store';
|
||||||
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { getPersonNameWithHiddenValue, searchNameLocal } from '$lib/utils/person';
|
||||||
|
import { AssetTypeEnum, ThumbnailFormat, type AssetFaceResponseDto, type PersonResponseDto } from '@api';
|
||||||
|
import { searchPerson } from '@immich/sdk';
|
||||||
|
import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { linear } from 'svelte/easing';
|
import { linear } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Icon from '../elements/icon.svelte';
|
|
||||||
import { mdiArrowLeftThin, mdiClose, mdiMagnify, mdiPlus } from '@mdi/js';
|
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import { getPersonNameWithHiddenValue, searchNameLocal } from '$lib/utils/person';
|
import Icon from '../elements/icon.svelte';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
import { photoViewer } from '$lib/stores/assets.store';
|
|
||||||
import { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
|
||||||
|
|
||||||
export let peopleWithFaces: AssetFaceResponseDto[];
|
export let peopleWithFaces: AssetFaceResponseDto[];
|
||||||
export let allPeople: PersonResponseDto[];
|
export let allPeople: PersonResponseDto[];
|
||||||
|
@ -42,7 +44,7 @@
|
||||||
if (assetType === AssetTypeEnum.Image) {
|
if (assetType === AssetTypeEnum.Image) {
|
||||||
image = $photoViewer;
|
image = $photoViewer;
|
||||||
} else if (assetType === AssetTypeEnum.Video) {
|
} else if (assetType === AssetTypeEnum.Video) {
|
||||||
const data = await api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp);
|
const data = await getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp);
|
||||||
const img: HTMLImageElement = new Image();
|
const img: HTMLImageElement = new Image();
|
||||||
img.src = data;
|
img.src = data;
|
||||||
|
|
||||||
|
@ -116,7 +118,7 @@
|
||||||
}
|
}
|
||||||
const timeout = setTimeout(() => (isShowLoadingSearch = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isShowLoadingSearch = true), timeBeforeShowLoadingSpinner);
|
||||||
try {
|
try {
|
||||||
const { data } = await api.searchApi.searchPerson({ name: searchName });
|
const data = await searchPerson({ name: searchName });
|
||||||
searchedPeople = data;
|
searchedPeople = data;
|
||||||
searchedPeopleCopy = data;
|
searchedPeopleCopy = data;
|
||||||
searchWord = searchName;
|
searchWord = searchName;
|
||||||
|
@ -229,7 +231,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
||||||
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
||||||
widthStyle="90px"
|
widthStyle="90px"
|
||||||
|
@ -255,7 +257,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
||||||
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
|
||||||
widthStyle="90px"
|
widthStyle="90px"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, type PersonResponseDto } from '@api';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import { type PersonResponseDto } from '@api';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
|
|
||||||
export let person: PersonResponseDto;
|
export let person: PersonResponseDto;
|
||||||
export let selectable = false;
|
export let selectable = false;
|
||||||
|
@ -34,13 +35,7 @@
|
||||||
class:dark:border-immich-dark-primary={border}
|
class:dark:border-immich-dark-primary={border}
|
||||||
class:border-immich-primary={border}
|
class:border-immich-primary={border}
|
||||||
>
|
>
|
||||||
<ImageThumbnail
|
<ImageThumbnail {circle} url={getPeopleThumbnailUrl(person.id)} altText={person.name} widthStyle="100%" shadow />
|
||||||
{circle}
|
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
|
||||||
altText={person.name}
|
|
||||||
widthStyle="100%"
|
|
||||||
shadow
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { ActionQueryParameterValue, AppRoute, QueryParameter } from '$lib/constants';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { type PersonResponseDto } from '@api';
|
||||||
|
import { getAllPeople, getPerson, mergePerson } from '@immich/sdk';
|
||||||
|
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import { api, type PersonResponseDto } from '@api';
|
import { flip } from 'svelte/animate';
|
||||||
import FaceThumbnail from './face-thumbnail.svelte';
|
|
||||||
import { quintOut } from 'svelte/easing';
|
import { quintOut } from 'svelte/easing';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import { flip } from 'svelte/animate';
|
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
|
||||||
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { goto } from '$app/navigation';
|
|
||||||
import { ActionQueryParameterValue, AppRoute, QueryParameter } from '$lib/constants';
|
|
||||||
import { mdiCallMerge, mdiMerge, mdiSwapHorizontal } from '@mdi/js';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
|
import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
|
||||||
|
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
||||||
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
|
import FaceThumbnail from './face-thumbnail.svelte';
|
||||||
import PeopleList from './people-list.svelte';
|
import PeopleList from './people-list.svelte';
|
||||||
import { page } from '$app/stores';
|
|
||||||
|
|
||||||
export let person: PersonResponseDto;
|
export let person: PersonResponseDto;
|
||||||
let people: PersonResponseDto[] = [];
|
let people: PersonResponseDto[] = [];
|
||||||
|
@ -35,7 +36,7 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const { data } = await api.personApi.getAllPeople({ withHidden: false });
|
const data = await getAllPeople({ withHidden: false });
|
||||||
people = data.people;
|
people = data.people;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,11 +69,11 @@
|
||||||
|
|
||||||
const handleMerge = async () => {
|
const handleMerge = async () => {
|
||||||
try {
|
try {
|
||||||
let { data: results } = await api.personApi.mergePerson({
|
let results = await mergePerson({
|
||||||
id: person.id,
|
id: person.id,
|
||||||
mergePersonDto: { ids: selectedPeople.map(({ id }) => id) },
|
mergePersonDto: { ids: selectedPeople.map(({ id }) => id) },
|
||||||
});
|
});
|
||||||
const { data: mergedPerson } = await api.personApi.getPerson({ id: person.id });
|
const mergedPerson = await getPerson({ id: person.id });
|
||||||
const count = results.filter(({ success }) => success).length;
|
const count = results.filter(({ success }) => success).length;
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
message: `Merged ${count} ${count === 1 ? 'person' : 'people'}`,
|
message: `Merged ${count} ${count === 1 ? 'person' : 'people'}`,
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
import { api, type PersonResponseDto } from '@api';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
import { type PersonResponseDto } from '@api';
|
||||||
|
import { mdiArrowLeft, mdiClose, mdiMerge } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import Button from '../elements/buttons/button.svelte';
|
import Button from '../elements/buttons/button.svelte';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import { mdiArrowLeft, mdiClose, mdiMerge } from '@mdi/js';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
|
|
||||||
export let personMerge1: PersonResponseDto;
|
export let personMerge1: PersonResponseDto;
|
||||||
export let personMerge2: PersonResponseDto;
|
export let personMerge2: PersonResponseDto;
|
||||||
|
@ -60,7 +61,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(personMerge1.id)}
|
url={getPeopleThumbnailUrl(personMerge1.id)}
|
||||||
altText={personMerge1.name}
|
altText={personMerge1.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
/>
|
/>
|
||||||
|
@ -85,7 +86,7 @@
|
||||||
border={potentialMergePeople.length > 0}
|
border={potentialMergePeople.length > 0}
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(personMerge2.id)}
|
url={getPeopleThumbnailUrl(personMerge2.id)}
|
||||||
altText={personMerge2.name}
|
altText={personMerge2.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
/>
|
/>
|
||||||
|
@ -104,7 +105,7 @@
|
||||||
border={true}
|
border={true}
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
on:click={() => changePersonToMerge(person)}
|
on:click={() => changePersonToMerge(person)}
|
||||||
|
|
|
@ -1,15 +1,16 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { type PersonResponseDto, api } from '@api';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { getContextMenuPosition } from '$lib/utils/context-menu';
|
import { getContextMenuPosition } from '$lib/utils/context-menu';
|
||||||
|
import { type PersonResponseDto } from '@api';
|
||||||
|
import { mdiDotsVertical } from '@mdi/js';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import IconButton from '../elements/buttons/icon-button.svelte';
|
import IconButton from '../elements/buttons/icon-button.svelte';
|
||||||
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
|
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
|
||||||
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
|
||||||
import Portal from '../shared-components/portal/portal.svelte';
|
import Portal from '../shared-components/portal/portal.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
|
||||||
import { mdiDotsVertical } from '@mdi/js';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
|
|
||||||
export let person: PersonResponseDto;
|
export let person: PersonResponseDto;
|
||||||
export let preload = false;
|
export let preload = false;
|
||||||
|
@ -50,7 +51,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
shadow
|
shadow
|
||||||
{preload}
|
{preload}
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
title={person.name}
|
title={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, type PersonResponseDto } from '@api';
|
import { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
import FaceThumbnail from './face-thumbnail.svelte';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { searchNameLocal } from '$lib/utils/person';
|
import { searchNameLocal } from '$lib/utils/person';
|
||||||
|
import { type PersonResponseDto } from '@api';
|
||||||
|
import { searchPerson } from '@immich/sdk';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import FaceThumbnail from './face-thumbnail.svelte';
|
||||||
import SearchBar from './search-bar.svelte';
|
import SearchBar from './search-bar.svelte';
|
||||||
import { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
|
||||||
|
|
||||||
export let screenHeight: number;
|
export let screenHeight: number;
|
||||||
export let people: PersonResponseDto[];
|
export let people: PersonResponseDto[];
|
||||||
|
@ -40,8 +41,7 @@
|
||||||
|
|
||||||
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
|
||||||
try {
|
try {
|
||||||
const { data } = await api.searchApi.searchPerson({ name });
|
people = await searchPerson({ name });
|
||||||
people = data;
|
|
||||||
searchWord = name;
|
searchWord = name;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "Can't search people");
|
handleError(error, "Can't search people");
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from 'svelte/transition';
|
|
||||||
import { linear } from 'svelte/easing';
|
|
||||||
import { api, type PersonResponseDto, type AssetFaceResponseDto, AssetTypeEnum } from '@api';
|
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
|
||||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
import { mdiArrowLeftThin, mdiRestart } from '@mdi/js';
|
|
||||||
import Icon from '../elements/icon.svelte';
|
|
||||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||||
import { websocketStore } from '$lib/stores/websocket';
|
import { websocketStore } from '$lib/stores/websocket';
|
||||||
import AssignFaceSidePanel from './assign-face-side-panel.svelte';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getPersonNameWithHiddenValue } from '$lib/utils/person';
|
import { getPersonNameWithHiddenValue } from '$lib/utils/person';
|
||||||
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
import { AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto } from '@api';
|
||||||
import { getFaces, reassignFacesById } from '@immich/sdk';
|
import { createPerson, getAllPeople, getFaces, reassignFacesById } from '@immich/sdk';
|
||||||
|
import { mdiArrowLeftThin, mdiRestart } from '@mdi/js';
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
import { linear } from 'svelte/easing';
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
|
import Icon from '../elements/icon.svelte';
|
||||||
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
|
import AssignFaceSidePanel from './assign-face-side-panel.svelte';
|
||||||
|
|
||||||
export let assetId: string;
|
export let assetId: string;
|
||||||
export let assetType: AssetTypeEnum;
|
export let assetType: AssetTypeEnum;
|
||||||
|
@ -69,8 +70,8 @@
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const timeout = setTimeout(() => (isShowLoadingPeople = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isShowLoadingPeople = true), timeBeforeShowLoadingSpinner);
|
||||||
try {
|
try {
|
||||||
const { data } = await api.personApi.getAllPeople({ withHidden: true });
|
const { people } = await getAllPeople({ withHidden: true });
|
||||||
allPeople = data.people;
|
allPeople = people;
|
||||||
peopleWithFaces = await getFaces({ id: assetId });
|
peopleWithFaces = await getFaces({ id: assetId });
|
||||||
selectedPersonToCreate = Array.from({ length: peopleWithFaces.length });
|
selectedPersonToCreate = Array.from({ length: peopleWithFaces.length });
|
||||||
selectedPersonToReassign = Array.from({ length: peopleWithFaces.length });
|
selectedPersonToReassign = Array.from({ length: peopleWithFaces.length });
|
||||||
|
@ -115,7 +116,7 @@
|
||||||
faceDto: { id: peopleWithFace.id },
|
faceDto: { id: peopleWithFace.id },
|
||||||
});
|
});
|
||||||
} else if (selectedPersonToCreate[index]) {
|
} else if (selectedPersonToCreate[index]) {
|
||||||
const { data } = await api.personApi.createPerson();
|
const data = await createPerson();
|
||||||
numberOfPersonToCreate.push(data.id);
|
numberOfPersonToCreate.push(data.id);
|
||||||
await reassignFacesById({
|
await reassignFacesById({
|
||||||
id: data.id,
|
id: data.id,
|
||||||
|
@ -214,7 +215,7 @@
|
||||||
curve
|
curve
|
||||||
shadow
|
shadow
|
||||||
url={selectedPersonToCreate[index] ||
|
url={selectedPersonToCreate[index] ||
|
||||||
api.getPeopleThumbnailUrl(selectedPersonToReassign[index]?.id || face.person.id)}
|
getPeopleThumbnailUrl(selectedPersonToReassign[index]?.id || face.person.id)}
|
||||||
altText={selectedPersonToReassign[index]
|
altText={selectedPersonToReassign[index]
|
||||||
? selectedPersonToReassign[index]?.name || ''
|
? selectedPersonToReassign[index]?.name || ''
|
||||||
: selectedPersonToCreate[index]
|
: selectedPersonToCreate[index]
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
|
||||||
import FaceThumbnail from './face-thumbnail.svelte';
|
|
||||||
import { quintOut } from 'svelte/easing';
|
|
||||||
import { fly } from 'svelte/transition';
|
|
||||||
import { api, type AssetFaceUpdateItem, type PersonResponseDto } from '@api';
|
|
||||||
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
|
||||||
import Button from '../elements/buttons/button.svelte';
|
|
||||||
import { mdiPlus, mdiMerge } from '@mdi/js';
|
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { notificationController, NotificationType } from '../shared-components/notification/notification';
|
|
||||||
import PeopleList from './people-list.svelte';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import {
|
||||||
|
createPerson,
|
||||||
|
getAllPeople,
|
||||||
|
reassignFaces,
|
||||||
|
type AssetFaceUpdateItem,
|
||||||
|
type PersonResponseDto,
|
||||||
|
} from '@immich/sdk';
|
||||||
|
import { mdiMerge, mdiPlus } from '@mdi/js';
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
import { quintOut } from 'svelte/easing';
|
||||||
|
import { fly } from 'svelte/transition';
|
||||||
|
import Button from '../elements/buttons/button.svelte';
|
||||||
|
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
||||||
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
|
import FaceThumbnail from './face-thumbnail.svelte';
|
||||||
|
import PeopleList from './people-list.svelte';
|
||||||
|
|
||||||
export let assetIds: string[];
|
export let assetIds: string[];
|
||||||
export let personAssets: PersonResponseDto;
|
export let personAssets: PersonResponseDto;
|
||||||
|
@ -41,7 +47,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const { data } = await api.personApi.getAllPeople({ withHidden: false });
|
const data = await getAllPeople({ withHidden: false });
|
||||||
people = data.people;
|
people = data.people;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -68,11 +74,8 @@
|
||||||
|
|
||||||
try {
|
try {
|
||||||
disableButtons = true;
|
disableButtons = true;
|
||||||
const { data } = await api.personApi.createPerson();
|
const data = await createPerson();
|
||||||
await api.personApi.reassignFaces({
|
await reassignFaces({ id: data.id, assetFaceUpdateDto: { data: selectedPeople } });
|
||||||
id: data.id,
|
|
||||||
assetFaceUpdateDto: { data: selectedPeople },
|
|
||||||
});
|
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to a new person`,
|
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to a new person`,
|
||||||
|
@ -93,10 +96,7 @@
|
||||||
try {
|
try {
|
||||||
disableButtons = true;
|
disableButtons = true;
|
||||||
if (selectedPerson) {
|
if (selectedPerson) {
|
||||||
await api.personApi.reassignFaces({
|
await reassignFaces({ id: selectedPerson.id, assetFaceUpdateDto: { data: selectedPeople } });
|
||||||
id: selectedPerson.id,
|
|
||||||
assetFaceUpdateDto: { data: selectedPeople },
|
|
||||||
});
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to ${
|
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to ${
|
||||||
selectedPerson.name || 'an existing person'
|
selectedPerson.name || 'an existing person'
|
||||||
|
|
|
@ -1,20 +1,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { memoryStore } from '$lib/stores/memory.store';
|
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { api } from '@api';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
|
||||||
import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
|
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
|
||||||
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
|
||||||
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
import IntersectionObserver from '$lib/components/asset-viewer/intersection-observer.svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import { tweened } from 'svelte/motion';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
|
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
|
import { memoryStore } from '$lib/stores/memory.store';
|
||||||
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
|
import { fromLocalDateTime } from '$lib/utils/timeline-util';
|
||||||
|
import { getMemoryLane } from '@immich/sdk';
|
||||||
import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js';
|
import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { tweened } from 'svelte/motion';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
const parseIndex = (s: string | null, max: number | null) =>
|
const parseIndex = (s: string | null, max: number | null) =>
|
||||||
Math.max(Math.min(Number.parseInt(s ?? '') || 0, max ?? 0), 0);
|
Math.max(Math.min(Number.parseInt(s ?? '') || 0, max ?? 0), 0);
|
||||||
|
@ -87,11 +88,10 @@
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
if (!$memoryStore) {
|
if (!$memoryStore) {
|
||||||
const localTime = new Date();
|
const localTime = new Date();
|
||||||
const { data } = await api.assetApi.getMemoryLane({
|
$memoryStore = await getMemoryLane({
|
||||||
month: localTime.getMonth() + 1,
|
month: localTime.getMonth() + 1,
|
||||||
day: localTime.getDate(),
|
day: localTime.getDate(),
|
||||||
});
|
});
|
||||||
$memoryStore = data;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@
|
||||||
<button class="relative h-full w-full rounded-2xl" disabled={!previousMemory} on:click={toPreviousMemory}>
|
<button class="relative h-full w-full rounded-2xl" disabled={!previousMemory} on:click={toPreviousMemory}>
|
||||||
<img
|
<img
|
||||||
class="h-full w-full rounded-2xl object-cover"
|
class="h-full w-full rounded-2xl object-cover"
|
||||||
src={previousMemory ? api.getAssetThumbnailUrl(previousMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
|
src={previousMemory ? getAssetThumbnailUrl(previousMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
|
||||||
alt=""
|
alt=""
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
|
@ -191,7 +191,7 @@
|
||||||
<img
|
<img
|
||||||
transition:fade
|
transition:fade
|
||||||
class="h-full w-full rounded-2xl object-contain transition-all"
|
class="h-full w-full rounded-2xl object-contain transition-all"
|
||||||
src={api.getAssetThumbnailUrl(currentAsset.id, 'JPEG')}
|
src={getAssetThumbnailUrl(currentAsset.id, 'JPEG')}
|
||||||
alt=""
|
alt=""
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
|
@ -231,7 +231,7 @@
|
||||||
<button class="relative h-full w-full rounded-2xl" on:click={toNextMemory} disabled={!nextMemory}>
|
<button class="relative h-full w-full rounded-2xl" on:click={toNextMemory} disabled={!nextMemory}>
|
||||||
<img
|
<img
|
||||||
class="h-full w-full rounded-2xl object-cover"
|
class="h-full w-full rounded-2xl object-cover"
|
||||||
src={nextMemory ? api.getAssetThumbnailUrl(nextMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
|
src={nextMemory ? getAssetThumbnailUrl(nextMemory.assets[0].id, 'JPEG') : noThumbnailUrl}
|
||||||
alt=""
|
alt=""
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import type { OnArchive } from '$lib/utils/actions';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { api } from '@api';
|
import { updateAssets } from '@immich/sdk';
|
||||||
|
import { mdiArchiveArrowDownOutline, mdiArchiveArrowUpOutline, mdiTimerSand } from '@mdi/js';
|
||||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
import { mdiArchiveArrowUpOutline, mdiArchiveArrowDownOutline, mdiTimerSand } from '@mdi/js';
|
|
||||||
import type { OnArchive } from '$lib/utils/actions';
|
|
||||||
|
|
||||||
export let onArchive: OnArchive | undefined = undefined;
|
export let onArchive: OnArchive | undefined = undefined;
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
const ids = assets.map(({ id }) => id);
|
const ids = assets.map(({ id }) => id);
|
||||||
|
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
|
await updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
|
|
|
@ -4,8 +4,10 @@
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
|
import { getAssetJobMessage, getAssetJobName } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { AssetJobName, AssetTypeEnum, api } from '@api';
|
import { AssetJobName, AssetTypeEnum } from '@api';
|
||||||
|
import { runAssetJobs } from '@immich/sdk';
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
|
||||||
export let jobs: AssetJobName[] = [
|
export let jobs: AssetJobName[] = [
|
||||||
|
@ -21,8 +23,8 @@
|
||||||
const handleRunJob = async (name: AssetJobName) => {
|
const handleRunJob = async (name: AssetJobName) => {
|
||||||
try {
|
try {
|
||||||
const ids = [...getOwnedAssets()].map(({ id }) => id);
|
const ids = [...getOwnedAssets()].map(({ id }) => id);
|
||||||
await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: ids, name } });
|
await runAssetJobs({ assetJobsDto: { assetIds: ids, name } });
|
||||||
notificationController.show({ message: api.getAssetJobMessage(name), type: NotificationType.Info });
|
notificationController.show({ message: getAssetJobMessage(name), type: NotificationType.Info });
|
||||||
clearSelect();
|
clearSelect();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to submit job');
|
handleError(error, 'Unable to submit job');
|
||||||
|
@ -32,6 +34,6 @@
|
||||||
|
|
||||||
{#each jobs as job}
|
{#each jobs as job}
|
||||||
{#if isAllVideos || job !== AssetJobName.TranscodeVideo}
|
{#if isAllVideos || job !== AssetJobName.TranscodeVideo}
|
||||||
<MenuOption text={api.getAssetJobName(job)} on:click={() => handleRunJob(job)} />
|
<MenuOption text={getAssetJobName(job)} on:click={() => handleRunJob(job)} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { api } from '@api';
|
import { updateAssets } from '@immich/sdk';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
|
||||||
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
|
||||||
export let menuItem = false;
|
export let menuItem = false;
|
||||||
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
||||||
|
|
||||||
|
@ -17,9 +17,7 @@
|
||||||
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.assetApi.updateAssets({
|
await updateAssets({ assetBulkUpdateDto: { ids, dateTimeOriginal } });
|
||||||
assetBulkUpdateDto: { ids, dateTimeOriginal },
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to change date');
|
handleError(error, 'Unable to change date');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api } from '@api';
|
|
||||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
|
||||||
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
||||||
import { handleError } from '../../../utils/handle-error';
|
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { updateAssets } from '@immich/sdk';
|
||||||
|
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||||
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
|
||||||
export let menuItem = false;
|
export let menuItem = false;
|
||||||
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
||||||
|
@ -17,13 +17,7 @@
|
||||||
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.assetApi.updateAssets({
|
await updateAssets({ assetBulkUpdateDto: { ids, latitude: point.lat, longitude: point.lng } });
|
||||||
assetBulkUpdateDto: {
|
|
||||||
ids,
|
|
||||||
latitude: point.lat,
|
|
||||||
longitude: point.lng,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to update location');
|
handleError(error, 'Unable to update location');
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { api } from '@api';
|
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
|
||||||
import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js';
|
|
||||||
import type { OnFavorite } from '$lib/utils/actions';
|
import type { OnFavorite } from '$lib/utils/actions';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { updateAssets } from '@immich/sdk';
|
||||||
|
import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js';
|
||||||
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
|
||||||
export let onFavorite: OnFavorite | undefined = undefined;
|
export let onFavorite: OnFavorite | undefined = undefined;
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
const ids = assets.map(({ id }) => id);
|
const ids = assets.map(({ id }) => id);
|
||||||
|
|
||||||
if (ids.length > 0) {
|
if (ids.length > 0) {
|
||||||
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isFavorite } });
|
await updateAssets({ assetBulkUpdateDto: { ids, isFavorite } });
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||||
import { type SharedLinkResponseDto, api } from '@api';
|
import { getKey } from '$lib/utils';
|
||||||
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
import { type SharedLinkResponseDto } from '@api';
|
||||||
import { NotificationType, notificationController } from '../../shared-components/notification/notification';
|
import { removeSharedLinkAssets } from '@immich/sdk';
|
||||||
import { handleError } from '../../../utils/handle-error';
|
|
||||||
import { mdiDeleteOutline } from '@mdi/js';
|
import { mdiDeleteOutline } from '@mdi/js';
|
||||||
|
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
|
||||||
|
import { NotificationType, notificationController } from '../../shared-components/notification/notification';
|
||||||
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
|
||||||
export let sharedLink: SharedLinkResponseDto;
|
export let sharedLink: SharedLinkResponseDto;
|
||||||
|
|
||||||
|
@ -15,12 +17,12 @@
|
||||||
|
|
||||||
const handleRemove = async () => {
|
const handleRemove = async () => {
|
||||||
try {
|
try {
|
||||||
const { data: results } = await api.sharedLinkApi.removeSharedLinkAssets({
|
const results = await removeSharedLinkAssets({
|
||||||
id: sharedLink.id,
|
id: sharedLink.id,
|
||||||
assetIdsDto: {
|
assetIdsDto: {
|
||||||
assetIds: [...getAssets()].map((asset) => asset.id),
|
assetIds: [...getAssets()].map((asset) => asset.id),
|
||||||
},
|
},
|
||||||
key: api.getKey(),
|
key: getKey(),
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const result of results) {
|
for (const result of results) {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import { api } from '@api';
|
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import type { OnStack } from '$lib/utils/actions';
|
import type { OnStack } from '$lib/utils/actions';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { updateAssets } from '@immich/sdk';
|
||||||
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
|
||||||
export let onStack: OnStack | undefined;
|
export let onStack: OnStack | undefined;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
const ids = children.map(({ id }) => id);
|
const ids = children.map(({ id }) => id);
|
||||||
|
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, stackParentId: parent.id } });
|
await updateAssets({ assetBulkUpdateDto: { ids, stackParentId: parent.id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
let childrenCount = parent.stackCount ?? 0;
|
let childrenCount = parent.stackCount ?? 0;
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { api } from '@api';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
import { memoryStore } from '$lib/stores/memory.store';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { fade } from 'svelte/transition';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
|
||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
|
import { memoryStore } from '$lib/stores/memory.store';
|
||||||
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
|
import { getMemoryLane } from '@immich/sdk';
|
||||||
|
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
$: shouldRender = $memoryStore?.length > 0;
|
$: shouldRender = $memoryStore?.length > 0;
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const localTime = new Date();
|
const localTime = new Date();
|
||||||
const { data } = await api.assetApi.getMemoryLane({
|
$memoryStore = await getMemoryLane({ month: localTime.getMonth() + 1, day: localTime.getDate() });
|
||||||
month: localTime.getMonth() + 1,
|
|
||||||
day: localTime.getDate(),
|
|
||||||
});
|
|
||||||
$memoryStore = data;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let memoryLaneElement: HTMLElement;
|
let memoryLaneElement: HTMLElement;
|
||||||
|
@ -76,7 +73,7 @@
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
class="h-full w-full rounded-xl object-cover"
|
class="h-full w-full rounded-xl object-cover"
|
||||||
src={api.getAssetThumbnailUrl(memory.assets[0].id, 'JPEG')}
|
src={getAssetThumbnailUrl(memory.assets[0].id, 'JPEG')}
|
||||||
alt={memory.title}
|
alt={memory.title}
|
||||||
draggable="false"
|
draggable="false"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { downloadArchive } from '$lib/utils/asset-utils';
|
|
||||||
import { api, type AssetResponseDto, type SharedLinkResponseDto } from '@api';
|
|
||||||
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
|
||||||
|
import { getKey } from '$lib/utils';
|
||||||
|
import { downloadArchive } from '$lib/utils/asset-utils';
|
||||||
|
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { type AssetResponseDto, type SharedLinkResponseDto } from '@api';
|
||||||
|
import { addSharedLinkAssets } from '@immich/sdk';
|
||||||
|
import { mdiArrowLeft, mdiFileImagePlusOutline, mdiFolderDownloadOutline, mdiSelectAll } from '@mdi/js';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import DownloadAction from '../photos-page/actions/download-action.svelte';
|
import DownloadAction from '../photos-page/actions/download-action.svelte';
|
||||||
import RemoveFromSharedLink from '../photos-page/actions/remove-from-shared-link.svelte';
|
import RemoveFromSharedLink from '../photos-page/actions/remove-from-shared-link.svelte';
|
||||||
|
@ -11,10 +16,7 @@
|
||||||
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
||||||
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
import ImmichLogo from '../shared-components/immich-logo.svelte';
|
||||||
import { notificationController, NotificationType } from '../shared-components/notification/notification';
|
import { NotificationType, notificationController } from '../shared-components/notification/notification';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { mdiArrowLeft, mdiFileImagePlusOutline, mdiFolderDownloadOutline, mdiSelectAll } from '@mdi/js';
|
|
||||||
import { AppRoute } from '$lib/constants';
|
|
||||||
|
|
||||||
export let sharedLink: SharedLinkResponseDto;
|
export let sharedLink: SharedLinkResponseDto;
|
||||||
export let isOwned: boolean;
|
export let isOwned: boolean;
|
||||||
|
@ -41,12 +43,12 @@
|
||||||
results = await (!files || files.length === 0 || !Array.isArray(files)
|
results = await (!files || files.length === 0 || !Array.isArray(files)
|
||||||
? openFileUploadDialog()
|
? openFileUploadDialog()
|
||||||
: fileUploadHandler(files));
|
: fileUploadHandler(files));
|
||||||
const { data } = await api.sharedLinkApi.addSharedLinkAssets({
|
const data = await addSharedLinkAssets({
|
||||||
id: sharedLink.id,
|
id: sharedLink.id,
|
||||||
assetIdsDto: {
|
assetIdsDto: {
|
||||||
assetIds: results.filter((id) => !!id) as string[],
|
assetIds: results.filter((id) => !!id) as string[],
|
||||||
},
|
},
|
||||||
key: api.getKey(),
|
key: getKey(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const added = data.filter((item) => item.success).length;
|
const added = data.filter((item) => item.success).length;
|
||||||
|
|
|
@ -4,16 +4,17 @@
|
||||||
} from '$lib/components/admin-page/settings/setting-input-field.svelte';
|
} from '$lib/components/admin-page/settings/setting-input-field.svelte';
|
||||||
import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte';
|
import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte';
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { api, copyToClipboard, makeSharedLinkUrl, type SharedLinkResponseDto, SharedLinkType } from '@api';
|
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { SharedLinkType, copyToClipboard, makeSharedLinkUrl, type SharedLinkResponseDto } from '@api';
|
||||||
|
import { createSharedLink, updateSharedLink } from '@immich/sdk';
|
||||||
|
import { mdiLink } from '@mdi/js';
|
||||||
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import BaseModal from '../base-modal.svelte';
|
import BaseModal from '../base-modal.svelte';
|
||||||
import type { ImmichDropDownOption } from '../dropdown-button.svelte';
|
import type { ImmichDropDownOption } from '../dropdown-button.svelte';
|
||||||
import DropdownButton from '../dropdown-button.svelte';
|
import DropdownButton from '../dropdown-button.svelte';
|
||||||
import { notificationController, NotificationType } from '../notification/notification';
|
import { NotificationType, notificationController } from '../notification/notification';
|
||||||
import { mdiLink } from '@mdi/js';
|
|
||||||
import { serverConfig } from '$lib/stores/server-config.store';
|
|
||||||
|
|
||||||
export let albumId: string | undefined = undefined;
|
export let albumId: string | undefined = undefined;
|
||||||
export let assetIds: string[] = [];
|
export let assetIds: string[] = [];
|
||||||
|
@ -70,7 +71,7 @@
|
||||||
const expirationDate = expirationTime ? new Date(currentTime + expirationTime).toISOString() : undefined;
|
const expirationDate = expirationTime ? new Date(currentTime + expirationTime).toISOString() : undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.sharedLinkApi.createSharedLink({
|
const data = await createSharedLink({
|
||||||
sharedLinkCreateDto: {
|
sharedLinkCreateDto: {
|
||||||
type: shareType,
|
type: shareType,
|
||||||
albumId,
|
albumId,
|
||||||
|
@ -135,7 +136,7 @@
|
||||||
? new Date(currentTime + expirationTime).toISOString()
|
? new Date(currentTime + expirationTime).toISOString()
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
await api.sharedLinkApi.updateSharedLink({
|
await updateSharedLink({
|
||||||
id: editingLink.id,
|
id: editingLink.id,
|
||||||
sharedLinkEditDto: {
|
sharedLinkEditDto: {
|
||||||
description,
|
description,
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { Theme } from '$lib/constants';
|
import { Theme } from '$lib/constants';
|
||||||
import { colorTheme, mapSettings } from '$lib/stores/preferences.store';
|
import { colorTheme, mapSettings } from '$lib/stores/preferences.store';
|
||||||
import { api, type MapMarkerResponseDto } from '@api';
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { getMapStyle } from '@immich/sdk';
|
import { getMapStyle, type MapMarkerResponseDto } from '@immich/sdk';
|
||||||
import { mdiCog, mdiMapMarker } from '@mdi/js';
|
import { mdiCog, mdiMapMarker } from '@mdi/js';
|
||||||
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
|
||||||
import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl';
|
import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl';
|
||||||
|
@ -174,7 +174,7 @@
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<img
|
<img
|
||||||
src={api.getAssetThumbnailUrl(feature.properties?.id)}
|
src={getAssetThumbnailUrl(feature.properties?.id, undefined)}
|
||||||
class="rounded-full w-[60px] h-[60px] border-2 border-immich-primary shadow-lg hover:border-immich-dark-primary transition-all duration-200 hover:scale-150 object-cover bg-immich-primary"
|
class="rounded-full w-[60px] h-[60px] border-2 border-immich-primary shadow-lg hover:border-immich-dark-primary transition-all duration-200 hover:scale-150 object-cover bg-immich-primary"
|
||||||
alt={`Image with id ${feature.properties?.id}`}
|
alt={`Image with id ${feature.properties?.id}`}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -4,12 +4,11 @@
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { UserAvatarColor } from '@api';
|
import { deleteProfileImage, updateUser, type UserAvatarColor } from '@immich/sdk';
|
||||||
import { deleteProfileImage, updateUser } from '@immich/sdk';
|
|
||||||
import { mdiCog, mdiLogout, mdiPencil } from '@mdi/js';
|
import { mdiCog, mdiLogout, mdiPencil } from '@mdi/js';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
import { notificationController, NotificationType } from '../notification/notification';
|
import { NotificationType, notificationController } from '../notification/notification';
|
||||||
import UserAvatar from '../user-avatar.svelte';
|
import UserAvatar from '../user-avatar.svelte';
|
||||||
import AvatarSelector from './avatar-selector.svelte';
|
import AvatarSelector from './avatar-selector.svelte';
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { SearchSuggestionType, type PersonResponseDto } from '@api';
|
||||||
|
import { getAllPeople, getSearchSuggestions } from '@immich/sdk';
|
||||||
|
import { mdiArrowRight, mdiClose } from '@mdi/js';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import Combobox, { type ComboBoxOption } from '../combobox.svelte';
|
import Combobox, { type ComboBoxOption } from '../combobox.svelte';
|
||||||
import { SearchSuggestionType, api, type PersonResponseDto } from '@api';
|
|
||||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
import { mdiArrowRight, mdiClose } from '@mdi/js';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
enum MediaType {
|
enum MediaType {
|
||||||
All = 'all',
|
All = 'all',
|
||||||
|
@ -108,8 +110,8 @@
|
||||||
|
|
||||||
const getPeople = async () => {
|
const getPeople = async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.personApi.getAllPeople({ withHidden: false });
|
const { people } = await getAllPeople({ withHidden: false });
|
||||||
suggestions.people = data.people;
|
suggestions.people = people;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Failed to get people');
|
handleError(error, 'Failed to get people');
|
||||||
}
|
}
|
||||||
|
@ -143,8 +145,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.searchApi.getSearchSuggestions({
|
const data = await getSearchSuggestions({
|
||||||
type: type,
|
$type: type,
|
||||||
country: params.country,
|
country: params.country,
|
||||||
state: params.state,
|
state: params.state,
|
||||||
make: params.cameraMake,
|
make: params.cameraMake,
|
||||||
|
@ -251,7 +253,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="100px"
|
widthStyle="100px"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { locale, sidebarSettings } from '$lib/stores/preferences.store';
|
import { locale, sidebarSettings } from '$lib/stores/preferences.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { api, type AssetApiGetAssetStatisticsRequest } from '@api';
|
import { type AssetApiGetAssetStatisticsRequest } from '@api';
|
||||||
import { getAlbumCount } from '@immich/sdk';
|
import { getAlbumCount, getAssetStatistics } from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
mdiAccount,
|
mdiAccount,
|
||||||
mdiAccountMultiple,
|
mdiAccountMultiple,
|
||||||
|
@ -24,10 +24,7 @@
|
||||||
import SideBarButton from './side-bar-button.svelte';
|
import SideBarButton from './side-bar-button.svelte';
|
||||||
import SideBarSection from './side-bar-section.svelte';
|
import SideBarSection from './side-bar-section.svelte';
|
||||||
|
|
||||||
const getStats = async (dto: AssetApiGetAssetStatisticsRequest) => {
|
const getStats = (dto: AssetApiGetAssetStatisticsRequest) => getAssetStatistics(dto);
|
||||||
const { data: stats } = await api.assetApi.getAssetStatistics(dto);
|
|
||||||
return stats;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleAlbumCount = async () => {
|
const handleAlbumCount = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { getProfileImageUrl } from '$lib/utils';
|
||||||
|
import { type UserAvatarColor } from '@immich/sdk';
|
||||||
import { onMount, tick } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import { UserAvatarColor, api } from '@api';
|
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -74,7 +75,7 @@
|
||||||
{#if showProfileImage && user.profileImagePath}
|
{#if showProfileImage && user.profileImagePath}
|
||||||
<img
|
<img
|
||||||
bind:this={img}
|
bind:this={img}
|
||||||
src={api.getProfileImageUrl(user.id)}
|
src={getProfileImageUrl(user.id)}
|
||||||
alt="Profile image of {title}"
|
alt="Profile image of {title}"
|
||||||
class="h-full w-full object-cover"
|
class="h-full w-full object-cover"
|
||||||
class:hidden={showFallback}
|
class:hidden={showFallback}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, type AssetResponseDto, type SharedLinkResponseDto, SharedLinkType, ThumbnailFormat } from '@api';
|
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
import * as luxon from 'luxon';
|
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { mdiCircleEditOutline, mdiContentCopy, mdiDelete, mdiOpenInNew } from '@mdi/js';
|
|
||||||
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
|
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
|
import { SharedLinkType, ThumbnailFormat, type AssetResponseDto, type SharedLinkResponseDto } from '@api';
|
||||||
|
import { getAssetInfo } from '@immich/sdk';
|
||||||
|
import { mdiCircleEditOutline, mdiContentCopy, mdiDelete, mdiOpenInNew } from '@mdi/js';
|
||||||
|
import * as luxon from 'luxon';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
|
||||||
export let link: SharedLinkResponseDto;
|
export let link: SharedLinkResponseDto;
|
||||||
|
|
||||||
|
@ -28,9 +30,7 @@
|
||||||
assetId = link.assets[0].id;
|
assetId = link.assets[0].id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data } = await api.assetApi.getAssetInfo({ id: assetId });
|
return getAssetInfo({ id: assetId });
|
||||||
|
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCountDownExpirationDate = () => {
|
const getCountDownExpirationDate = () => {
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
{:then asset}
|
{:then asset}
|
||||||
<img
|
<img
|
||||||
id={asset.id}
|
id={asset.id}
|
||||||
src={api.getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
|
src={getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
|
||||||
alt={asset.id}
|
alt={asset.id}
|
||||||
class="h-[100px] w-[100px] rounded-lg object-cover"
|
class="h-[100px] w-[100px] rounded-lg object-cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
import { getKey } from '$lib/utils';
|
||||||
|
import { type AssetResponseDto } from '@api';
|
||||||
|
import { getAssetInfo } from '@immich/sdk';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { api, type AssetResponseDto } from '@api';
|
|
||||||
|
|
||||||
function createAssetViewingStore() {
|
function createAssetViewingStore() {
|
||||||
const viewingAssetStoreState = writable<AssetResponseDto>();
|
const viewingAssetStoreState = writable<AssetResponseDto>();
|
||||||
const viewState = writable<boolean>(false);
|
const viewState = writable<boolean>(false);
|
||||||
|
|
||||||
const setAssetId = async (id: string) => {
|
const setAssetId = async (id: string) => {
|
||||||
const { data } = await api.assetApi.getAssetInfo({ id, key: api.getKey() });
|
const data = await getAssetInfo({ id, key: getKey() });
|
||||||
viewingAssetStoreState.set(data);
|
viewingAssetStoreState.set(data);
|
||||||
viewState.set(true);
|
viewState.set(true);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { api, type AssetApiGetTimeBucketsRequest, type AssetResponseDto, TimeBucketSize } from '@api';
|
import { getKey } from '$lib/utils';
|
||||||
|
import { getTimeBucket, getTimeBuckets, type AssetResponseDto } from '@immich/sdk';
|
||||||
|
import { TimeBucketSize, type AssetApiGetTimeBucketsRequest } from '@immich/sdk/axios';
|
||||||
import { throttle } from 'lodash-es';
|
import { throttle } from 'lodash-es';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { type Unsubscriber, writable } from 'svelte/store';
|
import { writable, type Unsubscriber } from 'svelte/store';
|
||||||
import { handleError } from '../utils/handle-error';
|
import { handleError } from '../utils/handle-error';
|
||||||
import { websocketStore } from './websocket';
|
import { websocketStore } from './websocket';
|
||||||
|
|
||||||
|
@ -155,9 +157,9 @@ export class AssetStore {
|
||||||
this.assetToBucket = {};
|
this.assetToBucket = {};
|
||||||
this.albumAssets = new Set();
|
this.albumAssets = new Set();
|
||||||
|
|
||||||
const { data: buckets } = await api.assetApi.getTimeBuckets({
|
const buckets = await getTimeBuckets({
|
||||||
...this.options,
|
...this.options,
|
||||||
key: api.getKey(),
|
key: getKey(),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
|
@ -209,22 +211,22 @@ export class AssetStore {
|
||||||
|
|
||||||
bucket.cancelToken = new AbortController();
|
bucket.cancelToken = new AbortController();
|
||||||
|
|
||||||
const { data: assets } = await api.assetApi.getTimeBucket(
|
const assets = await getTimeBucket(
|
||||||
{
|
{
|
||||||
...this.options,
|
...this.options,
|
||||||
timeBucket: bucketDate,
|
timeBucket: bucketDate,
|
||||||
key: api.getKey(),
|
key: getKey(),
|
||||||
},
|
},
|
||||||
{ signal: bucket.cancelToken.signal },
|
{ signal: bucket.cancelToken.signal },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.albumId) {
|
if (this.albumId) {
|
||||||
const { data: albumAssets } = await api.assetApi.getTimeBucket(
|
const albumAssets = await getTimeBucket(
|
||||||
{
|
{
|
||||||
albumId: this.albumId,
|
albumId: this.albumId,
|
||||||
timeBucket: bucketDate,
|
timeBucket: bucketDate,
|
||||||
size: this.options.size,
|
size: this.options.size,
|
||||||
key: api.getKey(),
|
key: getKey(),
|
||||||
},
|
},
|
||||||
{ signal: bucket.cancelToken.signal },
|
{ signal: bucket.cancelToken.signal },
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
import { defaults } from '@immich/sdk';
|
||||||
|
import { AssetJobName, JobName, ThumbnailFormat, common } from '@immich/sdk/axios';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
|
|
||||||
interface UpdateParamAction {
|
interface UpdateParamAction {
|
||||||
|
@ -31,3 +33,91 @@ export const updateParamList = async ({ param, value, add }: UpdateParamAction)
|
||||||
|
|
||||||
await goto(`?${searchParams.toString()}`, { replaceState: true, noScroll: true, keepFocus: true });
|
await goto(`?${searchParams.toString()}`, { replaceState: true, noScroll: true, keepFocus: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getJobName = (jobName: JobName) => {
|
||||||
|
const names: Record<JobName, string> = {
|
||||||
|
[JobName.ThumbnailGeneration]: 'Generate Thumbnails',
|
||||||
|
[JobName.MetadataExtraction]: 'Extract Metadata',
|
||||||
|
[JobName.Sidecar]: 'Sidecar Metadata',
|
||||||
|
[JobName.SmartSearch]: 'Smart Search',
|
||||||
|
[JobName.FaceDetection]: 'Face Detection',
|
||||||
|
[JobName.FacialRecognition]: 'Facial Recognition',
|
||||||
|
[JobName.VideoConversion]: 'Transcode Videos',
|
||||||
|
[JobName.StorageTemplateMigration]: 'Storage Template Migration',
|
||||||
|
[JobName.Migration]: 'Migration',
|
||||||
|
[JobName.BackgroundTask]: 'Background Tasks',
|
||||||
|
[JobName.Search]: 'Search',
|
||||||
|
[JobName.Library]: 'Library',
|
||||||
|
};
|
||||||
|
|
||||||
|
return names[jobName];
|
||||||
|
};
|
||||||
|
|
||||||
|
let _key: string | undefined;
|
||||||
|
|
||||||
|
export const setKey = (key: string) => {
|
||||||
|
_key = key;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getKey = (): string | undefined => {
|
||||||
|
return _key;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isSharedLink = () => {
|
||||||
|
return !!_key;
|
||||||
|
};
|
||||||
|
|
||||||
|
const createUrl = (path: string, parameters?: Record<string, unknown>) => {
|
||||||
|
const searchParameters = new URLSearchParams();
|
||||||
|
for (const key in parameters) {
|
||||||
|
const value = parameters[key];
|
||||||
|
if (value !== undefined && value !== null) {
|
||||||
|
searchParameters.set(key, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(path, 'https://example.com');
|
||||||
|
url.search = searchParameters.toString();
|
||||||
|
|
||||||
|
return defaults.baseUrl + common.toPathString(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAssetFileUrl = (...[assetId, isWeb, isThumb]: [string, boolean, boolean]) => {
|
||||||
|
const path = `/asset/file/${assetId}`;
|
||||||
|
return createUrl(path, { isThumb, isWeb, key: getKey() });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAssetThumbnailUrl = (...[assetId, format]: [string, ThumbnailFormat | undefined]) => {
|
||||||
|
const path = `/asset/thumbnail/${assetId}`;
|
||||||
|
return createUrl(path, { format, key: getKey() });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProfileImageUrl = (...[userId]: [string]) => {
|
||||||
|
const path = `/user/profile-image/${userId}`;
|
||||||
|
return createUrl(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPeopleThumbnailUrl = (personId: string) => {
|
||||||
|
const path = `/person/${personId}/thumbnail`;
|
||||||
|
return createUrl(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAssetJobName = (job: AssetJobName) => {
|
||||||
|
const names: Record<AssetJobName, string> = {
|
||||||
|
[AssetJobName.RefreshMetadata]: 'Refresh metadata',
|
||||||
|
[AssetJobName.RegenerateThumbnail]: 'Refresh thumbnails',
|
||||||
|
[AssetJobName.TranscodeVideo]: 'Refresh encoded videos',
|
||||||
|
};
|
||||||
|
|
||||||
|
return names[job];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAssetJobMessage = (job: AssetJobName) => {
|
||||||
|
const messages: Record<AssetJobName, string> = {
|
||||||
|
[AssetJobName.RefreshMetadata]: 'Refreshing metadata',
|
||||||
|
[AssetJobName.RegenerateThumbnail]: `Regenerating thumbnails`,
|
||||||
|
[AssetJobName.TranscodeVideo]: `Refreshing encoded video`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return messages[job];
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
|
import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
|
||||||
import { api } from '@api';
|
import { deleteAssets as deleteBulk } from '@immich/sdk';
|
||||||
import { handleError } from './handle-error';
|
import { handleError } from './handle-error';
|
||||||
|
|
||||||
export type OnDelete = (assetId: string) => void;
|
export type OnDelete = (assetId: string) => void;
|
||||||
|
@ -10,7 +10,7 @@ export type OnStack = (ids: string[]) => void;
|
||||||
|
|
||||||
export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => {
|
export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => {
|
||||||
try {
|
try {
|
||||||
await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } });
|
await deleteBulk({ assetBulkDeleteDto: { ids, force } });
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
onAssetDelete(id);
|
onAssetDelete(id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { downloadManager } from '$lib/stores/download';
|
||||||
import { api } from '@api';
|
import { api } from '@api';
|
||||||
import {
|
import {
|
||||||
addAssetsToAlbum as addAssets,
|
addAssetsToAlbum as addAssets,
|
||||||
|
getDownloadInfo,
|
||||||
type AssetResponseDto,
|
type AssetResponseDto,
|
||||||
type AssetTypeEnum,
|
type AssetTypeEnum,
|
||||||
type BulkIdResponseDto,
|
type BulkIdResponseDto,
|
||||||
|
@ -11,13 +12,14 @@ import {
|
||||||
type UserResponseDto,
|
type UserResponseDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { getKey } from '../utils';
|
||||||
import { handleError } from './handle-error';
|
import { handleError } from './handle-error';
|
||||||
|
|
||||||
export const addAssetsToAlbum = async (albumId: string, assetIds: Array<string>): Promise<BulkIdResponseDto[]> =>
|
export const addAssetsToAlbum = async (albumId: string, assetIds: Array<string>): Promise<BulkIdResponseDto[]> =>
|
||||||
addAssets({
|
addAssets({
|
||||||
id: albumId,
|
id: albumId,
|
||||||
bulkIdsDto: { ids: assetIds },
|
bulkIdsDto: { ids: assetIds },
|
||||||
key: api.getKey(),
|
key: getKey(),
|
||||||
}).then((results) => {
|
}).then((results) => {
|
||||||
const count = results.filter(({ success }) => success).length;
|
const count = results.filter(({ success }) => success).length;
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
|
@ -46,8 +48,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
|
||||||
let downloadInfo: DownloadResponseDto | null = null;
|
let downloadInfo: DownloadResponseDto | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.downloadApi.getDownloadInfo({ downloadInfoDto: options, key: api.getKey() });
|
downloadInfo = await getDownloadInfo({ downloadInfoDto: options, key: getKey() });
|
||||||
downloadInfo = data;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, 'Unable to download files');
|
handleError(error, 'Unable to download files');
|
||||||
return;
|
return;
|
||||||
|
@ -71,7 +72,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data } = await api.downloadApi.downloadArchive(
|
const { data } = await api.downloadApi.downloadArchive(
|
||||||
{ assetIdsDto: { assetIds: archive.assetIds }, key: api.getKey() },
|
{ assetIdsDto: { assetIds: archive.assetIds }, key: getKey() },
|
||||||
{
|
{
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
signal: abort.signal,
|
signal: abort.signal,
|
||||||
|
@ -121,7 +122,7 @@ export const downloadFile = async (asset: AssetResponseDto) => {
|
||||||
downloadManager.add(downloadKey, size, abort);
|
downloadManager.add(downloadKey, size, abort);
|
||||||
|
|
||||||
const { data } = await api.downloadApi.downloadFile(
|
const { data } = await api.downloadApi.downloadFile(
|
||||||
{ id, key: api.getKey() },
|
{ id, key: getKey() },
|
||||||
{
|
{
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
onDownloadProgress: ({ event }) => {
|
onDownloadProgress: ({ event }) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { UploadState } from '$lib/models/upload-asset';
|
import { UploadState } from '$lib/models/upload-asset';
|
||||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||||
|
import { getKey } from '$lib/utils';
|
||||||
import { addAssetsToAlbum } from '$lib/utils/asset-utils';
|
import { addAssetsToAlbum } from '$lib/utils/asset-utils';
|
||||||
import { ExecutorQueue } from '$lib/utils/executor-queue';
|
import { ExecutorQueue } from '$lib/utils/executor-queue';
|
||||||
import { api, type AssetFileUploadResponseDto } from '@api';
|
import { api, type AssetFileUploadResponseDto } from '@api';
|
||||||
|
@ -81,7 +82,7 @@ async function fileUploader(asset: File, albumId: string | undefined = undefined
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
duration: '0:00:00.000000',
|
duration: '0:00:00.000000',
|
||||||
assetData: new File([asset], asset.name),
|
assetData: new File([asset], asset.name),
|
||||||
key: api.getKey(),
|
key: getKey(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onUploadProgress: ({ event }) => {
|
onUploadProgress: ({ event }) => {
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { type SearchExploreResponseDto, api } from '@api';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
import type { SearchExploreResponseDto } from '@immich/sdk';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
@ -58,7 +59,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { api } from '@api';
|
import { getAllPeople, getExploreData } from '@immich/sdk';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = (async () => {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
const { data: items } = await api.searchApi.getExploreData();
|
const [items, response] = await Promise.all([getExploreData(), getAllPeople({ withHidden: false })]);
|
||||||
const { data: response } = await api.personApi.getAllPeople({ withHidden: false });
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
response,
|
response,
|
||||||
|
|
|
@ -3,18 +3,19 @@
|
||||||
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import MapSettingsModal from '$lib/components/map-page/map-settings-modal.svelte';
|
import MapSettingsModal from '$lib/components/map-page/map-settings-modal.svelte';
|
||||||
|
import Map from '$lib/components/shared-components/map/map.svelte';
|
||||||
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
import Portal from '$lib/components/shared-components/portal/portal.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
import type { MapSettings } from '$lib/stores/preferences.store';
|
||||||
import { mapSettings } from '$lib/stores/preferences.store';
|
import { mapSettings } from '$lib/stores/preferences.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { type MapMarkerResponseDto, api } from '@api';
|
import { type MapMarkerResponseDto } from '@api';
|
||||||
|
import { getMapMarkers } from '@immich/sdk';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime, Duration } from 'luxon';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import Map from '$lib/components/shared-components/map/map.svelte';
|
|
||||||
import type { MapSettings } from '$lib/stores/preferences.store';
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -49,7 +50,7 @@
|
||||||
const { includeArchived, onlyFavorites } = $mapSettings;
|
const { includeArchived, onlyFavorites } = $mapSettings;
|
||||||
const { fileCreatedAfter, fileCreatedBefore } = getFileCreatedDates();
|
const { fileCreatedAfter, fileCreatedBefore } = getFileCreatedDates();
|
||||||
|
|
||||||
const { data } = await api.assetApi.getMapMarkers(
|
return await getMapMarkers(
|
||||||
{
|
{
|
||||||
isArchived: includeArchived && undefined,
|
isArchived: includeArchived && undefined,
|
||||||
isFavorite: onlyFavorites || undefined,
|
isFavorite: onlyFavorites || undefined,
|
||||||
|
@ -60,7 +61,6 @@
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileCreatedDates() {
|
function getFileCreatedDates() {
|
||||||
|
|
|
@ -1,36 +1,38 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import { browser } from '$app/environment';
|
||||||
import type { PageData } from './$types';
|
|
||||||
import PeopleCard from '$lib/components/faces-page/people-card.svelte';
|
|
||||||
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
|
||||||
import { api, type PeopleUpdateItem, type PersonResponseDto } from '@api';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
||||||
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
|
import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
|
||||||
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte';
|
||||||
|
import PeopleCard from '$lib/components/faces-page/people-card.svelte';
|
||||||
|
import SearchBar from '$lib/components/faces-page/search-bar.svelte';
|
||||||
|
import SetBirthDateModal from '$lib/components/faces-page/set-birth-date-modal.svelte';
|
||||||
|
import ShowHide from '$lib/components/faces-page/show-hide.svelte';
|
||||||
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
|
import FullScreenModal from '$lib/components/shared-components/full-screen-modal.svelte';
|
||||||
|
import {
|
||||||
|
notificationController,
|
||||||
|
NotificationType,
|
||||||
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import {
|
import {
|
||||||
ActionQueryParameterValue,
|
ActionQueryParameterValue,
|
||||||
AppRoute,
|
AppRoute,
|
||||||
QueryParameter,
|
|
||||||
maximumLengthSearchPeople,
|
maximumLengthSearchPeople,
|
||||||
|
QueryParameter,
|
||||||
timeBeforeShowLoadingSpinner,
|
timeBeforeShowLoadingSpinner,
|
||||||
} from '$lib/constants';
|
} from '$lib/constants';
|
||||||
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import {
|
|
||||||
NotificationType,
|
|
||||||
notificationController,
|
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
|
||||||
import ShowHide from '$lib/components/faces-page/show-hide.svelte';
|
|
||||||
import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
|
|
||||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
|
||||||
import { onDestroy, onMount } from 'svelte';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte';
|
|
||||||
import SetBirthDateModal from '$lib/components/faces-page/set-birth-date-modal.svelte';
|
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
|
||||||
import { mdiAccountOff, mdiEyeOutline } from '@mdi/js';
|
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
|
||||||
import { searchNameLocal } from '$lib/utils/person';
|
import { searchNameLocal } from '$lib/utils/person';
|
||||||
import SearchBar from '$lib/components/faces-page/search-bar.svelte';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
import { page } from '$app/stores';
|
import { type PeopleUpdateItem, type PersonResponseDto } from '@api';
|
||||||
|
import { getPerson, mergePerson, searchPerson, updatePeople, updatePerson } from '@immich/sdk';
|
||||||
|
import { mdiAccountOff, mdiEyeOutline } from '@mdi/js';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -75,7 +77,7 @@
|
||||||
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
|
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
|
||||||
if (getSearchedPeople) {
|
if (getSearchedPeople) {
|
||||||
searchName = getSearchedPeople;
|
searchName = getSearchedPeople;
|
||||||
searchPeople(true);
|
handleSearchPeople(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -100,7 +102,7 @@
|
||||||
const handleSearch = (force: boolean) => {
|
const handleSearch = (force: boolean) => {
|
||||||
$page.url.searchParams.set(QueryParameter.SEARCHED_PEOPLE, searchName);
|
$page.url.searchParams.set(QueryParameter.SEARCHED_PEOPLE, searchName);
|
||||||
goto($page.url);
|
goto($page.url);
|
||||||
searchPeople(force);
|
handleSearchPeople(force);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseClick = () => {
|
const handleCloseClick = () => {
|
||||||
|
@ -150,7 +152,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed.length > 0) {
|
if (changed.length > 0) {
|
||||||
const { data: results } = await api.personApi.updatePeople({
|
const results = await updatePeople({
|
||||||
peopleUpdateDto: { people: changed },
|
peopleUpdateDto: { people: changed },
|
||||||
});
|
});
|
||||||
const count = results.filter(({ success }) => success).length;
|
const count = results.filter(({ success }) => success).length;
|
||||||
|
@ -187,12 +189,12 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
await api.personApi.mergePerson({
|
await mergePerson({
|
||||||
id: personToBeMergedIn.id,
|
id: personToBeMergedIn.id,
|
||||||
mergePersonDto: { ids: [personToMerge.id] },
|
mergePersonDto: { ids: [personToMerge.id] },
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: mergedPerson } = await api.personApi.getPerson({ id: personToBeMergedIn.id });
|
const mergedPerson = await getPerson({ id: personToBeMergedIn.id });
|
||||||
|
|
||||||
countVisiblePeople--;
|
countVisiblePeople--;
|
||||||
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
|
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
|
||||||
|
@ -213,7 +215,7 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
try {
|
try {
|
||||||
await api.personApi.updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: personName } });
|
await updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: personName } });
|
||||||
|
|
||||||
for (const person of people) {
|
for (const person of people) {
|
||||||
if (person.id === personToBeMergedIn.id) {
|
if (person.id === personToBeMergedIn.id) {
|
||||||
|
@ -248,7 +250,7 @@
|
||||||
|
|
||||||
const handleHidePerson = async (detail: PersonResponseDto) => {
|
const handleHidePerson = async (detail: PersonResponseDto) => {
|
||||||
try {
|
try {
|
||||||
const { data: updatedPerson } = await api.personApi.updatePerson({
|
const updatedPerson = await updatePerson({
|
||||||
id: detail.id,
|
id: detail.id,
|
||||||
personUpdateDto: { isHidden: true },
|
personUpdateDto: { isHidden: true },
|
||||||
});
|
});
|
||||||
|
@ -281,7 +283,7 @@
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchPeople = async (force: boolean) => {
|
const handleSearchPeople = async (force: boolean) => {
|
||||||
if (searchName === '') {
|
if (searchName === '') {
|
||||||
if ($page.url.searchParams.has(QueryParameter.SEARCHED_PEOPLE)) {
|
if ($page.url.searchParams.has(QueryParameter.SEARCHED_PEOPLE)) {
|
||||||
$page.url.searchParams.delete(QueryParameter.SEARCHED_PEOPLE);
|
$page.url.searchParams.delete(QueryParameter.SEARCHED_PEOPLE);
|
||||||
|
@ -295,9 +297,7 @@
|
||||||
|
|
||||||
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
|
||||||
try {
|
try {
|
||||||
const { data } = await api.searchApi.searchPerson({ name: searchName, withHidden: false });
|
searchedPeople = await searchPerson({ name: searchName, withHidden: false });
|
||||||
|
|
||||||
searchedPeople = data;
|
|
||||||
searchWord = searchName;
|
searchWord = searchName;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "Can't search people");
|
handleError(error, "Can't search people");
|
||||||
|
@ -318,7 +318,7 @@
|
||||||
changeName();
|
changeName();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const { data } = await api.searchApi.searchPerson({ name: personName, withHidden: true });
|
const data = await searchPerson({ name: personName, withHidden: true });
|
||||||
|
|
||||||
// We check if another person has the same name as the name entered by the user
|
// We check if another person has the same name as the name entered by the user
|
||||||
|
|
||||||
|
@ -353,7 +353,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: updatedPerson } = await api.personApi.updatePerson({
|
const updatedPerson = await updatePerson({
|
||||||
id: edittingPerson.id,
|
id: edittingPerson.id,
|
||||||
personUpdateDto: { birthDate: value.length > 0 ? value : null },
|
personUpdateDto: { birthDate: value.length > 0 ? value : null },
|
||||||
});
|
});
|
||||||
|
@ -381,7 +381,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { data: updatedPerson } = await api.personApi.updatePerson({
|
const updatedPerson = await updatePerson({
|
||||||
id: edittingPerson.id,
|
id: edittingPerson.id,
|
||||||
personUpdateDto: { name: personName },
|
personUpdateDto: { name: personName },
|
||||||
});
|
});
|
||||||
|
@ -529,7 +529,7 @@
|
||||||
preload={searchName !== '' || index < 20}
|
preload={searchName !== '' || index < 20}
|
||||||
bind:hidden={person.isHidden}
|
bind:hidden={person.isHidden}
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="100%"
|
widthStyle="100%"
|
||||||
bind:eyeColor={eyeColorMap[person.id]}
|
bind:eyeColor={eyeColorMap[person.id]}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { api } from '@api';
|
import { getAllPeople } from '@immich/sdk';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = (async () => {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
|
|
||||||
const { data: people } = await api.personApi.getAllPeople({ withHidden: true });
|
const people = await getAllPeople({ withHidden: true });
|
||||||
return {
|
return {
|
||||||
people,
|
people,
|
||||||
meta: {
|
meta: {
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
||||||
import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
|
import EditNameInput from '$lib/components/faces-page/edit-name-input.svelte';
|
||||||
import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte';
|
import MergeFaceSelector from '$lib/components/faces-page/merge-face-selector.svelte';
|
||||||
import UnMergeFaceSelector from '$lib/components/faces-page/unmerge-face-selector.svelte';
|
|
||||||
import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte';
|
import MergeSuggestionModal from '$lib/components/faces-page/merge-suggestion-modal.svelte';
|
||||||
import SetBirthDateModal from '$lib/components/faces-page/set-birth-date-modal.svelte';
|
import SetBirthDateModal from '$lib/components/faces-page/set-birth-date-modal.svelte';
|
||||||
|
import UnMergeFaceSelector from '$lib/components/faces-page/unmerge-face-selector.svelte';
|
||||||
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
|
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
|
||||||
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
|
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
|
||||||
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
|
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
|
||||||
|
@ -21,24 +21,32 @@
|
||||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
|
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
||||||
import {
|
import {
|
||||||
NotificationType,
|
NotificationType,
|
||||||
notificationController,
|
notificationController,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { AppRoute, QueryParameter, maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
import { AppRoute, QueryParameter, maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||||
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||||
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import { AssetStore } from '$lib/stores/assets.store';
|
import { AssetStore } from '$lib/stores/assets.store';
|
||||||
import { websocketStore } from '$lib/stores/websocket';
|
import { websocketStore } from '$lib/stores/websocket';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { type AssetResponseDto, type PersonResponseDto, api } from '@api';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import type { PageData } from './$types';
|
|
||||||
import { clickOutside } from '$lib/utils/click-outside';
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
|
|
||||||
import { mdiPlus, mdiDotsVertical, mdiArrowLeft } from '@mdi/js';
|
|
||||||
import { isExternalUrl } from '$lib/utils/navigation';
|
import { isExternalUrl } from '$lib/utils/navigation';
|
||||||
import { searchNameLocal } from '$lib/utils/person';
|
import { searchNameLocal } from '$lib/utils/person';
|
||||||
|
import {
|
||||||
|
getPersonStatistics,
|
||||||
|
mergePerson,
|
||||||
|
searchPerson,
|
||||||
|
updatePerson,
|
||||||
|
type AssetResponseDto,
|
||||||
|
type PersonResponseDto,
|
||||||
|
} from '@immich/sdk';
|
||||||
|
import { mdiArrowLeft, mdiDotsVertical, mdiPlus } from '@mdi/js';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -74,7 +82,7 @@
|
||||||
let refreshAssetGrid = false;
|
let refreshAssetGrid = false;
|
||||||
|
|
||||||
let personName = '';
|
let personName = '';
|
||||||
$: thumbnailData = api.getPeopleThumbnailUrl(data.person.id);
|
$: thumbnailData = getPeopleThumbnailUrl(data.person.id);
|
||||||
|
|
||||||
let name: string = data.person.name;
|
let name: string = data.person.name;
|
||||||
let suggestedPeople: PersonResponseDto[] = [];
|
let suggestedPeople: PersonResponseDto[] = [];
|
||||||
|
@ -97,8 +105,7 @@
|
||||||
}
|
}
|
||||||
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
|
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
|
||||||
try {
|
try {
|
||||||
const { data } = await api.searchApi.searchPerson({ name });
|
people = await searchPerson({ name });
|
||||||
people = data;
|
|
||||||
searchWord = name;
|
searchWord = name;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
people = [];
|
people = [];
|
||||||
|
@ -113,7 +120,7 @@
|
||||||
$: isAllArchive = [...$selectedAssets].every((asset) => asset.isArchived);
|
$: isAllArchive = [...$selectedAssets].every((asset) => asset.isArchived);
|
||||||
$: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite);
|
$: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite);
|
||||||
$: $onPersonThumbnail === data.person.id &&
|
$: $onPersonThumbnail === data.person.id &&
|
||||||
(thumbnailData = api.getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`);
|
(thumbnailData = getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`);
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
if (people) {
|
if (people) {
|
||||||
|
@ -215,7 +222,7 @@
|
||||||
|
|
||||||
const toggleHidePerson = async () => {
|
const toggleHidePerson = async () => {
|
||||||
try {
|
try {
|
||||||
await api.personApi.updatePerson({
|
await updatePerson({
|
||||||
id: data.person.id,
|
id: data.person.id,
|
||||||
personUpdateDto: { isHidden: !data.person.isHidden },
|
personUpdateDto: { isHidden: !data.person.isHidden },
|
||||||
});
|
});
|
||||||
|
@ -232,8 +239,8 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMerge = async (person: PersonResponseDto) => {
|
const handleMerge = async (person: PersonResponseDto) => {
|
||||||
const { data: statistics } = await api.personApi.getPersonStatistics({ id: person.id });
|
const { assets } = await getPersonStatistics({ id: person.id });
|
||||||
numberOfAssets = statistics.assets;
|
numberOfAssets = assets;
|
||||||
handleGoBack();
|
handleGoBack();
|
||||||
|
|
||||||
data.person = person;
|
data.person = person;
|
||||||
|
@ -246,7 +253,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.personApi.updatePerson({ id: data.person.id, personUpdateDto: { featureFaceAssetId: asset.id } });
|
await updatePerson({ id: data.person.id, personUpdateDto: { featureFaceAssetId: asset.id } });
|
||||||
|
|
||||||
notificationController.show({ message: 'Feature photo updated', type: NotificationType.Info });
|
notificationController.show({ message: 'Feature photo updated', type: NotificationType.Info });
|
||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
|
@ -256,10 +263,8 @@
|
||||||
|
|
||||||
const updateAssetCount = async () => {
|
const updateAssetCount = async () => {
|
||||||
try {
|
try {
|
||||||
const { data: statistics } = await api.personApi.getPersonStatistics({
|
const { assets } = await getPersonStatistics({ id: data.person.id });
|
||||||
id: data.person.id,
|
numberOfAssets = assets;
|
||||||
});
|
|
||||||
numberOfAssets = statistics.assets;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "Can't update the asset count");
|
handleError(error, "Can't update the asset count");
|
||||||
}
|
}
|
||||||
|
@ -270,7 +275,7 @@
|
||||||
viewMode = ViewMode.VIEW_ASSETS;
|
viewMode = ViewMode.VIEW_ASSETS;
|
||||||
isEditingName = false;
|
isEditingName = false;
|
||||||
try {
|
try {
|
||||||
await api.personApi.mergePerson({
|
await mergePerson({
|
||||||
id: personToBeMergedIn.id,
|
id: personToBeMergedIn.id,
|
||||||
mergePersonDto: { ids: [personToMerge.id] },
|
mergePersonDto: { ids: [personToMerge.id] },
|
||||||
});
|
});
|
||||||
|
@ -305,10 +310,7 @@
|
||||||
try {
|
try {
|
||||||
isEditingName = false;
|
isEditingName = false;
|
||||||
|
|
||||||
await api.personApi.updatePerson({
|
await updatePerson({ id: data.person.id, personUpdateDto: { name: personName } });
|
||||||
id: data.person.id,
|
|
||||||
personUpdateDto: { name: personName },
|
|
||||||
});
|
|
||||||
|
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
message: 'Change name successfully',
|
message: 'Change name successfully',
|
||||||
|
@ -340,16 +342,16 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await api.searchApi.searchPerson({ name: personName, withHidden: true });
|
const result = await searchPerson({ name: personName, withHidden: true });
|
||||||
|
|
||||||
const existingPerson = result.data.find(
|
const existingPerson = result.find(
|
||||||
(person: PersonResponseDto) =>
|
(person: PersonResponseDto) =>
|
||||||
person.name.toLowerCase() === personName.toLowerCase() && person.id !== data.person.id && person.name,
|
person.name.toLowerCase() === personName.toLowerCase() && person.id !== data.person.id && person.name,
|
||||||
);
|
);
|
||||||
if (existingPerson) {
|
if (existingPerson) {
|
||||||
personMerge2 = existingPerson;
|
personMerge2 = existingPerson;
|
||||||
personMerge1 = data.person;
|
personMerge1 = data.person;
|
||||||
potentialMergePeople = result.data
|
potentialMergePeople = result
|
||||||
.filter(
|
.filter(
|
||||||
(person: PersonResponseDto) =>
|
(person: PersonResponseDto) =>
|
||||||
personMerge2.name.toLowerCase() === person.name.toLowerCase() &&
|
personMerge2.name.toLowerCase() === person.name.toLowerCase() &&
|
||||||
|
@ -369,7 +371,7 @@
|
||||||
viewMode = ViewMode.VIEW_ASSETS;
|
viewMode = ViewMode.VIEW_ASSETS;
|
||||||
data.person.birthDate = birthDate;
|
data.person.birthDate = birthDate;
|
||||||
|
|
||||||
const { data: updatedPerson } = await api.personApi.updatePerson({
|
const updatedPerson = await updatePerson({
|
||||||
id: data.person.id,
|
id: data.person.id,
|
||||||
personUpdateDto: { birthDate: birthDate.length > 0 ? birthDate : null },
|
personUpdateDto: { birthDate: birthDate.length > 0 ? birthDate : null },
|
||||||
});
|
});
|
||||||
|
@ -557,7 +559,7 @@
|
||||||
<ImageThumbnail
|
<ImageThumbnail
|
||||||
circle
|
circle
|
||||||
shadow
|
shadow
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
url={getPeopleThumbnailUrl(person.id)}
|
||||||
altText={person.name}
|
altText={person.name}
|
||||||
widthStyle="2rem"
|
widthStyle="2rem"
|
||||||
heightStyle="2rem"
|
heightStyle="2rem"
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { api } from '@api';
|
import { getPerson, getPersonStatistics } from '@immich/sdk';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async ({ params }) => {
|
export const load = (async ({ params }) => {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
|
|
||||||
const { data: person } = await api.personApi.getPerson({ id: params.personId });
|
const [person, statistics] = await Promise.all([
|
||||||
const { data: statistics } = await api.personApi.getPersonStatistics({ id: params.personId });
|
getPerson({ id: params.personId }),
|
||||||
|
getPersonStatistics({ id: params.personId }),
|
||||||
|
]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
person,
|
person,
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { api } from '@api';
|
import { getExploreData } from '@immich/sdk';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async () => {
|
export const load = (async () => {
|
||||||
await authenticate();
|
await authenticate();
|
||||||
const { data: items } = await api.searchApi.getExploreData();
|
const items = await getExploreData();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
|
|
|
@ -26,9 +26,8 @@
|
||||||
import { preventRaceConditionSearchBar } from '$lib/stores/search.store';
|
import { preventRaceConditionSearchBar } from '$lib/stores/search.store';
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
||||||
import type { AssetResponseDto, SearchResponseDto } from '@immich/sdk';
|
import { search, type AssetResponseDto, type SearchResponseDto } from '@immich/sdk';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { api } from '@api';
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
@ -120,20 +119,20 @@
|
||||||
await authenticate();
|
await authenticate();
|
||||||
let results: SearchResponseDto | null = null;
|
let results: SearchResponseDto | null = null;
|
||||||
$page.url.searchParams.set('page', curPage.toString());
|
$page.url.searchParams.set('page', curPage.toString());
|
||||||
const res = await api.searchApi.search({}, { params: $page.url.searchParams });
|
const res = await search({ ...$page.url.searchParams });
|
||||||
if (searchResultAssets) {
|
if (searchResultAssets) {
|
||||||
searchResultAssets.push(...res.data.assets.items);
|
searchResultAssets.push(...res.assets.items);
|
||||||
} else {
|
} else {
|
||||||
searchResultAssets = res.data.assets.items;
|
searchResultAssets = res.assets.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assets = {
|
const assets = {
|
||||||
...res.data.assets,
|
...res.assets,
|
||||||
items: searchResultAssets,
|
items: searchResultAssets,
|
||||||
};
|
};
|
||||||
results = {
|
results = {
|
||||||
assets,
|
assets,
|
||||||
albums: res.data.albums,
|
albums: res.albums,
|
||||||
};
|
};
|
||||||
|
|
||||||
data.results = results;
|
data.results = results;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { type AssetResponseDto, type SearchResponseDto, api } from '@api';
|
import { search, type AssetResponseDto, type SearchResponseDto } from '@immich/sdk';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
import { QueryParameter } from '$lib/constants';
|
import { QueryParameter } from '$lib/constants';
|
||||||
|
|
||||||
|
@ -10,17 +10,17 @@ export const load = (async (data) => {
|
||||||
url.searchParams.get(QueryParameter.SEARCH_TERM) || url.searchParams.get(QueryParameter.QUERY) || undefined;
|
url.searchParams.get(QueryParameter.SEARCH_TERM) || url.searchParams.get(QueryParameter.QUERY) || undefined;
|
||||||
let results: SearchResponseDto | null = null;
|
let results: SearchResponseDto | null = null;
|
||||||
if (term) {
|
if (term) {
|
||||||
const res = await api.searchApi.search({}, { params: data.url.searchParams });
|
const response = await search({ ...data.url.searchParams });
|
||||||
let items: AssetResponseDto[] = (data as unknown as { results: SearchResponseDto }).results?.assets.items;
|
let items: AssetResponseDto[] = (data as unknown as { results: SearchResponseDto }).results?.assets.items;
|
||||||
if (items) {
|
if (items) {
|
||||||
items.push(...res.data.assets.items);
|
items.push(...response.assets.items);
|
||||||
} else {
|
} else {
|
||||||
items = res.data.assets.items;
|
items = response.assets.items;
|
||||||
}
|
}
|
||||||
const assets = { ...res.data.assets, items };
|
const assets = { ...response.assets, items };
|
||||||
results = {
|
results = {
|
||||||
assets,
|
assets,
|
||||||
albums: res.data.albums,
|
albums: response.albums,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
|
import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
|
||||||
|
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||||
import IndividualSharedViewer from '$lib/components/share-page/individual-shared-viewer.svelte';
|
import IndividualSharedViewer from '$lib/components/share-page/individual-shared-viewer.svelte';
|
||||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
|
import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
|
||||||
import ThemeButton from '$lib/components/shared-components/theme-button.svelte';
|
import ThemeButton from '$lib/components/shared-components/theme-button.svelte';
|
||||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
|
||||||
import { api, SharedLinkType } from '@api';
|
|
||||||
import type { PageData } from './$types';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { user } from '$lib/stores/user.store';
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { SharedLinkType } from '@api';
|
||||||
|
import { getMySharedLink } from '@immich/sdk';
|
||||||
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
let { sharedLink, passwordRequired, sharedLinkKey: key, meta } = data;
|
let { sharedLink, passwordRequired, sharedLinkKey: key, meta } = data;
|
||||||
|
@ -18,9 +19,8 @@
|
||||||
|
|
||||||
const handlePasswordSubmit = async () => {
|
const handlePasswordSubmit = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await api.sharedLinkApi.getMySharedLink({ password, key });
|
sharedLink = await getMySharedLink({ password, key });
|
||||||
passwordRequired = false;
|
passwordRequired = false;
|
||||||
sharedLink = result.data;
|
|
||||||
isOwned = $user ? $user.id === sharedLink.userId : false;
|
isOwned = $user ? $user.id === sharedLink.userId : false;
|
||||||
title = (sharedLink.album ? sharedLink.album.albumName : 'Public Share') + ' - Immich';
|
title = (sharedLink.album ? sharedLink.album.albumName : 'Public Share') + ' - Immich';
|
||||||
description = sharedLink.description || `${sharedLink.assets.length} shared photos & videos.`;
|
description = sharedLink.description || `${sharedLink.assets.length} shared photos & videos.`;
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
import { getAssetThumbnailUrl } from '$lib/utils';
|
||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { api, ThumbnailFormat } from '@api';
|
import { ThumbnailFormat } from '@api';
|
||||||
|
import { getMySharedLink } from '@immich/sdk';
|
||||||
|
import { error as throwError } from '@sveltejs/kit';
|
||||||
import type { AxiosError } from 'axios';
|
import type { AxiosError } from 'axios';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
import { error as throwError } from '@sveltejs/kit';
|
|
||||||
|
|
||||||
export const load = (async ({ params }) => {
|
export const load = (async ({ params }) => {
|
||||||
const { key } = params;
|
const { key } = params;
|
||||||
await authenticate({ public: true });
|
await authenticate({ public: true });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key });
|
const sharedLink = await getMySharedLink({ key });
|
||||||
|
|
||||||
const assetCount = sharedLink.assets.length;
|
const assetCount = sharedLink.assets.length;
|
||||||
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
|
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
|
||||||
|
|
||||||
|
@ -19,9 +20,7 @@ export const load = (async ({ params }) => {
|
||||||
meta: {
|
meta: {
|
||||||
title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
|
title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
|
||||||
description: sharedLink.description || `${assetCount} shared photos & videos.`,
|
description: sharedLink.description || `${assetCount} shared photos & videos.`,
|
||||||
imageUrl: assetId
|
imageUrl: assetId ? getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp) : '/feature-panel.png',
|
||||||
? api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
|
|
||||||
: '/feature-panel.png',
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { api } from '@api';
|
import { getAssetInfo } from '@immich/sdk';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async ({ params }) => {
|
export const load = (async ({ params }) => {
|
||||||
const { key, assetId } = params;
|
const { key, assetId } = params;
|
||||||
const { data: asset } = await api.assetApi.getAssetInfo({ id: assetId, key });
|
const asset = await getAssetInfo({ id: assetId, key });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
asset,
|
asset,
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
|
||||||
import { api, copyToClipboard, makeSharedLinkUrl, type SharedLinkResponseDto } from '@api';
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
|
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
||||||
|
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||||
|
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType,
|
NotificationType,
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { onMount } from 'svelte';
|
import SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
|
||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
|
||||||
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { mdiArrowLeft } from '@mdi/js';
|
|
||||||
import { serverConfig } from '$lib/stores/server-config.store';
|
import { serverConfig } from '$lib/stores/server-config.store';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { copyToClipboard, makeSharedLinkUrl, type SharedLinkResponseDto } from '@api';
|
||||||
|
import { getAllSharedLinks, removeSharedLink } from '@immich/sdk';
|
||||||
|
import { mdiArrowLeft } from '@mdi/js';
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
let sharedLinks: SharedLinkResponseDto[] = [];
|
let sharedLinks: SharedLinkResponseDto[] = [];
|
||||||
let editSharedLink: SharedLinkResponseDto | null = null;
|
let editSharedLink: SharedLinkResponseDto | null = null;
|
||||||
|
@ -21,8 +22,7 @@
|
||||||
let deleteLinkId: string | null = null;
|
let deleteLinkId: string | null = null;
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
const { data } = await api.sharedLinkApi.getAllSharedLinks();
|
sharedLinks = await getAllSharedLinks();
|
||||||
sharedLinks = data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api.sharedLinkApi.removeSharedLink({ id: deleteLinkId });
|
await removeSharedLink({ id: deleteLinkId });
|
||||||
notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
|
notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
|
||||||
deleteLinkId = null;
|
deleteLinkId = null;
|
||||||
await refresh();
|
await refresh();
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import '../app.css';
|
|
||||||
import { page } from '$app/stores';
|
|
||||||
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
import { afterNavigate, beforeNavigate } from '$app/navigation';
|
||||||
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
|
import { page } from '$app/stores';
|
||||||
import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
|
import DownloadPanel from '$lib/components/asset-viewer/download-panel.svelte';
|
||||||
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
|
|
||||||
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
|
|
||||||
import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte';
|
|
||||||
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
|
|
||||||
import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
|
import AppleHeader from '$lib/components/shared-components/apple-header.svelte';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
|
||||||
import { loadConfig } from '$lib/stores/server-config.store';
|
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
|
||||||
import { api } from '@api';
|
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
|
||||||
import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket';
|
import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte';
|
||||||
import { user } from '$lib/stores/user.store';
|
|
||||||
import { type ThemeSetting, colorTheme, handleToggleTheme } from '$lib/stores/preferences.store';
|
|
||||||
import { Theme } from '$lib/constants';
|
import { Theme } from '$lib/constants';
|
||||||
|
import { colorTheme, handleToggleTheme, type ThemeSetting } from '$lib/stores/preferences.store';
|
||||||
|
import { loadConfig } from '$lib/stores/server-config.store';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket';
|
||||||
|
import { setKey } from '$lib/utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
import '../app.css';
|
||||||
|
|
||||||
let showNavigationLoadingBar = false;
|
let showNavigationLoadingBar = false;
|
||||||
let albumId: string | undefined;
|
let albumId: string | undefined;
|
||||||
|
@ -55,7 +55,7 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSharedLinkRoute($page.route?.id)) {
|
if (isSharedLinkRoute($page.route?.id)) {
|
||||||
api.setKey($page.params.key);
|
setKey($page.params.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeNavigate(({ from, to }) => {
|
beforeNavigate(({ from, to }) => {
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
import { type AllJobStatusResponseDto } from '@api';
|
import { getAllJobsStatus, type AllJobStatusResponseDto } from '@immich/sdk';
|
||||||
import { getAllJobsStatus } from '@immich/sdk';
|
|
||||||
import { mdiCog } from '@mdi/js';
|
import { mdiCog } from '@mdi/js';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { AlbumResponseDto } from '@api';
|
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import type { AlbumResponseDto } from '@immich/sdk';
|
||||||
import { Sync } from 'factory.ts';
|
import { Sync } from 'factory.ts';
|
||||||
import { userFactory } from './user-factory';
|
import { userFactory } from './user-factory';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue