updates working
This commit is contained in:
parent
f484c9b54c
commit
4e9e01bf94
@ -1269,7 +1269,13 @@ impl ObjectWrite for Content {
|
|||||||
let obj = self.parts[0].to_primitive(update)?;
|
let obj = self.parts[0].to_primitive(update)?;
|
||||||
update.create(obj)?.to_primitive(update)
|
update.create(obj)?.to_primitive(update)
|
||||||
} else {
|
} else {
|
||||||
self.parts.to_primitive(update)
|
let mut arr = vec![];
|
||||||
|
for part in &self.parts {
|
||||||
|
let pdf_stream = part.to_primitive(update)?;
|
||||||
|
let rc_ref = update.create(pdf_stream)?;
|
||||||
|
arr.push(rc_ref.to_primitive(update)?);
|
||||||
|
}
|
||||||
|
Ok(Primitive::Array(arr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -377,11 +377,12 @@ where
|
|||||||
let r = match self.refs.get(old.id)? {
|
let r = match self.refs.get(old.id)? {
|
||||||
XRef::Free { .. } => panic!(),
|
XRef::Free { .. } => panic!(),
|
||||||
XRef::Raw { gen_nr, .. } => PlainRef { id: old.id, gen: gen_nr },
|
XRef::Raw { gen_nr, .. } => PlainRef { id: old.id, gen: gen_nr },
|
||||||
XRef::Stream { .. } => return self.create(obj),
|
XRef::Stream { .. } => PlainRef { id: old.id, gen: 0 },
|
||||||
XRef::Promised => PlainRef { id: old.id, gen: 0 },
|
XRef::Promised => PlainRef { id: old.id, gen: 0 },
|
||||||
XRef::Invalid => panic!()
|
XRef::Invalid => panic!()
|
||||||
};
|
};
|
||||||
let primitive = obj.to_primitive(self)?;
|
let primitive = obj.to_primitive(self)?;
|
||||||
|
|
||||||
match self.changes.entry(old.id) {
|
match self.changes.entry(old.id) {
|
||||||
Entry::Vacant(e) => {
|
Entry::Vacant(e) => {
|
||||||
e.insert((primitive, r.gen));
|
e.insert((primitive, r.gen));
|
||||||
@ -499,6 +500,7 @@ where
|
|||||||
fn fulfill<T: ObjectWrite>(&mut self, promise: PromisedRef<T>, obj: T) -> Result<RcRef<T>> {
|
fn fulfill<T: ObjectWrite>(&mut self, promise: PromisedRef<T>, obj: T) -> Result<RcRef<T>> {
|
||||||
self.storage.fulfill(promise, obj)
|
self.storage.fulfill(promise, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<OC, SC, L> File<Vec<u8>, OC, SC, L>
|
impl<OC, SC, L> File<Vec<u8>, OC, SC, L>
|
||||||
@ -660,6 +662,14 @@ where
|
|||||||
pub fn log(&self) -> &L {
|
pub fn log(&self) -> &L {
|
||||||
&self.storage.log
|
&self.storage.log
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn changes(&self) -> &HashMap<ObjNr, (Primitive, GenNr)> {
|
||||||
|
&self.storage.changes
|
||||||
|
}
|
||||||
|
pub fn clear_changes(&mut self) {
|
||||||
|
self.storage.changes.clear();
|
||||||
|
self.storage.refs.remove_promised();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Object, ObjectWrite, DataSize)]
|
#[derive(Object, ObjectWrite, DataSize)]
|
||||||
|
|||||||
@ -93,6 +93,18 @@ impl XRefTable {
|
|||||||
pub fn num_entries(&self) -> usize {
|
pub fn num_entries(&self) -> usize {
|
||||||
self.entries.len()
|
self.entries.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_promised(&mut self) {
|
||||||
|
let mut new_entries = vec![];
|
||||||
|
for entry in self.entries.iter() {
|
||||||
|
if let XRef::Promised = entry {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
new_entries.push(entry.clone());
|
||||||
|
}
|
||||||
|
self.entries = new_entries;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn max_field_widths(&self) -> (u64, u64) {
|
pub fn max_field_widths(&self) -> (u64, u64) {
|
||||||
let mut max_a = 0;
|
let mut max_a = 0;
|
||||||
let mut max_b = 0;
|
let mut max_b = 0;
|
||||||
|
|||||||
1
src-tauri/Cargo.lock
generated
1
src-tauri/Cargo.lock
generated
@ -3689,6 +3689,7 @@ dependencies = [
|
|||||||
"image 0.25.5",
|
"image 0.25.5",
|
||||||
"indexmap 1.9.3",
|
"indexmap 1.9.3",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"log",
|
||||||
"pathfinder_geometry",
|
"pathfinder_geometry",
|
||||||
"pathfinder_rasterize",
|
"pathfinder_rasterize",
|
||||||
"pdf",
|
"pdf",
|
||||||
|
|||||||
@ -35,3 +35,4 @@ pathfinder_rasterize = { git = "https://github.com/s3bk/pathfinder_rasterizer" }
|
|||||||
pathfinder_geometry = { git = "https://github.com/servo/pathfinder" }
|
pathfinder_geometry = { git = "https://github.com/servo/pathfinder" }
|
||||||
indexmap = "1.9.3"
|
indexmap = "1.9.3"
|
||||||
tauri-plugin-websocket = "2"
|
tauri-plugin-websocket = "2"
|
||||||
|
log = "0.4.25"
|
||||||
|
|||||||
@ -11,18 +11,20 @@ use crate::render::Renderer;
|
|||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use pdf::file::{File, FileOptions, NoLog, ObjectCache, StreamCache};
|
use pdf::file::{File, FileOptions, NoLog, ObjectCache, StreamCache};
|
||||||
use pdf::object::{Object, PlainRef, Stream, Updater};
|
use pdf::object::{Object, ObjectWrite, Page, PageRc, PlainRef, Stream, Updater};
|
||||||
use pdf::primitive::{Dictionary, Primitive};
|
use pdf::primitive::{Dictionary, Primitive};
|
||||||
use pdf::xref::XRef;
|
use pdf::xref::XRef;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use retrieval::{get_prim_by_path_with_file, get_stream_data_by_path_with_file};
|
use retrieval::{get_prim_by_path_with_file, get_stream_data_by_path_with_file};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
use std::ops::Deref;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use tauri::ipc::{InvokeResponseBody, Response};
|
use tauri::ipc::{InvokeResponseBody, Response};
|
||||||
use tauri::{Manager, State};
|
use tauri::{Manager, State};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
use pdf::backend::Backend;
|
||||||
|
|
||||||
type CosFile = File<Vec<u8>, ObjectCache, StreamCache, NoLog>;
|
type CosFile = File<Vec<u8>, ObjectCache, StreamCache, NoLog>;
|
||||||
#[derive(Serialize, Debug, Clone)]
|
#[derive(Serialize, Debug, Clone)]
|
||||||
@ -74,11 +76,27 @@ pub struct PrimitiveTreeView {
|
|||||||
#[derive(Serialize, Debug, Clone)]
|
#[derive(Serialize, Debug, Clone)]
|
||||||
pub struct PathTrace {
|
pub struct PathTrace {
|
||||||
pub key: String,
|
pub key: String,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
|
pub last_ref: Option<PlainRef>,
|
||||||
pub last_jump: String,
|
pub last_jump: String,
|
||||||
}
|
}
|
||||||
impl PathTrace {
|
impl PathTrace {
|
||||||
fn new(key: String, last_jump: String) -> PathTrace {
|
fn new(key: String, last_ref: Option<PlainRef>) -> PathTrace {
|
||||||
PathTrace { key, last_jump }
|
PathTrace {
|
||||||
|
key,
|
||||||
|
last_jump: last_ref
|
||||||
|
.map(|r| r.id.to_string())
|
||||||
|
.unwrap_or("Trailer".to_string()),
|
||||||
|
last_ref: last_ref,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trailer() -> PathTrace {
|
||||||
|
PathTrace {
|
||||||
|
key: "Trailer".to_string(),
|
||||||
|
last_ref: None,
|
||||||
|
last_jump: "Trailer".to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +133,22 @@ fn get_all_files(session: State<Session>) -> Vec<PdfFile> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn get_changes(id: &str, session: State<Session>) -> Result<Vec<PrimitiveModel>, String> {
|
||||||
|
let lock = session.get_file(id)?;
|
||||||
|
let file = lock.read().unwrap();
|
||||||
|
Ok(file.cos_file.changes().iter()
|
||||||
|
.map(|(obj, (prim, gen))| PrimitiveModel::from_primitive_until_refs(prim, PathTrace::new(obj.to_string(), Some(PlainRef{id: *obj, gen: *gen}))))
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn clear_changes(id: &str, session: State<Session>) -> Result<(), String> {
|
||||||
|
let lock = session.get_file(id)?;
|
||||||
|
let mut file = lock.write().unwrap();
|
||||||
|
file.cos_file.clear_changes();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_all_file_ids(session: State<Session>) -> Vec<String> {
|
fn get_all_file_ids(session: State<Session>) -> Vec<String> {
|
||||||
session.get_all_file_ids()
|
session.get_all_file_ids()
|
||||||
@ -181,21 +215,16 @@ fn to_pdf_file(path: &str, file: &CosFile) -> Result<PdfFile, String> {
|
|||||||
Ok(pdf_file)
|
Ok(pdf_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
fn read_contents(page: &Page, resolver: &impl Resolve) -> Result<ContentsModel, String> {
|
||||||
fn get_contents(id: &str, path: &str, session: State<Session>) -> Result<ContentsModel, String> {
|
if let Some(ref contents) = page.contents {
|
||||||
let lock = session.get_file(id)?;
|
|
||||||
let file = lock.read().unwrap();
|
|
||||||
|
|
||||||
let (_, page_prim, _) = get_prim_by_path_with_file(path, &file.cos_file)?;
|
|
||||||
let resolver = file.cos_file.resolver();
|
|
||||||
|
|
||||||
let page = t!(pdf::object::Page::from_primitive(page_prim, &resolver));
|
|
||||||
if let Some(contents) = page.contents {
|
|
||||||
let mut parts = vec![];
|
let mut parts = vec![];
|
||||||
for part in contents.parts {
|
for part in &contents.parts {
|
||||||
let data = &t!(part.data(&resolver));
|
let data = &t!(part.data(resolver));
|
||||||
let ops = t!(pdf::content::parse_ops(&data, &resolver));
|
let string = String::from_utf8_lossy(data);
|
||||||
let part = t!(pdf::content::display_ops(&ops));
|
let part = string
|
||||||
|
.split("\n")
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
parts.push(part);
|
parts.push(part);
|
||||||
}
|
}
|
||||||
return Ok(ContentsModel { parts });
|
return Ok(ContentsModel { parts });
|
||||||
@ -204,10 +233,53 @@ fn get_contents(id: &str, path: &str, session: State<Session>) -> Result<Content
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn get_stream_data_as_string(
|
async fn get_contents(id: &str, path: &str, session: State<'_, Session>) -> Result<ContentsModel, String> {
|
||||||
|
let lock = session.get_file(id)?;
|
||||||
|
let file = lock.read().unwrap();
|
||||||
|
|
||||||
|
let (_, page_prim, _) = get_prim_by_path_with_file(path, &file.cos_file)?;
|
||||||
|
let resolver = file.cos_file.resolver();
|
||||||
|
|
||||||
|
let page = t!(pdf::object::Page::from_primitive(page_prim, &resolver));
|
||||||
|
read_contents(&page, &resolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn update_contents(
|
||||||
id: &str,
|
id: &str,
|
||||||
path: &str,
|
path: &str,
|
||||||
session: State<Session>,
|
session: State<'_, Session>,
|
||||||
|
contents: ContentsModel,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let lock = session.get_file(id)?;
|
||||||
|
let mut file = lock.write().unwrap();
|
||||||
|
update_contents_with_file(path, contents, &mut file.cos_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_contents_with_file(path: &str, contents: ContentsModel, file: &mut CosFile) -> Result<(), String> {
|
||||||
|
let mut parts = vec![];
|
||||||
|
for part in contents.parts {
|
||||||
|
let stream_ref = Stream::new((), part.join("\n").as_bytes());
|
||||||
|
parts.push(stream_ref);
|
||||||
|
}
|
||||||
|
let new_contents = pdf::content::Content{parts};
|
||||||
|
|
||||||
|
if let Some(Step::Page(num)) = Step::parse(path).pop_front() {
|
||||||
|
let page_rc: PageRc = t!(file.get_page(num - 1));
|
||||||
|
let page_ref = page_rc.get_ref().get_inner().clone();
|
||||||
|
let mut page = page_rc.deref().clone();
|
||||||
|
page.contents = Some(new_contents);
|
||||||
|
let _ = t!(file.update(page_ref, page));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Err(String::from("Path does not lead to a page!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn get_stream_data_as_string(
|
||||||
|
id: &str,
|
||||||
|
path: &str,
|
||||||
|
session: State<'_, Session>,
|
||||||
) -> Result<String, String> {
|
) -> Result<String, String> {
|
||||||
let lock = session.get_file(id)?;
|
let lock = session.get_file(id)?;
|
||||||
let file = lock.read().unwrap();
|
let file = lock.read().unwrap();
|
||||||
@ -226,8 +298,7 @@ async fn update_stream_data_as_string(
|
|||||||
let mut file = lock.write().unwrap();
|
let mut file = lock.write().unwrap();
|
||||||
let (_, _, trace) = get_prim_by_path_with_file(path, &file.cos_file)?;
|
let (_, _, trace) = get_prim_by_path_with_file(path, &file.cos_file)?;
|
||||||
let i = trace.len() - 1;
|
let i = trace.len() - 1;
|
||||||
let id = t!(trace[i].last_jump.parse::<u64>());
|
let plain_ref = trace[i].last_ref.expect("No trace to update");
|
||||||
let plain_ref = PlainRef { id, gen: 0 };
|
|
||||||
let stream = Stream::new((), new_data.as_bytes());
|
let stream = Stream::new((), new_data.as_bytes());
|
||||||
let _ = t!(file.cos_file.update(plain_ref, stream));
|
let _ = t!(file.cos_file.update(plain_ref, stream));
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -398,18 +469,19 @@ fn expand(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let step = node.step()?;
|
let step = node.step()?;
|
||||||
let prim = retrieval::resolve_step(parent, &step)?;
|
if let Ok(prim) = retrieval::resolve_step(parent, &step) {
|
||||||
if let Primitive::Reference(x_ref) = prim {
|
if let Primitive::Reference(x_ref) = prim {
|
||||||
let jump = retrieval::resolve_xref(x_ref.id, file)?;
|
let jump = retrieval::resolve_xref(x_ref.id, file)?;
|
||||||
let mut jump_trace = parent_model.trace.clone();
|
let mut jump_trace = parent_model.trace.clone();
|
||||||
jump_trace.push(PathTrace::new(step.get_key(), x_ref.id.to_string()));
|
jump_trace.push(PathTrace::new(step.get_key(), Some(x_ref.clone())));
|
||||||
let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
|
let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
|
||||||
to_expand.add_children(&jump, &jump_trace);
|
to_expand.add_children(&jump, &jump_trace);
|
||||||
expand_children(node, file, &jump, &mut to_expand)?;
|
expand_children(node, file, &jump, &mut to_expand)?;
|
||||||
} else {
|
} else {
|
||||||
let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
|
let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
|
||||||
to_expand.add_children(prim, &to_expand.trace.clone());
|
to_expand.add_children(prim, &to_expand.trace.clone());
|
||||||
expand_children(node, file, prim, &mut to_expand)?;
|
expand_children(node, file, prim, &mut to_expand)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -491,7 +563,7 @@ impl Step {
|
|||||||
|
|
||||||
fn append_path(key: String, path: &Vec<PathTrace>) -> Vec<PathTrace> {
|
fn append_path(key: String, path: &Vec<PathTrace>) -> Vec<PathTrace> {
|
||||||
let mut new_path = path.clone();
|
let mut new_path = path.clone();
|
||||||
let last_jump = new_path.last().unwrap().last_jump.clone();
|
let last_jump = new_path.last().unwrap().last_ref.clone();
|
||||||
new_path.push(PathTrace::new(key, last_jump));
|
new_path.push(PathTrace::new(key, last_jump));
|
||||||
new_path
|
new_path
|
||||||
}
|
}
|
||||||
@ -611,6 +683,17 @@ impl PrimitiveModel {
|
|||||||
model
|
model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_primitive_until_refs(primitive: &Primitive, trace: PathTrace) -> PrimitiveModel {
|
||||||
|
let mut parent_model = PrimitiveModel::from_primitive(
|
||||||
|
trace.key.clone(),
|
||||||
|
primitive,
|
||||||
|
vec![trace.clone()],
|
||||||
|
);
|
||||||
|
let mut model = parent_model;
|
||||||
|
model.add_children(primitive, &vec!(trace));
|
||||||
|
model
|
||||||
|
}
|
||||||
|
|
||||||
fn add_children(&mut self, primitive: &Primitive, path: &Vec<PathTrace>) {
|
fn add_children(&mut self, primitive: &Primitive, path: &Vec<PathTrace>) {
|
||||||
self.expanded = true;
|
self.expanded = true;
|
||||||
match primitive {
|
match primitive {
|
||||||
@ -837,10 +920,13 @@ pub fn run() {
|
|||||||
get_prim_tree_by_path,
|
get_prim_tree_by_path,
|
||||||
get_xref_table,
|
get_xref_table,
|
||||||
get_contents,
|
get_contents,
|
||||||
|
update_contents,
|
||||||
get_stream_data_as_string,
|
get_stream_data_as_string,
|
||||||
|
update_stream_data_as_string,
|
||||||
get_stream_data_as_image,
|
get_stream_data_as_image,
|
||||||
get_page_by_num,
|
get_page_by_num,
|
||||||
update_stream_data_as_string
|
get_changes,
|
||||||
|
clear_changes
|
||||||
])
|
])
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
|
|||||||
@ -34,7 +34,7 @@ pub fn get_prim_by_steps_with_file(
|
|||||||
let step = steps.pop_front().unwrap();
|
let step = steps.pop_front().unwrap();
|
||||||
let (mut parent, trace) = resolve_parent(step.clone(), file)?;
|
let (mut parent, trace) = resolve_parent(step.clone(), file)?;
|
||||||
|
|
||||||
let mut last_jump = trace.last_jump.clone();
|
let mut last_jump: Option<PlainRef> = trace.last_ref.clone();
|
||||||
let mut trace = vec![trace];
|
let mut trace = vec![trace];
|
||||||
|
|
||||||
let mut current_prim = &parent;
|
let mut current_prim = &parent;
|
||||||
@ -43,7 +43,7 @@ pub fn get_prim_by_steps_with_file(
|
|||||||
|
|
||||||
current_prim = resolve_step(¤t_prim, &step)?;
|
current_prim = resolve_step(¤t_prim, &step)?;
|
||||||
if let Primitive::Reference(xref) = current_prim {
|
if let Primitive::Reference(xref) = current_prim {
|
||||||
last_jump = xref.id.to_string();
|
last_jump = Some(xref.clone());
|
||||||
parent = resolve_p_ref(xref.clone(), file)?;
|
parent = resolve_p_ref(xref.clone(), file)?;
|
||||||
current_prim = &parent;
|
current_prim = &parent;
|
||||||
}
|
}
|
||||||
@ -53,13 +53,18 @@ pub fn get_prim_by_steps_with_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_parent(step: Step, file: &CosFile) -> Result<(Primitive, PathTrace), String> {
|
pub fn resolve_parent(step: Step, file: &CosFile) -> Result<(Primitive, PathTrace), String> {
|
||||||
let parent = match step {
|
match step {
|
||||||
Step::Page(page_num) => return retrieve_page(page_num, file),
|
Step::Page(page_num) => retrieve_page(page_num, file),
|
||||||
Step::Number(obj_num) => resolve_xref(obj_num, file)?,
|
Step::Number(obj_num) => {
|
||||||
Step::Trailer => retrieve_trailer(file),
|
let parent = resolve_xref(obj_num, file)?;
|
||||||
_ => return Err(String::from(format!("{:?} is not a valid path!", step))),
|
Ok((parent, PathTrace::new(step.get_key(), Some(PlainRef {id: obj_num, gen: 0}))))
|
||||||
};
|
},
|
||||||
Ok((parent, PathTrace::new(step.get_key(), step.get_key())))
|
Step::Trailer => {
|
||||||
|
let parent = retrieve_trailer(file);
|
||||||
|
Ok((parent, PathTrace::trailer()))
|
||||||
|
},
|
||||||
|
_ => Err(String::from(format!("{:?} is not a valid path!", step))),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve_step<'a>(current_prim: &'a Primitive, step: &Step) -> Result<&'a Primitive, String> {
|
pub fn resolve_step<'a>(current_prim: &'a Primitive, step: &Step) -> Result<&'a Primitive, String> {
|
||||||
@ -125,7 +130,7 @@ pub fn retrieve_page(page_num: u32, file: &CosFile) -> Result<(Primitive, PathTr
|
|||||||
let p_ref = page_rc.get_ref().get_inner();
|
let p_ref = page_rc.get_ref().get_inner();
|
||||||
Ok((
|
Ok((
|
||||||
resolve_p_ref(p_ref, file)?,
|
resolve_p_ref(p_ref, file)?,
|
||||||
PathTrace::new(format!("Page{}", page_num), p_ref.id.to_string()),
|
PathTrace::new(format!("Page{}", page_num), Some(p_ref)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,8 @@ 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, TreeViewRequest,
|
get_xref_table_model_with_file, to_pdf_file, update_contents, update_contents_with_file,
|
||||||
|
ContentsModel, PathTrace, PrimitiveModel, TreeViewRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
use image::codecs::jpeg::JpegEncoder;
|
use image::codecs::jpeg::JpegEncoder;
|
||||||
@ -155,7 +156,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let trail_model = PrimitiveModel::from_primitive_with_children(
|
let trail_model = PrimitiveModel::from_primitive_with_children(
|
||||||
&trail,
|
&trail,
|
||||||
vec![PathTrace::new("Trailer".to_string(), "Trailer".to_string())],
|
vec![PathTrace::new("Trailer".to_string(), None)],
|
||||||
);
|
);
|
||||||
print_node(trail_model, 5);
|
print_node(trail_model, 5);
|
||||||
println!("{:?}", file.trailer.info_dict);
|
println!("{:?}", file.trailer.info_dict);
|
||||||
@ -264,7 +265,10 @@ mod tests {
|
|||||||
let mut rendered_pages: Vec<DynamicImage> = vec![];
|
let mut rendered_pages: Vec<DynamicImage> = vec![];
|
||||||
timed!(
|
timed!(
|
||||||
for i in 0..1 {
|
for i in 0..1 {
|
||||||
let img = timed!(renderer.render(i), format!("render page {}", i));
|
let img = timed!(
|
||||||
|
renderer.render(&file.get_page(i).unwrap()),
|
||||||
|
format!("render page {}", i)
|
||||||
|
);
|
||||||
rendered_pages.push(img.unwrap())
|
rendered_pages.push(img.unwrap())
|
||||||
},
|
},
|
||||||
"rendering some pages"
|
"rendering some pages"
|
||||||
@ -276,4 +280,21 @@ mod tests {
|
|||||||
"saving images"
|
"saving images"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_update_contents() {
|
||||||
|
let mut file = timed!(
|
||||||
|
FileOptions::cached().open(PDF_SPEC_PATH).unwrap(),
|
||||||
|
"Loading file"
|
||||||
|
);
|
||||||
|
let string = timed!(update_contents_with_file(
|
||||||
|
"Page1",
|
||||||
|
ContentsModel {
|
||||||
|
parts: vec![vec!["q".to_string(), "Q".to_string()]],
|
||||||
|
},
|
||||||
|
&mut file,
|
||||||
|
)
|
||||||
|
.unwrap(), "Updating contents");
|
||||||
|
|
||||||
|
println!("{}", string);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,7 +97,6 @@
|
|||||||
closeTab={closeFile}
|
closeTab={closeFile}
|
||||||
openTab={upload}
|
openTab={upload}
|
||||||
selectTab={(state) => (fState = state)}
|
selectTab={(state) => (fState = state)}
|
||||||
reload={() => fState?.reload()}
|
|
||||||
></TitleBar>
|
></TitleBar>
|
||||||
</div>
|
</div>
|
||||||
<div class="fileview_container" style="height: {fileViewHeight}px">
|
<div class="fileview_container" style="height: {fileViewHeight}px">
|
||||||
|
|||||||
49
src/components/ChangesModal.svelte
Normal file
49
src/components/ChangesModal.svelte
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import type FileViewState from "../models/FileViewState.svelte";
|
||||||
|
import {
|
||||||
|
TrashBinSolid
|
||||||
|
} from "flowbite-svelte-icons";
|
||||||
|
|
||||||
|
let {fState}: { fState: FileViewState } = $props();
|
||||||
|
let changes = $derived(fState.changes);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="border-l border-r border-forge-sec bg-forge-sec flex flex-row" style="width: 100%; height: 24px">
|
||||||
|
<div class="text-sm p-1">
|
||||||
|
Changes:
|
||||||
|
</div>
|
||||||
|
<button class="clear" onclick={ () => fState.clearChanges()}>
|
||||||
|
<div class="img">
|
||||||
|
<TrashBinSolid size="sm"/>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
{#each changes as change}
|
||||||
|
<div class="text-xs flex flex-row">
|
||||||
|
<p class="whitespace-nowrap">{change.key + ":"}</p>
|
||||||
|
 
|
||||||
|
<p>{change.ptype}</p> | 
|
||||||
|
<p>{change.sub_type}</p> | 
|
||||||
|
<p>{change.value}</p>
|
||||||
|
</div>
|
||||||
|
<div class="bg-forge-bound mt-1 mb-1" style="height: 1px; width: 100%"></div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="postcss">
|
||||||
|
|
||||||
|
.clear {
|
||||||
|
@apply hover:bg-forge-acc h-[24px] w-[24px] rounded-2xl;
|
||||||
|
position: fixed;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img {
|
||||||
|
position: fixed;
|
||||||
|
right: 4px;
|
||||||
|
top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,59 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import {invoke} from "@tauri-apps/api/core";
|
|
||||||
import type ContentModel from "../models/ContentModel.svelte";
|
|
||||||
import type FileViewState from "../models/FileViewState.svelte";
|
|
||||||
import StreamEditor from "./StreamEditor.svelte";
|
|
||||||
|
|
||||||
let {fState, height}: { fState: FileViewState; height: number } =
|
|
||||||
$props();
|
|
||||||
let h = $derived(height);
|
|
||||||
let path = $derived(fState.container_prim?.getFirstJump()?.toString());
|
|
||||||
let id = $derived(fState.file.id);
|
|
||||||
let contentsModel: ContentModel | undefined = $state(undefined);
|
|
||||||
let contents: string | undefined = $derived(mapToString(contentsModel));
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
loadContents(path, id);
|
|
||||||
});
|
|
||||||
|
|
||||||
function loadContents(path: string | undefined, id: string) {
|
|
||||||
if (!path || !id) return;
|
|
||||||
|
|
||||||
invoke<ContentModel>("get_contents", {id, path})
|
|
||||||
.then((result) => {
|
|
||||||
contentsModel = result;
|
|
||||||
|
|
||||||
})
|
|
||||||
.catch((err) => console.error(err));
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapToString(model: ContentModel | undefined) {
|
|
||||||
if (!model) return "";
|
|
||||||
|
|
||||||
let text = "";
|
|
||||||
if (model.parts.length > 1) {
|
|
||||||
let i = 0;
|
|
||||||
for (let part of model.parts) {
|
|
||||||
text +=
|
|
||||||
"%----------------% Contents[" +
|
|
||||||
i +
|
|
||||||
"] %--------------%\n\n";
|
|
||||||
for (let line of part) {
|
|
||||||
text += " " + line + "\n";
|
|
||||||
}
|
|
||||||
text +=
|
|
||||||
"\n%-------------------% EOF %-------------------%\n\n";
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text = model.parts[0].join("\n");
|
|
||||||
}
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style lang="postcss">
|
|
||||||
</style>
|
|
||||||
@ -8,6 +8,7 @@
|
|||||||
import type {PathSelectedEvent} from "../events/PathSelectedEvent";
|
import type {PathSelectedEvent} from "../events/PathSelectedEvent";
|
||||||
import {onMount} from "svelte";
|
import {onMount} from "svelte";
|
||||||
import NotificationModal from "./NotificationModal.svelte";
|
import NotificationModal from "./NotificationModal.svelte";
|
||||||
|
import ChangesModal from "./ChangesModal.svelte";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
fState,
|
fState,
|
||||||
@ -18,20 +19,26 @@
|
|||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let width: number = $state(0);
|
let width: number = $state(0);
|
||||||
let modalWidth = $derived(fState.xRefShowing || fState.notificationsShowing ? 281 : 0);
|
let modalWidth = $derived(fState.xRefShowing || fState.notificationsShowing || fState.changesShowing ? 281 : 0);
|
||||||
let splitPanesWidth: number = $derived(width - modalWidth);
|
let splitPanesWidth: number = $derived(width - modalWidth);
|
||||||
|
|
||||||
function handleKeydown(event: KeyboardEvent) {
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
// Check for "Alt + Left Arrow"
|
// Check for "Alt + Left Arrow"
|
||||||
if (event.altKey && event.key === "ArrowLeft") {
|
if (event.altKey && event.key === "ArrowLeft") {
|
||||||
fState.popPath();
|
fState.back();
|
||||||
|
}
|
||||||
|
if (event.altKey && event.key === "ArrowRight") {
|
||||||
|
fState.forward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleMouseButton(event: MouseEvent) {
|
function handleMouseButton(event: MouseEvent) {
|
||||||
// Check for mouse back button (button 4)
|
// Check for mouse back button (button 4)
|
||||||
if (event.button === 3) {
|
if (event.button === 3) {
|
||||||
fState.popPath();
|
fState.back();
|
||||||
|
}
|
||||||
|
if (event.button === 4) {
|
||||||
|
fState.forward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,6 +93,11 @@
|
|||||||
<NotificationModal {fState}></NotificationModal>
|
<NotificationModal {fState}></NotificationModal>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if fState.changesShowing}
|
||||||
|
<div class="xref-modal" class:visible={fState.changesShowing}>
|
||||||
|
<ChangesModal {fState}></ChangesModal>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
|||||||
@ -4,16 +4,18 @@
|
|||||||
import {PageViewState} from "../models/PageViewState.svelte";
|
import {PageViewState} from "../models/PageViewState.svelte";
|
||||||
import type FileViewState from "../models/FileViewState.svelte";
|
import type FileViewState from "../models/FileViewState.svelte";
|
||||||
import StreamEditor from "./StreamEditor.svelte";
|
import StreamEditor from "./StreamEditor.svelte";
|
||||||
|
import ContentModel from "../models/ContentModel.svelte";
|
||||||
|
|
||||||
let {fState, height}: {fState: FileViewState, height: number} = $props();
|
let {fState, height}: {fState: FileViewState, height: number} = $props();
|
||||||
let state: PageViewState | undefined = $derived(fState.pageViewState);
|
let state: PageViewState | undefined = $derived(fState.pageViewState);
|
||||||
|
let contents: ContentModel | undefined = $derived(state?.contents)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{#if state}
|
{#if state}
|
||||||
<Splitpanes theme="forge-movable">
|
<Splitpanes theme="forge-movable">
|
||||||
<Pane minSize={1}>
|
<Pane minSize={1}>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
<StreamEditor save={state.handleSave} stream_data={state.contents.toDisplay()} height={height - 1}></StreamEditor>
|
<StreamEditor save={(newData) => state.handleSave(newData)} stream_data={contents ? contents.toDisplay() : ""} height={height - 1}></StreamEditor>
|
||||||
</div>
|
</div>
|
||||||
</Pane>
|
</Pane>
|
||||||
<Pane minSize={1}>
|
<Pane minSize={1}>
|
||||||
|
|||||||
@ -7,6 +7,10 @@
|
|||||||
PlusOutline,
|
PlusOutline,
|
||||||
FilePdfSolid,
|
FilePdfSolid,
|
||||||
ArrowsRepeatOutline,
|
ArrowsRepeatOutline,
|
||||||
|
DownloadOutline,
|
||||||
|
ArrowLeftOutline,
|
||||||
|
ArrowRightOutline,
|
||||||
|
AngleDownOutline
|
||||||
} from "flowbite-svelte-icons";
|
} from "flowbite-svelte-icons";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
import { homeDir } from "@tauri-apps/api/path";
|
import { homeDir } from "@tauri-apps/api/path";
|
||||||
@ -22,25 +26,25 @@
|
|||||||
selectTab,
|
selectTab,
|
||||||
closeTab,
|
closeTab,
|
||||||
openTab: newFile,
|
openTab: newFile,
|
||||||
reload
|
|
||||||
}: {
|
}: {
|
||||||
fStates: FileViewState[];
|
fStates: FileViewState[];
|
||||||
fState: FileViewState | undefined;
|
fState: FileViewState | undefined;
|
||||||
selectTab: (arg0: FileViewState) => void;
|
selectTab: (arg0: FileViewState) => void;
|
||||||
closeTab: (arg0: FileViewState) => void;
|
closeTab: (arg0: FileViewState) => void;
|
||||||
openTab: () => void;
|
openTab: () => void;
|
||||||
reload: () => void;
|
|
||||||
} = $props();
|
} = $props();
|
||||||
|
|
||||||
let dropdownVisible = $state(false);
|
let dropdownVisible = $state(false);
|
||||||
let dropdownEl: HTMLElement;
|
let dropdownEl: HTMLElement;
|
||||||
|
let historyVisible = $state(false);
|
||||||
|
let historyEl: HTMLElement;
|
||||||
|
|
||||||
async function getHome() {
|
async function getHome() {
|
||||||
home = await homeDir();
|
home = await homeDir();
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatFileName(name: string) {
|
function formatFileName(name: string) {
|
||||||
if (name.length < 20) {
|
if (name.length < 100) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
return name.substring(0, 17) + "...";
|
return name.substring(0, 17) + "...";
|
||||||
@ -84,6 +88,20 @@
|
|||||||
dropdownVisible = !dropdownVisible;
|
dropdownVisible = !dropdownVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleHistoryDropdown() {
|
||||||
|
historyVisible = !historyVisible;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFocusOutHistory(e: FocusEvent) {
|
||||||
|
const dropdown = historyEl;
|
||||||
|
if (!dropdown) return;
|
||||||
|
|
||||||
|
const relatedTarget = e.relatedTarget as HTMLElement;
|
||||||
|
if (!dropdown.contains(relatedTarget)) {
|
||||||
|
historyVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleFocusOut(e: FocusEvent) {
|
function handleFocusOut(e: FocusEvent) {
|
||||||
const dropdown = dropdownEl;
|
const dropdown = dropdownEl;
|
||||||
if (!dropdown) return;
|
if (!dropdown) return;
|
||||||
@ -94,6 +112,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function handleSelectTab(state: FileViewState) {
|
function handleSelectTab(state: FileViewState) {
|
||||||
selectTab(state);
|
selectTab(state);
|
||||||
dropdownVisible = false;
|
dropdownVisible = false;
|
||||||
@ -117,10 +137,46 @@
|
|||||||
alt="PDF Forge Logo"
|
alt="PDF Forge Logo"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mx-4"></div>
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<button
|
||||||
|
onclick={() => fState?.back()}
|
||||||
|
class="nav-button"
|
||||||
|
id="titlebar-back"
|
||||||
|
class:possible={fState?.canBack}
|
||||||
|
>
|
||||||
|
<ArrowLeftOutline/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onfocusout={handleFocusOutHistory}
|
||||||
|
onclick={() => toggleHistoryDropdown()}
|
||||||
|
class="nav-button possible"
|
||||||
|
id="titlebar-forward"
|
||||||
|
>
|
||||||
|
<AngleDownOutline/>
|
||||||
|
</button>
|
||||||
|
{#if historyVisible && fState}
|
||||||
|
<div class="dropdown-menu" bind:this={historyEl}>
|
||||||
|
{#each fState.pathHistory.history as path, index}
|
||||||
|
<button class="dropdown-item new-tab active:bg-forge-sec" class:active={fState.pathHistory.currentIndex === index} onclick={() => fState.jump(index)}>
|
||||||
|
{path.join("/")}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
onclick={() => fState?.forward()}
|
||||||
|
class="nav-button"
|
||||||
|
id="titlebar-forward"
|
||||||
|
class:possible={fState?.canForward}
|
||||||
|
>
|
||||||
|
<ArrowRightOutline/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="file-selector" onfocusout={handleFocusOut}>
|
<div class="file-selector" onfocusout={handleFocusOut}>
|
||||||
<button class="file-dropdown-button" onclick={toggleDropdown}>
|
<button class="file-dropdown-button" onclick={toggleDropdown}>
|
||||||
<span
|
<FilePdfSolid size="sm" />
|
||||||
|
<span class="px-2"
|
||||||
>{fState
|
>{fState
|
||||||
? formatFileName(fState.file.name)
|
? formatFileName(fState.file.name)
|
||||||
: "Select File"}</span
|
: "Select File"}</span
|
||||||
@ -170,15 +226,25 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-row">
|
||||||
|
<button
|
||||||
|
onclick={() => fState?.reload()}
|
||||||
|
class="titlebar-button"
|
||||||
|
id="titlebar-reload"
|
||||||
|
>
|
||||||
|
<ArrowsRepeatOutline/>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onclick={() => fState?.save()}
|
||||||
|
class="titlebar-button"
|
||||||
|
id="titlebar-save"
|
||||||
|
>
|
||||||
|
<DownloadOutline/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="titlebar-button-group">
|
<div class="titlebar-button-group">
|
||||||
<button
|
|
||||||
onclick={reload}
|
|
||||||
class="titlebar-button"
|
|
||||||
id="titlebar-maximize"
|
|
||||||
>
|
|
||||||
<ArrowsRepeatOutline />
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
onclick={minimize}
|
onclick={minimize}
|
||||||
class="titlebar-button"
|
class="titlebar-button"
|
||||||
@ -200,6 +266,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
|
||||||
|
.nav-button.possible {
|
||||||
|
@apply hover:bg-forge-acc text-forge-text
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
@apply text-forge-text_sec rounded ;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
.titlebar {
|
.titlebar {
|
||||||
@apply border border-forge-bound;
|
@apply border border-forge-bound;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
@ -220,7 +302,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.titlebar-button {
|
.titlebar-button {
|
||||||
@apply text-forge-text hover:bg-forge-acc;
|
@apply text-forge-text hover:bg-forge-acc rounded;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -231,8 +313,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-selector {
|
.file-selector {
|
||||||
@apply relative ml-10;
|
@apply relative ml-4;
|
||||||
z-index: 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-dropdown-button {
|
.file-dropdown-button {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {ToolbarButton} from "flowbite-svelte";
|
import {ToolbarButton} from "flowbite-svelte";
|
||||||
import {ListOutline} from "flowbite-svelte-icons";
|
import {ListOutline, InfoCircleOutline, UndoOutline} from "flowbite-svelte-icons";
|
||||||
import type FileViewState from "../models/FileViewState.svelte";
|
import type FileViewState from "../models/FileViewState.svelte";
|
||||||
|
|
||||||
let {fState}: { fState: FileViewState | undefined } = $props()
|
let {fState}: { fState: FileViewState | undefined } = $props()
|
||||||
@ -10,6 +10,9 @@
|
|||||||
if (fState.notificationsShowing) {
|
if (fState.notificationsShowing) {
|
||||||
fState.notificationsShowing = false;
|
fState.notificationsShowing = false;
|
||||||
}
|
}
|
||||||
|
if (fState.changesShowing) {
|
||||||
|
fState.changesShowing = false
|
||||||
|
}
|
||||||
fState.xRefShowing = !fState.xRefShowing;
|
fState.xRefShowing = !fState.xRefShowing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -18,9 +21,23 @@
|
|||||||
if (fState.xRefShowing) {
|
if (fState.xRefShowing) {
|
||||||
fState.xRefShowing = false;
|
fState.xRefShowing = false;
|
||||||
}
|
}
|
||||||
|
if (fState.changesShowing) {
|
||||||
|
fState.changesShowing = false
|
||||||
|
}
|
||||||
fState.notificationsShowing = !fState.notificationsShowing;
|
fState.notificationsShowing = !fState.notificationsShowing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleChanges() {
|
||||||
|
if (!fState) return;
|
||||||
|
if (fState.xRefShowing) {
|
||||||
|
fState.xRefShowing = false;
|
||||||
|
}
|
||||||
|
if (fState.notificationsShowing) {
|
||||||
|
fState.notificationsShowing = false;
|
||||||
|
}
|
||||||
|
fState.changesShowing = !fState.changesShowing;
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<div class="grid grid-cols-1">
|
<div class="grid grid-cols-1">
|
||||||
|
|
||||||
@ -33,10 +50,17 @@
|
|||||||
|
|
||||||
<button class={ fState?.notificationsShowing ? "tool-button active" : "tool-button" } onclick={toggleNots}>
|
<button class={ fState?.notificationsShowing ? "tool-button active" : "tool-button" } onclick={toggleNots}>
|
||||||
<div class="justify-center flex text-forge-text">
|
<div class="justify-center flex text-forge-text">
|
||||||
<ListOutline/>
|
<InfoCircleOutline/>
|
||||||
</div>
|
</div>
|
||||||
<b class="button-title">Notifications</b>
|
<b class="button-title">Notifications</b>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button class={ fState?.changesShowing ? "tool-button active" : "tool-button" } onclick={toggleChanges}>
|
||||||
|
<div class="justify-center flex text-forge-text">
|
||||||
|
<UndoOutline/>
|
||||||
|
</div>
|
||||||
|
<b class="button-title">Changes</b>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
import type FileViewState from "../models/FileViewState.svelte";
|
import type FileViewState from "../models/FileViewState.svelte";
|
||||||
import type {TreeViewModel} from "../models/TreeViewModel";
|
import type {TreeViewModel} from "../models/TreeViewModel";
|
||||||
import TreeViewEntry from "./TreeViewEntry.svelte";
|
import TreeViewEntry from "./TreeViewEntry.svelte";
|
||||||
|
import {onMount} from "svelte";
|
||||||
|
|
||||||
let {
|
let {
|
||||||
fState,
|
fState,
|
||||||
@ -28,24 +29,34 @@
|
|||||||
treeState ? treeState.stickies : [],
|
treeState ? treeState.stickies : [],
|
||||||
);
|
);
|
||||||
|
|
||||||
$effect(() => {
|
onMount(() => {
|
||||||
|
scrollY = Math.min(scrollY, totalHeight);
|
||||||
treeState?.loadTreeView(scrollY, height);
|
treeState?.loadTreeView(scrollY, height);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
treeState = fState.treeState;
|
||||||
|
if (treeState) {
|
||||||
|
scrollY = Math.min(scrollY, totalHeight);
|
||||||
|
treeState.updateTreeView(scrollY, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
function handleSelect(prim: TreeViewModel) {
|
function handleSelect(prim: TreeViewModel) {
|
||||||
if (prim.expanded && prim.container) {
|
if (prim.expanded && prim.container) {
|
||||||
treeState
|
treeState
|
||||||
?.collapseTree(prim.path.map((path) => path.key))
|
?.collapseTree(prim.path.map((path) => path.key))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
treeState?.loadTreeView(scrollY, height);
|
treeState?.updateTreeView(scrollY, height);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (prim.container) {
|
} else if (prim.container) {
|
||||||
treeState
|
treeState
|
||||||
?.expandTree(prim.path.map((path) => path.key))
|
?.expandTree(prim.path.map((path) => path.key))
|
||||||
.then(() => {
|
.then(() => {
|
||||||
treeState?.loadTreeView(scrollY, height);
|
treeState?.updateTreeView(scrollY, height);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
fState.selectPathHandler(
|
fState.selectPathHandler(
|
||||||
@ -54,12 +65,12 @@
|
|||||||
prim.path.map((path) => path.key),
|
prim.path.map((path) => path.key),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
treeState?.loadTreeView(scrollY, height);
|
treeState?.updateTreeView(scrollY, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleScroll(event: Event & { currentTarget: HTMLElement }) {
|
function handleScroll(event: Event & { currentTarget: HTMLElement }) {
|
||||||
scrollY = event.currentTarget.scrollTop;
|
scrollY = event.currentTarget.scrollTop;
|
||||||
treeState?.loadTreeView(scrollY, height);
|
treeState?.updateTreeView(scrollY, height);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -102,34 +113,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.prim_name {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
position: relative;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caret {
|
|
||||||
@apply text-forge-sec;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
@apply ml-1 text-forge-prim whitespace-nowrap font-extralight;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row {
|
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-caret {
|
|
||||||
@apply pl-5;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -15,50 +15,50 @@
|
|||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{#if entry.depth == 0}
|
{#if entry.depth == 0}
|
||||||
<div
|
<div
|
||||||
style="height: 1px; width: 100%;"
|
style="height: 1px; width: 100%;"
|
||||||
class="bg-forge-bound"
|
class="bg-forge-bound"
|
||||||
></div>
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
class="row text-sm hover:bg-forge-sec w-full group whitespace-nowrap"
|
class="row text-sm hover:bg-forge-sec w-full group whitespace-nowrap"
|
||||||
style="height: {rowHeight}px;"
|
style="height: {rowHeight}px;"
|
||||||
onclick={onclick}
|
onclick={onclick}
|
||||||
>
|
>
|
||||||
<div style="margin-left: {entry.depth * 1.25}em">
|
<div style="margin-left: {entry.depth * 1.25}em">
|
||||||
{#if entry.container}
|
{#if entry.container}
|
||||||
<div>
|
<div class="caret group-hover:text-forge-text_hint">
|
||||||
<span
|
{#if entry.expanded}
|
||||||
class="caret group-hover:text-forge-text_hint"
|
<CaretDownOutline />
|
||||||
>{#if entry.expanded}<CaretDownOutline
|
{:else}
|
||||||
/>{:else}<CaretRightOutline
|
<CaretRightOutline />
|
||||||
/>{/if}</span
|
{/if}
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<span class="no-caret"></span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<PrimitiveIcon ptype={entry.ptype}/>
|
|
||||||
</div>
|
|
||||||
<div class="pl-1 prim_name whitespace-nowrap">
|
|
||||||
<p>
|
|
||||||
{formatDisplayKey(entry.key)}
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
class="details group-hover:text-forge-text_hint"
|
|
||||||
>
|
|
||||||
{" | " +
|
|
||||||
entry.value +
|
|
||||||
" | " +
|
|
||||||
entry.sub_type +
|
|
||||||
" | " +
|
|
||||||
entry.ptype}
|
|
||||||
</div>
|
</div>
|
||||||
|
{:else}
|
||||||
|
<span class="no-caret"></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<PrimitiveIcon ptype={entry.ptype}/>
|
||||||
|
</div>
|
||||||
|
<div class="pl-1 prim_name whitespace-nowrap">
|
||||||
|
<p>
|
||||||
|
{formatDisplayKey(entry.key)}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="details group-hover:text-forge-text_hint"
|
||||||
|
>
|
||||||
|
{" | " +
|
||||||
|
entry.value +
|
||||||
|
" | " +
|
||||||
|
entry.sub_type +
|
||||||
|
" | " +
|
||||||
|
entry.ptype}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
.prim_name {
|
.prim_name {
|
||||||
@ -70,9 +70,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.caret {
|
.caret {
|
||||||
@apply text-forge-sec;
|
@apply text-forge-sec align-middle;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details {
|
.details {
|
||||||
|
|||||||
@ -20,7 +20,7 @@
|
|||||||
fState.getLastJump(),
|
fState.getLastJump(),
|
||||||
);
|
);
|
||||||
let totalBodyHeight: number = $derived(
|
let totalBodyHeight: number = $derived(
|
||||||
Math.max(0, fState.file.xref_entries * cellH - headerOffset),
|
Math.max(0, fState.xref_entries.length * cellH - headerOffset),
|
||||||
);
|
);
|
||||||
let scrollContainer: HTMLElement;
|
let scrollContainer: HTMLElement;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const EOF: string = "\n%-------------------% EOF %-------------------%\n\n";
|
const EOF: string = "\n%-------------------% EOF %-------------------%\n\n";
|
||||||
const CONTENTS: string = "%----------------% Contents[%d] %--------------%\n\n";
|
const CONTENTS: string = "%----------------% Contents[%d] %--------------%\n\n";
|
||||||
const CONTENTS_PATTERN = /%----------------% Contents\[\d+] %--------------%\n\n([\s\S]*?)(?=%-------------------% EOF %-------------------%|$)/gm;
|
const CONTENTS_PATTERN = /%----------------% Contents\[\d+\] %--------------%((?:.|\s)*?)%-------------------% EOF %-------------------%/gmu;
|
||||||
export default class ContentModel {
|
export default class ContentModel {
|
||||||
parts: string[][];
|
parts: string[][];
|
||||||
constructor(parts: string[][]) {
|
constructor(parts: string[][]) {
|
||||||
@ -26,17 +26,17 @@ export default class ContentModel {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
public fromDisplay(display: string): ContentModel {
|
public static fromDisplay(display: string): ContentModel {
|
||||||
const parts: string[][] = [];
|
const parts: string[][] = [];
|
||||||
let match;
|
let match;
|
||||||
while ((match = CONTENTS_PATTERN.exec(display)) !== null) {
|
while ((match = CONTENTS_PATTERN.exec(display)) !== null) {
|
||||||
const lines = match[1].split("\n").map(line => line.trim()).filter(line => line !== "");
|
let lines = match[1].split("\n").map(line => line.trim()).filter(line => line.length > 0);
|
||||||
parts.push(lines);
|
parts.push(lines);
|
||||||
}
|
}
|
||||||
|
if (parts.length == 0) {
|
||||||
if (parts.length === 0) {
|
|
||||||
parts.push(display.split("\n").map(line => line.trim()));
|
parts.push(display.split("\n").map(line => line.trim()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ContentModel(parts);
|
return new ContentModel(parts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -11,15 +11,20 @@ import {Mutex} from 'async-mutex';
|
|||||||
import {RawImageData} from "./RawImageData";
|
import {RawImageData} from "./RawImageData";
|
||||||
import TreeViewState from "./TreeViewState.svelte";
|
import TreeViewState from "./TreeViewState.svelte";
|
||||||
import {PageViewState} from "./PageViewState.svelte";
|
import {PageViewState} from "./PageViewState.svelte";
|
||||||
|
import PathHistory from "./PathHistory.svelte";
|
||||||
|
|
||||||
export default class FileViewState {
|
export default class FileViewState {
|
||||||
|
public pathHistory: PathHistory = new PathHistory();
|
||||||
|
|
||||||
public file: PdfFile;
|
public file: PdfFile;
|
||||||
|
|
||||||
public treeMode: boolean = $state(true);
|
public treeMode: boolean = $state(true);
|
||||||
public pageMode: boolean = $state(false);
|
public pageMode: boolean = $state(false);
|
||||||
public xRefShowing: boolean = $state(false);
|
public xRefShowing: boolean = $state(false);
|
||||||
public notificationsShowing: boolean = $state(false);
|
public notificationsShowing: boolean = $state(false);
|
||||||
|
public changesShowing: boolean = $state(false);
|
||||||
|
public canForward: boolean = $derived(this.pathHistory.canForward)
|
||||||
|
public canBack: boolean = $derived(this.pathHistory.canBack)
|
||||||
|
|
||||||
public path: string[] = $state(["Trailer"]);
|
public path: string[] = $state(["Trailer"]);
|
||||||
public container_prim: Primitive | undefined = $state();
|
public container_prim: Primitive | undefined = $state();
|
||||||
@ -33,6 +38,9 @@ export default class FileViewState {
|
|||||||
public notifications: ForgeNotification[] = $state([]);
|
public notifications: ForgeNotification[] = $state([]);
|
||||||
notificationMutex = new Mutex();
|
notificationMutex = new Mutex();
|
||||||
|
|
||||||
|
public changes: PrimitiveModel[] = $state([]);
|
||||||
|
|
||||||
|
|
||||||
constructor(file: PdfFile) {
|
constructor(file: PdfFile) {
|
||||||
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
@ -59,20 +67,24 @@ export default class FileViewState {
|
|||||||
newData: updated_data
|
newData: updated_data
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("saved");
|
|
||||||
this.reload();
|
this.reload();
|
||||||
this.container_prim?.stream_data?.setData(updated_data);
|
this.container_prim?.stream_data?.setData(updated_data);
|
||||||
})
|
})
|
||||||
.catch(err => this.logError(err));
|
.catch(err => this.logError(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
public reload() {
|
public async reload() {
|
||||||
this.container_prim = undefined;
|
this.container_prim = undefined;
|
||||||
this.selected_leaf_prim = undefined;
|
this.selected_leaf_prim = undefined;
|
||||||
|
|
||||||
this.loadXrefEntries();
|
this.loadXrefEntries();
|
||||||
this.selectPath(this.path);
|
|
||||||
this.treeState?.reload();
|
this.treeState?.reload();
|
||||||
this.reloadPageState();
|
await this.selectPath(this.path);
|
||||||
|
this.pageViewStates.clear();
|
||||||
|
if (this.pageViewState) {
|
||||||
|
this.pageViewState.reload()
|
||||||
|
this.pageViewStates.set(this.pageViewState.page_num, this.pageViewState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public logError(message: string) {
|
public logError(message: string) {
|
||||||
@ -86,10 +98,6 @@ export default class FileViewState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setTreeViewState(state: TreeViewState) {
|
|
||||||
this.treeState = state;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteNotification(timestamp: number) {
|
public async deleteNotification(timestamp: number) {
|
||||||
const release = await this.notificationMutex.acquire();
|
const release = await this.notificationMutex.acquire();
|
||||||
try {
|
try {
|
||||||
@ -108,12 +116,26 @@ export default class FileViewState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async clearChanges() {
|
||||||
|
invoke<PrimitiveModel[]>("clear_changes", {id: this.file.id})
|
||||||
|
.then(_ => {
|
||||||
|
this.reload()
|
||||||
|
})
|
||||||
|
.catch(err => this.logError(err));
|
||||||
|
}
|
||||||
|
|
||||||
public loadXrefEntries() {
|
public loadXrefEntries() {
|
||||||
invoke<XRefTable>("get_xref_table", {id: this.file.id})
|
invoke<XRefTable>("get_xref_table", {id: this.file.id})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
this.xref_entries = result.entries;
|
this.xref_entries = result.entries;
|
||||||
})
|
})
|
||||||
.catch(err => this.logError(err));
|
.catch(err => this.logError(err));
|
||||||
|
|
||||||
|
invoke<PrimitiveModel[]>("get_changes", {id: this.file.id})
|
||||||
|
.then(result => {
|
||||||
|
this.changes = result;
|
||||||
|
})
|
||||||
|
.catch(err => this.logError(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async selectPath(newPath: string[]) {
|
public async selectPath(newPath: string[]) {
|
||||||
@ -131,6 +153,7 @@ export default class FileViewState {
|
|||||||
if (newPrim.isContainer()) {
|
if (newPrim.isContainer()) {
|
||||||
this.container_prim = newPrim;
|
this.container_prim = newPrim;
|
||||||
this.path = newPath
|
this.path = newPath
|
||||||
|
this.pathHistory.newPath(newPath);
|
||||||
this.loadPageState().catch(err => this.logError(err));
|
this.loadPageState().catch(err => this.logError(err));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -173,13 +196,31 @@ export default class FileViewState {
|
|||||||
return this.formatPaths(this.path);
|
return this.formatPaths(this.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
public popPath() {
|
public back() {
|
||||||
let path = this.copyPath();
|
if (!this.canBack) return;
|
||||||
if (path.length == 1) {
|
let prevPath = this.pathHistory.goBack();
|
||||||
return
|
if (prevPath) {
|
||||||
|
this.selectPath(prevPath)
|
||||||
}
|
}
|
||||||
path.pop()
|
}
|
||||||
this.selectPath(path);
|
|
||||||
|
public forward() {
|
||||||
|
if (!this.canForward) return;
|
||||||
|
let prevPath = this.pathHistory.goForward();
|
||||||
|
if (prevPath) {
|
||||||
|
this.selectPath(prevPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public jump(i: number) {
|
||||||
|
let prevPath = this.pathHistory.jump(i);
|
||||||
|
if (prevPath) {
|
||||||
|
this.selectPath(prevPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public save() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public copyPath() {
|
public copyPath() {
|
||||||
@ -211,9 +252,10 @@ export default class FileViewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async reloadPageState() {
|
private async reloadPageState(page_num: number) {
|
||||||
this.pageViewState = undefined;
|
|
||||||
this.pageViewStates = new Map();
|
this.pageViewState = new PageViewState(page_num, this);
|
||||||
|
this.pageViewStates.set(page_num, this.pageViewState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadPageState() {
|
private async loadPageState() {
|
||||||
@ -221,11 +263,10 @@ export default class FileViewState {
|
|||||||
let page_num = this.container_prim.getPageNumber();
|
let page_num = this.container_prim.getPageNumber();
|
||||||
|
|
||||||
if (this.pageViewState && this.pageViewState.page_num === page_num) return;
|
if (this.pageViewState && this.pageViewState.page_num === page_num) return;
|
||||||
|
|
||||||
this.pageViewState = this.pageViewStates.get(page_num);
|
this.pageViewState = this.pageViewStates.get(page_num);
|
||||||
|
|
||||||
if (!this.pageViewState) {
|
if (!this.pageViewState) {
|
||||||
this.pageViewState = new PageViewState(page_num, this);
|
await this.reloadPageState(page_num);
|
||||||
this.pageViewStates.set(page_num, this.pageViewState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -9,34 +9,40 @@ export class PageViewState {
|
|||||||
fState: FileViewState;
|
fState: FileViewState;
|
||||||
|
|
||||||
img_data: RawImageData | undefined = $state();
|
img_data: RawImageData | undefined = $state();
|
||||||
contents: ContentModel = $state(new ContentModel([]));
|
contents: ContentModel | undefined = $state(undefined);
|
||||||
|
|
||||||
constructor(page_num: number, fState: FileViewState) {
|
constructor(page_num: number, fState: FileViewState) {
|
||||||
this.file_id = fState.file.id;
|
this.file_id = fState.file.id;
|
||||||
this.page_num = page_num;
|
this.page_num = page_num;
|
||||||
this.fState = fState;
|
this.fState = fState;
|
||||||
|
|
||||||
this.load(page_num);
|
this.load();
|
||||||
}
|
}
|
||||||
|
|
||||||
private logError(err: string) {
|
private logError(err: string) {
|
||||||
this.fState.logError(err);
|
this.fState.logError(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async load(page_num: number) {
|
public async reload() {
|
||||||
invoke<ContentModel>("get_contents", {id: this.file_id, path: "Page" + page_num})
|
await this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async load() {
|
||||||
|
invoke<ContentModel>("get_contents", {id: this.file_id, path: "Page" + this.page_num})
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
this.page_num = page_num;
|
this.contents = undefined;
|
||||||
this.contents = new ContentModel(result.parts);
|
this.contents = new ContentModel(result.parts);
|
||||||
this.loadImage(page_num);
|
this.loadImage();
|
||||||
})
|
})
|
||||||
.catch(this.logError);
|
.catch(this.logError);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadImage(page_num: number) {
|
public async loadImage() {
|
||||||
|
this.img_data?.dispose();
|
||||||
|
this.img_data = undefined;
|
||||||
let result = await invoke<ArrayBuffer>("get_page_by_num", {
|
let result = await invoke<ArrayBuffer>("get_page_by_num", {
|
||||||
id: this.file_id,
|
id: this.file_id,
|
||||||
num: page_num,
|
num: this.page_num,
|
||||||
})
|
})
|
||||||
.catch(this.logError);
|
.catch(this.logError);
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -45,9 +51,12 @@ export class PageViewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public handleSave(newData: string) {
|
public handleSave(newData: string) {
|
||||||
invoke("update_contents", {id: this.file_id, path: "Page" + this.page_num, newData: newData})
|
let contents = ContentModel.fromDisplay(newData);
|
||||||
.then((_) => {
|
console.log(contents);
|
||||||
this.loadImage(this.page_num);
|
invoke<ContentModel>("update_contents", {id: this.file_id, path: "Page" + this.page_num, contents: contents})
|
||||||
|
.then((result) => {
|
||||||
|
this.contents = undefined;
|
||||||
|
this.fState.reload();
|
||||||
})
|
})
|
||||||
.catch(this.logError);
|
.catch(this.logError);
|
||||||
}
|
}
|
||||||
|
|||||||
54
src/models/PathHistory.svelte.ts
Normal file
54
src/models/PathHistory.svelte.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import {arraysAreEqual} from "../utils";
|
||||||
|
|
||||||
|
export default class PathHistory {
|
||||||
|
public currentIndex: number = $state(-1);
|
||||||
|
public history: string[][] = $state([]);
|
||||||
|
|
||||||
|
public canForward: boolean = $derived(this.currentIndex < this.history.length - 1);
|
||||||
|
public canBack: boolean = $derived(this.currentIndex > 0);
|
||||||
|
|
||||||
|
public newPath(path: string[]) {
|
||||||
|
let current = this.getCurrent();
|
||||||
|
if (current && arraysAreEqual(path, current)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentIndex ++;
|
||||||
|
this.history = this.history.slice(0, this.currentIndex);
|
||||||
|
this.history.push(this.copy(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public goBack(): string[] | undefined {
|
||||||
|
if (!this.canBack) return undefined;
|
||||||
|
this.currentIndex--;
|
||||||
|
return this.getCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public goForward(): string[] | undefined {
|
||||||
|
if (!this.canForward) return undefined;
|
||||||
|
this.currentIndex++;
|
||||||
|
return this.getCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public jump(i: number): string[] | undefined {
|
||||||
|
if (i >= 0 && i < this.history.length) {
|
||||||
|
this.currentIndex = i;
|
||||||
|
return this.getCurrent();
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrent(): string[] | undefined {
|
||||||
|
if (!this.history || this.currentIndex < 0 || this.currentIndex >= this.history.length) return undefined;
|
||||||
|
return this.copy(this.history[this.currentIndex])
|
||||||
|
}
|
||||||
|
|
||||||
|
private copy(path: string[]) {
|
||||||
|
const _path: string[] = [];
|
||||||
|
|
||||||
|
for (let item of path) {
|
||||||
|
_path.push(item);
|
||||||
|
}
|
||||||
|
return _path;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,8 +25,9 @@ export default class TreeViewState {
|
|||||||
|
|
||||||
this.initialRequest = [TreeViewRequest.TRAILER];
|
this.initialRequest = [TreeViewRequest.TRAILER];
|
||||||
this.initialRequest = this.initialRequest.concat(TreeViewRequest.fromPageCount(+fState.file.page_count));
|
this.initialRequest = this.initialRequest.concat(TreeViewRequest.fromPageCount(+fState.file.page_count));
|
||||||
this.activeRequest.set(this.initialRequest[0].key, this.initialRequest[0]);
|
|
||||||
this.file_id = fState.file.id;
|
this.file_id = fState.file.id;
|
||||||
|
this.updateTreeViewRequest([this.initialRequest[0]]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEntryCount() {
|
public getEntryCount() {
|
||||||
@ -121,6 +122,10 @@ export default class TreeViewState {
|
|||||||
public async loadTreeView(scrollY: number, height: number) {
|
public async loadTreeView(scrollY: number, height: number) {
|
||||||
const activeRequests = Array.from(this.activeRequest.values());
|
const activeRequests = Array.from(this.activeRequest.values());
|
||||||
await this.updateTreeViewRequest(activeRequests);
|
await this.updateTreeViewRequest(activeRequests);
|
||||||
|
await this.updateTreeView(scrollY, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateTreeView(scrollY: number, height: number) {
|
||||||
let firstEntry = Math.floor(scrollY / ROW_HEIGHT);
|
let firstEntry = Math.floor(scrollY / ROW_HEIGHT);
|
||||||
let lastEntry = Math.ceil((scrollY + height) / ROW_HEIGHT);
|
let lastEntry = Math.ceil((scrollY + height) / ROW_HEIGHT);
|
||||||
this.total_height = this.getEntryCount() * ROW_HEIGHT;
|
this.total_height = this.getEntryCount() * ROW_HEIGHT;
|
||||||
@ -141,7 +146,6 @@ export default class TreeViewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateTreeViewRequest(treeViewRequests: TreeViewRequest[]) {
|
public async updateTreeViewRequest(treeViewRequests: TreeViewRequest[]) {
|
||||||
|
|
||||||
let result = await invoke<TreeViewModel[]>("get_prim_tree_by_path", {
|
let result = await invoke<TreeViewModel[]>("get_prim_tree_by_path", {
|
||||||
id: this.file_id,
|
id: this.file_id,
|
||||||
paths: treeViewRequests,
|
paths: treeViewRequests,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user