mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Dir sync filesystem watching
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"workspaces": [
|
||||
"packages/plugin-runtime",
|
||||
"packages/plugin-runtime-types",
|
||||
"packages/common-lib",
|
||||
"src-tauri/yaak-license",
|
||||
"src-tauri/yaak-models",
|
||||
"src-tauri/yaak-plugins",
|
||||
@@ -3638,6 +3639,10 @@
|
||||
"integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@yaakapp-internal/lib": {
|
||||
"resolved": "packages/common-lib",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp-internal/license": {
|
||||
"resolved": "src-tauri/yaak-license",
|
||||
"link": true
|
||||
@@ -15799,6 +15804,10 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"packages/common-lib": {
|
||||
"name": "@yaakapp-internal/lib",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"packages/plugin-runtime": {
|
||||
"name": "@yaakapp-internal/plugin-runtime",
|
||||
"dependencies": {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"workspaces": [
|
||||
"packages/plugin-runtime",
|
||||
"packages/plugin-runtime-types",
|
||||
"packages/common-lib",
|
||||
"src-tauri/yaak-license",
|
||||
"src-tauri/yaak-models",
|
||||
"src-tauri/yaak-plugins",
|
||||
|
||||
1
packages/common-lib/index.ts
Normal file
1
packages/common-lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './debounce';
|
||||
6
packages/common-lib/package.json
Normal file
6
packages/common-lib/package.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "@yaakapp-internal/lib",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"main": "index.ts"
|
||||
}
|
||||
362
src-tauri/Cargo.lock
generated
362
src-tauri/Cargo.lock
generated
@@ -1257,48 +1257,6 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "devtools-core"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78cdd51f6f62ad4eb9b6581d7e238e1779db3144ddbd711388f552e6ed3194b"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"devtools-wire-format",
|
||||
"futures",
|
||||
"http 0.2.12",
|
||||
"hyper 0.14.30",
|
||||
"log",
|
||||
"prost-types 0.12.6",
|
||||
"ringbuf",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tonic 0.10.2",
|
||||
"tonic-health",
|
||||
"tonic-web",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-layer",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "devtools-wire-format"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1c0de542960449c9566001c1879d10ede95f3f2e0013fdae0cc3b153bfabb0d"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"prost 0.12.6",
|
||||
"prost-types 0.12.6",
|
||||
"tonic 0.10.2",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -1726,6 +1684,15 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@@ -2377,12 +2344,6 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.4"
|
||||
@@ -2648,6 +2609,35 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.9.0"
|
||||
@@ -2805,6 +2795,26 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.2"
|
||||
@@ -2911,18 +2921,6 @@ version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "local-ip-address"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "612ed4ea9ce5acfb5d26339302528a5e1e59dfed95e9e11af3c083236ff1d15d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"neli",
|
||||
"thiserror 1.0.63",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "locale"
|
||||
version = "0.2.2"
|
||||
@@ -2980,15 +2978,6 @@ dependencies = [
|
||||
"tendril",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
|
||||
dependencies = [
|
||||
"regex-automata 0.1.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.10"
|
||||
@@ -3087,6 +3076,7 @@ checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.9",
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -3173,31 +3163,6 @@ dependencies = [
|
||||
"jni-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neli"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"libc",
|
||||
"log",
|
||||
"neli-proc-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "neli-proc-macros"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4"
|
||||
dependencies = [
|
||||
"either",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
@@ -3243,13 +3208,31 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
name = "notify"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
"bitflags 2.6.0",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3689,12 +3672,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pad"
|
||||
version = "0.1.6"
|
||||
@@ -4046,12 +4023,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -4521,17 +4492,8 @@ checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata 0.4.8",
|
||||
"regex-syntax 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
dependencies = [
|
||||
"regex-syntax 0.6.29",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4542,15 +4504,9 @@ checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.8.5",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
@@ -4670,16 +4626,6 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ringbuf"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "726bb493fe9cac765e8f96a144c3a8396bdf766dedad22e504b70b908dcbceb4"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv"
|
||||
version = "0.7.44"
|
||||
@@ -5252,15 +5198,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared_child"
|
||||
version = "1.0.1"
|
||||
@@ -5886,7 +5823,6 @@ dependencies = [
|
||||
"tauri-utils",
|
||||
"thiserror 2.0.7",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tray-icon",
|
||||
"url",
|
||||
"urlpattern",
|
||||
@@ -5991,33 +5927,6 @@ dependencies = [
|
||||
"thiserror 2.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-devtools"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5cd17faa36a826e5686bd0fda5bc3f4c903682263f00cd50f2f778fc4bb866"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"bytes",
|
||||
"cocoa 0.26.0",
|
||||
"colored",
|
||||
"devtools-core",
|
||||
"futures",
|
||||
"local-ip-address",
|
||||
"log",
|
||||
"objc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"swift-rs",
|
||||
"tauri",
|
||||
"tauri-plugin",
|
||||
"tokio",
|
||||
"tonic 0.10.2",
|
||||
"tonic-health",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tauri-plugin-dialog"
|
||||
version = "2.2.0"
|
||||
@@ -6225,7 +6134,6 @@ dependencies = [
|
||||
"tao",
|
||||
"tauri-runtime",
|
||||
"tauri-utils",
|
||||
"tracing",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webview2-com",
|
||||
@@ -6359,16 +6267,6 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiff"
|
||||
version = "0.9.1"
|
||||
@@ -6662,19 +6560,6 @@ dependencies = [
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-health"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f80db390246dfb46553481f6024f0082ba00178ea495dbb99e70ba9a4fafb5e1"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"prost 0.12.6",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tonic 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-reflection"
|
||||
version = "0.10.2"
|
||||
@@ -6688,26 +6573,6 @@ dependencies = [
|
||||
"tonic 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tonic-web"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fddb2a37b247e6adcb9f239f4e5cefdcc5ed526141a416b943929f13aea2cce"
|
||||
dependencies = [
|
||||
"base64 0.21.7",
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.30",
|
||||
"pin-project",
|
||||
"tokio-stream",
|
||||
"tonic 0.10.2",
|
||||
"tower-http",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
@@ -6728,24 +6593,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"http-body 0.4.6",
|
||||
"http-range-header",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
@@ -6788,36 +6635,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||
dependencies = [
|
||||
"matchers",
|
||||
"nu-ansi-term",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7055,12 +6872,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "value-bag"
|
||||
version = "1.9.0"
|
||||
@@ -7803,7 +7614,6 @@ dependencies = [
|
||||
"soup3",
|
||||
"tao-macros",
|
||||
"thiserror 2.0.7",
|
||||
"tracing",
|
||||
"url",
|
||||
"webkit2gtk",
|
||||
"webkit2gtk-sys",
|
||||
@@ -7906,7 +7716,6 @@ dependencies = [
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"tauri-plugin-clipboard-manager",
|
||||
"tauri-plugin-devtools",
|
||||
"tauri-plugin-dialog",
|
||||
"tauri-plugin-fs",
|
||||
"tauri-plugin-log",
|
||||
@@ -8027,6 +7836,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"hex",
|
||||
"log",
|
||||
"notify",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
|
||||
@@ -50,7 +50,6 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, features = ["raw_value"] }
|
||||
tauri = { workspace = true, features = ["devtools", "protocol-asset"] }
|
||||
tauri-plugin-clipboard-manager = "2.2.0"
|
||||
tauri-plugin-devtools = "2.0.0"
|
||||
tauri-plugin-dialog = "2.2.0"
|
||||
tauri-plugin-fs = "2.2.0"
|
||||
tauri-plugin-log = { version = "2.2.0", features = ["colored"] }
|
||||
|
||||
2
src-tauri/gen/schemas/acl-manifests.json
generated
2
src-tauri/gen/schemas/acl-manifests.json
generated
File diff suppressed because one or more lines are too long
10
src-tauri/gen/schemas/desktop-schema.json
generated
10
src-tauri/gen/schemas/desktop-schema.json
generated
@@ -5547,6 +5547,11 @@
|
||||
"type": "string",
|
||||
"const": "yaak-sync:allow-unstage"
|
||||
},
|
||||
{
|
||||
"description": "Enables the watch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-sync:allow-watch"
|
||||
},
|
||||
{
|
||||
"description": "Denies the activate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5651,6 +5656,11 @@
|
||||
"description": "Denies the unstage command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-sync:deny-unstage"
|
||||
},
|
||||
{
|
||||
"description": "Denies the watch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-sync:deny-watch"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
10
src-tauri/gen/schemas/macOS-schema.json
generated
10
src-tauri/gen/schemas/macOS-schema.json
generated
@@ -5547,6 +5547,11 @@
|
||||
"type": "string",
|
||||
"const": "yaak-sync:allow-unstage"
|
||||
},
|
||||
{
|
||||
"description": "Enables the watch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-sync:allow-watch"
|
||||
},
|
||||
{
|
||||
"description": "Denies the activate command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -5651,6 +5656,11 @@
|
||||
"description": "Denies the unstage command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-sync:deny-unstage"
|
||||
},
|
||||
{
|
||||
"description": "Denies the watch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "yaak-sync:deny-watch"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1668,20 +1668,20 @@ async fn cmd_check_for_updates(
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
#[cfg(debug_assertions)] // only enable instrumentation in development builds
|
||||
let devtools = tauri_plugin_devtools::init();
|
||||
// #[cfg(debug_assertions)] // only enable instrumentation in development builds
|
||||
// let devtools = tauri_plugin_devtools::init();
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
builder = builder.plugin(devtools);
|
||||
}
|
||||
// #[cfg(debug_assertions)]
|
||||
// {
|
||||
// builder = builder.plugin(devtools);
|
||||
// }
|
||||
|
||||
// Only use logger in production, because it conflicts with Tauri devtools
|
||||
#[cfg(not(debug_assertions))]
|
||||
// #[cfg(not(debug_assertions))]
|
||||
{
|
||||
use tauri_plugin_log::fern::colors::ColoredLevelConfig;
|
||||
use tauri_plugin_log::{Builder, Target, TargetKind};
|
||||
use fern::colors::ColoredLevelConfig;
|
||||
|
||||
let log_plugin = Builder::default()
|
||||
.targets([
|
||||
@@ -1706,7 +1706,7 @@ pub fn run() {
|
||||
.level_for("swc_ecma_codegen", log::LevelFilter::Off)
|
||||
.level_for("swc_ecma_transforms_base", log::LevelFilter::Off)
|
||||
.with_colors(ColoredLevelConfig::default())
|
||||
.level(log::LevelFilter::Info)
|
||||
.level(if is_dev() { log::LevelFilter::Debug } else { log::LevelFilter::Info })
|
||||
.build();
|
||||
|
||||
builder = builder.plugin(log_plugin);
|
||||
@@ -1995,7 +1995,9 @@ fn create_window(handle: &AppHandle, config: CreateWindowConfig) -> WebviewWindo
|
||||
"zoom_out" => w.emit("zoom_out", true).unwrap(),
|
||||
"settings" => w.emit("settings", true).unwrap(),
|
||||
"open_feedback" => {
|
||||
if let Err(e) = w.app_handle().opener().open_url("https://yaak.app/feedback", None::<&str>) {
|
||||
if let Err(e) =
|
||||
w.app_handle().opener().open_url("https://yaak.app/feedback", None::<&str>)
|
||||
{
|
||||
warn!("Failed to open feedback {e:?}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ log = "0.4.22"
|
||||
serde_json = "1.0.132"
|
||||
hex = "0.4.3"
|
||||
sha1 = "0.10.6"
|
||||
tokio = {version = "1.42.0", features = ["fs"]}
|
||||
tokio = { version = "1.42.0", features = ["fs", "sync", "macros"] }
|
||||
notify = "7.0.0"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { version = "2.0.3", features = ["build"] }
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type GitCommit = { author: string, when: string, message: string | null, };
|
||||
|
||||
export type GitStatus = "added" | "conflict" | "current" | "modified" | "removed" | "renamed" | "type_change";
|
||||
|
||||
export type GitStatusEntry = { relaPath: string, status: GitStatus, staged: boolean, prev: string | null, next: string | null, };
|
||||
5
src-tauri/yaak-sync/bindings/watch.ts
Normal file
5
src-tauri/yaak-sync/bindings/watch.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type WatchEvent = { paths: Array<string>, kind: string, };
|
||||
|
||||
export type WatchResult = { unlistenEvent: string, };
|
||||
@@ -1,4 +1,4 @@
|
||||
const COMMANDS: &[&str] = &["calculate", "apply"];
|
||||
const COMMANDS: &[&str] = &["calculate", "apply", "watch"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS).build();
|
||||
|
||||
@@ -1,21 +1,48 @@
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { Channel, invoke } from '@tauri-apps/api/core';
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import { Workspace } from '@yaakapp-internal/models';
|
||||
import { useEffect } from 'react';
|
||||
import { SyncOp } from './bindings/sync';
|
||||
import { WatchEvent, WatchResult } from './bindings/watch';
|
||||
|
||||
export const calculateSync = async (workspace: Workspace) => {
|
||||
if (!workspace.settingSyncDir) throw new Error("Workspace sync dir not configured");
|
||||
export async function calculateSync(workspace: Workspace) {
|
||||
if (!workspace.settingSyncDir) throw new Error('Workspace sync dir not configured');
|
||||
|
||||
return invoke<SyncOp[]>('plugin:yaak-sync|calculate', {
|
||||
workspaceId: workspace.id,
|
||||
dir: workspace.settingSyncDir,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export const applySync = async (workspace: Workspace, syncOps: SyncOp[]) => {
|
||||
export async function applySync(workspace: Workspace, syncOps: SyncOp[]) {
|
||||
console.log('Applying sync', syncOps);
|
||||
return invoke<void>('plugin:yaak-sync|apply', {
|
||||
workspaceId: workspace.id,
|
||||
dir: workspace.settingSyncDir,
|
||||
syncOps: syncOps
|
||||
syncOps: syncOps,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function useWatchWorkspace(workspace: Workspace | null, cb: (e: WatchEvent) => void) {
|
||||
const workspaceId = workspace?.id ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
if (workspaceId == null) return;
|
||||
|
||||
console.log('Watching workspace', workspaceId);
|
||||
const channel = new Channel<WatchEvent>();
|
||||
channel.onmessage = (event) => {
|
||||
cb(event);
|
||||
};
|
||||
const promise = invoke<WatchResult>('plugin:yaak-sync|watch', { workspaceId, channel });
|
||||
|
||||
return () => {
|
||||
promise
|
||||
.then(({ unlistenEvent }) => {
|
||||
console.log('Cancelling workspace watch', workspaceId, unlistenEvent);
|
||||
return emit(unlistenEvent);
|
||||
})
|
||||
.catch(console.error);
|
||||
};
|
||||
}, [workspaceId]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-watch"
|
||||
description = "Enables the watch command without any pre-configured scope."
|
||||
commands.allow = ["watch"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-watch"
|
||||
description = "Denies the watch command without any pre-configured scope."
|
||||
commands.deny = ["watch"]
|
||||
@@ -4,6 +4,7 @@ Default permissions for the plugin
|
||||
|
||||
- `allow-calculate`
|
||||
- `allow-apply`
|
||||
- `allow-watch`
|
||||
|
||||
## Permission Table
|
||||
|
||||
@@ -557,6 +558,32 @@ Enables the unstage command without any pre-configured scope.
|
||||
|
||||
Denies the unstage command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-sync:allow-watch`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the watch command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`yaak-sync:deny-watch`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the watch command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -3,4 +3,5 @@ description = "Default permissions for the plugin"
|
||||
permissions = [
|
||||
"allow-calculate",
|
||||
"allow-apply",
|
||||
"allow-watch",
|
||||
]
|
||||
|
||||
@@ -504,6 +504,16 @@
|
||||
"type": "string",
|
||||
"const": "deny-unstage"
|
||||
},
|
||||
{
|
||||
"description": "Enables the watch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "allow-watch"
|
||||
},
|
||||
{
|
||||
"description": "Denies the watch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "deny-watch"
|
||||
},
|
||||
{
|
||||
"description": "Default permissions for the plugin",
|
||||
"type": "string",
|
||||
|
||||
@@ -1,13 +1,27 @@
|
||||
use crate::error::Result;
|
||||
use crate::sync::{apply_sync, calculate_sync, SyncOp};
|
||||
use tauri::{command, Runtime, WebviewWindow};
|
||||
use crate::sync::{
|
||||
apply_sync_ops, apply_sync_state_ops, compute_sync_ops, get_db_candidates, get_fs_candidates,
|
||||
get_workspace_sync_dir, SyncOp,
|
||||
};
|
||||
use crate::watch::{watch_directory, WatchEvent};
|
||||
use chrono::Utc;
|
||||
use log::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::ipc::Channel;
|
||||
use tauri::{command, Listener, Runtime, WebviewWindow};
|
||||
use tokio::sync::watch;
|
||||
use ts_rs::TS;
|
||||
use yaak_models::queries::get_workspace;
|
||||
|
||||
#[command]
|
||||
pub async fn calculate<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<SyncOp>> {
|
||||
calculate_sync(&window, workspace_id).await
|
||||
let workspace = get_workspace(&window, workspace_id).await?;
|
||||
let db_candidates = get_db_candidates(&window, &workspace).await?;
|
||||
let fs_candidates = get_fs_candidates(&workspace).await?;
|
||||
Ok(compute_sync_ops(db_candidates, fs_candidates))
|
||||
}
|
||||
|
||||
#[command]
|
||||
@@ -16,5 +30,44 @@ pub async fn apply<R: Runtime>(
|
||||
sync_ops: Vec<SyncOp>,
|
||||
workspace_id: &str,
|
||||
) -> Result<()> {
|
||||
apply_sync(&window, workspace_id, sync_ops).await
|
||||
let workspace = get_workspace(&window, workspace_id).await?;
|
||||
let sync_state_ops = apply_sync_ops(&window, &workspace, sync_ops).await?;
|
||||
apply_sync_state_ops(&window, &workspace, sync_state_ops).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "watch.ts")]
|
||||
pub(crate) struct WatchResult {
|
||||
unlisten_event: String,
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn watch<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
workspace_id: &str,
|
||||
channel: Channel<WatchEvent>,
|
||||
) -> Result<WatchResult> {
|
||||
let workspace = get_workspace(&window, workspace_id).await?;
|
||||
let sync_dir = get_workspace_sync_dir(&workspace)?;
|
||||
let (cancel_tx, cancel_rx) = watch::channel(());
|
||||
|
||||
watch_directory(&sync_dir, channel, cancel_rx).await?;
|
||||
|
||||
let window_inner = window.clone();
|
||||
let unlisten_event =
|
||||
format!("watch-unlisten-{}-{}", workspace_id, Utc::now().timestamp_millis());
|
||||
|
||||
// TODO: Figure out a way to unlisten when the client window refreshes or closes. Perhaps with
|
||||
// a heartbeat mechanism, or ensuring only a single subscription per workspace (at least
|
||||
// this won't create `n` subs). We could also maybe have a global fs watcher that we keep
|
||||
// adding to here.
|
||||
window.listen_any(unlisten_event.clone(), move |event| {
|
||||
window_inner.unlisten(event.id());
|
||||
if let Err(e) = cancel_tx.send(()) {
|
||||
warn!("Failed to send cancel signal to watcher {e:?}");
|
||||
}
|
||||
});
|
||||
|
||||
Ok(WatchResult { unlisten_event })
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ pub enum Error {
|
||||
|
||||
#[error("Invalid sync file: {0}")]
|
||||
InvalidSyncFile(String),
|
||||
|
||||
#[error("Watch error: {0}")]
|
||||
NotifyError(#[from] notify::Error),
|
||||
}
|
||||
|
||||
impl Serialize for Error {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::commands::{apply, calculate};
|
||||
use crate::commands::{apply, calculate, watch};
|
||||
use tauri::{
|
||||
generate_handler,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
@@ -9,7 +9,8 @@ mod commands;
|
||||
mod error;
|
||||
mod models;
|
||||
mod sync;
|
||||
mod watch;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("yaak-sync").invoke_handler(generate_handler![calculate, apply]).build()
|
||||
Builder::new("yaak-sync").invoke_handler(generate_handler![calculate, apply, watch]).build()
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use ts_rs::TS;
|
||||
use yaak_models::models::{SyncState, Workspace};
|
||||
use yaak_models::queries::{
|
||||
delete_environment, delete_folder, delete_grpc_request, delete_http_request, delete_sync_state,
|
||||
delete_workspace, get_workspace, get_workspace_export_resources,
|
||||
delete_workspace, get_workspace_export_resources,
|
||||
list_sync_states_for_workspace, upsert_environment, upsert_folder, upsert_grpc_request,
|
||||
upsert_http_request, upsert_sync_state, upsert_workspace, UpdateSource,
|
||||
};
|
||||
@@ -65,7 +65,7 @@ impl Display for SyncOp {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum DbCandidate {
|
||||
pub(crate) enum DbCandidate {
|
||||
Added(SyncModel),
|
||||
Modified(SyncModel, SyncState),
|
||||
Deleted(SyncState),
|
||||
@@ -92,31 +92,7 @@ pub(crate) struct FsCandidate {
|
||||
checksum: String,
|
||||
}
|
||||
|
||||
pub(crate) async fn calculate_sync<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
workspace_id: &str,
|
||||
) -> Result<Vec<SyncOp>> {
|
||||
let workspace = get_workspace(window, workspace_id).await?;
|
||||
let db_candidates = get_db_candidates(window, &workspace).await?;
|
||||
let fs_candidates = get_fs_candidates(&workspace).await?;
|
||||
let sync_ops = compute_sync_ops(db_candidates, fs_candidates);
|
||||
|
||||
Ok(sync_ops)
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_sync<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
workspace_id: &str,
|
||||
sync_ops: Vec<SyncOp>,
|
||||
) -> Result<()> {
|
||||
let workspace = get_workspace(window, workspace_id).await?;
|
||||
let sync_state_ops = apply_sync_ops(window, &workspace, sync_ops).await?;
|
||||
let result = apply_sync_state_ops(window, &workspace, sync_state_ops).await;
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
async fn get_db_candidates<R: Runtime>(
|
||||
pub(crate) async fn get_db_candidates<R: Runtime>(
|
||||
mgr: &impl Manager<R>,
|
||||
workspace: &Workspace,
|
||||
) -> Result<Vec<DbCandidate>> {
|
||||
@@ -163,7 +139,7 @@ async fn get_db_candidates<R: Runtime>(
|
||||
Ok(candidates)
|
||||
}
|
||||
|
||||
async fn get_fs_candidates(workspace: &Workspace) -> Result<Vec<FsCandidate>> {
|
||||
pub(crate) async fn get_fs_candidates(workspace: &Workspace) -> Result<Vec<FsCandidate>> {
|
||||
let dir = match workspace.setting_sync_dir.clone() {
|
||||
None => return Ok(Vec::new()),
|
||||
Some(d) => d,
|
||||
@@ -207,7 +183,7 @@ async fn get_fs_candidates(workspace: &Workspace) -> Result<Vec<FsCandidate>> {
|
||||
Ok(candidates)
|
||||
}
|
||||
|
||||
fn compute_sync_ops(
|
||||
pub(crate) fn compute_sync_ops(
|
||||
db_candidates: Vec<DbCandidate>,
|
||||
fs_candidates: Vec<FsCandidate>,
|
||||
) -> Vec<SyncOp> {
|
||||
@@ -317,7 +293,7 @@ async fn workspace_models<R: Runtime>(
|
||||
sync_models
|
||||
}
|
||||
|
||||
async fn apply_sync_ops<R: Runtime>(
|
||||
pub(crate) async fn apply_sync_ops<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
workspace: &Workspace,
|
||||
sync_ops: Vec<SyncOp>,
|
||||
@@ -339,7 +315,7 @@ async fn apply_sync_ops<R: Runtime>(
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum SyncStateOp {
|
||||
pub(crate) enum SyncStateOp {
|
||||
Create {
|
||||
model_id: String,
|
||||
checksum: String,
|
||||
@@ -427,7 +403,8 @@ async fn apply_sync_op<R: Runtime>(
|
||||
|
||||
Ok(sync_state_op)
|
||||
}
|
||||
async fn apply_sync_state_ops<R: Runtime>(
|
||||
|
||||
pub(crate) async fn apply_sync_state_ops<R: Runtime>(
|
||||
window: &WebviewWindow<R>,
|
||||
workspace: &Workspace,
|
||||
ops: Vec<SyncStateOp>,
|
||||
@@ -483,7 +460,7 @@ async fn apply_sync_state_op<R: Runtime>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_workspace_sync_dir(workspace: &Workspace) -> Result<PathBuf> {
|
||||
pub(crate) fn get_workspace_sync_dir(workspace: &Workspace) -> Result<PathBuf> {
|
||||
let workspace_id = workspace.to_owned().id;
|
||||
match workspace.setting_sync_dir.to_owned() {
|
||||
Some(d) => Ok(Path::new(&d).to_path_buf()),
|
||||
|
||||
70
src-tauri/yaak-sync/src/watch.rs
Normal file
70
src-tauri/yaak-sync/src/watch.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use crate::error::Result;
|
||||
use log::{error, info};
|
||||
use notify::Watcher;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::mpsc;
|
||||
use tauri::ipc::Channel;
|
||||
use tokio::select;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "watch.ts")]
|
||||
pub(crate) struct WatchEvent {
|
||||
paths: Vec<PathBuf>,
|
||||
kind: String,
|
||||
}
|
||||
|
||||
pub(crate) async fn watch_directory(
|
||||
dir: &Path,
|
||||
channel: Channel<WatchEvent>,
|
||||
mut cancel_rx: tokio::sync::watch::Receiver<()>,
|
||||
) -> Result<()> {
|
||||
let dir = dir.to_owned();
|
||||
let (tx, rx) = mpsc::channel::<notify::Result<notify::Event>>();
|
||||
let mut watcher = notify::recommended_watcher(tx)?;
|
||||
|
||||
// Spawn a blocking thread to handle the blocking `std::sync::mpsc::Receiver`
|
||||
let (async_tx, mut async_rx) = tokio::sync::mpsc::channel::<notify::Result<notify::Event>>(100);
|
||||
std::thread::spawn(move || {
|
||||
for res in rx {
|
||||
if async_tx.blocking_send(res).is_err() {
|
||||
break; // Exit the thread if the async receiver is closed
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
watcher.watch(&dir, notify::RecursiveMode::Recursive).expect("Failed to watch directory");
|
||||
info!("Watching directory {:?}", dir);
|
||||
|
||||
loop {
|
||||
select! {
|
||||
// Listen for new watch events
|
||||
Some(event_res) = async_rx.recv() => {
|
||||
match event_res {
|
||||
Ok(event) => {
|
||||
channel
|
||||
.send(WatchEvent {
|
||||
paths: event.paths,
|
||||
kind: format!("{:?}", event.kind),
|
||||
})
|
||||
.expect("Failed to send watch event");
|
||||
}
|
||||
Err(e) => error!("Directory watch error: {:?}", e),
|
||||
}
|
||||
}
|
||||
// Listen for cancellation
|
||||
_ = cancel_rx.changed() => {
|
||||
// To cancel, we break from the loop, which will exit the task and make the
|
||||
// watcher go out of scope (cancelling it)
|
||||
info!("Cancelling watch for {:?}", dir);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { emit } from '@tauri-apps/api/event';
|
||||
import type { PromptTextRequest, PromptTextResponse } from '@yaakapp-internal/plugins';
|
||||
import {useWatchWorkspace} from "@yaakapp-internal/sync";
|
||||
import {
|
||||
useEnsureActiveCookieJar,
|
||||
useSubscribeActiveCookieJarId,
|
||||
@@ -7,7 +8,7 @@ import {
|
||||
import { useSubscribeActiveEnvironmentId } from '../hooks/useActiveEnvironment';
|
||||
import { useActiveRequest } from '../hooks/useActiveRequest';
|
||||
import { useSubscribeActiveRequestId } from '../hooks/useActiveRequestId';
|
||||
import { useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspace, useSubscribeActiveWorkspaceId } from '../hooks/useActiveWorkspace';
|
||||
import { useActiveWorkspaceChangedToast } from '../hooks/useActiveWorkspaceChangedToast';
|
||||
import { useDuplicateGrpcRequest } from '../hooks/useDuplicateGrpcRequest';
|
||||
import { useDuplicateHttpRequest } from '../hooks/useDuplicateHttpRequest';
|
||||
@@ -22,6 +23,7 @@ import { useSubscribeRecentRequests } from '../hooks/useRecentRequests';
|
||||
import { useSubscribeRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { useSyncFontSizeSetting } from '../hooks/useSyncFontSizeSetting';
|
||||
import { useSyncModelStores } from '../hooks/useSyncModelStores';
|
||||
import { useSyncWorkspace } from '../hooks/useSyncWorkspace';
|
||||
import { useSyncWorkspaceChildModels } from '../hooks/useSyncWorkspaceChildModels';
|
||||
import { useSyncWorkspaceRequestTitle } from '../hooks/useSyncWorkspaceRequestTitle';
|
||||
import { useSyncZoomSetting } from '../hooks/useSyncZoomSetting';
|
||||
@@ -53,6 +55,12 @@ export function GlobalHooks() {
|
||||
useActiveWorkspaceChangedToast();
|
||||
useEnsureActiveCookieJar();
|
||||
|
||||
// Trigger workspace sync operation when workspace files change
|
||||
const activeWorkspace = useActiveWorkspace();
|
||||
const { debouncedSync } = useSyncWorkspace(activeWorkspace, { debounceMillis: 1000 });
|
||||
useListenToTauriEvent('upserted_model', debouncedSync);
|
||||
useWatchWorkspace(activeWorkspace, debouncedSync);
|
||||
|
||||
const activeRequest = useActiveRequest();
|
||||
const duplicateHttpRequest = useDuplicateHttpRequest({
|
||||
id: activeRequest?.id ?? null,
|
||||
|
||||
@@ -11,7 +11,7 @@ import { SplitLayout } from './core/SplitLayout';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { Prose } from './Prose';
|
||||
|
||||
interface Props extends Pick<EditorProps, 'heightMode' | 'stateKey'> {
|
||||
interface Props extends Pick<EditorProps, 'heightMode' | 'stateKey' | 'forceUpdateKey'> {
|
||||
placeholder: string;
|
||||
className?: string;
|
||||
defaultValue: string;
|
||||
@@ -19,15 +19,7 @@ interface Props extends Pick<EditorProps, 'heightMode' | 'stateKey'> {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export function MarkdownEditor({
|
||||
className,
|
||||
defaultValue,
|
||||
onChange,
|
||||
name,
|
||||
placeholder,
|
||||
heightMode,
|
||||
stateKey,
|
||||
}: Props) {
|
||||
export function MarkdownEditor({ className, defaultValue, onChange, name, ...editorProps }: Props) {
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const [width] = useSize(containerRef.current);
|
||||
@@ -54,9 +46,7 @@ export function MarkdownEditor({
|
||||
language="markdown"
|
||||
defaultValue={defaultValue}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
heightMode={heightMode}
|
||||
stateKey={stateKey}
|
||||
{...editorProps}
|
||||
/>
|
||||
);
|
||||
|
||||
|
||||
@@ -488,6 +488,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
<PlainInput
|
||||
label="Request Name"
|
||||
hideLabel
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
defaultValue={activeRequest.name}
|
||||
className="font-sans !text-xl !px-0"
|
||||
containerClassName="border-0"
|
||||
@@ -499,6 +500,7 @@ export const RequestPane = memo(function RequestPane({
|
||||
placeholder="Request description"
|
||||
defaultValue={activeRequest.description}
|
||||
stateKey={`description.${activeRequest.id}`}
|
||||
forceUpdateKey={forceUpdateKey}
|
||||
onChange={(description) =>
|
||||
updateRequest({ id: activeRequestId, update: { description } })
|
||||
}
|
||||
|
||||
@@ -1,26 +1,20 @@
|
||||
import { applySync, calculateSync } from '@yaakapp-internal/sync';
|
||||
import classNames from 'classnames';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useActiveWorkspace } from '../hooks/useActiveWorkspace';
|
||||
import { useConfirm } from '../hooks/useConfirm';
|
||||
import { useCreateWorkspace } from '../hooks/useCreateWorkspace';
|
||||
import { useDeleteSendHistory } from '../hooks/useDeleteSendHistory';
|
||||
import { useDialog } from '../hooks/useDialog';
|
||||
import { useOpenWorkspace } from '../hooks/useOpenWorkspace';
|
||||
import { useSettings } from '../hooks/useSettings';
|
||||
import { useToast } from '../hooks/useToast';
|
||||
import { useSyncWorkspace } from '../hooks/useSyncWorkspace';
|
||||
import { useWorkspaces } from '../hooks/useWorkspaces';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { getWorkspace } from '../lib/store';
|
||||
import type { ButtonProps } from './core/Button';
|
||||
import { Button } from './core/Button';
|
||||
import type { DropdownItem } from './core/Dropdown';
|
||||
import { Icon } from './core/Icon';
|
||||
import { InlineCode } from './core/InlineCode';
|
||||
import type { RadioDropdownItem } from './core/RadioDropdown';
|
||||
import { RadioDropdown } from './core/RadioDropdown';
|
||||
import { VStack } from './core/Stacks';
|
||||
import { OpenWorkspaceDialog } from './OpenWorkspaceDialog';
|
||||
import { WorkspaceSettingsDialog } from './WorkspaceSettingsDialog';
|
||||
|
||||
@@ -35,11 +29,10 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
const createWorkspace = useCreateWorkspace();
|
||||
const { mutate: deleteSendHistory } = useDeleteSendHistory();
|
||||
const dialog = useDialog();
|
||||
const confirm = useConfirm();
|
||||
const toast = useToast();
|
||||
const settings = useSettings();
|
||||
const openWorkspace = useOpenWorkspace();
|
||||
const openWorkspaceNewWindow = settings?.openWorkspaceNewWindow ?? null;
|
||||
const { sync } = useSyncWorkspace(activeWorkspace);
|
||||
|
||||
const orderedWorkspaces = useMemo(
|
||||
() => [...workspaces].sort((a, b) => (a.name.localeCompare(b.name) > 0 ? 1 : -1)),
|
||||
@@ -79,92 +72,7 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
label: 'Sync Workspace',
|
||||
leftSlot: <Icon icon="folder_sync" />,
|
||||
hidden: !activeWorkspace?.settingSyncDir,
|
||||
onSelect: async () => {
|
||||
if (activeWorkspace == null) return;
|
||||
|
||||
const ops = await calculateSync(activeWorkspace);
|
||||
if (ops.length === 0) {
|
||||
toast.show({
|
||||
id: 'no-sync-changes',
|
||||
message: 'No changes to sync',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const dbChanges = ops.filter((o) => o.type.startsWith('db'));
|
||||
|
||||
if (dbChanges.length === 0) {
|
||||
await applySync(activeWorkspace, ops);
|
||||
toast.show({
|
||||
id: 'applied-sync-changes',
|
||||
message: `Wrote ${pluralizeCount('change', ops.length)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await confirm({
|
||||
id: 'commit-sync',
|
||||
title: 'Filesystem Changes Detected',
|
||||
confirmText: 'Apply Changes',
|
||||
description: (
|
||||
<VStack space={3}>
|
||||
<p>
|
||||
{pluralizeCount('file', dbChanges.length)} in the directory have changed. Do you want to apply the updates to your
|
||||
workspace?
|
||||
</p>
|
||||
<div className="overflow-y-auto max-h-[10rem]">
|
||||
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="py-1 text-left">Name</th>
|
||||
<th className="py-1 text-right pl-4">Operation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{dbChanges.map((op, i) => {
|
||||
let name = '';
|
||||
let label = '';
|
||||
let color = '';
|
||||
|
||||
if (op.type === 'dbCreate') {
|
||||
label = 'create';
|
||||
name = fallbackRequestName(op.fs.model);
|
||||
color = 'text-success';
|
||||
} else if (op.type === 'dbUpdate') {
|
||||
label = 'update';
|
||||
name = fallbackRequestName(op.fs.model);
|
||||
color = 'text-info';
|
||||
} else if (op.type === 'dbDelete') {
|
||||
label = 'delete';
|
||||
name = fallbackRequestName(op.model);
|
||||
color = 'text-danger';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={i} className="text-text">
|
||||
<td className="py-1">{name}</td>
|
||||
<td className="py-1 pl-4 text-right">
|
||||
<InlineCode className={color}>{label}</InlineCode>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</VStack>
|
||||
),
|
||||
});
|
||||
if (confirmed) {
|
||||
await applySync(activeWorkspace, ops);
|
||||
toast.show({
|
||||
id: 'applied-confirmed-sync-changes',
|
||||
message: `Wrote ${pluralizeCount('change', ops.length)}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
onSelect: sync,
|
||||
},
|
||||
{
|
||||
key: 'delete-responses',
|
||||
@@ -184,12 +92,12 @@ export const WorkspaceActionsDropdown = memo(function WorkspaceActionsDropdown({
|
||||
return { workspaceItems, extraItems };
|
||||
}, [
|
||||
orderedWorkspaces,
|
||||
activeWorkspace,
|
||||
activeWorkspace?.settingSyncDir,
|
||||
activeWorkspace?.id,
|
||||
sync,
|
||||
deleteSendHistory,
|
||||
createWorkspace,
|
||||
dialog,
|
||||
confirm,
|
||||
toast,
|
||||
]);
|
||||
|
||||
const handleChange = useCallback(
|
||||
|
||||
@@ -117,7 +117,7 @@ export const PairEditor = forwardRef<PairEditorRef, PairEditorProps>(function Pa
|
||||
|
||||
// Add empty last pair if there is none
|
||||
const lastPair = newPairs[newPairs.length - 1];
|
||||
if (lastPair != null && !isPairEmpty(lastPair)) {
|
||||
if (lastPair == null || !isPairEmpty(lastPair)) {
|
||||
newPairs.push(emptyPair());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { DialogProps } from '../components/core/Dialog';
|
||||
import type { ConfirmProps } from './Confirm';
|
||||
import { Confirm } from './Confirm';
|
||||
@@ -5,27 +6,30 @@ import { useDialog } from './useDialog';
|
||||
|
||||
export function useConfirm() {
|
||||
const dialog = useDialog();
|
||||
return ({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
variant,
|
||||
confirmText,
|
||||
}: {
|
||||
id: string;
|
||||
title: DialogProps['title'];
|
||||
description?: DialogProps['description'];
|
||||
variant?: ConfirmProps['variant'];
|
||||
confirmText?: ConfirmProps['confirmText'];
|
||||
}) =>
|
||||
new Promise((onResult: ConfirmProps['onResult']) => {
|
||||
dialog.show({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
hideX: true,
|
||||
size: 'sm',
|
||||
render: ({ hide }) => Confirm({ onHide: hide, variant, onResult, confirmText }),
|
||||
});
|
||||
});
|
||||
return useCallback(
|
||||
({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
variant,
|
||||
confirmText,
|
||||
}: {
|
||||
id: string;
|
||||
title: DialogProps['title'];
|
||||
description?: DialogProps['description'];
|
||||
variant?: ConfirmProps['variant'];
|
||||
confirmText?: ConfirmProps['confirmText'];
|
||||
}) =>
|
||||
new Promise((onResult: ConfirmProps['onResult']) => {
|
||||
dialog.show({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
hideX: true,
|
||||
size: 'sm',
|
||||
render: ({ hide }) => Confirm({ onHide: hide, variant, onResult, confirmText }),
|
||||
});
|
||||
}),
|
||||
[dialog],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { debounce } from '@yaakapp-internal/lib';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { debounce } from '../lib/debounce';
|
||||
|
||||
export function useDebouncedState<T>(
|
||||
defaultValue: T,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { OsType } from '@tauri-apps/plugin-os';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { capitalize } from '../lib/capitalize';
|
||||
import { debounce } from '../lib/debounce';
|
||||
import { useOsInfo } from './useOsInfo';
|
||||
import { debounce } from '@yaakapp-internal/lib';
|
||||
|
||||
const HOLD_KEYS = ['Shift', 'Control', 'Command', 'Alt', 'Meta'];
|
||||
|
||||
|
||||
102
src-web/hooks/useSyncWorkspace.tsx
Normal file
102
src-web/hooks/useSyncWorkspace.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { debounce } from '@yaakapp-internal/lib';
|
||||
import type { Workspace } from '@yaakapp-internal/models';
|
||||
import { applySync, calculateSync } from '@yaakapp-internal/sync';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { InlineCode } from '../components/core/InlineCode';
|
||||
import { VStack } from '../components/core/Stacks';
|
||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||
import { pluralizeCount } from '../lib/pluralize';
|
||||
import { useConfirm } from './useConfirm';
|
||||
|
||||
export function useSyncWorkspace(
|
||||
workspace: Workspace | null,
|
||||
{
|
||||
debounceMillis = 1000,
|
||||
}: {
|
||||
debounceMillis?: number;
|
||||
} = {},
|
||||
) {
|
||||
const confirm = useConfirm();
|
||||
|
||||
const sync = useCallback(async () => {
|
||||
if (workspace == null) return;
|
||||
|
||||
const ops = await calculateSync(workspace);
|
||||
if (ops.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dbChanges = ops.filter((o) => o.type.startsWith('db'));
|
||||
|
||||
if (dbChanges.length === 0) {
|
||||
await applySync(workspace, ops);
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await confirm({
|
||||
id: 'commit-sync',
|
||||
title: 'Filesystem Changes Detected',
|
||||
confirmText: 'Apply Changes',
|
||||
description: (
|
||||
<VStack space={3}>
|
||||
<p>
|
||||
{pluralizeCount('file', dbChanges.length)} in the directory have changed. Do you want to
|
||||
apply the updates to your workspace?
|
||||
</p>
|
||||
<div className="overflow-y-auto max-h-[10rem]">
|
||||
<table className="w-full text-sm mb-auto min-w-full max-w-full divide-y divide-surface-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="py-1 text-left">Name</th>
|
||||
<th className="py-1 text-right pl-4">Operation</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-surface-highlight">
|
||||
{dbChanges.map((op, i) => {
|
||||
let name = '';
|
||||
let label = '';
|
||||
let color = '';
|
||||
|
||||
if (op.type === 'dbCreate') {
|
||||
label = 'create';
|
||||
name = fallbackRequestName(op.fs.model);
|
||||
color = 'text-success';
|
||||
} else if (op.type === 'dbUpdate') {
|
||||
label = 'update';
|
||||
name = fallbackRequestName(op.fs.model);
|
||||
color = 'text-info';
|
||||
} else if (op.type === 'dbDelete') {
|
||||
label = 'delete';
|
||||
name = fallbackRequestName(op.model);
|
||||
color = 'text-danger';
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr key={i} className="text-text">
|
||||
<td className="py-1">{name}</td>
|
||||
<td className="py-1 pl-4 text-right">
|
||||
<InlineCode className={color}>{label}</InlineCode>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</VStack>
|
||||
),
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
await applySync(workspace, ops);
|
||||
}
|
||||
}, [confirm, workspace]);
|
||||
|
||||
const debouncedSync = useMemo(() => {
|
||||
return debounce(sync, debounceMillis);
|
||||
}, [debounceMillis, sync]);
|
||||
|
||||
return { sync, debouncedSync };
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev --force --debug hmr",
|
||||
"dev": "vite dev --force",
|
||||
"build": "vite build",
|
||||
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user