chore(web): remove unused code (#30768)

This commit is contained in:
Jason Rasmussen 2026-08-14 16:04:12 -04:00 committed by GitHub
parent 66d010381e
commit b19c4a591e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 1 additions and 329 deletions

View file

@ -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 };

View file

@ -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);
};
};
}

View file

@ -1,43 +0,0 @@
<script lang="ts">
import type { CropAspectRatio } from '$lib/managers/edit/transform-manager.svelte';
import { Button, Icon, type Color } from '@immich/ui';
interface Props {
size: {
icon: string;
name: CropAspectRatio;
viewBox: string;
rotate?: boolean;
};
selectedSize: CropAspectRatio;
rotateHorizontal: boolean;
selectType: (size: CropAspectRatio) => void;
}
let { size, selectedSize, rotateHorizontal, selectType }: Props = $props();
let isSelected = $derived(selectedSize === size.name);
let buttonColor = $derived<Color>(isSelected ? 'primary' : 'secondary');
let rotatedTitle = $derived((title: string, toRotate: boolean) => {
let sides = title.split(':');
if (toRotate) {
sides.reverse();
}
return sides.join(':');
});
let toRotate = $derived((def: boolean | undefined) => {
if (def === false) {
return false;
}
return (def && !rotateHorizontal) || (!def && rotateHorizontal);
});
</script>
<li>
<Button shape="round" color={buttonColor} class="flex-col gap-1" size="small" onclick={() => selectType(size.name)}>
<Icon size="1.75em" icon={size.icon} viewBox={size.viewBox} class={toRotate(size.rotate) ? 'rotate-90' : ''} />
<span>{rotatedTitle(size.name, rotateHorizontal)}</span>
</Button>
</li>

View file

@ -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;

View file

@ -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;

View file

@ -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<T extends { isInOrNearViewport: boolean }>(items: T[]) {

View file

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

View file

@ -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 });
};

View file

@ -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 = '';

View file

@ -70,11 +70,6 @@ export interface PlacesViewSettings {
};
}
export interface SidebarSettings {
people: boolean;
sharing: boolean;
}
export enum SortOrder {
Asc = 'asc',
Desc = 'desc',

View file

@ -24,12 +24,6 @@ export enum SlideshowMetadataOverlayMode {
Full = 'full',
}
export const slideshowLookCssMapping: Record<SlideshowLook, string> = {
[SlideshowLook.Contain]: 'object-contain',
[SlideshowLook.Cover]: 'object-cover',
[SlideshowLook.BlurredBackground]: 'object-contain',
};
function createSlideshowStore() {
const restartState = writable<boolean>(false);
const stopState = writable<boolean>(false);

View file

@ -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;

View file

@ -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;

View file

@ -78,12 +78,3 @@ export const getAlbumDateRange = (album: { startDate?: string; endDate?: string
*/
export const asLocalTimeISO = (date: DateTime<true>) =>
(date.setZone('utc', { keepLocalTime: true }) as DateTime<true>).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))));
};

View file

@ -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[] };

View file

@ -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;

View file

@ -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);

View file

@ -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), {

View file

@ -33,36 +33,12 @@ export type ScrubberListener = (scrubberData: {
export const fromISODateTime = (isoDateTime: string, timeZone: string): DateTime<true> =>
DateTime.fromISO(isoDateTime, { zone: timeZone, locale: get(locale) }) as DateTime<true>;
export const fromISODateTimeToObject = (isoDateTime: string, timeZone: string): TimelineDateTime =>
(fromISODateTime(isoDateTime, timeZone) as DateTime<true>).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<true>).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<true>
).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<true>).toObject();
};
export const getTimes = (isoDateTimeUtc: string, localUtcOffsetHours: number) => {
const utcDateTime = fromISODateTimeUTC(isoDateTimeUtc);
const fileCreatedAt = (utcDateTime as DateTime<true>).toObject();

View file

@ -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<UserResponseDto>({
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<UserAdminResponseDto>({
id: Sync.each(() => faker.string.uuid()),
email: Sync.each(() => faker.internet.email()),