Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

treeview #1527

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions frontend/src/reports/editor/EditorMenu.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
import AppMenu from "./AppMenu.svelte";
import AppMenuItem from "./AppMenuItem.svelte";
import AppMenuSubItem from "./AppMenuSubItem.svelte";
import File from "./File.svelte";
import Folder from "./Folder.svelte";
import Key from "./Key.svelte";
import { source_tree } from "./treeview";

export let file_path: string;
export let editor: EditorView;
Expand All @@ -33,18 +36,17 @@
editor.focus();
}
}
$: [filenodes, foldernodes] = source_tree(sources, goToFileAndLine);
</script>

<div class="fieldset">
<AppMenu>
<AppMenuItem name={_("File")}>
{#each sources as source}
<AppMenuSubItem
action={() => goToFileAndLine(source)}
selected={source === file_path}
>
{source}
</AppMenuSubItem>
{#each filenodes as file}
<File {file} {file_path} />
{/each}
{#each foldernodes as folder}
<Folder {folder} {file_path} force_expand />
{/each}
</AppMenuItem>
<AppMenuItem name={_("Edit")}>
Expand Down
28 changes: 28 additions & 0 deletions frontend/src/reports/editor/File.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<script lang="ts">
import type { FileNode } from "./treeview";

export let file: FileNode;
export let file_path: string;
</script>

<span class:selected={file.path === file_path}>
<button type="button" on:click={file.action}>
{file.name}
</button>
</span>

<style>
.selected::before {
content: "›";
}

span {
padding: 2px 0;
cursor: pointer;
}

button {
display: contents;
color: blue;
}
</style>
67 changes: 67 additions & 0 deletions frontend/src/reports/editor/Folder.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script lang="ts">
import File from "./File.svelte";
import type { FolderNode } from "./treeview";

export let force_expand = false;
export let folder: FolderNode;
export let file_path: string;
</script>

<div class:force-expand={force_expand}>
<span>{folder.name}</span>

<ul>
{#each folder.subfiles as file}
<li>
<File {file} {file_path} />
</li>
{/each}
{#each folder.subfolders as subfolder}
<li>
<svelte:self folder={subfolder} {file_path} />
</li>
{/each}
</ul>
</div>

<style>
span {
padding: 2px 0;
font-weight: bold;
cursor: pointer;
}

ul {
padding: 0.2em 0 0 1em;
margin: 0 0 0 0.27em;
list-style: none;
}

li {
padding: 0.2em 0;
}

div {
display: inline-block;
}

div > ul {
display: none;
}

div:hover > ul,
div.force-expand > ul {
display: block;
}

div:not(:hover, .force-expand) > span::before {
font-family: monospace;
content: "+";
}

div:hover > span::before,
div.force-expand > span::before {
font-family: monospace;
content: "-";
}
</style>
90 changes: 90 additions & 0 deletions frontend/src/reports/editor/treeview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
export interface FileNode {
name: string;
path: string;
action: () => void;
}
export interface FolderNode {
name: string;
subfolders: FolderNode[];
subfiles: FileNode[];
}

const zip: <A, B>(a: A[], b: B[]) => [A, B][] = <A, B>(a: A[], b: B[]) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zip from d3-array could probably be used here instead

a.reduce((l: [A, B][], k: A, i) => {
const n = b[i];
if (n !== undefined) {
l.push([k, n]);
}
return l;
}, []);

function shorten_folder(folder: FolderNode): FolderNode {
if (folder.subfiles.length === 0) {
const subfolder = folder.subfolders[0];
if (subfolder !== undefined) {
const new_name = `${folder.name}/${subfolder.name}`;
return shorten_folder({
name: new_name,
subfolders: subfolder.subfolders,
subfiles: subfolder.subfiles,
});
}
}
return {
name: folder.name,
subfolders: folder.subfolders.map(shorten_folder),
subfiles: folder.subfiles,
};
}
function _source_tree(
paths: string[][],
filenodes: FileNode[]
): [FileNode[], FolderNode[]] {
const groupBy = <T>(arr: T[], key: (i: T) => string) =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

group from d3-array could probably be used here instead

arr.reduce<Record<string, T[]>>((groups, item) => {
(groups[key(item)] ||= []).push(item);
return groups;
}, {});

const r1 = zip(filenodes, paths)
.filter((a) => a[1].length === 0)
.map((a) => a[0]);
const r2 = Object.entries(
groupBy(
zip(filenodes, paths).reduce(
(acc: [FileNode, string[], string][], [file, path]) => {
const root = path[0];
if (root !== undefined) {
acc.push([file, path.slice(1), root]);
}
return acc;
},
[]
),
(a: [FileNode, string[], string]) => a[2]
)
).map(([dir, group]: [string, [FileNode, string[], string][]]) => {
const [subfiles, subfolders] = _source_tree(
group.map((a: [FileNode, string[], string]) => a[1]),
group.map((a: [FileNode, string[], string]) => a[0])
);
return { name: dir, subfolders, subfiles };
});
return [r1, r2];
}
export function source_tree(
files: string[],
goToFileAndLine: (filename: string, line?: number) => void
): [FileNode[], FolderNode[]] {
files.sort();
const paths = files.map((file) => file.split("/"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't work for paths on Windows...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess just splitting by forward and backward slashes might be good enough for our use case here

const raw_filenodes = zip(files, paths).map(
([file, path]: [string, string[]]) => ({
name: path.pop() ?? file,
path: file,
action: () => goToFileAndLine(file),
})
);
const [filenodes, foldernodes] = _source_tree(paths, raw_filenodes);
return [filenodes, foldernodes.map(shorten_folder)];
}