getting there
* stream view and page list working
This commit is contained in:
parent
7e71cb947b
commit
570d495faa
@ -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(
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
56
src/components/StreamEditor.svelte
Normal file
56
src/components/StreamEditor.svelte
Normal 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>
|
||||||
@ -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) */
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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("/");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
src/models/TreeViewRequest.svelte.ts
Normal file
45
src/models/TreeViewRequest.svelte.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user