forked from Cutlery/immich
		
	feat(web): force delete with shift key (#6239)
* feat: force delete with shift key * fix: types import * pr feedback * fix: permanently delete assets * fix: format * fix: remove unused variable * change info title * simplify * fix: rename function name * pr feedback * simplify * pr feedback * add toggle in the user settings * fix: trash settings, input label, and wording --------- Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
		
							parent
							
								
									0350058689
								
							
						
					
					
						commit
						c317feaf93
					
				@ -20,10 +20,9 @@
 | 
				
			|||||||
  import VideoViewer from './video-viewer.svelte';
 | 
					  import VideoViewer from './video-viewer.svelte';
 | 
				
			||||||
  import PanoramaViewer from './panorama-viewer.svelte';
 | 
					  import PanoramaViewer from './panorama-viewer.svelte';
 | 
				
			||||||
  import { AppRoute, AssetAction, ProjectionType } from '$lib/constants';
 | 
					  import { AppRoute, AssetAction, ProjectionType } from '$lib/constants';
 | 
				
			||||||
  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
					 | 
				
			||||||
  import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte';
 | 
					  import ProfileImageCropper from '../shared-components/profile-image-cropper.svelte';
 | 
				
			||||||
  import { isShowDetail } from '$lib/stores/preferences.store';
 | 
					  import { isShowDetail, showDeleteModal } from '$lib/stores/preferences.store';
 | 
				
			||||||
  import { addAssetsToAlbum, downloadFile, getAssetType } from '$lib/utils/asset-utils';
 | 
					  import { addAssetsToAlbum, downloadFile } from '$lib/utils/asset-utils';
 | 
				
			||||||
  import NavigationArea from './navigation-area.svelte';
 | 
					  import NavigationArea from './navigation-area.svelte';
 | 
				
			||||||
  import { browser } from '$app/environment';
 | 
					  import { browser } from '$app/environment';
 | 
				
			||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					  import { handleError } from '$lib/utils/handle-error';
 | 
				
			||||||
@ -42,13 +41,13 @@
 | 
				
			|||||||
  import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
 | 
					  import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
 | 
				
			||||||
  import SlideshowBar from './slideshow-bar.svelte';
 | 
					  import SlideshowBar from './slideshow-bar.svelte';
 | 
				
			||||||
  import { user } from '$lib/stores/user.store';
 | 
					  import { user } from '$lib/stores/user.store';
 | 
				
			||||||
 | 
					  import DeleteAssetDialog from '../photos-page/delete-asset-dialog.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let assetStore: AssetStore | null = null;
 | 
					  export let assetStore: AssetStore | null = null;
 | 
				
			||||||
  export let asset: AssetResponseDto;
 | 
					  export let asset: AssetResponseDto;
 | 
				
			||||||
  export let showNavigation = true;
 | 
					  export let showNavigation = true;
 | 
				
			||||||
  export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 | 
					  export let sharedLink: SharedLinkResponseDto | undefined = undefined;
 | 
				
			||||||
  $: isTrashEnabled = $featureFlags.trash;
 | 
					  $: isTrashEnabled = $featureFlags.trash;
 | 
				
			||||||
  export let force = false;
 | 
					 | 
				
			||||||
  export let withStacked = false;
 | 
					  export let withStacked = false;
 | 
				
			||||||
  export let isShared = false;
 | 
					  export let isShared = false;
 | 
				
			||||||
  export let album: AlbumResponseDto | null = null;
 | 
					  export let album: AlbumResponseDto | null = null;
 | 
				
			||||||
@ -279,7 +278,7 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      case 'Delete':
 | 
					      case 'Delete':
 | 
				
			||||||
        trashOrDelete();
 | 
					        trashOrDelete(shiftKey);
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
      case 'Escape':
 | 
					      case 'Escape':
 | 
				
			||||||
        if (isShowDeleteConfirmation) {
 | 
					        if (isShowDeleteConfirmation) {
 | 
				
			||||||
@ -360,10 +359,18 @@
 | 
				
			|||||||
    $isShowDetail = !$isShowDetail;
 | 
					    $isShowDetail = !$isShowDetail;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: trashOrDelete = !(force || !isTrashEnabled)
 | 
					  const trashOrDelete = (force: boolean = false) => {
 | 
				
			||||||
    ? trashAsset
 | 
					    if (force || !isTrashEnabled) {
 | 
				
			||||||
    : () => {
 | 
					      if ($showDeleteModal) {
 | 
				
			||||||
        isShowDeleteConfirmation = true;
 | 
					        isShowDeleteConfirmation = true;
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      deleteAsset();
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    trashAsset();
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const trashAsset = async () => {
 | 
					  const trashAsset = async () => {
 | 
				
			||||||
@ -576,7 +583,7 @@
 | 
				
			|||||||
        on:back={closeViewer}
 | 
					        on:back={closeViewer}
 | 
				
			||||||
        on:showDetail={showDetailInfoHandler}
 | 
					        on:showDetail={showDetailInfoHandler}
 | 
				
			||||||
        on:download={() => downloadFile(asset)}
 | 
					        on:download={() => downloadFile(asset)}
 | 
				
			||||||
        on:delete={trashOrDelete}
 | 
					        on:delete={() => trashOrDelete()}
 | 
				
			||||||
        on:favorite={toggleFavorite}
 | 
					        on:favorite={toggleFavorite}
 | 
				
			||||||
        on:addToAlbum={() => openAlbumPicker(false)}
 | 
					        on:addToAlbum={() => openAlbumPicker(false)}
 | 
				
			||||||
        on:addToSharedAlbum={() => openAlbumPicker(true)}
 | 
					        on:addToSharedAlbum={() => openAlbumPicker(true)}
 | 
				
			||||||
@ -764,20 +771,12 @@
 | 
				
			|||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {#if isShowDeleteConfirmation}
 | 
					  {#if isShowDeleteConfirmation}
 | 
				
			||||||
    <ConfirmDialogue
 | 
					    <DeleteAssetDialog
 | 
				
			||||||
      title="Delete {getAssetType(asset.type)}"
 | 
					      size={1}
 | 
				
			||||||
      confirmText="Delete"
 | 
					 | 
				
			||||||
      on:confirm={deleteAsset}
 | 
					 | 
				
			||||||
      on:cancel={() => (isShowDeleteConfirmation = false)}
 | 
					      on:cancel={() => (isShowDeleteConfirmation = false)}
 | 
				
			||||||
    >
 | 
					      on:escape={() => (isShowDeleteConfirmation = false)}
 | 
				
			||||||
      <svelte:fragment slot="prompt">
 | 
					      on:confirm={() => deleteAsset()}
 | 
				
			||||||
        <p>
 | 
					    />
 | 
				
			||||||
          Are you sure you want to delete this {getAssetType(asset.type).toLowerCase()}? This will also remove it from
 | 
					 | 
				
			||||||
          its album(s).
 | 
					 | 
				
			||||||
        </p>
 | 
					 | 
				
			||||||
        <p><b>You cannot undo this action!</b></p>
 | 
					 | 
				
			||||||
      </svelte:fragment>
 | 
					 | 
				
			||||||
    </ConfirmDialogue>
 | 
					 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {#if isShowProfileImageCrop}
 | 
					  {#if isShowProfileImageCrop}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,8 +7,9 @@
 | 
				
			|||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					  import { handleError } from '$lib/utils/handle-error';
 | 
				
			||||||
  import { api } from '@api';
 | 
					  import { api } from '@api';
 | 
				
			||||||
  import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
 | 
					  import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
 | 
				
			||||||
  import { OnArchive, getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
					  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
				
			||||||
  import { mdiArchiveArrowUpOutline, mdiArchiveArrowDownOutline, mdiTimerSand } from '@mdi/js';
 | 
					  import { mdiArchiveArrowUpOutline, mdiArchiveArrowDownOutline, mdiTimerSand } from '@mdi/js';
 | 
				
			||||||
 | 
					  import type { OnArchive } from '$lib/utils/actions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let onArchive: OnArchive | undefined = undefined;
 | 
					  export let onArchive: OnArchive | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,23 +1,18 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
					  import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
 | 
				
			||||||
  import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
 | 
					 | 
				
			||||||
  import {
 | 
					 | 
				
			||||||
    NotificationType,
 | 
					 | 
				
			||||||
    notificationController,
 | 
					 | 
				
			||||||
  } from '$lib/components/shared-components/notification/notification';
 | 
					 | 
				
			||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					 | 
				
			||||||
  import { api } from '@api';
 | 
					 | 
				
			||||||
  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 { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
				
			||||||
  import { createEventDispatcher } from '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 { OnDelete, deleteAssets } from '$lib/utils/actions';
 | 
				
			||||||
 | 
					  import DeleteAssetDialog from '../delete-asset-dialog.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let onAssetDelete: OnAssetDelete;
 | 
					  export let onAssetDelete: OnDelete;
 | 
				
			||||||
  export let menuItem = false;
 | 
					  export let menuItem = false;
 | 
				
			||||||
  export let force = !$featureFlags.trash;
 | 
					  export let force = !$featureFlags.trash;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { clearSelect, getOwnedAssets } = getAssetControlContext();
 | 
					  const { getOwnedAssets } = getAssetControlContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const dispatch = createEventDispatcher<{
 | 
					  const dispatch = createEventDispatcher<{
 | 
				
			||||||
    escape: void;
 | 
					    escape: void;
 | 
				
			||||||
@ -37,28 +32,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const handleDelete = async () => {
 | 
					  const handleDelete = async () => {
 | 
				
			||||||
    loading = true;
 | 
					    loading = true;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
    const ids = Array.from(getOwnedAssets())
 | 
					    const ids = Array.from(getOwnedAssets())
 | 
				
			||||||
      .filter((a) => !a.isExternal)
 | 
					      .filter((a) => !a.isExternal)
 | 
				
			||||||
      .map((a) => a.id);
 | 
					      .map((a) => a.id);
 | 
				
			||||||
      await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } });
 | 
					    await deleteAssets(force, onAssetDelete, ids);
 | 
				
			||||||
      for (const id of ids) {
 | 
					 | 
				
			||||||
        onAssetDelete(id);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      notificationController.show({
 | 
					 | 
				
			||||||
        message: `${force ? 'Permanently deleted' : 'Trashed'} ${ids.length} assets`,
 | 
					 | 
				
			||||||
        type: NotificationType.Info,
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      clearSelect();
 | 
					 | 
				
			||||||
    } catch (e) {
 | 
					 | 
				
			||||||
      handleError(e, 'Error deleting assets');
 | 
					 | 
				
			||||||
    } finally {
 | 
					 | 
				
			||||||
    isShowConfirmation = false;
 | 
					    isShowConfirmation = false;
 | 
				
			||||||
    loading = false;
 | 
					    loading = false;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const escape = () => {
 | 
					  const escape = () => {
 | 
				
			||||||
@ -76,23 +55,10 @@
 | 
				
			|||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if isShowConfirmation}
 | 
					{#if isShowConfirmation}
 | 
				
			||||||
  <ConfirmDialogue
 | 
					  <DeleteAssetDialog
 | 
				
			||||||
    title="Permanently Delete Asset{getOwnedAssets().size > 1 ? 's' : ''}"
 | 
					    size={getOwnedAssets().size}
 | 
				
			||||||
    confirmText="Delete"
 | 
					 | 
				
			||||||
    on:confirm={handleDelete}
 | 
					    on:confirm={handleDelete}
 | 
				
			||||||
    on:cancel={() => (isShowConfirmation = false)}
 | 
					    on:cancel={() => (isShowConfirmation = false)}
 | 
				
			||||||
    on:escape={escape}
 | 
					    on:escape={escape}
 | 
				
			||||||
  >
 | 
					  />
 | 
				
			||||||
    <svelte:fragment slot="prompt">
 | 
					 | 
				
			||||||
      <p>
 | 
					 | 
				
			||||||
        Are you sure you want to permanently delete
 | 
					 | 
				
			||||||
        {#if getOwnedAssets().size > 1}
 | 
					 | 
				
			||||||
          these <b>{getOwnedAssets().size}</b> assets? This will also remove them from their album(s).
 | 
					 | 
				
			||||||
        {:else}
 | 
					 | 
				
			||||||
          this asset? This will also remove it from its album(s).
 | 
					 | 
				
			||||||
        {/if}
 | 
					 | 
				
			||||||
      </p>
 | 
					 | 
				
			||||||
      <p><b>You cannot undo this action!</b></p>
 | 
					 | 
				
			||||||
    </svelte:fragment>
 | 
					 | 
				
			||||||
  </ConfirmDialogue>
 | 
					 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,8 +7,9 @@
 | 
				
			|||||||
  } from '$lib/components/shared-components/notification/notification';
 | 
					  } from '$lib/components/shared-components/notification/notification';
 | 
				
			||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					  import { handleError } from '$lib/utils/handle-error';
 | 
				
			||||||
  import { api } from '@api';
 | 
					  import { api } from '@api';
 | 
				
			||||||
  import { OnFavorite, getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
					  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
				
			||||||
  import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js';
 | 
					  import { mdiHeartMinusOutline, mdiHeartOutline, mdiTimerSand } from '@mdi/js';
 | 
				
			||||||
 | 
					  import type { OnFavorite } from '$lib/utils/actions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let onFavorite: OnFavorite | undefined = undefined;
 | 
					  export let onFavorite: OnFavorite | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,8 +7,9 @@
 | 
				
			|||||||
  import { api } from '@api';
 | 
					  import { api } from '@api';
 | 
				
			||||||
  import Icon from '$lib/components/elements/icon.svelte';
 | 
					  import Icon from '$lib/components/elements/icon.svelte';
 | 
				
			||||||
  import Button from '../../elements/buttons/button.svelte';
 | 
					  import Button from '../../elements/buttons/button.svelte';
 | 
				
			||||||
  import { OnRestore, getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
					  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
				
			||||||
  import { mdiHistory } from '@mdi/js';
 | 
					  import { mdiHistory } from '@mdi/js';
 | 
				
			||||||
 | 
					  import type { OnRestore } from '$lib/utils/actions';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let onRestore: OnRestore | undefined = undefined;
 | 
					  export let onRestore: OnRestore | undefined = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,13 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
 | 
					  import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
 | 
				
			||||||
  import { api } from '@api';
 | 
					  import { api } from '@api';
 | 
				
			||||||
  import { OnStack, getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
					  import { getAssetControlContext } from '../asset-select-control-bar.svelte';
 | 
				
			||||||
  import {
 | 
					  import {
 | 
				
			||||||
    NotificationType,
 | 
					    NotificationType,
 | 
				
			||||||
    notificationController,
 | 
					    notificationController,
 | 
				
			||||||
  } from '$lib/components/shared-components/notification/notification';
 | 
					  } from '$lib/components/shared-components/notification/notification';
 | 
				
			||||||
  import { handleError } from '$lib/utils/handle-error';
 | 
					  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 = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@
 | 
				
			|||||||
  import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
 | 
					  import type { AssetInteractionStore } from '$lib/stores/asset-interaction.store';
 | 
				
			||||||
  import { assetViewingStore } from '$lib/stores/asset-viewing.store';
 | 
					  import { assetViewingStore } from '$lib/stores/asset-viewing.store';
 | 
				
			||||||
  import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store';
 | 
					  import { BucketPosition, type AssetStore, type Viewport } from '$lib/stores/assets.store';
 | 
				
			||||||
  import { locale } from '$lib/stores/preferences.store';
 | 
					  import { locale, showDeleteModal } from '$lib/stores/preferences.store';
 | 
				
			||||||
  import { isSearchEnabled } from '$lib/stores/search.store';
 | 
					  import { isSearchEnabled } from '$lib/stores/search.store';
 | 
				
			||||||
  import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
 | 
					  import { formatGroupTitle, splitBucketIntoDateGroups } from '$lib/utils/timeline-util';
 | 
				
			||||||
  import type { AlbumResponseDto, AssetResponseDto } from '@api';
 | 
					  import type { AlbumResponseDto, AssetResponseDto } from '@api';
 | 
				
			||||||
@ -19,6 +19,8 @@
 | 
				
			|||||||
  import AssetDateGroup from './asset-date-group.svelte';
 | 
					  import AssetDateGroup from './asset-date-group.svelte';
 | 
				
			||||||
  import { featureFlags } from '$lib/stores/server-config.store';
 | 
					  import { featureFlags } from '$lib/stores/server-config.store';
 | 
				
			||||||
  import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
 | 
					  import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
 | 
				
			||||||
 | 
					  import { deleteAssets } from '$lib/utils/actions';
 | 
				
			||||||
 | 
					  import DeleteAssetDialog from './delete-asset-dialog.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let isSelectionMode = false;
 | 
					  export let isSelectionMode = false;
 | 
				
			||||||
  export let singleSelect = false;
 | 
					  export let singleSelect = false;
 | 
				
			||||||
@ -28,9 +30,9 @@
 | 
				
			|||||||
  export let withStacked = false;
 | 
					  export let withStacked = false;
 | 
				
			||||||
  export let isShared = false;
 | 
					  export let isShared = false;
 | 
				
			||||||
  export let album: AlbumResponseDto | null = null;
 | 
					  export let album: AlbumResponseDto | null = null;
 | 
				
			||||||
 | 
					  export let isShowDeleteConfirmation = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: isTrashEnabled = $featureFlags.loaded && $featureFlags.trash;
 | 
					  $: isTrashEnabled = $featureFlags.loaded && $featureFlags.trash;
 | 
				
			||||||
  export let forceDelete = false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
 | 
					  const { assetSelectionCandidates, assetSelectionStart, selectedGroup, selectedAssets, isMultiSelectState } =
 | 
				
			||||||
    assetInteractionStore;
 | 
					    assetInteractionStore;
 | 
				
			||||||
@ -42,6 +44,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  $: timelineY = element?.scrollTop || 0;
 | 
					  $: timelineY = element?.scrollTop || 0;
 | 
				
			||||||
  $: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
 | 
					  $: isEmpty = $assetStore.initialized && $assetStore.buckets.length === 0;
 | 
				
			||||||
 | 
					  $: idsSelectedAssets = Array.from($selectedAssets)
 | 
				
			||||||
 | 
					    .filter((a) => !a.isExternal)
 | 
				
			||||||
 | 
					    .map((a) => a.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
 | 
					  const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event);
 | 
				
			||||||
  const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
 | 
					  const dispatch = createEventDispatcher<{ select: AssetResponseDto; escape: void }>();
 | 
				
			||||||
@ -65,13 +70,22 @@
 | 
				
			|||||||
    assetStore.disconnect();
 | 
					    assetStore.disconnect();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const trashOrDelete = (force: boolean = false) => {
 | 
				
			||||||
 | 
					    isShowDeleteConfirmation = false;
 | 
				
			||||||
 | 
					    deleteAssets(!(isTrashEnabled && !force), (assetId) => assetStore.removeAsset(assetId), idsSelectedAssets);
 | 
				
			||||||
 | 
					    assetInteractionStore.clearMultiselect();
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const handleKeyboardPress = (event: KeyboardEvent) => {
 | 
					  const handleKeyboardPress = (event: KeyboardEvent) => {
 | 
				
			||||||
    if ($isSearchEnabled || shouldIgnoreShortcut(event)) {
 | 
					    if ($isSearchEnabled || shouldIgnoreShortcut(event)) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const key = event.key;
 | 
				
			||||||
 | 
					    const shiftKey = event.shiftKey;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!$showAssetViewer) {
 | 
					    if (!$showAssetViewer) {
 | 
				
			||||||
      switch (event.key) {
 | 
					      switch (key) {
 | 
				
			||||||
        case 'Escape':
 | 
					        case 'Escape':
 | 
				
			||||||
          dispatch('escape');
 | 
					          dispatch('escape');
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
@ -85,6 +99,20 @@
 | 
				
			|||||||
          event.preventDefault();
 | 
					          event.preventDefault();
 | 
				
			||||||
          goto(AppRoute.EXPLORE);
 | 
					          goto(AppRoute.EXPLORE);
 | 
				
			||||||
          return;
 | 
					          return;
 | 
				
			||||||
 | 
					        case 'Delete':
 | 
				
			||||||
 | 
					          if ($isMultiSelectState) {
 | 
				
			||||||
 | 
					            let force = false;
 | 
				
			||||||
 | 
					            if (shiftKey || !isTrashEnabled) {
 | 
				
			||||||
 | 
					              if ($showDeleteModal) {
 | 
				
			||||||
 | 
					                isShowDeleteConfirmation = true;
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              force = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            trashOrDelete(force);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
@ -331,6 +359,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
<svelte:window on:keydown={onKeyDown} on:keyup={onKeyUp} on:selectstart={onSelectStart} />
 | 
					<svelte:window on:keydown={onKeyDown} on:keyup={onKeyUp} on:selectstart={onSelectStart} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if isShowDeleteConfirmation}
 | 
				
			||||||
 | 
					  <DeleteAssetDialog
 | 
				
			||||||
 | 
					    size={idsSelectedAssets.length}
 | 
				
			||||||
 | 
					    on:cancel={() => (isShowDeleteConfirmation = false)}
 | 
				
			||||||
 | 
					    on:confirm={() => trashOrDelete()}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if showShortcuts}
 | 
					{#if showShortcuts}
 | 
				
			||||||
  <ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
 | 
					  <ShowShortcuts on:close={() => (showShortcuts = !showShortcuts)} />
 | 
				
			||||||
{/if}
 | 
					{/if}
 | 
				
			||||||
@ -411,7 +447,6 @@
 | 
				
			|||||||
      {withStacked}
 | 
					      {withStacked}
 | 
				
			||||||
      {assetStore}
 | 
					      {assetStore}
 | 
				
			||||||
      asset={$viewingAsset}
 | 
					      asset={$viewingAsset}
 | 
				
			||||||
      force={forceDelete || !isTrashEnabled}
 | 
					 | 
				
			||||||
      {isShared}
 | 
					      {isShared}
 | 
				
			||||||
      {album}
 | 
					      {album}
 | 
				
			||||||
      on:previous={() => handlePrevious()}
 | 
					      on:previous={() => handlePrevious()}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,6 @@
 | 
				
			|||||||
<script lang="ts" context="module">
 | 
					<script lang="ts" context="module">
 | 
				
			||||||
  import { createContext } from '$lib/utils/context';
 | 
					  import { createContext } from '$lib/utils/context';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type OnAssetDelete = (assetId: string) => void;
 | 
					 | 
				
			||||||
  export type OnRestore = (ids: string[]) => void;
 | 
					 | 
				
			||||||
  export type OnArchive = (ids: string[], isArchived: boolean) => void;
 | 
					 | 
				
			||||||
  export type OnFavorite = (ids: string[], favorite: boolean) => void;
 | 
					 | 
				
			||||||
  export type OnStack = (ids: string[]) => void;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  export interface AssetControlContext {
 | 
					  export interface AssetControlContext {
 | 
				
			||||||
    // Wrap assets in a function, because context isn't reactive.
 | 
					    // Wrap assets in a function, because context isn't reactive.
 | 
				
			||||||
    getAssets: () => Set<AssetResponseDto>; // All assets includes partners' assets
 | 
					    getAssets: () => Set<AssetResponseDto>; // All assets includes partners' assets
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,57 @@
 | 
				
			|||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
 | 
					  import ConfirmDialogue from '../shared-components/confirm-dialogue.svelte';
 | 
				
			||||||
 | 
					  import { showDeleteModal } from '$lib/stores/preferences.store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export let size: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let checked = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const dispatch = createEventDispatcher<{
 | 
				
			||||||
 | 
					    confirm: void;
 | 
				
			||||||
 | 
					    cancel: void;
 | 
				
			||||||
 | 
					  }>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const onToggle = () => {
 | 
				
			||||||
 | 
					    checked = !checked;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const handleConfirm = () => {
 | 
				
			||||||
 | 
					    if (checked) {
 | 
				
			||||||
 | 
					      $showDeleteModal = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    dispatch('confirm');
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<ConfirmDialogue
 | 
				
			||||||
 | 
					  title="Permanently Delete Asset{size > 1 ? 's' : ''}"
 | 
				
			||||||
 | 
					  confirmText="Delete"
 | 
				
			||||||
 | 
					  on:confirm={handleConfirm}
 | 
				
			||||||
 | 
					  on:cancel={() => dispatch('cancel')}
 | 
				
			||||||
 | 
					  on:escape={() => dispatch('cancel')}
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
					  <svelte:fragment slot="prompt">
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					      Are you sure you want to permanently delete
 | 
				
			||||||
 | 
					      {#if size > 1}
 | 
				
			||||||
 | 
					        these <b>{size}</b> assets? This will also remove them from their album(s).
 | 
				
			||||||
 | 
					      {:else}
 | 
				
			||||||
 | 
					        this asset? This will also remove it from its album(s).
 | 
				
			||||||
 | 
					      {/if}
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					    <p><b>You cannot undo this action!</b></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div class="flex gap-2 items-center justify-center pt-4">
 | 
				
			||||||
 | 
					      <label id="confirm-label" for="confirm-input">Do not show this message again</label>
 | 
				
			||||||
 | 
					      <input
 | 
				
			||||||
 | 
					        id="confirm-input"
 | 
				
			||||||
 | 
					        aria-labelledby="confirm-input"
 | 
				
			||||||
 | 
					        class="disabled::cursor-not-allowed h-3 w-3 opacity-1"
 | 
				
			||||||
 | 
					        type="checkbox"
 | 
				
			||||||
 | 
					        bind:checked
 | 
				
			||||||
 | 
					        on:click={onToggle}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </svelte:fragment>
 | 
				
			||||||
 | 
					</ConfirmDialogue>
 | 
				
			||||||
@ -2,9 +2,21 @@
 | 
				
			|||||||
  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 | 
					  import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
 | 
				
			||||||
  import { createEventDispatcher } from 'svelte';
 | 
					  import { createEventDispatcher } from 'svelte';
 | 
				
			||||||
  import FullScreenModal from './full-screen-modal.svelte';
 | 
					  import FullScreenModal from './full-screen-modal.svelte';
 | 
				
			||||||
  import { mdiClose } from '@mdi/js';
 | 
					  import { mdiClose, mdiInformationOutline } from '@mdi/js';
 | 
				
			||||||
 | 
					  import Icon from '../elements/icon.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const shortcuts = {
 | 
					  interface Shortcuts {
 | 
				
			||||||
 | 
					    general: ExplainedShortcut[];
 | 
				
			||||||
 | 
					    actions: ExplainedShortcut[];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  interface ExplainedShortcut {
 | 
				
			||||||
 | 
					    key: string[];
 | 
				
			||||||
 | 
					    action: string;
 | 
				
			||||||
 | 
					    info?: string;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const shortcuts: Shortcuts = {
 | 
				
			||||||
    general: [
 | 
					    general: [
 | 
				
			||||||
      { key: ['←', '→'], action: 'Previous or next photo' },
 | 
					      { key: ['←', '→'], action: 'Previous or next photo' },
 | 
				
			||||||
      { key: ['Esc'], action: 'Back, close, or deselect' },
 | 
					      { key: ['Esc'], action: 'Back, close, or deselect' },
 | 
				
			||||||
@ -16,7 +28,7 @@
 | 
				
			|||||||
      { key: ['⇧', 'a'], action: 'Archive or unarchive photo' },
 | 
					      { key: ['⇧', 'a'], action: 'Archive or unarchive photo' },
 | 
				
			||||||
      { key: ['⇧', 'd'], action: 'Download' },
 | 
					      { key: ['⇧', 'd'], action: 'Download' },
 | 
				
			||||||
      { key: ['Space'], action: 'Play or pause video' },
 | 
					      { key: ['Space'], action: 'Play or pause video' },
 | 
				
			||||||
      { key: ['Del'], action: 'Delete Asset' },
 | 
					      { key: ['Del'], action: 'Trash/Delete Asset', info: 'press ⇧ to permanently delete asset' },
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  const dispatch = createEventDispatcher<{
 | 
					  const dispatch = createEventDispatcher<{
 | 
				
			||||||
@ -71,7 +83,12 @@
 | 
				
			|||||||
                    </p>
 | 
					                    </p>
 | 
				
			||||||
                  {/each}
 | 
					                  {/each}
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
 | 
					                <div class="flex items-center gap-2">
 | 
				
			||||||
                  <p class="mb-1 mt-1 flex">{shortcut.action}</p>
 | 
					                  <p class="mb-1 mt-1 flex">{shortcut.action}</p>
 | 
				
			||||||
 | 
					                  {#if shortcut.info}
 | 
				
			||||||
 | 
					                    <Icon path={mdiInformationOutline} title={shortcut.info} />
 | 
				
			||||||
 | 
					                  {/if}
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
            {/each}
 | 
					            {/each}
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import SettingSwitch from '../admin-page/settings/setting-switch.svelte';
 | 
				
			||||||
 | 
					  import { showDeleteModal } from '$lib/stores/preferences.store';
 | 
				
			||||||
 | 
					  import { fade } from 'svelte/transition';
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<section class="my-4">
 | 
				
			||||||
 | 
					  <div in:fade={{ duration: 500 }}>
 | 
				
			||||||
 | 
					    <form autocomplete="off" on:submit|preventDefault>
 | 
				
			||||||
 | 
					      <div class="ml-4 mt-4 flex flex-col gap-4">
 | 
				
			||||||
 | 
					        <div class="ml-4">
 | 
				
			||||||
 | 
					          <SettingSwitch
 | 
				
			||||||
 | 
					            title="Permanent deletion warning"
 | 
				
			||||||
 | 
					            subtitle="Show a warning when permanently deleting assets"
 | 
				
			||||||
 | 
					            bind:checked={$showDeleteModal}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </form>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</section>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="ml-4 mb-4"></div>
 | 
				
			||||||
@ -15,6 +15,7 @@
 | 
				
			|||||||
  import UserProfileSettings from './user-profile-settings.svelte';
 | 
					  import UserProfileSettings from './user-profile-settings.svelte';
 | 
				
			||||||
  import { user } from '$lib/stores/user.store';
 | 
					  import { user } from '$lib/stores/user.store';
 | 
				
			||||||
  import AppearanceSettings from './appearance-settings.svelte';
 | 
					  import AppearanceSettings from './appearance-settings.svelte';
 | 
				
			||||||
 | 
					  import TrashSettings from './trash-settings.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let keys: APIKeyResponseDto[] = [];
 | 
					  export let keys: APIKeyResponseDto[] = [];
 | 
				
			||||||
  export let devices: AuthDeviceResponseDto[] = [];
 | 
					  export let devices: AuthDeviceResponseDto[] = [];
 | 
				
			||||||
@ -70,3 +71,7 @@
 | 
				
			|||||||
<SettingAccordion title="Sidebar" subtitle="Manage sidebar settings">
 | 
					<SettingAccordion title="Sidebar" subtitle="Manage sidebar settings">
 | 
				
			||||||
  <SidebarSettings />
 | 
					  <SidebarSettings />
 | 
				
			||||||
</SettingAccordion>
 | 
					</SettingAccordion>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<SettingAccordion title="Trash" subtitle="Manage trash settings">
 | 
				
			||||||
 | 
					  <TrashSettings />
 | 
				
			||||||
 | 
					</SettingAccordion>
 | 
				
			||||||
 | 
				
			|||||||
@ -96,3 +96,5 @@ export const albumViewSettings = persisted<AlbumViewSettings>('album-view-settin
 | 
				
			|||||||
  sortDesc: true,
 | 
					  sortDesc: true,
 | 
				
			||||||
  view: AlbumViewMode.Cover,
 | 
					  view: AlbumViewMode.Cover,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const showDeleteModal = persisted<boolean>('delete-confirm-dialog', true, {});
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										25
									
								
								web/src/lib/utils/actions.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								web/src/lib/utils/actions.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { notificationController, NotificationType } from '$lib/components/shared-components/notification/notification';
 | 
				
			||||||
 | 
					import { api } from '@api';
 | 
				
			||||||
 | 
					import { handleError } from './handle-error';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type OnDelete = (assetId: string) => void;
 | 
				
			||||||
 | 
					export type OnRestore = (ids: string[]) => void;
 | 
				
			||||||
 | 
					export type OnArchive = (ids: string[], isArchived: boolean) => void;
 | 
				
			||||||
 | 
					export type OnFavorite = (ids: string[], favorite: boolean) => void;
 | 
				
			||||||
 | 
					export type OnStack = (ids: string[]) => void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const deleteAssets = async (force: boolean, onAssetDelete: OnDelete, ids: string[]) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    await api.assetApi.deleteAssets({ assetBulkDeleteDto: { ids, force } });
 | 
				
			||||||
 | 
					    for (const id of ids) {
 | 
				
			||||||
 | 
					      onAssetDelete(id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notificationController.show({
 | 
				
			||||||
 | 
					      message: `${force ? 'Permanently deleted' : 'Trashed'} ${ids.length} assets`,
 | 
				
			||||||
 | 
					      type: NotificationType.Info,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    handleError(e, 'Error deleting assets');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -181,7 +181,7 @@
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const test = (searched: string): Sort => {
 | 
					  const searchSort = (searched: string): Sort => {
 | 
				
			||||||
    for (const key in sortByOptions) {
 | 
					    for (const key in sortByOptions) {
 | 
				
			||||||
      if (sortByOptions[key].title === searched) {
 | 
					      if (sortByOptions[key].title === searched) {
 | 
				
			||||||
        return sortByOptions[key];
 | 
					        return sortByOptions[key];
 | 
				
			||||||
@ -256,7 +256,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <Dropdown
 | 
					    <Dropdown
 | 
				
			||||||
      options={Object.values(sortByOptions)}
 | 
					      options={Object.values(sortByOptions)}
 | 
				
			||||||
      selectedOption={test($albumViewSettings.sortBy)}
 | 
					      selectedOption={searchSort($albumViewSettings.sortBy)}
 | 
				
			||||||
      render={(option) => {
 | 
					      render={(option) => {
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
          title: option.title,
 | 
					          title: option.title,
 | 
				
			||||||
 | 
				
			|||||||
@ -87,7 +87,7 @@
 | 
				
			|||||||
      </LinkButton>
 | 
					      </LinkButton>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <AssetGrid forceDelete {assetStore} {assetInteractionStore}>
 | 
					    <AssetGrid {assetStore} {assetInteractionStore}>
 | 
				
			||||||
      <p class="font-medium text-gray-500/60 dark:text-gray-300/60 p-4">
 | 
					      <p class="font-medium text-gray-500/60 dark:text-gray-300/60 p-4">
 | 
				
			||||||
        Trashed items will be permanently deleted after {$serverConfig.trashDays} days.
 | 
					        Trashed items will be permanently deleted after {$serverConfig.trashDays} days.
 | 
				
			||||||
      </p>
 | 
					      </p>
 | 
				
			||||||
 | 
				
			|||||||
@ -1,15 +1,29 @@
 | 
				
			|||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					  import IconButton from '$lib/components/elements/buttons/icon-button.svelte';
 | 
				
			||||||
  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 | 
					  import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
 | 
				
			||||||
  import UserSettingsList from '$lib/components/user-settings-page/user-settings-list.svelte';
 | 
					  import UserSettingsList from '$lib/components/user-settings-page/user-settings-list.svelte';
 | 
				
			||||||
 | 
					  import { mdiKeyboard } from '@mdi/js';
 | 
				
			||||||
  import type { PageData } from './$types';
 | 
					  import type { PageData } from './$types';
 | 
				
			||||||
 | 
					  import Icon from '$lib/components/elements/icon.svelte';
 | 
				
			||||||
 | 
					  import ShowShortcuts from '$lib/components/shared-components/show-shortcuts.svelte';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let data: PageData;
 | 
					  export let data: PageData;
 | 
				
			||||||
 | 
					  export let isShowKeyboardShortcut = false;
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<UserPageLayout title={data.meta.title}>
 | 
					<UserPageLayout title={data.meta.title}>
 | 
				
			||||||
 | 
					  <svelte:fragment slot="buttons">
 | 
				
			||||||
 | 
					    <IconButton on:click={() => (isShowKeyboardShortcut = !isShowKeyboardShortcut)}>
 | 
				
			||||||
 | 
					      <Icon path={mdiKeyboard} />
 | 
				
			||||||
 | 
					    </IconButton>
 | 
				
			||||||
 | 
					  </svelte:fragment>
 | 
				
			||||||
  <section class="mx-4 flex place-content-center">
 | 
					  <section class="mx-4 flex place-content-center">
 | 
				
			||||||
    <div class="w-full max-w-3xl">
 | 
					    <div class="w-full max-w-3xl">
 | 
				
			||||||
      <UserSettingsList keys={data.keys} devices={data.devices} />
 | 
					      <UserSettingsList keys={data.keys} devices={data.devices} />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </section>
 | 
					  </section>
 | 
				
			||||||
</UserPageLayout>
 | 
					</UserPageLayout>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					{#if isShowKeyboardShortcut}
 | 
				
			||||||
 | 
					  <ShowShortcuts on:close={() => (isShowKeyboardShortcut = false)} />
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user