mirror of
https://github.com/Xevion/byte-me.git
synced 2025-12-05 23:14:31 -06:00
feat: implement TypeScript bindings generation and enhance drop overlay component
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,6 @@
|
|||||||
|
src/bindings/*.ts
|
||||||
|
src-tauri/bindings/*.ts
|
||||||
|
|
||||||
# Seed data
|
# Seed data
|
||||||
.data/*
|
.data/*
|
||||||
!.data/seed.ps1
|
!.data/seed.ps1
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri",
|
||||||
|
"generate-types": "tsx scripts/generate-types.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nivo/core": "^0.99.0",
|
"@nivo/core": "^0.99.0",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
|
"tsx": "^4.19.2",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^6.0.3",
|
"vite": "^6.0.3",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
|
|||||||
2723
pnpm-lock.yaml
generated
2723
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
51
scripts/generate-types.ts
Normal file
51
scripts/generate-types.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
import { copyFileSync, mkdirSync, existsSync, readdirSync } from "fs";
|
||||||
|
import { join, dirname } from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
console.log("🔄 Generating TypeScript bindings...");
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Run the test to generate bindings
|
||||||
|
execSync("cargo test export_bindings", {
|
||||||
|
cwd: "./src-tauri",
|
||||||
|
stdio: "inherit",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existsSync(join(__dirname, "../src-tauri/bindings"))) {
|
||||||
|
throw new Error(
|
||||||
|
"Bindings directory not found. Bindings generation failed or improperly configured.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ TypeScript bindings generated successfully!");
|
||||||
|
|
||||||
|
// Copy bindings to src directory
|
||||||
|
const srcBindingsDir = join(__dirname, "../src/bindings");
|
||||||
|
const files = readdirSync(join(__dirname, "../src-tauri/bindings")).filter(
|
||||||
|
(file) => file.endsWith(".ts"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
throw new Error(
|
||||||
|
"No bindings files found. Bindings generation failed or improperly configured.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
const source = join(__dirname, "../src-tauri/bindings", file);
|
||||||
|
const dest = join(srcBindingsDir, file);
|
||||||
|
copyFileSync(source, dest);
|
||||||
|
console.log(`📁 Copied ${file} to src/bindings/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🎉 All done! TypeScript bindings are up to date.");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Failed to generate TypeScript bindings:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
558
src-tauri/Cargo.lock
generated
558
src-tauri/Cargo.lock
generated
@@ -7,6 +7,10 @@ name = "Inflector"
|
|||||||
version = "0.11.4"
|
version = "0.11.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "addr2line"
|
name = "addr2line"
|
||||||
@@ -23,6 +27,18 @@ version = "2.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@@ -47,6 +63,12 @@ dependencies = [
|
|||||||
"alloc-no-stdlib",
|
"alloc-no-stdlib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-tzdata"
|
name = "android-tzdata"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -68,6 +90,18 @@ version = "1.0.98"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ast_node"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9184f2b369b3e8625712493c89b785881f27eedc6cde480a81883cef78868b2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"swc_macros_common",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-broadcast"
|
name = "async-broadcast"
|
||||||
version = "0.7.2"
|
version = "0.7.2"
|
||||||
@@ -263,6 +297,15 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "better_scoped_tls"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "297b153aa5e573b5863108a6ddc9d5c968bd0b20e75cc614ee9821d2f45679c7"
|
||||||
|
dependencies = [
|
||||||
|
"scoped-tls",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.3.2"
|
version = "1.3.2"
|
||||||
@@ -344,20 +387,22 @@ name = "bumpalo"
|
|||||||
version = "3.19.0"
|
version = "3.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byte-me"
|
name = "byte-me"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
|
"infer",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
|
||||||
"specta-typescript",
|
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-opener",
|
"tauri-plugin-opener",
|
||||||
"tauri-specta",
|
"ts-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -699,6 +744,56 @@ dependencies = [
|
|||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-url"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_ast"
|
||||||
|
version = "0.38.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "584547d27786a734536fde7088f8429d355569c39410427be44695c300618408"
|
||||||
|
dependencies = [
|
||||||
|
"deno_media_type",
|
||||||
|
"deno_terminal",
|
||||||
|
"dprint-swc-ext",
|
||||||
|
"once_cell",
|
||||||
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
|
"swc_atoms",
|
||||||
|
"swc_common",
|
||||||
|
"swc_ecma_ast",
|
||||||
|
"swc_ecma_parser",
|
||||||
|
"swc_eq_ignore_macros",
|
||||||
|
"text_lines",
|
||||||
|
"thiserror 1.0.69",
|
||||||
|
"unicode-width",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_media_type"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8978229b82552bf8457a0125aa20863f023619cfc21ebb007b1e571d68fd85b"
|
||||||
|
dependencies = [
|
||||||
|
"data-url",
|
||||||
|
"serde",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_terminal"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e6337d4e7f375f8b986409a76fbeecfa4bd8a1343e63355729ae4befa058eaf"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deranged"
|
name = "deranged"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -812,6 +907,63 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dprint-core"
|
||||||
|
version = "0.66.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3ab0dd2bedc109d25f0d21afb09b7d329f6c6fa83b095daf31d2d967e091548"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"bumpalo",
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
"indexmap 2.10.0",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dprint-core-macros"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1675ad2b358481f3cc46202040d64ac7a36c4ade414a696df32e0e45421a6e9f"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dprint-plugin-typescript"
|
||||||
|
version = "0.90.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d7c3c339020ebbbbbe5fc049350935ee2ea2ba5a3fc01f753588639a30404cda"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"deno_ast",
|
||||||
|
"dprint-core",
|
||||||
|
"dprint-core-macros",
|
||||||
|
"percent-encoding",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dprint-swc-ext"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "019d17f2c2457c5a70a7cf4505b1a562ca8ab168c0ac0c005744efbd29fcb8fe"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"bumpalo",
|
||||||
|
"num-bigint",
|
||||||
|
"rustc-hash",
|
||||||
|
"swc_atoms",
|
||||||
|
"swc_common",
|
||||||
|
"swc_ecma_ast",
|
||||||
|
"swc_ecma_parser",
|
||||||
|
"text_lines",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dtoa"
|
name = "dtoa"
|
||||||
version = "1.0.10"
|
version = "1.0.10"
|
||||||
@@ -839,6 +991,12 @@ version = "1.0.19"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "embed-resource"
|
name = "embed-resource"
|
||||||
version = "3.0.5"
|
version = "3.0.5"
|
||||||
@@ -1020,6 +1178,17 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "from_variant"
|
||||||
|
version = "0.1.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32016f1242eb82af5474752d00fd8ebcd9004bd69b462b1c91de833972d08ed4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"swc_macros_common",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futf"
|
name = "futf"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
@@ -1427,6 +1596,16 @@ version = "0.12.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"allocator-api2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.4"
|
version = "0.15.4"
|
||||||
@@ -1457,6 +1636,20 @@ version = "0.4.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hstr"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1a26def229ea95a8709dad32868d975d0dd40235bd2ce82920e4a8fe692b5e0"
|
||||||
|
dependencies = [
|
||||||
|
"hashbrown 0.14.5",
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"once_cell",
|
||||||
|
"phf 0.11.3",
|
||||||
|
"rustc-hash",
|
||||||
|
"triomphe",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "html5ever"
|
name = "html5ever"
|
||||||
version = "0.29.1"
|
version = "0.29.1"
|
||||||
@@ -1766,6 +1959,18 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-macro"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d57a3e447e24c22647738e4607f1df1e0ec6f72e16182c4cd199f647cdfb0e4"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is-wsl"
|
name = "is-wsl"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -2121,12 +2326,32 @@ version = "0.1.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -2469,12 +2694,6 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -2788,6 +3007,15 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psm"
|
||||||
|
version = "0.1.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e944464ec8536cd1beb0bbfd96987eb5e3b72f2ecdafdc5c769a37f1fa2ae1f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.38.0"
|
version = "0.38.0"
|
||||||
@@ -3009,6 +3237,12 @@ version = "0.1.25"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -3103,6 +3337,12 @@ dependencies = [
|
|||||||
"syn 2.0.104",
|
"syn 2.0.104",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scoped-tls"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -3351,6 +3591,17 @@ version = "1.15.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smartstring"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"static_assertions",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.10"
|
version = "0.5.10"
|
||||||
@@ -3409,56 +3660,25 @@ dependencies = [
|
|||||||
"system-deps",
|
"system-deps",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "specta"
|
|
||||||
version = "2.0.0-rc.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971"
|
|
||||||
dependencies = [
|
|
||||||
"paste",
|
|
||||||
"specta-macros",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "specta-macros"
|
|
||||||
version = "2.0.0-rc.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517"
|
|
||||||
dependencies = [
|
|
||||||
"Inflector",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.104",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "specta-serde"
|
|
||||||
version = "0.0.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77216504061374659e7245eac53d30c7b3e5fe64b88da97c753e7184b0781e63"
|
|
||||||
dependencies = [
|
|
||||||
"specta",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "specta-typescript"
|
|
||||||
version = "0.0.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3220a0c365e51e248ac98eab5a6a32f544ff6f961906f09d3ee10903a4f52b2d"
|
|
||||||
dependencies = [
|
|
||||||
"specta",
|
|
||||||
"specta-serde",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stacker"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cddb07e32ddb770749da91081d8d0ac3a16f1a569a18b20348cd371f5dead06b"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"psm",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "static_assertions"
|
name = "static_assertions"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -3490,12 +3710,146 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "string_enum"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "05e383308aebc257e7d7920224fa055c632478d92744eca77f99be8fa1545b90"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"swc_macros_common",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_atoms"
|
||||||
|
version = "0.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb6567e4e67485b3e7662b486f1565bdae54bd5b9d6b16b2ba1a9babb1e42125"
|
||||||
|
dependencies = [
|
||||||
|
"hstr",
|
||||||
|
"once_cell",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_common"
|
||||||
|
version = "0.33.26"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2f9706038906e66f3919028f9f7a37f3ed552f1b85578e93f4468742e2da438"
|
||||||
|
dependencies = [
|
||||||
|
"ast_node",
|
||||||
|
"better_scoped_tls",
|
||||||
|
"cfg-if",
|
||||||
|
"either",
|
||||||
|
"from_variant",
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"num-bigint",
|
||||||
|
"once_cell",
|
||||||
|
"rustc-hash",
|
||||||
|
"serde",
|
||||||
|
"siphasher 0.3.11",
|
||||||
|
"swc_atoms",
|
||||||
|
"swc_eq_ignore_macros",
|
||||||
|
"swc_visit",
|
||||||
|
"tracing",
|
||||||
|
"unicode-width",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_ecma_ast"
|
||||||
|
version = "0.113.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc1690cc0c9ab60b44ac0225ba1e231ac532f7ba1d754df761c6ee607561afae"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.1",
|
||||||
|
"is-macro",
|
||||||
|
"num-bigint",
|
||||||
|
"phf 0.11.3",
|
||||||
|
"scoped-tls",
|
||||||
|
"serde",
|
||||||
|
"string_enum",
|
||||||
|
"swc_atoms",
|
||||||
|
"swc_common",
|
||||||
|
"unicode-id-start",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_ecma_parser"
|
||||||
|
version = "0.144.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0499e69683ae5d67a20ff0279b94bc90f29df7922a46331b54d5dd367bf89570"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"phf 0.11.3",
|
||||||
|
"serde",
|
||||||
|
"smallvec",
|
||||||
|
"smartstring",
|
||||||
|
"stacker",
|
||||||
|
"swc_atoms",
|
||||||
|
"swc_common",
|
||||||
|
"swc_ecma_ast",
|
||||||
|
"tracing",
|
||||||
|
"typed-arena",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_eq_ignore_macros"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "695a1d8b461033d32429b5befbf0ad4d7a2c4d6ba9cd5ba4e0645c615839e8e4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_macros_common"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27e18fbfe83811ffae2bb23727e45829a0d19c6870bced7c0f545cc99ad248dd"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_visit"
|
||||||
|
version = "0.5.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "043d11fe683dcb934583ead49405c0896a5af5face522e4682c16971ef7871b9"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
"swc_visit_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "swc_visit_macros"
|
||||||
|
version = "0.5.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92807d840959f39c60ce8a774a3f83e8193c658068e6d270dbe0a05e40e90b41"
|
||||||
|
dependencies = [
|
||||||
|
"Inflector",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"swc_macros_common",
|
||||||
|
"syn 2.0.104",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "swift-rs"
|
name = "swift-rs"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
@@ -3651,7 +4005,6 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serialize-to-javascript",
|
"serialize-to-javascript",
|
||||||
"specta",
|
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-macros",
|
"tauri-macros",
|
||||||
@@ -3820,34 +4173,6 @@ dependencies = [
|
|||||||
"wry",
|
"wry",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-specta"
|
|
||||||
version = "2.0.0-rc.21"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b23c0132dd3cf6064e5cd919b82b3f47780e9280e7b5910babfe139829b76655"
|
|
||||||
dependencies = [
|
|
||||||
"heck 0.5.0",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"specta",
|
|
||||||
"specta-typescript",
|
|
||||||
"tauri",
|
|
||||||
"tauri-specta-macros",
|
|
||||||
"thiserror 2.0.12",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-specta-macros"
|
|
||||||
version = "2.0.0-rc.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a4aa93823e07859546aa796b8a5d608190cd8037a3a5dce3eb63d491c34bda8"
|
|
||||||
dependencies = [
|
|
||||||
"heck 0.5.0",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.104",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -3921,6 +4246,24 @@ dependencies = [
|
|||||||
"utf-8",
|
"utf-8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "text_lines"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fd5828de7deaa782e1dd713006ae96b3bee32d3279b79eb67ecf8072c059bcf"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.69"
|
version = "1.0.69"
|
||||||
@@ -4232,12 +4575,51 @@ dependencies = [
|
|||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "triomphe"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.5"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ts-rs"
|
||||||
|
version = "11.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
|
||||||
|
dependencies = [
|
||||||
|
"dprint-plugin-typescript",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"ts-rs-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ts-rs-macros"
|
||||||
|
version = "11.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.104",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typed-arena"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typeid"
|
name = "typeid"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@@ -4302,6 +4684,12 @@ dependencies = [
|
|||||||
"unic-common",
|
"unic-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-id-start"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02aebfa694eccbbbffdd92922c7de136b9fe764396d2f10e21bce1681477cfc1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
@@ -4314,6 +4702,12 @@ version = "1.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.4"
|
version = "2.5.4"
|
||||||
|
|||||||
@@ -23,7 +23,5 @@ tauri-plugin-opener = "2"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
ffprobe = "0.4.0"
|
ffprobe = "0.4.0"
|
||||||
specta = "=2.0.0-rc.22"
|
ts-rs = { version = "11.0", features = ["format"] }
|
||||||
specta-typescript = "0.0.9"
|
infer = "0.19.0"
|
||||||
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +1,320 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use specta::Type;
|
use std::fs::File;
|
||||||
use specta_typescript::Typescript;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use tauri_specta::{collect_commands, Builder};
|
use ts_rs::TS;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
enum MediaType {
|
||||||
|
Audio,
|
||||||
|
Video,
|
||||||
|
Image,
|
||||||
|
Document,
|
||||||
|
Executable,
|
||||||
|
Archive,
|
||||||
|
Library,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
|
||||||
|
#[ts(export)]
|
||||||
struct StreamResult {
|
struct StreamResult {
|
||||||
path: String,
|
path: String,
|
||||||
filename: String,
|
filename: String,
|
||||||
|
media_type: MediaType,
|
||||||
|
duration: Option<f64>,
|
||||||
|
size: u64,
|
||||||
streams: Vec<StreamDetail>,
|
streams: Vec<StreamDetail>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
|
||||||
|
#[ts(export)]
|
||||||
enum StreamDetail {
|
enum StreamDetail {
|
||||||
Video { codec: String },
|
Video {
|
||||||
Audio { codec: String },
|
codec: String,
|
||||||
Subtitle { codec: String },
|
width: Option<u32>,
|
||||||
|
height: Option<u32>,
|
||||||
|
bit_rate: Option<String>,
|
||||||
|
frame_rate: Option<String>,
|
||||||
|
},
|
||||||
|
Audio {
|
||||||
|
codec: String,
|
||||||
|
sample_rate: Option<String>,
|
||||||
|
channels: Option<u32>,
|
||||||
|
bit_rate: Option<String>,
|
||||||
|
},
|
||||||
|
Subtitle {
|
||||||
|
codec: String,
|
||||||
|
language: Option<String>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Type)]
|
#[derive(Serialize, Deserialize, Debug, Clone, TS)]
|
||||||
|
#[ts(export)]
|
||||||
struct StreamResultError {
|
struct StreamResultError {
|
||||||
filename: Option<String>,
|
filename: Option<String>,
|
||||||
reason: String,
|
reason: String,
|
||||||
|
error_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_media_type(path: &Path) -> MediaType {
|
||||||
|
// First try to detect using infer crate (magic number detection)
|
||||||
|
if let Ok(mut file) = File::open(path) {
|
||||||
|
let mut buffer = [0; 512]; // Read first 512 bytes for magic number detection
|
||||||
|
if let Ok(bytes_read) = file.read(&mut buffer) {
|
||||||
|
if let Some(kind) = infer::get(&buffer[..bytes_read]) {
|
||||||
|
return match kind.mime_type() {
|
||||||
|
// Audio types
|
||||||
|
"audio/mpeg" | "audio/mp3" | "audio/m4a" | "audio/ogg" | "audio/x-flac"
|
||||||
|
| "audio/x-wav" | "audio/amr" | "audio/aac" | "audio/x-aiff"
|
||||||
|
| "audio/x-dsf" | "audio/x-ape" | "audio/midi" => MediaType::Audio,
|
||||||
|
|
||||||
|
// Video types
|
||||||
|
"video/mp4" | "video/x-m4v" | "video/x-matroska" | "video/webm"
|
||||||
|
| "video/quicktime" | "video/x-msvideo" | "video/x-ms-wmv" | "video/mpeg"
|
||||||
|
| "video/x-flv" => MediaType::Video,
|
||||||
|
|
||||||
|
// Image types
|
||||||
|
"image/jpeg"
|
||||||
|
| "image/png"
|
||||||
|
| "image/gif"
|
||||||
|
| "image/webp"
|
||||||
|
| "image/x-canon-cr2"
|
||||||
|
| "image/tiff"
|
||||||
|
| "image/bmp"
|
||||||
|
| "image/heif"
|
||||||
|
| "image/avif"
|
||||||
|
| "image/vnd.ms-photo"
|
||||||
|
| "image/vnd.adobe.photoshop"
|
||||||
|
| "image/vnd.microsoft.icon"
|
||||||
|
| "image/openraster"
|
||||||
|
| "image/vnd.djvu" => MediaType::Image,
|
||||||
|
|
||||||
|
// Document types
|
||||||
|
"application/pdf"
|
||||||
|
| "application/rtf"
|
||||||
|
| "application/msword"
|
||||||
|
| "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
| "application/vnd.ms-excel"
|
||||||
|
| "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
||||||
|
| "application/vnd.ms-powerpoint"
|
||||||
|
| "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
||||||
|
| "application/vnd.oasis.opendocument.text"
|
||||||
|
| "application/vnd.oasis.opendocument.spreadsheet"
|
||||||
|
| "application/vnd.oasis.opendocument.presentation" => MediaType::Document,
|
||||||
|
|
||||||
|
// Archive types
|
||||||
|
"application/zip"
|
||||||
|
| "application/x-tar"
|
||||||
|
| "application/vnd.rar"
|
||||||
|
| "application/gzip"
|
||||||
|
| "application/x-bzip2"
|
||||||
|
| "application/vnd.bzip3"
|
||||||
|
| "application/x-7z-compressed"
|
||||||
|
| "application/x-xz"
|
||||||
|
| "application/x-shockwave-flash"
|
||||||
|
| "application/octet-stream"
|
||||||
|
| "application/postscript"
|
||||||
|
| "application/vnd.sqlite3"
|
||||||
|
| "application/x-nintendo-nes-rom"
|
||||||
|
| "application/x-google-chrome-extension"
|
||||||
|
| "application/vnd.ms-cab-compressed"
|
||||||
|
| "application/vnd.debian.binary-package"
|
||||||
|
| "application/x-unix-archive"
|
||||||
|
| "application/x-compress"
|
||||||
|
| "application/x-lzip"
|
||||||
|
| "application/x-rpm"
|
||||||
|
| "application/dicom"
|
||||||
|
| "application/zstd"
|
||||||
|
| "application/x-lz4"
|
||||||
|
| "application/x-ole-storage"
|
||||||
|
| "application/x-cpio"
|
||||||
|
| "application/x-par2"
|
||||||
|
| "application/epub+zip"
|
||||||
|
| "application/x-mobipocket-ebook" => MediaType::Archive,
|
||||||
|
|
||||||
|
// Executable types
|
||||||
|
"application/vnd.microsoft.portable-executable"
|
||||||
|
| "application/x-executable"
|
||||||
|
| "application/llvm"
|
||||||
|
| "application/x-mach-binary"
|
||||||
|
| "application/java"
|
||||||
|
| "application/vnd.android.dex"
|
||||||
|
| "application/vnd.android.dey"
|
||||||
|
| "application/x-x509-ca-cert" => MediaType::Executable,
|
||||||
|
|
||||||
|
// Library types (covered by executable types above, but keeping for clarity)
|
||||||
|
_ => MediaType::Unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to extension-based detection
|
||||||
|
if let Some(extension) = path.extension() {
|
||||||
|
match extension.to_str().unwrap_or("").to_lowercase().as_str() {
|
||||||
|
// Audio extensions
|
||||||
|
"mp3" | "wav" | "flac" | "ogg" | "m4a" | "aac" | "wma" | "mid" | "amr" | "aiff"
|
||||||
|
| "dsf" | "ape" => MediaType::Audio,
|
||||||
|
|
||||||
|
// Video extensions
|
||||||
|
"mp4" | "mkv" | "webm" | "mov" | "avi" | "wmv" | "mpg" | "flv" | "m4v" => {
|
||||||
|
MediaType::Video
|
||||||
|
}
|
||||||
|
|
||||||
|
// Image extensions
|
||||||
|
"gif" | "png" | "jpg" | "jpeg" | "bmp" | "tiff" | "webp" | "cr2" | "heif" | "avif"
|
||||||
|
| "jxr" | "psd" | "ico" | "ora" | "djvu" => MediaType::Image,
|
||||||
|
|
||||||
|
// Document extensions
|
||||||
|
"txt" | "md" | "pdf" | "doc" | "docx" | "xls" | "xlsx" | "ppt" | "pptx" | "odt"
|
||||||
|
| "ods" | "odp" | "rtf" => MediaType::Document,
|
||||||
|
|
||||||
|
// Archive extensions
|
||||||
|
"zip" | "rar" | "7z" | "tar" | "gz" | "bz2" | "bz3" | "xz" | "swf" | "sqlite"
|
||||||
|
| "nes" | "crx" | "cab" | "deb" | "ar" | "Z" | "lz" | "rpm" | "dcm" | "zst" | "lz4"
|
||||||
|
| "msi" | "cpio" | "par2" | "epub" | "mobi" => MediaType::Archive,
|
||||||
|
|
||||||
|
// Executable extensions
|
||||||
|
"exe" | "dll" | "msi" | "dmg" | "pkg" | "deb" | "rpm" | "app" | "elf" | "bc"
|
||||||
|
| "mach" | "class" | "dex" | "dey" | "der" | "obj" => MediaType::Executable,
|
||||||
|
|
||||||
|
// Library extensions
|
||||||
|
"so" | "dylib" => MediaType::Library,
|
||||||
|
|
||||||
|
_ => MediaType::Unknown,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MediaType::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_media_file(media_type: &MediaType) -> bool {
|
||||||
|
matches!(
|
||||||
|
media_type,
|
||||||
|
MediaType::Audio | MediaType::Video | MediaType::Image
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_streams(info: &ffprobe::FfProbe) -> Vec<StreamDetail> {
|
||||||
|
let mut streams = Vec::new();
|
||||||
|
|
||||||
|
for stream in &info.streams {
|
||||||
|
match stream.codec_type.as_deref() {
|
||||||
|
Some("video") => {
|
||||||
|
streams.push(StreamDetail::Video {
|
||||||
|
codec: stream
|
||||||
|
.codec_name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
width: stream.width.map(|w| w as u32),
|
||||||
|
height: stream.height.map(|h| h as u32),
|
||||||
|
bit_rate: stream.bit_rate.as_ref().map(|b| b.to_string()),
|
||||||
|
frame_rate: Some(stream.r_frame_rate.clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some("audio") => {
|
||||||
|
streams.push(StreamDetail::Audio {
|
||||||
|
codec: stream
|
||||||
|
.codec_name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
sample_rate: stream.sample_rate.clone(),
|
||||||
|
channels: stream.channels.map(|c| c as u32),
|
||||||
|
bit_rate: stream.bit_rate.as_ref().map(|b| b.to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some("subtitle") => {
|
||||||
|
streams.push(StreamDetail::Subtitle {
|
||||||
|
codec: stream
|
||||||
|
.codec_name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "unknown".to_string()),
|
||||||
|
language: stream.tags.as_ref().and_then(|tags| tags.language.clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
streams
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
|
||||||
fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultError> {
|
fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultError> {
|
||||||
paths
|
paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|path_str| {
|
.map(|path_str| {
|
||||||
let path = Path::new(&path_str);
|
let path = Path::new(&path_str);
|
||||||
let filename = path.file_name().unwrap().to_str().unwrap().to_string();
|
let filename = path
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
if !path.exists() {
|
if !path.exists() {
|
||||||
return Err(StreamResultError {
|
return Err(StreamResultError {
|
||||||
filename: Some(filename),
|
filename: Some(filename),
|
||||||
reason: "File does not exist".to_string(),
|
reason: "File does not exist".to_string(),
|
||||||
});
|
error_type: "not_found".to_string(),
|
||||||
}
|
|
||||||
if !path.is_file() {
|
|
||||||
return Err(StreamResultError {
|
|
||||||
filename: Some(filename),
|
|
||||||
reason: "Not a file".to_string(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
match ffprobe::ffprobe(&path_str) {
|
// Check if it's a file (not directory)
|
||||||
Ok(info) => {
|
if !path.is_file() {
|
||||||
dbg!(info);
|
return Err(StreamResultError {
|
||||||
Ok(StreamResult {
|
filename: Some(filename),
|
||||||
filename,
|
reason: "Not a file (directory or other)".to_string(),
|
||||||
path: path_str,
|
error_type: "not_file".to_string(),
|
||||||
streams: vec![],
|
});
|
||||||
})
|
}
|
||||||
}
|
|
||||||
Err(err) => {
|
// Get file size
|
||||||
eprintln!("Could not analyze file with ffprobe: {:?}", err);
|
let size = std::fs::metadata(&path_str)
|
||||||
Err(StreamResultError {
|
.map(|metadata| metadata.len())
|
||||||
filename: Some(filename),
|
.unwrap_or(0);
|
||||||
reason: "Could not analyze file with ffprobe".to_string(),
|
|
||||||
})
|
// Detect media type using magic numbers and fallback to extensions
|
||||||
|
let media_type = detect_media_type(path);
|
||||||
|
|
||||||
|
// Only try to analyze media files with ffprobe
|
||||||
|
if is_media_file(&media_type) {
|
||||||
|
// Analyze with ffprobe
|
||||||
|
match ffprobe::ffprobe(&path_str) {
|
||||||
|
Ok(info) => {
|
||||||
|
let streams = extract_streams(&info);
|
||||||
|
let duration = info
|
||||||
|
.format
|
||||||
|
.duration
|
||||||
|
.and_then(|dur_str| dur_str.parse::<f64>().ok());
|
||||||
|
|
||||||
|
Ok(StreamResult {
|
||||||
|
filename,
|
||||||
|
path: path_str,
|
||||||
|
media_type,
|
||||||
|
duration,
|
||||||
|
size,
|
||||||
|
streams,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Could not analyze media file with ffprobe: {:?}", err);
|
||||||
|
Err(StreamResultError {
|
||||||
|
filename: Some(filename),
|
||||||
|
reason: format!("Could not analyze media file: {}", err),
|
||||||
|
error_type: "analysis_failed".to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// For non-media files, return an error indicating it's not a media file
|
||||||
|
Err(StreamResultError {
|
||||||
|
filename: Some(filename),
|
||||||
|
reason: format!("Not a media file (detected as {:?})", media_type),
|
||||||
|
error_type: "not_media".to_string(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
@@ -69,23 +322,22 @@ fn has_streams(paths: Vec<String>) -> Result<Vec<StreamResult>, StreamResultErro
|
|||||||
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
pub fn run() {
|
pub fn run() {
|
||||||
let builder = Builder::<tauri::Wry>::new()
|
|
||||||
// Then register them (separated by a comma)
|
|
||||||
.commands(collect_commands![has_streams,]);
|
|
||||||
|
|
||||||
#[cfg(debug_assertions)] // <- Only export on non-release builds
|
|
||||||
builder
|
|
||||||
.export(Typescript::default(), "../src/bindings.ts")
|
|
||||||
.expect("Failed to export typescript bindings");
|
|
||||||
|
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_opener::init())
|
.plugin(tauri_plugin_opener::init())
|
||||||
.invoke_handler(tauri::generate_handler![has_streams])
|
.invoke_handler(tauri::generate_handler![has_streams])
|
||||||
.setup(move |app| {
|
|
||||||
// Ensure you mount your events!
|
|
||||||
builder.mount_events(app);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.run(tauri::generate_context!())
|
.run(tauri::generate_context!())
|
||||||
.expect("error while running tauri application");
|
.expect("error while running tauri application");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn export_bindings() {
|
||||||
|
// This will generate TypeScript bindings when you run `cargo test export_bindings`
|
||||||
|
StreamResult::export().unwrap();
|
||||||
|
StreamDetail::export().unwrap();
|
||||||
|
StreamResultError::export().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
102
src/bindings.ts
102
src/bindings.ts
@@ -1,90 +1,24 @@
|
|||||||
|
// Import generated TypeScript types from ts-rs
|
||||||
|
export type { StreamResult } from "./bindings/StreamResult";
|
||||||
|
export type { StreamDetail } from "./bindings/StreamDetail";
|
||||||
|
export type { StreamResultError } from "./bindings/StreamResultError";
|
||||||
|
export type { MediaType } from "./bindings/MediaType";
|
||||||
|
|
||||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
// Tauri invoke wrapper
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
/** user-defined commands **/
|
|
||||||
|
|
||||||
|
export type Result<T, E> =
|
||||||
|
| { status: "ok"; data: T }
|
||||||
|
| { status: "error"; error: E };
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
async hasStreams(paths: string[]) : Promise<Result<StreamResult[], StreamResultError>> {
|
async hasStreams(paths: string[]): Promise<Result<StreamResult[], StreamResultError>> {
|
||||||
try {
|
try {
|
||||||
return { status: "ok", data: await TAURI_INVOKE("has_streams", { paths }) };
|
const data = await invoke<StreamResult[]>("has_streams", { paths });
|
||||||
} catch (e) {
|
return { status: "ok", data };
|
||||||
if(e instanceof Error) throw e;
|
} catch (e) {
|
||||||
else return { status: "error", error: e as any };
|
if (e instanceof Error) throw e;
|
||||||
}
|
else return { status: "error", error: e as any };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** user-defined events **/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** user-defined constants **/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** user-defined types **/
|
|
||||||
|
|
||||||
export type StreamDetail = { Video: { codec: string } } | { Audio: { codec: string } } | { Subtitle: { codec: string } }
|
|
||||||
export type StreamResult = { path: string; filename: string; streams: StreamDetail[] }
|
|
||||||
export type StreamResultError = { filename: string | null; reason: string }
|
|
||||||
|
|
||||||
/** tauri-specta globals **/
|
|
||||||
|
|
||||||
import {
|
|
||||||
invoke as TAURI_INVOKE,
|
|
||||||
Channel as TAURI_CHANNEL,
|
|
||||||
} from "@tauri-apps/api/core";
|
|
||||||
import * as TAURI_API_EVENT from "@tauri-apps/api/event";
|
|
||||||
import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow";
|
|
||||||
|
|
||||||
type __EventObj__<T> = {
|
|
||||||
listen: (
|
|
||||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
|
||||||
) => ReturnType<typeof TAURI_API_EVENT.listen<T>>;
|
|
||||||
once: (
|
|
||||||
cb: TAURI_API_EVENT.EventCallback<T>,
|
|
||||||
) => ReturnType<typeof TAURI_API_EVENT.once<T>>;
|
|
||||||
emit: null extends T
|
|
||||||
? (payload?: T) => ReturnType<typeof TAURI_API_EVENT.emit>
|
|
||||||
: (payload: T) => ReturnType<typeof TAURI_API_EVENT.emit>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Result<T, E> =
|
|
||||||
| { status: "ok"; data: T }
|
|
||||||
| { status: "error"; error: E };
|
|
||||||
|
|
||||||
function __makeEvents__<T extends Record<string, any>>(
|
|
||||||
mappings: Record<keyof T, string>,
|
|
||||||
) {
|
|
||||||
return new Proxy(
|
|
||||||
{} as unknown as {
|
|
||||||
[K in keyof T]: __EventObj__<T[K]> & {
|
|
||||||
(handle: __WebviewWindow__): __EventObj__<T[K]>;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
get: (_, event) => {
|
|
||||||
const name = mappings[event as keyof T];
|
|
||||||
|
|
||||||
return new Proxy((() => {}) as any, {
|
|
||||||
apply: (_, __, [window]: [__WebviewWindow__]) => ({
|
|
||||||
listen: (arg: any) => window.listen(name, arg),
|
|
||||||
once: (arg: any) => window.once(name, arg),
|
|
||||||
emit: (arg: any) => window.emit(name, arg),
|
|
||||||
}),
|
|
||||||
get: (_, command: keyof __EventObj__<any>) => {
|
|
||||||
switch (command) {
|
|
||||||
case "listen":
|
|
||||||
return (arg: any) => TAURI_API_EVENT.listen(name, arg);
|
|
||||||
case "once":
|
|
||||||
return (arg: any) => TAURI_API_EVENT.once(name, arg);
|
|
||||||
case "emit":
|
|
||||||
return (arg: any) => TAURI_API_EVENT.emit(name, arg);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,56 +8,225 @@ type DropOverlayProps = {
|
|||||||
type State =
|
type State =
|
||||||
| { status: "hidden" }
|
| { status: "hidden" }
|
||||||
| { status: "loading"; count: number }
|
| { status: "loading"; count: number }
|
||||||
| { status: "ready"; files: { name: string; key: string }[] }
|
| {
|
||||||
| { status: "error"; reason: string; filename?: string };
|
status: "ready";
|
||||||
|
files: {
|
||||||
|
name: string;
|
||||||
|
key: string;
|
||||||
|
media_type: MediaType;
|
||||||
|
duration?: number;
|
||||||
|
size: number;
|
||||||
|
streams: any[];
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
| { status: "error"; reason: string; filename?: string; error_type?: string };
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CircleQuestionMarkIcon,
|
CheckCircle,
|
||||||
File as FileIcon,
|
File as FileIcon,
|
||||||
|
FileText,
|
||||||
Film,
|
Film,
|
||||||
Image,
|
Image,
|
||||||
|
Loader2,
|
||||||
Music,
|
Music,
|
||||||
|
XCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { commands } from "../bindings";
|
import { commands, MediaType } from "../bindings";
|
||||||
|
|
||||||
type FileItemProps = {
|
type FileItemProps = {
|
||||||
filename: string;
|
filename: string;
|
||||||
|
media_type: MediaType;
|
||||||
|
duration?: number;
|
||||||
|
size: number;
|
||||||
|
streams: any[];
|
||||||
error?: string;
|
error?: string;
|
||||||
|
error_type?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Item = ({ icon, text }: { icon: ReactNode; text: ReactNode }) => {
|
const formatFileSize = (bytes: number): string => {
|
||||||
|
if (bytes === 0) return "0 B";
|
||||||
|
const k = 1024;
|
||||||
|
const sizes = ["B", "KB", "MB", "GB"];
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatDuration = (seconds: number): string => {
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${minutes.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFileIcon = (
|
||||||
|
mediaType: MediaType,
|
||||||
|
error?: string,
|
||||||
|
errorType?: string,
|
||||||
|
) => {
|
||||||
|
// For non-media files, show a neutral icon instead of error icon
|
||||||
|
if (errorType === "not_media") {
|
||||||
|
switch (mediaType) {
|
||||||
|
case "Executable":
|
||||||
|
return <FileIcon className="w-5 h-5 text-orange-400" />;
|
||||||
|
case "Archive":
|
||||||
|
return <FileIcon className="w-5 h-5 text-yellow-400" />;
|
||||||
|
case "Library":
|
||||||
|
return <FileIcon className="w-5 h-5 text-indigo-400" />;
|
||||||
|
case "Document":
|
||||||
|
return <FileText className="w-5 h-5 text-green-400" />;
|
||||||
|
default:
|
||||||
|
return <FileIcon className="w-5 h-5 text-neutral-300" />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return <XCircle className="w-5 h-5 text-red-400" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (mediaType) {
|
||||||
|
case "Audio":
|
||||||
|
return <Music className="w-5 h-5 text-blue-400" />;
|
||||||
|
case "Video":
|
||||||
|
return <Film className="w-5 h-5 text-purple-400" />;
|
||||||
|
case "Image":
|
||||||
|
return <Image className="w-5 h-5 text-pink-400" />;
|
||||||
|
case "Document":
|
||||||
|
return <FileText className="w-5 h-5 text-green-400" />;
|
||||||
|
case "Executable":
|
||||||
|
return <FileIcon className="w-5 h-5 text-orange-400" />;
|
||||||
|
case "Archive":
|
||||||
|
return <FileIcon className="w-5 h-5 text-yellow-400" />;
|
||||||
|
case "Library":
|
||||||
|
return <FileIcon className="w-5 h-5 text-indigo-400" />;
|
||||||
|
default:
|
||||||
|
return <FileIcon className="w-5 h-5 text-neutral-300" />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStreamInfo = (streams: any[], mediaType: MediaType): string => {
|
||||||
|
// For non-media files, return file type description
|
||||||
|
if (!["Audio", "Video", "Image"].includes(mediaType)) {
|
||||||
|
switch (mediaType) {
|
||||||
|
case "Executable":
|
||||||
|
return "Executable file";
|
||||||
|
case "Archive":
|
||||||
|
return "Archive file";
|
||||||
|
case "Library":
|
||||||
|
return "Library file";
|
||||||
|
case "Document":
|
||||||
|
return "Document file";
|
||||||
|
default:
|
||||||
|
return "Unknown file type";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For media files, analyze streams
|
||||||
|
const videoStreams = streams.filter((s) => "Video" in s);
|
||||||
|
const audioStreams = streams.filter((s) => "Audio" in s);
|
||||||
|
const subtitleStreams = streams.filter((s) => "Subtitle" in s);
|
||||||
|
|
||||||
|
const parts = [];
|
||||||
|
if (videoStreams.length > 0) {
|
||||||
|
const video = videoStreams[0];
|
||||||
|
if ("Video" in video) {
|
||||||
|
const width = video.Video.width;
|
||||||
|
const height = video.Video.height;
|
||||||
|
const codec = video.Video.codec;
|
||||||
|
if (width && height) {
|
||||||
|
parts.push(`${width}x${height} ${codec}`);
|
||||||
|
} else {
|
||||||
|
parts.push(codec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (audioStreams.length > 0) {
|
||||||
|
const audio = audioStreams[0];
|
||||||
|
if ("Audio" in audio) {
|
||||||
|
parts.push(`${audio.Audio.codec} audio`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (subtitleStreams.length > 0) {
|
||||||
|
parts.push(`${subtitleStreams.length} subtitle(s)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts.join(", ");
|
||||||
|
};
|
||||||
|
|
||||||
|
const Item = ({
|
||||||
|
icon,
|
||||||
|
text,
|
||||||
|
subtitle,
|
||||||
|
status,
|
||||||
|
}: {
|
||||||
|
icon: ReactNode;
|
||||||
|
text: ReactNode;
|
||||||
|
subtitle?: ReactNode;
|
||||||
|
status?: "success" | "error" | "loading";
|
||||||
|
}) => {
|
||||||
|
const statusColor =
|
||||||
|
status === "success"
|
||||||
|
? "border-green-500"
|
||||||
|
: status === "error"
|
||||||
|
? "border-red-500"
|
||||||
|
: status === "loading"
|
||||||
|
? "border-blue-500"
|
||||||
|
: "border-neutral-600";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex items-center gap-2 px-3 py-2 bg-neutral-800 rounded-md shadow-sm"
|
className={`flex items-center gap-3 px-4 py-3 bg-neutral-800 rounded-lg shadow-lg border-2 ${statusColor} transition-all duration-200`}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
marginBottom: "0.5rem",
|
marginBottom: "0.75rem",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
<span className="truncate text-neutral-100 max-w-md">{text}</span>
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="truncate text-neutral-100 font-medium">{text}</div>
|
||||||
|
{subtitle && (
|
||||||
|
<div className="truncate text-neutral-400 text-sm mt-1">
|
||||||
|
{subtitle}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FileItem = ({ filename, error }: FileItemProps) => {
|
const FileItem = ({
|
||||||
const ext = filename.split(".").pop()?.toLowerCase();
|
filename,
|
||||||
const icon =
|
media_type,
|
||||||
error == null ? (
|
duration,
|
||||||
match(ext)
|
size,
|
||||||
.with("mp3", "wav", "flac", "ogg", "m4a", "aac", () => (
|
streams,
|
||||||
<Music className="w-5 h-5 text-blue-400" />
|
error,
|
||||||
))
|
error_type,
|
||||||
.with("mp4", "mkv", "webm", "mov", "avi", () => (
|
}: FileItemProps) => {
|
||||||
<Film className="w-5 h-5 text-purple-400" />
|
const icon = getFileIcon(media_type, error, error_type);
|
||||||
))
|
const fileSize = formatFileSize(size);
|
||||||
.with("gif", () => <Image className="w-5 h-5 text-pink-400" />)
|
|
||||||
.otherwise(() => <FileIcon className="w-5 h-5 text-neutral-300" />)
|
|
||||||
) : (
|
|
||||||
<CircleQuestionMarkIcon className="w-5 h-5 text-neutral-300" />
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Item icon={icon} text={filename} />;
|
let subtitle: ReactNode;
|
||||||
|
let status: "success" | "error" | "loading" | undefined;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
subtitle = error;
|
||||||
|
// For non-media files, show as neutral instead of error
|
||||||
|
status = error_type === "not_media" ? undefined : "error";
|
||||||
|
} else {
|
||||||
|
const streamInfo = getStreamInfo(streams, media_type);
|
||||||
|
const durationStr = duration ? formatDuration(duration) : null;
|
||||||
|
const details = [streamInfo, durationStr, fileSize].filter(Boolean);
|
||||||
|
subtitle = details.join(" • ");
|
||||||
|
status = "success";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Item icon={icon} text={filename} subtitle={subtitle} status={status} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const DropOverlay = ({ paths }: DropOverlayProps) => {
|
const DropOverlay = ({ paths }: DropOverlayProps) => {
|
||||||
@@ -82,6 +251,10 @@ const DropOverlay = ({ paths }: DropOverlayProps) => {
|
|||||||
files: r.data.map((item) => ({
|
files: r.data.map((item) => ({
|
||||||
name: item.filename,
|
name: item.filename,
|
||||||
key: item.path,
|
key: item.path,
|
||||||
|
media_type: item.media_type,
|
||||||
|
duration: item.duration,
|
||||||
|
size: item.size,
|
||||||
|
streams: item.streams,
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
.with({ status: "error" }, (r) => {
|
.with({ status: "error" }, (r) => {
|
||||||
@@ -90,10 +263,15 @@ const DropOverlay = ({ paths }: DropOverlayProps) => {
|
|||||||
status: "error" as const,
|
status: "error" as const,
|
||||||
reason: r.error.reason,
|
reason: r.error.reason,
|
||||||
filename: r.error.filename,
|
filename: r.error.filename,
|
||||||
|
error_type: r.error.error_type,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return { status: "error" as const, reason: r.error.reason };
|
return {
|
||||||
|
status: "error" as const,
|
||||||
|
reason: r.error.reason,
|
||||||
|
error_type: r.error.error_type,
|
||||||
|
};
|
||||||
})
|
})
|
||||||
.exhaustive();
|
.exhaustive();
|
||||||
});
|
});
|
||||||
@@ -105,41 +283,89 @@ const DropOverlay = ({ paths }: DropOverlayProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const inner = match(state)
|
const inner = match(state)
|
||||||
.with({ status: "loading" }, ({ count }) =>
|
.with({ status: "loading" }, ({ count }) => (
|
||||||
Array.from({ length: count }).map((_, i) => (
|
<div className="flex flex-col items-center gap-4">
|
||||||
<Item
|
<Loader2 className="w-8 h-8 text-blue-400 animate-spin" />
|
||||||
key={i}
|
<div className="text-white text-lg font-medium">
|
||||||
icon={
|
Analyzing {count} file{count > 1 ? "s" : ""}...
|
||||||
<CircleQuestionMarkIcon className="w-5 h-5 text-neutral-300/50" />
|
</div>
|
||||||
}
|
{Array.from({ length: Math.min(count, 3) }).map((_, i) => (
|
||||||
text={
|
<Item
|
||||||
<span className="inline-block w-32 h-5 bg-neutral-300/10 rounded animate-pulse" />
|
key={i}
|
||||||
}
|
icon={
|
||||||
/>
|
<Loader2 className="w-5 h-5 text-neutral-300/50 animate-spin" />
|
||||||
)),
|
}
|
||||||
)
|
text={
|
||||||
|
<span className="inline-block w-32 h-5 bg-neutral-300/10 rounded animate-pulse" />
|
||||||
|
}
|
||||||
|
status="loading"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
.with({ status: "ready" }, (r) => {
|
.with({ status: "ready" }, (r) => {
|
||||||
return r.files
|
return (
|
||||||
.slice(0, 8)
|
<div className="flex flex-col items-center gap-4">
|
||||||
.map((file) => <FileItem key={file.key} filename={file.name} />);
|
<div className="flex items-center gap-2 text-green-400">
|
||||||
|
<CheckCircle className="w-6 h-6" />
|
||||||
|
<span className="text-lg font-medium">Files Ready</span>
|
||||||
|
</div>
|
||||||
|
<div className="max-h-96 overflow-y-auto">
|
||||||
|
{r.files.slice(0, 8).map((file) => (
|
||||||
|
<FileItem
|
||||||
|
key={file.key}
|
||||||
|
filename={file.name}
|
||||||
|
media_type={file.media_type}
|
||||||
|
duration={file.duration}
|
||||||
|
size={file.size}
|
||||||
|
streams={file.streams}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.with({ status: "error", filename: P.string }, (r) => {
|
.with({ status: "error", filename: P.string }, (r) => {
|
||||||
return <FileItem filename={r.filename} error={r.reason} />;
|
|
||||||
})
|
|
||||||
.with({ status: "error" }, ({ reason }) => {
|
|
||||||
return (
|
return (
|
||||||
<Item
|
<div className="flex flex-col items-center gap-4">
|
||||||
icon={<CircleQuestionMarkIcon className="w-5 h-5 text-neutral-300" />}
|
<div className="flex items-center gap-2 text-red-400">
|
||||||
text={reason}
|
<XCircle className="w-6 h-6" />
|
||||||
/>
|
<span className="text-lg font-medium">Error</span>
|
||||||
|
</div>
|
||||||
|
<FileItem
|
||||||
|
filename={r.filename}
|
||||||
|
media_type="Unknown"
|
||||||
|
size={0}
|
||||||
|
streams={[]}
|
||||||
|
error={r.reason}
|
||||||
|
error_type={r.error_type}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.with({ status: "error" }, ({ reason, error_type }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center gap-4">
|
||||||
|
<div className="flex items-center gap-2 text-red-400">
|
||||||
|
<XCircle className="w-6 h-6" />
|
||||||
|
<span className="text-lg font-medium">Error</span>
|
||||||
|
</div>
|
||||||
|
<Item
|
||||||
|
icon={<XCircle className="w-5 h-5 text-red-400" />}
|
||||||
|
text={reason}
|
||||||
|
status="error"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.exhaustive();
|
.exhaustive();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="absolute z-10 top-0 left-0 w-full h-full bg-black/40 backdrop-blur-sm transition-all duration-300 ease-in-out">
|
<div className="absolute z-10 top-0 left-0 w-full h-full bg-black/60 backdrop-blur-sm transition-all duration-300 ease-in-out">
|
||||||
<div className="flex flex-col justify-center items-center h-full">
|
<div className="flex flex-col justify-center items-center h-full p-8">
|
||||||
<span className="text-white text-2xl">{inner}</span>
|
<div className="bg-neutral-900 rounded-xl p-6 shadow-2xl max-w-2xl w-full">
|
||||||
|
{inner}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user