From 67164918de3943985ccdaad77092c4c681bf9916 Mon Sep 17 00:00:00 2001 From: Kohei Yoshino Date: Thu, 19 Dec 2024 21:11:47 -0500 Subject: [PATCH] Migrate to Runes, part 1 --- src/lib/components/assets/assets-page.svelte | 4 +- .../details/asset-details-overlay.svelte | 76 +++++----- .../assets/details/edit-asset-dialog.svelte | 23 ++- .../assets/details/rename-asset-dialog.svelte | 53 +++---- .../components/assets/details/toolbar.svelte | 2 +- .../assets/list/asset-list-item.svelte | 19 ++- .../components/assets/list/asset-list.svelte | 4 +- .../assets/list/primary-sidebar.svelte | 6 +- .../assets/list/primary-toolbar.svelte | 6 +- .../assets/list/secondary-sidebar.svelte | 2 + .../assets/list/secondary-toolbar.svelte | 4 +- .../assets/shared/asset-preview.svelte | 135 +++++++----------- .../assets/shared/assets-panel.svelte | 51 +++---- .../components/assets/shared/drop-zone.svelte | 61 ++++---- .../shared/external-assets-panel.svelte | 92 ++++++------ .../assets/shared/file-picker.svelte | 28 ++-- .../assets/shared/info-panel.svelte | 59 +++----- .../shared/internal-assets-panel.svelte | 44 +++--- .../assets/shared/select-assets-dialog.svelte | 97 +++++++------ .../upload-assets-confirm-dialog.svelte | 19 ++- .../assets/shared/upload-assets-dialog.svelte | 24 ++-- .../shared/upload-assets-preview.svelte | 13 +- .../assets/toolbar/copy-assets-button.svelte | 54 +++---- .../toolbar/delete-assets-button.svelte | 32 ++--- .../toolbar/download-assets-button.svelte | 16 ++- .../assets/toolbar/edit-options-button.svelte | 32 +++-- .../toolbar/preview-asset-button.svelte | 11 +- .../toolbar/upload-assets-button.svelte | 2 +- src/lib/services/assets/index.js | 9 ++ 29 files changed, 491 insertions(+), 487 deletions(-) diff --git a/src/lib/components/assets/assets-page.svelte b/src/lib/components/assets/assets-page.svelte index dcae4310..9a4195fe 100644 --- a/src/lib/components/assets/assets-page.svelte +++ b/src/lib/components/assets/assets-page.svelte @@ -27,7 +27,9 @@ const routeRegex = /^\/assets(?:\/(?[/\-\w]+))?(?:\/(?[^/]+\.[A-Za-z0-9]+))?$/; - $: selectedAssetFolderLabel = getFolderLabelByPath($selectedAssetFolder?.internalPath); + const selectedAssetFolderLabel = $derived( + getFolderLabelByPath($selectedAssetFolder?.internalPath), + ); /** * Navigate to the asset list or asset details page given the URL hash. diff --git a/src/lib/components/assets/details/asset-details-overlay.svelte b/src/lib/components/assets/details/asset-details-overlay.svelte index 06b4f0a9..bb3a85bc 100644 --- a/src/lib/components/assets/details/asset-details-overlay.svelte +++ b/src/lib/components/assets/details/asset-details-overlay.svelte @@ -3,7 +3,7 @@ import { isTextFileType } from '@sveltia/utils/file'; import DOMPurify from 'isomorphic-dompurify'; import { marked } from 'marked'; - import { onMount, tick } from 'svelte'; + import { tick } from 'svelte'; import { _ } from 'svelte-i18n'; import { getAssetBlob, isMediaKind, overlaidAsset, showAssetOverlay } from '$lib/services/assets'; import EmptyState from '$lib/components/common/empty-state.svelte'; @@ -11,36 +11,16 @@ import AssetPreview from '$lib/components/assets/shared/asset-preview.svelte'; import Toolbar from '$lib/components/assets/details/toolbar.svelte'; - /** - * A reference to the wrapper element. - * @type {HTMLElement} - */ - let wrapper; - /** - * A reference to the group element. - * @type {HTMLElement} - */ - let group; - /** - * @type {boolean} - */ - let hiding = false; - /** - * @type {boolean} - */ - let hidden = true; - /** - * @type {Blob | undefined} - */ - let blob; - - $: ({ kind, blobURL, name } = $overlaidAsset || /** @type {Asset} */ ({})); + /** @type {HTMLElement | undefined} */ + let wrapper = $state(); + /** @type {HTMLElement | undefined} */ + let group = undefined; + let hiding = $state(false); + let hidden = $state(true); + /** @type {Blob | undefined} */ + let blob = $state(); - $: (async () => { - if ($overlaidAsset) { - blob = await getAssetBlob($overlaidAsset); - } - })(); + const { kind, blobURL, name } = $derived($overlaidAsset ?? /** @type {Asset} */ ({})); /** * Move focus to the wrapper once the overlay is loaded. @@ -49,22 +29,34 @@ // Wait until `inert` is updated await tick(); - group.tabIndex = 0; - group.focus(); + if (group) { + group.tabIndex = 0; + group.focus(); + } }; - onMount(() => { - group = /** @type {HTMLElement} */ (wrapper.querySelector('[role="group"]')); + $effect(() => { + if ($overlaidAsset) { + (async () => { + blob = await getAssetBlob($overlaidAsset); + })(); + } + }); - group.addEventListener('transitionend', () => { - if (!$showAssetOverlay) { - hiding = false; - hidden = true; - } - }); + $effect(() => { + if (wrapper && !group) { + group = /** @type {HTMLElement} */ (wrapper.querySelector('[role="group"]')); + + group.addEventListener('transitionend', () => { + if (!$showAssetOverlay) { + hiding = false; + hidden = true; + } + }); + } }); - $: { + $effect(() => { if (wrapper) { if ($showAssetOverlay) { hiding = false; @@ -74,7 +66,7 @@ hiding = true; } } - } + });
{ if (asset && blob === undefined) { initState(); } - } + }); - $: { + $effect(() => { if (!$showAssetOverlay) { open = false; } - } + }); { + if (!newName.trim()) return 'empty'; + if (newName.includes('/')) return 'character'; + if (otherNames.includes(`${newName}${extension ? `.${extension}` : ''}`)) return 'duplicate'; + return undefined; + }); + + const invalid = $derived(!!error); /** * Initialize the state. */ const initState = async () => { if (asset) { - ({ dirname, filename, extension } = getPathInfo(asset.path)); + pathInfo = getPathInfo(asset.path); newName = filename; otherNames = getAssetsByDirName(/** @type {string} */ (dirname)) .map((a) => a.name) @@ -46,28 +49,18 @@ } }; - $: { + $effect(() => { if (asset) { initState(); } - } + }); - $: { + $effect(() => { if (!$showAssetOverlay) { open = false; $renamingAsset = undefined; } - } - - $: error = (() => { - if (!newName.trim()) return 'empty'; - if (newName.includes('/')) return 'character'; - if (otherNames.includes(`${newName}${extension ? `.${extension}` : ''}`)) return 'duplicate'; - - return undefined; - })(); - - $: invalid = !!error; + }); diff --git a/src/lib/components/assets/list/asset-list-item.svelte b/src/lib/components/assets/list/asset-list-item.svelte index ced2dcc2..ca2b157b 100644 --- a/src/lib/components/assets/list/asset-list-item.svelte +++ b/src/lib/components/assets/list/asset-list-item.svelte @@ -6,15 +6,20 @@ import { listedAssets } from '$lib/services/assets/view'; /** - * @type {Asset} + * @typedef {object} Props + * @property {Asset} asset - Asset. + * @property {ViewType} viewType - View type. */ - export let asset; - /** - * @type {ViewType} - */ - export let viewType; - $: ({ name, kind } = asset); + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + asset, + viewType, + /* eslint-enable prefer-const */ + } = $props(); + + const { name, kind } = $derived(asset); /** * Update the asset selection. diff --git a/src/lib/components/assets/list/asset-list.svelte b/src/lib/components/assets/list/asset-list.svelte index d7cecb67..f255e07d 100644 --- a/src/lib/components/assets/list/asset-list.svelte +++ b/src/lib/components/assets/list/asset-list.svelte @@ -10,9 +10,9 @@ import { globalAssetFolder, selectedAssetFolder, uploadingAssets } from '$lib/services/assets'; import { assetGroups, currentView, listedAssets } from '$lib/services/assets/view'; - $: viewType = $currentView.type; + const viewType = $derived($currentView.type); // Can’t upload assets if collection assets are saved at entry-relative paths - $: uploadDisabled = !!$selectedAssetFolder?.entryRelative; + const uploadDisabled = $derived(!!$selectedAssetFolder?.entryRelative); diff --git a/src/lib/components/assets/list/primary-sidebar.svelte b/src/lib/components/assets/list/primary-sidebar.svelte index a8f0a608..9b4ea14c 100644 --- a/src/lib/components/assets/list/primary-sidebar.svelte +++ b/src/lib/components/assets/list/primary-sidebar.svelte @@ -12,9 +12,9 @@ import { getFolderLabelByCollection } from '$lib/services/assets/view'; import { getCollection } from '$lib/services/contents/collection'; - $: numberFormatter = Intl.NumberFormat($appLocale ?? undefined); + const numberFormatter = $derived(Intl.NumberFormat($appLocale ?? undefined)); - $: folders = [ + const folders = $derived([ { collectionName: '*', internalPath: undefined, @@ -22,7 +22,7 @@ entryRelative: false, }, ...$allAssetFolders, - ]; + ]);
diff --git a/src/lib/components/assets/list/primary-toolbar.svelte b/src/lib/components/assets/list/primary-toolbar.svelte index 2be108fa..16c30279 100644 --- a/src/lib/components/assets/list/primary-toolbar.svelte +++ b/src/lib/components/assets/list/primary-toolbar.svelte @@ -10,7 +10,11 @@ import { focusedAsset, selectedAssetFolder, selectedAssets } from '$lib/services/assets'; import { getFolderLabelByPath, listedAssets } from '$lib/services/assets/view'; - $: assets = $selectedAssets.length ? $selectedAssets : $focusedAsset ? [$focusedAsset] : []; + const assets = $derived.by(() => { + if ($selectedAssets.length) return $selectedAssets; + if ($focusedAsset) return [$focusedAsset]; + return []; + }); diff --git a/src/lib/components/assets/list/secondary-sidebar.svelte b/src/lib/components/assets/list/secondary-sidebar.svelte index 2aa8389e..1af889c5 100644 --- a/src/lib/components/assets/list/secondary-sidebar.svelte +++ b/src/lib/components/assets/list/secondary-sidebar.svelte @@ -1,3 +1,5 @@ + + diff --git a/src/lib/components/assets/shared/asset-preview.svelte b/src/lib/components/assets/shared/asset-preview.svelte index fd2da628..adc889ce 100644 --- a/src/lib/components/assets/shared/asset-preview.svelte +++ b/src/lib/components/assets/shared/asset-preview.svelte @@ -4,73 +4,51 @@ import { getAssetBlobURL, getAssetThumbnailURL } from '$lib/services/assets'; /** - * Asset type. - * @type {AssetKind} + * @typedef {object} Props + * @property {AssetKind} kind - Asset type. + * @property {'lazy' | 'eager'} [loading] - Loading method. + * @property {Asset} [asset] - Asset. + * @property {string} [src] - Source URL. + * @property {'tile' | 'icon'} [variant] - Style variant. + * @property {boolean} [blurBackground] - Whether to show a blurred background (like Slack’s media + * overlay). + * @property {boolean} [cover] - Whether to use `object-fit: cover`. + * @property {boolean} [checkerboard] - Whether to show a checkerboard background below a + * transparent image. + * @property {boolean} [dissolve] - Whether to add a short dissolve transition (fade-in effect) to + * the image/video when it’s first loaded to avoid a sudden appearance. + * @property {string} [alt] - Alt text for the image. + * @property {boolean} [controls] - Whether to show controls for audio/video. If this is `false` + * and {@link kind} is `audio`, an icon will be displayed instead. */ - export let kind; - /** - * Loading method. - * @type {'lazy' | 'eager'} - */ - export let loading = 'lazy'; - /** - * Asset. - * @type {Asset | undefined} - */ - export let asset = undefined; - /** - * Source URL. - * @type {string | undefined} - */ - export let src = undefined; - /** - * Style variant. - * @type {'tile' | 'icon' | undefined} - */ - export let variant = undefined; - /** - * Whether to show a blurred background (like Slack’s media overlay). - * @type {boolean} - */ - export let blurBackground = false; - /** - * Whether to use `object-fit: cover`. - * @type {boolean} - */ - export let cover = false; - /** - * Whether to show a checkerboard background below a transparent image. - * @type {boolean} - */ - export let checkerboard = false; - /** - * Whether to add a short dissolve transition (fade-in effect) to the image/video when it’s first - * loaded to avoid a sudden appearance. - * @type {boolean} - */ - export let dissolve = true; - /** - * Alt text for the image. - * @type {string} - */ - export let alt = ''; - /** - * Whether to show controls for audio/video. If this is `false` and {@link kind} is `audio`, an - * icon will be displayed instead. - * @type {boolean} - */ - export let controls = false; - /** - * @type {HTMLImageElement | HTMLMediaElement} - */ - let mediaElement; - let updatingSrc = false; - let hasError = false; - let loaded = false; + /** @type {Props & Record} */ + let { + /* eslint-disable prefer-const */ + kind, + loading = 'lazy', + asset = undefined, + src = $bindable(undefined), + variant = undefined, + blurBackground = false, + cover = false, + checkerboard = false, + dissolve = true, + alt = '', + controls = false, + ...rest + /* eslint-enable prefer-const */ + } = $props(); + + /** @type {HTMLImageElement | HTMLMediaElement | undefined} */ + let mediaElement = $state(); + let hasError = $state(false); + let loaded = $state(false); + + const isThumbnail = $derived(!!asset && !!variant); + const isImage = $derived(isThumbnail || kind === 'image' || asset?.name.endsWith('.pdf')); - $: isThumbnail = !!asset && !!variant; - $: isImage = isThumbnail || kind === 'image' || asset?.name.endsWith('.pdf'); + let updatingSrc = false; /** * Update the {@link src} property. @@ -96,12 +74,6 @@ updatingSrc = false; }; - $: { - void mediaElement; - void asset; - updateSrc(); - } - /** * Update the {@link loaded} state when the media is loaded. */ @@ -117,7 +89,7 @@ ) { // Not loaded yet; wait until it’s ready await new Promise((resolve) => { - mediaElement.addEventListener( + mediaElement?.addEventListener( isImage ? 'load' : 'loadedmetadata', () => { resolve(void 0); @@ -140,11 +112,17 @@ } }; - $: { + $effect(() => { + void mediaElement; + void asset; + updateSrc(); + }); + + $effect(() => { void mediaElement; void src; checkLoaded(); - } + });
{:else if isImage} - + {:else if kind === 'video'} - {:else if kind === 'audio'} {#if controls} - + {:else} {/if} diff --git a/src/lib/components/assets/shared/assets-panel.svelte b/src/lib/components/assets/shared/assets-panel.svelte index 655261bc..222852c8 100644 --- a/src/lib/components/assets/shared/assets-panel.svelte +++ b/src/lib/components/assets/shared/assets-panel.svelte @@ -9,36 +9,31 @@ import { normalize } from '$lib/services/search'; /** - * @type {Asset[]} + * @typedef {object} Props + * @property {Asset[]} [assets] - Asset list. + * @property {ViewType} [viewType] - View type. + * @property {string} [searchTerms] - Search terms for filtering assets. + * @property {string} [gridId] - The `id` attribute of the inner listbox. + * @property {boolean} [checkerboard] - Whether to show a checkerboard background below a + * transparent image. + * @property {(detail: { asset: Asset }) => void} [onSelect] - Custom `select` event handler. */ - export let assets = []; - /** - * @type {ViewType} - */ - export let viewType = 'grid'; - /** - * @type {string} - */ - export let searchTerms = ''; - /** - * The `id` attribute of the inner listbox. - * @type {string | undefined} - */ - export let gridId = undefined; - /** - * Whether to show a checkerboard background below a transparent image. - * @type {boolean} - */ - export let checkerboard = false; - /** - * Custom `select` event handler. - * @type {((detail: { asset: Asset }) => void) | undefined} - */ - export let onSelect = undefined; - $: filteredAssets = searchTerms - ? assets.filter(({ name }) => normalize(name).includes(searchTerms)) - : assets; + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + assets = [], + viewType = 'grid', + searchTerms = '', + gridId = undefined, + checkerboard = false, + onSelect = undefined, + /* eslint-enable prefer-const */ + } = $props(); + + const filteredAssets = $derived( + searchTerms ? assets.filter(({ name }) => normalize(name).includes(searchTerms)) : assets, + ); {#if filteredAssets.length} diff --git a/src/lib/components/assets/shared/drop-zone.svelte b/src/lib/components/assets/shared/drop-zone.svelte index 6886189d..f1f00038 100644 --- a/src/lib/components/assets/shared/drop-zone.svelte +++ b/src/lib/components/assets/shared/drop-zone.svelte @@ -6,40 +6,41 @@ import FilePicker from '$lib/components/assets/shared/file-picker.svelte'; /** - * @type {string | undefined} + * @typedef {object} Props + * @property {string} [accept] - The `accept` attribute for the ``. + * @property {boolean} [disabled] - Whether to disable new file selection. + * @property {boolean} [multiple] - Whether to accept multiple files. + * @property {boolean} [showUploadButton] - Whether to show the upload button. + * @property {boolean} [showFilePreview] - Whether to show file preview after files are selected. + * @property {(detail: { files: File[] }) => void} [onSelect] - Custom `select` event handler. + * @property {import('svelte').Snippet} [children] - Slot content. */ - export let accept = undefined; - export let disabled = false; - export let multiple = false; - export let showUploadButton = false; - export let showFilePreview = false; - /** - * Slot content. - * @type {import('svelte').Snippet | undefined} - */ - export let children = undefined; - /** - * Custom `select` event handler. - * @type {((detail: { files: File[] }) => void) | undefined} - */ - export let onSelect = undefined; - let dragging = false; - let typeMismatch = false; - /** - * @type {FilePicker} - */ - let filePicker; - /** - * @type {File[]} - */ - let files = []; + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + accept = undefined, + disabled = false, + multiple = false, + showUploadButton = false, + showFilePreview = false, + onSelect = undefined, + children = undefined, + /* eslint-enable prefer-const */ + } = $props(); + + let dragging = $state(false); + let typeMismatch = $state(false); + /** @type {FilePicker | undefined} */ + let filePicker = $state(); + /** @type {File[]} */ + let files = $state([]); /** * Open the file picker to let the user choose file(s). */ export const openFilePicker = () => { - filePicker.open(); + filePicker?.open(); }; /** @@ -47,6 +48,7 @@ */ export const reset = () => { files = []; + onSelect?.({ files }); }; /** @@ -55,11 +57,8 @@ */ const updateFileList = (allFiles) => { files = multiple ? allFiles : allFiles.slice(0, 1); - }; - - $: { onSelect?.({ files }); - } + };
void} [onSelect] - Custom `select` event handler. */ - export let kind = 'image'; - /** - * @type {string} - */ - export let searchTerms = ''; - /** - * @type {MediaLibraryService} - */ - export let serviceProps; - /** - * The `id` attribute of the inner listbox. - * @type {string | undefined} - */ - export let gridId = undefined; - /** - * Custom `select` event handler. - * @type {((detail: SelectedAsset) => void) | undefined} - */ - export let onSelect = undefined; - $: ({ + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + kind = 'image', + searchTerms = '', + serviceProps, + gridId = undefined, + onSelect = undefined, + /* eslint-enable prefer-const */ + } = $props(); + + const { serviceType = 'stock_photos', serviceId = '', serviceLabel = '', @@ -50,27 +47,22 @@ init, signIn, search, - } = serviceProps); + } = $derived(serviceProps); + + const input = $state({ userName: '', password: '' }); + let hasConfig = $state(true); + let hasAuthInfo = $state(false); + let apiKey = $state(''); + let userName = $state(''); + let password = $state(''); + /** @type {'initial' | 'requested' | 'success' | 'error'} */ + let authState = $state('initial'); + /** @type {ExternalAsset[] | null} */ + let searchResults = $state(null); + /** @type {string | undefined} */ + let error = $state(); - const input = { userName: '', password: '' }; - let hasConfig = true; - let hasAuthInfo = false; - let apiKey = ''; - let userName = ''; - let password = ''; let debounceTimer = 0; - /** - * @type {'initial' | 'requested' | 'success' | 'error'} - */ - let authState = 'initial'; - /** - * @type {ExternalAsset[] | null} - */ - let searchResults = null; - /** - * @type {string | undefined} - */ - let error; /** * Search assets. @@ -122,16 +114,6 @@ } }; - $: { - void searchTerms; - window.clearTimeout(debounceTimer); - debounceTimer = window.setTimeout(() => { - if (hasAuthInfo) { - searchAssets(searchTerms); - } - }, 1000); - } - onMount(() => { (async () => { if (typeof init === 'function') { @@ -153,6 +135,16 @@ } })(); }); + + $effect(() => { + void searchTerms; + window.clearTimeout(debounceTimer); + debounceTimer = window.setTimeout(() => { + if (hasAuthInfo) { + searchAssets(searchTerms); + } + }, 1000); + }); {#if hasAuthInfo} diff --git a/src/lib/components/assets/shared/file-picker.svelte b/src/lib/components/assets/shared/file-picker.svelte index 52f556d7..4629cd24 100644 --- a/src/lib/components/assets/shared/file-picker.svelte +++ b/src/lib/components/assets/shared/file-picker.svelte @@ -1,19 +1,23 @@ {#snippet usedEntryLink( diff --git a/src/lib/components/assets/shared/internal-assets-panel.svelte b/src/lib/components/assets/shared/internal-assets-panel.svelte index a01e7fc2..b0027f6a 100644 --- a/src/lib/components/assets/shared/internal-assets-panel.svelte +++ b/src/lib/components/assets/shared/internal-assets-panel.svelte @@ -1,27 +1,39 @@ void} [onSelect] - Custom `select` event + * handler. */ - export let kind; - export let canEnterURL = true; - /** @type {Entry | undefined} */ - export let entry; - /** - * Custom `select` event handler. - * @type {((detail: { asset: SelectedAsset }) => void) | undefined} - */ - export let onSelect = undefined; - const title = kind === 'image' ? $_('assets_dialog.title.image') : $_('assets_dialog.title.file'); - let elementIdPrefix = ''; - /** - * @type {SelectedAsset | null} - */ - let selectedAsset = null; - let enteredURL = ''; - let rawSearchTerms = ''; + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + open = $bindable(false), + kind, + canEnterURL = true, + entry, + onSelect = undefined, + /* eslint-enable prefer-const */ + } = $props(); + + let elementIdPrefix = $state(''); + /** @type {SelectedAsset | null} */ + let selectedAsset = $state(null); + let enteredURL = $state(''); + let rawSearchTerms = $state(''); + let libraryName = $state('uncategorized-assets'); - $: searchTerms = normalize(rawSearchTerms); - $: ({ internalPath = '', entryRelative = false } = - $selectedCollection?._assetFolder ?? /** @type {any} */ ({})); - $: showCollectionAssets = !!internalPath && !entryRelative; - $: showEntryAssets = !!entry && entryRelative; - $: libraryName = showEntryAssets - ? 'entry-assets' - : showCollectionAssets - ? 'collection-assets' - : 'uncategorized-assets'; - $: showUploader = libraryName === 'upload'; - $: entryDirName = entry ? getPathInfo(Object.values(entry.locales)[0].path).dirname : undefined; - $: isLocalLibrary = libraryName.endsWith('-assets'); - $: isEnabledMediaService = + const title = $derived( + kind === 'image' ? $_('assets_dialog.title.image') : $_('assets_dialog.title.file'), + ); + const searchTerms = $derived(normalize(rawSearchTerms)); + const { internalPath = '', entryRelative = false } = $derived( + $selectedCollection?._assetFolder ?? /** @type {any} */ ({}), + ); + const showCollectionAssets = $derived(!!internalPath && !entryRelative); + const showEntryAssets = $derived(!!entry && entryRelative); + const showUploader = $derived(libraryName === 'upload'); + const entryDirName = $derived( + entry ? getPathInfo(Object.values(entry.locales)[0].path).dirname : undefined, + ); + const isLocalLibrary = $derived(libraryName.endsWith('-assets')); + const isEnabledMediaService = $derived( (Object.keys(allStockPhotoServices).includes(libraryName) && $prefs?.apiKeys?.[libraryName]) || - (Object.keys(allCloudStorageServices).includes(libraryName) && $prefs?.logins?.[libraryName]); + (Object.keys(allCloudStorageServices).includes(libraryName) && $prefs?.logins?.[libraryName]), + ); + + onMount(() => { + elementIdPrefix = `library-${generateUUID('short')}`; + }); - $: { + $effect(() => { + libraryName = showEntryAssets + ? 'entry-assets' + : showCollectionAssets + ? 'collection-assets' + : 'uncategorized-assets'; + }); + + $effect(() => { if (open) { // Reset values enteredURL = ''; } - } + }); - $: { + $effect(() => { if (!$showContentOverlay) { open = false; } - } - - onMount(() => { - elementIdPrefix = `library-${generateUUID('short')}`; }); diff --git a/src/lib/components/assets/shared/upload-assets-confirm-dialog.svelte b/src/lib/components/assets/shared/upload-assets-confirm-dialog.svelte index 26bce33a..2df997d0 100644 --- a/src/lib/components/assets/shared/upload-assets-confirm-dialog.svelte +++ b/src/lib/components/assets/shared/upload-assets-confirm-dialog.svelte @@ -6,14 +6,21 @@ import { saveAssets } from '$lib/services/assets/data'; import { showUploadAssetsConfirmDialog } from '$lib/services/assets/view'; - $: ({ files, folder, originalAsset } = $uploadingAssets); + const { files, folder, originalAsset } = $derived($uploadingAssets); - $: { + /** @type {File[]} */ + let uploadingFiles = $state([]); + + $effect(() => { + uploadingFiles = [...files]; + }); + + $effect(() => { if (!$showAssetOverlay) { // Close the dialog $uploadingAssets = { folder: undefined, files: [] }; } - } + }); @@ -38,13 +45,13 @@ }, })} {:else} - {$_(files.length === 1 ? 'confirm_uploading_file' : 'confirm_uploading_files', { + {$_(uploadingFiles.length === 1 ? 'confirm_uploading_file' : 'confirm_uploading_files', { values: { - count: files.length, + count: uploadingFiles.length, folder: `/${folder}`, }, })} {/if}
- + diff --git a/src/lib/components/assets/shared/upload-assets-dialog.svelte b/src/lib/components/assets/shared/upload-assets-dialog.svelte index ef1f887a..bf2c0221 100644 --- a/src/lib/components/assets/shared/upload-assets-dialog.svelte +++ b/src/lib/components/assets/shared/upload-assets-dialog.svelte @@ -13,12 +13,14 @@ import { showUploadAssetsDialog } from '$lib/services/assets/view'; import { canDragDrop } from '$lib/services/utils/file'; - /** @type {FilePicker} */ - let filePicker; + /** @type {FilePicker | undefined} */ + let filePicker = $state(); - $: ({ originalAsset } = $uploadingAssets); - $: multiple = !originalAsset; - $: accept = originalAsset ? (mime.getType(originalAsset.name) ?? undefined) : undefined; + const { originalAsset } = $derived($uploadingAssets); + const multiple = $derived(!originalAsset); + const accept = $derived( + originalAsset ? (mime.getType(originalAsset.name) ?? undefined) : undefined, + ); /** * Update the asset list, which will show the confirmation dialog. @@ -39,18 +41,18 @@ $showUploadAssetsDialog = false; }; - $: { + $effect(() => { // Open the file picker directly if drag & drop is not supported (on mobile) - if (!canDragDrop() && filePicker && $showUploadAssetsDialog) { - filePicker.open(); + if (!canDragDrop() && $showUploadAssetsDialog) { + filePicker?.open(); } - } + }); - $: { + $effect(() => { if (!$showAssetOverlay) { $showUploadAssetsDialog = false; } - } + }); {#if canDragDrop()} diff --git a/src/lib/components/assets/shared/upload-assets-preview.svelte b/src/lib/components/assets/shared/upload-assets-preview.svelte index 3e7ad2b4..030282fe 100644 --- a/src/lib/components/assets/shared/upload-assets-preview.svelte +++ b/src/lib/components/assets/shared/upload-assets-preview.svelte @@ -6,9 +6,16 @@ import Image from '$lib/components/common/image.svelte'; /** - * @type {File[]} + * @typedef {object} Props + * @property {File[]} [files] - File list. */ - export let files = []; + + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + files = $bindable([]), + /* eslint-enable prefer-const */ + } = $props();
@@ -39,7 +46,7 @@ onclick={(event) => { event.stopPropagation(); files.splice(index, 1); - files = files; + files = [...files]; }} > diff --git a/src/lib/components/assets/toolbar/copy-assets-button.svelte b/src/lib/components/assets/toolbar/copy-assets-button.svelte index 8a731a87..8bf845c5 100644 --- a/src/lib/components/assets/toolbar/copy-assets-button.svelte +++ b/src/lib/components/assets/toolbar/copy-assets-button.svelte @@ -6,32 +6,30 @@ import { getAssetBlob, getAssetDetails } from '$lib/services/assets'; /** - * @type {Asset[]} + * @typedef {object} Props + * @property {Asset[]} [assets] - Selected assets. */ - export let assets = []; - /** - * @type {AssetDetails[]} - */ - let assetsDetails = []; - /** - * @type {Blob | undefined} - */ + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + assets = [], + /* eslint-enable prefer-const */ + } = $props(); + + /** @type {AssetDetails[]} */ + let assetsDetailList = $state([]); + let canCopyFileData = $state(false); + /** @type {{ show: boolean, text: string, status: 'success' | 'error' }} */ + const toast = $state({ show: false, text: '', status: 'success' }); + + const singleAsset = $derived(assets.length === 1); + const publicURLs = $derived( + assetsDetailList.filter(({ publicURL }) => !!publicURL).map(({ publicURL }) => publicURL), + ); + + /** @type {Blob | undefined} */ let assetBlob = undefined; - /** - * @type {boolean} - */ - let canCopyFileData = false; - /** - * @type {{ show: boolean, text: string, status: 'success' | 'error' }} - */ - const toast = { show: false, text: '', status: 'success' }; - - $: singleAsset = assets.length === 1; - - $: publicURLs = assetsDetails - .filter(({ publicURL }) => !!publicURL) - .map(({ publicURL }) => publicURL); /** * Check if the file data can be copied to clipboard. Since OSes usually support only one item, @@ -118,10 +116,12 @@ } }; - $: (async () => { - assetsDetails = await Promise.all(assets.map(getAssetDetails)); - canCopyFileData = await checkCanCopyFileData(); - })(); + $effect(() => { + (async () => { + assetsDetailList = await Promise.all(assets.map(getAssetDetails)); + canCopyFileData = await checkCanCopyFileData(); + })(); + }); void) | undefined} [onDelete] - Custom `delete` event handler. */ - export let assets = []; - /** - * @type {string} - */ - export let buttonDescription = ''; - /** - * @type {string} - */ - export let dialogDescription = ''; - /** - * Custom `delete` event handler. - * @type {(() => void) | undefined} - */ - export let onDelete = undefined; - let showDialog = false; + /** @type {Props} */ + let { + /* eslint-disable prefer-const */ + assets = [], + buttonDescription = '', + dialogDescription = '', + onDelete = undefined, + /* eslint-enable prefer-const */ + } = $props(); + + let showDialog = $state(false);