mirror of
https://github.com/immich-app/immich.git
synced 2025-12-06 15:44:59 -08:00
Compare commits
1 Commits
feat/asset
...
ben/tree-a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e525aa04ab |
@@ -1,7 +1,3 @@
|
||||
<script lang="ts" module>
|
||||
export const headerId = 'user-page-header';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { useActions, type ActionArray } from '$lib/actions/use-actions';
|
||||
import NavigationBar from '$lib/components/shared-components/navigation-bar/navigation-bar.svelte';
|
||||
@@ -68,7 +64,7 @@
|
||||
<div class="absolute flex h-16 w-full place-items-center justify-between border-b p-2 text-dark">
|
||||
<div class="flex gap-2 items-center">
|
||||
{#if title}
|
||||
<div class="font-medium outline-none pe-8" tabindex="-1" id={headerId}>{title}</div>
|
||||
<div class="font-medium outline-none pe-8" tabindex="-1">{title}</div>
|
||||
{/if}
|
||||
{#if description}
|
||||
<p class="text-sm text-gray-400 dark:text-gray-600">{description}</p>
|
||||
|
||||
@@ -7,15 +7,14 @@
|
||||
active: string;
|
||||
icons: { default: string; active: string };
|
||||
getLink: (path: string) => string;
|
||||
isNested?: boolean;
|
||||
}
|
||||
|
||||
let { tree, active, icons, getLink }: Props = $props();
|
||||
let { tree, active, icons, getLink, isNested = false }: Props = $props();
|
||||
</script>
|
||||
|
||||
<ul class="list-none ms-2">
|
||||
<ul role={isNested ? 'group' : 'tree'} class="list-none ms-2">
|
||||
{#each tree.children as node (node.color ? node.path + node.color : node.path)}
|
||||
<li>
|
||||
<Tree {node} {icons} {active} {getLink} />
|
||||
</li>
|
||||
<Tree {node} {icons} {active} {getLink} />
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte';
|
||||
import { TreeNode } from '$lib/utils/tree-utils';
|
||||
import { Icon } from '@immich/ui';
|
||||
@@ -21,30 +22,108 @@
|
||||
event.preventDefault();
|
||||
isOpen = !isOpen;
|
||||
};
|
||||
|
||||
const handleSelect = (event: MouseEvent | KeyboardEvent, path: string) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
navigateTo(path);
|
||||
};
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent, node: TreeNode) => {
|
||||
switch (event.key) {
|
||||
case 'Enter':
|
||||
case ' ': {
|
||||
handleSelect(event, node.path);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'ArrowRight': {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const hasChildren = node.children.length > 0;
|
||||
if (isOpen && hasChildren) {
|
||||
const target = event.target as HTMLElement;
|
||||
const child = target.querySelector<HTMLLIElement>('ul[role="group"] > li[role="treeitem"]');
|
||||
child?.focus();
|
||||
} else if (!isOpen && hasChildren) {
|
||||
isOpen = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'ArrowLeft': {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const hasChildren = node.children.length > 0;
|
||||
if (isOpen && hasChildren) {
|
||||
isOpen = false;
|
||||
} else if (node.parents.length > 0) {
|
||||
const target = event.target as HTMLElement;
|
||||
const parent = target.parentElement?.closest<HTMLLIElement>('li[role="treeitem"]');
|
||||
parent?.focus();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 'ArrowUp': {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
console.log('focus previous node');
|
||||
|
||||
break;
|
||||
}
|
||||
case 'ArrowDown': {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
console.log('focus next node');
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const navigateTo = (path: string) => {
|
||||
const link = getLink(path);
|
||||
void goto(link, { keepFocus: true });
|
||||
};
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={getLink(node.path)}
|
||||
title={node.value}
|
||||
class={`flex grow place-items-center ps-2 py-1 text-sm rounded-lg hover:bg-slate-200 dark:hover:bg-slate-800 hover:font-semibold ${isTarget ? 'bg-slate-100 dark:bg-slate-700 font-semibold text-primary' : 'dark:text-gray-200'}`}
|
||||
data-sveltekit-keepfocus
|
||||
<!-- href={getLink(node.path)} -->
|
||||
<li
|
||||
role="treeitem"
|
||||
aria-selected={false}
|
||||
tabindex="0"
|
||||
class="outline-none"
|
||||
onkeydown={(event) => handleKeydown(event, node)}
|
||||
onclick={(event) => handleSelect(event, node.path)}
|
||||
>
|
||||
{#if node.size > 0}
|
||||
<button type="button" {onclick}>
|
||||
<Icon icon={isOpen ? mdiChevronDown : mdiChevronRight} class="text-gray-400" size="20" />
|
||||
</button>
|
||||
{/if}
|
||||
<div class={node.size === 0 ? 'ml-[1.5em] ' : ''}>
|
||||
<Icon
|
||||
icon={isActive ? icons.active : icons.default}
|
||||
class={isActive ? 'text-primary' : 'text-gray-400'}
|
||||
color={node.color}
|
||||
size="20"
|
||||
/>
|
||||
<div
|
||||
class={`flex grow place-items-center ps-2 py-1 text-sm rounded-lg cursor-pointer hover:bg-slate-200 dark:hover:bg-slate-800 hover:font-semibold ${isTarget ? 'bg-slate-100 dark:bg-slate-700 font-semibold text-primary' : 'dark:text-gray-200'}`}
|
||||
>
|
||||
{#if node.size > 0}
|
||||
<button tabindex={-1} aria-hidden="true" type="button" {onclick}>
|
||||
<Icon icon={isOpen ? mdiChevronDown : mdiChevronRight} class="text-gray-400" size="20" />
|
||||
</button>
|
||||
{/if}
|
||||
<div class={node.size === 0 ? 'ml-[1.5em] ' : ''}>
|
||||
<Icon
|
||||
icon={isActive ? icons.active : icons.default}
|
||||
class={isActive ? 'text-primary' : 'text-gray-400'}
|
||||
color={node.color}
|
||||
size="20"
|
||||
/>
|
||||
</div>
|
||||
<span class="text-nowrap overflow-hidden text-ellipsis font-mono ps-1 pt-1 whitespace-pre-wrap">{node.value}</span>
|
||||
</div>
|
||||
<span class="text-nowrap overflow-hidden text-ellipsis font-mono ps-1 pt-1 whitespace-pre-wrap">{node.value}</span>
|
||||
</a>
|
||||
|
||||
{#if isOpen}
|
||||
<TreeItems tree={node} {icons} {active} {getLink} />
|
||||
{/if}
|
||||
{#if isOpen}
|
||||
<TreeItems tree={node} {icons} {active} {getLink} isNested />
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
<style>
|
||||
li[role='treeitem']:focus-visible > div {
|
||||
outline-style: var(--tw-outline-style);
|
||||
outline-width: 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto, invalidateAll } from '$app/navigation';
|
||||
import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||
import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte';
|
||||
@@ -20,7 +20,6 @@
|
||||
import TagAction from '$lib/components/timeline/actions/TagAction.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/timeline/AssetSelectControlBar.svelte';
|
||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||
import SkipLink from '$lib/elements/SkipLink.svelte';
|
||||
import type { Viewport } from '$lib/managers/timeline-manager/types';
|
||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||
import { foldersStore } from '$lib/stores/folders.svelte';
|
||||
@@ -79,7 +78,6 @@
|
||||
<UserPageLayout title={data.meta.title}>
|
||||
{#snippet sidebar()}
|
||||
<Sidebar>
|
||||
<SkipLink target={`#${headerId}`} text={$t('skip_to_folders')} breakpoint="md" />
|
||||
<section>
|
||||
<div class="uppercase text-xs ps-4 mb-2 dark:text-white">{$t('explorer')}</div>
|
||||
<div class="h-full">
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import UserPageLayout, { headerId } from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import UserPageLayout from '$lib/components/layouts/user-page-layout.svelte';
|
||||
import Breadcrumbs from '$lib/components/shared-components/tree/breadcrumbs.svelte';
|
||||
import TreeItemThumbnails from '$lib/components/shared-components/tree/tree-item-thumbnails.svelte';
|
||||
import TreeItems from '$lib/components/shared-components/tree/tree-items.svelte';
|
||||
import Sidebar from '$lib/components/sidebar/sidebar.svelte';
|
||||
import Timeline from '$lib/components/timeline/Timeline.svelte';
|
||||
import { AppRoute, AssetAction, QueryParameter } from '$lib/constants';
|
||||
import SkipLink from '$lib/elements/SkipLink.svelte';
|
||||
import { TimelineManager } from '$lib/managers/timeline-manager/timeline-manager.svelte';
|
||||
import TagCreateModal from '$lib/modals/TagCreateModal.svelte';
|
||||
import TagEditModal from '$lib/modals/TagEditModal.svelte';
|
||||
@@ -84,7 +83,6 @@
|
||||
<UserPageLayout title={data.meta.title}>
|
||||
{#snippet sidebar()}
|
||||
<Sidebar>
|
||||
<SkipLink target={`#${headerId}`} text={$t('skip_to_tags')} breakpoint="md" />
|
||||
<section>
|
||||
<div class="uppercase text-xs ps-4 mb-2 dark:text-white">{$t('explorer')}</div>
|
||||
<div class="h-full">
|
||||
|
||||
Reference in New Issue
Block a user