mirror of
https://github.com/immich-app/immich.git
synced 2025-01-27 22:22:45 +01:00
feat(web): Enable selection interactions in folder view (#15049)
* feat(web): Enable selection interactions in folder view * feat(web): Add link to parent folder in detail pane, if folders are enabled * Added invalidation and refreshing of asset cache on changes * fix: removed unused imports and changed link * chore: styling --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
007caa26bd
commit
b45ff8d09f
5 changed files with 96 additions and 13 deletions
i18n
web/src
lib
routes/(user)/folders/[[photos=photos]]/[[assetId=id]]
|
@ -760,6 +760,7 @@
|
|||
"getting_started": "Getting Started",
|
||||
"go_back": "Go back",
|
||||
"go_to_search": "Go to search",
|
||||
"go_to_folder": "Go to folder",
|
||||
"group_albums_by": "Group albums by...",
|
||||
"group_no": "No grouping",
|
||||
"group_owner": "Group by owner",
|
||||
|
@ -1343,4 +1344,4 @@
|
|||
"yes": "Yes",
|
||||
"you_dont_have_any_shared_links": "You don't have any shared links",
|
||||
"zoom_image": "Zoom Image"
|
||||
}
|
||||
}
|
|
@ -132,6 +132,14 @@
|
|||
showEditFaces = false;
|
||||
};
|
||||
|
||||
const getAssetFolderHref = (asset: AssetResponseDto) => {
|
||||
const folderUrl = new URL(AppRoute.FOLDERS, globalThis.location.href);
|
||||
// Remove the last part of the path to get the parent path
|
||||
const assetParentPath = asset.originalPath.split('/').slice(0, -1).join('/');
|
||||
folderUrl.searchParams.set(QueryParameter.PATH, assetParentPath);
|
||||
return folderUrl.href;
|
||||
};
|
||||
|
||||
const toggleAssetPath = () => (showAssetPath = !showAssetPath);
|
||||
|
||||
let isShowChangeDate = $state(false);
|
||||
|
@ -369,9 +377,14 @@
|
|||
{/if}
|
||||
</p>
|
||||
{#if showAssetPath}
|
||||
<p class="text-xs opacity-50 break-all pb-2" transition:slide={{ duration: 250 }}>
|
||||
{asset.originalPath}
|
||||
</p>
|
||||
<a href={getAssetFolderHref(asset)} title={$t('go_to_folder')}>
|
||||
<p
|
||||
class="text-xs opacity-50 break-all pb-2 hover:dark:text-immich-dark-primary hover:text-immich-primary"
|
||||
transition:slide={{ duration: 250 }}
|
||||
>
|
||||
{asset.originalPath}
|
||||
</p>
|
||||
</a>
|
||||
{/if}
|
||||
{#if (asset.exifInfo?.exifImageHeight && asset.exifInfo?.exifImageWidth) || asset.exifInfo?.fileSizeInByte}
|
||||
<div class="flex gap-2 text-sm">
|
||||
|
|
|
@ -27,6 +27,17 @@ class FoldersStore {
|
|||
this.uniquePaths.sort();
|
||||
}
|
||||
|
||||
bustAssetCache() {
|
||||
this.assets = {};
|
||||
}
|
||||
|
||||
async refreshAssetsByPath(path: string | null) {
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
this.assets[path] = await getAssetsByOriginalPath({ path });
|
||||
}
|
||||
|
||||
async fetchAssetsByPath(path: string) {
|
||||
if (this.assets[path]) {
|
||||
return;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||
|
@ -10,13 +10,28 @@
|
|||
import type { Viewport } from '$lib/stores/assets.store';
|
||||
import { foldersStore } from '$lib/stores/folders.svelte';
|
||||
import { buildTree, normalizeTreePath } from '$lib/utils/tree-utils';
|
||||
import { mdiFolder, mdiFolderHome, mdiFolderOutline } from '@mdi/js';
|
||||
import { mdiDotsVertical, mdiFolder, mdiFolderHome, mdiFolderOutline, mdiPlus, mdiSelectAll } from '@mdi/js';
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
import type { PageData } from './$types';
|
||||
import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte';
|
||||
import SkipLink from '$lib/components/elements/buttons/skip-link.svelte';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
|
||||
import AssetJobActions from '$lib/components/photos-page/actions/asset-job-actions.svelte';
|
||||
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
|
||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import { preferences } from '$lib/stores/user.store';
|
||||
import { cancelMultiselect } from '$lib/utils/asset-utils';
|
||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
|
||||
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
|
||||
import ChangeLocation from '$lib/components/photos-page/actions/change-location-action.svelte';
|
||||
|
||||
interface Props {
|
||||
data: PageData;
|
||||
|
@ -31,6 +46,8 @@
|
|||
let currentPath = $derived($page.url.searchParams.get(QueryParameter.PATH) || '');
|
||||
let currentTreeItems = $derived(currentPath ? data.currentFolders : Object.keys(tree));
|
||||
|
||||
$inspect(data).with(console.log);
|
||||
|
||||
const assetInteraction = new AssetInteraction();
|
||||
|
||||
onMount(async () => {
|
||||
|
@ -50,8 +67,51 @@
|
|||
};
|
||||
|
||||
const navigateToView = (path: string) => goto(getLink(path));
|
||||
|
||||
const triggerAssetUpdate = async () => {
|
||||
await foldersStore.refreshAssetsByPath(data.path);
|
||||
await invalidateAll();
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (!data.pathAssets) {
|
||||
return;
|
||||
}
|
||||
|
||||
assetInteraction.selectAssets(data.pathAssets);
|
||||
};
|
||||
</script>
|
||||
|
||||
{#if assetInteraction.selectionActive}
|
||||
<div class="fixed z-[910] top-0 left-0 w-full">
|
||||
<AssetSelectControlBar
|
||||
assets={assetInteraction.selectedAssets}
|
||||
clearSelect={() => cancelMultiselect(assetInteraction)}
|
||||
>
|
||||
<CreateSharedLink />
|
||||
<CircleIconButton title={$t('select_all')} icon={mdiSelectAll} onclick={handleSelectAll} />
|
||||
<ButtonContextMenu icon={mdiPlus} title={$t('add_to')}>
|
||||
<AddToAlbum />
|
||||
<AddToAlbum shared />
|
||||
</ButtonContextMenu>
|
||||
<FavoriteAction removeFavorite={assetInteraction.isAllFavorite} onFavorite={triggerAssetUpdate} />
|
||||
|
||||
<ButtonContextMenu icon={mdiDotsVertical} title={$t('add')}>
|
||||
<DownloadAction menuItem />
|
||||
<ChangeDate menuItem />
|
||||
<ChangeLocation menuItem />
|
||||
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} onArchive={triggerAssetUpdate} />
|
||||
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
||||
<TagAction menuItem />
|
||||
{/if}
|
||||
<DeleteAssets menuItem onAssetDelete={triggerAssetUpdate} />
|
||||
<hr />
|
||||
<AssetJobActions />
|
||||
</ButtonContextMenu>
|
||||
</AssetSelectControlBar>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<UserPageLayout title={data.meta.title}>
|
||||
{#snippet sidebar()}
|
||||
<SideBarSection>
|
||||
|
@ -78,13 +138,7 @@
|
|||
<!-- Assets -->
|
||||
{#if data.pathAssets && data.pathAssets.length > 0}
|
||||
<div bind:clientHeight={viewport.height} bind:clientWidth={viewport.width} class="mt-2">
|
||||
<GalleryViewer
|
||||
assets={data.pathAssets}
|
||||
{assetInteraction}
|
||||
{viewport}
|
||||
disableAssetSelect={true}
|
||||
showAssetName={true}
|
||||
/>
|
||||
<GalleryViewer assets={data.pathAssets} {assetInteraction} {viewport} showAssetName={true} />
|
||||
</div>
|
||||
{/if}
|
||||
</section>
|
||||
|
|
|
@ -19,6 +19,10 @@ export const load = (async ({ params, url }) => {
|
|||
if (path) {
|
||||
await foldersStore.fetchAssetsByPath(path);
|
||||
pathAssets = foldersStore.assets[path] || null;
|
||||
} else {
|
||||
// If no path is provided, we we're at the root level
|
||||
// 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);
|
||||
|
|
Loading…
Reference in a new issue