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

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