Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b76426ba83 | ||
|
|
1cb2a177c6 | ||
|
|
db87577a1b | ||
|
|
c219eb0ee9 | ||
|
|
bfe5b56c99 | ||
|
|
4453690521 |
33
CHANGELOG.md
33
CHANGELOG.md
@@ -1,39 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [1.76.0](https://github.com/windmill-labs/windmill/compare/v1.75.0...v1.76.0) (2023-03-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** add frontend (JS) scripts to apps ([f0b1b1f](https://github.com/windmill-labs/windmill/commit/f0b1b1f752731ba434b960a75624118152f53c00))
|
||||
* **frontend:** Copy, Cut and Paste ([#1279](https://github.com/windmill-labs/windmill/issues/1279)) ([82c139e](https://github.com/windmill-labs/windmill/commit/82c139ed0992be401e250cfb7ecc0fca61b76772))
|
||||
* **frontend:** disabled for action buttons can now depend on row ([75f87e7](https://github.com/windmill-labs/windmill/commit/75f87e7e1117a9c12afcf626379e94b134a9a493))
|
||||
* **frontend:** improve drag-n-drop behavior ([cfd489a](https://github.com/windmill-labs/windmill/commit/cfd489a55059e7b6843f99bab261c70b3852e6a2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** improve worker ping api ([c958480](https://github.com/windmill-labs/windmill/commit/c958480ce83844a989f58dd5a70eb288582e2194))
|
||||
* **frontend:** General fixes and updates ([#1281](https://github.com/windmill-labs/windmill/issues/1281)) ([3e5a179](https://github.com/windmill-labs/windmill/commit/3e5a179eb8cd8001f49c92305141dade1571e20f))
|
||||
|
||||
## [1.75.0](https://github.com/windmill-labs/windmill/compare/v1.74.2...v1.75.0) (2023-03-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add filter jobs by args or result ([3b44f9a](https://github.com/windmill-labs/windmill/commit/3b44f9a72ca0466a44963a4b9657a0ee59b44753))
|
||||
* **apps:** add resource picker ([8681e83](https://github.com/windmill-labs/windmill/commit/8681e83b574141acbf7e5a389a9e8a4f340336d1))
|
||||
* **bash:** add default argument handling for bash ([1d5c194](https://github.com/windmill-labs/windmill/commit/1d5c194f09ffba963d52e418c5954843d84ae337))
|
||||
* **frontend-apps:** add variable picker for static string input on apps ([bc440f8](https://github.com/windmill-labs/windmill/commit/bc440f8d4154ce464c0e027d93b7a0a3b76d782e))
|
||||
* **frontend:** make runs filters synced with query args ([61a5e1f](https://github.com/windmill-labs/windmill/commit/61a5e1f1accc988628b785b3b9be04c4ea719874))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** add killpill for lines reading ([7c825c2](https://github.com/windmill-labs/windmill/commit/7c825c212dd0f1e8be427eabd9a9756303241d1b))
|
||||
* **cli:** many small fixes ([ce32370](https://github.com/windmill-labs/windmill/commit/ce323709a94d27fb24214719180ea1aafc66d646))
|
||||
|
||||
## [1.74.2](https://github.com/windmill-labs/windmill/compare/v1.74.1...v1.74.2) (2023-03-09)
|
||||
|
||||
|
||||
|
||||
223
backend/Cargo.lock
generated
223
backend/Cargo.lock
generated
@@ -41,17 +41,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
@@ -265,9 +254,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.11"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13d8068b6ccb8b34db9de397c7043f91db8b4c66414952c6db944f238c4d3db3"
|
||||
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -292,6 +281,7 @@ dependencies = [
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http 0.3.5",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
@@ -477,7 +467,7 @@ version = "8.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996"
|
||||
dependencies = [
|
||||
"semver 1.0.17",
|
||||
"semver 1.0.16",
|
||||
"serde",
|
||||
"toml",
|
||||
"url",
|
||||
@@ -500,9 +490,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.24"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
@@ -1133,9 +1123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549"
|
||||
checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1148,9 +1138,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac"
|
||||
checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
@@ -1158,15 +1148,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd"
|
||||
checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83"
|
||||
checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
@@ -1186,9 +1176,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91"
|
||||
checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
|
||||
|
||||
[[package]]
|
||||
name = "futures-lite"
|
||||
@@ -1207,9 +1197,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6"
|
||||
checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1218,21 +1208,21 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2"
|
||||
checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879"
|
||||
checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.27"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab"
|
||||
checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
@@ -1347,16 +1337,7 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1365,7 +1346,7 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||
dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1522,9 +1503,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.25"
|
||||
version = "0.14.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899"
|
||||
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
@@ -1610,7 +1591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -2534,9 +2515,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.52"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2544,7 +2525,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"built",
|
||||
@@ -2562,7 +2543,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-client"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2576,7 +2557,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-impl"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"heck",
|
||||
@@ -2598,7 +2579,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-macro"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
@@ -2643,9 +2624,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -2769,11 +2750,10 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "regress"
|
||||
version = "0.5.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d995d590bd8ec096d1893f414bf3f5e8b0ee4c9eed9a5642b9766ef2c8e2e8e9"
|
||||
checksum = "0a92ff21fe8026ce3f2627faaf43606f0b67b014dbc9ccf027181a804f75d92e"
|
||||
dependencies = [
|
||||
"hashbrown 0.13.2",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -2919,7 +2899,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver 1.0.17",
|
||||
"semver 1.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2973,7 +2953,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#8ff947e83a65b74c920edc63aed8a0a5854b8612"
|
||||
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -2982,7 +2962,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#8ff947e83a65b74c920edc63aed8a0a5854b8612"
|
||||
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bstr",
|
||||
@@ -2995,9 +2975,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#8ff947e83a65b74c920edc63aed8a0a5854b8612"
|
||||
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"ahash",
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"lalrpop",
|
||||
@@ -3143,9 +3123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.17"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -3167,9 +3147,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.155"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71f2b4817415c6d4210bfe1c7bfcf4801b2d904cb4d0e1a8fdb651013c9e86b8"
|
||||
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3196,9 +3176,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.155"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d071a94a3fac4aff69d023a7f411e33f40f3483f8c5190b1953822b6b76d7630"
|
||||
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3503,7 +3483,7 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"ahash",
|
||||
"atoi",
|
||||
"base64 0.13.1",
|
||||
"bitflags",
|
||||
@@ -3638,9 +3618,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "string_enum"
|
||||
version = "0.3.3"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41491e23e7db79343236a6ced96325ff132eb09e29ac4c5b8132b9c55aaaae89"
|
||||
checksum = "994453cd270ad0265796eb24abf5540091ed03e681c5f3c12bc33e4db33253e1"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
@@ -3687,11 +3667,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "swc_common"
|
||||
version = "0.29.37"
|
||||
version = "0.29.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5005cd73617e18592faa31298225b26f1c407b84a681d67efb735c3d3458e101"
|
||||
checksum = "2d515be281f603cb97afaa89896aca5e5c748fde11c7f926e35cdaa8ff8da705"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"ahash",
|
||||
"ast_node",
|
||||
"better_scoped_tls",
|
||||
"cfg-if",
|
||||
@@ -4072,9 +4052,9 @@ checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.6"
|
||||
version = "0.19.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08de71aa0d6e348f070457f85af8bd566e2bc452156a423ddf22861b3a953fae"
|
||||
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@@ -4127,6 +4107,25 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-range-header",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.4.0"
|
||||
@@ -4275,7 +4274,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#bf60070e09d6a3753d5b173d9c605a8bf2ada748"
|
||||
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
@@ -4284,7 +4283,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#bf60070e09d6a3753d5b173d9c605a8bf2ada748"
|
||||
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
@@ -4302,7 +4301,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#bf60070e09d6a3753d5b173d9c605a8bf2ada748"
|
||||
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4702,9 +4701,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.4.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68"
|
||||
checksum = "45dbc71f0cdca27dc261a9bd37ddec174e4a0af2b900b890f378460f745426e3"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
@@ -4743,7 +4742,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4770,7 +4769,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4809,7 +4808,7 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-cookies",
|
||||
"tower-http",
|
||||
"tower-http 0.4.0",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
@@ -4825,7 +4824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4840,7 +4839,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4853,7 +4852,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4878,7 +4877,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4886,7 +4885,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4901,7 +4900,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4913,7 +4912,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4928,7 +4927,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4942,7 +4941,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -4965,7 +4964,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -5024,9 +5023,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
@@ -5039,45 +5038,45 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.76.0"
|
||||
version = "1.74.2"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -2501,57 +2501,6 @@
|
||||
},
|
||||
"query": "SELECT set_config('session.user', $1, true)"
|
||||
},
|
||||
"6d50a8dc9cfc040b6f37b58053daa0709671e008a10ab4189114ceff66efe603": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "worker",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "worker_instance",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "last_ping",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "started_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "ip",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "jobs_executed",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2"
|
||||
},
|
||||
"6dfd1ac7168f098a9e7c92d530a470c0a156041d5081053d43d9efb00d81c464": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -2573,6 +2522,57 @@
|
||||
},
|
||||
"query": "SELECT policy from app WHERE path = $1 AND workspace_id = $2"
|
||||
},
|
||||
"6fc2cfae9df83eb24ea33e4c9567740100f4dd2285afc3ef474fc70041b0567b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "worker",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "worker_instance",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "ping_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "started_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "ip",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "jobs_executed",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT * FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2"
|
||||
},
|
||||
"701f215eb14ba67a79afea15d7effc0dd394ba6c4a72c95d4560c5a377015d4e": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -2984,6 +2984,20 @@
|
||||
},
|
||||
"query": "UPDATE group_ SET summary = $1 WHERE name = $2 AND workspace_id = $3"
|
||||
},
|
||||
"83fc9bf32b9b40987feff61fea82e20613e74efb7bf8ba52485566667ccf2284": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE resource SET path = $1 WHERE path = $2 AND workspace_id = $3"
|
||||
},
|
||||
"8543f029d9784234e4c6a6dcd7b03e62d544b98be261334ee210594e0bb839f2": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -3154,21 +3168,6 @@
|
||||
},
|
||||
"query": "INSERT INTO usage (id, is_workspace, month_, usage) \n VALUES ($1, false, EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date), 0) \n ON CONFLICT (id, is_workspace, month_) DO UPDATE SET usage = usage.usage + 1 \n RETURNING usage.usage"
|
||||
},
|
||||
"8c0131a9cc61f2daa258d49767242bcaab6bb34a977ff7fb0c18aa9202d11f47": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Jsonb",
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4"
|
||||
},
|
||||
"8c11511a74a41a65f448249a00ebe6964a61d00c2f7b4875a55e64741bf1f0ca": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -4698,27 +4697,6 @@
|
||||
},
|
||||
"query": "SELECT null FROM queue WHERE id = $1 FOR UPDATE"
|
||||
},
|
||||
"be1de9116b18a40681cb8c5ec7578fd7c5f7ce77f7af63f766c2899d85fe0bef": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "value",
|
||||
"ordinal": 0,
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT value FROM resource WHERE path = $1 AND workspace_id = $2"
|
||||
},
|
||||
"be7a99a5bb6858323ca61dd51077010f51ba58ae76b9a413339255024dcb524d": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.76.0
|
||||
version: 1.74.2
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -204,6 +204,7 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/users/is_owner/{path}:
|
||||
get:
|
||||
summary: is owner of path
|
||||
@@ -1548,7 +1549,7 @@ paths:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
value: {}
|
||||
@@ -2102,6 +2103,7 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/scripts/create:
|
||||
post:
|
||||
summary: create script
|
||||
@@ -2833,6 +2835,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppWithLastVersion"
|
||||
|
||||
|
||||
/w/{workspace}/apps/secret_of/{path}:
|
||||
get:
|
||||
summary: get public secret of app
|
||||
@@ -3165,14 +3168,12 @@ paths:
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
- $ref: "#/components/parameters/ScriptExactHash"
|
||||
- $ref: "#/components/parameters/StartedBefore"
|
||||
- $ref: "#/components/parameters/StartedAfter"
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/Success"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/Suspended"
|
||||
- $ref: "#/components/parameters/Running"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
responses:
|
||||
"200":
|
||||
description: All available queued jobs
|
||||
@@ -3197,12 +3198,10 @@ paths:
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
- $ref: "#/components/parameters/ScriptExactHash"
|
||||
- $ref: "#/components/parameters/StartedBefore"
|
||||
- $ref: "#/components/parameters/StartedAfter"
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/Success"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
@@ -3236,11 +3235,9 @@ paths:
|
||||
- $ref: "#/components/parameters/ScriptExactPath"
|
||||
- $ref: "#/components/parameters/ScriptStartPath"
|
||||
- $ref: "#/components/parameters/ScriptExactHash"
|
||||
- $ref: "#/components/parameters/StartedBefore"
|
||||
- $ref: "#/components/parameters/StartedAfter"
|
||||
- $ref: "#/components/parameters/CreatedBefore"
|
||||
- $ref: "#/components/parameters/CreatedAfter"
|
||||
- $ref: "#/components/parameters/JobKinds"
|
||||
- $ref: "#/components/parameters/ArgsFilter"
|
||||
- $ref: "#/components/parameters/ResultFilter"
|
||||
- name: is_skipped
|
||||
description: is the job skipped
|
||||
in: query
|
||||
@@ -3441,7 +3438,7 @@ paths:
|
||||
|
||||
/w/{workspace}/jobs/resume_urls/{id}/{resume_id}:
|
||||
get:
|
||||
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
operationId: getResumeUrls
|
||||
tags:
|
||||
- job
|
||||
@@ -3467,7 +3464,7 @@ paths:
|
||||
properties:
|
||||
approvalPage:
|
||||
type: string
|
||||
resume:
|
||||
resume:
|
||||
type: string
|
||||
cancel:
|
||||
type: string
|
||||
@@ -4112,7 +4109,7 @@ paths:
|
||||
type: string
|
||||
owners:
|
||||
type: array
|
||||
items:
|
||||
items:
|
||||
type: string
|
||||
extra_perms:
|
||||
additionalProperties:
|
||||
@@ -4146,7 +4143,7 @@ paths:
|
||||
properties:
|
||||
owners:
|
||||
type: array
|
||||
items:
|
||||
items:
|
||||
type: string
|
||||
extra_perms:
|
||||
additionalProperties:
|
||||
@@ -4317,8 +4314,7 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
[script, group_, resource, schedule, variable, flow, folder, app]
|
||||
enum: [script, group_, resource, schedule, variable, flow, folder, app]
|
||||
responses:
|
||||
"200":
|
||||
description: acls
|
||||
@@ -4343,8 +4339,7 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
[script, group_, resource, schedule, variable, flow, folder, app]
|
||||
enum: [script, group_, resource, schedule, variable, flow, folder, app]
|
||||
requestBody:
|
||||
description: acl to add
|
||||
required: true
|
||||
@@ -4380,8 +4375,7 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
[script, group_, resource, schedule, variable, flow, folder, app]
|
||||
enum: [script, group_, resource, schedule, variable, flow, folder, app]
|
||||
requestBody:
|
||||
description: acl to add
|
||||
required: true
|
||||
@@ -4401,18 +4395,19 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/capture_u/{path}:
|
||||
post:
|
||||
summary: update flow preview capture
|
||||
operationId: updateCapture
|
||||
tags:
|
||||
- capture
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"204":
|
||||
description: flow preview captured
|
||||
post:
|
||||
summary: update flow preview capture
|
||||
operationId: updateCapture
|
||||
tags:
|
||||
- capture
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"204":
|
||||
description: flow preview captured
|
||||
|
||||
/w/{workspace}/capture/{path}:
|
||||
put:
|
||||
@@ -4619,15 +4614,15 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
StartedBefore:
|
||||
name: started_before
|
||||
CreatedBefore:
|
||||
name: created_before
|
||||
description: filter on created before (inclusive) timestamp
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
format: date-time
|
||||
StartedAfter:
|
||||
name: started_after
|
||||
CreatedAfter:
|
||||
name: created_after
|
||||
description: filter on created after (exclusive) timestamp
|
||||
in: query
|
||||
schema:
|
||||
@@ -4651,19 +4646,6 @@ components:
|
||||
in: query
|
||||
schema:
|
||||
type: boolean
|
||||
ArgsFilter:
|
||||
name: args
|
||||
description: filter on jobs containing those args as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
|
||||
ResultFilter:
|
||||
name: result
|
||||
description: filter on jobs containing those result as a json subset (@> in postgres)
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
After:
|
||||
name: after
|
||||
description: filter on created after (exclusive) timestamp
|
||||
@@ -5568,8 +5550,9 @@ components:
|
||||
type: string
|
||||
worker_instance:
|
||||
type: string
|
||||
last_ping:
|
||||
type: number
|
||||
ping_at:
|
||||
type: string
|
||||
format: date-time
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
@@ -16,9 +16,10 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
error::{self, to_anyhow, Error, JsonResult, Result},
|
||||
error::{self, Error, JsonResult, Result},
|
||||
users::username_to_permissioned_as,
|
||||
utils::{not_found_if_none, paginate, Pagination},
|
||||
};
|
||||
@@ -261,26 +262,13 @@ async fn update_folder(
|
||||
sqlb.and_where_eq("workspace_id", "?".bind(&w_id));
|
||||
|
||||
if let Some(display_name) = ng.display_name {
|
||||
sqlb.set("display_name", "?".bind(&display_name));
|
||||
sqlb.set("display_name", display_name);
|
||||
}
|
||||
if let Some(owners) = ng.owners {
|
||||
sqlb.set(
|
||||
"owners",
|
||||
"?".bind(&format!(
|
||||
"{{{}}}",
|
||||
owners
|
||||
.iter()
|
||||
.map(|x| format!("\"{x}\""))
|
||||
.collect::<Vec<_>>()
|
||||
.join(","),
|
||||
)),
|
||||
);
|
||||
sqlb.set_str("owners", format!("{{{}}}", owners.into_iter().join(",")));
|
||||
}
|
||||
if let Some(extra_perms) = ng.extra_perms {
|
||||
sqlb.set(
|
||||
"extra_perms",
|
||||
"?".bind(&serde_json::to_string(&extra_perms).map_err(to_anyhow)?),
|
||||
);
|
||||
sqlb.set_str("extra_perms", extra_perms.to_string());
|
||||
}
|
||||
|
||||
sqlb.returning("*");
|
||||
|
||||
@@ -327,15 +327,13 @@ pub struct ListQueueQuery {
|
||||
pub script_path_exact: Option<String>,
|
||||
pub script_hash: Option<String>,
|
||||
pub created_by: Option<String>,
|
||||
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub running: Option<bool>,
|
||||
pub parent_job: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
pub job_kinds: Option<String>,
|
||||
pub suspended: Option<bool>,
|
||||
// filter by matching a subset of the args using base64 encoded json subset
|
||||
pub args: Option<String>,
|
||||
}
|
||||
|
||||
fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> SqlBuilder {
|
||||
@@ -364,11 +362,11 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
|
||||
if let Some(pj) = &lq.parent_job {
|
||||
sqlb.and_where_eq("parent_job", "?".bind(pj));
|
||||
}
|
||||
if let Some(dt) = &lq.started_before {
|
||||
sqlb.and_where_le("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.created_before {
|
||||
sqlb.and_where_lt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
if let Some(dt) = &lq.started_after {
|
||||
sqlb.and_where_ge("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.created_after {
|
||||
sqlb.and_where_gt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
|
||||
if let Some(s) = &lq.suspended {
|
||||
@@ -378,7 +376,6 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
|
||||
sqlb.and_where_eq("suspend", 0);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(jk) = &lq.job_kinds {
|
||||
sqlb.and_where_in(
|
||||
"job_kind",
|
||||
@@ -386,10 +383,6 @@ fn list_queue_jobs_query(w_id: &str, lq: &ListQueueQuery, fields: &[&str]) -> Sq
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(args) = &lq.args {
|
||||
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
|
||||
}
|
||||
|
||||
sqlb
|
||||
}
|
||||
|
||||
@@ -462,14 +455,13 @@ async fn list_jobs(
|
||||
script_path_exact: lq.script_path_exact,
|
||||
script_hash: lq.script_hash,
|
||||
created_by: lq.created_by,
|
||||
started_before: lq.started_before,
|
||||
started_after: lq.started_after,
|
||||
created_before: lq.created_before,
|
||||
created_after: lq.created_after,
|
||||
running: None,
|
||||
parent_job: lq.parent_job,
|
||||
order_desc: Some(true),
|
||||
job_kinds: lq.job_kinds,
|
||||
suspended: lq.suspended,
|
||||
args: lq.args,
|
||||
},
|
||||
&[
|
||||
"'QueuedJob' as typ",
|
||||
@@ -1144,14 +1136,14 @@ where
|
||||
struct InPayload {
|
||||
payload: Option<String>,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_payload<D: DeserializeOwned, T: AsRef<[u8]>>(t: T) -> anyhow::Result<D> {
|
||||
let vec = base64::engine::general_purpose::URL_SAFE
|
||||
.decode(t)
|
||||
.context("invalid base64")?;
|
||||
serde_json::from_slice(vec.as_slice()).context("invalid json")
|
||||
fn decode_payload<D: DeserializeOwned, T: AsRef<[u8]>>(t: T) -> anyhow::Result<D> {
|
||||
let vec = base64::engine::general_purpose::URL_SAFE
|
||||
.decode(t)
|
||||
.context("invalid base64")?;
|
||||
serde_json::from_slice(vec.as_slice()).context("invalid json")
|
||||
}
|
||||
}
|
||||
}
|
||||
pub async fn run_flow_by_path(
|
||||
authed: Authed,
|
||||
@@ -1695,11 +1687,11 @@ fn list_completed_jobs_query(
|
||||
if let Some(pj) = &lq.parent_job {
|
||||
sqlb.and_where_eq("parent_job", "?".bind(pj));
|
||||
}
|
||||
if let Some(dt) = &lq.started_before {
|
||||
sqlb.and_where_le("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.created_before {
|
||||
sqlb.and_where_lt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
if let Some(dt) = &lq.started_after {
|
||||
sqlb.and_where_ge("started_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
if let Some(dt) = &lq.created_after {
|
||||
sqlb.and_where_gt("created_at", format!("to_timestamp({})", dt.timestamp()));
|
||||
}
|
||||
if let Some(sk) = &lq.is_skipped {
|
||||
sqlb.and_where_eq("is_skipped", sk);
|
||||
@@ -1714,14 +1706,6 @@ fn list_completed_jobs_query(
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(args) = &lq.args {
|
||||
sqlb.and_where("args @> ?".bind(&args.replace("'", "''")));
|
||||
}
|
||||
|
||||
if let Some(result) = &lq.result {
|
||||
sqlb.and_where("result @> ?".bind(&result.replace("'", "''")));
|
||||
}
|
||||
|
||||
sqlb
|
||||
}
|
||||
#[derive(Deserialize, Clone)]
|
||||
@@ -1730,8 +1714,8 @@ pub struct ListCompletedQuery {
|
||||
pub script_path_exact: Option<String>,
|
||||
pub script_hash: Option<String>,
|
||||
pub created_by: Option<String>,
|
||||
pub started_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub started_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_before: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub created_after: Option<chrono::DateTime<chrono::Utc>>,
|
||||
pub success: Option<bool>,
|
||||
pub parent_job: Option<String>,
|
||||
pub order_desc: Option<bool>,
|
||||
@@ -1739,10 +1723,6 @@ pub struct ListCompletedQuery {
|
||||
pub is_skipped: Option<bool>,
|
||||
pub is_flow_step: Option<bool>,
|
||||
pub suspended: Option<bool>,
|
||||
// filter by matching a subset of the args using base64 encoded json subset
|
||||
pub args: Option<String>,
|
||||
// filter by matching a subset of the result using base64 encoded json subset
|
||||
pub result: Option<String>,
|
||||
}
|
||||
|
||||
async fn list_completed_jobs(
|
||||
|
||||
@@ -26,7 +26,6 @@ use axum::{
|
||||
Json, Router,
|
||||
};
|
||||
use hyper::StatusCode;
|
||||
use serde_json::Value;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
error::{Error, JsonResult, Result},
|
||||
@@ -379,26 +378,9 @@ async fn update_variable(
|
||||
if !authed.is_admin {
|
||||
require_owner_of_path(&w_id, &authed.username, &authed.groups, &path, &db).await?;
|
||||
}
|
||||
let mut v = sqlx::query_scalar!(
|
||||
"SELECT value FROM resource WHERE path = $1 AND workspace_id = $2",
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some(old_v) = v {
|
||||
v = Some(replace_path(
|
||||
old_v,
|
||||
&format!("$var:{path}"),
|
||||
&format!("$var:{npath}"),
|
||||
))
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"UPDATE resource SET path = $1, value = $2 WHERE path = $3 AND workspace_id = $4",
|
||||
"UPDATE resource SET path = $1 WHERE path = $2 AND workspace_id = $3",
|
||||
npath,
|
||||
v,
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
@@ -437,23 +419,6 @@ async fn update_variable(
|
||||
Ok(format!("variable {} updated (npath: {:?})", path, npath))
|
||||
}
|
||||
|
||||
fn replace_path(v: serde_json::Value, path: &str, npath: &str) -> Value {
|
||||
match v {
|
||||
Value::Object(v) => Value::Object(
|
||||
v.into_iter()
|
||||
.map(|(k, v)| (k, replace_path(v, path, npath)))
|
||||
.collect(),
|
||||
),
|
||||
Value::Array(arr) => Value::Array(
|
||||
arr.into_iter()
|
||||
.map(|v| replace_path(v, path, npath))
|
||||
.collect(),
|
||||
),
|
||||
Value::String(s) if s == path => Value::String(npath.to_owned()),
|
||||
_ => v,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn build_crypt<'c>(
|
||||
db: &mut Transaction<'c, Postgres>,
|
||||
w_id: &str,
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn global_service() -> Router {
|
||||
struct WorkerPing {
|
||||
worker: String,
|
||||
worker_instance: String,
|
||||
last_ping: Option<i32>,
|
||||
ping_at: chrono::DateTime<chrono::Utc>,
|
||||
started_at: chrono::DateTime<chrono::Utc>,
|
||||
ip: String,
|
||||
jobs_executed: i32,
|
||||
@@ -45,7 +45,7 @@ async fn list_worker_pings(
|
||||
|
||||
let rows = sqlx::query_as!(
|
||||
WorkerPing,
|
||||
"SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2",
|
||||
"SELECT * FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2",
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
)
|
||||
|
||||
@@ -1073,7 +1073,6 @@ struct ScriptMetadata {
|
||||
schema: Option<Schema>,
|
||||
is_template: bool,
|
||||
lock: Vec<String>,
|
||||
kind: String,
|
||||
}
|
||||
|
||||
enum ArchiveImpl {
|
||||
@@ -1236,7 +1235,6 @@ async fn tarball_workspace(
|
||||
description: script.description,
|
||||
schema: script.schema,
|
||||
is_template: script.is_template,
|
||||
kind: script.kind.to_string(),
|
||||
lock,
|
||||
};
|
||||
let metadata_str = serde_json::to_string_pretty(&metadata).unwrap();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
use std::{
|
||||
fmt::{self, Display},
|
||||
fmt::Display,
|
||||
hash::{Hash, Hasher},
|
||||
};
|
||||
|
||||
@@ -103,18 +103,6 @@ pub enum ScriptKind {
|
||||
Approval,
|
||||
}
|
||||
|
||||
impl Display for ScriptKind {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
fmt.write_str(match self {
|
||||
ScriptKind::Trigger => "trigger",
|
||||
ScriptKind::Failure => "failure",
|
||||
ScriptKind::Script => "script",
|
||||
ScriptKind::Approval => "approval",
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[cfg_attr(feature = "sqlx", derive(sqlx::FromRow))]
|
||||
pub struct Script {
|
||||
|
||||
@@ -264,7 +264,7 @@ const ROOT_CACHE_DIR: &str = "/tmp/windmill/cache/";
|
||||
const PIP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "pip");
|
||||
const DENO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "deno");
|
||||
const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go");
|
||||
const NUM_SECS_PING: u64 = 5;
|
||||
const NUM_SECS_ENV_CHECK: u64 = 15;
|
||||
const NUM_SECS_SYNC: u64 = 60 * 10;
|
||||
|
||||
const INCLUDE_DEPS_PY_SH_CONTENT: &str = include_str!("../nsjail/download_deps.py.sh");
|
||||
@@ -553,7 +553,7 @@ pub async fn run_worker(
|
||||
|
||||
|
||||
let do_break = async {
|
||||
if last_ping.elapsed().as_secs() > NUM_SECS_PING {
|
||||
if last_ping.elapsed().as_secs() > NUM_SECS_ENV_CHECK {
|
||||
sqlx::query!(
|
||||
"UPDATE worker_ping SET ping_at = now(), jobs_executed = $1 WHERE worker = $2",
|
||||
jobs_executed,
|
||||
|
||||
30
cli/apps.ts
30
cli/apps.ts
@@ -28,18 +28,14 @@ export class AppFile implements Resource, PushDiffs {
|
||||
remotePath: string,
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
let app: AppWithLastVersion | undefined = undefined;
|
||||
try {
|
||||
app = await AppService.getAppByPath({ workspace, path: remotePath });
|
||||
} catch (e) {}
|
||||
|
||||
if (app) {
|
||||
if (await AppService.existsApp({ workspace, path: remotePath })) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing app... ${remotePath}`,
|
||||
`Applying ${diffs.length} diffs to existing app...`,
|
||||
),
|
||||
);
|
||||
const changeset: {
|
||||
path?: string | undefined;
|
||||
summary?: string | undefined;
|
||||
value?: any;
|
||||
policy?: Policy | undefined;
|
||||
@@ -50,7 +46,7 @@ export class AppFile implements Resource, PushDiffs {
|
||||
(
|
||||
diff.path[0] !== "value" && diff.path[0] !== "policy" && (
|
||||
diff.path.length !== 1 ||
|
||||
!["summary"].includes(
|
||||
!["path", "summary"].includes(
|
||||
diff.path[0] as string,
|
||||
)
|
||||
)
|
||||
@@ -65,13 +61,6 @@ export class AppFile implements Resource, PushDiffs {
|
||||
}
|
||||
}
|
||||
|
||||
if ((!changeset?.policy || JSON.stringify(changeset?.policy) == JSON.stringify(app.policy))
|
||||
&& (!changeset?.value || JSON.stringify(changeset?.value) == JSON.stringify(app.value))
|
||||
&& (!changeset?.summary || changeset.summary == app.summary)) {
|
||||
console.log(colors.yellow(`No changes to push for app ${remotePath}, skipping`))
|
||||
return;
|
||||
}
|
||||
|
||||
const hasChanges = Object.values(changeset).some((v) =>
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
@@ -98,10 +87,19 @@ export class AppFile implements Resource, PushDiffs {
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let existing: AppWithLastVersion | undefined;
|
||||
try {
|
||||
existing = await AppService.getAppByPath({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff({}, this, { cyclesFix: false }),
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export type Context = {
|
||||
};
|
||||
|
||||
async function tryResolveWorkspace(
|
||||
opts: GlobalOptions
|
||||
opts: GlobalOptions,
|
||||
): Promise<
|
||||
{ isError: false; value: Workspace } | { isError: true; error: string }
|
||||
> {
|
||||
@@ -49,20 +49,17 @@ async function tryResolveWorkspace(
|
||||
}
|
||||
|
||||
export async function resolveWorkspace(
|
||||
opts: GlobalOptions
|
||||
opts: GlobalOptions,
|
||||
): Promise<Workspace> {
|
||||
const res = await tryResolveWorkspace(opts);
|
||||
if (res.isError) {
|
||||
console.log(colors.red.bold(res.error));
|
||||
return Deno.exit(-1);
|
||||
} else {
|
||||
return res.value;
|
||||
}
|
||||
}
|
||||
|
||||
export async function requireLogin(
|
||||
opts: GlobalOptions
|
||||
): Promise<GlobalUserInfo> {
|
||||
export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo> {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
let token = await tryGetLoginInfo(opts);
|
||||
|
||||
@@ -76,7 +73,7 @@ export async function requireLogin(
|
||||
return await UserService.globalWhoami();
|
||||
} catch {
|
||||
console.log(
|
||||
"! Could not reach API given existing credentials. Attempting to reauth..."
|
||||
"! Could not reach API given existing credentials. Attempting to reauth...",
|
||||
);
|
||||
const newToken = await loginInteractive(workspace.remote);
|
||||
if (!newToken) {
|
||||
@@ -88,14 +85,14 @@ export async function requireLogin(
|
||||
|
||||
setClient(
|
||||
token,
|
||||
workspace.remote.substring(0, workspace.remote.length - 1)
|
||||
workspace.remote.substring(0, workspace.remote.length - 1),
|
||||
);
|
||||
return await UserService.globalWhoami();
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryResolveVersion(
|
||||
opts: GlobalOptions
|
||||
opts: GlobalOptions,
|
||||
): Promise<number | undefined> {
|
||||
if ((opts as any).__cache_version) {
|
||||
return (opts as any).__cache_version;
|
||||
@@ -105,12 +102,12 @@ export async function tryResolveVersion(
|
||||
if (workspaceRes.isError) return undefined;
|
||||
|
||||
const response = await fetch(
|
||||
new URL(new URL(workspaceRes.value.remote).origin + "/api/version")
|
||||
new URL(new URL(workspaceRes.value.remote).origin + "/api/version"),
|
||||
);
|
||||
const version = await response.text();
|
||||
try {
|
||||
return Number.parseInt(
|
||||
version.split("-", 1)[0].replaceAll(".", "").replace("v", "")
|
||||
version.split("-", 1)[0].replaceAll(".", "").replace("v", ""),
|
||||
);
|
||||
} catch {
|
||||
return undefined;
|
||||
@@ -119,7 +116,7 @@ export async function tryResolveVersion(
|
||||
|
||||
export async function validatePath(
|
||||
opts: GlobalOptions,
|
||||
path: string
|
||||
path: string,
|
||||
): Promise<boolean> {
|
||||
const backendVersion = await tryResolveVersion(opts);
|
||||
if (path.startsWith("f")) {
|
||||
@@ -127,16 +124,19 @@ export async function validatePath(
|
||||
return true;
|
||||
}
|
||||
console.log(
|
||||
`Attempting to use folders, but the current remote does not have support. Remote version is ${backendVersion} but folders are supported from 1560.`
|
||||
`Attempting to use folders, but the current remote does not have support. Remote version is ${backendVersion} but folders are supported from 1560.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(path.startsWith("g") || path.startsWith("u"))) {
|
||||
if (
|
||||
!(path.startsWith("g") ||
|
||||
path.startsWith("u"))
|
||||
) {
|
||||
console.log(
|
||||
colors.red(
|
||||
"Given remote path looks invalid. Remote paths are typically of the form <u|g|f>/<username|group|folder>/..."
|
||||
)
|
||||
"Given remote path looks invalid. Remote paths are typically of the form <u|g|f>/<username|group|folder>/...",
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
12
cli/flow.ts
12
cli/flow.ts
@@ -94,7 +94,6 @@ export class FlowFile implements Resource, PushDiffs {
|
||||
...changeset,
|
||||
...base_changeset,
|
||||
}
|
||||
|
||||
await FlowService.updateFlow({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
@@ -115,11 +114,20 @@ export class FlowFile implements Resource, PushDiffs {
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let remote: Flow | undefined;
|
||||
try {
|
||||
remote = await FlowService.getFlowByPath({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
|
||||
remote = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff({}, this, { cyclesFix: false }),
|
||||
microdiff(remote ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { colors, Command, FolderService, microdiff } from "./deps.ts";
|
||||
import { colors, Command, Folder, FolderService, microdiff } from "./deps.ts";
|
||||
import { requireLogin, resolveWorkspace, validatePath } from "./context.ts";
|
||||
import {
|
||||
Difference,
|
||||
@@ -22,9 +22,7 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
owners: Array<string> | undefined;
|
||||
@property(map(() => String, () => Boolean, { shape: MapShape.Object }))
|
||||
extra_perms: Map<string, boolean> | undefined;
|
||||
@property(() => String)
|
||||
display_name: string| undefined;
|
||||
|
||||
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
if (remotePath.startsWith("/")) {
|
||||
remotePath = remotePath.substring(1);
|
||||
@@ -33,10 +31,16 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
remotePath = remotePath.substring(2);
|
||||
}
|
||||
|
||||
let existing: Folder | undefined;
|
||||
try {
|
||||
existing = await FolderService.getFolder({ workspace, name: remotePath });
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff({}, this, { cyclesFix: false }),
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,24 +66,22 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
if (exists) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing folder... ${remotePath}`,
|
||||
`Applying ${diffs.length} diffs to existing folder...`,
|
||||
),
|
||||
);
|
||||
|
||||
const changeset: {
|
||||
owners?: string[] | undefined;
|
||||
extra_perms?: any;
|
||||
display_name?: string | undefined;
|
||||
} = {};
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path.length !== 1 ||
|
||||
!["owners", "extra_perms", "display_name"].includes(diff.path[0] as string)
|
||||
!["owners", "extra_perms"].includes(diff.path[0] as string)
|
||||
)
|
||||
) {
|
||||
console.log(diff.path)
|
||||
throw new Error("Invalid folder diff with path " + diff.path);
|
||||
}
|
||||
if (diff.type === "CREATE" || diff.type === "CHANGE") {
|
||||
@@ -95,10 +97,11 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
if (!hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
await FolderService.updateFolder({
|
||||
workspace: workspace,
|
||||
name: remotePath,
|
||||
requestBody: {...changeset, extra_perms: changeset.extra_perms ? Object.fromEntries(this.extra_perms?.entries() ?? []) : undefined}
|
||||
requestBody: changeset,
|
||||
});
|
||||
} else {
|
||||
console.log(colors.bold.yellow("Creating new folder: " + remotePath));
|
||||
|
||||
@@ -13,7 +13,7 @@ import sync from "./sync.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
const VERSION = "v1.76.0";
|
||||
const VERSION = "v1.74.2";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
EditResourceType,
|
||||
microdiff,
|
||||
ResourceService,
|
||||
ResourceType,
|
||||
Table,
|
||||
} from "./deps.ts";
|
||||
import { Any, decoverto, model, property } from "./decoverto.ts";
|
||||
@@ -25,10 +26,19 @@ export class ResourceTypeFile implements ResourceI, PushDiffs {
|
||||
description?: string;
|
||||
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
await this.pushDiffs(
|
||||
let existing: ResourceType | undefined;
|
||||
try {
|
||||
existing = await ResourceService.getResourceType({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff({}, this, { cyclesFix: false }),
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,8 +65,8 @@ export class ResourceTypeFile implements ResourceI, PushDiffs {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
colors.yellow.bold(
|
||||
`Applying ${diffs.length} diffs to existing resource type... ${remotePath}`,
|
||||
colors.yellow(
|
||||
`Applying ${diffs.length} diffs to existing resource type...`,
|
||||
),
|
||||
);
|
||||
const changeset: EditResourceType = {};
|
||||
|
||||
@@ -43,7 +43,7 @@ export class ResourceFile implements Resource2, PushDiffs {
|
||||
})
|
||||
) {
|
||||
console.log(
|
||||
colors.yellow.bold(`Applying ${diffs.length} diffs to existing resource... ${remotePath}`),
|
||||
colors.yellow(`Applying ${diffs.length} diffs to existing resource...`),
|
||||
);
|
||||
|
||||
const changeset: EditResource = {
|
||||
@@ -51,7 +51,11 @@ export class ResourceFile implements Resource2, PushDiffs {
|
||||
};
|
||||
for (const diff of diffs) {
|
||||
if (diff.path[0] === "is_oauth") {
|
||||
//is_oauth is not updatable
|
||||
console.log(
|
||||
colors.yellow(
|
||||
"! is_oauth has been removed in newer versions. Ignoring.",
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
@@ -60,10 +64,9 @@ export class ResourceFile implements Resource2, PushDiffs {
|
||||
diff.path[0] !== "value" && (
|
||||
diff.path.length !== 1 ||
|
||||
diff.path[0] !== "description"
|
||||
) && diff.path[0] !== "resource_type"
|
||||
)
|
||||
)
|
||||
) {
|
||||
console.log(colors.red("Invalid variable diff with path " + diff.path));
|
||||
throw new Error("Invalid folder diff with path " + diff.path);
|
||||
}
|
||||
if (diff.type === "CREATE" || diff.type === "CHANGE") {
|
||||
@@ -107,10 +110,19 @@ export class ResourceFile implements Resource2, PushDiffs {
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let existing: Resource | undefined;
|
||||
try {
|
||||
existing = await ResourceService.getResource({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff({}, this, { cyclesFix: false }),
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ export class ScriptFile {
|
||||
},
|
||||
toPlain: (data) => data,
|
||||
})
|
||||
@property(() => String)
|
||||
kind?: "script" | "failure" | "trigger" | "command" | "approval";
|
||||
|
||||
constructor(summary: string, description: string) {
|
||||
@@ -106,21 +105,11 @@ export async function handleFile(path: string, content: string, workspace: strin
|
||||
} catch { }
|
||||
const language = inferContentTypeFromFilePath(path);
|
||||
|
||||
let remote = undefined
|
||||
try {
|
||||
remote = await ScriptService.getScriptByPath({
|
||||
const remote = await ScriptService.getScriptByPath({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch { }
|
||||
|
||||
if (remote) {
|
||||
if (typed.description === remote.description && content === remote.content && typed.summary === remote.summary && typed.is_template === remote.is_template && typed.kind == remote.kind && remote?.lock == typed.lock?.join('\n') && JSON.stringify(typed.schema) == JSON.stringify(remote.schema)) {
|
||||
console.log(colors.yellow(`No change to push for script ${remotePath}, skipping`))
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
await ScriptService.createScript({
|
||||
workspace,
|
||||
requestBody: {
|
||||
@@ -134,11 +123,10 @@ export async function handleFile(path: string, content: string, workspace: strin
|
||||
lock: typed.lock,
|
||||
parent_hash: remote.hash,
|
||||
schema: typed.schema,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
console.log(colors.yellow.bold(`Creating script with a parent ${remotePath}`))
|
||||
} else {
|
||||
} catch {
|
||||
// no parent hash
|
||||
await ScriptService.createScript({
|
||||
workspace: workspace,
|
||||
@@ -156,6 +144,7 @@ export async function handleFile(path: string, content: string, workspace: strin
|
||||
},
|
||||
});
|
||||
console.log(colors.yellow.bold(`Creating script without parent ${remotePath}`))
|
||||
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
484
cli/sync.ts
484
cli/sync.ts
@@ -13,7 +13,7 @@ import {
|
||||
ResourceService,
|
||||
VariableService,
|
||||
AppService,
|
||||
FlowService,
|
||||
FlowService
|
||||
} from "./deps.ts";
|
||||
import {
|
||||
Difference,
|
||||
@@ -25,13 +25,16 @@ import {
|
||||
import { downloadZip } from "./pull.ts";
|
||||
import { FolderFile } from "./folder.ts";
|
||||
import { ResourceTypeFile } from "./resource-type.ts";
|
||||
import { handleScriptMetadata, ScriptFile } from "./script.ts";
|
||||
import {
|
||||
handleScriptMetadata,
|
||||
ScriptFile,
|
||||
} from "./script.ts";
|
||||
import { ResourceFile } from "./resource.ts";
|
||||
import { FlowFile } from "./flow.ts";
|
||||
import { VariableFile } from "./variable.ts";
|
||||
import { handleFile } from "./script.ts";
|
||||
import { equal } from "https://deno.land/x/equal@v1.5.0/mod.ts";
|
||||
import { diffCharacters } from "https://deno.land/x/diff@v0.3.5/mod.ts";
|
||||
import { equal } from "https://deno.land/x/equal/mod.ts";
|
||||
import { diffCharacters } from "https://deno.land/x/diff/mod.ts";
|
||||
type DynFSElement = {
|
||||
isDirectory: boolean;
|
||||
path: string;
|
||||
@@ -41,7 +44,10 @@ type DynFSElement = {
|
||||
};
|
||||
|
||||
async function FSFSElement(p: string): Promise<DynFSElement> {
|
||||
function _internal_element(localP: string, isDir: boolean): DynFSElement {
|
||||
function _internal_element(
|
||||
localP: string,
|
||||
isDir: boolean,
|
||||
): DynFSElement {
|
||||
return {
|
||||
isDirectory: isDir,
|
||||
path: localP.substring(p.length + 1),
|
||||
@@ -107,14 +113,16 @@ function ZipFSElement(zip: JSZip): DynFSElement {
|
||||
|
||||
async function* readDirRecursiveWithIgnore(
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
root: DynFSElement
|
||||
): AsyncGenerator<{
|
||||
path: string;
|
||||
ignored: boolean;
|
||||
isDirectory: boolean;
|
||||
getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}> {
|
||||
root: DynFSElement,
|
||||
): AsyncGenerator<
|
||||
{
|
||||
path: string;
|
||||
ignored: boolean;
|
||||
isDirectory: boolean;
|
||||
getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}
|
||||
> {
|
||||
const stack: {
|
||||
path: string;
|
||||
isDirectory: boolean;
|
||||
@@ -122,20 +130,18 @@ async function* readDirRecursiveWithIgnore(
|
||||
c(): AsyncIterable<DynFSElement>;
|
||||
getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}[] = [
|
||||
{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
getContentBytes(): Promise<Uint8Array> {
|
||||
throw undefined;
|
||||
},
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
},
|
||||
}[] = [{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
getContentBytes(): Promise<Uint8Array> {
|
||||
throw undefined;
|
||||
},
|
||||
];
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
},
|
||||
}];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const e = stack.pop()!;
|
||||
@@ -155,17 +161,17 @@ async function* readDirRecursiveWithIgnore(
|
||||
}
|
||||
|
||||
type Added = { name: "added"; path: string; content: string };
|
||||
type Deleted = { name: "deleted"; path: string };
|
||||
type Edit = { name: "edited"; path: string; before: string; after: string };
|
||||
type Deleted = { name: "deleted"; path: string; };
|
||||
type Edit = { name: "edited"; path: string; before: string; after: string; };
|
||||
|
||||
type Change = Added | Deleted | Edit;
|
||||
|
||||
async function elementsToMap(
|
||||
els: DynFSElement,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean
|
||||
): Promise<{ [key: string]: string }> {
|
||||
async function elementsToMap(els: DynFSElement, ignore: (path: string, isDirectory: boolean) => boolean): Promise<{ [key: string]: string }> {
|
||||
const map: { [key: string]: string } = {};
|
||||
for await (const entry of readDirRecursiveWithIgnore(ignore, els)) {
|
||||
for await (const entry of readDirRecursiveWithIgnore(
|
||||
ignore,
|
||||
els,
|
||||
)) {
|
||||
if (entry.isDirectory || entry.ignored) continue;
|
||||
const content = await entry.getContentText();
|
||||
map[entry.path] = content;
|
||||
@@ -173,26 +179,20 @@ async function elementsToMap(
|
||||
return map;
|
||||
}
|
||||
async function compareDynFSElement(
|
||||
els1: DynFSElement,
|
||||
els2: DynFSElement | undefined,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean
|
||||
els1: DynFSElement, els2: DynFSElement,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
raw: boolean
|
||||
): Promise<Change[]> {
|
||||
const [m1, m2] = els2
|
||||
? await Promise.all([
|
||||
elementsToMap(els1, ignore),
|
||||
elementsToMap(els2, ignore),
|
||||
])
|
||||
: [await elementsToMap(els1, ignore), {}];
|
||||
|
||||
const [m1, m2] = raw ? [await elementsToMap(els1, ignore), {}] :
|
||||
await Promise.all([elementsToMap(els1, ignore), elementsToMap(els2, ignore)]);
|
||||
|
||||
const changes: Change[] = [];
|
||||
|
||||
for (const [k, v] of Object.entries(m1)) {
|
||||
if (m2[k] === undefined) {
|
||||
changes.push({ name: "added", path: k, content: v });
|
||||
} else if (
|
||||
m2[k] != v &&
|
||||
(!k.endsWith(".json") || !equal(JSON.parse(v), JSON.parse(m2[k])))
|
||||
) {
|
||||
} else if (m2[k] != v && (!k.endsWith(".json") || !equal(JSON.parse(v), JSON.parse(m2[k])))) {
|
||||
// await Deno.writeTextFile("/tmp/k", m2[k])
|
||||
// await Deno.writeTextFile("/tmp/v", v)
|
||||
// console.log(k)
|
||||
@@ -208,54 +208,54 @@ async function compareDynFSElement(
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
return changes
|
||||
}
|
||||
|
||||
const isNotWmillFile = (p: string, isDirectory: boolean) => {
|
||||
if (p.endsWith("/")) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (isDirectory) {
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/");
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
|
||||
}
|
||||
|
||||
try {
|
||||
const typ = getTypeStrFromPath(p);
|
||||
if (typ == "resource-type") {
|
||||
return p.includes("/");
|
||||
const typ = getTypeStrFromPath(p)
|
||||
if (typ == 'resource-type') {
|
||||
return p.includes('/')
|
||||
} else {
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/");
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
|
||||
}
|
||||
} catch {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const isWhitelisted = (p: string) => {
|
||||
return p == "./" || p == "" || p == "u" || p == "f" || p == "g";
|
||||
};
|
||||
return p == "./" || p == "" || p == "u" || p == "f" || p == "g"
|
||||
}
|
||||
async function ignoreF() {
|
||||
try {
|
||||
const ignore: {
|
||||
accepts(file: string): boolean;
|
||||
denies(file: string): boolean;
|
||||
} = gitignore_parser.compile(await Deno.readTextFile(".wmillignore"));
|
||||
} = gitignore_parser.compile(
|
||||
await Deno.readTextFile(".wmillignore"),
|
||||
);
|
||||
|
||||
return (p: string, isDirectory: boolean) => {
|
||||
return (
|
||||
!isWhitelisted(p) &&
|
||||
(isNotWmillFile(p, isDirectory) || ignore.denies(p))
|
||||
);
|
||||
};
|
||||
return !isWhitelisted(p) && (isNotWmillFile(p, isDirectory) || ignore.denies(p));
|
||||
}
|
||||
} catch (e) {
|
||||
return (p: string, isDirectory: boolean) =>
|
||||
!isWhitelisted(p) && isNotWmillFile(p, isDirectory);
|
||||
return (p: string, isDirectory: boolean) => !isWhitelisted(p) && isNotWmillFile(p, isDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
async function pull(
|
||||
opts: GlobalOptions & { raw: boolean; yes: boolean; failConflicts: boolean }
|
||||
opts: GlobalOptions & { raw: boolean; yes: boolean, failConflicts: boolean },
|
||||
) {
|
||||
|
||||
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.join(Deno.cwd(), ".wmill"));
|
||||
}
|
||||
@@ -263,96 +263,74 @@ async function pull(
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
console.log(
|
||||
colors.gray(
|
||||
"Computing the files to update locally to match remote (taking .wmillignore into account)"
|
||||
)
|
||||
);
|
||||
const remote = ZipFSElement((await downloadZip(workspace))!);
|
||||
const local = opts.raw
|
||||
? undefined
|
||||
: await FSFSElement(path.join(Deno.cwd(), opts.raw ? "" : ".wmill"));
|
||||
const changes = await compareDynFSElement(remote, local, await ignoreF());
|
||||
|
||||
console.log(colors.gray("Computing the files to update locally to match remote (taking .wmillignore into account)"));
|
||||
const remote = ZipFSElement((await downloadZip(workspace))!)
|
||||
const local = await FSFSElement(path.join(Deno.cwd(), opts.raw ? "" : ".wmill"))
|
||||
const changes = await compareDynFSElement(remote, local, await ignoreF(), opts.raw)
|
||||
|
||||
|
||||
console.log(`remote -> local: ${changes.length} changes to apply`);
|
||||
if (changes.length > 0) {
|
||||
prettyChanges(changes);
|
||||
|
||||
prettyChanges(changes)
|
||||
if (
|
||||
!opts.yes &&
|
||||
!opts.raw &&
|
||||
!(await Confirm.prompt({
|
||||
message: `Do you want to apply these ${changes.length} changes?`,
|
||||
default: true,
|
||||
}))
|
||||
!opts.yes && !opts.raw && !(await Confirm.prompt({ message: `Do you want to apply these ${changes.length} changes?`, default: true }))
|
||||
) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const conflicts = [];
|
||||
const conflicts = []
|
||||
console.log(colors.gray(`Applying changes to files ...`));
|
||||
for await (const change of changes) {
|
||||
const target = path.join(Deno.cwd(), change.path);
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path);
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
|
||||
if (change.name === "edited") {
|
||||
|
||||
try {
|
||||
const currentLocal = await Deno.readTextFile(target);
|
||||
if (currentLocal !== change.before && currentLocal !== change.after) {
|
||||
console.log(
|
||||
colors.red(
|
||||
`Conflict detected on ${change.path}\nBoth local and remote have been modified.`
|
||||
)
|
||||
);
|
||||
const currentLocal = await Deno.readTextFile(target)
|
||||
if (currentLocal !== change.before) {
|
||||
console.log(colors.red(`Conflict detected on ${change.path}\nBoth local and remote have been modified.`))
|
||||
if (opts.failConflicts) {
|
||||
conflicts.push({
|
||||
local: currentLocal,
|
||||
change,
|
||||
path: change.path,
|
||||
});
|
||||
conflicts.push({ local: currentLocal, change, path: change.path })
|
||||
continue;
|
||||
} else if (opts.yes) {
|
||||
console.log(
|
||||
colors.red(
|
||||
`Override local version with remote since --yes was passed and no --fail-conflicts.`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
showConflict(change.path, currentLocal, change.after);
|
||||
if (
|
||||
await Confirm.prompt(
|
||||
"Preserve local (push to change remote and avoid seeing this again)?"
|
||||
)
|
||||
) {
|
||||
console.log(colors.red(`Override local version with remote since --yes was passed and no --fail-conflicts.`))
|
||||
}
|
||||
else {
|
||||
showConflict(change.path, currentLocal, change.after)
|
||||
if (await Confirm.prompt("Preserve local (push to change remote and avoid seeing this again)?")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch { }
|
||||
if (change.path.endsWith(".json")) {
|
||||
const diffs = microdiff(
|
||||
JSON.parse(change.before),
|
||||
JSON.parse(change.after),
|
||||
{ cyclesFix: false }
|
||||
);
|
||||
const diffs =
|
||||
microdiff(
|
||||
JSON.parse(change.before),
|
||||
JSON.parse(change.after),
|
||||
{ cyclesFix: false },
|
||||
)
|
||||
|
||||
console.log(
|
||||
`Editing ${getTypeStrFromPath(change.path)} json ${change.path}`
|
||||
console.log(`Editing ${getTypeStrFromPath(change.path)} json ${change.path}`)
|
||||
await applyDiff(
|
||||
diffs,
|
||||
target,
|
||||
);
|
||||
await applyDiff(diffs, target);
|
||||
} else {
|
||||
console.log(`Editing script ${change.path}`);
|
||||
console.log(`Editing script ${change.path}`)
|
||||
await Deno.writeTextFile(target, change.after);
|
||||
}
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
await Deno.copyFile(target, stateTarget);
|
||||
}
|
||||
} else if (change.name === "added") {
|
||||
await ensureDir(path.dirname(target));
|
||||
await ensureDir(path.dirname(target))
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
console.log(
|
||||
`Adding ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
console.log(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
}
|
||||
await Deno.writeTextFile(target, change.content);
|
||||
if (!opts.raw) {
|
||||
@@ -360,10 +338,8 @@ async function pull(
|
||||
}
|
||||
} else if (change.name === "deleted") {
|
||||
try {
|
||||
console.log(
|
||||
`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await Deno.remove(target);
|
||||
console.log(`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
await Deno.remove(target)
|
||||
if (!opts.raw) {
|
||||
await Deno.remove(stateTarget);
|
||||
}
|
||||
@@ -376,29 +352,24 @@ async function pull(
|
||||
}
|
||||
if (opts.failConflicts) {
|
||||
if (conflicts.length > 0) {
|
||||
console.error(colors.red(`Conflicts were found`));
|
||||
console.log("Conflicts:");
|
||||
console.error(colors.red(`Conflicts were found`))
|
||||
console.log("Conflicts:")
|
||||
for (const conflict of conflicts) {
|
||||
showConflict(conflict.path, conflict.local, conflict.change.after);
|
||||
showConflict(conflict.path, conflict.local, conflict.change.after)
|
||||
}
|
||||
console.log(
|
||||
colors.red(`Please resolve theses conflicts manually by either:
|
||||
console.log(colors.red(`Please resolve theses conflicts manually by either:
|
||||
- reverting the content back to its remote (\`wmill pull\` and refuse to preserve local when prompted)
|
||||
- pushing the changes with \`wmill push --skip-pull\` to override wmill with all your local changes
|
||||
`)
|
||||
);
|
||||
Deno.exit(1);
|
||||
`))
|
||||
Deno.exit(1)
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
colors.green.underline(
|
||||
`Done! All ${changes.length} changes applied locally.`
|
||||
)
|
||||
);
|
||||
console.log(colors.green.underline(`Done! All ${changes.length} changes applied locally.`));
|
||||
}
|
||||
|
||||
|
||||
function showConflict(path: string, local: string, remote: string) {
|
||||
console.log(colors.yellow(`- ${path}`));
|
||||
console.log(colors.yellow(`- ${path}`))
|
||||
|
||||
let finalString = "";
|
||||
for (const character of diffCharacters(local, remote)) {
|
||||
@@ -414,8 +385,8 @@ async function pull(
|
||||
}
|
||||
}
|
||||
console.log(finalString);
|
||||
console.log("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m");
|
||||
console.log();
|
||||
console.log("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m")
|
||||
console.log()
|
||||
}
|
||||
async function applyDiff(diffs: Difference[], file: string) {
|
||||
ensureDir(path.dirname(file));
|
||||
@@ -444,20 +415,17 @@ async function pull(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function prettyChanges(changes: Change[]) {
|
||||
for (const change of changes) {
|
||||
if (change.name === "added") {
|
||||
console.log(
|
||||
colors.green(`+ ${getTypeStrFromPath(change.path)} ` + change.path)
|
||||
);
|
||||
console.log(colors.green(`+ ${getTypeStrFromPath(change.path)} ` + change.path));
|
||||
} else if (change.name === "deleted") {
|
||||
console.log(
|
||||
colors.red(`- ${getTypeStrFromPath(change.path)} ` + change.path)
|
||||
);
|
||||
console.log(colors.red(`- ${getTypeStrFromPath(change.path)} ` + change.path));
|
||||
} else if (change.name === "edited") {
|
||||
console.log(
|
||||
colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path)
|
||||
);
|
||||
console.log(colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,208 +452,138 @@ function prettyDiff(diffs: Difference[]) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function removeSuffix(str: string, suffix: string) {
|
||||
return str.slice(0, str.length - suffix.length);
|
||||
}
|
||||
|
||||
async function push(
|
||||
opts: GlobalOptions & {
|
||||
raw: boolean;
|
||||
yes: boolean;
|
||||
skipPull: boolean;
|
||||
failConflicts: boolean;
|
||||
}
|
||||
) {
|
||||
async function push(opts: GlobalOptions & { raw: boolean, yes: boolean, skipPull: boolean, failConflicts: boolean }) {
|
||||
|
||||
|
||||
if (!opts.raw) {
|
||||
if (!opts.skipPull) {
|
||||
console.log(
|
||||
colors.gray("You need to be up-to-date before pushing, pulling first.")
|
||||
);
|
||||
await pull(opts);
|
||||
console.log(colors.green("Pull done, now pushing."));
|
||||
console.log();
|
||||
console.log(colors.gray("You need to be up-to-date before pushing, pulling first."))
|
||||
await pull(opts)
|
||||
console.log(colors.green("Pull done, now pushing."))
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
console.log(
|
||||
colors.gray(
|
||||
"Computing the files to update on the remote to match local (taking .wmillignore into account)"
|
||||
)
|
||||
);
|
||||
const remote = opts.raw
|
||||
? undefined
|
||||
: ZipFSElement((await downloadZip(workspace))!);
|
||||
const local = await FSFSElement(path.join(Deno.cwd(), ""));
|
||||
const changes = await compareDynFSElement(local, remote, await ignoreF());
|
||||
|
||||
console.log(colors.gray("Computing the files to update on the remote to match local (taking .wmillignore into account)"));
|
||||
const remote = ZipFSElement((await downloadZip(workspace))!)
|
||||
const local = await FSFSElement(path.join(Deno.cwd(), ""))
|
||||
const changes = await compareDynFSElement(local, remote, await ignoreF(), opts.raw)
|
||||
|
||||
console.log(`remote <- local: ${changes.length} changes to apply`);
|
||||
if (changes.length > 0) {
|
||||
prettyChanges(changes);
|
||||
|
||||
prettyChanges(changes)
|
||||
if (
|
||||
!opts.yes &&
|
||||
!(await Confirm.prompt({
|
||||
message: `Do you want to apply these ${changes.length} changes?`,
|
||||
default: true,
|
||||
}))
|
||||
!opts.yes && !(await Confirm.prompt({ message: `Do you want to apply these ${changes.length} changes?`, default: true }))
|
||||
) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
console.log(colors.gray(`Applying changes to files ...`));
|
||||
const alreadySynced: string[] = [];
|
||||
const alreadySynced: string[] = []
|
||||
for await (const change of changes) {
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path);
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
|
||||
if (change.name === "edited") {
|
||||
if (
|
||||
await handleScriptMetadata(
|
||||
change.path,
|
||||
workspace.workspaceId,
|
||||
alreadySynced
|
||||
)
|
||||
) {
|
||||
if (await handleScriptMetadata(change.path, workspace.workspaceId, alreadySynced)) {
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.after);
|
||||
}
|
||||
continue;
|
||||
} else if (
|
||||
await handleFile(
|
||||
change.path,
|
||||
change.after,
|
||||
workspace.workspaceId,
|
||||
alreadySynced
|
||||
)
|
||||
) {
|
||||
continue
|
||||
} else if (await handleFile(change.path, change.after, workspace.workspaceId, alreadySynced)) {
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.after);
|
||||
}
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
console.log(
|
||||
`Editing ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
console.log(`Editing ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
}
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.after));
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.after))
|
||||
|
||||
const diff = microdiff(
|
||||
inferTypeFromPath(change.path, JSON.parse(change.before)),
|
||||
obj,
|
||||
{ cyclesFix: false }
|
||||
);
|
||||
const diff = microdiff(inferTypeFromPath(change.path, JSON.parse(change.before)), obj, { cyclesFix: false });
|
||||
await applyDiff(
|
||||
workspace.workspaceId,
|
||||
change.path.split(".")[0],
|
||||
obj,
|
||||
diff
|
||||
diff,
|
||||
);
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.after);
|
||||
}
|
||||
} else if (change.name === "added") {
|
||||
if (change.path.endsWith(".script.json")) {
|
||||
continue;
|
||||
} else if (
|
||||
await handleFile(
|
||||
change.path,
|
||||
change.content,
|
||||
workspace.workspaceId,
|
||||
alreadySynced
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
continue
|
||||
} else if (await handleFile(change.path, change.content, workspace.workspaceId, alreadySynced)) {
|
||||
continue
|
||||
}
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
console.log(
|
||||
`Adding ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
console.log(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
}
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.content));
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.content))
|
||||
const diff = microdiff({}, obj, { cyclesFix: false });
|
||||
await applyDiff(
|
||||
workspace.workspaceId,
|
||||
change.path.split(".")[0],
|
||||
obj,
|
||||
diff
|
||||
diff,
|
||||
);
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.content);
|
||||
}
|
||||
} else if (change.name === "deleted") {
|
||||
if (!change.path.includes(".json")) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
console.log(
|
||||
`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
const typ = getTypeStrFromPath(change.path);
|
||||
console.log(`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
const typ = getTypeStrFromPath(change.path)
|
||||
const workspaceId = workspace.workspaceId;
|
||||
switch (typ) {
|
||||
case "script": {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".script.json"),
|
||||
});
|
||||
await ScriptService.deleteScriptByHash({
|
||||
workspace: workspaceId,
|
||||
hash: script.hash,
|
||||
});
|
||||
const script = await ScriptService.getScriptByPath({ workspace: workspaceId, path: removeSuffix(change.path, ".script.json") })
|
||||
await ScriptService.deleteScriptByHash({ workspace: workspaceId, hash: script.hash })
|
||||
break;
|
||||
}
|
||||
case "folder":
|
||||
await FolderService.deleteFolder({
|
||||
workspace: workspaceId,
|
||||
name: change.path.split("/")[1],
|
||||
});
|
||||
await FolderService.deleteFolder({ workspace: workspaceId, name: change.path.split('/')[1] })
|
||||
break;
|
||||
case "resource":
|
||||
await ResourceService.deleteResource({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".resource.json"),
|
||||
});
|
||||
await ResourceService.deleteResource({ workspace: workspaceId, path: removeSuffix(change.path, ".resource.json") })
|
||||
break;
|
||||
case "resource-type":
|
||||
await ResourceService.deleteResourceType({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".resource-type.json"),
|
||||
});
|
||||
break;
|
||||
await ResourceService.deleteResourceType({ workspace: workspaceId, path: removeSuffix(change.path, ".resource-type.json") })
|
||||
break
|
||||
case "flow":
|
||||
await FlowService.deleteFlowByPath({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".flow.json"),
|
||||
});
|
||||
break;
|
||||
await FlowService.deleteFlowByPath({ workspace: workspaceId, path: removeSuffix(change.path, ".flow.json") })
|
||||
break
|
||||
case "app":
|
||||
await AppService.deleteApp({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".app.json"),
|
||||
});
|
||||
break;
|
||||
await AppService.deleteApp({ workspace: workspaceId, path: removeSuffix(change.path, ".app.json") })
|
||||
break
|
||||
case "variable":
|
||||
await VariableService.deleteVariable({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".variable.json"),
|
||||
});
|
||||
break;
|
||||
await VariableService.deleteVariable({ workspace: workspaceId, path: removeSuffix(change.path, ".variable.json") })
|
||||
break
|
||||
default:
|
||||
break;
|
||||
}
|
||||
try {
|
||||
await Deno.remove(stateTarget);
|
||||
} catch {}
|
||||
Deno.remove(stateTarget)
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
colors.green.underline(
|
||||
`Done! All ${changes.length} changes pushed to the remote workspace.`
|
||||
)
|
||||
);
|
||||
console.log(colors.green.underline(`Done! All ${changes.length} changes pushed to the remote workspace.`));
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function applyDiff(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
@@ -696,11 +594,11 @@ async function push(
|
||||
| ResourceFile
|
||||
| ResourceTypeFile
|
||||
| FolderFile,
|
||||
diffs: Difference[]
|
||||
diffs: Difference[],
|
||||
) {
|
||||
if (file instanceof ScriptFile) {
|
||||
throw new Error(
|
||||
"This code path should be unreachable - we should never generate diffs for scripts"
|
||||
"This code path should be unreachable - we should never generate diffs for scripts",
|
||||
);
|
||||
} else if (file instanceof FolderFile) {
|
||||
const parts = remotePath.split("/");
|
||||
@@ -711,14 +609,14 @@ async function push(
|
||||
}
|
||||
}
|
||||
if (diffs.length === 0) {
|
||||
console.log("No diffs to apply to " + remotePath);
|
||||
console.log("No diffs to apply to " + remotePath)
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await file.pushDiffs(workspace, remotePath, diffs);
|
||||
} catch (e) {
|
||||
console.error("Failing to apply diffs to " + remotePath);
|
||||
console.error(JSON.stringify(e));
|
||||
console.error("Failing to apply diffs to " + remotePath)
|
||||
console.error(e.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,24 +624,18 @@ async function push(
|
||||
const command = new Command()
|
||||
.command("pull")
|
||||
.description(
|
||||
"Pull any remote changes and apply them locally. Use --raw for usage without local state tracking."
|
||||
)
|
||||
.option(
|
||||
"--fail-conflicts",
|
||||
"Error on conflicts (both remote and local have changes on the same item)"
|
||||
"Pull any remote changes and apply them locally. Use --raw for usage without local state tracking.",
|
||||
)
|
||||
.option("--fail-conflicts", "Error on conflicts (both remote and local have changes on the same item)")
|
||||
.option("--yes", "Pull without needing confirmation")
|
||||
.option("--raw", "Pull without using state, just overwrite.")
|
||||
.action(pull as any)
|
||||
.command("push")
|
||||
.description(
|
||||
"Push any local changes and apply them remotely. Use --raw for usage without local state tracking."
|
||||
"Push any local changes and apply them remotely. Use --raw for usage without local state tracking.",
|
||||
)
|
||||
.option(
|
||||
"--fail-conflicts",
|
||||
"Error on conflicts (both remote and local have changes on the same item)"
|
||||
)
|
||||
.option("--skip-pull", "Push without pulling first (you have pulled prior)")
|
||||
.option("--fail-conflicts", "Error on conflicts (both remote and local have changes on the same item)")
|
||||
.option("--skip-pull", "Push without pulling first")
|
||||
.option("--yes", "Push without needing confirmation")
|
||||
.option("--raw", "Push without using state, just overwrite.")
|
||||
.action(push as any);
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
colors,
|
||||
Command,
|
||||
EditVariable,
|
||||
ListableVariable,
|
||||
microdiff,
|
||||
Table,
|
||||
VariableService,
|
||||
@@ -66,7 +67,7 @@ export class VariableFile implements Resource, PushDiffs {
|
||||
if (await VariableService.existsVariable({ workspace, path: remotePath })) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing variable... ${remotePath}`,
|
||||
`Applying ${diffs.length} diffs to existing variable...`,
|
||||
),
|
||||
);
|
||||
const changeset: EditVariable = {};
|
||||
@@ -75,12 +76,11 @@ export class VariableFile implements Resource, PushDiffs {
|
||||
diff.type !== "REMOVE" &&
|
||||
(
|
||||
diff.path.length !== 1 ||
|
||||
!["path", "value", "is_secret", "description", "account", "is_oauth"].includes(
|
||||
!["path", "value", "is_secret", "description"].includes(
|
||||
diff.path[0] as string,
|
||||
)
|
||||
)
|
||||
) {
|
||||
console.log(colors.red("Invalid variable diff with path " + diff.path));
|
||||
throw new Error("Invalid variable diff with path " + diff.path);
|
||||
}
|
||||
if (diff.type === "CREATE" || diff.type === "CHANGE") {
|
||||
@@ -96,7 +96,6 @@ export class VariableFile implements Resource, PushDiffs {
|
||||
if (!hasChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
await VariableService.updateVariable({
|
||||
workspace,
|
||||
path: remotePath,
|
||||
@@ -104,6 +103,7 @@ export class VariableFile implements Resource, PushDiffs {
|
||||
requestBody: changeset,
|
||||
});
|
||||
|
||||
console.log(changeset);
|
||||
} else {
|
||||
console.log(colors.yellow.bold("Creating new variable..."));
|
||||
await VariableService.createVariable({
|
||||
@@ -121,10 +121,19 @@ export class VariableFile implements Resource, PushDiffs {
|
||||
}
|
||||
}
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
let existing: ListableVariable | undefined;
|
||||
try {
|
||||
existing = await VariableService.getVariable({
|
||||
workspace: workspace,
|
||||
path: remotePath,
|
||||
});
|
||||
} catch {
|
||||
existing = undefined;
|
||||
}
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff({}, this, { cyclesFix: false }),
|
||||
microdiff(existing ?? {}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
59
frontend/package-lock.json
generated
59
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.76.0",
|
||||
"version": "1.74.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill",
|
||||
"version": "1.76.0",
|
||||
"version": "1.74.2",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
@@ -48,6 +48,7 @@
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@windmill-labs/svelte-grid": "^5.1.6",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cssnano": "^5.1.14",
|
||||
@@ -63,6 +64,7 @@
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"shepherd.js": "^10.0.1",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"svelte": "^3.55.1",
|
||||
@@ -1600,6 +1602,12 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@windmill-labs/svelte-grid": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
|
||||
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@zeit/schemas": {
|
||||
"version": "2.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.21.0.tgz",
|
||||
@@ -6646,6 +6654,24 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shepherd.js": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
|
||||
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"deepmerge": "^4.2.2",
|
||||
"smoothscroll-polyfill": "^0.4.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "12.* || 14.* || >= 16"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/rwwagner90"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
@@ -6732,6 +6758,12 @@
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/smoothscroll-polyfill": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
|
||||
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sorcery": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
|
||||
@@ -9277,6 +9309,12 @@
|
||||
"eslint-visitor-keys": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@windmill-labs/svelte-grid": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
|
||||
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@zeit/schemas": {
|
||||
"version": "2.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.21.0.tgz",
|
||||
@@ -12976,6 +13014,17 @@
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true
|
||||
},
|
||||
"shepherd.js": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
|
||||
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@popperjs/core": "^2.11.5",
|
||||
"deepmerge": "^4.2.2",
|
||||
"smoothscroll-polyfill": "^0.4.4"
|
||||
}
|
||||
},
|
||||
"signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
@@ -13036,6 +13085,12 @@
|
||||
"is-fullwidth-code-point": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"smoothscroll-polyfill": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
|
||||
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
|
||||
"dev": true
|
||||
},
|
||||
"sorcery": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.76.0",
|
||||
"version": "1.74.2",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -27,6 +27,7 @@
|
||||
"@types/vscode": "~1.74.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.49.0",
|
||||
"@typescript-eslint/parser": "^5.48.0",
|
||||
"@windmill-labs/svelte-grid": "^5.1.6",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cssnano": "^5.1.14",
|
||||
@@ -42,6 +43,7 @@
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.3",
|
||||
"prettier-plugin-svelte": "^2.9.0",
|
||||
"shepherd.js": "^10.0.1",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"svelte": "^3.55.1",
|
||||
|
||||
77
frontend/src/global.d.ts
vendored
77
frontend/src/global.d.ts
vendored
@@ -1,2 +1,79 @@
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
|
||||
declare type Item = import('svelte-dnd-action').Item
|
||||
declare type DndEvent<ItemType = Item> = import('svelte-dnd-action').DndEvent<ItemType>
|
||||
declare namespace svelte.JSX {
|
||||
interface HTMLAttributes<T> {
|
||||
onconsider?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void
|
||||
onfinalize?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void
|
||||
}
|
||||
}
|
||||
|
||||
declare module '@windmill-labs/svelte-grid' {
|
||||
import type { SvelteComponentTyped } from 'svelte'
|
||||
|
||||
export interface Size {
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
|
||||
export interface Positon {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
interface ItemLayout extends Size, Positon {
|
||||
fixed?: boolean
|
||||
resizable?: boolean
|
||||
draggable?: boolean
|
||||
customDragger?: boolean
|
||||
customResizer?: boolean
|
||||
min?: Size
|
||||
max?: Size
|
||||
}
|
||||
|
||||
export type Item<T> = T & { [width: number]: ItemLayout; data: any }
|
||||
export type FilledItem<T> = T & { [width: number]: Required<ItemLayout>; data: any }
|
||||
|
||||
export interface Props<T> {
|
||||
fillSpace?: boolean
|
||||
items: FilledItem<T>[]
|
||||
rowHeight: number
|
||||
cols: [number, number][]
|
||||
gap?: [number, number]
|
||||
fastStart?: boolean
|
||||
throttleUpdate?: number
|
||||
throttleResize?: number
|
||||
onTopId?: string
|
||||
scroller?: undefined
|
||||
sensor?: number
|
||||
parentWidth?: number
|
||||
}
|
||||
|
||||
export interface Slots<T> {
|
||||
default: { item: ItemLayout; dataItem: Item<T> }
|
||||
}
|
||||
|
||||
export default class Grid<T = {}> extends SvelteComponentTyped<
|
||||
Props<T>,
|
||||
{
|
||||
pointerup: CustomEvent<{ id: string }>
|
||||
mount: CustomEvent<>
|
||||
},
|
||||
Slots<T>
|
||||
> { }
|
||||
}
|
||||
|
||||
declare module '@windmill-labs/svelte-grid/build/helper/index.mjs' {
|
||||
import { ItemLayout } from '@windmill-labs/svelte-grid'
|
||||
|
||||
const x: {
|
||||
normalize(items: any[], col: any): unknown[]
|
||||
adjust(items: any[], col: any): unknown[]
|
||||
findSpace(item: any, items: any, cols: any): unknown
|
||||
|
||||
item<T>(obj: ItemLayout): Required<ItemLayout>
|
||||
}
|
||||
|
||||
export default x
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
$: key =
|
||||
apiTokenApps[resource_type]?.key ??
|
||||
(args != undefined
|
||||
? Object.keys(args).filter((x) => ['token', 'password', 'api_key', 'key'].includes(x))[0]
|
||||
? Object.keys(args).filter((x) => ['token', 'password', 'api_key'].includes(x))[0]
|
||||
: undefined)
|
||||
|
||||
let scopes: string[] = []
|
||||
@@ -259,7 +259,6 @@
|
||||
args['token'] == '' &&
|
||||
args['password'] == '' &&
|
||||
args['api_key'] == '' &&
|
||||
args['key'] == '' &&
|
||||
key != undefined) ||
|
||||
(step == 3 && pathError != '') ||
|
||||
!isValid
|
||||
@@ -397,7 +396,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
initialPath=""
|
||||
namePlaceholder={resource_type}
|
||||
namePlaceholder="{resource_type}"
|
||||
kind="resource"
|
||||
/>
|
||||
|
||||
@@ -434,7 +433,7 @@
|
||||
{:else}
|
||||
<Path
|
||||
initialPath=""
|
||||
namePlaceholder={resource_type}
|
||||
namePlaceholder="{resource_type}"
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
kind="resource"
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
import type { DropdownItem } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { MoreVertical } from 'lucide-svelte'
|
||||
import { MoreHorizontal } from 'lucide-svelte'
|
||||
import { Button, Menu } from './common'
|
||||
import { goto } from '$app/navigation'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
type Alignment = 'start' | 'end'
|
||||
type Side = 'top' | 'bottom'
|
||||
@@ -14,24 +13,15 @@
|
||||
export let dropdownItems: DropdownItem[]
|
||||
export let name: string | undefined = undefined
|
||||
export let placement: Placement = 'bottom-start'
|
||||
export let btnClasses = ''
|
||||
$: buttonClass = twMerge('!border-0 bg-transparent !p-[6px]', btnClasses)
|
||||
export let btnClasses = '!text-blue-500 bg-transparent'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<Menu {placement} let:close>
|
||||
<Button
|
||||
nonCaptureEvent
|
||||
color="dark"
|
||||
variant="border"
|
||||
size="xs"
|
||||
btnClasses={buttonClass}
|
||||
{...$$restProps}
|
||||
slot="trigger"
|
||||
>
|
||||
<Button nonCaptureEvent color="light" size="xs" {btnClasses} {...$$restProps} slot="trigger">
|
||||
{#if !$$slots.default}
|
||||
<MoreVertical size={20} />
|
||||
<MoreHorizontal size={20} />
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
|
||||
@@ -232,8 +232,14 @@
|
||||
<Badge color={validCode ? 'green' : 'red'} class="min-w-[60px] mr-3">
|
||||
{validCode ? 'Valid' : 'Invalid'}
|
||||
</Badge>
|
||||
<div class="flex items-center divide-x">
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="pr-1" disablePopup={!iconOnly}>
|
||||
<div id="script-tutorial-3" class="flex items-center divide-x">
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="pr-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
@@ -245,11 +251,15 @@
|
||||
>
|
||||
+Context Var
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add context variable
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add context variable</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
color="light"
|
||||
btnClasses="!font-medium !h-full"
|
||||
@@ -261,11 +271,15 @@
|
||||
>
|
||||
+Variable
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add variable
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add variable</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
@@ -277,11 +291,15 @@
|
||||
>
|
||||
+Resource
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Add resource
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Add resource</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
@@ -293,11 +311,15 @@
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reset
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Reset</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium !h-full"
|
||||
size="xs"
|
||||
@@ -320,11 +342,15 @@
|
||||
{/if}
|
||||
</span>
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Reload assistant
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Reload assistant</svelte:fragment>
|
||||
</Popover>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
@@ -342,7 +368,13 @@
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
|
||||
<Popover
|
||||
notClickable
|
||||
placement="bottom"
|
||||
disapperTimoout={0}
|
||||
class="px-1"
|
||||
disablePopup={!iconOnly}
|
||||
>
|
||||
<Button
|
||||
btnClasses="!font-medium"
|
||||
size="xs"
|
||||
@@ -354,9 +386,7 @@
|
||||
>
|
||||
Script
|
||||
</Button>
|
||||
<svelte:fragment slot="text">
|
||||
Script
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="text">Script</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
|
||||
import type { FlowEditorContext } from './flows/types'
|
||||
import { cleanInputs } from './flows/utils'
|
||||
import { Tour } from './tutorial'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let selectedId: string | undefined
|
||||
@@ -27,6 +28,7 @@
|
||||
export let loading = false
|
||||
export let flowStore: Writable<Flow>
|
||||
export let flowStateStore: Writable<FlowState>
|
||||
export let tour = false
|
||||
|
||||
async function createSchedule(path: string) {
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
@@ -246,6 +248,10 @@
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if tour}
|
||||
<Tour tutorial="flow" />
|
||||
{/if}
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<ScriptEditorDrawer bind:this={$scriptEditorDrawer} />
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
export let name: string
|
||||
export let tabular = false
|
||||
export let order = ['scripts', 'flows', 'apps', 'schedules', 'variables', 'resources']
|
||||
|
||||
$: $workspaceStore && loadUsage()
|
||||
|
||||
@@ -15,18 +13,10 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if tabular}
|
||||
{#each order as key}
|
||||
<td class="text-center">
|
||||
{usage[key] ?? ''}
|
||||
</td>
|
||||
<div class="flex flex-col text-xs text-gray-600">
|
||||
{#each Object.entries(usage) as [k, v]}
|
||||
<div>
|
||||
{k}: {v}
|
||||
</div>
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="flex flex-col text-xs text-gray-600">
|
||||
{#each Object.entries(usage) as [k, v]}
|
||||
<div>
|
||||
{k}: {v}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -234,14 +234,6 @@
|
||||
name,
|
||||
requestBody: { username: member_name }
|
||||
})
|
||||
await GranularAclService.removeGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name
|
||||
}
|
||||
})
|
||||
loadGroup()
|
||||
}}>remove</button
|
||||
>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<span class="flex items-center space-x-2 mb-2">
|
||||
<h1>{title}</h1>
|
||||
{#if tooltip}
|
||||
<Tooltip scale={0.9}>{tooltip}</Tooltip>
|
||||
<Tooltip>{tooltip}</Tooltip>
|
||||
{/if}
|
||||
</span>
|
||||
{:else}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import GroupEditor from './GroupEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -307,7 +306,7 @@
|
||||
{#if meta != undefined}
|
||||
<div class="flex gap-4 shrink">
|
||||
<label class="block">
|
||||
<span class="text-gray-700 text-sm whitespace-nowrap">Owner</span>
|
||||
<span class="text-gray-700 text-sm whitespace-nowrap"> </span>
|
||||
|
||||
<ToggleButtonGroup
|
||||
class="mt-0.5"
|
||||
@@ -410,26 +409,8 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full mt-4">
|
||||
<div class="flex justify-start w-full">
|
||||
<Badge
|
||||
color="gray"
|
||||
class="center-center !bg-gray-300 !text-gray-600 !w-[70px] !h-[24px] rounded-r-none"
|
||||
>
|
||||
Full path
|
||||
</Badge>
|
||||
<input
|
||||
type="text"
|
||||
readonly
|
||||
value={path}
|
||||
size={path?.length || 50}
|
||||
class="font-mono !text-xs max-w-[calc(100%-70px)] !w-auto !h-[24px] !py-0 !border-l-0 !rounded-l-none"
|
||||
on:focus={({ currentTarget }) => {
|
||||
currentTarget.select()
|
||||
}}
|
||||
/>
|
||||
<!-- <span class="font-mono text-sm break-all">{path}</span> -->
|
||||
</div>
|
||||
<div class="flex-row flex justify-between w-full">
|
||||
<div><span class="font-mono text-sm break-all">{path}</span></div>
|
||||
<div class="text-red-600 text-2xs">{error}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
}}
|
||||
items={collection}
|
||||
class="text-clip grow min-w-0"
|
||||
placeholder="{resourceType ?? 'any'} resource"
|
||||
placeholder="{resourceType} resource"
|
||||
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
|
||||
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles}
|
||||
/>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
label: 'Failed',
|
||||
data:
|
||||
failed?.map((job) => ({
|
||||
x: job.started_at as any,
|
||||
x: job.created_at as any,
|
||||
y: job.duration_ms,
|
||||
id: job.id,
|
||||
path: job.script_path
|
||||
@@ -57,7 +57,7 @@
|
||||
label: 'Successful',
|
||||
data:
|
||||
success?.map((job) => ({
|
||||
x: job.started_at as any,
|
||||
x: job.created_at as any,
|
||||
y: job.duration_ms,
|
||||
id: job.id,
|
||||
path: job.script_path
|
||||
@@ -71,10 +71,7 @@
|
||||
enabled: true,
|
||||
modifierKey: 'ctrl' as 'ctrl',
|
||||
onPanComplete: ({ chart }) => {
|
||||
dispatch('zoom', {
|
||||
min: addSeconds(new Date(chart.scales.x.min), -1),
|
||||
max: addSeconds(new Date(chart.scales.x.max), 1)
|
||||
})
|
||||
dispatch('zoom', { min: new Date(chart.scales.x.min), max: new Date(chart.scales.x.max) })
|
||||
}
|
||||
},
|
||||
zoom: {
|
||||
@@ -83,10 +80,7 @@
|
||||
},
|
||||
mode: 'x' as 'x',
|
||||
onZoom: ({ chart }) => {
|
||||
dispatch('zoom', {
|
||||
min: addSeconds(new Date(chart.scales.x.min), -1),
|
||||
max: addSeconds(new Date(chart.scales.x.max), 1)
|
||||
})
|
||||
dispatch('zoom', { min: new Date(chart.scales.x.min), max: new Date(chart.scales.x.max) })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,22 +88,6 @@
|
||||
function getPath(x: any): string {
|
||||
return x.path
|
||||
}
|
||||
|
||||
$: minTime = getMinTime(jobs)
|
||||
|
||||
function addSeconds(date: Date, seconds: number): Date {
|
||||
date.setTime(date.getTime() + seconds * 1000)
|
||||
return date
|
||||
}
|
||||
function getMinTime(jobs: CompletedJob[] | undefined): Date {
|
||||
return addSeconds(new Date(jobs?.[jobs?.length - 1]?.started_at ?? new Date().toString()), -15)
|
||||
}
|
||||
|
||||
$: maxTime = getMaxTime(jobs)
|
||||
|
||||
function getMaxTime(jobs: CompletedJob[] | undefined): Date {
|
||||
return addSeconds(new Date(jobs?.[0]?.started_at ?? new Date().toString()), 15)
|
||||
}
|
||||
</script>
|
||||
|
||||
<Scatter
|
||||
@@ -137,8 +115,7 @@
|
||||
display: false
|
||||
},
|
||||
type: 'time',
|
||||
min: minTime,
|
||||
max: maxTime
|
||||
min: jobs?.[jobs?.length - 1]?.created_at ?? new Date().toString()
|
||||
},
|
||||
y: {
|
||||
grid: {
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { Script, ScriptService } from '$lib/gen'
|
||||
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { initialCode, isInitialCode } from '$lib/script_helpers'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { emptySchema, encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
|
||||
import Path from './Path.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
import { dirtyStore } from './common/confirmationModal/dirtyStore'
|
||||
import { Button, ButtonPopup, ButtonPopupItem, Kbd } from './common'
|
||||
import { faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faChevronDown, faChevronUp, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import Breadcrumb from './common/breadcrumb/Breadcrumb.svelte'
|
||||
import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import SettingSection from './SettingSection.svelte'
|
||||
|
||||
export let script: Script
|
||||
export let initialPath: string = ''
|
||||
@@ -31,34 +31,14 @@
|
||||
['Go', Script.language.GO],
|
||||
['Bash', Script.language.BASH]
|
||||
]
|
||||
const scriptKindOptions: { value: Script.kind; title: string; desc?: string }[] = [
|
||||
{
|
||||
value: Script.kind.SCRIPT,
|
||||
title: 'Action'
|
||||
},
|
||||
{
|
||||
value: Script.kind.TRIGGER,
|
||||
title: 'Trigger',
|
||||
desc: 'First module of flows to trigger them based on external changes. These kind of scripts are usually running on a schedule to periodically look for changes.'
|
||||
},
|
||||
{
|
||||
value: Script.kind.APPROVAL,
|
||||
title: 'Approval',
|
||||
desc: 'Send notifications externally to ask for approval to continue a flow.'
|
||||
},
|
||||
{
|
||||
value: Script.kind.FAILURE,
|
||||
title: 'Error Handler',
|
||||
desc: 'Handle errors in flows after all retry attempts have been exhausted.'
|
||||
}
|
||||
]
|
||||
let viewScriptKind = script.kind !== Script.kind.SCRIPT
|
||||
|
||||
let pathError = ''
|
||||
|
||||
let summaryC: HTMLInputElement | undefined = undefined
|
||||
let pathC: Path | undefined = undefined
|
||||
let loadingSave = false
|
||||
|
||||
$: setQueryWithoutLoad($page.url, [{ key: 'state', value: encodeState(script) }])
|
||||
$: setQueryWithoutLoad($page.url, 'state', encodeState(script))
|
||||
$: step = Number($page.url.searchParams.get('step')) || 1
|
||||
|
||||
if (script.content == '') {
|
||||
@@ -73,6 +53,7 @@
|
||||
script.content = initialCode(language, kind, template)
|
||||
}
|
||||
|
||||
let loadingSave = false
|
||||
async function editScript(leave: boolean): Promise<void> {
|
||||
loadingSave = true
|
||||
try {
|
||||
@@ -269,8 +250,7 @@
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
>
|
||||
<LanguageIcon {lang} />
|
||||
<span class="ml-2 py-2">{label}</span>
|
||||
<LanguageIcon {lang} /><span class="ml-2 py-4">{label}</span>
|
||||
</Button>
|
||||
{/each}
|
||||
<Button
|
||||
@@ -285,7 +265,7 @@
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-2">PostgreSQL</span>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-4">PostgreSQL</span>
|
||||
</Button>
|
||||
<!-- <Button
|
||||
size="sm"
|
||||
@@ -298,41 +278,65 @@
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-2">MySQL</span>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-4">MySQL</span>
|
||||
</Button> -->
|
||||
</div>
|
||||
<SettingSection
|
||||
title="Script kind"
|
||||
element="h3"
|
||||
tooltip="Tag this script as having a specific purpose inside flows. If it won't be used in flows,
|
||||
you don't have to worry about this."
|
||||
accordion
|
||||
>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{#each scriptKindOptions as { value, title, desc }}
|
||||
{@const isPicked = script.kind === value}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses="font-medium {isPicked ? '!bg-blue-50/75' : ''}"
|
||||
on:click={() => {
|
||||
<div class="mt-16 mb-4">
|
||||
<Button
|
||||
color="light"
|
||||
size="sm"
|
||||
endIcon={{ icon: viewScriptKind ? faChevronUp : faChevronDown }}
|
||||
on:click={() => (viewScriptKind = !viewScriptKind)}
|
||||
>
|
||||
Tag this script as having a specific purpose inside flows
|
||||
</Button>
|
||||
</div>
|
||||
{#if viewScriptKind}
|
||||
<div class="max-w-lg">
|
||||
<RadioButton
|
||||
label="Script Type"
|
||||
options={[
|
||||
['Action', Script.kind.SCRIPT],
|
||||
[
|
||||
{
|
||||
title: 'Trigger',
|
||||
desc: `First module of flows to trigger them based on watching changes external periodically using an internal state`
|
||||
},
|
||||
Script.kind.TRIGGER
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'Approval',
|
||||
desc: `Send notification externally to ask for approval to continue a flow`
|
||||
},
|
||||
Script.kind.APPROVAL
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'Error Handler',
|
||||
desc: `Handle errors for flows after all retries attempts have been exhausted`
|
||||
},
|
||||
Script.kind.FAILURE
|
||||
]
|
||||
|
||||
// ['Command Handler', Script.kind.COMMAND]
|
||||
]}
|
||||
on:change={(e) => {
|
||||
if (isInitialCode(script.content)) {
|
||||
template = 'script'
|
||||
script.kind = value
|
||||
initContent(script.language, value, template)
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
>
|
||||
{title}
|
||||
{#if desc}
|
||||
<Tooltip class="mb-0.5 ml-1">
|
||||
{desc}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</Button>
|
||||
{/each}
|
||||
initContent(script.language, e.detail, template)
|
||||
}
|
||||
}}
|
||||
bind:value={script.kind}
|
||||
/>
|
||||
</div>
|
||||
</SettingSection>
|
||||
{/if}
|
||||
<!-- <div class="ml-3">
|
||||
<Toggle
|
||||
bind:checked={script.is_template}
|
||||
options={{ right: 'Save as a workspace template' }}
|
||||
/>
|
||||
</div> -->
|
||||
</CenteredPage>
|
||||
{:else if step === 2}
|
||||
<ScriptEditor
|
||||
@@ -342,6 +346,7 @@
|
||||
lang={script.language}
|
||||
{initialArgs}
|
||||
{kind}
|
||||
tour
|
||||
/>
|
||||
{:else if step === 3}
|
||||
<CenteredPage>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import { Button, Kbd } from './common'
|
||||
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
|
||||
import WindmillIcon from './icons/WindmillIcon.svelte'
|
||||
import { Tour } from './tutorial'
|
||||
|
||||
// Exported
|
||||
export let schema: Schema = emptySchema()
|
||||
@@ -27,6 +28,7 @@
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let fixedOverflowWidgets = true
|
||||
export let noSyncFromGithub = false
|
||||
export let tour = false
|
||||
|
||||
let websocketAlive = { pyright: false, black: false, deno: false, go: false }
|
||||
|
||||
@@ -114,6 +116,10 @@
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
|
||||
{#if tour}
|
||||
<Tour tutorial="script" />
|
||||
{/if}
|
||||
|
||||
<div class="border-b-2 shadow-sm px-1 pr-4" bind:clientWidth={width}>
|
||||
<div class="flex justify-between space-x-2">
|
||||
<EditorBar
|
||||
@@ -146,6 +152,7 @@
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane size={60} minSize={10} class="!overflow-visible">
|
||||
<div
|
||||
id="script-tutorial-1"
|
||||
class="pl-2 h-full !overflow-visible"
|
||||
on:mouseleave={() => {
|
||||
inferSchema(code)
|
||||
@@ -180,7 +187,7 @@
|
||||
</Pane>
|
||||
<Pane size={40} minSize={10}>
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="px-2 w-full border-b py-1">
|
||||
<div id="script-tutorial-4" class="px-2 w-full border-b py-1">
|
||||
{#if testIsLoading}
|
||||
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full" color="red" size="xs">
|
||||
<WindmillIcon
|
||||
@@ -211,7 +218,7 @@
|
||||
</div>
|
||||
<Splitpanes horizontal class="!max-h-[calc(100%-43px)]">
|
||||
<Pane size={33}>
|
||||
<div class="px-2">
|
||||
<div id="script-tutorial-2" class="px-2">
|
||||
<div class="break-words relative font-sans">
|
||||
<SchemaForm compact {schema} bind:args bind:isValid />
|
||||
</div>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ChevronDown } from 'lucide-svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import type { IntRange } from '../common'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let title: string
|
||||
export let tooltip: string
|
||||
export let element: `h${IntRange<1, 6>}` = 'h2'
|
||||
export let accordion = false
|
||||
let showContent = !accordion
|
||||
</script>
|
||||
|
||||
<div class="border-b [&:has(button:hover)]:border-gray-400 duration-200 pb-1 mt-8 mb-2">
|
||||
{#if accordion}
|
||||
<button
|
||||
class="flex w-full justify-between items-center"
|
||||
on:click={() => (showContent = !showContent)}
|
||||
>
|
||||
<svelte:element this={element}>
|
||||
{title}
|
||||
<Tooltip scale={0.9} class="mb-0.5">
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
</svelte:element>
|
||||
<span class="rounded-full hover:bg-gray-100 focus:bg-gray-100 p-1">
|
||||
<ChevronDown size={22} class="rotate-0 duration-300 {showContent ? '!rotate-180' : ''}" />
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<svelte:element this={element}>
|
||||
{title}
|
||||
<Tooltip scale={0.9} class="mb-0.5">
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
</svelte:element>
|
||||
{/if}
|
||||
</div>
|
||||
{#if showContent}
|
||||
<div transition:slide={{ duration: 300 }}>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
@@ -2,18 +2,13 @@
|
||||
import { ChevronDown, ChevronUp } from 'lucide-svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let text: string
|
||||
export let tooltip: string | undefined = undefined
|
||||
export let view = false
|
||||
</script>
|
||||
|
||||
<Button color="light" on:click={() => (view = !view)} variant="border"
|
||||
>{text}
|
||||
{#if tooltip}
|
||||
<Tooltip wrapperClass="mx-1">{tooltip}</Tooltip>
|
||||
{/if}
|
||||
{#if !view}<ChevronDown />{:else}<ChevronUp />{/if}</Button
|
||||
>
|
||||
{#if view}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<span class="{$$props.class} z-auto">
|
||||
<span class="{$$props.class ?? ''} z-auto">
|
||||
<label
|
||||
for={id}
|
||||
class="inline-flex items-center mt-2 duration-200 {disabled
|
||||
|
||||
@@ -18,7 +18,5 @@
|
||||
data={faInfoCircle}
|
||||
{scale}
|
||||
/>
|
||||
<svelte:fragment slot="text">
|
||||
<slot />
|
||||
</svelte:fragment>
|
||||
<span slot="text"> <slot /> </span>
|
||||
</Popover>
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
...new Set(
|
||||
workers
|
||||
.filter((worker) => {
|
||||
return worker.ip != 'unretrievable IP' && worker.last_ping && worker.last_ping < 300
|
||||
const date = new Date().getTime() - 300 * 60
|
||||
const ping_at = new Date(worker.ping_at).getTime()
|
||||
return worker.ip != 'unretrievable IP' && ping_at > date
|
||||
})
|
||||
.map((worker) => worker.ip)
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { runnableComponents, worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let labelValue: string
|
||||
let color: ButtonType.Color
|
||||
@@ -109,6 +109,10 @@
|
||||
} else {
|
||||
await runnableComponent?.runComponent()
|
||||
}
|
||||
|
||||
if (recomputeIds) {
|
||||
await Promise.all(recomputeIds.map((id) => $runnableComponents?.[id]?.()))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -123,7 +127,6 @@
|
||||
<InputValue
|
||||
{id}
|
||||
input={configuration.disabled}
|
||||
extraContext={extraQueryParams}
|
||||
bind:value={disabled}
|
||||
bind:error={errors.disabled}
|
||||
/>
|
||||
@@ -132,7 +135,6 @@
|
||||
|
||||
<RunnableWrapper
|
||||
flexWrap
|
||||
{recomputeIds}
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
{id}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { app, worldStore, stateId } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, runnableComponents, worldStore, stateId } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let color: ButtonType.Color
|
||||
@@ -61,7 +62,6 @@
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
|
||||
<RunnableWrapper
|
||||
{recomputeIds}
|
||||
{render}
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
@@ -103,6 +103,12 @@
|
||||
}}
|
||||
on:click={() => {
|
||||
runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
$runnableComponents[id]?.()
|
||||
})
|
||||
}
|
||||
}}
|
||||
{size}
|
||||
{color}
|
||||
|
||||
@@ -94,7 +94,6 @@
|
||||
}}
|
||||
>
|
||||
<RunnableWrapper
|
||||
{recomputeIds}
|
||||
{render}
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
@@ -129,6 +128,12 @@
|
||||
on:click={async () => {
|
||||
await runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
$runnableComponents[id]?.()
|
||||
})
|
||||
}
|
||||
|
||||
open = false
|
||||
}}
|
||||
size="xs"
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
export let name: string
|
||||
export let inlineScript: InlineScript | undefined
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
export let autoRefresh: boolean = false
|
||||
|
||||
let result: any = undefined
|
||||
|
||||
@@ -23,7 +22,6 @@
|
||||
render={false}
|
||||
{id}
|
||||
{fields}
|
||||
{autoRefresh}
|
||||
bind:result
|
||||
runnable={{
|
||||
name,
|
||||
@@ -31,7 +29,6 @@
|
||||
type: 'runnableByName'
|
||||
}}
|
||||
wrapperClass="hidden"
|
||||
recomputable
|
||||
>
|
||||
<slot />
|
||||
</RunnableComponent>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
|
||||
type T = string | number | boolean | Record<string | number, any> | undefined
|
||||
|
||||
@@ -13,7 +12,6 @@
|
||||
export let value: T
|
||||
export let id: string | undefined = undefined
|
||||
export let error: string = ''
|
||||
export let extraContext: Record<string, any> = {}
|
||||
|
||||
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
|
||||
|
||||
@@ -25,13 +23,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
const { worldStore, state, mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: stateId = $worldStore?.state
|
||||
$: state = $worldStore?.state
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
const debounce_ms = 50
|
||||
function debounce(cb: () => Promise<void>) {
|
||||
function debounce(cb: () => void) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
@@ -41,22 +39,20 @@
|
||||
$: lastInput && $worldStore && debounce(handleConnection)
|
||||
$: lastInput &&
|
||||
lastInput.type == 'template' &&
|
||||
$stateId &&
|
||||
$state &&
|
||||
debounce(async () => (value = await getValue(lastInput)))
|
||||
debounce(() => (value = getValue(lastInput)))
|
||||
$: lastInput &&
|
||||
lastInput.type == 'eval' &&
|
||||
$stateId &&
|
||||
state &&
|
||||
debounce(async () => (value = await evalExpr(lastInput)))
|
||||
$state &&
|
||||
debounce(() => (value = evalExpr(lastInput)))
|
||||
|
||||
async function handleConnection() {
|
||||
function handleConnection() {
|
||||
if (lastInput.type === 'connected') {
|
||||
$worldStore?.connect<any>(lastInput, onValueChange)
|
||||
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
|
||||
value = await getValue(lastInput)
|
||||
value = getValue(lastInput)
|
||||
} else if (lastInput.type == 'eval') {
|
||||
value = await evalExpr(lastInput as EvalAppInput)
|
||||
value = evalExpr(lastInput as EvalAppInput)
|
||||
} else if (lastInput.type == 'upload') {
|
||||
value = (lastInput as UploadAppInput).value
|
||||
} else {
|
||||
@@ -64,15 +60,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function evalExpr(input: EvalAppInput) {
|
||||
function evalExpr(input: EvalAppInput) {
|
||||
try {
|
||||
const r = await eval_like(
|
||||
input.expr,
|
||||
computeGlobalContext($worldStore, id, extraContext),
|
||||
true,
|
||||
$state,
|
||||
$mode == 'dnd'
|
||||
)
|
||||
const r = eval_like(input.expr, computeGlobalContext())
|
||||
error = ''
|
||||
return r
|
||||
} catch (e) {
|
||||
@@ -81,16 +71,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getValue(input: AppInput) {
|
||||
function computeGlobalContext() {
|
||||
return Object.fromEntries(
|
||||
Object.entries($worldStore?.outputsById ?? {})
|
||||
.filter(([k, _]) => k != id)
|
||||
.map(([key, value]) => {
|
||||
return [
|
||||
key,
|
||||
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
|
||||
]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export function getValue(input: AppInput) {
|
||||
if (input.type === 'template' && isCodeInjection(input.eval)) {
|
||||
try {
|
||||
const r = await eval_like(
|
||||
'`' + input.eval + '`',
|
||||
computeGlobalContext($worldStore, id, extraContext),
|
||||
true,
|
||||
$state,
|
||||
$mode == 'dnd'
|
||||
)
|
||||
const r = eval_like('`' + input.eval + '`', computeGlobalContext())
|
||||
error = ''
|
||||
return r
|
||||
} catch (e) {
|
||||
@@ -103,6 +100,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
function create_context_function_template(eval_string, context) {
|
||||
return `
|
||||
return function (context) {
|
||||
"use strict";
|
||||
${
|
||||
Object.keys(context).length > 0
|
||||
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
|
||||
: ``
|
||||
}
|
||||
return ${eval_string};
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
function make_context_evaluator(eval_string, context) {
|
||||
let template = create_context_function_template(eval_string, context)
|
||||
let functor = Function(template)
|
||||
return functor()
|
||||
}
|
||||
|
||||
function eval_like(text, context = {}) {
|
||||
let evaluator = make_context_evaluator(text, context)
|
||||
return evaluator(context)
|
||||
}
|
||||
|
||||
function onValueChange(newValue: any): void {
|
||||
if (lastInput.type === 'connected' && newValue !== undefined && newValue !== null) {
|
||||
const { connection } = lastInput
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { RefreshCw } from 'lucide-svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faRefresh } from '@fortawesome/free-solid-svg-icons'
|
||||
import { RefreshCcw, RefreshCw } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import { AppService, type CompletedJob } from '$lib/gen'
|
||||
import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { Bug } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { Bug, Loader2 } from 'lucide-svelte'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { AppInputs, Runnable } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
import InputValue from './InputValue.svelte'
|
||||
import RefreshButton from './RefreshButton.svelte'
|
||||
|
||||
@@ -31,8 +31,6 @@
|
||||
export let gotoUrl: string | undefined = undefined
|
||||
export let gotoNewTab: boolean | undefined = undefined
|
||||
export let render: boolean
|
||||
export let recomputable: boolean = false
|
||||
export let recomputeIds: string[] = []
|
||||
|
||||
const {
|
||||
worldStore,
|
||||
@@ -44,27 +42,17 @@
|
||||
noBackend,
|
||||
errorByComponent,
|
||||
mode,
|
||||
stateId,
|
||||
state
|
||||
stateId
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: autoRefresh && handleAutorefresh()
|
||||
|
||||
if (recomputable) {
|
||||
$runnableComponents[id] = async () => {
|
||||
await executeComponent(true)
|
||||
}
|
||||
$runnableComponents = $runnableComponents
|
||||
}
|
||||
|
||||
function handleAutorefresh() {
|
||||
if (autoRefresh && $worldStore) {
|
||||
onMount(() => {
|
||||
if (autoRefresh) {
|
||||
$runnableComponents[id] = async () => {
|
||||
await executeComponent(true)
|
||||
}
|
||||
executeComponent(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let args: Record<string, any> | undefined = undefined
|
||||
let testIsLoading = false
|
||||
@@ -92,23 +80,30 @@
|
||||
$: fields && (currentStaticValues = computeStaticValues())
|
||||
$: if (JSON.stringify(currentStaticValues) != JSON.stringify(lazyStaticValues)) {
|
||||
lazyStaticValues = currentStaticValues
|
||||
refreshIfAutoRefresh()
|
||||
}
|
||||
|
||||
function refreshIfAutoRefresh() {
|
||||
if (autoRefresh) {
|
||||
setDebouncedExecute()
|
||||
}
|
||||
}
|
||||
|
||||
$: fields && (lazyStaticValues = computeStaticValues())
|
||||
$: (runnableInputValues || extraQueryParams || args) && testJobLoader && refreshIfAutoRefresh()
|
||||
$: (runnableInputValues || extraQueryParams || args) &&
|
||||
autoRefresh &&
|
||||
testJobLoader &&
|
||||
setDebouncedExecute()
|
||||
|
||||
// Test job internal state
|
||||
let testJob: CompletedJob | undefined = undefined
|
||||
let testJobLoader: TestJobLoader | undefined = undefined
|
||||
|
||||
$: outputs = initOutput($worldStore, id, { result: undefined, loading: false })
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<Array<any>>
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
$: outputs?.loading?.set(testIsLoading)
|
||||
$: schemaStripped = stripSchema(fields, $stateId)
|
||||
@@ -149,24 +144,6 @@
|
||||
)
|
||||
|
||||
async function executeComponent(noToast = false) {
|
||||
if (runnable?.type === 'runnableByName' && runnable.inlineScript?.language === 'frontend') {
|
||||
outputs?.loading?.set(true)
|
||||
try {
|
||||
const r = await eval_like(
|
||||
runnable.inlineScript?.content,
|
||||
computeGlobalContext($worldStore, id, {}),
|
||||
false,
|
||||
$state,
|
||||
$mode == 'dnd'
|
||||
)
|
||||
setResult(r)
|
||||
$state = $state
|
||||
} catch (e) {
|
||||
sendUserToast('Error running frontend script: ' + e.message, true)
|
||||
}
|
||||
outputs?.loading?.set(false)
|
||||
return
|
||||
}
|
||||
if (noBackend) {
|
||||
if (!noToast) {
|
||||
sendUserToast('This app is not connected to a windmill backend, it is a static preview')
|
||||
@@ -239,31 +216,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setResult(res: any) {
|
||||
outputs.result?.set(res)
|
||||
|
||||
result = res
|
||||
|
||||
const previousJobId = Object.keys($errorByComponent).find(
|
||||
(key) => $errorByComponent[key].componentId === id
|
||||
)
|
||||
|
||||
if (previousJobId && !result?.error) {
|
||||
delete $errorByComponent[previousJobId]
|
||||
$errorByComponent = $errorByComponent
|
||||
}
|
||||
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
|
||||
if (gotoNewTab) {
|
||||
window.open(gotoUrl, '_blank')
|
||||
} else {
|
||||
goto(gotoUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.map((id) => $runnableComponents?.[id]?.())
|
||||
}
|
||||
}
|
||||
$: result?.error && recordError(result.error)
|
||||
</script>
|
||||
|
||||
@@ -280,7 +232,24 @@
|
||||
const startedAt = new Date(testJob.started_at).getTime()
|
||||
if (startedAt > lastStartedAt) {
|
||||
lastStartedAt = startedAt
|
||||
setResult(e.detail.result)
|
||||
outputs.result?.set(testJob?.result)
|
||||
result = testJob.result
|
||||
|
||||
const previousJobId = Object.keys($errorByComponent).find(
|
||||
(key) => $errorByComponent[key].componentId === id
|
||||
)
|
||||
|
||||
if (previousJobId && !result?.error) {
|
||||
delete $errorByComponent[previousJobId]
|
||||
$errorByComponent = $errorByComponent
|
||||
}
|
||||
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
|
||||
if (gotoNewTab) {
|
||||
window.open(gotoUrl, '_blank')
|
||||
} else {
|
||||
goto(gotoUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -21,7 +21,6 @@
|
||||
export let goto: string | undefined = undefined
|
||||
export let gotoNewTab: boolean | undefined = undefined
|
||||
export let render: boolean
|
||||
export let recomputeIds: string[] = []
|
||||
|
||||
const { staticExporter, noBackend } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -45,7 +44,6 @@
|
||||
<slot />
|
||||
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
|
||||
<RunnableComponent
|
||||
{recomputeIds}
|
||||
gotoUrl={goto}
|
||||
{gotoNewTab}
|
||||
{flexWrap}
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { goto } from '$app/navigation'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import type { World } from '../../rx'
|
||||
|
||||
export function computeGlobalContext(
|
||||
world: World | undefined,
|
||||
id: string | undefined,
|
||||
extraContext: any = {}
|
||||
) {
|
||||
return {
|
||||
...Object.fromEntries(
|
||||
Object.entries(world?.outputsById ?? {})
|
||||
.filter(([k, _]) => k != id)
|
||||
.map(([key, value]) => {
|
||||
return [
|
||||
key,
|
||||
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
|
||||
]
|
||||
})
|
||||
),
|
||||
...extraContext
|
||||
}
|
||||
}
|
||||
|
||||
function create_context_function_template(eval_string, context, noReturn: boolean) {
|
||||
return `
|
||||
return async function (context, state, goto) {
|
||||
"use strict";
|
||||
${
|
||||
Object.keys(context).length > 0
|
||||
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
|
||||
: ``
|
||||
}
|
||||
${noReturn ? `return ${eval_string}` : eval_string}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
function make_context_evaluator(
|
||||
eval_string,
|
||||
context,
|
||||
noReturn: boolean
|
||||
): (context, state, goto) => Promise<any> {
|
||||
let template = create_context_function_template(eval_string, context, noReturn)
|
||||
let functor = Function(template)
|
||||
return functor()
|
||||
}
|
||||
|
||||
export async function eval_like(
|
||||
text,
|
||||
context = {},
|
||||
noReturn: boolean,
|
||||
state: any,
|
||||
editor: boolean
|
||||
) {
|
||||
let evaluator = make_context_evaluator(text, context, noReturn)
|
||||
return await evaluator(context, state, async (x, newTab) => {
|
||||
if (newTab || editor) {
|
||||
if (!newTab) {
|
||||
sendUserToast(
|
||||
'In editor mode, `goto` opens a new tab to prevent losing your work. To test the redirection , use the preview mode.'
|
||||
)
|
||||
}
|
||||
window.open(x, '_blank')
|
||||
} else {
|
||||
await goto(x)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -43,8 +43,6 @@
|
||||
: []
|
||||
if (defaultValue) {
|
||||
value = JSON.stringify(defaultValue)
|
||||
} else if (listItems.length > 0) {
|
||||
value = listItems[0]?.value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Tab, Tabs } from '$lib/components/common'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
@@ -28,12 +27,6 @@
|
||||
let noPadding: boolean | undefined = undefined
|
||||
let tabHeight: number = 0
|
||||
|
||||
$: outputs = $worldStore
|
||||
? initOutput($worldStore, id, {
|
||||
selectedTabIndex: 0
|
||||
})
|
||||
: undefined
|
||||
|
||||
function handleTabSelection() {
|
||||
const selectedIndex = tabs?.indexOf(selected)
|
||||
outputs?.selectedTabIndex.set(selectedIndex, true)
|
||||
@@ -83,6 +76,10 @@
|
||||
|
||||
$: selected && handleTabSelection()
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedTabIndex: Output<number>
|
||||
}
|
||||
|
||||
$: selectedIndex = tabs?.indexOf(selected) ?? -1
|
||||
|
||||
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
|
||||
@@ -91,7 +88,7 @@
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
|
||||
<div>
|
||||
<div bind:clientHeight={tabHeight}>
|
||||
<div bind:clientHeight={tabHeight} on:pointerdown|stopPropagation>
|
||||
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
|
||||
{#each tabs ?? [] as res}
|
||||
<Tab
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import { page } from '$app/stores'
|
||||
import CssSettings from './componentsPanel/CssSettings.svelte'
|
||||
import { initHistory } from '$lib/history'
|
||||
import { Tour } from '../../tutorial'
|
||||
import ComponentNavigation from './component/ComponentNavigation.svelte'
|
||||
import ItemPicker from '$lib/components/ItemPicker.svelte'
|
||||
import VariableEditor from '$lib/components/VariableEditor.svelte'
|
||||
@@ -47,6 +48,7 @@
|
||||
export let policy: Policy
|
||||
export let summary: string
|
||||
export let fromHub: boolean = false
|
||||
export let tour: boolean = false
|
||||
|
||||
const appStore = writable<App>(app)
|
||||
const worldStore = writable<World | undefined>(undefined)
|
||||
@@ -88,8 +90,7 @@
|
||||
openDebugRun: writable(undefined),
|
||||
focusedGrid,
|
||||
stateId: writable(0),
|
||||
parentWidth: writable(0),
|
||||
state: writable({})
|
||||
parentWidth: writable(0)
|
||||
})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
@@ -153,154 +154,155 @@
|
||||
|
||||
<svelte:window on:hashchange={hashchange} />
|
||||
|
||||
{#if tour}
|
||||
<Tour tutorial="app" />
|
||||
{/if}
|
||||
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
/>
|
||||
{/if}
|
||||
{#if !$userStore?.operator}
|
||||
{#if $appStore}
|
||||
{#if initialMode !== 'preview'}
|
||||
<AppEditorHeader {policy} {fromHub} />
|
||||
{/if}
|
||||
{#if initialMode !== 'preview'}
|
||||
<AppEditorHeader {policy} {fromHub} />
|
||||
{/if}
|
||||
|
||||
{#if previewing}
|
||||
<SplitPanesWrapper>
|
||||
<div
|
||||
class={twMerge('h-full w-full', $appStore.css?.['app']?.['viewer']?.class)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
workspace={$workspaceStore ?? ''}
|
||||
summary={$summaryStore}
|
||||
app={$appStore}
|
||||
appPath={path}
|
||||
{breakpoint}
|
||||
{policy}
|
||||
isEditor
|
||||
{context}
|
||||
noBackend={false}
|
||||
/>
|
||||
</div>
|
||||
</SplitPanesWrapper>
|
||||
{:else}
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="max-w-full overflow-hidden">
|
||||
<Pane size={$connectingInput?.opened ? 40 : 15} minSize={5} maxSize={33}>
|
||||
<ContextPanel />
|
||||
</Pane>
|
||||
<Pane size={64}>
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={$connectingInput?.opened ? 100 : 70}>
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
class={twMerge(
|
||||
'bg-gray-100 h-full w-full',
|
||||
$appStore.css?.['app']?.['viewer']?.class
|
||||
)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'relative mx-auto w-full h-full overflow-auto',
|
||||
app.fullscreen ? '' : 'max-w-6xl'
|
||||
)}
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<ComponentNavigation />
|
||||
|
||||
<div on:pointerdown|stopPropagation class={width}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={$connectingInput?.opened ? 0 : 30}>
|
||||
<div class="relative h-full w-full">
|
||||
<InlineScriptsPanel />
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
</Pane>
|
||||
<Pane size={21} minSize={5} maxSize={33}>
|
||||
<div class="relative flex flex-col h-full">
|
||||
<Tabs bind:selected={selectedTab} wrapperClass="!h-[40px]" class="!h-full">
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faPlus} />
|
||||
<span>Insert</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="settings" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faSliders} />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="css" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faCode} />
|
||||
<span>CSS</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<div slot="content" class="h-full overflow-y-auto pb-4">
|
||||
<TabContent class="overflow-auto h-full" value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
<SettingsPanel />
|
||||
{:else}
|
||||
<div class="min-w-[150px] text-sm text-gray-500 text-center py-8 px-2">
|
||||
Select a component to see the settings for it
|
||||
</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="insert">
|
||||
<ComponentList />
|
||||
</TabContent>
|
||||
<TabContent value="css">
|
||||
<CssSettings />
|
||||
</TabContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="fixed top-32 p-2 z-50 flex justify-center items-center"
|
||||
transition:fly|local={{ duration: 100, y: -100 }}
|
||||
>
|
||||
<Alert title="Connecting" type="info">
|
||||
<div class="flex gap-2 flex-col">
|
||||
Click on the output of the component you want to connect to on the left panel.
|
||||
<div>
|
||||
<Button
|
||||
color="blue"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
$connectingInput.opened = false
|
||||
$connectingInput.input = undefined
|
||||
}}
|
||||
>
|
||||
Stop connecting
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
{/if}
|
||||
{#if previewing}
|
||||
<SplitPanesWrapper>
|
||||
<div
|
||||
class={twMerge('h-full w-full', $appStore.css?.['app']?.['viewer']?.class)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<AppPreview
|
||||
workspace={$workspaceStore ?? ''}
|
||||
summary={$summaryStore}
|
||||
app={$appStore}
|
||||
appPath={path}
|
||||
{breakpoint}
|
||||
{policy}
|
||||
isEditor
|
||||
{context}
|
||||
noBackend={false}
|
||||
/>
|
||||
</div>
|
||||
</SplitPanesWrapper>
|
||||
{:else}
|
||||
App is null
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="max-w-full overflow-hidden">
|
||||
<Pane size={$connectingInput?.opened ? 40 : 15} minSize={5} maxSize={33}>
|
||||
<ContextPanel />
|
||||
</Pane>
|
||||
<Pane size={64}>
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes horizontal>
|
||||
<Pane size={$connectingInput?.opened ? 100 : 70}>
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
id="app-tutorial-3"
|
||||
class={twMerge(
|
||||
'bg-gray-100 h-full w-full',
|
||||
$appStore.css?.['app']?.['viewer']?.class
|
||||
)}
|
||||
style={$appStore.css?.['app']?.['viewer']?.style}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'relative mx-auto w-full h-full overflow-auto',
|
||||
app.fullscreen ? '' : 'max-w-6xl'
|
||||
)}
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<ComponentNavigation />
|
||||
|
||||
<div on:pointerdown|stopPropagation class={width}>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={$connectingInput?.opened ? 0 : 30}>
|
||||
<div id="app-tutorial-4" class="relative h-full w-full">
|
||||
<InlineScriptsPanel />
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
</Pane>
|
||||
<Pane size={21} minSize={5} maxSize={33}>
|
||||
<div class="relative flex flex-col h-full">
|
||||
<Tabs bind:selected={selectedTab} wrapperClass="!h-[40px]" class="!h-full">
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faPlus} />
|
||||
<span>Insert</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="settings" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faSliders} />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="css" size="xs">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faCode} />
|
||||
<span>CSS</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<div slot="content" class="h-full overflow-y-auto pb-4">
|
||||
<TabContent class="overflow-auto" value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
<SettingsPanel />
|
||||
{:else}
|
||||
<div class="min-w-[150px] text-sm text-gray-500 text-center py-8 px-2">
|
||||
Select a component to see the settings for it
|
||||
</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="insert">
|
||||
<ComponentList />
|
||||
</TabContent>
|
||||
<TabContent value="css">
|
||||
<CssSettings />
|
||||
</TabContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="fixed top-32 p-2 z-50 flex justify-center items-center"
|
||||
transition:fly|local={{ duration: 100, y: -100 }}
|
||||
>
|
||||
<Alert title="Connecting" type="info">
|
||||
<div class="flex gap-2 flex-col">
|
||||
Click on the output of the component you want to connect to on the left panel.
|
||||
<div>
|
||||
<Button
|
||||
color="blue"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
$connectingInput.opened = false
|
||||
$connectingInput.input = undefined
|
||||
}}
|
||||
>
|
||||
Stop connecting
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</SplitPanesWrapper>
|
||||
{/if}
|
||||
{:else}
|
||||
App editor not available to operators
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
import FlowProgressBar from '$lib/components/flows/FlowProgressBar.svelte'
|
||||
import { idMutex } from '$lib/components/flows/flowStateUtils'
|
||||
import FlowStatusViewer from '$lib/components/FlowStatusViewer.svelte'
|
||||
import JobArgs from '$lib/components/JobArgs.svelte'
|
||||
import LogViewer from '$lib/components/LogViewer.svelte'
|
||||
@@ -221,23 +220,18 @@
|
||||
return
|
||||
}
|
||||
loading.save = true
|
||||
try {
|
||||
await computeTriggerables()
|
||||
await AppService.updateApp({
|
||||
workspace: $workspaceStore!,
|
||||
path: $page.params.path,
|
||||
requestBody: {
|
||||
value: $app!,
|
||||
summary: $summary,
|
||||
policy
|
||||
}
|
||||
})
|
||||
sendUserToast('App saved')
|
||||
loading.save = false
|
||||
} catch (e) {
|
||||
loading.save = false
|
||||
throw e
|
||||
}
|
||||
await computeTriggerables()
|
||||
await AppService.updateApp({
|
||||
workspace: $workspaceStore!,
|
||||
path: $page.params.path,
|
||||
requestBody: {
|
||||
value: $app!,
|
||||
summary: $summary,
|
||||
policy
|
||||
}
|
||||
})
|
||||
loading.save = false
|
||||
sendUserToast('App saved')
|
||||
}
|
||||
|
||||
let selectedJobId: string | undefined = undefined
|
||||
@@ -248,10 +242,7 @@
|
||||
$: selectedJobId && testJobLoader?.watchJob(selectedJobId)
|
||||
$: hasErrors = Object.keys($errorByComponent).length > 0
|
||||
|
||||
let lock = false
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (lock) return
|
||||
lock = true
|
||||
switch (event.key) {
|
||||
case 'Z':
|
||||
if (event.ctrlKey) {
|
||||
@@ -262,7 +253,6 @@
|
||||
case 'z':
|
||||
if (event.ctrlKey) {
|
||||
$app = undo(history, $app)
|
||||
|
||||
event.preventDefault()
|
||||
}
|
||||
break
|
||||
@@ -291,7 +281,6 @@
|
||||
// break
|
||||
// }
|
||||
}
|
||||
lock = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -482,8 +471,8 @@
|
||||
<input type="text" placeholder="App summary" class="text-sm w-full" bind:value={$summary} />
|
||||
</div>
|
||||
<UndoRedo
|
||||
undoProps={{ disabled: $history?.index === 0 }}
|
||||
redoProps={{ disabled: $history && $history?.index === $history.history.length - 1 }}
|
||||
undoProps={{ disabled: $history.index === 0 }}
|
||||
redoProps={{ disabled: $history.index === $history.history.length - 1 }}
|
||||
on:undo={() => {
|
||||
$app = undo(history, $app)
|
||||
}}
|
||||
@@ -497,12 +486,12 @@
|
||||
<ToggleButton position="left" value="dnd" size="xs">
|
||||
<div class="inline-flex gap-1 items-center">
|
||||
<Pencil size={14} />
|
||||
<span class="hidden lg:inline">Editor</span>
|
||||
<span class="hidden md:inline">Editor</span>
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="preview" size="xs">
|
||||
<div class="inline-flex gap-1 items-center">
|
||||
<Eye size={14} /> <span class="hidden lg:inline">Preview</span>
|
||||
<Eye size={14} /> <span class="hidden md:inline">Preview</span>
|
||||
</div>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
@@ -519,46 +508,42 @@
|
||||
</div>
|
||||
|
||||
<div class="hidden lg:block">
|
||||
{#if $app}
|
||||
<ToggleButtonGroup bind:selected={$app.fullscreen}>
|
||||
<ToggleButton position="left" value={false} size="xs">
|
||||
<div class="flex gap-1 justify-start items-center">
|
||||
<AlignHorizontalSpaceAround size={14} />
|
||||
<Tooltip light class="mb-0.5">
|
||||
The max width is 1168px and the content stay centered instead of taking the full
|
||||
page width
|
||||
</Tooltip>
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value={true} size="xs">
|
||||
<Expand size={14} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
<ToggleButtonGroup bind:selected={$app.fullscreen}>
|
||||
<ToggleButton position="left" value={false} size="xs">
|
||||
<div class="flex gap-1 justify-start items-center">
|
||||
<AlignHorizontalSpaceAround size={14} />
|
||||
<Tooltip light class="mb-0.5">
|
||||
The max width is 1168px and the content stay centered instead of taking the full page
|
||||
width
|
||||
</Tooltip>
|
||||
</div>
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value={true} size="xs">
|
||||
<Expand size={14} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
{#if $focusedGrid !== undefined}
|
||||
<div class="hidden lg:block">
|
||||
<Badge color="indigo">
|
||||
<div class="flex flex-row gap-2 justify-center items-center">
|
||||
<div>{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}</div>
|
||||
<button
|
||||
on:click={() => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</Badge>
|
||||
</div>
|
||||
<Badge color="indigo">
|
||||
<div class="flex flex-row gap-2 justify-center items-center">
|
||||
<div>{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}</div>
|
||||
<button
|
||||
on:click={() => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</Badge>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-row gap-2 justify-end items-center overflow-visible">
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
btnClasses="hidden lg:block"
|
||||
btnClasses="!text-gray-700 !bg-transparent hover:!bg-gray-400/20 !p-[6px] hidden lg:block"
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'JSON',
|
||||
@@ -583,7 +568,9 @@
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
>
|
||||
<MoreVertical size={20} />
|
||||
</Dropdown>
|
||||
<span class="hidden md:inline">
|
||||
<Button on:click={() => (inputsDrawerOpen = true)} color="light" size="xs" variant="border">
|
||||
<span class="flex gap-2">
|
||||
@@ -600,7 +587,7 @@
|
||||
variant="border"
|
||||
startIcon={{ icon: faBug }}
|
||||
>
|
||||
<span class="hidden xl:inline">Debug Runs</span>
|
||||
<span class="hidden md:inline">Debug Runs</span>
|
||||
</Button>
|
||||
</span>
|
||||
<AppExportButton bind:this={appExport} />
|
||||
@@ -611,7 +598,7 @@
|
||||
variant="border"
|
||||
startIcon={{ icon: faExternalLink }}
|
||||
>
|
||||
<span class="hidden xl:inline">Publish</span>
|
||||
<span class="hidden md:inline">Publish</span>
|
||||
</Button>
|
||||
{#if appPath == ''}
|
||||
<Button
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import { buildWorld, type World } from '../rx'
|
||||
import type {
|
||||
App,
|
||||
AppEditorContext,
|
||||
AppViewerContext,
|
||||
ConnectingInput,
|
||||
EditorBreakpoint,
|
||||
@@ -62,14 +61,7 @@
|
||||
openDebugRun: writable(undefined),
|
||||
focusedGrid: writable(undefined),
|
||||
stateId: writable(0),
|
||||
parentWidth: writable(0),
|
||||
state: writable({})
|
||||
})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
history: undefined,
|
||||
componentControl: writable({}),
|
||||
pickVariableCallback: writable(undefined)
|
||||
parentWidth: writable(0)
|
||||
})
|
||||
|
||||
let mounted = false
|
||||
|
||||
@@ -41,14 +41,13 @@
|
||||
|
||||
{#if pointerdown || selected || hover}
|
||||
<button
|
||||
title="Lock Position"
|
||||
title="Position locking"
|
||||
class={classNames(
|
||||
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[2.5rem] z-50 cursor-pointer',
|
||||
' hover:bg-gray-300',
|
||||
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
|
||||
)}
|
||||
on:click={() => dispatch('lock')}
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
{#if locked}
|
||||
<Anchor aria-label="Unlock position" size={14} class="text-orange-500" />
|
||||
@@ -57,16 +56,15 @@
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
title="Expand"
|
||||
title="Position locking"
|
||||
class={classNames(
|
||||
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[4.5rem] z-50 cursor-pointer',
|
||||
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[0.5rem] z-50 cursor-pointer',
|
||||
' hover:bg-gray-300',
|
||||
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
|
||||
)}
|
||||
on:click={() => dispatch('expand')}
|
||||
on:pointerdown|stopPropagation
|
||||
>
|
||||
<Expand aria-label="Expand position" size={14} />
|
||||
<Expand aria-label="Lock position" size={14} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -75,7 +73,7 @@
|
||||
title="Move"
|
||||
on:mousedown|stopPropagation|capture
|
||||
class={classNames(
|
||||
'text-gray-600 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute border border-gray-300 -top-1 shadow right-[0.5rem] z-50 cursor-move',
|
||||
'text-gray-600 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute border border-gray-300 -top-1 shadow right-[4.5rem] z-50 cursor-move',
|
||||
'bg-gray-200/80'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getContext, afterUpdate } from 'svelte'
|
||||
import type { App, AppEditorContext, AppViewerContext, GridItem } from '../types'
|
||||
import Grid from '@windmill-labs/svelte-grid'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -12,7 +13,6 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { push } from '$lib/history'
|
||||
import { expandGriditem, findGridItem } from './appUtils'
|
||||
import Grid from '../svelte-grid/Grid.svelte'
|
||||
|
||||
export let policy: Policy
|
||||
|
||||
@@ -108,17 +108,13 @@
|
||||
let pointerdown = false
|
||||
let lastapp: App | undefined = undefined
|
||||
const onpointerdown = () => {
|
||||
if (history) {
|
||||
lastapp = JSON.parse(JSON.stringify($app))
|
||||
pointerdown = true
|
||||
}
|
||||
lastapp = JSON.parse(JSON.stringify($app))
|
||||
pointerdown = true
|
||||
}
|
||||
const onpointerup = () => {
|
||||
if (history) {
|
||||
pointerdown = false
|
||||
if (!deepEqual(lastapp, $app)) {
|
||||
push(history, lastapp, false, true)
|
||||
}
|
||||
pointerdown = false
|
||||
if (!deepEqual(lastapp, $app)) {
|
||||
push(history, lastapp, false, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,13 +211,11 @@
|
||||
if (gridItem) {
|
||||
toggleFixed(gridItem)
|
||||
}
|
||||
$app = $app
|
||||
}}
|
||||
on:expand={() => {
|
||||
push(history, $app)
|
||||
$selectedComponent = gridComponent.data.id
|
||||
expandGriditem($app.grid, gridComponent, $breakpoint)
|
||||
$app = $app
|
||||
$app = { ...$app }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -240,7 +234,6 @@
|
||||
inlineScript={script.inlineScript}
|
||||
name={script.name}
|
||||
fields={script.fields}
|
||||
autoRefresh={script.autoRefresh ?? false}
|
||||
bind:staticOutputs={$staticOutputs[`bg_${index}`]}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import Button from '../../common/button/Button.svelte'
|
||||
import type { AppViewerContext } from '../types'
|
||||
|
||||
const { runnableComponents, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
|
||||
let loading: boolean = false
|
||||
let timeout: NodeJS.Timer | undefined = undefined
|
||||
let interval: number | undefined = undefined
|
||||
@@ -36,12 +36,6 @@
|
||||
loading = true
|
||||
Promise.all(
|
||||
Object.keys($runnableComponents).map((id) => {
|
||||
if (id.startsWith('bg_')) {
|
||||
let index = parseInt(id.split('_')[1])
|
||||
if (!$app.hiddenInlineScripts[index]?.autoRefresh) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return $runnableComponents?.[id]?.()
|
||||
})
|
||||
).finally(() => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../types'
|
||||
import GridPanel from './GridPanel.svelte'
|
||||
@@ -21,27 +20,15 @@
|
||||
|
||||
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
|
||||
{#if $selectedComponent === `bg_${index}`}
|
||||
<div class="min-h-full flex flex-col divide-y">
|
||||
<PanelSection title={`Configuration`}>
|
||||
<Toggle
|
||||
bind:checked={script.autoRefresh}
|
||||
options={{ right: 'Run on start and app refresh' }}
|
||||
<PanelSection title={`Background script inputs`}>
|
||||
{#key $stateId}
|
||||
<InputsSpecsEditor
|
||||
id={`bg_${index}`}
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={script.fields}
|
||||
userInputEnabled={false}
|
||||
/>
|
||||
</PanelSection>
|
||||
|
||||
{#if Object.keys(script.fields).length > 0}
|
||||
<PanelSection title={`Inputs`}>
|
||||
{#key $stateId}
|
||||
<InputsSpecsEditor
|
||||
id={`bg_${index}`}
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={script.fields}
|
||||
userInputEnabled={false}
|
||||
/>
|
||||
{/key}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
<div class="grow shrink" />
|
||||
</div>
|
||||
{/key}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Grid from '@windmill-labs/svelte-grid'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
|
||||
import type { AppEditorContext, AppViewerContext, GridItem } from '../types'
|
||||
import Component from './component/Component.svelte'
|
||||
import { expandGriditem, findGridItem } from './appUtils'
|
||||
import { push } from '$lib/history'
|
||||
import Grid from '../svelte-grid/Grid.svelte'
|
||||
|
||||
export let containerHeight: number
|
||||
let classes = ''
|
||||
@@ -63,7 +63,6 @@
|
||||
if (fComponent) {
|
||||
fComponent = toggleFixed(fComponent)
|
||||
}
|
||||
$app = $app
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
@@ -137,11 +136,11 @@
|
||||
if (!parentGridItem) {
|
||||
return
|
||||
}
|
||||
$selectedComponent = gridComponent.data.id
|
||||
|
||||
push(history, $app)
|
||||
|
||||
expandGriditem(subGrid, gridComponent, $breakpoint, parentGridItem)
|
||||
$app = $app
|
||||
$app = { ...$app }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import type { App, EditorBreakpoint, FocusedGrid, GridItem } from '../types'
|
||||
import { getRecommendedDimensionsByComponent, type AppComponent } from './component'
|
||||
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
|
||||
import { gridColumns } from '../gridUtils'
|
||||
import { allItems } from '../utils'
|
||||
import type { Output, World } from '../rx'
|
||||
import gridHelp from '../svelte-grid/utils/helper'
|
||||
|
||||
function findGridItemById(
|
||||
root: GridItem[],
|
||||
@@ -19,7 +18,7 @@ function findGridItemById(
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function findGridItemParentGrid(app: App, id: string): string | undefined {
|
||||
export function findGridItemParentId(app: App, id: string): string | undefined {
|
||||
const gridItem = app.grid.find((x) => x.id === id)
|
||||
if (gridItem) {
|
||||
return undefined
|
||||
@@ -28,7 +27,7 @@ export function findGridItemParentGrid(app: App, id: string): string | undefined
|
||||
const subGrid = app.subgrids[key]
|
||||
const gridItem = subGrid.find((x) => x.id === id)
|
||||
if (gridItem) {
|
||||
return key
|
||||
return key.split('-')[0]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +46,10 @@ export function getNextGridItemId(app: App): string {
|
||||
}
|
||||
|
||||
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
|
||||
const appComponent = data
|
||||
|
||||
appComponent.id = id
|
||||
|
||||
const newComponent = {
|
||||
resizable: true,
|
||||
draggable: true,
|
||||
@@ -54,8 +57,7 @@ export function createNewGridItem(grid: GridItem[], id: string, data: AppCompone
|
||||
y: 0
|
||||
}
|
||||
|
||||
let newData: AppComponent = JSON.parse(JSON.stringify(data))
|
||||
newData.id = id
|
||||
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
|
||||
|
||||
const newItem: GridItem = {
|
||||
data: newData,
|
||||
@@ -63,7 +65,7 @@ export function createNewGridItem(grid: GridItem[], id: string, data: AppCompone
|
||||
}
|
||||
|
||||
gridColumns.forEach((column) => {
|
||||
const rec = getRecommendedDimensionsByComponent(newData.type, column)
|
||||
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
|
||||
|
||||
newItem[column] = {
|
||||
...newComponent,
|
||||
@@ -82,16 +84,6 @@ export function createNewGridItem(grid: GridItem[], id: string, data: AppCompone
|
||||
return newItem
|
||||
}
|
||||
|
||||
export function getGridItems(app: App, focusedGrid: FocusedGrid | undefined): GridItem[] {
|
||||
if (!focusedGrid) {
|
||||
return app.grid
|
||||
} else {
|
||||
const { parentComponentId, subGridIndex } = focusedGrid
|
||||
const key = `${parentComponentId}-${subGridIndex ?? 0}`
|
||||
return app?.subgrids?.[key] ?? []
|
||||
}
|
||||
}
|
||||
|
||||
export function insertNewGridItem(
|
||||
app: App,
|
||||
data: AppComponent,
|
||||
@@ -104,6 +96,18 @@ export function insertNewGridItem(
|
||||
app.subgrids = {}
|
||||
}
|
||||
|
||||
if (!focusedGrid) {
|
||||
const newItem = createNewGridItem(app.grid, id, data)
|
||||
app.grid.push(newItem)
|
||||
} else {
|
||||
const { parentComponentId, subGridIndex } = focusedGrid
|
||||
|
||||
const key = `${parentComponentId}-${subGridIndex ?? 0}`
|
||||
|
||||
const subGrid = app.subgrids[key] ?? []
|
||||
subGrid.push(createNewGridItem(subGrid, id, data))
|
||||
app.subgrids[key] = subGrid
|
||||
}
|
||||
// We only want to set subgrids when we are not moving
|
||||
if (!keepId) {
|
||||
for (let i = 0; i < (data.numberOfSubgrids ?? 0); i++) {
|
||||
@@ -111,17 +115,6 @@ export function insertNewGridItem(
|
||||
}
|
||||
}
|
||||
|
||||
const key = focusedGrid
|
||||
? `${focusedGrid?.parentComponentId}-${focusedGrid?.subGridIndex ?? 0}`
|
||||
: undefined
|
||||
let grid = focusedGrid ? app.subgrids[key!] : app.grid
|
||||
|
||||
const newItem = createNewGridItem(grid, id, data)
|
||||
grid.push(newItem)
|
||||
|
||||
if (focusedGrid) {
|
||||
app.subgrids[key!] = grid
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -176,6 +169,28 @@ export function deleteGridItem(
|
||||
return components
|
||||
}
|
||||
|
||||
export function duplicateGridItem(
|
||||
app: App,
|
||||
parent: string | undefined,
|
||||
id: string
|
||||
): string | undefined {
|
||||
const gridItem = findGridItem(app, id)
|
||||
|
||||
if (gridItem) {
|
||||
const newId = getNextGridItemId(app)
|
||||
const newItem = JSON.parse(JSON.stringify(gridItem))
|
||||
newItem.id = newId
|
||||
newItem.data.id = newId
|
||||
|
||||
let focusedGrid = parent
|
||||
? { parentComponentId: parent.split('-')[0], subGridIndex: Number(parent.split('-')[1]) }
|
||||
: undefined
|
||||
|
||||
return insertNewGridItem(app, newItem.data, focusedGrid)
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
type AvailableSpace = {
|
||||
left: number
|
||||
right: number
|
||||
@@ -191,7 +206,7 @@ export function findAvailableSpace(
|
||||
): AvailableSpace | undefined {
|
||||
if (gridItem) {
|
||||
const breakpoint = editorBreakpoint === 'sm' ? 3 : 12
|
||||
const maxHeight = parentGridItem ? parentGridItem[breakpoint].h - 1 : 16
|
||||
const maxHeight = parentGridItem ? parentGridItem[breakpoint].h - 1 : 12
|
||||
const maxWidth = 12
|
||||
|
||||
const availableSpace = {
|
||||
@@ -282,28 +297,6 @@ function isOverlapping(item1: any, item2: any) {
|
||||
)
|
||||
}
|
||||
|
||||
type Outputtable<Type> = {
|
||||
-readonly [Property in keyof Type]: Output<Type[Property]>
|
||||
}
|
||||
|
||||
export function initOutput<I extends Record<string, any>>(
|
||||
world: World | undefined,
|
||||
id: string,
|
||||
init: I
|
||||
): Outputtable<I> {
|
||||
if (!world) {
|
||||
return {} as any
|
||||
}
|
||||
const output = world.outputsById[id] as Outputtable<I>
|
||||
if (init) {
|
||||
for (const key in init) {
|
||||
if (output && output[key] && output[key].peak() == undefined) {
|
||||
output[key].set(init[key] as any)
|
||||
}
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
export function expandGriditem(
|
||||
grid: GridItem[],
|
||||
gridComponent: GridItem,
|
||||
@@ -318,10 +311,10 @@ export function expandGriditem(
|
||||
|
||||
const { left, right, top, bottom } = availableSpace
|
||||
const width = $breakpoint === 'sm' ? 3 : 12
|
||||
const item = gridComponent[width]
|
||||
const previousGridItem = JSON.parse(JSON.stringify(gridComponent[width]))
|
||||
|
||||
item.x = item.x - left
|
||||
item.y = item.y - top
|
||||
item.w = item.w + left + right
|
||||
item.h = item.h + top + bottom
|
||||
gridComponent[width].x = previousGridItem.x - left
|
||||
gridComponent[width].y = previousGridItem.y - top
|
||||
gridComponent[width].w = previousGridItem.w + left + right
|
||||
gridComponent[width].h = previousGridItem.h + top + bottom
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@
|
||||
recomputeIds={component.recomputeIds}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'}
|
||||
{:else if component.type === 'selectcomponent'}
|
||||
<AppSelect
|
||||
id={component.id}
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
|
||||
@@ -1,35 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import {
|
||||
deleteGridItem,
|
||||
findGridItem,
|
||||
findGridItemParentGrid,
|
||||
getAllSubgridsAndComponentIds,
|
||||
getGridItems,
|
||||
insertNewGridItem
|
||||
} from '../appUtils'
|
||||
import type { AppEditorContext, AppViewerContext, EditorBreakpoint, GridItem } from '../../types'
|
||||
import { push } from '$lib/history'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import { findGridItemParentId } from '../appUtils'
|
||||
|
||||
const { app, selectedComponent, breakpoint, focusedGrid } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { componentControl, history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let tempGridItem: GridItem | undefined = undefined
|
||||
let copiedGridItem: GridItem | undefined = undefined
|
||||
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function getSortedGridItemsOfChildren(): GridItem[] {
|
||||
if (!$focusedGrid) {
|
||||
return sortGridItemsPosition($app.grid, $breakpoint)
|
||||
return sortGridItems($app.grid, $breakpoint)
|
||||
}
|
||||
|
||||
if (!$app.subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return sortGridItemsPosition(
|
||||
return sortGridItems(
|
||||
$app.subgrids[`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`],
|
||||
$breakpoint
|
||||
)
|
||||
@@ -37,14 +25,14 @@
|
||||
|
||||
function getSortedGridItemsOfGrid(): GridItem[] {
|
||||
if ($app.grid.find((item) => item.id === $selectedComponent)) {
|
||||
return sortGridItemsPosition($app.grid, $breakpoint)
|
||||
return sortGridItems($app.grid, $breakpoint)
|
||||
}
|
||||
|
||||
if (!$app.subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return sortGridItemsPosition(
|
||||
return sortGridItems(
|
||||
Object.values($app.subgrids ?? {}).find((grid) =>
|
||||
grid.find((item) => item.id === $selectedComponent)
|
||||
) ?? [],
|
||||
@@ -91,7 +79,7 @@
|
||||
return
|
||||
}
|
||||
|
||||
const sortedGridItems = sortGridItemsPosition(subgrid, $breakpoint)
|
||||
const sortedGridItems = sortGridItems(subgrid, $breakpoint)
|
||||
|
||||
if (sortedGridItems) {
|
||||
$selectedComponent = sortedGridItems[0].id
|
||||
@@ -100,88 +88,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleEscape(event: KeyboardEvent) {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
function handleArrowUp(event: KeyboardEvent) {
|
||||
if (!$selectedComponent) return
|
||||
let parentId = findGridItemParentGrid($app, $selectedComponent)?.split('-')[0]
|
||||
if (parentId) {
|
||||
$selectedComponent = parentId
|
||||
} else {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopy(event: KeyboardEvent) {
|
||||
if (!$selectedComponent) {
|
||||
return
|
||||
}
|
||||
tempGridItem = undefined
|
||||
copiedGridItem = findGridItem($app, $selectedComponent)
|
||||
}
|
||||
|
||||
function handleCut(event: KeyboardEvent) {
|
||||
if (!$selectedComponent) {
|
||||
return
|
||||
}
|
||||
push(history, $app)
|
||||
|
||||
const gridItem = findGridItem($app, $selectedComponent)
|
||||
copiedGridItem = gridItem
|
||||
|
||||
if (!gridItem) {
|
||||
return
|
||||
}
|
||||
|
||||
// Store the grid item in a temp variable so we can paste it later
|
||||
tempGridItem = gridItem
|
||||
}
|
||||
|
||||
function handlePaste(event: KeyboardEvent) {
|
||||
push(history, $app)
|
||||
|
||||
if (tempGridItem) {
|
||||
if (
|
||||
$focusedGrid &&
|
||||
getAllSubgridsAndComponentIds($app, tempGridItem.data)[0].includes(
|
||||
`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`
|
||||
)
|
||||
) {
|
||||
sendUserToast('Cannot paste a component into itself', true)
|
||||
return
|
||||
}
|
||||
let parentGrid = findGridItemParentGrid($app, tempGridItem.data.id)
|
||||
const grid = parentGrid ? $app.subgrids![parentGrid] : $app.grid
|
||||
let idx = grid.findIndex((item) => {
|
||||
return item.id == tempGridItem!.data.id
|
||||
})
|
||||
if (idx > -1) {
|
||||
grid.splice(idx, 1)
|
||||
}
|
||||
|
||||
insertNewGridItem($app, tempGridItem.data, $focusedGrid, true)
|
||||
copiedGridItem = tempGridItem
|
||||
tempGridItem = undefined
|
||||
} else if (copiedGridItem) {
|
||||
insertNewGridItem($app, copiedGridItem.data, $focusedGrid, false)
|
||||
}
|
||||
|
||||
$app = $app
|
||||
}
|
||||
|
||||
function keydown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
handleEscape(event)
|
||||
$selectedComponent = undefined
|
||||
event.preventDefault()
|
||||
break
|
||||
|
||||
case 'ArrowUp': {
|
||||
handleArrowUp(event)
|
||||
if (!$selectedComponent) return
|
||||
let parentId = findGridItemParentId($app, $selectedComponent)
|
||||
if (parentId) {
|
||||
$selectedComponent = parentId
|
||||
} else {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'ArrowDown': {
|
||||
@@ -199,30 +122,12 @@
|
||||
break
|
||||
}
|
||||
|
||||
case 'c':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
handleCopy(event)
|
||||
}
|
||||
break
|
||||
|
||||
case 'v':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
handlePaste(event)
|
||||
}
|
||||
break
|
||||
|
||||
case 'x':
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
handleCut(event)
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function sortGridItemsPosition(gridItems: GridItem[], breakpoint: EditorBreakpoint): GridItem[] {
|
||||
function sortGridItems(gridItems: GridItem[], breakpoint: EditorBreakpoint): GridItem[] {
|
||||
return gridItems.sort((a: GridItem, b: GridItem) => {
|
||||
const width = breakpoint === 'lg' ? 12 : 3
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { Size } from '@windmill-labs/svelte-grid'
|
||||
import type { IntRange } from '../../../../common'
|
||||
import type { NARROW_GRID_COLUMNS, WIDE_GRID_COLUMNS } from '../../gridUtils'
|
||||
import { defaultAlignement } from '../componentsPanel/componentDefaultProps'
|
||||
@@ -35,7 +36,6 @@ import {
|
||||
AtSignIcon
|
||||
} from 'lucide-svelte'
|
||||
import type { BaseAppComponent } from '../../types'
|
||||
import type { Size } from '../../svelte-grid/types'
|
||||
|
||||
type BaseComponent<T extends string> = {
|
||||
type: T
|
||||
@@ -74,8 +74,7 @@ export type AggridComponent = BaseComponent<'aggridcomponent'>
|
||||
export type DisplayComponent = BaseComponent<'displaycomponent'>
|
||||
export type ImageComponent = BaseComponent<'imagecomponent'>
|
||||
export type InputComponent = BaseComponent<'inputcomponent'>
|
||||
export type SelectComponent = BaseComponent<'resourceselectcomponent'>
|
||||
export type ResourceSelectComponent = BaseComponent<'selectcomponent'>
|
||||
export type SelectComponent = BaseComponent<'selectcomponent'>
|
||||
export type MultiSelectComponent = BaseComponent<'multiselectcomponent'>
|
||||
export type CheckboxComponent = BaseComponent<'checkboxcomponent'>
|
||||
export type RadioComponent = BaseComponent<'radiocomponent'>
|
||||
@@ -117,7 +116,6 @@ export type AppComponent = BaseAppComponent &
|
||||
| PieChartComponent
|
||||
| ScatterChartComponent
|
||||
| SelectComponent
|
||||
| ResourceSelectComponent
|
||||
| MultiSelectComponent
|
||||
| CheckboxComponent
|
||||
| FormComponent
|
||||
@@ -913,7 +911,7 @@ Hello \${ctx.username}
|
||||
}
|
||||
},
|
||||
multiselectcomponent: {
|
||||
name: 'Multi Select',
|
||||
name: 'MultiSelect',
|
||||
icon: List,
|
||||
dims: '2:1-3:1',
|
||||
data: {
|
||||
@@ -942,36 +940,6 @@ Hello \${ctx.username}
|
||||
softWrap: true
|
||||
}
|
||||
},
|
||||
resourceselectcomponent: {
|
||||
name: 'Resource Select',
|
||||
icon: List,
|
||||
dims: '2:1-3:1',
|
||||
data: {
|
||||
verticalAlignment: 'center',
|
||||
id: '',
|
||||
type: 'resourceselectcomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {
|
||||
items: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'labeledresource',
|
||||
value: []
|
||||
},
|
||||
placeholder: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
value: 'Select an item',
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
input: { style: '' }
|
||||
} as const,
|
||||
card: false,
|
||||
softWrap: true
|
||||
}
|
||||
},
|
||||
numberinputcomponent: {
|
||||
name: 'Number',
|
||||
icon: Binary,
|
||||
|
||||
@@ -32,7 +32,6 @@ const inputs: ComponentSet = {
|
||||
'fileinputcomponent',
|
||||
'checkboxcomponent',
|
||||
'selectcomponent',
|
||||
'resourceselectcomponent',
|
||||
'multiselectcomponent',
|
||||
]
|
||||
} as const
|
||||
|
||||
@@ -65,7 +65,11 @@
|
||||
No components found
|
||||
</div>
|
||||
{:else}
|
||||
<div in:fade|local={{ duration: 50, delay: 50 }} out:fade|local={{ duration: 50 }}>
|
||||
<div
|
||||
id="app-tutorial-1"
|
||||
in:fade|local={{ duration: 50, delay: 50 }}
|
||||
out:fade|local={{ duration: 50 }}
|
||||
>
|
||||
{#each componentsFiltered as { title, components }, index (index)}
|
||||
{#if components.length}
|
||||
<div transition:slide|local={{ duration: 300 }}>
|
||||
|
||||
@@ -23,13 +23,7 @@
|
||||
<label class="block pb-2">
|
||||
<div class="text-xs font-medium pb-0.5"> Style </div>
|
||||
<div class="relative">
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
type="text"
|
||||
class="!pr-7"
|
||||
bind:value={value.style}
|
||||
on:focus
|
||||
/>
|
||||
<input type="text" class="!pr-7" bind:value={value.style} on:focus />
|
||||
{#if value?.style}
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
@@ -47,13 +41,7 @@
|
||||
<label class="block">
|
||||
<div class="text-xs font-medium pb-0.5"> Tailwind classes </div>
|
||||
<div class="relative">
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
type="text"
|
||||
class="!pr-7"
|
||||
bind:value={value.class}
|
||||
on:focus
|
||||
/>
|
||||
<input type="text" class="!pr-7" bind:value={value.class} on:focus />
|
||||
{#if value?.class}
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { X } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
@@ -11,7 +10,7 @@
|
||||
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
|
||||
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
|
||||
|
||||
const { connectingInput, staticOutputs, worldStore, selectedComponent, app, state } =
|
||||
const { connectingInput, staticOutputs, worldStore, selectedComponent, app } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function connectInput(componentId: string, path: string) {
|
||||
@@ -79,7 +78,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative p-2">
|
||||
<div id="app-tutorial-2" class="relative p-2">
|
||||
{#each filteredPanels as [componentId, outputs] (componentId)}
|
||||
<div
|
||||
animate:flip={{ duration: 300 }}
|
||||
@@ -156,12 +155,5 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<PanelSection noPadding titlePadding="px-4 pt-2 pb-0.5" title="State">
|
||||
<div class="mx-2 px-1 border w-full mb-8">
|
||||
{#key $state}
|
||||
<ObjectViewer json={$state} />
|
||||
{/key}
|
||||
</div>
|
||||
</PanelSection>
|
||||
</div>
|
||||
</PanelSection>
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { Button, Drawer, DrawerContent, Tab, Tabs } from '$lib/components/common'
|
||||
import FlowScriptPicker from '$lib/components/flows/pickers/FlowScriptPicker.svelte'
|
||||
import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { Script, type Preview } from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
@@ -146,29 +145,7 @@
|
||||
</Drawer>
|
||||
|
||||
<div class="flex flex-col px-4 py-2 gap-2 text-sm" in:fly={{ duration: 50 }}>
|
||||
<div class="mt-4 flex justify-between gap-8 mb-2">
|
||||
<div class="font-bold items-baseline">Choose a language:</div>
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
on:click={() => picker?.openDrawer()}
|
||||
size="xs"
|
||||
color="blue"
|
||||
startIcon={{ icon: faCodeBranch }}
|
||||
btnClasses="truncate"
|
||||
>
|
||||
fork a detached script
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => dispatch('delete')}
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
btnClasses="truncate"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>Choose a language:</div>
|
||||
<div class="flex gap-2 flex-row flex-wrap">
|
||||
{#each langs as lang}
|
||||
<FlowScriptPicker
|
||||
@@ -187,7 +164,6 @@
|
||||
createInlineScriptByLanguage(Script.language.DENO, name, 'pgsql')
|
||||
}}
|
||||
/>
|
||||
|
||||
<!-- <FlowScriptPicker
|
||||
label={`MySQL`}
|
||||
lang="mysql"
|
||||
@@ -196,34 +172,26 @@
|
||||
}}
|
||||
/> -->
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-xs mb-1 mt-2"
|
||||
>Frontend only script: <Tooltip
|
||||
>Frontend scripts are executed in the browser and can manipulate the app context directly</Tooltip
|
||||
></div
|
||||
|
||||
<div class="mt-4">
|
||||
<Button
|
||||
on:click={() => picker?.openDrawer()}
|
||||
size="xs"
|
||||
color="blue"
|
||||
startIcon={{ icon: faCodeBranch }}
|
||||
btnClasses="truncate"
|
||||
>
|
||||
<FlowScriptPicker
|
||||
label={`JavaScript`}
|
||||
lang="javascript"
|
||||
on:click={() => {
|
||||
const newInlineScript = {
|
||||
content: `// read outputs and ctx
|
||||
console.log(ctx.email)
|
||||
|
||||
// access a global state store
|
||||
if (!state.foo) { state.foo = 0 }
|
||||
state.foo += 1
|
||||
|
||||
// you can also navigate to another page
|
||||
//await goto("?foo=bar")
|
||||
|
||||
return state.foo`,
|
||||
language: 'frontend',
|
||||
path: 'frontend script',
|
||||
schema: undefined
|
||||
}
|
||||
dispatch('new', newInlineScript)
|
||||
}}
|
||||
/>
|
||||
or fork a detached/Workspace/Hub script
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => dispatch('delete')}
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
startIcon={{ icon: faTrash }}
|
||||
btnClasses="truncate"
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import type { Schema } from '$lib/common'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
import Editor from '$lib/components/Editor.svelte'
|
||||
import { emptySchema, scriptLangToEditorLang } from '$lib/utils'
|
||||
import Popover from '../../../Popover.svelte'
|
||||
@@ -15,8 +16,6 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import Kbd from '$lib/components/common/kbd/Kbd.svelte'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { buildExtraLib } from '../../utils'
|
||||
|
||||
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
|
||||
@@ -27,8 +26,7 @@
|
||||
export let fields: Record<string, AppInput> = {}
|
||||
export let syncFields: boolean = false
|
||||
|
||||
const { runnableComponents, stateId, worldStore, state } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const { runnableComponents, stateId } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let editor: Editor
|
||||
let validCode = true
|
||||
@@ -51,13 +49,11 @@
|
||||
|
||||
onMount(async () => {
|
||||
if (inlineScript && !inlineScript.schema) {
|
||||
if (inlineScript.language != 'frontend') {
|
||||
inlineScript.schema = await inferInlineScriptSchema(
|
||||
inlineScript?.language,
|
||||
inlineScript?.content,
|
||||
emptySchema()
|
||||
)
|
||||
}
|
||||
inlineScript.schema = await inferInlineScriptSchema(
|
||||
inlineScript?.language,
|
||||
inlineScript?.content,
|
||||
emptySchema()
|
||||
)
|
||||
}
|
||||
if (inlineScript.schema) {
|
||||
loadSchemaAndInputsByName()
|
||||
@@ -68,7 +64,7 @@
|
||||
|
||||
async function loadSchemaAndInputsByName() {
|
||||
if (syncFields) {
|
||||
const newSchema = inlineScript.schema ?? emptySchema()
|
||||
const newSchema = inlineScript.schema
|
||||
const newFields = computeFields(newSchema, defaultUserInput, fields)
|
||||
|
||||
if (!deepEqual(newFields, fields)) {
|
||||
@@ -79,26 +75,14 @@
|
||||
}
|
||||
|
||||
let isMac = navigator.userAgent.indexOf('Mac OS X') !== -1
|
||||
|
||||
$: extraLib =
|
||||
inlineScript.language == 'frontend' && worldStore
|
||||
? buildExtraLib($worldStore?.outputsById ?? {}, id, false, $state, true)
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
{#if inlineScript.language != 'frontend'}
|
||||
<InlineScriptEditorDrawer {editor} bind:this={inlineScriptEditorDrawer} bind:inlineScript />
|
||||
{/if}
|
||||
<InlineScriptEditorDrawer {editor} bind:this={inlineScriptEditorDrawer} bind:inlineScript />
|
||||
|
||||
<div class="h-full flex flex-col gap-1">
|
||||
<div class="flex justify-between w-full gap-2 px-2 pt-1 flex-row items-center">
|
||||
{#if name !== undefined}
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
bind:value={name}
|
||||
placeholder="Inline script name"
|
||||
class="!text-xs !rounded-xs"
|
||||
/>
|
||||
<input bind:value={name} placeholder="Inline script name" class="!text-xs !rounded-xs" />
|
||||
{/if}
|
||||
<div class="flex w-full flex-row gap-2 items-center justify-end">
|
||||
{#if validCode}
|
||||
@@ -121,23 +105,20 @@
|
||||
<svelte:fragment slot="text">Delete</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
{#if inlineScript.language != 'frontend'}
|
||||
<Popover notClickable placement="bottom">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
btnClasses="!px-2 !bg-gray-100 hover:!bg-gray-200"
|
||||
aria-label="Open full editor"
|
||||
on:click={() => {
|
||||
inlineScriptEditorDrawer?.openDrawer()
|
||||
}}
|
||||
>
|
||||
<Maximize2 size={14} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Open full editor</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
|
||||
<Popover notClickable placement="bottom">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
btnClasses="!px-2 !bg-gray-100 hover:!bg-gray-200"
|
||||
aria-label="Open full editor"
|
||||
on:click={() => {
|
||||
inlineScriptEditorDrawer?.openDrawer()
|
||||
}}
|
||||
>
|
||||
<Maximize2 size={14} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Open full editor</svelte:fragment>
|
||||
</Popover>
|
||||
<Button
|
||||
variant="border"
|
||||
size="xs"
|
||||
@@ -187,44 +168,30 @@
|
||||
</div>
|
||||
|
||||
<div class="border h-full">
|
||||
{#if inlineScript.language != 'frontend'}
|
||||
<Editor
|
||||
bind:this={editor}
|
||||
class="flex flex-1 grow h-full"
|
||||
lang={scriptLangToEditorLang(inlineScript?.language)}
|
||||
bind:code={inlineScript.content}
|
||||
fixedOverflowWidgets={true}
|
||||
cmdEnterAction={async () => {
|
||||
runLoading = true
|
||||
await $runnableComponents[id]?.()
|
||||
runLoading = false
|
||||
}}
|
||||
on:change={async (e) => {
|
||||
if (inlineScript && inlineScript.language != 'frontend') {
|
||||
const oldSchema = JSON.stringify(inlineScript.schema)
|
||||
if (inlineScript.schema == undefined) {
|
||||
inlineScript.schema = emptySchema()
|
||||
}
|
||||
await inferInlineScriptSchema(inlineScript?.language, e.detail, inlineScript.schema)
|
||||
if (JSON.stringify(inlineScript.schema) != oldSchema) {
|
||||
inlineScript = inlineScript
|
||||
loadSchemaAndInputsByName()
|
||||
}
|
||||
<Editor
|
||||
bind:this={editor}
|
||||
class="flex flex-1 grow h-full"
|
||||
lang={scriptLangToEditorLang(inlineScript?.language)}
|
||||
bind:code={inlineScript.content}
|
||||
fixedOverflowWidgets={true}
|
||||
cmdEnterAction={async () => {
|
||||
runLoading = true
|
||||
await $runnableComponents[id]?.()
|
||||
runLoading = false
|
||||
}}
|
||||
on:change={async (e) => {
|
||||
if (inlineScript) {
|
||||
const oldSchema = JSON.stringify(inlineScript.schema)
|
||||
if (inlineScript.schema == undefined) {
|
||||
inlineScript.schema = emptySchema()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<SimpleEditor
|
||||
cmdEnterAction={async () => {
|
||||
runLoading = true
|
||||
await $runnableComponents[id]?.()
|
||||
runLoading = false
|
||||
}}
|
||||
class="h-full"
|
||||
{extraLib}
|
||||
bind:code={inlineScript.content}
|
||||
lang="javascript"
|
||||
/>
|
||||
{/if}
|
||||
await inferInlineScriptSchema(inlineScript?.language, e.detail, inlineScript.schema)
|
||||
if (JSON.stringify(inlineScript.schema) != oldSchema) {
|
||||
inlineScript = inlineScript
|
||||
loadSchemaAndInputsByName()
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Button, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import type Editor from '$lib/components/Editor.svelte'
|
||||
import ScriptEditor from '$lib/components/ScriptEditor.svelte'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { InlineScript } from '../../types'
|
||||
|
||||
@@ -25,7 +24,7 @@
|
||||
editor?.setCode(inlineScript.content)
|
||||
}}
|
||||
>
|
||||
{#if inlineScript && inlineScript.language != 'frontend'}
|
||||
{#if inlineScript}
|
||||
<ScriptEditor
|
||||
noSyncFromGithub
|
||||
lang={inlineScript.language}
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
componentInput.value.push(0)
|
||||
} else if (componentInput.subFieldType === 'object') {
|
||||
componentInput.value.push({})
|
||||
} else if (componentInput.subFieldType === 'labeledresource') {
|
||||
componentInput.value.push({ value: '', label: '' })
|
||||
} else if (
|
||||
componentInput.subFieldType === 'text' ||
|
||||
componentInput.subFieldType === 'textarea' ||
|
||||
|
||||
@@ -19,12 +19,14 @@
|
||||
import TemplateEditor from '$lib/components/TemplateEditor.svelte'
|
||||
import type { AppComponent } from '../component'
|
||||
import CssProperty from '../componentsPanel/CssProperty.svelte'
|
||||
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
|
||||
import GridTab from './GridTab.svelte'
|
||||
import { duplicateGridItem } from '../appUtils'
|
||||
import { deleteGridItem } from '../appUtils'
|
||||
import MoveToOtherGrid from './MoveToOtherGrid.svelte'
|
||||
import GridPane from './GridPane.svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { push } from '$lib/history'
|
||||
import Kbd from '$lib/components/common/kbd/Kbd.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
export let rowColumns = false
|
||||
@@ -40,12 +42,18 @@
|
||||
selectedComponent,
|
||||
worldStore,
|
||||
focusedGrid,
|
||||
stateId,
|
||||
state
|
||||
stateId
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function duplicateElement(id: string) {
|
||||
push(history, $app)
|
||||
$dirtyStore = true
|
||||
const newId = duplicateGridItem($app, parent, id)
|
||||
$selectedComponent = newId
|
||||
}
|
||||
|
||||
function removeGridElement() {
|
||||
push(history, $app)
|
||||
$selectedComponent = undefined
|
||||
@@ -71,7 +79,7 @@
|
||||
|
||||
$: extraLib =
|
||||
component?.componentInput?.type === 'template' && $worldStore
|
||||
? buildExtraLib($worldStore?.outputsById ?? {}, component?.id, false, $state, false)
|
||||
? buildExtraLib($worldStore?.outputsById ?? {}, component?.id, false)
|
||||
: undefined
|
||||
|
||||
function keydown(event: KeyboardEvent) {
|
||||
@@ -88,7 +96,7 @@
|
||||
<svelte:window on:keydown={keydown} />
|
||||
|
||||
{#if component}
|
||||
<div class="flex min-h-full flex-col min-w-[150px] w-full divide-y">
|
||||
<div class="flex flex-col min-w-[150px] w-full divide-y">
|
||||
{#if component.componentInput}
|
||||
<PanelSection
|
||||
smallPadding
|
||||
@@ -187,8 +195,6 @@
|
||||
<Recompute bind:recomputeIds={component.recomputeIds} ownId={component.id} />
|
||||
{/if}
|
||||
|
||||
<div class="grow shrink" />
|
||||
|
||||
{#if Object.keys(component.customCss ?? {}).length > 0}
|
||||
<PanelSection title="Custom CSS">
|
||||
<div slot="action">
|
||||
@@ -216,20 +222,8 @@
|
||||
{/if}
|
||||
|
||||
{#if duplicateMoveAllowed}
|
||||
<PanelSection title="Copy/Move">
|
||||
<div slot="action">
|
||||
<Button size="xs" color="red" variant="border" on:click={removeGridElement}>
|
||||
Delete
|
||||
<Kbd>Del</Kbd>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<div
|
||||
><span class="text-gray-600 text-xs mr-2">Copy:</span><Kbd>CTRL+C</Kbd> + <Kbd
|
||||
>CTRL+V</Kbd
|
||||
></div
|
||||
>
|
||||
<!-- <Button
|
||||
<PanelSection title="Duplicate">
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
startIcon={{ icon: faCopy }}
|
||||
@@ -240,19 +234,24 @@
|
||||
}}
|
||||
>
|
||||
Duplicate component: {component.id}
|
||||
</Button> -->
|
||||
<div
|
||||
><span class="text-gray-600 text-xs mr-2">Move:</span><Kbd>CTRL+X</Kbd> + <Kbd
|
||||
>CTRL+V</Kbd
|
||||
></div
|
||||
>
|
||||
<div
|
||||
><span class="text-gray-600 text-xs mr-2">Navigate:</span><Kbd>←</Kbd><Kbd
|
||||
>↑</Kbd
|
||||
><Kbd>→</Kbd><Kbd>ESC</Kbd></div
|
||||
>
|
||||
</div>
|
||||
</Button>
|
||||
</PanelSection>
|
||||
|
||||
<PanelSection title="Move to other grid">
|
||||
<MoveToOtherGrid bind:component {parent} />
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
<PanelSection title="Danger zone">
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
startIcon={{ icon: faTrashAlt }}
|
||||
on:click={removeGridElement}
|
||||
>
|
||||
Delete component: {component.id}
|
||||
</Button>
|
||||
</PanelSection>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<div class="w-full flex gap-2 flex-col mt-2">
|
||||
{#each panes as value, index (index)}
|
||||
<div class="w-full flex flex-row gap-2 items-center relative">
|
||||
<input on:keydown|stopPropagation type="number" bind:value />
|
||||
<input type="number" bind:value />
|
||||
|
||||
<div class="absolute top-1 right-1">
|
||||
<Button
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import CloseButton from '$lib/components/common/CloseButton.svelte'
|
||||
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { CrossIcon } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { deleteGridItem } from '../appUtils'
|
||||
@@ -56,10 +54,20 @@
|
||||
<div class="w-full flex gap-2 flex-col mt-2">
|
||||
{#each tabs as value, index (index)}
|
||||
<div class="w-full flex flex-row gap-2 items-center relative">
|
||||
<input on:keydown|stopPropagation type="text" bind:value />
|
||||
<input type="text" bind:value />
|
||||
|
||||
<div class="absolute right-1">
|
||||
<CloseButton noBg on:close={() => deleteSubgrid(index)} />
|
||||
<div class="absolute top-1 right-1">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
deleteSubgrid(index)
|
||||
}}
|
||||
iconOnly
|
||||
btnClasses="!text-red-500"
|
||||
startIcon={{ icon: faTrashAlt }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/common'
|
||||
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
|
||||
import { push } from '$lib/history'
|
||||
import { faCopy } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { App, AppEditorContext, AppViewerContext } from '../../types'
|
||||
import {
|
||||
createNewGridItem,
|
||||
deleteGridItem,
|
||||
findGridItem,
|
||||
getAllSubgridsAndComponentIds,
|
||||
insertNewGridItem
|
||||
} from '../appUtils'
|
||||
import type { AppComponent } from '../component'
|
||||
|
||||
export let component: AppComponent | undefined
|
||||
export let parent: string | undefined
|
||||
|
||||
let selectedOption: string
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function listAllSubGrids(app: App) {
|
||||
return app.subgrids ? Object.keys(app.subgrids) : []
|
||||
}
|
||||
|
||||
function findAndDelete(id: string) {
|
||||
const node = findGridItem($app, id)
|
||||
|
||||
if (!node) {
|
||||
return
|
||||
}
|
||||
|
||||
const data: AppComponent = JSON.parse(JSON.stringify(node.data))
|
||||
const shouldKeepSubGrid = data.numberOfSubgrids ? data.numberOfSubgrids >= 1 : false
|
||||
|
||||
$dirtyStore = true
|
||||
|
||||
deleteGridItem($app, data, parent, shouldKeepSubGrid)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
function insertComponentInSubGrid(
|
||||
data: AppComponent,
|
||||
targetId: string,
|
||||
targetSubGridIndex: number
|
||||
) {
|
||||
push(history, $app)
|
||||
const focusedGrid = {
|
||||
parentComponentId: targetId,
|
||||
subGridIndex: targetSubGridIndex
|
||||
}
|
||||
|
||||
insertNewGridItem($app, data, focusedGrid, true)
|
||||
|
||||
$app.grid = [...$app.grid]
|
||||
}
|
||||
|
||||
function insertComponentInMainGrid(data: AppComponent) {
|
||||
const newComponent = createNewGridItem($app.grid, data.id, data)
|
||||
$app.grid = [...$app.grid, newComponent]
|
||||
}
|
||||
|
||||
function onMove(component: AppComponent) {
|
||||
push(history, $app)
|
||||
const data = findAndDelete(component.id)
|
||||
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedOption !== 'main-grid') {
|
||||
const [targetId, targetSubGridIndex] = selectedOption.split('-')
|
||||
insertComponentInSubGrid(data, targetId, parseInt(targetSubGridIndex))
|
||||
} else {
|
||||
insertComponentInMainGrid(data)
|
||||
}
|
||||
}
|
||||
|
||||
const defaultOption = {
|
||||
label: 'Main grid',
|
||||
value: 'main-grid',
|
||||
disabled: parent === undefined
|
||||
}
|
||||
|
||||
$: [subgrids] = component ? getAllSubgridsAndComponentIds($app, component) : [[], []]
|
||||
$: availableGrids = listAllSubGrids($app)
|
||||
$: options = availableGrids
|
||||
? [
|
||||
defaultOption,
|
||||
...availableGrids?.map((grid) => ({
|
||||
label: grid,
|
||||
value: grid,
|
||||
disabled: grid === parent || subgrids.includes(grid)
|
||||
}))
|
||||
]
|
||||
: [defaultOption]
|
||||
</script>
|
||||
|
||||
{#if component && !options.every((option) => option.disabled)}
|
||||
<select bind:value={selectedOption} class="w-full">
|
||||
{#each options as option}
|
||||
<option value={option.value} disabled={option.disabled}>{option.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button
|
||||
size="xs"
|
||||
color="dark"
|
||||
startIcon={{ icon: faCopy }}
|
||||
on:click={() => {
|
||||
if (component) {
|
||||
onMove(component)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Move component: {component.id}
|
||||
</Button>
|
||||
{:else}
|
||||
<p class="text-gray-500 text-sm">No grids available</p>
|
||||
{/if}
|
||||
@@ -47,7 +47,6 @@
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap px-4 py-2 ">
|
||||
<input
|
||||
class="windmillapp"
|
||||
type="checkbox"
|
||||
on:change={(event) => onChange(event, id)}
|
||||
checked={recomputeIds?.includes(id)}
|
||||
|
||||
@@ -43,8 +43,7 @@
|
||||
disabled: {
|
||||
fieldType: 'boolean',
|
||||
type: 'eval',
|
||||
expr: 'false',
|
||||
tooltip: "Disabled based on a predicate. Use `row` to access the current row's data"
|
||||
expr: 'false'
|
||||
},
|
||||
goto: {
|
||||
tooltip: 'Go to an url on success if not empty',
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
export let id: string
|
||||
export let hasRows: boolean = false
|
||||
|
||||
const { onchange, worldStore, state } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { onchange, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: componentInput && onchange?.()
|
||||
|
||||
$: extraLib =
|
||||
componentInput?.expr && $worldStore
|
||||
? buildExtraLib($worldStore?.outputsById ?? {}, id, hasRows, $state, false)
|
||||
? buildExtraLib($worldStore?.outputsById ?? {}, id, hasRows)
|
||||
: undefined
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { AppInput, StaticInput } from '../../../inputType'
|
||||
import { Loader2, X } from 'lucide-svelte'
|
||||
import { Popup } from '../../../../common'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
export let componentInput: StaticInput<string> & Extract<AppInput, { fieldType: 'icon-select' }>
|
||||
let anchor: HTMLElement
|
||||
@@ -10,7 +9,6 @@
|
||||
let items: { label: string; icon: any }[]
|
||||
let filteredItems: { label: string; icon: any }[]
|
||||
let search = ''
|
||||
let openPopup: () => void
|
||||
|
||||
$: if (search) {
|
||||
filteredItems = items.filter((item) => {
|
||||
@@ -21,16 +19,13 @@
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
if (items) {
|
||||
return openPopup()
|
||||
}
|
||||
if (items) return
|
||||
loading = true
|
||||
const data = await import('lucide-svelte/dist/svelte/icons')
|
||||
filteredItems = items = Object.entries(data)
|
||||
.filter(([key]) => !(key.endsWith('Icon') || key.startsWith('Lucide')))
|
||||
.map(([key, icon]) => ({ label: key, icon }))
|
||||
loading = false
|
||||
openPopup()
|
||||
}
|
||||
|
||||
function formatName(name?: string) {
|
||||
@@ -59,11 +54,7 @@
|
||||
on:focus={getData}
|
||||
class="pr-8"
|
||||
/>
|
||||
{#if loading}
|
||||
<div class="center-center absolute right-2 top-1/2 transform -translate-y-1/2 p-0.5">
|
||||
<Loader2 class="animate-spin" size={16} />
|
||||
</div>
|
||||
{:else if componentInput.value}
|
||||
{#if componentInput.value}
|
||||
<button
|
||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 hover:bg-gray-200 rounded-full p-0.5"
|
||||
on:click|stopPropagation|preventDefault={() => (componentInput.value = undefined)}
|
||||
@@ -75,52 +66,42 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if anchor}
|
||||
<Popup
|
||||
ref={anchor}
|
||||
options={{ placement: 'bottom' }}
|
||||
bind:open={openPopup}
|
||||
let:close
|
||||
transition={fade}
|
||||
>
|
||||
{#if !loading}
|
||||
<div class="max-w-xs shadow-[0_10px_40px_-5px_rgba(0,0,0,0.25)] bg-white rounded-md p-2">
|
||||
{#if filteredItems}
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
bind:value={search}
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
class="col-span-4 mb-2"
|
||||
/>
|
||||
<div class="grid gap-1 grid-cols-4 max-h-[300px] overflow-auto">
|
||||
{#each filteredItems as { label, icon }}
|
||||
{@const formatedLabel = formatName(label)}
|
||||
<button
|
||||
type="button"
|
||||
title={formatedLabel}
|
||||
on:click={() => {
|
||||
select(label)
|
||||
close()
|
||||
}}
|
||||
class="w-full center-center flex-col font-normal p-1
|
||||
hover:bg-gray-100 focus:bg-gray-100 rounded duration-200
|
||||
{label === componentInput.value ? 'text-blue-600 bg-blue-50 pointer-events-none' : ''}"
|
||||
>
|
||||
<svelte:component this={icon} size={22} />
|
||||
<span class="inline-block w-full text-[10px] ellipsize pt-0.5">
|
||||
{formatedLabel}
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<div class="col-span-4 text-center text-gray-700 text-sm p-2">
|
||||
No icons match your search
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center text-sm text-gray-600 p-2"> Couldn't load options </div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<Popup ref={anchor} options={{ placement: 'bottom' }} let:close>
|
||||
<div class="max-w-xs shadow-[0_10px_40px_-5px_rgba(0,0,0,0.25)] bg-white rounded-md p-2">
|
||||
{#if loading}
|
||||
<div class="center-center p-2">
|
||||
<Loader2 class="animate-spin" size={18} />
|
||||
</div>
|
||||
{:else if filteredItems}
|
||||
<input bind:value={search} type="text" placeholder="Search" class="col-span-4 mb-2" />
|
||||
<div class="grid gap-1 grid-cols-4 max-h-[300px] overflow-auto">
|
||||
{#each filteredItems as { label, icon }}
|
||||
{@const formatedLabel = formatName(label)}
|
||||
<button
|
||||
type="button"
|
||||
title={formatedLabel}
|
||||
on:click={() => {
|
||||
select(label)
|
||||
close()
|
||||
}}
|
||||
class="w-full center-center flex-col font-normal p-1
|
||||
hover:bg-gray-100 focus:bg-gray-100 rounded duration-200
|
||||
{label === componentInput.value ? 'text-blue-600 bg-blue-50 pointer-events-none' : ''}"
|
||||
>
|
||||
<svelte:component this={icon} size={22} />
|
||||
<span class="inline-block w-full text-[10px] ellipsize pt-0.5">
|
||||
{formatedLabel}
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<div class="col-span-4 text-center text-gray-700 text-sm p-2">
|
||||
No icons match your search
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-center text-sm text-gray-600 p-2"> Couldn't load options </div>
|
||||
{/if}
|
||||
</div>
|
||||
</Popup>
|
||||
{/if}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
export let code: string
|
||||
export let value: any = undefined
|
||||
export let error = ''
|
||||
export let value: any
|
||||
|
||||
function parseJson() {
|
||||
try {
|
||||
@@ -12,12 +11,13 @@
|
||||
error = e.message
|
||||
}
|
||||
}
|
||||
let error = ''
|
||||
$: code && parseJson()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="border border-gray-300 w-full">
|
||||
<SimpleEditor on:change autoHeight lang="json" bind:code />
|
||||
<SimpleEditor autoHeight lang="json" bind:code />
|
||||
</div>
|
||||
{#if error != ''}
|
||||
<span class="text-red-600 text-xs">{error}</span>
|
||||
|
||||
@@ -39,29 +39,6 @@
|
||||
</select>
|
||||
{:else if componentInput.fieldType === 'icon-select'}
|
||||
<IconSelectInput bind:componentInput />
|
||||
{:else if componentInput.fieldType === 'labeledresource'}
|
||||
{#if componentInput?.value && typeof componentInput?.value == 'object' && 'label' in componentInput?.value}
|
||||
<div class="flex flex-col gap-1 w-full">
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
placeholder="Label"
|
||||
type="text"
|
||||
bind:value={componentInput.value['label']}
|
||||
/>
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.['value']?.split('$res:')[1] || ''}
|
||||
on:change={(e) => {
|
||||
let path = e.detail
|
||||
|
||||
if (componentInput && path) {
|
||||
componentInput.value['value'] = `$res:${path}`
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
Inconsistent labeled resource object
|
||||
{/if}
|
||||
{:else if componentInput.fieldType === 'color'}
|
||||
<ColorInput bind:componentInput />
|
||||
{:else if componentInput.fieldType === 'object'}
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
<div class="w-full flex mt-1 items-center gap-2">
|
||||
<slot />
|
||||
<input
|
||||
on:keydown|stopPropagation
|
||||
type="text"
|
||||
placeholder="Search inline scripts"
|
||||
bind:value={filter}
|
||||
|
||||
@@ -17,7 +17,6 @@ export type InputType =
|
||||
| 'object'
|
||||
| 'array'
|
||||
| 'any'
|
||||
| 'labeledresource'
|
||||
|
||||
// Connection to an output of another component
|
||||
// defined by the id of the component and the path of the output
|
||||
@@ -140,11 +139,11 @@ export type AppInput =
|
||||
| AppInputSpec<'object', Record<string | number, any>>
|
||||
| AppInputSpec<'object', string>
|
||||
| (AppInputSpec<'select', string> & {
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
| (AppInputSpec<'select', string> & StaticOptions)
|
||||
| AppInputSpec<'icon-select', string>
|
||||
| AppInputSpec<'color', string>
|
||||
@@ -157,12 +156,9 @@ export type AppInput =
|
||||
| AppInputSpec<'array', string[], 'datetime'>
|
||||
| AppInputSpec<'array', object[], 'object'>
|
||||
| (AppInputSpec<'array', string[], 'select'> & {
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
|
||||
| AppInputSpec<'array', object[], 'labeledresource'>
|
||||
| AppInputSpec<'labeledresource', object>
|
||||
|
||||
|
||||
export type RowAppInput = Extract<AppInput, { type: 'row' }>
|
||||
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContainerHeight } from './utils/container'
|
||||
import { moveItemsAroundItem, moveItem, getItemById, specifyUndefinedColumns } from './utils/item'
|
||||
import { onMount, createEventDispatcher } from 'svelte'
|
||||
import { getColumn, throttle } from './utils/other'
|
||||
import MoveResize from './MoveResize.svelte'
|
||||
import type { FilledItem } from './types'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
type T = $$Generic
|
||||
|
||||
export let fillSpace = false
|
||||
export let items: FilledItem<T>[]
|
||||
export let rowHeight: number
|
||||
export let cols: [number, number][]
|
||||
export let gap = [10, 10]
|
||||
export let fastStart = false
|
||||
export let throttleUpdate = 100
|
||||
export let throttleResize = 100
|
||||
export let onTopId: string | undefined = undefined
|
||||
|
||||
export let scroller = undefined
|
||||
export let sensor = 20
|
||||
|
||||
export let parentWidth: number | undefined = undefined
|
||||
|
||||
let getComputedCols
|
||||
|
||||
let container
|
||||
|
||||
$: [gapX, gapY] = gap
|
||||
|
||||
let xPerPx = 0
|
||||
let yPerPx = rowHeight
|
||||
|
||||
let containerWidth
|
||||
|
||||
$: containerHeight = getContainerHeight(items, yPerPx, getComputedCols)
|
||||
|
||||
const pointerup = (ev) => {
|
||||
dispatch('pointerup', {
|
||||
id: ev.detail.id,
|
||||
cols: getComputedCols
|
||||
})
|
||||
}
|
||||
|
||||
const onResize = throttle(() => {
|
||||
items = specifyUndefinedColumns(items, getComputedCols, cols)
|
||||
dispatch('resize', {
|
||||
cols: getComputedCols,
|
||||
xPerPx,
|
||||
yPerPx,
|
||||
width: containerWidth
|
||||
})
|
||||
}, throttleUpdate)
|
||||
|
||||
onMount(() => {
|
||||
const sizeObserver = new ResizeObserver((entries) => {
|
||||
requestAnimationFrame(() => {
|
||||
let width = entries[0].contentRect.width
|
||||
|
||||
if (width === containerWidth) return
|
||||
|
||||
getComputedCols = getColumn(parentWidth ?? width, cols)
|
||||
|
||||
xPerPx = width / getComputedCols
|
||||
|
||||
if (!containerWidth) {
|
||||
items = specifyUndefinedColumns(items, getComputedCols, cols)
|
||||
|
||||
dispatch('mount', {
|
||||
cols: getComputedCols,
|
||||
xPerPx,
|
||||
yPerPx // same as rowHeight
|
||||
})
|
||||
} else {
|
||||
onResize()
|
||||
}
|
||||
|
||||
containerWidth = width
|
||||
})
|
||||
})
|
||||
|
||||
sizeObserver.observe(container)
|
||||
|
||||
return () => sizeObserver.disconnect()
|
||||
})
|
||||
|
||||
let initItems: FilledItem<T>[] | undefined = undefined
|
||||
const updateMatrix = ({ detail }) => {
|
||||
let isPointerUp = detail.isPointerUp
|
||||
let citems: FilledItem<T>[]
|
||||
if (isPointerUp) {
|
||||
try {
|
||||
citems = JSON.parse(JSON.stringify(initItems))
|
||||
} catch (e) {
|
||||
citems = JSON.parse(JSON.stringify(items))
|
||||
}
|
||||
initItems = undefined
|
||||
} else {
|
||||
if (initItems == undefined) {
|
||||
initItems = JSON.parse(JSON.stringify(items))
|
||||
}
|
||||
citems = JSON.parse(JSON.stringify(initItems))
|
||||
}
|
||||
|
||||
let activeItem = getItemById(detail.id, citems)
|
||||
|
||||
if (activeItem) {
|
||||
activeItem = {
|
||||
...activeItem,
|
||||
[getComputedCols]: {
|
||||
...activeItem[getComputedCols],
|
||||
...detail.shadow
|
||||
}
|
||||
}
|
||||
|
||||
if (fillSpace) {
|
||||
items = moveItemsAroundItem(
|
||||
activeItem,
|
||||
citems,
|
||||
getComputedCols,
|
||||
getItemById(detail.id, citems)
|
||||
)
|
||||
} else {
|
||||
items = moveItem(activeItem, citems, getComputedCols, getItemById(detail.id, citems))
|
||||
}
|
||||
|
||||
if (detail.onUpdate) detail.onUpdate()
|
||||
|
||||
dispatch('change', {
|
||||
unsafeItem: activeItem,
|
||||
id: activeItem.id,
|
||||
cols: getComputedCols
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const throttleMatrix = throttle(updateMatrix, throttleResize)
|
||||
|
||||
const handleRepaint = ({ detail }) => {
|
||||
if (!detail.isPointerUp) {
|
||||
throttleMatrix({ detail })
|
||||
} else {
|
||||
updateMatrix({ detail })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="svlt-grid-container" style="height: {containerHeight}px" bind:this={container}>
|
||||
{#if xPerPx || !fastStart}
|
||||
{#each items as item, i (item.id)}
|
||||
<MoveResize
|
||||
on:repaint={handleRepaint}
|
||||
on:pointerup={pointerup}
|
||||
onTop={item.id == onTopId}
|
||||
id={item.id}
|
||||
resizable={item[getComputedCols] && item[getComputedCols].resizable}
|
||||
draggable={item[getComputedCols] && item[getComputedCols].draggable}
|
||||
{xPerPx}
|
||||
{yPerPx}
|
||||
width={Math.min(getComputedCols, item[getComputedCols] && item[getComputedCols].w) *
|
||||
xPerPx -
|
||||
gapX * 2}
|
||||
height={(item[getComputedCols] && item[getComputedCols].h) * yPerPx - gapY * 2}
|
||||
top={(item[getComputedCols] && item[getComputedCols].y) * yPerPx + gapY}
|
||||
left={(item[getComputedCols] && item[getComputedCols].x) * xPerPx + gapX}
|
||||
item={item[getComputedCols]}
|
||||
min={item[getComputedCols] && item[getComputedCols].min}
|
||||
max={item[getComputedCols] && item[getComputedCols].max}
|
||||
cols={getComputedCols}
|
||||
{gapX}
|
||||
{gapY}
|
||||
{sensor}
|
||||
container={scroller}
|
||||
nativeContainer={container}
|
||||
let:resizePointerDown
|
||||
let:movePointerDown
|
||||
>
|
||||
{#if item[getComputedCols]}
|
||||
<slot
|
||||
{movePointerDown}
|
||||
{resizePointerDown}
|
||||
dataItem={item}
|
||||
item={item[getComputedCols]}
|
||||
index={i}
|
||||
/>
|
||||
{/if}
|
||||
</MoveResize>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.svlt-grid-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +0,0 @@
|
||||
Anything under this repo was initially forked from svelte-grid whose LICENSE is below
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Vahe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,404 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let sensor
|
||||
export let width
|
||||
export let height
|
||||
export let left
|
||||
export let top
|
||||
|
||||
export let resizable
|
||||
export let draggable
|
||||
|
||||
export let id
|
||||
export let container
|
||||
|
||||
export let xPerPx
|
||||
export let yPerPx
|
||||
|
||||
export let gapX
|
||||
export let gapY
|
||||
export let item
|
||||
|
||||
export let max
|
||||
export let min
|
||||
|
||||
export let cols
|
||||
|
||||
export let nativeContainer
|
||||
export let onTop
|
||||
|
||||
let shadowElement
|
||||
let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
|
||||
|
||||
let active = false
|
||||
|
||||
let initX, initY
|
||||
|
||||
let capturePos = {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
let cordDiff = { x: 0, y: 0 }
|
||||
|
||||
let newSize = { width, height }
|
||||
let trans = false
|
||||
|
||||
let anima
|
||||
|
||||
const inActivate = () => {
|
||||
if (shadowElement && shadow != undefined) {
|
||||
let subgrid = shadowElement.closest('.subgrid')
|
||||
let irect = shadowElement.getBoundingClientRect()
|
||||
let shadowBound
|
||||
|
||||
if (subgrid) {
|
||||
let subGridParent = subgrid.parentElement
|
||||
let subGridParentRect = subGridParent.getBoundingClientRect()
|
||||
let prect = subgrid.getBoundingClientRect()
|
||||
|
||||
const subGridOffset = {
|
||||
x: subGridParentRect.x - prect.x,
|
||||
y: subGridParentRect.y - prect.y
|
||||
}
|
||||
|
||||
shadowBound = {
|
||||
x: irect.x - prect.left - subGridOffset.x,
|
||||
y: irect.y - prect.top - subGridOffset.y
|
||||
}
|
||||
} else {
|
||||
shadowBound = irect
|
||||
}
|
||||
|
||||
const xdragBound = rect.left + cordDiff.x
|
||||
const ydragBound = rect.top + cordDiff.y
|
||||
|
||||
cordDiff.x = shadow.x * xPerPx + gapX - (shadowBound.x - xdragBound)
|
||||
cordDiff.y = shadow.y * yPerPx + gapY - (shadowBound.y - ydragBound)
|
||||
|
||||
active = false
|
||||
trans = true
|
||||
|
||||
clearTimeout(anima)
|
||||
|
||||
anima = setTimeout(() => {
|
||||
trans = false
|
||||
}, 100)
|
||||
|
||||
dispatch('pointerup', {
|
||||
id
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let repaint = (cb: (() => void) | undefined, isPointerUp: boolean) => {
|
||||
dispatch('repaint', {
|
||||
id,
|
||||
shadow,
|
||||
isPointerUp,
|
||||
onUpdate: cb
|
||||
})
|
||||
}
|
||||
|
||||
// Autoscroll
|
||||
let _scrollTop = 0
|
||||
let containerFrame
|
||||
let rect
|
||||
let scrollElement
|
||||
|
||||
const getContainerFrame = (element) => {
|
||||
if (element === document.documentElement || !element) {
|
||||
const { height, top, right, bottom, left } = nativeContainer.getBoundingClientRect()
|
||||
|
||||
return {
|
||||
top: Math.max(0, top),
|
||||
bottom: Math.min(window.innerHeight, bottom)
|
||||
}
|
||||
}
|
||||
|
||||
return element.getBoundingClientRect()
|
||||
}
|
||||
|
||||
const getScroller = (element) => (!element ? document.documentElement : element)
|
||||
|
||||
function computeRect(target) {
|
||||
let gridItem = target.closest('.svlt-grid-item')
|
||||
let subgrid = gridItem.closest('.subgrid')
|
||||
|
||||
let irect = gridItem.getBoundingClientRect()
|
||||
if (subgrid) {
|
||||
let subGridParent = subgrid.parentElement
|
||||
let subGridParentRect = subGridParent.getBoundingClientRect()
|
||||
|
||||
let prect = subgrid.getBoundingClientRect()
|
||||
|
||||
const subGridOffset = {
|
||||
x: subGridParentRect.x - prect.x,
|
||||
y: subGridParentRect.y - prect.y
|
||||
}
|
||||
|
||||
rect = {
|
||||
top: irect.top - prect.top - subGridOffset.y,
|
||||
left: irect.left - prect.left - subGridOffset.x
|
||||
}
|
||||
} else {
|
||||
rect = irect
|
||||
}
|
||||
}
|
||||
|
||||
const pointerdown = ({ clientX, clientY, target }) => {
|
||||
initX = clientX
|
||||
initY = clientY
|
||||
|
||||
capturePos = { x: left, y: top }
|
||||
shadow = { x: item.x, y: item.y, w: item.w, h: item.h }
|
||||
newSize = { width, height }
|
||||
|
||||
containerFrame = getContainerFrame(container)
|
||||
scrollElement = getScroller(container)
|
||||
|
||||
cordDiff = { x: 0, y: 0 }
|
||||
|
||||
computeRect(target)
|
||||
|
||||
active = true
|
||||
trans = false
|
||||
_scrollTop = scrollElement.scrollTop
|
||||
|
||||
window.addEventListener('pointermove', pointermove)
|
||||
window.addEventListener('pointerup', pointerup)
|
||||
}
|
||||
|
||||
let sign = { x: 0, y: 0 }
|
||||
let vel = { x: 0, y: 0 }
|
||||
let intervalId: NodeJS.Timer | undefined = undefined
|
||||
|
||||
const stopAutoscroll = () => {
|
||||
intervalId && clearInterval(intervalId)
|
||||
intervalId = undefined
|
||||
sign = { x: 0, y: 0 }
|
||||
vel = { x: 0, y: 0 }
|
||||
}
|
||||
|
||||
const update = () => {
|
||||
const _newScrollTop = scrollElement.scrollTop - _scrollTop
|
||||
|
||||
const boundX = capturePos.x + cordDiff.x
|
||||
const boundY = capturePos.y + (cordDiff.y + _newScrollTop)
|
||||
|
||||
let gridX = Math.round(boundX / xPerPx)
|
||||
let gridY = Math.round(boundY / yPerPx)
|
||||
|
||||
if (shadow) {
|
||||
shadow.x = Math.max(Math.min(gridX, cols - shadow.w), 0)
|
||||
shadow.y = Math.max(gridY, 0)
|
||||
|
||||
if (max.y) {
|
||||
shadow.y = Math.min(shadow.y, max.y)
|
||||
}
|
||||
}
|
||||
repaint(undefined, false)
|
||||
}
|
||||
|
||||
const pointermove = (event) => {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
event.stopImmediatePropagation()
|
||||
|
||||
const { clientX, clientY } = event
|
||||
cordDiff = { x: clientX - initX, y: clientY - initY }
|
||||
|
||||
const Y_SENSOR = sensor
|
||||
|
||||
let velocityTop = Math.max(0, (containerFrame.top + Y_SENSOR - clientY) / Y_SENSOR)
|
||||
let velocityBottom = Math.max(0, (clientY - (containerFrame.bottom - Y_SENSOR)) / Y_SENSOR)
|
||||
|
||||
const topSensor = velocityTop > 0 && velocityBottom === 0
|
||||
const bottomSensor = velocityBottom > 0 && velocityTop === 0
|
||||
|
||||
sign.y = topSensor ? -1 : bottomSensor ? 1 : 0
|
||||
vel.y = sign.y === -1 ? velocityTop : velocityBottom
|
||||
|
||||
if (vel.y > 0) {
|
||||
if (!intervalId) {
|
||||
// Start scrolling
|
||||
// TODO Use requestAnimationFrame
|
||||
intervalId = setInterval(() => {
|
||||
scrollElement.scrollTop += 2 * (vel.y + Math.sign(vel.y)) * sign.y
|
||||
update()
|
||||
}, 10)
|
||||
}
|
||||
} else if (intervalId) {
|
||||
stopAutoscroll()
|
||||
} else {
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
const pointerup = (e) => {
|
||||
stopAutoscroll()
|
||||
|
||||
window.removeEventListener('pointerdown', pointerdown)
|
||||
window.removeEventListener('pointermove', pointermove)
|
||||
window.removeEventListener('pointerup', pointerup)
|
||||
repaint(inActivate, true)
|
||||
}
|
||||
|
||||
// Resize
|
||||
|
||||
let resizeInitPos = { x: 0, y: 0 }
|
||||
let initSize = { width: 0, height: 0 }
|
||||
|
||||
const resizePointerDown = (e) => {
|
||||
e.stopPropagation()
|
||||
const { pageX, pageY, target } = e
|
||||
|
||||
resizeInitPos = { x: pageX, y: pageY }
|
||||
initSize = { width, height }
|
||||
|
||||
cordDiff = { x: 0, y: 0 }
|
||||
|
||||
computeRect(target)
|
||||
|
||||
newSize = { width, height }
|
||||
|
||||
active = true
|
||||
trans = false
|
||||
shadow = { x: item.x, y: item.y, w: item.w, h: item.h }
|
||||
|
||||
containerFrame = getContainerFrame(container)
|
||||
scrollElement = getScroller(container)
|
||||
|
||||
window.addEventListener('pointermove', resizePointerMove)
|
||||
window.addEventListener('pointerup', resizePointerUp)
|
||||
}
|
||||
|
||||
const resizePointerMove = ({ pageX, pageY }) => {
|
||||
if (shadow) {
|
||||
newSize.width = initSize.width + pageX - resizeInitPos.x
|
||||
newSize.height = initSize.height + pageY - resizeInitPos.y
|
||||
|
||||
// Get max col number
|
||||
let maxWidth = cols - shadow.x
|
||||
maxWidth = Math.min(max.w, maxWidth) || maxWidth
|
||||
|
||||
// Limit bound
|
||||
newSize.width = Math.max(
|
||||
Math.min(newSize.width, maxWidth * xPerPx - gapX * 2),
|
||||
min.w * xPerPx - gapX * 2
|
||||
)
|
||||
|
||||
newSize.height = Math.max(newSize.height, min.h * yPerPx - gapY * 2)
|
||||
|
||||
if (max.h) {
|
||||
newSize.height = Math.min(newSize.height, max.h * yPerPx - gapY * 2)
|
||||
}
|
||||
// Limit col & row
|
||||
shadow.w = Math.round((newSize.width + gapX * 2) / xPerPx)
|
||||
shadow.h = Math.round((newSize.height + gapY * 2) / yPerPx)
|
||||
|
||||
repaint(undefined, false)
|
||||
}
|
||||
}
|
||||
|
||||
const resizePointerUp = (e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
repaint(inActivate, true)
|
||||
|
||||
window.removeEventListener('pointermove', resizePointerMove)
|
||||
window.removeEventListener('pointerup', resizePointerUp)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
draggable={false}
|
||||
on:pointerdown={item && item.customDragger ? null : draggable && pointerdown}
|
||||
class="svlt-grid-item"
|
||||
class:svlt-grid-active={active || (trans && rect)}
|
||||
style="width: {active ? newSize.width : width}px; height:{active
|
||||
? newSize.height
|
||||
: height}px; {onTop ? 'z-index: 100;' : ''}
|
||||
{active
|
||||
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px);top:${rect.top}px;left:${rect.left}px;`
|
||||
: trans
|
||||
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
|
||||
: `transition: transform 0.2s, opacity 0.2s; transform: translate(${left}px, ${top}px); `} "
|
||||
>
|
||||
<slot movePointerDown={pointerdown} {resizePointerDown} />
|
||||
{#if resizable && !item.customResizer}
|
||||
<div class="svlt-grid-resizer" on:pointerdown={resizePointerDown} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if (active || trans) && shadow}
|
||||
<div
|
||||
class="svlt-grid-shadow shadow-active"
|
||||
style="width: {shadow.w * xPerPx - gapX * 2}px; height: {shadow.h * yPerPx -
|
||||
gapY * 2}px; transform: translate({shadow.x * xPerPx + gapX}px, {shadow.y * yPerPx +
|
||||
gapY}px); "
|
||||
bind:this={shadowElement}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.svlt-grid-item {
|
||||
touch-action: none;
|
||||
position: absolute;
|
||||
will-change: auto;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
|
||||
.svlt-grid-resizer {
|
||||
user-select: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
cursor: se-resize;
|
||||
}
|
||||
.svlt-grid-resizer::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
bottom: 3px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-right: 2px solid rgba(0, 0, 0, 0.4);
|
||||
border-bottom: 2px solid rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.svlt-grid-active {
|
||||
z-index: 3;
|
||||
cursor: grabbing;
|
||||
position: fixed;
|
||||
opacity: 0.5;
|
||||
|
||||
/*No user*/
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
-moz-backface-visibility: hidden;
|
||||
-o-backface-visibility: hidden;
|
||||
-ms-backface-visibility: hidden;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.shadow-active {
|
||||
z-index: 2;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.svlt-grid-shadow {
|
||||
position: absolute;
|
||||
background: red;
|
||||
will-change: transform;
|
||||
background: pink;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
export interface Size {
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
|
||||
export interface Positon {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
interface ItemLayout extends Size, Positon {
|
||||
fixed?: boolean
|
||||
resizable?: boolean
|
||||
draggable?: boolean
|
||||
customDragger?: boolean
|
||||
customResizer?: boolean
|
||||
min?: Size
|
||||
max?: Size
|
||||
}
|
||||
|
||||
export type FilledItem<T> = T & { [width: number]: Required<ItemLayout>; data: any, id: string }
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { getRowsCount } from "./other";
|
||||
|
||||
export function getContainerHeight(items, yPerPx, cols) {
|
||||
return getRowsCount(items, cols) * yPerPx;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { makeMatrixFromItems } from "./matrix";
|
||||
import { findFreeSpaceForItem, normalize, adjust } from "./item";
|
||||
import { getRowsCount } from "./other";
|
||||
|
||||
function makeItem(item) {
|
||||
const { min = { w: 1, h: 1 }, max } = item;
|
||||
return {
|
||||
fixed: false,
|
||||
resizable: !item.fixed,
|
||||
draggable: !item.fixed,
|
||||
customDragger: false,
|
||||
customResizer: false,
|
||||
min: {
|
||||
w: Math.max(1, min.w),
|
||||
h: Math.max(1, min.h),
|
||||
},
|
||||
max: { ...max },
|
||||
...item,
|
||||
};
|
||||
}
|
||||
|
||||
const gridHelp = {
|
||||
normalize(items, col) {
|
||||
return normalize(items, col);
|
||||
},
|
||||
|
||||
adjust(items, col) {
|
||||
return adjust(items, col);
|
||||
},
|
||||
|
||||
item(obj) {
|
||||
return makeItem(obj);
|
||||
},
|
||||
|
||||
findSpace(item, items, cols) {
|
||||
let matrix = makeMatrixFromItems(items, getRowsCount(items, cols), cols);
|
||||
|
||||
let position = findFreeSpaceForItem(matrix, item[cols]);
|
||||
return position;
|
||||
},
|
||||
};
|
||||
|
||||
export default gridHelp;
|
||||
@@ -1,260 +0,0 @@
|
||||
import type { FilledItem, ItemLayout } from '../types'
|
||||
import {
|
||||
makeMatrix,
|
||||
makeMatrixFromItemsIgnore,
|
||||
findCloseBlocks,
|
||||
findItemsById,
|
||||
makeMatrixFromItems
|
||||
} from './matrix'
|
||||
import { getRowsCount } from './other'
|
||||
|
||||
export function getItemById(id, items) {
|
||||
return items.find((value) => value.id === id)
|
||||
}
|
||||
|
||||
export function isEmpty(matrix: any[][], x: number, y: number, w: number, h: number) {
|
||||
for (var i = 0; i < h; i++) {
|
||||
if (matrix[y + i]) {
|
||||
for (var j = 0; j < w; j++) {
|
||||
if (matrix[y + i][x + j] != undefined) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function distance(a, b): number {
|
||||
return Math.abs(a.x - b.x + 0.25) + Math.abs(a.y - b.y + 0.25)
|
||||
}
|
||||
|
||||
export function findFreeSpaceForItem<T>(matrix: FilledItem<T>[][], item: ItemLayout) {
|
||||
const cols = matrix[0].length
|
||||
const w = Math.min(cols, item.w)
|
||||
const h = item.h
|
||||
let xNtime = cols - w
|
||||
let getMatrixRows = matrix.length
|
||||
|
||||
const range = Array.from({ length: getMatrixRows }, (_, y) =>
|
||||
Array.from({ length: xNtime }, (_, x) => ({ x, y }))
|
||||
)
|
||||
.flat(1)
|
||||
.sort((a, b) => {
|
||||
let dst1 = distance(a, item)
|
||||
let dst2 = distance(b, item)
|
||||
if (dst1 > dst2) {
|
||||
return 1
|
||||
} else if (dst1 < dst2) {
|
||||
return -1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
for (const { x, y } of range.values()) {
|
||||
if (isEmpty(matrix, x, y, w, h)) {
|
||||
return { x, y }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
y: getMatrixRows,
|
||||
x: 0
|
||||
}
|
||||
}
|
||||
|
||||
const getItem = (item, col) => {
|
||||
return { ...item[col], id: item.id }
|
||||
}
|
||||
|
||||
const updateItem = (elements, active, position, col) => {
|
||||
return elements.map((value) => {
|
||||
if (value.id === active.id) {
|
||||
return { ...value, [col]: { ...value[col], ...position } }
|
||||
}
|
||||
return value
|
||||
})
|
||||
}
|
||||
|
||||
export function moveItemsAroundItem(active, items, cols, original) {
|
||||
// Get current item from the breakpoint
|
||||
const activeItem = getItem(active, cols)
|
||||
const ids = items.map((value) => value.id).filter((value) => value !== activeItem.id)
|
||||
|
||||
const els = items.filter((value) => value.id !== activeItem.id)
|
||||
|
||||
// Update items
|
||||
let newItems = updateItem(items, active, activeItem, cols)
|
||||
|
||||
let matrix = makeMatrixFromItemsIgnore(newItems, ids, getRowsCount(newItems, cols), cols)
|
||||
let tempItems = newItems
|
||||
|
||||
// Exclude resolved elements ids in array
|
||||
let exclude: string[] = []
|
||||
|
||||
els.forEach((item) => {
|
||||
// Find position for element
|
||||
let position = findFreeSpaceForItem(matrix, item[cols])
|
||||
// Exclude item
|
||||
exclude.push(item.id)
|
||||
|
||||
tempItems = updateItem(tempItems, item, position, cols)
|
||||
|
||||
// Recreate ids of elements
|
||||
let getIgnoreItems = ids.filter((value) => exclude.indexOf(value) === -1)
|
||||
|
||||
// Update matrix for next iteration
|
||||
matrix = makeMatrixFromItemsIgnore(
|
||||
tempItems,
|
||||
getIgnoreItems,
|
||||
getRowsCount(tempItems, cols),
|
||||
cols
|
||||
)
|
||||
})
|
||||
|
||||
// Return result
|
||||
return tempItems
|
||||
}
|
||||
|
||||
export function moveItem(active, items, cols, original) {
|
||||
// Get current item from the breakpoint
|
||||
const item = getItem(active, cols)
|
||||
|
||||
// Create matrix from the items expect the active
|
||||
let matrix = makeMatrixFromItemsIgnore(items, [item.id], getRowsCount(items, cols), cols)
|
||||
// Getting the ids of items under active Array<String>
|
||||
const closeBlocks = findCloseBlocks(matrix, item)
|
||||
// Getting the objects of items under active Array<Object>
|
||||
let closeObj = findItemsById(closeBlocks, items)
|
||||
// Getting whenever of these items is fixed
|
||||
const fixed = closeObj.find((value) => value[cols].fixed)
|
||||
|
||||
// If found fixed, reset the active to its original position
|
||||
if (fixed) return items
|
||||
|
||||
// Update items
|
||||
items = updateItem(items, active, item, cols)
|
||||
|
||||
// Create matrix of items expect close elements
|
||||
matrix = makeMatrixFromItemsIgnore(items, closeBlocks, getRowsCount(items, cols), cols)
|
||||
|
||||
// Create temp vars
|
||||
let tempItems = items
|
||||
let tempCloseBlocks = closeBlocks
|
||||
|
||||
// Exclude resolved elements ids in array
|
||||
let exclude: string[] = []
|
||||
|
||||
// Iterate over close elements under active item
|
||||
closeObj.forEach((item) => {
|
||||
// Find position for element
|
||||
let position = findFreeSpaceForItem(matrix, item[cols])
|
||||
// Exclude item
|
||||
exclude.push(item.id)
|
||||
|
||||
// Assign the position to the element in the column
|
||||
tempItems = updateItem(tempItems, item, position, cols)
|
||||
|
||||
// Recreate ids of elements
|
||||
let getIgnoreItems = tempCloseBlocks.filter((value) => exclude.indexOf(value) === -1)
|
||||
|
||||
// Update matrix for next iteration
|
||||
matrix = makeMatrixFromItemsIgnore(
|
||||
tempItems,
|
||||
getIgnoreItems,
|
||||
getRowsCount(tempItems, cols),
|
||||
cols
|
||||
)
|
||||
})
|
||||
|
||||
// Return result
|
||||
return tempItems
|
||||
}
|
||||
|
||||
// Helper function
|
||||
export function normalize(items, col) {
|
||||
let result = items.slice()
|
||||
|
||||
result.forEach((value) => {
|
||||
const getItem = value[col]
|
||||
if (!getItem.static) {
|
||||
result = moveItem(getItem, result, col, { ...getItem })
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Helper function
|
||||
export function adjust<T>(items: FilledItem<T>[], col) {
|
||||
let matrix = makeMatrix(getRowsCount(items, col), col)
|
||||
|
||||
let res: FilledItem<T>[] = []
|
||||
|
||||
items.forEach((item) => {
|
||||
let position = findFreeSpaceForItem(matrix, item[col])
|
||||
|
||||
res.push({
|
||||
...item,
|
||||
[col]: {
|
||||
...item[col],
|
||||
...position
|
||||
}
|
||||
})
|
||||
|
||||
matrix = makeMatrixFromItems(res, getRowsCount(res, col), col)
|
||||
})
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export function getUndefinedItems(items, col, breakpoints) {
|
||||
return items
|
||||
.map((value) => {
|
||||
if (!value[col]) {
|
||||
return value.id
|
||||
}
|
||||
})
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
export function getClosestColumn(items, item, col, breakpoints) {
|
||||
return breakpoints
|
||||
.map(([_, column]) => item[column] && column)
|
||||
.filter(Boolean)
|
||||
.reduce(function (acc, value) {
|
||||
const isLower = Math.abs(value - col) < Math.abs(acc - col)
|
||||
|
||||
return isLower ? value : acc
|
||||
})
|
||||
}
|
||||
|
||||
export function specifyUndefinedColumns(items, col, breakpoints) {
|
||||
let matrix = makeMatrixFromItems(items, getRowsCount(items, col), col)
|
||||
|
||||
const getUndefinedElements = getUndefinedItems(items, col, breakpoints)
|
||||
|
||||
let newItems = [...items]
|
||||
|
||||
getUndefinedElements.forEach((elementId) => {
|
||||
const getElement = items.find((item) => item.id === elementId)
|
||||
|
||||
const closestColumn = getClosestColumn(items, getElement, col, breakpoints)
|
||||
|
||||
const position = findFreeSpaceForItem(matrix, getElement[closestColumn])
|
||||
|
||||
const newItem = {
|
||||
...getElement,
|
||||
[col]: {
|
||||
...getElement[closestColumn],
|
||||
...position
|
||||
}
|
||||
}
|
||||
|
||||
newItems = newItems.map((value) => (value.id === elementId ? newItem : value))
|
||||
|
||||
matrix = makeMatrixFromItems(newItems, getRowsCount(newItems, col), col)
|
||||
})
|
||||
return newItems
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import type { FilledItem } from "../types";
|
||||
|
||||
export const makeMatrix: (w: number, h: number) => any[][] = (rows, cols) => Array.from(Array(rows), () => new Array(cols)); // make 2d array
|
||||
|
||||
export function makeMatrixFromItems<T>(items: FilledItem<T>[], row: number, col: number): FilledItem<T>[][] {
|
||||
let matrix = makeMatrix(row, col);
|
||||
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
const value = items[i][col];
|
||||
if (value) {
|
||||
const { x, y, h } = value;
|
||||
const id = items[i].id;
|
||||
const w = Math.min(col, value.w);
|
||||
|
||||
for (var j = y; j < y + h; j++) {
|
||||
const row = matrix[j];
|
||||
for (var k = x; k < x + w; k++) {
|
||||
row[k] = { ...value, id };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
export function findCloseBlocks<T>(matrix: FilledItem<T>[][], curObject) {
|
||||
const { h, x, y } = curObject;
|
||||
|
||||
const w = Math.min(matrix[0].length, curObject.w);
|
||||
const tempR = matrix.slice(y, y + h);
|
||||
|
||||
let result: string[] = [];
|
||||
for (var i = 0; i < tempR.length; i++) {
|
||||
let tempA = tempR[i].slice(x, x + w);
|
||||
result = [...result, ...tempA.map((val) => val?.id).filter((id) => id !== undefined && id !== curObject.id)];
|
||||
}
|
||||
|
||||
return [...new Set(result)];
|
||||
}
|
||||
|
||||
export function makeMatrixFromItemsIgnore(items, ignoreList, _row, _col) {
|
||||
let matrix = makeMatrix(_row, _col);
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
const value = items[i][_col];
|
||||
const id = items[i].id;
|
||||
const { x, y, h } = value;
|
||||
const w = Math.min(_col, value.w);
|
||||
|
||||
if (ignoreList.indexOf(id) === -1) {
|
||||
for (var j = y; j < y + h; j++) {
|
||||
const row = matrix[j];
|
||||
if (row) {
|
||||
for (var k = x; k < x + w; k++) {
|
||||
row[k] = { ...value, id };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return matrix;
|
||||
}
|
||||
|
||||
export function findItemsById<T>(closeBlocks: string[], items: FilledItem<T>[]) {
|
||||
return items.filter((value) => closeBlocks.indexOf(value.id) !== -1);
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
export function throttle(func, timeFrame) {
|
||||
let lastTime = new Date().getTime();
|
||||
return function (...args) {
|
||||
let now = new Date().getTime();
|
||||
if (now - lastTime >= timeFrame) {
|
||||
func(...args);
|
||||
lastTime = now;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function getRowsCount(items, cols) {
|
||||
const getItemsMaxHeight = items.map((val) => {
|
||||
const item = val[cols];
|
||||
|
||||
return (item && item.y) + (item && item.h) || 0;
|
||||
});
|
||||
|
||||
return Math.max(...getItemsMaxHeight, 1);
|
||||
}
|
||||
|
||||
export const getColumn = (containerWidth, columns) => {
|
||||
const sortColumns = columns.slice().sort((a, b) => a[0] - b[0]);
|
||||
|
||||
const breakpoint = sortColumns.find((value) => {
|
||||
const [width] = value;
|
||||
return containerWidth <= width;
|
||||
});
|
||||
|
||||
if (breakpoint) {
|
||||
return breakpoint[1];
|
||||
} else {
|
||||
return sortColumns[sortColumns.length - 1][1];
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import type { History } from '$lib/history'
|
||||
|
||||
import type { FilledItem } from '@windmill-labs/svelte-grid'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type VariableEditor from '../VariableEditor.svelte'
|
||||
import type { AppComponent } from './editor/component/components'
|
||||
import type {
|
||||
AppInput,
|
||||
@@ -15,7 +16,6 @@ import type {
|
||||
UserAppInput
|
||||
} from './inputType'
|
||||
import type { World } from './rx'
|
||||
import type { FilledItem } from './svelte-grid/types'
|
||||
|
||||
export type HorizontalAlignment = 'left' | 'center' | 'right'
|
||||
export type VerticalAlignment = 'top' | 'center' | 'bottom'
|
||||
@@ -44,14 +44,14 @@ export interface BaseAppComponent extends Partial<Aligned> {
|
||||
configuration: Record<
|
||||
string,
|
||||
GeneralAppInput &
|
||||
(
|
||||
| StaticAppInput
|
||||
| ConnectedAppInput
|
||||
| UserAppInput
|
||||
| RowAppInput
|
||||
| EvalAppInput
|
||||
| UploadAppInput
|
||||
)
|
||||
(
|
||||
| StaticAppInput
|
||||
| ConnectedAppInput
|
||||
| UserAppInput
|
||||
| RowAppInput
|
||||
| EvalAppInput
|
||||
| UploadAppInput
|
||||
)
|
||||
>
|
||||
card: boolean | undefined
|
||||
customCss?: ComponentCustomCSS
|
||||
@@ -84,9 +84,9 @@ export type GridItem = FilledItem<{
|
||||
|
||||
export type InlineScript = {
|
||||
content: string
|
||||
language: Preview.language | 'frontend'
|
||||
path?: string
|
||||
schema?: Schema
|
||||
language: Preview.language
|
||||
path: string
|
||||
schema: Schema
|
||||
}
|
||||
|
||||
export type App = {
|
||||
@@ -100,7 +100,6 @@ export type App = {
|
||||
name: string
|
||||
inlineScript: InlineScript | undefined
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
autoRefresh?: boolean
|
||||
}>
|
||||
css?: Record<'viewer' | 'grid' | AppComponent['type'], ComponentCustomCSS>
|
||||
subgrids?: Record<string, GridItem[]>
|
||||
@@ -135,11 +134,10 @@ export type AppViewerContext = {
|
||||
focusedGrid: Writable<FocusedGrid | undefined>
|
||||
stateId: Writable<number>
|
||||
parentWidth: Writable<number>
|
||||
state: Writable<Record<string, any>>
|
||||
}
|
||||
|
||||
export type AppEditorContext = {
|
||||
history: History<App> | undefined
|
||||
history: History<App>
|
||||
componentControl: Writable<Record<string, { left?: () => boolean; right?: () => boolean }>>
|
||||
pickVariableCallback: Writable<((path: string) => void) | undefined>
|
||||
}
|
||||
|
||||
@@ -162,24 +162,21 @@ export function toStatic(
|
||||
export function buildExtraLib(
|
||||
components: Record<string, Record<string, Output<any>>>,
|
||||
idToExclude: string,
|
||||
hasRows: boolean,
|
||||
state: Record<string, any>,
|
||||
goto: boolean
|
||||
hasRows: boolean
|
||||
): string {
|
||||
const cs = Object.entries(components)
|
||||
.filter(([k, v]) => k != idToExclude)
|
||||
.map(([k, v]) => [k, Object.fromEntries(Object.entries(v).map(([k, v]) => [k, v.peak()]))])
|
||||
.map(
|
||||
([k, v]) => `declare const ${k}: ${JSON.stringify(v)};
|
||||
`
|
||||
)
|
||||
.join('\n')
|
||||
return (
|
||||
Object.entries(components)
|
||||
.filter(([k, v]) => k != idToExclude)
|
||||
.map(([k, v]) => [k, Object.fromEntries(Object.entries(v).map(([k, v]) => [k, v.peak()]))])
|
||||
.map(
|
||||
([k, v]) => `
|
||||
|
||||
declare const ${k} = ${JSON.stringify(v)};
|
||||
|
||||
return `${cs}
|
||||
${hasRows ? 'declare const row: Record<string, any>;' : ''}
|
||||
${goto ? 'declare const goto: async (path: string, newTab?: boolean) => void)' : ''}
|
||||
declare const state: ${JSON.stringify(state)};
|
||||
`
|
||||
)
|
||||
.join('\n') + (hasRows ? 'declare const row: Record<string, any>;' : '')
|
||||
)
|
||||
}
|
||||
|
||||
export function getAllScriptNames(app: App): string[] {
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
<script>
|
||||
import { faClose } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import { Icon } from 'svelte-awesome'
|
||||
|
||||
export let noBg = false
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="hover:bg-gray-200 {noBg
|
||||
? ''
|
||||
: 'bg-gray-100'} rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<Icon data={faClose} class="text-gray-500" />
|
||||
</button>
|
||||
@@ -9,7 +9,6 @@
|
||||
export let href = ''
|
||||
export let rounded = false
|
||||
export let dismissable = false
|
||||
export let wrapperClass = ''
|
||||
export let baseClass = 'text-center'
|
||||
export let capitalize = false
|
||||
export let icon: BadgeIconProps | undefined = undefined
|
||||
@@ -57,11 +56,7 @@
|
||||
const handleHide = () => (hidden = !hidden)
|
||||
</script>
|
||||
|
||||
<span
|
||||
on:click
|
||||
on:keydown
|
||||
class="inline-flex justify-center items-center whitespace-nowrap {wrapperClass}"
|
||||
>
|
||||
<span on:click on:keydown class="inline-flex justify-center items-center whitespace-nowrap">
|
||||
<svelte:element
|
||||
this={href ? 'a' : 'span'}
|
||||
{href}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { faClose } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import CloseButton from '../CloseButton.svelte'
|
||||
|
||||
export let title: string | undefined = undefined
|
||||
export let overflow_y = true
|
||||
@@ -16,7 +15,12 @@
|
||||
<div class="flex flex-col divide-y h-screen max-h-screen">
|
||||
<div class="flex justify-between w-wull items-center px-2 py-2 ">
|
||||
<div class="flex items-center gap-2">
|
||||
<CloseButton on:close />
|
||||
<button
|
||||
on:click={() => dispatch('close')}
|
||||
class="hover:bg-gray-200 bg-gray-100 rounded-full w-8 h-8 flex items-center justify-center transition-all"
|
||||
>
|
||||
<Icon data={faClose} class="text-gray-500" />
|
||||
</button>
|
||||
|
||||
<span class="font-semibold truncate text-gray-800">{title}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<script>
|
||||
export let width = 16
|
||||
export let height = 16
|
||||
</script>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" {width} {height} viewBox="0 0 630 630">
|
||||
<rect width="630" height="630" fill="#f7df1e" />
|
||||
<path
|
||||
d="m423.2 492.19c12.69 20.72 29.2 35.95 58.4 35.95 24.53 0 40.2-12.26 40.2-29.2 0-20.3-16.1-27.49-43.1-39.3l-14.8-6.35c-42.72-18.2-71.1-41-71.1-89.2 0-44.4 33.83-78.2 86.7-78.2 37.64 0 64.7 13.1 84.2 47.4l-46.1 29.6c-10.15-18.2-21.1-25.37-38.1-25.37-17.34 0-28.33 11-28.33 25.37 0 17.76 11 24.95 36.4 35.95l14.8 6.34c50.3 21.57 78.7 43.56 78.7 93 0 53.3-41.87 82.5-98.1 82.5-54.98 0-90.5-26.2-107.88-60.54zm-209.13 5.13c9.3 16.5 17.76 30.45 38.1 30.45 19.45 0 31.72-7.61 31.72-37.2v-201.3h59.2v202.1c0 61.3-35.94 89.2-88.4 89.2-47.4 0-74.85-24.53-88.81-54.075z"
|
||||
/>
|
||||
</svg>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user