mirror of
https://github.com/immich-app/immich.git
synced 2024-12-25 10:43:13 +02:00
fix(web): escape shortcut (#3753)
* fix: escape shortcut * feat: more escape scenarios * feat: more escape shortcuts --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
parent
8873c9a02f
commit
f63d6d5b67
@ -6,18 +6,17 @@
|
|||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
close: void;
|
close: void;
|
||||||
updated: string;
|
save: string;
|
||||||
}>();
|
}>();
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
|
|
||||||
let description = album.description;
|
let description = album.description;
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleCancel = () => dispatch('close');
|
||||||
dispatch('updated', description);
|
const handleSubmit = () => dispatch('save', description);
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal on:clickOutside={() => dispatch('close')}>
|
<FullScreenModal on:clickOutside={handleCancel} on:escape={handleCancel}>
|
||||||
<div
|
<div
|
||||||
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
|
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
@ -27,7 +26,7 @@
|
|||||||
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Edit description</h1>
|
<h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Edit description</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form on:submit|preventDefault={handleSave} autocomplete="off">
|
<form on:submit|preventDefault={handleSubmit} autocomplete="off">
|
||||||
<div class="m-4 flex flex-col gap-2">
|
<div class="m-4 flex flex-col gap-2">
|
||||||
<label class="immich-form-label" for="name">Description</label>
|
<label class="immich-form-label" for="name">Description</label>
|
||||||
<!-- svelte-ignore a11y-autofocus -->
|
<!-- svelte-ignore a11y-autofocus -->
|
||||||
@ -42,7 +41,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 flex w-full gap-4 px-4">
|
<div class="mt-8 flex w-full gap-4 px-4">
|
||||||
<Button color="gray" fullwidth on:click={() => dispatch('close')}>Cancel</Button>
|
<Button color="gray" fullwidth on:click={handleCancel}>Cancel</Button>
|
||||||
<Button type="submit" fullwidth>Ok</Button>
|
<Button type="submit" fullwidth>Ok</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -120,6 +120,10 @@
|
|||||||
isShowDeleteConfirmation = true;
|
isShowDeleteConfirmation = true;
|
||||||
return;
|
return;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
|
if (isShowDeleteConfirmation) {
|
||||||
|
isShowDeleteConfirmation = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
closeViewer();
|
closeViewer();
|
||||||
return;
|
return;
|
||||||
case 'f':
|
case 'f':
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
$: icon = icons?.[index];
|
$: icon = icons?.[index];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside}>
|
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
|
||||||
<!-- BUTTON TITLE -->
|
<!-- BUTTON TITLE -->
|
||||||
<LinkButton on:click={() => (showMenu = true)}>
|
<LinkButton on:click={() => (showMenu = true)}>
|
||||||
<div class="flex place-items-center gap-2 text-sm">
|
<div class="flex place-items-center gap-2 text-sm">
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
close: void;
|
close: void;
|
||||||
save: MapSettings;
|
save: MapSettings;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const handleClose = () => dispatch('close');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal on:clickOutside={() => dispatch('close')}>
|
<FullScreenModal on:clickOutside={handleClose} on:escape={handleClose}>
|
||||||
<div
|
<div
|
||||||
class="flex w-96 max-w-lg flex-col gap-8 rounded-3xl border bg-white p-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
class="flex w-96 max-w-lg flex-col gap-8 rounded-3xl border bg-white p-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray"
|
||||||
>
|
>
|
||||||
@ -105,7 +107,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="mt-4 flex w-full gap-4">
|
<div class="mt-4 flex w-full gap-4">
|
||||||
<Button color="gray" size="sm" fullwidth on:click={() => dispatch('close')}>Cancel</Button>
|
<Button color="gray" size="sm" fullwidth on:click={handleClose}>Cancel</Button>
|
||||||
<Button type="submit" size="sm" fullwidth>Save</Button>
|
<Button type="submit" size="sm" fullwidth>Save</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -3,13 +3,23 @@
|
|||||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
||||||
import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
|
import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
|
||||||
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
let showModal = false;
|
let showModal = false;
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
const { getAssets } = getAssetControlContext();
|
const { getAssets } = getAssetControlContext();
|
||||||
|
const escape = () => {
|
||||||
|
dispatch('escape');
|
||||||
|
showModal = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CircleIconButton title="Share" logo={ShareVariantOutline} on:click={() => (showModal = true)} />
|
<CircleIconButton title="Share" logo={ShareVariantOutline} on:click={() => (showModal = true)} />
|
||||||
|
|
||||||
{#if showModal}
|
{#if showModal}
|
||||||
<CreateSharedLinkModal assetIds={Array.from(getAssets()).map(({ id }) => id)} on:close={() => (showModal = false)} />
|
<CreateSharedLinkModal
|
||||||
|
assetIds={Array.from(getAssets()).map(({ id }) => id)}
|
||||||
|
on:close={() => (showModal = false)}
|
||||||
|
on:escape={escape}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -11,11 +11,14 @@
|
|||||||
import TimerSand from 'svelte-material-icons/TimerSand.svelte';
|
import TimerSand from 'svelte-material-icons/TimerSand.svelte';
|
||||||
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||||
import { OnAssetDelete, getAssetControlContext } from '../asset-select-control-bar.svelte';
|
import { OnAssetDelete, getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let onAssetDelete: OnAssetDelete;
|
export let onAssetDelete: OnAssetDelete;
|
||||||
export let menuItem = false;
|
export let menuItem = false;
|
||||||
const { getAssets, clearSelect } = getAssetControlContext();
|
const { getAssets, clearSelect } = getAssetControlContext();
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let isShowConfirmation = false;
|
let isShowConfirmation = false;
|
||||||
let loading = false;
|
let loading = false;
|
||||||
|
|
||||||
@ -51,6 +54,11 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const escape = () => {
|
||||||
|
dispatch('escape');
|
||||||
|
isShowConfirmation = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if menuItem}
|
{#if menuItem}
|
||||||
@ -71,6 +79,7 @@
|
|||||||
confirmText="Delete"
|
confirmText="Delete"
|
||||||
on:confirm={handleDelete}
|
on:confirm={handleDelete}
|
||||||
on:cancel={() => (isShowConfirmation = false)}
|
on:cancel={() => (isShowConfirmation = false)}
|
||||||
|
on:escape={escape}
|
||||||
>
|
>
|
||||||
<svelte:fragment slot="prompt">
|
<svelte:fragment slot="prompt">
|
||||||
<p>
|
<p>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
$: timelineY = element?.scrollTop || 0;
|
$: timelineY = element?.scrollTop || 0;
|
||||||
|
|
||||||
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
|
const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
|
||||||
const dispatch = createEventDispatcher<{ select: AssetResponseDto }>();
|
const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
showSkeleton = false;
|
showSkeleton = false;
|
||||||
@ -62,7 +62,7 @@
|
|||||||
if (!$showAssetViewer) {
|
if (!$showAssetViewer) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
assetInteractionStore.clearMultiselect();
|
dispatch('escape');
|
||||||
return;
|
return;
|
||||||
case '?':
|
case '?':
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
<div
|
<div
|
||||||
use:clickOutside
|
use:clickOutside
|
||||||
on:outclick={() => dispatch('close')}
|
on:outclick={() => dispatch('close')}
|
||||||
|
on:escape={() => dispatch('escape')}
|
||||||
class="max-h-[600px] min-h-[200px] w-[450px] rounded-lg bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg"
|
class="max-h-[600px] min-h-[200px] w-[450px] rounded-lg bg-immich-bg shadow-md dark:bg-immich-dark-gray dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
<div class="flex place-items-center justify-between px-5 py-3">
|
<div class="flex place-items-center justify-between px-5 py-3">
|
||||||
|
@ -17,6 +17,11 @@
|
|||||||
let isConfirmButtonDisabled = false;
|
let isConfirmButtonDisabled = false;
|
||||||
|
|
||||||
const handleCancel = () => dispatch('cancel');
|
const handleCancel = () => dispatch('cancel');
|
||||||
|
const handleEscape = () => {
|
||||||
|
if (!isConfirmButtonDisabled) {
|
||||||
|
dispatch('cancel');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = () => {
|
||||||
isConfirmButtonDisabled = true;
|
isConfirmButtonDisabled = true;
|
||||||
@ -24,7 +29,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal on:clickOutside={handleCancel}>
|
<FullScreenModal on:clickOutside={handleCancel} on:escape={() => handleEscape()}>
|
||||||
<div
|
<div
|
||||||
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
|
class="w-[500px] max-w-[95vw] rounded-3xl border bg-immich-bg p-4 py-8 shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg"
|
||||||
>
|
>
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
role="menu"
|
role="menu"
|
||||||
use:clickOutside
|
use:clickOutside
|
||||||
on:outclick
|
on:outclick
|
||||||
|
on:escape
|
||||||
>
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
@ -137,7 +137,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<BaseModal on:close={() => dispatch('close')}>
|
<BaseModal on:close={() => dispatch('close')} on:escape={() => dispatch('escape')}>
|
||||||
<svelte:fragment slot="title">
|
<svelte:fragment slot="title">
|
||||||
<span class="flex place-items-center gap-2">
|
<span class="flex place-items-center gap-2">
|
||||||
<Link size={24} />
|
<Link size={24} />
|
||||||
|
@ -3,7 +3,10 @@
|
|||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade } from 'svelte/transition';
|
import { fade } from 'svelte/transition';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ clickOutside: void }>();
|
const dispatch = createEventDispatcher<{
|
||||||
|
clickOutside: void;
|
||||||
|
escape: void;
|
||||||
|
}>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@ -11,7 +14,12 @@
|
|||||||
out:fade={{ duration: 100 }}
|
out:fade={{ duration: 100 }}
|
||||||
class="fixed left-0 top-0 z-[990] flex h-screen w-screen place-content-center place-items-center bg-black/40"
|
class="fixed left-0 top-0 z-[990] flex h-screen w-screen place-content-center place-items-center bg-black/40"
|
||||||
>
|
>
|
||||||
<div class="z-[9999]" use:clickOutside on:outclick={() => dispatch('clickOutside')}>
|
<div
|
||||||
|
class="z-[9999]"
|
||||||
|
use:clickOutside
|
||||||
|
on:outclick={() => dispatch('clickOutside')}
|
||||||
|
on:escape={() => dispatch('escape')}
|
||||||
|
>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -106,7 +106,11 @@
|
|||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
|
<div
|
||||||
|
use:clickOutside
|
||||||
|
on:outclick={() => (shouldShowAccountInfoPanel = false)}
|
||||||
|
on:escape={() => (shouldShowAccountInfoPanel = false)}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="flex"
|
class="flex"
|
||||||
on:mouseover={() => (shouldShowAccountInfo = true)}
|
on:mouseover={() => (shouldShowAccountInfo = true)}
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
showBigSearchBar = false;
|
showBigSearchBar = false;
|
||||||
|
$isSearchEnabled = false;
|
||||||
goto(`${AppRoute.SEARCH}?${params}`);
|
goto(`${AppRoute.SEARCH}?${params}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +69,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div role="button" class="w-full" use:clickOutside on:outclick={onFocusOut}>
|
<div role="button" class="w-full" use:clickOutside on:outclick={onFocusOut} on:escape={onFocusOut}>
|
||||||
<form
|
<form
|
||||||
draggable="false"
|
draggable="false"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FullScreenModal on:clickOutside={() => dispatch('close')}>
|
<FullScreenModal on:clickOutside={() => dispatch('close')} on:escape={() => dispatch('close')}>
|
||||||
<div class="flex h-full w-full place-content-center place-items-center overflow-hidden">
|
<div class="flex h-full w-full place-content-center place-items-center overflow-hidden">
|
||||||
<div
|
<div
|
||||||
class="w-[400px] max-w-[125vw] rounded-3xl border bg-immich-bg shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg md:w-[650px]"
|
class="w-[400px] max-w-[125vw] rounded-3xl border bg-immich-bg shadow-sm dark:border-immich-dark-gray dark:bg-immich-dark-gray dark:text-immich-dark-fg md:w-[650px]"
|
||||||
|
@ -2,6 +2,7 @@ import type { ActionReturn } from 'svelte/action';
|
|||||||
|
|
||||||
interface Attributes {
|
interface Attributes {
|
||||||
'on:outclick'?: (e: CustomEvent) => void;
|
'on:outclick'?: (e: CustomEvent) => void;
|
||||||
|
'on:escape'?: (e: CustomEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function clickOutside(node: HTMLElement): ActionReturn<void, Attributes> {
|
export function clickOutside(node: HTMLElement): ActionReturn<void, Attributes> {
|
||||||
@ -14,7 +15,7 @@ export function clickOutside(node: HTMLElement): ActionReturn<void, Attributes>
|
|||||||
|
|
||||||
const handleKey = (event: KeyboardEvent) => {
|
const handleKey = (event: KeyboardEvent) => {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
node.dispatchEvent(new CustomEvent('outclick'));
|
node.dispatchEvent(new CustomEvent('escape'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@
|
|||||||
|
|
||||||
<!-- Context Menu -->
|
<!-- Context Menu -->
|
||||||
{#if $isShowContextMenu}
|
{#if $isShowContextMenu}
|
||||||
<ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}>
|
<ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu} on:escape={closeAlbumContextMenu}>
|
||||||
<MenuOption on:click={() => setAlbumToDelete()}>
|
<MenuOption on:click={() => setAlbumToDelete()}>
|
||||||
<span class="flex place-content-center place-items-center gap-2">
|
<span class="flex place-content-center place-items-center gap-2">
|
||||||
<DeleteOutline size="18" />
|
<DeleteOutline size="18" />
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
|
|
||||||
let album = data.album;
|
let album = data.album;
|
||||||
$: album = data.album;
|
$: album = data.album;
|
||||||
|
|
||||||
@ -102,6 +104,30 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleEscape = () => {
|
||||||
|
if (viewMode === ViewMode.SELECT_USERS) {
|
||||||
|
viewMode = ViewMode.VIEW;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (viewMode === ViewMode.CONFIRM_DELETE) {
|
||||||
|
viewMode = ViewMode.VIEW;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (viewMode === ViewMode.SELECT_ASSETS) {
|
||||||
|
handleCloseSelectAssets();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($showAssetViewer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($isMultiSelectState) {
|
||||||
|
assetInteractionStore.clearMultiselect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goto(backUrl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
const refreshAlbum = async () => {
|
const refreshAlbum = async () => {
|
||||||
const { data } = await api.albumApi.getAlbumInfo({ id: album.id, withoutAssets: true });
|
const { data } = await api.albumApi.getAlbumInfo({ id: album.id, withoutAssets: true });
|
||||||
album = data;
|
album = data;
|
||||||
@ -403,6 +429,7 @@
|
|||||||
isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
|
isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||||
singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
|
singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
|
||||||
on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)}
|
on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)}
|
||||||
|
on:escape={handleEscape}
|
||||||
>
|
>
|
||||||
{#if viewMode !== ViewMode.SELECT_THUMBNAIL}
|
{#if viewMode !== ViewMode.SELECT_THUMBNAIL}
|
||||||
<!-- ALBUM TITLE -->
|
<!-- ALBUM TITLE -->
|
||||||
@ -540,6 +567,6 @@
|
|||||||
<EditDescriptionModal
|
<EditDescriptionModal
|
||||||
{album}
|
{album}
|
||||||
on:close={() => (isEditingDescription = false)}
|
on:close={() => (isEditingDescription = false)}
|
||||||
on:updated={({ detail: description }) => handleUpdateDescription(description)}
|
on:save={({ detail: description }) => handleUpdateDescription(description)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -33,9 +33,12 @@
|
|||||||
import Plus from 'svelte-material-icons/Plus.svelte';
|
import Plus from 'svelte-material-icons/Plus.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { clickOutside } from '$lib/utils/click-outside';
|
import { clickOutside } from '$lib/utils/click-outside';
|
||||||
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
|
|
||||||
enum ViewMode {
|
enum ViewMode {
|
||||||
VIEW_ASSETS = 'view-assets',
|
VIEW_ASSETS = 'view-assets',
|
||||||
SELECT_FACE = 'select-face',
|
SELECT_FACE = 'select-face',
|
||||||
@ -86,6 +89,18 @@
|
|||||||
viewMode = ViewMode.MERGE_FACES;
|
viewMode = ViewMode.MERGE_FACES;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const handleEscape = () => {
|
||||||
|
if ($showAssetViewer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($isMultiSelectState) {
|
||||||
|
assetInteractionStore.clearMultiselect();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
goto(previousRoute);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
afterNavigate(({ from }) => {
|
afterNavigate(({ from }) => {
|
||||||
// Prevent setting previousRoute to the current page.
|
// Prevent setting previousRoute to the current page.
|
||||||
if (from && from.route.id !== $page.route.id) {
|
if (from && from.route.id !== $page.route.id) {
|
||||||
@ -337,6 +352,7 @@
|
|||||||
isSelectionMode={viewMode === ViewMode.SELECT_FACE}
|
isSelectionMode={viewMode === ViewMode.SELECT_FACE}
|
||||||
singleSelect={viewMode === ViewMode.SELECT_FACE}
|
singleSelect={viewMode === ViewMode.SELECT_FACE}
|
||||||
on:select={({ detail: asset }) => handleSelectFeaturePhoto(asset)}
|
on:select={({ detail: asset }) => handleSelectFeaturePhoto(asset)}
|
||||||
|
on:escape={handleEscape}
|
||||||
>
|
>
|
||||||
{#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
|
{#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
|
||||||
<!-- Face information block -->
|
<!-- Face information block -->
|
||||||
@ -344,7 +360,8 @@
|
|||||||
role="button"
|
role="button"
|
||||||
class="relative w-fit p-4 sm:px-6"
|
class="relative w-fit p-4 sm:px-6"
|
||||||
use:clickOutside
|
use:clickOutside
|
||||||
on:outclick={() => handleCancelEditName()}
|
on:outclick={handleCancelEditName}
|
||||||
|
on:escape={handleCancelEditName}
|
||||||
>
|
>
|
||||||
<section class="flex w-96 place-items-center border-black">
|
<section class="flex w-96 place-items-center border-black">
|
||||||
{#if isEditingName}
|
{#if isEditingName}
|
||||||
|
@ -21,27 +21,47 @@
|
|||||||
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
|
import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
|
||||||
import Plus from 'svelte-material-icons/Plus.svelte';
|
import Plus from 'svelte-material-icons/Plus.svelte';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
|
let { isViewing: showAssetViewer } = assetViewingStore;
|
||||||
|
let handleEscapeKey = false;
|
||||||
const assetStore = new AssetStore({ size: TimeBucketSize.Month, isArchived: false });
|
const assetStore = new AssetStore({ size: TimeBucketSize.Month, isArchived: false });
|
||||||
const assetInteractionStore = createAssetInteractionStore();
|
const assetInteractionStore = createAssetInteractionStore();
|
||||||
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
|
const { isMultiSelectState, selectedAssets } = assetInteractionStore;
|
||||||
|
|
||||||
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
|
$: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
|
||||||
|
|
||||||
|
const handleEscape = () => {
|
||||||
|
if ($showAssetViewer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (handleEscapeKey) {
|
||||||
|
handleEscapeKey = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($isMultiSelectState) {
|
||||||
|
assetInteractionStore.clearMultiselect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} showUploadButton>
|
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} showUploadButton>
|
||||||
<svelte:fragment slot="header">
|
<svelte:fragment slot="header">
|
||||||
{#if $isMultiSelectState}
|
{#if $isMultiSelectState}
|
||||||
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
<AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
|
||||||
<CreateSharedLink />
|
<CreateSharedLink on:escape={() => (handleEscapeKey = true)} />
|
||||||
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
<SelectAllAssets {assetStore} {assetInteractionStore} />
|
||||||
<AssetSelectContextMenu icon={Plus} title="Add">
|
<AssetSelectContextMenu icon={Plus} title="Add">
|
||||||
<AddToAlbum />
|
<AddToAlbum />
|
||||||
<AddToAlbum shared />
|
<AddToAlbum shared />
|
||||||
</AssetSelectContextMenu>
|
</AssetSelectContextMenu>
|
||||||
<DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
|
<DeleteAssets
|
||||||
|
on:escape={() => (handleEscapeKey = true)}
|
||||||
|
onAssetDelete={(assetId) => assetStore.removeAsset(assetId)}
|
||||||
|
/>
|
||||||
<AssetSelectContextMenu icon={DotsVertical} title="Menu">
|
<AssetSelectContextMenu icon={DotsVertical} title="Menu">
|
||||||
<FavoriteAction menuItem removeFavorite={isAllFavorite} />
|
<FavoriteAction menuItem removeFavorite={isAllFavorite} />
|
||||||
<DownloadAction menuItem />
|
<DownloadAction menuItem />
|
||||||
@ -52,7 +72,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="content">
|
<svelte:fragment slot="content">
|
||||||
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE}>
|
<AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE} on:escape={handleEscape}>
|
||||||
{#if data.user.memoriesEnabled}
|
{#if data.user.memoriesEnabled}
|
||||||
<MemoryLane />
|
<MemoryLane />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -58,6 +58,10 @@
|
|||||||
if (!$showAssetViewer) {
|
if (!$showAssetViewer) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
|
if (isMultiSelectionMode) {
|
||||||
|
selectedAssets = new Set();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!$preventRaceConditionSearchBar) {
|
if (!$preventRaceConditionSearchBar) {
|
||||||
goto(previousRoute);
|
goto(previousRoute);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user