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()),