refactor(web): tree data structure for folder and tag views (#18980)
* refactor folder view inline link * improved tree collapsing * handle tags * linting * formatting * simplify * .from is faster * simplify * add key
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto, invalidateAll } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
|
||||
import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
|
||||
@@ -28,10 +27,9 @@
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
|
||||
import { joinPaths } from '$lib/utils/tree-utils';
|
||||
import { IconButton } from '@immich/ui';
|
||||
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
@@ -43,51 +41,40 @@
|
||||
|
||||
const viewport: Viewport = $state({ width: 0, height: 0 });
|
||||
|
||||
let pathSegments = $derived(data.path ? data.path.split('/') : []);
|
||||
let tree = $derived(buildTree(foldersStore.uniquePaths));
|
||||
let currentPath = $derived($page.url.searchParams.get(QueryParameter.PATH) || '');
|
||||
let currentTreeItems = $derived(currentPath ? data.currentFolders : Object.keys(tree).sort());
|
||||
|
||||
const assetInteraction = new AssetInteraction();
|
||||
|
||||
onMount(async function initializeFolders() {
|
||||
await foldersStore.fetchUniquePaths();
|
||||
});
|
||||
const handleNavigateToFolder = (folderName: string) => navigateToView(joinPaths(data.tree.path, folderName));
|
||||
|
||||
const handleNavigateToFolder = async function handleNavigateToFolder(folderName: string) {
|
||||
await navigateToView(normalizeTreePath(`${data.path || ''}/${folderName}`));
|
||||
};
|
||||
|
||||
const getLinkForPath = function getLinkForPath(path: string) {
|
||||
function getLinkForPath(path: string) {
|
||||
const url = new URL(AppRoute.FOLDERS, globalThis.location.href);
|
||||
if (path) {
|
||||
url.searchParams.set(QueryParameter.PATH, path);
|
||||
}
|
||||
url.searchParams.set(QueryParameter.PATH, path);
|
||||
return url.href;
|
||||
};
|
||||
}
|
||||
|
||||
afterNavigate(function clearAssetSelection() {
|
||||
// Clear the asset selection when we navigate (like going to another folder)
|
||||
cancelMultiselect(assetInteraction);
|
||||
});
|
||||
|
||||
const navigateToView = function navigateToView(path: string) {
|
||||
return goto(getLinkForPath(path));
|
||||
};
|
||||
function navigateToView(path: string) {
|
||||
return goto(getLinkForPath(path), { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const triggerAssetUpdate = async function updateAssets() {
|
||||
async function triggerAssetUpdate() {
|
||||
cancelMultiselect(assetInteraction);
|
||||
await foldersStore.refreshAssetsByPath(data.path);
|
||||
if (data.tree.path) {
|
||||
await foldersStore.refreshAssetsByPath(data.tree.path);
|
||||
}
|
||||
await invalidateAll();
|
||||
};
|
||||
}
|
||||
|
||||
const handleSelectAllAssets = function handleSelectAllAssets() {
|
||||
function handleSelectAllAssets() {
|
||||
if (!data.pathAssets) {
|
||||
return;
|
||||
}
|
||||
|
||||
assetInteraction.selectAssets(data.pathAssets.map((asset) => toTimelineAsset(asset)));
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<UserPageLayout title={data.meta.title}>
|
||||
@@ -99,8 +86,8 @@
|
||||
<div class="h-full">
|
||||
<TreeItems
|
||||
icons={{ default: mdiFolderOutline, active: mdiFolder }}
|
||||
items={tree}
|
||||
active={currentPath}
|
||||
tree={foldersStore.folders!}
|
||||
active={data.tree.path}
|
||||
getLink={getLinkForPath}
|
||||
/>
|
||||
</div>
|
||||
@@ -108,10 +95,10 @@
|
||||
</Sidebar>
|
||||
{/snippet}
|
||||
|
||||
<Breadcrumbs {pathSegments} icon={mdiFolderHome} title={$t('folders')} getLink={getLinkForPath} />
|
||||
<Breadcrumbs node={data.tree} icon={mdiFolderHome} title={$t('folders')} getLink={getLinkForPath} />
|
||||
|
||||
<section class="mt-2 h-[calc(100%-(--spacing(20)))] overflow-auto immich-scrollbar">
|
||||
<TreeItemThumbnails items={currentTreeItems} icon={mdiFolder} onClick={handleNavigateToFolder} />
|
||||
<TreeItemThumbnails items={data.tree.children} icon={mdiFolder} onClick={handleNavigateToFolder} />
|
||||
|
||||
<!-- Assets -->
|
||||
{#if data.pathAssets && data.pathAssets.length > 0}
|
||||
|
||||
@@ -3,38 +3,28 @@ import { foldersStore } from '$lib/stores/folders.svelte';
|
||||
import { authenticate } from '$lib/utils/auth';
|
||||
import { getFormatter } from '$lib/utils/i18n';
|
||||
import { getAssetInfoFromParam } from '$lib/utils/navigation';
|
||||
import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
export const load = (async ({ params, url }) => {
|
||||
await authenticate(url);
|
||||
const asset = await getAssetInfoFromParam(params);
|
||||
const $t = await getFormatter();
|
||||
|
||||
await foldersStore.fetchUniquePaths();
|
||||
|
||||
let pathAssets = null;
|
||||
const [, asset, $t] = await Promise.all([foldersStore.fetchTree(), getAssetInfoFromParam(params), getFormatter()]);
|
||||
|
||||
let tree = foldersStore.folders!;
|
||||
const path = url.searchParams.get(QueryParameter.PATH);
|
||||
if (path) {
|
||||
await foldersStore.fetchAssetsByPath(path);
|
||||
pathAssets = foldersStore.assets[path] || null;
|
||||
} else {
|
||||
// If no path is provided, we we're at the root level
|
||||
tree = tree.traverse(path);
|
||||
} else if (path === null) {
|
||||
// If no path is provided, we've just navigated to the folders page.
|
||||
// We should bust the asset cache of the folder store, to make sure we don't show stale data
|
||||
foldersStore.bustAssetCache();
|
||||
}
|
||||
|
||||
let tree = buildTree(foldersStore.uniquePaths);
|
||||
const parts = normalizeTreePath(path || '').split('/');
|
||||
for (const part of parts) {
|
||||
tree = tree?.[part];
|
||||
}
|
||||
// only fetch assets if the folder has assets
|
||||
const pathAssets = tree.hasAssets ? await foldersStore.fetchAssetsByPath(tree.path) : null;
|
||||
|
||||
return {
|
||||
asset,
|
||||
path,
|
||||
currentFolders: Object.keys(tree || {}).sort(),
|
||||
tree,
|
||||
pathAssets,
|
||||
meta: {
|
||||
title: $t('folders'),
|
||||
|
||||
Reference in New Issue
Block a user