mirror of
https://github.com/immich-app/immich.git
synced 2026-03-21 01:28:35 -07:00
Compare commits
7 Commits
fix/nullab
...
feat/deepl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4865938f55 | ||
|
|
3b20bf4a6a | ||
|
|
b092c8b601 | ||
|
|
48e6e17829 | ||
|
|
0519833d75 | ||
|
|
34caed3b2b | ||
|
|
096927a835 |
@@ -20,7 +20,7 @@ import {
|
||||
MaintenanceStatusResponseDto,
|
||||
SetMaintenanceModeDto,
|
||||
} from 'src/dtos/maintenance.dto';
|
||||
import { ServerConfigDto, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||
import { ServerConfigDto, ServerPingResponse, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||
import { ImmichCookie } from 'src/enum';
|
||||
import { MaintenanceRoute } from 'src/maintenance/maintenance-auth.guard';
|
||||
import { MaintenanceWorkerService } from 'src/maintenance/maintenance-worker.service';
|
||||
@@ -52,6 +52,11 @@ export class MaintenanceWorkerController {
|
||||
return this.service.getSystemConfig();
|
||||
}
|
||||
|
||||
@Get('server/ping')
|
||||
pingServer(): ServerPingResponse {
|
||||
return this.service.ping();
|
||||
}
|
||||
|
||||
@Get('server/version')
|
||||
getServerVersion(): ServerVersionResponseDto {
|
||||
return this.service.getVersion();
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
MaintenanceStatusResponseDto,
|
||||
SetMaintenanceModeDto,
|
||||
} from 'src/dtos/maintenance.dto';
|
||||
import { ServerConfigDto, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||
import { ServerConfigDto, ServerPingResponse, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||
import { DatabaseLock, ImmichCookie, MaintenanceAction, SystemMetadataKey } from 'src/enum';
|
||||
import { MaintenanceHealthRepository } from 'src/maintenance/maintenance-health.repository';
|
||||
import { MaintenanceWebsocketRepository } from 'src/maintenance/maintenance-websocket.repository';
|
||||
@@ -121,6 +121,10 @@ export class MaintenanceWorkerService {
|
||||
return ServerVersionResponseDto.fromSemVer(serverVersion);
|
||||
}
|
||||
|
||||
ping(): ServerPingResponse {
|
||||
return { res: 'pong' };
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link _ApiService.ssr}
|
||||
*/
|
||||
|
||||
@@ -330,7 +330,7 @@ describe(MetadataService.name, () => {
|
||||
duration: null,
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
localDateTime: asset.localDateTime,
|
||||
localDateTime: asset.fileCreatedAt,
|
||||
width: null,
|
||||
height: null,
|
||||
});
|
||||
@@ -360,7 +360,7 @@ describe(MetadataService.name, () => {
|
||||
duration: null,
|
||||
fileCreatedAt: asset.fileCreatedAt,
|
||||
fileModifiedAt: asset.fileModifiedAt,
|
||||
localDateTime: asset.localDateTime,
|
||||
localDateTime: asset.fileCreatedAt,
|
||||
width: null,
|
||||
height: null,
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
UserLike,
|
||||
} from 'test/factories/types';
|
||||
import { UserFactory } from 'test/factories/user.factory';
|
||||
import { newDate, newSha1, newUuid, newUuidV7 } from 'test/small.factory';
|
||||
import { newSha1, newUuid, newUuidV7 } from 'test/small.factory';
|
||||
|
||||
export class AssetFactory {
|
||||
#owner!: UserFactory;
|
||||
@@ -43,10 +43,12 @@ export class AssetFactory {
|
||||
|
||||
const originalFileName = dto.originalFileName ?? (dto.type === AssetType.Video ? `MOV_${id}.mp4` : `IMG_${id}.jpg`);
|
||||
|
||||
let now = Date.now();
|
||||
|
||||
return new AssetFactory({
|
||||
id,
|
||||
createdAt: newDate(),
|
||||
updatedAt: newDate(),
|
||||
createdAt: new Date(now++),
|
||||
updatedAt: new Date(now++),
|
||||
deletedAt: null,
|
||||
updateId: newUuidV7(),
|
||||
status: AssetStatus.Active,
|
||||
@@ -55,14 +57,14 @@ export class AssetFactory {
|
||||
deviceId: '',
|
||||
duplicateId: null,
|
||||
duration: null,
|
||||
fileCreatedAt: newDate(),
|
||||
fileModifiedAt: newDate(),
|
||||
fileCreatedAt: new Date(now++),
|
||||
fileModifiedAt: new Date(now++),
|
||||
isExternal: false,
|
||||
isFavorite: false,
|
||||
isOffline: false,
|
||||
libraryId: null,
|
||||
livePhotoVideoId: null,
|
||||
localDateTime: newDate(),
|
||||
localDateTime: new Date(now),
|
||||
originalFileName,
|
||||
originalPath: `/data/library/${originalFileName}`,
|
||||
ownerId: newUuid(),
|
||||
|
||||
@@ -112,7 +112,7 @@
|
||||
|
||||
switch (dto.command) {
|
||||
case QueueCommand.Empty: {
|
||||
toastManager.success($t('admin.cleared_jobs', { values: { job: item.title } }));
|
||||
toastManager.primary($t('admin.cleared_jobs', { values: { job: item.title } }));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|
||||
try {
|
||||
await unlinkAllOAuthAccountsAdmin();
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.something_went_wrong'));
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
},
|
||||
});
|
||||
|
||||
toastManager.success($t('admin.notification_email_test_email_sent', { values: { email: $user.email } }));
|
||||
toastManager.primary($t('admin.notification_email_test_email_sent', { values: { email: $user.email } }));
|
||||
|
||||
if (!disabled) {
|
||||
await handleSystemConfigSave({ notifications: configToEdit.notifications });
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { updateAlbumInfo } from '@immich/sdk';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { tv } from 'tailwind-variants';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
@@ -36,14 +37,22 @@
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const styles = tv({
|
||||
base: 'w-[99%] mb-2 border-b-2 border-transparent text-2xl md:text-4xl lg:text-6xl text-primary outline-none transition-all focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray placeholder:text-primary/90',
|
||||
variants: {
|
||||
isOwned: {
|
||||
true: 'hover:border-gray-400',
|
||||
false: 'hover:border-transparent',
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<input
|
||||
use:shortcut={{ shortcut: { key: 'Enter' }, onShortcut: (e) => e.currentTarget.blur() }}
|
||||
onblur={handleUpdateName}
|
||||
class="w-[99%] mb-2 border-b-2 border-transparent text-2xl md:text-4xl lg:text-6xl text-primary outline-none transition-all {isOwned
|
||||
? 'hover:border-gray-400'
|
||||
: 'hover:border-transparent'} focus:border-b-2 focus:border-immich-primary focus:outline-none bg-light dark:focus:border-immich-dark-primary dark:focus:bg-immich-dark-gray placeholder:text-primary/90"
|
||||
class={styles({ isOwned })}
|
||||
type="text"
|
||||
bind:value={newAlbumName}
|
||||
disabled={!isOwned}
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
preAction({ type: AssetAction.DELETE, asset: timelineAsset });
|
||||
await deleteAssets({ assetBulkDeleteDto: { ids: [asset.id], force: true } });
|
||||
onAction({ type: AssetAction.DELETE, asset: timelineAsset });
|
||||
toastManager.success($t('permanently_deleted_asset'));
|
||||
toastManager.primary($t('permanently_deleted_asset'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_delete_asset'));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
await restoreAssets({ bulkIdsDto: { ids: [asset.id] } });
|
||||
asset.isTrashed = false;
|
||||
onAction({ type: AssetAction.RESTORE, asset: toTimelineAsset(asset) });
|
||||
toastManager.success($t('restored_asset'));
|
||||
toastManager.primary($t('restored_asset'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_restore_assets'));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
});
|
||||
eventManager.emit('AlbumUpdate', response);
|
||||
toastManager.success($t('album_cover_updated'));
|
||||
toastManager.primary($t('album_cover_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_album_cover'));
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
person,
|
||||
});
|
||||
|
||||
toastManager.success($t('feature_photo_updated'));
|
||||
toastManager.primary($t('feature_photo_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_set_feature_photo'));
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
[ReactionType.Comment]: $t('comment_deleted'),
|
||||
[ReactionType.Like]: $t('like_deleted'),
|
||||
};
|
||||
toastManager.success(deleteMessages[reaction.type]);
|
||||
toastManager.primary(deleteMessages[reaction.type]);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_remove_reaction'));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
try {
|
||||
await updateAsset({ id: asset.id, updateAssetDto: { description } });
|
||||
toastManager.success($t('asset_description_updated'));
|
||||
toastManager.primary($t('asset_description_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('cannot_update_the_description'));
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
if (failCount > 0) {
|
||||
toastManager.warning($t('errors.unable_to_change_visibility', { values: { count: failCount } }));
|
||||
}
|
||||
toastManager.success($t('visibility_changed', { values: { count: successCount } }));
|
||||
toastManager.primary($t('visibility_changed', { values: { count: successCount } }));
|
||||
}
|
||||
|
||||
for (const person of people) {
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
});
|
||||
const mergedPerson = await getPerson({ id: person.id });
|
||||
const count = results.filter(({ success }) => success).length;
|
||||
toastManager.success($t('merged_people_count', { values: { count } }));
|
||||
toastManager.primary($t('merged_people_count', { values: { count } }));
|
||||
onMerge(mergedPerson);
|
||||
} catch (error) {
|
||||
handleError(error, $t('cannot_merge_people'));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||
import { timeBeforeShowLoadingSpinner } from '$lib/constants';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { boundingBoxesArray } from '$lib/stores/people.store';
|
||||
@@ -25,7 +26,6 @@
|
||||
import { fly } from 'svelte/transition';
|
||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||
import AssignFaceSidePanel from './assign-face-side-panel.svelte';
|
||||
import { assetViewerManager } from '$lib/managers/asset-viewer-manager.svelte';
|
||||
|
||||
interface Props {
|
||||
assetId: string;
|
||||
@@ -126,7 +126,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
toastManager.success($t('people_edits_count', { values: { count: numberOfChanges } }));
|
||||
toastManager.primary($t('people_edits_count', { values: { count: numberOfChanges } }));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.cant_apply_changes'));
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
disableButtons = true;
|
||||
const data = await createPerson({ personCreateDto: {} });
|
||||
await reassignFaces({ id: data.id, assetFaceUpdateDto: { data: selectedPeople } });
|
||||
toastManager.success($t('reassigned_assets_to_new_person', { values: { count: assetIds.length } }));
|
||||
toastManager.primary($t('reassigned_assets_to_new_person', { values: { count: assetIds.length } }));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_reassign_assets_new_person'));
|
||||
} finally {
|
||||
@@ -88,7 +88,7 @@
|
||||
disableButtons = true;
|
||||
if (selectedPerson) {
|
||||
await reassignFaces({ id: selectedPerson.id, assetFaceUpdateDto: { data: selectedPeople } });
|
||||
toastManager.success(
|
||||
toastManager.primary(
|
||||
$t('reassigned_assets_to_existing_person', {
|
||||
values: { count: assetIds.length, name: selectedPerson.name || null },
|
||||
}),
|
||||
|
||||
@@ -206,7 +206,7 @@
|
||||
}
|
||||
|
||||
await memoryStore.deleteMemory(current.memory.id);
|
||||
toastManager.success($t('removed_memory'));
|
||||
toastManager.primary($t('removed_memory'));
|
||||
init(page);
|
||||
};
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
|
||||
const newSavedState = !current.memory.isSaved;
|
||||
await memoryStore.updateMemorySaved(current.memory.id, newSavedState);
|
||||
toastManager.success(newSavedState ? $t('added_to_favorites') : $t('removed_from_favorites'));
|
||||
toastManager.primary(newSavedState ? $t('added_to_favorites') : $t('removed_from_favorites'));
|
||||
init(page);
|
||||
};
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
? openFileUploadDialog()
|
||||
: fileUploadHandler({ files }));
|
||||
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_assets_to_shared_link'));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import { mdiArrowRight, mdiClose } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { SvelteSet } from 'svelte/reactivity';
|
||||
import { tv } from 'tailwind-variants';
|
||||
|
||||
interface Props {
|
||||
selectedPeople: SvelteSet<string>;
|
||||
@@ -49,6 +50,16 @@
|
||||
const nameLower = name.toLowerCase();
|
||||
return name ? list.filter((p) => p.name.toLowerCase().includes(nameLower)) : list;
|
||||
};
|
||||
|
||||
const styles = tv({
|
||||
base: 'flex flex-col items-center rounded-3xl border-2 hover:bg-subtle dark:hover:bg-immich-dark-primary/20 p-2 transition-all',
|
||||
variants: {
|
||||
selected: {
|
||||
true: 'dark:border-slate-500 border-slate-400 bg-slate-200 dark:bg-slate-800 dark:text-white',
|
||||
false: 'border-transparent',
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
{#await peoplePromise}
|
||||
@@ -74,11 +85,7 @@
|
||||
{#each peopleList as person (person.id)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex flex-col items-center rounded-3xl border-2 hover:bg-subtle dark:hover:bg-immich-dark-primary/20 p-2 transition-all {selectedPeople.has(
|
||||
person.id,
|
||||
)
|
||||
? 'dark:border-slate-500 border-slate-400 bg-slate-200 dark:bg-slate-800 dark:text-white'
|
||||
: 'border-transparent'}"
|
||||
class={styles({ selected: selectedPeople.has(person.id) })}
|
||||
onclick={() => togglePersonSelection(person.id)}
|
||||
>
|
||||
<ImageThumbnail circle shadow url={getPeopleThumbnailUrl(person)} altText={person.name} widthStyle="100%" />
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
children?: import('svelte').Snippet<[{ itemCount: number }]>;
|
||||
}
|
||||
|
||||
let { class: className = '', itemCount = $bindable(1), children }: Props = $props();
|
||||
let { class: className, itemCount = $bindable(1), children }: Props = $props();
|
||||
|
||||
let container: HTMLElement | undefined = $state();
|
||||
let contentRect: DOMRectReadOnly | undefined = $state();
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
if ($stats.errors > 0) {
|
||||
toastManager.danger($t('upload_errors', { values: { count: $stats.errors } }));
|
||||
} else if ($stats.success > 0) {
|
||||
toastManager.success($t('upload_success'));
|
||||
toastManager.primary($t('upload_success'));
|
||||
}
|
||||
if ($stats.duplicates > 0) {
|
||||
toastManager.warning($t('upload_skipped_duplicates', { values: { count: $stats.duplicates } }));
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
onFavorite?.(ids, isFavorite);
|
||||
|
||||
toastManager.success(
|
||||
toastManager.primary(
|
||||
isFavorite
|
||||
? $t('added_to_favorites_count', { values: { count: ids.length } })
|
||||
: $t('removed_from_favorites_count', { values: { count: ids.length } }),
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
onRemove?.(ids);
|
||||
|
||||
const count = results.filter(({ success }) => success).length;
|
||||
toastManager.success($t('assets_removed_count', { values: { count } }));
|
||||
toastManager.primary($t('assets_removed_count', { values: { count } }));
|
||||
|
||||
clearSelect();
|
||||
} catch (error) {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
const ids = [...getAssets()].map((a) => a.id);
|
||||
await restoreAssets({ bulkIdsDto: { ids } });
|
||||
onRestore?.(ids);
|
||||
toastManager.success($t('assets_restored_count', { values: { count: ids.length } }));
|
||||
toastManager.primary($t('assets_restored_count', { values: { count: ids.length } }));
|
||||
clearSelect();
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_restore_assets'));
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
try {
|
||||
await changePinCode({ pinCodeChangeDto: { pinCode: currentPinCode, newPinCode } });
|
||||
resetForm();
|
||||
toastManager.success($t('pin_code_changed_successfully'));
|
||||
toastManager.primary($t('pin_code_changed_successfully'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('unable_to_change_pin_code'));
|
||||
} finally {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
isLoading = true;
|
||||
try {
|
||||
await setupPinCode({ pinCodeSetupDto: { pinCode: newPinCode } });
|
||||
toastManager.success($t('pin_code_setup_successfully'));
|
||||
toastManager.primary($t('pin_code_setup_successfully'));
|
||||
onCreated?.(newPinCode);
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
try {
|
||||
await deleteSession({ id: device.id });
|
||||
toastManager.success($t('logged_out_device'));
|
||||
toastManager.primary($t('logged_out_device'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_log_out_device'));
|
||||
} finally {
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
try {
|
||||
await deleteAllSessions();
|
||||
toastManager.success($t('logged_out_all_devices'));
|
||||
toastManager.primary($t('logged_out_all_devices'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_log_out_all_devices'));
|
||||
} finally {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
});
|
||||
$preferences = newPreferences;
|
||||
|
||||
toastManager.success($t('saved_settings'));
|
||||
toastManager.primary($t('saved_settings'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_settings'));
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
|
||||
$preferences = { ...data };
|
||||
|
||||
toastManager.success($t('saved_settings'));
|
||||
toastManager.primary($t('saved_settings'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_settings'));
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
$preferences.emailNotifications.albumInvite = data.emailNotifications.albumInvite;
|
||||
$preferences.emailNotifications.albumUpdate = data.emailNotifications.albumUpdate;
|
||||
|
||||
toastManager.success($t('saved_settings'));
|
||||
toastManager.primary($t('saved_settings'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_settings'));
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
try {
|
||||
loading = true;
|
||||
user = await oauth.link(globalThis.location);
|
||||
toastManager.success($t('linked_oauth_account'));
|
||||
toastManager.primary($t('linked_oauth_account'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_link_oauth_account'));
|
||||
} finally {
|
||||
@@ -36,7 +36,7 @@
|
||||
const handleUnlink = async () => {
|
||||
try {
|
||||
user = await oauth.unlink();
|
||||
toastManager.success($t('unlinked_oauth_account'));
|
||||
toastManager.primary($t('unlinked_oauth_account'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_unlink_account'));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
Object.assign(editedUser, data);
|
||||
$user = data;
|
||||
|
||||
toastManager.success($t('saved_profile'));
|
||||
toastManager.primary($t('saved_profile'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_profile'));
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { oauth } from '$lib/utils';
|
||||
import { type ApiKeyResponseDto, type SessionResponseDto } from '@immich/sdk';
|
||||
import { getApiKeys, type ApiKeyResponseDto, type SessionResponseDto } from '@immich/sdk';
|
||||
import {
|
||||
mdiAccountGroupOutline,
|
||||
mdiAccountOutline,
|
||||
@@ -26,6 +26,7 @@
|
||||
mdiServerOutline,
|
||||
mdiTwoFactorAuthentication,
|
||||
} from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import SettingAccordionState from '../shared-components/settings/setting-accordion-state.svelte';
|
||||
import SettingAccordion from '../shared-components/settings/setting-accordion.svelte';
|
||||
@@ -38,11 +39,16 @@
|
||||
import UserProfileSettings from './user-profile-settings.svelte';
|
||||
|
||||
interface Props {
|
||||
keys?: ApiKeyResponseDto[];
|
||||
sessions?: SessionResponseDto[];
|
||||
}
|
||||
|
||||
let { keys = $bindable([]), sessions = $bindable([]) }: Props = $props();
|
||||
let { sessions = $bindable([]) }: Props = $props();
|
||||
|
||||
let keys: ApiKeyResponseDto[] = $state([]);
|
||||
|
||||
onMount(async () => {
|
||||
keys = await getApiKeys();
|
||||
});
|
||||
|
||||
let oauthOpen =
|
||||
oauth.isCallback(globalThis.location) ||
|
||||
|
||||
@@ -62,6 +62,7 @@ export enum SessionStorageKey {
|
||||
|
||||
// TODO split into user settings vs system settings
|
||||
export enum OpenQueryParam {
|
||||
API_KEYS = 'api-keys',
|
||||
OAUTH = 'oauth',
|
||||
JOB = 'job',
|
||||
STORAGE_TEMPLATE = 'storage-template',
|
||||
|
||||
@@ -142,7 +142,7 @@ export class EditManager {
|
||||
|
||||
eventManager.emit('AssetEditsApplied', assetId);
|
||||
|
||||
toastManager.success(t('editor_edits_applied_success'));
|
||||
toastManager.primary(t('editor_edits_applied_success'));
|
||||
this.hasAppliedEdits = true;
|
||||
|
||||
return true;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
await deleteProfileImage();
|
||||
}
|
||||
|
||||
toastManager.success($t('saved_profile'));
|
||||
toastManager.primary($t('saved_profile'));
|
||||
|
||||
$user = await updateMyUser({ userUpdateMeDto: { avatarColor: color } });
|
||||
onClose();
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
id: personToBeMergedInto.id,
|
||||
mergePersonDto: { ids: [personToMerge.id] },
|
||||
});
|
||||
toastManager.success($t('merge_people_successfully'));
|
||||
toastManager.primary($t('merge_people_successfully'));
|
||||
onClose([personToMerge, personToBeMergedInto]);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_name'));
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
}
|
||||
const file = new File([blob], 'profile-picture.png', { type: 'image/png' });
|
||||
const { profileImagePath, profileChangedAt } = await createProfileImage({ createProfileImageDto: { file } });
|
||||
toastManager.success($t('profile_picture_set'));
|
||||
toastManager.primary($t('profile_picture_set'));
|
||||
$user.profileImagePath = profileImagePath;
|
||||
$user.profileChangedAt = profileChangedAt;
|
||||
|
||||
|
||||
@@ -116,6 +116,7 @@ export const Route = {
|
||||
|
||||
// settings
|
||||
userSettings: (params?: { isOpen?: OpenQueryParam }) => '/user-settings' + asQueryString(params),
|
||||
newApiKey: (params?: { permissions?: string }) => '/user-settings/new-api-key' + asQueryString(params),
|
||||
|
||||
// system
|
||||
systemSettings: (params?: { isOpen?: OpenQueryParam }) => '/admin/system-settings' + asQueryString(params),
|
||||
|
||||
@@ -163,7 +163,7 @@ const notifyAddToAlbums = (
|
||||
} else if (results.error) {
|
||||
toastManager.warning($t('assets_cannot_be_added_to_albums', { values: { count: assetIds.length } }));
|
||||
} else {
|
||||
toastManager.success(
|
||||
toastManager.primary(
|
||||
$t('assets_added_to_albums_count', {
|
||||
values: { albumTotal: albumIds.length, assetTotal: assetIds.length },
|
||||
}),
|
||||
@@ -269,7 +269,7 @@ export const handleDeleteAlbum = async (album: AlbumResponseDto, options?: { pro
|
||||
await deleteAlbum({ id: album.id });
|
||||
eventManager.emit('AlbumDelete', album);
|
||||
if (notify) {
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
}
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { eventManager } from '$lib/managers/event-manager.svelte';
|
||||
import ApiKeyCreateModal from '$lib/modals/ApiKeyCreateModal.svelte';
|
||||
import ApiKeyUpdateModal from '$lib/modals/ApiKeyUpdateModal.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import {
|
||||
@@ -19,7 +20,7 @@ export const getApiKeysActions = ($t: MessageFormatter) => {
|
||||
const Create: ActionItem = {
|
||||
title: $t('new_api_key'),
|
||||
icon: mdiPlus,
|
||||
onAction: () => modalManager.show(ApiKeyCreateModal, {}),
|
||||
onAction: () => goto(Route.newApiKey()),
|
||||
};
|
||||
|
||||
return { Create };
|
||||
@@ -80,7 +81,7 @@ export const handleUpdateApiKey = async (apiKey: { id: string }, dto: ApiKeyUpda
|
||||
try {
|
||||
const response = await updateApiKey({ id: apiKey.id, apiKeyUpdateDto: dto });
|
||||
eventManager.emit('ApiKeyUpdate', response);
|
||||
toastManager.success($t('saved_api_key'));
|
||||
toastManager.primary($t('saved_api_key'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_api_key'));
|
||||
@@ -98,7 +99,7 @@ export const handleDeleteApiKey = async (apiKey: ApiKeyResponseDto) => {
|
||||
try {
|
||||
await deleteApiKey({ id: apiKey.id });
|
||||
eventManager.emit('ApiKeyDelete', apiKey);
|
||||
toastManager.success($t('removed_api_key', { values: { name: apiKey.name } }));
|
||||
toastManager.primary($t('removed_api_key', { values: { name: apiKey.name } }));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_remove_api_key'));
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { vitest } from 'vitest';
|
||||
|
||||
vitest.mock('@immich/ui', () => ({
|
||||
toastManager: {
|
||||
success: vitest.fn(),
|
||||
primary: vitest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('AssetService', () => {
|
||||
const asset = assetFactory.build({ originalFileName: 'asset.heic' });
|
||||
await handleDownloadAsset(asset, { edited: false });
|
||||
expect($t).toHaveBeenNthCalledWith(1, 'downloading_asset_filename', { values: { filename: 'asset.heic' } });
|
||||
expect(toastManager.success).toHaveBeenCalledWith('formatter');
|
||||
expect(toastManager.primary).toHaveBeenCalledWith('formatter');
|
||||
});
|
||||
|
||||
it('should use the motion asset originalFileName when showing toasts', async () => {
|
||||
@@ -79,7 +79,7 @@ describe('AssetService', () => {
|
||||
await handleDownloadAsset(asset, { edited: false });
|
||||
expect($t).toHaveBeenNthCalledWith(1, 'downloading_asset_filename', { values: { filename: 'asset.heic' } });
|
||||
expect($t).toHaveBeenNthCalledWith(2, 'downloading_asset_filename', { values: { filename: 'asset.mov' } });
|
||||
expect(toastManager.success).toHaveBeenCalledWith('formatter');
|
||||
expect(toastManager.primary).toHaveBeenCalledWith('formatter');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -334,7 +334,7 @@ export const handleDownloadAsset = async (asset: AssetResponseDto, { edited }: {
|
||||
}
|
||||
|
||||
try {
|
||||
toastManager.success($t('downloading_asset_filename', { values: { filename } }));
|
||||
toastManager.primary($t('downloading_asset_filename', { values: { filename } }));
|
||||
downloadUrl(
|
||||
getBaseUrl() +
|
||||
`/assets/${id}/original` +
|
||||
@@ -352,7 +352,7 @@ const handleFavorite = async (asset: AssetResponseDto) => {
|
||||
|
||||
try {
|
||||
const response = await updateAsset({ id: asset.id, updateAssetDto: { isFavorite: true } });
|
||||
toastManager.success($t('added_to_favorites'));
|
||||
toastManager.primary($t('added_to_favorites'));
|
||||
eventManager.emit('AssetUpdate', response);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: asset.isFavorite } }));
|
||||
@@ -364,7 +364,7 @@ const handleUnfavorite = async (asset: AssetResponseDto) => {
|
||||
|
||||
try {
|
||||
const response = await updateAsset({ id: asset.id, updateAssetDto: { isFavorite: false } });
|
||||
toastManager.success($t('removed_from_favorites'));
|
||||
toastManager.primary($t('removed_from_favorites'));
|
||||
eventManager.emit('AssetUpdate', response);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: asset.isFavorite } }));
|
||||
@@ -387,7 +387,7 @@ const handleRunAssetJob = async (dto: AssetJobsDto) => {
|
||||
|
||||
try {
|
||||
await runAssetJobs({ assetJobsDto: dto });
|
||||
toastManager.success(getAssetJobMessage($t, dto.name));
|
||||
toastManager.primary(getAssetJobMessage($t, dto.name));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_submit_job'));
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const handleCreateJob = async (dto: JobCreateDto) => {
|
||||
|
||||
try {
|
||||
await createJob({ jobCreateDto: dto });
|
||||
toastManager.success($t('admin.job_created'));
|
||||
toastManager.primary($t('admin.job_created'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_submit_job'));
|
||||
|
||||
@@ -161,7 +161,7 @@ export const handleCreateLibrary = async (dto: CreateLibraryDto) => {
|
||||
try {
|
||||
const library = await createLibrary({ createLibraryDto: dto });
|
||||
eventManager.emit('LibraryCreate', library);
|
||||
toastManager.success($t('admin.library_created', { values: { library: library.name } }));
|
||||
toastManager.primary($t('admin.library_created', { values: { library: library.name } }));
|
||||
return library;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_create_library'));
|
||||
@@ -174,7 +174,7 @@ export const handleUpdateLibrary = async (library: LibraryResponseDto, dto: Upda
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: dto });
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.success($t('admin.library_updated'));
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
@@ -205,7 +205,7 @@ const handleDeleteLibrary = async (library: LibraryResponseDto) => {
|
||||
try {
|
||||
await deleteLibrary({ id: library.id });
|
||||
eventManager.emit('LibraryDelete', { id: library.id });
|
||||
toastManager.success($t('admin.library_deleted'));
|
||||
toastManager.primary($t('admin.library_deleted'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_remove_library'));
|
||||
}
|
||||
@@ -225,7 +225,7 @@ export const handleAddLibraryFolder = async (library: LibraryResponseDto, folder
|
||||
updateLibraryDto: { importPaths: [...library.importPaths, folder] },
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.success($t('admin.library_updated'));
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
@@ -246,7 +246,7 @@ export const handleEditLibraryFolder = async (library: LibraryResponseDto, oldVa
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: { importPaths } });
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.success($t('admin.library_updated'));
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
@@ -273,7 +273,7 @@ const handleDeleteLibraryFolder = async (library: LibraryResponseDto, folder: st
|
||||
updateLibraryDto: { importPaths: library.importPaths.filter((path) => path !== folder) },
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.success($t('admin.library_updated'));
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
}
|
||||
@@ -293,7 +293,7 @@ export const handleAddLibraryExclusionPattern = async (library: LibraryResponseD
|
||||
updateLibraryDto: { exclusionPatterns: [...library.exclusionPatterns, exclusionPattern] },
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.success($t('admin.library_updated'));
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
@@ -314,7 +314,7 @@ export const handleEditExclusionPattern = async (library: LibraryResponseDto, ol
|
||||
try {
|
||||
const updatedLibrary = await updateLibrary({ id: library.id, updateLibraryDto: { exclusionPatterns } });
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.success($t('admin.library_updated'));
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
return false;
|
||||
@@ -339,7 +339,7 @@ const handleDeleteExclusionPattern = async (library: LibraryResponseDto, exclusi
|
||||
},
|
||||
});
|
||||
eventManager.emit('LibraryUpdate', updatedLibrary);
|
||||
toastManager.success($t('admin.library_updated'));
|
||||
toastManager.primary($t('admin.library_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_library'));
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ const handleFavoritePerson = async (person: { id: string }) => {
|
||||
try {
|
||||
const response = await updatePerson({ id: person.id, personUpdateDto: { isFavorite: true } });
|
||||
eventManager.emit('PersonUpdate', response);
|
||||
toastManager.success($t('added_to_favorites'));
|
||||
toastManager.primary($t('added_to_favorites'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: false } }));
|
||||
}
|
||||
@@ -69,7 +69,7 @@ const handleUnfavoritePerson = async (person: { id: string }) => {
|
||||
try {
|
||||
const response = await updatePerson({ id: person.id, personUpdateDto: { isFavorite: false } });
|
||||
eventManager.emit('PersonUpdate', response);
|
||||
toastManager.success($t('removed_from_favorites'));
|
||||
toastManager.primary($t('removed_from_favorites'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: false } }));
|
||||
}
|
||||
@@ -80,7 +80,7 @@ const handleHidePerson = async (person: { id: string }) => {
|
||||
|
||||
try {
|
||||
const response = await updatePerson({ id: person.id, personUpdateDto: { isHidden: true } });
|
||||
toastManager.success($t('changed_visibility_successfully'));
|
||||
toastManager.primary($t('changed_visibility_successfully'));
|
||||
eventManager.emit('PersonUpdate', response);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_hide_person'));
|
||||
@@ -92,7 +92,7 @@ const handleShowPerson = async (person: { id: string }) => {
|
||||
|
||||
try {
|
||||
const response = await updatePerson({ id: person.id, personUpdateDto: { isHidden: false } });
|
||||
toastManager.success($t('changed_visibility_successfully'));
|
||||
toastManager.primary($t('changed_visibility_successfully'));
|
||||
eventManager.emit('PersonUpdate', response);
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.something_went_wrong'));
|
||||
@@ -104,7 +104,7 @@ export const handleUpdatePersonBirthDate = async (person: PersonResponseDto, bir
|
||||
|
||||
try {
|
||||
const response = await updatePerson({ id: person.id, personUpdateDto: { birthDate } });
|
||||
toastManager.success($t('date_of_birth_saved'));
|
||||
toastManager.primary($t('date_of_birth_saved'));
|
||||
eventManager.emit('PersonUpdate', response);
|
||||
return true;
|
||||
} catch (error) {
|
||||
|
||||
@@ -129,7 +129,7 @@ export const handleEmptyQueue = async (queue: QueueResponseDto) => {
|
||||
await emptyQueue({ name: queue.name, queueDeleteDto: { failed: false } });
|
||||
const response = await getQueue({ name: queue.name });
|
||||
eventManager.emit('QueueUpdate', response);
|
||||
toastManager.success($t('admin.cleared_jobs', { values: { job: item.title } }));
|
||||
toastManager.primary($t('admin.cleared_jobs', { values: { job: item.title } }));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.something_went_wrong'));
|
||||
}
|
||||
@@ -155,7 +155,7 @@ const handleRemoveFailedJobs = async (queue: QueueResponseDto) => {
|
||||
await emptyQueue({ name: queue.name, queueDeleteDto: { failed: true } });
|
||||
const response = await getQueue({ name: queue.name });
|
||||
eventManager.emit('QueueUpdate', response);
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.something_went_wrong'));
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export const handleUpdateSharedLink = async (sharedLink: SharedLinkResponseDto,
|
||||
const response = await updateSharedLink({ id: sharedLink.id, sharedLinkEditDto: dto });
|
||||
|
||||
eventManager.emit('SharedLinkUpdate', { album: sharedLink.album, ...response });
|
||||
toastManager.success($t('saved'));
|
||||
toastManager.primary($t('saved'));
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -118,7 +118,7 @@ const handleDeleteSharedLink = async (sharedLink: SharedLinkResponseDto) => {
|
||||
try {
|
||||
await removeSharedLink({ id: sharedLink.id });
|
||||
eventManager.emit('SharedLinkDelete', sharedLink);
|
||||
toastManager.success($t('deleted_shared_link'));
|
||||
toastManager.primary($t('deleted_shared_link'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_delete_shared_link'));
|
||||
}
|
||||
@@ -150,7 +150,7 @@ export const handleRemoveSharedLinkAssets = async (sharedLink: SharedLinkRespons
|
||||
}
|
||||
|
||||
const count = results.filter((item) => item.success).length;
|
||||
toastManager.success($t('assets_removed_count', { values: { count } }));
|
||||
toastManager.primary($t('assets_removed_count', { values: { count } }));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_remove_assets_from_shared_link'));
|
||||
|
||||
@@ -62,7 +62,7 @@ export const handleSystemConfigSave = async (update: Partial<SystemConfigDto>) =
|
||||
const newConfig = await updateConfig({ systemConfigDto });
|
||||
|
||||
eventManager.emit('SystemConfigUpdate', newConfig);
|
||||
toastManager.success($t('settings_saved'));
|
||||
toastManager.primary($t('settings_saved'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_settings'));
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export const handleCreateTag = async (tagValue: string) => {
|
||||
return;
|
||||
}
|
||||
|
||||
toastManager.success($t('tag_created', { values: { tag: tag.value } }));
|
||||
toastManager.primary($t('tag_created', { values: { tag: tag.value } }));
|
||||
eventManager.emit('TagCreate', tag);
|
||||
|
||||
return true;
|
||||
@@ -61,7 +61,7 @@ export const handleUpdateTag = async (tag: TreeNode, dto: TagUpdateDto) => {
|
||||
try {
|
||||
const response = await updateTag({ id: tag.id, tagUpdateDto: dto });
|
||||
|
||||
toastManager.success($t('tag_updated', { values: { tag: tag.value } }));
|
||||
toastManager.primary($t('tag_updated', { values: { tag: tag.value } }));
|
||||
eventManager.emit('TagUpdate', response);
|
||||
|
||||
return true;
|
||||
@@ -91,7 +91,7 @@ const handleDeleteTag = async (tag: TreeNode) => {
|
||||
try {
|
||||
await deleteTag({ id: tagId });
|
||||
eventManager.emit('TagDelete', tag);
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.something_went_wrong'));
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export const handleEmptyTrash = async () => {
|
||||
|
||||
try {
|
||||
const { count } = await emptyTrash();
|
||||
toastManager.success($t('assets_permanently_deleted_count', { values: { count } }));
|
||||
toastManager.primary($t('assets_permanently_deleted_count', { values: { count } }));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_empty_trash'));
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export const handleRestoreTrash = async () => {
|
||||
|
||||
try {
|
||||
const { count } = await restoreTrash();
|
||||
toastManager.success($t('assets_restored_count', { values: { count } }));
|
||||
toastManager.primary($t('assets_restored_count', { values: { count } }));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_restore_trash'));
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ export const handleCreateUserAdmin = async (dto: UserAdminCreateDto) => {
|
||||
try {
|
||||
const response = await createUserAdmin({ userAdminCreateDto: dto });
|
||||
eventManager.emit('UserAdminCreate', response);
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
return response;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_create_user'));
|
||||
@@ -122,7 +122,7 @@ export const handleUpdateUserAdmin = async (user: UserAdminResponseDto, dto: Use
|
||||
try {
|
||||
const response = await updateUserAdmin({ id: user.id, userAdminUpdateDto: dto });
|
||||
eventManager.emit('UserAdminUpdate', response);
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_user'));
|
||||
@@ -136,7 +136,7 @@ export const handleDeleteUserAdmin = async (user: UserAdminResponseDto, dto: Use
|
||||
try {
|
||||
const result = await deleteUserAdmin({ id: user.id, userAdminDeleteDto: dto });
|
||||
eventManager.emit('UserAdminDelete', result);
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_delete_user'));
|
||||
@@ -149,7 +149,7 @@ export const handleRestoreUserAdmin = async (user: UserAdminResponseDto) => {
|
||||
try {
|
||||
const response = await restoreUserAdmin({ id: user.id });
|
||||
eventManager.emit('UserAdminRestore', response);
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_restore_user'));
|
||||
@@ -190,7 +190,7 @@ const handleResetPasswordUserAdmin = async (user: UserAdminResponseDto) => {
|
||||
const dto = { password: generatePassword(), shouldChangePassword: true };
|
||||
const response = await updateUserAdmin({ id: user.id, userAdminUpdateDto: dto });
|
||||
eventManager.emit('UserAdminUpdate', response);
|
||||
toastManager.success();
|
||||
toastManager.primary();
|
||||
await modalManager.show(PasswordResetSuccessModal, { newPassword: dto.password });
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_reset_password'));
|
||||
@@ -208,7 +208,7 @@ const handleResetPinCodeUserAdmin = async (user: UserAdminResponseDto) => {
|
||||
try {
|
||||
const response = await updateUserAdmin({ id: user.id, userAdminUpdateDto: { pinCode: null } });
|
||||
eventManager.emit('UserAdminUpdate', response);
|
||||
toastManager.success($t('pin_code_reset_successfully'));
|
||||
toastManager.primary($t('pin_code_reset_successfully'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_reset_pin_code'));
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const handleResetPinCode = async (dto: PinCodeResetDto) => {
|
||||
|
||||
try {
|
||||
await resetPinCode({ pinCodeResetDto: dto });
|
||||
toastManager.success($t('pin_code_reset_successfully'));
|
||||
toastManager.primary($t('pin_code_reset_successfully'));
|
||||
eventManager.emit('UserPinCodeReset');
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -52,7 +52,7 @@ export const handleChangePassword = async (dto: ChangePasswordDto) => {
|
||||
|
||||
try {
|
||||
await changePassword({ changePasswordDto: dto });
|
||||
toastManager.success($t('updated_password'));
|
||||
toastManager.primary($t('updated_password'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_change_password'));
|
||||
|
||||
@@ -397,7 +397,7 @@ export const handleToggleWorkflowEnabled = async (
|
||||
});
|
||||
|
||||
eventManager.emit('WorkflowUpdate', updated);
|
||||
toastManager.success($t('workflow_updated'));
|
||||
toastManager.primary($t('workflow_updated'));
|
||||
return updated;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_workflow'));
|
||||
@@ -419,7 +419,7 @@ export const handleDeleteWorkflow = async (workflow: WorkflowResponseDto): Promi
|
||||
try {
|
||||
await deleteWorkflow({ id: workflow.id });
|
||||
eventManager.emit('WorkflowDelete', workflow);
|
||||
toastManager.success($t('workflow_deleted'));
|
||||
toastManager.primary($t('workflow_deleted'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_delete_workflow'));
|
||||
|
||||
@@ -49,7 +49,7 @@ export const tagAssets = async ({
|
||||
|
||||
if (showNotification) {
|
||||
const $t = await getFormatter();
|
||||
toastManager.success($t('tagged_assets', { values: { count: assetIds.length } }));
|
||||
toastManager.primary($t('tagged_assets', { values: { count: assetIds.length } }));
|
||||
}
|
||||
|
||||
return assetIds;
|
||||
@@ -70,7 +70,7 @@ export const removeTag = async ({
|
||||
|
||||
if (showNotification) {
|
||||
const $t = await getFormatter();
|
||||
toastManager.success($t('removed_tagged_assets', { values: { count: assetIds.length } }));
|
||||
toastManager.primary($t('removed_tagged_assets', { values: { count: assetIds.length } }));
|
||||
}
|
||||
|
||||
return assetIds;
|
||||
@@ -364,7 +364,7 @@ export const deleteStack = async (stackIds: string[]) => {
|
||||
|
||||
await deleteStacks({ bulkIdsDto: { ids: [...ids] } });
|
||||
|
||||
toastManager.success($t('unstacked_assets_count', { values: { count } }));
|
||||
toastManager.primary($t('unstacked_assets_count', { values: { count } }));
|
||||
|
||||
const assets = stacks.flatMap((stack) => stack.assets);
|
||||
for (const asset of assets) {
|
||||
@@ -385,7 +385,7 @@ export const keepThisDeleteOthers = async (keepAsset: AssetResponseDto, stack: S
|
||||
await deleteAssets({ assetBulkDeleteDto: { ids: assetsToDeleteIds } });
|
||||
await deleteStacks({ bulkIdsDto: { ids: [stack.id] } });
|
||||
|
||||
toastManager.success($t('kept_this_deleted_others', { values: { count: assetsToDeleteIds.length } }));
|
||||
toastManager.primary($t('kept_this_deleted_others', { values: { count: assetsToDeleteIds.length } }));
|
||||
|
||||
keepAsset.stack = null;
|
||||
return keepAsset;
|
||||
@@ -440,7 +440,7 @@ export const toggleArchive = async (asset: AssetResponseDto) => {
|
||||
});
|
||||
|
||||
asset.isArchived = data.isArchived;
|
||||
toastManager.success(asset.isArchived ? $t(`added_to_archive`) : $t(`removed_from_archive`));
|
||||
toastManager.primary(asset.isArchived ? $t(`added_to_archive`) : $t(`removed_from_archive`));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_archive', { values: { archived: asset.isArchived } }));
|
||||
}
|
||||
@@ -459,7 +459,7 @@ export const archiveAssets = async (assets: { id: string }[], visibility: AssetV
|
||||
});
|
||||
}
|
||||
|
||||
toastManager.success(
|
||||
toastManager.primary(
|
||||
visibility === AssetVisibility.Archive
|
||||
? $t('archived_count', { values: { count: ids.length } })
|
||||
: $t('unarchived_count', { values: { count: ids.length } }),
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
},
|
||||
});
|
||||
eventManager.emit('AlbumUpdate', response);
|
||||
toastManager.success($t('album_cover_updated'));
|
||||
toastManager.primary($t('album_cover_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_update_album_cover'));
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
break;
|
||||
}
|
||||
}
|
||||
toastManager.success($t('change_name_successfully'));
|
||||
toastManager.primary($t('change_name_successfully'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_name'));
|
||||
}
|
||||
@@ -178,7 +178,7 @@
|
||||
return person;
|
||||
});
|
||||
|
||||
toastManager.success($t('changed_visibility_successfully'));
|
||||
toastManager.primary($t('changed_visibility_successfully'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_hide_person'));
|
||||
}
|
||||
@@ -198,7 +198,7 @@
|
||||
return person;
|
||||
});
|
||||
|
||||
toastManager.success(updatedPerson.isFavorite ? $t('added_to_favorites') : $t('removed_from_favorites'));
|
||||
toastManager.primary(updatedPerson.isFavorite ? $t('added_to_favorites') : $t('removed_from_favorites'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_add_remove_favorites', { values: { favorite: detail.isFavorite } }));
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
}
|
||||
try {
|
||||
person = await updatePerson({ id: person.id, personUpdateDto: { featureFaceAssetId: asset.id } });
|
||||
toastManager.success($t('feature_photo_updated'));
|
||||
toastManager.primary($t('feature_photo_updated'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_set_feature_photo'));
|
||||
}
|
||||
@@ -210,7 +210,7 @@
|
||||
|
||||
try {
|
||||
person = await updatePerson({ id: person.id, personUpdateDto: { name: personName } });
|
||||
toastManager.success($t('change_name_successfully'));
|
||||
toastManager.primary($t('change_name_successfully'));
|
||||
} catch (error) {
|
||||
handleError(error, $t('errors.unable_to_save_name'));
|
||||
}
|
||||
|
||||
23
web/src/routes/(user)/user-settings/+layout.svelte
Normal file
23
web/src/routes/(user)/user-settings/+layout.svelte
Normal file
@@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import { getKeyboardActions } from '$lib/services/keyboard.service';
|
||||
import { Container } from '@immich/ui';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
type Props = {
|
||||
children?: Snippet;
|
||||
data: PageData;
|
||||
};
|
||||
|
||||
let { children, data }: Props = $props();
|
||||
|
||||
const { KeyboardShortcuts } = $derived(getKeyboardActions($t));
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} actions={[KeyboardShortcuts]}>
|
||||
<Container size="medium" center>
|
||||
{@render children?.()}
|
||||
</Container>
|
||||
</UserPageLayout>
|
||||
15
web/src/routes/(user)/user-settings/+layout.ts
Normal file
15
web/src/routes/(user)/user-settings/+layout.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url);
|
||||
|
||||
const $t = await getFormatter();
|
||||
|
||||
return {
|
||||
meta: {
|
||||
title: $t('settings'),
|
||||
},
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -1,9 +1,5 @@
|
||||
<script lang="ts">
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import UserSettingsList from '$lib/components/user-settings-page/user-settings-list.svelte';
|
||||
import { getKeyboardActions } from '$lib/services/keyboard.service';
|
||||
import { Container } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
type Props = {
|
||||
@@ -11,12 +7,6 @@
|
||||
};
|
||||
|
||||
let { data }: Props = $props();
|
||||
|
||||
const { KeyboardShortcuts } = $derived(getKeyboardActions($t));
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title} actions={[KeyboardShortcuts]}>
|
||||
<Container size="medium" center>
|
||||
<UserSettingsList keys={data.keys} sessions={data.sessions} />
|
||||
</Container>
|
||||
</UserPageLayout>
|
||||
<UserSettingsList sessions={data.sessions} />
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { getApiKeys, getSessions } from '@immich/sdk';
|
||||
import { getSessions } from '@immich/sdk';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url);
|
||||
|
||||
const keys = await getApiKeys();
|
||||
const sessions = await getSessions();
|
||||
const $t = await getFormatter();
|
||||
|
||||
return {
|
||||
keys,
|
||||
sessions,
|
||||
meta: {
|
||||
title: $t('settings'),
|
||||
},
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
|
||||
55
web/src/routes/(user)/user-settings/new-api-key/+page.svelte
Normal file
55
web/src/routes/(user)/user-settings/new-api-key/+page.svelte
Normal file
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import ApiKeyPermissionsPicker from '$lib/components/ApiKeyPermissionsPicker.svelte';
|
||||
import { OpenQueryParam } from '$lib/constants';
|
||||
import ApiKeySecretModal from '$lib/modals/ApiKeySecretModal.svelte';
|
||||
import { Route } from '$lib/route';
|
||||
import { handleCreateApiKey } from '$lib/services/api-key.service';
|
||||
import { Permission } from '@immich/sdk';
|
||||
import { Field, FormModal, Input } from '@immich/ui';
|
||||
import { mdiKeyVariant } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
const validPermissions = new Set(Object.values(Permission));
|
||||
const queryPermissions = (page.url.searchParams.get('permissions') || '')
|
||||
.split(' ')
|
||||
.filter((x) => x !== '')
|
||||
.map((value) => {
|
||||
if (value === Permission.All || !validPermissions.has(value as Permission)) {
|
||||
console.warn(`Invalid permission value: ${value}`);
|
||||
}
|
||||
return value as Permission;
|
||||
});
|
||||
|
||||
let name = $state('API Key');
|
||||
let selectedPermissions = $state<Permission[]>(queryPermissions);
|
||||
const isAllPermissions = $derived(selectedPermissions.length === Object.keys(Permission).length - 1);
|
||||
|
||||
const onClose = async () => {
|
||||
await goto(Route.userSettings({ isOpen: OpenQueryParam.API_KEYS }));
|
||||
};
|
||||
|
||||
let secret: string | undefined = $state();
|
||||
|
||||
const onSubmit = async () => {
|
||||
const permissions = isAllPermissions ? [Permission.All] : selectedPermissions;
|
||||
const response = await handleCreateApiKey({ name, permissions });
|
||||
if (response) {
|
||||
secret = response.secret;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if !secret}
|
||||
<FormModal title={$t('new_api_key')} icon={mdiKeyVariant} {onClose} {onSubmit} submitText={$t('create')} size="giant">
|
||||
<div class="mb-4 flex flex-col gap-2">
|
||||
<Field label={$t('name')}>
|
||||
<Input bind:value={name} />
|
||||
</Field>
|
||||
</div>
|
||||
<ApiKeyPermissionsPicker bind:selectedPermissions />
|
||||
</FormModal>
|
||||
{:else}
|
||||
<ApiKeySecretModal {secret} {onClose} />
|
||||
{/if}
|
||||
14
web/src/routes/(user)/user-settings/new-api-key/+page.ts
Normal file
14
web/src/routes/(user)/user-settings/new-api-key/+page.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ url }) => {
|
||||
await authenticate(url);
|
||||
const $t = await getFormatter();
|
||||
|
||||
return {
|
||||
meta: {
|
||||
title: $t('settings'),
|
||||
},
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -95,7 +95,7 @@
|
||||
const message = featureFlagsManager.value.trash
|
||||
? $t('assets_moved_to_trash_count', { values: { count: trashedCount } })
|
||||
: $t('permanently_deleted_assets_count', { values: { count: trashedCount } });
|
||||
toastManager.success(message);
|
||||
toastManager.primary(message);
|
||||
};
|
||||
|
||||
const handleResolve = async (duplicateId: string, duplicateAssetIds: string[], trashIds: string[]) => {
|
||||
@@ -167,7 +167,7 @@
|
||||
|
||||
duplicates = [];
|
||||
|
||||
toastManager.success($t('resolved_all_duplicates'));
|
||||
toastManager.primary($t('resolved_all_duplicates'));
|
||||
page.url.searchParams.delete('index');
|
||||
await goto(Route.duplicatesUtility());
|
||||
},
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
previousWorkflow = updated;
|
||||
editWorkflow = updated;
|
||||
|
||||
toastManager.success($t('workflow_update_success'), {
|
||||
toastManager.primary($t('workflow_update_success'), {
|
||||
closable: true,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user