1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-29 15:11:58 +00:00

refactor(web) open api client (#7103)

* refactor: person api

* refactor: shared link and others
This commit is contained in:
Jason Rasmussen 2024-02-14 08:09:49 -05:00 committed by GitHub
parent 5fc1d43012
commit d8631a00bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 638 additions and 656 deletions

View file

@ -1,135 +1,15 @@
import {
AssetApi,
AssetApiFp,
AssetJobName,
DownloadApi,
JobName,
PersonApi,
SearchApi,
SharedLinkApi,
UserApiFp,
base,
common,
configuration,
} from '@immich/sdk/axios';
import type { ApiParams as ApiParameters } from './types';
import { AssetApi, DownloadApi, configuration } from '@immich/sdk/axios';
class ImmichApi {
public downloadApi: DownloadApi;
public assetApi: AssetApi;
public searchApi: SearchApi;
public sharedLinkApi: SharedLinkApi;
public personApi: PersonApi;
private config: configuration.Configuration;
private key?: string;
get isSharedLink() {
return !!this.key;
}
constructor(parameters: configuration.ConfigurationParameters) {
this.config = new configuration.Configuration(parameters);
this.downloadApi = new DownloadApi(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];
}
}

View file

@ -1,5 +1,5 @@
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 {
NotificationType,

View file

@ -1,12 +1,10 @@
<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 JobTileButton from './job-tile-button.svelte';
import JobTileStatus from './job-tile-status.svelte';
import Button from '$lib/components/elements/buttons/button.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 {
mdiAlertCircle,
mdiAllInclusive,
@ -16,6 +14,9 @@
mdiPlay,
mdiSelectionSearch,
} 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 subtitle: string | undefined;

View file

@ -4,9 +4,10 @@
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { featureFlags } from '$lib/stores/server-config.store';
import { getJobName } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { type AllJobStatusResponseDto, api, JobCommand, type JobCommandDto, JobName } from '@api';
import type { ComponentType } from 'svelte';
import { sendJobCommand, type AllJobStatusResponseDto, type JobCommandDto } from '@immich/sdk';
import { JobCommand, JobName } from '@immich/sdk/axios';
import {
mdiFaceRecognition,
mdiFileJpgBox,
@ -18,10 +19,10 @@
mdiTagFaces,
mdiVideo,
} from '@mdi/js';
import type { ComponentType } from 'svelte';
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
import JobTile from './job-tile.svelte';
import StorageMigrationDescription from './storage-migration-description.svelte';
import { sendJobCommand } from '@immich/sdk';
export let jobs: AllJobStatusResponseDto;
@ -59,23 +60,23 @@
$: jobDetails = <Partial<Record<JobName, JobDetails>>>{
[JobName.ThumbnailGeneration]: {
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',
},
[JobName.MetadataExtraction]: {
icon: mdiTable,
title: api.getJobName(JobName.MetadataExtraction),
title: getJobName(JobName.MetadataExtraction),
subtitle: 'Extract metadata information from each asset, such as GPS and resolution',
},
[JobName.Library]: {
icon: mdiLibraryShelves,
title: api.getJobName(JobName.Library),
title: getJobName(JobName.Library),
subtitle: 'Perform library tasks',
allText: 'ALL',
missingText: 'REFRESH',
},
[JobName.Sidecar]: {
title: api.getJobName(JobName.Sidecar),
title: getJobName(JobName.Sidecar),
icon: mdiFileXmlBox,
subtitle: 'Discover or synchronize sidecar metadata from the filesystem',
allText: 'SYNC',
@ -84,13 +85,13 @@
},
[JobName.SmartSearch]: {
icon: mdiImageSearch,
title: api.getJobName(JobName.SmartSearch),
title: getJobName(JobName.SmartSearch),
subtitle: 'Run machine learning on assets to support smart search',
disabled: !$featureFlags.smartSearch,
},
[JobName.FaceDetection]: {
icon: mdiFaceRecognition,
title: api.getJobName(JobName.FaceDetection),
title: getJobName(JobName.FaceDetection),
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.',
handleCommand: handleConfirmCommand,
@ -98,7 +99,7 @@
},
[JobName.FacialRecognition]: {
icon: mdiTagFaces,
title: api.getJobName(JobName.FacialRecognition),
title: getJobName(JobName.FacialRecognition),
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.',
handleCommand: handleConfirmCommand,
@ -106,18 +107,18 @@
},
[JobName.VideoConversion]: {
icon: mdiVideo,
title: api.getJobName(JobName.VideoConversion),
title: getJobName(JobName.VideoConversion),
subtitle: 'Transcode videos for wider compatibility with browsers and devices',
},
[JobName.StorageTemplateMigration]: {
icon: mdiFolderMove,
title: api.getJobName(JobName.StorageTemplateMigration),
title: getJobName(JobName.StorageTemplateMigration),
allowForceCommand: false,
component: StorageMigrationDescription,
},
[JobName.Migration]: {
icon: mdiFolderMove,
title: api.getJobName(JobName.Migration),
title: getJobName(JobName.Migration),
subtitle: 'Migrate thumbnails for assets and faces to the latest folder structure',
allowForceCommand: false,
},

View file

@ -1,10 +1,10 @@
<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 { 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 = {
photos: 0,

View file

@ -1,5 +1,5 @@
import type { ResetOptions } from '$lib/utils/dipatch';
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
export type SettingsEventType = {
reset: ResetOptions & { configKeys: Array<keyof SystemConfigDto> };

View file

@ -1,25 +1,18 @@
<script lang="ts">
import {
AudioCodec,
CQMode,
type SystemConfigDto,
ToneMapping,
TranscodeHWAccel,
TranscodePolicy,
VideoCodec,
} from '@api';
import Icon from '$lib/components/elements/icon.svelte';
import { type SystemConfigDto } from '@immich/sdk';
import { AudioCodec, CQMode, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@immich/sdk/axios';
import { mdiHelpCircleOutline } from '@mdi/js';
import { isEqual, sortBy } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import type { SettingsEventType } from '../admin-settings';
import SettingAccordion from '../setting-accordion.svelte';
import SettingButtonsRow from '../setting-buttons-row.svelte';
import SettingCheckboxes from '../setting-checkboxes.svelte';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import SettingSelect from '../setting-select.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 defaultConfig: SystemConfigDto;

View file

@ -1,5 +1,7 @@
<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 { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
@ -41,7 +43,7 @@
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
{disabled}
label="{api.getJobName(jobName)} Concurrency"
label="{getJobName(jobName)} Concurrency"
desc=""
bind:value={config.job[jobName].concurrency}
required={true}
@ -50,7 +52,7 @@
{:else}
<SettingInputField
inputType={SettingInputFieldType.NUMBER}
label="{api.getJobName(jobName)} Concurrency"
label="{getJobName(jobName)} Concurrency"
desc=""
value="1"
disabled={true}

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,5 +1,6 @@
<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 { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,11 +1,11 @@
<script lang="ts">
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 { fade } from 'svelte/transition';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import type { SettingsEventType } from '../admin-settings';
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
export let savedConfig: SystemConfigDto;
export let defaultConfig: SystemConfigDto;

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigTemplateStorageOptionDto } from '@api';
import type { SystemConfigTemplateStorageOptionDto } from '@immich/sdk';
import * as luxon from 'luxon';
export let options: SystemConfigTemplateStorageOptionDto;

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,7 +1,8 @@
<script lang="ts">
import SettingButtonsRow from '$lib/components/admin-page/settings/setting-buttons-row.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 { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -1,5 +1,5 @@
<script lang="ts">
import type { SystemConfigDto } from '@api';
import type { SystemConfigDto } from '@immich/sdk';
import { isEqual } from 'lodash-es';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';

View file

@ -3,6 +3,7 @@
import Icon from '$lib/components/elements/icon.svelte';
import { locale } from '$lib/stores/preferences.store';
import { user } from '$lib/stores/user.store';
import { getAssetThumbnailUrl } from '$lib/utils';
import { ThumbnailFormat, api, type AlbumResponseDto } from '@api';
import { getUserById } from '@immich/sdk';
import { mdiDotsVertical } from '@mdi/js';
@ -19,7 +20,7 @@
let showVerticalDots = false;
$: imageData = album.albumThumbnailAssetId
? api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
? getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)
: noThumbnailUrl;
const dispatchClick = createEventDispatcher<OnClick>();

View file

@ -1,4 +1,4 @@
import type { AlbumResponseDto } from '@api';
import type { AlbumResponseDto } from '@immich/sdk';
export type OnShowContextMenu = {
showalbumcontextmenu: OnShowContextMenuDetail;

View file

@ -1,5 +1,5 @@
<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 { mdiDotsVertical } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';

View file

@ -2,8 +2,13 @@
import { goto } from '$app/navigation';
import Icon from '$lib/components/elements/icon.svelte';
import { AppRoute } from '$lib/constants';
import { api, type AlbumResponseDto, type SharedLinkResponseDto, type UserResponseDto } from '@api';
import { getAllUsers } from '@immich/sdk';
import {
getAllSharedLinks,
getAllUsers,
type AlbumResponseDto,
type SharedLinkResponseDto,
type UserResponseDto,
} from '@immich/sdk';
import { mdiCheck, mdiLink, mdiShareCircle } from '@mdi/js';
import { createEventDispatcher, onMount } from 'svelte';
import Button from '../elements/buttons/button.svelte';
@ -35,8 +40,7 @@
});
const getSharedLinks = async () => {
const { data } = await api.sharedLinkApi.getAllSharedLinks();
const data = await getAllSharedLinks();
sharedLinks = data.filter((link) => link.album?.id === album.id);
};

View file

@ -1,20 +1,21 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.svelte';
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
import { getAssetThumbnailUrl } from '$lib/utils';
import { getAssetType } from '$lib/utils/asset-utils';
import { autoGrowHeight } from '$lib/utils/autogrow';
import { clickOutside } from '$lib/utils/click-outside';
import { handleError } from '$lib/utils/handle-error';
import { isTenMinutesApart } from '$lib/utils/timesince';
import {
AssetTypeEnum,
ReactionType,
ThumbnailFormat,
api,
createActivity,
deleteActivity,
getActivities,
type ActivityResponseDto,
type AssetTypeEnum,
type UserResponseDto,
} from '@api';
import { createActivity, deleteActivity, getActivities } from '@immich/sdk';
} from '@immich/sdk';
import { ReactionType, ThumbnailFormat } from '@immich/sdk/axios';
import { mdiClose, mdiDotsVertical, mdiHeart, mdiSend } from '@mdi/js';
import * as luxon from 'luxon';
import { createEventDispatcher, onMount } from 'svelte';
@ -191,7 +192,7 @@
<div class="aspect-square w-[75px] h-[75px]">
<img
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"
/>
</div>
@ -237,7 +238,7 @@
<div class="aspect-square w-[75px] h-[75px]">
<img
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"
/>
</div>

View file

@ -1,5 +1,6 @@
<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';
const dispatch = createEventDispatcher<{
@ -32,7 +33,7 @@
<div class="h-12 w-12 shrink-0 rounded-xl bg-slate-300">
{#if album.albumThumbnailAssetId}
<img
src={api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
src={getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Webp)}
alt={album.albumName}
class="z-0 h-full w-full rounded-xl object-cover transition-all duration-300 hover:shadow-lg"
data-testid="album-image"

View file

@ -1,9 +1,11 @@
<script lang="ts">
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 { getAssetJobName } from '$lib/utils';
import { clickOutside } from '$lib/utils/click-outside';
import { getContextMenuPosition } from '$lib/utils/context-menu';
import { AssetJobName, type AssetResponseDto, AssetTypeEnum, api } from '@api';
import { AssetJobName, AssetTypeEnum, type AssetResponseDto } from '@api';
import {
mdiAlertOutline,
mdiArrowLeft,
@ -22,7 +24,6 @@
import { createEventDispatcher } from 'svelte';
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.svelte';
import { user } from '$lib/stores/user.store';
export let asset: AssetResponseDto;
export let showCopyButton: boolean;
@ -182,16 +183,16 @@
<MenuOption
on:click={() => onJobClick(AssetJobName.RefreshMetadata)}
text={api.getAssetJobName(AssetJobName.RefreshMetadata)}
text={getAssetJobName(AssetJobName.RefreshMetadata)}
/>
<MenuOption
on:click={() => onJobClick(AssetJobName.RegenerateThumbnail)}
text={api.getAssetJobName(AssetJobName.RegenerateThumbnail)}
text={getAssetJobName(AssetJobName.RegenerateThumbnail)}
/>
{#if asset.type === AssetTypeEnum.Video}
<MenuOption
on:click={() => onJobClick(AssetJobName.TranscodeVideo)}
text={api.getAssetJobName(AssetJobName.TranscodeVideo)}
text={getAssetJobName(AssetJobName.TranscodeVideo)}
/>
{/if}
{/if}

View file

@ -11,6 +11,7 @@
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
import { user } from '$lib/stores/user.store';
import { getAssetJobMessage, isSharedLink } from '$lib/utils';
import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
@ -19,7 +20,6 @@
AssetJobName,
AssetTypeEnum,
ReactionType,
api,
type ActivityResponseDto,
type AlbumResponseDto,
type AssetResponseDto,
@ -29,9 +29,13 @@
createActivity,
createAlbum,
deleteActivity,
deleteAssets,
getActivities,
getActivityStatistics,
getAllAlbums,
runAssetJobs,
updateAsset,
updateAssets,
} from '@immich/sdk';
import { mdiChevronLeft, mdiChevronRight, mdiImageBrokenVariant } from '@mdi/js';
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
@ -235,7 +239,7 @@
$: asset.id && !sharedLink && handleGetAllAlbums(); // Update the album information when the asset ID changes
const handleGetAllAlbums = async () => {
if (api.isSharedLink) {
if (isSharedLink()) {
return;
}
@ -386,7 +390,7 @@
const trashAsset = async () => {
try {
await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } });
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id] } });
dispatch('action', { type: AssetAction.TRASH, asset });
@ -401,7 +405,7 @@
const deleteAsset = async () => {
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 });
@ -418,7 +422,7 @@
const toggleFavorite = async () => {
try {
const { data } = await api.assetApi.updateAsset({
const data = await updateAsset({
id: asset.id,
updateAssetDto: {
isFavorite: !asset.isFavorite,
@ -470,7 +474,7 @@
const toggleArchive = async () => {
try {
const { data } = await api.assetApi.updateAsset({
const data = await updateAsset({
id: asset.id,
updateAssetDto: {
isArchived: !asset.isArchived,
@ -491,8 +495,8 @@
const handleRunJob = async (name: AssetJobName) => {
try {
await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
notificationController.show({ type: NotificationType.Info, message: api.getAssetJobMessage(name) });
await runAssetJobs({ assetJobsDto: { assetIds: [asset.id], name } });
notificationController.show({ type: NotificationType.Info, message: getAssetJobMessage(name) });
} catch (error) {
await handleError(error, `Unable to submit job`);
}
@ -552,7 +556,7 @@
const handleUnstack = async () => {
try {
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) {
child.stackParentId = null;
child.stackCount = 0;

View file

@ -1,15 +1,18 @@
<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 { 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 { type AlbumResponseDto, type AssetResponseDto, ThumbnailFormat, api } from '@api';
import { DateTime } from 'luxon';
import { createEventDispatcher, onDestroy } from 'svelte';
import { slide } from 'svelte/transition';
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 { autoGrowHeight } from '$lib/utils/autogrow';
import { clickOutside } from '$lib/utils/click-outside';
import { ThumbnailFormat, type AlbumResponseDto, type AssetResponseDto } from '@api';
import { getAssetInfo, updateAsset } from '@immich/sdk';
import {
mdiCalendar,
mdiCameraIris,
@ -17,22 +20,21 @@
mdiEye,
mdiEyeOff,
mdiImageOutline,
mdiMapMarkerOutline,
mdiInformationOutline,
mdiMapMarkerOutline,
mdiPencil,
} from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte';
import PersonSidePanel from '../faces-page/person-side-panel.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
import Map from '../shared-components/map/map.svelte';
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 { DateTime } from 'luxon';
import { createEventDispatcher, onDestroy } from 'svelte';
import { slide } from 'svelte/transition';
import { asByteUnitString } from '../../utils/byte-units';
import { handleError } from '../../utils/handle-error';
import { user } from '$lib/stores/user.store';
import { autoGrowHeight } from '$lib/utils/autogrow';
import { clickOutside } from '$lib/utils/click-outside';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
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 albums: AlbumResponseDto[] = [];
@ -61,8 +63,8 @@
description = newAsset?.exifInfo?.description || '';
// Get latest description from server
if (newAsset.id && !api.isSharedLink) {
const { data } = await api.assetApi.getAssetInfo({ id: asset.id });
if (newAsset.id && !isSharedLink()) {
const data = await getAssetInfo({ id: asset.id });
people = data?.people || [];
description = data.exifInfo?.description || '';
@ -127,9 +129,9 @@
};
const handleRefreshPeople = async () => {
await api.assetApi.getAssetInfo({ id: asset.id }).then((res) => {
people = res.data?.people || [];
textArea.value = res.data?.exifInfo?.description || '';
await getAssetInfo({ id: asset.id }).then((data) => {
people = data?.people || [];
textArea.value = data?.exifInfo?.description || '';
});
showEditFaces = false;
};
@ -146,10 +148,7 @@
originalDescription = description;
dispatch('descriptionFocusOut');
try {
await api.assetApi.updateAsset({
id: asset.id,
updateAssetDto: { description },
});
await updateAsset({ id: asset.id, updateAssetDto: { description } });
} catch (error) {
handleError(error, 'Cannot update the description');
}
@ -162,7 +161,7 @@
async function handleConfirmChangeDate(dateTimeOriginal: string) {
isShowChangeDate = false;
try {
await api.assetApi.updateAsset({ id: asset.id, updateAssetDto: { dateTimeOriginal } });
await updateAsset({ id: asset.id, updateAssetDto: { dateTimeOriginal } });
} catch (error) {
handleError(error, 'Unable to change date');
}
@ -174,13 +173,7 @@
isShowChangeLocation = false;
try {
await api.assetApi.updateAsset({
id: asset.id,
updateAssetDto: {
latitude: gps.lat,
longitude: gps.lng,
},
});
await updateAsset({ id: asset.id, updateAssetDto: { latitude: gps.lat, longitude: gps.lng } });
} catch (error) {
handleError(error, 'Unable to change location');
}
@ -219,7 +212,7 @@
<section class="px-4 mt-10">
{#key asset.id}
<textarea
disabled={!isOwner || api.isSharedLink}
disabled={!isOwner || isSharedLink()}
bind:this={textArea}
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"
@ -238,7 +231,7 @@
<p class="px-4 break-words whitespace-pre-line w-full text-black dark:text-white text-base">{description}</p>
{/if}
{#if !api.isSharedLink && people.length > 0}
{#if !isSharedLink() && people.length > 0}
<section class="px-4 py-4 text-sm">
<div class="flex h-10 w-full items-center justify-between">
<h2>PEOPLE</h2>
@ -284,7 +277,7 @@
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="90px"
@ -670,7 +663,7 @@
alt={album.albumName}
class="h-[50px] w-[50px] rounded object-cover"
src={album.albumThumbnailAssetId &&
api.getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
getAssetThumbnailUrl(album.albumThumbnailAssetId, ThumbnailFormat.Jpeg)}
draggable="false"
/>
</div>

View file

@ -2,12 +2,13 @@
import { fade } from 'svelte/transition';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { api, type AssetResponseDto } from '@api';
import { getKey } from '$lib/utils';
export let asset: AssetResponseDto;
const loadAssetData = async () => {
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' },
);
if (data instanceof Blob) {

View file

@ -1,17 +1,18 @@
<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 { getBoundingBox } from '$lib/utils/people-utils';
import { boundingBoxesArray } from '$lib/stores/people.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 element: HTMLDivElement | undefined = undefined;
@ -50,7 +51,7 @@
abortController = new AbortController();
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',
signal: abortController.signal,

View file

@ -1,10 +1,11 @@
<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 LoadingSpinner from '../shared-components/loading-spinner.svelte';
import { getAssetFileUrl, getAssetThumbnailUrl } from '$lib/utils';
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;
@ -35,9 +36,9 @@
on:canplay={handleCanPlay}
on:ended={() => dispatch('onVideoEnded')}
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" />
</video>

View file

@ -1,12 +1,10 @@
<script lang="ts">
import { ProjectionType } from '$lib/constants';
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 { api, type AssetResponseDto, AssetTypeEnum, ThumbnailFormat } from '@api';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import ImageThumbnail from './image-thumbnail.svelte';
import VideoThumbnail from './video-thumbnail.svelte';
import { AssetTypeEnum, ThumbnailFormat, type AssetResponseDto } from '@api';
import {
mdiArchiveArrowDownOutline,
mdiCameraBurst,
@ -17,7 +15,10 @@
mdiMotionPlayOutline,
mdiRotate360,
} 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<{
click: { asset: AssetResponseDto };
@ -138,13 +139,13 @@
/>
<!-- Favorite asset star -->
{#if !api.isSharedLink && asset.isFavorite}
{#if !isSharedLink() && asset.isFavorite}
<div class="absolute bottom-2 left-2 z-10">
<Icon path={mdiHeart} size="24" class="text-white" />
</div>
{/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">
<Icon path={mdiArchiveArrowDownOutline} size="24" class="text-white" />
</div>
@ -175,7 +176,7 @@
{#if asset.resized}
<ImageThumbnail
url={api.getAssetThumbnailUrl(asset.id, format)}
url={getAssetThumbnailUrl(asset.id, format)}
altText={asset.originalFileName}
widthStyle="{width}px"
heightStyle="{height}px"
@ -191,7 +192,7 @@
{#if asset.type === AssetTypeEnum.Video}
<div class="absolute top-0 h-full w-full">
<VideoThumbnail
url={api.getAssetFileUrl(asset.id, false, true)}
url={getAssetFileUrl(asset.id, false, true)}
enablePlayback={mouseOver}
curve={selected}
durationInSeconds={timeToSeconds(asset.duration)}
@ -202,7 +203,7 @@
{#if asset.type === AssetTypeEnum.Image && asset.livePhotoVideoId}
<div class="absolute top-0 h-full w-full">
<VideoThumbnail
url={api.getAssetFileUrl(asset.livePhotoVideoId, false, true)}
url={getAssetFileUrl(asset.livePhotoVideoId, false, true)}
pauseIcon={mdiMotionPauseOutline}
playIcon={mdiMotionPlayOutline}
showTime={false}

View file

@ -1,16 +1,18 @@
<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 { linear } from 'svelte/easing';
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 { getPersonNameWithHiddenValue, searchNameLocal } from '$lib/utils/person';
import { handleError } from '$lib/utils/handle-error';
import { photoViewer } from '$lib/stores/assets.store';
import { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
import Icon from '../elements/icon.svelte';
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
export let peopleWithFaces: AssetFaceResponseDto[];
export let allPeople: PersonResponseDto[];
@ -42,7 +44,7 @@
if (assetType === AssetTypeEnum.Image) {
image = $photoViewer;
} 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();
img.src = data;
@ -116,7 +118,7 @@
}
const timeout = setTimeout(() => (isShowLoadingSearch = true), timeBeforeShowLoadingSpinner);
try {
const { data } = await api.searchApi.searchPerson({ name: searchName });
const data = await searchPerson({ name: searchName });
searchedPeople = data;
searchedPeopleCopy = data;
searchWord = searchName;
@ -229,7 +231,7 @@
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
widthStyle="90px"
@ -255,7 +257,7 @@
<ImageThumbnail
curve
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={getPersonNameWithHiddenValue(person.name, person.isHidden)}
title={getPersonNameWithHiddenValue(person.name, person.isHidden)}
widthStyle="90px"

View file

@ -1,7 +1,8 @@
<script lang="ts">
import { api, type PersonResponseDto } from '@api';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import { getPeopleThumbnailUrl } from '$lib/utils';
import { type PersonResponseDto } from '@api';
import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
export let person: PersonResponseDto;
export let selectable = false;
@ -34,13 +35,7 @@
class:dark:border-immich-dark-primary={border}
class:border-immich-primary={border}
>
<ImageThumbnail
{circle}
url={api.getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="100%"
shadow
/>
<ImageThumbnail {circle} url={getPeopleThumbnailUrl(person.id)} altText={person.name} widthStyle="100%" shadow />
</div>
<div

View file

@ -1,22 +1,23 @@
<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 { api, type PersonResponseDto } from '@api';
import FaceThumbnail from './face-thumbnail.svelte';
import { flip } from 'svelte/animate';
import { quintOut } from 'svelte/easing';
import { fly } from 'svelte/transition';
import ControlAppBar from '../shared-components/control-app-bar.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 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 { page } from '$app/stores';
export let person: PersonResponseDto;
let people: PersonResponseDto[] = [];
@ -35,7 +36,7 @@
);
onMount(async () => {
const { data } = await api.personApi.getAllPeople({ withHidden: false });
const data = await getAllPeople({ withHidden: false });
people = data.people;
});
@ -68,11 +69,11 @@
const handleMerge = async () => {
try {
let { data: results } = await api.personApi.mergePerson({
let results = await mergePerson({
id: person.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;
notificationController.show({
message: `Merged ${count} ${count === 1 ? 'person' : 'people'}`,

View file

@ -1,12 +1,13 @@
<script lang="ts">
import Icon from '$lib/components/elements/icon.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 ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import Button from '../elements/buttons/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 personMerge2: PersonResponseDto;
@ -60,7 +61,7 @@
<ImageThumbnail
circle
shadow
url={api.getPeopleThumbnailUrl(personMerge1.id)}
url={getPeopleThumbnailUrl(personMerge1.id)}
altText={personMerge1.name}
widthStyle="100%"
/>
@ -85,7 +86,7 @@
border={potentialMergePeople.length > 0}
circle
shadow
url={api.getPeopleThumbnailUrl(personMerge2.id)}
url={getPeopleThumbnailUrl(personMerge2.id)}
altText={personMerge2.name}
widthStyle="100%"
/>
@ -104,7 +105,7 @@
border={true}
circle
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="100%"
on:click={() => changePersonToMerge(person)}

View file

@ -1,15 +1,16 @@
<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 { type PersonResponseDto } from '@api';
import { mdiDotsVertical } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
import IconButton from '../elements/buttons/icon-button.svelte';
import ContextMenu from '../shared-components/context-menu/context-menu.svelte';
import MenuOption from '../shared-components/context-menu/menu-option.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 preload = false;
@ -50,7 +51,7 @@
<ImageThumbnail
shadow
{preload}
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={person.name}
title={person.name}
widthStyle="100%"

View file

@ -1,11 +1,12 @@
<script lang="ts">
import { api, type PersonResponseDto } from '@api';
import FaceThumbnail from './face-thumbnail.svelte';
import { createEventDispatcher } from 'svelte';
import { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
import { handleError } from '$lib/utils/handle-error';
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 { maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
export let screenHeight: number;
export let people: PersonResponseDto[];
@ -40,8 +41,7 @@
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
try {
const { data } = await api.searchApi.searchPerson({ name });
people = data;
people = await searchPerson({ name });
searchWord = name;
} catch (error) {
handleError(error, "Can't search people");

View file

@ -1,20 +1,21 @@
<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 { NotificationType, notificationController } from '../shared-components/notification/notification';
import { mdiArrowLeftThin, mdiRestart } from '@mdi/js';
import Icon from '../elements/icon.svelte';
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
import { boundingBoxesArray } from '$lib/stores/people.store';
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 { timeBeforeShowLoadingSpinner } from '$lib/constants';
import { getFaces, reassignFacesById } from '@immich/sdk';
import { AssetTypeEnum, type AssetFaceResponseDto, type PersonResponseDto } from '@api';
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 assetType: AssetTypeEnum;
@ -69,8 +70,8 @@
onMount(async () => {
const timeout = setTimeout(() => (isShowLoadingPeople = true), timeBeforeShowLoadingSpinner);
try {
const { data } = await api.personApi.getAllPeople({ withHidden: true });
allPeople = data.people;
const { people } = await getAllPeople({ withHidden: true });
allPeople = people;
peopleWithFaces = await getFaces({ id: assetId });
selectedPersonToCreate = Array.from({ length: peopleWithFaces.length });
selectedPersonToReassign = Array.from({ length: peopleWithFaces.length });
@ -115,7 +116,7 @@
faceDto: { id: peopleWithFace.id },
});
} else if (selectedPersonToCreate[index]) {
const { data } = await api.personApi.createPerson();
const data = await createPerson();
numberOfPersonToCreate.push(data.id);
await reassignFacesById({
id: data.id,
@ -214,7 +215,7 @@
curve
shadow
url={selectedPersonToCreate[index] ||
api.getPeopleThumbnailUrl(selectedPersonToReassign[index]?.id || face.person.id)}
getPeopleThumbnailUrl(selectedPersonToReassign[index]?.id || face.person.id)}
altText={selectedPersonToReassign[index]
? selectedPersonToReassign[index]?.name || ''
: selectedPersonToCreate[index]

View file

@ -1,18 +1,24 @@
<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 { 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 personAssets: PersonResponseDto;
@ -41,7 +47,7 @@
}
onMount(async () => {
const { data } = await api.personApi.getAllPeople({ withHidden: false });
const data = await getAllPeople({ withHidden: false });
people = data.people;
});
@ -68,11 +74,8 @@
try {
disableButtons = true;
const { data } = await api.personApi.createPerson();
await api.personApi.reassignFaces({
id: data.id,
assetFaceUpdateDto: { data: selectedPeople },
});
const data = await createPerson();
await reassignFaces({ id: data.id, assetFaceUpdateDto: { data: selectedPeople } });
notificationController.show({
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to a new person`,
@ -93,10 +96,7 @@
try {
disableButtons = true;
if (selectedPerson) {
await api.personApi.reassignFaces({
id: selectedPerson.id,
assetFaceUpdateDto: { data: selectedPeople },
});
await reassignFaces({ id: selectedPerson.id, assetFaceUpdateDto: { data: selectedPeople } });
notificationController.show({
message: `Re-assigned ${assetIds.length} asset${assetIds.length > 1 ? 's' : ''} to ${
selectedPerson.name || 'an existing person'

View file

@ -1,20 +1,21 @@
<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 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 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 { fade } from 'svelte/transition';
import { tweened } from 'svelte/motion';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
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 { 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) =>
Math.max(Math.min(Number.parseInt(s ?? '') || 0, max ?? 0), 0);
@ -87,11 +88,10 @@
onMount(async () => {
if (!$memoryStore) {
const localTime = new Date();
const { data } = await api.assetApi.getMemoryLane({
$memoryStore = await getMemoryLane({
month: localTime.getMonth() + 1,
day: localTime.getDate(),
});
$memoryStore = data;
}
});
@ -168,7 +168,7 @@
<button class="relative h-full w-full rounded-2xl" disabled={!previousMemory} on:click={toPreviousMemory}>
<img
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=""
draggable="false"
/>
@ -191,7 +191,7 @@
<img
transition:fade
class="h-full w-full rounded-2xl object-contain transition-all"
src={api.getAssetThumbnailUrl(currentAsset.id, 'JPEG')}
src={getAssetThumbnailUrl(currentAsset.id, 'JPEG')}
alt=""
draggable="false"
/>
@ -231,7 +231,7 @@
<button class="relative h-full w-full rounded-2xl" on:click={toNextMemory} disabled={!nextMemory}>
<img
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=""
draggable="false"
/>

View file

@ -4,12 +4,12 @@
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import type { OnArchive } from '$lib/utils/actions';
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 { 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;
@ -32,7 +32,7 @@
const ids = assets.map(({ id }) => id);
if (ids.length > 0) {
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
await updateAssets({ assetBulkUpdateDto: { ids, isArchived } });
}
for (const asset of assets) {

View file

@ -4,8 +4,10 @@
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { getAssetJobMessage, getAssetJobName } from '$lib/utils';
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';
export let jobs: AssetJobName[] = [
@ -21,8 +23,8 @@
const handleRunJob = async (name: AssetJobName) => {
try {
const ids = [...getOwnedAssets()].map(({ id }) => id);
await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: ids, name } });
notificationController.show({ message: api.getAssetJobMessage(name), type: NotificationType.Info });
await runAssetJobs({ assetJobsDto: { assetIds: ids, name } });
notificationController.show({ message: getAssetJobMessage(name), type: NotificationType.Info });
clearSelect();
} catch (error) {
handleError(error, 'Unable to submit job');
@ -32,6 +34,6 @@
{#each jobs as job}
{#if isAllVideos || job !== AssetJobName.TranscodeVideo}
<MenuOption text={api.getAssetJobName(job)} on:click={() => handleRunJob(job)} />
<MenuOption text={getAssetJobName(job)} on:click={() => handleRunJob(job)} />
{/if}
{/each}

View file

@ -1,12 +1,12 @@
<script lang="ts">
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 { api } from '@api';
import { updateAssets } from '@immich/sdk';
import { DateTime } from 'luxon';
import MenuOption from '../../shared-components/context-menu/menu-option.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;
const { clearSelect, getOwnedAssets } = getAssetControlContext();
@ -17,9 +17,7 @@
const ids = getSelectedAssets(getOwnedAssets(), $user);
try {
await api.assetApi.updateAssets({
assetBulkUpdateDto: { ids, dateTimeOriginal },
});
await updateAssets({ assetBulkUpdateDto: { ids, dateTimeOriginal } });
} catch (error) {
handleError(error, 'Unable to change date');
}

View file

@ -1,11 +1,11 @@
<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 { handleError } from '../../../utils/handle-error';
import { user } from '$lib/stores/user.store';
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;
const { clearSelect, getOwnedAssets } = getAssetControlContext();
@ -17,13 +17,7 @@
const ids = getSelectedAssets(getOwnedAssets(), $user);
try {
await api.assetApi.updateAssets({
assetBulkUpdateDto: {
ids,
latitude: point.lat,
longitude: point.lng,
},
});
await updateAssets({ assetBulkUpdateDto: { ids, latitude: point.lat, longitude: point.lng } });
} catch (error) {
handleError(error, 'Unable to update location');
}

View file

@ -5,11 +5,11 @@
NotificationType,
notificationController,
} 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 { 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;
@ -33,7 +33,7 @@
const ids = assets.map(({ id }) => id);
if (ids.length > 0) {
await api.assetApi.updateAssets({ assetBulkUpdateDto: { ids, isFavorite } });
await updateAssets({ assetBulkUpdateDto: { ids, isFavorite } });
}
for (const asset of assets) {

View file

@ -1,11 +1,13 @@
<script lang="ts">
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { type SharedLinkResponseDto, api } from '@api';
import ConfirmDialogue from '../../shared-components/confirm-dialogue.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { NotificationType, notificationController } from '../../shared-components/notification/notification';
import { handleError } from '../../../utils/handle-error';
import { getKey } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { type SharedLinkResponseDto } from '@api';
import { removeSharedLinkAssets } from '@immich/sdk';
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;
@ -15,12 +17,12 @@
const handleRemove = async () => {
try {
const { data: results } = await api.sharedLinkApi.removeSharedLinkAssets({
const results = await removeSharedLinkAssets({
id: sharedLink.id,
assetIdsDto: {
assetIds: [...getAssets()].map((asset) => asset.id),
},
key: api.getKey(),
key: getKey(),
});
for (const result of results) {

View file

@ -1,13 +1,13 @@
<script lang="ts">
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 {
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { handleError } from '$lib/utils/handle-error';
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;
@ -26,7 +26,7 @@
const ids = children.map(({ id }) => id);
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;

View file

@ -1,22 +1,19 @@
<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 { fade } from 'svelte/transition';
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte';
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;
onMount(async () => {
const localTime = new Date();
const { data } = await api.assetApi.getMemoryLane({
month: localTime.getMonth() + 1,
day: localTime.getDate(),
});
$memoryStore = data;
$memoryStore = await getMemoryLane({ month: localTime.getMonth() + 1, day: localTime.getDate() });
});
let memoryLaneElement: HTMLElement;
@ -76,7 +73,7 @@
>
<img
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}
draggable="false"
/>

View file

@ -1,9 +1,14 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
import { downloadArchive } from '$lib/utils/asset-utils';
import { api, type AssetResponseDto, type SharedLinkResponseDto } from '@api';
import { AppRoute } from '$lib/constants';
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 DownloadAction from '../photos-page/actions/download-action.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 GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
import ImmichLogo from '../shared-components/immich-logo.svelte';
import { notificationController, NotificationType } 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';
import { NotificationType, notificationController } from '../shared-components/notification/notification';
export let sharedLink: SharedLinkResponseDto;
export let isOwned: boolean;
@ -41,12 +43,12 @@
results = await (!files || files.length === 0 || !Array.isArray(files)
? openFileUploadDialog()
: fileUploadHandler(files));
const { data } = await api.sharedLinkApi.addSharedLinkAssets({
const data = await addSharedLinkAssets({
id: sharedLink.id,
assetIdsDto: {
assetIds: results.filter((id) => !!id) as string[],
},
key: api.getKey(),
key: getKey(),
});
const added = data.filter((item) => item.success).length;

View file

@ -4,16 +4,17 @@
} from '$lib/components/admin-page/settings/setting-input-field.svelte';
import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.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 { 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 type { ImmichDropDownOption } from '../dropdown-button.svelte';
import DropdownButton from '../dropdown-button.svelte';
import { notificationController, NotificationType } from '../notification/notification';
import { mdiLink } from '@mdi/js';
import { serverConfig } from '$lib/stores/server-config.store';
import { NotificationType, notificationController } from '../notification/notification';
export let albumId: string | undefined = undefined;
export let assetIds: string[] = [];
@ -70,7 +71,7 @@
const expirationDate = expirationTime ? new Date(currentTime + expirationTime).toISOString() : undefined;
try {
const { data } = await api.sharedLinkApi.createSharedLink({
const data = await createSharedLink({
sharedLinkCreateDto: {
type: shareType,
albumId,
@ -135,7 +136,7 @@
? new Date(currentTime + expirationTime).toISOString()
: null;
await api.sharedLinkApi.updateSharedLink({
await updateSharedLink({
id: editingLink.id,
sharedLinkEditDto: {
description,

View file

@ -2,8 +2,8 @@
import Icon from '$lib/components/elements/icon.svelte';
import { Theme } from '$lib/constants';
import { colorTheme, mapSettings } from '$lib/stores/preferences.store';
import { api, type MapMarkerResponseDto } from '@api';
import { getMapStyle } from '@immich/sdk';
import { getAssetThumbnailUrl } from '$lib/utils';
import { getMapStyle, type MapMarkerResponseDto } from '@immich/sdk';
import { mdiCog, mdiMapMarker } from '@mdi/js';
import type { Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
import type { GeoJSONSource, LngLatLike, StyleSpecification } from 'maplibre-gl';
@ -174,7 +174,7 @@
/>
{:else}
<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"
alt={`Image with id ${feature.properties?.id}`}
/>

View file

@ -4,12 +4,11 @@
import { AppRoute } from '$lib/constants';
import { user } from '$lib/stores/user.store';
import { handleError } from '$lib/utils/handle-error';
import { UserAvatarColor } from '@api';
import { deleteProfileImage, updateUser } from '@immich/sdk';
import { deleteProfileImage, updateUser, type UserAvatarColor } from '@immich/sdk';
import { mdiCog, mdiLogout, mdiPencil } from '@mdi/js';
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
import { notificationController, NotificationType } from '../notification/notification';
import { NotificationType, notificationController } from '../notification/notification';
import UserAvatar from '../user-avatar.svelte';
import AvatarSelector from './avatar-selector.svelte';

View file

@ -1,13 +1,15 @@
<script lang="ts">
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.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 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 {
All = 'all',
@ -108,8 +110,8 @@
const getPeople = async () => {
try {
const { data } = await api.personApi.getAllPeople({ withHidden: false });
suggestions.people = data.people;
const { people } = await getAllPeople({ withHidden: false });
suggestions.people = people;
} catch (error) {
handleError(error, 'Failed to get people');
}
@ -143,8 +145,8 @@
}
try {
const { data } = await api.searchApi.getSearchSuggestions({
type: type,
const data = await getSearchSuggestions({
$type: type,
country: params.country,
state: params.state,
make: params.cameraMake,
@ -251,7 +253,7 @@
<ImageThumbnail
circle
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="100px"
/>

View file

@ -2,8 +2,8 @@
import { page } from '$app/stores';
import { locale, sidebarSettings } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/server-config.store';
import { api, type AssetApiGetAssetStatisticsRequest } from '@api';
import { getAlbumCount } from '@immich/sdk';
import { type AssetApiGetAssetStatisticsRequest } from '@api';
import { getAlbumCount, getAssetStatistics } from '@immich/sdk';
import {
mdiAccount,
mdiAccountMultiple,
@ -24,10 +24,7 @@
import SideBarButton from './side-bar-button.svelte';
import SideBarSection from './side-bar-section.svelte';
const getStats = async (dto: AssetApiGetAssetStatisticsRequest) => {
const { data: stats } = await api.assetApi.getAssetStatistics(dto);
return stats;
};
const getStats = (dto: AssetApiGetAssetStatisticsRequest) => getAssetStatistics(dto);
const handleAlbumCount = async () => {
try {

View file

@ -3,8 +3,9 @@
</script>
<script lang="ts">
import { getProfileImageUrl } from '$lib/utils';
import { type UserAvatarColor } from '@immich/sdk';
import { onMount, tick } from 'svelte';
import { UserAvatarColor, api } from '@api';
interface User {
id: string;
@ -74,7 +75,7 @@
{#if showProfileImage && user.profileImagePath}
<img
bind:this={img}
src={api.getProfileImageUrl(user.id)}
src={getProfileImageUrl(user.id)}
alt="Profile image of {title}"
class="h-full w-full object-cover"
class:hidden={showFallback}

View file

@ -1,14 +1,16 @@
<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 { mdiCircleEditOutline, mdiContentCopy, mdiDelete, mdiOpenInNew } from '@mdi/js';
import noThumbnailUrl from '$lib/assets/no-thumbnail.png';
import Icon from '$lib/components/elements/icon.svelte';
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;
@ -28,9 +30,7 @@
assetId = link.assets[0].id;
}
const { data } = await api.assetApi.getAssetInfo({ id: assetId });
return data;
return getAssetInfo({ id: assetId });
};
const getCountDownExpirationDate = () => {
@ -72,7 +72,7 @@
{:then asset}
<img
id={asset.id}
src={api.getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
src={getAssetThumbnailUrl(asset.id, ThumbnailFormat.Webp)}
alt={asset.id}
class="h-[100px] w-[100px] rounded-lg object-cover"
loading="lazy"

View file

@ -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 { api, type AssetResponseDto } from '@api';
function createAssetViewingStore() {
const viewingAssetStoreState = writable<AssetResponseDto>();
const viewState = writable<boolean>(false);
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);
viewState.set(true);
};

View file

@ -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 { DateTime } from 'luxon';
import { type Unsubscriber, writable } from 'svelte/store';
import { writable, type Unsubscriber } from 'svelte/store';
import { handleError } from '../utils/handle-error';
import { websocketStore } from './websocket';
@ -155,9 +157,9 @@ export class AssetStore {
this.assetToBucket = {};
this.albumAssets = new Set();
const { data: buckets } = await api.assetApi.getTimeBuckets({
const buckets = await getTimeBuckets({
...this.options,
key: api.getKey(),
key: getKey(),
});
this.initialized = true;
@ -209,22 +211,22 @@ export class AssetStore {
bucket.cancelToken = new AbortController();
const { data: assets } = await api.assetApi.getTimeBucket(
const assets = await getTimeBucket(
{
...this.options,
timeBucket: bucketDate,
key: api.getKey(),
key: getKey(),
},
{ signal: bucket.cancelToken.signal },
);
if (this.albumId) {
const { data: albumAssets } = await api.assetApi.getTimeBucket(
const albumAssets = await getTimeBucket(
{
albumId: this.albumId,
timeBucket: bucketDate,
size: this.options.size,
key: api.getKey(),
key: getKey(),
},
{ signal: bucket.cancelToken.signal },
);

View file

@ -1,5 +1,7 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { defaults } from '@immich/sdk';
import { AssetJobName, JobName, ThumbnailFormat, common } from '@immich/sdk/axios';
import { get } from 'svelte/store';
interface UpdateParamAction {
@ -31,3 +33,91 @@ export const updateParamList = async ({ param, value, add }: UpdateParamAction)
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];
};

View file

@ -1,5 +1,5 @@
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';
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[]) => {
try {
await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } });
await deleteBulk({ assetBulkDeleteDto: { ids, force } });
for (const id of ids) {
onAssetDelete(id);
}

View file

@ -3,6 +3,7 @@ import { downloadManager } from '$lib/stores/download';
import { api } from '@api';
import {
addAssetsToAlbum as addAssets,
getDownloadInfo,
type AssetResponseDto,
type AssetTypeEnum,
type BulkIdResponseDto,
@ -11,13 +12,14 @@ import {
type UserResponseDto,
} from '@immich/sdk';
import { DateTime } from 'luxon';
import { getKey } from '../utils';
import { handleError } from './handle-error';
export const addAssetsToAlbum = async (albumId: string, assetIds: Array<string>): Promise<BulkIdResponseDto[]> =>
addAssets({
id: albumId,
bulkIdsDto: { ids: assetIds },
key: api.getKey(),
key: getKey(),
}).then((results) => {
const count = results.filter(({ success }) => success).length;
notificationController.show({
@ -46,8 +48,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
let downloadInfo: DownloadResponseDto | null = null;
try {
const { data } = await api.downloadApi.getDownloadInfo({ downloadInfoDto: options, key: api.getKey() });
downloadInfo = data;
downloadInfo = await getDownloadInfo({ downloadInfoDto: options, key: getKey() });
} catch (error) {
handleError(error, 'Unable to download files');
return;
@ -71,7 +72,7 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
try {
const { data } = await api.downloadApi.downloadArchive(
{ assetIdsDto: { assetIds: archive.assetIds }, key: api.getKey() },
{ assetIdsDto: { assetIds: archive.assetIds }, key: getKey() },
{
responseType: 'blob',
signal: abort.signal,
@ -121,7 +122,7 @@ export const downloadFile = async (asset: AssetResponseDto) => {
downloadManager.add(downloadKey, size, abort);
const { data } = await api.downloadApi.downloadFile(
{ id, key: api.getKey() },
{ id, key: getKey() },
{
responseType: 'blob',
onDownloadProgress: ({ event }) => {

View file

@ -1,5 +1,6 @@
import { UploadState } from '$lib/models/upload-asset';
import { uploadAssetsStore } from '$lib/stores/upload';
import { getKey } from '$lib/utils';
import { addAssetsToAlbum } from '$lib/utils/asset-utils';
import { ExecutorQueue } from '$lib/utils/executor-queue';
import { api, type AssetFileUploadResponseDto } from '@api';
@ -81,7 +82,7 @@ async function fileUploader(asset: File, albumId: string | undefined = undefined
isFavorite: false,
duration: '0:00:00.000000',
assetData: new File([asset], asset.name),
key: api.getKey(),
key: getKey(),
},
{
onUploadProgress: ({ event }) => {

View file

@ -3,7 +3,8 @@
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
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';
export let data: PageData;
@ -58,7 +59,7 @@
<ImageThumbnail
circle
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="100%"
/>

View file

@ -1,11 +1,11 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import { getAllPeople, getExploreData } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async () => {
await authenticate();
const { data: items } = await api.searchApi.getExploreData();
const { data: response } = await api.personApi.getAllPeople({ withHidden: false });
const [items, response] = await Promise.all([getExploreData(), getAllPeople({ withHidden: false })]);
return {
items,
response,

View file

@ -3,18 +3,19 @@
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.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 { AppRoute } from '$lib/constants';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import type { MapSettings } from '$lib/stores/preferences.store';
import { mapSettings } from '$lib/stores/preferences.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 { DateTime, Duration } from 'luxon';
import { onDestroy, onMount } from 'svelte';
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;
@ -49,7 +50,7 @@
const { includeArchived, onlyFavorites } = $mapSettings;
const { fileCreatedAfter, fileCreatedBefore } = getFileCreatedDates();
const { data } = await api.assetApi.getMapMarkers(
return await getMapMarkers(
{
isArchived: includeArchived && undefined,
isFavorite: onlyFavorites || undefined,
@ -60,7 +61,6 @@
signal: abortController.signal,
},
);
return data;
}
function getFileCreatedDates() {

View file

@ -1,36 +1,38 @@
<script lang="ts">
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
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 { browser } from '$app/environment';
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 {
ActionQueryParameterValue,
AppRoute,
QueryParameter,
maximumLengthSearchPeople,
QueryParameter,
timeBeforeShowLoadingSpinner,
} from '$lib/constants';
import { getPeopleThumbnailUrl } from '$lib/utils';
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 SearchBar from '$lib/components/faces-page/search-bar.svelte';
import { page } from '$app/stores';
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
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;
@ -75,7 +77,7 @@
const getSearchedPeople = $page.url.searchParams.get(QueryParameter.SEARCHED_PEOPLE);
if (getSearchedPeople) {
searchName = getSearchedPeople;
searchPeople(true);
handleSearchPeople(true);
}
});
@ -100,7 +102,7 @@
const handleSearch = (force: boolean) => {
$page.url.searchParams.set(QueryParameter.SEARCHED_PEOPLE, searchName);
goto($page.url);
searchPeople(force);
handleSearchPeople(force);
};
const handleCloseClick = () => {
@ -150,7 +152,7 @@
}
if (changed.length > 0) {
const { data: results } = await api.personApi.updatePeople({
const results = await updatePeople({
peopleUpdateDto: { people: changed },
});
const count = results.filter(({ success }) => success).length;
@ -187,12 +189,12 @@
return;
}
try {
await api.personApi.mergePerson({
await mergePerson({
id: personToBeMergedIn.id,
mergePersonDto: { ids: [personToMerge.id] },
});
const { data: mergedPerson } = await api.personApi.getPerson({ id: personToBeMergedIn.id });
const mergedPerson = await getPerson({ id: personToBeMergedIn.id });
countVisiblePeople--;
people = people.filter((person: PersonResponseDto) => person.id !== personToMerge.id);
@ -213,7 +215,7 @@
*
*/
try {
await api.personApi.updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: personName } });
await updatePerson({ id: personToBeMergedIn.id, personUpdateDto: { name: personName } });
for (const person of people) {
if (person.id === personToBeMergedIn.id) {
@ -248,7 +250,7 @@
const handleHidePerson = async (detail: PersonResponseDto) => {
try {
const { data: updatedPerson } = await api.personApi.updatePerson({
const updatedPerson = await updatePerson({
id: detail.id,
personUpdateDto: { isHidden: true },
});
@ -281,7 +283,7 @@
);
};
const searchPeople = async (force: boolean) => {
const handleSearchPeople = async (force: boolean) => {
if (searchName === '') {
if ($page.url.searchParams.has(QueryParameter.SEARCHED_PEOPLE)) {
$page.url.searchParams.delete(QueryParameter.SEARCHED_PEOPLE);
@ -295,9 +297,7 @@
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
try {
const { data } = await api.searchApi.searchPerson({ name: searchName, withHidden: false });
searchedPeople = data;
searchedPeople = await searchPerson({ name: searchName, withHidden: false });
searchWord = searchName;
} catch (error) {
handleError(error, "Can't search people");
@ -318,7 +318,7 @@
changeName();
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
@ -353,7 +353,7 @@
}
try {
const { data: updatedPerson } = await api.personApi.updatePerson({
const updatedPerson = await updatePerson({
id: edittingPerson.id,
personUpdateDto: { birthDate: value.length > 0 ? value : null },
});
@ -381,7 +381,7 @@
return;
}
try {
const { data: updatedPerson } = await api.personApi.updatePerson({
const updatedPerson = await updatePerson({
id: edittingPerson.id,
personUpdateDto: { name: personName },
});
@ -529,7 +529,7 @@
preload={searchName !== '' || index < 20}
bind:hidden={person.isHidden}
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="100%"
bind:eyeColor={eyeColorMap[person.id]}

View file

@ -1,11 +1,11 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import { getAllPeople } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async () => {
await authenticate();
const { data: people } = await api.personApi.getAllPeople({ withHidden: true });
const people = await getAllPeople({ withHidden: true });
return {
people,
meta: {

View file

@ -4,9 +4,9 @@
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
import EditNameInput from '$lib/components/faces-page/edit-name-input.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 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 ArchiveAction from '$lib/components/photos-page/actions/archive-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 MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import {
NotificationType,
notificationController,
} from '$lib/components/shared-components/notification/notification';
import { AppRoute, QueryParameter, maximumLengthSearchPeople, timeBeforeShowLoadingSpinner } from '$lib/constants';
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { AssetStore } from '$lib/stores/assets.store';
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 { assetViewingStore } from '$lib/stores/asset-viewing.store';
import LoadingSpinner from '$lib/components/shared-components/loading-spinner.svelte';
import { mdiPlus, mdiDotsVertical, mdiArrowLeft } from '@mdi/js';
import { handleError } from '$lib/utils/handle-error';
import { isExternalUrl } from '$lib/utils/navigation';
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;
@ -74,7 +82,7 @@
let refreshAssetGrid = false;
let personName = '';
$: thumbnailData = api.getPeopleThumbnailUrl(data.person.id);
$: thumbnailData = getPeopleThumbnailUrl(data.person.id);
let name: string = data.person.name;
let suggestedPeople: PersonResponseDto[] = [];
@ -97,8 +105,7 @@
}
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
try {
const { data } = await api.searchApi.searchPerson({ name });
people = data;
people = await searchPerson({ name });
searchWord = name;
} catch (error) {
people = [];
@ -113,7 +120,7 @@
$: isAllArchive = [...$selectedAssets].every((asset) => asset.isArchived);
$: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite);
$: $onPersonThumbnail === data.person.id &&
(thumbnailData = api.getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`);
(thumbnailData = getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`);
$: {
if (people) {
@ -215,7 +222,7 @@
const toggleHidePerson = async () => {
try {
await api.personApi.updatePerson({
await updatePerson({
id: data.person.id,
personUpdateDto: { isHidden: !data.person.isHidden },
});
@ -232,8 +239,8 @@
};
const handleMerge = async (person: PersonResponseDto) => {
const { data: statistics } = await api.personApi.getPersonStatistics({ id: person.id });
numberOfAssets = statistics.assets;
const { assets } = await getPersonStatistics({ id: person.id });
numberOfAssets = assets;
handleGoBack();
data.person = person;
@ -246,7 +253,7 @@
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 });
assetInteractionStore.clearMultiselect();
@ -256,10 +263,8 @@
const updateAssetCount = async () => {
try {
const { data: statistics } = await api.personApi.getPersonStatistics({
id: data.person.id,
});
numberOfAssets = statistics.assets;
const { assets } = await getPersonStatistics({ id: data.person.id });
numberOfAssets = assets;
} catch (error) {
handleError(error, "Can't update the asset count");
}
@ -270,7 +275,7 @@
viewMode = ViewMode.VIEW_ASSETS;
isEditingName = false;
try {
await api.personApi.mergePerson({
await mergePerson({
id: personToBeMergedIn.id,
mergePersonDto: { ids: [personToMerge.id] },
});
@ -305,10 +310,7 @@
try {
isEditingName = false;
await api.personApi.updatePerson({
id: data.person.id,
personUpdateDto: { name: personName },
});
await updatePerson({ id: data.person.id, personUpdateDto: { name: personName } });
notificationController.show({
message: 'Change name successfully',
@ -340,16 +342,16 @@
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.name.toLowerCase() === personName.toLowerCase() && person.id !== data.person.id && person.name,
);
if (existingPerson) {
personMerge2 = existingPerson;
personMerge1 = data.person;
potentialMergePeople = result.data
potentialMergePeople = result
.filter(
(person: PersonResponseDto) =>
personMerge2.name.toLowerCase() === person.name.toLowerCase() &&
@ -369,7 +371,7 @@
viewMode = ViewMode.VIEW_ASSETS;
data.person.birthDate = birthDate;
const { data: updatedPerson } = await api.personApi.updatePerson({
const updatedPerson = await updatePerson({
id: data.person.id,
personUpdateDto: { birthDate: birthDate.length > 0 ? birthDate : null },
});
@ -557,7 +559,7 @@
<ImageThumbnail
circle
shadow
url={api.getPeopleThumbnailUrl(person.id)}
url={getPeopleThumbnailUrl(person.id)}
altText={person.name}
widthStyle="2rem"
heightStyle="2rem"

View file

@ -1,12 +1,14 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import { getPerson, getPersonStatistics } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
await authenticate();
const { data: person } = await api.personApi.getPerson({ id: params.personId });
const { data: statistics } = await api.personApi.getPersonStatistics({ id: params.personId });
const [person, statistics] = await Promise.all([
getPerson({ id: params.personId }),
getPersonStatistics({ id: params.personId }),
]);
return {
person,

View file

@ -1,10 +1,10 @@
import { authenticate } from '$lib/utils/auth';
import { api } from '@api';
import { getExploreData } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async () => {
await authenticate();
const { data: items } = await api.searchApi.getExploreData();
const items = await getExploreData();
return {
items,

View file

@ -26,9 +26,8 @@
import { preventRaceConditionSearchBar } from '$lib/stores/search.store';
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
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 { api } from '@api';
export let data: PageData;
@ -120,20 +119,20 @@
await authenticate();
let results: SearchResponseDto | null = null;
$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) {
searchResultAssets.push(...res.data.assets.items);
searchResultAssets.push(...res.assets.items);
} else {
searchResultAssets = res.data.assets.items;
searchResultAssets = res.assets.items;
}
const assets = {
...res.data.assets,
...res.assets,
items: searchResultAssets,
};
results = {
assets,
albums: res.data.albums,
albums: res.albums,
};
data.results = results;

View file

@ -1,5 +1,5 @@
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 { 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;
let results: SearchResponseDto | null = null;
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;
if (items) {
items.push(...res.data.assets.items);
items.push(...response.assets.items);
} else {
items = res.data.assets.items;
items = response.assets.items;
}
const assets = { ...res.data.assets, items };
const assets = { ...response.assets, items };
results = {
assets,
albums: res.data.albums,
albums: response.albums,
};
}

View file

@ -1,14 +1,15 @@
<script lang="ts">
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 ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
import ImmichLogo from '$lib/components/shared-components/immich-logo.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 { handleError } from '$lib/utils/handle-error';
import { SharedLinkType } from '@api';
import { getMySharedLink } from '@immich/sdk';
import type { PageData } from './$types';
export let data: PageData;
let { sharedLink, passwordRequired, sharedLinkKey: key, meta } = data;
@ -18,9 +19,8 @@
const handlePasswordSubmit = async () => {
try {
const result = await api.sharedLinkApi.getMySharedLink({ password, key });
sharedLink = await getMySharedLink({ password, key });
passwordRequired = false;
sharedLink = result.data;
isOwned = $user ? $user.id === sharedLink.userId : false;
title = (sharedLink.album ? sharedLink.album.albumName : 'Public Share') + ' - Immich';
description = sharedLink.description || `${sharedLink.assets.length} shared photos & videos.`;

View file

@ -1,16 +1,17 @@
import { getAssetThumbnailUrl } from '$lib/utils';
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 { PageLoad } from './$types';
import { error as throwError } from '@sveltejs/kit';
export const load = (async ({ params }) => {
const { key } = params;
await authenticate({ public: true });
try {
const { data: sharedLink } = await api.sharedLinkApi.getMySharedLink({ key });
const sharedLink = await getMySharedLink({ key });
const assetCount = sharedLink.assets.length;
const assetId = sharedLink.album?.albumThumbnailAssetId || sharedLink.assets[0]?.id;
@ -19,9 +20,7 @@ export const load = (async ({ params }) => {
meta: {
title: sharedLink.album ? sharedLink.album.albumName : 'Public Share',
description: sharedLink.description || `${assetCount} shared photos & videos.`,
imageUrl: assetId
? api.getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
: '/feature-panel.png',
imageUrl: assetId ? getAssetThumbnailUrl(assetId, ThumbnailFormat.Webp) : '/feature-panel.png',
},
};
} catch (error) {

View file

@ -1,9 +1,9 @@
import { api } from '@api';
import { getAssetInfo } from '@immich/sdk';
import type { PageLoad } from './$types';
export const load = (async ({ params }) => {
const { key, assetId } = params;
const { data: asset } = await api.assetApi.getAssetInfo({ id: assetId, key });
const asset = await getAssetInfo({ id: assetId, key });
return {
asset,

View file

@ -1,19 +1,20 @@
<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 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 {
notificationController,
NotificationType,
} from '$lib/components/shared-components/notification/notification';
import { onMount } from '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 SharedLinkCard from '$lib/components/sharedlinks-page/shared-link-card.svelte';
import { AppRoute } from '$lib/constants';
import { mdiArrowLeft } from '@mdi/js';
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 editSharedLink: SharedLinkResponseDto | null = null;
@ -21,8 +22,7 @@
let deleteLinkId: string | null = null;
const refresh = async () => {
const { data } = await api.sharedLinkApi.getAllSharedLinks();
sharedLinks = data;
sharedLinks = await getAllSharedLinks();
};
onMount(async () => {
@ -35,7 +35,7 @@
}
try {
await api.sharedLinkApi.removeSharedLink({ id: deleteLinkId });
await removeSharedLink({ id: deleteLinkId });
notificationController.show({ message: 'Deleted shared link', type: NotificationType.Info });
deleteLinkId = null;
await refresh();

View file

@ -1,22 +1,22 @@
<script lang="ts">
import '../app.css';
import { page } from '$app/stores';
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 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 { onDestroy, onMount } from 'svelte';
import { loadConfig } from '$lib/stores/server-config.store';
import { handleError } from '$lib/utils/handle-error';
import { api } from '@api';
import { closeWebsocketConnection, openWebsocketConnection } from '$lib/stores/websocket';
import { user } from '$lib/stores/user.store';
import { type ThemeSetting, colorTheme, handleToggleTheme } from '$lib/stores/preferences.store';
import FullscreenContainer from '$lib/components/shared-components/fullscreen-container.svelte';
import NavigationLoadingBar from '$lib/components/shared-components/navigation-loading-bar.svelte';
import NotificationList from '$lib/components/shared-components/notification/notification-list.svelte';
import UploadPanel from '$lib/components/shared-components/upload-panel.svelte';
import VersionAnnouncementBox from '$lib/components/shared-components/version-announcement-box.svelte';
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 albumId: string | undefined;
@ -55,7 +55,7 @@
});
if (isSharedLinkRoute($page.route?.id)) {
api.setKey($page.params.key);
setKey($page.params.key);
}
beforeNavigate(({ from, to }) => {

View file

@ -4,8 +4,7 @@
import Icon from '$lib/components/elements/icon.svelte';
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
import { AppRoute } from '$lib/constants';
import { type AllJobStatusResponseDto } from '@api';
import { getAllJobsStatus } from '@immich/sdk';
import { getAllJobsStatus, type AllJobStatusResponseDto } from '@immich/sdk';
import { mdiCog } from '@mdi/js';
import { onDestroy, onMount } from 'svelte';
import type { PageData } from './$types';

View file

@ -1,5 +1,5 @@
import type { AlbumResponseDto } from '@api';
import { faker } from '@faker-js/faker';
import type { AlbumResponseDto } from '@immich/sdk';
import { Sync } from 'factory.ts';
import { userFactory } from './user-factory';