diff --git a/web/src/lib/__mocks__/jsdom-url.mock.ts b/web/src/lib/__mocks__/jsdom-url.mock.ts
deleted file mode 100644
index d65da98141..0000000000
--- a/web/src/lib/__mocks__/jsdom-url.mock.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-const createObjectURLMock = vi.fn();
-
-// eslint-disable-next-line unicorn/no-top-level-side-effects
-Object.defineProperty(URL, 'createObjectURL', {
- writable: true,
- value: createObjectURLMock,
-});
-
-export { createObjectURLMock };
diff --git a/web/src/lib/attachments/drag-and-drop.svelte.ts b/web/src/lib/attachments/drag-and-drop.svelte.ts
deleted file mode 100644
index 28afd25615..0000000000
--- a/web/src/lib/attachments/drag-and-drop.svelte.ts
+++ /dev/null
@@ -1,105 +0,0 @@
-import type { Attachment } from 'svelte/attachments';
-
-export interface DragAndDropOptions {
- index: number;
- onDragStart?: (index: number) => void;
- onDragEnter?: (index: number) => void;
- onDrop?: (e: DragEvent, index: number) => void;
- onDragEnd?: () => void;
- isDragging?: boolean;
- isDragOver?: boolean;
-}
-
-export function dragAndDrop(options: DragAndDropOptions): Attachment {
- return (node: Element) => {
- const element = node as HTMLElement;
- const { index, onDragStart, onDragEnter, onDrop, onDragEnd, isDragging, isDragOver } = options;
-
- const isFormElement = (el: HTMLElement) => {
- return ['INPUT', 'TEXTAREA', 'SELECT'].includes(el.tagName);
- };
-
- const handleDragStart = (e: DragEvent) => {
- // Prevent drag if it originated from an input, textarea, or select element
- const target = e.target as HTMLElement;
- if (isFormElement(target)) {
- e.preventDefault();
- return;
- }
- onDragStart?.(index);
- };
-
- const handleDragEnter = () => {
- onDragEnter?.(index);
- };
-
- const handleDragOver = (e: DragEvent) => {
- e.preventDefault();
- };
-
- const handleDrop = (e: DragEvent) => {
- onDrop?.(e, index);
- };
-
- const handleDragEnd = () => {
- onDragEnd?.();
- };
-
- // Disable draggable when focusing on form elements (fixes Firefox input interaction)
- const handleFocusIn = (e: FocusEvent) => {
- const target = e.target as HTMLElement;
- if (isFormElement(target)) {
- element.setAttribute('draggable', 'false');
- }
- };
-
- const handleFocusOut = (e: FocusEvent) => {
- const target = e.target as HTMLElement;
- if (isFormElement(target)) {
- element.setAttribute('draggable', 'true');
- }
- };
-
- // Update classes based on drag state
- const updateClasses = (dragging: boolean, dragOver: boolean) => {
- // Remove all drag-related classes first
- element.classList.remove('opacity-50', 'border-light-500', 'border-solid');
-
- // Add back only the active ones
- if (dragging) {
- element.classList.add('opacity-50');
- }
-
- if (dragOver) {
- element.classList.add('border-light-500', 'border-solid');
- element.classList.remove('border-transparent');
- } else {
- element.classList.add('border-transparent');
- }
- };
-
- element.setAttribute('draggable', 'true');
- element.setAttribute('role', 'button');
- element.setAttribute('tabindex', '0');
-
- element.addEventListener('dragstart', handleDragStart);
- element.addEventListener('dragenter', handleDragEnter);
- element.addEventListener('dragover', handleDragOver);
- element.addEventListener('drop', handleDrop);
- element.addEventListener('dragend', handleDragEnd);
- element.addEventListener('focusin', handleFocusIn);
- element.addEventListener('focusout', handleFocusOut);
-
- updateClasses(isDragging || false, isDragOver || false);
-
- return () => {
- element.removeEventListener('dragstart', handleDragStart);
- element.removeEventListener('dragenter', handleDragEnter);
- element.removeEventListener('dragover', handleDragOver);
- element.removeEventListener('drop', handleDrop);
- element.removeEventListener('dragend', handleDragEnd);
- element.removeEventListener('focusin', handleFocusIn);
- element.removeEventListener('focusout', handleFocusOut);
- };
- };
-}
diff --git a/web/src/lib/components/asset-viewer/editor/transform-tool/CropPreset.svelte b/web/src/lib/components/asset-viewer/editor/transform-tool/CropPreset.svelte
deleted file mode 100644
index 9817870d93..0000000000
--- a/web/src/lib/components/asset-viewer/editor/transform-tool/CropPreset.svelte
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
diff --git a/web/src/lib/managers/edit/transform-manager.svelte.ts b/web/src/lib/managers/edit/transform-manager.svelte.ts
index 9704af420b..23ddc1f3ea 100644
--- a/web/src/lib/managers/edit/transform-manager.svelte.ts
+++ b/web/src/lib/managers/edit/transform-manager.svelte.ts
@@ -7,9 +7,6 @@ import { getDimensions } from '$lib/utils/asset-utils';
import { normalizeTransformEdits } from '$lib/utils/editor';
import { handleError } from '$lib/utils/handle-error';
-export type CropAspectRatio =
- '1:1' | '16:9' | '4:3' | '3:2' | '7:5' | '9:16' | '3:4' | '2:3' | '5:7' | 'free' | 'reset';
-
type Region = {
x: number;
y: number;
diff --git a/web/src/lib/managers/timeline-manager/types.ts b/web/src/lib/managers/timeline-manager/types.ts
index 3bd921a657..70a53368fd 100644
--- a/web/src/lib/managers/timeline-manager/types.ts
+++ b/web/src/lib/managers/timeline-manager/types.ts
@@ -47,11 +47,6 @@ export interface Viewport {
height: number;
}
-export type ViewportXY = Viewport & {
- x: number;
- y: number;
-};
-
export interface AddAsset {
type: 'add';
values: TimelineAsset[];
@@ -82,12 +77,6 @@ export type ScrubberMonth = {
title: string;
};
-export type TimelineManagerLayoutOptions = {
- rowHeight?: number;
- headerHeight?: number;
- gap?: number;
-};
-
export interface UpdateGeometryOptions {
invalidateHeight: boolean;
noDefer?: boolean;
diff --git a/web/src/lib/managers/timeline-manager/utils.svelte.ts b/web/src/lib/managers/timeline-manager/utils.svelte.ts
index efc94206ea..377f841f98 100644
--- a/web/src/lib/managers/timeline-manager/utils.svelte.ts
+++ b/web/src/lib/managers/timeline-manager/utils.svelte.ts
@@ -1,6 +1,5 @@
import type { TimelineAsset } from './types';
-export const assetSnapshot = (asset: TimelineAsset): TimelineAsset => $state.snapshot(asset);
export const assetsSnapshot = (assets: TimelineAsset[]) => assets.map((asset) => $state.snapshot(asset));
export function filterIsInOrNearViewport(items: T[]) {
diff --git a/web/src/lib/route.ts b/web/src/lib/route.ts
index d47340a34b..d0a1a3593d 100644
--- a/web/src/lib/route.ts
+++ b/web/src/lib/route.ts
@@ -38,12 +38,6 @@ const asQueryString = (
return items.length === 0 ? '' : `?${items.join('&')}`;
};
-const DOCS_BASE = 'https://docs.immich.app';
-
-export const Docs = {
- duplicates: () => `${DOCS_BASE}/features/duplicates-utility`,
-};
-
export const Route = {
// auth
login: (params?: { continue?: string; autoLaunch?: 0 | 1 }) => '/auth/login' + asQueryString(params),
diff --git a/web/src/lib/services/album.service.ts b/web/src/lib/services/album.service.ts
index fc09a11215..08b1245e66 100644
--- a/web/src/lib/services/album.service.ts
+++ b/web/src/lib/services/album.service.ts
@@ -286,16 +286,3 @@ export const handleDeleteAlbum = async (album: AlbumResponseDto, options?: { pro
export const handleDownloadAlbum = async (album: AlbumResponseDto) => {
await downloadArchive(`${album.albumName}.zip`, { albumId: album.id });
};
-
-export const handleConfirmAlbumDelete = async (album: AlbumResponseDto) => {
- const $t = await getFormatter();
- const confirmation =
- album.albumName.length > 0
- ? $t('album_delete_confirmation', { values: { album: album.albumName } })
- : $t('unnamed_album_delete_confirmation');
-
- const description = $t('album_delete_confirmation_description');
- const prompt = `${confirmation} ${description}`;
-
- return modalManager.showDialog({ prompt });
-};
diff --git a/web/src/lib/services/user-admin.service.ts b/web/src/lib/services/user-admin.service.ts
index 51b39b9d22..00807c84bd 100644
--- a/web/src/lib/services/user-admin.service.ts
+++ b/web/src/lib/services/user-admin.service.ts
@@ -151,10 +151,6 @@ export const handleRestoreUserAdmin = async (user: UserAdminResponseDto) => {
}
};
-export const handleNavigateUserAdmin = async (user: UserAdminResponseDto) => {
- await goto(`/admin/users/${user.id}`);
-};
-
// TODO move password reset server-side
const generatePassword = (length: number = 16) => {
let generatedPassword = '';
diff --git a/web/src/lib/stores/preferences.store.ts b/web/src/lib/stores/preferences.store.ts
index 2584312220..ccd07d529c 100644
--- a/web/src/lib/stores/preferences.store.ts
+++ b/web/src/lib/stores/preferences.store.ts
@@ -70,11 +70,6 @@ export interface PlacesViewSettings {
};
}
-export interface SidebarSettings {
- people: boolean;
- sharing: boolean;
-}
-
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',
diff --git a/web/src/lib/stores/slideshow.store.ts b/web/src/lib/stores/slideshow.store.ts
index 17ba266ee1..eb1ec6720e 100644
--- a/web/src/lib/stores/slideshow.store.ts
+++ b/web/src/lib/stores/slideshow.store.ts
@@ -24,12 +24,6 @@ export enum SlideshowMetadataOverlayMode {
Full = 'full',
}
-export const slideshowLookCssMapping: Record = {
- [SlideshowLook.Contain]: 'object-contain',
- [SlideshowLook.Cover]: 'object-cover',
- [SlideshowLook.BlurredBackground]: 'object-contain',
-};
-
function createSlideshowStore() {
const restartState = writable(false);
const stopState = writable(false);
diff --git a/web/src/lib/types.ts b/web/src/lib/types.ts
index f2451b1bc5..0d42b3cdc6 100644
--- a/web/src/lib/types.ts
+++ b/web/src/lib/types.ts
@@ -3,7 +3,6 @@ import type { ActionItem } from '@immich/ui';
import type { DateTime } from 'luxon';
import type { SvelteSet } from 'svelte/reactivity';
import { MediaType } from '$lib/constants';
-import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
export type LatLng = { lng: number; lat: number };
@@ -39,13 +38,6 @@ export enum OnboardingRole {
USER = 'user',
}
-export type AssetControlContext = {
- // Wrap assets in a function, because context isn't reactive.
- getAssets: () => TimelineAsset[]; // All assets includes partners' assets
- getOwnedAssets: () => TimelineAsset[]; // Only assets owned by the user
- clearSelect: () => void;
-};
-
export type SearchCameraFilter = {
make?: string;
model?: string;
diff --git a/web/src/lib/utils/actions.ts b/web/src/lib/utils/actions.ts
index f5733fd5b2..ed5011319c 100644
--- a/web/src/lib/utils/actions.ts
+++ b/web/src/lib/utils/actions.ts
@@ -12,7 +12,6 @@ export type OnUndoDelete = (assets: TimelineAsset[]) => void;
export type OnRestore = (ids: string[]) => void;
export type OnLink = (assets: { still: TimelineAsset; motion: TimelineAsset }) => void;
export type OnUnlink = (assets: { still: TimelineAsset; motion: TimelineAsset }) => void;
-export type OnAddToAlbum = (ids: string[], albumId: string) => void;
export type OnArchive = (ids: string[], visibility: AssetVisibility) => void;
export type OnFavorite = (ids: string[], favorite: boolean) => void;
export type OnStack = (result: StackResponse) => void;
diff --git a/web/src/lib/utils/date-time.ts b/web/src/lib/utils/date-time.ts
index 7cfeadb9dc..dd37776739 100644
--- a/web/src/lib/utils/date-time.ts
+++ b/web/src/lib/utils/date-time.ts
@@ -78,12 +78,3 @@ export const getAlbumDateRange = (album: { startDate?: string; endDate?: string
*/
export const asLocalTimeISO = (date: DateTime) =>
(date.setZone('utc', { keepLocalTime: true }) as DateTime).toISO();
-
-const days = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
-
-type DayOfWeek = (typeof days)[number];
-export const dayOfWeek = (day: DayOfWeek, options?: { locale?: string; style?: 'long' | 'short' | 'narrow' }) => {
- const fmt = new Intl.DateTimeFormat(options?.locale, { weekday: options?.style ?? 'long', timeZone: 'UTC' });
- // 2021-08-01 is a Sunday
- return fmt.format(new Date(Date.UTC(2021, 7, 1 + days.indexOf(day))));
-};
diff --git a/web/src/lib/utils/file-uploader.ts b/web/src/lib/utils/file-uploader.ts
index 10ed7b08d3..c926fd9cb6 100644
--- a/web/src/lib/utils/file-uploader.ts
+++ b/web/src/lib/utils/file-uploader.ts
@@ -20,27 +20,6 @@ import { ExecutorQueue } from '$lib/utils/executor-queue';
import { asQueryString } from '$lib/utils/shared-links';
import { handleError } from './handle-error';
-export const addDummyItems = () => {
- uploadAssetsStore.addItem({ id: 'asset-0', file: { name: 'asset0.jpg', size: 123_456 } as File });
- uploadAssetsStore.updateItem('asset-0', { state: UploadState.PENDING });
- uploadAssetsStore.addItem({ id: 'asset-1', file: { name: 'asset1.jpg', size: 123_456 } as File });
- uploadAssetsStore.updateItem('asset-1', { state: UploadState.STARTED });
- uploadAssetsStore.updateProgress('asset-1', 75, 100);
- uploadAssetsStore.addItem({ id: 'asset-2', file: { name: 'asset2.jpg', size: 123_456 } as File });
- uploadAssetsStore.updateItem('asset-2', { state: UploadState.ERROR, error: new Error('Internal server error') });
- uploadAssetsStore.addItem({ id: 'asset-3', file: { name: 'asset3.jpg', size: 123_456 } as File });
- uploadAssetsStore.updateItem('asset-3', { state: UploadState.DUPLICATED, assetId: 'asset-2' });
- uploadAssetsStore.addItem({ id: 'asset-4', file: { name: 'asset3.jpg', size: 123_456 } as File });
- uploadAssetsStore.updateItem('asset-4', { state: UploadState.DUPLICATED, assetId: 'asset-2', isTrashed: true });
- uploadAssetsStore.addItem({ id: 'asset-10', file: { name: 'asset3.jpg', size: 123_456 } as File });
- uploadAssetsStore.updateItem('asset-10', { state: UploadState.DONE });
- uploadAssetsStore.track('error');
- uploadAssetsStore.track('success');
- uploadAssetsStore.track('duplicate');
-};
-
-// addDummyItems();
-
export const uploadExecutionQueue = new ExecutorQueue({ concurrency: 2 });
type FilePickerParam = { multiple?: boolean; extensions?: string[] };
diff --git a/web/src/lib/utils/layout-utils.ts b/web/src/lib/utils/layout-utils.ts
index 141d201356..f781bde3f0 100644
--- a/web/src/lib/utils/layout-utils.ts
+++ b/web/src/lib/utils/layout-utils.ts
@@ -6,8 +6,6 @@ import { getAssetRatio } from '$lib/utils/asset-utils';
import { isTimelineAsset, isTimelineAssets } from '$lib/utils/timeline-util';
import { TUNABLES } from '$lib/utils/tunables';
-export type getJustifiedLayoutFromAssetsFunction = typeof getJustifiedLayoutFromAssets;
-
const useWasm = TUNABLES.LAYOUT.WASM;
export type CommonJustifiedLayout = {
@@ -115,13 +113,6 @@ export function justifiedLayout(assets: (TimelineAsset | AssetResponseDto)[], op
return new Adapter(result);
}
-export const emptyGeometry = () =>
- new Adapter({
- containerHeight: 0,
- widowCount: 0,
- boxes: [],
- });
-
export type CommonPosition = {
top: number;
left: number;
diff --git a/web/src/lib/utils/navigation.ts b/web/src/lib/utils/navigation.ts
index 2f88da1f43..9bba61d915 100644
--- a/web/src/lib/utils/navigation.ts
+++ b/web/src/lib/utils/navigation.ts
@@ -15,7 +15,6 @@ export const isPhotosRoute = (route?: string | null) => !!route?.startsWith('/(u
const isSharedLinkSlugRoute = (route?: string | null) => !!route?.startsWith('/(user)/s/[slug]');
export const isSharedLinkRoute = (route?: string | null) =>
!!route?.startsWith('/(user)/share/[key]') || isSharedLinkSlugRoute(route);
-export const isSearchRoute = (route?: string | null) => !!route?.startsWith('/(user)/search');
export const isAlbumsRoute = (route?: string | null) => !!route?.startsWith('/(user)/albums/[albumId=id]');
export const isPeopleRoute = (route?: string | null) => !!route?.startsWith('/(user)/people/[personId]');
export const isLockedFolderRoute = (route?: string | null) => !!route?.startsWith('/(user)/locked');
@@ -149,12 +148,6 @@ export const clearQueryParam = async (queryParam: string, url: URL) => {
await goto(url, { keepFocus: true });
};
-export const getQueryValue = (queryKey: string) => {
- const url = location.href;
- const urlObject = new URL(url);
- return urlObject.searchParams.get(queryKey);
-};
-
export const setQueryValue = async (queryKey: string, queryValue: string) => {
const url = location.href;
const urlObject = new URL(url);
diff --git a/web/src/lib/utils/thumbnail-util.ts b/web/src/lib/utils/thumbnail-util.ts
index 24bf377647..dc892c82cb 100644
--- a/web/src/lib/utils/thumbnail-util.ts
+++ b/web/src/lib/utils/thumbnail-util.ts
@@ -4,40 +4,6 @@ import type { TimelineAsset } from '$lib/managers/timeline-manager/types';
import { locale } from '$lib/stores/preferences.store';
import { fromTimelinePlainDateTime } from '$lib/utils/timeline-util';
-/**
- * Calculate thumbnail size based on number of assets and viewport width
- * @param assetCount Number of assets in the view
- * @param viewWidth viewport width
- * @returns thumbnail size
- */
-export function getThumbnailSize(assetCount: number, viewWidth: number): number {
- if (assetCount < 6) {
- return Math.min(320, Math.floor(viewWidth / assetCount - assetCount));
- }
-
- if (viewWidth > 600) {
- return viewWidth / 7 - 7;
- }
-
- if (viewWidth > 400) {
- return viewWidth / 4 - 6;
- }
-
- if (viewWidth > 300) {
- return viewWidth / 2 - 6;
- }
-
- if (viewWidth > 200) {
- return viewWidth / 2 - 6;
- }
-
- if (viewWidth > 100) {
- return viewWidth / 1 - 6;
- }
-
- return 300;
-}
-
export const getAltText = derived(t, ($t) => {
return (asset: TimelineAsset) => {
const date = fromTimelinePlainDateTime(asset.localDateTime).toJSDate().toLocaleString(get(locale), {
diff --git a/web/src/lib/utils/timeline-util.ts b/web/src/lib/utils/timeline-util.ts
index e1e717f45a..2d7af5bb60 100644
--- a/web/src/lib/utils/timeline-util.ts
+++ b/web/src/lib/utils/timeline-util.ts
@@ -33,36 +33,12 @@ export type ScrubberListener = (scrubberData: {
export const fromISODateTime = (isoDateTime: string, timeZone: string): DateTime =>
DateTime.fromISO(isoDateTime, { zone: timeZone, locale: get(locale) }) as DateTime;
-export const fromISODateTimeToObject = (isoDateTime: string, timeZone: string): TimelineDateTime =>
- (fromISODateTime(isoDateTime, timeZone) as DateTime).toObject();
-
// used for AssetResponseDto.localDateTime, amongst others
export const fromISODateTimeUTC = (isoDateTimeUtc: string) => fromISODateTime(isoDateTimeUtc, 'UTC');
export const fromISODateTimeUTCToObject = (isoDateTimeUtc: string): TimelineDateTime =>
(fromISODateTimeUTC(isoDateTimeUtc) as DateTime).toObject();
-// used to create equivalent of AssetResponseDto.localDateTime in UTC, but without timezone information
-export const fromISODateTimeTruncateTZToObject = (
- isoDateTimeUtc: string,
- timeZone: string | undefined,
-): TimelineDateTime =>
- (
- fromISODateTime(isoDateTimeUtc, timeZone ?? 'UTC').setZone('UTC', { keepLocalTime: true }) as DateTime
- ).toObject();
-
-// Used to derive a local date time from an ISO string and a UTC offset in hours
-export const fromISODateTimeWithOffsetToObject = (isoDateTimeUtc: string, utcOffsetHours: number): TimelineDateTime => {
- const utcDateTime = fromISODateTimeUTC(isoDateTimeUtc);
-
- // Apply the offset to get the local time
- // Note: offset is in hours (may be fractional), positive for east of UTC, negative for west
- const localDateTime = utcDateTime.plus({ hours: utcOffsetHours });
-
- // Return as plain object (keeping the local time but in UTC zone context)
- return (localDateTime.setZone('UTC', { keepLocalTime: true }) as DateTime).toObject();
-};
-
export const getTimes = (isoDateTimeUtc: string, localUtcOffsetHours: number) => {
const utcDateTime = fromISODateTimeUTC(isoDateTimeUtc);
const fileCreatedAt = (utcDateTime as DateTime).toObject();
diff --git a/web/src/test-data/factories/user-factory.ts b/web/src/test-data/factories/user-factory.ts
index 92d1510d40..7b56c275e2 100644
--- a/web/src/test-data/factories/user-factory.ts
+++ b/web/src/test-data/factories/user-factory.ts
@@ -1,16 +1,7 @@
import { faker } from '@faker-js/faker';
-import { UserAvatarColor, UserStatus, type UserAdminResponseDto, type UserResponseDto } from '@immich/sdk';
+import { UserAvatarColor, UserStatus, type UserAdminResponseDto } from '@immich/sdk';
import { Sync } from 'factory.ts';
-export const userFactory = Sync.makeFactory({
- id: Sync.each(() => faker.string.uuid()),
- email: Sync.each(() => faker.internet.email()),
- name: Sync.each(() => faker.person.fullName()),
- profileImagePath: '',
- avatarColor: UserAvatarColor.Primary,
- profileChangedAt: Sync.each(() => faker.date.recent().toISOString()),
-});
-
export const userAdminFactory = Sync.makeFactory({
id: Sync.each(() => faker.string.uuid()),
email: Sync.each(() => faker.internet.email()),