Compare commits

..

40 Commits

Author SHA1 Message Date
Ruben Fiszel
f4fe71e074 chore(main): release 1.72.0 (#1250)
* chore(main): release 1.72.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-03-03 11:12:18 +01:00
Ruben Fiszel
fd4e18f62f fix minSize for app splitpanes to 20 2023-03-03 01:08:20 +01:00
Ruben Fiszel
e428662481 feat(frontend): add creatable select 2023-03-03 01:06:36 +01:00
Ruben Fiszel
b796aeef7a calculate all previous ids inside flows 2023-03-02 20:54:44 +01:00
Ruben Fiszel
55eb48c553 fix(frontend): background script not showing inputs 2023-03-02 17:54:05 +01:00
Ruben Fiszel
a43139fe53 flow improvements 2023-03-02 17:37:51 +01:00
Ruben Fiszel
c4463bb029 fix(backend): improve result retrieval 2023-03-02 16:33:24 +01:00
Ruben Fiszel
cc6eaaf473 fix tailwind JIT for devmode + graph fixes 2023-03-02 14:49:36 +01:00
Ádám Kovács
ed25d9f186 feat(frontend): Add app map component (#1251)
* feat(frontend): Add app map component (wip)

* fix(frontend): Revert

* feat(frontend): sync map configuration (#1252)

* fix(frontend): Map markers

* fix(frontend): Switching between input types

* fix(frontend): Customize map controls

* feat(frontend): Fix output + add set region button

* feat(frontend): Fix output + add set region button

* feat(frontend): Fix output + add set region button

* feat(frontend): Only display set region button on edit mode

---------

Co-authored-by: Faton Ramadani <faton.ramadani14@gmail.com>
2023-03-02 14:25:30 +01:00
Ruben Fiszel
35ea2b27b1 fix(cli): fix workspace option + run script/flow + whoami 2023-03-02 13:21:50 +01:00
Ruben Fiszel
2c1e3b3372 UX nits 2023-03-02 12:08:20 +01:00
Ruben Fiszel
4101d587de remove slide causing issues 2023-03-02 11:59:10 +01:00
Ruben Fiszel
e6ff3ab6cc remove slide causing issues 2023-03-02 11:49:57 +01:00
Ruben Fiszel
8fc6c39129 remove bg-gray-50 from viewed apps 2023-03-02 11:06:11 +01:00
Ruben Fiszel
fcb5cf4d41 revert caddyfileremote target change 2023-03-02 10:39:01 +01:00
Ruben Fiszel
2679386bf8 fix(frontend): fix table bindings 2023-03-02 09:54:30 +01:00
Ryan Rich
580388ce19 Add support for binding server listener to a specific IP address (#1253) 2023-03-02 08:01:50 +01:00
Ruben Fiszel
4e6e66d7b1 fix splitpanes 2023-03-02 02:31:39 +01:00
Faton Ramadani
f4d79ee263 feat(frontend): app splitpanes (#1248)
* feat(frontend): app splitpanes

* feat(frontend): app splitpanes vertical

* feat(frontend): support both splitpanes

* done

* done

* default select value

* container height

---------

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
2023-03-02 01:30:07 +01:00
Ruben Fiszel
38fb3450c8 fix apps tabs + make inputvalue more resilient 2023-03-01 22:15:25 +01:00
Ruben Fiszel
94b20d2f5e fix(frontend): rework app reactivity 2023-03-01 21:33:23 +01:00
Ruben Fiszel
1753cb7da6 fix(frontend): rework app reactivity 2023-03-01 20:01:59 +01:00
Ruben Fiszel
2a75cd250e fix(backend): incorrect get_result_by_id for list_result job 2023-03-01 12:43:00 +01:00
Ruben Fiszel
29f3fe2663 update sqlx-data.json 2023-03-01 12:01:22 +01:00
Ruben Fiszel
4c913dc4b6 feat(backend): get_result_by_id do a downward pass to find node at any depth (#1249)
* downwardRec

* downwardRec

* any node

* any node

* any node
2023-03-01 11:33:48 +01:00
Ruben Fiszel
5c40ff4290 Update LICENSE 2023-03-01 09:53:26 +01:00
Ruben Fiszel
2bbe112444 handle more undefined cases in app 2023-03-01 08:46:20 +01:00
Ruben Fiszel
90a12f6131 drawer focus 2023-03-01 01:21:32 +01:00
Ruben Fiszel
f3f95fa865 active grid border-dashed for apps 2023-03-01 01:18:10 +01:00
Ruben Fiszel
26784464a4 revert pips change 2023-02-28 22:53:44 +01:00
Ruben Fiszel
c96e2351d9 chore(main): release 1.71.0 (#1242)
* chore(main): release 1.71.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-28 22:51:57 +01:00
Ruben Fiszel
ddb4916a2e fix app fields 2023-02-28 22:41:21 +01:00
Ádám Kovács
1bb5ed9ae0 fix(frontend): Add more app custom css (#1247)
* fix(frontend): Add number input custom css

* fix(frontend): Add currency input custom css

* fix(frontend): Add slider custom css

* fix(frontend): Add range custom css

* fix(frontend): Add password input custom css

* fix(frontend): Add date input custom css

* fix(frontend): Add tabs custom css

* fix(frontend): Minor stylings

* fix(frontend): Add icon custom css

* fix(frontend): Add dividers custom css

* fix(frontend): Add file input custom css

* fix(frontend): Add image custom css
2023-02-28 21:05:58 +01:00
Ruben Fiszel
b5b32f00b3 fix overflow-y on debug runs 2023-02-28 19:54:20 +01:00
Ruben Fiszel
c06311faf8 add workspace_add events 2023-02-28 19:41:01 +01:00
Ruben Fiszel
8a639b6e7d select input fix 2023-02-28 19:28:36 +01:00
Ruben Fiszel
05f568fb8c display startup info in all cases 2023-02-28 17:52:47 +01:00
Ruben Fiszel
e515c70e71 fix incorrect user sttings redirect 2023-02-28 16:57:00 +01:00
Ruben Fiszel
6adc875610 feat(frontend): drawer for editing workspace scripts in flows 2023-02-28 15:51:56 +01:00
Faton Ramadani
8a0d1158c4 feat(frontend): App drawer (#1246)
* feat(frontend): app drawer

* feat(frontend): app drawer

* feat(frontend): app drawer

* feat(frontend): app drawer wip

* feat(frontend): drawer wip

* feat(frontend): drawer wip

* feat(frontend): app missing prop

* feat(frontend): revert drawer changes

* feat(frontend): highlight subgrid
2023-02-28 15:49:57 +01:00
131 changed files with 2995 additions and 1024 deletions

View File

@@ -1,6 +1,45 @@
# Changelog
## [1.72.0](https://github.com/windmill-labs/windmill/compare/v1.71.0...v1.72.0) (2023-03-02)
### Features
* **backend:** get_result_by_id do a downward pass to find node at any depth ([#1249](https://github.com/windmill-labs/windmill/issues/1249)) ([4c913dc](https://github.com/windmill-labs/windmill/commit/4c913dc4b6be03571a015c97a13829adffb61479))
* **frontend:** Add app map component ([#1251](https://github.com/windmill-labs/windmill/issues/1251)) ([ed25d9f](https://github.com/windmill-labs/windmill/commit/ed25d9f186d9925f75404cb193a025d8a41c4540))
* **frontend:** app splitpanes ([#1248](https://github.com/windmill-labs/windmill/issues/1248)) ([f4d79ee](https://github.com/windmill-labs/windmill/commit/f4d79ee2633e6cdab0fa2410108b31cfa77e10da))
### Bug Fixes
* **backend:** improve result retrieval ([c4463bb](https://github.com/windmill-labs/windmill/commit/c4463bb029907f3c8d77abb194f872aae7876bf6))
* **backend:** incorrect get_result_by_id for list_result job ([2a75cd2](https://github.com/windmill-labs/windmill/commit/2a75cd250ea5e01849fc8bbb69bf44f147d0acb8))
* **cli:** fix workspace option + run script/flow + whoami ([35ea2b2](https://github.com/windmill-labs/windmill/commit/35ea2b27b12159c68c8507ec1f8686028c975387))
* **frontend:** background script not showing inputs ([55eb48c](https://github.com/windmill-labs/windmill/commit/55eb48c55332431304cedbf3bcbbbcff61ec3645))
* **frontend:** fix table bindings ([2679386](https://github.com/windmill-labs/windmill/commit/2679386bf87a56352269911bd89e52df5ee9f314))
* **frontend:** rework app reactivity ([94b20d2](https://github.com/windmill-labs/windmill/commit/94b20d2f5e3b551974c57ea82b6e3dc16e97b9b8))
* **frontend:** rework app reactivity ([1753cb7](https://github.com/windmill-labs/windmill/commit/1753cb7da658f47be974c15da82c71a8e19309a6))
## [1.71.0](https://github.com/windmill-labs/windmill/compare/v1.70.1...v1.71.0) (2023-02-28)
### Features
* **backend:** use counter for sleep/execution/pull durations ([e568690](https://github.com/windmill-labs/windmill/commit/e56869092a03fec4703ddd9ef65c89edb8122962))
* **cli:** add autocompletions ([287b2db](https://github.com/windmill-labs/windmill/commit/287b2db22f7b56e90bcd0c4727c00096695c2e0d))
* **frontend:** App drawer ([#1246](https://github.com/windmill-labs/windmill/issues/1246)) ([8a0d115](https://github.com/windmill-labs/windmill/commit/8a0d1158c4d7e970cb91e1adf4838e5efdbb39ff))
* **frontend:** drawer for editing workspace scripts in flows ([6adc875](https://github.com/windmill-labs/windmill/commit/6adc87561070d8aceaba1838008cd7e6be2e2660))
### Bug Fixes
* **frontend:** Add more app custom css ([#1229](https://github.com/windmill-labs/windmill/issues/1229)) ([a4e4d18](https://github.com/windmill-labs/windmill/commit/a4e4d188ad10443dd0b7f104389594efc768dc59))
* **frontend:** Add more app custom css ([#1247](https://github.com/windmill-labs/windmill/issues/1247)) ([1bb5ed9](https://github.com/windmill-labs/windmill/commit/1bb5ed9ae01fd7998b06833b6222e5dd5d774d35))
* **frontend:** display currently selected filter even if not in list ([42d1cd6](https://github.com/windmill-labs/windmill/commit/42d1cd6456620ba917c560c87d736dc93634adff))
* **frontend:** Fix deeply nested move ([#1245](https://github.com/windmill-labs/windmill/issues/1245)) ([a67f10e](https://github.com/windmill-labs/windmill/commit/a67f10eeb6fdb44bbb3a510badcc5ad0ae187a2b))
* **frontend:** invisible subgrids have h-0 + app policies fix ([2244e83](https://github.com/windmill-labs/windmill/commit/2244e83b9da803a4cf46ab0825d7cb6cb0e24872))
## [1.70.1](https://github.com/windmill-labs/windmill/compare/v1.70.0...v1.70.1) (2023-02-27)

View File

@@ -8,5 +8,9 @@ or belonging to one of the below cases:
The files under backend/ are AGPL Licensed.
The files under frontend/ are AGPL Licensed.
The files under python-client/ are Apache 2.0 Licensed.
The files under community/ are Apache 2.0 Licensed.
The files under python-client/ deno-client/ go-client/ are Apache 2.0 Licensed.
The openapi files, including the OpenFlow spec is Apache 2.0 Licensed.
All third party components incorporated into the Windmill Software are licensed under the
original license provided by the owner of the applicable component.

View File

@@ -284,6 +284,7 @@ you to have it being synced automatically everyday.
| ------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| DATABASE_URL | | The Postgres database url. | All |
| DISABLE_NSJAIL | true | Disable Nsjail Sandboxing | Worker |
| SERVER_BIND_ADDR | 0.0.0.0 | IP Address on which to bind listening socket | Server |
| PORT | 8000 | Exposed port | Server | |
| NUM_WORKERS | 3 | The number of worker per Worker instance (set to 1 on Eks to have 1 pod = 1 worker, set to 0 for an API only instance) | Worker |
| DISABLE_SERVER | false | Binary would operate as a worker only instance | Worker |

171
backend/Cargo.lock generated
View File

@@ -129,12 +129,11 @@ dependencies = [
[[package]]
name = "async-lock"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
dependencies = [
"event-listener",
"futures-lite",
]
[[package]]
@@ -337,15 +336,6 @@ dependencies = [
"scoped-tls",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.3"
@@ -525,9 +515,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.1.6"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0b0588d44d4d63a87dbd75c136c166bbfd9a86a31cb89e09906521c7d3f5e3"
checksum = "c3d7ae14b20b94cb02149ed21a86c423859cbe18dc7ed69845cace50e52b40a5"
dependencies = [
"bitflags",
"clap_derive",
@@ -540,9 +530,9 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.1.0"
version = "4.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
checksum = "44bec8e5c9d09e439c4335b1af0abaab56dcf3b94999a936e1bb47b9134288f0"
dependencies = [
"heck",
"proc-macro-error",
@@ -704,9 +694,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
@@ -966,9 +956,9 @@ checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0"
[[package]]
name = "dyn-clone"
version = "1.0.10"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60"
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
[[package]]
name = "dyn-iter"
@@ -1324,9 +1314,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
dependencies = [
"bytes",
"fnv",
@@ -1678,9 +1668,9 @@ checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
[[package]]
name = "jobserver"
version = "0.1.25"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
dependencies = [
"libc",
]
@@ -2048,15 +2038,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@@ -2103,7 +2084,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
]
[[package]]
@@ -2147,27 +2127,6 @@ dependencies = [
"libc",
]
[[package]]
name = "num_enum"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@@ -2516,9 +2475,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro-crate"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66618389e4ec1c7afe67d51a9bf34ff9236480f8d51e7489b7d5ab0303c13f34"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit",
@@ -2566,7 +2525,7 @@ dependencies = [
[[package]]
name = "progenitor"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
dependencies = [
"anyhow",
"built",
@@ -2584,7 +2543,7 @@ dependencies = [
[[package]]
name = "progenitor-client"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
dependencies = [
"bytes",
"futures-core",
@@ -2598,7 +2557,7 @@ dependencies = [
[[package]]
name = "progenitor-impl"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
dependencies = [
"getopts",
"heck",
@@ -2620,7 +2579,7 @@ dependencies = [
[[package]]
name = "progenitor-macro"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#82c979df65476fe4dfc2590970ccdf64113e9e0c"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
dependencies = [
"openapiv3",
"proc-macro2",
@@ -2994,7 +2953,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -3003,24 +2962,20 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
dependencies = [
"bincode",
"bitflags",
"bstr",
"itertools",
"lz4_flex",
"num-bigint",
"num-complex",
"num_enum",
"serde",
"thiserror",
]
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#351d464448607413dc12de6905f1165f8d45af54"
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
dependencies = [
"ahash",
"anyhow",
@@ -3035,7 +2990,6 @@ dependencies = [
"rustc-hash",
"rustpython-ast",
"rustpython-compiler-core",
"thiserror",
"tiny-keccak",
"unic-emoji-char",
"unic-ucd-ident",
@@ -3688,9 +3642,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "swc_atoms"
version = "0.4.37"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f88175a66f5a7c189e752bda520e148317776ecb22c75adc2c2f24c490834bd0"
checksum = "5a172f2e444ae1378286cd27ff2a5cb26eadfd7a77c98ccb0edde8992857be1e"
dependencies = [
"once_cell",
"rustc-hash",
@@ -3702,9 +3656,9 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.29.32"
version = "0.29.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc8e0e8109b26be70c82d9709562fc88cbcc09e03c2458221cf216c0088dea2"
checksum = "a8a75c46065858a37cdda2c1c6fd056986e3b7752d5ec332e91ce312c6a22749"
dependencies = [
"ahash",
"ast_node",
@@ -3729,9 +3683,9 @@ dependencies = [
[[package]]
name = "swc_ecma_ast"
version = "0.96.7"
version = "0.98.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "621c66e27fbb6cbb6434a4e2b25e439e9a2583cc3419a4a83eba51d16ac0cd7b"
checksum = "7b1f6bc913d0f1daf0fa713a64660aed27848e0047ee671ab2206ad602730d1f"
dependencies = [
"bitflags",
"is-macro",
@@ -3746,9 +3700,9 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
version = "0.124.12"
version = "0.127.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b3f472b3dfbfd279de364d2b014459a281824b938e243a8739037c445d6b6c"
checksum = "3875db58a1515382c670679062133294e5ec88aff08776f5fb6e4f3c09c0f5f9"
dependencies = [
"either",
"enum_kind",
@@ -3967,9 +3921,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"bytes",
@@ -3983,7 +3937,7 @@ dependencies = [
"socket2",
"tokio-macros",
"tracing",
"windows-sys 0.42.0",
"windows-sys 0.45.0",
]
[[package]]
@@ -4080,19 +4034,19 @@ dependencies = [
[[package]]
name = "toml_datetime"
version = "0.5.1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4553f467ac8e3d374bc9a177a26801e5d0f9b211aa1673fb137a403afd1c9cf5"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
[[package]]
name = "toml_edit"
version = "0.18.1"
version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c59d8dd7d0dcbc6428bf7aa2f0e823e26e43b3c9aca15bbc9475d23e5fa12b"
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
dependencies = [
"indexmap",
"nom8",
"toml_datetime",
"winnow",
]
[[package]]
@@ -4290,7 +4244,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "typify"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
dependencies = [
"typify-impl",
"typify-macro",
@@ -4299,7 +4253,7 @@ dependencies = [
[[package]]
name = "typify-impl"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
dependencies = [
"heck",
"log",
@@ -4317,7 +4271,7 @@ dependencies = [
[[package]]
name = "typify-macro"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#d579a526b3cc2e0c36d17fae8df549a03187f177"
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
dependencies = [
"proc-macro2",
"quote",
@@ -4465,9 +4419,11 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unicode_names2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029df4cc8238cefc911704ff8fa210853a0f3bce2694d8f51181dd41ee0f3301"
version = "0.6.0"
source = "git+https://github.com/youknowone/unicode_names2.git?tag=v0.6.0+character-alias#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
dependencies = [
"phf",
]
[[package]]
name = "unsafe-libyaml"
@@ -4756,7 +4712,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"axum",
@@ -4783,7 +4739,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"argon2",
@@ -4838,7 +4794,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"base64 0.21.0",
"chrono",
@@ -4853,7 +4809,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"chrono",
"serde",
@@ -4866,7 +4822,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"axum",
@@ -4891,7 +4847,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"serde",
"serde_json",
@@ -4899,7 +4855,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"itertools",
@@ -4913,7 +4869,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"itertools",
@@ -4925,7 +4881,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"itertools",
@@ -4940,7 +4896,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"deno_core",
@@ -4954,7 +4910,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"chrono",
@@ -4977,7 +4933,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.70.1"
version = "1.72.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -5091,6 +5047,15 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.10.1"

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.70.1"
version = "1.72.0"
authors.workspace = true
edition.workspace = true
@@ -19,7 +19,7 @@ members = [
]
[workspace.package]
version = "1.70.1"
version = "1.72.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"

View File

@@ -550,21 +550,6 @@
},
"query": "SELECT * FROM workspace LIMIT $1 OFFSET $2"
},
"15de975d9be141c9ed9647935a508492aabbbddbf986d5c5c0f0c415293c432d": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Varchar",
"Varchar",
"Varchar",
"Varchar"
]
}
},
"query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, description)\n VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'), \n ($3, 'g/all/not_secret', $4, false, 'This item is not secret')"
},
"163f00eb8b1a489d5f382cdba22a5744e88a8e6f1532d7cb02af560f5f5d49f7": {
"describe": {
"columns": [
@@ -660,6 +645,27 @@
},
"query": "SELECT * FROM workspace_settings WHERE workspace_id = $1"
},
"176afff6dfceac701d499663f15fb7f569fd9d449406df4c5ecc811414429d42": {
"describe": {
"columns": [
{
"name": "flow_status",
"ordinal": 0,
"type_info": "Jsonb"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
}
},
"query": "SELECT flow_status FROM completed_job WHERE id = $1 AND workspace_id = $2 \n UNION ALL SELECT flow_status FROM queue WHERE id = $1 AND workspace_id = $2 "
},
"187a27d18f78f068319a0ec684f71a76c49dd09c5c74c2c777e35233c4f5a281": {
"describe": {
"columns": [
@@ -1183,33 +1189,6 @@
},
"query": "UPDATE script SET archived = true WHERE hash = $1 AND workspace_id = $2"
},
"2be0cfd075df9624ccbcbe5fd645e0a5c25460c2d01493f86dcdd9b2b71f6181": {
"describe": {
"columns": [
{
"name": "flow_status",
"ordinal": 0,
"type_info": "Jsonb"
},
{
"name": "parent_job",
"ordinal": 1,
"type_info": "Uuid"
}
],
"nullable": [
null,
null
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
}
},
"query": "SELECT flow_status, parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT flow_status, parent_job FROM queue WHERE id = $1 AND workspace_id = $2 "
},
"2e4115bb2e6c8c85ad1492ad135d6b0454b342126cb5fa17e58caf71b32ee755": {
"describe": {
"columns": [],
@@ -3678,6 +3657,33 @@
},
"query": "SELECT is_admin FROM usr where username = $1 AND workspace_id = $2 AND disabled = false"
},
"92ce38eaaef9e793a49788bf3b7fc88340e83da57b785364a477f08c776f66f3": {
"describe": {
"columns": [
{
"name": "flow_status",
"ordinal": 0,
"type_info": "Jsonb"
},
{
"name": "parent_job",
"ordinal": 1,
"type_info": "Uuid"
}
],
"nullable": [
null,
null
],
"parameters": {
"Left": [
"Uuid",
"Text"
]
}
},
"query": "SELECT flow_status, parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2 \n UNION ALL SELECT flow_status, parent_job FROM queue WHERE id = $1 AND workspace_id = $2 "
},
"930ad84a4db26fa6d2c8447d447099e944a0ea7b1266b4d02cee620fe3d761a5": {
"describe": {
"columns": [],

View File

@@ -6,7 +6,7 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::net::SocketAddr;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use git_version::git_version;
use sqlx::{Pool, Postgres};
@@ -15,6 +15,7 @@ use windmill_common::utils::rd_string;
const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version");
const DEFAULT_NUM_WORKERS: usize = 3;
const DEFAULT_PORT: u16 = 8000;
const DEFAULT_SERVER_BIND_ADDR: Ipv4Addr = Ipv4Addr::new(0, 0, 0, 0);
mod ee;
@@ -39,6 +40,11 @@ async fn main() -> anyhow::Result<()> {
.transpose()?
.flatten();
let server_bind_address: IpAddr = std::env::var("SERVER_BIND_ADDR")
.ok()
.and_then(|x| x.parse().ok() )
.unwrap_or(IpAddr::from(DEFAULT_SERVER_BIND_ADDR));
let port: u16 = std::env::var("PORT")
.ok()
.and_then(|x| x.parse::<u16>().ok())
@@ -60,8 +66,63 @@ async fn main() -> anyhow::Result<()> {
let (tx, rx) = tokio::sync::broadcast::channel::<()>(3);
let shutdown_signal = windmill_common::shutdown_signal(tx);
#[cfg(feature = "enterprise")]
tracing::info!(
"
##############################
Windmill Enterprise Edition {GIT_VERSION}
##############################"
);
#[cfg(not(feature = "enterprise"))]
tracing::info!(
"
##############################
Windmill Community Edition {GIT_VERSION}
##############################"
);
display_config(vec![
"DISABLE_NSJAIL",
"DISABLE_SERVER",
"NUM_WORKERS",
"METRICS_ADDR",
"JSON_FMT",
"BASE_URL",
"BASE_INTERNAL_URL",
"TIMEOUT",
"SLEEP_QUEUE",
"MAX_LOG_SIZE",
"SERVER_BIND_ADDR",
"PORT",
"KEEP_JOB_DIR",
"S3_CACHE_BUCKET",
"TAR_CACHE_RATE",
"COOKIE_DOMAIN",
"PYTHON_PATH",
"DENO_PATH",
"GO_PATH",
"PIP_INDEX_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_TRUSTED_HOST",
"PATH",
"HOME",
"DATABASE_CONNECTIONS",
"TIMEOUT_WAIT_RESULT",
"QUEUE_LIMIT_WAIT_RESULT",
"DENO_AUTH_TOKENS",
"DENO_FLAGS",
"PIP_LOCAL_DEPENDENCIES",
"ADDITIONAL_PYTHON_PATHS",
"INCLUDE_HEADERS",
"WHITELIST_WORKSPACES",
"BLACKLIST_WORKSPACES",
"NEW_USER_WEBHOOK",
"CLOUD_HOSTED",
]);
if server_mode || num_workers > 0 {
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let addr = SocketAddr::from((server_bind_address, port));
let server_f = async {
if server_mode {
@@ -72,60 +133,6 @@ async fn main() -> anyhow::Result<()> {
let workers_f = async {
if num_workers > 0 {
#[cfg(feature = "enterprise")]
tracing::info!(
"
##############################
Windmill Enterprise Edition {GIT_VERSION}
##############################"
);
#[cfg(not(feature = "enterprise"))]
tracing::info!(
"
##############################
Windmill Community Edition {GIT_VERSION}
##############################"
);
display_config(vec![
"DISABLE_NSJAIL",
"DISABLE_SERVER",
"NUM_WORKERS",
"METRICS_ADDR",
"JSON_FMT",
"BASE_URL",
"BASE_INTERNAL_URL",
"TIMEOUT",
"SLEEP_QUEUE",
"MAX_LOG_SIZE",
"PORT",
"KEEP_JOB_DIR",
"S3_CACHE_BUCKET",
"TAR_CACHE_RATE",
"COOKIE_DOMAIN",
"PYTHON_PATH",
"DENO_PATH",
"GO_PATH",
"PIP_INDEX_URL",
"PIP_EXTRA_INDEX_URL",
"PIP_TRUSTED_HOST",
"PATH",
"HOME",
"DATABASE_CONNECTIONS",
"TIMEOUT_WAIT_RESULT",
"QUEUE_LIMIT_WAIT_RESULT",
"DENO_AUTH_TOKENS",
"DENO_FLAGS",
"PIP_LOCAL_DEPENDENCIES",
"ADDITIONAL_PYTHON_PATHS",
"INCLUDE_HEADERS",
"WHITELIST_WORKSPACES",
"BLACKLIST_WORKSPACES",
"NEW_USER_WEBHOOK",
"CLOUD_HOSTED",
]);
run_workers(
db.clone(),
rx.resubscribe(),

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.70.1
version: 1.72.0
title: Windmill API
contact:
@@ -3337,6 +3337,22 @@ paths:
schema:
$ref: "#/components/schemas/CompletedJob"
/w/{workspace}/jobs/completed/get_result/{id}:
get:
summary: get completed job result
operationId: getCompletedJobResult
tags:
- job
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/JobId"
responses:
"200":
description: result
content:
application/json:
schema: {}
/w/{workspace}/jobs/completed/delete/{id}:
post:
summary: delete completed job (erase content but keep run id)

View File

@@ -7,11 +7,12 @@
*/
use axum::{
extract::{Extension, Path},
extract::{Extension, Path, Query},
routing::{get, post, put},
Json, Router,
};
use hyper::StatusCode;
use hyper::{HeaderMap, StatusCode};
use serde::Deserialize;
use windmill_common::{
error::{JsonResult, Result},
utils::{not_found_if_none, StripPath},
@@ -19,6 +20,7 @@ use windmill_common::{
use crate::{
db::{UserDB, DB},
jobs::add_include_headers,
users::Authed,
};
@@ -83,13 +85,21 @@ pub async fn new_payload(
Ok(StatusCode::CREATED)
}
#[derive(Deserialize, Clone)]
pub struct IncludeHeaderQuery {
include_header: Option<String>,
}
pub async fn update_payload(
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
Json(payload): Json<serde_json::Value>,
Query(run_query): Query<IncludeHeaderQuery>,
headers: HeaderMap,
Json(args): Json<Option<serde_json::Map<String, serde_json::Value>>>,
) -> Result<StatusCode> {
let mut tx = db.begin().await?;
let args = add_include_headers(&run_query.include_header, headers, args.unwrap_or_default());
sqlx::query!(
"
UPDATE capture
@@ -99,7 +109,7 @@ pub async fn update_payload(
",
&w_id,
&path.to_path(),
&payload,
serde_json::json!(args),
)
.execute(&mut tx)
.await?;

View File

@@ -295,28 +295,38 @@ impl RunJobQuery {
fn add_include_headers(
&self,
headers: HeaderMap,
mut args: serde_json::Map<String, serde_json::Value>,
args: serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
let whitelist = self
.include_header
.as_ref()
.map(|s| s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default();
whitelist
.iter()
.chain(INCLUDE_HEADERS.iter())
.for_each(|h| {
if let Some(v) = headers.get(h) {
args.insert(
h.to_string().to_lowercase().replace('-', "_"),
serde_json::Value::String(v.to_str().unwrap().to_string()),
);
}
});
args
return add_include_headers(&self.include_header, headers, args);
}
}
pub fn add_include_headers(
include_header: &Option<String>,
headers: HeaderMap,
mut args: serde_json::Map<String, serde_json::Value>,
) -> serde_json::Map<String, serde_json::Value> {
if include_header.is_none() {
return args;
}
let whitelist = include_header
.as_ref()
.map(|s| s.split(",").map(|s| s.to_string()).collect::<Vec<_>>())
.unwrap_or_default();
whitelist
.iter()
.chain(INCLUDE_HEADERS.iter())
.for_each(|h| {
if let Some(v) = headers.get(h) {
args.insert(
h.to_string().to_lowercase().replace('-', "_"),
serde_json::Value::String(v.to_str().unwrap().to_string()),
);
}
});
args
}
#[derive(Deserialize)]
pub struct ListQueueQuery {
pub script_path_start: Option<String>,
@@ -1775,6 +1785,7 @@ async fn get_completed_job(
.fetch_optional(&db)
.await?;
tracing::info!("job_o: {:?}", job_o);
let job = not_found_if_none(job_o, "Completed Job", id.to_string())?;
Ok(Json(job))
}

View File

@@ -1311,7 +1311,9 @@ async fn create_user(
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
let _ = HTTP_CLIENT
.post(&new_user_webhook)
.json(&serde_json::json!({"email" : &nu.email, "name": &nu.name, "event": "new_user"}))
.json(
&serde_json::json!({"email" : &nu.email, "name": &nu.name, "event": "global_add"}),
)
.send()
.await
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));

View File

@@ -965,7 +965,7 @@ async fn invite_user(
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
let _ = &HTTP_CLIENT
.post(&new_user_webhook)
.json(&serde_json::json!({"email" : &nu.email, "event": "new_invite"}))
.json(&serde_json::json!({"email" : &nu.email, "event": "workspace_invite"}))
.send()
.await
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
@@ -1002,6 +1002,15 @@ async fn add_user(
tx.commit().await?;
if let Some(new_user_webhook) = NEW_USER_WEBHOOK.clone() {
let _ = HTTP_CLIENT
.post(&new_user_webhook)
.json(&serde_json::json!({"email" : &nu.email, "event": "workspace_add"}))
.send()
.await
.map_err(|e| tracing::error!("Error sending new user webhook: {}", e.to_string()));
}
Ok((
StatusCode::CREATED,
format!("user with email {} added", nu.email),

View File

@@ -6,7 +6,10 @@
* LICENSE-AGPL for a copy of the license.
*/
use std::{collections::HashMap, str::FromStr};
use std::{
collections::{HashMap, VecDeque},
str::FromStr,
};
use anyhow::Context;
use reqwest::Client;
@@ -17,7 +20,7 @@ use ulid::Ulid;
use uuid::Uuid;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
error::{self, Error},
error::{self, to_anyhow, Error},
flow_status::{FlowStatus, JobResult, MAX_RETRY_ATTEMPTS, MAX_RETRY_INTERVAL},
flows::{FlowModule, FlowModuleValue, FlowValue},
scripts::{get_full_hub_script_by_path, HubScript, ScriptHash, ScriptLang},
@@ -150,6 +153,41 @@ pub async fn pull(
Ok(job)
}
pub async fn find_recursively_downward(
db: &Pool<Postgres>,
w_id: &str,
flow_id: Uuid,
node_id: &str,
) -> windmill_common::error::Result<Option<JobResult>> {
let mut bfs_stack = VecDeque::new();
bfs_stack.push_back(flow_id);
while bfs_stack.len() > 0 {
let parent_id = bfs_stack.pop_front().unwrap();
let job = sqlx::query_scalar!(
"SELECT flow_status FROM completed_job WHERE id = $1 AND workspace_id = $2
UNION ALL SELECT flow_status FROM queue WHERE id = $1 AND workspace_id = $2 ",
parent_id,
w_id
)
.fetch_optional(db)
.await?
.flatten();
if let Some(r) = job {
let status = serde_json::from_value::<FlowStatus>(r).map_err(to_anyhow)?;
for m in status.modules.iter() {
let id = m.id();
if id == node_id {
return Ok(m.job_result());
}
if let Some(job_id) = m.job() {
bfs_stack.push_back(job_id);
}
}
}
}
Ok(None)
}
pub async fn get_result_by_id(
db: Pool<Postgres>,
mut skip_direct: bool,
@@ -159,10 +197,12 @@ pub async fn get_result_by_id(
) -> error::Result<serde_json::Value> {
let mut result_id: Option<JobResult> = None;
let mut parent_id = Uuid::from_str(&flow_id).ok();
let mut lparent_id = parent_id.clone();
while result_id.is_none() && parent_id.is_some() {
if !skip_direct {
let r = sqlx::query!(
"SELECT flow_status, parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2 UNION ALL SELECT flow_status, parent_job FROM queue WHERE id = $1 AND workspace_id = $2 ",
"SELECT flow_status, parent_job FROM completed_job WHERE id = $1 AND workspace_id = $2
UNION ALL SELECT flow_status, parent_job FROM queue WHERE id = $1 AND workspace_id = $2 ",
parent_id.unwrap(),
w_id,
)
@@ -174,6 +214,7 @@ pub async fn get_result_by_id(
.as_ref()
.ok_or_else(|| Error::InternalErr(format!("requiring a flow status value")))?
.to_owned();
lparent_id = parent_id;
parent_id = r.parent_job;
let status_o = serde_json::from_value::<FlowStatus>(value).ok();
result_id = status_o.and_then(|status| {
@@ -195,10 +236,17 @@ pub async fn get_result_by_id(
.fetch_optional(&db)
.await?
.flatten();
lparent_id = parent_id;
parent_id = q_parent;
skip_direct = false
}
}
// we could not find the node going upward from the flow by looking at all the jobs (in progress or completed)
// we now look downward from the flow root to the all the children completed job for a job that might hide itself
// in a deep non-direct parent job such as in nested branches
if result_id.is_none() && lparent_id.is_some() {
result_id = find_recursively_downward(&db, &w_id, lparent_id.unwrap(), &node_id).await?;
}
let result_id = windmill_common::utils::not_found_if_none(
result_id,
"Flow result by id",

View File

@@ -145,7 +145,7 @@ fn add_closing_bracket(s: &str) -> String {
s
}
const SPLIT_PAT: &str = ";\n";
const SPLIT_PAT: &str = ";";
async fn eval(
context: &mut JsRuntime,
expr: &str,
@@ -154,14 +154,21 @@ async fn eval(
by_id: Option<IdContext>,
base_internal_url: &str,
) -> anyhow::Result<serde_json::Value> {
let expr = expr.trim();
let expr = format!(
"{}\nreturn {};",
expr.split(SPLIT_PAT)
.take(expr.split(SPLIT_PAT).count() - 1)
.join("\n"),
expr.split(SPLIT_PAT).last().unwrap_or_else(|| "")
);
let exprs = expr
.trim()
.split(SPLIT_PAT)
.map(|x| x.trim())
.filter(|x| !x.is_empty())
.collect::<Vec<&str>>();
let expr = if exprs.is_empty() {
"return undefined;".to_string()
} else {
format!(
"{};\n return {};",
exprs.iter().take(exprs.len() - 1).join(";\n"),
exprs.last().unwrap()
)
};
let (api_code, by_id_code) = if let Some(EvalCreds { workspace, token }) = creds {
let by_id_code = if let Some(by_id) = by_id {
format!(
@@ -198,12 +205,12 @@ const results = new Proxy({{}}, {{
.into_iter()
.map(|(k, v)| {
let v_str = match v {
JobResult::SingleJob(x) => x.to_string(),
JobResult::SingleJob(x) => format!("\"{x}\""),
JobResult::ListJob(x) => {
format!("[{}]", x.iter().map(|x| x.to_string()).join(","))
format!("[{}]", x.iter().map(|x| format!("\"{x}\"")).join(","))
}
};
format!("\"{k}\": \"{v_str}\"")
format!("\"{k}\": {v_str}")
})
.join(","),
by_id.previous_id,
@@ -292,9 +299,8 @@ async fn op_get_result(args: Vec<String>) -> Result<serde_json::Value, anyhow::E
let base_url = &args[3];
let client = windmill_api_client::create_client(base_url, token.clone());
let result = client
.get_completed_job(workspace, &id.parse()?)
.get_completed_job_result(workspace, &id.parse()?)
.await?
.result
.clone();
Ok(serde_json::json!(result))
}

View File

@@ -23,7 +23,7 @@ async function tryResolveWorkspace(
{ isError: false; value: Workspace } | { isError: true; error: string }
> {
const cache = (opts as any).__secret_workspace;
if (cache) return cache;
if (cache) return { isError: false, value: cache };
if (opts.workspace) {
const e = await getWorkspaceByName(opts.workspace);
@@ -53,7 +53,6 @@ export async function resolveWorkspace(
): Promise<Workspace> {
const res = await tryResolveWorkspace(opts);
if (res.isError) {
console.log(res.error);
return Deno.exit(-1);
} else {
return res.value;
@@ -62,8 +61,8 @@ export async function resolveWorkspace(
export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo> {
const workspace = await resolveWorkspace(opts);
let token = await tryGetLoginInfo(opts);
if (!token) {
token = workspace.token;
}
@@ -80,9 +79,9 @@ export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo>
if (!newToken) {
throw new Error("Could not reauth");
}
removeWorkspace(workspace.name);
removeWorkspace(workspace.name, false, opts);
workspace.token = newToken;
addWorkspace(workspace);
addWorkspace(workspace, opts);
setClient(
token,

View File

@@ -192,7 +192,7 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
}
async function run(
opts: GlobalOptions & {
input: string[];
data?: string;
silent: boolean;
},
path: string,
@@ -200,7 +200,8 @@ async function run(
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
const input = await resolve(opts.input);
const input = opts.data ? await resolve(opts.data) : {};
const id = await JobService.runFlowByPath({
workspace: workspace.workspaceId,
@@ -236,6 +237,7 @@ async function run(
if (!opts.silent) {
console.log(colors.green.underline.bold("Flow ran to completion"));
console.log()
}
const jobInfo = await JobService.getCompletedJob({
workspace: workspace.workspaceId,
@@ -257,8 +259,8 @@ const command = new Command()
.command("run", "run a flow by path.")
.arguments("<path:string>")
.option(
"-i --input [inputs...:string]",
"Inputs specified as JSON objects or simply as <name>=<value>. Supports file inputs using @<filename> and stdin using @- these also need to be formatted as JSON. Later inputs override earlier ones.",
"-d --data <data:string>",
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-.",
)
.option(
"-s --silent",

View File

@@ -13,7 +13,7 @@ import sync from "./sync.ts";
import { tryResolveVersion } from "./context.ts";
import { GlobalOptions } from "./types.ts";
const VERSION = "v1.70.1";
const VERSION = "v1.72.0";
let command: any = new Command()
.name("wmill")

View File

@@ -11,6 +11,7 @@ import {
Table,
} from "./deps.ts";
import { Any, array, decoverto, model, property } from "./decoverto.ts";
import { writeAllSync } from "https://deno.land/std@0.176.0/streams/mod.ts";
@model()
export class ScriptFile {
@@ -280,50 +281,27 @@ async function list(opts: GlobalOptions & { showArchived?: boolean }) {
.render();
}
export async function resolve(inputs: string[]): Promise<Record<string, any>> {
let result = {};
if (!inputs) {
return result;
export async function resolve(input: string): Promise<Record<string, any>> {
if (!input) {
throw new Error("No data given");
}
for (const input of inputs) {
let data: string;
if (input.startsWith("@")) {
if (input == "@-") {
data = new TextDecoder().decode(await readAll(Deno.stdin));
} else {
data = await Deno.readTextFile(input.substring(1));
}
} else {
if (input.startsWith("{")) {
data = input;
} else {
const key = input.split("=", 1)[0];
const value = input.substring(key.length + 1);
let o;
try {
o = JSON.parse(value);
} catch {
o = value;
}
data = JSON.stringify(Object.fromEntries([[key, o]]));
}
}
let jsonObj;
try {
jsonObj = JSON.parse(data);
} catch {
jsonObj = data;
}
result = { ...result, ...jsonObj };
if (input == "@-") {
input = new TextDecoder().decode(await readAll(Deno.stdin));
} if (input[0] == "@") {
input = await Deno.readTextFile(input.substring(1));
}
try {
return JSON.parse(input);
} catch (e) {
console.error("Impossible to parse input as JSON", input)
throw e
}
return result;
}
async function run(
opts: GlobalOptions & {
input: string[];
data?: string;
silent: boolean;
},
path: string,
@@ -331,7 +309,8 @@ async function run(
const workspace = await resolveWorkspace(opts);
await requireLogin(opts);
const input = await resolve(opts.input);
const input = opts.data ? await resolve(opts.data) : {};
const id = await JobService.runScriptByPath({
workspace: workspace.workspaceId,
path,
@@ -364,7 +343,9 @@ export async function track_job(workspace: string, id: string) {
const result = await JobService.getCompletedJob({ workspace, id });
console.log(result.logs);
console.log()
console.log(colors.bold.underline.green("Job Completed"));
console.log()
return;
} catch {
/* ignore */
@@ -403,7 +384,7 @@ export async function track_job(workspace: string, id: string) {
}
if (updates.new_logs) {
console.log(updates.new_logs);
writeAllSync(Deno.stdout, new TextEncoder().encode(updates.new_logs));
logOffset += updates.new_logs.length;
}
@@ -426,12 +407,15 @@ export async function track_job(workspace: string, id: string) {
if ((final_job.logs?.length ?? -1) > logOffset) {
console.log(final_job.logs!.substring(logOffset));
}
console.log("\n")
if (final_job.success) {
console.log(colors.bold.underline.green("Job Completed"));
} else {
console.log(colors.bold.underline.red("Job Completed"));
}
console.log()
} catch {
console.log("Job appears to have completed, but no data can be retrieved");
}
@@ -466,8 +450,8 @@ const command = new Command()
.command("run", "run a script by path")
.arguments("<path:string>")
.option(
"-i --input [inputs...:string]",
"Inputs specified as JSON objects or simply as <name>=<value>. Supports file inputs using @<filename> and stdin using @- these also need to be formatted as JSON. Later inputs override earlier ones.",
"-d --data <data:string>",
"Inputs specified as a JSON string or a file using @<filename> or stdin using @-.",
)
.option(
"-s --silent",

View File

@@ -9,9 +9,11 @@ import {
Input,
setClient,
Table,
UserService,
WorkspaceService,
} from "./deps.ts";
import { decoverto, model, property } from "./decoverto.ts";
import { requireLogin } from "./context.ts";
@model()
export class Workspace {
@@ -308,6 +310,11 @@ async function remove(_opts: GlobalOptions, name: string) {
await removeWorkspace(name, false, _opts);
}
async function whoami(_opts: GlobalOptions) {
await requireLogin(_opts)
console.log(await UserService.globalWhoami())
}
const command = new Command()
.description("workspace related commands")
.action(list as any)
@@ -335,6 +342,9 @@ const command = new Command()
.command("remove")
.description("Remove a workspace")
.arguments("<workspace_name:string>")
.action(remove as any);
.action(remove as any)
.command("whoami")
.description("Show the currently active user")
.action(whoami as any);
export default command;

View File

@@ -1,12 +1,12 @@
{
"name": "windmill",
"version": "1.70.1",
"version": "1.72.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "windmill",
"version": "1.70.1",
"version": "1.72.0",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
@@ -45,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.4",
"@windmill-labs/svelte-grid": "^5.1.6",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
@@ -54,6 +54,7 @@
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-svelte3": "^4.0.0",
"ol": "^7.2.2",
"openapi-typescript-codegen": "^0.23.0",
"path-browserify": "^1.0.1",
"postcss": "^8.4.18",
@@ -718,6 +719,49 @@
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
},
"node_modules/@mapbox/jsonlint-lines-primitives": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
"integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/@mapbox/mapbox-gl-style-spec": {
"version": "13.28.0",
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz",
"integrity": "sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==",
"dev": true,
"dependencies": {
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
"@mapbox/point-geometry": "^0.1.0",
"@mapbox/unitbezier": "^0.0.0",
"csscolorparser": "~1.0.2",
"json-stringify-pretty-compact": "^2.0.0",
"minimist": "^1.2.6",
"rw": "^1.3.3",
"sort-object": "^0.3.2"
},
"bin": {
"gl-style-composite": "bin/gl-style-composite.js",
"gl-style-format": "bin/gl-style-format.js",
"gl-style-migrate": "bin/gl-style-migrate.js",
"gl-style-validate": "bin/gl-style-validate.js"
}
},
"node_modules/@mapbox/point-geometry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
"integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==",
"dev": true
},
"node_modules/@mapbox/unitbezier": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz",
"integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==",
"dev": true
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -753,6 +797,12 @@
"node": ">= 8"
}
},
"node_modules/@petamoriken/float16": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.7.1.tgz",
"integrity": "sha512-oXZOc+aePd0FnhTWk15pyqK+Do87n0TyLV1nxdEougE95X/WXWDqmQobfhgnSY7QsWn5euZUWuDVeTQvoQ5VNw==",
"dev": true
},
"node_modules/@playwright/test": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.31.1.tgz",
@@ -1268,9 +1318,9 @@
}
},
"node_modules/@windmill-labs/svelte-grid": {
"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==",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
"dev": true
},
"node_modules/@windmill-labs/svelvet": {
@@ -2173,6 +2223,12 @@
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/csscolorparser": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
"integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==",
"dev": true
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -2618,6 +2674,12 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/earcut": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
"dev": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -3181,6 +3243,36 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/geotiff": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.0.7.tgz",
"integrity": "sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==",
"dev": true,
"dependencies": {
"@petamoriken/float16": "^3.4.7",
"lerc": "^3.0.0",
"pako": "^2.0.4",
"parse-headers": "^2.0.2",
"quick-lru": "^6.1.1",
"web-worker": "^1.2.0",
"xml-utils": "^1.0.2"
},
"engines": {
"node": ">=10.19"
}
},
"node_modules/geotiff/node_modules/quick-lru": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz",
"integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -3442,6 +3534,26 @@
"node": ">=10.17.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -3749,6 +3861,12 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
"node_modules/json-stringify-pretty-compact": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz",
"integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==",
"dev": true
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
@@ -3792,6 +3910,12 @@
"dev": true,
"peer": true
},
"node_modules/lerc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
"integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==",
"dev": true
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -3926,6 +4050,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/mapbox-to-css-font": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.2.tgz",
"integrity": "sha512-f+NBjJJY4T3dHtlEz1wCG7YFlkODEjFIYlxDdLIDMNpkSksqTt+l/d4rjuwItxuzkuMFvPyrjzV2lxRM4ePcIA==",
"dev": true
},
"node_modules/mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@@ -4365,6 +4495,33 @@
"node": ">= 6"
}
},
"node_modules/ol": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/ol/-/ol-7.2.2.tgz",
"integrity": "sha512-eqJ1hhVQQ3Ap4OhYq9DRu5pz9RMpLhmoTauDoIqpn7logVi1AJE+lXjEHrPrTSuZYjtFbMgqr07sxoLNR65nrw==",
"dev": true,
"dependencies": {
"earcut": "^2.2.3",
"geotiff": "^2.0.7",
"ol-mapbox-style": "^9.2.0",
"pbf": "3.2.1",
"rbush": "^3.0.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/openlayers"
}
},
"node_modules/ol-mapbox-style": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-9.7.0.tgz",
"integrity": "sha512-YX3u8FBJHsRHaoGxmd724Mp5WPTuV7wLQW6zZhcihMuInsSdCX1EiZfU+8IAL7jG0pbgl5YgC0aWE/MXJcUXxg==",
"dev": true,
"dependencies": {
"@mapbox/mapbox-gl-style-spec": "^13.23.1",
"mapbox-to-css-font": "^2.4.1"
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
@@ -4473,6 +4630,12 @@
"node": ">=6"
}
},
"node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"dev": true
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -4485,6 +4648,12 @@
"node": ">=6"
}
},
"node_modules/parse-headers": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz",
"integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==",
"dev": true
},
"node_modules/parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -4575,6 +4744,19 @@
"node": ">=8"
}
},
"node_modules/pbf": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
"integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
"dev": true,
"dependencies": {
"ieee754": "^1.1.12",
"resolve-protobuf-schema": "^2.1.0"
},
"bin": {
"pbf": "bin/pbf"
}
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -5219,6 +5401,12 @@
"svelte": "^3.2.0"
}
},
"node_modules/protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"dev": true
},
"node_modules/punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -5267,6 +5455,12 @@
"node": ">=8"
}
},
"node_modules/quickselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
"dev": true
},
"node_modules/range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
@@ -5277,6 +5471,15 @@
"node": ">= 0.6"
}
},
"node_modules/rbush": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
"integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
"dev": true,
"dependencies": {
"quickselect": "^2.0.0"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -5549,6 +5752,15 @@
"node": ">=4"
}
},
"node_modules/resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
"dev": true,
"dependencies": {
"protocol-buffers-schema": "^3.3.1"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -5613,6 +5825,12 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/rw": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"dev": true
},
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -5868,6 +6086,37 @@
"sorcery": "bin/sorcery"
}
},
"node_modules/sort-asc": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz",
"integrity": "sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sort-desc": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.1.1.tgz",
"integrity": "sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sort-object": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/sort-object/-/sort-object-0.3.2.tgz",
"integrity": "sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==",
"dev": true,
"dependencies": {
"sort-asc": "^0.1.0",
"sort-desc": "^0.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -6937,6 +7186,12 @@
"npm": ">=8.0.0"
}
},
"node_modules/web-worker": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
"integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==",
"dev": true
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -7142,6 +7397,12 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/xml-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.3.0.tgz",
"integrity": "sha512-i4PIrX33Wd66dvwo4syicwlwmnr6wuvvn4f2ku9hA67C2Uk62Xubczuhct+Evnd12/DV71qKNeDdJwES8HX1RA==",
"dev": true
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -7570,6 +7831,40 @@
"resolved": "https://registry.npmjs.org/@leeoniya/ufuzzy/-/ufuzzy-0.9.1.tgz",
"integrity": "sha512-xlL3HMhHdZX/UVTyjiLB0hpUa71HERhegvrfkUD9NxGnYh9kk9+/llynEWS+NmGRB2KlRKoY+HmWZzt/c0whJQ=="
},
"@mapbox/jsonlint-lines-primitives": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz",
"integrity": "sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==",
"dev": true
},
"@mapbox/mapbox-gl-style-spec": {
"version": "13.28.0",
"resolved": "https://registry.npmjs.org/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz",
"integrity": "sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==",
"dev": true,
"requires": {
"@mapbox/jsonlint-lines-primitives": "~2.0.2",
"@mapbox/point-geometry": "^0.1.0",
"@mapbox/unitbezier": "^0.0.0",
"csscolorparser": "~1.0.2",
"json-stringify-pretty-compact": "^2.0.0",
"minimist": "^1.2.6",
"rw": "^1.3.3",
"sort-object": "^0.3.2"
}
},
"@mapbox/point-geometry": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz",
"integrity": "sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==",
"dev": true
},
"@mapbox/unitbezier": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz",
"integrity": "sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==",
"dev": true
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7596,6 +7891,12 @@
"fastq": "^1.6.0"
}
},
"@petamoriken/float16": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.7.1.tgz",
"integrity": "sha512-oXZOc+aePd0FnhTWk15pyqK+Do87n0TyLV1nxdEougE95X/WXWDqmQobfhgnSY7QsWn5euZUWuDVeTQvoQ5VNw==",
"dev": true
},
"@playwright/test": {
"version": "1.31.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.31.1.tgz",
@@ -7924,9 +8225,9 @@
}
},
"@windmill-labs/svelte-grid": {
"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==",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
"dev": true
},
"@windmill-labs/svelvet": {
@@ -8567,6 +8868,12 @@
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
"dev": true
},
"csscolorparser": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/csscolorparser/-/csscolorparser-1.0.3.tgz",
"integrity": "sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==",
"dev": true
},
"cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -8895,6 +9202,12 @@
"domhandler": "^4.2.0"
}
},
"earcut": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz",
"integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==",
"dev": true
},
"eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -9337,6 +9650,29 @@
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"geotiff": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/geotiff/-/geotiff-2.0.7.tgz",
"integrity": "sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==",
"dev": true,
"requires": {
"@petamoriken/float16": "^3.4.7",
"lerc": "^3.0.0",
"pako": "^2.0.4",
"parse-headers": "^2.0.2",
"quick-lru": "^6.1.1",
"web-worker": "^1.2.0",
"xml-utils": "^1.0.2"
},
"dependencies": {
"quick-lru": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz",
"integrity": "sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==",
"dev": true
}
}
},
"get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -9529,6 +9865,12 @@
"dev": true,
"optional": true
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true
},
"ignore": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -9758,6 +10100,12 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
"json-stringify-pretty-compact": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz",
"integrity": "sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==",
"dev": true
},
"jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
@@ -9793,6 +10141,12 @@
"dev": true,
"peer": true
},
"lerc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lerc/-/lerc-3.0.0.tgz",
"integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==",
"dev": true
},
"levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
@@ -9901,6 +10255,12 @@
"dev": true,
"peer": true
},
"mapbox-to-css-font": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/mapbox-to-css-font/-/mapbox-to-css-font-2.4.2.tgz",
"integrity": "sha512-f+NBjJJY4T3dHtlEz1wCG7YFlkODEjFIYlxDdLIDMNpkSksqTt+l/d4rjuwItxuzkuMFvPyrjzV2lxRM4ePcIA==",
"dev": true
},
"mathml-tag-names": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@@ -10239,6 +10599,29 @@
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true
},
"ol": {
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/ol/-/ol-7.2.2.tgz",
"integrity": "sha512-eqJ1hhVQQ3Ap4OhYq9DRu5pz9RMpLhmoTauDoIqpn7logVi1AJE+lXjEHrPrTSuZYjtFbMgqr07sxoLNR65nrw==",
"dev": true,
"requires": {
"earcut": "^2.2.3",
"geotiff": "^2.0.7",
"ol-mapbox-style": "^9.2.0",
"pbf": "3.2.1",
"rbush": "^3.0.1"
}
},
"ol-mapbox-style": {
"version": "9.7.0",
"resolved": "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-9.7.0.tgz",
"integrity": "sha512-YX3u8FBJHsRHaoGxmd724Mp5WPTuV7wLQW6zZhcihMuInsSdCX1EiZfU+8IAL7jG0pbgl5YgC0aWE/MXJcUXxg==",
"dev": true,
"requires": {
"@mapbox/mapbox-gl-style-spec": "^13.23.1",
"mapbox-to-css-font": "^2.4.1"
}
},
"on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
@@ -10317,6 +10700,12 @@
"dev": true,
"peer": true
},
"pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"dev": true
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -10326,6 +10715,12 @@
"callsites": "^3.0.0"
}
},
"parse-headers": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz",
"integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==",
"dev": true
},
"parse-json": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
@@ -10398,6 +10793,16 @@
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"pbf": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
"integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
"dev": true,
"requires": {
"ieee754": "^1.1.12",
"resolve-protobuf-schema": "^2.1.0"
}
},
"picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -10787,6 +11192,12 @@
"dev": true,
"requires": {}
},
"protocol-buffers-schema": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz",
"integrity": "sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==",
"dev": true
},
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
@@ -10812,6 +11223,12 @@
"dev": true,
"peer": true
},
"quickselect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
"dev": true
},
"range-parser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
@@ -10819,6 +11236,15 @@
"dev": true,
"optional": true
},
"rbush": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
"integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
"dev": true,
"requires": {
"quickselect": "^2.0.0"
}
},
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -11031,6 +11457,15 @@
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"resolve-protobuf-schema": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
"integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
"dev": true,
"requires": {
"protocol-buffers-schema": "^3.3.1"
}
},
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
@@ -11064,6 +11499,12 @@
"queue-microtask": "^1.2.2"
}
},
"rw": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
"integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==",
"dev": true
},
"sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
@@ -11270,6 +11711,28 @@
"sander": "^0.5.0"
}
},
"sort-asc": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.1.0.tgz",
"integrity": "sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==",
"dev": true
},
"sort-desc": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.1.1.tgz",
"integrity": "sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==",
"dev": true
},
"sort-object": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/sort-object/-/sort-object-0.3.2.tgz",
"integrity": "sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==",
"dev": true,
"requires": {
"sort-asc": "^0.1.0",
"sort-desc": "^0.1.1"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -12027,6 +12490,12 @@
"vscode-jsonrpc": "8.0.2"
}
},
"web-worker": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.2.0.tgz",
"integrity": "sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==",
"dev": true
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -12170,6 +12639,12 @@
"signal-exit": "^3.0.7"
}
},
"xml-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/xml-utils/-/xml-utils-1.3.0.tgz",
"integrity": "sha512-i4PIrX33Wd66dvwo4syicwlwmnr6wuvvn4f2ku9hA67C2Uk62Xubczuhct+Evnd12/DV71qKNeDdJwES8HX1RA==",
"dev": true
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.70.1",
"version": "1.72.0",
"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.4",
"@windmill-labs/svelte-grid": "^5.1.6",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
@@ -34,6 +34,7 @@
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-svelte3": "^4.0.0",
"ol": "^7.2.2",
"openapi-typescript-codegen": "^0.23.0",
"path-browserify": "^1.0.1",
"postcss": "^8.4.18",

View File

@@ -47,6 +47,7 @@ declare module '@windmill-labs/svelte-grid' {
onTopId?: string
scroller?: undefined
sensor?: number
parentWidth?: number
}
export interface Slots<T> {

View File

@@ -112,7 +112,7 @@
<div class="mb-2 text-gray-500 text-sm bg-gray-50/20">
as JSON&nbsp;<input type="checkbox" bind:checked={forceJson} /></div
>{/if}{#if typeof result == 'object' && Object.keys(result).length > 0}<div
class="mb-2 text-sm text-gray-700 relative"
class="mb-2 min-w-[400px] text-sm text-gray-700 relative"
>The result keys are: <b>{truncate(Object.keys(result).join(', '), 50)}</b>
<div class="text-gray-500 text-sm absolute top-0 right-2">
<button on:click={jsonViewer.openDrawer}>Expand JSON</button>

View File

@@ -113,7 +113,6 @@
await createSchedule(flow.path)
}
}
sendUserToast(`Flow saved at ${$flowStore.path}`)
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
}

View File

@@ -97,7 +97,9 @@
class="w-full border-r border-b border-t border-gray-400 min-h-[150px] p-2 overflow-auto hidden sm:block"
>
{#if stepDetail == undefined}
<span class="font-black text-lg w-full my-4">
<SchemaViewer schema={flow?.schema} />
<span class="font-black text-lg w-full my-4 mt-14">
<span>Click on a step to see its details</span>
</span>
{:else if stepDetail == 'Input'}

View File

@@ -12,9 +12,9 @@
<div
class:border={!noBorder}
class="grid {!col ? 'grid-cols-2' : 'grid-rows-2'} shadow border-gray-400 h-full"
class="grid {!col ? 'grid-cols-2' : 'grid-rows-2'} shadow border-gray-400 h-full max-h-screen"
>
<div class="bg-white max-h-80 h-full p-1 overflow-auto relative">
<div class="bg-white {col ? '' : 'max-h-80'} h-full p-1 overflow-auto relative">
<span class="text-gray-500">Result</span>
{#if result}
<DisplayResult {result} />
@@ -24,7 +24,7 @@
<div class="text-gray-400">No result (result is undefined)</div>
{/if}
</div>
<div class="overflow-auto max-h-80 h-full relative">
<div class="overflow-auto {col ? '' : 'max-h-80'} h-full relative">
<LogViewer content={logs ?? ''} isLoading={false} />
</div>
</div>

View File

@@ -21,7 +21,6 @@
export let jobId: string | undefined = undefined
export let job: Job | undefined = undefined
let isValid: boolean = true
let isRunning: boolean = false
let jobProgressReset: () => void
@@ -105,7 +104,6 @@
{#if isRunning}
<Button
disabled={!isValid}
color="red"
on:click={async () => {
isRunning = false
@@ -132,7 +130,6 @@
color="blue"
size="sm"
btnClasses="w-full max-w-lg"
disabled={!isValid}
on:click={() => runPreview($previewArgs)}
>
Test flow <Kbd class="ml-2">Ctrl+Enter</Kbd>
@@ -156,7 +153,6 @@
compact
class="py-4 max-w-3xl"
schema={$flowStore.schema}
bind:isValid
bind:args={$previewArgs}
/>
</div>

View File

@@ -505,6 +505,7 @@
<div class="px-1 border-b border-black">
<JobArgs args={node.args} />
</div>
<FlowJobResult
loading={job['running'] == true}
noBorder

View File

@@ -82,16 +82,10 @@
}
function connectProperty(rawValue: string) {
if (isStaticTemplate(inputCat)) {
arg.value = `\$\{${rawValue}}`
setPropertyType(arg.value)
monacoTemplate?.setCode(arg.value)
} else {
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
arg.type = 'javascript'
propertyType = 'javascript'
monaco?.setCode(arg.expr)
}
arg.expr = getDefaultExpr(undefined, previousModuleId, rawValue)
arg.type = 'javascript'
propertyType = 'javascript'
monaco?.setCode(arg.expr)
}
function onFocus() {

View File

@@ -86,6 +86,7 @@
detailed={false}
topButton
bind:args={stepArgs}
isFlow={false}
/>
{#if testIsLoading}
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full mt-4" color="red" size="sm">

View File

@@ -277,15 +277,19 @@
folderCreated = undefined
}}
>
<div class="flex flex-row">
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
<Button size="md" startIcon={{ icon: faPlus }} disabled={!newFolderName} on:click={addFolder}>
New&nbsp;folder
</Button>
</div>
{#if folderCreated}
<div class="mt-8" />
{#if !folderCreated}
<div class="flex flex-row">
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
<Button
size="md"
startIcon={{ icon: faPlus }}
disabled={!newFolderName}
on:click={addFolder}
>
New&nbsp;folder
</Button>
</div>
{:else}
<FolderEditor name={folderCreated} />
{/if}
</DrawerContent>

View File

@@ -49,6 +49,7 @@
export let loading = false
export let noVariablePicker = false
export let viewCliRun = false
export let isFlow: boolean
export let args: Record<string, any> = decodeArgs($page.url.searchParams.get('args') ?? undefined)
@@ -64,9 +65,9 @@
let scheduledForStr: string | undefined
let invisible_to_owner: false
$: cliCommand = `wmill ${runnable?.kind} run ${runnable?.path} ${Object.entries(args)
.map(([k, v]) => `-i ${k}=${JSON.stringify(v)}`)
.join(' ')}`
$: cliCommand = `wmill ${isFlow ? 'flow' : 'script'} run ${runnable?.path} -d '${JSON.stringify(
args
)}'`
</script>
<div class="max-w-6xl">
@@ -145,7 +146,7 @@
</Button>
</div>
{#if viewOptions}
<div transition:slide class="mt-6">
<div transition:slide|local class="mt-6">
<div class="border rounded-md p-3 pt-4">
<div class="flex flex-row items-end">
<div class="w-max md:w-2/3 mt-2 mb-1">
@@ -213,14 +214,14 @@
<div class="my-10" />
<Button
color="light"
size="sm"
size="xs"
endIcon={{ icon: viewCliOptions ? faChevronUp : faChevronDown }}
on:click={() => (viewCliOptions = !viewCliOptions)}
>
Run it from the CLI
</Button>
{#if viewCliOptions}
<div transition:slide class="mt-2 px-4 pt-2">
<div transition:slide|local class="mt-2 px-4 pt-2">
<InlineCodeCopy content={cliCommand} />
<CliHelpBox />
</div>

View File

@@ -4,7 +4,6 @@
import { workspaceStore } from '$lib/stores'
import { allTrue } from '$lib/utils'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { slide } from 'svelte/transition'
import ArgInput from './ArgInput.svelte'
import { Button } from './common'
import InputTransformForm from './InputTransformForm.svelte'
@@ -69,8 +68,8 @@
<div class="w-full {clazz} {flexWrap ? 'flex flex-row flex-wrap gap-x-6 gap-y-2' : ''}">
{#if keys.length > 0}
{#each keys as argName, i (argName)}
{#if !filter || filter.includes(argName)}
<div transition:slide|local>
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
<div>
{#if inputTransform}
<InputTransformForm
{previousModuleId}
@@ -84,7 +83,7 @@
bind:pickForField
{noDynamicToggle}
/>
{:else if typeof args == 'object'}
{:else if typeof args == 'object' && schema?.properties[argName]}
<ArgInput
autofocus={i == 0 && autofocus}
label={argName}

View File

@@ -83,7 +83,6 @@
kind: script.kind
}
})
sendUserToast(`New script created at hash ${newHash}`)
history.replaceState(history.state, '', `/scripts/edit/${newHash}?step=2`)
goto(`/scripts/get/${newHash}?workspace_id=${$workspaceStore}`)
} catch (error) {

View File

@@ -39,6 +39,7 @@
export function closeDrawer() {
drawer?.closeDrawer()
const index = $page.url.href.lastIndexOf('#')
if (index === -1) return
const hashRemoved = $page.url.href.slice(0, index)
goto(hashRemoved)
}

View File

@@ -10,6 +10,7 @@
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { loadIcon } from '../icon'
import { twMerge } from 'tailwind-merge'
import { goto } from '$app/navigation'
export let id: string
export let componentInput: AppInput | undefined
@@ -32,7 +33,8 @@
let runnableComponent: RunnableComponent
let disabled: boolean | undefined = undefined
let fillContainer: boolean | undefined = undefined
let goto: string | undefined = undefined
let gotoUrl: string | undefined = undefined
let gotoNewTab: boolean | undefined = undefined
let isLoading: boolean = false
let ownClick: boolean = false
@@ -87,7 +89,7 @@
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.goto} bind:value={goto} />
<InputValue {id} input={configuration.goto} bind:value={gotoUrl} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue {id} input={configuration.beforeIcon} bind:value={beforeIcon} />
@@ -101,15 +103,17 @@
bind:error={errors.disabled}
/>
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
<InputValue {id} input={configuration.gotoNewTab} bind:value={gotoNewTab} />
<RunnableWrapper
flexWrap
bind:runnableComponent
bind:componentInput
{componentInput}
{id}
{extraQueryParams}
autoRefresh={false}
{goto}
goto={gotoUrl}
{gotoNewTab}
>
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
{#if errorsMessage}
@@ -134,7 +138,18 @@
e?.stopPropagation()
e?.preventDefault()
ownClick = true
await runnableComponent?.runComponent()
if (!runnableComponent) {
if (gotoUrl) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
} else {
goto(gotoUrl)
}
}
} else {
await runnableComponent?.runComponent()
}
if (recomputeIds) {
recomputeIds.forEach((id) => {

View File

@@ -22,7 +22,8 @@
export const staticOutputs: string[] = ['loading', 'result']
const { app, runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, runnableComponents, worldStore, stateId } =
getContext<AppEditorContext>('AppEditorContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -33,7 +34,8 @@
let isLoading: boolean = false
$: noInputs =
componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0
$stateId != undefined &&
(componentInput?.type != 'runnable' || Object.keys(componentInput?.fields ?? {}).length == 0)
$: outputs = $worldStore?.outputsById[id] as {
result: Output<Array<any>>
@@ -59,9 +61,8 @@
<InputValue {id} input={configuration.size} bind:value={size} />
<RunnableWrapper
defaultUserInput
bind:runnableComponent
bind:componentInput
{componentInput}
{id}
{goto}
{extraQueryParams}

View File

@@ -93,9 +93,8 @@
}}
>
<RunnableWrapper
defaultUserInput
bind:runnableComponent
bind:componentInput
{componentInput}
{id}
{extraQueryParams}
autoRefresh={false}

View File

@@ -89,7 +89,7 @@
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.line} bind:value={lineChart} />
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
{#if lineChart}

View File

@@ -3,7 +3,11 @@
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 {
IS_APP_PUBLIC_CONTEXT_KEY,
type AppEditorContext,
type ComponentCustomCSS
} from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
@@ -18,19 +22,23 @@
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
)}>
<RunnableWrapper flexWrap {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
)}>
<div
class={twMerge(
'p-2',
$app.css?.['displaycomponent']?.['container']?.class,
customCss?.container?.class
)}
>
<DisplayResult {result} {requireHtmlApproval} />
</div>
</RunnableWrapper>

View File

@@ -28,7 +28,7 @@
bind:clientHeight={h}
bind:clientWidth={w}
>
<RunnableWrapper autoRefresh flexWrap bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper autoRefresh flexWrap {componentInput} {id} bind:initializing bind:result>
{#key result}
<iframe
frameborder="0"

View File

@@ -1,6 +1,9 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import { AlignWrapper, InputValue, RunnableWrapper } from '../helpers'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import { AlignWrapper, InputValue } from '../helpers'
import { loadIcon } from '../icon'
export let id: string
@@ -8,6 +11,9 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = []
export let customCss: ComponentCustomCSS<'container' | 'icon'> | undefined = undefined
const { app } = getContext<AppEditorContext>('AppEditorContext')
let icon: string | undefined = undefined
let size: number
@@ -22,6 +28,8 @@
iconComponent = await loadIcon(icon)
}
}
$: css = concatCustomCss($app.css?.iconcomponent, customCss)
</script>
<InputValue {id} input={configuration.icon} bind:value={icon} />
@@ -29,13 +37,20 @@
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.strokeWidth} bind:value={strokeWidth} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<AlignWrapper
{horizontalAlignment}
{verticalAlignment}
class={css?.container?.class ?? ''}
style={css?.container?.style ?? ''}
>
{#if iconComponent}
<svelte:component
this={iconComponent}
size={size || 24}
color={color || 'currentColor'}
strokeWidth={strokeWidth || 2}
class={css?.icon?.class ?? ''}
style={css?.icon?.style ?? ''}
/>
{/if}
</AlignWrapper>

View File

@@ -1,6 +1,10 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { staticValues } from '../../editor/componentsPanel/componentStaticValues'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
type FitOption = (typeof staticValues)['objectFitOptions'][number]
@@ -8,7 +12,9 @@
export let id: string
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['loading']
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
const { app } = getContext<AppEditorContext>('AppEditorContext')
const fit: Record<FitOption, string> = {
cover: 'object-cover',
contain: 'object-contain',
@@ -18,18 +24,18 @@
let source: string | undefined = undefined
let imageFit: FitOption | undefined = undefined
let altText: string | undefined = undefined
let customStyles: string | undefined = undefined
$: css = concatCustomCss($app.css?.imagecomponent, customCss)
</script>
<InputValue {id} input={configuration.source} bind:value={source} />
<InputValue {id} input={configuration.imageFit} bind:value={imageFit} />
<InputValue {id} input={configuration.altText} bind:value={altText} />
<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']}"
style={css?.image?.style ?? ''}
class={twMerge(`w-full h-full ${fit[imageFit || 'cover']}`, css?.image?.class ?? '')}
/>

View File

@@ -0,0 +1,222 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import { concatCustomCss } from '../../utils'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { InputValue } from '../helpers'
import { twMerge } from 'tailwind-merge'
import { Map, View, Feature } from 'ol'
import { useGeographic } from 'ol/proj'
import { OSM, Vector as VectorSource } from 'ol/source'
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer'
import { Point } from 'ol/geom'
import { defaults as defaultControls } from 'ol/control'
import { findGridItem } from '../../editor/appUtils'
import type { Output } from '../../rx'
interface Marker {
lon: number
lat: number
title?: string
radius?: number
color?: string
strokeWidth?: number
strokeColor?: string
}
const LAYER_NAME = {
MARKER: 'Marker'
} as const
export let id: string
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['mapRegion']
export let customCss: ComponentCustomCSS<'map'> | undefined = undefined
const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
getContext<AppEditorContext>('AppEditorContext')
$: outputs = $worldStore?.outputsById[id] as {
mapRegion: Output<{
topLeft: { lat: number; lon: number }
bottomRight: { lat: number; lon: number }
}>
}
let map: Map
let mapElement: HTMLDivElement
let longitude: number | undefined = undefined
let latitude: number | undefined = undefined
let zoom: number | undefined = undefined
// If string, it's a JSON file read as text
let markers: Marker[] | string | undefined = undefined
$: if (map && longitude && latitude) {
map.getView().setCenter([longitude, latitude])
}
$: if (map && zoom) {
map.getView().setZoom(zoom)
}
$: if (map && markers) {
updateMarkers()
}
function selectComponent() {
if (!$connectingInput.opened) {
$selectedComponent = id
$focusedGrid = undefined
}
}
function getLayersByName(name: keyof typeof LAYER_NAME) {
return map
.getLayers()
.getArray()
.filter((l) => l.getProperties().name === LAYER_NAME[name])
}
function getMarkerArray(): Marker[] | undefined {
let array: Marker[] | undefined = undefined
if (typeof markers === 'string') {
try {
array = JSON.parse(markers)
} catch (e) {
return undefined
}
} else {
array = markers
}
return array?.filter((m) => !isNaN(+m.lat) && !isNaN(+m.lon))
}
function createMarkerLayers() {
const markerArray = getMarkerArray()
return markerArray?.map((m) => {
return new VectorLayer({
properties: {
name: LAYER_NAME.MARKER
},
source: new VectorSource({
features: [
new Feature({
geometry: new Point([+m.lon, +m.lat]),
name: m.title
})
]
}),
style: {
'circle-radius': m.radius ?? 7,
'circle-fill-color': m.color ?? '#dc2626',
'circle-stroke-width': m.strokeWidth ?? 3,
'circle-stroke-color': m.strokeColor ?? '#fca5a5'
}
})
})
}
function updateMarkers() {
const layers = getLayersByName('MARKER')
if (layers?.length) {
layers.forEach((l) => map.removeLayer(l))
}
createMarkerLayers()?.forEach((l) => map.addLayer(l))
}
onMount(() => {
useGeographic()
map = new Map({
target: mapElement,
layers: [
new TileLayer({
source: new OSM()
}),
...(createMarkerLayers() || [])
],
view: new View({
center: [longitude ?? 0, latitude ?? 0],
zoom: zoom ?? 2
}),
controls: defaultControls({
attribution: false
})
})
updateRegionOutput()
})
$: css = concatCustomCss($app.css?.mapcomponent, customCss)
$: gridItem = findGridItem($app, id)
function updateRegionOutput() {
if (map) {
let extent = map.getView().calculateExtent(map.getSize())
const [left, bottom, right, top] = extent
if (outputs?.mapRegion) {
outputs.mapRegion.set({
topLeft: { lat: top, lon: left },
bottomRight: { lat: bottom, lon: right }
})
}
}
}
function handleSyncRegion() {
if (!map || !gridItem) {
return
}
const z = map.getView().getZoom()
updateRegionOutput()
if (z) {
gridItem.data.configuration.zoom.value = z
}
const center = map.getView().getCenter()
if (!center) {
return
}
if (gridItem) {
gridItem.data.configuration.longitude.value = center[0]
gridItem.data.configuration.latitude.value = center[1]
}
}
</script>
<InputValue {id} input={configuration.longitude} bind:value={longitude} />
<InputValue {id} input={configuration.latitude} bind:value={latitude} />
<InputValue {id} input={configuration.zoom} bind:value={zoom} />
<InputValue {id} input={configuration.markers} bind:value={markers} />
<div class="relative h-full w-full">
<div
on:pointerdown|stopPropagation={selectComponent}
bind:this={mapElement}
class={twMerge(`w-full h-full`, css?.map?.class ?? '')}
style={css?.map?.style ?? ''}
/>
{#if $mode !== 'preview'}
<div
class="absolute bottom-0 left-0 px-1 py-0.5 bg-indigo-500 text-white text-2xs"
on:pointerdown={handleSyncRegion}
>
Set region
</div>
{/if}
</div>
<style global lang="postcss">
.ol-overlaycontainer-stopevent {
@apply flex flex-col justify-start items-end;
}
.ol-control button {
@apply w-7 h-7 center-center bg-white border border-gray-300 text-gray-700
rounded mt-1 mr-1 shadow duration-200 hover:bg-gray-100 focus:bg-gray-100
hover:border-gray-500 focus:border-gray-500;
}
</style>

View File

@@ -72,7 +72,7 @@
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
{#if doughnut}

View File

@@ -78,7 +78,7 @@
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
<Scatter {data} {options} />

View File

@@ -64,7 +64,7 @@
<InputValue {id} input={configuration.style} bind:value={style} />
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if !result || result === ''}
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">

View File

@@ -93,7 +93,7 @@
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap autoRefresh {componentInput} {id} bind:initializing bind:result>
<div class="w-full h-full {css?.container?.class ?? ''}" style={css?.container?.style ?? ''}>
{#if result}
<Scatter {data} {options} />

View File

@@ -40,7 +40,7 @@
</script>
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -52,7 +52,7 @@
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -1,12 +1,14 @@
export { default as AppTable } from './table/AppTable.svelte'
export { default as AppAggridTable } from './table/AppAggridTable.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 AppMap } from './AppMap.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'
export { default as VegaLiteHtml } from './VegaLiteHtml.svelte'

View File

@@ -84,7 +84,7 @@
<InputValue {id} input={configuration.pagination} bind:value={pagination} />
<InputValue {id} input={configuration.pageSize} bind:value={pageSize} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap {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"

View File

@@ -147,7 +147,7 @@
<InputValue {id} input={configuration.search} bind:value={search} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
{#if Array.isArray(result) && result.every(isObject)}
<div
class={twMerge(

View File

@@ -1,41 +1,44 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import { twMerge } from 'tailwind-merge'
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let noWFull = false
let c = ''
export { c as class }
export let style = ''
function tailwindHorizontalAlignment(alignment?: HorizontalAlignment) {
if(!alignment) return '';
if (!alignment) return ''
const classes: Record<HorizontalAlignment, string> = {
left: 'justify-start',
center: 'justify-center',
right: 'justify-end',
right: 'justify-end'
}
return classes[alignment]
}
function tailwindVerticalAlignment(alignment?: VerticalAlignment) {
if(!alignment) return '';
if (!alignment) return ''
const classes: Record<VerticalAlignment, string> = {
top: 'items-start',
center: 'items-center',
bottom: 'items-end',
bottom: 'items-end'
}
return classes[alignment]
}
$: classes = classNames(
$: classes = twMerge(
'flex z-auto',
noWFull ? '' : 'w-full',
tailwindHorizontalAlignment(horizontalAlignment),
tailwindVerticalAlignment(verticalAlignment),
verticalAlignment ? 'h-full' : '',
$$props.class || ''
c
)
</script>
<div class={classes}>
<div class={classes} {style}>
<slot />
</div>

View File

@@ -1,5 +1,10 @@
<script lang="ts">
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../../inputType'
import type {
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '../../inputType'
import type { InlineScript } from '../../types'
import RunnableComponent from './RunnableComponent.svelte'
@@ -15,7 +20,7 @@
<RunnableComponent
{id}
bind:fields
{fields}
bind:result
runnable={{
name,

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { isCodeInjection } from '$lib/components/flows/utils'
import { deepEqual } from 'fast-equals'
import { getContext } from 'svelte'
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
import type { AppEditorContext } from '../../types'
@@ -11,24 +12,33 @@
export let value: T
export let id: string | undefined = undefined
export let error: string = ''
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
$: if (input && !deepEqual(input, lastInput)) {
lastInput = JSON.parse(JSON.stringify(input))
}
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
$: state = $worldStore?.state
$: input && $worldStore && handleConnection()
$: input && input.type == 'template' && $state && (value = getValue(input))
$: input && input.type == 'eval' && $state && (value = evalExpr(input))
$: lastInput && $worldStore && handleConnection()
$: lastInput && lastInput.type == 'template' && $state && (value = getValue(lastInput))
$: lastInput && lastInput.type == 'eval' && $state && (value = evalExpr(lastInput))
function handleConnection() {
if (input.type === 'connected') {
$worldStore?.connect<any>(input, onValueChange)
} else if (input.type === 'static' || input.type == 'template') {
setTimeout(() => (value = getValue(input)), 0)
} else if (input.type == 'eval') {
setTimeout(() => ((value = evalExpr(input as EvalAppInput)), 0))
} else if (input.type == 'upload') {
setTimeout(() => ((value = (input as UploadAppInput).value), 0))
// console.log('handle connection', id, lastInput)
if (lastInput.type === 'connected') {
$worldStore?.connect<any>(lastInput, onValueChange)
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
value = getValue(lastInput)
} else if (lastInput.type == 'eval') {
value = evalExpr(lastInput as EvalAppInput)
} else if (lastInput.type == 'upload') {
value = (lastInput as UploadAppInput).value
} else {
setTimeout(() => (value = undefined), 0)
value = undefined
}
}
@@ -98,8 +108,8 @@
}
function onValueChange(newValue: any): void {
if (input.type === 'connected' && newValue !== undefined && newValue !== null) {
const { connection } = input
if (lastInput.type === 'connected' && newValue !== undefined && newValue !== null) {
const { connection } = lastInput
if (!connection) {
// No connection

View File

@@ -7,7 +7,6 @@
import { AppService, type CompletedJob } from '$lib/gen'
import { defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
import { getContext, onMount } from 'svelte'
import { computeFields } from '../../editor/inlineScriptsPanel/utils'
import type { AppInputs, Runnable } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext } from '../../types'
@@ -22,12 +21,12 @@
export let autoRefresh: boolean = true
export let result: any = undefined
export let forceSchemaDisplay: boolean = false
export let defaultUserInput = false
export let flexWrap = false
export let wrapperClass = ''
export let wrapperStyle = ''
export let initializing: boolean | undefined = undefined
export let gotoUrl: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
const {
worldStore,
@@ -38,7 +37,8 @@
jobs,
noBackend,
errorByComponent,
mode
mode,
stateId
} = getContext<AppEditorContext>('AppEditorContext')
onMount(() => {
@@ -105,29 +105,9 @@
$: outputs?.loading?.set(testIsLoading)
$: runnable?.type === 'runnableByName' && loadSchemaAndInputsByName()
$: schemaStripped = stripSchema(fields, $stateId)
async function loadSchemaAndInputsByName() {
if (runnable?.type === 'runnableByName') {
const { inlineScript } = runnable
// Inline scripts directly provide the schema
if (inlineScript) {
const newSchema = inlineScript.schema
const newFields = computeFields(newSchema, defaultUserInput, fields)
if (JSON.stringify(newFields) !== JSON.stringify(fields)) {
setTimeout(() => {
fields = newFields
}, 0)
}
}
}
}
$: schemaStripped = runnable && stripSchema(fields)
function stripSchema(inputs: AppInputs): Schema {
function stripSchema(inputs: AppInputs, s: any): Schema {
let schema =
runnable?.type == 'runnableByName' ? runnable.inlineScript?.schema : runnable?.schema
try {
@@ -262,7 +242,13 @@
delete $errorByComponent[previousJobId]
$errorByComponent = $errorByComponent
}
gotoUrl && gotoUrl != '' && result?.error == undefined && goto(gotoUrl)
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
if (gotoNewTab) {
window.open(gotoUrl, '_blank')
} else {
goto(gotoUrl)
}
}
}
}
}}
@@ -272,11 +258,6 @@
/>
<div class="h-full flex relative flex-row flex-wrap {wrapperClass}" style={wrapperStyle}>
{#if !initializing && autoRefresh === true}
<div class="flex absolute top-1 right-1">
<RefreshButton componentId={id} />
</div>
{/if}
{#if schemaStripped && Object.keys(schemaStripped?.properties ?? {}).length > 0 && (autoRefresh || forceSchemaDisplay)}
<div class="px-2 h-fit min-h-0">
<SchemaForm
@@ -312,4 +293,9 @@
<slot />
</div>
{/if}
{#if !initializing && autoRefresh === true}
<div class="flex absolute top-1 right-1 z-50">
<RefreshButton componentId={id} />
</div>
{/if}
</div>

View File

@@ -15,11 +15,11 @@
export let autoRefresh: boolean = true
export let runnableComponent: RunnableComponent | undefined = undefined
export let forceSchemaDisplay: boolean = false
export let defaultUserInput = false
export let flexWrap = false
export let runnableClass = ''
export let runnableStyle = ''
export let goto: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
const { staticExporter, noBackend } = getContext<AppEditorContext>('AppEditorContext')
@@ -44,10 +44,10 @@
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
<RunnableComponent
gotoUrl={goto}
{gotoNewTab}
{flexWrap}
{defaultUserInput}
bind:this={runnableComponent}
bind:fields={componentInput.fields}
fields={componentInput.fields}
bind:result
runnable={componentInput.runnable}
{autoRefresh}

View File

@@ -1,8 +1,10 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext } from '../../types'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputDefaultValue from '../helpers/InputDefaultValue.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -12,8 +14,9 @@
export let inputType: 'date' | 'time' | 'datetime-local'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let input: HTMLInputElement
let labelValue: string = 'Title'
let minValue: string = ''
@@ -29,6 +32,8 @@
}
$: input && handleInput()
$: css = concatCustomCss($app.css?.dateinputcomponent, customCss)
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
@@ -46,6 +51,7 @@
min={minValue}
max={maxValue}
placeholder="Type..."
class="h-full"
class={twMerge('mx-0.5', css?.input?.class ?? '')}
style={css?.input?.style ?? ''}
/>
</AlignWrapper>

View File

@@ -1,16 +1,19 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { FileInput } from '../../../common'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext } from '../../types'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let acceptedFileTypes: string[] | undefined = undefined
let allowMultiple: boolean | undefined = undefined
@@ -24,6 +27,8 @@
async function handleChange(files: string[] | undefined) {
outputs?.result.set(files)
}
$: css = concatCustomCss($app.css?.fileinputcomponent, customCss)
</script>
<InputValue {id} input={configuration.acceptedFileTypes} bind:value={acceptedFileTypes} />
@@ -34,11 +39,12 @@
<FileInput
accept={acceptedFileTypes?.length ? acceptedFileTypes?.join(', ') : undefined}
multiple={allowMultiple}
convertToBase64
convertTo="base64"
on:change={({ detail }) => {
handleChange(detail)
}}
class="w-full h-full"
class={twMerge('w-full h-full', css?.container?.class)}
style={css?.container?.style}
>
{text}
</FileInput>

View File

@@ -0,0 +1,97 @@
<script lang="ts">
import { getContext } from 'svelte'
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { stopPropagation } from 'ol/events/Event'
export const staticOutputs: string[] = ['result']
export let id: string
export let configuration: Record<string, AppInput>
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
let items: { label: string; value: string }[]
let placeholder: string = 'Select an item'
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string[] | undefined>
}
// $: outputs && handleOutputs()
// function handleOutputs() {
// value = outputs.result.peak()
// }
let value: { value: string }[] | undefined = outputs?.result.peak()
$: labels && handleItems()
let labels: string[] | undefined = []
function handleItems() {
if (labels) {
items = labels?.map((label) => {
return {
label,
value: label
}
})
}
}
$: value && outputs?.result.set(value.map((v) => v.value))
$: css = concatCustomCss($app.css?.selectcomponent, customCss)
</script>
<InputValue {id} input={configuration.items} bind:value={labels} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px" on:pointerdown|stopPropagation>
{#if !value || Array.isArray(value)}
<Select
multiple
on:change={(e) => e.stopPropagation()}
{items}
class={css?.input?.class ?? ''}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles + css?.input?.style}
bind:value
{placeholder}
on:click={() => {
if (!$connectingInput.opened) {
$selectedComponent = id
}
}}
on:focus={() => {
$selectedComponent = id
}}
floatingConfig={{
strategy: 'fixed'
}}
/>
{:else}
Value {value} is not an array
{/if}
</div>
</AlignWrapper>
<style global>
.app-select .value-container {
padding: 0 !important;
}
.svelte-select-list {
z-index: 1000 !important;
}
</style>

View File

@@ -2,7 +2,8 @@
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 { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputDefaultValue from '../helpers/InputDefaultValue.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -11,8 +12,9 @@
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let input: HTMLInputElement
let defaultValue: number | undefined = undefined
@@ -33,6 +35,8 @@
}
$: input && handleInput()
$: css = concatCustomCss($app.css?.numberinputcomponent, customCss)
</script>
<InputValue {id} input={configuration.step} bind:value={step} />
@@ -44,6 +48,8 @@
<AlignWrapper {verticalAlignment}>
<input
class="mx-0.5 {css?.input?.class ?? ''}"
style={css?.input?.style ?? ''}
bind:this={input}
on:input={handleInput}
on:focus={(e) => {

View File

@@ -3,20 +3,24 @@
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 { Badge } from '$lib/components/common'
import { concatCustomCss } from '../../utils'
import { twMerge } from 'tailwind-merge'
export let id: string
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'handles' | 'bar' | 'limits' | 'values'> | undefined =
undefined
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let min = 0
let max = 42
let step = 1
let slider: HTMLElement
let values: [number, number] = [0, 0]
@@ -25,6 +29,17 @@
}
$: outputs?.result.set(values)
$: css = concatCustomCss($app.css?.rangecomponent, customCss)
let lastStyle: string | undefined = undefined
$: if (css && slider && lastStyle !== css?.handles?.style) {
const handles = slider.querySelectorAll<HTMLSpanElement>('.rangeNub')
if (handles) {
lastStyle = css?.handles?.style
handles.forEach((handle) => (handle.style.cssText = css?.handles?.style ?? ''))
}
}
</script>
<InputValue {id} input={configuration.step} bind:value={step} />
@@ -36,20 +51,43 @@
<AlignWrapper {verticalAlignment}>
<div class="flex flex-col w-full">
<div class="flex items-center w-full gap-1 px-1">
<span>{min}</span>
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
{+min}
</span>
<div class="grow" on:pointerdown|stopPropagation>
<RangeSlider {step} range min={min ?? 0} max={max ?? 1} bind:values />
<!-- <DoubleRange bind:start bind:end /> -->
<RangeSlider
bind:slider
bind:values
{step}
min={!min ? 0 : +min}
max={!max ? 1 : +max}
range
/>
<!-- <RangeSlider {step} range min={min ?? 0} max={max ?? 1} bind:values /> -->
</div>
<span>{max}</span>
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
{+max}
</span>
</div>
<div class="flex justify-between px-1">
<div>
<Badge color="blue">{values[0]}</Badge>
</div>
<div>
<Badge color="blue">{values[1]}</Badge>
</div>
<span
class={twMerge(
'text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5',
css?.values?.class ?? ''
)}
style={css?.values?.style ?? ''}
>
{values[0]}
</span>
<span
class={twMerge(
'text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5',
css?.values?.class ?? ''
)}
style={css?.values?.style ?? ''}
>
{values[1]}
</span>
</div>
</div>
</AlignWrapper>

View File

@@ -18,25 +18,18 @@
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
let items: { label: string; value: any }[]
let multiple: boolean = false
let items: { label: string; value: any; created?: boolean }[]
let placeholder: string = 'Select an item'
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string | undefined>
result: Output<any | undefined>
}
// $: outputs && handleOutputs()
// function handleOutputs() {
// value = outputs.result.peak()
// }
let value: string | undefined = outputs?.result.peak()
$: items && handleItems()
let listItems: { label: string; value: string }[] = []
let listItems: { label: string; value: string; created?: boolean }[] = []
function handleItems() {
listItems = Array.isArray(items)
@@ -47,21 +40,22 @@
}
})
: []
const valuePeak = outputs?.result.peak()
if (!valuePeak) {
const value0 = items?.[0]?.['value']
if (value0 != value) {
value = JSON.stringify(value0)
outputs?.result.set(value0)
}
} else {
value = JSON.stringify(valuePeak)
if (defaultValue) {
value = JSON.stringify(defaultValue)
}
}
function onChange(e: CustomEvent) {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
if (create) {
listItems = listItems.map((i) => {
delete i.created
return i
})
}
let result: any = undefined
try {
result = JSON.parse(e.detail?.['value'])
@@ -70,22 +64,49 @@
}
$: css = concatCustomCss($app.css?.selectcomponent, customCss)
let defaultValue: any = undefined
function handleFilter(e) {
if (create) {
if (e.detail.length === 0 && filterText.length > 0) {
const prev = listItems.filter((i) => !i.created)
listItems = [
...prev,
{ value: JSON.stringify(filterText), label: filterText, created: true }
]
}
}
}
$: defaultValue && handleDefault()
function handleDefault() {
if (defaultValue) {
value = JSON.stringify(defaultValue)
outputs?.result.set(defaultValue)
}
}
let create = false
let filterText = ''
</script>
<InputValue {id} input={configuration.items} bind:value={items} />
<InputValue {id} input={configuration.multiple} bind:value={multiple} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<InputValue {id} input={configuration.create} bind:value={create} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px" on:pointerdown|stopPropagation>
<Select
{multiple}
bind:filterText
on:filter={handleFilter}
on:clear={onChange}
on:change={onChange}
items={listItems}
class={css?.input?.class ?? ''}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles + css?.input.style}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles + css?.input?.style}
{value}
{placeholder}
on:click={() => {
@@ -99,7 +120,15 @@
floatingConfig={{
strategy: 'fixed'
}}
/>
>
<div slot="item" let:item>
{#if create}
{item.created ? 'Add new: ' : ''}
{/if}
{item.label}
</div>
</Select>
</div>
</AlignWrapper>

View File

@@ -1,22 +1,25 @@
<script lang="ts">
import { Badge } from '$lib/components/common'
import RangeSlider from 'svelte-range-slider-pips'
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 { twMerge } from 'tailwind-merge'
import { concatCustomCss } from '../../utils'
export let id: string
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'handle' | 'limits' | 'value'> | undefined = undefined
const { worldStore, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let min = 0
let max = 42
let step = 1
let slider: HTMLElement
$: outputs = $worldStore?.outputsById[id] as {
result: Output<number | null>
@@ -34,6 +37,17 @@
const num = isNaN(+values[0]) ? null : +values[0]
outputs?.result.set(num)
}
$: css = concatCustomCss($app.css?.slidercomponent, customCss)
let lastStyle: string | undefined = undefined
$: if (css && slider && lastStyle !== css?.handle?.style) {
const handle = slider.querySelector<HTMLSpanElement>('.rangeNub')
if (handle) {
lastStyle = css?.handle?.style
handle.style.cssText = css?.handle?.style ?? ''
}
}
</script>
<InputValue {id} input={configuration.step} bind:value={step} />
@@ -43,11 +57,25 @@
<AlignWrapper {verticalAlignment}>
<div class="flex items-center w-full gap-1 px-1">
<span>{min}</span>
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
{+min}
</span>
<div class="grow" on:pointerdown|stopPropagation={() => ($selectedComponent = id)}>
<RangeSlider {step} min={min ?? 0} max={max ?? 1} bind:values />
<RangeSlider bind:slider bind:values {step} min={+min} max={+max} />
</div>
<span>{max}</span>
<span class="mx-2"><Badge large color="blue">{values[0]}</Badge></span>
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
{+max}
</span>
<span class="mx-2">
<span
class={twMerge(
'text-center text-sm font-medium bg-blue-100 text-blue-800 rounded px-2.5 py-0.5',
css?.value?.class ?? ''
)}
style={css?.value?.style ?? ''}
>
{values[0]}
</span>
</span>
</div>
</AlignWrapper>

View File

@@ -1,5 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
@@ -14,6 +15,7 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let appCssKey: 'textinputcomponent' | 'passwordinputcomponent' = 'textinputcomponent'
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let input: HTMLInputElement
@@ -31,7 +33,7 @@
$: input && handleInput()
$: css = concatCustomCss($app.css?.textinputcomponent, customCss)
$: css = concatCustomCss($app.css?.[appCssKey], customCss)
</script>
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
@@ -40,7 +42,7 @@
<AlignWrapper {verticalAlignment}>
<input
class="mx-0.5 {css?.input?.class ?? ''}"
class={twMerge('mx-0.5', css?.input?.class ?? '')}
style={css?.input?.style ?? ''}
on:focus={(e) => {
e?.stopPropagation()

View File

@@ -2,7 +2,8 @@
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 { concatCustomCss } from '../../../utils'
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
import InputValue from '../../helpers/InputValue.svelte'
import CurrencyInput from './CurrencyInput.svelte'
@@ -11,8 +12,9 @@
export let configuration: Record<string, AppInput>
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let defaultValue: number | undefined = undefined
@@ -37,6 +39,8 @@
$: value != undefined && handleInput()
$: defaultValue != undefined && handleDefault()
$: css = concatCustomCss($app.css?.currencycomponent, customCss)
</script>
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
@@ -49,7 +53,12 @@
{#key locale}
{#key currency}
<CurrencyInput
inputClasses={{ formatted: 'p-0', wrapper: 'w-full', formattedZero: 'text-black' }}
inputClasses={{
formatted: 'p-0 ' + css?.input?.class,
wrapper: 'w-full',
formattedZero: 'text-black ' + css?.input?.class
}}
style={css?.input?.style}
bind:value
{currency}
{locale}

View File

@@ -35,6 +35,7 @@
export let isNegativeAllowed: boolean = true
export let fractionDigits: number = DEFAULT_FRACTION_DIGITS
export let inputClasses: InputClasses | null = null
export let style: string = ''
// Formats value as: e.g. $1,523.00 | -$1,523.00
const formatCurrency = (
@@ -175,6 +176,7 @@
? inputClasses?.formattedNegative ?? DEFAULT_CLASS_FORMATTED_NEGATIVE
: ''}
"
{style}
type="text"
inputmode="numeric"
name={`formatted-${name}`}

View File

@@ -16,8 +16,6 @@
export const staticOutputs: string[] = []
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let gridContent: string[] | undefined = undefined
function onFocus() {
$focusedGrid = {
parentComponentId: id,
@@ -31,17 +29,19 @@
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
{noPadding}
class={css?.container.class}
style={css?.container.style}
bind:subGrid={$app.subgrids[`${id}-0`]}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
<div class="border">
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
{noPadding}
{id}
class={css?.container.class}
style={css?.container.style}
bind:subGrid={$app.subgrids[`${id}-0`]}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</div>

View File

@@ -1,6 +1,14 @@
<script lang="ts">
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
import type {
AppEditorContext,
ComponentCustomCSS,
HorizontalAlignment,
VerticalAlignment
} from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -8,17 +16,34 @@
export let configuration: Record<string, AppInput>
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'divider'> | undefined = undefined
export let position: 'horizontal' | 'vertical'
const { app } = getContext<AppEditorContext>('AppEditorContext')
let size = 2
let color = '#00000060'
$: css = concatCustomCss($app.css?.[position + 'dividercomponent'], customCss)
</script>
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue {id} input={configuration.color} bind:value={color} />
<AlignWrapper {horizontalAlignment} {verticalAlignment} class="h-full">
<AlignWrapper
{horizontalAlignment}
{verticalAlignment}
class={twMerge(css?.container?.class, 'h-full')}
style={css?.container?.style}
>
<div
class="rounded-full {position === 'horizontal' ? 'w-full' : 'h-full'}"
style="{position === 'horizontal' ? 'height' : 'width'}: {size}px; background-color: {color}"
class={twMerge(
`rounded-full ${position === 'horizontal' ? 'w-full' : 'h-full'}`,
css?.divider?.class ?? ''
)}
style="
{position === 'horizontal' ? 'height' : 'width'}: {size}px;
background-color: {color};
{css?.divider?.style ?? ''}
"
/>
</AlignWrapper>

View File

@@ -0,0 +1,95 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import Portal from 'svelte-portal'
import { concatCustomCss } from '../../utils'
import { Button, ButtonType, Drawer, DrawerContent } from '$lib/components/common'
import { twMerge } from 'tailwind-merge'
import { AlignWrapper } from '../helpers'
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let id: string
export let configuration: Record<string, AppInput>
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let gridContent: string[] | undefined = undefined
let noPadding: boolean | undefined = undefined
let drawerTitle: string | undefined = undefined
let appDrawer: Drawer
let labelValue: string
let color: ButtonType.Color
let size: ButtonType.Size
let disabled: boolean | undefined = undefined
let fillContainer: boolean | undefined = undefined
$: css = concatCustomCss($app.css?.containercomponent, customCss)
</script>
<div class="h-full w-full">
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
<Button
btnClasses={twMerge(
$app.css?.['buttoncomponent']?.['button']?.class,
fillContainer ? 'w-full h-full' : ''
)}
{disabled}
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={async (e) => {
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
appDrawer.toggleDrawer()
}}
{size}
{color}
>
<div>{labelValue}</div>
</Button>
</AlignWrapper>
</div>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<InputValue {id} input={configuration.drawerTitle} bind:value={drawerTitle} />
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.color} bind:value={color} />
<InputValue {id} input={configuration.size} bind:value={size} />
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
<Portal target="#app-editor-top-level-drawer">
<Drawer bind:this={appDrawer} size="800px" alwaysOpen positionClass="!absolute">
<DrawerContent
title={drawerTitle}
on:close={() => {
appDrawer?.toggleDrawer()
$focusedGrid = undefined
}}
>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
{noPadding}
{id}
class={css?.container.class}
style={css?.container.style}
bind:subGrid={$app.subgrids[`${id}-0`]}
containerHeight={1200}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</DrawerContent>
</Drawer>
</Portal>

View File

@@ -0,0 +1,73 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { deepEqual } from 'fast-equals'
export let id: string
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let horizontal: boolean = false
export let panes: number[]
export const staticOutputs: string[] = []
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let noPadding: boolean | undefined = undefined
function onFocus() {
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
$: $selectedComponent === id && onFocus()
$: css = concatCustomCss($app.css?.containercomponent, customCss)
let sumedup = [50, 50]
$: {
let ns = panes.map((x) => (x / panes.reduce((a, b) => a + b, 0)) * 100)
if (!deepEqual(ns, sumedup)) {
sumedup = ns
}
}
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div class="h-full w-full border" on:pointerdown|stopPropagation>
<Splitpanes {horizontal}>
{#each sumedup as paneSize, index (index)}
<Pane size={paneSize} minSize={20}>
{#if $app.subgrids?.[`${id}-${index}`]}
<SubGridEditor
noYPadding
{noPadding}
{id}
shouldHighlight={$focusedGrid?.subGridIndex === index}
class={css?.container.class}
style={css?.container.style}
bind:subGrid={$app.subgrids[`${id}-${index}`]}
containerHeight={horizontal
? (componentContainerHeight * paneSize) / 100 - 1
: componentContainerHeight - 8}
on:focus={() => {
$selectedComponent = id
$focusedGrid = {
parentComponentId: id,
subGridIndex: index
}
}}
/>
{/if}
</Pane>
{/each}
</Splitpanes>
</div>

View File

@@ -4,13 +4,17 @@
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, GridItem } from '../../types'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
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 let customCss:
| ComponentCustomCSS<'tabRow' | 'tabs' | 'selectedTab' | 'container'>
| undefined = undefined
export const staticOutputs: string[] = ['selectedTabIndex']
const { app, worldStore, focusedGrid, selectedComponent } =
@@ -29,6 +33,8 @@
selectedTabIndex: Output<number | null>
}
$: outputs?.selectedTabIndex && handleOutputs()
function handleTabSelection() {
outputs?.selectedTabIndex.set(selectedIndex)
if ($selectedComponent != id) {
@@ -54,16 +60,27 @@
}
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
function handleOutputs() {
outputs?.selectedTabIndex.set(outputs.selectedTabIndex.peak())
}
</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>
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
{#each tabs ?? [] as res}
<Tab value={res}>
<Tab
value={res}
class={css?.tabs?.class}
style={css?.tabs?.style}
selectedClass={css?.selectedTab?.class}
selectedStyle={css?.selectedTab?.style}
>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
@@ -72,9 +89,12 @@
{#if $app.subgrids}
{#each tabs ?? [] as res, i}
<SubGridEditor
{id}
visible={i === selectedIndex}
bind:subGrid={$app.subgrids[`${id}-${i}`]}
{noPadding}
class={css?.container?.class}
style={css?.container?.style}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id

View File

@@ -1,3 +1,5 @@
export { default as AppContainer } from './AppContainer.svelte'
export { default as AppDivider } from './AppDivider.svelte'
export { default as AppTabs } from './AppTabs.svelte'
export { default as AppDrawer } from './AppDrawer.svelte'
export { default as AppSplitpanes } from './AppSplitpanes.svelte'
export { default as AppTabs } from './AppTabs.svelte'

View File

@@ -24,7 +24,7 @@
import Icon from 'svelte-awesome'
import { faCode, faPlus, faSliders } from '@fortawesome/free-solid-svg-icons'
import ContextPanel from './contextPanel/ContextPanel.svelte'
import { encodeState } from '$lib/utils'
import { classNames, encodeState } from '$lib/utils'
import AppPreview from './AppPreview.svelte'
import { userStore, workspaceStore } from '$lib/stores'
@@ -59,14 +59,12 @@
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
const errorByComponent = writable<Record<string, { error: string; componentId: string }>>({})
const focusedGrid = writable<FocusedGrid | undefined>(undefined)
setContext<AppEditorContext>('AppEditorContext', {
worldStore,
staticOutputs,
app: appStore,
lazyGrid: writable([]),
summary: summaryStore,
selectedComponent,
mode,
@@ -82,7 +80,9 @@
noBackend: false,
errorByComponent,
openDebugRun: writable(undefined),
focusedGrid
focusedGrid,
stateId: writable(0),
parentWidth: writable(0)
})
let timeout: NodeJS.Timeout | undefined = undefined
@@ -126,7 +126,7 @@
{#if $connectingInput.opened}
<div
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
/>
{/if}
{#if !$userStore?.operator}
@@ -138,7 +138,7 @@
{#if previewing}
<SplitPanesWrapper>
<div
class={twMerge('bg-gray-100 h-full w-full', $appStore.css?.['app']?.['viewer']?.class)}
class={twMerge('h-full w-full', $appStore.css?.['app']?.['viewer']?.class)}
style={$appStore.css?.['app']?.['viewer']?.style}
>
<AppPreview
@@ -165,6 +165,10 @@
<Splitpanes horizontal>
<Pane size={$connectingInput?.opened ? 100 : 70}>
<div
on:pointerdown={(e) => {
$selectedComponent = undefined
$focusedGrid = undefined
}}
class={twMerge(
'bg-gray-100 h-full w-full',
$appStore.css?.['app']?.['viewer']?.class
@@ -172,14 +176,17 @@
style={$appStore.css?.['app']?.['viewer']?.style}
>
<div
class="relative mx-auto w-full h-full overflow-auto {app.fullscreen
? ''
: 'max-w-6xl'}"
class={classNames(
'relative mx-auto w-full h-full overflow-auto',
app.fullscreen ? '' : 'max-w-6xl'
)}
>
{#if $appStore.grid}
<div class={width}>
<div on:pointerdown|stopPropagation class={width}>
<GridEditor {policy} />
</div>
<div id="app-editor-top-level-drawer" />
{/if}
</div>
</div>

View File

@@ -293,7 +293,7 @@
</PanelSection>
</Pane>
<Pane size={75}>
<div class="h-full w-full overflow-hidden">
<div class="h-full w-full overflow-auto">
{#if selectedJobId}
{#if !job}
<Skeleton layout={[[40]]} />

View File

@@ -6,7 +6,7 @@
import AppComponentInput from './AppComponentInput.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
const { app, lazyGrid } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppEditorContext>('AppEditorContext')
let resourceOnly: boolean = true
</script>
@@ -18,9 +18,9 @@
<Toggle bind:checked={resourceOnly} options={{ right: 'Resource only' }} />
</div>
<div class="gap-4 flex flex-col pt-4">
{#each $lazyGrid as gridItem (gridItem.data.id)}
{#each $app.grid as gridItem (gridItem.data.id)}
<div>
{#if gridItem.data.type === 'tablecomponent'}
{#if gridItem?.data?.type === 'tablecomponent'}
<div>
<AppComponentInput bind:component={gridItem.data} {resourceOnly} />
<div class="ml-4 mt-4">

View File

@@ -45,7 +45,6 @@
setContext<AppEditorContext>('AppEditorContext', {
worldStore,
staticOutputs,
lazyGrid: writable(app.grid),
app: appStore,
summary: writable(summary),
selectedComponent,
@@ -62,7 +61,9 @@
noBackend,
errorByComponent: writable({}),
openDebugRun: writable(undefined),
focusedGrid: writable(undefined)
focusedGrid: writable(undefined),
stateId: writable(0),
parentWidth: writable(0)
})
let mounted = false
@@ -76,6 +77,8 @@
$: lockedClasses = isLocked ? '!max-h-[400px] overflow-hidden pointer-events-none' : ''
</script>
<div id="app-editor-top-level-drawer" />
<div class="relative">
<div
class="{$$props.class} {lockedClasses} h-full
@@ -87,6 +90,7 @@
</div>
{/if}
</div>
{#if isLocked}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div

View File

@@ -20,9 +20,9 @@
connectingInput,
staticOutputs,
runnableComponents,
lazyGrid,
summary,
focusedGrid
focusedGrid,
parentWidth
} = getContext<AppEditorContext>('AppEditorContext')
// The drag is disabled when the user is connecting an input
@@ -36,7 +36,7 @@
$app.grid.map((gridItem) => {
const disabledGridItem = fct(gridItem)
if (disabledGridItem.data.subGrids) {
if (disabledGridItem?.data?.subGrids) {
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map(
(subgrid: GridItem[]) => subgrid?.map((subgridItem: GridItem) => fct(subgridItem)) ?? []
)
@@ -89,27 +89,16 @@
}
function selectComponent(id: string) {
// Component selection is handled manually in the Map component (pointerdown
// event propagation is stopped to enable paning).
// Update the 'selectComponent()' function as well when this is updated.
if (!$connectingInput.opened) {
$selectedComponent = id
$focusedGrid = undefined
}
}
$: $app.grid && recomputeLazyGrid()
let intervalId: NodeJS.Timeout | undefined = undefined
function recomputeLazyGrid() {
{
if (intervalId) {
clearTimeout(intervalId)
}
intervalId = setTimeout(() => {
lazyGrid.set($app.grid)
intervalId = undefined
}, 100)
}
}
let pointerdown = false
const onpointerdown = () => {
pointerdown = true
@@ -135,9 +124,9 @@
<div class="relative w-full z-20 overflow-visible">
<div
class="w-full sticky top-0 flex justify-between border-l border-r border-b {$connectingInput?.opened
class="w-full sticky top-0 flex justify-between border-b {$connectingInput?.opened
? ''
: 'bg-gray-50 '} px-4 py-2 items-center gap-4"
: 'bg-gray-50 '} px-4 py-1 items-center gap-4"
style="z-index: 1000;"
>
<h2 class="truncate">{$summary}</h2>
@@ -149,9 +138,10 @@
</div>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
style={$app.css?.['app']?.['grid']?.style}
class={twMerge('px-4 pt-4 pb-2 overflow-visible', $app.css?.['app']?.['grid']?.class ?? '')}
class={twMerge('px-4 pt-4 pb-2 overflow-visible', $app.css?.['app']?.['grid']?.class ?? '')}
on:pointerdown={onpointerdown}
on:pointerleave={onpointerup}
on:pointerup={onpointerup}
@@ -159,59 +149,62 @@
$selectedComponent = undefined
$focusedGrid = undefined
}}
bind:clientWidth={$parentWidth}
>
<Grid
onTopId={$selectedComponent}
fillSpace={false}
bind:items={$app.grid}
let:dataItem
rowHeight={36}
cols={columnConfiguration}
fastStart={true}
gap={[4, 2]}
>
{#each $lazyGrid as gridComponent (gridComponent.id)}
{#if gridComponent.data.id === dataItem.data.id}
<!-- svelte-ignore a11y-click-events-have-key-events -->
{#if $connectingInput.opened}
<div class={!$focusedGrid && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
<Grid
onTopId={$selectedComponent}
fillSpace={false}
bind:items={$app.grid}
let:dataItem
rowHeight={36}
cols={columnConfiguration}
fastStart={true}
gap={[4, 2]}
>
{#each $app.grid as gridComponent (gridComponent.id)}
{#if gridComponent?.data?.id && gridComponent?.data?.id === dataItem?.data?.id}
<!-- svelte-ignore a11y-click-events-have-key-events -->
{#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"
/>
<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"
>{dataItem.data.id}</div
>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<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"
>{dataItem.data.id}</div
on:click|stopPropagation
on:pointerdown={() => selectComponent(dataItem.data.id)}
class={classNames(
'h-full w-full center-center',
$selectedComponent === dataItem.data.id ? 'active-grid-item' : '',
gridComponent.data.card ? 'border border-gray-100' : ''
)}
>
<Component
{pointerdown}
component={gridComponent.data}
selected={$selectedComponent === dataItem.data.id}
locked={isFixed(gridComponent)}
on:delete={() => removeGridElement(gridComponent.data)}
on:lock={() => {
let fComponent = $app.grid.find((c) => c.id === gridComponent.id)
if (fComponent) {
fComponent = toggleFixed(fComponent)
}
}}
/>
</div>
{/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',
$selectedComponent === dataItem.data.id ? 'active-grid-item' : '',
gridComponent.data.card ? 'border border-gray-100' : ''
)}
>
<Component
{pointerdown}
bind:pComponent={gridComponent.data}
selected={$selectedComponent === dataItem.data.id}
locked={isFixed(gridComponent)}
on:delete={() => removeGridElement(gridComponent.data)}
on:lock={() => {
let fComponent = $app.grid.find((c) => c.id === gridComponent.id)
if (fComponent) {
fComponent = toggleFixed(fComponent)
}
}}
/>
</div>
{/if}
{/each}
</Grid>
{/each}
</Grid>
</div>
</div>
</div>
@@ -222,7 +215,7 @@
id={`bg_${index}`}
inlineScript={script.inlineScript}
name={script.name}
bind:fields={script.fields}
fields={script.fields}
bind:staticOutputs={$staticOutputs[`bg_${index}`]}
/>
{/if}

View File

@@ -0,0 +1,32 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext, GridItem } from '../types'
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
const { selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
export let gridItems: GridItem[]
export let parent: string | undefined
</script>
{#each gridItems as gridItem, i (gridItem?.data?.id ?? i)}
{#if gridItem?.data?.id === $selectedComponent}
<ComponentPanel {parent} bind:component={gridItem.data} />
{:else if gridItem?.data?.type === 'tablecomponent'}
{#each gridItem.data.actionButtons as actionButton (actionButton.id)}
{#if actionButton.id === $selectedComponent}
<ComponentPanel
parent={undefined}
noGrid
rowColumns
bind:component={actionButton}
duplicateMoveAllowed={false}
onDelete={() => {
gridItem.data.actionButtons = gridItem.data.actionButtons.filter(
(c) => c.id !== actionButton.id
)
}}
/>
{/if}
{/each}
{/if}
{/each}

View File

@@ -1,44 +1,34 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext } from '../types'
import { allItems, allItemsWithParent } from '../utils'
import GridPanel from './GridPanel.svelte'
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, app } = getContext<AppEditorContext>('AppEditorContext')
const { selectedComponent, app, stateId } = getContext<AppEditorContext>('AppEditorContext')
</script>
{#each $app.grid as gridItem (gridItem.data.id)}
{#if gridItem.data.id === $selectedComponent}
<ComponentPanel parent={undefined} bind:component={gridItem.data} />
{:else if gridItem.data.type === 'tablecomponent'}
<TablePanel bind:component={gridItem.data} />
{/if}
{/each}
{#if $app.grid}
<GridPanel parent={undefined} bind:gridItems={$app.grid} />
{/if}
{#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}
<GridPanel parent={key} bind:gridItems={$app.subgrids[key]} />
{/each}
{/if}
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
{#if $selectedComponent === `bg_${index}`}
<PanelSection title={`Background script inputs`}>
<InputsSpecsEditor
id={`bg_${index}`}
shouldCapitalize={false}
bind:inputSpecs={script.fields}
userInputEnabled={false}
/>
{#key $stateId}
<InputsSpecsEditor
id={`bg_${index}`}
shouldCapitalize={false}
bind:inputSpecs={script.fields}
userInputEnabled={false}
/>
{/key}
</PanelSection>
{/if}
{/each}

View File

@@ -13,17 +13,20 @@
export { classes as class }
export let style = ''
export let noPadding = false
//export let id: string
export let noYPadding = false
export let subGrid: GridItem[] = []
export let visible: boolean = true
export let id: string
export let shouldHighlight: boolean = true
const dispatch = createEventDispatcher()
const { app, connectingInput, selectedComponent, focusedGrid } =
const { app, connectingInput, selectedComponent, focusedGrid, mode, parentWidth } =
getContext<AppEditorContext>('AppEditorContext')
let pointerdown = false
$: highlight = id === $focusedGrid?.parentComponentId && shouldHighlight
let pointerdown = false
let onComponent: string | undefined = undefined
const onpointerdown = (e) => {
@@ -64,17 +67,22 @@
</script>
<div
class="relative w-full subgrid {visible ? 'visible' : 'invisible h-0 overflow-hidden'}"
class="relative w-full subgrid {visible ? 'visible' : 'invisible h-0 overflow-hidden'} "
bind:this={container}
>
<div
class={twMerge('py-2 overflow-auto', classes ?? '', noPadding ? 'px-0' : 'px-2')}
class={twMerge(
'overflow-auto',
noYPadding ? '' : 'py-2',
classes ?? '',
noPadding ? 'px-0' : 'px-2'
)}
on:pointerdown|stopPropagation={onpointerdown}
on:pointerleave={onpointerup}
on:pointerup={onpointerup}
style="height: {containerHeight}px; {style ?? ''}"
>
<div>
<div class={highlight && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
<Grid
bind:items={subGrid}
let:dataItem
@@ -83,19 +91,17 @@
fastStart={true}
gap={[4, 2]}
scroller={container}
parentWidth={$parentWidth - 17}
>
{#each subGrid as gridComponent (gridComponent.id)}
{#if gridComponent.data.id === dataItem.data.id}
{#if gridComponent?.data?.id && 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"
@@ -116,7 +122,7 @@
>
<Component
{pointerdown}
bind:pComponent={gridComponent.data}
component={gridComponent.data}
selected={$selectedComponent === dataItem.data.id}
locked={isFixed(gridComponent)}
on:lock={() => lock(gridComponent)}

View File

@@ -1,23 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext } from '../types'
import type { TableComponent } from './component'
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
export let component: TableComponent
const { selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
</script>
{#each component.actionButtons as actionButton (actionButton.id)}
{#if actionButton.id === $selectedComponent}
<ComponentPanel
parent={undefined}
noGrid
rowColumns
bind:component={actionButton}
onDelete={() => {
component.actionButtons = component.actionButtons.filter((c) => c.id !== actionButton.id)
}}
/>
{/if}
{/each}

View File

@@ -5,8 +5,7 @@
import { twMerge } from 'tailwind-merge'
import type { AppEditorContext } from '../../types'
import ComponentHeader from '../ComponentHeader.svelte'
import { deepEqual } from 'fast-equals'
import type { AppComponent } from './components'
import {
AppBarChart,
AppDisplayComponent,
@@ -34,12 +33,15 @@
AppDivider,
AppFileInput,
AppImage,
AppContainer
AppContainer,
AppAggridTable,
AppDrawer,
AppMap,
AppSplitpanes
} from '../../components'
import type { AppComponent } from './components'
import AppAggridTable from '../../components/display/table/AppAggridTable.svelte'
import AppMultiSelect from '../../components/inputs/AppMultiSelect.svelte'
export let pComponent: AppComponent
export let component: AppComponent
export let selected: boolean
export let locked: boolean = false
export let pointerdown: boolean = false
@@ -50,10 +52,10 @@
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))
}
// let component = JSON.parse(JSON.stringify({ conf }))
// $: if (!deepEqual(component, pComponent)) {
// component = JSON.parse(JSON.stringify(pComponent))
// }
</script>
<div
@@ -89,148 +91,302 @@
>
{#if component.type === 'displaycomponent'}
<AppDisplayComponent
{...component}
id={component.id}
customCss={component.customCss}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'barchartcomponent'}
<AppBarChart
{...component}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'timeseriescomponent'}
<AppTimeseries
{...component}
id={component.id}
customCss={component.customCss}
configuration={component.configuration}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'htmlcomponent'}
<AppHtml
{...component}
id={component.id}
customCss={component.customCss}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'vegalitecomponent'}
<VegaLiteHtml
{...component}
configuration={component.configuration}
id={component.id}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'plotlycomponent'}
<PlotlyHtml
{...component}
id={component.id}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'scatterchartcomponent'}
<AppScatterChart
{...component}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'piechartcomponent'}
<AppPieChart
{...component}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
/>
{:else if component.type === 'tablecomponent'}
<AppTable
{...component}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:actionButtons={component.actionButtons}
/>
{:else if component.type === 'aggridcomponent'}
<AppAggridTable
{...component}
id={component.id}
configuration={component.configuration}
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
/>
{:else if component.type === 'textcomponent'}
<AppText
{...component}
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
bind:initializing
bind:componentInput={component.componentInput}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'buttoncomponent'}
<AppButton
{...component}
bind:componentInput={component.componentInput}
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'selectcomponent'}
<AppSelect {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppSelect
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'multiselectcomponent'}
<AppMultiSelect
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'formcomponent'}
<AppForm
{...component}
bind:componentInput={component.componentInput}
id={component.id}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'formbuttoncomponent'}
<AppFormButton
{...component}
bind:componentInput={component.componentInput}
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'checkboxcomponent'}
<AppCheckbox {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppCheckbox
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'textinputcomponent'}
<AppTextInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppTextInput
id={component.id}
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'passwordinputcomponent'}
<AppTextInput
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
inputType="password"
{...component}
appCssKey="passwordinputcomponent"
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'dateinputcomponent'}
<AppDateInput
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
inputType="date"
{...component}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'numberinputcomponent'}
<AppNumberInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppNumberInput
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'currencycomponent'}
<AppCurrencyInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppCurrencyInput
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'slidercomponent'}
<AppSliderInputs {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppSliderInputs
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'horizontaldividercomponent'}
<AppDivider {...component} position="horizontal" />
<AppDivider
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
position="horizontal"
/>
{:else if component.type === 'verticaldividercomponent'}
<AppDivider {...component} position="vertical" />
<AppDivider
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
position="vertical"
/>
{:else if component.type === 'rangecomponent'}
<AppRangeInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppRangeInput
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'tabscomponent'}
<AppTabs
{...component}
configuration={component.configuration}
id={component.id}
tabs={component.tabs}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
/>
{:else if component.type === 'containercomponent'}
<AppContainer
{...component}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
/>
{:else if component.type === 'verticalsplitpanescomponent'}
<AppSplitpanes
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
panes={component.panes}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
/>
{:else if component.type === 'horizontalsplitpanescomponent'}
<AppSplitpanes
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
panes={component.panes}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
horizontal={true}
/>
{:else if component.type === 'iconcomponent'}
<AppIcon {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppIcon
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'fileinputcomponent'}
<AppFileInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppFileInput
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'imagecomponent'}
<AppImage {...component} bind:staticOutputs={$staticOutputs[component.id]} />
<AppImage
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'drawercomponent'}
<AppDrawer
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
/>
{:else if component.type === 'mapcomponent'}
<AppMap {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{/if}
</div>
</div>

View File

@@ -27,7 +27,11 @@ import {
SeparatorHorizontal,
SeparatorVertical,
Paperclip,
Image
Image,
SidebarClose,
MapPin,
FlipHorizontal,
FlipVertical
} from 'lucide-svelte'
import type { BaseAppComponent } from '../../types'
@@ -68,6 +72,7 @@ export type DisplayComponent = BaseComponent<'displaycomponent'>
export type ImageComponent = BaseComponent<'imagecomponent'>
export type InputComponent = BaseComponent<'inputcomponent'>
export type SelectComponent = BaseComponent<'selectcomponent'>
export type MultiSelectComponent = BaseComponent<'multiselectcomponent'>
export type CheckboxComponent = BaseComponent<'checkboxcomponent'>
export type RadioComponent = BaseComponent<'radiocomponent'>
export type IconComponent = BaseComponent<'iconcomponent'>
@@ -78,6 +83,14 @@ export type TabsComponent = BaseComponent<'tabscomponent'> & {
tabs: string[]
}
export type ContainerComponent = BaseComponent<'containercomponent'>
export type DrawerComponent = BaseComponent<'drawercomponent'>
export type MapComponent = BaseComponent<'mapcomponent'>
export type VerticalSplitPanesComponent = BaseComponent<'verticalsplitpanescomponent'> & {
panes: number[]
}
export type HorizontalSplitPanesComponent = BaseComponent<'horizontalsplitpanescomponent'> & {
panes: number[]
}
export type AppComponent = BaseAppComponent &
(
@@ -94,11 +107,11 @@ export type AppComponent = BaseAppComponent &
| HtmlComponent
| TableComponent
| TextComponent
| TableComponent
| ButtonComponent
| PieChartComponent
| ScatterChartComponent
| SelectComponent
| MultiSelectComponent
| CheckboxComponent
| FormComponent
| FormButtonComponent
@@ -112,6 +125,10 @@ export type AppComponent = BaseAppComponent &
| FileInputComponent
| ImageComponent
| AggridComponent
| DrawerComponent
| MapComponent
| VerticalSplitPanesComponent
| HorizontalSplitPanesComponent
)
export type AppComponentDimensions = `${IntRange<
@@ -272,6 +289,12 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
type: 'static',
value: ''
},
gotoNewTab: {
tooltip: 'Go to create a new tab',
fieldType: 'boolean',
type: 'static',
value: true
},
beforeIcon: {
type: 'static',
value: undefined,
@@ -850,7 +873,7 @@ Hello \${ctx.username}
{ value: 'bar', label: 'Bar' }
]
},
multiple: {
create: {
type: 'static',
fieldType: 'boolean',
value: false,
@@ -861,6 +884,43 @@ Hello \${ctx.username}
fieldType: 'text',
value: 'Select an item',
onlyStatic: true
},
defaultValue: {
type: 'static',
value: undefined,
fieldType: 'object'
}
},
customCss: {
input: { style: '' }
} as const,
card: false,
softWrap: true
}
},
multiselectcomponent: {
name: 'MultiSelect',
icon: List,
dims: '2:1-3:1',
data: {
verticalAlignment: 'center',
id: '',
type: 'multiselectcomponent',
componentInput: undefined,
configuration: {
items: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: [
"Foo", "Bar"
]
},
placeholder: {
type: 'static',
fieldType: 'text',
value: 'Select items',
onlyStatic: true
}
},
customCss: {
@@ -908,7 +968,9 @@ Hello \${ctx.username}
fieldType: 'number'
}
},
customCss: {},
customCss: {
input: { class: '', style: '' }
} as const,
card: false
}
},
@@ -949,7 +1011,9 @@ Hello \${ctx.username}
optionValuesKey: 'localeOptions'
}
},
customCss: {},
customCss: {
input: { class: '', style: '' }
} as const,
card: false
}
},
@@ -986,7 +1050,11 @@ Hello \${ctx.username}
fieldType: 'number'
}
},
customCss: {},
customCss: {
handle: { style: '' },
limits: { class: '', style: '' },
value: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1027,7 +1095,11 @@ Hello \${ctx.username}
fieldType: 'number'
}
},
customCss: {},
customCss: {
handles: { style: '' },
limits: { class: '', style: '' },
values: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1049,7 +1121,9 @@ Hello \${ctx.username}
onlyStatic: true
}
},
customCss: {},
customCss: {
input: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1080,7 +1154,9 @@ Hello \${ctx.username}
fieldType: 'date'
}
},
customCss: {},
customCss: {
input: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1100,6 +1176,12 @@ Hello \${ctx.username}
onlyStatic: true
}
},
customCss: {
tabRow: { class: '', style: '' },
allTabs: { class: '', style: '' },
selectedTab: { class: '', style: '' },
container: { class: '', style: '' }
} as const,
componentInput: undefined,
card: false,
numberOfSubgrids: 2,
@@ -1141,7 +1223,10 @@ Hello \${ctx.username}
onlyStatic: true
}
},
customCss: {},
customCss: {
container: { class: '', style: '' },
icon: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1168,7 +1253,10 @@ Hello \${ctx.username}
onlyStatic: true
}
},
customCss: {},
customCss: {
container: { class: '', style: '' },
divider: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1195,7 +1283,10 @@ Hello \${ctx.username}
onlyStatic: true
}
},
customCss: {},
customCss: {
container: { class: '', style: '' },
divider: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1228,7 +1319,9 @@ Hello \${ctx.username}
onlyStatic: true
}
},
customCss: {},
customCss: {
container: { class: '', style: '' }
} as const,
card: false
}
},
@@ -1247,7 +1340,7 @@ Hello \${ctx.username}
fieldType: 'text',
fileUpload: {
accept: 'image/*',
base64: true
convertTo: 'base64'
}
},
imageFit: {
@@ -1263,16 +1356,177 @@ Hello \${ctx.username}
fieldType: 'text',
onlyStatic: true,
tooltip: "This text will appear if the image can't be loaded for any reason"
},
customStyles: {
}
},
customCss: {
image: { class: '', style: '' }
} as const,
card: false
}
},
drawercomponent: {
name: 'Drawer',
icon: SidebarClose,
dims: '1:1-2:1',
data: {
softWrap: true,
id: '',
type: 'drawercomponent',
horizontalAlignment: 'center',
verticalAlignment: 'center',
configuration: {
noPadding: {
type: 'static',
value: '',
fieldType: 'textarea',
fieldType: 'boolean',
value: false,
onlyStatic: true
},
drawerTitle: {
type: 'static',
fieldType: 'text',
value: 'Drawer title',
onlyStatic: true
},
label: {
type: 'static',
fieldType: 'text',
value: 'Press me'
},
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonColorOptions',
value: 'blue'
},
size: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions',
value: 'xs'
},
fillContainer: {
fieldType: 'boolean',
type: 'static',
onlyStatic: true,
value: false
},
disabled: {
fieldType: 'boolean',
type: 'eval',
expr: 'false'
}
},
componentInput: undefined,
card: false,
numberOfSubgrids: 1
}
},
mapcomponent: {
name: 'Map',
icon: MapPin,
dims: '3:6-6:10',
data: {
id: '',
type: 'mapcomponent',
componentInput: undefined,
configuration: {
longitude: {
fieldType: 'number',
type: 'static',
value: 15
},
latitude: {
fieldType: 'number',
type: 'static',
value: 50
},
zoom: {
fieldType: 'number',
type: 'static',
value: 3
},
markers: {
fieldType: 'array',
type: 'static',
subFieldType: 'object',
fileUpload: {
accept: 'application/json',
convertTo: 'text'
},
value: [
{
lon: 12.496366,
lat: 41.902783,
title: 'Rome',
radius: 7,
color: '#dc2626',
strokeWidth: 3,
strokeColor: '#fca5a5'
},
{
lon: -0.136439,
lat: 51.507359,
title: 'London'
}
]
}
},
customCss: {
map: { class: '', style: '' }
} as const,
card: false
}
},
verticalsplitpanescomponent: {
name: 'Vertical Split Panes',
icon: FlipHorizontal,
dims: '2:8-6:8',
data: {
softWrap: true,
id: '',
type: 'verticalsplitpanescomponent',
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
}
},
customCss: {},
card: false
customCss: {
container: { class: '', style: '' }
} as const,
componentInput: undefined,
card: false,
panes: [50, 50],
numberOfSubgrids: 2
}
},
horizontalsplitpanescomponent: {
name: 'Horizontal Split Panes',
icon: FlipVertical,
dims: '2:8-6:8',
data: {
softWrap: true,
id: '',
type: 'horizontalsplitpanescomponent',
configuration: {
noPadding: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true
}
},
customCss: {
container: { class: '', style: '' }
} as const,
componentInput: undefined,
card: false,
panes: [50, 50],
numberOfSubgrids: 2
}
}
}

View File

@@ -6,7 +6,10 @@ const layout: ComponentSet = {
'tabscomponent',
'containercomponent',
'horizontaldividercomponent',
'verticaldividercomponent'
'verticaldividercomponent',
'drawercomponent',
'verticalsplitpanescomponent',
'horizontalsplitpanescomponent'
]
} as const
@@ -27,7 +30,8 @@ const inputs: ComponentSet = {
'dateinputcomponent',
'fileinputcomponent',
'checkboxcomponent',
'selectcomponent'
'selectcomponent',
'multiselectcomponent',
]
} as const
@@ -37,6 +41,7 @@ const display: ComponentSet = {
'textcomponent',
'iconcomponent',
'imagecomponent',
'mapcomponent',
'htmlcomponent',
'tablecomponent',
'aggridcomponent',
@@ -46,8 +51,8 @@ const display: ComponentSet = {
'plotlycomponent',
'scatterchartcomponent',
'timeseriescomponent',
'displaycomponent',
'displaycomponent'
]
} as const
export const COMPONENT_SETS = [layout, buttons, inputs, display] as const
export const COMPONENT_SETS = [layout, buttons, inputs, display] as const

View File

@@ -178,7 +178,6 @@
{#each ids as id}
<div class="mb-3">
{#if $app?.css?.[type][id]}
{JSON.stringify($app.css[type][id])}
<CssProperty
name={id}
bind:value={$app.css[type][id]}

View File

@@ -9,6 +9,18 @@ export const staticValues = {
chartThemeOptions: ['theme1', 'theme2', 'theme3'],
textStyleOptions: ['Title', 'Subtitle', 'Body', 'Label', 'Caption'],
currencyOptions: ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'JPY', 'CNY', 'INR', 'BRL'],
localeOptions: ['en-US', 'en-GB', 'en-IE', 'de-DE', 'fr-FR', 'br-FR', 'ja-JP', 'pt-TL', 'fr-CA', 'en-CA'],
objectFitOptions: ['contain', 'cover', 'fill']
localeOptions: [
'en-US',
'en-GB',
'en-IE',
'de-DE',
'fr-FR',
'br-FR',
'ja-JP',
'pt-TL',
'fr-CA',
'en-CA'
],
objectFitOptions: ['contain', 'cover', 'fill'],
splitPanelOptions: ['2', '3', '4']
} as const

View File

@@ -29,7 +29,7 @@
function getComponentNameById(componentId: string) {
const component = findGridItem($app, componentId)
if (component?.data.type) {
if (component?.data?.type) {
return components[component?.data.type].name
} else if (componentId == 'ctx') {
return 'Context'
@@ -50,6 +50,7 @@
>
{#each panels as [componentId, outputs] (componentId)}
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
{@const name = getComponentNameById(componentId)}
<div>
<div
class="flex {$connectingInput?.opened
@@ -75,7 +76,7 @@
'bg-gray-500 text-white'
)}
>
{getComponentNameById(componentId)}
{name}
</span>
</div>
@@ -92,7 +93,9 @@
{#key $connectingInput?.opened}
<ComponentOutputViewer
outputs={$connectingInput?.opened && $selectedComponent === componentId
? ['search']
? name == 'Table'
? ['search']
: []
: outputs}
{componentId}
on:select={({ detail }) => {

View File

@@ -13,14 +13,20 @@
import { emptySchema, scriptLangToEditorLang } from '$lib/utils'
import Tooltip from '$lib/components/Tooltip.svelte'
import Popover from '../../../Popover.svelte'
import { computeFields } from './utils'
import { deepEqual } from 'fast-equals'
import type { AppInput } from '../../inputType'
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
export let inlineScript: InlineScript
export let name: string | undefined = undefined
export let id: string
export let defaultUserInput: boolean = false
export let fields: Record<string, AppInput> = {}
export let syncFields: boolean = false
const { runnableComponents } = getContext<AppEditorContext>('AppEditorContext')
const { runnableComponents, stateId } = getContext<AppEditorContext>('AppEditorContext')
let editor: Editor
let validCode = true
@@ -49,9 +55,24 @@
emptySchema()
)
}
if (inlineScript.schema) {
loadSchemaAndInputsByName()
}
})
const dispatch = createEventDispatcher()
let runLoading = false
async function loadSchemaAndInputsByName() {
if (syncFields) {
const newSchema = inlineScript.schema
const newFields = computeFields(newSchema, defaultUserInput, fields)
if (!deepEqual(newFields, fields)) {
fields = newFields
$stateId++
}
}
}
</script>
<InlineScriptEditorDrawer {editor} bind:this={inlineScriptEditorDrawer} bind:inlineScript />
@@ -151,6 +172,7 @@
await inferInlineScriptSchema(inlineScript?.language, e.detail, inlineScript.schema)
if (JSON.stringify(inlineScript.schema) != oldSchema) {
inlineScript = inlineScript
loadSchemaAndInputsByName()
}
}
}}

View File

@@ -15,6 +15,7 @@
import { deepEqual } from 'fast-equals'
export let componentInput: AppInput | undefined
export let defaultUserInput = false
export let id: string
async function fork(path: string) {
@@ -73,9 +74,12 @@
{#if componentInput?.runnable?.type === 'runnableByName' && componentInput?.runnable?.name !== undefined}
{#if componentInput.runnable.inlineScript}
<InlineScriptEditor
{defaultUserInput}
{id}
bind:inlineScript={componentInput.runnable.inlineScript}
bind:name={componentInput.runnable.name}
bind:fields={componentInput.fields}
syncFields
on:delete={() => {
if (componentInput && componentInput.type == 'runnable') {
componentInput = clearResultAppInput(componentInput)

View File

@@ -21,18 +21,22 @@
</Pane>
<Pane size={75}>
{#if !selectedScriptComponentId}
<span class="p-2 text-sm text-gray-600">Select a script on the left panel</span>
<div class="text-sm text-gray-500 text-center py-8 px-2">
Select a script on the left panel
</div>
{/if}
{#each $app.grid as gridItem (gridItem.data.id)}
{#if gridItem.data.id === selectedScriptComponentId}
{#each $app.grid as gridItem, index (gridItem?.data?.id ?? index)}
{#if gridItem?.data?.id && gridItem.data.id === selectedScriptComponentId}
<InlineScriptEditorPanel
defaultUserInput={gridItem?.data?.type == 'formcomponent' ||
gridItem?.data?.type == 'buttonformcomponent'}
id={gridItem.data.id}
bind:componentInput={gridItem.data.componentInput}
/>
{/if}
{#if gridItem.data.type === 'tablecomponent'}
{#if gridItem?.data?.type === 'tablecomponent'}
{#each gridItem.data.actionButtons as actionButton, index (index)}
{#if actionButton.id === selectedScriptComponentId}
<InlineScriptEditorPanel
@@ -46,15 +50,17 @@
{#if $app.subgrids}
{#each Object.keys($app.subgrids ?? {}) as key (key)}
{#each $app.subgrids[key] as gridItem (gridItem.data.id)}
{#if gridItem.data.id === selectedScriptComponentId}
{#each $app.subgrids[key] as gridItem (gridItem?.data?.id)}
{#if gridItem?.data?.id && gridItem.data.id === selectedScriptComponentId}
<InlineScriptEditorPanel
defaultUserInput={gridItem.data?.type == 'formcomponent' ||
gridItem.data?.type == 'buttonformcomponent'}
id={gridItem.data.id}
bind:componentInput={gridItem.data.componentInput}
/>
{/if}
{#if gridItem.data.type === 'tablecomponent'}
{#if gridItem?.data?.type === 'tablecomponent'}
{#each gridItem.data.actionButtons as actionButton, index (index)}
{#if actionButton.id === selectedScriptComponentId}
<InlineScriptEditorPanel
@@ -89,6 +95,8 @@
id={`bg_${index}`}
bind:inlineScript={hiddenInlineScript.inlineScript}
bind:name={hiddenInlineScript.name}
bind:fields={hiddenInlineScript.fields}
syncFields
on:delete={() => {
// remove the script from the array at the index
$app.hiddenInlineScripts.splice(index, 1)

View File

@@ -11,7 +11,7 @@
const PREFIX = 'script-selector-' as const
export let selectedScriptComponentId: string | undefined = undefined
const { app, selectedComponent, lazyGrid } = getContext<AppEditorContext>('AppEditorContext')
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let list: HTMLElement
function selectScript(id: string) {
@@ -21,7 +21,7 @@
}
}
$: runnables = getAppScripts($lazyGrid, $app.subgrids)
$: runnables = getAppScripts($app.grid, $app.subgrids)
// When selected component changes, update selectedScriptComponentId
$: if ($selectedComponent != selectedScriptComponentId) {
@@ -53,7 +53,11 @@
}
</script>
<div class="mb-0 pb-0 pl-2 sticky top-0 bg-white border-b text-xs font-semibold">Runnables</div>
<div
class="sticky top-0 py-0.5 px-2 text-center bg-gray-200 text-gray-500 text-2xs font-bold tracking-wide border-b border-gray-300"
>
RUNNABLES
</div>
<div bind:this={list} class="grow flex flex-col gap-4">
<PanelSection title="Inline scripts" smallPadding>
<div class="flex flex-col gap-2 w-full">

View File

@@ -44,26 +44,28 @@ export function computeFields(schema: Schema, defaultUserInput: boolean, fields:
function processGridItemRunnable(gridItem: GridItem, list: AppScriptsList): AppScriptsList {
const component: AppComponent = gridItem.data
const componentInput = component.componentInput
if (component.type === 'tablecomponent') {
component.actionButtons.forEach((actionButton) => {
if (actionButton.componentInput?.type !== 'runnable') {
return
}
processRunnable(actionButton.componentInput.runnable, actionButton.id, list)
})
}
if (componentInput?.type === 'runnable') {
processRunnable(componentInput.runnable, gridItem.id, list)
if (component) {
const componentInput = component.componentInput
if (component.type === 'tablecomponent') {
component.actionButtons.forEach((actionButton) => {
if (actionButton.componentInput?.type !== 'runnable') {
return
}
processRunnable(actionButton.componentInput.runnable, actionButton.id, list)
})
}
if (componentInput?.type === 'runnable') {
processRunnable(componentInput.runnable, gridItem.id, list)
}
}
return list
}
export function getAppScripts(
lazyGrid: GridItem[],
grid: GridItem[],
subgrids: Record<string, GridItem[]> | undefined
): AppScriptsList {
const scriptsList = lazyGrid.reduce(
const scriptsList = grid.reduce(
(acc, gridComponent) => processGridItemRunnable(gridComponent, acc),
{ inline: [], imported: [] } as AppScriptsList
)

View File

@@ -12,6 +12,9 @@
const dispatch = createEventDispatcher()
function addElementByType() {
if (!Array.isArray(componentInput.value)) {
componentInput.value = []
}
if (componentInput.subFieldType && componentInput.value) {
if (componentInput.subFieldType === 'boolean') {
componentInput.value.push(false)
@@ -47,7 +50,7 @@
</script>
<div class="flex gap-2 flex-col mt-2">
{#if componentInput.value}
{#if Array.isArray(componentInput.value)}
{#each componentInput.value as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<SubTypeEditor bind:componentInput bind:value />
@@ -65,13 +68,8 @@
</div>
</div>
{/each}
<Button
size="xs"
color="light"
startIcon={{ icon: faPlus }}
on:click={() => addElementByType()}
>
Add
</Button>
{/if}
<Button size="xs" color="light" startIcon={{ icon: faPlus }} on:click={() => addElementByType()}>
Add
</Button>
</div>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import { faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { faChevronDown, faChevronUp, faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext, GridItem } from '../../types'
import PanelSection from './common/PanelSection.svelte'
@@ -24,15 +24,25 @@
import { duplicateGridItem } from '../appUtils'
import { deleteGridItem } from '../appUtils'
import MoveToOtherGrid from './MoveToOtherGrid.svelte'
import GridPane from './GridPane.svelte'
import { slide } from 'svelte/transition'
export let component: AppComponent
export let rowColumns = false
export let onDelete: (() => void) | undefined = undefined
export let parent: string | undefined
export let noGrid = false
export let duplicateMoveAllowed = true
const { app, staticOutputs, runnableComponents, selectedComponent, worldStore, focusedGrid } =
getContext<AppEditorContext>('AppEditorContext')
const {
app,
staticOutputs,
runnableComponents,
selectedComponent,
worldStore,
focusedGrid,
stateId
} = getContext<AppEditorContext>('AppEditorContext')
function duplicateElement(id: string) {
$dirtyStore = true
@@ -60,6 +70,8 @@
onDelete?.()
}
let viewCssOptions = false
$: extraLib =
component?.componentInput?.type === 'template' && $worldStore
? buildExtraLib($worldStore?.outputsById ?? {}, component?.id, false)
@@ -106,38 +118,42 @@
{:else if component.componentInput?.type === 'runnable' && component.componentInput !== undefined}
<RunnableInputEditor
bind:appInput={component.componentInput}
defaultUserInput={component.type == 'formcomponent'}
defaultUserInput={component.type == 'formcomponent' ||
component.type == 'formbuttoncomponent'}
/>
{/if}
</div>
{#if component.componentInput?.type === 'runnable' && Object.keys(component.componentInput.fields ?? {}).length > 0}
<div class="border w-full">
<PanelSection
smallPadding
title={`Runnable Inputs (${
Object.keys(component.componentInput.fields ?? {}).length
})`}
>
<svelte:fragment slot="action">
<Tooltip>
The runnable inputs of a button component are not settable by the user. They must
be defined statically or connected.
</Tooltip>
</svelte:fragment>
{#key $stateId}
{#if component.componentInput?.type === 'runnable'}
{#if Object.keys(component.componentInput.fields ?? {}).length > 0}
<div class="border w-full">
<PanelSection
smallPadding
title={`Runnable Inputs (${
Object.keys(component.componentInput.fields ?? {}).length
})`}
>
<svelte:fragment slot="action">
<Tooltip>
The runnable inputs of a button component are not settable by the user. They
must be defined statically or connected.
</Tooltip>
</svelte:fragment>
<InputsSpecsEditor
id={component.id}
shouldCapitalize={false}
bind:inputSpecs={component.componentInput.fields}
userInputEnabled={component.type !== 'buttoncomponent'}
{rowColumns}
/>
</PanelSection>
</div>
{/if}
<InputsSpecsEditor
id={component.id}
shouldCapitalize={false}
bind:inputSpecs={component.componentInput.fields}
userInputEnabled={component.type !== 'buttoncomponent'}
{rowColumns}
/>
</PanelSection>
</div>
{/if}
{/if}
{/key}
</PanelSection>
{/if}
{#if Object.values(component.configuration).length > 0}
<PanelSection title={`Configuration (${Object.values(component.configuration).length})`}>
<InputsSpecsEditor
@@ -151,9 +167,9 @@
{#if component.type === 'tabscomponent'}
<GridTab bind:tabs={component.tabs} {component} />
{/if}
{#if component.type === 'tablecomponent' && Array.isArray(component.actionButtons)}
{:else if component.type === 'verticalsplitpanescomponent' || component.type === 'horizontalsplitpanescomponent'}
<GridPane bind:panes={component.panes} {component} />
{:else if component.type === 'tablecomponent' && Array.isArray(component.actionButtons)}
<TableActions id={component.id} bind:components={component.actionButtons} />
{/if}
@@ -164,34 +180,50 @@
{#if Object.keys(component.customCss ?? {}).length > 0}
<PanelSection title="Custom CSS">
{#each Object.keys(component.customCss ?? {}) as name}
{#if component?.customCss?.[name]}
<div class="w-full mb-2">
<CssProperty {name} bind:value={component.customCss[name]} />
</div>
{/if}
{/each}
<div slot="action">
<Button
color="light"
size="xs"
endIcon={{ icon: viewCssOptions ? faChevronUp : faChevronDown }}
on:click={() => (viewCssOptions = !viewCssOptions)}
>
{viewCssOptions ? 'Hide' : 'Show'}
</Button>
</div>
{#if viewCssOptions}
<div transition:slide>
{#each Object.keys(component.customCss ?? {}) as name}
{#if component?.customCss?.[name]}
<div class="w-full mb-2">
<CssProperty {name} bind:value={component.customCss[name]} />
</div>
{/if}
{/each}
</div>
{/if}
</PanelSection>
{/if}
<PanelSection title="Duplicate">
<Button
size="xs"
color="dark"
startIcon={{ icon: faCopy }}
on:click={() => {
if (component) {
duplicateElement(component.id)
}
}}
>
Duplicate component: {component.id}
</Button>
</PanelSection>
{#if duplicateMoveAllowed}
<PanelSection title="Duplicate">
<Button
size="xs"
color="dark"
startIcon={{ icon: faCopy }}
on:click={() => {
if (component) {
duplicateElement(component.id)
}
}}
>
Duplicate component: {component.id}
</Button>
</PanelSection>
<PanelSection title="Move to other grid">
<MoveToOtherGrid bind:component {parent} />
</PanelSection>
<PanelSection title="Move to other grid">
<MoveToOtherGrid bind:component {parent} />
</PanelSection>
{/if}
<PanelSection title="Danger zone">
<Button

View File

@@ -0,0 +1,85 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import { deleteGridItem } from '../appUtils'
import type { AppComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
export let panes: number[]
export let component: AppComponent
const { app, staticOutputs, runnableComponents } =
getContext<AppEditorContext>('AppEditorContext')
function addTab() {
const numberOfPanes = panes.length
panes = Array(panes.length + 1)
.fill(0)
.map((_) => Math.floor(100 / (panes.length + 1)))
if (!$app.subgrids) {
$app.subgrids = {}
}
$app.subgrids[`${component.id}-${numberOfPanes}`] = []
component.numberOfSubgrids = panes.length
}
function deleteSubgrid(index: number) {
let subgrid = `${component.id}-${index}`
for (const item of $app!.subgrids![subgrid]) {
const components = deleteGridItem($app, item.data, subgrid, false)
console.log(components)
for (const key in components) {
delete $staticOutputs[key]
delete $runnableComponents[key]
}
}
$staticOutputs = $staticOutputs
$runnableComponents = $runnableComponents
for (let i = index; i < panes.length - 1; i++) {
$app!.subgrids![`${component.id}-${i}`] = $app!.subgrids![`${component.id}-${i + 1}`]
}
panes.splice(index, 1)
panes = panes
component.numberOfSubgrids = panes.length
$app = $app
}
</script>
<PanelSection title={`panes ${panes.length > 0 ? `(${panes.length})` : ''}`}>
{#if panes.length == 0}
<span class="text-xs text-gray-500">No panes</span>
{/if}
<div class="flex gap-2 flex-col mt-2">
{#each panes as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<input type="number" bind:value />
<div class="absolute top-1 right-1">
<Button
size="xs"
color="light"
variant="border"
on:click={() => {
deleteSubgrid(index)
}}
iconOnly
btnClasses="!text-red-500"
startIcon={{ icon: faTrashAlt }}
/>
</div>
</div>
{/each}
<Button
size="xs"
color="light"
variant="border"
startIcon={{ icon: faPlus }}
on:click={addTab}
iconOnly
/>
</div>
</PanelSection>

View File

@@ -44,6 +44,30 @@
fieldType: 'boolean',
type: 'eval',
expr: 'false'
},
goto: {
tooltip: 'Go to an url on success if not empty',
fieldType: 'text',
type: 'static',
value: ''
},
gotoNewTab: {
tooltip: 'Go to create a new tab',
fieldType: 'boolean',
type: 'static',
value: true
},
beforeIcon: {
type: 'static',
value: undefined,
fieldType: 'icon-select',
onlyStatic: true
},
afterIcon: {
type: 'static',
value: undefined,
fieldType: 'icon-select',
onlyStatic: true
}
},
componentInput: {
@@ -54,6 +78,9 @@
value: undefined
},
recomputeIds: undefined,
customCss: {
button: { style: '', class: '' }
} as const,
card: false
}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { FileInput } from "../../../../common"
import type { UploadAppInput } from "../../../inputType"
import { FileInput } from '../../../../common'
import type { UploadAppInput } from '../../../inputType'
export let componentInput: UploadAppInput | undefined
</script>
@@ -8,19 +8,17 @@
<FileInput
accept={componentInput?.fileUpload?.accept}
multiple={componentInput?.fileUpload?.multiple}
convertToBase64={componentInput?.fileUpload?.base64}
convertTo={componentInput?.fileUpload?.convertTo}
iconSize={24}
class="text-sm py-4"
on:change={({detail}) => {
if(componentInput) {
componentInput.value = componentInput?.fileUpload?.multiple
? detail
: detail?.[0]
on:change={({ detail }) => {
if (componentInput) {
componentInput.value = componentInput?.fileUpload?.multiple ? detail : detail?.[0]
}
}}
>
<svelte:fragment slot="selected-title">
<!-- Removing the title when there is a selected file -->
<span></span>
<span />
</svelte:fragment>
</FileInput>
</FileInput>

View File

@@ -1,3 +1,4 @@
import type { ReadFileAs } from '../common/fileInput/model'
import type { staticValues } from './editor/componentsPanel/componentStaticValues'
import type { InlineScript } from './types'
@@ -110,10 +111,10 @@ type InputConfiguration<T extends InputType, U, V extends InputType> = {
*/
multiple?: boolean
/**
* Controls if the uploaded file(s) will be returned as `Base64` strings.
* Controls if the uploaded file(s) will be converted or not.
* @default false
*/
base64?: boolean
convertTo?: ReadFileAs
}
}

Some files were not shown because too many files have changed in this diff Show More