diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..3e0c0ac --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,48 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'conduct'", + "cargo": { + "args": [ + "build", + "--bin=conduct", + "--package=conduct" + ], + "filter": { + "name": "conduct", + "kind": "bin" + } + }, + "args": [ + "--project-dir", + "./example/basic" + ], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'conduct'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=conduct", + "--package=conduct" + ], + "filter": { + "name": "conduct", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8062d1a..114921b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "android-tzdata" @@ -136,9 +136,9 @@ dependencies = [ [[package]] name = "block2" -version = "0.5.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" dependencies = [ "objc2", ] @@ -172,7 +172,7 @@ dependencies = [ "glib", "libc", "once_cell", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -262,7 +262,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -337,7 +337,7 @@ dependencies = [ "dunce", "enum_dispatch", "include_directory", - "indexmap 2.6.0", + "indexmap", "log", "matchit", "path-absolutize", @@ -349,8 +349,11 @@ dependencies = [ "similar", "stderrlog", "tao", + "time", "ts-rs", "url", + "urlencoding", + "whoami", "wry", ] @@ -360,6 +363,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -436,15 +449,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", + "itoa", "matches", - "phf 0.8.0", + "phf 0.10.1", "proc-macro2", "quote", "smallvec", @@ -458,7 +471,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.79", + "syn 2.0.106", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", ] [[package]] @@ -471,7 +493,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -484,12 +506,43 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + [[package]] name = "dispatch" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.6.0", + "objc2", +] + [[package]] name = "dlopen2" version = "0.7.0" @@ -510,7 +563,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -549,7 +602,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -592,7 +645,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -660,7 +713,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -839,7 +892,7 @@ dependencies = [ "once_cell", "pin-project-lite", "smallvec", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -875,7 +928,7 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -889,7 +942,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -962,15 +1015,9 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.15.0" @@ -997,16 +1044,14 @@ checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "html5ever" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", + "match_token", ] [[package]] @@ -1017,7 +1062,7 @@ checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", - "itoa 1.0.11", + "itoa", ] [[package]] @@ -1075,16 +1120,6 @@ dependencies = [ "quote", ] -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - [[package]] name = "indexmap" version = "2.6.0" @@ -1092,7 +1127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.15.0", + "hashbrown", "serde", ] @@ -1122,12 +1157,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.11" @@ -1168,7 +1197,7 @@ dependencies = [ "combine", "jni-sys", "log", - "thiserror", + "thiserror 1.0.65", "walkdir", "windows-sys 0.45.0", ] @@ -1190,14 +1219,13 @@ dependencies = [ [[package]] name = "kuchikiki" -version = "0.8.2" +version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 1.9.3", - "matches", + "indexmap", "selectors", ] @@ -1213,6 +1241,17 @@ version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -1246,18 +1285,29 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf 0.11.3", + "phf_codegen 0.11.3", "string_cache", "string_cache_codegen", "tendril", ] +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "matches" version = "0.1.10" @@ -1303,7 +1353,7 @@ dependencies = [ "ndk-sys", "num_enum", "raw-window-handle", - "thiserror", + "thiserror 1.0.65", ] [[package]] @@ -1343,6 +1393,12 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -1370,233 +1426,109 @@ dependencies = [ "proc-macro-crate 2.0.2", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] -name = "objc" -version = "0.2.7" +name = "num_threads" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ - "malloc_buf", + "libc", ] [[package]] -name = "objc-sys" -version = "0.3.5" +name = "objc" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ - "cc", + "malloc_buf", ] [[package]] name = "objc2" -version = "0.5.2" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +checksum = "561f357ba7f3a2a61563a186a163d0a3a5247e1089524a3981d49adb775078bc" dependencies = [ - "objc-sys", "objc2-encode", + "objc2-exception-helper", ] [[package]] name = "objc2-app-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" -dependencies = [ - "bitflags 2.6.0", - "block2", - "libc", - "objc2", - "objc2-core-data", - "objc2-core-image", - "objc2-foundation", - "objc2-quartz-core", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-core-location", - "objc2-foundation", -] - -[[package]] -name = "objc2-contacts" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" -dependencies = [ - "block2", "objc2", + "objc2-core-foundation", "objc2-foundation", ] [[package]] -name = "objc2-core-data" -version = "0.2.2" +name = "objc2-core-foundation" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-image" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-core-location" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" -dependencies = [ - "block2", + "dispatch2", "objc2", - "objc2-contacts", - "objc2-foundation", ] [[package]] name = "objc2-encode" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" - -[[package]] -name = "objc2-foundation" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" -dependencies = [ - "bitflags 2.6.0", - "block2", - "libc", - "objc2", -] - -[[package]] -name = "objc2-link-presentation" -version = "0.2.2" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" -dependencies = [ - "block2", - "objc2", - "objc2-app-kit", - "objc2-foundation", -] +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] -name = "objc2-metal" -version = "0.2.2" +name = "objc2-exception-helper" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" dependencies = [ - "bitflags 2.6.0", - "block2", - "objc2", - "objc2-foundation", + "cc", ] [[package]] -name = "objc2-quartz-core" -version = "0.2.2" +name = "objc2-foundation" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ "bitflags 2.6.0", - "block2", - "objc2", - "objc2-foundation", - "objc2-metal", -] - -[[package]] -name = "objc2-symbols" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" -dependencies = [ "objc2", - "objc2-foundation", + "objc2-core-foundation", ] [[package]] name = "objc2-ui-kit" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" -dependencies = [ - "bitflags 2.6.0", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-image", - "objc2-core-location", - "objc2-foundation", - "objc2-link-presentation", - "objc2-quartz-core", - "objc2-symbols", - "objc2-uniform-type-identifiers", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-uniform-type-identifiers" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" -dependencies = [ - "block2", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ "bitflags 2.6.0", - "block2", "objc2", - "objc2-core-location", + "objc2-core-foundation", "objc2-foundation", ] [[package]] name = "objc2-web-kit" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68bc69301064cebefc6c4c90ce9cba69225239e4b8ff99d445a2b5563797da65" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ "bitflags 2.6.0", "block2", "objc2", "objc2-app-kit", + "objc2-core-foundation", "objc2-foundation", ] @@ -1606,6 +1538,12 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "pango" version = "0.18.3" @@ -1690,9 +1628,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros", "phf_shared 0.8.0", - "proc-macro-hack", ] [[package]] @@ -1701,7 +1637,18 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ + "phf_macros", "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_shared 0.11.3", ] [[package]] @@ -1716,12 +1663,12 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] @@ -1744,14 +1691,24 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + [[package]] name = "phf_macros" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro-hack", "proc-macro2", "quote", @@ -1764,7 +1721,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" dependencies = [ - "siphasher", + "siphasher 0.3.11", ] [[package]] @@ -1773,7 +1730,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ - "siphasher", + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", ] [[package]] @@ -1794,6 +1760,12 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1861,9 +1833,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -1975,13 +1947,24 @@ checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror 2.0.16", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -2014,22 +1997,20 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "selectors" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", "log", - "matches", "phf 0.8.0", "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] @@ -2055,7 +2036,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -2064,7 +2045,7 @@ version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "itoa 1.0.11", + "itoa", "memchr", "ryu", "serde", @@ -2085,8 +2066,8 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.6.0", - "itoa 1.0.11", + "indexmap", + "itoa", "ryu", "serde", "unsafe-libyaml", @@ -2094,9 +2075,9 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" dependencies = [ "nodrop", "stable_deref_trait", @@ -2131,6 +2112,12 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "slab" version = "0.4.9" @@ -2236,9 +2223,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" dependencies = [ "proc-macro2", "quote", @@ -2291,7 +2278,7 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows", + "windows 0.58.0", "windows-core 0.58.0", "windows-version", "x11-dl", @@ -2305,7 +2292,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] @@ -2335,18 +2322,21 @@ dependencies = [ ] [[package]] -name = "thin-slice" -version = "0.1.1" +name = "thiserror" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl 1.0.65", +] [[package]] name = "thiserror" -version = "1.0.65" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.16", ] [[package]] @@ -2357,7 +2347,18 @@ checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -2370,6 +2371,39 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2412,7 +2446,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.6.0", + "indexmap", "toml_datetime", "winnow", ] @@ -2423,7 +2457,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ - "indexmap 2.6.0", + "indexmap", "serde", "serde_spanned", "toml_datetime", @@ -2437,7 +2471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9" dependencies = [ "lazy_static", - "thiserror", + "thiserror 1.0.65", "ts-rs-macros", ] @@ -2449,7 +2483,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", "termcolor", ] @@ -2509,6 +2543,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -2555,6 +2595,12 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.95" @@ -2577,7 +2623,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", "wasm-bindgen-shared", ] @@ -2599,7 +2645,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2610,6 +2656,16 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webkit2gtk" version = "2.0.1" @@ -2656,16 +2712,16 @@ dependencies = [ [[package]] name = "webview2-com" -version = "0.33.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows", - "windows-core 0.58.0", - "windows-implement", - "windows-interface", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.0", + "windows-interface 0.59.1", ] [[package]] @@ -2676,18 +2732,29 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] [[package]] name = "webview2-com-sys" -version = "0.33.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ - "thiserror", - "windows", - "windows-core 0.58.0", + "thiserror 2.0.16", + "windows 0.61.3", + "windows-core 0.61.2", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", ] [[package]] @@ -2731,6 +2798,28 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -2746,13 +2835,37 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", - "windows-result", - "windows-strings", + "windows-implement 0.58.0", + "windows-interface 0.58.0", + "windows-result 0.2.0", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -2761,7 +2874,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", ] [[package]] @@ -2772,7 +2896,34 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link", ] [[package]] @@ -2784,16 +2935,34 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" dependencies = [ - "windows-result", + "windows-result 0.2.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -2876,6 +3045,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-version" version = "0.1.1" @@ -3028,13 +3206,15 @@ dependencies = [ [[package]] name = "wry" -version = "0.46.3" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd5cdf57c66813d97601181349c63b96994b3074fc3d7a31a8cce96e968e3bbd" +checksum = "31f0e9642a0d061f6236c54ccae64c2722a7879ad4ec7dff59bd376d446d8e90" dependencies = [ "base64", "block2", + "cookie", "crossbeam-channel", + "dirs", "dpi", "dunce", "gdkx11", @@ -3048,6 +3228,7 @@ dependencies = [ "ndk", "objc2", "objc2-app-kit", + "objc2-core-foundation", "objc2-foundation", "objc2-ui-kit", "objc2-web-kit", @@ -3057,12 +3238,13 @@ dependencies = [ "sha2", "soup3", "tao-macros", - "thiserror", + "thiserror 2.0.16", + "url", "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows", - "windows-core 0.58.0", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -3106,5 +3288,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.79", + "syn 2.0.106", ] diff --git a/Cargo.toml b/Cargo.toml index e77bac3..cbef45b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,12 @@ serde_json = "1.0.132" serde_yaml = "0.9.34" stderrlog = "0.6.0" tao = "0.30.3" +time = { version = "0.3.40", features = ["local-offset"]} ts-rs = { version = "10.0.0", features = ["serde-compat"] } url = "2.5.2" -wry = { version = "0.46.3", features = ["linux-body"] } +urlencoding = "2.1.3" +whoami = "1.6.1" +wry = { version = "0.53.3", features = ["linux-body"] } [dev-dependencies] colored = "2.1.0" diff --git a/bindings/ResolvedElementData.ts b/bindings/ResolvedElementData.ts new file mode 100644 index 0000000..8a306ef --- /dev/null +++ b/bindings/ResolvedElementData.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type ResolvedElementData = { shot_local: boolean, dependencies: Array | null, asset: string | null, shot: string | null, owning_department: string | null, }; diff --git a/bindings/ResolvedElementResult.ts b/bindings/ResolvedElementResult.ts new file mode 100644 index 0000000..3ab9509 --- /dev/null +++ b/bindings/ResolvedElementResult.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ResolvedElementData } from "./ResolvedElementData"; +import type { VersionControlFile } from "./VersionControlFile"; + +export type ResolvedElementResult = { info: ResolvedElementData, versions: Array, }; diff --git a/bindings/VersionControlFile.ts b/bindings/VersionControlFile.ts new file mode 100644 index 0000000..39b865b --- /dev/null +++ b/bindings/VersionControlFile.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type VersionControlFile = { path: string, version: string, }; diff --git a/example/basic/manifest.yaml b/example/basic/manifest.yaml index be4d493..4fa6155 100644 --- a/example/basic/manifest.yaml +++ b/example/basic/manifest.yaml @@ -4,18 +4,21 @@ display_name: Basic Project programs: blender: exports: + .animbake.abc: export_alembic.py .mesh.blend: export_blend_library.py .shadergraph.blend: export_blend_library.py - .animbake.abc: export_alembic.py imports: .animbake.abc: import_animbake_alembic.py .glb: import_glb_mesh.py - .shadergraph.blend: import_blend_shadergraph.py .mesh.blend: import_blend_library_mesh.py + .shadergraph.blend: import_blend_shadergraph.py + ingest: + exports: + .ogg: ingest_audio_file.js inkscape: exports: - .png: export_png_512.py .2x.png: export_png_1024.py + .png: export_png_512.py departments: anim: @@ -23,23 +26,23 @@ departments: blender: exports: - .animbake.abc - light: - default_elements: - - default_light_rig + layout: programs: blender: + exports: + - .instancer.blend imports: - - .glb - - .mesh.blend - - .animbake.abc - - .shadergraph.blend - layout: + - .mesh.blend + light: + default_elements: + - default_light_rig programs: blender: - exports: - - .instancer.blend imports: - - .mesh.blend + - .glb + - .mesh.blend + - .animbake.abc + - .shadergraph.blend lookdev: programs: blender: @@ -55,34 +58,54 @@ departments: - .mesh.blend - .shadergraph.blend - .animbake.abc + sound: + programs: + ingest: + exports: + - .ogg assets: + 2d: + icons: + - iconA: + departments: + design: + - vector + - 2x + - iconB: {} + sprites: + enemy: + - zombieA: {} + player: + - playerA: {} 3d: + environment: + - cubeWorldA: + departments: + layout: + - !depends(defaultCubeA;defaultCubeB) cubeInstancer + - cubeWorldB: {} prop: - $template: departments: + lookdev: + - shadergraph model: - !shot_local mesh - lod - test - lookdev: - - shadergraph - - defaultCubeA: - departments: {} - - defaultCubeB: - departments: {} - environment: - - cubeWorldA: - departments: - layout: - - !depends(defaultCubeA;defaultCubeB) cubeInstancer - 2d: - icons: - - iconA: - departments: - design: - - vector - - 2x + - defaultCubeA: {} + - defaultCubeB: {} + audio: + sfx: + impact: + - impactGlassLight: + departments: + sound: + - varA + - varB + - varC + - varD shots: '103': @@ -104,3 +127,4 @@ shots: version_control: type: versioned_directories + seperate_shots_and_assets: true diff --git a/example/basic/scripts/ingest/ingest_audio_file.js b/example/basic/scripts/ingest/ingest_audio_file.js new file mode 100644 index 0000000..a142d3f --- /dev/null +++ b/example/basic/scripts/ingest/ingest_audio_file.js @@ -0,0 +1,17 @@ +async (data) => { + console.log('Ingest running user script!') + console.log(data) + + var export_result = await conduct.api.doExport(data['department'], data['asset'], data['element'], data['shot'], 'ingest', data['format']) + console.log(export_result) + + var directory = export_result['directory'] + var name = export_result['recommended_file_name'] + var extension = export_result['file_format'] + + var ingest_file = data['new_file'] + var result = directory + "/" + name + extension + + let command = `ffmpeg -i '${ingest_file}' -c:a libvorbis '${result}'` + os.execute(command) +}; diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_000.ogg b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_000.ogg new file mode 100644 index 0000000..d59cffe Binary files /dev/null and b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_000.ogg differ diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_001.ogg b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_001.ogg new file mode 100644 index 0000000..4953510 Binary files /dev/null and b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_001.ogg differ diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_002.ogg b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_002.ogg new file mode 100644 index 0000000..4ec4d86 Binary files /dev/null and b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_002.ogg differ diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_003.ogg b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_003.ogg new file mode 100644 index 0000000..3eef070 Binary files /dev/null and b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_003.ogg differ diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_004.ogg b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_004.ogg new file mode 100644 index 0000000..0115fe4 Binary files /dev/null and b/example/basic/setup/asset/sound/impactGlassLight/ingest/impactGlass_light_004.ogg differ diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_000 - License.txt b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_000 - License.txt new file mode 100644 index 0000000..4890fe5 --- /dev/null +++ b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_000 - License.txt @@ -0,0 +1,23 @@ + + + Impact Sounds (1.0) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 19-12-2019 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_001 - License.txt b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_001 - License.txt new file mode 100644 index 0000000..4890fe5 --- /dev/null +++ b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_001 - License.txt @@ -0,0 +1,23 @@ + + + Impact Sounds (1.0) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 19-12-2019 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_002 - License.txt b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_002 - License.txt new file mode 100644 index 0000000..4890fe5 --- /dev/null +++ b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_002 - License.txt @@ -0,0 +1,23 @@ + + + Impact Sounds (1.0) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 19-12-2019 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_003 - License.txt b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_003 - License.txt new file mode 100644 index 0000000..4890fe5 --- /dev/null +++ b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_003 - License.txt @@ -0,0 +1,23 @@ + + + Impact Sounds (1.0) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 19-12-2019 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_004 - License.txt b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_004 - License.txt new file mode 100644 index 0000000..4890fe5 --- /dev/null +++ b/example/basic/setup/asset/sound/impactGlassLight/ingest/license/impactGlass_light_004 - License.txt @@ -0,0 +1,23 @@ + + + Impact Sounds (1.0) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 19-12-2019 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Request: http://request.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/example/basic/setup/asset/sound/impactGlassLight/ingest/sources.md b/example/basic/setup/asset/sound/impactGlassLight/ingest/sources.md new file mode 100644 index 0000000..a03954a --- /dev/null +++ b/example/basic/setup/asset/sound/impactGlassLight/ingest/sources.md @@ -0,0 +1,6 @@ + - `impactGlass_light_000.ogg`: https://kenney.nl/assets/impact-sounds at 2025-03-23 14:12:15.092518611 +10:30:00 + - `impactGlass_light_001.ogg`: https://kenney.nl/assets/impact-sounds at 2025-03-23 14:12:15.220441588 +10:30:00 + - `impactGlass_light_002.ogg`: https://kenney.nl/assets/impact-sounds at 2025-03-23 14:12:15.346174524 +10:30:00 + - `impactGlass_light_003.ogg`: https://kenney.nl/assets/impact-sounds at 2025-03-23 14:12:15.471324395 +10:30:00 + - `impactGlass_light_004.ogg`: https://kenney.nl/assets/impact-sounds at 2025-03-23 14:12:15.597987283 +10:30:00 + - `impactGlass_light_001.ogg`: https://kenney.nl/assets/impact-sounds at 2025-03-23 14:19:38.710524974 +10:30:00 diff --git a/src/cli/mod.rs b/src/cli/mod.rs index a70ae1e..db2bd73 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -7,7 +7,7 @@ use clap::Parser; use log::*; pub use result::CliResult; -use crate::core::commands::{write_command_result, Command, CommandType}; +use crate::core::commands::{write_command_result, Command, CommandContext, CommandType}; #[derive(Debug, Parser)] #[command(name = "conduct")] @@ -67,7 +67,7 @@ fn get_project_manifest_path(cli: &CLI) -> PathBuf { } } - panic!("Could not find project manifest") + panic!("Could not find project manifest, you might want to use `--project-dir `") } pub fn cli() -> CliResult { @@ -93,7 +93,7 @@ pub fn cli() -> CliResult { match args.command { Some(command) => { info!("Running command: {:?}", command); - let result = CommandType::execute(command, &project); + let result = CommandType::execute(command, &project, CommandContext { is_cli: true }); match result { Ok(value) => match value { @@ -103,7 +103,7 @@ pub fn cli() -> CliResult { } None => CliResult::Success, }, - Err(_) => CliResult::Error("".to_string()), + Err(error) => CliResult::Error(error.to_string()), } } None => { diff --git a/src/core/mod.rs b/src/core.rs similarity index 92% rename from src/core/mod.rs rename to src/core.rs index bc6be8f..bdaad01 100644 --- a/src/core/mod.rs +++ b/src/core.rs @@ -3,6 +3,7 @@ pub mod commands; pub mod context; pub mod department; pub mod element; +pub mod error; pub mod format; pub mod program; pub mod project; diff --git a/src/core/commands/command_create.rs b/src/core/commands/command_create.rs index 6402627..150438c 100644 --- a/src/core/commands/command_create.rs +++ b/src/core/commands/command_create.rs @@ -1,29 +1,109 @@ -use std::sync::RwLock; +use core::fmt; +use std::{collections::BTreeMap, sync::RwLock}; use clap::{command, Args}; use log::info; -use crate::core::project::Project; +use crate::core::{ + asset::{Asset, AssetEntry}, + project::Project, +}; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct CreateArgs { #[command(flatten)] #[serde(flatten)] common: CommonArgs, + + #[arg(short, long)] + pub category: Option, } impl Command for CreateArgs { fn execute( self, project: &RwLock, + context: CommandContext, ) -> Result, CommandError> { info!("Returning result from command create!"); - project.read().unwrap().save(); + let mut p = project.write().unwrap(); + + match self.common.asset { + Some(asset) => match create_asset(&mut p, asset) { + Err(err) => return Err(err), + Ok(_) => (), + }, + None => (), + }; + + match self.category { + Some(category) => match p.create_category_tree_from_path(&category) { + Some(err) => return Err(CommandError::Message(format!("{}", err).to_string())), + None => (), + }, + None => (), + } + + if context.is_cli { + p.save(); + } Ok(None) } } + +fn create_asset( + p: &mut std::sync::RwLockWriteGuard<'_, Project>, + asset: String, +) -> Result<(), CommandError> { + let parts: Vec = asset.split("/").map(|x| x.to_string()).collect(); + let asset_name = parts.last().unwrap(); + match p.get_asset_by_name(asset_name.clone()) { + Some(existing) => { + return Err(CommandError::Message(format!( + "Asset '{}' already exists at '{}'", + asset_name, existing.1 + ))) + } + None => (), + } + let path = &parts[..(parts.len() - 1)]; + let category_path = path.join("/"); + match p.create_category_tree_from_path(&category_path) { + Some(err) => return Err(CommandError::Message(format!("{}", err).to_string())), + None => (), + } + + let category = p.get_mut_category_by_path(category_path); + + match category { + Some(category) => { + for child in category.children.iter() { + match child.1 { + AssetEntry::Asset(asset) => (), + AssetEntry::Category(asset_category) => { + return Err(CommandError::Message( + "Trying to add an asset to a category which contains other categories, this is not currently supported!" + .to_string(), + )) + } + } + } + + info!("Adding asset '{}' to category: '{}'", asset, category.name); + category.children.insert( + asset_name.clone(), + AssetEntry::Asset(Asset { + departments: BTreeMap::new(), + }), + ); + } + None => (), + } + + Ok(()) +} diff --git a/src/core/commands/command_dialog.rs b/src/core/commands/command_dialog.rs index 08e1e16..91dc118 100644 --- a/src/core/commands/command_dialog.rs +++ b/src/core/commands/command_dialog.rs @@ -6,7 +6,7 @@ use query_string_builder::QueryString; use crate::{core::project::Project, gui}; use serde::{Deserialize, Serialize}; -use super::{error::CommandError, Command}; +use super::{error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct DialogArgs { @@ -26,6 +26,7 @@ impl Command for DialogArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { let (_, argv) = argmap::parse(self.extras.iter()); log::debug!("Got extras: {:?}", argv); diff --git a/src/core/commands/command_export.rs b/src/core/commands/command_export.rs index 526aafb..1dce1e3 100644 --- a/src/core/commands/command_export.rs +++ b/src/core/commands/command_export.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use crate::core::{project::Project, version_control::VersionControl}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct ExportArgs { @@ -26,6 +26,7 @@ impl Command for ExportArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { info!("Exporting Asset!"); diff --git a/src/core/commands/command_get_asset_tree.rs b/src/core/commands/command_get_asset_tree.rs index 8342f32..091c629 100644 --- a/src/core/commands/command_get_asset_tree.rs +++ b/src/core/commands/command_get_asset_tree.rs @@ -10,7 +10,7 @@ use crate::core::{ }; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct GetAssetTreeArgs { @@ -61,6 +61,7 @@ impl Command for GetAssetTreeArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { let project = project.read().unwrap(); diff --git a/src/core/commands/command_ingest.rs b/src/core/commands/command_ingest.rs new file mode 100644 index 0000000..ac80c5a --- /dev/null +++ b/src/core/commands/command_ingest.rs @@ -0,0 +1,244 @@ +use std::{fmt::format, fs::OpenOptions, path::PathBuf, sync::RwLock, time::SystemTime}; + +use clap::{command, Args}; +use log::{error, info, warn}; +use time::OffsetDateTime; +use ts_rs::TS; + +use super::{ + args::CommonArgs, command_setup::SetupArgs, error::CommandError, Command, CommandContext, +}; +use crate::{ + core::{program, project::Project, shot::shot_resolver::ShotResolver}, + utils, +}; +use serde::{Deserialize, Serialize}; +use std::io::prelude::*; + +#[derive(Debug, Args, Serialize, Deserialize)] +pub struct IngestArgs { + #[command(flatten)] + #[serde(flatten)] + common: CommonArgs, + + #[arg(long)] + target_format: Option, + + #[clap(long)] + file: Option, + + #[clap(long)] + license: Option, + + #[clap(long)] + source: Option, +} + +#[derive(Debug, Args, Serialize, Deserialize, TS)] +#[ts(export, export_to = "../ui/src/bindings/bindings_gen.ts")] +pub struct IngestResult { + original_file: Option, + new_file: Option, + new_license_file: Option, + script: Option, +} + +impl Command for IngestArgs { + fn execute( + self, + project: &RwLock, + context: CommandContext, + ) -> Result, CommandError> { + if self.common.asset.is_none() || self.common.department.is_none() { + return Err(CommandError::InvalidArguments); + } + + let setup = SetupArgs { + common: self.common, + file_format: "".to_string(), + dry: true, + }; + + let project_path = project.try_read().unwrap().get_root_directory(); + + let result = Command::execute(setup, project, context); + + let folder = match result { + Ok(val) => match val { + Some(result) => match result { + serde_json::Value::Object(map) => Some( + map.get("folder") + .unwrap() + .clone() + .as_str() + .unwrap() + .to_string(), + ), + _ => None, + }, + None => None, + }, + Err(_) => None, + }; + + let mut result = IngestResult { + original_file: self.file.clone(), + new_file: None, + script: None, + new_license_file: None, + }; + + match folder { + Some(folder) => { + let mut path = PathBuf::from(folder); + path.push("ingest"); + + _ = std::fs::create_dir_all(&path); + + match self.file.clone() { + Some(file) => { + let mut file_path = path.clone(); + let original = PathBuf::from(file.clone()); + file_path.push(original.file_name().unwrap()); + + match std::fs::copy(&original, &file_path) { + Ok(_) => info!("Copied file to {}", file_path.to_str().unwrap()), + Err(err) => { + return Err(CommandError::Message(format!( + "Failed to copy file!: {:?}", + err + ))) + } + } + + result.new_file = Some(file_path.to_str().unwrap().to_string()); + + let file_relative = file_path.strip_prefix(&project_path).unwrap(); + + match self.source { + Some(source) => { + let mut sources_path = project_path.clone(); + sources_path.push("logs"); + std::fs::create_dir_all(&sources_path); + + sources_path.push("ingest_sources.csv"); + + let exists = std::fs::exists(&sources_path).unwrap(); + + let mut file = OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(&sources_path) + .unwrap(); + + if !exists { + writeln!(file, "FILE,ORIGINAL_FILE_NAME,ORIGINAL_FILE_PATH,SOURCE,TIMESTAMP,USER"); + } + + let now = OffsetDateTime::now_local().unwrap(); + + write!(file, "{},", file_relative.to_str().unwrap()); + write!(file, "{},", original.file_name().unwrap().to_str().unwrap()); + write!(file, "{},", original.to_str().unwrap()); + write!(file, "{},", source); + write!(file, "{},", now); + writeln!(file, "{},", whoami::username()); + + } + None => warn!("No source for this ingest has been specified! continuing regardless"), + } + } + None => (), + } + + match self.license { + Some(license) => { + let mut license_path = path.clone(); + license_path.push("license"); + _ = std::fs::create_dir_all(&license_path); + + let original = PathBuf::from(license.clone()); + let file_name = PathBuf::from(self.file.unwrap()); + let file_name = file_name.file_stem().unwrap().to_str().unwrap(); + let license_file_name = original.file_name().unwrap().to_str().unwrap(); + license_path.push(format!("{} - {}", file_name, license_file_name)); + + info!( + "Copying license from {} to: {}", + license, + license_path.to_str().unwrap() + ); + + match std::fs::copy(&original, &license_path) { + Ok(_) => { + info!("Copied license file to {}", license_path.to_str().unwrap()); + result.new_license_file = + Some(license_path.to_str().unwrap().to_string()); + } + Err(err) => { + return Err(CommandError::Message(format!( + "Failed to copy license file!: {:?}", + err + ))) + } + } + } + None => (), + } + + let project = project.read().unwrap(); + let program = project.programs.get("ingest"); + + let program = match program { + Some(program) => program, + None => { + return Err(CommandError::Message( + "No 'ingest' program specified".to_string(), + )) + } + }; + + match self.target_format { + Some(file_format) => { + let script = program.exports.get(&file_format); + + match script { + Some(script) => { + info!("Ingesting with script: {}", script); + + let mut script_path = project.get_root_directory(); + script_path.push("scripts"); + script_path.push("ingest"); + script_path.push(script); + + match std::fs::read_to_string(script_path) { + Ok(text) => { + result.script = Some(text); + } + Err(_) => { + warn!("Script file not found!"); + } + } + } + None => { + return Err(CommandError::Message(format!( + "No script has been specified for the format {}", + file_format + ))) + } + } + } + None => warn!("No target format was specified, so we cant run any script! continuing on regardless"), + } + } + None => { + return Err(CommandError::Message( + "Failed to get setup folder".to_string(), + )) + } + } + + return Ok(Some(serde_json::to_value(result).unwrap())); + } +} diff --git a/src/core/commands/command_list_assets.rs b/src/core/commands/command_list_assets.rs index 5a8eff5..2d284c6 100644 --- a/src/core/commands/command_list_assets.rs +++ b/src/core/commands/command_list_assets.rs @@ -6,7 +6,7 @@ use ts_rs::TS; use crate::core::{department::DepartmentFinder, project::Project}; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct ListAssetsArgs { @@ -25,6 +25,7 @@ impl Command for ListAssetsArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { let project = project.read().unwrap(); let assets = project.get_assets_flattened(); diff --git a/src/core/commands/command_list_elements.rs b/src/core/commands/command_list_elements.rs index ea2c7dd..096214b 100644 --- a/src/core/commands/command_list_elements.rs +++ b/src/core/commands/command_list_elements.rs @@ -10,7 +10,7 @@ use crate::core::{ }; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct ListElementsArgs { @@ -38,6 +38,7 @@ impl Command for ListElementsArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { if self.common.asset.is_none() { return Err(CommandError::InvalidArguments); @@ -54,13 +55,18 @@ impl Command for ListElementsArgs { }; let project = project.read().unwrap(); + + let asset = self.common.asset.unwrap(); + let asset = asset.split("/").last().unwrap(); + + let elements = project.get_elements(asset.to_string(), &context); + let elements = match elements { + Ok(elements) => elements, + Err(err) => return Err(CommandError::Message(format!("{}", err))), + }; + let mut result = ListElementsResult { - elements: project - .get_elements(self.common.asset.unwrap(), &context) - .keys() - .into_iter() - .map(|f| f.to_string()) - .collect(), + elements: elements.keys().into_iter().map(|f| f.to_string()).collect(), }; result.elements.sort(); diff --git a/src/core/commands/command_list_export_formats.rs b/src/core/commands/command_list_export_formats.rs index d78bc8a..28a56af 100644 --- a/src/core/commands/command_list_export_formats.rs +++ b/src/core/commands/command_list_export_formats.rs @@ -7,7 +7,7 @@ use ts_rs::TS; use crate::core::project::Project; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct ListExportFormatsArgs { @@ -29,6 +29,7 @@ impl Command for ListExportFormatsArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { if self.common.department.is_none() { return Err(CommandError::InvalidArguments); diff --git a/src/core/commands/command_list_shots.rs b/src/core/commands/command_list_shots.rs index 6ec106b..6c73bf8 100644 --- a/src/core/commands/command_list_shots.rs +++ b/src/core/commands/command_list_shots.rs @@ -6,7 +6,7 @@ use ts_rs::TS; use crate::core::project::Project; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; use crate::core::shot::shot_resolver::ShotResolver; #[derive(Debug, Args, Serialize, Deserialize)] @@ -26,6 +26,7 @@ impl Command for ListShotsArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { let project = project.read().unwrap(); diff --git a/src/core/commands/command_load_assets.rs b/src/core/commands/command_load_assets.rs index 2a1a2e1..2cfa670 100644 --- a/src/core/commands/command_load_assets.rs +++ b/src/core/commands/command_load_assets.rs @@ -10,7 +10,7 @@ use crate::core::{ }; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; pub enum LoadReason { Requested, @@ -51,6 +51,7 @@ impl Command for LoadAssetsArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { let project = project.read().unwrap(); @@ -86,6 +87,12 @@ impl Command for LoadAssetsArgs { info!("----"); info!("Loading Asset: `{}`", asset); let elements = project.get_elements(asset.to_string(), &c); + + let elements = match elements { + Ok(elements) => elements, + Err(err) => return Err(CommandError::Message(format!("{}", err))), + }; + for element in elements.iter() { debug!("Resolved element: {:?}", element); } @@ -169,6 +176,12 @@ fn get_required_assets( let mut result = Vec::new(); for asset in asset_names.into_iter() { let elements = project.get_elements(asset.to_string(), context); + + let elements = match elements { + Ok(elements) => elements, + Err(_) => return vec![], + }; + for (element, data) in elements.iter() { match data.get_dependencies() { Some(dependencies) => { diff --git a/src/core/commands/command_resolve_elements.rs b/src/core/commands/command_resolve_elements.rs new file mode 100644 index 0000000..19b227f --- /dev/null +++ b/src/core/commands/command_resolve_elements.rs @@ -0,0 +1,91 @@ +use std::{ + collections::{BTreeMap, HashMap}, + sync::RwLock, +}; + +use argmap::new; +use clap::{command, Args}; +use ts_rs::TS; + +use crate::core::{ + context::{Context, ContextMode}, + element::{ + self, element_resolver::ElementResolver, resolved_element_data::ResolvedElementData, + }, + project::Project, + version_control::{VersionControl, VersionControlFile}, +}; +use serde::{Deserialize, Serialize}; + +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; + +#[derive(Debug, Args, Serialize, Deserialize)] +pub struct ResolveElementsArgs { + #[command(flatten)] + #[serde(flatten)] + common: CommonArgs, +} + +#[derive(Debug, Serialize, Deserialize, TS)] +pub struct ResolvedElementResult { + info: ResolvedElementData, + versions: Vec, +} + +#[derive(Debug, Serialize, Deserialize, TS)] +#[ts(export, export_to = "../ui/src/bindings/bindings_gen.ts")] +pub struct ResolveElementsResult { + pub result: BTreeMap, +} + +impl Command for ResolveElementsArgs { + fn execute( + self, + project: &RwLock, + _context: CommandContext, + ) -> Result, CommandError> { + if self.common.asset.is_none() { + return Err(CommandError::InvalidArguments); + } + + let context = Context { + department: self.common.department, + shot: self.common.shot, + mode: ContextMode::Export, + }; + + let project = project.read().unwrap(); + + let asset = self.common.asset.unwrap(); + let asset = asset.split("/").last().unwrap(); + + let elements = project.get_elements(asset.to_string(), &context); + let elements = match elements { + Ok(elements) => elements, + Err(err) => return Err(CommandError::Message(format!("{}", err))), + }; + + let mut result: BTreeMap = BTreeMap::new(); + + for element in elements.iter() { + let files = VersionControl::get_element_files( + &project.version_control, + &project, + element.0.clone(), + element.1, + ); + + result.insert( + element.0.clone(), + ResolvedElementResult { + info: element.1.clone(), + versions: files.clone(), + }, + ); + } + + Ok(Some( + serde_json::to_value(ResolveElementsResult { result: result }).unwrap(), + )) + } +} diff --git a/src/core/commands/command_save.rs b/src/core/commands/command_save.rs new file mode 100644 index 0000000..dda3c7c --- /dev/null +++ b/src/core/commands/command_save.rs @@ -0,0 +1,30 @@ +use std::{collections::BTreeMap, sync::RwLock}; + +use clap::{command, Args}; +use log::info; + +use crate::core::{ + asset::{Asset, AssetEntry}, + project::Project, +}; +use serde::{Deserialize, Serialize}; + +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; + +#[derive(Debug, Args, Serialize, Deserialize)] +pub struct SaveArgs {} + +impl Command for SaveArgs { + fn execute( + self, + project: &RwLock, + context: CommandContext, + ) -> Result, CommandError> { + info!("Saving Project!"); + + let project = project.read().unwrap(); + project.save(); + + Ok(None) + } +} diff --git a/src/core/commands/command_setup.rs b/src/core/commands/command_setup.rs index fa0f2d9..4d9770d 100644 --- a/src/core/commands/command_setup.rs +++ b/src/core/commands/command_setup.rs @@ -7,16 +7,16 @@ use ts_rs::TS; use crate::core::{project::Project, shot::shot_resolver::ShotResolver}; use serde::{Deserialize, Serialize}; -use super::{args::CommonArgs, error::CommandError, Command}; +use super::{args::CommonArgs, error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct SetupArgs { #[command(flatten)] #[serde(flatten)] - common: CommonArgs, + pub common: CommonArgs, #[arg(short, long)] - file_format: String, + pub file_format: String, #[arg(long)] pub dry: bool, @@ -37,6 +37,7 @@ impl Command for SetupArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { if self.common.asset.is_none() || self.common.department.is_none() { return Err(CommandError::InvalidArguments); diff --git a/src/core/commands/command_summary.rs b/src/core/commands/command_summary.rs index 282fdfc..64ed0ea 100644 --- a/src/core/commands/command_summary.rs +++ b/src/core/commands/command_summary.rs @@ -8,7 +8,7 @@ use ts_rs::TS; use crate::core::project::Project; -use super::{error::CommandError, Command}; +use super::{error::CommandError, Command, CommandContext}; #[derive(Debug, Args, Serialize, Deserialize)] pub struct SummaryArgs {} @@ -26,6 +26,7 @@ impl Command for SummaryArgs { fn execute( self, project: &RwLock, + _context: CommandContext, ) -> Result, CommandError> { let project = project.read().unwrap(); diff --git a/src/core/commands/mod.rs b/src/core/commands/mod.rs index 60d029f..9ede73b 100644 --- a/src/core/commands/mod.rs +++ b/src/core/commands/mod.rs @@ -2,11 +2,14 @@ mod command_create; mod command_dialog; mod command_export; mod command_get_asset_tree; +mod command_ingest; mod command_list_assets; mod command_list_elements; mod command_list_export_formats; mod command_list_shots; mod command_load_assets; +mod command_resolve_elements; +mod command_save; mod command_setup; mod command_summary; mod error; @@ -27,11 +30,14 @@ pub use command_dialog::DialogOptions; pub use command_export::ExportArgs; use command_get_asset_tree::GetAssetTreeArgs; +use command_ingest::IngestArgs; use command_list_assets::ListAssetsArgs; use command_list_elements::ListElementsArgs; use command_list_export_formats::ListExportFormatsArgs; use command_list_shots::ListShotsArgs; use command_load_assets::LoadAssetsArgs; +use command_resolve_elements::ResolveElementsArgs; +use command_save::SaveArgs; use command_setup::SetupArgs; use command_summary::SummaryArgs; use log::{info, warn}; @@ -41,10 +47,17 @@ use enum_dispatch::enum_dispatch; use error::CommandError; use serde::{Deserialize, Serialize}; +pub struct CommandContext { + pub is_cli: bool, +} + #[enum_dispatch] pub trait Command { - fn execute(self, _project: &RwLock) - -> Result, CommandError>; + fn execute( + self, + _project: &RwLock, + context: CommandContext, + ) -> Result, CommandError>; } #[derive(Debug, Subcommand, Serialize, Deserialize)] @@ -79,9 +92,15 @@ pub enum CommandType { /// List all shots ListShots(ListShotsArgs), + ResolveElements(ResolveElementsArgs), + GetAssetTree(GetAssetTreeArgs), LoadAssets(LoadAssetsArgs), + + Save(SaveArgs), + + Ingest(IngestArgs), } pub fn write_command_result(result: serde_json::Value) { diff --git a/src/core/element/element_resolver.rs b/src/core/element/element_resolver.rs index db603e8..15cf969 100644 --- a/src/core/element/element_resolver.rs +++ b/src/core/element/element_resolver.rs @@ -5,6 +5,7 @@ use log::{debug, trace, warn}; use crate::core::{ asset::Asset, context::{Context, ContextMode}, + error::ProjectError, project::Project, }; @@ -18,7 +19,7 @@ pub trait ElementResolver { &self, asset_name: String, context: &Context, - ) -> BTreeMap; + ) -> Result, ProjectError>; fn get_element( &self, @@ -42,7 +43,10 @@ impl ElementResolver for Project { ) -> Option { let result = self.get_elements(asset_name, context); - return result.get(&element_name).cloned(); + match result { + Ok(results) => results.get(&element_name).cloned(), + Err(_) => None, + } } // Resolve the list of elements for a given asset @@ -50,7 +54,7 @@ impl ElementResolver for Project { &self, asset_name: String, context: &Context, - ) -> BTreeMap { + ) -> Result, ProjectError> { let asset = self.get_asset_by_name(asset_name.clone()); trace!("Getting assets with context: {:#?}", context); @@ -59,7 +63,7 @@ impl ElementResolver for Project { Some(asset) => asset, None => { warn!("Asset {} does not exist", asset_name); - panic!() + return Err(ProjectError::Message("Asset does not exist".to_string())); } }; @@ -81,7 +85,7 @@ impl ElementResolver for Project { ); add_elements_from_department_default(&mut result, self, asset, context, element_data); - result + Ok(result) } } diff --git a/src/core/element/resolved_element_data.rs b/src/core/element/resolved_element_data.rs index 8e73c67..449ba78 100644 --- a/src/core/element/resolved_element_data.rs +++ b/src/core/element/resolved_element_data.rs @@ -1,4 +1,7 @@ -#[derive(Clone, Debug)] +use serde::{Deserialize, Serialize}; +use ts_rs::TS; + +#[derive(Debug, Clone, Serialize, Deserialize, TS)] pub struct ResolvedElementData { shot_local: bool, dependencies: Option>, diff --git a/src/core/error.rs b/src/core/error.rs new file mode 100644 index 0000000..1b1d8cd --- /dev/null +++ b/src/core/error.rs @@ -0,0 +1,19 @@ +use std::{ + error::Error, + fmt::{self}, +}; + +#[derive(Debug)] +pub enum ProjectError { + Message(String), +} + +impl Error for ProjectError {} + +impl fmt::Display for ProjectError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ProjectError::Message(msg) => write!(f, "{}", msg), + } + } +} diff --git a/src/core/project.rs b/src/core/project.rs index 3d2af27..b9a125f 100644 --- a/src/core/project.rs +++ b/src/core/project.rs @@ -11,6 +11,7 @@ use crate::core::{asset, format}; use super::asset::{Asset, AssetCategory, AssetEntry}; use super::department::{self, Department}; +use super::error::ProjectError; use super::program::{self, Program}; use super::version_control::VersionControlConfig; @@ -184,6 +185,71 @@ impl Project { None } + + pub fn get_mut_category_by_path(&mut self, path: String) -> Option<&mut AssetCategory> { + let parts = path.split('/'); + + let mut current = &mut self.assets; + + for part in parts.into_iter() { + trace!("Looking for part: {}", part); + let result = current.children.get_mut(part); + match result { + Some(result) => match result { + AssetEntry::Category(asset_category) => current = asset_category, + _ => panic!("Found unexpected asset while parsing category path"), + }, + None => return None, + } + } + + Some(current) + } + + pub fn create_category_tree_from_path(&mut self, path: &String) -> Option { + let parts: Vec = path.split('/').map(|x| x.to_string()).collect(); + let mut current = &mut self.assets; + + for i in 0..parts.len() { + let part = &parts[i]; + + let entry = current.children.get(part); + current = match entry { + Some(_) => match current.children.get_mut(part) { + Some(entry) => match entry { + AssetEntry::Asset(_) => panic!(), + AssetEntry::Category(asset_category) => asset_category, + }, + _ => panic!(), + }, + None => { + for child in current.children.values().into_iter() { + match child { + AssetEntry::Asset(_) => return Some(ProjectError::Message("Cannot create a new category in a category which already contains assets".to_string())), + AssetEntry::Category(_) => continue, + } + } + + current.children.insert( + part.clone(), + AssetEntry::Category(AssetCategory { + name: part.clone(), + template: None, + children: BTreeMap::new(), + }), + ); + + let result = current.children.get_mut(part).unwrap(); + match result { + AssetEntry::Asset(_) => panic!(), + AssetEntry::Category(asset_category) => asset_category, + } + } + } + } + + return None; + } } fn insert_assets_to_map<'a>( diff --git a/src/core/version_control/common.rs b/src/core/version_control/common.rs index 80832c6..c1d82c9 100644 --- a/src/core/version_control/common.rs +++ b/src/core/version_control/common.rs @@ -76,7 +76,7 @@ pub fn resolve_element_path( trace!("Resolved shot: {}", shot) }, None => { - return Err(ExportError::Message("Tried to resolve the path of a shot_local element, but we are not in a shot context".into())) + return Err(ExportError::Message(format!("Tried to resolve the path of a shot_local element '{}', but we are not in a shot context", element_name).into())) }, } } diff --git a/src/core/version_control/mod.rs b/src/core/version_control/mod.rs index c977bca..1d5ae83 100644 --- a/src/core/version_control/mod.rs +++ b/src/core/version_control/mod.rs @@ -7,6 +7,7 @@ use direct::VersionControlConfigDirect; use enum_dispatch::enum_dispatch; use serde::{Deserialize, Serialize}; use symlink::VersionControlConfigSymlink; +use ts_rs::TS; use versioned_directories::VersionControlConfigVersionedDirectories; use super::{commands::ExportArgs, element::resolved_element_data::ResolvedElementData, project}; @@ -28,7 +29,7 @@ pub struct ExportResult { pub script: String, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, TS)] pub struct VersionControlFile { pub path: String, pub version: String, diff --git a/src/gui/mod.rs b/src/gui.rs similarity index 54% rename from src/gui/mod.rs rename to src/gui.rs index 691dd73..84d7abe 100644 --- a/src/gui/mod.rs +++ b/src/gui.rs @@ -3,11 +3,15 @@ mod embedded_files; mod router; mod routes; -use std::sync::{Arc, RwLock}; +use std::{ + clone, + sync::{Arc, RwLock}, +}; -use log::{debug, info, trace}; +use log::{debug, info, trace, warn, Log}; use router::{ApiEntry, RequestContext}; use routes::register_routes; +use serde_json::json; use tao::{ dpi::{LogicalSize, PhysicalPosition, Position, Size}, event::{Event, WindowEvent}, @@ -15,9 +19,9 @@ use tao::{ window::WindowBuilder, }; -use wry::WebViewBuilder; +use wry::{WebView, WebViewBuilder}; -use crate::core::commands::DialogOptions; +use crate::{core::commands::DialogOptions, utils}; enum UserWindowEvent { Exit, @@ -40,7 +44,7 @@ fn get_homepage_url() -> String { } fn get_init_script() -> String { - let str = include_str!("../../ui/api.js").to_string(); + let str = include_str!("../ui/api.js").to_string(); let base = get_custom_protocol_url(); let result = str.replace("${BASE_PATH}", &base); @@ -66,6 +70,8 @@ pub fn gui(project: crate::core::project::Project, dialog_options: Option>> = Arc::new(RwLock::new(None)); + if let Some(options) = dialog_options { page += options.path.as_str(); title = options.title; @@ -97,10 +103,13 @@ pub fn gui(project: crate::core::project::Project, dialog_options: Option { *control_flow = ControlFlow::Exit; } - _ => (), + event => {} }, Event::UserEvent(event) => match event { UserWindowEvent::Exit => { @@ -162,3 +177,94 @@ pub fn gui(project: crate::core::project::Project, dialog_options: Option>>, event: wry::DragDropEvent) -> bool { + match event { + wry::DragDropEvent::Drop { paths, position } => { + info!("Received drop event"); + + match webview_ref.clone().read() { + Ok(webview) => { + info!("Handling drop!"); + if webview.is_some() { + info!("Posting event"); + + let mut result = vec![]; + + for path in paths.iter() { + let mime = utils::mime::mime_from_file_path(path); + + result.push(json!({ + "path": path, + "mime": mime + })); + } + + let _ = webview.as_ref().unwrap().evaluate_script( + &format!( + "window.postMessage({});", + json!({ + "type": "drag_drop_dropped", + "data": result + }) + ) + .to_string(), + ); + } else { + info!("Failed to get webview") + } + + info!("Done handling drop"); + } + Err(_) => warn!("Failed to read"), + } + + info!("Done handling drop event"); + return true; + } + wry::DragDropEvent::Enter { paths, position } => match webview_ref.clone().read() { + Ok(webview) => { + info!("Handling Drag Drop Enter!"); + if webview.is_some() { + let _ = webview.as_ref().unwrap().evaluate_script( + &format!( + "window.postMessage({});", + json!({ + "type": "drag_drop_enter", + "data": paths + }) + ) + .to_string(), + ); + } else { + warn!("Failed to get webview") + } + } + Err(_) => warn!("Failed to read"), + }, + wry::DragDropEvent::Leave => match webview_ref.clone().read() { + Ok(webview) => { + info!("Handling Drag Drop Leave!"); + if webview.is_some() { + let _ = webview.as_ref().unwrap().evaluate_script( + &format!( + "window.postMessage({});", + json!({ + "type": "drag_drop_leave" + }) + ) + .to_string(), + ); + } else { + warn!("Failed to get webview") + } + } + Err(_) => warn!("Failed to read"), + }, + wry::DragDropEvent::Over { position } => (), + _ => { + info!("Some other event :o"); + } + } + true +} diff --git a/src/gui/api_result.rs b/src/gui/api_result.rs index e4f8777..4b16f1b 100644 --- a/src/gui/api_result.rs +++ b/src/gui/api_result.rs @@ -1,6 +1,13 @@ +#[derive(Debug)] +pub enum ApiResultType { + Json(serde_json::Value), + Binary(Vec), + None, +} + #[derive(Debug)] pub enum ApiResult { - Ok(Option), + Ok(ApiResultType), Error(String), OkExit, } diff --git a/src/gui/router.rs b/src/gui/router.rs index 0e81cba..c5868da 100644 --- a/src/gui/router.rs +++ b/src/gui/router.rs @@ -11,7 +11,10 @@ use wry::{ RequestAsyncResponder, }; -use crate::{core::project::Project, gui::embedded_files}; +use crate::{ + core::project::Project, + gui::{api_result::ApiResultType, embedded_files}, +}; use super::api_result::ApiResult; @@ -111,14 +114,18 @@ fn handle_api( match (handler.handler)(&request, m.params, context) { Some(result) => match result { ApiResult::Ok(value) => match value { - Some(response) => response_builder + ApiResultType::Json(value) => response_builder .status(200) .header("Content-Type", "text/json") .body(Cow::Owned::<[u8]>( - serde_json::to_string(&response).unwrap().into(), + serde_json::to_string(&value).unwrap().into(), )) .unwrap(), - None => response_builder + ApiResultType::Binary(value) => response_builder + .status(200) + .body(Cow::Owned::<[u8]>(value)) + .unwrap(), + ApiResultType::None => response_builder .status(200) .body(Cow::Owned("Ok".into())) .unwrap(), diff --git a/src/gui/routes/mod.rs b/src/gui/routes.rs similarity index 83% rename from src/gui/routes/mod.rs rename to src/gui/routes.rs index d68b7d3..acd7ec4 100644 --- a/src/gui/routes/mod.rs +++ b/src/gui/routes.rs @@ -2,8 +2,10 @@ use super::router::ApiEntry; mod command; mod dialogue; +mod os; pub fn register_routes(router: &mut matchit::Router) { command::register_routes(router); dialogue::register_routes(router); + os::register_routes(router); } diff --git a/src/gui/routes/command.rs b/src/gui/routes/command.rs index 82f056c..837734b 100644 --- a/src/gui/routes/command.rs +++ b/src/gui/routes/command.rs @@ -5,9 +5,9 @@ use url::Url; use wry::http::Request; use crate::{ - core::commands::{Command, CommandType}, + core::commands::{Command, CommandContext, CommandType}, gui::{ - api_result::ApiResult, + api_result::{ApiResult, ApiResultType}, router::{ApiEntry, RequestContext}, }, }; @@ -67,10 +67,14 @@ fn do_command( match command { Ok(command) => { debug!("Got command: {:?}", command); - let command_result = CommandType::execute(command, &context.project); + let command_result = + CommandType::execute(command, &context.project, CommandContext { is_cli: false }); match command_result { - Ok(value) => Some(ApiResult::Ok(value)), + Ok(value) => match value { + Some(value) => Some(ApiResult::Ok(ApiResultType::Json(value))), + None => Some(ApiResult::Ok(ApiResultType::None)), + }, Err(err) => Some(ApiResult::Error(err.to_string())), } } diff --git a/src/gui/routes/os.rs b/src/gui/routes/os.rs new file mode 100644 index 0000000..37a830c --- /dev/null +++ b/src/gui/routes/os.rs @@ -0,0 +1,116 @@ +use std::{ + collections::HashMap, + fs::File, + io::{BufReader, Read}, + os, + process::Command, +}; + +use log::{info, warn}; +use matchit::Params; +use serde_json::json; +use url::Url; +use urlencoding::decode; +use wry::http::{Method, Request}; + +use crate::{ + core::commands::write_command_result, + gui::{ + api_result::{ApiResult, ApiResultType}, + router::{ApiEntry, RequestContext}, + }, +}; + +pub fn register_routes(router: &mut matchit::Router) { + router + .insert( + "/os/execute", + ApiEntry { + handler: execute, + threaded: true, + }, + ) + .unwrap(); + + router + .insert( + "/os/file", + ApiEntry { + handler: file, + threaded: true, + }, + ) + .unwrap(); +} + +fn file( + request: &Request>, + _params: Params, + _context: RequestContext, +) -> Option { + if request.method() != Method::GET { + return Some(ApiResult::Error("Invalid http method".to_string())); + } + + let url = Url::parse(request.uri().to_string().as_str()).unwrap(); + + let hash_query: HashMap<_, _> = url.query_pairs().into_owned().collect(); + + let path = hash_query.get("path"); + + match path { + Some(path) => { + let path = decode(path).unwrap(); + + info!("Reading file at path: {}", path); + + let f = File::open(path.to_string()).unwrap(); + let mut reader = BufReader::new(f); + let mut buffer = Vec::new(); + + reader.read_to_end(&mut buffer).unwrap(); + + info!("Returning {} bytes", buffer.len()); + return Some(ApiResult::Ok(ApiResultType::Binary(buffer))); + } + None => todo!(), + } +} + +fn execute( + request: &Request>, + _params: Params, + _context: RequestContext, +) -> Option { + if request.method() != Method::POST { + return Some(ApiResult::Error("Invalid http method".to_string())); + } + + let body = request.body(); + + let s = match std::str::from_utf8(body) { + Ok(v) => v, + Err(_) => return Some(ApiResult::Error("Invalid body".to_string())), + }; + + info!("Executing os command: {}", s); + + let output = if cfg!(target_os = "windows") { + Command::new("cmd").args(["/C", s]).output() + } else { + Command::new("sh").arg("-c").arg(s).output() + }; + + match output { + Ok(output) => { + info!("status: {}", output.status); + info!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + info!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + } + Err(_) => { + warn!("Failed to execute command! :(") + } + } + + Some(ApiResult::Ok(ApiResultType::None)) +} diff --git a/src/main.rs b/src/main.rs index 97fb8c8..2d9d396 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod cli; pub mod core; mod gui; +mod utils; use log::*; diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..909a531 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1 @@ +pub mod mime; diff --git a/src/utils/mime.rs b/src/utils/mime.rs new file mode 100644 index 0000000..90a8a41 --- /dev/null +++ b/src/utils/mime.rs @@ -0,0 +1,25 @@ +use std::{ + collections::HashMap, + path::{Path, PathBuf}, +}; + +pub fn mime_from_file_path(path: &PathBuf) -> Option { + let known_types = HashMap::from([("wav", "audio/wav")]); + + let path = Path::new(&path); + let extension = path.extension(); + + match extension { + Some(extension) => { + let ext = extension.to_str().unwrap(); + let mime = known_types.get(ext); + match mime { + Some(mime) => { + return Some(mime.to_string()); + } + None => None, + } + } + None => None, + } +} diff --git a/ui/api.js b/ui/api.js index 00eddf7..e51303c 100644 --- a/ui/api.js +++ b/ui/api.js @@ -1,6 +1,11 @@ let base_path = "${BASE_PATH}"; window.conduct = { + + url_base_path: function () { + return base_path + }, + get: function (path) { return fetch(`${base_path}/${path}`); }, @@ -12,3 +17,18 @@ window.conduct = { }); } }; + +window.os = { + execute: function (command) { + return fetch(`${base_path}/os/execute`, { + method: "POST", + body: command + }); + }, + + file: function (path) { + return fetch(`${base_path}/os/file?path=${encodeURIComponent(path)}`, { + method: "GET", + }); + }, +} diff --git a/ui/package-lock.json b/ui/package-lock.json index 8188767..bb92e50 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "license": "MIT", "dependencies": { - "@kobalte/core": "^0.13.7", + "@kobalte/core": "^0.13.9", "@solidjs/router": "^0.15.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -783,9 +783,10 @@ } }, "node_modules/@kobalte/core": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/@kobalte/core/-/core-0.13.7.tgz", - "integrity": "sha512-COhjWk1KnCkl3qMJDvdrOsvpTlJ9gMLdemkAn5SWfbPn/lxJYabejnNOk+b/ILGg7apzQycgbuo48qb8ppqsAg==", + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/@kobalte/core/-/core-0.13.9.tgz", + "integrity": "sha512-TkeSpgNy7I5k8jwjqT9CK3teAxN0aFb3yyL9ODb06JVYMwXIk+UKrizoAF1ahLUP85lKnxv44B4Y5cXkHShgqw==", + "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.5.1", "@internationalized/date": "^3.4.0", diff --git a/ui/package.json b/ui/package.json index a09e179..e99e06c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -20,7 +20,7 @@ "vite-plugin-solid": "^2.8.2" }, "dependencies": { - "@kobalte/core": "^0.13.7", + "@kobalte/core": "^0.13.9", "@solidjs/router": "^0.15.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index ae09591..de3b491 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,30 +1,297 @@ -import { createResource, type Component, Show, Switch, Match } from 'solid-js'; +import { createResource, type Component, Show, Switch, Match, createSignal, For } from 'solid-js'; -import { get, getSummary, doExport, doCreate } from './api'; +import { getSummary, getAssetTree, doCreate, get, saveChanges, listElements, resolveElements } from './api'; import { Button } from './components/ui/button'; import { ColorModeProvider } from '@kobalte/core/color-mode'; import { Separator } from './components/ui/separator'; -import { SummaryResponse } from './bindings/summary_response'; +import AssetTree from './components/organisms/asset_tree'; +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from './components/ui/dialog'; +import { ContextMenuCheckboxItem, ContextMenuGroupLabel, ContextMenuItem } from './components/ui/context-menu'; +import { TextField, TextFieldInput } from './components/ui/text-field'; +import { Callout, CalloutContent, CalloutTitle } from './components/ui/callout'; +import { Menubar, MenubarContent, MenubarItem, MenubarItemLabel, MenubarMenu, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger } from './components/ui/menubar'; +import { comma } from 'postcss/lib/list'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './components/ui/card'; +import { Accordion } from '@kobalte/core/accordion'; +import { AccordionContent, AccordionItem, AccordionTrigger } from './components/ui/accordion'; +import { Label } from './components/ui/label'; +import { Tooltip, TooltipContent, TooltipTrigger } from './components/ui/tooltip'; + +interface CommandEntry { + command: string, + content: any, + label: string +} const App: Component = () => { const [info] = createResource(getSummary); + const [assets, { mutate, refetch }] = createResource(() => getAssetTree(null)); + + const [isCreateAssetDialogOpen, setOpenCreateAssetDialog] = createSignal(false); + + const [isCreateCategoryDialogOpen, setOpenCreateCategoryDialog] = createSignal(false); + const [category, setCategory] = createSignal(""); + const [newAssetName, setNewAssetname] = createSignal("") + + const [selectedPath, setSelectedPath] = createSignal("") + + const [resolvedElements] = createResource(selectedPath, resolveElements) + + const [error, setError] = createSignal("") + + const newAssetPath = () => { + let path = category() + if (path.length > 0) path += "/" + path += newAssetName() + return path + } + + const openCreateAssetDialog = (parent: string) => { + setOpenCreateAssetDialog(true) + setNewAssetname("") + setError("") + setCategory(parent) + } + + const openCreateCategoryDialog = (parent: string) => { + setNewAssetname("") + setError("") + setCategory(parent) + setOpenCreateCategoryDialog(true); + } + return ( -
-

{info()!.display_name}

-

{info()!.identifier}

- -
- - - +
+ + +
+

{info()!.display_name}

+

{info()!.identifier}

+
+ + + File + + { + saveChanges() + } + }>Save Changes + + + + Edit + + { + openCreateCategoryDialog("") + }}>Create Category + { + openCreateAssetDialog("") + }}>Create Asset + + + Undo + + + + + + + +
+
+
+
+ + setSelectedPath(path)} assets={assets} contextMenuBuilder={(path, entry) => ( + <> + console.log(path)}>Inspect + { + entry.type == "Category" && Object.entries(entry.children).every((e) => e[1]?.type != "Category") ? { openCreateAssetDialog(path) }} >Add Asset : <> + } + { + entry.type == "Category" && Object.entries(entry.children).every((e) => e[1]?.type == "Category") ? { openCreateCategoryDialog(path) }} >Add Subcategory : <> + } + + )}> +
+
+ + + + + {selectedPath().split("/").slice(-1)[0]} + + + {selectedPath()} + + + + + + {(item) => { + let entry = resolvedElements()?.result[item[0]]! + let versions = entry.versions + + return + + { +
+ +
+ + + + + + + + + + +
+
+ } +
+ + + {(version) => + +
+
+ + + + + +
+
+ + } +
+ +
+
; + } + }
+
+
+ +
+
+ +
+
+
+ +
+
+ + + + {category()} + + Add an asset to the category + + +
+ + + +
+ {newAssetPath()} +
+
+ + + Warning + + {error()} + + + + + + +
+
+ + + + + + {category()} + + Add a new asset category + + +
+ + + +
+ {newAssetPath()} +
+
+ + + Warning + + {error()} + + + + + + +
+
+ + +
- + ); }; diff --git a/ui/src/api.ts b/ui/src/api.ts index acbaa6e..60e2a4d 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -1,11 +1,18 @@ -import { AssetTreeCategory, ListAssetsResult, ListElementsResult, ListExportFormatsResult, ListShotsResult, SetupResult, SummaryResponse } from "./bindings/bindings_gen"; +import { AssetTreeCategory, IngestResult, ListAssetsResult, ListElementsResult, ListExportFormatsResult, ListShotsResult, ResolveElementsResult, SetupResult, SummaryResponse } from "./bindings/bindings_gen"; declare global { interface Window { conduct: { + url_base_path: () => String, get: (path: string) => Promise, post: (path: string, body: string) => Promise + api: any + }, + + os: { + execute: (command: any) => Promise, + file: (path: String) => Promise, } } } @@ -57,9 +64,17 @@ export async function doExport(department: string, asset: string, element: strin return await result.json() as SummaryResponse } -export async function doCreate(): Promise { - let result = await get("api/v1/command/create?asset=suzanneA&department=model") - return await result.json() as SummaryResponse +export async function doCreate(asset: string | null, category: string | null): Promise { + let result = await get("api/v1/command/create", { + "asset": asset, + "category": category + }) + + if (result.status == 200) { + return true + } else { + return await result.json() + } } export async function exitDialog(result: any) { @@ -110,6 +125,12 @@ export async function getAssetTree(department_filter: null | string = null): Pro return json as AssetTreeCategory } +export async function saveChanges(): Promise { + let result = await get("api/v1/command/save"); + let json = await result.json() + return json +} + export async function loadAssets(program: string, department: string, shot: null | string = null, assets: string[]): Promise { let result = await get("api/v1/command/load_assets", { "program": program, @@ -138,3 +159,30 @@ export async function listShots(): Promise { let result = await get("api/v1/command/list_shots") return await result.json() as ListShotsResult } + +export async function resolveElements(asset: string): Promise { + let result = await get("api/v1/command/resolve_elements", { + "asset": asset + }) + return await result.json() as ResolveElementsResult +} + +export async function doIngest(asset: string, element: string | null, department: string, shot: string | null, file: string, target_format: string | null, license: string, source: string): Promise { + let result = await get("api/v1/command/ingest", { + "asset": asset, + "element": element, + "department": department, + "file": file, + "target_format": target_format, + "shot": shot, + "license": license, + "source": source + }) + return await result.json() as IngestResult +} + +window.conduct.api = { + doExport, + doIngest, + listShots +} \ No newline at end of file diff --git a/ui/src/bindings/bindings_gen.ts b/ui/src/bindings/bindings_gen.ts index 13df221..abc4ea6 100644 --- a/ui/src/bindings/bindings_gen.ts +++ b/ui/src/bindings/bindings_gen.ts @@ -1,9 +1,14 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ResolvedElementResult } from "../../../bindings/ResolvedElementResult"; + +export type AssetLoadStep = { asset: string, element: string, script: string, file: string, file_type: string, version: string, }; export type AssetTreeCategory = { children: { [key in string]?: AssetTreeEntry }, }; export type AssetTreeEntry = { "type": "Asset" } | { "type": "Category" } & AssetTreeCategory; +export type IngestResult = { original_file: string | null, new_file: string | null, new_license_file: string | null, script: string | null, }; + export type ListAssetsResult = { assets: Array, }; export type ListElementsResult = { elements: Array, }; @@ -12,6 +17,10 @@ export type ListExportFormatsResult = { formats: Array, }; export type ListShotsResult = { shots: Array, }; -export type SetupResult = { asset: string, department: string, path: string, file_name: string, shot: string | null, }; +export type LoadAssetsResult = { results: Array, }; + +export type ResolveElementsResult = { result: { [key in string]?: ResolvedElementResult }, }; + +export type SetupResult = { asset: string, department: string, folder: string, file_name: string, path: string, shot: string | null, }; export type SummaryResponse = { display_name: string, identifier: string, assets_flat: Array, departments: Array, }; diff --git a/ui/src/components/organisms/asset_tree.tsx b/ui/src/components/organisms/asset_tree.tsx new file mode 100644 index 0000000..3907b14 --- /dev/null +++ b/ui/src/components/organisms/asset_tree.tsx @@ -0,0 +1,121 @@ +import { Component, createResource, createSignal, For, Resource, Show } from "solid-js"; +import { getAssetTree } from "~/api"; +import { ToggleGroup } from "../ui/toggle-group"; +import { Label } from "../ui/label"; +import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../ui/accordion"; +import { AssetTreeCategory, AssetTreeEntry } from "~/bindings/bindings_gen"; +import { Checkbox } from "../ui/checkbox"; +import { ContextMenu } from "@kobalte/core/context-menu"; +import { ContextMenuContent, ContextMenuGroupLabel, ContextMenuItem, ContextMenuSeparator, ContextMenuTrigger } from "../ui/context-menu"; +import { Button } from "../ui/button"; + +import * as AccordionPrimitive from "@kobalte/core/accordion" +import { Tooltip } from "@kobalte/core/tooltip"; +import { TooltipContent, TooltipTrigger } from "../ui/tooltip"; + +export interface AssetTreeProps { + contextMenuBuilder?(name: string, entry: AssetTreeEntry): any + categoryContextMenuBuilder?(name: string): any + onPathClicked?(path: string): any + assets: Resource +} + +const AssetTree = (props: AssetTreeProps) => { + + let closedPaths: string[] = []; + let contextMenuBuilder = props.contextMenuBuilder; + let onClick = props.onPathClicked; + + + const assetEntry: Component<{ entry_name: string, entry: AssetTreeEntry, current_path: string }> = (props) => { + let path = props.current_path + (props.current_path == "" ? props.entry_name : ("/" + props.entry_name)); + if (props.entry.type == "Asset") { + return ( +
+ + + + + + + + {contextMenuBuilder?.(path, props.entry)} + + + + +
+ ) + } + + return ( + + { + if (selection.includes(path)) { + let idx = closedPaths.indexOf(path) + if (idx != -1) { + closedPaths.splice(idx, 1); + } + } else { + closedPaths.push(path) + } + }} > + +
+ + + + + + + + + + + {contextMenuBuilder?.(path, props.entry)} + + +
+ + + {(item) => + assetEntry({ entry_name: item[0], entry: item[1]!, current_path: path }) + } + + +
+
+ ) + } + + + + return ( +
+ + + + {(item) => item[1]!.type == 'Asset' ? ( +
+ {item[0]} +
+ ) :
{ + assetEntry({ + entry_name: item[0]!, + entry: item[1]!, + current_path: "" + }) + } +
+ } +
+
+
+
+ ); + +} + +export default AssetTree \ No newline at end of file diff --git a/ui/src/components/ui/accordion.tsx b/ui/src/components/ui/accordion.tsx index 3099a95..958dce4 100644 --- a/ui/src/components/ui/accordion.tsx +++ b/ui/src/components/ui/accordion.tsx @@ -17,7 +17,7 @@ const AccordionItem = ( props: PolymorphicProps> ) => { const [local, others] = splitProps(props as AccordionItemProps, ["class"]) - return + return } type AccordionTriggerProps = @@ -34,7 +34,7 @@ const AccordionTrigger = ( svg]:rotate-180", + "flex flex-1 items-center justify-between py-1 font-medium transition-all hover:underline [&[data-expanded]>svg]:rotate-90", local.class )} {...others} @@ -50,7 +50,7 @@ const AccordionTrigger = ( stroke-linejoin="round" class="size-4 shrink-0 transition-transform duration-200" > - + diff --git a/ui/src/components/ui/context-menu.tsx b/ui/src/components/ui/context-menu.tsx new file mode 100644 index 0000000..0cf6c94 --- /dev/null +++ b/ui/src/components/ui/context-menu.tsx @@ -0,0 +1,249 @@ +import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js" +import { splitProps } from "solid-js" + +import * as ContextMenuPrimitive from "@kobalte/core/context-menu" +import type { PolymorphicProps } from "@kobalte/core/polymorphic" + +import { cn } from "~/lib/utils" + +const ContextMenuTrigger = ContextMenuPrimitive.Trigger +const ContextMenuPortal = ContextMenuPrimitive.Portal +const ContextMenuSub = ContextMenuPrimitive.Sub +const ContextMenuGroup = ContextMenuPrimitive.Group +const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup + +const ContextMenu: Component = (props) => { + return +} + +type ContextMenuContentProps = + ContextMenuPrimitive.ContextMenuContentProps & { + class?: string | undefined + } + +const ContextMenuContent = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuContentProps, ["class"]) + return ( + + + + ) +} + +type ContextMenuItemProps = + ContextMenuPrimitive.ContextMenuItemProps & { + class?: string | undefined + } + +const ContextMenuItem = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuItemProps, ["class"]) + return ( + + ) +} + +const ContextMenuShortcut: Component> = (props) => { + const [local, others] = splitProps(props, ["class"]) + return +} + +type ContextMenuSeparatorProps = + ContextMenuPrimitive.ContextMenuSeparatorProps & { + class?: string | undefined + } + +const ContextMenuSeparator = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuSeparatorProps, ["class"]) + return ( + + ) +} + +type ContextMenuSubTriggerProps = + ContextMenuPrimitive.ContextMenuSubTriggerProps & { + class?: string | undefined + children?: JSX.Element + } + +const ContextMenuSubTrigger = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuSubTriggerProps, ["class", "children"]) + return ( + + {local.children} + + + + + ) +} + +type ContextMenuSubContentProps = + ContextMenuPrimitive.ContextMenuSubContentProps & { + class?: string | undefined + } + +const ContextMenuSubContent = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuSubContentProps, ["class"]) + return ( + + ) +} + +type ContextMenuCheckboxItemProps = + ContextMenuPrimitive.ContextMenuCheckboxItemProps & { + class?: string | undefined + children?: JSX.Element + } + +const ContextMenuCheckboxItem = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuCheckboxItemProps, ["class", "children"]) + return ( + + + + + + + + + {local.children} + + ) +} + +type ContextMenuGroupLabelProps = + ContextMenuPrimitive.ContextMenuGroupLabelProps & { + class?: string | undefined + } + +const ContextMenuGroupLabel = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuGroupLabelProps, ["class"]) + return ( + + ) +} + +type ContextMenuRadioItemProps = + ContextMenuPrimitive.ContextMenuRadioItemProps & { + class?: string | undefined + children?: JSX.Element + } + +const ContextMenuRadioItem = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as ContextMenuRadioItemProps, ["class", "children"]) + return ( + + + + + + + + + {local.children} + + ) +} + +export { + ContextMenu, + ContextMenuTrigger, + ContextMenuPortal, + ContextMenuContent, + ContextMenuItem, + ContextMenuShortcut, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubTrigger, + ContextMenuSubContent, + ContextMenuCheckboxItem, + ContextMenuGroup, + ContextMenuGroupLabel, + ContextMenuRadioGroup, + ContextMenuRadioItem +} \ No newline at end of file diff --git a/ui/src/components/ui/dialog.tsx b/ui/src/components/ui/dialog.tsx new file mode 100644 index 0000000..a703e9c --- /dev/null +++ b/ui/src/components/ui/dialog.tsx @@ -0,0 +1,141 @@ +import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js" +import { splitProps } from "solid-js" + +import * as DialogPrimitive from "@kobalte/core/dialog" +import type { PolymorphicProps } from "@kobalte/core/polymorphic" + +import { cn } from "~/lib/utils" + +const Dialog = DialogPrimitive.Root +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal: Component = (props) => { + const [, rest] = splitProps(props, ["children"]) + return ( + +
+ {props.children} +
+
+ ) +} + +type DialogOverlayProps = + DialogPrimitive.DialogOverlayProps & { class?: string | undefined } + +const DialogOverlay = ( + props: PolymorphicProps> +) => { + const [, rest] = splitProps(props as DialogOverlayProps, ["class"]) + return ( + + ) +} + +type DialogContentProps = + DialogPrimitive.DialogContentProps & { + class?: string | undefined + children?: JSX.Element + } + +const DialogContent = ( + props: PolymorphicProps> +) => { + const [, rest] = splitProps(props as DialogContentProps, ["class", "children"]) + return ( + + + + {props.children} + + + + + + Close + + + + ) +} + +const DialogHeader: Component> = (props) => { + const [, rest] = splitProps(props, ["class"]) + return ( +
+ ) +} + +const DialogFooter: Component> = (props) => { + const [, rest] = splitProps(props, ["class"]) + return ( +
+ ) +} + +type DialogTitleProps = DialogPrimitive.DialogTitleProps & { + class?: string | undefined +} + +const DialogTitle = ( + props: PolymorphicProps> +) => { + const [, rest] = splitProps(props as DialogTitleProps, ["class"]) + return ( + + ) +} + +type DialogDescriptionProps = + DialogPrimitive.DialogDescriptionProps & { + class?: string | undefined + } + +const DialogDescription = ( + props: PolymorphicProps> +) => { + const [, rest] = splitProps(props as DialogDescriptionProps, ["class"]) + return ( + + ) +} + +export { + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription +} diff --git a/ui/src/components/ui/menubar.tsx b/ui/src/components/ui/menubar.tsx new file mode 100644 index 0000000..fc5179f --- /dev/null +++ b/ui/src/components/ui/menubar.tsx @@ -0,0 +1,313 @@ +import type { Component, ComponentProps, JSX, ValidComponent } from "solid-js" +import { splitProps } from "solid-js" + +import * as MenubarPrimitive from "@kobalte/core/menubar" +import type { PolymorphicProps } from "@kobalte/core/polymorphic" + +import { cn } from "~/lib/utils" + +const MenubarGroup = MenubarPrimitive.Group +const MenubarPortal = MenubarPrimitive.Portal +const MenubarSub = MenubarPrimitive.Sub +const MenubarRadioGroup = MenubarPrimitive.RadioGroup + +type MenubarRootProps = MenubarPrimitive.MenubarRootProps & { + class?: string | undefined +} + +const Menubar = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarRootProps, ["class"]) + return ( + + ) +} + +const MenubarMenu: Component = (props) => { + return +} + +type MenubarTriggerProps = + MenubarPrimitive.MenubarTriggerProps & { class?: string | undefined } + +const MenubarTrigger = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarTriggerProps, ["class"]) + return ( + + ) +} + +type MenubarContentProps = + MenubarPrimitive.MenubarContentProps & { class?: string | undefined } + +const MenubarContent = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarContentProps, ["class"]) + return ( + + + + ) +} + +type MenubarSubTriggerProps = + MenubarPrimitive.MenubarSubTriggerProps & { + class?: string | undefined + children?: JSX.Element + inset?: boolean + } + +const MenubarSubTrigger = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarSubTriggerProps, [ + "class", + "children", + "inset" + ]) + return ( + + {local.children} + + + + + ) +} + +type MenubarSubContentProps = + MenubarPrimitive.MenubarSubContentProps & { + class?: string | undefined + } + +const MenubarSubContent = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarSubContentProps, ["class"]) + return ( + + + + ) +} + +type MenubarItemProps = MenubarPrimitive.MenubarItemProps & { + class?: string | undefined + inset?: boolean +} + +const MenubarItem = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarItemProps, ["class", "inset"]) + return ( + + ) +} + +type MenubarCheckboxItemProps = + MenubarPrimitive.MenubarCheckboxItemProps & { + class?: string | undefined + children?: JSX.Element + } + +const MenubarCheckboxItem = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarCheckboxItemProps, ["class", "children"]) + return ( + + + + + + + + + {local.children} + + ) +} + +type MenubarRadioItemProps = + MenubarPrimitive.MenubarRadioItemProps & { + class?: string | undefined + children?: JSX.Element + } + +const MenubarRadioItem = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarRadioItemProps, ["class", "children"]) + return ( + + + + + + + + + {local.children} + + ) +} + +type MenubarItemLabelProps = + MenubarPrimitive.MenubarItemLabelProps & { + class?: string | undefined + inset?: boolean + } + +const MenubarItemLabel = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarItemLabelProps, ["class", "inset"]) + return ( + + ) +} + +type MenubarGroupLabelProps = + MenubarPrimitive.MenubarGroupLabelProps & { + class?: string | undefined + inset?: boolean + } + +const MenubarGroupLabel = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarGroupLabelProps, ["class", "inset"]) + return ( + + ) +} + +type MenubarSeparatorProps = + MenubarPrimitive.MenubarSeparatorProps & { class?: string | undefined } + +const MenubarSeparator = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as MenubarSeparatorProps, ["class"]) + return ( + + ) +} + +const MenubarShortcut: Component> = (props) => { + const [local, others] = splitProps(props, ["class"]) + return ( + + ) +} + +export { + Menubar, + MenubarMenu, + MenubarTrigger, + MenubarContent, + MenubarItem, + MenubarSeparator, + MenubarItemLabel, + MenubarGroupLabel, + MenubarCheckboxItem, + MenubarRadioGroup, + MenubarRadioItem, + MenubarPortal, + MenubarSubContent, + MenubarSubTrigger, + MenubarGroup, + MenubarSub, + MenubarShortcut +} diff --git a/ui/src/components/ui/text-field.tsx b/ui/src/components/ui/text-field.tsx new file mode 100644 index 0000000..e5b821b --- /dev/null +++ b/ui/src/components/ui/text-field.tsx @@ -0,0 +1,152 @@ +import type { ValidComponent } from "solid-js" +import { mergeProps, splitProps } from "solid-js" + +import type { PolymorphicProps } from "@kobalte/core" +import * as TextFieldPrimitive from "@kobalte/core/text-field" +import { cva } from "class-variance-authority" + +import { cn } from "~/lib/utils" + +type TextFieldRootProps = + TextFieldPrimitive.TextFieldRootProps & { + class?: string | undefined + } + +const TextField = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as TextFieldRootProps, ["class"]) + return +} + +type TextFieldInputProps = + TextFieldPrimitive.TextFieldInputProps & { + class?: string | undefined + type?: + | "button" + | "checkbox" + | "color" + | "date" + | "datetime-local" + | "email" + | "file" + | "hidden" + | "image" + | "month" + | "number" + | "password" + | "radio" + | "range" + | "reset" + | "search" + | "submit" + | "tel" + | "text" + | "time" + | "url" + | "week" + } + +const TextFieldInput = ( + rawProps: PolymorphicProps> +) => { + const props = mergeProps[]>({ type: "text" }, rawProps) + const [local, others] = splitProps(props as TextFieldInputProps, ["type", "class"]) + return ( + + ) +} + +type TextFieldTextAreaProps = + TextFieldPrimitive.TextFieldTextAreaProps & { class?: string | undefined } + +const TextFieldTextArea = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as TextFieldTextAreaProps, ["class"]) + return ( + + ) +} + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", + { + variants: { + variant: { + label: "data-[invalid]:text-destructive", + description: "font-normal text-muted-foreground", + error: "text-xs text-destructive" + } + }, + defaultVariants: { + variant: "label" + } + } +) + +type TextFieldLabelProps = + TextFieldPrimitive.TextFieldLabelProps & { class?: string | undefined } + +const TextFieldLabel = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as TextFieldLabelProps, ["class"]) + return +} + +type TextFieldDescriptionProps = + TextFieldPrimitive.TextFieldDescriptionProps & { + class?: string | undefined + } + +const TextFieldDescription = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as TextFieldDescriptionProps, ["class"]) + return ( + + ) +} + +type TextFieldErrorMessageProps = + TextFieldPrimitive.TextFieldErrorMessageProps & { + class?: string | undefined + } + +const TextFieldErrorMessage = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as TextFieldErrorMessageProps, ["class"]) + return ( + + ) +} + +export { + TextField, + TextFieldInput, + TextFieldTextArea, + TextFieldLabel, + TextFieldDescription, + TextFieldErrorMessage +} diff --git a/ui/src/components/ui/tooltip.tsx b/ui/src/components/ui/tooltip.tsx new file mode 100644 index 0000000..444a329 --- /dev/null +++ b/ui/src/components/ui/tooltip.tsx @@ -0,0 +1,35 @@ +import type { ValidComponent } from "solid-js" +import { splitProps, type Component } from "solid-js" + +import type { PolymorphicProps } from "@kobalte/core/polymorphic" +import * as TooltipPrimitive from "@kobalte/core/tooltip" + +import { cn } from "~/lib/utils" + +const TooltipTrigger = TooltipPrimitive.Trigger + +const Tooltip: Component = (props) => { + return +} + +type TooltipContentProps = + TooltipPrimitive.TooltipContentProps & { class?: string | undefined } + +const TooltipContent = ( + props: PolymorphicProps> +) => { + const [local, others] = splitProps(props as TooltipContentProps, ["class"]) + return ( + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent } diff --git a/ui/src/index.tsx b/ui/src/index.tsx index 9b4c997..d486e1f 100644 --- a/ui/src/index.tsx +++ b/ui/src/index.tsx @@ -9,6 +9,7 @@ import DialogLoadAsset from './pages/dialogs/load_asset'; import { ColorModeProvider } from '@kobalte/core/color-mode'; import { Component } from 'solid-js'; import DialogExport from './pages/dialogs/export'; +import DialogIngest from './pages/dialogs/ingest'; const root = document.getElementById('root'); @@ -20,7 +21,7 @@ if (!(root instanceof HTMLElement)) { const rootComponent: Component = (props) => { return ( - + {props.children} ) @@ -31,4 +32,5 @@ render(() => ( + ), root!); diff --git a/ui/src/pages/dialogs/ingest.tsx b/ui/src/pages/dialogs/ingest.tsx new file mode 100644 index 0000000..85ee95b --- /dev/null +++ b/ui/src/pages/dialogs/ingest.tsx @@ -0,0 +1,412 @@ + +import { Combobox } from '@kobalte/core/*'; +import { useSearchParams } from '@solidjs/router'; +import { createEffect, createResource, createSignal, For, Show, type Component } from 'solid-js'; +import { build } from 'vite'; +import { doIngest, exitDialog, getSummary, listElements, listExportFormats, listShots } from '~/api'; +import { Button } from '~/components/ui/button'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '~/components/ui/card'; +import { Label } from '~/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '~/components/ui/select'; +import { TextField, TextFieldInput } from '~/components/ui/text-field'; + +interface file { + path: string; + mime: string; +} + +const DialogIngest: Component = () => { + const [files, setFiles] = createSignal([]) + const [blobs, setBlobs] = createSignal>(new Map()); + + const [selectedDepartment, setSelectedDepartment] = createSignal() + const [selectedShot, setSelectedShot] = createSignal() + + const [info] = createResource(getSummary); + const [shots] = createResource(listShots); + + const [license, setLicenseFiles] = createSignal([]) + + const [step, setStep] = createSignal("select_files") + + const [searchParams, setSearchParams] = useSearchParams(); + const targetAsset = () => searchParams.asset; + + const [elements] = createResource(selectedDepartment, (department) => listElements(targetAsset() as string, department)); + const [elementSelections, setElementSelections] = createSignal({}); + + const [formats] = createResource(selectedDepartment, (department) => listExportFormats(department, "ingest")); + const [formatSelections, setFormatSelections] = createSignal({}); + + const [shotSelections, setShotSelections] = createSignal({}); + + const [source, setSource] = createSignal(""); + + const [isIngesting, setIsIngesting] = createSignal(false); + const [ingestFinished, setIsIngestFinished] = createSignal(false); + const [ingestStatus, setIngestStatus] = createSignal(""); + + createEffect(async () => { + console.log("Selected files:", files()) + let f = files(); + + let map = new Map(); + + await Promise.all(f.map(async (file) => { + let req = await window.os.file(file.path); + let data = await req.arrayBuffer(); + console.log(data); + let blob = new Blob([data]); + map.set(file.path, blob); + + })); + + setBlobs(map); + }) + + addEventListener("drop", function () { + console.log("File entered"); + }); + + addEventListener("message", (event) => { + console.log("Received message!") + console.log(event) + + if (event.data.type == "drag_drop_dropped") { + console.log("Files dropped"); + + if (step() == "select_files") { + setFiles(event.data.data) + + setStep("select_license") + } else if (step() == "select_license") { + setLicenseFiles([event.data.data[0].path]) + } + + if (license().length > 0) { + setStep("add_source") + } + + } + + if (event.data.type == "drag_drop_enter") { + console.log("Drag drop started"); + } + + if (event.data.type == "drag_drop_leave") { + console.log("Drag drop ended"); + } + }); + + function onSourceChanged(value: string) { + setSource(value) + setStep("finish") + } + + + const delay = (ms: any) => new Promise(res => setTimeout(res, ms)); + + async function runIngest() { + setIsIngesting(true) + + for (var file of files()) { + try { + let element = elementSelections()[file.path] + let asset = targetAsset() as string + setIngestStatus(file + " -> " + asset + " " + "(" + element + ")") + let format = formatSelections()[file.path] + let dept = selectedDepartment() + let shot = shotSelections()[file.path] + let license_file = license()[0]; + let result = await doIngest(targetAsset() as string, element, dept!, shot, file.path, format, license_file, source()) + + + if (result['script'] != null) { + setIngestStatus("Running user script") + let data = { + ...result, + "element": element, + "asset": asset, + "department": dept, + "shot": shot, + "format": format + } + + let obj = eval(result['script']) + + console.log(obj) + await obj(data) + } + + console.log(result) + setIngestStatus("Ingested: " + JSON.stringify(result)) + + } catch (error) { + setIngestStatus("Error: " + JSON.stringify(error)) + await delay(5000) + } + } + + setIsIngestFinished(true) + setIngestStatus("Ingest finished") + } + + function onFinished() { + console.log("test") + console.log(history.length) + if (history.length > 1) { + history.back() + } else { + exitDialog(null) + } + } + + function canPreviewFileType(mime: String) { + if (mime.startsWith("audio/")) { + return true; + } + } + + function buildPreview(path: string, mime: string) { + var safe_path = encodeURIComponent(path); + let blob = blobs().get(path); + + if (blob == null) { + return ( +
+ ) + } + + let url = URL.createObjectURL(blob); + + if (mime.startsWith("audio")) { + + return ( +
+ +
+ ) + } + return ( +
+ TESTTT +
+ ) + } + + return ( + +
+ +
+
+ + + Ingesting + {ingestStatus()} + + + + + + + +
+
+
+ +
+
+ + + Ingest Files to {targetAsset()} + + + Drag and drop files to ingest + + + + + + + 0 && shots()}> + + + Selected Files + + + + { + (e) => { + return
+
+
+ + +
+ + +
+ + + + + + + + +
+
+
+ + + { + buildPreview(e.path, e.mime) + } + + +
+
+ } + } +
+ 0}> +
+ +
+
+
+
+
+ + + + + License + + Now, drag and drop the license associated with these files + + + + + { + (e) => { + return
{e}
+ } + } +
+
+
+ +
+
+ + + + + Source + + Please enter the URL where these files were sourced from (Download / Store page) + + + + + + + + + + +
+ + + + Import + + + + + + +
+
+
+
+ ); +}; + + +export default DialogIngest;