Added image rendering and notifications
This commit is contained in:
parent
f6fa5d7269
commit
98d34ad5c3
@ -19,6 +19,7 @@
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-fs": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"async-mutex": "^0.5.0",
|
||||
"flowbite-svelte": "^0.47.4",
|
||||
"flowbite-svelte-icons": "^2.0.2",
|
||||
"monaco-editor": "^0.52.2",
|
||||
|
||||
486
src-tauri/Cargo.lock
generated
486
src-tauri/Cargo.lock
generated
@ -55,6 +55,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aligned-vec"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
|
||||
|
||||
[[package]]
|
||||
name = "alloc-no-stdlib"
|
||||
version = "2.0.4"
|
||||
@ -97,6 +103,29 @@ version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
|
||||
|
||||
[[package]]
|
||||
name = "arg_enum_proc_macro"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "ashpd"
|
||||
version = "0.10.2"
|
||||
@ -296,6 +325,29 @@ version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
|
||||
|
||||
[[package]]
|
||||
name = "av1-grain"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arrayvec",
|
||||
"log",
|
||||
"nom",
|
||||
"num-rational",
|
||||
"v_frame",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "avif-serialize"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
@ -323,6 +375,12 @@ version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
@ -338,6 +396,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitstream-io"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
||||
|
||||
[[package]]
|
||||
name = "block"
|
||||
version = "0.1.6"
|
||||
@ -405,6 +469,12 @@ dependencies = [
|
||||
"alloc-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "built"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
@ -423,6 +493,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.9.0"
|
||||
@ -514,6 +590,8 @@ version = "1.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
@ -609,6 +687,12 @@ dependencies = [
|
||||
"objc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "4.6.7"
|
||||
@ -720,12 +804,37 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@ -1088,6 +1197,21 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.73.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"half",
|
||||
"lebe",
|
||||
"miniz_oxide",
|
||||
"rayon-core",
|
||||
"smallvec",
|
||||
"zune-inflate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@ -1420,6 +1544,16 @@ dependencies = [
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gif"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
|
||||
dependencies = [
|
||||
"color_quant",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.31.1"
|
||||
@ -1585,6 +1719,16 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@ -1901,6 +2045,45 @@ dependencies = [
|
||||
"icu_properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"color_quant",
|
||||
"exr",
|
||||
"gif",
|
||||
"image-webp",
|
||||
"num-traits",
|
||||
"png",
|
||||
"qoi",
|
||||
"ravif",
|
||||
"rayon",
|
||||
"rgb",
|
||||
"tiff",
|
||||
"zune-core",
|
||||
"zune-jpeg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "image-webp"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f"
|
||||
dependencies = [
|
||||
"byteorder-lite",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "imgref"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@ -1942,6 +2125,17 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interpolate_name"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.11.0"
|
||||
@ -1976,6 +2170,15 @@ dependencies = [
|
||||
"datasize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
@ -2042,6 +2245,15 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.3.1"
|
||||
@ -2110,6 +2322,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "lebe"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libappindicator"
|
||||
version = "0.9.0"
|
||||
@ -2164,6 +2382,16 @@ dependencies = [
|
||||
"rle-decode-fast",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.7.4"
|
||||
@ -2212,6 +2440,15 @@ version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
|
||||
dependencies = [
|
||||
"imgref",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mac"
|
||||
version = "0.1.1"
|
||||
@ -2247,6 +2484,16 @@ version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-rayon"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rayon",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
@ -2274,6 +2521,12 @@ version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.3"
|
||||
@ -2370,12 +2623,69 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "noop_proc_macro"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||
dependencies = [
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@ -2731,6 +3041,12 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
@ -2750,7 +3066,7 @@ dependencies = [
|
||||
"globalcache",
|
||||
"indexmap 2.7.1",
|
||||
"istring",
|
||||
"itertools",
|
||||
"itertools 0.13.0",
|
||||
"jpeg-decoder",
|
||||
"libflate",
|
||||
"log",
|
||||
@ -2767,6 +3083,9 @@ dependencies = [
|
||||
name = "pdf-forge"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"fax",
|
||||
"image",
|
||||
"lazy_static",
|
||||
"pdf",
|
||||
"regex",
|
||||
@ -3087,6 +3406,40 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d"
|
||||
dependencies = [
|
||||
"profiling-procmacros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling-procmacros"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "qoi"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.32.0"
|
||||
@ -3195,12 +3548,82 @@ dependencies = [
|
||||
"rand_core 0.5.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rav1e"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"arg_enum_proc_macro",
|
||||
"arrayvec",
|
||||
"av1-grain",
|
||||
"bitstream-io",
|
||||
"built",
|
||||
"cfg-if",
|
||||
"interpolate_name",
|
||||
"itertools 0.12.1",
|
||||
"libc",
|
||||
"libfuzzer-sys",
|
||||
"log",
|
||||
"maybe-rayon",
|
||||
"new_debug_unreachable",
|
||||
"noop_proc_macro",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"profiling",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"simd_helpers",
|
||||
"system-deps",
|
||||
"thiserror 1.0.69",
|
||||
"v_frame",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ravif"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6"
|
||||
dependencies = [
|
||||
"avif-serialize",
|
||||
"imgref",
|
||||
"loop9",
|
||||
"quick-error",
|
||||
"rav1e",
|
||||
"rayon",
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.8"
|
||||
@ -3313,6 +3736,12 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
|
||||
|
||||
[[package]]
|
||||
name = "rle-decode-fast"
|
||||
version = "1.0.3"
|
||||
@ -3616,6 +4045,15 @@ version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simd_helpers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
@ -4254,6 +4692,17 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"jpeg-decoder",
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
@ -4654,6 +5103,17 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_frame"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
|
||||
dependencies = [
|
||||
"aligned-vec",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version-compare"
|
||||
version = "0.2.0"
|
||||
@ -5649,6 +6109,30 @@ dependencies = [
|
||||
"syn 2.0.96",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-core"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
|
||||
|
||||
[[package]]
|
||||
name = "zune-inflate"
|
||||
version = "0.2.54"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.2.0"
|
||||
|
||||
@ -28,4 +28,6 @@ tauri-plugin-dialog = "2"
|
||||
uuid = { version = "1.12.0", features = ["v4"] }
|
||||
regex = "1.10.3"
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
fax = "0.2"
|
||||
base64 = "0.21"
|
||||
image = { version = "0.25.5", features = ["jpeg"] }
|
||||
|
||||
@ -1,34 +1,34 @@
|
||||
mod retrieval;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
extern crate pdf;
|
||||
|
||||
use crate::pdf::object::Resolve;
|
||||
|
||||
use base64;
|
||||
use image::codecs::jpeg::JpegEncoder;
|
||||
use image::ImageFormat;
|
||||
use lazy_static::lazy_static;
|
||||
use pdf::file::{File, FileOptions, NoLog, ObjectCache, StreamCache};
|
||||
use pdf::object::{Object, ObjectWrite, PlainRef, Stream, Trace};
|
||||
use pdf::object::{ImageXObject, Object, PlainRef};
|
||||
use pdf::primitive::{Dictionary, Primitive};
|
||||
use pdf::xref::XRef;
|
||||
use regex::Regex;
|
||||
use retrieval::{
|
||||
get_pdf_stream_by_path_with_file, get_prim_by_path_with_file, get_stream_data_by_path_with_file,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::io::Cursor;
|
||||
use std::ops::DerefMut;
|
||||
use std::path::Path;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
use std::sync::{Arc, Mutex, MutexGuard, RwLock};
|
||||
use tauri::{Manager, State};
|
||||
use uuid::Uuid;
|
||||
|
||||
type CosFile = File<Vec<u8>, ObjectCache, StreamCache, NoLog>;
|
||||
|
||||
macro_rules! t {
|
||||
($result:expr) => {{
|
||||
match $result {
|
||||
Ok(f) => f,
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
}};
|
||||
}
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct XRefTableModel {
|
||||
pub size: usize,
|
||||
@ -111,50 +111,35 @@ pub struct ContentsModel {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_all_files(session: State<Mutex<Session>>) -> Vec<PdfFile> {
|
||||
let files = &session.lock().unwrap().files;
|
||||
files
|
||||
.values()
|
||||
.map(|sf| sf.pdf_file.clone())
|
||||
.collect::<Vec<PdfFile>>()
|
||||
fn get_all_files(session: State<Session>) -> Vec<PdfFile> {
|
||||
session.get_all_files().iter().map(|s| s.pdf_file.clone()).collect()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_all_file_ids(session: State<Mutex<Session>>) -> Vec<String> {
|
||||
let files = &session.lock().unwrap().files;
|
||||
files
|
||||
.values()
|
||||
.map(|sf| sf.pdf_file.id.clone())
|
||||
.collect::<Vec<String>>()
|
||||
fn get_all_file_ids(session: State<Session>) -> Vec<String> {
|
||||
session.get_all_file_ids()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn close_file(id: &str, session: State<Mutex<Session>>) {
|
||||
session.lock().unwrap().deref_mut().handle_close(&id);
|
||||
fn close_file(id: &str, session: State<Session>) {
|
||||
session.handle_close(id);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_file_by_id(id: &str, session: State<Mutex<Session>>) -> Result<PdfFile, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = &get_file_from_state(id, &session_guard)?;
|
||||
Ok(file.pdf_file.clone())
|
||||
fn get_file_by_id(id: &str, session: State<Session>) -> Result<PdfFile, String> {
|
||||
session.get_file(id).map(|file| file.pdf_file.clone())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn upload(path: &str, session: State<Mutex<Session>>) -> Result<String, String> {
|
||||
fn upload(path: &str, session: State<Session>) -> Result<String, String> {
|
||||
let file = t!(FileOptions::cached().open(path));
|
||||
|
||||
let pdf_file = to_pdf_file(path, &file)?;
|
||||
let id = pdf_file.id.clone();
|
||||
|
||||
session
|
||||
.lock()
|
||||
.unwrap()
|
||||
.deref_mut()
|
||||
.handle_upload(&pdf_file, file);
|
||||
session.handle_upload(pdf_file, file)?;
|
||||
|
||||
Ok(pdf_file.id.to_string())
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
fn to_pdf_file(path: &str, file: &CosFile) -> Result<PdfFile, String> {
|
||||
@ -198,12 +183,10 @@ fn to_pdf_file(path: &str, file: &CosFile) -> Result<PdfFile, String> {
|
||||
fn get_contents(
|
||||
id: &str,
|
||||
path: &str,
|
||||
session: State<Mutex<Session>>,
|
||||
session: State<Session>,
|
||||
) -> Result<ContentsModel, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = get_file_from_state(id, &session_guard)?;
|
||||
|
||||
let file = session.get_file(id)?;
|
||||
|
||||
let (_, page_prim, _) = get_prim_by_path_with_file(path, &file.cos_file)?;
|
||||
let resolver = file.cos_file.resolver();
|
||||
@ -223,112 +206,87 @@ fn get_contents(
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_stream_data(
|
||||
fn get_stream_data_as_string(
|
||||
id: &str,
|
||||
path: &str,
|
||||
session: State<Mutex<Session>>,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = get_file_from_state(id, &session_guard)?;
|
||||
get_stream_data_by_path_with_file(path, &file.cos_file)
|
||||
session: State<Session>,
|
||||
) -> Result<String, String> {
|
||||
|
||||
let file = session.get_file(id)?;
|
||||
let data = get_stream_data_by_path_with_file(path, &file.cos_file)?;
|
||||
Ok(String::from_utf8_lossy(&data).into_owned())
|
||||
}
|
||||
|
||||
fn get_stream_data_by_path_with_file(path: &str, file: &CosFile) -> Result<Vec<u8>, String> {
|
||||
let mut steps = Step::parse(path);
|
||||
if steps
|
||||
.pop_back()
|
||||
.filter(|last| *last == Step::Data)
|
||||
.is_none()
|
||||
{
|
||||
return Err(format!("Path {} does not end with Data", path));
|
||||
}
|
||||
let (_, prim, _) = get_prim_by_steps_with_file(steps, file)?;
|
||||
#[tauri::command]
|
||||
fn get_stream_data_as_image(
|
||||
id: &str,
|
||||
path: &str,
|
||||
session: State<Session>,
|
||||
) -> Result<String, String> {
|
||||
use base64::prelude::*;
|
||||
|
||||
let Primitive::Stream(stream) = prim else {
|
||||
return Err(format!("Path {} does not point to a stream", path));
|
||||
};
|
||||
let resolver = file.resolver();
|
||||
let data = t!(t!(Stream::<Primitive>::from_stream(stream, &resolver)).data(&resolver));
|
||||
Ok(data.to_vec())
|
||||
let file = session.get_file(id)?;
|
||||
let img = retrieval::get_image_by_path(path, &file.cos_file)?;
|
||||
let mut writer = Cursor::new(Vec::new());
|
||||
match img.write_to(&mut writer, ImageFormat::Jpeg) {
|
||||
Ok(_) => Ok(format!(
|
||||
"data:image/{};base64,{}",
|
||||
"jpeg",
|
||||
BASE64_STANDARD.encode(writer.into_inner())
|
||||
)),
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_prim_by_path(
|
||||
id: &str,
|
||||
path: &str,
|
||||
session: State<Mutex<Session>>,
|
||||
session: State<Session>,
|
||||
) -> Result<PrimitiveModel, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = get_file_from_state(id, &session_guard)?;
|
||||
let file = session.get_file(id)?;
|
||||
|
||||
get_prim_model_by_path_with_file(path, &file.cos_file)
|
||||
}
|
||||
|
||||
fn get_prim_model_by_path_with_file(path: &str, file: &CosFile) -> Result<PrimitiveModel, String> {
|
||||
let (step, prim, trace) = get_prim_by_path_with_file(path, file)?;
|
||||
let steps = Step::parse(path);
|
||||
if steps.len() == 0 {
|
||||
return Err(String::from(format!("{:?} is not a valid path!", steps)));
|
||||
}
|
||||
if steps.back().unwrap() == &Step::Data {
|
||||
return handle_data_step(steps, file);
|
||||
}
|
||||
|
||||
let (_, prim, trace) = retrieval::get_prim_by_steps_with_file(steps, file)?;
|
||||
|
||||
Ok(PrimitiveModel::from_primitive_with_children(&prim, trace))
|
||||
}
|
||||
|
||||
fn get_prim_by_path_with_file(
|
||||
path: &str,
|
||||
file: &CosFile,
|
||||
) -> Result<(Step, Primitive, Vec<PathTrace>), String> {
|
||||
get_prim_by_steps_with_file(Step::parse(path), file)
|
||||
}
|
||||
|
||||
fn get_prim_by_steps_with_file(
|
||||
mut steps: VecDeque<Step>,
|
||||
file: &CosFile,
|
||||
) -> Result<(Step, Primitive, Vec<PathTrace>), String> {
|
||||
if steps.len() == 0 {
|
||||
return Err(String::from(format!("{:?} is not a valid path!", steps)));
|
||||
fn handle_data_step(mut steps: VecDeque<Step>, file: &CosFile) -> Result<PrimitiveModel, String> {
|
||||
let _data = steps.pop_back();
|
||||
let (_, prim, trace) = retrieval::get_prim_by_steps_with_file(steps, file)?;
|
||||
if let Primitive::Stream(stream) = prim {
|
||||
let sub_type = match stream.info.get("Subtype") {
|
||||
Some(element) => match element {
|
||||
Primitive::Name(name) => name.as_str().to_string(),
|
||||
_ => String::from("-"),
|
||||
},
|
||||
_ => String::from("-"),
|
||||
};
|
||||
return Ok(PrimitiveModel::as_data(sub_type, trace));
|
||||
}
|
||||
let step = steps.pop_front().unwrap();
|
||||
let (mut parent, trace) = resolve_parent(step.clone(), file)?;
|
||||
|
||||
let mut last_jump = trace.last_jump.clone();
|
||||
let mut trace = vec![trace];
|
||||
|
||||
let mut current_prim = &parent;
|
||||
while !steps.is_empty() {
|
||||
let step = steps.pop_front().unwrap();
|
||||
|
||||
current_prim = resolve_step(¤t_prim, &step)?;
|
||||
if let Primitive::Reference(xref) = current_prim {
|
||||
last_jump = xref.id.to_string();
|
||||
parent = resolve_p_ref(xref.clone(), file)?;
|
||||
current_prim = &parent;
|
||||
}
|
||||
trace.push(PathTrace::new(step.get_key(), last_jump.clone()));
|
||||
}
|
||||
Ok((step, current_prim.clone(), trace))
|
||||
}
|
||||
|
||||
fn resolve_parent(step: Step, file: &CosFile) -> Result<(Primitive, PathTrace), String> {
|
||||
let parent = match step {
|
||||
Step::Page(page_num) => return retrieve_page(page_num, file),
|
||||
Step::Number(obj_num) => resolve_xref(obj_num, file)?,
|
||||
Step::Trailer => retrieve_trailer(file),
|
||||
_ => return Err(String::from(format!("{:?} is not a valid path!", step))),
|
||||
};
|
||||
Ok((parent, PathTrace::new(step.get_key(), step.get_key())))
|
||||
Err(String::from("Parent of Data is not a stream"))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn get_prim_tree_by_path(
|
||||
id: &str,
|
||||
paths: Vec<TreeViewRequest>,
|
||||
session: State<Mutex<Session>>,
|
||||
session: State<Session>,
|
||||
) -> Result<Vec<PrimitiveTreeView>, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = get_file_from_state(id, &session_guard)?;
|
||||
|
||||
let file = session.get_file(id)?;
|
||||
|
||||
let results = paths
|
||||
.into_iter()
|
||||
@ -346,7 +304,7 @@ fn get_prim_tree_by_path_with_file(
|
||||
file: &CosFile,
|
||||
) -> Result<Vec<PrimitiveTreeView>, String> {
|
||||
let step = node.step()?;
|
||||
let (parent, trace) = resolve_parent(step.clone(), file)?;
|
||||
let (parent, trace) = retrieval::resolve_parent(step.clone(), file)?;
|
||||
|
||||
let trace = vec![trace];
|
||||
let mut parent_model: PrimitiveModel;
|
||||
@ -373,9 +331,9 @@ fn expand(
|
||||
return Ok(());
|
||||
}
|
||||
let step = node.step()?;
|
||||
let prim = resolve_step(parent, &step)?;
|
||||
let prim = retrieval::resolve_step(parent, &step)?;
|
||||
if let Primitive::Reference(x_ref) = prim {
|
||||
let jump = resolve_xref(x_ref.id, file)?;
|
||||
let jump = retrieval::resolve_xref(x_ref.id, file)?;
|
||||
let mut jump_trace = parent_model.trace.clone();
|
||||
jump_trace.push(PathTrace::new(step.get_key(), x_ref.id.to_string()));
|
||||
let mut to_expand = parent_model.get_child(step.get_key()).unwrap();
|
||||
@ -402,72 +360,6 @@ fn expand_children(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_step<'a>(current_prim: &'a Primitive, step: &Step) -> Result<&'a Primitive, String> {
|
||||
Ok(match step {
|
||||
Step::Number(index) => match current_prim {
|
||||
Primitive::Array(prim_array) => {
|
||||
let i = index.clone() as usize;
|
||||
if prim_array.len() <= i {
|
||||
return Err(String::from(format!(
|
||||
"{} index out of bounds!",
|
||||
step.get_key()
|
||||
)));
|
||||
}
|
||||
&prim_array[i]
|
||||
}
|
||||
p => {
|
||||
return Err(String::from(format!(
|
||||
"{} is not indexed with numbers!",
|
||||
p.get_debug_name()
|
||||
)))
|
||||
}
|
||||
},
|
||||
Step::String(key) => match current_prim {
|
||||
Primitive::Dictionary(dict) => match dict.get(key) {
|
||||
Some(prim) => prim,
|
||||
None => {
|
||||
return Err(String::from(format!(
|
||||
"Key {} does not exist in Dictionary!",
|
||||
key
|
||||
)))
|
||||
}
|
||||
},
|
||||
Primitive::Stream(stream) => match stream.info.get(key) {
|
||||
Some(prim) => prim,
|
||||
None => {
|
||||
return Err(String::from(format!(
|
||||
"Key {} does not exist in Info Dictionary!",
|
||||
key
|
||||
)))
|
||||
}
|
||||
},
|
||||
p => {
|
||||
return Err(String::from(format!(
|
||||
"{} has no String paths!",
|
||||
p.get_debug_name()
|
||||
)))
|
||||
}
|
||||
},
|
||||
_ => return Err(format!("Invalid Step: {}", step.get_key())),
|
||||
})
|
||||
}
|
||||
|
||||
fn retrieve_trailer(file: &CosFile) -> Primitive {
|
||||
let mut updater = FileOptions::uncached().storage();
|
||||
file.trailer.to_primitive(&mut updater).unwrap()
|
||||
}
|
||||
|
||||
fn retrieve_page(page_num: u32, file: &CosFile) -> Result<(Primitive, PathTrace), String> {
|
||||
if page_num <= 0 {
|
||||
return Err("Page 0 does not exist, use 1-based index!".to_string());
|
||||
}
|
||||
let page_rc = t!(file.get_page(page_num - 1));
|
||||
let p_ref = page_rc.get_ref().get_inner();
|
||||
Ok((
|
||||
resolve_p_ref(p_ref, file)?,
|
||||
PathTrace::new(format!("Page{}", page_num), p_ref.id.to_string()),
|
||||
))
|
||||
}
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Step {
|
||||
String(String),
|
||||
@ -530,27 +422,6 @@ impl Step {
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_xref(id: u64, file: &CosFile) -> Result<Primitive, String> {
|
||||
let plain_ref = PlainRef { id, gen: 0 };
|
||||
resolve_p_ref(plain_ref, file)
|
||||
}
|
||||
|
||||
fn resolve_p_ref(plain_ref: PlainRef, file: &CosFile) -> Result<Primitive, String> {
|
||||
file.resolver()
|
||||
.resolve(plain_ref)
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
fn get_file_from_state<'a>(
|
||||
id: &str,
|
||||
session_guard: &'a MutexGuard<Session>,
|
||||
) -> Result<&'a SessionFile, String> {
|
||||
session_guard
|
||||
.files
|
||||
.get(id)
|
||||
.ok_or_else(|| format!("File with id {} does not exist!", id))
|
||||
}
|
||||
|
||||
fn append_path(key: String, path: &Vec<PathTrace>) -> Vec<PathTrace> {
|
||||
let mut new_path = path.clone();
|
||||
let last_jump = new_path.last().unwrap().last_jump.clone();
|
||||
@ -596,6 +467,18 @@ impl PrimitiveModel {
|
||||
}
|
||||
}
|
||||
|
||||
fn as_data(sub_type: String, trace: Vec<PathTrace>) -> PrimitiveModel {
|
||||
PrimitiveModel {
|
||||
key: "Data".to_string(),
|
||||
ptype: "Stream Data".to_string(),
|
||||
sub_type: sub_type,
|
||||
value: "".to_string(),
|
||||
children: Vec::new(),
|
||||
trace: append_path("Data".to_string(), &trace),
|
||||
expanded: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_dict_content(dict: &Dictionary) -> String {
|
||||
let mut result = String::from("{");
|
||||
let mut count = 0;
|
||||
@ -748,11 +631,9 @@ impl PrimitiveTreeView {
|
||||
}
|
||||
}
|
||||
#[tauri::command]
|
||||
fn get_xref_table(id: &str, session: State<Mutex<Session>>) -> Result<XRefTableModel, String> {
|
||||
let session_guard = session
|
||||
.lock()
|
||||
.map_err(|_| "Failed to lock the session mutex.".to_string())?;
|
||||
let file = get_file_from_state(id, &session_guard)?;
|
||||
fn get_xref_table(id: &str, session: State<Session>) -> Result<XRefTableModel, String> {
|
||||
|
||||
let file = session.get_file(id)?;
|
||||
get_xref_table_model_with_file(&file.cos_file)
|
||||
}
|
||||
fn get_xref_table_model_with_file(file: &CosFile) -> Result<XRefTableModel, String> {
|
||||
@ -812,7 +693,7 @@ fn get_xref_table_model_with_file(file: &CosFile) -> Result<XRefTableModel, Stri
|
||||
}
|
||||
|
||||
struct Session {
|
||||
files: HashMap<String, SessionFile>,
|
||||
files: RwLock<HashMap<String, Arc<SessionFile>>>,
|
||||
}
|
||||
|
||||
struct SessionFile {
|
||||
@ -820,30 +701,48 @@ struct SessionFile {
|
||||
cos_file: CosFile,
|
||||
}
|
||||
|
||||
unsafe impl Send for SessionFile {}
|
||||
unsafe impl Sync for SessionFile {}
|
||||
|
||||
impl Session {
|
||||
fn load() -> Session {
|
||||
Session {
|
||||
files: HashMap::new(),
|
||||
files: RwLock::new(HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_upload(&mut self, pdf_file: &PdfFile, cos_file: CosFile) {
|
||||
self.files.insert(
|
||||
pdf_file.id.clone(),
|
||||
SessionFile {
|
||||
pdf_file: pdf_file.clone(),
|
||||
cos_file: cos_file,
|
||||
},
|
||||
);
|
||||
fn get_file(&self, id: &str) -> Result<Arc<SessionFile>, String> {
|
||||
let lock = self.files.read().unwrap();
|
||||
lock.get(id).cloned().ok_or(format!(" File {} not found!", id))
|
||||
}
|
||||
|
||||
fn handle_close(&mut self, id: &str) {
|
||||
self.files.remove(id);
|
||||
fn get_all_files(&self) -> Vec<Arc<SessionFile>> {
|
||||
self.files.read().unwrap().values().map(|f| f.clone()).collect()
|
||||
}
|
||||
|
||||
fn get_all_file_ids(&self) -> Vec<String> {
|
||||
self.files.read().unwrap().keys().map(|f| f.clone()).collect()
|
||||
}
|
||||
|
||||
fn handle_upload(&self, pdf_file: PdfFile, cos_file: CosFile) -> Result<(), String> {
|
||||
if let Ok(mut files) = self.files.write() {
|
||||
return match files.insert(
|
||||
pdf_file.id.clone(),
|
||||
Arc::new(SessionFile {
|
||||
pdf_file: pdf_file,
|
||||
cos_file: cos_file,
|
||||
},
|
||||
)) {
|
||||
Some(_) => Err("File could not be uploaded!".to_string()),
|
||||
None => Ok(()),
|
||||
}
|
||||
};
|
||||
Err("Lock could not be acquired!".to_string())
|
||||
}
|
||||
|
||||
fn handle_close(&self, id: &str) {
|
||||
self.files.write().unwrap().remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
@ -851,7 +750,7 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.setup(|app| {
|
||||
app.manage(Mutex::new(Session::load()));
|
||||
app.manage(Session::load());
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
@ -864,7 +763,8 @@ pub fn run() {
|
||||
get_prim_tree_by_path,
|
||||
get_xref_table,
|
||||
get_contents,
|
||||
get_stream_data
|
||||
get_stream_data_as_string,
|
||||
get_stream_data_as_image
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
|
||||
@ -3,16 +3,21 @@ extern crate pdf;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use crate::retrieval::{get_image_by_path, get_pdf_stream_by_path_with_file};
|
||||
use crate::{
|
||||
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_xref_table_model_with_file, to_pdf_file, PathTrace, PrimitiveModel, TreeViewRequest,
|
||||
};
|
||||
|
||||
use image::codecs::jpeg::JpegEncoder;
|
||||
use image::imageops::FilterType;
|
||||
use image::ImageFormat;
|
||||
use pdf::content::{display_ops, serialize_ops, Op};
|
||||
use pdf::file::FileOptions;
|
||||
use pdf::object::{Object, ObjectWrite, Page, PlainRef, Resolve};
|
||||
use pdf::primitive::Primitive;
|
||||
use std::io::Cursor;
|
||||
use std::time::Instant;
|
||||
macro_rules! timed {
|
||||
($func_call:expr, $label:expr) => {{
|
||||
@ -107,27 +112,36 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_by_path() {
|
||||
let file = timed!(
|
||||
FileOptions::cached().open(FILE_PATH).unwrap(),
|
||||
FileOptions::cached()
|
||||
.open("/home/kschuettler/Dokumente/TestFiles/402Study.pdf")
|
||||
.unwrap(),
|
||||
"Loading file"
|
||||
);
|
||||
let path = "/Trailer/Root/Pages";
|
||||
let path = "/Page4/Resources/XObject/X13/Data";
|
||||
|
||||
let message = format!("Retrieval of {:?}", path);
|
||||
let prim = timed!(get_prim_model_by_path_with_file(path, &file), message);
|
||||
print_node(prim.unwrap(), 0);
|
||||
match prim {
|
||||
Ok(prim) => print_node(prim, 0),
|
||||
Err(e) => println!("Error: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn print_node(node: PrimitiveModel, depth: usize) {
|
||||
let spaces = " ".repeat(depth);
|
||||
println!("{:?}", node.trace);
|
||||
println!("{}{} | {} | {}", spaces, node.key, node.ptype, node.value);
|
||||
println!(
|
||||
"{}{} | {} | {} | {}",
|
||||
spaces, node.key, node.ptype, node.sub_type, node.value
|
||||
);
|
||||
for child in node.children {
|
||||
print_node(child, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_trailer() {
|
||||
let file = timed!(
|
||||
@ -198,6 +212,44 @@ mod tests {
|
||||
get_stream_data_by_path_with_file("1/Contents/1/Data", &file).unwrap(),
|
||||
"get content 1"
|
||||
);
|
||||
println!("{}", content1);
|
||||
println!("{}", String::from_utf8(content1).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_img_by_path() {
|
||||
let file = timed!(
|
||||
FileOptions::cached()
|
||||
.open("/home/kschuettler/Dokumente/TestFiles/402Study.pdf")
|
||||
.unwrap(),
|
||||
"Loading file"
|
||||
);
|
||||
let path = "/Page4/Resources/XObject/X13/Data";
|
||||
|
||||
let img = timed!(get_image_by_path(path, &file), "get image").expect("Failed to get image");
|
||||
let w = img.width();
|
||||
let h = img.height();
|
||||
let bytes = (h * w * 3) as usize;
|
||||
println!("Image Dimensions: {}-{}", w, h);
|
||||
println!("Image Bytes: {}", bytes);
|
||||
let mut writer = Cursor::new(Vec::new());
|
||||
timed!(
|
||||
img.write_to(&mut writer, ImageFormat::Jpeg),
|
||||
"save image jpeg"
|
||||
);
|
||||
let size = writer.get_ref().len();
|
||||
println!(
|
||||
"size: {} bytes {}% compression",
|
||||
size,
|
||||
100.0 - ((size as f32 / bytes as f32) * 100.0)
|
||||
);
|
||||
let mut writer = Cursor::new(Vec::new());
|
||||
let mut enc = JpegEncoder::new_with_quality(&mut writer, 10);
|
||||
timed!(enc.encode_image(&img), "encode image jpeg 10");
|
||||
let size = writer.get_ref().len();
|
||||
println!(
|
||||
"size: {} bytes {}% compression",
|
||||
size,
|
||||
100.0 - ((size as f32 / bytes as f32) * 100.0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import WelcomeScreen from "./WelcomeScreen.svelte";
|
||||
import { Pane, Splitpanes } from "svelte-splitpanes";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {Pane, Splitpanes} from "svelte-splitpanes";
|
||||
import {open} from "@tauri-apps/plugin-dialog";
|
||||
import {invoke} from "@tauri-apps/api/core";
|
||||
import type PdfFile from "../models/PdfFile";
|
||||
import ToolbarLeft from "./ToolbarLeft.svelte";
|
||||
import FileView from "./FileView.svelte";
|
||||
@ -11,16 +11,16 @@
|
||||
import TitleBar from "./TitleBar.svelte";
|
||||
import ToolbarRight from "./ToolbarRight.svelte";
|
||||
import Footer from "./Footer.svelte";
|
||||
import NotificationModal from "./NotificationModal.svelte";
|
||||
|
||||
const footerHeight: number = 30;
|
||||
const titleBarHeight: number = 30;
|
||||
const tabBarHeight: number = 30;
|
||||
let files: PdfFile[] = $state([]);
|
||||
let innerHeight: number = $state(1060);
|
||||
let errorMessage: string = $state("");
|
||||
let xrefTableWidth: number = $state(0);
|
||||
let xrefTableShowing: boolean = $state(false);
|
||||
let treeShowing: boolean = $state(true);
|
||||
let notificationsShowing: boolean = $state(false);
|
||||
let pagesShowing: boolean = $state(false);
|
||||
let fileViewHeight: number = $derived(
|
||||
Math.max(innerHeight - footerHeight - tabBarHeight - titleBarHeight, 0),
|
||||
@ -41,7 +41,6 @@
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMessage = error;
|
||||
console.error("File retrieval failed: " + error);
|
||||
});
|
||||
}
|
||||
@ -60,13 +59,13 @@
|
||||
let file_path = await open({
|
||||
multiple: false,
|
||||
directory: false,
|
||||
filters: [{ name: "pdf", extensions: ["pdf"] }],
|
||||
filters: [{name: "pdf", extensions: ["pdf"]}],
|
||||
});
|
||||
if (file_path === null || Array.isArray(file_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
invoke<String>("upload", { path: file_path })
|
||||
invoke<String>("upload", {path: file_path})
|
||||
.then((result) => {
|
||||
invoke<PdfFile[]>("get_all_files")
|
||||
.then((result_list) => {
|
||||
@ -78,12 +77,10 @@
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMessage = error;
|
||||
console.error("Fetch all files failed with: " + error);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
errorMessage = error;
|
||||
console.error("File upload failed with: " + error);
|
||||
});
|
||||
}
|
||||
@ -93,7 +90,7 @@
|
||||
}
|
||||
|
||||
function closeFile(file: PdfFile) {
|
||||
invoke("close_file", { id: file.id })
|
||||
invoke("close_file", {id: file.id})
|
||||
.then((_) => {
|
||||
files = files.filter((f) => f.id != file.id);
|
||||
if (file === selected_file) {
|
||||
@ -104,7 +101,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window bind:innerHeight />
|
||||
<svelte:window bind:innerHeight/>
|
||||
<main style="height: {innerHeight}px; overflow: hidden;">
|
||||
<div style="height: {titleBarHeight}px">
|
||||
<TitleBar></TitleBar>
|
||||
@ -112,31 +109,29 @@
|
||||
<div class="fileview_container" style="height: {fileViewHeight + 30}px">
|
||||
<Splitpanes theme="forge-movable" dblClickSplitter={false}>
|
||||
<Pane size={2.5} minSize={1.5} maxSize={4}>
|
||||
<ToolbarLeft bind:tree={treeShowing} bind:pages={pagesShowing}
|
||||
<ToolbarLeft {fState}
|
||||
></ToolbarLeft>
|
||||
</Pane>
|
||||
<Pane>
|
||||
<TabBar
|
||||
{files}
|
||||
{selected_file}
|
||||
closeTab={closeFile}
|
||||
openTab={upload}
|
||||
selectTab={selectFile}
|
||||
{files}
|
||||
{selected_file}
|
||||
closeTab={closeFile}
|
||||
openTab={upload}
|
||||
selectTab={selectFile}
|
||||
></TabBar>
|
||||
{#if fState}
|
||||
<FileView
|
||||
{treeShowing}
|
||||
{xrefTableShowing}
|
||||
{pagesShowing}
|
||||
{fState}
|
||||
height={fileViewHeight}
|
||||
{fState}
|
||||
height={fileViewHeight}
|
||||
></FileView>
|
||||
{:else}
|
||||
<WelcomeScreen {upload}></WelcomeScreen>
|
||||
{/if}
|
||||
</Pane>
|
||||
|
||||
<Pane size={2.5} minSize={1.5} maxSize={4}>
|
||||
<ToolbarRight bind:xref={xrefTableShowing}></ToolbarRight>
|
||||
<ToolbarRight {fState}></ToolbarRight>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
@ -168,102 +163,102 @@
|
||||
}
|
||||
|
||||
:global(.splitpanes.forge-movable)
|
||||
:global(.splitpanes__splitter:hover:before),
|
||||
:global(.splitpanes__splitter:hover:before),
|
||||
:global(.splitpanes.forge-movable)
|
||||
:global(.splitpanes__splitter:hover:after) {
|
||||
:global(.splitpanes__splitter:hover:after) {
|
||||
background-color: var(--boundary-color);
|
||||
}
|
||||
|
||||
:global(.splitpanes.forge-movable)
|
||||
:global(.splitpanes__splitter:first-child) {
|
||||
:global(.splitpanes__splitter:first-child) {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes)
|
||||
:global(.splitpanes)
|
||||
:global(.splitpanes__splitter) {
|
||||
:global(.splitpanes)
|
||||
:global(.splitpanes__splitter) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter),
|
||||
> :global(.splitpanes__splitter),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter) {
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter) {
|
||||
width: 2px;
|
||||
border-left: 1px solid var(--boundary-color);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before) {
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:before) {
|
||||
margin-left: -2px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
:global(.splitpanes--vertical)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
margin-left: 1px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter),
|
||||
> :global(.splitpanes__splitter),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter) {
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter) {
|
||||
height: 2px;
|
||||
border-top: 1px solid var(--boundary-color);
|
||||
cursor: row-resize;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
transform: translateX(-50%);
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before),
|
||||
> :global(.splitpanes__splitter:before),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before) {
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:before) {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
:global(.forge-movable.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after),
|
||||
> :global(.splitpanes__splitter:after),
|
||||
:global(.forge-movable)
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
:global(.splitpanes--horizontal)
|
||||
> :global(.splitpanes__splitter:after) {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
|
||||
@ -1,34 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {invoke} from "@tauri-apps/api/core";
|
||||
import type ContentModel from "../models/ContentModel.svelte";
|
||||
import { onMount } from "svelte";
|
||||
import {onMount} from "svelte";
|
||||
import * as monaco from "monaco-editor";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import StreamEditor from "./StreamEditor.svelte";
|
||||
|
||||
let { fState, height }: { fState: FileViewState; height: number } =
|
||||
let {fState, height}: { fState: FileViewState; height: number } =
|
||||
$props();
|
||||
let h = $derived(height);
|
||||
let path = $derived(fState.prim?.getFirstJump()?.toString());
|
||||
let path = $derived(fState.container_prim?.getFirstJump()?.toString());
|
||||
let id = $derived(fState.file.id);
|
||||
let contents: ContentModel | 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();
|
||||
};
|
||||
});
|
||||
let contentsModel: ContentModel | undefined = $state(undefined);
|
||||
let contents: string | undefined = $derived(mapToString(contentsModel));
|
||||
|
||||
$effect(() => {
|
||||
loadContents(path, id);
|
||||
@ -37,36 +21,41 @@
|
||||
function loadContents(path: string | undefined, id: string) {
|
||||
if (!path || !id) return;
|
||||
|
||||
invoke<ContentModel>("get_contents", { id, path })
|
||||
invoke<ContentModel>("get_contents", {id, path})
|
||||
.then((result) => {
|
||||
contents = result;
|
||||
if (contents && editor) {
|
||||
let text = "";
|
||||
if (contents.parts.length > 1) {
|
||||
let i = 0;
|
||||
for (let part of contents.parts) {
|
||||
text +=
|
||||
"%----------------% Contents[" +
|
||||
i +
|
||||
"] %--------------%\n\n";
|
||||
for (let line of part) {
|
||||
text += " " + line + "\n";
|
||||
}
|
||||
text +=
|
||||
"\n%-------------------% EOF %-------------------%\n\n";
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
text = contents.parts[0].join("\n");
|
||||
}
|
||||
editor.setValue(text);
|
||||
}
|
||||
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>
|
||||
|
||||
<div bind:this={editorContainer} style="height: {h}px; width: 100%;"></div>
|
||||
<StreamEditor stream_data={contents} height={height}></StreamEditor>
|
||||
|
||||
<style lang="postcss">
|
||||
</style>
|
||||
|
||||
@ -1,29 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from "svelte-splitpanes";
|
||||
import {Pane, Splitpanes} from "svelte-splitpanes";
|
||||
import XRefTable from "./XRefTable.svelte";
|
||||
import PrimitiveView from "./PrimitiveView.svelte";
|
||||
import TreeView from "./TreeView.svelte";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import ContentsView from "./ContentsView.svelte";
|
||||
import type { PathSelectedEvent } from "../events/PathSelectedEvent";
|
||||
import { onMount } from "svelte";
|
||||
import type {PathSelectedEvent} from "../events/PathSelectedEvent";
|
||||
import {onMount} from "svelte";
|
||||
import NotificationModal from "./NotificationModal.svelte";
|
||||
|
||||
let {
|
||||
treeShowing,
|
||||
xrefTableShowing,
|
||||
pagesShowing,
|
||||
fState,
|
||||
height,
|
||||
}: {
|
||||
treeShowing: boolean;
|
||||
xrefTableShowing: boolean;
|
||||
pagesShowing: boolean;
|
||||
fState: FileViewState;
|
||||
height: number;
|
||||
} = $props();
|
||||
|
||||
let width: number = $state(0);
|
||||
let xRefTableWidth = $derived(xrefTableShowing ? 281 : 0);
|
||||
let splitPanesWidth: number = $derived(width - xRefTableWidth);
|
||||
let modalWidth = $derived(fState.xRefShowing || fState.notificationsShowing ? 281 : 0);
|
||||
let splitPanesWidth: number = $derived(width - modalWidth);
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
// Check for "Alt + Left Arrow"
|
||||
@ -48,6 +44,7 @@
|
||||
window.removeEventListener("mousedown", handleMouseButton);
|
||||
};
|
||||
});
|
||||
|
||||
function pathSelectedHandler(event: PathSelectedEvent) {
|
||||
fState.selectPathHandler(event);
|
||||
}
|
||||
@ -55,37 +52,42 @@
|
||||
|
||||
<div bind:clientWidth={width} class="file-view-container">
|
||||
<div
|
||||
class="flex flex-row"
|
||||
style="width: {splitPanesWidth}px; height: {height}px"
|
||||
class="flex flex-row"
|
||||
style="width: {splitPanesWidth}px; height: {height}px"
|
||||
>
|
||||
<Splitpanes theme="forge-movable" pushOtherPanes={false}>
|
||||
<Pane
|
||||
size={treeShowing || pagesShowing ? 15 : 0}
|
||||
minSize={treeShowing || pagesShowing ? 2 : 0}
|
||||
maxSize={treeShowing || pagesShowing ? 100 : 0}
|
||||
size={fState.pageMode || fState.treeMode ? 15 : 0}
|
||||
minSize={fState.pageMode || fState.treeMode ? 2 : 0}
|
||||
maxSize={fState.pageMode || fState.treeMode ? 100 : 0}
|
||||
>
|
||||
<TreeView {fState} {height}></TreeView>
|
||||
</Pane>
|
||||
|
||||
<Pane minSize={1}>
|
||||
<div>
|
||||
<PrimitiveView {fState} {height}></PrimitiveView>
|
||||
<div class="bg-forge-dark w-full" style="height: {height}px">
|
||||
{#if fState.treeMode}
|
||||
<PrimitiveView {fState} {height}></PrimitiveView>
|
||||
{/if}
|
||||
{#if fState.pageMode}
|
||||
<div class="overflow-hidden">
|
||||
<ContentsView {fState} {height}></ContentsView>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
{#if fState.prim?.isPage()}
|
||||
<Pane minSize={1}>
|
||||
<div class="overflow-hidden">
|
||||
<ContentsView {fState} {height}></ContentsView>
|
||||
</div>
|
||||
</Pane>
|
||||
{/if}
|
||||
</Splitpanes>
|
||||
</div>
|
||||
{#if xrefTableShowing}
|
||||
<div class="xref-modal" class:visible={xrefTableShowing}>
|
||||
{#if fState.xRefShowing}
|
||||
<div class="xref-modal" class:visible={fState.xRefShowing}>
|
||||
<XRefTable {fState} {height}></XRefTable>
|
||||
</div>
|
||||
{/if}
|
||||
{#if fState.notificationsShowing}
|
||||
<div class="xref-modal" class:visible={fState.notificationsShowing}>
|
||||
<NotificationModal {fState}></NotificationModal>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
footerHeight,
|
||||
}: { fState: FileViewState | undefined; footerHeight: number } = $props();
|
||||
let elements: Path[] | undefined = $derived(
|
||||
fState && fState.prim ? toElements(fState.prim.trace) : undefined,
|
||||
fState && fState.container_prim ? toElements(fState.container_prim.trace) : undefined,
|
||||
);
|
||||
|
||||
function toElements(path: Trace[]): Path[] {
|
||||
|
||||
49
src/components/NotificationModal.svelte
Normal file
49
src/components/NotificationModal.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 notifications = $derived(fState.notifications);
|
||||
</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">
|
||||
Notifications:
|
||||
</div>
|
||||
<button class="clear" onclick={ () => fState.clearNotifications()}>
|
||||
<div class="img">
|
||||
<TrashBinSolid size="sm"/>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
{#each notifications as notification}
|
||||
<div class="text-xs flex flex-row">
|
||||
<small class="whitespace-nowrap">{notification.getDate() + ":"}</small>  <p class:error={notification.isError()} class:debug={notification.isDebug()}> {notification.level} </p>  
|
||||
<p> {" - " + notification.message}</p>
|
||||
</div>
|
||||
<div class="bg-forge-bound mt-1 mb-1" style="height: 1px; width: 100%"></div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
<style lang="postcss">
|
||||
.error {
|
||||
@apply text-red-600;
|
||||
}
|
||||
|
||||
.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,20 +1,19 @@
|
||||
<script lang="ts">
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import type Primitive from "../models/Primitive.svelte";
|
||||
import ContentsView from "./ContentsView.svelte";
|
||||
import PrimitiveIcon from "./PrimitiveIcon.svelte";
|
||||
import StreamEditor from "./StreamEditor.svelte";
|
||||
import StreamDataView from "./StreamDataView.svelte";
|
||||
|
||||
const cellH = 29;
|
||||
const headerOffset = 24;
|
||||
let { fState, height }: { fState: FileViewState; height: number } =
|
||||
const headerOffset = 0;
|
||||
let {fState, height}: { fState: FileViewState; height: number } =
|
||||
$props();
|
||||
let fillerHeight: number = $state(0);
|
||||
|
||||
let firstEntry = $state(0);
|
||||
let lastEntry = $state(100);
|
||||
let scrollY = $state(0);
|
||||
let prim = $derived(fState.prim);
|
||||
let prim = $derived(fState.container_prim);
|
||||
let entriesToDisplay: Primitive[] = $derived(
|
||||
prim ? prim.children.slice(firstEntry, lastEntry) : [],
|
||||
);
|
||||
@ -22,19 +21,17 @@
|
||||
|
||||
let bodyHeight = $derived(height - headerOffset);
|
||||
let editorHeight = $derived(Math.max(800, bodyHeight - tableHeight));
|
||||
let imageUrl = "";
|
||||
|
||||
// Example: Simulating a binary Uint8Array (normally fetched from an API)
|
||||
let binaryData = new Uint8Array([fState.stream_data]);
|
||||
let locallySelected: Primitive | undefined = $state(undefined);
|
||||
$effect(() => {
|
||||
locallySelected = fState.selected_leaf_prim;
|
||||
});
|
||||
|
||||
if (binaryData.length > 0) {
|
||||
const blob = new Blob([binaryData], { type: "image/png" });
|
||||
imageUrl = URL.createObjectURL(blob);
|
||||
}
|
||||
function handlePrimSelect(prim: Primitive) {
|
||||
const _path: string[] = fState.copyPath();
|
||||
_path.push(prim?.key);
|
||||
fState.selectPath(_path);
|
||||
function handlePrimClick(prim: Primitive) {
|
||||
locallySelected = prim;
|
||||
if (!prim.isContainer()) {
|
||||
handlePrimDbLClick(prim);
|
||||
}
|
||||
}
|
||||
|
||||
function handleScroll(event: Event & { currentTarget: HTMLElement }) {
|
||||
@ -44,70 +41,61 @@
|
||||
|
||||
fillerHeight = firstEntry * cellH;
|
||||
}
|
||||
|
||||
function handlePrimDbLClick(prim: Primitive) {
|
||||
const _path: string[] = fState.copyPath();
|
||||
_path.push(prim.key);
|
||||
fState.selectPath(_path);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if fState.stream_data}
|
||||
<img src={fState.stream_data} />
|
||||
{/if}
|
||||
{#if prim && prim.children && prim.children.length > 0}
|
||||
<div class="overflow-x-auto">
|
||||
<div class="overflow-auto" onscroll={handleScroll}
|
||||
style="height: {bodyHeight}px">
|
||||
<div class="w-[851px]">
|
||||
<table>
|
||||
<table style="position: relative; top: {scrollY}px">
|
||||
<thead>
|
||||
<tr>
|
||||
<td class="page-cell t-header border-forge-prim">Key</td
|
||||
>
|
||||
<td class="ref-cell t-header border-forge-prim">Type</td
|
||||
>
|
||||
<td class="cell t-header border-forge-sec">Value</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="page-cell t-header border-forge-prim">Key</td>
|
||||
<td class="ref-cell t-header border-forge-prim">Type</td>
|
||||
<td class="cell t-header border-forge-sec">Value</td>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div
|
||||
onscroll={handleScroll}
|
||||
class="overflow-y-auto"
|
||||
style="height: {bodyHeight}px"
|
||||
>
|
||||
<div class="container" style="height: {tableHeight}px">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr class="filler" style="height: {fillerHeight}px"
|
||||
></tr>
|
||||
{#each entriesToDisplay as entry}
|
||||
<tr
|
||||
class:selected={entry.key ===
|
||||
fState.highlighted_prim}
|
||||
class="row"
|
||||
onclick={() =>
|
||||
(fState.highlighted_prim = entry.key)}
|
||||
ondblclick={() => handlePrimSelect(entry)}
|
||||
>
|
||||
<td class="page-cell t-data">
|
||||
<div class="key-field">
|
||||
<PrimitiveIcon
|
||||
ptype={entry.ptype}
|
||||
/>
|
||||
<p class="text-left">
|
||||
{entry.key}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="ref-cell t-data"
|
||||
>{entry.ptype}</td
|
||||
>
|
||||
<td class="cell t-data">{entry.value}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{#if fState.stream_data}
|
||||
<StreamEditor
|
||||
stream_data={fState.stream_data}
|
||||
height={editorHeight}
|
||||
></StreamEditor>
|
||||
{/if}
|
||||
<div class="container" style="height: {tableHeight}px">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr class="filler" style="height: {fillerHeight}px"
|
||||
></tr>
|
||||
{#each entriesToDisplay as entry}
|
||||
<tr
|
||||
class:selected={entry.key === locallySelected?.key}
|
||||
class="row"
|
||||
onclick={() => handlePrimClick(entry)}
|
||||
ondblclick={() => handlePrimDbLClick(entry)}
|
||||
>
|
||||
<td class="page-cell t-data">
|
||||
<div class="key-field">
|
||||
<PrimitiveIcon
|
||||
ptype={entry.ptype}
|
||||
/>
|
||||
<p class="text-left">
|
||||
{entry.key}
|
||||
</p>
|
||||
</div>
|
||||
</td>
|
||||
<td class="ref-cell t-data"
|
||||
>{entry.ptype}</td
|
||||
>
|
||||
<td class="cell t-data">{entry.value}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{#if fState.container_prim?.stream_data}
|
||||
<StreamDataView {fState} {editorHeight}></StreamDataView>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
27
src/components/StreamDataView.svelte
Normal file
27
src/components/StreamDataView.svelte
Normal file
@ -0,0 +1,27 @@
|
||||
<script lang="ts">
|
||||
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import StreamEditor from "./StreamEditor.svelte";
|
||||
import type {StreamData} from "../models/StreamData";
|
||||
|
||||
let {fState, editorHeight}: { fState: FileViewState, editorHeight: number } = $props()
|
||||
let streamData: StreamData = $derived(fState.container_prim?.stream_data ?);
|
||||
|
||||
|
||||
</script>
|
||||
{#if streamData.type === "Image"}
|
||||
<div class="w-full m-auto">
|
||||
<img alt="x-object" src={streamData.data}/>
|
||||
</div>
|
||||
{:else}
|
||||
<StreamEditor
|
||||
stream_data={streamData.data}
|
||||
height={editorHeight}
|
||||
></StreamEditor>
|
||||
|
||||
{/if}
|
||||
|
||||
<style lang="postcss">
|
||||
|
||||
|
||||
</style>
|
||||
@ -1,9 +1,15 @@
|
||||
<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";
|
||||
|
||||
monaco.editor.defineTheme('forge-dark', {
|
||||
base: "vs-dark",
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {
|
||||
"editor.background": '#1E1F22FF'
|
||||
}
|
||||
});
|
||||
let { stream_data, height }: { stream_data: string; height: number } =
|
||||
$props();
|
||||
|
||||
@ -14,7 +20,7 @@
|
||||
editor = monaco.editor.create(editorContainer, {
|
||||
value: "",
|
||||
language: "plaintext",
|
||||
theme: "vs-dark",
|
||||
theme: "forge-dark",
|
||||
minimap: { enabled: false },
|
||||
scrollBeyondLastLine: false,
|
||||
fontSize: 14,
|
||||
|
||||
@ -1,29 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { BookOpenSolid, CodeBranchSolid } from "flowbite-svelte-icons";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
|
||||
let {
|
||||
tree = $bindable(false),
|
||||
pages = $bindable(false),
|
||||
}: { tree: boolean; pages: boolean } = $props();
|
||||
fState
|
||||
}: { fState: FileViewState | undefined } = $props();
|
||||
|
||||
function toggleTree() {
|
||||
tree = !tree;
|
||||
if (tree) {
|
||||
pages = false;
|
||||
if (!fState) return;
|
||||
fState.treeMode = !fState.treeMode;
|
||||
if (fState.treeMode) {
|
||||
fState.pageMode = false;
|
||||
}
|
||||
}
|
||||
|
||||
function togglePages() {
|
||||
pages = !pages;
|
||||
if (pages) {
|
||||
tree = false;
|
||||
if (!fState) return;
|
||||
fState.pageMode = !fState.pageMode;
|
||||
if (fState.pageMode) {
|
||||
fState.treeMode = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-1">
|
||||
<button
|
||||
class={tree ? "tool-button active" : "tool-button"}
|
||||
class={fState?.treeMode ? "tool-button active" : "tool-button"}
|
||||
onclick={toggleTree}
|
||||
>
|
||||
<div class="justify-center flex m-0">
|
||||
@ -33,7 +35,7 @@
|
||||
</button>
|
||||
<button
|
||||
id="#page"
|
||||
class={pages ? "tool-button active" : "tool-button"}
|
||||
class={fState?.pageMode ? "tool-button active" : "tool-button"}
|
||||
onclick={togglePages}
|
||||
>
|
||||
<div class="justify-center flex">
|
||||
|
||||
@ -1,22 +1,42 @@
|
||||
<script lang="ts">
|
||||
import {ToolbarButton} from "flowbite-svelte";
|
||||
import {ListOutline} from "flowbite-svelte-icons";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
|
||||
let {xref = $bindable(false)}: { xref: boolean } = $props()
|
||||
let {fState}: { fState: FileViewState | undefined } = $props()
|
||||
|
||||
function toggleXref() {
|
||||
xref = !xref;
|
||||
if (!fState) return;
|
||||
if (fState.notificationsShowing) {
|
||||
fState.notificationsShowing = false;
|
||||
}
|
||||
fState.xRefShowing = !fState.xRefShowing;
|
||||
}
|
||||
|
||||
function toggleNots() {
|
||||
if (!fState) return;
|
||||
if (fState.xRefShowing) {
|
||||
fState.xRefShowing = false;
|
||||
}
|
||||
fState.notificationsShowing = !fState.notificationsShowing;
|
||||
}
|
||||
|
||||
</script>
|
||||
<div class="grid grid-cols-1">
|
||||
|
||||
<button class={ xref ? "tool-button active" : "tool-button" } onclick={toggleXref}>
|
||||
<button class={ fState?.xRefShowing ? "tool-button active" : "tool-button" } onclick={toggleXref}>
|
||||
<div class="justify-center flex text-forge-text">
|
||||
<ListOutline/>
|
||||
</div>
|
||||
<b class="button-title">XRef</b>
|
||||
</button>
|
||||
|
||||
<button class={ fState?.notificationsShowing ? "tool-button active" : "tool-button" } onclick={toggleNots}>
|
||||
<div class="justify-center flex text-forge-text">
|
||||
<ListOutline/>
|
||||
</div>
|
||||
<b class="button-title">Notifications</b>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
<script lang="ts">
|
||||
import PrimitiveIcon from "./PrimitiveIcon.svelte";
|
||||
import TreeViewState from "../models/TreeViewState.svelte";
|
||||
import TreeViewRequest from "../models/TreeViewRequest.svelte";
|
||||
import { PathSelectedEvent } from "../events/PathSelectedEvent";
|
||||
import { CaretDownOutline, CaretRightOutline } from "flowbite-svelte-icons";
|
||||
import type { PrimitiveView } from "../models/PrimitiveView";
|
||||
import { onMount } from "svelte";
|
||||
import {PathSelectedEvent} from "../events/PathSelectedEvent";
|
||||
import type FileViewState from "../models/FileViewState.svelte";
|
||||
import type {TreeViewModel} from "../models/TreeViewModel";
|
||||
import TreeViewEntry from "./TreeViewEntry.svelte";
|
||||
|
||||
const rowHeight = 24;
|
||||
let {
|
||||
@ -19,40 +16,45 @@
|
||||
|
||||
const file_id = $derived(fState.file.id);
|
||||
|
||||
let scrollY = $state(0);
|
||||
let scrollContainer: HTMLElement;
|
||||
let fillerHeight: number = $state(0);
|
||||
let states: TreeViewState[] = [];
|
||||
let firstEntry = $state(0);
|
||||
let lastEntry = $state(100);
|
||||
let scrollY = $state(0);
|
||||
let entryCount = $state(0);
|
||||
|
||||
let treeState: TreeViewState | undefined = $state(undefined);
|
||||
|
||||
let totalHeight: number = $state(100);
|
||||
let entries: PrimitiveView[] | undefined = $state([]);
|
||||
let entries: TreeViewModel[] = $state([]);
|
||||
let stickies: TreeViewModel[] = $state([]);
|
||||
|
||||
$effect(() => {
|
||||
console.log("Effect triggered")
|
||||
treeState = states.find((state) => state.file_id === file_id);
|
||||
updateTreeView();
|
||||
});
|
||||
|
||||
|
||||
async function updateTreeView() {
|
||||
if (!treeState || treeState.file_id !== file_id) {
|
||||
console.log("New State")
|
||||
let newState = new TreeViewState(file_id, fState);
|
||||
await newState.loadTreeView();
|
||||
states.push(newState);
|
||||
treeState = newState;
|
||||
totalHeight = treeState.getEntryCount() * rowHeight;
|
||||
}
|
||||
firstEntry = Math.floor(scrollY / rowHeight);
|
||||
lastEntry = Math.ceil((scrollY + height) / rowHeight);
|
||||
entryCount = treeState?.getEntryCount();
|
||||
scrollY = scrollContainer.scrollTop;
|
||||
let firstEntry = Math.floor(scrollY / rowHeight);
|
||||
let lastEntry = Math.ceil((scrollY + height) / rowHeight);
|
||||
let entryCount = treeState?.getEntryCount();
|
||||
totalHeight = Math.max(entryCount * rowHeight, 0);
|
||||
fillerHeight = firstEntry * rowHeight;
|
||||
entries = treeState.getEntries(firstEntry, lastEntry);
|
||||
let entriesAndStickies = treeState.getEntries(firstEntry, lastEntry);
|
||||
entries = entriesAndStickies[0];
|
||||
stickies = entriesAndStickies[1];
|
||||
}
|
||||
|
||||
function handleSelect(prim: PrimitiveView) {
|
||||
function handleSelect(prim: TreeViewModel) {
|
||||
if (prim.expanded && prim.container) {
|
||||
treeState
|
||||
?.collapseTree(prim.path.map((path) => path.key))
|
||||
@ -76,72 +78,42 @@
|
||||
updateTreeView();
|
||||
}
|
||||
|
||||
function formatDisplayKey(key: string) {
|
||||
if (key.startsWith("Page") && !key.startsWith("Pages")) {
|
||||
return key.replace("Page", "Page ");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
function handleScroll(event: Event & { currentTarget: HTMLElement }) {
|
||||
scrollY = event.currentTarget.scrollTop;
|
||||
updateTreeView();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div onscroll={handleScroll} class="overflow-auto" style="height: {height}px">
|
||||
<div bind:this={scrollContainer} onscroll={handleScroll} class="overflow-auto" style="height: {height}px">
|
||||
{#if entries}
|
||||
<div style="height: {totalHeight}px; width: 100%">
|
||||
<div
|
||||
class="filler"
|
||||
style="height: {fillerHeight}px; width: 100%"
|
||||
<table class="border-b-2 border-forge-bound bg-forge-prim" style="position: relative; top: {scrollY}px">
|
||||
<thead>
|
||||
<tr>
|
||||
{#each stickies as entry}
|
||||
{#if entry.active}
|
||||
<TreeViewEntry
|
||||
{entry}
|
||||
onclick={() => handleSelect(entry)}
|
||||
></TreeViewEntry>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
<div class="filler"
|
||||
style="height: {fillerHeight}px; width: 100%"
|
||||
></div>
|
||||
{#each entries as entry}
|
||||
{#if entry.active}
|
||||
{#if entry.depth == 0}
|
||||
<div
|
||||
style="height: 1px; width: 100%;"
|
||||
class="bg-forge-bound"
|
||||
></div>
|
||||
<div>
|
||||
{#each entries as entry}
|
||||
{#if entry.active}
|
||||
<TreeViewEntry
|
||||
onclick={() => handleSelect(entry)}
|
||||
{entry}
|
||||
></TreeViewEntry>
|
||||
{/if}
|
||||
<button
|
||||
class="row text-sm hover:bg-forge-sec w-full group whitespace-nowrap"
|
||||
style="height: {rowHeight}px;"
|
||||
onclick={() => handleSelect(entry)}
|
||||
>
|
||||
<div style="margin-left: {entry.depth * 1.25}em">
|
||||
{#if entry.container}
|
||||
<div>
|
||||
<span
|
||||
class="caret group-hover:text-forge-text_hint"
|
||||
>{#if entry.expanded}<CaretDownOutline
|
||||
/>{:else}<CaretRightOutline
|
||||
/>{/if}</span
|
||||
>
|
||||
</div>
|
||||
{:else}
|
||||
<span class="no-caret"></span>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<PrimitiveIcon ptype={entry.ptype} />
|
||||
</div>
|
||||
<div class="pl-1 prim_name whitespace-nowrap">
|
||||
{formatDisplayKey(entry.key)}
|
||||
<div
|
||||
class="details group-hover:text-forge-text_hint"
|
||||
>
|
||||
{" | " +
|
||||
entry.value +
|
||||
" | " +
|
||||
entry.sub_type +
|
||||
" | " +
|
||||
entry.ptype}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@ -155,12 +127,6 @@
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
.item {
|
||||
@apply text-sm rounded-sm;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.caret {
|
||||
@apply text-forge-sec;
|
||||
@ -170,21 +136,6 @@
|
||||
|
||||
.details {
|
||||
@apply ml-1 text-forge-prim whitespace-nowrap font-extralight;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.item {
|
||||
@apply text-sm rounded;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.small {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.row {
|
||||
|
||||
93
src/components/TreeViewEntry.svelte
Normal file
93
src/components/TreeViewEntry.svelte
Normal file
@ -0,0 +1,93 @@
|
||||
<script lang="ts">
|
||||
const rowHeight = 24;
|
||||
|
||||
import PrimitiveIcon from "./PrimitiveIcon.svelte";
|
||||
import type {TreeViewModel} from "../models/TreeViewModel";
|
||||
import {CaretDownOutline, CaretRightOutline} from "flowbite-svelte-icons";
|
||||
|
||||
let {entry, onclick = undefined}: { entry: TreeViewModel, onclick: any } = $props();
|
||||
|
||||
|
||||
function formatDisplayKey(key: string) {
|
||||
if (key.startsWith("Page") && !key.startsWith("Pages")) {
|
||||
return key.replace("Page", "Page ");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
</script>
|
||||
{#if entry.depth == 0}
|
||||
<div
|
||||
style="height: 1px; width: 100%;"
|
||||
class="bg-forge-bound"
|
||||
></div>
|
||||
{/if}
|
||||
<button
|
||||
class="row text-sm hover:bg-forge-sec w-full group whitespace-nowrap"
|
||||
style="height: {rowHeight}px;"
|
||||
onclick={onclick}
|
||||
>
|
||||
<div style="margin-left: {entry.depth * 1.25}em">
|
||||
{#if entry.container}
|
||||
<div>
|
||||
<span
|
||||
class="caret group-hover:text-forge-text_hint"
|
||||
>{#if entry.expanded}<CaretDownOutline
|
||||
/>{:else}<CaretRightOutline
|
||||
/>{/if}</span
|
||||
>
|
||||
</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>
|
||||
|
||||
<style lang="postcss">
|
||||
.prim_name {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
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>
|
||||
@ -1,20 +1,30 @@
|
||||
import type PdfFile from "./PdfFile";
|
||||
import type XRefEntry from "./XRefEntry";
|
||||
import Primitive from "./Primitive.svelte";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import TreeViewRequest from "./TreeViewRequest.svelte";
|
||||
import {invoke} from "@tauri-apps/api/core";
|
||||
import type XRefTable from "./XRefTable";
|
||||
import TreeViewState from "./TreeViewState.svelte";
|
||||
import type { PathSelectedEvent } from "../events/PathSelectedEvent";
|
||||
import type {PathSelectedEvent} from "../events/PathSelectedEvent";
|
||||
import type {PrimitiveModel} from "./PrimitiveModel";
|
||||
import {StreamData} from "./StreamData";
|
||||
import {ForgeNotification} from "./ForgeNotification.svelte";
|
||||
import {Mutex} from 'async-mutex';
|
||||
|
||||
export default class FileViewState {
|
||||
public file: PdfFile;
|
||||
|
||||
public treeMode: boolean = $state(true);
|
||||
public pageMode: boolean = $state(false);
|
||||
public xRefShowing: boolean = $state(true);
|
||||
public notificationsShowing: boolean = $state(false);
|
||||
|
||||
|
||||
public path: string[] = $state(["Trailer"]);
|
||||
public file: PdfFile;
|
||||
public prim: Primitive | undefined = $state();
|
||||
public highlighted_prim: string | undefined = $state();
|
||||
public container_prim: Primitive | undefined = $state();
|
||||
public selected_leaf_prim: Primitive | undefined = $state();
|
||||
public xref_entries: XRefEntry[] = $state([]);
|
||||
public stream_data: string | undefined = $state();
|
||||
|
||||
public notifications: ForgeNotification[] = $state([]);
|
||||
notificationMutex = new Mutex();
|
||||
|
||||
constructor(file: PdfFile) {
|
||||
|
||||
@ -24,48 +34,92 @@ export default class FileViewState {
|
||||
}
|
||||
|
||||
getLastJump(): string | number | undefined {
|
||||
return this.prim?.getLastJump()
|
||||
return this.container_prim?.getLastJump()
|
||||
}
|
||||
|
||||
getFirstJump(): string | number | undefined {
|
||||
return this.prim?.getFirstJump()
|
||||
return this.container_prim?.getFirstJump()
|
||||
}
|
||||
|
||||
public async logError(message: string) {
|
||||
const release = await this.notificationMutex.acquire();
|
||||
try {
|
||||
console.error(message)
|
||||
this.notifications.push(new ForgeNotification(Date.now(), "ERROR", message));
|
||||
} finally {
|
||||
release(); // Always release the lock
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteNotification(timestamp: number) {
|
||||
const release = await this.notificationMutex.acquire();
|
||||
try {
|
||||
this.notifications = this.notifications.filter(n => n.timestamp != timestamp);
|
||||
} finally {
|
||||
release(); // Always release the lock
|
||||
}
|
||||
}
|
||||
|
||||
public async clearNotifications() {
|
||||
const release = await this.notificationMutex.acquire();
|
||||
try {
|
||||
this.notifications = [];
|
||||
} finally {
|
||||
release(); // Always release the lock
|
||||
}
|
||||
}
|
||||
|
||||
public loadXrefEntries() {
|
||||
invoke<XRefTable>("get_xref_table", { id: this.file.id })
|
||||
invoke<XRefTable>("get_xref_table", {id: this.file.id})
|
||||
.then(result => {
|
||||
this.xref_entries = result.entries;
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
.catch(err => this.logError(err));
|
||||
}
|
||||
|
||||
public async selectPath(newPath: string[]) {
|
||||
if (newPath.at(-1) === "Data") {
|
||||
let _path = newPath.slice(0, newPath.length - 1)
|
||||
const _prim = await invoke<Primitive>("get_prim_by_path", { id: this.file.id, path: this.formatPaths(_path) })
|
||||
this.stream_data = await invoke<string>("get_stream_data", { id: this.file.id, path: this.formatPaths(newPath) })
|
||||
this.prim = new Primitive(_prim);
|
||||
this.path = _path;
|
||||
this.highlighted_prim = "Data";
|
||||
if (this.container_prim?.pathEquals(newPath)) {
|
||||
return;
|
||||
} else {
|
||||
this.stream_data = undefined;
|
||||
}
|
||||
invoke<Primitive>("get_prim_by_path", { id: this.file.id, path: this.formatPaths(newPath) })
|
||||
.then(result => {
|
||||
let _prim = new Primitive(result)
|
||||
if (_prim.isContainer()) {
|
||||
this.prim = _prim;
|
||||
this.path = newPath
|
||||
return;
|
||||
} else {
|
||||
this.highlighted_prim = _prim.key;
|
||||
this.selectPath(newPath.slice(0, newPath.length - 1))
|
||||
}
|
||||
})
|
||||
.catch(err => console.error(err));
|
||||
const result = await invoke<PrimitiveModel>("get_prim_by_path", {
|
||||
id: this.file.id,
|
||||
path: this.formatPaths(newPath)
|
||||
}).catch(err => this.logError(err))
|
||||
if (!result) return;
|
||||
|
||||
let newPrim = new Primitive(result)
|
||||
|
||||
if (newPrim.isContainer()) {
|
||||
this.container_prim = newPrim;
|
||||
this.path = newPath
|
||||
return;
|
||||
}
|
||||
this.selected_leaf_prim = newPrim;
|
||||
await this.selectPath(newPath.slice(0, newPath.length - 1)).catch(err => this.logError(err));
|
||||
await this.loadStreamData(this.container_prim, newPrim,).catch(err => this.logError(err));
|
||||
}
|
||||
|
||||
public async loadStreamData(container: Primitive | undefined, leaf: Primitive | undefined) {
|
||||
if (!container || !leaf) return;
|
||||
if (leaf.ptype === "Stream Data" &&
|
||||
leaf.isChildOf(container)
|
||||
) {
|
||||
const path = this.formatPaths(this.path) + "/Data";
|
||||
if (leaf.sub_type === "Image") {
|
||||
const data = await invoke<string>("get_stream_data_as_image", {id: this.file.id, path: path})
|
||||
.catch(err => this.logError(err))
|
||||
if (!data) return;
|
||||
container.stream_data = new StreamData(leaf.sub_type, data);
|
||||
} else {
|
||||
const data = await invoke<string>("get_stream_data_as_string", {id: this.file.id, path: path})
|
||||
.catch(err => this.logError(err))
|
||||
if (!data) return;
|
||||
container.stream_data = new StreamData(leaf.sub_type, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public selectPathHandler(event: PathSelectedEvent) {
|
||||
|
||||
if (event.detail.file_id !== this.file.id) {
|
||||
|
||||
27
src/models/ForgeNotification.svelte.ts
Normal file
27
src/models/ForgeNotification.svelte.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export class ForgeNotification {
|
||||
public timestamp: number;
|
||||
date: Date;
|
||||
public level: string;
|
||||
public message: string;
|
||||
public read: boolean = false;
|
||||
constructor(timestamp: number, level: string, message: string) {
|
||||
this.timestamp = timestamp;
|
||||
this.level = level;
|
||||
this.message = message;
|
||||
this.date = new Date(timestamp);
|
||||
}
|
||||
|
||||
public getDate(): string {
|
||||
const hours = this.date.getHours().toString().padStart(2, '0');
|
||||
const minutes = this.date.getMinutes().toString().padStart(2, '0');
|
||||
const seconds = this.date.getSeconds().toString().padStart(2, '0');
|
||||
return `${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
public isError() {
|
||||
return this.level === "ERROR";
|
||||
}
|
||||
public isDebug() {
|
||||
return this.level === "DEBUG";
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,9 @@
|
||||
import {tracesAreEqual, tracesAreEqual2} from "../utils";
|
||||
import type {PrimitiveModel} from "./PrimitiveModel";
|
||||
import type {StreamData} from "./StreamData";
|
||||
|
||||
export default class Primitive {
|
||||
// input from api
|
||||
public key: string;
|
||||
public ptype: string;
|
||||
public sub_type: string;
|
||||
@ -7,8 +12,11 @@ export default class Primitive {
|
||||
public trace: Trace[] = $state([]);
|
||||
public expanded: boolean = $state(false);
|
||||
|
||||
//local state
|
||||
public stream_data: StreamData | undefined = $state();
|
||||
|
||||
constructor(
|
||||
p: Primitive
|
||||
p: PrimitiveModel
|
||||
) {
|
||||
this.key = p.key;
|
||||
this.ptype = p.ptype;
|
||||
@ -33,9 +41,19 @@ export default class Primitive {
|
||||
return this.trace.map(path => path.key);
|
||||
}
|
||||
|
||||
public traceEquals(trace: Trace[]): boolean {
|
||||
return tracesAreEqual(trace, this.trace)
|
||||
}
|
||||
public pathEquals(path: string[]): boolean {
|
||||
return tracesAreEqual2(this.trace, path)
|
||||
}
|
||||
|
||||
public getLastJump(): string | number {
|
||||
let path = this.trace[this.trace.length - 1].last_jump;
|
||||
if (path === "/") { return path };
|
||||
if (path === "/") {
|
||||
return path
|
||||
}
|
||||
;
|
||||
return +path;
|
||||
}
|
||||
|
||||
@ -52,8 +70,14 @@ export default class Primitive {
|
||||
|
||||
public getFirstJump(): string | number | undefined {
|
||||
let path = this.trace[0].last_jump;
|
||||
if (path === "Trailer") { return path };
|
||||
if (path.startsWith("Page")) { return path };
|
||||
if (path === "Trailer") {
|
||||
return path
|
||||
}
|
||||
;
|
||||
if (path.startsWith("Page")) {
|
||||
return path
|
||||
}
|
||||
;
|
||||
return +path;
|
||||
}
|
||||
|
||||
@ -64,6 +88,13 @@ export default class Primitive {
|
||||
expanded: false,
|
||||
});
|
||||
}
|
||||
|
||||
public isChildOf(prim: Primitive | undefined): boolean {
|
||||
if (!prim) {
|
||||
return false;
|
||||
}
|
||||
return tracesAreEqual(this.trace.slice(0, -1), prim.trace);
|
||||
}
|
||||
}
|
||||
|
||||
export interface Trace {
|
||||
|
||||
11
src/models/PrimitiveModel.ts
Normal file
11
src/models/PrimitiveModel.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type {Trace} from "./Primitive.svelte"
|
||||
|
||||
export interface PrimitiveModel {
|
||||
readonly key: string,
|
||||
readonly ptype: string,
|
||||
readonly sub_type: string,
|
||||
readonly value: string,
|
||||
readonly children: PrimitiveModel[],
|
||||
readonly trace: Trace[],
|
||||
readonly expanded: boolean,
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import type { Trace } from "./Primitive.svelte";
|
||||
|
||||
export class PrimitiveView {
|
||||
constructor(
|
||||
public depth: number,
|
||||
public key: string,
|
||||
public ptype: string,
|
||||
public sub_type: string,
|
||||
public value: string,
|
||||
public container: boolean,
|
||||
public expanded: boolean,
|
||||
public path: Trace[],
|
||||
public active: boolean,
|
||||
) { }
|
||||
}
|
||||
9
src/models/StreamData.ts
Normal file
9
src/models/StreamData.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export class StreamData {
|
||||
// Image, Text
|
||||
public type: string;
|
||||
public data: string;
|
||||
constructor(type: string, value: string) {
|
||||
this.type = type;
|
||||
this.data = value;
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,7 @@ export default class TreeViewRequest {
|
||||
}
|
||||
|
||||
static fromPageCount(pageCount: number) {
|
||||
let roots = [TreeViewRequest.TRAILER];
|
||||
let roots = [];
|
||||
for (let i = 0; i < pageCount; i++) {
|
||||
roots.push(new TreeViewRequest("Page" + (i + 1), []));
|
||||
}
|
||||
|
||||
@ -1,19 +1,23 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {invoke} from "@tauri-apps/api/core";
|
||||
import TreeViewRequest from "./TreeViewRequest.svelte";
|
||||
import { PrimitiveView } from "./PrimitiveView";
|
||||
import type { Trace } from "./Primitive.svelte";
|
||||
import {TreeViewModel} from "./TreeViewModel";
|
||||
import type {Trace} from "./Primitive.svelte";
|
||||
import type FileViewState from "./FileViewState.svelte";
|
||||
|
||||
const MAX_STICKIES: number = 5;
|
||||
export default class TreeViewState {
|
||||
private request: TreeViewRequest[];
|
||||
|
||||
private initialRequest: TreeViewRequest[];
|
||||
private activeRequest: Map<string, TreeViewRequest> = $state(new Map());
|
||||
private activeEntries: Map<string, PrimitiveView[]> = $state(new Map());
|
||||
private activeEntries: Map<string, TreeViewModel[]> = $state(new Map());
|
||||
scrollY: number = 0;
|
||||
file_id: string;
|
||||
|
||||
constructor(file_id: string, fState: FileViewState) {
|
||||
|
||||
this.request = TreeViewRequest.fromPageCount(+fState.file.page_count);
|
||||
this.activeRequest.set(this.request[0].key, this.request[0]);
|
||||
this.initialRequest = [TreeViewRequest.TRAILER];
|
||||
this.initialRequest = this.initialRequest.concat(TreeViewRequest.fromPageCount(+fState.file.page_count));
|
||||
this.activeRequest.set(this.initialRequest[0].key, this.initialRequest[0]);
|
||||
this.file_id = file_id;
|
||||
}
|
||||
|
||||
@ -22,28 +26,33 @@ export default class TreeViewState {
|
||||
this.activeEntries.forEach((value, key) => {
|
||||
count += value.length - 1;
|
||||
});
|
||||
return this.request.length + count;
|
||||
return this.initialRequest.length + count;
|
||||
}
|
||||
|
||||
public getEntries(start: number, end: number): PrimitiveView[] {
|
||||
let i = 0;
|
||||
let result: PrimitiveView[] = [];
|
||||
for (let request of this.request) {
|
||||
if (this.activeEntries.has(request.key)) {
|
||||
let entries = this.activeEntries.get(request.key);
|
||||
for (let entry of entries) {
|
||||
if (i >= start) {
|
||||
public getEntries(start: number, end: number): [TreeViewModel[], TreeViewModel[]] {
|
||||
let stickies: TreeViewModel[] = [];
|
||||
let totalIndex = 0;
|
||||
let result: TreeViewModel[] = [];
|
||||
for (let request of this.initialRequest) {
|
||||
let entries = this.activeEntries.get(request.key);
|
||||
if (entries) {
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
let entry = entries[i];
|
||||
if (totalIndex > 0 && totalIndex == start) {
|
||||
stickies = this.findParents(entries, i);
|
||||
}
|
||||
if (totalIndex >= start) {
|
||||
result.push(entry);
|
||||
}
|
||||
i += 1;
|
||||
if (i >= end) {
|
||||
return result;
|
||||
totalIndex += 1;
|
||||
if (totalIndex >= end) {
|
||||
return [result, stickies];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (i >= start) {
|
||||
if (totalIndex >= start) {
|
||||
result.push(
|
||||
new PrimitiveView(
|
||||
new TreeViewModel(
|
||||
0,
|
||||
request.key,
|
||||
"Dictionary",
|
||||
@ -51,21 +60,40 @@ export default class TreeViewState {
|
||||
"-",
|
||||
true,
|
||||
false,
|
||||
[{ key: request.key, last_jump: request.key } as Trace],
|
||||
[{key: request.key, last_jump: request.key} as Trace],
|
||||
true,
|
||||
))
|
||||
}
|
||||
i += 1;
|
||||
if (i >= end) {
|
||||
return result;
|
||||
totalIndex += 1;
|
||||
if (totalIndex >= end) {
|
||||
return [result, stickies];
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return [result, stickies];
|
||||
}
|
||||
|
||||
public toView(request: TreeViewRequest): PrimitiveView {
|
||||
return new PrimitiveView(
|
||||
private findParents(entries: TreeViewModel[], start: number): TreeViewModel[] {
|
||||
|
||||
let parents = [];
|
||||
let lastDepth = entries[start].depth;
|
||||
for (let i = start; i >= 0; i--) {
|
||||
let entry = entries[i];
|
||||
if (entry.depth < lastDepth) {
|
||||
parents.push(entry);
|
||||
if (entry.depth == 0) {
|
||||
return parents.reverse().slice(0, MAX_STICKIES);
|
||||
}
|
||||
lastDepth = entry.depth;
|
||||
|
||||
}
|
||||
}
|
||||
return parents.reverse().slice(0, MAX_STICKIES);
|
||||
}
|
||||
|
||||
|
||||
public toView(request: TreeViewRequest): TreeViewModel {
|
||||
return new TreeViewModel(
|
||||
0,
|
||||
request.key,
|
||||
"Dictionary",
|
||||
@ -73,7 +101,7 @@ export default class TreeViewState {
|
||||
"-",
|
||||
true,
|
||||
false,
|
||||
[{ key: request.key, last_jump: request.key } as Trace],
|
||||
[{key: request.key, last_jump: request.key} as Trace],
|
||||
false,
|
||||
);
|
||||
}
|
||||
@ -85,7 +113,7 @@ export default class TreeViewState {
|
||||
|
||||
public getRoot(): TreeViewRequest[] {
|
||||
let _roots: TreeViewRequest[] = [];
|
||||
this.request.forEach((root) => {
|
||||
this.initialRequest.forEach((root) => {
|
||||
_roots.push(root.clone());
|
||||
});
|
||||
return _roots;
|
||||
@ -93,7 +121,7 @@ export default class TreeViewState {
|
||||
|
||||
public async updateTreeViewRequest(treeViewRequests: TreeViewRequest[]) {
|
||||
|
||||
let result = await invoke<PrimitiveView[]>("get_prim_tree_by_path", {
|
||||
let result = await invoke<TreeViewModel[]>("get_prim_tree_by_path", {
|
||||
id: this.file_id,
|
||||
paths: treeViewRequests,
|
||||
});
|
||||
|
||||
28
src/utils.ts
28
src/utils.ts
@ -1,4 +1,5 @@
|
||||
import type {Action} from "@sveltejs/kit";
|
||||
import type { Action } from "@sveltejs/kit";
|
||||
import type { Trace } from "./models/Primitive.svelte";
|
||||
|
||||
export function arraysAreEqual(arr1: string[], arr2: string[]) {
|
||||
if (arr1.length !== arr2.length) {
|
||||
@ -12,6 +13,31 @@ export function arraysAreEqual(arr1: string[], arr2: string[]) {
|
||||
return true; // All elements match
|
||||
}
|
||||
|
||||
|
||||
export function tracesAreEqual(arr1: Trace[], arr2: Trace[]) {
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false; // Arrays of different lengths are not equal
|
||||
}
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
if (arr1[i].key !== arr2[i].key) {
|
||||
return false; // Mismatched element found
|
||||
}
|
||||
}
|
||||
return true; // All elements match
|
||||
}
|
||||
|
||||
export function tracesAreEqual2(arr1: Trace[], arr2: string[]) {
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false; // Arrays of different lengths are not equal
|
||||
}
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
if (arr1[i].key !== arr2[i]) {
|
||||
return false; // Mismatched element found
|
||||
}
|
||||
}
|
||||
return true; // All elements match
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
@ -603,6 +603,13 @@ aria-query@^5.3.1:
|
||||
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz"
|
||||
integrity sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==
|
||||
|
||||
async-mutex@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.5.0.tgz#353c69a0b9e75250971a64ac203b0ebfddd75482"
|
||||
integrity sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
autoprefixer@^10.4.20:
|
||||
version "10.4.20"
|
||||
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz"
|
||||
@ -1812,7 +1819,7 @@ ts-interface-checker@^0.1.9:
|
||||
resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
|
||||
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||
|
||||
tslib@^2.1.0:
|
||||
tslib@^2.1.0, tslib@^2.4.0:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
|
||||
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user