Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
020e5f7b32 | ||
|
|
1414030afe | ||
|
|
92bf928b78 | ||
|
|
0bcc036a84 | ||
|
|
6277188c1b | ||
|
|
65c9d43419 | ||
|
|
cc5744ee2d | ||
|
|
1e796881b3 | ||
|
|
a55d99a8b7 | ||
|
|
07477f9e35 | ||
|
|
9409d5e266 | ||
|
|
2d71ebbe09 | ||
|
|
c1673ac036 | ||
|
|
8ca54e02c0 | ||
|
|
cd25570003 | ||
|
|
e95f8ef6bf | ||
|
|
c3d1c8ac39 | ||
|
|
d38aff2fe2 | ||
|
|
95851ea486 | ||
|
|
b690d801d4 | ||
|
|
104e4ac5e7 | ||
|
|
e87f4fc44b | ||
|
|
e1f686d850 | ||
|
|
7da7dac3ac | ||
|
|
c2e5afd4e0 | ||
|
|
ad9c386f41 | ||
|
|
a4e3f98b7d | ||
|
|
dd28308c3c | ||
|
|
833c2655ea | ||
|
|
a8295d0b5a | ||
|
|
897e2f6b53 | ||
|
|
5bb77edf45 | ||
|
|
8ddcf4d9c1 | ||
|
|
33ebe2da8e | ||
|
|
b3ee747014 | ||
|
|
fa105b4cae | ||
|
|
483407cdf0 | ||
|
|
008c30fcaa | ||
|
|
3387bb0d83 | ||
|
|
e08e7e4ae6 | ||
|
|
ea1b2c29b9 | ||
|
|
4ad6fbefd3 | ||
|
|
397ecd64d4 | ||
|
|
dd7e8c742c | ||
|
|
2f78132e08 | ||
|
|
0041411c06 | ||
|
|
54955b710c | ||
|
|
1eb5a0d1d3 | ||
|
|
ddda14c52b | ||
|
|
53ddf013df | ||
|
|
5123c9365c | ||
|
|
834e7b1d1c | ||
|
|
6e9a5b026e | ||
|
|
7ad8879b09 | ||
|
|
27cac3ffe6 | ||
|
|
705703a5e2 | ||
|
|
fac31c6628 | ||
|
|
90c0e140a1 | ||
|
|
d543650b31 | ||
|
|
089a6b6ae5 | ||
|
|
857ee5f318 | ||
|
|
6ad876ebb4 | ||
|
|
ab4137640e | ||
|
|
2bd8fabcf7 | ||
|
|
3b7160e84a | ||
|
|
40c12e6139 | ||
|
|
6044e3b6ef | ||
|
|
18ff5c7cef | ||
|
|
e54dc3ff97 | ||
|
|
4d5aae69c8 | ||
|
|
ec57c5977f | ||
|
|
df1b724626 |
108
CHANGELOG.md
108
CHANGELOG.md
@@ -1,6 +1,114 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [1.70.1](https://github.com/windmill-labs/windmill/compare/v1.70.0...v1.70.1) (2023-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** make cli resilient to systems without openable browsers ([c051ffe](https://github.com/windmill-labs/windmill/commit/c051ffeb42c1cff609f93da7745036ea722e17d4))
|
||||
* **frontend:** Disable move in nested subgrid ([#1238](https://github.com/windmill-labs/windmill/issues/1238)) ([70eab30](https://github.com/windmill-labs/windmill/commit/70eab303bd45111ae198d9b710bfd6f9f59e53b0))
|
||||
* **frontend:** Fix inline scripts list ([#1240](https://github.com/windmill-labs/windmill/issues/1240)) ([97602ac](https://github.com/windmill-labs/windmill/commit/97602ac6db1404d36d160a431ffcea6c0f567a48))
|
||||
* **frontend:** Fix subgrid lock ([#1232](https://github.com/windmill-labs/windmill/issues/1232)) ([8ee9d67](https://github.com/windmill-labs/windmill/commit/8ee9d67f4faa91446338b41c664ef91913eb8b81))
|
||||
|
||||
## [1.70.1](https://github.com/windmill-labs/windmill/compare/v1.70.0...v1.70.1) (2023-02-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** make cli resilient to systems without openable browsers ([c051ffe](https://github.com/windmill-labs/windmill/commit/c051ffeb42c1cff609f93da7745036ea722e17d4))
|
||||
* **frontend:** Disable move in nested subgrid ([#1238](https://github.com/windmill-labs/windmill/issues/1238)) ([70eab30](https://github.com/windmill-labs/windmill/commit/70eab303bd45111ae198d9b710bfd6f9f59e53b0))
|
||||
* **frontend:** Fix subgrid lock ([#1232](https://github.com/windmill-labs/windmill/issues/1232)) ([8ee9d67](https://github.com/windmill-labs/windmill/commit/8ee9d67f4faa91446338b41c664ef91913eb8b81))
|
||||
|
||||
## [1.70.0](https://github.com/windmill-labs/windmill/compare/v1.69.3...v1.70.0) (2023-02-27)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **apps:** add ag grid ([b690d80](https://github.com/windmill-labs/windmill/commit/b690d801d4aa5695ee558e81d1ed114074dfcb83))
|
||||
* **frontend:** move to other grid ([#1230](https://github.com/windmill-labs/windmill/issues/1230)) ([104e4ac](https://github.com/windmill-labs/windmill/commit/104e4ac5e790c30e6fb6b27726776693038d4f19))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* app setup and sync now uses 1.69.3 ([d38aff2](https://github.com/windmill-labs/windmill/commit/d38aff2fe228f23eb18c3991392928c064e6aca2))
|
||||
* **frontend:** Fix duplication ([#1237](https://github.com/windmill-labs/windmill/issues/1237)) ([e87f4fc](https://github.com/windmill-labs/windmill/commit/e87f4fc44b847a573f5acafc0348fbcbfcb2258f))
|
||||
* **frontend:** fix graph viewer id assignment ([e1f686d](https://github.com/windmill-labs/windmill/commit/e1f686d8508cfc1f73c43be08facc44217ca8de0))
|
||||
|
||||
## [1.69.3](https://github.com/windmill-labs/windmill/compare/v1.69.2...v1.69.3) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deno:** fix denoify buffer handling ([c2e5afd](https://github.com/windmill-labs/windmill/commit/c2e5afd4e07fb63375832f308da8c744616ee188))
|
||||
|
||||
## [1.69.2](https://github.com/windmill-labs/windmill/compare/v1.69.1...v1.69.2) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **app:** fix all nested behavior ([dd28308](https://github.com/windmill-labs/windmill/commit/dd28308c3cf1877ba3f19dcd2bd20bf1c7896a99))
|
||||
* **frontend:** delete grid item ([008c30f](https://github.com/windmill-labs/windmill/commit/008c30fcaad64af512407f9889a9881fafac0868))
|
||||
* **frontend:** duplicate ([483407c](https://github.com/windmill-labs/windmill/commit/483407cdf0e1ed61de180a904934e950fed4adc3))
|
||||
* **frontend:** Fix findGridItem ([a8295d0](https://github.com/windmill-labs/windmill/commit/a8295d0b5acd08cec42b7939d907df5c25132644))
|
||||
* **frontend:** Fix findGridItem ([5bb77ed](https://github.com/windmill-labs/windmill/commit/5bb77edf45740a75e969b1bef31580271c9d5505))
|
||||
* **frontend:** Fix next id ([8ddcf4d](https://github.com/windmill-labs/windmill/commit/8ddcf4d9c1a8d6dd20ee241a3f308811c49e58f1))
|
||||
* **frontend:** gridtab ([fa105b4](https://github.com/windmill-labs/windmill/commit/fa105b4caeaa2d0e9704a48f6caf8d846839c23e))
|
||||
* **frontend:** rewrote utils ([ea1b2c2](https://github.com/windmill-labs/windmill/commit/ea1b2c29b95282df347ef9c5973917fa3880e843))
|
||||
* **frontend:** wip ([33ebe2d](https://github.com/windmill-labs/windmill/commit/33ebe2da8e81476be62a2567d5012573a8a010b6))
|
||||
|
||||
## [1.69.1](https://github.com/windmill-labs/windmill/compare/v1.69.0...v1.69.1) (2023-02-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deno:** remove mysql support waiting for deno fix ([dd7e8c7](https://github.com/windmill-labs/windmill/commit/dd7e8c742c83f6a1d13e4343ca626c0b5efc06fb))
|
||||
* **deno:** remove mysql support waiting for deno fix ([2f78132](https://github.com/windmill-labs/windmill/commit/2f78132e081bdf3d7468e022f0e981ebfa52cfb3))
|
||||
* **frontend:** containers and tab fixes v1 ([27cac3f](https://github.com/windmill-labs/windmill/commit/27cac3ffe69c4dac160e9e55ffd1eb8ea348d487))
|
||||
* **frontend:** containers and tab fixes v1 ([705703a](https://github.com/windmill-labs/windmill/commit/705703a5e2f2dc7ceb4c215221f72bf624799841))
|
||||
* **frontend:** containers and tab fixes v1 ([fac31c6](https://github.com/windmill-labs/windmill/commit/fac31c6628b289ad6aae92434e312c4be281a4d2))
|
||||
|
||||
## [1.69.0](https://github.com/windmill-labs/windmill/compare/v1.68.0...v1.69.0) (2023-02-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** Duplicate component ([#1228](https://github.com/windmill-labs/windmill/issues/1228)) ([089a6b6](https://github.com/windmill-labs/windmill/commit/089a6b6ae52e8d28dd15e2f9a6ad900c5853d0a1))
|
||||
* **frontend:** Properly delete tab content ([#1227](https://github.com/windmill-labs/windmill/issues/1227)) ([857ee5f](https://github.com/windmill-labs/windmill/commit/857ee5f318466d12bf0d41515451798df087ab74))
|
||||
* **frontend:** Support deeply nested components ([#1225](https://github.com/windmill-labs/windmill/issues/1225)) ([6ad876e](https://github.com/windmill-labs/windmill/commit/6ad876ebb45a934b7a4dc980cf38a5228d7d11f1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** .wmillignore whitelist behavior ([d543650](https://github.com/windmill-labs/windmill/commit/d543650b313c434e794ad800aefe4aeda83c0fed))
|
||||
|
||||
## [1.68.0](https://github.com/windmill-labs/windmill/compare/v1.67.4...v1.68.0) (2023-02-23)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** Add more app component CSS customisation ([#1218](https://github.com/windmill-labs/windmill/issues/1218)) ([6044e3b](https://github.com/windmill-labs/windmill/commit/6044e3b6ef92e89b8f15f38bc2d0986ec64105d5))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** better ergonomics around workspace add ([40c12e6](https://github.com/windmill-labs/windmill/commit/40c12e6139c7b42d7ab169bab2dd37f8b43bea06))
|
||||
* **cli:** better ergonomics around workspaces ([3b7160e](https://github.com/windmill-labs/windmill/commit/3b7160e84aa454bdb5f343da99cfd97a6b319937))
|
||||
|
||||
## [1.67.4](https://github.com/windmill-labs/windmill/compare/v1.67.3...v1.67.4) (2023-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** workflow check for has_failure_module ([e54dc3f](https://github.com/windmill-labs/windmill/commit/e54dc3ff97e4454a15b9efe25cc12f6c9e1e176b))
|
||||
|
||||
## [1.67.3](https://github.com/windmill-labs/windmill/compare/v1.67.2...v1.67.3) (2023-02-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **cli:** ignone non wmill looking files ([ec57c59](https://github.com/windmill-labs/windmill/commit/ec57c5977f122b629a07e05bc3551662d518ce30))
|
||||
|
||||
## [1.67.2](https://github.com/windmill-labs/windmill/compare/v1.67.1...v1.67.2) (2023-02-23)
|
||||
|
||||
|
||||
|
||||
108
backend/Cargo.lock
generated
108
backend/Cargo.lock
generated
@@ -255,9 +255,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.7"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
|
||||
checksum = "6137c6234afb339e75e764c866e3594900f0211e1315d33779f269bbe2ec6967"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -324,9 +324,9 @@ checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.5.3"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "better_scoped_tls"
|
||||
@@ -553,9 +553,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.3.1"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
|
||||
checksum = "350b9cf31731f9957399229e9b2adc51eeabdfbe9d71d9a0552275fd12710d09"
|
||||
dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
@@ -581,9 +581,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b"
|
||||
checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913"
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
@@ -619,7 +619,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
"time 0.3.19",
|
||||
"time 0.3.20",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@@ -987,9 +987,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.0"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
|
||||
checksum = "b2e5d13ca2353ab7d0230988629def93914a8c4015f621f9b13ed2955614731d"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
@@ -1651,9 +1651,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
|
||||
checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.1",
|
||||
"io-lifetimes",
|
||||
@@ -2149,18 +2149,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.5.10"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e0072973714303aa6e3631c7e8e777970cf4bdd25dc4932e41031027b8bcc4e"
|
||||
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.5.10"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0629cbd6b897944899b1f10496d9c4a7ac5878d45fd61bc22e9e79bfbbc29597"
|
||||
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
@@ -2798,15 +2798,6 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.14"
|
||||
@@ -3003,7 +2994,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#4a579b5752355de4e9efc63f9c1351c12ae90556"
|
||||
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -3012,7 +3003,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#4a579b5752355de4e9efc63f9c1351c12ae90556"
|
||||
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"bitflags",
|
||||
@@ -3029,7 +3020,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#4a579b5752355de4e9efc63f9c1351c12ae90556"
|
||||
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -3083,9 +3074,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.11"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a5fb6c61f29e723026dc8e923d94c694313212abbecbbe5f55a7748eec5b307"
|
||||
checksum = "02c613288622e5f0c3fdc5dbd4db1c5fbe752746b1d1a56a0630b78fd00de44f"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
@@ -3097,9 +3088,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.11"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f188d036977451159430f3b8dc82ec76364a42b7e289c2b18a9a18f4470058e9"
|
||||
checksum = "109da1e6b197438deb6db99952990c7f959572794b80ff93707d55a232545e7c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3823,9 +3814,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.108"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d56e159d99e6c2b93995d171050271edb50ecc5288fbc7cc17de8fdce4e58c14"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3840,16 +3831,15 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
"rustix",
|
||||
"windows-sys 0.42.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3926,9 +3916,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.19"
|
||||
version = "0.3.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53250a3b3fed8ff8fd988587d8925d26a83ac3845d9e03b220b37f34c2b8d6c2"
|
||||
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
@@ -3944,9 +3934,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.7"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a460aeb8de6dcb0f381e1ee05f1cd56fcf5a5f6eb8187ff3d8f0b11078d38b7c"
|
||||
checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
@@ -4153,9 +4143,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.3.5"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
|
||||
checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
@@ -4766,7 +4756,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4793,7 +4783,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4826,7 +4816,7 @@ dependencies = [
|
||||
"sql-builder",
|
||||
"sqlx",
|
||||
"tempfile",
|
||||
"time 0.3.19",
|
||||
"time 0.3.20",
|
||||
"tokio",
|
||||
"tokio-tar",
|
||||
"tokio-util",
|
||||
@@ -4848,7 +4838,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4863,7 +4853,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4876,7 +4866,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4901,7 +4891,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4909,7 +4899,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4923,7 +4913,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4935,7 +4925,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4950,7 +4940,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4964,7 +4954,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -4987,7 +4977,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.67.2"
|
||||
version = "1.70.1"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.67.2
|
||||
version: 1.70.1
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use crate::BASE_URL;
|
||||
use crate::{
|
||||
apps::AppWithLastVersion,
|
||||
@@ -20,7 +20,7 @@ use crate::{
|
||||
utils::require_super_admin,
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use axum::response::Redirect;
|
||||
use axum::{
|
||||
body::StreamBody,
|
||||
@@ -30,7 +30,7 @@ use axum::{
|
||||
routing::{delete, get, post},
|
||||
Json, Router,
|
||||
};
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
use stripe::CustomerId;
|
||||
use windmill_audit::{audit_log, ActionKind};
|
||||
use windmill_common::{
|
||||
@@ -63,12 +63,13 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/tarball", get(tarball_workspace))
|
||||
.route("/premium_info", get(premium_info));
|
||||
|
||||
#[cfg(enterprise)]
|
||||
let router = {
|
||||
router
|
||||
.route("/checkout", get(stripe_checkout))
|
||||
.route("/billing_portal", get(stripe_portal));
|
||||
};
|
||||
#[cfg(feature = "enterprise")]
|
||||
tracing::info!("stripe enabled");
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
let router = router
|
||||
.route("/checkout", get(stripe_checkout))
|
||||
.route("/billing_portal", get(stripe_portal));
|
||||
|
||||
router
|
||||
}
|
||||
@@ -230,13 +231,13 @@ async fn premium_info(
|
||||
Ok(Json(row))
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
#[derive(Deserialize)]
|
||||
struct PlanQuery {
|
||||
plan: String,
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn stripe_checkout(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
@@ -302,7 +303,7 @@ async fn stripe_checkout(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(enterprise)]
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn stripe_portal(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
@@ -676,19 +677,19 @@ async fn create_workspace(
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
let mc = magic_crypt::new_magic_crypt!(key, 256);
|
||||
sqlx::query!(
|
||||
"INSERT INTO variable
|
||||
(workspace_id, path, value, is_secret, description)
|
||||
VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'),
|
||||
($3, 'g/all/not_secret', $4, false, 'This item is not secret')",
|
||||
nw.id,
|
||||
crate::variables::encrypt(&mc, "pretty secret value"),
|
||||
nw.id,
|
||||
"finland does not actually exist",
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
// let mc = magic_crypt::new_magic_crypt!(key, 256);
|
||||
// sqlx::query!(
|
||||
// "INSERT INTO variable
|
||||
// (workspace_id, path, value, is_secret, description)
|
||||
// VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'),
|
||||
// ($3, 'g/all/not_secret', $4, false, 'This item is not secret')",
|
||||
// nw.id,
|
||||
// crate::variables::encrypt(&mc, "pretty secret value"),
|
||||
// nw.id,
|
||||
// "finland does not actually exist",
|
||||
// )
|
||||
// .execute(&mut tx)
|
||||
// .await?;
|
||||
|
||||
sqlx::query!(
|
||||
"INSERT INTO usr
|
||||
|
||||
@@ -351,7 +351,10 @@ pub async fn push<'c>(
|
||||
.unwrap_or(false);
|
||||
|
||||
if !is_super_admin {
|
||||
if usage > MAX_FREE_EXECS {
|
||||
if usage > MAX_FREE_EXECS
|
||||
&& !matches!(job_payload, JobPayload::Dependencies { .. })
|
||||
&& !matches!(job_payload, JobPayload::FlowDependencies { .. })
|
||||
{
|
||||
return Err(error::Error::BadRequest(format!(
|
||||
"User {email} has exceeded the free usage limit of {MAX_FREE_EXECS} that applies outside of premium workspaces."
|
||||
)));
|
||||
@@ -466,10 +469,10 @@ pub async fn push<'c>(
|
||||
}
|
||||
JobPayload::Flow(flow) => {
|
||||
let value_json = sqlx::query_scalar!(
|
||||
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
flow,
|
||||
workspace_id
|
||||
)
|
||||
"SELECT value FROM flow WHERE path = $1 AND workspace_id = $2",
|
||||
flow,
|
||||
workspace_id
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.ok_or_else(|| Error::InternalErr(format!("not found flow at path {:?}", flow)))?;
|
||||
|
||||
@@ -383,7 +383,7 @@ lazy_static::lazy_static! {
|
||||
.unwrap();
|
||||
static ref WORKER_UPTIME_OPTS: prometheus::Opts = prometheus::opts!(
|
||||
"worker_uptime",
|
||||
"Total number of milliseconds since the worker has started"
|
||||
"Total number of seconds since the worker has started"
|
||||
);
|
||||
|
||||
static ref TIMEOUT: u16 = std::env::var("TIMEOUT")
|
||||
@@ -445,15 +445,11 @@ pub async fn run_worker(
|
||||
|
||||
insert_initial_ping(worker_instance, &worker_name, ip, db).await;
|
||||
|
||||
let uptime_metric = prometheus::register_int_counter!(WORKER_UPTIME_OPTS
|
||||
let uptime_metric = prometheus::register_counter!(WORKER_UPTIME_OPTS
|
||||
.clone()
|
||||
.const_label("name", &worker_name))
|
||||
.unwrap();
|
||||
uptime_metric.inc_by(
|
||||
((Instant::now() - start_time).as_millis() - uptime_metric.get() as u128)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
|
||||
let worker_execution_duration = prometheus::register_histogram_vec!(
|
||||
prometheus::HistogramOpts::new(
|
||||
@@ -465,6 +461,14 @@ pub async fn run_worker(
|
||||
)
|
||||
.expect("register prometheus metric");
|
||||
|
||||
let worker_execution_duration_counter = prometheus::register_counter!(prometheus::opts!(
|
||||
"worker_execution_duration_counter",
|
||||
"Total number of seconds spent executing jobs"
|
||||
)
|
||||
.const_label("name", &worker_name))
|
||||
.expect("register prometheus metric");
|
||||
|
||||
|
||||
let worker_sleep_duration = prometheus::register_histogram!(prometheus::HistogramOpts::new(
|
||||
"worker_sleep_duration",
|
||||
"Duration sleeping waiting for job",
|
||||
@@ -472,6 +476,15 @@ pub async fn run_worker(
|
||||
.const_label("name", &worker_name),)
|
||||
.expect("register prometheus metric");
|
||||
|
||||
|
||||
let worker_sleep_duration_counter = prometheus::register_counter!(prometheus::opts!(
|
||||
"worker_execution_sleep_counter",
|
||||
"Total number of seconds spent sleeping between pulling jobs from the queue"
|
||||
)
|
||||
.const_label("name", &worker_name))
|
||||
.expect("register prometheus metric");
|
||||
|
||||
|
||||
let worker_pull_duration = prometheus::register_histogram!(prometheus::HistogramOpts::new(
|
||||
"worker_pull_duration",
|
||||
"Duration pulling next job",
|
||||
@@ -479,6 +492,13 @@ pub async fn run_worker(
|
||||
.const_label("name", &worker_name),)
|
||||
.expect("register prometheus metric");
|
||||
|
||||
let worker_pull_duration_counter = prometheus::register_counter!(prometheus::opts!(
|
||||
"worker_pull_sleep_counter",
|
||||
"Total number of seconds spent pulling jobs (if growing large the db is undersized)"
|
||||
)
|
||||
.const_label("name", &worker_name))
|
||||
.expect("register prometheus metric");
|
||||
|
||||
let worker_execution_failed = prometheus::register_int_counter_vec!(
|
||||
prometheus::Opts::new("worker_execution_failed", "Number of failed jobs",)
|
||||
.const_label("name", &worker_name),
|
||||
@@ -526,11 +546,12 @@ pub async fn run_worker(
|
||||
worker_busy.set(0);
|
||||
|
||||
uptime_metric.inc_by(
|
||||
((Instant::now() - start_time).as_millis() - uptime_metric.get() as u128)
|
||||
(((Instant::now() - start_time).as_millis() as f64)/1000.0 - uptime_metric.get())
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
|
||||
let do_break = async {
|
||||
if last_ping.elapsed().as_secs() > NUM_SECS_ENV_CHECK {
|
||||
sqlx::query!(
|
||||
@@ -574,7 +595,8 @@ pub async fn run_worker(
|
||||
(job, timer) = {
|
||||
let timer = worker_pull_duration.start_timer();
|
||||
pull(&db, WHITELIST_WORKSPACES.clone(), BLACKLIST_WORKSPACES.clone()).map(|x| (x, timer)) } => {
|
||||
drop(timer);
|
||||
let duration_pull_s = timer.stop_and_record();
|
||||
worker_pull_duration_counter.inc_by(duration_pull_s);
|
||||
(false, job)
|
||||
},
|
||||
}
|
||||
@@ -680,6 +702,8 @@ pub async fn run_worker(
|
||||
.await;
|
||||
};
|
||||
|
||||
let duration = _timer.stop_and_record();
|
||||
worker_execution_duration_counter.inc_by(duration);
|
||||
|
||||
if !*KEEP_JOB_DIR && !(is_flow && same_worker) {
|
||||
let _ = tokio::fs::remove_dir_all(job_dir).await;
|
||||
@@ -689,9 +713,9 @@ pub async fn run_worker(
|
||||
|
||||
let _timer = worker_sleep_duration
|
||||
.start_timer();
|
||||
|
||||
tokio::time::sleep(Duration::from_millis(*SLEEP_QUEUE)).await;
|
||||
|
||||
let duration = _timer.stop_and_record();
|
||||
worker_sleep_duration_counter.inc_by(duration);
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::error!(worker = %worker_name, "run_worker: pulling jobs: {}", err);
|
||||
|
||||
@@ -557,7 +557,7 @@ async fn has_failure_module<'c>(
|
||||
flow: Uuid,
|
||||
tx: &mut sqlx::Transaction<'c, sqlx::Postgres>,
|
||||
) -> Result<bool, Error> {
|
||||
sqlx::query_scalar(
|
||||
sqlx::query_scalar::<_, Option<bool>>(
|
||||
"
|
||||
SELECT raw_flow->'failure_module' != 'null'::jsonb
|
||||
FROM queue
|
||||
@@ -568,6 +568,7 @@ async fn has_failure_module<'c>(
|
||||
.fetch_one(tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("error during retrieval of has_failure_module: {e}")))
|
||||
.map(|v| v.unwrap_or(false))
|
||||
}
|
||||
|
||||
fn next_retry(retry: &Retry, status: &RetryStatus) -> Option<(u16, Duration)> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// windmill
|
||||
export { setClient } from "https://deno.land/x/windmill@v1.66.0/mod.ts";
|
||||
export * from "https://deno.land/x/windmill@v1.66.0/windmill-api/index.ts";
|
||||
export { setClient } from "https://deno.land/x/windmill@v1.69.3/mod.ts";
|
||||
export * from "https://deno.land/x/windmill@v1.69.3/windmill-api/index.ts";
|
||||
|
||||
// cliffy
|
||||
export { Command } from "https://deno.land/x/cliffy@v0.25.7/command/command.ts";
|
||||
|
||||
@@ -142,7 +142,7 @@ async function push(opts: Options, filePath: string, remotePath: string) {
|
||||
await requireLogin(opts);
|
||||
|
||||
await pushFlow(filePath, workspace.remote, remotePath);
|
||||
console.log(colors.bold.underline.green("Flow successfully pushed"));
|
||||
console.log(colors.bold.underline.green("Flow pushed"));
|
||||
}
|
||||
|
||||
export async function pushFlow(
|
||||
|
||||
@@ -133,7 +133,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushFolder(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
console.log(colors.bold.underline.green("Resource pushed"));
|
||||
}
|
||||
|
||||
export async function pushFolder(
|
||||
|
||||
@@ -51,9 +51,11 @@ export async function browserLogin(
|
||||
const url = `${baseUrl}user/cli?port=${port}`
|
||||
console.log(`Login by going to ${url}`);
|
||||
try {
|
||||
open(url)
|
||||
await open(url)
|
||||
console.log("Opened browser for you");
|
||||
} catch { }
|
||||
} catch {
|
||||
console.error(`Failed to open browser, please navigate to ${url}`)
|
||||
}
|
||||
const firstConnection = await server.accept();
|
||||
const httpFirstConnection = Deno.serveHttp(firstConnection);
|
||||
const firstRequest = (await httpFirstConnection.nextRequest())!;
|
||||
|
||||
@@ -13,7 +13,7 @@ import sync from "./sync.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
const VERSION = "v1.67.2";
|
||||
const VERSION = "v1.70.1";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function downloadZip(
|
||||
|
||||
const zipResponse = await fetch(
|
||||
workspace.remote + "api/w/" + workspace.workspaceId +
|
||||
"/workspaces/tarball?archive_type=zip",
|
||||
"/workspaces/tarball?archive_type=zip",
|
||||
{
|
||||
headers: requestHeaders,
|
||||
method: "GET",
|
||||
@@ -25,8 +25,7 @@ export async function downloadZip(
|
||||
"Failed to request tarball from API " + zipResponse.statusText,
|
||||
),
|
||||
);
|
||||
console.log(await zipResponse.text());
|
||||
return undefined;
|
||||
throw new Error(await zipResponse.text());
|
||||
}
|
||||
const blob = await zipResponse.blob();
|
||||
return await JSZip.loadAsync(blob);
|
||||
|
||||
@@ -137,7 +137,7 @@ async function push(opts: PushOptions, filePath: string, name: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushResourceType(workspace.workspaceId, filePath, name);
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
console.log(colors.bold.underline.green("Resource pushed"));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
|
||||
@@ -155,7 +155,7 @@ async function push(opts: PushOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushResource(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
console.log(colors.bold.underline.green(`Resource ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
|
||||
@@ -76,7 +76,7 @@ async function push(
|
||||
|
||||
await requireLogin(opts);
|
||||
await pushScript(filePath, contentPath, workspace.workspaceId, remotePath);
|
||||
console.log(colors.bold.underline.green("Script successfully pushed"));
|
||||
console.log(colors.bold.underline.green(`Script ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
export async function handleScriptMetadata(path: string, workspace: string, alreadySynced: string[]): Promise<boolean> {
|
||||
|
||||
28
cli/sync.ts
28
cli/sync.ts
@@ -112,7 +112,7 @@ function ZipFSElement(zip: JSZip): DynFSElement {
|
||||
}
|
||||
|
||||
async function* readDirRecursiveWithIgnore(
|
||||
ignore: (path: string) => boolean,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
root: DynFSElement,
|
||||
): AsyncGenerator<
|
||||
{
|
||||
@@ -132,7 +132,7 @@ async function* readDirRecursiveWithIgnore(
|
||||
getContentText(): Promise<string>;
|
||||
}[] = [{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path),
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
getContentBytes(): Promise<Uint8Array> {
|
||||
@@ -150,7 +150,7 @@ async function* readDirRecursiveWithIgnore(
|
||||
for await (const e2 of e.c()) {
|
||||
stack.push({
|
||||
path: e2.path,
|
||||
ignored: e.ignored || ignore(e2.path),
|
||||
ignored: e.ignored || ignore(e2.path, e2.isDirectory),
|
||||
isDirectory: e2.isDirectory,
|
||||
getContentBytes: e2.getContentBytes,
|
||||
getContentText: e2.getContentText,
|
||||
@@ -166,7 +166,7 @@ type Edit = { name: "edited"; path: string; before: string; after: string; };
|
||||
|
||||
type Change = Added | Deleted | Edit;
|
||||
|
||||
async function elementsToMap(els: DynFSElement, ignore: (path: string) => 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,
|
||||
@@ -180,7 +180,7 @@ async function elementsToMap(els: DynFSElement, ignore: (path: string) => boolea
|
||||
}
|
||||
async function compareDynFSElement(
|
||||
els1: DynFSElement, els2: DynFSElement,
|
||||
ignore: (path: string) => boolean,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
raw: boolean
|
||||
): Promise<Change[]> {
|
||||
|
||||
@@ -211,10 +211,14 @@ async function compareDynFSElement(
|
||||
return changes
|
||||
}
|
||||
|
||||
const isNotWmillFile = (p: string) => {
|
||||
if (p == "./") {
|
||||
const isNotWmillFile = (p: string, isDirectory: boolean) => {
|
||||
if (p.endsWith("/")) {
|
||||
return false
|
||||
}
|
||||
if (isDirectory) {
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
|
||||
}
|
||||
|
||||
try {
|
||||
const typ = getTypeStrFromPath(p)
|
||||
if (typ == 'resource-type') {
|
||||
@@ -227,6 +231,9 @@ const isNotWmillFile = (p: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
const isWhitelisted = (p: string) => {
|
||||
return p == "./" || p == "" || p == "u" || p == "f" || p == "g"
|
||||
}
|
||||
async function ignoreF() {
|
||||
try {
|
||||
const ignore: {
|
||||
@@ -235,9 +242,12 @@ async function ignoreF() {
|
||||
} = gitignore_parser.compile(
|
||||
await Deno.readTextFile(".wmillignore"),
|
||||
);
|
||||
return (p: string) => isNotWmillFile(p) || ignore.denies(p);
|
||||
|
||||
return (p: string, isDirectory: boolean) => {
|
||||
return !isWhitelisted(p) && (isNotWmillFile(p, isDirectory) || ignore.denies(p));
|
||||
}
|
||||
} catch (e) {
|
||||
return isNotWmillFile
|
||||
return (p: string, isDirectory: boolean) => !isWhitelisted(p) && isNotWmillFile(p, isDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing variable..."));
|
||||
|
||||
await pushVariable(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green("Variable successfully pushed"));
|
||||
console.log(colors.bold.underline.green(`Variable ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
export async function pushVariable(
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"lock": "",
|
||||
"path": null,
|
||||
"type": "rawscript",
|
||||
"content": "// import * as wmill from \"https://deno.land/x/windmill@v1.50.0/mod.ts\"\n\nexport async function main(x: string) {\n console.log(\"Hello from Deno! The argument x is \" + x);\n return x;\n}\n",
|
||||
"content": "// import * as wmill from \"https://deno.land/x/windmill@v1.69.3/mod.ts\"\n\nexport async function main(x: string) {\n console.log(\"Hello from Deno! The argument x is \" + x);\n return x;\n}\n",
|
||||
"language": "deno",
|
||||
"input_transforms": {
|
||||
"x": {
|
||||
|
||||
@@ -151,7 +151,11 @@ async function switchC(opts: GlobalOptions, workspaceName: string) {
|
||||
|
||||
const all = await allWorkspaces();
|
||||
if (all.findIndex((x) => x.name === workspaceName) === -1) {
|
||||
console.log(colors.red.bold("! This workspace name does not exist."));
|
||||
console.log(colors.red.bold(`! This workspace profile ${workspaceName} does not exist locally.`));
|
||||
console.log("available workspace profiles:")
|
||||
for (const w of all) {
|
||||
console.log(' - ' + w.name)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,12 +188,6 @@ export async function add(
|
||||
workspaceName = await Input.prompt("Name this workspace:");
|
||||
}
|
||||
|
||||
const all = await allWorkspaces();
|
||||
if (all.findIndex((x) => x.name === workspaceName) !== -1) {
|
||||
console.log(colors.red.bold("! Workspace name already exists"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
workspaceId = await Input.prompt({ message: "Enter the ID of this workspace", default: workspaceName, suggestions: [workspaceName] });
|
||||
}
|
||||
@@ -219,18 +217,24 @@ export async function add(
|
||||
token = await loginInteractive(remote);
|
||||
}
|
||||
|
||||
setClient(
|
||||
token,
|
||||
remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote,
|
||||
);
|
||||
let alreadyExists = false
|
||||
try {
|
||||
alreadyExists = await WorkspaceService.existsWorkspace({
|
||||
requestBody: { id: workspaceId },
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(colors.red.bold("! Credentials or instance is invalid. Aborting."));
|
||||
throw e
|
||||
}
|
||||
if (opts.create) {
|
||||
setClient(
|
||||
token,
|
||||
remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote,
|
||||
);
|
||||
|
||||
if (
|
||||
!await WorkspaceService.existsWorkspace({
|
||||
requestBody: { id: workspaceId },
|
||||
})
|
||||
!alreadyExists
|
||||
) {
|
||||
console.log(colors.yellow("Workspace does not exist. Creating..."));
|
||||
console.log(colors.yellow(`Workspace at id ${workspaceId} on ${remote} does not exist. Creating...`));
|
||||
await WorkspaceService.createWorkspace({
|
||||
requestBody: {
|
||||
id: workspaceId,
|
||||
@@ -239,6 +243,14 @@ export async function add(
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (!alreadyExists) {
|
||||
console.log(colors.red.bold(`! Workspace at id ${workspaceId} on ${remote} does not exist. Re-run with --create to create it. Aborting.`));
|
||||
console.log("On that instance and with those credentials, the workspaces that you can access are:")
|
||||
const workspaces = await WorkspaceService.listWorkspaces()
|
||||
for (const workspace of workspaces) {
|
||||
console.log(`- ${workspace.id} (name: ${workspace.name})`)
|
||||
}
|
||||
Deno.exit(1);
|
||||
}
|
||||
|
||||
await addWorkspace({
|
||||
@@ -246,28 +258,40 @@ export async function add(
|
||||
remote: remote,
|
||||
workspaceId: workspaceId,
|
||||
token: token,
|
||||
});
|
||||
}, opts);
|
||||
await Deno.writeTextFile(
|
||||
(await getRootStore()) + "/activeWorkspace",
|
||||
workspaceName,
|
||||
);
|
||||
console.log(colors.green.underline("Succesfully added workspace!"));
|
||||
console.log(colors.green.underline(`Added workspace ${workspaceName} for ${workspaceId} on ${remote}!`));
|
||||
}
|
||||
|
||||
export async function addWorkspace(workspace: Workspace) {
|
||||
export async function addWorkspace(workspace: Workspace, opts: any) {
|
||||
workspace.remote = new URL(workspace.remote).toString(); // add trailing slash in all cases!
|
||||
const file = await Deno.open((await getRootStore()) + "remotes.ndjson", {
|
||||
append: true,
|
||||
write: true,
|
||||
read: false,
|
||||
read: true,
|
||||
create: true,
|
||||
});
|
||||
await removeWorkspace(workspace.name, true, opts);
|
||||
await file.write(new TextEncoder().encode(JSON.stringify(workspace) + "\n"));
|
||||
file.close();
|
||||
}
|
||||
|
||||
export async function removeWorkspace(name: string) {
|
||||
export async function removeWorkspace(name: string, silent: boolean, opts: any) {
|
||||
const orgWorkspaces = await allWorkspaces();
|
||||
if (orgWorkspaces.findIndex((x) => x.name === name) === -1) {
|
||||
if (!silent) {
|
||||
console.log(colors.red.bold(`! Workspace profile ${name} does not exist locally`));
|
||||
console.log("available workspace profiles:")
|
||||
await list(opts)
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (silent) {
|
||||
console.log(colors.yellow(`Replacing existing workspace ${name}`))
|
||||
}
|
||||
await Deno.writeTextFile(
|
||||
(await getRootStore()) + "remotes.ndjson",
|
||||
orgWorkspaces
|
||||
@@ -275,11 +299,13 @@ export async function removeWorkspace(name: string) {
|
||||
.map((x) => JSON.stringify(x))
|
||||
.join("\n"),
|
||||
);
|
||||
if (!silent) {
|
||||
console.log(colors.green.underline(`Succesfully removed workspace ${name}!`));
|
||||
}
|
||||
}
|
||||
|
||||
async function remove(_opts: GlobalOptions, name: string) {
|
||||
await removeWorkspace(name);
|
||||
console.log(colors.green.underline("Succesfully removed workspace!"));
|
||||
await removeWorkspace(name, false, _opts);
|
||||
}
|
||||
|
||||
const command = new Command()
|
||||
|
||||
@@ -4,8 +4,9 @@ set -e
|
||||
npm ci --ignore-scripts
|
||||
npx --yes openapi-typescript-codegen --input ../backend/windmill-api/openapi.yaml \
|
||||
--output ./src --useOptions \
|
||||
&& sed -i '213 i \\ request.referrerPolicy = \"no-referrer\"\n' src/core/request.ts
|
||||
npx --yes denoify
|
||||
&& sed -i '213 i \\ request.referrerPolicy = \"no-referrer\"\n' src/core/request.ts
|
||||
npx --yes denoify
|
||||
rm -rf windmill-api
|
||||
mv deno_dist windmill-api
|
||||
sed -i 's/buffer DENOIFY: UNKNOWN NODE BUILTIN/node:buffer/' ./windmill-api/core/request.ts
|
||||
rm -rf src/
|
||||
|
||||
@@ -14,7 +14,6 @@ BigInt.prototype.toJSON = function () {
|
||||
};
|
||||
|
||||
export { pgSql, pgClient } from './pg.ts'
|
||||
export { mySql, mysqlClient } from './mysql.ts'
|
||||
|
||||
export type Sql = string
|
||||
export type Email = string
|
||||
|
||||
61
frontend/package-lock.json
generated
61
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.67.2",
|
||||
"version": "1.70.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill",
|
||||
"version": "1.67.2",
|
||||
"version": "1.70.1",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
@@ -14,11 +14,13 @@
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.7.6",
|
||||
"ag-grid-svelte": "^0.1.4",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chart.js": "^3.9.1",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"fast-equals": "^4.0.3",
|
||||
"highlight.js": "^11.7.0",
|
||||
"lucide-svelte": "^0.115.0",
|
||||
"monaco-editor": "0.33.0",
|
||||
@@ -43,7 +45,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.3",
|
||||
"@windmill-labs/svelte-grid": "^5.1.4",
|
||||
"@windmill-labs/svelvet": "^4.0.20",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
@@ -1263,9 +1265,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@windmill-labs/svelte-grid": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.3.tgz",
|
||||
"integrity": "sha512-SzfuJ2qYkBYC73x3l+x9l7WcgU+kMlRoFkCgSCOfggi3z5zJU9eX1VYsV1wDjtQVU9OUaAciv1xlUR4OPxrFlg==",
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.4.tgz",
|
||||
"integrity": "sha512-alxQ8su/osmHodl1JMta+0dvlc2qnYwiXsoeC8Ys8hrLywD03y5tjxYV0L0WXvOnMKG1S9GbqGuDVCk+UmVuKg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@windmill-labs/svelvet": {
|
||||
@@ -1357,6 +1359,23 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ag-grid-community": {
|
||||
"version": "28.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-28.2.1.tgz",
|
||||
"integrity": "sha512-DMZh/xD/FqYP17qJ1M92PolTYe+hrKuEaf+A4h13O6qn2x/xZQrTRGW5DgnQLR/uLMe1XXZQPKR3UKgAlKo69A==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ag-grid-svelte": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-svelte/-/ag-grid-svelte-0.1.4.tgz",
|
||||
"integrity": "sha512-yKJKZ1o3aE9dgaK4WtmjG+vA9GKqErznvFisDnzi+PPGWXDCSNWuoDGCUpEFJtHKrfooLoUZZHYHi8I9sZ+BCA==",
|
||||
"dependencies": {
|
||||
"svelte": "^3.55.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ag-grid-community": "^28"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -2971,6 +2990,11 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz",
|
||||
"integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
|
||||
@@ -7904,9 +7928,9 @@
|
||||
}
|
||||
},
|
||||
"@windmill-labs/svelte-grid": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.3.tgz",
|
||||
"integrity": "sha512-SzfuJ2qYkBYC73x3l+x9l7WcgU+kMlRoFkCgSCOfggi3z5zJU9eX1VYsV1wDjtQVU9OUaAciv1xlUR4OPxrFlg==",
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.4.tgz",
|
||||
"integrity": "sha512-alxQ8su/osmHodl1JMta+0dvlc2qnYwiXsoeC8Ys8hrLywD03y5tjxYV0L0WXvOnMKG1S9GbqGuDVCk+UmVuKg==",
|
||||
"dev": true
|
||||
},
|
||||
"@windmill-labs/svelvet": {
|
||||
@@ -7980,6 +8004,20 @@
|
||||
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
|
||||
"dev": true
|
||||
},
|
||||
"ag-grid-community": {
|
||||
"version": "28.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-community/-/ag-grid-community-28.2.1.tgz",
|
||||
"integrity": "sha512-DMZh/xD/FqYP17qJ1M92PolTYe+hrKuEaf+A4h13O6qn2x/xZQrTRGW5DgnQLR/uLMe1XXZQPKR3UKgAlKo69A==",
|
||||
"peer": true
|
||||
},
|
||||
"ag-grid-svelte": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ag-grid-svelte/-/ag-grid-svelte-0.1.4.tgz",
|
||||
"integrity": "sha512-yKJKZ1o3aE9dgaK4WtmjG+vA9GKqErznvFisDnzi+PPGWXDCSNWuoDGCUpEFJtHKrfooLoUZZHYHi8I9sZ+BCA==",
|
||||
"requires": {
|
||||
"svelte": "^3.55.0"
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
@@ -9151,6 +9189,11 @@
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-equals": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz",
|
||||
"integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg=="
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "3.2.12",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.67.2",
|
||||
"version": "1.70.1",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
@@ -25,7 +25,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.3",
|
||||
"@windmill-labs/svelte-grid": "^5.1.4",
|
||||
"@windmill-labs/svelvet": "^4.0.20",
|
||||
"@zerodevx/svelte-toast": "^0.8.1",
|
||||
"autoprefixer": "^10.4.13",
|
||||
@@ -66,11 +66,13 @@
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.7.6",
|
||||
"ag-grid-svelte": "^0.1.4",
|
||||
"async-mutex": "^0.4.0",
|
||||
"chart.js": "^3.9.1",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
"date-fns": "^2.29.3",
|
||||
"fast-equals": "^4.0.3",
|
||||
"highlight.js": "^11.7.0",
|
||||
"lucide-svelte": "^0.115.0",
|
||||
"monaco-editor": "0.33.0",
|
||||
|
||||
4
frontend/src/global.d.ts
vendored
4
frontend/src/global.d.ts
vendored
@@ -44,7 +44,7 @@ declare module '@windmill-labs/svelte-grid' {
|
||||
fastStart?: boolean
|
||||
throttleUpdate?: number
|
||||
throttleResize?: number
|
||||
|
||||
onTopId?: string
|
||||
scroller?: undefined
|
||||
sensor?: number
|
||||
}
|
||||
@@ -60,7 +60,7 @@ declare module '@windmill-labs/svelte-grid' {
|
||||
mount: CustomEvent<>
|
||||
},
|
||||
Slots<T>
|
||||
> {}
|
||||
> { }
|
||||
}
|
||||
|
||||
declare module '@windmill-labs/svelte-grid/build/helper/index.mjs' {
|
||||
|
||||
@@ -396,7 +396,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
initialPath=""
|
||||
namePlaceholder="my_{resource_type}"
|
||||
namePlaceholder="{resource_type}"
|
||||
kind="resource"
|
||||
/>
|
||||
|
||||
@@ -433,7 +433,7 @@
|
||||
{:else}
|
||||
<Path
|
||||
initialPath=""
|
||||
namePlaceholder="my_{resource_type}"
|
||||
namePlaceholder="{resource_type}"
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
kind="resource"
|
||||
|
||||
@@ -138,8 +138,18 @@
|
||||
|
||||
export function setCode(ncode: string): void {
|
||||
code = ncode
|
||||
if (editor) {
|
||||
editor.setValue(ncode)
|
||||
if (editor?.getModel()) {
|
||||
// editor.setValue(ncode)
|
||||
editor.pushUndoStop()
|
||||
|
||||
editor.executeEdits('set', [
|
||||
{
|
||||
range: editor.getModel()!.getFullModelRange(), // full range
|
||||
text: ncode
|
||||
}
|
||||
])
|
||||
|
||||
editor.pushUndoStop()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import GroupEditor from './GroupEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -69,15 +70,23 @@
|
||||
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
meta = { ownerKind: 'user', name: namePlaceholder, owner: '' }
|
||||
meta = {
|
||||
ownerKind: 'user',
|
||||
name: random_adj() + '_' + namePlaceholder,
|
||||
owner: ''
|
||||
}
|
||||
|
||||
meta.owner = $userStore!.username.split('@')[0]
|
||||
|
||||
let i = 1
|
||||
while (await pathExists(metaToPath(meta), kind)) {
|
||||
meta.name = `${namePlaceholder}_${i}`
|
||||
i += 1
|
||||
let newMeta = { ...meta }
|
||||
while (await pathExists(metaToPath(newMeta), kind)) {
|
||||
disabled = true
|
||||
error = 'finding an available name...'
|
||||
newMeta.name = random_adj() + '_' + namePlaceholder
|
||||
}
|
||||
error = ''
|
||||
disabled = false
|
||||
meta = newMeta
|
||||
path = metaToPath(meta)
|
||||
} else {
|
||||
meta = pathToMeta(path)
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
disabled={!can_write}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="my_resource"
|
||||
namePlaceholder="resource"
|
||||
kind="resource"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder={'my_schedule'}
|
||||
namePlaceholder="schedule"
|
||||
kind="schedule"
|
||||
/>
|
||||
<div class="mb-8" />
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
export let initialPath: string = ''
|
||||
export let template: 'pgsql' | 'mysql' | 'script' = 'script'
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let lockedLanguage = false
|
||||
|
||||
const langs: [string, SupportedLanguage][] = [
|
||||
['Typescript', Script.language.DENO],
|
||||
@@ -204,7 +205,7 @@
|
||||
bind:path={script.path}
|
||||
{initialPath}
|
||||
on:enter={() => changeStep(2)}
|
||||
namePlaceholder="my_script"
|
||||
namePlaceholder="script"
|
||||
kind="script"
|
||||
/>
|
||||
<h2 class="border-b pb-1 mt-12 mb-4">Summary</h2>
|
||||
@@ -214,7 +215,13 @@
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
/>
|
||||
|
||||
<h2 class="border-b pb-1 mt-12 mb-6">Language</h2>
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-gray-600 italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
@@ -228,6 +235,7 @@
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
>
|
||||
<LanguageIcon {lang} /><span class="ml-2 py-4">{label}</span>
|
||||
</Button>
|
||||
@@ -237,6 +245,7 @@
|
||||
variant="border"
|
||||
color={template == 'pgsql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
disabled={lockedLanguage}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'pgsql'
|
||||
@@ -245,7 +254,7 @@
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-4">PostgreSQL</span>
|
||||
</Button>
|
||||
<Button
|
||||
<!-- <Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'mysql' ? 'blue' : 'dark'}
|
||||
@@ -257,7 +266,7 @@
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-4">MySQL</span>
|
||||
</Button>
|
||||
</Button> -->
|
||||
</div>
|
||||
<div class="mt-16 mb-4">
|
||||
<Button
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
|
||||
onMount(() => {
|
||||
inferSchema(code)
|
||||
loadPastTests()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="my_variable"
|
||||
namePlaceholder="variable"
|
||||
kind="variable"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import type { AppEditorContext, GridItem } from '../types'
|
||||
import InputValue from './helpers/InputValue.svelte'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let subGrids: GridItem[][] | undefined = undefined
|
||||
export let componentContainerHeight: number
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
const { focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let result: string[] | undefined = undefined
|
||||
let gridContent: string[] | undefined = undefined
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedComponent === id && onFocus()
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if subGrids && subGrids[0]}
|
||||
<SubGridEditor
|
||||
bind:subGrid={subGrids[0]}
|
||||
containerHeight={componentContainerHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
@@ -1,25 +0,0 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY } from '../types'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<div class="w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm">
|
||||
Results
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<DisplayResult {result} {requireHtmlApproval} />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -1,73 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Tab, Tabs } from '$lib/components/common'
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import type { Output } from '../rx'
|
||||
import type { AppEditorContext, GridItem } from '../types'
|
||||
import InputValue from './helpers/InputValue.svelte'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let subGrids: GridItem[][] | undefined = undefined
|
||||
export let componentContainerHeight: number
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result', 'selectedTabIndex']
|
||||
const { worldStore, focusedGrid, selectedComponent } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let result: string[] | undefined = undefined
|
||||
let gridContent: string[] | undefined = undefined
|
||||
let selected: string = ''
|
||||
|
||||
$: selectedIndex = result?.indexOf(selected) ?? -1
|
||||
|
||||
$: if (result && selected === '') {
|
||||
selected = result[0]
|
||||
}
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedTabIndex: Output<number | null>
|
||||
}
|
||||
|
||||
function handleTabSelection() {
|
||||
outputs?.selectedTabIndex.set(selectedIndex)
|
||||
}
|
||||
|
||||
$: selectedIndex >= 0 && handleTabSelection()
|
||||
let tabHeight: number = 0
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: selectedIndex ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<div bind:clientHeight={tabHeight}>
|
||||
<Tabs bind:selected>
|
||||
{#each result ?? [] as res}
|
||||
<Tab value={res}>
|
||||
<span class="font-semibold">{res}</span>
|
||||
</Tab>
|
||||
{/each}
|
||||
</Tabs>
|
||||
</div>
|
||||
{#if subGrids && subGrids[selectedIndex]}
|
||||
<SubGridEditor
|
||||
bind:subGrid={subGrids[selectedIndex]}
|
||||
containerHeight={componentContainerHeight - tabHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
@@ -20,7 +20,7 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
export let preclickAction: (() => Promise<void>) | undefined = undefined
|
||||
export let customCss: Record<'button', { class: string; style: string }> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
@@ -95,7 +95,6 @@
|
||||
<InputValue {id} input={configuration.triggerOnAppLoad} bind:value={triggerOnAppLoad} />
|
||||
|
||||
<InputValue
|
||||
row={extraQueryParams['row']}
|
||||
{id}
|
||||
input={configuration.disabled}
|
||||
bind:value={disabled}
|
||||
@@ -119,10 +118,10 @@
|
||||
<Button
|
||||
btnClasses={twMerge(
|
||||
$app.css?.['buttoncomponent']?.['button']?.class,
|
||||
customCss?.button?.class,
|
||||
customCss?.button.class,
|
||||
fillContainer ? 'w-full h-full' : ''
|
||||
)}
|
||||
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button?.style].join(';')}
|
||||
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button.style].join(';')}
|
||||
{disabled}
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
|
||||
@@ -69,7 +69,6 @@
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
<InputValue
|
||||
row={extraQueryParams['row']}
|
||||
{id}
|
||||
input={configuration.disabled}
|
||||
bind:value={disabled}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as AppButton } from './AppButton.svelte'
|
||||
export { default as AppForm } from './AppForm.svelte'
|
||||
export { default as AppFormButton } from './AppFormButton.svelte'
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY, type AppEditorContext, type ComponentCustomCSS } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'header' | 'container'> | undefined = undefined
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<div class={twMerge(
|
||||
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
|
||||
$app.css?.['displaycomponent']?.['header']?.class,
|
||||
customCss?.header?.class
|
||||
)}>
|
||||
Results
|
||||
</div>
|
||||
<div class={twMerge(
|
||||
'p-2',
|
||||
$app.css?.['displaycomponent']?.['container']?.class,
|
||||
customCss?.container?.class
|
||||
)}>
|
||||
<DisplayResult {result} {requireHtmlApproval} />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -3,7 +3,7 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
type FitOption = typeof staticValues['objectFitOptions'][number]
|
||||
type FitOption = (typeof staticValues)['objectFitOptions'][number]
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
@@ -12,7 +12,7 @@
|
||||
const fit: Record<FitOption, string> = {
|
||||
cover: 'object-cover',
|
||||
contain: 'object-contain',
|
||||
fill: 'object-fill',
|
||||
fill: 'object-fill'
|
||||
}
|
||||
|
||||
let source: string | undefined = undefined
|
||||
@@ -27,8 +27,9 @@
|
||||
<InputValue {id} input={configuration.customStyles} bind:value={customStyles} />
|
||||
|
||||
<img
|
||||
on:pointerdown|preventDefault
|
||||
src={source}
|
||||
alt={altText}
|
||||
style={customStyles}
|
||||
class="w-full h-full {fit[imageFit || 'cover']}"
|
||||
>
|
||||
/>
|
||||
@@ -8,7 +8,7 @@
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
export let id: string
|
||||
@@ -17,7 +17,7 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: Record<'text', { class: string; style: string }> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
12
frontend/src/lib/components/apps/components/display/index.ts
Normal file
12
frontend/src/lib/components/apps/components/display/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { default as AppTable } from './table/AppTable.svelte'
|
||||
export { default as AppBarChart } from './AppBarChart.svelte'
|
||||
export { default as AppDisplayComponent } from './AppDisplayComponent.svelte'
|
||||
export { default as AppHtml } from './AppHtml.svelte'
|
||||
export { default as AppIcon } from './AppIcon.svelte'
|
||||
export { default as AppImage } from './AppImage.svelte'
|
||||
export { default as AppPieChart } from './AppPieChart.svelte'
|
||||
export { default as AppScatterChart } from './AppScatterChart.svelte'
|
||||
export { default as AppText } from './AppText.svelte'
|
||||
export { default as AppTimeseries } from './AppTimeseries.svelte'
|
||||
export { default as PlotlyHtml } from './PlotlyHtml.svelte'
|
||||
export { default as VegaLiteHtml } from './VegaLiteHtml.svelte'
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import AgGridSvelte from 'ag-grid-svelte/AgGridSvelte.svelte'
|
||||
import 'ag-grid-community/styles/ag-grid.css'
|
||||
import 'ag-grid-community/styles/ag-theme-alpine.css'
|
||||
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import type { Output } from '../../../rx'
|
||||
import type { AppEditorContext } from '../../../types'
|
||||
import InputValue from '../../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
|
||||
import { isObject } from '$lib/utils'
|
||||
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result', 'selectedRowIndex']
|
||||
|
||||
let result: Record<string, any>[] | undefined = undefined
|
||||
|
||||
const {
|
||||
worldStore,
|
||||
staticOutputs: staticOutputsStore,
|
||||
selectedComponent
|
||||
} = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let selectedRowIndex = -1
|
||||
|
||||
function toggleRow(row: Record<string, any>, rowIndex: number) {
|
||||
if (selectedRowIndex !== rowIndex) {
|
||||
selectedRowIndex = rowIndex
|
||||
outputs?.selectedRow.set(row.original)
|
||||
outputs?.selectedRowIndex.set(rowIndex)
|
||||
}
|
||||
}
|
||||
|
||||
let mounted = false
|
||||
onMount(() => {
|
||||
mounted = true
|
||||
})
|
||||
|
||||
$: selectedRowIndex === -1 &&
|
||||
Array.isArray(result) &&
|
||||
result.length > 0 &&
|
||||
// We need to wait until the component is mounted so the world is created
|
||||
mounted &&
|
||||
outputs &&
|
||||
toggleRow({ original: result[0] }, 0)
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedRowIndex: Output<number>
|
||||
selectedRow: Output<any>
|
||||
result: Output<any>
|
||||
}
|
||||
|
||||
$: outputs?.result?.set(result)
|
||||
|
||||
let clientHeight
|
||||
let clientWidth
|
||||
|
||||
let columnDefs: any = undefined
|
||||
|
||||
let allEditable: boolean | undefined = undefined
|
||||
|
||||
function onCellValueChanged(event) {
|
||||
if (result) {
|
||||
let dataCell = event.newValue
|
||||
try {
|
||||
dataCell = JSON.parse(dataCell)
|
||||
} catch (e) {}
|
||||
result[event.node.rowIndex][event.column.colId] = dataCell
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.columnDefs} bind:value={columnDefs} />
|
||||
<InputValue {id} input={configuration.allEditable} bind:value={allEditable} />
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if Array.isArray(result) && result.every(isObject)}
|
||||
<div
|
||||
class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full"
|
||||
bind:clientHeight
|
||||
bind:clientWidth
|
||||
>
|
||||
<div
|
||||
on:pointerdown|stopPropagation={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
style:height="{clientHeight}px"
|
||||
style:width="{clientWidth}px"
|
||||
class="ag-theme-alpine"
|
||||
>
|
||||
<AgGridSvelte
|
||||
bind:rowData={result}
|
||||
{columnDefs}
|
||||
defaultColDef={{ flex: 1, editable: allEditable, onCellValueChanged }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{:else if result != undefined}
|
||||
<Alert title="Parsing issues" type="error" size="xs">
|
||||
The result should be an array of objects
|
||||
</Alert>
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
@@ -1,19 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext, BaseAppComponent } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { Output } from '../../../rx'
|
||||
import type { AppEditorContext, BaseAppComponent } from '../../../types'
|
||||
import InputValue from '../../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { createSvelteTable, flexRender, type TableOptions } from '@tanstack/svelte-table'
|
||||
import AppButton from '../buttons/AppButton.svelte'
|
||||
import AppButton from '../../buttons/AppButton.svelte'
|
||||
import { classNames, isObject } from '$lib/utils'
|
||||
import DebouncedInput from '../helpers/DebouncedInput.svelte'
|
||||
import DebouncedInput from '../../helpers/DebouncedInput.svelte'
|
||||
import AppTableFooter from './AppTableFooter.svelte'
|
||||
import { tableOptions } from './tableOptions'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import type { ButtonComponent } from '../../editor/Component.svelte'
|
||||
import type { ButtonComponent } from '../../../editor/component'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { InlineScript } from '../types'
|
||||
import RunnableComponent from './helpers/RunnableComponent.svelte'
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../../inputType'
|
||||
import type { InlineScript } from '../../types'
|
||||
import RunnableComponent from './RunnableComponent.svelte'
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
@@ -10,21 +10,17 @@
|
||||
export let input: AppInput
|
||||
export let value: T
|
||||
export let id: string | undefined = undefined
|
||||
export let row: Record<string, any> | undefined = {}
|
||||
export let error: string = ''
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: state = $worldStore?.state
|
||||
$: input && $worldStore && row && handleConnection()
|
||||
$: input && $worldStore && handleConnection()
|
||||
$: input && input.type == 'template' && $state && (value = getValue(input))
|
||||
$: input && input.type == 'eval' && $state && (value = evalExpr(input))
|
||||
|
||||
function handleConnection() {
|
||||
if (input.type === 'connected') {
|
||||
$worldStore?.connect<any>(input, onValueChange)
|
||||
} else if (input.type === 'row') {
|
||||
setTimeout(() => (value = row?.[input['column']]), 0)
|
||||
} else if (input.type === 'static' || input.type == 'template') {
|
||||
setTimeout(() => (value = getValue(input)), 0)
|
||||
} else if (input.type == 'eval') {
|
||||
@@ -57,7 +53,6 @@
|
||||
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
|
||||
]
|
||||
})
|
||||
.concat(row ? [['row', row]] : [])
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -239,12 +239,7 @@
|
||||
|
||||
{#each Object.entries(fields ?? {}) as [key, v]}
|
||||
{#if v.type != 'static' && v.type != 'user'}
|
||||
<InputValue
|
||||
{id}
|
||||
input={fields[key]}
|
||||
bind:value={runnableInputValues[key]}
|
||||
row={extraQueryParams['row'] ?? {}}
|
||||
/>
|
||||
<InputValue {id} input={fields[key]} bind:value={runnableInputValues[key]} />
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as AlignWrapper } from './AlignWrapper.svelte'
|
||||
export { default as DebouncedInput } from './DebouncedInput.svelte'
|
||||
export { default as HiddenComponent } from './HiddenComponent.svelte'
|
||||
export { default as InputDefaultValue } from './InputDefaultValue.svelte'
|
||||
export { default as InputValue } from './InputValue.svelte'
|
||||
export { default as MissingConnectionWarning } from './MissingConnectionWarning.svelte'
|
||||
|
||||
6
frontend/src/lib/components/apps/components/index.ts
Normal file
6
frontend/src/lib/components/apps/components/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './buttons'
|
||||
export * from './display'
|
||||
export * from './inputs'
|
||||
export * from './layout'
|
||||
|
||||
export * from './helpers'
|
||||
@@ -23,7 +23,13 @@
|
||||
result: Output<string | undefined>
|
||||
}
|
||||
|
||||
let value: string | undefined = undefined
|
||||
// $: outputs && handleOutputs()
|
||||
|
||||
// function handleOutputs() {
|
||||
// value = outputs.result.peak()
|
||||
// }
|
||||
|
||||
let value: string | undefined = outputs?.result.peak()
|
||||
|
||||
$: items && handleItems()
|
||||
|
||||
@@ -38,16 +44,26 @@
|
||||
}
|
||||
})
|
||||
: []
|
||||
if (items?.[0]?.['value'] != value) {
|
||||
value = JSON.stringify(items?.[0]?.['value'])
|
||||
outputs?.result.set(value)
|
||||
const valuePeak = outputs?.result.peak()
|
||||
if (!valuePeak) {
|
||||
const value0 = items?.[0]?.['value']
|
||||
if (value0 != value && !items.includes(value0)) {
|
||||
value = JSON.stringify(value0)
|
||||
outputs?.result.set(value0)
|
||||
}
|
||||
} else {
|
||||
value = valuePeak
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(e: CustomEvent) {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
outputs?.result.set(JSON.parse(e.detail?.['value']) || undefined)
|
||||
let result: any = undefined
|
||||
try {
|
||||
result = JSON.parse(e.detail?.['value'])
|
||||
} catch (_) {}
|
||||
outputs?.result.set(result)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -70,12 +86,21 @@
|
||||
$selectedComponent = id
|
||||
}
|
||||
}}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
floatingConfig={{
|
||||
strategy: 'fixed'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</AlignWrapper>
|
||||
|
||||
<style global>
|
||||
.app-select .value-container {
|
||||
@apply p-0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
.svelte-select-list {
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -13,8 +13,7 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export const staticOutputs: string[] = ['result']
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let values: [number] = [0]
|
||||
const { worldStore, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let min = 0
|
||||
let max = 42
|
||||
let step = 1
|
||||
@@ -22,7 +21,12 @@
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<number | null>
|
||||
}
|
||||
$: if (values) {
|
||||
|
||||
let values: [number] = [outputs?.result.peak() ?? 0]
|
||||
|
||||
$: values && handleValues()
|
||||
|
||||
function handleValues() {
|
||||
// Disallow 'e' character in numbers
|
||||
// if(value && value.toString().includes('e')) {
|
||||
// value = +value.toString().replaceAll('e', '')
|
||||
@@ -40,7 +44,7 @@
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<div class="flex items-center w-full gap-1 px-1">
|
||||
<span>{min}</span>
|
||||
<div class="grow" on:pointerdown|stopPropagation>
|
||||
<div class="grow" on:pointerdown|stopPropagation={() => ($selectedComponent = id)}>
|
||||
<RangeSlider {step} min={min ?? 0} max={max ?? 1} bind:values />
|
||||
</div>
|
||||
<span>{max}</span>
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import type { Output } from '../../../rx'
|
||||
import type { AppEditorContext } from '../../../types'
|
||||
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../../helpers/InputValue.svelte'
|
||||
import CurrencyInput from './CurrencyInput.svelte'
|
||||
|
||||
export let id: string
|
||||
@@ -0,0 +1,9 @@
|
||||
export { default as AppCheckbox } from './AppCheckbox.svelte'
|
||||
export { default as AppCurrencyInput } from './currency/AppCurrencyInput.svelte'
|
||||
export { default as AppDateInput } from './AppDateInput.svelte'
|
||||
export { default as AppFileInput } from './AppFileInput.svelte'
|
||||
export { default as AppNumberInput } from './AppNumberInput.svelte'
|
||||
export { default as AppRangeInput } from './AppRangeInput.svelte'
|
||||
export { default as AppSelect } from './AppSelect.svelte'
|
||||
export { default as AppSliderInputs } from './AppSliderInputs.svelte'
|
||||
export { default as AppTextInput } from './AppTextInput.svelte'
|
||||
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext, GridItem } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let componentContainerHeight: number
|
||||
|
||||
let noPadding: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = []
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let gridContent: string[] | undefined = undefined
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedComponent === id && onFocus()
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
|
||||
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
{noPadding}
|
||||
bind:subGrid={$app.subgrids[`${id}-0`]}
|
||||
containerHeight={componentContainerHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { AppInput } from '../inputType'
|
||||
import type { HorizontalAlignment, VerticalAlignment } from '../types'
|
||||
import AlignWrapper from './helpers/AlignWrapper.svelte'
|
||||
import InputValue from './helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
@@ -0,0 +1,76 @@
|
||||
<script lang="ts">
|
||||
import { Tab, Tabs } from '$lib/components/common'
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext, GridItem } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let componentContainerHeight: number
|
||||
export let tabs: string[]
|
||||
|
||||
export const staticOutputs: string[] = ['selectedTabIndex']
|
||||
const { app, worldStore, focusedGrid, selectedComponent } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let selected: string = ''
|
||||
let noPadding: boolean | undefined = undefined
|
||||
|
||||
$: selectedIndex = tabs?.indexOf(selected) ?? -1
|
||||
|
||||
$: if ((tabs && selected === '') || !tabs.includes(selected)) {
|
||||
selected = tabs[0]
|
||||
}
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedTabIndex: Output<number | null>
|
||||
}
|
||||
|
||||
function handleTabSelection() {
|
||||
outputs?.selectedTabIndex.set(selectedIndex)
|
||||
}
|
||||
|
||||
$: selectedIndex >= 0 && handleTabSelection()
|
||||
|
||||
let tabHeight: number = 0
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: selectedIndex ?? 0
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.tabs} bind:value={tabs} />
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
|
||||
<div>
|
||||
<div bind:clientHeight={tabHeight} on:pointerdown|stopPropagation>
|
||||
<Tabs bind:selected>
|
||||
{#each tabs ?? [] as res}
|
||||
<Tab value={res}>
|
||||
<span class="font-semibold">{res}</span>
|
||||
</Tab>
|
||||
{/each}
|
||||
</Tabs>
|
||||
</div>
|
||||
{#if $app.subgrids}
|
||||
{#each tabs ?? [] as res, i}
|
||||
<SubGridEditor
|
||||
visible={i === selectedIndex}
|
||||
bind:subGrid={$app.subgrids[`${id}-${i}`]}
|
||||
{noPadding}
|
||||
containerHeight={componentContainerHeight - tabHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as AppContainer } from './AppContainer.svelte'
|
||||
export { default as AppDivider } from './AppDivider.svelte'
|
||||
export { default as AppTabs } from './AppTabs.svelte'
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { components, type AppComponent } from './Component.svelte'
|
||||
import { components, type AppComponent } from './component'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { AppComponent } from './Component.svelte'
|
||||
import type { AppComponent } from './component'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
|
||||
@@ -194,21 +194,21 @@
|
||||
</Pane>
|
||||
<Pane size={21} minSize={5} maxSize={33}>
|
||||
<div class="relative flex flex-col h-full">
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Tabs bind:selected={selectedTab} class="!border-b-2 !border-gray-200">
|
||||
<Tab value="insert" size="xs" class="grow">
|
||||
<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 flex flex-row gap-2">
|
||||
<Tab value="settings" size="xs" class="grow">
|
||||
<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 flex flex-row gap-2">
|
||||
<Tab value="css" size="xs" class="grow">
|
||||
<div class="m-1 center-center gap-2">
|
||||
<Icon data={faCode} />
|
||||
<span>CSS</span>
|
||||
</div>
|
||||
@@ -218,7 +218,9 @@
|
||||
{#if $selectedComponent !== undefined}
|
||||
<SettingsPanel />
|
||||
{:else}
|
||||
<div class="p-2 min-w-[150px] text-sm">No component selected.</div>
|
||||
<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">
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
import { toStatic } from '../utils'
|
||||
import AppExportButton from './AppExportButton.svelte'
|
||||
import AppInputs from './AppInputs.svelte'
|
||||
import type { AppComponent } from './Component.svelte'
|
||||
import type { AppComponent } from './component/components'
|
||||
import PanelSection from './settingsPanel/common/PanelSection.svelte'
|
||||
|
||||
async function hash(message) {
|
||||
@@ -241,7 +241,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path={newPath}
|
||||
initialPath=""
|
||||
namePlaceholder="my_app"
|
||||
namePlaceholder="app"
|
||||
kind="app"
|
||||
/>
|
||||
|
||||
@@ -467,8 +467,8 @@
|
||||
<div>{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}</div>
|
||||
<button
|
||||
on:click={() => {
|
||||
$focusedGrid = undefined
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,7 @@
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { Alert, Button } from '$lib/components/common'
|
||||
import type { AppComponent } from './Component.svelte'
|
||||
import type { Output } from '../rx'
|
||||
import type { AppComponent } from './component'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
import type { Policy } from '$lib/gen'
|
||||
import HiddenComponent from '../components/HiddenComponent.svelte'
|
||||
import Component from './Component.svelte'
|
||||
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
|
||||
import Component from './component/Component.svelte'
|
||||
|
||||
export let policy: Policy
|
||||
|
||||
@@ -37,13 +37,17 @@
|
||||
const disabledGridItem = fct(gridItem)
|
||||
|
||||
if (disabledGridItem.data.subGrids) {
|
||||
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map((subgrid: GridItem[]) =>
|
||||
subgrid.map((subgridItem: GridItem) => fct(subgridItem))
|
||||
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map(
|
||||
(subgrid: GridItem[]) => subgrid?.map((subgridItem: GridItem) => fct(subgridItem)) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
return disabledGridItem
|
||||
})
|
||||
|
||||
Object.values($app.subgrids ?? {}).map(
|
||||
(subgrid: GridItem[]) => subgrid?.map((subgridItem: GridItem) => fct(subgridItem)) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
function removeGridElement(component) {
|
||||
@@ -87,6 +91,7 @@
|
||||
function selectComponent(id: string) {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = id
|
||||
$focusedGrid = undefined
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +131,6 @@
|
||||
})
|
||||
}
|
||||
})
|
||||
let mounted = false
|
||||
</script>
|
||||
|
||||
<div class="relative w-full z-20 overflow-visible">
|
||||
@@ -151,8 +155,13 @@
|
||||
on:pointerdown={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
on:click={() => {
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
onTopId={$selectedComponent}
|
||||
fillSpace={false}
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
@@ -165,13 +174,11 @@
|
||||
{#if gridComponent.data.id === dataItem.data.id}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
{#if $connectingInput.opened}
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
on:pointerenter={() => ($connectingInput.hoveredComponent = gridComponent.data.id)}
|
||||
on:pointerleave={() => ($connectingInput.hoveredComponent = undefined)}
|
||||
class="absolute w-full h-full bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
/>
|
||||
{/if}
|
||||
<div
|
||||
on:pointerenter={() => ($connectingInput.hoveredComponent = gridComponent.data.id)}
|
||||
on:pointerleave={() => ($connectingInput.hoveredComponent = undefined)}
|
||||
class="absolute w-full h-full bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
/>
|
||||
<div
|
||||
style="transform: translate(-50%, -50%);"
|
||||
class="absolute w-fit justify-center bg-indigo-500/90 left-[50%] top-[50%] z-50 px-6 rounded border text-white py-2 text-5xl center-center"
|
||||
@@ -180,6 +187,7 @@
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:click|stopPropagation
|
||||
on:pointerdown={() => selectComponent(dataItem.data.id)}
|
||||
class={classNames(
|
||||
'h-full w-full center-center',
|
||||
@@ -189,7 +197,7 @@
|
||||
>
|
||||
<Component
|
||||
{pointerdown}
|
||||
bind:component={gridComponent.data}
|
||||
bind:pComponent={gridComponent.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(gridComponent)}
|
||||
on:delete={() => removeGridElement(gridComponent.data)}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import { allItems, allItemsWithParent } from '../utils'
|
||||
import PanelSection from './settingsPanel/common/PanelSection.svelte'
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
import TablePanel from './TablePanel.svelte'
|
||||
|
||||
const { selectedComponent, lazyGrid, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { selectedComponent, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
</script>
|
||||
|
||||
{#each $lazyGrid as gridItem (gridItem.data.id)}
|
||||
{#each $app.grid as gridItem (gridItem.data.id)}
|
||||
{#if gridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel bind:component={gridItem.data} />
|
||||
<ComponentPanel parent={undefined} bind:component={gridItem.data} />
|
||||
{:else if gridItem.data.type === 'tablecomponent'}
|
||||
<TablePanel bind:component={gridItem.data} />
|
||||
{:else if gridItem.data.subGrids}
|
||||
{#each gridItem.data.subGrids as subGrid}
|
||||
{#each subGrid as subGridItem, index (subGridItem.data.id)}
|
||||
{#if subGridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel
|
||||
bind:component={subGridItem.data}
|
||||
onDelete={() => {
|
||||
subGrid.splice(index, 1)
|
||||
subGrid = subGrid
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
{#if $app.subgrids}
|
||||
{#each Object.keys($app.subgrids ?? {}) as key (key)}
|
||||
{#each $app.subgrids[key] as gridItem (gridItem.data.id)}
|
||||
{#if gridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel parent={key} bind:component={gridItem.data} />
|
||||
{:else if gridItem.data.type === 'tablecomponent'}
|
||||
<TablePanel bind:component={gridItem.data} />
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
|
||||
{#if $selectedComponent === `bg_${index}`}
|
||||
<PanelSection title={`Background script inputs`}>
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
import Grid from '@windmill-labs/svelte-grid'
|
||||
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
|
||||
import type { AppEditorContext, GridItem } from '../types'
|
||||
import Component from './Component.svelte'
|
||||
import Component from './component/Component.svelte'
|
||||
import { findGridItem } from './appUtils'
|
||||
|
||||
export let subGrid: GridItem[]
|
||||
export let containerHeight: number
|
||||
export let noPadding = false
|
||||
//export let id: string
|
||||
export let subGrid: GridItem[] = []
|
||||
export let visible: boolean = true
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -35,11 +39,17 @@
|
||||
onComponent = id
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = id
|
||||
/*
|
||||
$focusedGrid = {
|
||||
parentComponentId: parentId,
|
||||
subGridIndex: index
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
function lock(gridComponent: GridItem) {
|
||||
let fComponent = $app.grid.find((c) => c.id === gridComponent.id)
|
||||
let fComponent = findGridItem($app, gridComponent.data.id)
|
||||
if (fComponent) {
|
||||
fComponent = toggleFixed(fComponent)
|
||||
}
|
||||
@@ -47,33 +57,12 @@
|
||||
|
||||
// @ts-ignore
|
||||
let container
|
||||
|
||||
$: if ($focusedGrid?.parentComponentId !== $selectedComponent) {
|
||||
const gridItemIds = $app.grid
|
||||
.map((gridItem: GridItem) => {
|
||||
if (gridItem.data.id === $focusedGrid?.parentComponentId) {
|
||||
const subGrids = gridItem.data.subGrids ?? []
|
||||
return [
|
||||
gridItem.data.id,
|
||||
...subGrids.map((subGrid: GridItem[]) =>
|
||||
subGrid.map((gridItem: GridItem) => gridItem.data.id)
|
||||
)
|
||||
]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
})
|
||||
.flat(2)
|
||||
|
||||
if (!gridItemIds.includes($selectedComponent)) {
|
||||
$focusedGrid = undefined
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="relative w-full subgrid " bind:this={container}>
|
||||
<div class="relative w-full subgrid {visible ? 'visible' : 'invisible h-0 '}" bind:this={container}>
|
||||
<div
|
||||
class="px-4 pt-4 overflow-auto {$connectingInput?.opened ? '' : ''}"
|
||||
class:px-2={!noPadding}
|
||||
class="py-2 overflow-auto {$connectingInput?.opened ? '' : ''}"
|
||||
on:pointerdown|stopPropagation={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
@@ -121,7 +110,7 @@
|
||||
>
|
||||
<Component
|
||||
{pointerdown}
|
||||
bind:component={gridComponent.data}
|
||||
bind:pComponent={gridComponent.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(gridComponent)}
|
||||
on:lock={() => lock(gridComponent)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import type { TableComponent } from './Component.svelte'
|
||||
import type { TableComponent } from './component'
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
|
||||
export let component: TableComponent
|
||||
@@ -11,6 +11,8 @@
|
||||
{#each component.actionButtons as actionButton (actionButton.id)}
|
||||
{#if actionButton.id === $selectedComponent}
|
||||
<ComponentPanel
|
||||
parent={undefined}
|
||||
noGrid
|
||||
rowColumns
|
||||
bind:component={actionButton}
|
||||
onDelete={() => {
|
||||
|
||||
173
frontend/src/lib/components/apps/editor/appUtils.ts
Normal file
173
frontend/src/lib/components/apps/editor/appUtils.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import type { App, 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'
|
||||
|
||||
function findGridItemById(
|
||||
root: GridItem[],
|
||||
subGrids: Record<string, GridItem[]> | undefined,
|
||||
id: string
|
||||
): GridItem | undefined {
|
||||
for (const gridItem of allItems(root, subGrids)) {
|
||||
if (gridItem.id === id) {
|
||||
return gridItem
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function findGridItem(app: App, id: string): GridItem | undefined {
|
||||
return findGridItemById(app.grid, app.subgrids, id)
|
||||
}
|
||||
|
||||
export function getNextGridItemId(app: App): string {
|
||||
const subgridsKeys = allItems(app.grid, app.subgrids).map((x) => x.id)
|
||||
const withoutDash = subgridsKeys.map((element) => element.split('-')[0])
|
||||
const id = getNextId([...new Set(withoutDash)])
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
|
||||
const appComponent = data
|
||||
|
||||
appComponent.id = id
|
||||
|
||||
const newComponent = {
|
||||
fixed: false,
|
||||
resizable: true,
|
||||
draggable: true,
|
||||
customDragger: false,
|
||||
customResizer: false,
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
|
||||
|
||||
const newItem: GridItem = {
|
||||
data: newData,
|
||||
id: id
|
||||
}
|
||||
|
||||
gridColumns.forEach((column) => {
|
||||
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
|
||||
|
||||
newItem[column] = {
|
||||
...newComponent,
|
||||
min: { w: 1, h: 1 },
|
||||
max: { w: column, h: 100 },
|
||||
w: rec.w,
|
||||
h: rec.h
|
||||
}
|
||||
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
|
||||
newItem[column] = { ...newItem[column], ...position }
|
||||
})
|
||||
|
||||
return newItem
|
||||
}
|
||||
|
||||
export function insertNewGridItem(
|
||||
app: App,
|
||||
data: AppComponent,
|
||||
focusedGrid: FocusedGrid | undefined,
|
||||
keepId?: boolean
|
||||
) {
|
||||
const id = keepId ? data.id : getNextGridItemId(app)
|
||||
|
||||
if (!app.subgrids) {
|
||||
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
|
||||
}
|
||||
for (let i = 0; i < (data.numberOfSubgrids ?? 0); i++) {
|
||||
app.subgrids[`${id}-${i}`] = []
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
export function getAllSubgridsAndComponentIds(
|
||||
app: App,
|
||||
component: AppComponent
|
||||
): [string[], string[]] {
|
||||
const subgrids: string[] = []
|
||||
let components: string[] = [component.id]
|
||||
if (app.subgrids && component.numberOfSubgrids) {
|
||||
for (let i = 0; i < component.numberOfSubgrids; i++) {
|
||||
const key = `${component.id}-${i}`
|
||||
const subgrid = app.subgrids[key]
|
||||
if (subgrid) {
|
||||
subgrids.push(key)
|
||||
for (const item of subgrid) {
|
||||
let [recSubgrids, recComponents] = getAllSubgridsAndComponentIds(app, item.data)
|
||||
subgrids.push(...recSubgrids)
|
||||
components.push(...recComponents)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return [subgrids, components]
|
||||
}
|
||||
|
||||
export function deleteGridItem(
|
||||
app: App,
|
||||
component: AppComponent,
|
||||
parent: string | undefined
|
||||
): string[] {
|
||||
let [subgrids, components] = getAllSubgridsAndComponentIds(app, component)
|
||||
if (app.subgrids) {
|
||||
subgrids.forEach((id) => {
|
||||
delete app.subgrids![id]
|
||||
})
|
||||
}
|
||||
if (!parent) {
|
||||
let index = app.grid.findIndex((x) => x.id == component.id)
|
||||
if (index > -1) {
|
||||
app.grid.splice(index, 1)
|
||||
}
|
||||
} else {
|
||||
let grid = app.subgrids![parent]
|
||||
let index = grid.findIndex((x) => x.id == component.id)
|
||||
if (index > -1) {
|
||||
grid.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import ComponentHeader from '../ComponentHeader.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
|
||||
import {
|
||||
AppBarChart,
|
||||
AppDisplayComponent,
|
||||
AppTable,
|
||||
AppText,
|
||||
AppButton,
|
||||
AppPieChart,
|
||||
AppSelect,
|
||||
AppCheckbox,
|
||||
AppTextInput,
|
||||
AppNumberInput,
|
||||
AppDateInput,
|
||||
AppForm,
|
||||
AppScatterChart,
|
||||
AppTimeseries,
|
||||
AppHtml,
|
||||
AppSliderInputs,
|
||||
AppFormButton,
|
||||
VegaLiteHtml,
|
||||
PlotlyHtml,
|
||||
AppRangeInput,
|
||||
AppTabs,
|
||||
AppIcon,
|
||||
AppCurrencyInput,
|
||||
AppDivider,
|
||||
AppFileInput,
|
||||
AppImage,
|
||||
AppContainer
|
||||
} from '../../components'
|
||||
import type { AppComponent } from './components'
|
||||
import AppAggridTable from '../../components/display/table/AppAggridTable.svelte'
|
||||
|
||||
export let pComponent: AppComponent
|
||||
export let selected: boolean
|
||||
export let locked: boolean = false
|
||||
export let pointerdown: boolean = false
|
||||
|
||||
const { staticOutputs, mode, connectingInput, app } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
let hover = false
|
||||
let initializing: boolean | undefined = undefined
|
||||
let componentContainerHeight: number = 0
|
||||
|
||||
let component = JSON.parse(JSON.stringify(pComponent))
|
||||
$: if (pComponent && !deepEqual(pComponent, component)) {
|
||||
component = JSON.parse(JSON.stringify(pComponent))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
on:pointerenter={() => (hover = true)}
|
||||
on:pointerleave={() => (hover = false)}
|
||||
class="h-full flex flex-col w-full component"
|
||||
>
|
||||
{#if $mode !== 'preview'}
|
||||
<ComponentHeader {hover} {pointerdown} {component} {selected} on:delete on:lock {locked} />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
// Removed in https://github.com/windmill-labs/windmill/pull/1171
|
||||
// In case of a bug, try stopping propagation on the native event
|
||||
// and dispatch a custom event: `e?.stopPropagation(); dispatch('select');`
|
||||
// if ($mode === 'preview') {
|
||||
// e?.stopPropagation()
|
||||
// }
|
||||
}}
|
||||
class={twMerge(
|
||||
'h-full bg-white/40',
|
||||
selected && $mode !== 'preview' ? 'border border-blue-500' : '',
|
||||
!selected && $mode !== 'preview' && !component.card ? 'border-gray-100' : '',
|
||||
$mode !== 'preview' && !$connectingInput.opened ? 'hover:border-blue-500' : '',
|
||||
component.softWrap ? '' : 'overflow-auto',
|
||||
$mode != 'preview' ? 'cursor-pointer' : '',
|
||||
'relative z-auto',
|
||||
$app.css?.['app']?.['component']?.class
|
||||
)}
|
||||
style={$app.css?.['app']?.['component']?.style}
|
||||
bind:clientHeight={componentContainerHeight}
|
||||
>
|
||||
{#if component.type === 'displaycomponent'}
|
||||
<AppDisplayComponent
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'barchartcomponent'}
|
||||
<AppBarChart
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'timeseriescomponent'}
|
||||
<AppTimeseries
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'htmlcomponent'}
|
||||
<AppHtml
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'vegalitecomponent'}
|
||||
<VegaLiteHtml
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'plotlycomponent'}
|
||||
<PlotlyHtml
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'scatterchartcomponent'}
|
||||
<AppScatterChart
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'piechartcomponent'}
|
||||
<AppPieChart
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
bind:componentInput={component.componentInput}
|
||||
/>
|
||||
{:else if component.type === 'tablecomponent'}
|
||||
<AppTable
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:actionButtons={component.actionButtons}
|
||||
/>
|
||||
{:else if component.type === 'aggridcomponent'}
|
||||
<AppAggridTable
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
bind:componentInput={component.componentInput}
|
||||
/>
|
||||
{:else if component.type === 'textcomponent'}
|
||||
<AppText
|
||||
{...component}
|
||||
bind:initializing
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'buttoncomponent'}
|
||||
<AppButton
|
||||
{...component}
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'selectcomponent'}
|
||||
<AppSelect {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'formcomponent'}
|
||||
<AppForm
|
||||
{...component}
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'formbuttoncomponent'}
|
||||
<AppFormButton
|
||||
{...component}
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'checkboxcomponent'}
|
||||
<AppCheckbox {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'textinputcomponent'}
|
||||
<AppTextInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'passwordinputcomponent'}
|
||||
<AppTextInput
|
||||
inputType="password"
|
||||
{...component}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'dateinputcomponent'}
|
||||
<AppDateInput
|
||||
inputType="date"
|
||||
{...component}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
/>
|
||||
{:else if component.type === 'numberinputcomponent'}
|
||||
<AppNumberInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'currencycomponent'}
|
||||
<AppCurrencyInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'slidercomponent'}
|
||||
<AppSliderInputs {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'horizontaldividercomponent'}
|
||||
<AppDivider {...component} position="horizontal" />
|
||||
{:else if component.type === 'verticaldividercomponent'}
|
||||
<AppDivider {...component} position="vertical" />
|
||||
{:else if component.type === 'rangecomponent'}
|
||||
<AppRangeInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'tabscomponent'}
|
||||
<AppTabs
|
||||
{...component}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{componentContainerHeight}
|
||||
/>
|
||||
{:else if component.type === 'containercomponent'}
|
||||
<AppContainer
|
||||
{...component}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{componentContainerHeight}
|
||||
/>
|
||||
{:else if component.type === 'iconcomponent'}
|
||||
<AppIcon {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'fileinputcomponent'}
|
||||
<AppFileInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{:else if component.type === 'imagecomponent'}
|
||||
<AppImage {...component} bind:staticOutputs={$staticOutputs[component.id]} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if initializing}
|
||||
<div
|
||||
out:fade={{ duration: 200 }}
|
||||
class="absolute inset-0 center-center flex-col bg-white text-gray-600 border"
|
||||
>
|
||||
<Loader2 class="animate-spin" size={16} />
|
||||
<span class="text-xs mt-1">Loading</span>
|
||||
</div>
|
||||
{/if}
|
||||
15
frontend/src/lib/components/apps/editor/component/README.md
Normal file
15
frontend/src/lib/components/apps/editor/component/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# How to add new components
|
||||
|
||||
1. [**`components.ts`**](./components.ts):
|
||||
1. Create a type for the new component and add it to the `AppComponent` union
|
||||
type
|
||||
1. Add the component to the `components` record
|
||||
1. [**`sets.ts`**](./sets.ts):
|
||||
1. Add the component to one of the component sets: `layout`, `buttons`,
|
||||
`inputs` or `display` _(this controls which group the component will be
|
||||
placed in in the **Insert** menu)_
|
||||
1. [**`Component.svelte`**](./Component.svelte):
|
||||
1. Add the new component in the Svelte `if` statement
|
||||
1. [**`default-codes.ts`**](./default-codes.ts):
|
||||
1. _(optional)_ Add the default code associated with the new component to the
|
||||
`DEFAULT_CODES` record
|
||||
1236
frontend/src/lib/components/apps/editor/component/components.ts
Normal file
1236
frontend/src/lib/components/apps/editor/component/components.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,325 @@
|
||||
import type { AppComponent } from "."
|
||||
|
||||
export function defaultCode(component: string, language: string): string | undefined {
|
||||
return DEFAULT_CODES[component]?.[language]
|
||||
}
|
||||
|
||||
export const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'python3', string>>> = {
|
||||
tablecomponent: {
|
||||
deno: `export async function main() {
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "A cell with a long name",
|
||||
"age": 42
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "A briefer cell",
|
||||
"age": 84
|
||||
}
|
||||
]
|
||||
}`,
|
||||
python3: `def main():
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "A cell with a long name",
|
||||
"age": 42
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "A briefer cell",
|
||||
"age": 84
|
||||
}
|
||||
]`
|
||||
},
|
||||
textcomponent: {
|
||||
deno: `export async function main() {
|
||||
return "foo"
|
||||
}`,
|
||||
python3: `def main():
|
||||
return "foo"`
|
||||
},
|
||||
barchartcomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Bar",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Bar",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}`
|
||||
},
|
||||
displaycomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
"foo": 42
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"foo": 42
|
||||
}`
|
||||
},
|
||||
htmlcomponent: {
|
||||
deno: `export async function main() {
|
||||
return \`<img
|
||||
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&h=1280&q=80"
|
||||
>
|
||||
<h1 class="absolute top-4 left-2 text-white">
|
||||
Hello world
|
||||
</h1>\`
|
||||
}`,
|
||||
python3: `def main():
|
||||
return '''<img
|
||||
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&h=1280&q=80"
|
||||
>
|
||||
<h1 class="absolute top-4 left-2 text-white">
|
||||
Hello world
|
||||
</h1>'''`
|
||||
},
|
||||
vegalitecomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
data: {
|
||||
values: [
|
||||
{ a: "A", b: 28 },
|
||||
{ a: "B", b: 55 },
|
||||
{ a: "C", b: 43 },
|
||||
{ a: "D", b: 91 },
|
||||
],
|
||||
},
|
||||
mark: "bar",
|
||||
encoding: {
|
||||
x: { field: "a", type: "ordinal" },
|
||||
y: { field: "b", type: "quantitative" },
|
||||
},
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"data": {
|
||||
"values": [
|
||||
{ "a": "A", "b": 28 },
|
||||
{ "a": "B", "b": 55 },
|
||||
{ "a": "C", "b": 43 },
|
||||
{ "a": "D", "b": 91 },
|
||||
],
|
||||
},
|
||||
"mark": "bar",
|
||||
"encoding": {
|
||||
"x": { "field": "a", "type": "ordinal" },
|
||||
"y": { "field": "b", "type": "quantitative" },
|
||||
},
|
||||
}`
|
||||
},
|
||||
plotlycomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
type: 'bar',
|
||||
x: [1, 2, 3, 4],
|
||||
y: [5, 10, 2, 8],
|
||||
marker: {
|
||||
color: '#C8A2C8',
|
||||
line: {
|
||||
width: 2.5
|
||||
}
|
||||
}
|
||||
};
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"type": "bar",
|
||||
"x": [1, 2, 3, 4],
|
||||
"y": [5, 10, 2, 8],
|
||||
"marker": {
|
||||
"color": "#C8A2C8",
|
||||
"line": {
|
||||
"width": 2.5
|
||||
}
|
||||
}
|
||||
}`
|
||||
},
|
||||
piechartcomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Pie",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Pie",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}`
|
||||
},
|
||||
scatterchartcomponent: {
|
||||
deno: `export async function main() {
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{ "x": 25, "y": 50 },
|
||||
{ "x": 23, "y": 23 },
|
||||
{ "x": 12, "y": 37 }
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{ "x": 32, "y": 32 },
|
||||
{ "x": 25, "y": 42 },
|
||||
{ "x": 3, "y": 27 }
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
python3: `def main():
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{ "x": 25, "y": 50 },
|
||||
{ "x": 23, "y": 23 },
|
||||
{ "x": 12, "y": 37 }
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{ "x": 32, "y": 32 },
|
||||
{ "x": 25, "y": 42 },
|
||||
{ "x": 3, "y": 27 }
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]`
|
||||
},
|
||||
timeseriescomponent: {
|
||||
deno: `export async function main() {
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 50
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 60
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 20
|
||||
}
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 20
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 13
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 45
|
||||
}
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
python3: `def main():
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 50
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 60
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 20
|
||||
}
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 20
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 13
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 45
|
||||
}
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]`
|
||||
},
|
||||
iconcomponent: {
|
||||
deno: `export async function main() {
|
||||
return "smile";
|
||||
}`,
|
||||
python3: `def main():
|
||||
return "smile"`
|
||||
}
|
||||
} as const
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as Component } from './Component.svelte'
|
||||
|
||||
export * from './components'
|
||||
export * from './default-codes'
|
||||
export * from './sets'
|
||||
53
frontend/src/lib/components/apps/editor/component/sets.ts
Normal file
53
frontend/src/lib/components/apps/editor/component/sets.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { ComponentSet } from '../../types'
|
||||
|
||||
const layout: ComponentSet = {
|
||||
title: 'Layout',
|
||||
components: [
|
||||
'tabscomponent',
|
||||
'containercomponent',
|
||||
'horizontaldividercomponent',
|
||||
'verticaldividercomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
const buttons: ComponentSet = {
|
||||
title: 'Buttons',
|
||||
components: ['buttoncomponent', 'formcomponent', 'formbuttoncomponent']
|
||||
} as const
|
||||
|
||||
const inputs: ComponentSet = {
|
||||
title: 'Inputs',
|
||||
components: [
|
||||
'textinputcomponent',
|
||||
'passwordinputcomponent',
|
||||
'numberinputcomponent',
|
||||
'currencycomponent',
|
||||
'slidercomponent',
|
||||
'rangecomponent',
|
||||
'dateinputcomponent',
|
||||
'fileinputcomponent',
|
||||
'checkboxcomponent',
|
||||
'selectcomponent'
|
||||
]
|
||||
} as const
|
||||
|
||||
const display: ComponentSet = {
|
||||
title: 'Display',
|
||||
components: [
|
||||
'textcomponent',
|
||||
'iconcomponent',
|
||||
'imagecomponent',
|
||||
'htmlcomponent',
|
||||
'tablecomponent',
|
||||
'aggridcomponent',
|
||||
'barchartcomponent',
|
||||
'piechartcomponent',
|
||||
'vegalitecomponent',
|
||||
'plotlycomponent',
|
||||
'scatterchartcomponent',
|
||||
'timeseriescomponent',
|
||||
'displaycomponent',
|
||||
]
|
||||
} as const
|
||||
|
||||
export const COMPONENT_SETS = [layout, buttons, inputs, display] as const
|
||||
@@ -1,142 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { slide } from 'svelte/transition'
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { AppEditorContext, GridItem } from '../../types'
|
||||
|
||||
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import { isOpenStore } from './store'
|
||||
import { gridColumns } from '../../gridUtils'
|
||||
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
|
||||
import {
|
||||
components as componentsRecord,
|
||||
componentSets,
|
||||
getRecommendedDimensionsByComponent,
|
||||
type AppComponent
|
||||
} from '../Component.svelte'
|
||||
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
|
||||
import ListItem from './ListItem.svelte'
|
||||
import { insertNewGridItem } from '../appUtils'
|
||||
|
||||
const TITLE_PREFIX = 'Component.' as const
|
||||
const { app, selectedComponent, focusedGrid } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
// The grid is needed to find a space for the new component
|
||||
function createNewGridItem(grid: GridItem[], id: string, appComponentType: string): GridItem {
|
||||
const appComponent = componentsRecord[appComponentType].data
|
||||
|
||||
appComponent.id = id
|
||||
|
||||
const newComponent = {
|
||||
fixed: false,
|
||||
resizable: true,
|
||||
draggable: true,
|
||||
customDragger: false,
|
||||
customResizer: false,
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
|
||||
|
||||
const newItem: GridItem = {
|
||||
data: newData,
|
||||
id: id
|
||||
}
|
||||
|
||||
gridColumns.forEach((column) => {
|
||||
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
|
||||
|
||||
newItem[column] = {
|
||||
...newComponent,
|
||||
min: { w: 1, h: 1 },
|
||||
max: { w: column, h: 100 },
|
||||
w: rec.w,
|
||||
h: rec.h
|
||||
}
|
||||
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
|
||||
newItem[column] = { ...newItem[column], ...position }
|
||||
})
|
||||
|
||||
return newItem
|
||||
}
|
||||
|
||||
function addComponent(appComponentType: AppComponent['type']): void {
|
||||
// When a new component is added, we need to mark the app as dirty,
|
||||
// so a confirmation modal will appear if the user tries to leave the page
|
||||
$dirtyStore = true
|
||||
|
||||
const grid = $app.grid ?? []
|
||||
const data = componentsRecord[appComponentType].data
|
||||
const id = insertNewGridItem($app, data, $focusedGrid)
|
||||
|
||||
const gridItemIds = grid
|
||||
.map((gridItem: GridItem) => {
|
||||
const subGrids = gridItem.data.subGrids ?? []
|
||||
return [
|
||||
gridItem.data.id,
|
||||
...subGrids.map((subGrid: GridItem[]) =>
|
||||
subGrid.map((gridItem: GridItem) => gridItem.data.id)
|
||||
)
|
||||
]
|
||||
})
|
||||
.flat(2)
|
||||
|
||||
const id = getNextId(gridItemIds)
|
||||
|
||||
if ($focusedGrid) {
|
||||
const { parentComponentId, subGridIndex } = $focusedGrid
|
||||
|
||||
const gridItemIndex = $app.grid.findIndex((gridItem) => gridItem.id === parentComponentId)
|
||||
const subGrids = $app.grid[gridItemIndex].data.subGrids ?? []
|
||||
const newItem = createNewGridItem(subGrids[subGridIndex] ?? [], id, appComponentType)
|
||||
const subGrid = subGrids[subGridIndex] ?? []
|
||||
|
||||
$app.grid[gridItemIndex].data.subGrids[subGridIndex] = [...subGrid, newItem]
|
||||
$selectedComponent = id
|
||||
} else {
|
||||
const newItem = createNewGridItem(grid, id, appComponentType)
|
||||
$app.grid = [...grid, newItem]
|
||||
$selectedComponent = id
|
||||
}
|
||||
$selectedComponent = id
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
isOpenStore.addItems(componentSets.map((set) => ({ [set.title]: true })))
|
||||
isOpenStore.addItems(COMPONENT_SETS.map((set) => ({ [TITLE_PREFIX + set.title]: true })))
|
||||
})
|
||||
</script>
|
||||
|
||||
{#each componentSets as { title, components }, index (index)}
|
||||
{@const isOpen = $isOpenStore[title]}
|
||||
<section class="mt-1 mb-2 px-2">
|
||||
<button
|
||||
on:click|preventDefault={() => isOpenStore.toggle(title)}
|
||||
class="w-full flex justify-between items-center text-gray-700 px-2 py-1
|
||||
rounded-sm duration-200 hover:bg-gray-100"
|
||||
>
|
||||
<h1 class="text-sm font-semibold text-left">{title}</h1>
|
||||
<Icon data={faAngleDown} class="rotate-0 duration-300 {isOpen ? '!rotate-180' : ''}" />
|
||||
</button>
|
||||
{#if isOpen}
|
||||
<div transition:slide|local={{ duration: 300 }}>
|
||||
{#if components.length}
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
{#each components as item}
|
||||
<button
|
||||
on:click={() => addComponent(item)}
|
||||
title={componentsRecord[item].name}
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
|
||||
>
|
||||
<svelte:component this={componentsRecord[item].icon} />
|
||||
<div class="text-xs w-full text-center ellipsize">
|
||||
{componentsRecord[item].name}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-gray-500 py-1 px-2">
|
||||
There are no components in this group yet
|
||||
</div>
|
||||
{/if}
|
||||
{#each COMPONENT_SETS as { title, components }, index (index)}
|
||||
<ListItem {title} prefix={TITLE_PREFIX}>
|
||||
{#if components.length}
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
{#each components as item}
|
||||
<button
|
||||
on:click={() => addComponent(item)}
|
||||
title={componentsRecord[item].name}
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
|
||||
>
|
||||
<svelte:component this={componentsRecord[item].icon} />
|
||||
<div class="text-xs w-full text-center ellipsize">
|
||||
{componentsRecord[item].name}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-gray-500 py-1 px-2"> There are no components in this group yet </div>
|
||||
{/if}
|
||||
</section>
|
||||
</ListItem>
|
||||
{/each}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentCssProperty } from "../../types"
|
||||
|
||||
export let name: string
|
||||
export let value: ComponentCssProperty | undefined
|
||||
</script>
|
||||
|
||||
<div class="text-sm font-semibold text-gray-500 capitalize pt-2">
|
||||
{name}
|
||||
</div>
|
||||
{#if value}
|
||||
<div class="border-l border-gray-400/80 py-1 pl-3.5 mt-1 ml-0.5">
|
||||
<label class="block pb-2">
|
||||
<div class="text-xs font-medium pb-0.5">
|
||||
Style
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={value.style}
|
||||
on:focus
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
<div class="text-xs font-medium pb-0.5">
|
||||
Tailwind classes
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={value.class}
|
||||
on:focus
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user