0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-25 02:41:37 -05:00

Merge branches 'chore/web-bucketing-by-day' and 'main' of github.com:immich-app/immich into chore/web-bucketing-by-day

This commit is contained in:
Alex Tran 2024-06-02 16:06:58 -05:00
commit 8dea3ef74f
21 changed files with 34 additions and 103 deletions

View file

@ -1,19 +1,12 @@
import { matchesShortcut } from '$lib/actions/shortcut'; import { matchesShortcut } from '$lib/actions/shortcut';
import type { ActionReturn } from 'svelte/action'; import type { ActionReturn } from 'svelte/action';
interface Attributes {
/** @deprecated */
'on:outclick'?: (e: CustomEvent) => void;
/** @deprecated **/
'on:escape'?: (e: CustomEvent) => void;
}
interface Options { interface Options {
onOutclick?: () => void; onOutclick?: () => void;
onEscape?: () => void; onEscape?: () => void;
} }
export function clickOutside(node: HTMLElement, options: Options = {}): ActionReturn<void, Attributes> { export function clickOutside(node: HTMLElement, options: Options = {}): ActionReturn {
const { onOutclick, onEscape } = options; const { onOutclick, onEscape } = options;
const handleClick = (event: MouseEvent) => { const handleClick = (event: MouseEvent) => {
@ -22,11 +15,7 @@ export function clickOutside(node: HTMLElement, options: Options = {}): ActionRe
return; return;
} }
if (onOutclick) { onOutclick?.();
onOutclick();
} else {
node.dispatchEvent(new CustomEvent('outclick'));
}
}; };
const handleKey = (event: KeyboardEvent) => { const handleKey = (event: KeyboardEvent) => {
@ -37,8 +26,6 @@ export function clickOutside(node: HTMLElement, options: Options = {}): ActionRe
if (onEscape) { if (onEscape) {
event.stopPropagation(); event.stopPropagation();
onEscape(); onEscape();
} else {
node.dispatchEvent(new CustomEvent('escape'));
} }
}; };

View file

@ -126,7 +126,7 @@
/> />
{#if selectedMenuUser === user} {#if selectedMenuUser === user}
<ContextMenu {...position} on:outclick={() => (selectedMenuUser = null)}> <ContextMenu {...position} onClose={() => (selectedMenuUser = null)}>
{#if role === AlbumUserRole.Viewer} {#if role === AlbumUserRole.Viewer}
<MenuOption on:click={() => handleSetReadonly(user, AlbumUserRole.Editor)} text="Allow edits" /> <MenuOption on:click={() => handleSetReadonly(user, AlbumUserRole.Editor)} text="Allow edits" />
{:else} {:else}

View file

@ -201,8 +201,7 @@
<button <button
type="button" type="button"
class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors" class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
use:clickOutside use:clickOutside={{ onOutclick: () => (showDeleteReaction[index] = false) }}
on:outclick={() => (showDeleteReaction[index] = false)}
on:click={() => handleDeleteReaction(reaction, index)} on:click={() => handleDeleteReaction(reaction, index)}
> >
Remove Remove
@ -254,8 +253,7 @@
<button <button
type="button" type="button"
class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors" class="absolute right-6 rounded-xl items-center bg-gray-300 dark:bg-slate-100 py-3 px-6 text-left text-sm font-medium text-immich-fg hover:bg-red-300 focus:outline-none focus:ring-2 focus:ring-inset dark:text-immich-dark-bg dark:hover:bg-red-100 transition-colors"
use:clickOutside use:clickOutside={{ onOutclick: () => (showDeleteReaction[index] = false) }}
on:outclick={() => (showDeleteReaction[index] = false)}
on:click={() => handleDeleteReaction(reaction, index)} on:click={() => handleDeleteReaction(reaction, index)}
> >
Remove Remove

View file

@ -273,10 +273,12 @@
}; };
const closeViewer = async () => { const closeViewer = async () => {
$slideshowState = SlideshowState.StopSlideshow; if ($slideshowState === SlideshowState.None) {
document.body.style.cursor = ''; dispatch('close');
dispatch('close'); await navigate({ targetRoute: 'current', assetId: null });
await navigate({ targetRoute: 'current', assetId: null }); } else {
$slideshowState = SlideshowState.StopSlideshow;
}
}; };
const navigateAssetRandom = async () => { const navigateAssetRandom = async () => {
@ -793,7 +795,6 @@
<DeleteAssetDialog <DeleteAssetDialog
size={1} size={1}
on:cancel={() => (isShowDeleteConfirmation = false)} on:cancel={() => (isShowDeleteConfirmation = false)}
on:escape={() => (isShowDeleteConfirmation = false)}
on:confirm={() => deleteAsset()} on:confirm={() => deleteAsset()}
/> />
{/if} {/if}

View file

@ -72,7 +72,7 @@
$: renderedSelectedOption = renderOption(selectedOption); $: renderedSelectedOption = renderOption(selectedOption);
</script> </script>
<div use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}> <div use:clickOutside={{ onOutclick: handleClickOutside, onEscape: handleClickOutside }}>
<!-- BUTTON TITLE --> <!-- BUTTON TITLE -->
<LinkButton on:click={() => (showMenu = true)} fullwidth {title}> <LinkButton on:click={() => (showMenu = true)} fullwidth {title}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">

View file

@ -87,7 +87,7 @@
{#if showContextMenu} {#if showContextMenu}
<Portal target="body"> <Portal target="body">
<ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}> <ContextMenu {...contextMenuPosition} onClose={() => onMenuExit()}>
<MenuOption on:click={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text="Hide person" /> <MenuOption on:click={() => onMenuClick('hide-person')} icon={mdiEyeOffOutline} text="Hide person" />
<MenuOption on:click={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text="Change name" /> <MenuOption on:click={() => onMenuClick('change-name')} icon={mdiAccountEditOutline} text="Change name" />
<MenuOption <MenuOption

View file

@ -2,7 +2,6 @@
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import MenuOption from '../../shared-components/context-menu/menu-option.svelte'; import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
import { getAssetControlContext } from '../asset-select-control-bar.svelte'; import { getAssetControlContext } from '../asset-select-control-bar.svelte';
import { createEventDispatcher } from 'svelte';
import { featureFlags } from '$lib/stores/server-config.store'; import { featureFlags } from '$lib/stores/server-config.store';
import { mdiTimerSand, mdiDeleteOutline } from '@mdi/js'; import { mdiTimerSand, mdiDeleteOutline } from '@mdi/js';
import { type OnDelete, deleteAssets } from '$lib/utils/actions'; import { type OnDelete, deleteAssets } from '$lib/utils/actions';
@ -14,10 +13,6 @@
const { clearSelect, getOwnedAssets } = getAssetControlContext(); const { clearSelect, getOwnedAssets } = getAssetControlContext();
const dispatch = createEventDispatcher<{
escape: void;
}>();
let isShowConfirmation = false; let isShowConfirmation = false;
let loading = false; let loading = false;
@ -40,11 +35,6 @@
isShowConfirmation = false; isShowConfirmation = false;
loading = false; loading = false;
}; };
const escape = () => {
dispatch('escape');
isShowConfirmation = false;
};
</script> </script>
{#if menuItem} {#if menuItem}
@ -60,6 +50,5 @@
size={getOwnedAssets().size} size={getOwnedAssets().size}
on:confirm={handleDelete} on:confirm={handleDelete}
on:cancel={() => (isShowConfirmation = false)} on:cancel={() => (isShowConfirmation = false)}
on:escape={escape}
/> />
{/if} {/if}

View file

@ -25,7 +25,7 @@
setContext(() => (showContextMenu = false)); setContext(() => (showContextMenu = false));
</script> </script>
<div use:clickOutside on:outclick={() => (showContextMenu = false)}> <div use:clickOutside={{ onOutclick: () => (showContextMenu = false) }}>
<CircleIconButton {title} {icon} on:click={handleShowMenu} /> <CircleIconButton {title} {icon} on:click={handleShowMenu} />
{#if showContextMenu} {#if showContextMenu}
<ContextMenu {...contextMenuPosition}> <ContextMenu {...contextMenuPosition}>

View file

@ -18,8 +18,8 @@
import { shortcuts } from '$lib/actions/shortcut'; import { shortcuts } from '$lib/actions/shortcut';
import { clickOutside } from '$lib/actions/click-outside'; import { clickOutside } from '$lib/actions/click-outside';
import { focusOutside } from '$lib/actions/focus-outside'; import { focusOutside } from '$lib/actions/focus-outside';
import { generateId } from '$lib/utils/generate-id';
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
import { uniqueIdStore } from '$lib/stores/unique-id.store';
export let label: string; export let label: string;
export let hideLabel = false; export let hideLabel = false;
@ -30,7 +30,7 @@
/** /**
* Unique identifier for the combobox. * Unique identifier for the combobox.
*/ */
let id: string = uniqueIdStore.generateId(); let id: string = generateId();
/** /**
* Indicates whether or not the dropdown autocomplete list should be visible. * Indicates whether or not the dropdown autocomplete list should be visible.
*/ */

View file

@ -8,6 +8,7 @@
export let y = 0; export let y = 0;
export let menuElement: HTMLDivElement | undefined = undefined; export let menuElement: HTMLDivElement | undefined = undefined;
export let onClose: (() => void) | undefined = undefined;
let left: number; let left: number;
let top: number; let top: number;
@ -36,9 +37,7 @@
style:top="{top}px" style:top="{top}px"
style:left="{left}px" style:left="{left}px"
role="menu" role="menu"
use:clickOutside use:clickOutside={{ onOutclick: onClose, onEscape: onClose }}
on:outclick
on:escape
> >
<div class="flex flex-col rounded-lg"> <div class="flex flex-col rounded-lg">
<slot /> <slot />

View file

@ -49,14 +49,7 @@
on:contextmenu|preventDefault={reopenContextMenu} on:contextmenu|preventDefault={reopenContextMenu}
role="presentation" role="presentation"
> >
<ContextMenu <ContextMenu {x} {y} {direction} onClose={closeContextMenu} bind:menuElement={contextMenuElement}>
{x}
{y}
{direction}
on:outclick={closeContextMenu}
on:escape={closeContextMenu}
bind:menuElement={contextMenuElement}
>
<slot /> <slot />
</ContextMenu> </ContextMenu>
</section> </section>

View file

@ -3,7 +3,7 @@
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import FocusTrap from '$lib/components/shared-components/focus-trap.svelte'; import FocusTrap from '$lib/components/shared-components/focus-trap.svelte';
import ModalHeader from '$lib/components/shared-components/modal-header.svelte'; import ModalHeader from '$lib/components/shared-components/modal-header.svelte';
import { uniqueIdStore } from '$lib/stores/unique-id.store'; import { generateId } from '$lib/utils/generate-id';
export let onClose: () => void; export let onClose: () => void;
export let title: string; export let title: string;
@ -27,7 +27,7 @@
/** /**
* Unique identifier for the modal. * Unique identifier for the modal.
*/ */
let id: string = uniqueIdStore.generateId(); let id: string = generateId();
$: titleId = `${id}-title`; $: titleId = `${id}-title`;
$: isStickyBottom = !!$$slots['sticky-bottom']; $: isStickyBottom = !!$$slots['sticky-bottom'];

View file

@ -114,9 +114,10 @@
{/if} {/if}
<div <div
use:clickOutside use:clickOutside={{
on:outclick={() => (shouldShowAccountInfoPanel = false)} onOutclick: () => (shouldShowAccountInfoPanel = false),
on:escape={() => (shouldShowAccountInfoPanel = false)} onEscape: () => (shouldShowAccountInfoPanel = false),
}}
> >
<button <button
type="button" type="button"

View file

@ -3,7 +3,7 @@
import { fly } from 'svelte/transition'; import { fly } from 'svelte/transition';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import Slider from '$lib/components/elements/slider.svelte'; import Slider from '$lib/components/elements/slider.svelte';
import { uniqueIdStore } from '$lib/stores/unique-id.store'; import { generateId } from '$lib/utils/generate-id';
export let title: string; export let title: string;
export let subtitle = ''; export let subtitle = '';
@ -11,7 +11,7 @@
export let disabled = false; export let disabled = false;
export let isEdited = false; export let isEdited = false;
let id: string = uniqueIdStore.generateId(); let id: string = generateId();
$: sliderId = `${id}-slider`; $: sliderId = `${id}-slider`;
$: subtitleId = subtitle ? `${id}-subtitle` : undefined; $: subtitleId = subtitle ? `${id}-subtitle` : undefined;

View file

@ -1,14 +0,0 @@
import { uniqueIdStore } from '$lib/stores/unique-id.store';
describe('uniqueIdStore', () => {
afterEach(() => {
uniqueIdStore.update(() => -1);
});
it('should generate unique ids', () => {
const { generateId } = uniqueIdStore;
const ids = [generateId(), generateId(), generateId()];
expect(ids).toEqual(['id-0', 'id-1', 'id-2']);
});
});

View file

@ -1,16 +0,0 @@
import { get, writable } from 'svelte/store';
function createIdStore() {
const { subscribe, update } = writable(-1);
return {
subscribe,
update,
generateId: () => {
update((value) => value + 1);
return `id-${get(uniqueIdStore)}`;
},
};
}
export const uniqueIdStore = createIdStore();

View file

@ -0,0 +1,2 @@
let _count = 0;
export const generateId = (): string => `id-${_count++}`;

View file

@ -467,7 +467,7 @@
<CircleIconButton title="Download" on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} /> <CircleIconButton title="Download" on:click={handleDownloadAlbum} icon={mdiFolderDownloadOutline} />
{#if isOwned} {#if isOwned}
<div use:clickOutside on:outclick={() => (viewMode = ViewMode.VIEW)}> <div use:clickOutside={{ onOutclick: () => (viewMode = ViewMode.VIEW) }}>
<CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical} /> <CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical} />
{#if viewMode === ViewMode.ALBUM_OPTIONS} {#if viewMode === ViewMode.ALBUM_OPTIONS}
<ContextMenu {...contextMenuPosition}> <ContextMenu {...contextMenuPosition}>

View file

@ -25,7 +25,6 @@
import { preferences, user } from '$lib/stores/user.store'; import { preferences, user } from '$lib/stores/user.store';
let { isViewing: showAssetViewer } = assetViewingStore; let { isViewing: showAssetViewer } = assetViewingStore;
let handleEscapeKey = false;
const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true }); const assetStore = new AssetStore({ isArchived: false, withStacked: true, withPartners: true });
const assetInteractionStore = createAssetInteractionStore(); const assetInteractionStore = createAssetInteractionStore();
const { isMultiSelectState, selectedAssets } = assetInteractionStore; const { isMultiSelectState, selectedAssets } = assetInteractionStore;
@ -43,10 +42,6 @@
if ($showAssetViewer) { if ($showAssetViewer) {
return; return;
} }
if (handleEscapeKey) {
handleEscapeKey = false;
return;
}
if ($isMultiSelectState) { if ($isMultiSelectState) {
assetInteractionStore.clearMultiselect(); assetInteractionStore.clearMultiselect();
return; return;
@ -79,11 +74,7 @@
<ChangeDate menuItem /> <ChangeDate menuItem />
<ChangeLocation menuItem /> <ChangeLocation menuItem />
<ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} /> <ArchiveAction menuItem onArchive={(assetIds) => assetStore.removeAssets(assetIds)} />
<DeleteAssets <DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
menuItem
on:escape={() => (handleEscapeKey = true)}
onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)}
/>
<hr /> <hr />
<AssetJobActions /> <AssetJobActions />
</AssetSelectContextMenu> </AssetSelectContextMenu>

View file

@ -97,13 +97,13 @@
{#if $featureFlags.loaded && $featureFlags.trash} {#if $featureFlags.loaded && $featureFlags.trash}
<UserPageLayout hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={false}> <UserPageLayout hideNavbar={$isMultiSelectState} title={data.meta.title} scrollbar={false}>
<div class="flex place-items-center gap-2" slot="buttons"> <div class="flex place-items-center gap-2" slot="buttons">
<LinkButton on:click={handleRestoreTrash}> <LinkButton on:click={handleRestoreTrash} disabled={$isMultiSelectState}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiHistory} size="18" /> <Icon path={mdiHistory} size="18" />
Restore all Restore all
</div> </div>
</LinkButton> </LinkButton>
<LinkButton on:click={() => handleEmptyTrash()}> <LinkButton on:click={() => handleEmptyTrash()} disabled={$isMultiSelectState}>
<div class="flex place-items-center gap-2 text-sm"> <div class="flex place-items-center gap-2 text-sm">
<Icon path={mdiDeleteOutline} size="18" /> <Icon path={mdiDeleteOutline} size="18" />
Empty trash Empty trash

View file

@ -397,7 +397,7 @@
{#if showContextMenu} {#if showContextMenu}
<Portal target="body"> <Portal target="body">
<ContextMenu {...contextMenuPosition} on:outclick={() => onMenuExit()}> <ContextMenu {...contextMenuPosition} onClose={() => onMenuExit()}>
<MenuOption on:click={() => onRenameClicked()} text={`Rename`} /> <MenuOption on:click={() => onRenameClicked()} text={`Rename`} />
{#if selectedLibrary} {#if selectedLibrary}