refactor: server config and feature flags managers (#23894)

This commit is contained in:
Daniel Dietzler
2025-11-14 20:10:44 +01:00
committed by GitHub
parent f11bfb9581
commit 1200bfad13
68 changed files with 378 additions and 416 deletions
@@ -1,78 +0,0 @@
<script lang="ts">
import { retrieveServerConfig } from '$lib/stores/system-config-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { getConfig, getConfigDefaults, updateConfig, type SystemConfigDto } from '@immich/sdk';
import { toastManager } from '@immich/ui';
import { cloneDeep, isEqual } from 'lodash-es';
import { onMount } from 'svelte';
import { t } from 'svelte-i18n';
import type { SettingsResetOptions } from './admin-settings';
interface Props {
config: SystemConfigDto;
children: import('svelte').Snippet<[{ savedConfig: SystemConfigDto; defaultConfig: SystemConfigDto }]>;
}
let { config = $bindable(), children }: Props = $props();
let savedConfig: SystemConfigDto | undefined = $state();
let defaultConfig: SystemConfigDto | undefined = $state();
export const handleReset = async (options: SettingsResetOptions) => {
await (options.default ? resetToDefault(options.configKeys) : reset(options.configKeys));
};
export const handleSave = async (update: Partial<SystemConfigDto>) => {
let systemConfigDto = {
...savedConfig,
...update,
} as SystemConfigDto;
if (isEqual(systemConfigDto, savedConfig)) {
return;
}
try {
const newConfig = await updateConfig({
systemConfigDto,
});
config = cloneDeep(newConfig);
savedConfig = cloneDeep(newConfig);
toastManager.success($t('settings_saved'));
await retrieveServerConfig();
} catch (error) {
handleError(error, $t('errors.unable_to_save_settings'));
}
};
const reset = async (configKeys: Array<keyof SystemConfigDto>) => {
const resetConfig = await getConfig();
for (const key of configKeys) {
config = { ...config, [key]: resetConfig[key] };
}
toastManager.info($t('admin.reset_settings_to_recent_saved'));
};
const resetToDefault = (configKeys: Array<keyof SystemConfigDto>) => {
if (!defaultConfig) {
return;
}
for (const key of configKeys) {
config = { ...config, [key]: defaultConfig[key] };
}
toastManager.info($t('admin.reset_settings_to_default'));
};
onMount(async () => {
[savedConfig, defaultConfig] = await Promise.all([getConfig(), getConfigDefaults()]);
});
</script>
{#if savedConfig && defaultConfig}
{@render children({ savedConfig, defaultConfig })}
{/if}
@@ -6,8 +6,9 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import AuthDisableLoginConfirmModal from '$lib/modals/AuthDisableLoginConfirmModal.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { OAuthTokenEndpointAuthMethod, unlinkAllOAuthAccountsAdmin } from '@immich/sdk';
import { Button, modalManager, Text, toastManager } from '@immich/ui';
@@ -15,7 +16,7 @@
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
@@ -5,11 +5,12 @@
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
@@ -7,7 +7,8 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import {
AudioCodec,
CQMode,
@@ -23,7 +24,7 @@
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -8,10 +8,11 @@
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -2,13 +2,14 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { getJobName } from '$lib/utils';
import { JobName, type SystemConfigJobDto } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
@@ -6,11 +6,12 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
@@ -2,12 +2,13 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingSelect from '$lib/components/shared-components/settings/setting-select.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { LogLevel } from '@immich/sdk';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -6,14 +6,15 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { Button, IconButton } from '@immich/ui';
import { mdiPlus, mdiTrashCanOutline } from '@mdi/js';
import { isEqual } from 'lodash-es';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -167,7 +168,7 @@
min={0.001}
max={0.1}
description={$t('admin.machine_learning_max_detection_distance_description')}
disabled={disabled || !$featureFlags.duplicateDetection}
disabled={disabled || !featureFlagsManager.value.duplicateDetection}
isEdited={configToEdit.machineLearning.duplicateDetection.maxDistance !==
config.machineLearning.duplicateDetection.maxDistance}
/>
@@ -5,11 +5,12 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -1,11 +1,12 @@
<script lang="ts">
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -1,11 +1,12 @@
<script lang="ts">
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -3,11 +3,12 @@
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -5,8 +5,9 @@
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { user } from '$lib/stores/user.store';
import { handleError } from '$lib/utils/handle-error';
import { sendTestEmailAdmin } from '@immich/sdk';
@@ -14,7 +15,7 @@
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
@@ -3,11 +3,12 @@
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -7,8 +7,9 @@
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { AppRoute, SettingInputFieldType } from '$lib/constants';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { user } from '$lib/stores/user.store';
import { getStorageTemplateOptions, type SystemConfigTemplateStorageOptionDto } from '@immich/sdk';
import { LoadingSpinner } from '@immich/ui';
@@ -27,7 +28,7 @@
const { minified = false, duration = 500, saveOnClose = false }: Props = $props();
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
@@ -2,8 +2,8 @@
import SettingAccordion from '$lib/components/shared-components/settings/setting-accordion.svelte';
import SettingTextarea from '$lib/components/shared-components/settings/setting-textarea.svelte';
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import EmailTemplatePreviewModal from '$lib/modals/EmailTemplatePreviewModal.svelte';
import { systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { type SystemConfigDto, type SystemConfigTemplateEmailsDto, getNotificationTemplateAdmin } from '@immich/sdk';
import { Button, Icon, LoadingSpinner, modalManager } from '@immich/ui';
@@ -1,11 +1,12 @@
<script lang="ts">
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingTextarea from '$lib/components/shared-components/settings/setting-textarea.svelte';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -3,11 +3,12 @@
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
import { fade } from 'svelte/transition';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -4,10 +4,11 @@
import SettingButtonsRow from '$lib/components/shared-components/settings/SystemConfigButtonRow.svelte';
import SettingInputField from '$lib/components/shared-components/settings/setting-input-field.svelte';
import { SettingInputFieldType } from '$lib/constants';
import { featureFlags, systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { t } from 'svelte-i18n';
const disabled = $featureFlags.configFile;
const disabled = $derived(featureFlagsManager.value.configFile);
const config = $derived(systemConfigManager.value);
let configToEdit = $state(systemConfigManager.cloneValue());
</script>
@@ -1,4 +0,0 @@
import type { ResetOptions } from '$lib/utils/dipatch';
import type { SystemConfigDto } from '@immich/sdk';
export type SettingsResetOptions = ResetOptions & { configKeys: Array<keyof SystemConfigDto> };
@@ -6,12 +6,12 @@
import SelectAllAssets from '$lib/components/timeline/actions/SelectAllAction.svelte';
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
import Timeline from '$lib/components/timeline/Timeline.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import { handleDownloadAlbum } from '$lib/services/album.service';
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { dragAndDropFilesStore } from '$lib/stores/drag-and-drop-files.store';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { handlePromiseError } from '$lib/utils';
import { cancelMultiselect } from '$lib/utils/asset-utils';
import { fileUploadHandler, openFileUploadDialog } from '$lib/utils/file-uploader';
@@ -126,7 +126,7 @@
icon={mdiDownload}
/>
{/if}
{#if sharedLink.showMetadata && $featureFlags.loaded && $featureFlags.map}
{#if sharedLink.showMetadata && featureFlagsManager.value.map}
<AlbumMap {album} />
{/if}
<ThemeButton />
@@ -3,8 +3,8 @@
import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte';
import { AssetAction } from '$lib/constants';
import Portal from '$lib/elements/Portal.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { showDeleteModal } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { toTimelineAsset } from '$lib/utils/timeline-util';
import { deleteAssets, type AssetResponseDto } from '@immich/sdk';
@@ -24,7 +24,7 @@
let showConfirmModal = $state(false);
const trashOrDelete = async (force = false) => {
if (force || !$featureFlags.trash) {
if (force || !featureFlagsManager.value.trash) {
if ($showDeleteModal) {
showConfirmModal = true;
return;
@@ -32,6 +32,10 @@ describe('AssetViewerNavBar component', () => {
'ResizeObserver',
vi.fn(() => ({ observe: vi.fn(), unobserve: vi.fn(), disconnect: vi.fn() })),
);
vi.mock(import('$lib/managers/feature-flags-manager.svelte'), () => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return { featureFlagsManager: { init: vi.fn(), loadFeatureFlags: vi.fn(), value: { smartSearch: true } } as any };
});
});
afterEach(() => {
@@ -24,8 +24,8 @@
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
import { AppRoute } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { photoViewerImgElement } from '$lib/stores/assets-store.svelte';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { user } from '$lib/stores/user.store';
import { photoZoomState } from '$lib/stores/zoom-image.store';
import { getAssetJobName, getSharedLink } from '$lib/utils';
@@ -108,7 +108,7 @@
let isOwner = $derived($user && asset.ownerId === $user?.id);
let showDownloadButton = $derived(sharedLink ? sharedLink.allowDownload : !asset.isOffline);
let isLocked = $derived(asset.visibility === AssetVisibility.Locked);
let smartSearchEnabled = $derived($featureFlags.loaded && $featureFlags.smartSearch);
let smartSearchEnabled = $derived(featureFlagsManager.value.smartSearch);
// $: showEditorButton =
// isOwner &&
@@ -7,11 +7,11 @@
import DetailPanelTags from '$lib/components/asset-viewer/detail-panel-tags.svelte';
import { AppRoute, QueryParameter, timeToLoadTheMap } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import AssetChangeDateModal from '$lib/modals/AssetChangeDateModal.svelte';
import { isFaceEditMode } from '$lib/stores/face-edit.svelte';
import { boundingBoxesArray } from '$lib/stores/people.store';
import { locale } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { preferences, user } from '$lib/stores/user.store';
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
import { delay, getDimensions } from '$lib/utils/asset-utils';
@@ -438,7 +438,7 @@
</div>
</section>
{#if latlng && $featureFlags.loaded && $featureFlags.map}
{#if latlng && featureFlagsManager.value.map}
<div class="h-90">
{#await import('$lib/components/shared-components/map/map.svelte')}
{#await delay(timeToLoadTheMap) then}
+10 -9
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { getJobName } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { JobCommand, JobName, sendJobCommand, type AllJobStatusResponseDto, type JobCommandDto } from '@immich/sdk';
@@ -27,8 +27,9 @@
}
let { jobs = $bindable() }: Props = $props();
const featureFlags = featureFlagsManager.value;
interface JobDetails {
type JobDetails = {
title: string;
subtitle?: string;
description?: Component;
@@ -38,7 +39,7 @@
disabled?: boolean;
icon: string;
handleCommand?: (jobId: JobName, jobCommand: JobCommandDto) => Promise<void>;
}
};
const handleConfirmCommand = async (jobId: JobName, dto: JobCommandDto) => {
if (dto.force) {
@@ -84,7 +85,7 @@
subtitle: $t('admin.sidecar_job_description'),
allText: $t('sync'),
missingText: $t('discover'),
disabled: !$featureFlags.sidecar,
disabled: !featureFlags.sidecar,
},
[JobName.SmartSearch]: {
icon: mdiImageSearch,
@@ -92,7 +93,7 @@
subtitle: $t('admin.smart_search_job_description'),
allText: $t('all'),
missingText: $t('missing'),
disabled: !$featureFlags.smartSearch,
disabled: !featureFlags.smartSearch,
},
[JobName.DuplicateDetection]: {
icon: mdiContentDuplicate,
@@ -100,7 +101,7 @@
subtitle: $t('admin.duplicate_detection_job_description'),
allText: $t('all'),
missingText: $t('missing'),
disabled: !$featureFlags.duplicateDetection,
disabled: !featureFlags.duplicateDetection,
},
[JobName.FaceDetection]: {
icon: mdiFaceRecognition,
@@ -110,7 +111,7 @@
refreshText: $t('refresh'),
missingText: $t('missing'),
handleCommand: handleConfirmCommand,
disabled: !$featureFlags.facialRecognition,
disabled: !featureFlags.facialRecognition,
},
[JobName.FacialRecognition]: {
icon: mdiTagFaces,
@@ -119,7 +120,7 @@
allText: $t('reset'),
missingText: $t('missing'),
handleCommand: handleConfirmCommand,
disabled: !$featureFlags.facialRecognition,
disabled: !featureFlags.facialRecognition,
},
[JobName.Ocr]: {
icon: mdiOcr,
@@ -127,7 +128,7 @@
subtitle: $t('admin.ocr_job_description'),
allText: $t('all'),
missingText: $t('missing'),
disabled: !$featureFlags.ocr,
disabled: !featureFlags.ocr,
},
[JobName.VideoConversion]: {
icon: mdiVideo,
@@ -1,11 +1,13 @@
<script lang="ts">
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { OnboardingRole } from '$lib/models/onboarding-role';
import { serverConfig } from '$lib/stores/system-config-manager.svelte';
import { user } from '$lib/stores/user.store';
import { Logo } from '@immich/ui';
import { t } from 'svelte-i18n';
let userRole = $derived($user.isAdmin && !$serverConfig.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER);
let userRole = $derived(
$user.isAdmin && !serverConfigManager.value.isOnboarded ? OnboardingRole.SERVER : OnboardingRole.USER,
);
</script>
<div class="gap-4">
@@ -1,7 +1,7 @@
<script lang="ts">
import SettingSwitch from '$lib/components/shared-components/settings/setting-switch.svelte';
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import { onDestroy } from 'svelte';
import { t } from 'svelte-i18n';
@@ -5,12 +5,12 @@
import Thumbnail from '$lib/components/assets/thumbnail/thumbnail.svelte';
import { AppRoute, AssetAction } from '$lib/constants';
import Portal from '$lib/elements/Portal.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import type { TimelineAsset, Viewport } from '$lib/managers/timeline-manager/types';
import ShortcutsModal from '$lib/modals/ShortcutsModal.svelte';
import type { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { showDeleteModal } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { handlePromiseError } from '$lib/utils';
import { deleteAssets } from '$lib/utils/actions';
import { archiveAssets, cancelMultiselect } from '$lib/utils/asset-utils';
@@ -405,7 +405,7 @@
}
};
let isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash);
let isTrashEnabled = $derived(featureFlagsManager.value.trash);
$effect(() => {
if (!lastAssetMouseEvent) {
@@ -11,10 +11,10 @@
<script lang="ts">
import { afterNavigate } from '$app/navigation';
import { Theme } from '$lib/constants';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { themeManager } from '$lib/managers/theme-manager.svelte';
import MapSettingsModal from '$lib/modals/MapSettingsModal.svelte';
import { mapSettings } from '$lib/stores/preferences.store';
import { serverConfig } from '$lib/stores/system-config-manager.svelte';
import { getAssetThumbnailUrl, handlePromiseError } from '$lib/utils';
import { getMapMarkers, type MapMarkerResponseDto } from '@immich/sdk';
import { Icon, modalManager } from '@immich/ui';
@@ -103,7 +103,9 @@
let abortController: AbortController;
const theme = $derived($mapSettings.allowDarkMode ? themeManager.value : Theme.LIGHT);
const styleUrl = $derived(theme === Theme.DARK ? $serverConfig.mapDarkStyleUrl : $serverConfig.mapLightStyleUrl);
const styleUrl = $derived(
theme === Theme.DARK ? serverConfigManager.value.mapDarkStyleUrl : serverConfigManager.value.mapLightStyleUrl,
);
export function addClipMapMarker(lng: number, lat: number) {
if (map) {
@@ -11,10 +11,10 @@
import { AppRoute } from '$lib/constants';
import SkipLink from '$lib/elements/SkipLink.svelte';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { mobileDevice } from '$lib/stores/mobile-device.svelte';
import { notificationManager } from '$lib/stores/notification-manager.svelte';
import { sidebarStore } from '$lib/stores/sidebar.svelte';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { user } from '$lib/stores/user.store';
import { Button, IconButton, Logo } from '@immich/ui';
import { mdiBellBadge, mdiBellOutline, mdiMagnify, mdiMenu, mdiTrayArrowUp } from '@mdi/js';
@@ -82,13 +82,13 @@
</div>
<div class="flex justify-between gap-4 lg:gap-8 pe-6">
<div class="hidden w-full max-w-5xl flex-1 tall:ps-0 sm:block">
{#if $featureFlags.search}
{#if featureFlagsManager.value.search}
<SearchBar grayTheme={true} />
{/if}
</div>
<section class="flex place-items-center justify-end gap-1 md:gap-2 w-full sm:w-auto">
{#if $featureFlags.search}
{#if featureFlagsManager.value.search}
<IconButton
color="secondary"
shape="round"
@@ -1,6 +1,6 @@
<script lang="ts">
import RadioButton from '$lib/elements/RadioButton.svelte';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { t } from 'svelte-i18n';
interface Props {
@@ -14,7 +14,7 @@
<fieldset>
<legend class="immich-form-label">{$t('search_type')}</legend>
<div class="flex flex-wrap gap-x-5 gap-y-2 mt-1 mb-2">
{#if $featureFlags.loaded && $featureFlags.smartSearch}
{#if featureFlagsManager.value.smartSearch}
<RadioButton name="query-type" id="context-radio" label={$t('context')} bind:group={queryType} value="smart" />
{/if}
<RadioButton
@@ -31,7 +31,7 @@
bind:group={queryType}
value="description"
/>
{#if $featureFlags.loaded && $featureFlags.ocr}
{#if featureFlagsManager.value.ocr}
<RadioButton name="query-type" id="ocr-radio" label={$t('ocr')} bind:group={queryType} value="ocr" />
{/if}
</div>
@@ -1,6 +1,6 @@
<script lang="ts">
import { systemConfigManager } from '$lib/managers/system-config-manager.svelte';
import { handleSystemConfigSave } from '$lib/services/system-config.service';
import { systemConfigManager } from '$lib/stores/system-config-manager.svelte';
import type { SystemConfigDto } from '@immich/sdk';
import { Button, toastManager } from '@immich/ui';
import { isEqual, pick } from 'lodash-es';
@@ -3,8 +3,8 @@
import BottomInfo from '$lib/components/shared-components/side-bar/bottom-info.svelte';
import RecentAlbums from '$lib/components/shared-components/side-bar/recent-albums.svelte';
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { recentAlbumsDropdown } from '$lib/stores/preferences.store';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { preferences } from '$lib/stores/user.store';
import {
mdiAccount,
@@ -54,11 +54,11 @@
icon={isPhotosSelected ? mdiImageMultiple : mdiImageMultipleOutline}
></SideBarLink>
{#if $featureFlags.search}
{#if featureFlagsManager.value.search}
<SideBarLink title={$t('explore')} href={resolve('/(user)/explore')} icon={mdiMagnify} />
{/if}
{#if $featureFlags.map}
{#if featureFlagsManager.value.map}
<SideBarLink
title={$t('map')}
href={resolve('/(user)/map')}
@@ -139,7 +139,7 @@
icon={isLockedFolderSelected ? mdiLock : mdiLockOutline}
></SideBarLink>
{#if $featureFlags.trash}
{#if featureFlagsManager.value.trash}
<SideBarLink
title={$t('trash')}
href={resolve('/(user)/trash')}
@@ -233,7 +233,6 @@
// afterNavigate is only called after navigation to a new URL, {complete} will resolve
// after successful navigation.
afterNavigate(({ complete }) => {
console.log('after navigate');
void complete.finally(() => {
const isAssetViewerPage = isAssetViewerRoute(page);
@@ -1,7 +1,7 @@
<script lang="ts">
import DeleteAssetDialog from '$lib/components/photos-page/delete-asset-dialog.svelte';
import { getAssetControlContext } from '$lib/components/timeline/AssetSelectControlBar.svelte';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { type OnDelete, type OnUndoDelete, deleteAssets } from '$lib/utils/actions';
import { IconButton } from '@immich/ui';
import { mdiDeleteForeverOutline, mdiDeleteOutline, mdiTimerSand } from '@mdi/js';
@@ -15,7 +15,12 @@
force?: boolean;
}
let { onAssetDelete, onUndoDelete = undefined, menuItem = false, force = !$featureFlags.trash }: Props = $props();
let {
onAssetDelete,
onUndoDelete = undefined,
menuItem = false,
force = !featureFlagsManager.value.trash,
}: Props = $props();
const { clearSelect, getOwnedAssets } = getAssetControlContext();
@@ -7,6 +7,7 @@
setFocusTo as setFocusToInit,
} from '$lib/components/timeline/actions/focus-actions';
import { AppRoute } from '$lib/constants';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import NavigateToDateModal from '$lib/modals/NavigateToDateModal.svelte';
@@ -15,7 +16,6 @@
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
import { showDeleteModal } from '$lib/stores/preferences.store';
import { searchStore } from '$lib/stores/search.svelte';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { handlePromiseError } from '$lib/utils';
import { deleteAssets, updateStackedAssetInTimeline } from '$lib/utils/actions';
import { archiveAssets, cancelMultiselect, selectAllAssets, stackAssets } from '$lib/utils/asset-utils';
@@ -121,7 +121,7 @@
}
};
const isTrashEnabled = $derived($featureFlags.loaded && $featureFlags.trash);
const isTrashEnabled = $derived(featureFlagsManager.value.trash);
const isEmpty = $derived(timelineManager.isInitialized && timelineManager.months.length === 0);
const idsSelectedAssets = $derived(assetInteraction.selectedAssets.map(({ id }) => id));
let isShortcutModalOpen = false;
@@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { oauth } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { type UserAdminResponseDto } from '@immich/sdk';
@@ -50,7 +50,7 @@
<div class="flex place-content-center place-items-center">
<LoadingSpinner />
</div>
{:else if $featureFlags.oauth}
{:else if featureFlagsManager.value.oauth}
{#if user.oauthId}
<Button shape="round" size="small" onclick={() => handleUnlink()}>{$t('unlink_oauth')}</Button>
{:else}
@@ -7,7 +7,7 @@
import UserPurchaseSettings from '$lib/components/user-settings-page/user-purchase-settings.svelte';
import UserUsageStatistic from '$lib/components/user-settings-page/user-usage-statistic.svelte';
import { OpenSettingQueryParameterValue, QueryParameter } from '$lib/constants';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
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';
@@ -112,7 +112,7 @@
<NotificationsSettings />
</SettingAccordion>
{#if $featureFlags.loaded && $featureFlags.oauth}
{#if featureFlagsManager.value.oauth}
<SettingAccordion
icon={mdiTwoFactorAuthentication}
key="oauth"
@@ -0,0 +1,28 @@
import { eventManager } from '$lib/managers/event-manager.svelte';
import { getServerFeatures, type ServerFeaturesDto } from '@immich/sdk';
class FeatureFlagsManager {
#value?: ServerFeaturesDto = $state();
constructor() {
eventManager.on('SystemConfigUpdate', () => void this.#loadFeatureFlags());
}
async init() {
await this.#loadFeatureFlags();
}
get value() {
if (!this.#value) {
throw new Error('Feature flags manager must be initialized first');
}
return this.#value;
}
async #loadFeatureFlags() {
this.#value = await getServerFeatures();
}
}
export const featureFlagsManager = new FeatureFlagsManager();
@@ -0,0 +1,28 @@
import { eventManager } from '$lib/managers/event-manager.svelte';
import { getServerConfig, type ServerConfigDto } from '@immich/sdk';
class ServerConfigManager {
#value?: ServerConfigDto = $state();
constructor() {
eventManager.on('SystemConfigUpdate', () => void this.loadServerConfig());
}
async init() {
await this.loadServerConfig();
}
get value() {
if (!this.#value) {
throw new Error('Server config manager must be initialized first');
}
return this.#value;
}
async loadServerConfig() {
this.#value = await getServerConfig();
}
}
export const serverConfigManager = new ServerConfigManager();
@@ -0,0 +1,55 @@
import { eventManager } from '$lib/managers/event-manager.svelte';
import { getConfig, getConfigDefaults, type SystemConfigDto } from '@immich/sdk';
import { cloneDeep } from 'lodash-es';
class SystemConfigManager {
#value?: SystemConfigDto = $state();
#defaultValue?: SystemConfigDto = $state();
constructor() {
eventManager.on('SystemConfigUpdate', (config) => (this.#value = config));
}
async init() {
await this.#loadConfig();
await this.#loadDefault();
}
get value() {
if (!this.#value) {
throw new Error('Server config manager must be initialized first');
}
return this.#value;
}
set value(config: SystemConfigDto) {
this.#value = config;
}
get defaultValue() {
if (!this.#defaultValue) {
throw new Error('Server config manager must be initialized first');
}
return this.#defaultValue;
}
cloneValue() {
return cloneDeep(this.value);
}
cloneDefaultValue() {
return cloneDeep(this.defaultValue);
}
async #loadConfig() {
this.#value = await getConfig();
}
async #loadDefault() {
this.#defaultValue = await getConfigDefaults();
}
}
export const systemConfigManager = new SystemConfigManager();
+2 -2
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { handleError } from '$lib/utils/handle-error';
import { resetPinCode } from '@immich/sdk';
import {
@@ -24,7 +24,7 @@
let { onClose }: Props = $props();
let passwordLoginEnabled = $derived($featureFlags.passwordLogin);
let passwordLoginEnabled = $derived(featureFlagsManager.value.passwordLogin);
let password = $state('');
const handleReset = async () => {
+4 -4
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { handleCreateUserAdmin } from '$lib/services/user-admin.service';
import { featureFlags } from '$lib/stores/system-config-manager.svelte';
import { userInteraction } from '$lib/stores/user.svelte';
import { ByteUnit, convertToBytes } from '$lib/utils/byte-units';
import {
@@ -85,17 +85,17 @@
<Input bind:value={email} type="email" />
</Field>
{#if $featureFlags.email}
{#if featureFlagsManager.value.email}
<Field label={$t('admin.send_welcome_email')}>
<Switch id="send-welcome-email" bind:checked={notify} class="text-sm" />
</Field>
{/if}
<Field label={$t('password')} required={!$featureFlags.oauth}>
<Field label={$t('password')} required={!featureFlagsManager.value.oauth}>
<PasswordInput id="password" bind:value={password} autocomplete="new-password" />
</Field>
<Field label={$t('confirm_password')} required={!$featureFlags.oauth}>
<Field label={$t('confirm_password')} required={!featureFlagsManager.value.oauth}>
<PasswordInput id="confirmPassword" bind:value={passwordConfirm} autocomplete="new-password" />
<HelperText color="danger">{passwordMismatchMessage}</HelperText>
</Field>
@@ -1,7 +1,7 @@
<script lang="ts">
import FormatMessage from '$lib/elements/FormatMessage.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { handleDeleteUserAdmin } from '$lib/services/user-admin.service';
import { serverConfig } from '$lib/stores/system-config-manager.svelte';
import { type UserAdminResponseDto } from '@immich/sdk';
import { Alert, Checkbox, ConfirmModal, Field, Input, Label, Text } from '@immich/ui';
import { mdiTrashCanOutline } from '@mdi/js';
@@ -50,7 +50,7 @@
{:else}
<FormatMessage
key="admin.user_delete_delay"
values={{ user: user.name, delay: $serverConfig.userDeleteDelay }}
values={{ user: user.name, delay: serverConfigManager.value.userDeleteDelay }}
>
{#snippet children({ message })}
<b>{message}</b>
+2 -3
View File
@@ -2,8 +2,8 @@ import { goto } from '$app/navigation';
import { AppRoute } from '$lib/constants';
import { authManager } from '$lib/managers/auth-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import QrCodeModal from '$lib/modals/QrCodeModal.svelte';
import { serverConfig } from '$lib/stores/system-config-manager.svelte';
import { copyToClipboard } from '$lib/utils';
import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n';
@@ -19,7 +19,6 @@ import {
import { MenuItemType, menuManager, modalManager, toastManager, type MenuItem } from '@immich/ui';
import { mdiCircleEditOutline, mdiContentCopy, mdiDelete, mdiDotsVertical, mdiQrcode } from '@mdi/js';
import type { MessageFormatter } from 'svelte-i18n';
import { get } from 'svelte/store';
export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLinkResponseDto) => {
const Edit: MenuItem = {
@@ -63,7 +62,7 @@ export const getSharedLinkActions = ($t: MessageFormatter, sharedLink: SharedLin
const asUrl = (sharedLink: SharedLinkResponseDto) => {
const path = sharedLink.slug ? `s/${sharedLink.slug}` : `share/${sharedLink.key}`;
return new URL(path, get(serverConfig).externalDomain || globalThis.location.origin).href;
return new URL(path, serverConfigManager.value.externalDomain || globalThis.location.origin).href;
};
export const handleCreateSharedLink = async (dto: SharedLinkCreateDto) => {
@@ -1,18 +1,21 @@
import { downloadManager } from '$lib/managers/download-manager.svelte';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { type FeatureFlags } from '$lib/stores/system-config-manager.svelte';
import type { ActionItem } from '$lib/types';
import { copyToClipboard } from '$lib/utils';
import { downloadBlob } from '$lib/utils/asset-utils';
import { handleError } from '$lib/utils/handle-error';
import { getFormatter } from '$lib/utils/i18n';
import { getConfig, updateConfig, type SystemConfigDto } from '@immich/sdk';
import { getConfig, updateConfig, type ServerFeaturesDto, type SystemConfigDto } from '@immich/sdk';
import { toastManager } from '@immich/ui';
import { mdiContentCopy, mdiDownload, mdiUpload } from '@mdi/js';
import { isEqual } from 'lodash-es';
import type { MessageFormatter } from 'svelte-i18n';
export const getSystemConfigActions = ($t: MessageFormatter, $featureFlags: FeatureFlags, config: SystemConfigDto) => {
export const getSystemConfigActions = (
$t: MessageFormatter,
featureFlags: ServerFeaturesDto,
config: SystemConfigDto,
) => {
const CopyToClipboard: ActionItem = {
title: $t('copy_to_clipboard'),
icon: mdiContentCopy,
@@ -28,7 +31,7 @@ export const getSystemConfigActions = ($t: MessageFormatter, $featureFlags: Feat
const Upload: ActionItem = {
title: $t('import_from_json'),
icon: mdiUpload,
$if: () => !$featureFlags.configFile,
$if: () => !featureFlags.configFile,
onSelect: () => handleUploadConfig(),
};
+2 -4
View File
@@ -1,11 +1,11 @@
import { goto } from '$app/navigation';
import { eventManager } from '$lib/managers/event-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import PasswordResetSuccessModal from '$lib/modals/PasswordResetSuccessModal.svelte';
import UserCreateModal from '$lib/modals/UserCreateModal.svelte';
import UserDeleteConfirmModal from '$lib/modals/UserDeleteConfirmModal.svelte';
import UserEditModal from '$lib/modals/UserEditModal.svelte';
import UserRestoreConfirmModal from '$lib/modals/UserRestoreConfirmModal.svelte';
import { serverConfig } from '$lib/stores/system-config-manager.svelte';
import { user as authUser } from '$lib/stores/user.store';
import type { ActionItem } from '$lib/types';
import { handleError } from '$lib/utils/handle-error';
@@ -37,9 +37,7 @@ import type { MessageFormatter } from 'svelte-i18n';
import { get } from 'svelte/store';
const getDeleteDate = (deletedAt: string): Date =>
DateTime.fromISO(deletedAt)
.plus({ days: get(serverConfig).userDeleteDelay })
.toJSDate();
DateTime.fromISO(deletedAt).plus({ days: serverConfigManager.value.userDeleteDelay }).toJSDate();
export const getUserAdminsActions = ($t: MessageFormatter) => {
const Create: ActionItem = {
@@ -1,108 +0,0 @@
import { eventManager } from '$lib/managers/event-manager.svelte';
import {
getConfig,
getConfigDefaults,
getServerConfig,
getServerFeatures,
type ServerConfigDto,
type ServerFeaturesDto,
type SystemConfigDto,
} from '@immich/sdk';
import { cloneDeep } from 'lodash-es';
import { writable } from 'svelte/store';
export type FeatureFlags = ServerFeaturesDto & { loaded: boolean };
export const featureFlags = writable<FeatureFlags>({
loaded: false,
smartSearch: true,
duplicateDetection: false,
facialRecognition: true,
importFaces: false,
sidecar: true,
map: true,
reverseGeocoding: true,
search: true,
oauth: false,
oauthAutoLaunch: false,
passwordLogin: true,
configFile: false,
trash: true,
email: false,
ocr: true,
});
export type ServerConfig = ServerConfigDto & { loaded: boolean };
export const serverConfig = writable<ServerConfig>({
loaded: false,
oauthButtonText: '',
loginPageMessage: '',
trashDays: 30,
userDeleteDelay: 7,
isInitialized: false,
isOnboarded: false,
externalDomain: '',
mapDarkStyleUrl: '',
mapLightStyleUrl: '',
publicUsers: true,
});
class SystemConfigManager {
#value?: SystemConfigDto = $state();
#defaultValue?: SystemConfigDto = $state();
constructor() {
eventManager.on('SystemConfigUpdate', (config) => (this.#value = config));
}
get value() {
if (!this.#value) {
throw new Error('System config dto must be initialized first');
}
return this.#value;
}
set value(config: SystemConfigDto) {
this.#value = config;
}
get defaultValue() {
if (!this.#defaultValue) {
throw new Error('System config dto must be initialized first');
}
return this.#defaultValue;
}
cloneValue() {
return cloneDeep(this.value);
}
cloneDefaultValue() {
return cloneDeep(this.defaultValue);
}
async init() {
await this.#loadConfig();
await this.#loadDefault();
}
async #loadConfig() {
this.#value = await getConfig();
}
async #loadDefault() {
this.#defaultValue = await getConfigDefaults();
}
}
export const retrieveServerConfig = async () => {
const [flags, config] = await Promise.all([getServerFeatures(), getServerConfig()]);
featureFlags.update(() => ({ ...flags, loaded: true }));
serverConfig.update(() => ({ ...config, loaded: true }));
};
export const systemConfigManager = new SystemConfigManager();
+2 -3
View File
@@ -1,8 +1,7 @@
import { PUBLIC_IMMICH_BUY_HOST, PUBLIC_IMMICH_PAY_HOST } from '$env/static/public';
import type { ImmichProduct } from '$lib/constants';
import { serverConfig } from '$lib/stores/system-config-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { setServerLicense, setUserLicense, type LicenseResponseDto } from '@immich/sdk';
import { get } from 'svelte/store';
import { loadUser } from './auth';
export const activateProduct = async (licenseKey: string, activationKey: string): Promise<LicenseResponseDto> => {
@@ -24,6 +23,6 @@ export const getActivationKey = async (licenseKey: string): Promise<string> => {
export const getLicenseLink = (license: ImmichProduct) => {
const url = new URL('/', PUBLIC_IMMICH_BUY_HOST);
url.searchParams.append('productId', license);
url.searchParams.append('instanceUrl', get(serverConfig).externalDomain || globalThis.origin);
url.searchParams.append('instanceUrl', serverConfigManager.value.externalDomain || globalThis.origin);
return url.href;
};
+4 -2
View File
@@ -1,4 +1,5 @@
import { retrieveServerConfig } from '$lib/stores/system-config-manager.svelte';
import { featureFlagsManager } from '$lib/managers/feature-flags-manager.svelte';
import { serverConfigManager } from '$lib/managers/server-config-manager.svelte';
import { initLanguage } from '$lib/utils';
import { defaults } from '@immich/sdk';
import { memoize } from 'lodash-es';
@@ -11,7 +12,8 @@ async function _init(fetch: Fetch) {
// https://github.com/oazapfts/oazapfts/blob/main/README.md#fetch-options
defaults.fetch = fetch;
await initLanguage();
await retrieveServerConfig();
await featureFlagsManager.init();
await serverConfigManager.init();
}
export const init = memoize(_init, () => 'singlevalue');