getting there

* stream view and page list working
This commit is contained in:
Kilian Schuettler 2025-02-01 03:10:46 +01:00
parent 7e71cb947b
commit 570d495faa
15 changed files with 354 additions and 213 deletions

View File

@ -59,6 +59,7 @@ pub struct PrimitiveModel {
pub value: String, pub value: String,
pub children: Vec<PrimitiveModel>, pub children: Vec<PrimitiveModel>,
pub trace: Vec<PathTrace>, pub trace: Vec<PathTrace>,
pub expanded: bool,
} }
#[derive(Serialize, Debug, Clone)] #[derive(Serialize, Debug, Clone)]
@ -79,12 +80,12 @@ pub struct PageModel {
page_num: u64, page_num: u64,
} }
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
pub struct TreeViewNode { pub struct TreeViewRequest {
key: String, key: String,
children: Vec<TreeViewNode>, children: Vec<TreeViewRequest>,
} }
impl TreeViewNode { impl TreeViewRequest {
fn step(&self) -> Result<Step, String> { fn step(&self) -> Result<Step, String> {
Step::parse_step(&self.key) Step::parse_step(&self.key)
} }
@ -303,7 +304,7 @@ fn resolve_parent(step: Step, file: &CosFile) -> Result<(Primitive, PathTrace),
#[tauri::command] #[tauri::command]
fn get_prim_tree_by_path( fn get_prim_tree_by_path(
id: &str, id: &str,
path: TreeViewNode, path: TreeViewRequest,
session: State<Mutex<Session>>, session: State<Mutex<Session>>,
) -> Result<PrimitiveModel, String> { ) -> Result<PrimitiveModel, String> {
let session_guard = session let session_guard = session
@ -315,7 +316,7 @@ fn get_prim_tree_by_path(
} }
fn get_prim_tree_by_path_with_file( fn get_prim_tree_by_path_with_file(
node: TreeViewNode, node: TreeViewRequest,
file: &CosFile, file: &CosFile,
) -> Result<PrimitiveModel, String> { ) -> Result<PrimitiveModel, String> {
let step = node.step()?; let step = node.step()?;
@ -332,7 +333,7 @@ fn get_prim_tree_by_path_with_file(
} }
fn expand( fn expand(
node: &TreeViewNode, node: &TreeViewRequest,
parent_model: &mut PrimitiveModel, parent_model: &mut PrimitiveModel,
parent: &Primitive, parent: &Primitive,
file: &CosFile, file: &CosFile,
@ -357,7 +358,7 @@ fn expand(
} }
fn expand_children( fn expand_children(
node: &TreeViewNode, node: &TreeViewRequest,
file: &CosFile, file: &CosFile,
prim: &Primitive, prim: &Primitive,
mut expanded: &mut PrimitiveModel, mut expanded: &mut PrimitiveModel,
@ -562,6 +563,7 @@ impl PrimitiveModel {
value: value, value: value,
children: Vec::new(), children: Vec::new(),
trace: path, trace: path,
expanded: false,
} }
} }
@ -604,6 +606,7 @@ impl PrimitiveModel {
} }
fn add_children(&mut self, primitive: &Primitive, path: Vec<PathTrace>) { fn add_children(&mut self, primitive: &Primitive, path: Vec<PathTrace>) {
self.expanded = true;
match primitive { match primitive {
Primitive::Dictionary(dict) => dict.iter().for_each(|(name, value)| { Primitive::Dictionary(dict) => dict.iter().for_each(|(name, value)| {
self.add_child( self.add_child(
@ -623,6 +626,7 @@ impl PrimitiveModel {
value: "".to_string(), value: "".to_string(),
children: vec![], children: vec![],
trace: append_path("Data".to_string(), &path), trace: append_path("Data".to_string(), &path),
expanded: false,
}); });
stream.info.iter().for_each(|(name, value)| { stream.info.iter().for_each(|(name, value)| {
self.add_child( self.add_child(

View File

@ -6,7 +6,7 @@ mod tests {
use crate::{ use crate::{
get_prim_by_path_with_file, get_prim_model_by_path_with_file, get_prim_by_path_with_file, get_prim_model_by_path_with_file,
get_prim_tree_by_path_with_file, get_stream_data_by_path_with_file, get_prim_tree_by_path_with_file, get_stream_data_by_path_with_file,
get_xref_table_model_with_file, to_pdf_file, PathTrace, PrimitiveModel, TreeViewNode, get_xref_table_model_with_file, to_pdf_file, PathTrace, PrimitiveModel, TreeViewRequest,
}; };
use pdf::content::{display_ops, serialize_ops, Op}; use pdf::content::{display_ops, serialize_ops, Op};
@ -61,25 +61,25 @@ mod tests {
"Loading file" "Loading file"
); );
let mut path = Vec::new(); let mut path = Vec::new();
path.push(TreeViewNode { path.push(TreeViewRequest {
key: "Index".to_string(), key: "Index".to_string(),
children: vec![TreeViewNode { children: vec![TreeViewRequest {
key: "1".to_string(), key: "1".to_string(),
children: vec![], children: vec![],
}], }],
}); });
path.push(TreeViewNode { path.push(TreeViewRequest {
key: "Info".to_string(), key: "Info".to_string(),
children: vec![], children: vec![],
}); });
path.push(TreeViewNode { path.push(TreeViewRequest {
key: "Root".to_string(), key: "Root".to_string(),
children: vec![TreeViewNode { children: vec![TreeViewRequest {
key: "Pages".to_string(), key: "Pages".to_string(),
children: vec![], children: vec![],
}], }],
}); });
let root = TreeViewNode { let root = TreeViewRequest {
key: "/".to_string(), key: "/".to_string(),
children: path, children: path,
}; };

View File

@ -7,10 +7,10 @@
import { createEventDispatcher, onMount } from "svelte"; import { createEventDispatcher, onMount } from "svelte";
import PageList from "./PageList.svelte"; import PageList from "./PageList.svelte";
import ContentsView from "./ContentsView.svelte"; import ContentsView from "./ContentsView.svelte";
import TreeViewNode from "../models/TreeViewNode.svelte"; import TreeViewRequest from "../models/TreeViewRequest.svelte";
import TreeViewState from "../models/TreeViewState.svelte"; import TreeViewState from "../models/TreeViewState.svelte";
import type { PathSelectedEvent } from "../events/PathSelectedEvent"; import type { PathSelectedEvent } from "../events/PathSelectedEvent";
TreeViewRequest;
let { let {
treeShowing, treeShowing,
xrefTableShowing, xrefTableShowing,
@ -52,10 +52,8 @@
window.removeEventListener("mousedown", handleMouseButton); window.removeEventListener("mousedown", handleMouseButton);
}; };
}); });
function pathSelectedHandler(event: PathSelectedEvent) { function pathSelectedHandler(event: PathSelectedEvent) {
console.log("Path selected", event.detail.path); fState.selectPathHandler(event);
fState.selectPath(event.detail.path);
} }
</script> </script>
@ -73,11 +71,15 @@
{#if pagesShowing} {#if pagesShowing}
<PageList {fState} h={height}></PageList> <PageList {fState} h={height}></PageList>
{:else if treeShowing} {:else if treeShowing}
<div class="overflow-auto" style="height: {height}px">
<TreeView <TreeView
bind:pathSelectedHandler {pathSelectedHandler}
file_id={fState.file.id} file_id={fState.file.id}
root={TreeViewNode.TRAILER} root={TreeViewRequest.TRAILER}
active={true}
details={false}
></TreeView> ></TreeView>
</div>
{/if} {/if}
</Pane> </Pane>

View File

@ -1,48 +1,37 @@
<script lang="ts"> <script lang="ts">
import type FileViewState from "../models/FileViewState.svelte"; import type FileViewState from "../models/FileViewState.svelte";
import PrimitiveIcon from "./PrimitiveIcon.svelte"; import TreeView from "./TreeView.svelte";
import type PageModel from "../models/PageModel"; import TreeViewRequest from "../models/TreeViewRequest.svelte";
import type { PathSelectedEvent } from "../events/PathSelectedEvent";
let { fState, h }: { fState: FileViewState; h: number } = $props(); let { fState, h }: { fState: FileViewState; h: number } = $props();
let selected: PageModel | undefined = $state(undefined); let selected_page_num: number | undefined = $state(undefined);
let pages = $derived(fState.file.pages);
function handlePageSelect(page: PageModel) { function pathSelectedHandler(event: PathSelectedEvent) {
selected = page; let path = event.detail.path;
fState.selectPath(["Page" + page.page_num]); if (
path.length > 0 &&
path[0].startsWith("Page") &&
!isNaN(+path[0].replace("Page", ""))
) {
selected_page_num = +path[0].replace("Page", "");
}
fState.selectPath(path);
} }
</script> </script>
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table> <div class="overflow-y-auto" style="height: {h}px">
<thead> {#each pages as page}
<tr> <TreeView
<td class="page-cell t-header border-forge-prim">Page</td> file_id={fState.file.id}
<td class="ref-cell t-header border-forge-sec">Ref</td> root={new TreeViewRequest("Page" + page.page_num, [])}
</tr> {pathSelectedHandler}
</thead> active={selected_page_num === page.page_num}
</table> details={true}
<div class="overflow-y-auto" style="height: {h - 25}px"> ></TreeView>
<table>
<tbody>
{#each fState.file.pages as page}
<tr
class:selected={page === selected}
class="hover:bg-forge-sec"
ondblclick={() => handlePageSelect(page)}
>
<td class="page-cell t-data">
<div class="key-field">
<PrimitiveIcon ptype={"Reference"} />
<p class="text-left">
{page.key}
</p>
</div>
</td>
<td class="ref-cell t-data">{page.obj_num}</td>
</tr>
{/each} {/each}
</tbody>
</table>
</div> </div>
</div> </div>

View File

@ -3,12 +3,15 @@
import type Primitive from "../models/Primitive.svelte"; import type Primitive from "../models/Primitive.svelte";
import ContentsView from "./ContentsView.svelte"; import ContentsView from "./ContentsView.svelte";
import PrimitiveIcon from "./PrimitiveIcon.svelte"; import PrimitiveIcon from "./PrimitiveIcon.svelte";
import StreamEditor from "./StreamEditor.svelte";
let { fState, height }: { fState: FileViewState; height: number } = let { fState, height }: { fState: FileViewState; height: number } =
$props(); $props();
let bodyHeight = $derived(height - 24); let bodyHeight = $derived(height - 24);
let prim = $derived(fState.prim); let prim = $derived(fState.prim);
let showContents = $state(false); let showContents = $state(false);
let tableHeight = $state(0);
let editorHeight = $derived(Math.max(800, bodyHeight - tableHeight));
$inspect(fState.highlightedPrim); $inspect(fState.highlightedPrim);
function handlePrimSelect(prim: Primitive) { function handlePrimSelect(prim: Primitive) {
if (prim.isContainer()) { if (prim.isContainer()) {
@ -39,7 +42,7 @@
</thead> </thead>
</table> </table>
<div class="overflow-y-auto" style="height: {bodyHeight}px"> <div class="overflow-y-auto" style="height: {bodyHeight}px">
<table> <table bind:clientHeight={tableHeight}>
<tbody> <tbody>
{#each prim.children as entry} {#each prim.children as entry}
<tr <tr
@ -63,6 +66,13 @@
{/each} {/each}
</tbody> </tbody>
</table> </table>
{#if fState.prim?.ptype === "Stream"}
<StreamEditor
fileId={fState.file.id}
path={fState.getMergedPath()}
height={editorHeight}
></StreamEditor>
{/if}
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,56 @@
<script lang="ts">
import { invoke } from "@tauri-apps/api/core";
import type ContentModel from "../models/ContentModel.svelte";
import { onMount } from "svelte";
import * as monaco from "monaco-editor";
let {
fileId,
path,
height,
}: { fileId: string; path: string; height: number } = $props();
let contents: string | undefined = $state(undefined);
let editorContainer: HTMLElement;
let editor: monaco.editor.IStandaloneCodeEditor;
onMount(() => {
editor = monaco.editor.create(editorContainer, {
value: "",
language: "plaintext",
theme: "vs-dark",
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontSize: 14,
automaticLayout: true,
});
return () => {
editor.dispose();
};
});
$effect(() => {
loadContents(path, fileId);
});
$inspect(contents);
function loadContents(path: string | undefined, id: string) {
console.log("Loading contents for", path, id);
if (!path || !id) return;
path = path + "/Data";
invoke<string>("get_stream_data", { id, path })
.then((result) => {
console.log("Contents loaded", result);
contents = result;
if (contents && editor) {
editor.setValue(contents);
}
})
.catch((err) => console.error(err));
}
</script>
<div bind:this={editorContainer} style="height: {height}px; width: 100%;"></div>
<style lang="postcss">
</style>

View File

@ -7,15 +7,19 @@
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
let { let {
file_id,
parent_view, parent_view,
parent_path, parent_path,
treeState, treeState,
pathSelectedHandler = $bindable(), pathSelectedHandler = $bindable(),
details,
}: { }: {
parent_view: Primitive; file_id: string;
parent_view: Primitive | undefined;
parent_path: string[]; parent_path: string[];
treeState: TreeViewState; treeState: TreeViewState | undefined;
pathSelectedHandler: any; pathSelectedHandler: any;
details: boolean;
} = $props(); } = $props();
function copyPathAndAppend(key: string): string[] { function copyPathAndAppend(key: string): string[] {
@ -35,73 +39,75 @@
function selectItem(child: Primitive) { function selectItem(child: Primitive) {
let _path = copyPathAndAppend(child.key); let _path = copyPathAndAppend(child.key);
console.log("Selecting from tree", _path); if (child.expanded) {
treeState.expandTree(_path); treeState?.collapseTree(_path);
} else {
const event = new PathSelectedEvent(_path); treeState?.expandTree(_path);
}
const event = new PathSelectedEvent(file_id, _path);
pathSelectedHandler(event); pathSelectedHandler(event);
} }
</script> </script>
{#if parent_view} {#if parent_view}
<ul class="active">
{#each parent_view.children as child} {#each parent_view.children as child}
<li>
<div class="item">
{#if child.children.length > 0}
<button <button
onclick={() => class="row hover:bg-forge-sec w-full"
treeState.collapseTree( onclick={() => selectItem(child)}
copyPathAndAppend(child.key),
)}
><span class="caret"><CaretDownOutline /></span
></button
> >
{:else if child.isContainer()} <div class="item" style="margin-left: {parent_path.length * 1}em">
<button {#if child.isContainer()}
onclick={() => <div>
treeState.expandTree( <span class="caret"
copyPathAndAppend(child.key), >{#if child.expanded}<CaretDownOutline
)} />{:else}<CaretRightOutline />{/if}</span
><span class="caret"><CaretRightOutline /></span
></button
> >
</div>
{:else} {:else}
<span class="no-caret"></span> <span class="no-caret"></span>
{/if} {/if}
<button
class="select-button"
ondblclick={() => selectItem(child)}
>
<div class="item"> <div class="item">
<PrimitiveIcon ptype={child.ptype} /> <PrimitiveIcon ptype={child.ptype} />
<div class="row"> <div class="row">
<p> <p>
{child.key} {child.key}
</p> </p>
{#if child.sub_type} {#if details}
<p class="hint">
{" | " +
child.ptype +
" | " +
child.sub_type +
" | " +
child.value}
</p>
{:else}
<p class="ml-1 text-forge-sec small"> <p class="ml-1 text-forge-sec small">
{child.sub_type} {child.sub_type}
</p> </p>
{/if} {/if}
</div> </div>
</div> </div>
</button>
</div> </div>
</button>
{#if child.children.length > 0} {#if child.children.length > 0}
<svelte:self <svelte:self
{file_id}
parent_view={child} parent_view={child}
parent_path={copyPathAndAppend(child.key)} parent_path={copyPathAndAppend(child.key)}
{treeState} {treeState}
bind:pathSelectedHandler bind:pathSelectedHandler
{details}
></svelte:self> ></svelte:self>
{/if} {/if}
</li>
{/each} {/each}
</ul>
{/if} {/if}
<style lang="postcss"> <style lang="postcss">
.hint {
@apply ml-1 text-forge-text_hint text-xs whitespace-nowrap font-extralight;
}
.select-button { .select-button {
@apply hover:bg-forge-sec pr-3; @apply hover:bg-forge-sec pr-3;
cursor: pointer; cursor: pointer;
@ -144,17 +150,14 @@
user-select: none; user-select: none;
} }
/* Style the caret/arrow */
.caret { .caret {
@apply text-forge-sec; @apply text-forge-sec;
cursor: pointer; cursor: pointer;
user-select: none; /* Prevent text selection */ user-select: none;
} }
/* Create the caret/arrow with a unicode, and style it */ .caret-active {
.caret::before { transform: rotate(90deg);
display: inline-block;
margin-right: 6px;
} }
/* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */ /* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */

View File

@ -2,47 +2,79 @@
import TreeNode from "./TreeNode.svelte"; import TreeNode from "./TreeNode.svelte";
import PrimitiveIcon from "./PrimitiveIcon.svelte"; import PrimitiveIcon from "./PrimitiveIcon.svelte";
import TreeViewState from "../models/TreeViewState.svelte"; import TreeViewState from "../models/TreeViewState.svelte";
import type TreeViewNode from "../models/TreeViewNode.svelte"; import type TreeViewRequest from "../models/TreeViewRequest.svelte";
import { PathSelectedEvent } from "../events/PathSelectedEvent";
import { CaretDownOutline, CaretRightOutline } from "flowbite-svelte-icons";
let { let {
file_id, file_id,
root, root,
pathSelectedHandler = $bindable(), pathSelectedHandler,
active = true, active,
details,
}: { }: {
file_id: string; file_id: string;
root: TreeViewNode; root: TreeViewRequest;
pathSelectedHandler: any; pathSelectedHandler: any;
active: boolean; active: boolean;
details: boolean;
} = $props(); } = $props();
let h = $state(100); let h = $state(100);
let treeState = $state(new TreeViewState(file_id, root)); let states: TreeViewState[] = $state([]);
let treeState: TreeViewState | undefined = $derived(
states.find((state) => state.file_id === file_id),
);
if (active) {
loadTreeView();
}
function loadTreeView() {
if (!treeState) {
let newState = new TreeViewState(file_id, root);
newState.loadTreeView();
states.push(newState);
}
}
function select() {
loadTreeView();
pathSelectedHandler(new PathSelectedEvent(file_id, [root.key]));
}
</script> </script>
<div bind:clientHeight={h} class="full-container"> <ul id="myUL">
<div class="overflow-auto" style="height: {h}px"> {#if root}
<ul id="myUL">
{#if treeState.view}
<li> <li>
<button
class="item hover:bg-forge-sec w-full"
onmouseenter={() => loadTreeView()}
onclick={() => select()}
>
{#if active && treeState?.view}
<span class="caret"><CaretDownOutline /></span>
{:else}
<span class="caret"><CaretRightOutline /></span>
{/if}
<div class="item"> <div class="item">
<PrimitiveIcon ptype={"Dictionary"} /> <PrimitiveIcon ptype={"Dictionary"} />
{"Trailer "} {root.key}
</div> </div>
{#if active} </button>
{#if active && treeState?.view}
<TreeNode <TreeNode
{file_id}
bind:pathSelectedHandler bind:pathSelectedHandler
parent_view={treeState.view} parent_view={treeState.view}
parent_path={treeState.view.getPath()} parent_path={treeState.view.getPath()}
{treeState} {treeState}
{details}
></TreeNode> ></TreeNode>
{/if} {/if}
</li> </li>
{/if} {/if}
</ul> </ul>
</div>
getTrace
</div>
getPath
<style lang="postcss"> <style lang="postcss">
.item { .item {
@ -55,4 +87,10 @@ getPath
#myUL { #myUL {
list-style-type: none; list-style-type: none;
} }
/* Style the caret/arrow */
.caret {
@apply text-forge-sec;
cursor: pointer;
user-select: none; /* Prevent text selection */
}
</style> </style>

View File

@ -1,9 +1,9 @@
export class PathSelectedEvent extends CustomEvent<{ path: string[] }> { export class PathSelectedEvent extends CustomEvent<{ file_id: string, path: string[] }> {
static readonly eventName = 'pathselected'; static readonly eventName = 'pathselected';
constructor(path: string[]) { constructor(file_id: string, path: string[]) {
super(PathSelectedEvent.eventName, { super(PathSelectedEvent.eventName, {
detail: { path }, detail: { file_id, path },
bubbles: true bubbles: true
}); });
} }

View File

@ -2,8 +2,10 @@ import type PdfFile from "./PdfFile";
import type XRefEntry from "./XRefEntry"; import type XRefEntry from "./XRefEntry";
import Primitive from "./Primitive.svelte"; import Primitive from "./Primitive.svelte";
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import TreeViewNode from "./TreeViewNode.svelte"; import TreeViewRequest from "./TreeViewRequest.svelte";
import type XRefTable from "./XRefTable"; import type XRefTable from "./XRefTable";
import TreeViewState from "./TreeViewState.svelte";
import type { PathSelectedEvent } from "../events/PathSelectedEvent";
export default class FileViewState { export default class FileViewState {
@ -13,7 +15,6 @@ export default class FileViewState {
public highlightedPrim: Primitive | undefined = $state(); public highlightedPrim: Primitive | undefined = $state();
public xref_entries: XRefEntry[] = $state([]); public xref_entries: XRefEntry[] = $state([]);
constructor(file: PdfFile) { constructor(file: PdfFile) {
this.file = file; this.file = file;
@ -53,12 +54,13 @@ export default class FileViewState {
.catch(err => console.error(err)); .catch(err => console.error(err));
} }
public selectPathHandler(event: PathSelectedEvent) {
console.log("Selecting path", event.detail);
public getTreeRoot() { if (event.detail.file_id !== this.file.id) {
return this.treeState; return;
}
this.selectPath(event.detail.path);
} }
public getMergedPath() { public getMergedPath() {
return this.mergePaths(this.path); return this.mergePaths(this.path);
@ -96,7 +98,7 @@ export default class FileViewState {
return "/"; return "/";
} }
if (paths[0] === "/") { if (paths[0] === "/") {
return "/" + paths.slice(1, paths.length).join("/") return "Trailer/" + paths.slice(1, paths.length).join("/")
} }
return paths.join("/"); return paths.join("/");
} }

View File

@ -5,6 +5,7 @@ export default class Primitive {
public value: string; public value: string;
public children: Primitive[]; public children: Primitive[];
public trace: Trace[] = $state([]); public trace: Trace[] = $state([]);
public expanded: boolean = $state(false);
constructor( constructor(
p: Primitive p: Primitive
@ -18,6 +19,7 @@ export default class Primitive {
this.children.push(new Primitive(child)); this.children.push(new Primitive(child));
} }
this.trace = []; this.trace = [];
this.expanded = p.expanded;
for (let path of p.trace) { for (let path of p.trace) {
this.trace.push(path); this.trace.push(path);
} }
@ -54,6 +56,14 @@ export default class Primitive {
if (path.startsWith("Page")) { return path }; if (path.startsWith("Page")) { return path };
return +path; return +path;
} }
public withoutChildren(): Primitive {
return new Primitive({
...this,
children: [],
expanded: false,
});
}
} }
export interface Trace { export interface Trace {

View File

@ -1,33 +0,0 @@
export default class TreeViewNode {
public key: string;
public children: TreeViewNode[];
constructor(
key: string,
children: TreeViewNode[]
) {
this.key = key;
this.children = children;
}
static TRAILER = new TreeViewNode("Trailer", [new TreeViewNode("Root", [])]);
public getChild(key: string) {
return this.children.find(child => child.key === key);
}
public addChild(key: string) {
let child = new TreeViewNode(key, [])
this.children.push(child);
return child;
}
public clearChildren() {
this.children = [];
}
public removeChild(key: string) {
this.children = this.children.filter(child => child.key !== key);
}
}

View File

@ -0,0 +1,45 @@
export default class TreeViewRequest {
public key: string;
public children: TreeViewRequest[];
public displayName: string;
public active: boolean;
constructor(
key: string,
children: TreeViewRequest[]
) {
if (key.startsWith("Page")) {
this.displayName = "Page " + key.slice(4);
} else {
this.displayName = key;
}
this.key = key;
this.children = children;
this.active = true;
}
static TRAILER = new TreeViewRequest("Trailer", [new TreeViewRequest("Root", [])]);
public clone(): TreeViewRequest {
return new TreeViewRequest(this.key, this.children.map(child => child.clone()));
}
public getChild(key: string) {
return this.children.find(child => child.key === key);
}
public addChild(key: string) {
let child = new TreeViewRequest(key, [])
this.children.push(child);
return child;
}
public clearChildren() {
this.children = [];
}
public removeChild(key: string) {
this.children = this.children.filter(child => child.key !== key);
}
}

View File

@ -1,28 +1,39 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import Primitive from "./Primitive.svelte"; import Primitive from "./Primitive.svelte";
import TreeViewNode from "./TreeViewNode.svelte"; import TreeViewRequest from "./TreeViewRequest.svelte";
import type FileViewState from "./FileViewState.svelte"; import type { PathSelectedEvent } from "../events/PathSelectedEvent";
export default class TreeViewState { export default class TreeViewState {
public root: TreeViewNode = $state(new TreeViewNode("Trailer", [new TreeViewNode("Root", [])]));
private root: TreeViewRequest = $state(new TreeViewRequest("Trailer", [new TreeViewRequest("Root", [])]));
public view: Primitive | undefined = $state(); public view: Primitive | undefined = $state();
active: boolean = $state(false);
file_id: string; file_id: string;
constructor(file_id: string, root: TreeViewNode) { constructor(file_id: string, root: TreeViewRequest) {
console.log("Creating tree view state", file_id, root); console.log("Creating tree view state", file_id, root);
this.file_id = file_id; this.file_id = file_id;
this.root = root; this.root = root;
this.loadTreeView();
} }
public loadTreeView() { public loadTreeView() {
console.log("Loading tree view", this.root); this.setTreeViewRequest(this.getRoot());
}
public getRoot(): TreeViewRequest {
return this.root.clone();
}
public setTreeViewRequest(treeViewRequest: TreeViewRequest) {
console.log("Loading tree view", treeViewRequest);
invoke<Primitive>("get_prim_tree_by_path", { invoke<Primitive>("get_prim_tree_by_path", {
id: this.file_id, id: this.file_id,
path: this.root, path: treeViewRequest,
}) })
.then((result) => { .then((result) => {
this.view = new Primitive(result); this.view = new Primitive(result);
this.root = treeViewRequest;
}) })
.catch((err) => console.error(err)); .catch((err) => console.error(err));
} }
@ -32,16 +43,18 @@ export default class TreeViewState {
console.error("Empty path"); console.error("Empty path");
return; return;
} }
let node = this.root; console.log("Expanding tree", this.getRoot(), path);
let root = this.getRoot();
let node = root;
for (let key of path.slice(1, path.length)) { for (let key of path.slice(1, path.length)) {
let _node: TreeViewNode | undefined = node.getChild(key); let _node: TreeViewRequest | undefined = node.getChild(key);
if (_node) { if (_node) {
node = _node; node = _node;
} else { } else {
node = node.addChild(key); node = node.addChild(key);
} }
} }
this.loadTreeView(); this.setTreeViewRequest(root);
} }
public collapseTree(path: string[]) { public collapseTree(path: string[]) {
@ -50,16 +63,17 @@ export default class TreeViewState {
return; return;
} }
if (path.length == 1) { if (path.length == 1) {
this.root.clearChildren(); this.view = this.view?.withoutChildren();
return; return;
} }
let node: TreeViewNode | undefined = this.root; let root: TreeViewRequest = this.root;
let node: TreeViewRequest | undefined = root;
for (let key of path.slice(1, path.length - 1)) { for (let key of path.slice(1, path.length - 1)) {
node = node?.getChild(key); node = node?.getChild(key);
} }
if (node) { if (node) {
node.removeChild(path[path.length - 1]); node.removeChild(path[path.length - 1]);
this.setTreeViewRequest(root);
} }
this.loadTreeView();
} }
} }

View File

@ -27,7 +27,8 @@ export default {
active: 'rgb(44, 71, 73)', active: 'rgb(44, 71, 73)',
bound: 'rgba(0, 0, 0, 0.29)', bound: 'rgba(0, 0, 0, 0.29)',
text: '#dadada', text: '#dadada',
text_sec: '#6c6c6c', text_sec: '#838686',
text_hint: '#5adada',
} }
} }
} }