mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-10-19 09:22:12 +00:00
sticky folders
This commit is contained in:
1 parent
8fa88680b3
commit
593b43e433
5 files changed
+48
-4
No files matched your search
@@ -10,6 +10,9 @@
|
|||||||
id: string | number;
|
id: string | number;
|
||||||
class?: ClassValue;
|
class?: ClassValue;
|
||||||
trigger: Snippet;
|
trigger: Snippet;
|
||||||
|
/** Classes and style of the trigger row (when the trigger holds the content), to make it stick. */
|
||||||
|
triggerClass?: ClassValue;
|
||||||
|
triggerStyle?: string;
|
||||||
/** Gets whether the node is open, for contents that are only worth building then. */
|
/** Gets whether the node is open, for contents that are only worth building then. */
|
||||||
content: Snippet<[boolean]>;
|
content: Snippet<[boolean]>;
|
||||||
} = $props();
|
} = $props();
|
||||||
@@ -32,7 +35,7 @@
|
|||||||
|
|
||||||
<Collapsible.Root bind:open={open.current} class={props.class}>
|
<Collapsible.Root bind:open={open.current} class={props.class}>
|
||||||
{#if slotInsideTrigger}
|
{#if slotInsideTrigger}
|
||||||
<Collapsible.Trigger class="w-full">
|
<Collapsible.Trigger class={['w-full', props.triggerClass]} style={props.triggerStyle}>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { CollapsibleTree, CollapsibleTreeNode } from '$lib/components/collapsible-tree/index';
|
import { CollapsibleTree, CollapsibleTreeNode } from '$lib/components/collapsible-tree/index';
|
||||||
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
import { ScrollArea } from '$lib/components/ui/scroll-area/index';
|
||||||
import ExplorerTree from './ExplorerTree.svelte';
|
import ExplorerTree from './ExplorerTree.svelte';
|
||||||
|
import { stickyRowStyle } from './sticky';
|
||||||
import { i18n } from '$lib/i18n/i18n.svelte';
|
import { i18n } from '$lib/i18n/i18n.svelte';
|
||||||
import { engine } from '$lib/engine';
|
import { engine } from '$lib/engine';
|
||||||
import { folderName } from '$lib/engine/workspace';
|
import { folderName } from '$lib/engine/workspace';
|
||||||
@@ -24,7 +25,11 @@
|
|||||||
{#key folder}
|
{#key folder}
|
||||||
<!-- the folder itself, which is open at first -->
|
<!-- the folder itself, which is open at first -->
|
||||||
<CollapsibleTree side="left" defaultState="open">
|
<CollapsibleTree side="left" defaultState="open">
|
||||||
<CollapsibleTreeNode id="folder">
|
<CollapsibleTreeNode
|
||||||
|
id="folder"
|
||||||
|
triggerClass="sticky flex items-center bg-background"
|
||||||
|
triggerStyle={stickyRowStyle(0)}
|
||||||
|
>
|
||||||
{#snippet trigger()}
|
{#snippet trigger()}
|
||||||
<span class="grow truncate text-left" title={folder}>
|
<span class="grow truncate text-left" title={folder}>
|
||||||
{folderName(folder)}
|
{folderName(folder)}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { Button } from '$lib/components/ui/button';
|
import { Button } from '$lib/components/ui/button';
|
||||||
import { FileText } from '@lucide/svelte';
|
import { FileText } from '@lucide/svelte';
|
||||||
import ExplorerTree from './ExplorerTree.svelte';
|
import ExplorerTree from './ExplorerTree.svelte';
|
||||||
|
import { stickyRowStyle } from './sticky';
|
||||||
import { openPath } from '$lib/actions/file-actions';
|
import { openPath } from '$lib/actions/file-actions';
|
||||||
import { engine, type FolderEntry } from '$lib/engine';
|
import { engine, type FolderEntry } from '$lib/engine';
|
||||||
import { selectedFileIds } from '$lib/engine/selection-helpers';
|
import { selectedFileIds } from '$lib/engine/selection-helpers';
|
||||||
@@ -10,9 +11,12 @@
|
|||||||
|
|
||||||
let {
|
let {
|
||||||
path,
|
path,
|
||||||
|
depth = 1,
|
||||||
}: {
|
}: {
|
||||||
/** The folder whose content is listed, the open folder if it is not given. */
|
/** The folder whose content is listed, the open folder if it is not given. */
|
||||||
path?: string;
|
path?: string;
|
||||||
|
/** How many folders deep the folders listed are (the open folder is 0). */
|
||||||
|
depth?: number;
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
const { workspace, selection, folderVersion } = engine;
|
const { workspace, selection, folderVersion } = engine;
|
||||||
@@ -45,7 +49,11 @@
|
|||||||
|
|
||||||
{#each entries as entry (entry.path)}
|
{#each entries as entry (entry.path)}
|
||||||
{#if entry.isDir}
|
{#if entry.isDir}
|
||||||
<CollapsibleTreeNode id={entry.name}>
|
<CollapsibleTreeNode
|
||||||
|
id={entry.name}
|
||||||
|
triggerClass="sticky flex items-center bg-background"
|
||||||
|
triggerStyle={stickyRowStyle(depth)}
|
||||||
|
>
|
||||||
{#snippet trigger()}
|
{#snippet trigger()}
|
||||||
<span class="grow truncate text-left">{entry.name}</span>
|
<span class="grow truncate text-left">{entry.name}</span>
|
||||||
{/snippet}
|
{/snippet}
|
||||||
@@ -53,7 +61,7 @@
|
|||||||
<!-- the content of a folder is only read when it is opened -->
|
<!-- the content of a folder is only read when it is opened -->
|
||||||
{#if opened}
|
{#if opened}
|
||||||
<div class="ml-4">
|
<div class="ml-4">
|
||||||
<ExplorerTree path={entry.path} />
|
<ExplorerTree path={entry.path} depth={depth + 1} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/snippet}
|
{/snippet}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { describe, it } from 'node:test';
|
||||||
|
import { rowHeight, stickyRowStyle } from './sticky';
|
||||||
|
|
||||||
|
describe('stickyRowStyle', () => {
|
||||||
|
it('puts each row under the ones of the folders it is in', () => {
|
||||||
|
assert.match(stickyRowStyle(0), /top: 0px/);
|
||||||
|
assert.match(stickyRowStyle(2), new RegExp(`top: ${2 * rowHeight}px`));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('puts the rows of the folders above over the rows below', () => {
|
||||||
|
const zIndex = (depth: number) => Number(/z-index: (\d+)/.exec(stickyRowStyle(depth))![1]);
|
||||||
|
assert.ok(zIndex(0) > zIndex(1));
|
||||||
|
assert.ok(zIndex(1) > zIndex(5));
|
||||||
|
assert.ok(zIndex(500) >= 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
/** The height of a row of the explorer. */
|
||||||
|
export const rowHeight = 28;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The style of the row of a folder that is `depth` folders deep (0 for the open folder), to stick
|
||||||
|
* under the rows of the folders it is in while any of its content can be seen: the rows of the
|
||||||
|
* folders above are above it too.
|
||||||
|
*/
|
||||||
|
export function stickyRowStyle(depth: number): string {
|
||||||
|
return `top: ${depth * rowHeight}px; height: ${rowHeight}px; z-index: ${Math.max(100 - depth, 1)};`;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user