chore(server,cli,web): housekeeping and stricter code style (#6751)

* add unicorn to eslint

* fix lint errors for cli

* fix merge

* fix album name extraction

* Update cli/src/commands/upload.command.ts

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>

* es2k23

* use lowercase os

* return undefined album name

* fix bug in asset response dto

* auto fix issues

* fix server code style

* es2022 and formatting

* fix compilation error

* fix test

* fix config load

* fix last lint errors

* set string type

* bump ts

* start work on web

* web formatting

* Fix UUIDParamDto as UUIDParamDto

* fix library service lint

* fix web errors

* fix errors

* formatting

* wip

* lints fixed

* web can now start

* alphabetical package json

* rename error

* chore: clean up

---------

Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Jonathan Jogenfors 2024-02-02 04:18:00 +01:00 committed by GitHub
parent e4d0560d49
commit f44fa45aa0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
218 changed files with 2471 additions and 1244 deletions

View file

@ -26,7 +26,7 @@ import {
common,
configuration,
} from '@immich/sdk';
import type { ApiParams } from './types';
import type { ApiParams as ApiParameters } from './types';
class ImmichApi {
public activityApi: ActivityApi;
@ -56,8 +56,8 @@ class ImmichApi {
return !!this.key;
}
constructor(params: configuration.ConfigurationParameters) {
this.config = new configuration.Configuration(params);
constructor(parameters: configuration.ConfigurationParameters) {
this.config = new configuration.Configuration(parameters);
this.activityApi = new ActivityApi(this.config);
this.albumApi = new AlbumApi(this.config);
@ -80,17 +80,17 @@ class ImmichApi {
this.trashApi = new TrashApi(this.config);
}
private createUrl(path: string, params?: Record<string, unknown>) {
const searchParams = new URLSearchParams();
for (const key in params) {
const value = params[key];
private createUrl(path: string, parameters?: Record<string, unknown>) {
const searchParameters = new URLSearchParams();
for (const key in parameters) {
const value = parameters[key];
if (value !== undefined && value !== null) {
searchParams.set(key, value.toString());
searchParameters.set(key, value.toString());
}
}
const url = new URL(path, common.DUMMY_BASE_URL);
url.search = searchParams.toString();
url.search = searchParameters.toString();
return (this.config.basePath || base.BASE_PATH) + common.toPathString(url);
}
@ -115,17 +115,17 @@ class ImmichApi {
this.config.basePath = baseUrl;
}
public getAssetFileUrl(...[assetId, isThumb, isWeb]: ApiParams<typeof AssetApiFp, 'serveFile'>) {
public getAssetFileUrl(...[assetId, isThumb, isWeb]: ApiParameters<typeof AssetApiFp, 'serveFile'>) {
const path = `/asset/file/${assetId}`;
return this.createUrl(path, { isThumb, isWeb, key: this.getKey() });
}
public getAssetThumbnailUrl(...[assetId, format]: ApiParams<typeof AssetApiFp, 'getAssetThumbnail'>) {
public getAssetThumbnailUrl(...[assetId, format]: ApiParameters<typeof AssetApiFp, 'getAssetThumbnail'>) {
const path = `/asset/thumbnail/${assetId}`;
return this.createUrl(path, { format, key: this.getKey() });
}
public getProfileImageUrl(...[userId]: ApiParams<typeof UserApiFp, 'getProfileImage'>) {
public getProfileImageUrl(...[userId]: ApiParameters<typeof UserApiFp, 'getProfileImage'>) {
const path = `/user/profile-image/${userId}`;
return this.createUrl(path);
}

View file

@ -1,7 +1,7 @@
import type { Configuration } from '@immich/sdk';
/* eslint-disable @typescript-eslint/no-explicit-any */
export type ApiFp = (configuration: Configuration) => Record<any, (...args: any) => any>;
export type ApiFp = (configuration: Configuration) => Record<any, (...arguments_: any) => any>;
export type OmitLast<T extends readonly unknown[]> = T extends readonly [...infer U, any?] ? U : [...T];

View file

@ -14,10 +14,10 @@
const deleteUser = async () => {
try {
const deletedUser = await api.userApi.deleteUser({ id: user.id });
if (deletedUser.data.deletedAt != null) {
dispatch('success');
} else {
if (deletedUser.data.deletedAt == undefined) {
dispatch('fail');
} else {
dispatch('success');
}
} catch (error) {
handleError(error, 'Unable to delete user');

View file

@ -18,7 +18,7 @@
} from '@mdi/js';
export let title: string;
export let subtitle: string | undefined = undefined;
export let subtitle: string | undefined;
export let jobCounts: JobCountsDto;
export let queueStatus: QueueStatusDto;
export let allowForceCommand = true;

View file

@ -131,12 +131,13 @@
jobs[jobId] = data;
switch (jobCommand.command) {
case JobCommand.Empty:
case JobCommand.Empty: {
notificationController.show({
message: `Cleared jobs for: ${title}`,
type: NotificationType.Info,
});
break;
}
}
} catch (error) {
handleError(error, `Command '${jobCommand.command}' failed for job: ${title}`);

View file

@ -12,7 +12,7 @@
const restoreUser = async () => {
const restoredUser = await api.userApi.restoreUser({ id: user.id });
if (restoredUser.data.deletedAt == null) {
if (restoredUser.data.deletedAt == undefined) {
dispatch('success');
} else {
dispatch('fail');

View file

@ -19,11 +19,7 @@
const dispatch = createEventDispatcher<{ save: void }>();
const handleReset = async (detail: SettingsEventType['reset']) => {
if (detail.default) {
await resetToDefault(detail.configKeys);
} else {
await reset(detail.configKeys);
}
await (detail.default ? resetToDefault(detail.configKeys) : reset(detail.configKeys));
};
const handleSave = async (update: Partial<SystemConfigDto>) => {
@ -47,7 +43,10 @@
const reset = async (configKeys: Array<keyof SystemConfigDto>) => {
const { data: resetConfig } = await api.systemConfigApi.getConfig();
config = configKeys.reduce((acc, key) => ({ ...acc, [key]: resetConfig[key] }), config);
for (const key of configKeys) {
config = { ...config, [key]: resetConfig[key] };
}
notificationController.show({
message: 'Reset settings to the recent saved settings',
@ -56,7 +55,9 @@
};
const resetToDefault = async (configKeys: Array<keyof SystemConfigDto>) => {
config = configKeys.reduce((acc, key) => ({ ...acc, [key]: defaultConfig[key] }), config);
for (const key of configKeys) {
config = { ...config, [key]: defaultConfig[key] };
}
notificationController.show({
message: 'Reset settings to default',

View file

@ -11,11 +11,7 @@
export let disabled = false;
function handleCheckboxChange(option: string) {
if (value.includes(option)) {
value = value.filter((item) => item !== option);
} else {
value = [...value, option];
}
value = value.includes(option) ? value.filter((item) => item !== option) : [...value, option];
}
</script>

View file

@ -17,7 +17,7 @@
const handleChange = (e: Event) => {
value = (e.target as HTMLInputElement).value;
if (number) {
value = parseInt(value);
value = Number.parseInt(value);
}
dispatch('select', value);
};

View file

@ -38,7 +38,7 @@
$: parsedTemplate = () => {
try {
return renderTemplate(config.storageTemplate.template);
} catch (error) {
} catch {
return 'error';
}
};

View file

@ -122,10 +122,10 @@ describe('AlbumCard component', () => {
const onClickHandler = vi.fn();
sut.component.$on('showalbumcontextmenu', onClickHandler);
const contextMenuBtnParent = sut.getByTestId('context-button-parent');
const contextMenuButtonParent = sut.getByTestId('context-button-parent');
// Mock getBoundingClientRect to return a bounding rectangle that will result in the expected position
contextMenuBtnParent.getBoundingClientRect = () => ({
contextMenuButtonParent.getBoundingClientRect = () => ({
x: 123,
y: 456,
width: 0,
@ -138,7 +138,7 @@ describe('AlbumCard component', () => {
});
await fireEvent(
contextMenuBtnParent,
contextMenuButtonParent,
new MouseEvent('click', {
clientX: 123,
clientY: 456,

View file

@ -25,7 +25,7 @@
const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
const loadHighQualityThumbnail = async (thubmnailId: string | null) => {
if (thubmnailId == null) {
if (thubmnailId == undefined) {
return;
}

View file

@ -83,11 +83,12 @@
}
if (!$showAssetViewer) {
switch (event.key) {
case 'Escape':
case 'Escape': {
if ($isMultiSelectState) {
assetInteractionStore.clearMultiselect();
}
return;
}
}
}
};

View file

@ -30,8 +30,8 @@
try {
const { data } = await api.userApi.getMyUserInfo();
currentUser = data;
} catch (e) {
handleError(e, 'Unable to refresh user');
} catch (error) {
handleError(error, 'Unable to refresh user');
}
});
@ -58,8 +58,8 @@
dispatch('remove', userId);
const message = userId === 'me' ? `Left ${album.albumName}` : `Removed ${selectedRemoveUser.name}`;
notificationController.show({ type: NotificationType.Info, message });
} catch (e) {
handleError(e, 'Unable to remove user');
} catch (error) {
handleError(error, 'Unable to remove user');
} finally {
selectedRemoveUser = null;
}

View file

@ -16,11 +16,7 @@
}>();
$: isSelected = (id: string): boolean | undefined => {
if (!selectedThumbnail && album.albumThumbnailAssetId == id) {
return true;
} else {
return selectedThumbnail?.id == id;
}
return !selectedThumbnail && album.albumThumbnailAssetId == id ? true : selectedThumbnail?.id == id;
};
</script>

View file

@ -28,9 +28,9 @@
users = data.filter((user) => !(user.deletedAt || user.id === album.ownerId));
// Remove the existed shared users from the album
album.sharedUsers.forEach((sharedUser) => {
for (const sharedUser of album.sharedUsers) {
users = users.filter((user) => user.id !== sharedUser.id);
});
}
});
const getSharedLinks = async () => {
@ -40,11 +40,9 @@
};
const handleSelect = (user: UserResponseDto) => {
if (selectedUsers.includes(user)) {
selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
} else {
selectedUsers = [...selectedUsers, user];
}
selectedUsers = selectedUsers.includes(user)
? selectedUsers.filter((selectedUser) => selectedUser.id !== user.id)
: [...selectedUsers, user];
};
const handleUnselect = (user: UserResponseDto) => {
@ -122,7 +120,7 @@
size="sm"
fullwidth
rounded="full"
disabled={!selectedUsers.length}
disabled={selectedUsers.length === 0}
on:click={() => dispatch('select', selectedUsers)}>Add</Button
>
</div>

View file

@ -66,7 +66,7 @@
close: void;
}>();
$: showDeleteReaction = Array(reactions.length).fill(false);
$: showDeleteReaction = Array.from({ length: reactions.length }).fill(false);
$: {
if (innerHeight && activityHeight) {
divHeight = innerHeight - activityHeight;
@ -198,7 +198,7 @@
{/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="flex items-start w-fit pt-[5px]" title="Delete comment">
<button on:click={() => (!showDeleteReaction[index] ? showOptionsMenu(index) : '')}>
<button on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}>
<Icon path={mdiDotsVertical} />
</button>
</div>
@ -244,7 +244,7 @@
{/if}
{#if reaction.user.id === user.id || albumOwnerId === user.id}
<div class="flex items-start w-fit" title="Delete like">
<button on:click={() => (!showDeleteReaction[index] ? showOptionsMenu(index) : '')}>
<button on:click={() => (showDeleteReaction[index] ? '' : showOptionsMenu(index))}>
<Icon path={mdiDotsVertical} />
</button>
</div>

View file

@ -145,11 +145,7 @@
albumId: album.id,
type: ReactionType.Like,
});
if (data.length > 0) {
isLiked = data[0];
} else {
isLiked = null;
}
isLiked = data.length > 0 ? data[0] : null;
} catch (error) {
handleError(error, "Can't get Favorite");
}
@ -238,8 +234,8 @@
try {
const { data } = await api.albumApi.getAllAlbums({ assetId: asset.id });
appearsInAlbums = data;
} catch (e) {
console.error('Error getting album that asset belong to', e);
} catch (error) {
console.error('Error getting album that asset belong to', error);
}
};
@ -260,40 +256,48 @@
switch (key) {
case 'a':
case 'A':
case 'A': {
if (shiftKey) {
toggleArchive();
}
return;
case 'ArrowLeft':
}
case 'ArrowLeft': {
navigateAssetBackward();
return;
case 'ArrowRight':
}
case 'ArrowRight': {
navigateAssetForward();
return;
}
case 'd':
case 'D':
case 'D': {
if (shiftKey) {
downloadFile(asset);
}
return;
case 'Delete':
}
case 'Delete': {
trashOrDelete(shiftKey);
return;
case 'Escape':
}
case 'Escape': {
if (isShowDeleteConfirmation) {
isShowDeleteConfirmation = false;
return;
}
closeViewer();
return;
case 'f':
}
case 'f': {
toggleFavorite();
return;
case 'i':
}
case 'i': {
isShowActivity = false;
$isShowDetail = !$isShowDetail;
return;
}
}
};
@ -383,8 +387,8 @@
message: 'Moved to trash',
type: NotificationType.Info,
});
} catch (e) {
handleError(e, 'Unable to trash asset');
} catch (error) {
handleError(error, 'Unable to trash asset');
}
};
@ -398,8 +402,8 @@
message: 'Permanently deleted asset',
type: NotificationType.Info,
});
} catch (e) {
handleError(e, 'Unable to delete asset');
} catch (error) {
handleError(error, 'Unable to delete asset');
} finally {
isShowDeleteConfirmation = false;
}
@ -537,11 +541,7 @@
const handleStackedAssetMouseEvent = (e: CustomEvent<{ isMouseOver: boolean }>, asset: AssetResponseDto) => {
const { isMouseOver } = e.detail;
if (isMouseOver) {
previewStackedAsset = asset;
} else {
previewStackedAsset = undefined;
}
previewStackedAsset = isMouseOver ? asset : undefined;
};
const handleUnstack = async () => {

View file

@ -108,10 +108,11 @@
}
const ctrl = event.ctrlKey;
switch (event.key) {
case 'Enter':
case 'Enter': {
if (ctrl && event.target === textArea) {
handleFocusOut();
}
}
}
};
@ -222,7 +223,7 @@
bind:this={textArea}
class="max-h-[500px]
w-full resize-none overflow-hidden border-b border-gray-500 bg-transparent text-base text-black outline-none transition-all focus:border-b-2 focus:border-immich-primary disabled:border-none dark:text-white dark:focus:border-immich-dark-primary"
placeholder={!isOwner ? '' : 'Add a description'}
placeholder={isOwner ? 'Add a description' : ''}
on:focusin={handleFocusIn}
on:focusout={handleFocusOut}
on:input={() => autoGrowHeight(textArea)}

View file

@ -20,9 +20,9 @@
dataUrl = URL.createObjectURL(data);
return dataUrl;
} else {
throw new Error('Invalid data format');
throw new TypeError('Invalid data format');
}
} catch (error) {
} catch {
errorMessage = 'Failed to load asset';
return '';
}

View file

@ -20,7 +20,7 @@
let assetData: string;
let abortController: AbortController;
let hasZoomed = false;
let copyImageToClipboard: (src: string) => Promise<Blob>;
let copyImageToClipboard: (source: string) => Promise<Blob>;
let canCopyImagesToClipboard: () => boolean;
$: if (imgElement) {
@ -90,8 +90,8 @@
message: 'Copied image to clipboard.',
timeout: 3000,
});
} catch (err) {
console.error('Error [photo-viewer]:', err);
} catch (error) {
console.error('Error [photo-viewer]:', error);
notificationController.show({
type: NotificationType.Error,
message: 'Copying image to clipboard failed.',

View file

@ -2,6 +2,7 @@
import { onMount, tick } from 'svelte';
import { fade } from 'svelte/transition';
import { thumbHashToDataURL } from 'thumbhash';
// eslint-disable-next-line unicorn/prefer-node-protocol
import { Buffer } from 'buffer';
import { mdiEyeOffOutline } from '@mdi/js';
import Icon from '$lib/components/elements/icon.svelte';

View file

@ -161,7 +161,7 @@
{#if asset.stackCount && showStackedIcon}
<div
class="absolute {asset.type == AssetTypeEnum.Image && asset.livePhotoVideoId == null
class="absolute {asset.type == AssetTypeEnum.Image && asset.livePhotoVideoId == undefined
? 'top-0 right-0'
: 'top-7 right-1'} z-20 flex place-items-center gap-1 text-xs font-medium text-white"
>

View file

@ -26,7 +26,7 @@
export let options: T[];
export let selectedOption = options[0];
export let render: (item: T) => string | RenderedOption = (item) => String(item);
export let render: (item: T) => string | RenderedOption = String;
type RenderedOption = {
title: string;
@ -54,13 +54,15 @@
const renderOption = (option: T): RenderedOption => {
const renderedOption = render(option);
switch (typeof renderedOption) {
case 'string':
case 'string': {
return { title: renderedOption };
default:
}
default: {
return {
title: renderedOption.title,
icon: renderedOption.icon,
};
}
}
};

View file

@ -47,8 +47,8 @@
img.src = data;
await new Promise<void>((resolve) => {
img.onload = () => resolve();
img.onerror = () => resolve();
img.addEventListener('load', () => resolve());
img.addEventListener('error', () => resolve());
});
image = img;
@ -56,13 +56,20 @@
if (image === null) {
return null;
}
const { boundingBoxX1: x1, boundingBoxX2: x2, boundingBoxY1: y1, boundingBoxY2: y2 } = face;
const {
boundingBoxX1: x1,
boundingBoxX2: x2,
boundingBoxY1: y1,
boundingBoxY2: y2,
imageWidth,
imageHeight,
} = face;
const coordinates = {
x1: (image.naturalWidth / face.imageWidth) * x1,
x2: (image.naturalWidth / face.imageWidth) * x2,
y1: (image.naturalHeight / face.imageHeight) * y1,
y2: (image.naturalHeight / face.imageHeight) * y2,
x1: (image.naturalWidth / imageWidth) * x1,
x2: (image.naturalWidth / imageWidth) * x2,
y1: (image.naturalHeight / imageHeight) * y1,
y2: (image.naturalHeight / imageHeight) * y2,
};
const faceWidth = coordinates.x2 - coordinates.x1;
@ -72,17 +79,17 @@
faceImage.src = image.src;
await new Promise((resolve) => {
faceImage.onload = resolve;
faceImage.onerror = () => resolve(null);
faceImage.addEventListener('load', resolve);
faceImage.addEventListener('error', () => resolve(null));
});
const canvas = document.createElement('canvas');
canvas.width = faceWidth;
canvas.height = faceHeight;
const ctx = canvas.getContext('2d');
if (ctx) {
ctx.drawImage(faceImage, coordinates.x1, coordinates.y1, faceWidth, faceHeight, 0, 0, faceWidth, faceHeight);
const context = canvas.getContext('2d');
if (context) {
context.drawImage(faceImage, coordinates.x1, coordinates.y1, faceWidth, faceHeight, 0, 0, faceWidth, faceHeight);
return canvas.toDataURL();
} else {

View file

@ -71,7 +71,7 @@
}}
>
<ImageThumbnail
border={potentialMergePeople.length !== 0}
border={potentialMergePeople.length > 0}
circle
shadow
url={api.getPeopleThumbnailUrl(personMerge2.id)}

View file

@ -34,10 +34,8 @@
people = peopleCopy;
return;
}
if (!force) {
if (people.length < maximumLengthSearchPeople && name.startsWith(searchWord)) {
return;
}
if (!force && people.length < maximumLengthSearchPeople && name.startsWith(searchWord)) {
return;
}
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);

View file

@ -72,8 +72,8 @@
allPeople = data.people;
const result = await api.faceApi.getFaces({ id: assetId });
peopleWithFaces = result.data;
selectedPersonToCreate = new Array<string | null>(peopleWithFaces.length);
selectedPersonToReassign = new Array<PersonResponseDto | null>(peopleWithFaces.length);
selectedPersonToCreate = Array.from({ length: peopleWithFaces.length });
selectedPersonToReassign = Array.from({ length: peopleWithFaces.length });
} catch (error) {
handleError(error, "Can't get faces");
} finally {
@ -106,20 +106,20 @@
selectedPersonToReassign.filter((person) => person !== null).length;
if (numberOfChanges > 0) {
try {
for (let i = 0; i < peopleWithFaces.length; i++) {
const personId = selectedPersonToReassign[i]?.id;
for (const [index, peopleWithFace] of peopleWithFaces.entries()) {
const personId = selectedPersonToReassign[index]?.id;
if (personId) {
await api.faceApi.reassignFacesById({
id: personId,
faceDto: { id: peopleWithFaces[i].id },
faceDto: { id: peopleWithFace.id },
});
} else if (selectedPersonToCreate[i]) {
} else if (selectedPersonToCreate[index]) {
const { data } = await api.personApi.createPerson();
numberOfPersonToCreate.push(data.id);
await api.faceApi.reassignFacesById({
id: data.id,
faceDto: { id: peopleWithFaces[i].id },
faceDto: { id: peopleWithFace.id },
});
}
}
@ -138,7 +138,7 @@
clearTimeout(loaderLoadingDoneTimeout);
dispatch('refresh');
} else {
automaticRefreshTimeout = setTimeout(() => dispatch('refresh'), 15000);
automaticRefreshTimeout = setTimeout(() => dispatch('refresh'), 15_000);
}
};

View file

@ -14,7 +14,7 @@
let confirmPassowrd = '';
let canCreateUser = false;
let quotaSize: number | undefined = undefined;
let quotaSize: number | undefined;
let isCreatingUser = false;
$: quotaSizeWarning = quotaSize && convertToBytes(Number(quotaSize), 'GiB') > $serverInfo.diskSizeRaw;
@ -69,11 +69,10 @@
error = 'Error create user account';
isCreatingUser = false;
}
} catch (e) {
error = 'Error create user account';
} catch (error) {
isCreatingUser = false;
console.log('[ERROR] registerUser', e);
console.log('[ERROR] registerUser', error);
notificationController.show({
message: `Error create new user, check console for more detail`,

View file

@ -70,8 +70,8 @@
if (status == 200) {
dispatch('resetPasswordSuccess');
}
} catch (e) {
console.error('Error reseting user password', e);
} catch (error) {
console.error('Error reseting user password', error);
notificationController.show({
message: 'Error reseting user password, check console for more details',
type: NotificationType.Error,

View file

@ -110,7 +110,7 @@
/>
{/if}
{#if editImportPath != null}
{#if editImportPath != undefined}
<LibraryImportPathForm
title="Edit Import Path"
submitText="Save"

View file

@ -109,7 +109,7 @@
/>
{/if}
{#if editExclusionPattern != null}
{#if editExclusionPattern != undefined}
<LibraryExclusionPatternForm
submitText="Save"
canDelete={true}

View file

@ -33,9 +33,9 @@
await oauth.login(window.location);
dispatch('success');
return;
} catch (e) {
console.error('Error [login-form] [oauth.callback]', e);
oauthError = (await getServerErrorMessage(e)) || 'Unable to complete OAuth login';
} catch (error) {
console.error('Error [login-form] [oauth.callback]', error);
oauthError = (await getServerErrorMessage(error)) || 'Unable to complete OAuth login';
oauthLoading = false;
}
}

View file

@ -16,7 +16,8 @@
import { tweened } from 'svelte/motion';
import { mdiChevronDown, mdiChevronLeft, mdiChevronRight, mdiChevronUp, mdiPause, mdiPlay } from '@mdi/js';
const parseIndex = (s: string | null, max: number | null) => Math.max(Math.min(parseInt(s ?? '') || 0, max ?? 0), 0);
const parseIndex = (s: string | null, max: number | null) =>
Math.max(Math.min(Number.parseInt(s ?? '') || 0, max ?? 0), 0);
$: memoryIndex = parseIndex($page.url.searchParams.get(QueryParameter.MEMORY_INDEX), $memoryStore?.length - 1);
$: assetIndex = parseIndex($page.url.searchParams.get(QueryParameter.ASSET_INDEX), currentMemory?.assets.length - 1);
@ -114,18 +115,19 @@
<div class="flex place-content-center place-items-center gap-2 overflow-hidden">
<CircleIconButton icon={paused ? mdiPlay : mdiPause} forceDark on:click={() => (paused = !paused)} />
{#each currentMemory.assets as _, i}
{#each currentMemory.assets as _, index}
<button
class="relative w-full py-2"
on:click={() => goto(`?${QueryParameter.MEMORY_INDEX}=${memoryIndex}&${QueryParameter.ASSET_INDEX}=${i}`)}
on:click={() =>
goto(`?${QueryParameter.MEMORY_INDEX}=${memoryIndex}&${QueryParameter.ASSET_INDEX}=${index}`)}
>
<span class="absolute left-0 h-[2px] w-full bg-gray-500" />
{#await resetPromise}
<span class="absolute left-0 h-[2px] bg-white" style:width={`${i < assetIndex ? 100 : 0}%`} />
<span class="absolute left-0 h-[2px] bg-white" style:width={`${index < assetIndex ? 100 : 0}%`} />
{:then}
<span
class="absolute left-0 h-[2px] bg-white"
style:width={`${i < assetIndex ? 100 : i > assetIndex ? 0 : $progress * 100}%`}
style:width={`${index < assetIndex ? 100 : index > assetIndex ? 0 : $progress * 100}%`}
/>
{/await}
</button>

View file

@ -26,7 +26,7 @@
const handleAddToNewAlbum = (albumName: string) => {
showAlbumPicker = false;
const assetIds = Array.from(getAssets()).map((asset) => asset.id);
const assetIds = [...getAssets()].map((asset) => asset.id);
api.albumApi.createAlbum({ createAlbumDto: { albumName, assetIds } }).then((response) => {
const { id, albumName } = response.data;
@ -43,7 +43,7 @@
const handleAddToAlbum = async (album: AlbumResponseDto) => {
showAlbumPicker = false;
const assetIds = Array.from(getAssets()).map((asset) => asset.id);
const assetIds = [...getAssets()].map((asset) => asset.id);
await addAssetsToAlbum(album.id, assetIds);
clearSelect();
};

View file

@ -28,7 +28,7 @@
loading = true;
try {
const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isArchived !== isArchived);
const assets = [...getOwnedAssets()].filter((asset) => asset.isArchived !== isArchived);
const ids = assets.map(({ id }) => id);
if (ids.length > 0) {

View file

@ -16,11 +16,11 @@
const { clearSelect, getOwnedAssets } = getAssetControlContext();
$: isAllVideos = Array.from(getOwnedAssets()).every((asset) => asset.type === AssetTypeEnum.Video);
$: isAllVideos = [...getOwnedAssets()].every((asset) => asset.type === AssetTypeEnum.Video);
const handleRunJob = async (name: AssetJobName) => {
try {
const ids = Array.from(getOwnedAssets()).map(({ id }) => id);
const ids = [...getOwnedAssets()].map(({ id }) => id);
await api.assetApi.runAssetJobs({ assetJobsDto: { assetIds: ids, name } });
notificationController.show({ message: api.getAssetJobMessage(name), type: NotificationType.Info });
clearSelect();

View file

@ -20,7 +20,7 @@
{#if showModal}
<CreateSharedLinkModal
assetIds={Array.from(getAssets()).map(({ id }) => id)}
assetIds={[...getAssets()].map(({ id }) => id)}
on:close={() => (showModal = false)}
on:escape={escape}
/>

View file

@ -32,9 +32,7 @@
const handleDelete = async () => {
loading = true;
const ids = Array.from(getOwnedAssets())
.filter((a) => !a.isExternal)
.map((a) => a.id);
const ids = [...getOwnedAssets()].filter((a) => !a.isExternal).map((a) => a.id);
await deleteAssets(force, onAssetDelete, ids);
clearSelect();
isShowConfirmation = false;

View file

@ -11,7 +11,7 @@
const { getAssets, clearSelect } = getAssetControlContext();
const handleDownloadFiles = async () => {
const assets = Array.from(getAssets());
const assets = [...getAssets()];
if (assets.length === 1) {
clearSelect();
await downloadFile(assets[0]);

View file

@ -28,7 +28,7 @@
loading = true;
try {
const assets = Array.from(getOwnedAssets()).filter((asset) => asset.isFavorite !== isFavorite);
const assets = [...getOwnedAssets()].filter((asset) => asset.isFavorite !== isFavorite);
const ids = assets.map(({ id }) => id);

View file

@ -11,7 +11,7 @@
import { mdiDeleteOutline } from '@mdi/js';
export let album: AlbumResponseDto;
export let onRemove: ((assetIds: string[]) => void) | undefined = undefined;
export let onRemove: ((assetIds: string[]) => void) | undefined;
export let menuItem = false;
const { getAssets, clearSelect } = getAssetControlContext();
@ -20,7 +20,7 @@
const removeFromAlbum = async () => {
try {
const ids = Array.from(getAssets()).map((a) => a.id);
const ids = [...getAssets()].map((a) => a.id);
const { data: results } = await api.albumApi.removeAssetFromAlbum({
id: album.id,
bulkIdsDto: { ids },
@ -38,8 +38,8 @@
});
clearSelect();
} catch (e) {
console.error('Error [album-viewer] [removeAssetFromAlbum]', e);
} catch (error) {
console.error('Error [album-viewer] [removeAssetFromAlbum]', error);
notificationController.show({
type: NotificationType.Error,
message: 'Error removing assets from album, check console for more details',

View file

@ -18,7 +18,7 @@
const { data: results } = await api.sharedLinkApi.removeSharedLinkAssets({
id: sharedLink.id,
assetIdsDto: {
assetIds: Array.from(getAssets()).map((asset) => asset.id),
assetIds: [...getAssets()].map((asset) => asset.id),
},
key: api.getKey(),
});

View file

@ -11,7 +11,7 @@
import { mdiHistory } from '@mdi/js';
import type { OnRestore } from '$lib/utils/actions';
export let onRestore: OnRestore | undefined = undefined;
export let onRestore: OnRestore | undefined;
const { getAssets, clearSelect } = getAssetControlContext();
@ -21,7 +21,7 @@
loading = true;
try {
const ids = Array.from(getAssets()).map((a) => a.id);
const ids = [...getAssets()].map((a) => a.id);
await api.trashApi.restoreAssets({ bulkIdsDto: { ids } });
onRestore?.(ids);
@ -31,8 +31,8 @@
});
clearSelect();
} catch (e) {
handleError(e, 'Error restoring assets');
} catch (error) {
handleError(error, 'Error restoring assets');
} finally {
loading = false;
}

View file

@ -28,8 +28,8 @@
}
selecting = false;
} catch (e) {
handleError(e, 'Error selecting all assets');
} catch (error) {
handleError(error, 'Error selecting all assets');
}
};
</script>

View file

@ -9,13 +9,13 @@
import { handleError } from '$lib/utils/handle-error';
import type { OnStack } from '$lib/utils/actions';
export let onStack: OnStack | undefined = undefined;
export let onStack: OnStack | undefined;
const { clearSelect, getOwnedAssets } = getAssetControlContext();
const handleStack = async () => {
try {
const assets = Array.from(getOwnedAssets());
const assets = [...getOwnedAssets()];
const parent = assets.at(0);
if (parent == undefined) {
@ -33,7 +33,7 @@
for (const asset of children) {
asset.stackParentId = parent?.id;
// Add grand-children's count to new parent
childrenCount += asset.stackCount == null ? 1 : asset.stackCount + 1;
childrenCount += asset.stackCount == undefined ? 1 : asset.stackCount + 1;
// Reset children stack info
asset.stackCount = null;
asset.stack = [];

View file

@ -48,13 +48,16 @@
$: geometry = (() => {
const geometry = [];
for (let group of assetsGroupByDate) {
const justifiedLayoutResult = justifiedLayout(group.map(getAssetRatio), {
boxSpacing: 2,
containerWidth: Math.floor(viewport.width),
containerPadding: 0,
targetRowHeightTolerance: 0.15,
targetRowHeight: 235,
});
const justifiedLayoutResult = justifiedLayout(
group.map((assetGroup) => getAssetRatio(assetGroup)),
{
boxSpacing: 2,
containerWidth: Math.floor(viewport.width),
containerPadding: 0,
targetRowHeightTolerance: 0.15,
targetRowHeight: 235,
},
);
geometry.push({
...justifiedLayoutResult,
containerWidth: calculateWidth(justifiedLayoutResult.boxes),

View file

@ -44,9 +44,7 @@
$: timelineY = element?.scrollTop || 0;
$: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
$: idsSelectedAssets = Array.from($selectedAssets)
.filter((a) => !a.isExternal)
.map((a) => a.id);
$: idsSelectedAssets = [...$selectedAssets].filter((a) => !a.isExternal).map((a) => a.id);
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
@ -86,20 +84,23 @@
if (!$showAssetViewer) {
switch (key) {
case 'Escape':
case 'Escape': {
dispatch('escape');
return;
case '?':
}
case '?': {
if (event.shiftKey) {
event.preventDefault();
showShortcuts = !showShortcuts;
}
return;
case '/':
}
case '/': {
event.preventDefault();
goto(AppRoute.EXPLORE);
return;
case 'Delete':
}
case 'Delete': {
if ($isMultiSelectState) {
let force = false;
if (shiftKey || !isTrashEnabled) {
@ -113,6 +114,7 @@
trashOrDelete(force);
}
return;
}
}
}
};
@ -124,8 +126,8 @@
};
function intersectedHandler(event: CustomEvent) {
const el = event.detail.container as HTMLElement;
const target = el.firstChild as HTMLElement;
const element_ = event.detail.container as HTMLElement;
const target = element_.firstChild as HTMLElement;
if (target) {
const bucketDate = target.id.split('_')[1];
assetStore.loadBucket(bucketDate, event.detail.position);
@ -160,24 +162,27 @@
switch (action) {
case removeAction:
case AssetAction.TRASH:
case AssetAction.DELETE:
case AssetAction.DELETE: {
// find the next asset to show or close the viewer
(await handleNext()) || (await handlePrevious()) || handleClose();
// delete after find the next one
assetStore.removeAsset(asset.id);
break;
}
case AssetAction.ARCHIVE:
case AssetAction.UNARCHIVE:
case AssetAction.FAVORITE:
case AssetAction.UNFAVORITE:
case AssetAction.UNFAVORITE: {
assetStore.updateAsset(asset);
break;
}
case AssetAction.ADD:
case AssetAction.ADD: {
assetStore.addAsset(asset);
break;
}
}
};
@ -392,7 +397,7 @@
<div class="mt-8 animate-pulse">
<div class="mb-2 h-4 w-24 rounded-full bg-immich-primary/20 dark:bg-immich-dark-primary/20" />
<div class="flex w-[120%] flex-wrap">
{#each Array(100) as _}
{#each Array.from({ length: 100 }) as _}
<div class="m-[1px] h-[10em] w-[16em] bg-immich-primary/20 dark:bg-immich-dark-primary/20" />
{/each}
</div>

View file

@ -25,7 +25,7 @@
setContext({
getAssets: () => assets,
getOwnedAssets: () =>
ownerId !== undefined ? new Set(Array.from(assets).filter((asset) => asset.ownerId === ownerId)) : assets,
ownerId === undefined ? assets : new Set([...assets].filter((asset) => asset.ownerId === ownerId)),
clearSelect,
});
</script>

View file

@ -69,10 +69,10 @@
{/if}
<div class="inline-block" bind:offsetWidth={innerWidth}>
{#each $memoryStore as memory, i (memory.title)}
{#each $memoryStore as memory, index (memory.title)}
<button
class="memory-card relative mr-8 inline-block aspect-video h-[215px] rounded-xl"
on:click={() => goto(`${AppRoute.MEMORY}?${QueryParameter.MEMORY_INDEX}=${i}`)}
on:click={() => goto(`${AppRoute.MEMORY}?${QueryParameter.MEMORY_INDEX}=${index}`)}
>
<img
class="h-full w-full rounded-xl object-cover"

View file

@ -38,11 +38,9 @@
const handleUploadAssets = async (files: File[] = []) => {
try {
let results: (string | undefined)[] = [];
if (!files || files.length === 0 || !Array.isArray(files)) {
results = await openFileUploadDialog(undefined);
} else {
results = await fileUploadHandler(files, undefined);
}
results = await (!files || files.length === 0 || !Array.isArray(files)
? openFileUploadDialog()
: fileUploadHandler(files));
const { data } = await api.sharedLinkApi.addSharedLinkAssets({
id: sharedLink.id,
assetIdsDto: {
@ -57,8 +55,8 @@
message: `Added ${added} assets`,
type: NotificationType.Info,
});
} catch (e) {
await handleError(e, 'Unable to add assets to shared link');
} catch (error) {
await handleError(error, 'Unable to add assets to shared link');
}
};

View file

@ -30,13 +30,12 @@
});
$: {
if (search.length > 0 && albums.length > 0) {
filteredAlbums = albums.filter((album) => {
return album.albumName.toLowerCase().includes(search.toLowerCase());
});
} else {
filteredAlbums = albums;
}
filteredAlbums =
search.length > 0 && albums.length > 0
? albums.filter((album) => {
return album.albumName.toLowerCase().includes(search.toLowerCase());
})
: albums;
}
const handleSelect = (album: AlbumResponseDto) => {

View file

@ -18,15 +18,15 @@
if (browser) {
const scrollTop = document.documentElement.scrollTop;
const scrollLeft = document.documentElement.scrollLeft;
window.onscroll = function () {
window.addEventListener('scroll', function () {
window.scrollTo(scrollLeft, scrollTop);
};
});
}
});
onDestroy(() => {
if (browser) {
window.onscroll = null;
window.addEventListener('scroll', () => {});
}
});
</script>

View file

@ -29,10 +29,10 @@
};
const handleConfirm = () => {
if (!point) {
dispatch('cancel');
} else {
if (point) {
dispatch('confirm', point);
} else {
dispatch('cancel');
}
};
</script>

View file

@ -66,7 +66,7 @@
const handleCreateSharedLink = async () => {
const expirationTime = getExpirationTimeInMillisecond();
const currentTime = new Date().getTime();
const currentTime = Date.now();
const expirationDate = expirationTime ? new Date(currentTime + expirationTime).toISOString() : undefined;
try {
@ -84,8 +84,8 @@
},
});
sharedLink = makeSharedLinkUrl($serverConfig.externalDomain, data.key);
} catch (e) {
handleError(e, 'Failed to create shared link');
} catch (error) {
handleError(error, 'Failed to create shared link');
}
};
@ -99,20 +99,27 @@
const getExpirationTimeInMillisecond = () => {
switch (expirationTime) {
case '30 minutes':
case '30 minutes': {
return 30 * 60 * 1000;
case '1 hour':
}
case '1 hour': {
return 60 * 60 * 1000;
case '6 hours':
}
case '6 hours': {
return 6 * 60 * 60 * 1000;
case '1 day':
}
case '1 day': {
return 24 * 60 * 60 * 1000;
case '7 days':
}
case '7 days': {
return 7 * 24 * 60 * 60 * 1000;
case '30 days':
}
case '30 days': {
return 30 * 24 * 60 * 60 * 1000;
default:
}
default: {
return 0;
}
}
};
@ -123,7 +130,7 @@
try {
const expirationTime = getExpirationTimeInMillisecond();
const currentTime = new Date().getTime();
const currentTime = Date.now();
const expirationDate: string | null = expirationTime
? new Date(currentTime + expirationTime).toISOString()
: null;
@ -146,8 +153,8 @@
});
dispatch('close');
} catch (e) {
handleError(e, 'Failed to edit shared link');
} catch (error) {
handleError(error, 'Failed to edit shared link');
}
};
</script>

View file

@ -7,7 +7,7 @@
export let fullWidth = false;
export let src = empty1Url;
const noop = () => undefined;
const noop = () => {};
$: handler = actionHandler || noop;
$: width = fullWidth ? 'w-full' : 'w-[50%]';

View file

@ -17,14 +17,14 @@
const selectAssetHandler = (event: CustomEvent) => {
const { asset }: { asset: AssetResponseDto } = event.detail;
let temp = new Set(selectedAssets);
let temporary = new Set(selectedAssets);
if (selectedAssets.has(asset)) {
temp.delete(asset);
temporary.delete(asset);
} else {
temp.add(asset);
temporary.add(asset);
}
selectedAssets = temp;
selectedAssets = temporary;
dispatch('select', { asset, selectedAssets });
};
</script>

View file

@ -35,15 +35,15 @@
const selectAssetHandler = (event: CustomEvent) => {
const { asset }: { asset: AssetResponseDto } = event.detail;
let temp = new Set(selectedAssets);
let temporary = new Set(selectedAssets);
if (selectedAssets.has(asset)) {
temp.delete(asset);
temporary.delete(asset);
} else {
temp.add(asset);
temporary.add(asset);
}
selectedAssets = temp;
selectedAssets = temporary;
};
const navigateAssetForward = () => {
@ -53,8 +53,8 @@
selectedAsset = assets[currentViewAssetIndex];
pushState(selectedAsset.id);
}
} catch (e) {
handleError(e, 'Cannot navigate to the next asset');
} catch (error) {
handleError(error, 'Cannot navigate to the next asset');
}
};
@ -65,8 +65,8 @@
selectedAsset = assets[currentViewAssetIndex];
pushState(selectedAsset.id);
}
} catch (e) {
handleError(e, 'Cannot navigate to previous asset');
} catch (error) {
handleError(error, 'Cannot navigate to previous asset');
}
};

View file

@ -35,6 +35,7 @@
let map: maplibregl.Map;
let marker: maplibregl.Marker | null = null;
// eslint-disable-next-line unicorn/prefer-top-level-await
$: style = (async () => {
const { data } = await api.systemConfigApi.getMapStyle({
theme: $mapSettings.allowDarkMode ? $colorTheme.value : Theme.LIGHT,
@ -60,7 +61,7 @@
}
const mapSource = map?.getSource('geojson') as GeoJSONSource;
mapSource.getClusterLeaves(clusterId, 10000, 0, (error, leaves) => {
mapSource.getClusterLeaves(clusterId, 10_000, 0, (error, leaves) => {
if (error) {
return;
}

View file

@ -30,10 +30,10 @@
const logOut = async () => {
resetSavedUser();
const { data } = await api.authenticationApi.logout();
if (!data.redirectUri.startsWith('/')) {
window.location.href = data.redirectUri;
} else {
if (data.redirectUri.startsWith('/')) {
goto(data.redirectUri);
} else {
window.location.href = data.redirectUri;
}
};
</script>

View file

@ -59,7 +59,7 @@
}
};
let removeNotificationTimeout: NodeJS.Timeout | undefined = undefined;
let removeNotificationTimeout: NodeJS.Timeout | undefined;
onMount(() => {
removeNotificationTimeout = setTimeout(discard, notificationInfo.timeout);

View file

@ -7,7 +7,7 @@ export enum NotificationType {
}
export class ImmichNotification {
id = new Date().getTime() + Math.random();
id = Date.now() + Math.random();
type!: NotificationType;
message!: string;
action!: NotificationAction;

View file

@ -4,21 +4,21 @@
/**
* Usage: <div use:portal={'css selector'}> or <div use:portal={document.body}>
*/
export function portal(el: HTMLElement, target: HTMLElement | string = 'body') {
let targetEl;
export function portal(element: HTMLElement, target: HTMLElement | string = 'body') {
let targetElement;
async function update(newTarget: HTMLElement | string) {
target = newTarget;
if (typeof target === 'string') {
targetEl = document.querySelector(target);
if (targetEl === null) {
targetElement = document.querySelector(target);
if (targetElement === null) {
await tick();
targetEl = document.querySelector(target);
targetElement = document.querySelector(target);
}
if (targetEl === null) {
if (targetElement === null) {
throw new Error(`No element found matching css selector: "${target}"`);
}
} else if (target instanceof HTMLElement) {
targetEl = target;
targetElement = target;
} else {
throw new TypeError(
`Unknown portal target type: ${
@ -26,13 +26,13 @@
}. Allowed types: string (CSS selector) or HTMLElement.`,
);
}
targetEl.appendChild(el);
el.hidden = false;
targetElement.append(element);
element.hidden = false;
}
function destroy() {
if (el.parentNode) {
el.parentNode.removeChild(el);
if (element.parentNode) {
element.remove();
}
}

View file

@ -26,18 +26,18 @@
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
if (!ctx) {
const context = canvas.getContext('2d');
if (!context) {
throw new Error('Could not get canvas context.');
}
ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
context.drawImage(img, 0, 0);
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData?.data;
if (!data) {
throw new Error('Could not get image data.');
}
for (let i = 0; i < data.length; i += 4) {
if (data[i + 3] < 255) {
for (let index = 0; index < data.length; index += 4) {
if (data[index + 3] < 255) {
return true;
}
}
@ -62,8 +62,8 @@
message: 'Profile picture set.',
timeout: 3000,
});
} catch (err) {
handleError(err, 'Error setting profile picture.');
} catch (error) {
handleError(error, 'Error setting profile picture.');
}
dispatch('close');
};

View file

@ -34,7 +34,7 @@
const calculateSegments = (buckets: AssetBucket[]) => {
let height = 0;
let prev: Segment;
let previous: Segment;
return buckets.map((bucket) => {
const segment = new Segment();
segment.count = bucket.assets.length;
@ -42,13 +42,13 @@
segment.timeGroup = bucket.bucketDate;
segment.date = fromLocalDateTime(segment.timeGroup);
if (prev?.date.year !== segment.date.year && height > MIN_YEAR_LABEL_DISTANCE) {
prev.hasLabel = true;
if (previous?.date.year !== segment.date.year && height > MIN_YEAR_LABEL_DISTANCE) {
previous.hasLabel = true;
height = 0;
}
height += segment.height;
prev = segment;
previous = segment;
return segment;
});
};

View file

@ -26,14 +26,14 @@
$savedSearchTerms = $savedSearchTerms.filter((item) => item !== value);
saveSearchTerm(value);
const params = new URLSearchParams({
const parameters = new URLSearchParams({
q: searchValue,
smart: smartSearch,
});
showBigSearchBar = false;
$isSearchEnabled = false;
goto(`${AppRoute.SEARCH}?${params}`, { invalidateAll: true });
goto(`${AppRoute.SEARCH}?${parameters}`, { invalidateAll: true });
}
const clearSearchTerm = (searchTerm: string) => {
@ -140,7 +140,7 @@
</div>
{/if}
{#each $savedSearchTerms as savedSearchTerm, i (i)}
{#each $savedSearchTerms as savedSearchTerm, index (index)}
<div
class="flex w-full items-center justify-between text-sm text-black hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-500/10"
>

View file

@ -26,8 +26,8 @@
}
showModal = true;
} catch (err) {
console.error('Error [VersionAnnouncementBox]:', err);
} catch (error) {
console.error('Error [VersionAnnouncementBox]:', error);
}
};
</script>

View file

@ -55,7 +55,7 @@
};
const isExpired = (expiresAt: string) => {
const now = new Date().getTime();
const now = Date.now();
const expiration = new Date(expiresAt).getTime();
return now > expiration;

View file

@ -34,9 +34,9 @@
<Icon path={mdiAndroid} size="40" />
{:else if device.deviceOS === 'iOS' || device.deviceOS === 'Mac OS'}
<Icon path={mdiApple} size="40" />
{:else if device.deviceOS.indexOf('Safari') !== -1}
{:else if device.deviceOS.includes('Safari')}
<Icon path={mdiAppleSafari} size="40" />
{:else if device.deviceOS.indexOf('Windows') !== -1}
{:else if device.deviceOS.includes('Windows')}
<Icon path={mdiMicrosoftWindows} size="40" />
{:else if device.deviceOS === 'Linux'}
<Icon path={mdiLinux} size="40" />

View file

@ -73,9 +73,9 @@
{#if otherDevices.length > 0}
<div class="mb-6">
<h3 class="mb-2 text-xs font-medium text-immich-primary dark:text-immich-dark-primary">OTHER DEVICES</h3>
{#each otherDevices as device, i}
{#each otherDevices as device, index}
<DeviceCard {device} on:delete={() => (deleteDevice = device)} />
{#if i !== otherDevices.length - 1}
{#if index !== otherDevices.length - 1}
<hr class="my-3" />
{/if}
{/each}

View file

@ -56,8 +56,8 @@
updateLibraryIndex = null;
showContextMenu = false;
for (let i = 0; i < dropdownOpen.length; i++) {
dropdownOpen[i] = false;
for (let index = 0; index < dropdownOpen.length; index++) {
dropdownOpen[index] = false;
}
};
@ -87,9 +87,9 @@
dropdownOpen.length = libraries.length;
for (let i = 0; i < libraries.length; i++) {
await refreshStats(i);
dropdownOpen[i] = false;
for (let index = 0; index < libraries.length; index++) {
await refreshStats(index);
dropdownOpen[index] = false;
}
}

View file

@ -22,16 +22,14 @@
// exclude partners from the list of users available for selection
const { data: partners } = await api.partnerApi.getPartners({ direction: 'shared-by' });
const partnerIds = partners.map((partner) => partner.id);
availableUsers = users.filter((user) => !partnerIds.includes(user.id));
const partnerIds = new Set(partners.map((partner) => partner.id));
availableUsers = users.filter((user) => !partnerIds.has(user.id));
});
const selectUser = (user: UserResponseDto) => {
if (selectedUsers.includes(user)) {
selectedUsers = selectedUsers.filter((selectedUser) => selectedUser.id !== user.id);
} else {
selectedUsers = [...selectedUsers, user];
}
selectedUsers = selectedUsers.includes(user)
? selectedUsers.filter((selectedUser) => selectedUser.id !== user.id)
: [...selectedUsers, user];
};
</script>

View file

@ -129,11 +129,13 @@
</tr>
</thead>
<tbody class="block w-full overflow-y-auto rounded-md border dark:border-immich-dark-gray">
{#each keys as key, i}
{#each keys as key, index}
{#key key.id}
<tr
class={`flex h-[80px] w-full place-items-center text-center dark:text-immich-dark-fg ${
i % 2 == 0 ? 'bg-immich-gray dark:bg-immich-dark-gray/75' : 'bg-immich-bg dark:bg-immich-dark-gray/50'
index % 2 == 0
? 'bg-immich-gray dark:bg-immich-dark-gray/75'
: 'bg-immich-bg dark:bg-immich-dark-gray/50'
}`}
>
<td class="w-1/3 text-ellipsis px-4 text-sm">{key.name}</td>

View file

@ -90,7 +90,7 @@ export class AssetStore {
setTimeout(() => {
this.pendingChanges.push(...changes);
this.processPendingChanges();
}, 1_000);
}, 1000);
}
connect() {
@ -124,19 +124,22 @@ export class AssetStore {
processPendingChanges = throttle(() => {
for (const { type, value } of this.pendingChanges) {
switch (type) {
case 'add':
case 'add': {
this.addAsset(value);
break;
}
case 'trash':
case 'trash': {
if (!this.options.isTrashed) {
this.removeAsset(value);
}
break;
}
case 'delete':
case 'delete': {
this.removeAsset(value);
break;
}
}
}
@ -174,7 +177,7 @@ export class AssetStore {
};
});
this.timelineHeight = this.buckets.reduce((acc, b) => acc + b.bucketHeight, 0);
this.timelineHeight = this.buckets.reduce((accumulator, b) => accumulator + b.bucketHeight, 0);
this.emit(false);
@ -199,7 +202,7 @@ export class AssetStore {
bucket.position = position;
if (bucket.assets.length !== 0) {
if (bucket.assets.length > 0) {
this.emit(false);
return;
}
@ -324,7 +327,9 @@ export class AssetStore {
}
async getRandomAsset(): Promise<AssetResponseDto | null> {
let index = Math.floor(Math.random() * this.buckets.reduce((acc, bucket) => acc + bucket.bucketCount, 0));
let index = Math.floor(
Math.random() * this.buckets.reduce((accumulator, bucket) => accumulator + bucket.bucketCount, 0),
);
for (const bucket of this.buckets) {
if (index < bucket.bucketCount) {
await this.loadBucket(bucket.bucketDate, BucketPosition.Unknown);
@ -356,17 +361,17 @@ export class AssetStore {
}
removeAsset(id: string) {
for (let i = 0; i < this.buckets.length; i++) {
const bucket = this.buckets[i];
for (let j = 0; j < bucket.assets.length; j++) {
const asset = bucket.assets[j];
for (let index = 0; index < this.buckets.length; index++) {
const bucket = this.buckets[index];
for (let index_ = 0; index_ < bucket.assets.length; index_++) {
const asset = bucket.assets[index_];
if (asset.id !== id) {
continue;
}
bucket.assets.splice(j, 1);
bucket.assets.splice(index_, 1);
if (bucket.assets.length === 0) {
this.buckets.splice(i, 1);
this.buckets.splice(index, 1);
}
this.emit(true);
@ -422,14 +427,14 @@ export class AssetStore {
this.assets = this.buckets.flatMap(({ assets }) => assets);
const assetToBucket: Record<string, AssetLookup> = {};
for (let i = 0; i < this.buckets.length; i++) {
const bucket = this.buckets[i];
if (bucket.assets.length !== 0) {
for (let index = 0; index < this.buckets.length; index++) {
const bucket = this.buckets[index];
if (bucket.assets.length > 0) {
bucket.bucketCount = bucket.assets.length;
}
for (let j = 0; j < bucket.assets.length; j++) {
const asset = bucket.assets[j];
assetToBucket[asset.id] = { bucket, bucketIndex: i, assetIndex: j };
for (let index_ = 0; index_ < bucket.assets.length; index_++) {
const asset = bucket.assets[index_];
assetToBucket[asset.id] = { bucket, bucketIndex: index, assetIndex: index_ };
}
}
this.assetToBucket = assetToBucket;

View file

@ -10,7 +10,7 @@ export interface DownloadProgress {
export const downloadAssets = writable<Record<string, DownloadProgress>>({});
export const isDownloading = derived(downloadAssets, ($downloadAssets) => {
if (Object.keys($downloadAssets).length == 0) {
if (Object.keys($downloadAssets).length === 0) {
return false;
}

View file

@ -15,10 +15,8 @@ export const handleToggleTheme = () => {
};
const initTheme = (): ThemeSetting => {
if (browser) {
if (!window.matchMedia('(prefers-color-scheme: dark)').matches) {
return { value: Theme.LIGHT, system: false };
}
if (browser && !window.matchMedia('(prefers-color-scheme: dark)').matches) {
return { value: Theme.LIGHT, system: false };
}
return { value: Theme.DARK, system: false };
};
@ -30,13 +28,9 @@ export const colorTheme = persisted<ThemeSetting>('color-theme', initialTheme, {
serializer: {
parse: (text: string): ThemeSetting => {
const parsedText: ThemeSetting = JSON.parse(text);
if (Object.values(Theme).includes(parsedText.value)) {
return parsedText;
} else {
return initTheme();
}
return Object.values(Theme).includes(parsedText.value) ? parsedText : initTheme();
},
stringify: (obj) => JSON.stringify(obj),
stringify: (object) => JSON.stringify(object),
},
});
@ -44,7 +38,7 @@ export const colorTheme = persisted<ThemeSetting>('color-theme', initialTheme, {
export const locale = persisted<string | undefined>('locale', undefined, {
serializer: {
parse: (text) => text,
stringify: (obj) => obj ?? '',
stringify: (object) => object ?? '',
},
});

View file

@ -65,8 +65,8 @@ function createUploadStore() {
});
};
const updateAsset = (id: string, partialObj: Partial<UploadAsset>) => {
updateAssetMap(id, (v) => ({ ...v, ...partialObj }));
const updateAsset = (id: string, partialObject: Partial<UploadAsset>) => {
updateAssetMap(id, (v) => ({ ...v, ...partialObject }));
};
const removeUploadAsset = (id: string) => {

View file

@ -55,8 +55,8 @@ export const openWebsocketConnection = async () => {
.on('on_config_update', () => loadConfig())
.on('on_new_release', (data) => websocketStore.onRelease.set(data))
.on('error', (e) => console.log('Websocket Error', e));
} catch (e) {
console.log('Cannot connect to websocket ', e);
} catch (error) {
console.log('Cannot connect to websocket', error);
}
};

View file

@ -19,7 +19,7 @@ export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids:
message: `${force ? 'Permanently deleted' : 'Trashed'} ${ids.length} assets`,
type: NotificationType.Info,
});
} catch (e) {
handleError(e, 'Error deleting assets');
} catch (error) {
handleError(error, 'Error deleting assets');
}
};

View file

@ -29,7 +29,7 @@ describe('get file extension from filename', () => {
describe('get asset filename', () => {
it('returns the filename including file extension', () => {
[
for (const { asset, result } of [
{
asset: {
originalFileName: 'filename',
@ -51,8 +51,8 @@ describe('get asset filename', () => {
},
result: 'new-filename.txt.jpg',
},
].forEach(({ asset, result }) => {
]) {
expect(getAssetFilename(asset as AssetResponseDto)).toEqual(result);
});
}
});
});

View file

@ -36,9 +36,9 @@ export const downloadBlob = (data: Blob, filename: string) => {
anchor.href = url;
anchor.download = filename;
document.body.appendChild(anchor);
document.body.append(anchor);
anchor.click();
document.body.removeChild(anchor);
anchor.remove();
URL.revokeObjectURL(url);
};
@ -57,14 +57,14 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
// TODO: prompt for big download
// const total = downloadInfo.totalSize;
for (let i = 0; i < downloadInfo.archives.length; i++) {
const archive = downloadInfo.archives[i];
const suffix = downloadInfo.archives.length === 1 ? '' : `+${i + 1}`;
for (let index = 0; index < downloadInfo.archives.length; index++) {
const archive = downloadInfo.archives[index];
const suffix = downloadInfo.archives.length === 1 ? '' : `+${index + 1}`;
const archiveName = fileName.replace('.zip', `${suffix}-${DateTime.now().toFormat('yyyy-LL-dd-HH-mm-ss')}.zip`);
let downloadKey = `${archiveName} `;
if (downloadInfo.archives.length > 1) {
downloadKey = `${archiveName} (${i + 1}/${downloadInfo.archives.length})`;
downloadKey = `${archiveName} (${index + 1}/${downloadInfo.archives.length})`;
}
const abort = new AbortController();
@ -81,12 +81,12 @@ export const downloadArchive = async (fileName: string, options: DownloadInfoDto
);
downloadBlob(data, archiveName);
} catch (e) {
handleError(e, 'Unable to download files');
} catch (error) {
handleError(error, 'Unable to download files');
downloadManager.clear(downloadKey);
return;
} finally {
setTimeout(() => downloadManager.clear(downloadKey), 5_000);
setTimeout(() => downloadManager.clear(downloadKey), 5000);
}
}
};
@ -140,11 +140,11 @@ export const downloadFile = async (asset: AssetResponseDto) => {
});
downloadBlob(data, filename);
} catch (e) {
handleError(e, `Error downloading ${filename}`);
} catch (error) {
handleError(error, `Error downloading ${filename}`);
downloadManager.clear(downloadKey);
} finally {
setTimeout(() => downloadManager.clear(downloadKey), 5_000);
setTimeout(() => downloadManager.clear(downloadKey), 5000);
}
}
};
@ -155,7 +155,7 @@ export const downloadFile = async (asset: AssetResponseDto) => {
*/
export function getFilenameExtension(filename: string): string {
const lastIndex = Math.max(0, filename.lastIndexOf('.'));
const startIndex = (lastIndex || Infinity) + 1;
const startIndex = (lastIndex || Number.POSITIVE_INFINITY) + 1;
return filename.slice(startIndex).toLowerCase();
}
@ -182,10 +182,8 @@ export function getAssetRatio(asset: AssetResponseDto) {
let height = asset.exifInfo?.exifImageHeight || 235;
let width = asset.exifInfo?.exifImageWidth || 235;
const orientation = Number(asset.exifInfo?.orientation);
if (orientation) {
if (isRotated90CW(orientation) || isRotated270CW(orientation)) {
[width, height] = [height, width];
}
if (orientation && (isRotated90CW(orientation) || isRotated270CW(orientation))) {
[width, height] = [height, width];
}
return { width, height };
}
@ -204,21 +202,22 @@ export function isWebCompatibleImage(asset: AssetResponseDto): boolean {
export const getAssetType = (type: AssetTypeEnum) => {
switch (type) {
case 'IMAGE':
case 'IMAGE': {
return 'Photo';
case 'VIDEO':
}
case 'VIDEO': {
return 'Video';
default:
}
default: {
return 'Asset';
}
}
};
export const getSelectedAssets = (assets: Set<AssetResponseDto>, user: UserResponseDto | null): string[] => {
const ids = Array.from(assets)
.filter((a) => !a.isExternal && user && a.ownerId === user.id)
.map((a) => a.id);
const ids = [...assets].filter((a) => !a.isExternal && user && a.ownerId === user.id).map((a) => a.id);
const numberOfIssues = Array.from(assets).filter((a) => a.isExternal || (user && a.ownerId !== user.id)).length;
const numberOfIssues = [...assets].filter((a) => a.isExternal || (user && a.ownerId !== user.id)).length;
if (numberOfIssues > 0) {
notificationController.show({
message: `Can't change metadata of ${numberOfIssues} asset${numberOfIssues > 1 ? 's' : ''}`,

View file

@ -11,7 +11,7 @@ export function convertToBytes(size: number, unit: string): number {
let bytes = 0;
if (unit === 'GiB') {
bytes = size * 1073741824;
bytes = size * 1_073_741_824;
}
return bytes;
@ -30,7 +30,7 @@ export function convertFromBytes(bytes: number, unit: string): number {
let size = 0;
if (unit === 'GiB') {
size = bytes / 1073741824;
size = bytes / 1_073_741_824;
}
return size;

View file

@ -22,7 +22,7 @@ export function getBytesWithUnit(bytes: number, maxPrecision = 1): [number, stri
}
}
remainder = parseFloat(remainder.toFixed(maxPrecision));
remainder = Number.parseFloat(remainder.toFixed(maxPrecision));
return [remainder, units[magnitude]];
}

View file

@ -5,12 +5,15 @@ export const getContextMenuPosition = (event: MouseEvent, align: Align = 'middle
const box = ((currentTarget || target) as HTMLElement)?.getBoundingClientRect();
if (box) {
switch (align) {
case 'middle':
case 'middle': {
return { x: box.x + box.width / 2, y: box.y + box.height / 2 };
case 'top-left':
}
case 'top-left': {
return { x: box.x, y: box.y };
case 'top-right':
}
case 'top-right': {
return { x: box.x + box.width, y: box.y };
}
}
}

View file

@ -26,7 +26,9 @@ export class ExecutorQueue {
const v = concurrency - this.running;
if (v > 0) {
[...new Array(this._concurrency)].forEach(() => this.tryRun());
for (let i = 0; i < v; i++) {
this.tryRun();
}
}
}
@ -38,8 +40,8 @@ export class ExecutorQueue {
this.running++;
const result = task();
resolve(await result);
} catch (e) {
reject(e);
} catch (error) {
reject(error);
} finally {
this.taskFinished();
}

View file

@ -17,7 +17,7 @@ const getExtensions = async () => {
return _extensions;
};
export const openFileUploadDialog = async (albumId: string | undefined = undefined) => {
export const openFileUploadDialog = async (albumId?: string | undefined) => {
const extensions = await getExtensions();
return new Promise<(string | undefined)[]>((resolve, reject) => {
@ -27,7 +27,7 @@ export const openFileUploadDialog = async (albumId: string | undefined = undefin
fileSelector.type = 'file';
fileSelector.multiple = true;
fileSelector.accept = extensions.join(',');
fileSelector.onchange = async (e: Event) => {
fileSelector.addEventListener('change', async (e: Event) => {
const target = e.target as HTMLInputElement;
if (!target.files) {
return;
@ -35,12 +35,12 @@ export const openFileUploadDialog = async (albumId: string | undefined = undefin
const files = Array.from(target.files);
resolve(fileUploadHandler(files, albumId));
};
});
fileSelector.click();
} catch (e) {
console.log('Error selecting file', e);
reject(e);
} catch (error) {
console.log('Error selecting file', error);
reject(error);
}
});
};
@ -50,7 +50,7 @@ export const fileUploadHandler = async (files: File[], albumId: string | undefin
const promises = [];
for (const file of files) {
const name = file.name.toLowerCase();
if (extensions.some((ext) => name.endsWith(ext))) {
if (extensions.some((extension) => name.endsWith(extension))) {
uploadAssetsStore.addNewUploadAsset({ id: getDeviceAssetId(file), file, albumId });
promises.push(uploadExecutionQueue.addTask(() => fileUploader(file, albumId)));
}

View file

@ -6,27 +6,21 @@ export const searchNameLocal = (
slice: number,
personId?: string,
): PersonResponseDto[] => {
return name.indexOf(' ') >= 0
return name.includes(' ')
? people
.filter((person: PersonResponseDto) => {
if (personId) {
return person.name.toLowerCase().startsWith(name.toLowerCase()) && person.id !== personId;
} else {
return person.name.toLowerCase().startsWith(name.toLowerCase());
}
return personId
? person.name.toLowerCase().startsWith(name.toLowerCase()) && person.id !== personId
: person.name.toLowerCase().startsWith(name.toLowerCase());
})
.slice(0, slice)
: people
.filter((person: PersonResponseDto) => {
const nameParts = person.name.split(' ');
if (personId) {
return (
nameParts.some((splitName) => splitName.toLowerCase().startsWith(name.toLowerCase())) &&
person.id !== personId
);
} else {
return nameParts.some((splitName) => splitName.toLowerCase().startsWith(name.toLowerCase()));
}
return personId
? nameParts.some((splitName) => splitName.toLowerCase().startsWith(name.toLowerCase())) &&
person.id !== personId
: nameParts.some((splitName) => splitName.toLowerCase().startsWith(name.toLowerCase()));
})
.slice(0, slice);
};

View file

@ -14,7 +14,7 @@ describe('converting time to seconds', () => {
});
it('parses hhh:mm:ss.SSS correctly', () => {
expect(timeToSeconds('100:02:03.456')).toBeCloseTo(360123.456);
expect(timeToSeconds('100:02:03.456')).toBeCloseTo(360_123.456);
});
it('ignores ignores double milliseconds hh:mm:ss.SSS.SSSSSS', () => {

View file

@ -227,11 +227,8 @@
};
const handleChangeListMode = () => {
if ($albumViewSettings.view === AlbumViewMode.Cover) {
$albumViewSettings.view = AlbumViewMode.List;
} else {
$albumViewSettings.view = AlbumViewMode.Cover;
}
$albumViewSettings.view =
$albumViewSettings.view === AlbumViewMode.Cover ? AlbumViewMode.List : AlbumViewMode.Cover;
};
</script>
@ -285,14 +282,14 @@
</div>
</LinkButton>
</div>
{#if $albums.length !== 0}
{#if $albums.length > 0}
<!-- Album Card -->
{#if $albumViewSettings.view === AlbumViewMode.Cover}
<div class="grid grid-cols-[repeat(auto-fill,minmax(14rem,1fr))]">
{#each $albums as album, idx (album.id)}
{#each $albums as album, index (album.id)}
<a data-sveltekit-preload-data="hover" href="{AppRoute.ALBUMS}/{album.id}" animate:flip={{ duration: 200 }}>
<AlbumCard
preload={idx < 20}
preload={index < 20}
{album}
on:showalbumcontextmenu={(e) => showAlbumContextMenu(e.detail, album)}
/>

View file

@ -111,14 +111,10 @@
const { selectedAssets: timelineSelected } = timelineInteractionStore;
$: isOwned = $user.id == album.ownerId;
$: isAllUserOwned = Array.from($selectedAssets).every((asset) => asset.ownerId === $user.id);
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
$: isAllUserOwned = [...$selectedAssets].every((asset) => asset.ownerId === $user.id);
$: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite);
$: {
if (isShowActivity) {
assetGridWidth = globalWidth - (globalWidth < 768 ? 360 : 460);
} else {
assetGridWidth = globalWidth;
}
assetGridWidth = isShowActivity ? globalWidth - (globalWidth < 768 ? 360 : 460) : globalWidth;
}
$: showActivityStatus =
album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
@ -157,7 +153,7 @@
message: `Activity is ${album.isActivityEnabled ? 'enabled' : 'disabled'}`,
});
} catch (error) {
handleError(error, `Can't ${!album.isActivityEnabled ? 'enable' : 'disable'} activity`);
handleError(error, `Can't ${album.isActivityEnabled ? 'disable' : 'enable'} activity`);
}
};
@ -224,10 +220,11 @@
}
const ctrl = event.ctrlKey;
switch (event.key) {
case 'Enter':
case 'Enter': {
if (ctrl && event.target === textArea) {
textArea.blur();
}
}
}
};
@ -302,7 +299,7 @@
};
const handleAddAssets = async () => {
const assetIds = Array.from($timelineSelected).map((asset) => asset.id);
const assetIds = [...$timelineSelected].map((asset) => asset.id);
try {
const { data: results } = await api.albumApi.addAssetsToAlbum({
@ -352,7 +349,7 @@
const { data } = await api.albumApi.addUsersToAlbum({
id: album.id,
addUsersDto: {
sharedUserIds: Array.from(users).map(({ id }) => id),
sharedUserIds: [...users].map(({ id }) => id),
},
});
@ -373,8 +370,8 @@
try {
await refreshAlbum();
viewMode = album.sharedUsers.length > 1 ? ViewMode.SELECT_USERS : ViewMode.VIEW;
} catch (e) {
handleError(e, 'Error deleting share users');
} catch (error) {
handleError(error, 'Error deleting share users');
}
};

View file

@ -20,7 +20,9 @@ describe('Albums BLoC', () => {
afterEach(() => {
const notifications = get(notificationController.notificationList);
notifications.forEach((notification) => notificationController.removeNotificationById(notification.id));
for (const notification of notifications) {
notificationController.removeNotificationById(notification.id);
}
});
it('inits with provided albums', () => {

View file

@ -3,10 +3,10 @@ import { notificationController, NotificationType } from '$lib/components/shared
import { type AlbumResponseDto, api } from '@api';
import { derived, get, writable } from 'svelte/store';
type AlbumsProps = { albums: AlbumResponseDto[] };
type AlbumsProperties = { albums: AlbumResponseDto[] };
export const useAlbums = (props: AlbumsProps) => {
const albums = writable([...props.albums]);
export const useAlbums = (properties: AlbumsProperties) => {
const albums = writable([...properties.albums]);
const contextMenuPosition = writable<OnShowContextMenuDetail>({ x: 0, y: 0 });
const contextMenuTargetAlbum = writable<AlbumResponseDto | undefined>();
const isShowContextMenu = derived(contextMenuTargetAlbum, ($selectedAlbum) => !!$selectedAlbum);

View file

@ -24,7 +24,7 @@
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
$: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite);
</script>
{#if $isMultiSelectState}

View file

@ -26,7 +26,7 @@
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllArchive = Array.from($selectedAssets).every((asset) => asset.isArchived);
$: isAllArchive = [...$selectedAssets].every((asset) => asset.isArchived);
</script>
<!-- Multiselection mode app bar -->

View file

@ -19,7 +19,7 @@
const assetStore = new AssetStore({ userId: data.partner.id, isArchived: false, withStacked: true });
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
const { isMultiSelectState, selectedAssets, clearMultiselect } = assetInteractionStore;
onDestroy(() => {
assetInteractionStore.clearMultiselect();
@ -28,7 +28,7 @@
<main class="grid h-screen bg-immich-bg pt-18 dark:bg-immich-dark-bg">
{#if $isMultiSelectState}
<AssetSelectControlBar assets={$selectedAssets} clearSelect={assetInteractionStore.clearMultiselect}>
<AssetSelectControlBar assets={$selectedAssets} clearSelect={clearMultiselect}>
<CreateSharedLink />
<AssetSelectContextMenu icon={mdiPlus} title="Add">
<AddToAlbum />

View file

@ -90,9 +90,10 @@
return;
}
switch (event.key) {
case 'Escape':
case 'Escape': {
handleCloseClick();
return;
}
}
};
@ -288,10 +289,8 @@
}
return;
}
if (!force) {
if (people.length < maximumLengthSearchPeople && searchName.startsWith(searchWord)) {
return;
}
if (!force && people.length < maximumLengthSearchPeople && searchName.startsWith(searchWord)) {
return;
}
const timeout = setTimeout(() => (isSearchingPeople = true), timeBeforeShowLoadingSpinner);
@ -417,7 +416,7 @@
</FullScreenModal>
{/if}
<UserPageLayout title="People" description={countTotalPeople !== 0 ? `(${countTotalPeople.toString()})` : undefined}>
<UserPageLayout title="People" description={countTotalPeople === 0 ? undefined : `(${countTotalPeople.toString()})`}>
<svelte:fragment slot="buttons">
{#if countTotalPeople > 0}
<div class="flex gap-2 items-center justify-center">
@ -445,11 +444,11 @@
{#if countVisiblePeople > 0}
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-9 gap-1">
{#each people as person, idx (person.id)}
{#each people as person, index (person.id)}
{#if !person.isHidden && (searchName ? searchedPeopleLocal.some((searchedPerson) => searchedPerson.id === person.id) : true)}
<PeopleCard
{person}
preload={idx < 20}
preload={index < 20}
on:change-name={() => handleChangeName(person)}
on:set-birth-date={() => handleSetBirthDate(person)}
on:merge-people={() => handleMergePeople(person)}
@ -519,7 +518,7 @@
screenHeight={innerHeight}
>
<div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 xl:grid-cols-7 2xl:grid-cols-9 gap-1">
{#each people as person, idx (person.id)}
{#each people as person, index (person.id)}
<button
class="relative"
on:click={() => (person.isHidden = !person.isHidden)}
@ -527,7 +526,7 @@
on:mouseleave={() => (eyeColorMap[person.id] = 'white')}
>
<ImageThumbnail
preload={searchName !== '' || idx < 20}
preload={searchName !== '' || index < 20}
bind:hidden={person.isHidden}
shadow
url={api.getPeopleThumbnailUrl(person.id)}

View file

@ -108,14 +108,14 @@
isSearchingPeople = false;
};
$: isAllArchive = Array.from($selectedAssets).every((asset) => asset.isArchived);
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
$: isAllArchive = [...$selectedAssets].every((asset) => asset.isArchived);
$: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite);
$: $onPersonThumbnail === data.person.id &&
(thumbnailData = api.getPeopleThumbnailUrl(data.person.id) + `?now=${Date.now()}`);
$: {
if (people) {
suggestedPeople = !name ? [] : searchNameLocal(name, people, 5, data.person.id);
suggestedPeople = name ? searchNameLocal(name, people, 5, data.person.id) : [];
}
}
@ -158,7 +158,7 @@
});
const handleUnmerge = () => {
$assetStore.removeAssets(Array.from($selectedAssets).map((a) => a.id));
$assetStore.removeAssets([...$selectedAssets].map((a) => a.id));
assetInteractionStore.clearMultiselect();
viewMode = ViewMode.VIEW_ASSETS;
};
@ -352,7 +352,7 @@
{#if viewMode === ViewMode.UNASSIGN_ASSETS}
<UnMergeFaceSelector
assetIds={Array.from($selectedAssets).map((a) => a.id)}
assetIds={[...$selectedAssets].map((a) => a.id)}
personAssets={data.person}
on:close={() => (viewMode = ViewMode.VIEW_ASSETS)}
on:confirm={handleUnmerge}

View file

@ -31,7 +31,7 @@
const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
$: isAllFavorite = [...$selectedAssets].every((asset) => asset.isFavorite);
const handleEscape = () => {
if ($showAssetViewer) {

View file

@ -56,7 +56,7 @@
}
if (!$showAssetViewer) {
switch (event.key) {
case 'Escape':
case 'Escape': {
if (isMultiSelectionMode) {
selectedAssets = new Set();
return;
@ -66,6 +66,7 @@
}
$preventRaceConditionSearchBar = false;
return;
}
}
}
};
@ -96,8 +97,8 @@
let selectedAssets: Set<AssetResponseDto> = new Set();
$: isMultiSelectionMode = selectedAssets.size > 0;
$: isAllArchived = Array.from(selectedAssets).every((asset) => asset.isArchived);
$: isAllFavorite = Array.from(selectedAssets).every((asset) => asset.isFavorite);
$: isAllArchived = [...selectedAssets].every((asset) => asset.isArchived);
$: isAllFavorite = [...selectedAssets].every((asset) => asset.isFavorite);
$: searchResultAssets = data.results?.assets.items;
const onAssetDelete = (assetId: string) => {
@ -140,14 +141,14 @@
<section class="relative mb-12 bg-immich-bg pt-32 dark:bg-immich-dark-bg">
<section class="immich-scrollbar relative overflow-y-auto">
{#if albums && albums.length}
{#if albums && albums.length > 0}
<section>
<div class="ml-6 text-4xl font-medium text-black/70 dark:text-white/80">ALBUMS</div>
<div class="grid grid-cols-[repeat(auto-fill,minmax(14rem,1fr))]">
{#each albums as album, idx (album.id)}
{#each albums as album, index (album.id)}
<a data-sveltekit-preload-data="hover" href={`albums/${album.id}`} animate:flip={{ duration: 200 }}>
<AlbumCard
preload={idx < 20}
preload={index < 20}
{album}
isSharingView={false}
showItemCount={false}

View file

@ -11,8 +11,8 @@
import { user } from '$lib/stores/user.store';
export let data: PageData;
let { sharedLink, passwordRequired, sharedLinkKey: key } = data;
let { title, description } = data.meta;
let { sharedLink, passwordRequired, sharedLinkKey: key, meta } = data;
let { title, description } = meta;
let isOwned = $user ? $user.id === sharedLink?.userId : false;
let password = '';

Some files were not shown because too many files have changed in this diff Show more