mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-19 09:22:12 +00:00
fix sortable
This commit is contained in:
1 parent
970cf7d1e1
commit
01443943b3
6 files changed
+108
-10
No files matched your search
@@ -9,6 +9,7 @@
|
||||
nohover = false,
|
||||
slotInsideTrigger = true,
|
||||
sticky = false,
|
||||
treeState,
|
||||
children,
|
||||
}: {
|
||||
defaultState?: 'open' | 'closed';
|
||||
@@ -21,10 +22,12 @@
|
||||
* like its node.
|
||||
*/
|
||||
sticky?: boolean;
|
||||
/** Which nodes are open, to keep it or to open nodes from the outside. */
|
||||
treeState?: CollapsibleTreeState;
|
||||
children: Snippet;
|
||||
} = $props();
|
||||
|
||||
let open = $state(new CollapsibleTreeState(defaultState));
|
||||
let open = $state(treeState ?? new CollapsibleTreeState(defaultState));
|
||||
|
||||
setContext('collapsible-tree-state', open);
|
||||
setContext('collapsible-tree-side', side);
|
||||
|
||||
@@ -1,29 +1,93 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import { CollapsibleTree, CollapsibleTreeNode } from '$lib/components/collapsible-tree/index';
|
||||
import { CollapsibleTreeState } from '$lib/components/collapsible-tree/utils.svelte';
|
||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
||||
import ExplorerTree from './ExplorerTree.svelte';
|
||||
import { clickedInExplorer } from './state.svelte';
|
||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||
import { engine } from '$lib/engine';
|
||||
import { folderName } from '$lib/engine/workspace';
|
||||
import { selectedFileIds } from '$lib/engine/selection-helpers';
|
||||
import { folderName, subfolder } from '$lib/engine/workspace';
|
||||
|
||||
const { workspace } = engine;
|
||||
const { workspace, selection } = engine;
|
||||
|
||||
let folder = $derived($workspace?.folder);
|
||||
|
||||
// which folders are open: new ones when another folder is opened, the folder itself is open at
|
||||
// first and its folders are closed
|
||||
let trees = $derived(
|
||||
folder
|
||||
? {
|
||||
root: new CollapsibleTreeState('open'),
|
||||
folders: new CollapsibleTreeState('closed'),
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
|
||||
let container: HTMLElement | undefined = $state();
|
||||
|
||||
// the file that is selected, if it is in the folder
|
||||
let selectedPath = $derived.by(() => {
|
||||
const id = selectedFileIds($selection)[0];
|
||||
const path = id === undefined ? undefined : $workspace?.files[id]?.path;
|
||||
return subfolder(folder, path) === undefined ? undefined : path;
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// not what the click that was just made depends on, only the selection and the folder
|
||||
const clicked = untrack(() => clickedInExplorer.path);
|
||||
if (selectedPath === clicked) {
|
||||
return;
|
||||
}
|
||||
clickedInExplorer.path = undefined;
|
||||
if (folder && selectedPath !== undefined) {
|
||||
return reveal(folder, selectedPath);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Opens the folders that hold a file and scrolls to the file, if it is not in view already. Its
|
||||
* row may not be there yet, because the content of a folder is read when it is opened.
|
||||
*/
|
||||
function reveal(folder: string, path: string) {
|
||||
if (!trees) {
|
||||
return;
|
||||
}
|
||||
trees.root.get('root.folder').current = true;
|
||||
let id = 'root';
|
||||
for (const name of subfolder(folder, path)?.split('/').filter(Boolean) ?? []) {
|
||||
id += `.${name}`;
|
||||
trees.folders.get(id).current = true;
|
||||
}
|
||||
let frame = 0;
|
||||
let attempts = 0;
|
||||
const scroll = () => {
|
||||
const row = container?.querySelector(`[data-path="${CSS.escape(path)}"]`);
|
||||
if (row) {
|
||||
row.scrollIntoView({ block: 'nearest' });
|
||||
} else if (attempts++ < 120) {
|
||||
frame = requestAnimationFrame(scroll);
|
||||
}
|
||||
};
|
||||
frame = requestAnimationFrame(scroll);
|
||||
return () => cancelAnimationFrame(frame);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!--
|
||||
The tree of the open folder: its folders and GPX files, one level read at a time. The files are
|
||||
opened from it (the file list has the ones that are open).
|
||||
-->
|
||||
{#if folder}
|
||||
{#if folder && trees}
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="px-2 py-1 text-sm font-semibold shrink-0">{i18n._('menu.explorer')}</div>
|
||||
<ScrollArea class="grow min-h-0">
|
||||
<div class="flex flex-col pl-1 pr-3 pb-1">
|
||||
<div bind:this={container} class="flex flex-col pl-1 pr-3 pb-1">
|
||||
<!-- starts over when another folder is opened -->
|
||||
{#key folder}
|
||||
<!-- the folder itself, which is open at first -->
|
||||
<CollapsibleTree side="left" defaultState="open" sticky>
|
||||
<!-- the folder itself -->
|
||||
<CollapsibleTree side="left" treeState={trees.root} sticky>
|
||||
<CollapsibleTreeNode id="folder">
|
||||
{#snippet trigger()}
|
||||
<span class="grow truncate text-left" title={folder}>
|
||||
@@ -34,8 +98,7 @@
|
||||
<!-- what is in it is only read when it is open -->
|
||||
{#if opened}
|
||||
<div class="ml-4">
|
||||
<!-- its folders are closed at first -->
|
||||
<CollapsibleTree side="left" defaultState="closed">
|
||||
<CollapsibleTree side="left" treeState={trees.folders}>
|
||||
<ExplorerTree />
|
||||
</CollapsibleTree>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
import { CollapsibleTreeNode } from '$lib/components/collapsible-tree/index';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { FileText } from '@lucide/svelte';
|
||||
import { getContext } from 'svelte';
|
||||
import { clickedInExplorer } from './state.svelte';
|
||||
import { stickyKey, type StickyLevel } from '$lib/components/collapsible-tree/sticky';
|
||||
import ExplorerTree from './ExplorerTree.svelte';
|
||||
import { openPath } from '$lib/actions/file-actions';
|
||||
import { engine, type FolderEntry } from '$lib/engine';
|
||||
@@ -16,6 +19,8 @@
|
||||
} = $props();
|
||||
|
||||
const { workspace, selection, folderVersion } = engine;
|
||||
// the rows of the folders that stick above the files of this one
|
||||
const level = getContext<StickyLevel>(stickyKey);
|
||||
|
||||
let entries: FolderEntry[] = $state([]);
|
||||
|
||||
@@ -35,6 +40,9 @@
|
||||
|
||||
/** Opens the file, or selects it if it is open already. */
|
||||
function open(entry: FolderEntry, id: string | undefined) {
|
||||
// it is in view, the explorer does not have to scroll to it (which would move the rows
|
||||
// under the pointer, and leave the one it was on highlighted)
|
||||
clickedInExplorer.path = entry.path;
|
||||
if (id !== undefined) {
|
||||
engine.select([id]);
|
||||
} else {
|
||||
@@ -67,6 +75,8 @@
|
||||
? 'bg-accent'
|
||||
: ''} {id === undefined ? 'text-muted-foreground' : ''}"
|
||||
title={entry.path}
|
||||
data-path={entry.path}
|
||||
style="scroll-margin-top: {level.top}px"
|
||||
onclick={() => open(entry, id)}
|
||||
>
|
||||
<FileText size="16" class="shrink-0" />
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/** The file that the user just selected by clicking it in the explorer, until the selection changes. */
|
||||
export const clickedInExplorer: { path: string | undefined } = $state({ path: undefined });
|
||||
Reference in new issue
Block a user