mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00: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:
		@@ -6,18 +6,17 @@
 | 
			
		||||
 | 
			
		||||
  const dispatch = createEventDispatcher<{
 | 
			
		||||
    close: void;
 | 
			
		||||
    updated: string;
 | 
			
		||||
    save: string;
 | 
			
		||||
  }>();
 | 
			
		||||
  export let album: AlbumResponseDto;
 | 
			
		||||
 | 
			
		||||
  let description = album.description;
 | 
			
		||||
 | 
			
		||||
  const handleSave = () => {
 | 
			
		||||
    dispatch('updated', description);
 | 
			
		||||
  };
 | 
			
		||||
  const handleCancel = () => dispatch('close');
 | 
			
		||||
  const handleSubmit = () => dispatch('save', description);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<FullScreenModal on:clickOutside={() => dispatch('close')}>
 | 
			
		||||
<FullScreenModal on:clickOutside={handleCancel} on:escape={handleCancel}>
 | 
			
		||||
  <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"
 | 
			
		||||
  >
 | 
			
		||||
@@ -27,7 +26,7 @@
 | 
			
		||||
      <h1 class="text-2xl font-medium text-immich-primary dark:text-immich-dark-primary">Edit description</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <form on:submit|preventDefault={handleSave} autocomplete="off">
 | 
			
		||||
    <form on:submit|preventDefault={handleSubmit} autocomplete="off">
 | 
			
		||||
      <div class="m-4 flex flex-col gap-2">
 | 
			
		||||
        <label class="immich-form-label" for="name">Description</label>
 | 
			
		||||
        <!-- svelte-ignore a11y-autofocus -->
 | 
			
		||||
@@ -42,7 +41,7 @@
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <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>
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
 
 | 
			
		||||
@@ -120,6 +120,10 @@
 | 
			
		||||
        isShowDeleteConfirmation = true;
 | 
			
		||||
        return;
 | 
			
		||||
      case 'Escape':
 | 
			
		||||
        if (isShowDeleteConfirmation) {
 | 
			
		||||
          isShowDeleteConfirmation = false;
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        closeViewer();
 | 
			
		||||
        return;
 | 
			
		||||
      case 'f':
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
  $: icon = icons?.[index];
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside}>
 | 
			
		||||
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
 | 
			
		||||
  <!-- BUTTON TITLE -->
 | 
			
		||||
  <LinkButton on:click={() => (showMenu = true)}>
 | 
			
		||||
    <div class="flex place-items-center gap-2 text-sm">
 | 
			
		||||
 
 | 
			
		||||
@@ -16,9 +16,11 @@
 | 
			
		||||
    close: void;
 | 
			
		||||
    save: MapSettings;
 | 
			
		||||
  }>();
 | 
			
		||||
 | 
			
		||||
  const handleClose = () => dispatch('close');
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<FullScreenModal on:clickOutside={() => dispatch('close')}>
 | 
			
		||||
<FullScreenModal on:clickOutside={handleClose} on:escape={handleClose}>
 | 
			
		||||
  <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"
 | 
			
		||||
  >
 | 
			
		||||
@@ -105,7 +107,7 @@
 | 
			
		||||
      {/if}
 | 
			
		||||
 | 
			
		||||
      <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>
 | 
			
		||||
      </div>
 | 
			
		||||
    </form>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,13 +3,23 @@
 | 
			
		||||
  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 { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
			
		||||
  import { createEventDispatcher } from 'svelte';
 | 
			
		||||
 | 
			
		||||
  let showModal = false;
 | 
			
		||||
  const dispatch = createEventDispatcher();
 | 
			
		||||
  const { getAssets } = getAssetControlContext();
 | 
			
		||||
  const escape = () => {
 | 
			
		||||
    dispatch('escape');
 | 
			
		||||
    showModal = false;
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<CircleIconButton title="Share" logo={ShareVariantOutline} on:click={() => (showModal = true)} />
 | 
			
		||||
 | 
			
		||||
{#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}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,14 @@
 | 
			
		||||
  import TimerSand from 'svelte-material-icons/TimerSand.svelte';
 | 
			
		||||
  import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
 | 
			
		||||
  import { OnAssetDelete, getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
			
		||||
  import { createEventDispatcher } from 'svelte';
 | 
			
		||||
 | 
			
		||||
  export let onAssetDelete: OnAssetDelete;
 | 
			
		||||
  export let menuItem = false;
 | 
			
		||||
  const { getAssets, clearSelect } = getAssetControlContext();
 | 
			
		||||
 | 
			
		||||
  const dispatch = createEventDispatcher();
 | 
			
		||||
 | 
			
		||||
  let isShowConfirmation = false;
 | 
			
		||||
  let loading = false;
 | 
			
		||||
 | 
			
		||||
@@ -51,6 +54,11 @@
 | 
			
		||||
      loading = false;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const escape = () => {
 | 
			
		||||
    dispatch('escape');
 | 
			
		||||
    isShowConfirmation = false;
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
{#if menuItem}
 | 
			
		||||
@@ -71,6 +79,7 @@
 | 
			
		||||
    confirmText="Delete"
 | 
			
		||||
    on:confirm={handleDelete}
 | 
			
		||||
    on:cancel={() => (isShowConfirmation = false)}
 | 
			
		||||
    on:escape={escape}
 | 
			
		||||
  >
 | 
			
		||||
    <svelte:fragment slot="prompt">
 | 
			
		||||
      <p>
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@
 | 
			
		||||
  $: timelineY = element?.scrollTop || 0;
 | 
			
		||||
 | 
			
		||||
  const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
 | 
			
		||||
  const dispatch = createEventDispatcher<{ select: AssetResponseDto }>();
 | 
			
		||||
  const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
 | 
			
		||||
 | 
			
		||||
  onMount(async () => {
 | 
			
		||||
    showSkeleton = false;
 | 
			
		||||
@@ -62,7 +62,7 @@
 | 
			
		||||
    if (!$showAssetViewer) {
 | 
			
		||||
      switch (event.key) {
 | 
			
		||||
        case 'Escape':
 | 
			
		||||
          assetInteractionStore.clearMultiselect();
 | 
			
		||||
          dispatch('escape');
 | 
			
		||||
          return;
 | 
			
		||||
        case '?':
 | 
			
		||||
          if (event.shiftKey) {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@
 | 
			
		||||
  <div
 | 
			
		||||
    use:clickOutside
 | 
			
		||||
    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"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="flex place-items-center justify-between px-5 py-3">
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,11 @@
 | 
			
		||||
  let isConfirmButtonDisabled = false;
 | 
			
		||||
 | 
			
		||||
  const handleCancel = () => dispatch('cancel');
 | 
			
		||||
  const handleEscape = () => {
 | 
			
		||||
    if (!isConfirmButtonDisabled) {
 | 
			
		||||
      dispatch('cancel');
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleConfirm = () => {
 | 
			
		||||
    isConfirmButtonDisabled = true;
 | 
			
		||||
@@ -24,7 +29,7 @@
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<FullScreenModal on:clickOutside={handleCancel}>
 | 
			
		||||
<FullScreenModal on:clickOutside={handleCancel} on:escape={() => handleEscape()}>
 | 
			
		||||
  <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"
 | 
			
		||||
  >
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@
 | 
			
		||||
  role="menu"
 | 
			
		||||
  use:clickOutside
 | 
			
		||||
  on:outclick
 | 
			
		||||
  on:escape
 | 
			
		||||
>
 | 
			
		||||
  <slot />
 | 
			
		||||
</div>
 | 
			
		||||
 
 | 
			
		||||
@@ -137,7 +137,7 @@
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<BaseModal on:close={() => dispatch('close')}>
 | 
			
		||||
<BaseModal on:close={() => dispatch('close')} on:escape={() => dispatch('escape')}>
 | 
			
		||||
  <svelte:fragment slot="title">
 | 
			
		||||
    <span class="flex place-items-center gap-2">
 | 
			
		||||
      <Link size={24} />
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,10 @@
 | 
			
		||||
  import { createEventDispatcher } from 'svelte';
 | 
			
		||||
  import { fade } from 'svelte/transition';
 | 
			
		||||
 | 
			
		||||
  const dispatch = createEventDispatcher<{ clickOutside: void }>();
 | 
			
		||||
  const dispatch = createEventDispatcher<{
 | 
			
		||||
    clickOutside: void;
 | 
			
		||||
    escape: void;
 | 
			
		||||
  }>();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<section
 | 
			
		||||
@@ -11,7 +14,12 @@
 | 
			
		||||
  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"
 | 
			
		||||
>
 | 
			
		||||
  <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 />
 | 
			
		||||
  </div>
 | 
			
		||||
</section>
 | 
			
		||||
 
 | 
			
		||||
@@ -106,7 +106,11 @@
 | 
			
		||||
          </a>
 | 
			
		||||
        {/if}
 | 
			
		||||
 | 
			
		||||
        <div use:clickOutside on:outclick={() => (shouldShowAccountInfoPanel = false)}>
 | 
			
		||||
        <div
 | 
			
		||||
          use:clickOutside
 | 
			
		||||
          on:outclick={() => (shouldShowAccountInfoPanel = false)}
 | 
			
		||||
          on:escape={() => (shouldShowAccountInfoPanel = false)}
 | 
			
		||||
        >
 | 
			
		||||
          <button
 | 
			
		||||
            class="flex"
 | 
			
		||||
            on:mouseover={() => (shouldShowAccountInfo = true)}
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,7 @@
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    showBigSearchBar = false;
 | 
			
		||||
    $isSearchEnabled = false;
 | 
			
		||||
    goto(`${AppRoute.SEARCH}?${params}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -68,7 +69,7 @@
 | 
			
		||||
  };
 | 
			
		||||
</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
 | 
			
		||||
    draggable="false"
 | 
			
		||||
    autocomplete="off"
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@
 | 
			
		||||
  const dispatch = createEventDispatcher();
 | 
			
		||||
</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="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 {
 | 
			
		||||
  'on:outclick'?: (e: CustomEvent) => void;
 | 
			
		||||
  'on:escape'?: (e: CustomEvent) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function clickOutside(node: HTMLElement): ActionReturn<void, Attributes> {
 | 
			
		||||
@@ -14,7 +15,7 @@ export function clickOutside(node: HTMLElement): ActionReturn<void, Attributes>
 | 
			
		||||
 | 
			
		||||
  const handleKey = (event: KeyboardEvent) => {
 | 
			
		||||
    if (event.key === 'Escape') {
 | 
			
		||||
      node.dispatchEvent(new CustomEvent('outclick'));
 | 
			
		||||
      node.dispatchEvent(new CustomEvent('escape'));
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -314,7 +314,7 @@
 | 
			
		||||
 | 
			
		||||
<!-- Context Menu -->
 | 
			
		||||
{#if $isShowContextMenu}
 | 
			
		||||
  <ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu}>
 | 
			
		||||
  <ContextMenu {...$contextMenuPosition} on:outclick={closeAlbumContextMenu} on:escape={closeAlbumContextMenu}>
 | 
			
		||||
    <MenuOption on:click={() => setAlbumToDelete()}>
 | 
			
		||||
      <span class="flex place-content-center place-items-center gap-2">
 | 
			
		||||
        <DeleteOutline size="18" />
 | 
			
		||||
 
 | 
			
		||||
@@ -48,6 +48,8 @@
 | 
			
		||||
 | 
			
		||||
  export let data: PageData;
 | 
			
		||||
 | 
			
		||||
  let { isViewing: showAssetViewer } = assetViewingStore;
 | 
			
		||||
 | 
			
		||||
  let 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 { data } = await api.albumApi.getAlbumInfo({ id: album.id, withoutAssets: true });
 | 
			
		||||
    album = data;
 | 
			
		||||
@@ -403,6 +429,7 @@
 | 
			
		||||
      isSelectionMode={viewMode === ViewMode.SELECT_THUMBNAIL}
 | 
			
		||||
      singleSelect={viewMode === ViewMode.SELECT_THUMBNAIL}
 | 
			
		||||
      on:select={({ detail: asset }) => handleUpdateThumbnail(asset.id)}
 | 
			
		||||
      on:escape={handleEscape}
 | 
			
		||||
    >
 | 
			
		||||
      {#if viewMode !== ViewMode.SELECT_THUMBNAIL}
 | 
			
		||||
        <!-- ALBUM TITLE -->
 | 
			
		||||
@@ -540,6 +567,6 @@
 | 
			
		||||
  <EditDescriptionModal
 | 
			
		||||
    {album}
 | 
			
		||||
    on:close={() => (isEditingDescription = false)}
 | 
			
		||||
    on:updated={({ detail: description }) => handleUpdateDescription(description)}
 | 
			
		||||
    on:save={({ detail: description }) => handleUpdateDescription(description)}
 | 
			
		||||
  />
 | 
			
		||||
{/if}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,9 +33,12 @@
 | 
			
		||||
  import Plus from 'svelte-material-icons/Plus.svelte';
 | 
			
		||||
  import type { PageData } from './$types';
 | 
			
		||||
  import { clickOutside } from '$lib/utils/click-outside';
 | 
			
		||||
  import { assetViewingStore } from '$lib/stores/asset-viewing.store';
 | 
			
		||||
 | 
			
		||||
  export let data: PageData;
 | 
			
		||||
 | 
			
		||||
  let { isViewing: showAssetViewer } = assetViewingStore;
 | 
			
		||||
 | 
			
		||||
  enum ViewMode {
 | 
			
		||||
    VIEW_ASSETS = 'view-assets',
 | 
			
		||||
    SELECT_FACE = 'select-face',
 | 
			
		||||
@@ -86,6 +89,18 @@
 | 
			
		||||
      viewMode = ViewMode.MERGE_FACES;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
  const handleEscape = () => {
 | 
			
		||||
    if ($showAssetViewer) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if ($isMultiSelectState) {
 | 
			
		||||
      assetInteractionStore.clearMultiselect();
 | 
			
		||||
      return;
 | 
			
		||||
    } else {
 | 
			
		||||
      goto(previousRoute);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  afterNavigate(({ from }) => {
 | 
			
		||||
    // Prevent setting previousRoute to the current page.
 | 
			
		||||
    if (from && from.route.id !== $page.route.id) {
 | 
			
		||||
@@ -337,6 +352,7 @@
 | 
			
		||||
      isSelectionMode={viewMode === ViewMode.SELECT_FACE}
 | 
			
		||||
      singleSelect={viewMode === ViewMode.SELECT_FACE}
 | 
			
		||||
      on:select={({ detail: asset }) => handleSelectFeaturePhoto(asset)}
 | 
			
		||||
      on:escape={handleEscape}
 | 
			
		||||
    >
 | 
			
		||||
      {#if viewMode === ViewMode.VIEW_ASSETS || viewMode === ViewMode.SUGGEST_MERGE || viewMode === ViewMode.BIRTH_DATE}
 | 
			
		||||
        <!-- Face information block -->
 | 
			
		||||
@@ -344,7 +360,8 @@
 | 
			
		||||
          role="button"
 | 
			
		||||
          class="relative w-fit p-4 sm:px-6"
 | 
			
		||||
          use:clickOutside
 | 
			
		||||
          on:outclick={() => handleCancelEditName()}
 | 
			
		||||
          on:outclick={handleCancelEditName}
 | 
			
		||||
          on:escape={handleCancelEditName}
 | 
			
		||||
        >
 | 
			
		||||
          <section class="flex w-96 place-items-center border-black">
 | 
			
		||||
            {#if isEditingName}
 | 
			
		||||
 
 | 
			
		||||
@@ -21,27 +21,47 @@
 | 
			
		||||
  import DotsVertical from 'svelte-material-icons/DotsVertical.svelte';
 | 
			
		||||
  import Plus from 'svelte-material-icons/Plus.svelte';
 | 
			
		||||
  import type { PageData } from './$types';
 | 
			
		||||
  import { assetViewingStore } from '$lib/stores/asset-viewing.store';
 | 
			
		||||
 | 
			
		||||
  export let data: PageData;
 | 
			
		||||
 | 
			
		||||
  let { isViewing: showAssetViewer } = assetViewingStore;
 | 
			
		||||
  let handleEscapeKey = false;
 | 
			
		||||
  const assetStore = new AssetStore({ size: TimeBucketSize.Month, isArchived: false });
 | 
			
		||||
  const assetInteractionStore = createAssetInteractionStore();
 | 
			
		||||
  const { isMultiSelectState, selectedAssets } = assetInteractionStore;
 | 
			
		||||
 | 
			
		||||
  $: isAllFavorite = Array.from($selectedAssets).every((asset) => asset.isFavorite);
 | 
			
		||||
 | 
			
		||||
  const handleEscape = () => {
 | 
			
		||||
    if ($showAssetViewer) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (handleEscapeKey) {
 | 
			
		||||
      handleEscapeKey = false;
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if ($isMultiSelectState) {
 | 
			
		||||
      assetInteractionStore.clearMultiselect();
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<UserPageLayout user={data.user} hideNavbar={$isMultiSelectState} showUploadButton>
 | 
			
		||||
  <svelte:fragment slot="header">
 | 
			
		||||
    {#if $isMultiSelectState}
 | 
			
		||||
      <AssetSelectControlBar assets={$selectedAssets} clearSelect={() => assetInteractionStore.clearMultiselect()}>
 | 
			
		||||
        <CreateSharedLink />
 | 
			
		||||
        <CreateSharedLink on:escape={() => (handleEscapeKey = true)} />
 | 
			
		||||
        <SelectAllAssets {assetStore} {assetInteractionStore} />
 | 
			
		||||
        <AssetSelectContextMenu icon={Plus} title="Add">
 | 
			
		||||
          <AddToAlbum />
 | 
			
		||||
          <AddToAlbum shared />
 | 
			
		||||
        </AssetSelectContextMenu>
 | 
			
		||||
        <DeleteAssets onAssetDelete={(assetId) => assetStore.removeAsset(assetId)} />
 | 
			
		||||
        <DeleteAssets
 | 
			
		||||
          on:escape={() => (handleEscapeKey = true)}
 | 
			
		||||
          onAssetDelete={(assetId) => assetStore.removeAsset(assetId)}
 | 
			
		||||
        />
 | 
			
		||||
        <AssetSelectContextMenu icon={DotsVertical} title="Menu">
 | 
			
		||||
          <FavoriteAction menuItem removeFavorite={isAllFavorite} />
 | 
			
		||||
          <DownloadAction menuItem />
 | 
			
		||||
@@ -52,7 +72,7 @@
 | 
			
		||||
    {/if}
 | 
			
		||||
  </svelte:fragment>
 | 
			
		||||
  <svelte:fragment slot="content">
 | 
			
		||||
    <AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE}>
 | 
			
		||||
    <AssetGrid {assetStore} {assetInteractionStore} removeAction={AssetAction.ARCHIVE} on:escape={handleEscape}>
 | 
			
		||||
      {#if data.user.memoriesEnabled}
 | 
			
		||||
        <MemoryLane />
 | 
			
		||||
      {/if}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,10 @@
 | 
			
		||||
    if (!$showAssetViewer) {
 | 
			
		||||
      switch (event.key) {
 | 
			
		||||
        case 'Escape':
 | 
			
		||||
          if (isMultiSelectionMode) {
 | 
			
		||||
            selectedAssets = new Set();
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          if (!$preventRaceConditionSearchBar) {
 | 
			
		||||
            goto(previousRoute);
 | 
			
		||||
          }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user