| <script lang="ts"> | |
| import type { Node } from "./utils"; | |
| import { createEventDispatcher, tick } from "svelte"; | |
| import Arrow from "./ArrowIcon.svelte"; | |
| import Checkbox from "./Checkbox.svelte"; | |
| import FileIcon from "../icons/light-file.svg"; | |
| import FolderIcon from "../icons/light-folder.svg"; | |
| export let interactive: boolean; | |
| export let tree: Node[] = []; | |
| export let icons: any = {}; | |
| export let node_indices: number[] = []; | |
| export let file_count: "single" | "multiple" = "multiple"; | |
| const dispatch = createEventDispatcher<{ | |
| check: { node_indices: number[]; checked: boolean }; | |
| }>(); | |
| async function dispatch_change(i: number): Promise<void> { | |
| await tick(); | |
| dispatch("check", { | |
| node_indices: [...node_indices, i], | |
| checked: !tree[i].checked | |
| }); | |
| } | |
| </script> | |
| <ul> | |
| {#each tree as { type, path, children, children_visible, checked }, i} | |
| <li> | |
| <span class="wrap"> | |
| <Checkbox | |
| disabled={!interactive || | |
| (type === "folder" && file_count === "single")} | |
| bind:value={checked} | |
| on:change={() => dispatch_change(i)} | |
| /> | |
| {#if type === "folder"} | |
| <span | |
| class="icon" | |
| class:hidden={!tree[i].children_visible} | |
| on:click|stopPropagation={() => | |
| (tree[i].children_visible = !tree[i].children_visible)} | |
| role="button" | |
| aria-label="expand directory" | |
| tabindex="0" | |
| on:keydown={({ key }) => | |
| (key === " " || key === "Enter") && | |
| (tree[i].children_visible = !tree[i].children_visible)} | |
| ><Arrow /></span | |
| > | |
| {:else if path === ""} | |
| <span class="file-icon"> | |
| <img src={FolderIcon} alt="folder icon" /> | |
| </span> | |
| {:else} | |
| <span class="file-icon"> | |
| <img src={FileIcon} alt="file icon" /> | |
| </span> | |
| {/if} | |
| {path ? path : "."} | |
| </span> | |
| {#if children && children_visible} | |
| <svelte:self | |
| tree={children} | |
| {icons} | |
| on:check | |
| node_indices={[...node_indices, i]} | |
| {interactive} | |
| {file_count} | |
| /> | |
| {/if} | |
| </li> | |
| {/each} | |
| </ul> | |
| <style> | |
| .icon { | |
| display: inline-block; | |
| width: 18px; | |
| height: 18px; | |
| padding: 3px 2px 3px 3px; | |
| margin: 0; | |
| flex-grow: 0; | |
| display: inline-flex; | |
| justify-content: center; | |
| align-items: center; | |
| border-radius: 2px; | |
| cursor: pointer; | |
| transition: 0.1s; | |
| flex-shrink: 0; | |
| } | |
| .file-icon { | |
| display: inline-block; | |
| height: 20px; | |
| margin-left: -1px; | |
| margin: 0; | |
| flex-grow: 0; | |
| display: inline-flex; | |
| justify-content: center; | |
| align-items: center; | |
| transition: 0.1s; | |
| } | |
| .file-icon img { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| .icon:hover { | |
| background: #eee; | |
| } | |
| .icon:hover :global(> *) { | |
| color: var(--block-info-text-color); | |
| } | |
| .icon :global(> *) { | |
| transform: rotate(90deg); | |
| transform-origin: 40% 50%; | |
| transition: 0.2s; | |
| color: var(--color-accent); | |
| } | |
| .hidden :global(> *) { | |
| transform: rotate(0); | |
| color: var(--body-text-color-subdued); | |
| } | |
| ul { | |
| margin-left: 26px; | |
| padding-left: 0; | |
| list-style: none; | |
| } | |
| li { | |
| margin-left: 0; | |
| padding-left: 0; | |
| align-items: center; | |
| margin: 8px 0; | |
| font-family: var(--font-mono); | |
| font-size: var(--scale-00); | |
| overflow-wrap: anywhere; | |
| word-break: break-word; | |
| } | |
| .wrap { | |
| display: flex; | |
| gap: 8px; | |
| align-items: center; | |
| } | |
| </style> | |