mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
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:
parent
e4d0560d49
commit
f44fa45aa0
218 changed files with 2471 additions and 1244 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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}`);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
$: parsedTemplate = () => {
|
||||
try {
|
||||
return renderTemplate(config.storageTemplate.template);
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return 'error';
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
const dispatchShowContextMenu = createEventDispatcher<OnShowContextMenu>();
|
||||
|
||||
const loadHighQualityThumbnail = async (thubmnailId: string | null) => {
|
||||
if (thubmnailId == null) {
|
||||
if (thubmnailId == undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,11 +83,12 @@
|
|||
}
|
||||
if (!$showAssetViewer) {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
case 'Escape': {
|
||||
if ($isMultiSelectState) {
|
||||
assetInteractionStore.clearMultiselect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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 '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.',
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
}}
|
||||
>
|
||||
<ImageThumbnail
|
||||
border={potentialMergePeople.length !== 0}
|
||||
border={potentialMergePeople.length > 0}
|
||||
circle
|
||||
shadow
|
||||
url={api.getPeopleThumbnailUrl(personMerge2.id)}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
{#if editImportPath != null}
|
||||
{#if editImportPath != undefined}
|
||||
<LibraryImportPathForm
|
||||
title="Edit Import Path"
|
||||
submitText="Save"
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
/>
|
||||
{/if}
|
||||
|
||||
{#if editExclusionPattern != null}
|
||||
{#if editExclusionPattern != undefined}
|
||||
<LibraryExclusionPatternForm
|
||||
submitText="Save"
|
||||
canDelete={true}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@
|
|||
}
|
||||
|
||||
selecting = false;
|
||||
} catch (e) {
|
||||
handleError(e, 'Error selecting all assets');
|
||||
} catch (error) {
|
||||
handleError(error, 'Error selecting all assets');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -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 = [];
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@
|
|||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (!point) {
|
||||
dispatch('cancel');
|
||||
} else {
|
||||
if (point) {
|
||||
dispatch('confirm', point);
|
||||
} else {
|
||||
dispatch('cancel');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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%]';
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
}
|
||||
};
|
||||
|
||||
let removeNotificationTimeout: NodeJS.Timeout | undefined = undefined;
|
||||
let removeNotificationTimeout: NodeJS.Timeout | undefined;
|
||||
|
||||
onMount(() => {
|
||||
removeNotificationTimeout = setTimeout(discard, notificationInfo.timeout);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@
|
|||
}
|
||||
|
||||
showModal = true;
|
||||
} catch (err) {
|
||||
console.error('Error [VersionAnnouncementBox]:', err);
|
||||
} catch (error) {
|
||||
console.error('Error [VersionAnnouncementBox]:', error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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" />
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ?? '',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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' : ''}`,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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]];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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', () => {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue