Compare commits
10 Commits
supabase-c
...
app-compon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a3373edddf | ||
|
|
71da2f67e6 | ||
|
|
0f4da49886 | ||
|
|
ae81d7ee2c | ||
|
|
98f8b845a6 | ||
|
|
f521fb63fa | ||
|
|
fce464434b | ||
|
|
ee18804c87 | ||
|
|
6b5963f9d2 | ||
|
|
d00a80a8f6 |
49
CHANGELOG.md
49
CHANGELOG.md
@@ -1,55 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
## [1.78.0](https://github.com/windmill-labs/windmill/compare/v1.77.0...v1.78.0) (2023-03-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** app textcomponent editable + tooltip ([11567d6](https://github.com/windmill-labs/windmill/commit/11567d6280ea60f1a8c3c6607c724179775cbbe3))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** whitelist for include_header was ignored in some cases ([183a459](https://github.com/windmill-labs/windmill/commit/183a4591df700ab4720de6e92a83631256940089))
|
||||
* **frontend:** improve rendering performance after component moving ([6f890f2](https://github.com/windmill-labs/windmill/commit/6f890f2120885f90d986fbd655096b45bf9de539))
|
||||
* **frontend:** remove staticOutputs from apps ([dbdfd62](https://github.com/windmill-labs/windmill/commit/dbdfd626386398180ecba7976714f86365eeccd8))
|
||||
|
||||
## [1.77.0](https://github.com/windmill-labs/windmill/compare/v1.76.0...v1.77.0) (2023-03-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **apps:** state can be used as input in apps ([2f0acb9](https://github.com/windmill-labs/windmill/commit/2f0acb9ffa8dace4a886527dcee49809d019b271))
|
||||
* **apps:** tabs can be made pages or invisible + better frontend scripts reactivity ([cd645d0](https://github.com/windmill-labs/windmill/commit/cd645d0935f2d06e0ff71f14d2cf63accd378ff3))
|
||||
* **deno:** add support for custom npm repo ([#1291](https://github.com/windmill-labs/windmill/issues/1291)) ([944795f](https://github.com/windmill-labs/windmill/commit/944795f6eeaa7d01ab1a35a80570a55c363723e6))
|
||||
* **frontend:** add setTab to frontend scripts ([c2a97c5](https://github.com/windmill-labs/windmill/commit/c2a97c53cfff0fdb35dd8bc249490566eebdc1a9))
|
||||
* **frontend:** app components output panel ([#1283](https://github.com/windmill-labs/windmill/issues/1283)) ([751edcf](https://github.com/windmill-labs/windmill/commit/751edcf9b8e0976a1d073603c9eff5dc6e714490))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** do not cache reference to workspace scripts ([eb73f2a](https://github.com/windmill-labs/windmill/commit/eb73f2a687f6faad301b9038ab8585450bec7481))
|
||||
* **frontend:** fix app tabs ([#1288](https://github.com/windmill-labs/windmill/issues/1288)) ([c71a577](https://github.com/windmill-labs/windmill/commit/c71a577fead90c9cd01a736b54d859ec4f0b7807))
|
||||
* **frontend:** fix container deletion ([#1287](https://github.com/windmill-labs/windmill/issues/1287)) ([bc870bd](https://github.com/windmill-labs/windmill/commit/bc870bd03eb76cb8bc0e0c861f6cd8a9c661186b))
|
||||
* **frontend:** Update setting accordion ([#1285](https://github.com/windmill-labs/windmill/issues/1285)) ([dea12e8](https://github.com/windmill-labs/windmill/commit/dea12e8870ece998bb6607723cbaab9b9a958f22))
|
||||
|
||||
## [1.76.0](https://github.com/windmill-labs/windmill/compare/v1.75.0...v1.76.0) (2023-03-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **frontend:** add frontend (JS) scripts to apps ([f0b1b1f](https://github.com/windmill-labs/windmill/commit/f0b1b1f752731ba434b960a75624118152f53c00))
|
||||
* **frontend:** Copy, Cut and Paste ([#1279](https://github.com/windmill-labs/windmill/issues/1279)) ([82c139e](https://github.com/windmill-labs/windmill/commit/82c139ed0992be401e250cfb7ecc0fca61b76772))
|
||||
* **frontend:** disabled for action buttons can now depend on row ([75f87e7](https://github.com/windmill-labs/windmill/commit/75f87e7e1117a9c12afcf626379e94b134a9a493))
|
||||
* **frontend:** improve drag-n-drop behavior ([cfd489a](https://github.com/windmill-labs/windmill/commit/cfd489a55059e7b6843f99bab261c70b3852e6a2))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backend:** improve worker ping api ([c958480](https://github.com/windmill-labs/windmill/commit/c958480ce83844a989f58dd5a70eb288582e2194))
|
||||
* **frontend:** General fixes and updates ([#1281](https://github.com/windmill-labs/windmill/issues/1281)) ([3e5a179](https://github.com/windmill-labs/windmill/commit/3e5a179eb8cd8001f49c92305141dade1571e20f))
|
||||
|
||||
## [1.75.0](https://github.com/windmill-labs/windmill/compare/v1.74.2...v1.75.0) (2023-03-11)
|
||||
|
||||
|
||||
|
||||
@@ -316,7 +316,6 @@ you to have it being synced automatically everyday.
|
||||
| QUEUE_LIMIT_WAIT_RESULT | None | The number of max jobs in the queue before rejecting immediately the request in 'run_wait_result' endpoint. Takes precedence on the query arg. If none is specified, there are no limit. | Worker |
|
||||
| DENO_AUTH_TOKENS | None | Custom DENO_AUTH_TOKENS to pass to worker to allow the use of private modules | Worker |
|
||||
| DENO_FLAGS | None | Override the flags passed to deno (default --allow-all) to tighten permissions. Minimum permissions needed are "--allow-read=args.json --allow-write=result.json" | Worker |
|
||||
| NPM_CONFIG_REGISTRY | None | Registry to use for NPM dependencies, set if you have a private repository you need to use instead of the default public NPM registry | Worker |
|
||||
| PIP_LOCAL_DEPENDENCIES | None | Specify dependencies that are installed locally and do not need to be solved nor installed again | |
|
||||
| ADDITIONAL_PYTHON_PATHS | None | Specify python paths (separated by a :) to be appended to the PYTHONPATH of the python jobs. To be used with PIP_LOCAL_DEPENDENCIES to use python codebases within Windmill | Worker |
|
||||
| INCLUDE_HEADERS | None | Whitelist of headers that are passed to jobs as args (separated by a comma) | Server |
|
||||
|
||||
191
backend/Cargo.lock
generated
191
backend/Cargo.lock
generated
@@ -41,17 +41,6 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
@@ -265,9 +254,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.11"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13d8068b6ccb8b34db9de397c7043f91db8b4c66414952c6db944f238c4d3db3"
|
||||
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
@@ -292,6 +281,7 @@ dependencies = [
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http 0.3.5",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
@@ -477,7 +467,7 @@ version = "8.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996"
|
||||
dependencies = [
|
||||
"semver 1.0.17",
|
||||
"semver 1.0.16",
|
||||
"serde",
|
||||
"toml",
|
||||
"url",
|
||||
@@ -500,9 +490,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.24"
|
||||
version = "0.4.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
@@ -1347,16 +1337,7 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1365,7 +1346,7 @@ version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
|
||||
dependencies = [
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1610,7 +1591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -2171,9 +2152,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.46"
|
||||
version = "0.10.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd2523381e46256e40930512c7fd25562b9eae4812cb52078f155e87217c9d1e"
|
||||
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
@@ -2203,9 +2184,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.81"
|
||||
version = "0.9.80"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "176be2629957c157240f68f61f2d0053ad3a4ecfdd9ebf1e6521d18d9635cf67"
|
||||
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
@@ -2534,9 +2515,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.52"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d0e1ae9e836cc3beddd63db0df682593d7e2d3d891ae8c9083d2113e1744224"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2544,7 +2525,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"built",
|
||||
@@ -2562,7 +2543,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-client"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2576,7 +2557,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-impl"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"getopts",
|
||||
"heck",
|
||||
@@ -2598,7 +2579,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "progenitor-macro"
|
||||
version = "0.2.1-dev"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#ebc18cc6cd32d656ff787eb03777c1bbbbd8a0f2"
|
||||
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
|
||||
dependencies = [
|
||||
"openapiv3",
|
||||
"proc-macro2",
|
||||
@@ -2643,9 +2624,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -2769,11 +2750,10 @@ checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
|
||||
|
||||
[[package]]
|
||||
name = "regress"
|
||||
version = "0.5.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d995d590bd8ec096d1893f414bf3f5e8b0ee4c9eed9a5642b9766ef2c8e2e8e9"
|
||||
checksum = "0a92ff21fe8026ce3f2627faaf43606f0b67b014dbc9ccf027181a804f75d92e"
|
||||
dependencies = [
|
||||
"hashbrown 0.13.2",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -2919,7 +2899,7 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver 1.0.17",
|
||||
"semver 1.0.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2973,7 +2953,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-ast"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#2efc566b14e64c4a5d8120fcc167a49d2a9e5615"
|
||||
source = "git+https://github.com/RustPython/RustPython#415cdb1ef9b832fbca858a05f6894087e133dd31"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"rustpython-compiler-core",
|
||||
@@ -2982,7 +2962,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-compiler-core"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#2efc566b14e64c4a5d8120fcc167a49d2a9e5615"
|
||||
source = "git+https://github.com/RustPython/RustPython#415cdb1ef9b832fbca858a05f6894087e133dd31"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bstr",
|
||||
@@ -2995,9 +2975,9 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "rustpython-parser"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/RustPython/RustPython#2efc566b14e64c4a5d8120fcc167a49d2a9e5615"
|
||||
source = "git+https://github.com/RustPython/RustPython#415cdb1ef9b832fbca858a05f6894087e133dd31"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"ahash",
|
||||
"anyhow",
|
||||
"itertools",
|
||||
"lalrpop",
|
||||
@@ -3143,9 +3123,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.17"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed"
|
||||
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@@ -3167,9 +3147,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.156"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "314b5b092c0ade17c00142951e50ced110ec27cea304b1037c6969246c2469a4"
|
||||
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -3196,9 +3176,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.156"
|
||||
version = "1.0.154"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7e29c4601e36bcec74a223228dce795f4cd3616341a4af93520ca1a837c087d"
|
||||
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3503,7 +3483,7 @@ version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"ahash",
|
||||
"atoi",
|
||||
"base64 0.13.1",
|
||||
"bitflags",
|
||||
@@ -3638,9 +3618,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "string_enum"
|
||||
version = "0.3.3"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41491e23e7db79343236a6ced96325ff132eb09e29ac4c5b8132b9c55aaaae89"
|
||||
checksum = "994453cd270ad0265796eb24abf5540091ed03e681c5f3c12bc33e4db33253e1"
|
||||
dependencies = [
|
||||
"pmutil",
|
||||
"proc-macro2",
|
||||
@@ -3691,7 +3671,7 @@ version = "0.29.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5005cd73617e18592faa31298225b26f1c407b84a681d67efb735c3d3458e101"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
"ahash",
|
||||
"ast_node",
|
||||
"better_scoped_tls",
|
||||
"cfg-if",
|
||||
@@ -4072,9 +4052,9 @@ checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.19.7"
|
||||
version = "0.19.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc18466501acd8ac6a3f615dd29a3438f8ca6bb3b19537138b3106e575621274"
|
||||
checksum = "7082a95d48029677a28f181e5f6422d0c8339ad8396a39d3f33d62a90c1f6c30"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
@@ -4127,6 +4107,25 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-range-header",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.4.0"
|
||||
@@ -4275,7 +4274,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
[[package]]
|
||||
name = "typify"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#bf60070e09d6a3753d5b173d9c605a8bf2ada748"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d1cae0f7d5115782c9557a46396dae786ad5f592"
|
||||
dependencies = [
|
||||
"typify-impl",
|
||||
"typify-macro",
|
||||
@@ -4284,7 +4283,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-impl"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#bf60070e09d6a3753d5b173d9c605a8bf2ada748"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d1cae0f7d5115782c9557a46396dae786ad5f592"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"log",
|
||||
@@ -4302,7 +4301,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "typify-macro"
|
||||
version = "0.0.11-dev"
|
||||
source = "git+https://github.com/oxidecomputer/typify#bf60070e09d6a3753d5b173d9c605a8bf2ada748"
|
||||
source = "git+https://github.com/oxidecomputer/typify#d1cae0f7d5115782c9557a46396dae786ad5f592"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4702,9 +4701,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.4.0"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68"
|
||||
checksum = "45dbc71f0cdca27dc261a9bd37ddec174e4a0af2b900b890f378460f745426e3"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
@@ -4743,7 +4742,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4770,7 +4769,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4809,7 +4808,7 @@ dependencies = [
|
||||
"tokio-util",
|
||||
"tower",
|
||||
"tower-cookies",
|
||||
"tower-http",
|
||||
"tower-http 0.4.0",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
@@ -4825,7 +4824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4840,7 +4839,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4853,7 +4852,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4878,7 +4877,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4886,7 +4885,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4901,7 +4900,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4913,7 +4912,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4928,7 +4927,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4942,7 +4941,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -4965,7 +4964,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
@@ -5024,9 +5023,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
@@ -5039,51 +5038,51 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
version = "0.42.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.3.6"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d020b441f92996c80d94ae9166e8501e59c7bb56121189dc9eab3bd8216966"
|
||||
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.78.0"
|
||||
version = "1.75.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -2501,57 +2501,6 @@
|
||||
},
|
||||
"query": "SELECT set_config('session.user', $1, true)"
|
||||
},
|
||||
"6d50a8dc9cfc040b6f37b58053daa0709671e008a10ab4189114ceff66efe603": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "worker",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "worker_instance",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "last_ping",
|
||||
"ordinal": 2,
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"name": "started_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "ip",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "jobs_executed",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2"
|
||||
},
|
||||
"6dfd1ac7168f098a9e7c92d530a470c0a156041d5081053d43d9efb00d81c464": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@@ -2573,6 +2522,57 @@
|
||||
},
|
||||
"query": "SELECT policy from app WHERE path = $1 AND workspace_id = $2"
|
||||
},
|
||||
"6fc2cfae9df83eb24ea33e4c9567740100f4dd2285afc3ef474fc70041b0567b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "worker",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "worker_instance",
|
||||
"ordinal": 1,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "ping_at",
|
||||
"ordinal": 2,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "started_at",
|
||||
"ordinal": 3,
|
||||
"type_info": "Timestamptz"
|
||||
},
|
||||
{
|
||||
"name": "ip",
|
||||
"ordinal": 4,
|
||||
"type_info": "Varchar"
|
||||
},
|
||||
{
|
||||
"name": "jobs_executed",
|
||||
"ordinal": 5,
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Int8"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT * FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2"
|
||||
},
|
||||
"701f215eb14ba67a79afea15d7effc0dd394ba6c4a72c95d4560c5a377015d4e": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
||||
@@ -112,7 +112,6 @@ Windmill Community Edition {GIT_VERSION}
|
||||
"QUEUE_LIMIT_WAIT_RESULT",
|
||||
"DENO_AUTH_TOKENS",
|
||||
"DENO_FLAGS",
|
||||
"NPM_CONFIG_REGISTRY",
|
||||
"PIP_LOCAL_DEPENDENCIES",
|
||||
"ADDITIONAL_PYTHON_PATHS",
|
||||
"INCLUDE_HEADERS",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.78.0
|
||||
version: 1.75.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
@@ -204,6 +204,7 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/users/is_owner/{path}:
|
||||
get:
|
||||
summary: is owner of path
|
||||
@@ -1548,7 +1549,7 @@ paths:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
value: {}
|
||||
@@ -2102,6 +2103,7 @@ paths:
|
||||
items:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/scripts/create:
|
||||
post:
|
||||
summary: create script
|
||||
@@ -2833,6 +2835,7 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/AppWithLastVersion"
|
||||
|
||||
|
||||
/w/{workspace}/apps/secret_of/{path}:
|
||||
get:
|
||||
summary: get public secret of app
|
||||
@@ -3441,7 +3444,7 @@ paths:
|
||||
|
||||
/w/{workspace}/jobs/resume_urls/{id}/{resume_id}:
|
||||
get:
|
||||
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
summary: get resume urls given a job_id, resume_id and a nonce to resume a flow
|
||||
operationId: getResumeUrls
|
||||
tags:
|
||||
- job
|
||||
@@ -3467,7 +3470,7 @@ paths:
|
||||
properties:
|
||||
approvalPage:
|
||||
type: string
|
||||
resume:
|
||||
resume:
|
||||
type: string
|
||||
cancel:
|
||||
type: string
|
||||
@@ -4112,7 +4115,7 @@ paths:
|
||||
type: string
|
||||
owners:
|
||||
type: array
|
||||
items:
|
||||
items:
|
||||
type: string
|
||||
extra_perms:
|
||||
additionalProperties:
|
||||
@@ -4146,7 +4149,7 @@ paths:
|
||||
properties:
|
||||
owners:
|
||||
type: array
|
||||
items:
|
||||
items:
|
||||
type: string
|
||||
extra_perms:
|
||||
additionalProperties:
|
||||
@@ -4317,8 +4320,7 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
[script, group_, resource, schedule, variable, flow, folder, app]
|
||||
enum: [script, group_, resource, schedule, variable, flow, folder, app]
|
||||
responses:
|
||||
"200":
|
||||
description: acls
|
||||
@@ -4343,8 +4345,7 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
[script, group_, resource, schedule, variable, flow, folder, app]
|
||||
enum: [script, group_, resource, schedule, variable, flow, folder, app]
|
||||
requestBody:
|
||||
description: acl to add
|
||||
required: true
|
||||
@@ -4380,8 +4381,7 @@ paths:
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
enum:
|
||||
[script, group_, resource, schedule, variable, flow, folder, app]
|
||||
enum: [script, group_, resource, schedule, variable, flow, folder, app]
|
||||
requestBody:
|
||||
description: acl to add
|
||||
required: true
|
||||
@@ -4401,18 +4401,19 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
|
||||
/w/{workspace}/capture_u/{path}:
|
||||
post:
|
||||
summary: update flow preview capture
|
||||
operationId: updateCapture
|
||||
tags:
|
||||
- capture
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"204":
|
||||
description: flow preview captured
|
||||
post:
|
||||
summary: update flow preview capture
|
||||
operationId: updateCapture
|
||||
tags:
|
||||
- capture
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/Path"
|
||||
responses:
|
||||
"204":
|
||||
description: flow preview captured
|
||||
|
||||
/w/{workspace}/capture/{path}:
|
||||
put:
|
||||
@@ -5568,8 +5569,9 @@ components:
|
||||
type: string
|
||||
worker_instance:
|
||||
type: string
|
||||
last_ping:
|
||||
type: number
|
||||
ping_at:
|
||||
type: string
|
||||
format: date-time
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
@@ -300,6 +300,9 @@ pub fn add_include_headers(
|
||||
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<_>>())
|
||||
@@ -318,7 +321,6 @@ pub fn add_include_headers(
|
||||
});
|
||||
args
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ListQueueQuery {
|
||||
pub script_path_start: Option<String>,
|
||||
@@ -1720,6 +1722,7 @@ fn list_completed_jobs_query(
|
||||
sqlb.and_where("result @> ?".bind(&result.replace("'", "''")));
|
||||
}
|
||||
|
||||
tracing::info!("{:?}", sqlb.sql());
|
||||
sqlb
|
||||
}
|
||||
#[derive(Deserialize, Clone)]
|
||||
|
||||
@@ -384,9 +384,8 @@ async fn update_variable(
|
||||
path,
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.flatten();
|
||||
.fetch_one(&mut tx)
|
||||
.await?;
|
||||
|
||||
if let Some(old_v) = v {
|
||||
v = Some(replace_path(
|
||||
|
||||
@@ -28,7 +28,7 @@ pub fn global_service() -> Router {
|
||||
struct WorkerPing {
|
||||
worker: String,
|
||||
worker_instance: String,
|
||||
last_ping: Option<i32>,
|
||||
ping_at: chrono::DateTime<chrono::Utc>,
|
||||
started_at: chrono::DateTime<chrono::Utc>,
|
||||
ip: String,
|
||||
jobs_executed: i32,
|
||||
@@ -45,7 +45,7 @@ async fn list_worker_pings(
|
||||
|
||||
let rows = sqlx::query_as!(
|
||||
WorkerPing,
|
||||
"SELECT worker, worker_instance, EXTRACT(EPOCH FROM (now() - ping_at))::integer as last_ping, started_at, ip, jobs_executed FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2",
|
||||
"SELECT * FROM worker_ping ORDER BY ping_at desc LIMIT $1 OFFSET $2",
|
||||
per_page as i64,
|
||||
offset as i64
|
||||
)
|
||||
|
||||
@@ -236,7 +236,8 @@ pub async fn create_token_for_owner<'c>(
|
||||
) -> error::Result<(Transaction<'c, Postgres>, String)> {
|
||||
// TODO: Bad implementation. We should not have access to this DB here.
|
||||
let token: String = rd_string(30);
|
||||
let is_super_admin = sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", email)
|
||||
let is_super_admin = owner.contains('@')
|
||||
&& sqlx::query_scalar!("SELECT super_admin FROM password WHERE email = $1", owner)
|
||||
.fetch_optional(&mut tx)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
@@ -263,7 +264,7 @@ const ROOT_CACHE_DIR: &str = "/tmp/windmill/cache/";
|
||||
const PIP_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "pip");
|
||||
const DENO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "deno");
|
||||
const GO_CACHE_DIR: &str = concatcp!(ROOT_CACHE_DIR, "go");
|
||||
const NUM_SECS_PING: u64 = 5;
|
||||
const NUM_SECS_ENV_CHECK: u64 = 15;
|
||||
const NUM_SECS_SYNC: u64 = 60 * 10;
|
||||
|
||||
const INCLUDE_DEPS_PY_SH_CONTENT: &str = include_str!("../nsjail/download_deps.py.sh");
|
||||
@@ -329,7 +330,7 @@ lazy_static::lazy_static! {
|
||||
.ok()
|
||||
.map(|x| format!(";{x}"))
|
||||
.unwrap_or_else(|| String::new());
|
||||
static ref NPM_CONFIG_REGISTRY: Option<String> = std::env::var("NPM_CONFIG_REGISTRY").ok();
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -552,7 +553,7 @@ pub async fn run_worker(
|
||||
|
||||
|
||||
let do_break = async {
|
||||
if last_ping.elapsed().as_secs() > NUM_SECS_PING {
|
||||
if last_ping.elapsed().as_secs() > NUM_SECS_ENV_CHECK {
|
||||
sqlx::query!(
|
||||
"UPDATE worker_ping SET ping_at = now(), jobs_executed = $1 WHERE worker = $2",
|
||||
jobs_executed,
|
||||
@@ -1480,26 +1481,6 @@ fn capitalize(s: &str) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_common_deno_proc_envs(token: &str, base_internal_url: &str) -> HashMap<String, String> {
|
||||
let hostname_base = BASE_URL.split("://").last().unwrap_or("localhost");
|
||||
let hostname_internal = base_internal_url.split("://").last().unwrap_or("localhost");
|
||||
let deno_auth_tokens_base = DENO_AUTH_TOKENS.as_str();
|
||||
let deno_auth_tokens =
|
||||
format!("{token}@{hostname_base};{token}@{hostname_internal}{deno_auth_tokens_base}",);
|
||||
|
||||
let mut deno_envs: HashMap<String, String> = HashMap::from([
|
||||
(String::from("PATH"), PATH_ENV.clone()),
|
||||
(String::from("DENO_AUTH_TOKENS"), deno_auth_tokens),
|
||||
(String::from("BASE_INTERNAL_URL"), base_internal_url.to_string()),
|
||||
(String::from("NO_COLOR"), String::from("true")),
|
||||
]);
|
||||
|
||||
if let Some(ref s) = *NPM_CONFIG_REGISTRY {
|
||||
deno_envs.insert(String::from("NPM_CONFIG_REGISTRY"), s.clone());
|
||||
}
|
||||
return deno_envs;
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "trace", skip_all)]
|
||||
async fn handle_deno_job(
|
||||
logs: &mut String,
|
||||
@@ -1565,9 +1546,11 @@ run().catch(async (e) => {{
|
||||
let mut reserved_variables = get_reserved_variables(job, &token, db).await?;
|
||||
reserved_variables.insert("RUST_LOG".to_string(), "info".to_string());
|
||||
|
||||
let common_deno_proc_envs = get_common_deno_proc_envs(&token, base_internal_url);
|
||||
//do not cache local dependencies
|
||||
let reload = format!("--reload={base_internal_url}");
|
||||
let hostname_base = BASE_URL.split("://").last().unwrap_or("localhost");
|
||||
let hostname_internal = base_internal_url.split("://").last().unwrap_or("localhost");
|
||||
let deno_auth_tokens_base = DENO_AUTH_TOKENS.as_str();
|
||||
let deno_auth_tokens =
|
||||
format!("{token}@{hostname_base};{token}@{hostname_internal}{deno_auth_tokens_base}",);
|
||||
let child = async {
|
||||
Ok(if !*DISABLE_NSJAIL {
|
||||
let _ = write_file(
|
||||
@@ -1591,7 +1574,6 @@ run().catch(async (e) => {{
|
||||
}
|
||||
args.push("--import-map");
|
||||
args.push("/tmp/import_map.json");
|
||||
args.push(&reload);
|
||||
args.push("--unstable");
|
||||
if let Some(deno_flags) = DENO_FLAGS.as_ref() {
|
||||
for flag in deno_flags {
|
||||
@@ -1606,7 +1588,9 @@ run().catch(async (e) => {{
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.envs(reserved_variables)
|
||||
.envs(common_deno_proc_envs)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("DENO_AUTH_TOKENS", deno_auth_tokens)
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.args(args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
@@ -1618,7 +1602,6 @@ run().catch(async (e) => {{
|
||||
args.push("run");
|
||||
args.push("--import-map");
|
||||
args.push(&import_map_path);
|
||||
args.push(&reload);
|
||||
args.push("--unstable");
|
||||
if let Some(deno_flags) = DENO_FLAGS.as_ref() {
|
||||
for flag in deno_flags {
|
||||
@@ -1632,8 +1615,11 @@ run().catch(async (e) => {{
|
||||
.current_dir(job_dir)
|
||||
.env_clear()
|
||||
.envs(reserved_variables)
|
||||
.envs(common_deno_proc_envs)
|
||||
.env("PATH", PATH_ENV.as_str())
|
||||
.env("DENO_DIR", DENO_CACHE_DIR)
|
||||
.env("DENO_AUTH_TOKENS", deno_auth_tokens)
|
||||
.env("BASE_INTERNAL_URL", base_internal_url)
|
||||
.env("NO_COLOR", "true")
|
||||
.args(args)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
|
||||
@@ -18,7 +18,7 @@ export type Context = {
|
||||
};
|
||||
|
||||
async function tryResolveWorkspace(
|
||||
opts: GlobalOptions
|
||||
opts: GlobalOptions,
|
||||
): Promise<
|
||||
{ isError: false; value: Workspace } | { isError: true; error: string }
|
||||
> {
|
||||
@@ -49,20 +49,17 @@ async function tryResolveWorkspace(
|
||||
}
|
||||
|
||||
export async function resolveWorkspace(
|
||||
opts: GlobalOptions
|
||||
opts: GlobalOptions,
|
||||
): Promise<Workspace> {
|
||||
const res = await tryResolveWorkspace(opts);
|
||||
if (res.isError) {
|
||||
console.log(colors.red.bold(res.error));
|
||||
return Deno.exit(-1);
|
||||
} else {
|
||||
return res.value;
|
||||
}
|
||||
}
|
||||
|
||||
export async function requireLogin(
|
||||
opts: GlobalOptions
|
||||
): Promise<GlobalUserInfo> {
|
||||
export async function requireLogin(opts: GlobalOptions): Promise<GlobalUserInfo> {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
let token = await tryGetLoginInfo(opts);
|
||||
|
||||
@@ -76,7 +73,7 @@ export async function requireLogin(
|
||||
return await UserService.globalWhoami();
|
||||
} catch {
|
||||
console.log(
|
||||
"! Could not reach API given existing credentials. Attempting to reauth..."
|
||||
"! Could not reach API given existing credentials. Attempting to reauth...",
|
||||
);
|
||||
const newToken = await loginInteractive(workspace.remote);
|
||||
if (!newToken) {
|
||||
@@ -88,14 +85,14 @@ export async function requireLogin(
|
||||
|
||||
setClient(
|
||||
token,
|
||||
workspace.remote.substring(0, workspace.remote.length - 1)
|
||||
workspace.remote.substring(0, workspace.remote.length - 1),
|
||||
);
|
||||
return await UserService.globalWhoami();
|
||||
}
|
||||
}
|
||||
|
||||
export async function tryResolveVersion(
|
||||
opts: GlobalOptions
|
||||
opts: GlobalOptions,
|
||||
): Promise<number | undefined> {
|
||||
if ((opts as any).__cache_version) {
|
||||
return (opts as any).__cache_version;
|
||||
@@ -105,12 +102,12 @@ export async function tryResolveVersion(
|
||||
if (workspaceRes.isError) return undefined;
|
||||
|
||||
const response = await fetch(
|
||||
new URL(new URL(workspaceRes.value.remote).origin + "/api/version")
|
||||
new URL(new URL(workspaceRes.value.remote).origin + "/api/version"),
|
||||
);
|
||||
const version = await response.text();
|
||||
try {
|
||||
return Number.parseInt(
|
||||
version.split("-", 1)[0].replaceAll(".", "").replace("v", "")
|
||||
version.split("-", 1)[0].replaceAll(".", "").replace("v", ""),
|
||||
);
|
||||
} catch {
|
||||
return undefined;
|
||||
@@ -119,7 +116,7 @@ export async function tryResolveVersion(
|
||||
|
||||
export async function validatePath(
|
||||
opts: GlobalOptions,
|
||||
path: string
|
||||
path: string,
|
||||
): Promise<boolean> {
|
||||
const backendVersion = await tryResolveVersion(opts);
|
||||
if (path.startsWith("f")) {
|
||||
@@ -127,16 +124,19 @@ export async function validatePath(
|
||||
return true;
|
||||
}
|
||||
console.log(
|
||||
`Attempting to use folders, but the current remote does not have support. Remote version is ${backendVersion} but folders are supported from 1560.`
|
||||
`Attempting to use folders, but the current remote does not have support. Remote version is ${backendVersion} but folders are supported from 1560.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(path.startsWith("g") || path.startsWith("u"))) {
|
||||
if (
|
||||
!(path.startsWith("g") ||
|
||||
path.startsWith("u"))
|
||||
) {
|
||||
console.log(
|
||||
colors.red(
|
||||
"Given remote path looks invalid. Remote paths are typically of the form <u|g|f>/<username|group|folder>/..."
|
||||
)
|
||||
"Given remote path looks invalid. Remote paths are typically of the form <u|g|f>/<username|group|folder>/...",
|
||||
),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -20,17 +20,11 @@ import {
|
||||
export class FolderFile implements Resource, PushDiffs {
|
||||
@property(array(() => String))
|
||||
owners: Array<string> | undefined;
|
||||
@property(
|
||||
map(
|
||||
() => String,
|
||||
() => Boolean,
|
||||
{ shape: MapShape.Object }
|
||||
)
|
||||
)
|
||||
@property(map(() => String, () => Boolean, { shape: MapShape.Object }))
|
||||
extra_perms: Map<string, boolean> | undefined;
|
||||
@property(() => String)
|
||||
display_name: string | undefined;
|
||||
|
||||
display_name: string| undefined;
|
||||
|
||||
async push(workspace: string, remotePath: string): Promise<void> {
|
||||
if (remotePath.startsWith("/")) {
|
||||
remotePath = remotePath.substring(1);
|
||||
@@ -42,14 +36,14 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
await this.pushDiffs(
|
||||
workspace,
|
||||
remotePath,
|
||||
microdiff({}, this, { cyclesFix: false })
|
||||
microdiff({}, this, { cyclesFix: false }),
|
||||
);
|
||||
}
|
||||
|
||||
async pushDiffs(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
diffs: Difference[]
|
||||
diffs: Difference[],
|
||||
): Promise<void> {
|
||||
if (remotePath.startsWith("/")) {
|
||||
remotePath = remotePath.substring(1);
|
||||
@@ -61,18 +55,15 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
// TODO: Support this in backend
|
||||
let exists: boolean;
|
||||
try {
|
||||
exists = !!(await FolderService.getFolder({
|
||||
workspace,
|
||||
name: remotePath,
|
||||
}));
|
||||
exists = !!await FolderService.getFolder({ workspace, name: remotePath });
|
||||
} catch {
|
||||
exists = false;
|
||||
}
|
||||
if (exists) {
|
||||
console.log(
|
||||
colors.bold.yellow(
|
||||
`Applying ${diffs.length} diffs to existing folder... ${remotePath}`
|
||||
)
|
||||
`Applying ${diffs.length} diffs to existing folder... ${remotePath}`,
|
||||
),
|
||||
);
|
||||
|
||||
const changeset: {
|
||||
@@ -83,12 +74,12 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
for (const diff of diffs) {
|
||||
if (
|
||||
diff.type !== "REMOVE" &&
|
||||
(diff.path.length !== 1 ||
|
||||
!["owners", "extra_perms", "display_name"].includes(
|
||||
diff.path[0] as string
|
||||
))
|
||||
(
|
||||
diff.path.length !== 1 ||
|
||||
!["owners", "extra_perms", "display_name"].includes(diff.path[0] as string)
|
||||
)
|
||||
) {
|
||||
console.log(diff.path);
|
||||
console.log(diff.path)
|
||||
throw new Error("Invalid folder diff with path " + diff.path);
|
||||
}
|
||||
if (diff.type === "CREATE" || diff.type === "CHANGE") {
|
||||
@@ -98,27 +89,17 @@ export class FolderFile implements Resource, PushDiffs {
|
||||
}
|
||||
}
|
||||
|
||||
const hasChanges = Object.values(changeset).some(
|
||||
(v) => v !== null && typeof v !== "undefined"
|
||||
const hasChanges = Object.values(changeset).some((v) =>
|
||||
v !== null && typeof v !== "undefined"
|
||||
);
|
||||
if (!hasChanges) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await FolderService.updateFolder({
|
||||
workspace: workspace,
|
||||
name: remotePath,
|
||||
requestBody: {
|
||||
...changeset,
|
||||
extra_perms: changeset.extra_perms
|
||||
? Object.fromEntries(this.extra_perms?.entries() ?? [])
|
||||
: undefined,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(colors.red.bold(e.body));
|
||||
throw e;
|
||||
}
|
||||
await FolderService.updateFolder({
|
||||
workspace: workspace,
|
||||
name: remotePath,
|
||||
requestBody: {...changeset, extra_perms: changeset.extra_perms ? Object.fromEntries(this.extra_perms?.entries() ?? []) : undefined}
|
||||
});
|
||||
} else {
|
||||
console.log(colors.bold.yellow("Creating new folder: " + remotePath));
|
||||
await FolderService.createFolder({
|
||||
@@ -137,7 +118,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
if (!(await validatePath(opts, remotePath))) {
|
||||
if (!await validatePath(opts, remotePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -155,11 +136,11 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
export async function pushFolder(
|
||||
workspace: string,
|
||||
filePath: string,
|
||||
remotePath: string
|
||||
remotePath: string,
|
||||
) {
|
||||
const data = decoverto
|
||||
.type(FolderFile)
|
||||
.rawToInstance(await Deno.readTextFile(filePath));
|
||||
const data = decoverto.type(FolderFile).rawToInstance(
|
||||
await Deno.readTextFile(filePath),
|
||||
);
|
||||
data.push(workspace, remotePath);
|
||||
}
|
||||
|
||||
@@ -167,7 +148,7 @@ const command = new Command()
|
||||
.description("resource related commands")
|
||||
.command(
|
||||
"push",
|
||||
"push a local folder spec. This overrides any remote versions."
|
||||
"push a local folder spec. This overrides any remote versions.",
|
||||
)
|
||||
.arguments("<file_path:string> <remote_path:string>")
|
||||
.action(push as any);
|
||||
|
||||
20
cli/main.ts
20
cli/main.ts
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
Command,
|
||||
CompletionsCommand,
|
||||
DenoLandProvider,
|
||||
UpgradeCommand,
|
||||
} from "./deps.ts";
|
||||
import { Command, CompletionsCommand, DenoLandProvider, UpgradeCommand } from "./deps.ts";
|
||||
import flow from "./flow.ts";
|
||||
import script from "./script.ts";
|
||||
import workspace from "./workspace.ts";
|
||||
@@ -18,18 +13,19 @@ import sync from "./sync.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
export const VERSION = "v1.77.0";
|
||||
const VERSION = "v1.75.0";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
.description("A simple CLI tool for windmill.")
|
||||
.action(() => command.showHelp())
|
||||
.globalOption(
|
||||
"--workspace <workspace:string>",
|
||||
"Specify the target workspace. This overrides the default workspace."
|
||||
"Specify the target workspace. This overrides the default workspace.",
|
||||
)
|
||||
.globalOption(
|
||||
"--token <token:string>",
|
||||
"Specify an API token. This will override any stored token."
|
||||
"Specify an API token. This will override any stored token.",
|
||||
)
|
||||
.version(VERSION)
|
||||
.command("flow", flow)
|
||||
@@ -63,12 +59,14 @@ let command: any = new Command()
|
||||
"--unstable",
|
||||
],
|
||||
provider: new DenoLandProvider({ name: "wmill" }),
|
||||
})
|
||||
}),
|
||||
)
|
||||
.command("completions", new CompletionsCommand());
|
||||
|
||||
if (Number.parseInt(VERSION.replace("v", "").replace(".", "")) > 1700) {
|
||||
command = command.command("push", push).command("pull", pull);
|
||||
command = command
|
||||
.command("push", push)
|
||||
.command("pull", pull);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
471
cli/sync.ts
471
cli/sync.ts
@@ -13,7 +13,7 @@ import {
|
||||
ResourceService,
|
||||
VariableService,
|
||||
AppService,
|
||||
FlowService,
|
||||
FlowService
|
||||
} from "./deps.ts";
|
||||
import {
|
||||
Difference,
|
||||
@@ -25,7 +25,10 @@ import {
|
||||
import { downloadZip } from "./pull.ts";
|
||||
import { FolderFile } from "./folder.ts";
|
||||
import { ResourceTypeFile } from "./resource-type.ts";
|
||||
import { handleScriptMetadata, ScriptFile } from "./script.ts";
|
||||
import {
|
||||
handleScriptMetadata,
|
||||
ScriptFile,
|
||||
} from "./script.ts";
|
||||
import { ResourceFile } from "./resource.ts";
|
||||
import { FlowFile } from "./flow.ts";
|
||||
import { VariableFile } from "./variable.ts";
|
||||
@@ -41,7 +44,10 @@ type DynFSElement = {
|
||||
};
|
||||
|
||||
async function FSFSElement(p: string): Promise<DynFSElement> {
|
||||
function _internal_element(localP: string, isDir: boolean): DynFSElement {
|
||||
function _internal_element(
|
||||
localP: string,
|
||||
isDir: boolean,
|
||||
): DynFSElement {
|
||||
return {
|
||||
isDirectory: isDir,
|
||||
path: localP.substring(p.length + 1),
|
||||
@@ -107,14 +113,16 @@ function ZipFSElement(zip: JSZip): DynFSElement {
|
||||
|
||||
async function* readDirRecursiveWithIgnore(
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
root: DynFSElement
|
||||
): AsyncGenerator<{
|
||||
path: string;
|
||||
ignored: boolean;
|
||||
isDirectory: boolean;
|
||||
getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}> {
|
||||
root: DynFSElement,
|
||||
): AsyncGenerator<
|
||||
{
|
||||
path: string;
|
||||
ignored: boolean;
|
||||
isDirectory: boolean;
|
||||
getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}
|
||||
> {
|
||||
const stack: {
|
||||
path: string;
|
||||
isDirectory: boolean;
|
||||
@@ -122,20 +130,18 @@ async function* readDirRecursiveWithIgnore(
|
||||
c(): AsyncIterable<DynFSElement>;
|
||||
getContentBytes(): Promise<Uint8Array>;
|
||||
getContentText(): Promise<string>;
|
||||
}[] = [
|
||||
{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
getContentBytes(): Promise<Uint8Array> {
|
||||
throw undefined;
|
||||
},
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
},
|
||||
}[] = [{
|
||||
path: root.path,
|
||||
ignored: ignore(root.path, root.isDirectory),
|
||||
isDirectory: root.isDirectory,
|
||||
c: root.getChildren,
|
||||
getContentBytes(): Promise<Uint8Array> {
|
||||
throw undefined;
|
||||
},
|
||||
];
|
||||
getContentText(): Promise<string> {
|
||||
throw undefined;
|
||||
},
|
||||
}];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const e = stack.pop()!;
|
||||
@@ -155,17 +161,17 @@ async function* readDirRecursiveWithIgnore(
|
||||
}
|
||||
|
||||
type Added = { name: "added"; path: string; content: string };
|
||||
type Deleted = { name: "deleted"; path: string };
|
||||
type Edit = { name: "edited"; path: string; before: string; after: string };
|
||||
type Deleted = { name: "deleted"; path: string; };
|
||||
type Edit = { name: "edited"; path: string; before: string; after: string; };
|
||||
|
||||
type Change = Added | Deleted | Edit;
|
||||
|
||||
async function elementsToMap(
|
||||
els: DynFSElement,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean
|
||||
): Promise<{ [key: string]: string }> {
|
||||
async function elementsToMap(els: DynFSElement, ignore: (path: string, isDirectory: boolean) => boolean): Promise<{ [key: string]: string }> {
|
||||
const map: { [key: string]: string } = {};
|
||||
for await (const entry of readDirRecursiveWithIgnore(ignore, els)) {
|
||||
for await (const entry of readDirRecursiveWithIgnore(
|
||||
ignore,
|
||||
els,
|
||||
)) {
|
||||
if (entry.isDirectory || entry.ignored) continue;
|
||||
const content = await entry.getContentText();
|
||||
map[entry.path] = content;
|
||||
@@ -173,15 +179,12 @@ async function elementsToMap(
|
||||
return map;
|
||||
}
|
||||
async function compareDynFSElement(
|
||||
els1: DynFSElement,
|
||||
els2: DynFSElement | undefined,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean
|
||||
els1: DynFSElement, els2: DynFSElement | undefined,
|
||||
ignore: (path: string, isDirectory: boolean) => boolean,
|
||||
): Promise<Change[]> {
|
||||
|
||||
const [m1, m2] = els2
|
||||
? await Promise.all([
|
||||
elementsToMap(els1, ignore),
|
||||
elementsToMap(els2, ignore),
|
||||
])
|
||||
? await Promise.all([elementsToMap(els1, ignore), elementsToMap(els2, ignore)])
|
||||
: [await elementsToMap(els1, ignore), {}];
|
||||
|
||||
const changes: Change[] = [];
|
||||
@@ -189,10 +192,7 @@ async function compareDynFSElement(
|
||||
for (const [k, v] of Object.entries(m1)) {
|
||||
if (m2[k] === undefined) {
|
||||
changes.push({ name: "added", path: k, content: v });
|
||||
} else if (
|
||||
m2[k] != v &&
|
||||
(!k.endsWith(".json") || !equal(JSON.parse(v), JSON.parse(m2[k])))
|
||||
) {
|
||||
} else if (m2[k] != v && (!k.endsWith(".json") || !equal(JSON.parse(v), JSON.parse(m2[k])))) {
|
||||
// await Deno.writeTextFile("/tmp/k", m2[k])
|
||||
// await Deno.writeTextFile("/tmp/v", v)
|
||||
// console.log(k)
|
||||
@@ -208,54 +208,54 @@ async function compareDynFSElement(
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
return changes
|
||||
}
|
||||
|
||||
const isNotWmillFile = (p: string, isDirectory: boolean) => {
|
||||
if (p.endsWith("/")) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (isDirectory) {
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/");
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
|
||||
}
|
||||
|
||||
try {
|
||||
const typ = getTypeStrFromPath(p);
|
||||
if (typ == "resource-type") {
|
||||
return p.includes("/");
|
||||
const typ = getTypeStrFromPath(p)
|
||||
if (typ == 'resource-type') {
|
||||
return p.includes('/')
|
||||
} else {
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/");
|
||||
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
|
||||
}
|
||||
} catch {
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const isWhitelisted = (p: string) => {
|
||||
return p == "./" || p == "" || p == "u" || p == "f" || p == "g";
|
||||
};
|
||||
return p == "./" || p == "" || p == "u" || p == "f" || p == "g"
|
||||
}
|
||||
async function ignoreF() {
|
||||
try {
|
||||
const ignore: {
|
||||
accepts(file: string): boolean;
|
||||
denies(file: string): boolean;
|
||||
} = gitignore_parser.compile(await Deno.readTextFile(".wmillignore"));
|
||||
} = gitignore_parser.compile(
|
||||
await Deno.readTextFile(".wmillignore"),
|
||||
);
|
||||
|
||||
return (p: string, isDirectory: boolean) => {
|
||||
return (
|
||||
!isWhitelisted(p) &&
|
||||
(isNotWmillFile(p, isDirectory) || ignore.denies(p))
|
||||
);
|
||||
};
|
||||
return !isWhitelisted(p) && (isNotWmillFile(p, isDirectory) || ignore.denies(p));
|
||||
}
|
||||
} catch (e) {
|
||||
return (p: string, isDirectory: boolean) =>
|
||||
!isWhitelisted(p) && isNotWmillFile(p, isDirectory);
|
||||
return (p: string, isDirectory: boolean) => !isWhitelisted(p) && isNotWmillFile(p, isDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
async function pull(
|
||||
opts: GlobalOptions & { raw: boolean; yes: boolean; failConflicts: boolean }
|
||||
opts: GlobalOptions & { raw: boolean; yes: boolean, failConflicts: boolean },
|
||||
) {
|
||||
|
||||
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.join(Deno.cwd(), ".wmill"));
|
||||
}
|
||||
@@ -263,96 +263,74 @@ async function pull(
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
console.log(
|
||||
colors.gray(
|
||||
"Computing the files to update locally to match remote (taking .wmillignore into account)"
|
||||
)
|
||||
);
|
||||
const remote = ZipFSElement((await downloadZip(workspace))!);
|
||||
const local = opts.raw
|
||||
? undefined
|
||||
: await FSFSElement(path.join(Deno.cwd(), opts.raw ? "" : ".wmill"));
|
||||
const changes = await compareDynFSElement(remote, local, await ignoreF());
|
||||
|
||||
console.log(colors.gray("Computing the files to update locally to match remote (taking .wmillignore into account)"));
|
||||
const remote = ZipFSElement((await downloadZip(workspace))!)
|
||||
const local = opts.raw ? undefined : await FSFSElement(path.join(Deno.cwd(), opts.raw ? "" : ".wmill"))
|
||||
const changes = await compareDynFSElement(remote, local, await ignoreF())
|
||||
|
||||
|
||||
console.log(`remote -> local: ${changes.length} changes to apply`);
|
||||
if (changes.length > 0) {
|
||||
prettyChanges(changes);
|
||||
|
||||
prettyChanges(changes)
|
||||
if (
|
||||
!opts.yes &&
|
||||
!opts.raw &&
|
||||
!(await Confirm.prompt({
|
||||
message: `Do you want to apply these ${changes.length} changes?`,
|
||||
default: true,
|
||||
}))
|
||||
!opts.yes && !opts.raw && !(await Confirm.prompt({ message: `Do you want to apply these ${changes.length} changes?`, default: true }))
|
||||
) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
const conflicts = [];
|
||||
const conflicts = []
|
||||
console.log(colors.gray(`Applying changes to files ...`));
|
||||
for await (const change of changes) {
|
||||
const target = path.join(Deno.cwd(), change.path);
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path);
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
|
||||
if (change.name === "edited") {
|
||||
|
||||
try {
|
||||
const currentLocal = await Deno.readTextFile(target);
|
||||
const currentLocal = await Deno.readTextFile(target)
|
||||
if (currentLocal !== change.before && currentLocal !== change.after) {
|
||||
console.log(
|
||||
colors.red(
|
||||
`Conflict detected on ${change.path}\nBoth local and remote have been modified.`
|
||||
)
|
||||
);
|
||||
console.log(colors.red(`Conflict detected on ${change.path}\nBoth local and remote have been modified.`))
|
||||
if (opts.failConflicts) {
|
||||
conflicts.push({
|
||||
local: currentLocal,
|
||||
change,
|
||||
path: change.path,
|
||||
});
|
||||
conflicts.push({ local: currentLocal, change, path: change.path })
|
||||
continue;
|
||||
} else if (opts.yes) {
|
||||
console.log(
|
||||
colors.red(
|
||||
`Override local version with remote since --yes was passed and no --fail-conflicts.`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
showConflict(change.path, currentLocal, change.after);
|
||||
if (
|
||||
await Confirm.prompt(
|
||||
"Preserve local (push to change remote and avoid seeing this again)?"
|
||||
)
|
||||
) {
|
||||
console.log(colors.red(`Override local version with remote since --yes was passed and no --fail-conflicts.`))
|
||||
}
|
||||
else {
|
||||
showConflict(change.path, currentLocal, change.after)
|
||||
if (await Confirm.prompt("Preserve local (push to change remote and avoid seeing this again)?")) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
} catch { }
|
||||
if (change.path.endsWith(".json")) {
|
||||
const diffs = microdiff(
|
||||
JSON.parse(change.before),
|
||||
JSON.parse(change.after),
|
||||
{ cyclesFix: false }
|
||||
);
|
||||
const diffs =
|
||||
microdiff(
|
||||
JSON.parse(change.before),
|
||||
JSON.parse(change.after),
|
||||
{ cyclesFix: false },
|
||||
)
|
||||
|
||||
console.log(
|
||||
`Editing ${getTypeStrFromPath(change.path)} json ${change.path}`
|
||||
console.log(`Editing ${getTypeStrFromPath(change.path)} json ${change.path}`)
|
||||
await applyDiff(
|
||||
diffs,
|
||||
target,
|
||||
);
|
||||
await applyDiff(diffs, target);
|
||||
} else {
|
||||
console.log(`Editing script ${change.path}`);
|
||||
console.log(`Editing script ${change.path}`)
|
||||
await Deno.writeTextFile(target, change.after);
|
||||
}
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
await Deno.copyFile(target, stateTarget);
|
||||
}
|
||||
} else if (change.name === "added") {
|
||||
await ensureDir(path.dirname(target));
|
||||
await ensureDir(path.dirname(target))
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
console.log(
|
||||
`Adding ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
console.log(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
}
|
||||
await Deno.writeTextFile(target, change.content);
|
||||
if (!opts.raw) {
|
||||
@@ -360,10 +338,8 @@ async function pull(
|
||||
}
|
||||
} else if (change.name === "deleted") {
|
||||
try {
|
||||
console.log(
|
||||
`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await Deno.remove(target);
|
||||
console.log(`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
await Deno.remove(target)
|
||||
if (!opts.raw) {
|
||||
await Deno.remove(stateTarget);
|
||||
}
|
||||
@@ -376,29 +352,24 @@ async function pull(
|
||||
}
|
||||
if (opts.failConflicts) {
|
||||
if (conflicts.length > 0) {
|
||||
console.error(colors.red(`Conflicts were found`));
|
||||
console.log("Conflicts:");
|
||||
console.error(colors.red(`Conflicts were found`))
|
||||
console.log("Conflicts:")
|
||||
for (const conflict of conflicts) {
|
||||
showConflict(conflict.path, conflict.local, conflict.change.after);
|
||||
showConflict(conflict.path, conflict.local, conflict.change.after)
|
||||
}
|
||||
console.log(
|
||||
colors.red(`Please resolve theses conflicts manually by either:
|
||||
console.log(colors.red(`Please resolve theses conflicts manually by either:
|
||||
- reverting the content back to its remote (\`wmill pull\` and refuse to preserve local when prompted)
|
||||
- pushing the changes with \`wmill push --skip-pull\` to override wmill with all your local changes
|
||||
`)
|
||||
);
|
||||
Deno.exit(1);
|
||||
`))
|
||||
Deno.exit(1)
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
colors.green.underline(
|
||||
`Done! All ${changes.length} changes applied locally.`
|
||||
)
|
||||
);
|
||||
console.log(colors.green.underline(`Done! All ${changes.length} changes applied locally.`));
|
||||
}
|
||||
|
||||
|
||||
function showConflict(path: string, local: string, remote: string) {
|
||||
console.log(colors.yellow(`- ${path}`));
|
||||
console.log(colors.yellow(`- ${path}`))
|
||||
|
||||
let finalString = "";
|
||||
for (const character of diffCharacters(local, remote)) {
|
||||
@@ -414,8 +385,8 @@ async function pull(
|
||||
}
|
||||
}
|
||||
console.log(finalString);
|
||||
console.log("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m");
|
||||
console.log();
|
||||
console.log("\x1b[31mlocal\x1b[31m - \x1b[32mremote\x1b[32m")
|
||||
console.log()
|
||||
}
|
||||
async function applyDiff(diffs: Difference[], file: string) {
|
||||
ensureDir(path.dirname(file));
|
||||
@@ -444,20 +415,17 @@ async function pull(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function prettyChanges(changes: Change[]) {
|
||||
for (const change of changes) {
|
||||
if (change.name === "added") {
|
||||
console.log(
|
||||
colors.green(`+ ${getTypeStrFromPath(change.path)} ` + change.path)
|
||||
);
|
||||
console.log(colors.green(`+ ${getTypeStrFromPath(change.path)} ` + change.path));
|
||||
} else if (change.name === "deleted") {
|
||||
console.log(
|
||||
colors.red(`- ${getTypeStrFromPath(change.path)} ` + change.path)
|
||||
);
|
||||
console.log(colors.red(`- ${getTypeStrFromPath(change.path)} ` + change.path));
|
||||
} else if (change.name === "edited") {
|
||||
console.log(
|
||||
colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path)
|
||||
);
|
||||
console.log(colors.yellow(`~ ${getTypeStrFromPath(change.path)} ` + change.path));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -484,208 +452,137 @@ function prettyDiff(diffs: Difference[]) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function removeSuffix(str: string, suffix: string) {
|
||||
return str.slice(0, str.length - suffix.length);
|
||||
}
|
||||
|
||||
async function push(
|
||||
opts: GlobalOptions & {
|
||||
raw: boolean;
|
||||
yes: boolean;
|
||||
skipPull: boolean;
|
||||
failConflicts: boolean;
|
||||
}
|
||||
) {
|
||||
async function push(opts: GlobalOptions & { raw: boolean, yes: boolean, skipPull: boolean, failConflicts: boolean }) {
|
||||
|
||||
if (!opts.raw) {
|
||||
if (!opts.skipPull) {
|
||||
console.log(
|
||||
colors.gray("You need to be up-to-date before pushing, pulling first.")
|
||||
);
|
||||
await pull(opts);
|
||||
console.log(colors.green("Pull done, now pushing."));
|
||||
console.log();
|
||||
console.log(colors.gray("You need to be up-to-date before pushing, pulling first."))
|
||||
await pull(opts)
|
||||
console.log(colors.green("Pull done, now pushing."))
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
const workspace = await resolveWorkspace(opts);
|
||||
await requireLogin(opts);
|
||||
|
||||
console.log(
|
||||
colors.gray(
|
||||
"Computing the files to update on the remote to match local (taking .wmillignore into account)"
|
||||
)
|
||||
);
|
||||
const remote = opts.raw
|
||||
? undefined
|
||||
: ZipFSElement((await downloadZip(workspace))!);
|
||||
const local = await FSFSElement(path.join(Deno.cwd(), ""));
|
||||
const changes = await compareDynFSElement(local, remote, await ignoreF());
|
||||
|
||||
console.log(colors.gray("Computing the files to update on the remote to match local (taking .wmillignore into account)"));
|
||||
const remote = opts.raw ? undefined : ZipFSElement((await downloadZip(workspace))!)
|
||||
const local = await FSFSElement(path.join(Deno.cwd(), ""))
|
||||
const changes = await compareDynFSElement(local, remote, await ignoreF())
|
||||
|
||||
console.log(`remote <- local: ${changes.length} changes to apply`);
|
||||
if (changes.length > 0) {
|
||||
prettyChanges(changes);
|
||||
|
||||
prettyChanges(changes)
|
||||
if (
|
||||
!opts.yes &&
|
||||
!(await Confirm.prompt({
|
||||
message: `Do you want to apply these ${changes.length} changes?`,
|
||||
default: true,
|
||||
}))
|
||||
!opts.yes && !(await Confirm.prompt({ message: `Do you want to apply these ${changes.length} changes?`, default: true }))
|
||||
) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
console.log(colors.gray(`Applying changes to files ...`));
|
||||
const alreadySynced: string[] = [];
|
||||
const alreadySynced: string[] = []
|
||||
for await (const change of changes) {
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path);
|
||||
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
|
||||
if (change.name === "edited") {
|
||||
if (
|
||||
await handleScriptMetadata(
|
||||
change.path,
|
||||
workspace.workspaceId,
|
||||
alreadySynced
|
||||
)
|
||||
) {
|
||||
if (await handleScriptMetadata(change.path, workspace.workspaceId, alreadySynced)) {
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.after);
|
||||
}
|
||||
continue;
|
||||
} else if (
|
||||
await handleFile(
|
||||
change.path,
|
||||
change.after,
|
||||
workspace.workspaceId,
|
||||
alreadySynced
|
||||
)
|
||||
) {
|
||||
continue
|
||||
} else if (await handleFile(change.path, change.after, workspace.workspaceId, alreadySynced)) {
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.after);
|
||||
}
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
console.log(
|
||||
`Editing ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
console.log(`Editing ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
}
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.after));
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.after))
|
||||
|
||||
const diff = microdiff(
|
||||
inferTypeFromPath(change.path, JSON.parse(change.before)),
|
||||
obj,
|
||||
{ cyclesFix: false }
|
||||
);
|
||||
const diff = microdiff(inferTypeFromPath(change.path, JSON.parse(change.before)), obj, { cyclesFix: false });
|
||||
await applyDiff(
|
||||
workspace.workspaceId,
|
||||
change.path.split(".")[0],
|
||||
obj,
|
||||
diff
|
||||
diff,
|
||||
);
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.after);
|
||||
}
|
||||
} else if (change.name === "added") {
|
||||
if (change.path.endsWith(".script.json")) {
|
||||
continue;
|
||||
} else if (
|
||||
await handleFile(
|
||||
change.path,
|
||||
change.content,
|
||||
workspace.workspaceId,
|
||||
alreadySynced
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
continue
|
||||
} else if (await handleFile(change.path, change.content, workspace.workspaceId, alreadySynced)) {
|
||||
continue
|
||||
}
|
||||
if (!opts.raw) {
|
||||
await ensureDir(path.dirname(stateTarget));
|
||||
console.log(
|
||||
`Adding ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
await ensureDir(path.dirname(stateTarget))
|
||||
console.log(`Adding ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
}
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.content));
|
||||
const obj = inferTypeFromPath(change.path, JSON.parse(change.content))
|
||||
const diff = microdiff({}, obj, { cyclesFix: false });
|
||||
await applyDiff(
|
||||
workspace.workspaceId,
|
||||
change.path.split(".")[0],
|
||||
obj,
|
||||
diff
|
||||
diff,
|
||||
);
|
||||
if (!opts.raw) {
|
||||
await Deno.writeTextFile(stateTarget, change.content);
|
||||
}
|
||||
} else if (change.name === "deleted") {
|
||||
if (!change.path.includes(".json")) {
|
||||
continue;
|
||||
continue
|
||||
}
|
||||
console.log(
|
||||
`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`
|
||||
);
|
||||
const typ = getTypeStrFromPath(change.path);
|
||||
console.log(`Deleting ${getTypeStrFromPath(change.path)} ${change.path}`)
|
||||
const typ = getTypeStrFromPath(change.path)
|
||||
const workspaceId = workspace.workspaceId;
|
||||
switch (typ) {
|
||||
case "script": {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".script.json"),
|
||||
});
|
||||
await ScriptService.deleteScriptByHash({
|
||||
workspace: workspaceId,
|
||||
hash: script.hash,
|
||||
});
|
||||
const script = await ScriptService.getScriptByPath({ workspace: workspaceId, path: removeSuffix(change.path, ".script.json") })
|
||||
await ScriptService.deleteScriptByHash({ workspace: workspaceId, hash: script.hash })
|
||||
break;
|
||||
}
|
||||
case "folder":
|
||||
await FolderService.deleteFolder({
|
||||
workspace: workspaceId,
|
||||
name: change.path.split("/")[1],
|
||||
});
|
||||
await FolderService.deleteFolder({ workspace: workspaceId, name: change.path.split('/')[1] })
|
||||
break;
|
||||
case "resource":
|
||||
await ResourceService.deleteResource({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".resource.json"),
|
||||
});
|
||||
await ResourceService.deleteResource({ workspace: workspaceId, path: removeSuffix(change.path, ".resource.json") })
|
||||
break;
|
||||
case "resource-type":
|
||||
await ResourceService.deleteResourceType({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".resource-type.json"),
|
||||
});
|
||||
break;
|
||||
await ResourceService.deleteResourceType({ workspace: workspaceId, path: removeSuffix(change.path, ".resource-type.json") })
|
||||
break
|
||||
case "flow":
|
||||
await FlowService.deleteFlowByPath({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".flow.json"),
|
||||
});
|
||||
break;
|
||||
await FlowService.deleteFlowByPath({ workspace: workspaceId, path: removeSuffix(change.path, ".flow.json") })
|
||||
break
|
||||
case "app":
|
||||
await AppService.deleteApp({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".app.json"),
|
||||
});
|
||||
break;
|
||||
await AppService.deleteApp({ workspace: workspaceId, path: removeSuffix(change.path, ".app.json") })
|
||||
break
|
||||
case "variable":
|
||||
await VariableService.deleteVariable({
|
||||
workspace: workspaceId,
|
||||
path: removeSuffix(change.path, ".variable.json"),
|
||||
});
|
||||
break;
|
||||
await VariableService.deleteVariable({ workspace: workspaceId, path: removeSuffix(change.path, ".variable.json") })
|
||||
break
|
||||
default:
|
||||
break;
|
||||
}
|
||||
try {
|
||||
await Deno.remove(stateTarget);
|
||||
} catch {}
|
||||
await Deno.remove(stateTarget)
|
||||
} catch { }
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
colors.green.underline(
|
||||
`Done! All ${changes.length} changes pushed to the remote workspace.`
|
||||
)
|
||||
);
|
||||
console.log(colors.green.underline(`Done! All ${changes.length} changes pushed to the remote workspace.`));
|
||||
|
||||
}
|
||||
|
||||
|
||||
async function applyDiff(
|
||||
workspace: string,
|
||||
remotePath: string,
|
||||
@@ -696,11 +593,11 @@ async function push(
|
||||
| ResourceFile
|
||||
| ResourceTypeFile
|
||||
| FolderFile,
|
||||
diffs: Difference[]
|
||||
diffs: Difference[],
|
||||
) {
|
||||
if (file instanceof ScriptFile) {
|
||||
throw new Error(
|
||||
"This code path should be unreachable - we should never generate diffs for scripts"
|
||||
"This code path should be unreachable - we should never generate diffs for scripts",
|
||||
);
|
||||
} else if (file instanceof FolderFile) {
|
||||
const parts = remotePath.split("/");
|
||||
@@ -711,14 +608,14 @@ async function push(
|
||||
}
|
||||
}
|
||||
if (diffs.length === 0) {
|
||||
console.log("No diffs to apply to " + remotePath);
|
||||
console.log("No diffs to apply to " + remotePath)
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await file.pushDiffs(workspace, remotePath, diffs);
|
||||
} catch (e) {
|
||||
console.error("Failing to apply diffs to " + remotePath);
|
||||
console.error(JSON.stringify(e));
|
||||
console.error("Failing to apply diffs to " + remotePath)
|
||||
console.error(JSON.stringify(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -726,23 +623,17 @@ async function push(
|
||||
const command = new Command()
|
||||
.command("pull")
|
||||
.description(
|
||||
"Pull any remote changes and apply them locally. Use --raw for usage without local state tracking."
|
||||
)
|
||||
.option(
|
||||
"--fail-conflicts",
|
||||
"Error on conflicts (both remote and local have changes on the same item)"
|
||||
"Pull any remote changes and apply them locally. Use --raw for usage without local state tracking.",
|
||||
)
|
||||
.option("--fail-conflicts", "Error on conflicts (both remote and local have changes on the same item)")
|
||||
.option("--yes", "Pull without needing confirmation")
|
||||
.option("--raw", "Pull without using state, just overwrite.")
|
||||
.action(pull as any)
|
||||
.command("push")
|
||||
.description(
|
||||
"Push any local changes and apply them remotely. Use --raw for usage without local state tracking."
|
||||
)
|
||||
.option(
|
||||
"--fail-conflicts",
|
||||
"Error on conflicts (both remote and local have changes on the same item)"
|
||||
"Push any local changes and apply them remotely. Use --raw for usage without local state tracking.",
|
||||
)
|
||||
.option("--fail-conflicts", "Error on conflicts (both remote and local have changes on the same item)")
|
||||
.option("--skip-pull", "Push without pulling first (you have pulled prior)")
|
||||
.option("--yes", "Push without needing confirmation")
|
||||
.option("--raw", "Push without using state, just overwrite.")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
VITE_DEFAULT_WEBHOOK_TYPE=async
|
||||
1
frontend/.gitignore
vendored
1
frontend/.gitignore
vendored
@@ -7,4 +7,3 @@ node_modules
|
||||
CaddyfileRemoteRuben
|
||||
tests-out/
|
||||
storageState.json
|
||||
.env.production
|
||||
@@ -17,7 +17,7 @@ In the root folder:
|
||||
|
||||
```bash
|
||||
docker build . -t windmill
|
||||
docker compose up db windmill_server windmill_worker
|
||||
docker-compose up db windmill_server windmill_worker
|
||||
```
|
||||
|
||||
### 2. Backend is run by cargo
|
||||
@@ -145,25 +145,3 @@ Recommended config for VS Code:
|
||||
```
|
||||
|
||||
- turn _format on save_ on
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
The project is built with [SvelteKit](https://kit.svelte.dev/) and uses as output static files.
|
||||
There are others adapters for sveltekit, but we use the static adapter.
|
||||
|
||||
To build the frontend as static assets, use:
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
The output is in the `build` folder.
|
||||
|
||||
The default build assume you serve every non static files as the 200.html file which is catchall. If you prefer a normal layout, you can use:
|
||||
```
|
||||
NOTCATCHALL=true npm run build
|
||||
```
|
||||
which will generate an index.html and allow you to serve the frontend with any static server.
|
||||
|
||||
Env variables used for build are set in .env file. See [https://vitejs.dev/guide/env-and-mode.html#env-files](https://vitejs.dev/guide/env-and-mode.html#env-files) for more details.
|
||||
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.78.0",
|
||||
"version": "1.75.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill",
|
||||
"version": "1.78.0",
|
||||
"version": "1.75.0",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.78.0",
|
||||
"version": "1.75.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
faPlus
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { setInputCat as computeInputCat } from '$lib/utils'
|
||||
import { setInputCat as computeInputCat, type InputCat } from '$lib/utils'
|
||||
import { Badge, Button } from './common'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
@@ -152,8 +152,6 @@
|
||||
value = enum_?.[0]
|
||||
} else if (inputCat == 'boolean') {
|
||||
value = false
|
||||
} else if (inputCat == 'list') {
|
||||
value = []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +288,7 @@
|
||||
on:click={() => {
|
||||
value = value.filter((el) => el != v)
|
||||
if (value.length == 0) {
|
||||
value = []
|
||||
value = undefined
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -452,6 +450,19 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if !required && inputCat != 'resource-object'}
|
||||
<!-- <Tooltip placement="bottom" content="Reset to default value">
|
||||
<Button
|
||||
on:click={() => (value = undefined)}
|
||||
{disabled}
|
||||
color="alternative"
|
||||
size="sm"
|
||||
class="h-8"
|
||||
>
|
||||
<Icon data={faArrowRotateLeft} />
|
||||
</Button>
|
||||
</Tooltip> -->
|
||||
{/if}
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
{#if !compact || (error && error != '')}
|
||||
|
||||
@@ -248,6 +248,6 @@
|
||||
<Highlight language={json} code={JSON.stringify(result, null, 4).replace(/\\n/g, '\n')} />
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-gray-500 text-sm">No result: {JSON.stringify(result)}</div>
|
||||
<div class="text-gray-500 text-sm">No result</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
minHeight={400}
|
||||
modules={flow?.value?.modules}
|
||||
failureModule={flow?.value?.failure_module}
|
||||
on:select={(e) => (stepDetail = e.detail)}
|
||||
on:click={(e) => (stepDetail = e.detail)}
|
||||
/>
|
||||
</div>
|
||||
{#if !noSide}
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { setInputCat as computeInputCat } from '$lib/utils'
|
||||
import { Badge, Button } from './common'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import FieldHeader from './FieldHeader.svelte'
|
||||
import ObjectResourceInput from './ObjectResourceInput.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
import autosize from 'svelte-autosize'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import Range from './Range.svelte'
|
||||
import LightweightSchemaForm from './LightweightSchemaForm.svelte'
|
||||
|
||||
export let label: string = ''
|
||||
export let value: any
|
||||
|
||||
export let defaultValue: any = undefined
|
||||
|
||||
export let description: string = ''
|
||||
export let format: string = ''
|
||||
export let contentEncoding: 'base64' | 'binary' | undefined = undefined
|
||||
export let type: string | undefined = undefined
|
||||
export let required = false
|
||||
export let pattern: undefined | string = undefined
|
||||
export let valid = required ? false : true
|
||||
export let maxRows = 10
|
||||
export let enum_: string[] | undefined = undefined
|
||||
export let itemsType:
|
||||
| { type?: 'string' | 'number' | 'bytes'; contentEncoding?: 'base64' }
|
||||
| undefined = undefined
|
||||
export let displayHeader = true
|
||||
export let properties: { [name: string]: SchemaProperty } | undefined = undefined
|
||||
export let extra: Record<string, any> = {}
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
|
||||
|
||||
$: validateInput(pattern, value)
|
||||
|
||||
let error: string = ''
|
||||
|
||||
let el: HTMLTextAreaElement | undefined = undefined
|
||||
|
||||
let rawValue: string | undefined = undefined
|
||||
|
||||
$: {
|
||||
if (rawValue) {
|
||||
try {
|
||||
value = JSON.parse(rawValue)
|
||||
error = ''
|
||||
} catch (err) {
|
||||
error = err.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
error = ''
|
||||
if (inputCat === 'object') {
|
||||
evalValueToRaw()
|
||||
validateInput(pattern, value)
|
||||
}
|
||||
}
|
||||
|
||||
export function evalValueToRaw() {
|
||||
if (value) {
|
||||
rawValue = JSON.stringify(value, null, 4)
|
||||
}
|
||||
}
|
||||
|
||||
function fileChanged(e: any, cb: (v: string | undefined) => void) {
|
||||
let t = e.target
|
||||
if (t && 'files' in t && t.files.length > 0) {
|
||||
let reader = new FileReader()
|
||||
reader.onload = (e: any) => {
|
||||
cb(e.target.result.split('base64,')[1])
|
||||
}
|
||||
reader.readAsDataURL(t.files[0])
|
||||
} else {
|
||||
cb(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
el?.focus()
|
||||
if (el) {
|
||||
el.style.height = '5px'
|
||||
el.style.height = el.scrollHeight + 50 + 'px'
|
||||
}
|
||||
}
|
||||
|
||||
function validateInput(pattern: string | undefined, v: any): void {
|
||||
if (required && (v == undefined || v == null || v === '')) {
|
||||
error = 'This field is required'
|
||||
valid = false
|
||||
} else {
|
||||
if (pattern && !testRegex(pattern, v)) {
|
||||
error = `Should match ${pattern}`
|
||||
valid = false
|
||||
} else {
|
||||
error = ''
|
||||
valid = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function testRegex(pattern: string, value: any): boolean {
|
||||
try {
|
||||
const regex = new RegExp(pattern)
|
||||
return regex.test(value)
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
$: {
|
||||
if (value == undefined || value == null) {
|
||||
value = defaultValue
|
||||
if (defaultValue === undefined || defaultValue === null) {
|
||||
if (inputCat === 'string') {
|
||||
value = ''
|
||||
} else if (inputCat == 'enum') {
|
||||
value = enum_?.[0]
|
||||
} else if (inputCat == 'boolean') {
|
||||
value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: inputCat = computeInputCat(type, format, itemsType?.type, enum_, contentEncoding)
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col w-full min-w-[250px]">
|
||||
<div>
|
||||
{#if displayHeader}
|
||||
<FieldHeader {label} {required} {type} {contentEncoding} {format} {itemsType} />
|
||||
{/if}
|
||||
|
||||
{#if description}
|
||||
<div class="text-sm italic pb-1">
|
||||
{description}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex space-x-1">
|
||||
{#if inputCat == 'number'}
|
||||
{#if extra['min'] != undefined && extra['max'] != undefined}
|
||||
<div class="flex w-full gap-1">
|
||||
<span>{extra['min']}</span>
|
||||
<div class="grow">
|
||||
<Range bind:value min={extra['min']} max={extra['max']} />
|
||||
</div>
|
||||
<span>{extra['max']}</span>
|
||||
<span class="mx-2"><Badge large color="blue">{value}</Badge></span>
|
||||
</div>
|
||||
{:else}
|
||||
<input
|
||||
on:focus={(e) => {
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
dispatch('focus')
|
||||
}}
|
||||
type="number"
|
||||
class={valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}
|
||||
placeholder={defaultValue ?? ''}
|
||||
bind:value
|
||||
min={extra['min']}
|
||||
max={extra['max']}
|
||||
on:input={() => dispatch('input', { value, isRaw: true })}
|
||||
/>
|
||||
{/if}
|
||||
{:else if inputCat == 'boolean'}
|
||||
<Toggle
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
class={valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}
|
||||
bind:checked={value}
|
||||
/>
|
||||
{#if type == 'boolean' && value == undefined}
|
||||
<span> Not set</span>
|
||||
{/if}
|
||||
{:else if inputCat == 'list'}
|
||||
<div>
|
||||
<div>
|
||||
{#each value ?? [] as v, i}
|
||||
<div class="flex flex-row max-w-md mt-1">
|
||||
{#if itemsType?.type == 'number'}
|
||||
<input type="number" bind:value={v} />
|
||||
{:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'}
|
||||
<input
|
||||
type="file"
|
||||
class="my-6"
|
||||
on:change={(x) => fileChanged(x, (val) => (value[i] = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{:else}
|
||||
<input type="text" bind:value={v} />
|
||||
{/if}
|
||||
<Button
|
||||
variant="border"
|
||||
color="red"
|
||||
size="sm"
|
||||
btnClasses="mx-6"
|
||||
on:click={() => {
|
||||
value = value.filter((el) => el != v)
|
||||
if (value.length == 0) {
|
||||
value = undefined
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon data={faMinus} />
|
||||
</Button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
btnClasses="mt-1"
|
||||
on:click={() => {
|
||||
if (value == undefined || !Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
value = value.concat('')
|
||||
}}
|
||||
>
|
||||
<Icon data={faPlus} class="mr-2" />
|
||||
Add item
|
||||
</Button>
|
||||
<span class="ml-2">
|
||||
{(value ?? []).length} item{(value ?? []).length > 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
{:else if inputCat == 'resource-object'}
|
||||
<ObjectResourceInput {format} bind:value />
|
||||
{:else if inputCat == 'object'}
|
||||
{#if properties && Object.keys(properties).length > 0}
|
||||
<div class="p-4 pl-8 border rounded w-full">
|
||||
<LightweightSchemaForm
|
||||
schema={{ properties, $schema: '', required: [], type: 'object' }}
|
||||
bind:args={value}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<textarea
|
||||
bind:this={el}
|
||||
on:focus={(e) => {
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
dispatch('focus')
|
||||
}}
|
||||
use:autosize
|
||||
style="max-height: {maxHeight}"
|
||||
on:input={() => {
|
||||
dispatch('input', { rawValue: value, isRaw: false })
|
||||
}}
|
||||
class="col-span-10 {valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}"
|
||||
placeholder={defaultValue ? JSON.stringify(defaultValue, null, 4) : ''}
|
||||
bind:value={rawValue}
|
||||
/>
|
||||
{/if}
|
||||
{:else if inputCat == 'enum'}
|
||||
<select
|
||||
on:focus={(e) => {
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
dispatch('focus')
|
||||
}}
|
||||
class="px-6"
|
||||
bind:value
|
||||
>
|
||||
{#each enum_ ?? [] as e}
|
||||
<option>{e}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else if inputCat == 'date'}
|
||||
<input class="inline-block" type="datetime-local" bind:value />
|
||||
{:else if inputCat == 'base64'}
|
||||
<input
|
||||
type="file"
|
||||
class="my-6"
|
||||
on:change={(x) => fileChanged(x, (val) => (value = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{:else if inputCat == 'resource-string'}
|
||||
<ResourcePicker
|
||||
bind:value
|
||||
resourceType={format.split('-').length > 1
|
||||
? format.substring('resource-'.length)
|
||||
: undefined}
|
||||
/>
|
||||
{:else if inputCat == 'string'}
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex flex-row w-full items- justify-between">
|
||||
<textarea
|
||||
rows="1"
|
||||
bind:this={el}
|
||||
on:focus={(e) => {
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
dispatch('focus')
|
||||
}}
|
||||
on:blur={() => dispatch('blur')}
|
||||
use:autosize
|
||||
type="text"
|
||||
class="col-span-10 {valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-30 bg-red-100'}"
|
||||
placeholder={defaultValue ?? ''}
|
||||
bind:value
|
||||
on:input={() => {
|
||||
dispatch('input', { rawValue: value, isRaw: false })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,35 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import LightweightArgInput from './LightweightArgInput.svelte'
|
||||
|
||||
export let schema: Schema
|
||||
export let args: Record<string, any> | undefined = undefined
|
||||
|
||||
$: if (args === undefined) {
|
||||
args = {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
{#each Object.keys(schema.properties ?? {}) as argName, i (argName)}
|
||||
<div>
|
||||
{#if typeof args == 'object' && schema?.properties[argName] && args}
|
||||
<LightweightArgInput
|
||||
label={argName}
|
||||
description={schema.properties[argName].description}
|
||||
bind:value={args[argName]}
|
||||
type={schema.properties[argName].type}
|
||||
required={schema.required.includes(argName)}
|
||||
pattern={schema.properties[argName].pattern}
|
||||
defaultValue={schema.properties[argName].default}
|
||||
enum_={schema.properties[argName].enum}
|
||||
format={schema.properties[argName].format}
|
||||
contentEncoding={schema.properties[argName].contentEncoding}
|
||||
properties={schema.properties[argName].properties}
|
||||
itemsType={schema.properties[argName].items}
|
||||
extra={schema.properties[argName]}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -60,7 +60,6 @@
|
||||
if (testJob && !testJob.canceled && testJob.type == 'CompletedJob' && `result` in testJob) {
|
||||
if ($flowStateStore[mod.id]?.previewResult) {
|
||||
$flowStateStore[mod.id].previewResult = testJob.result
|
||||
$flowStateStore = $flowStateStore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,10 +200,6 @@
|
||||
}
|
||||
|
||||
function initPath() {
|
||||
if (path != undefined && path != '') {
|
||||
meta = pathToMeta(path)
|
||||
return
|
||||
}
|
||||
if (initialPath == undefined || initialPath == '') {
|
||||
reset()
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import { VariableService } from '$lib/gen'
|
||||
import { VariableService, type InputTransform } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { allTrue } from '$lib/utils'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
@@ -10,7 +10,7 @@
|
||||
import VariableEditor from './VariableEditor.svelte'
|
||||
|
||||
export let schema: Schema
|
||||
export let args: Record<string, any> = {}
|
||||
export let args: Record<string, InputTransform | any> = {}
|
||||
export let disabledArgs: string[] = []
|
||||
export let disabled = false
|
||||
|
||||
|
||||
@@ -230,79 +230,77 @@
|
||||
<!-- metadata -->
|
||||
{#if step === 1}
|
||||
<CenteredPage>
|
||||
<SettingSection title="Path" element="h2">
|
||||
<Path
|
||||
bind:this={pathC}
|
||||
bind:error={pathError}
|
||||
bind:path={script.path}
|
||||
{initialPath}
|
||||
on:enter={() => changeStep(2)}
|
||||
namePlaceholder="script"
|
||||
kind="script"
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingSection title="Summary" element="h2">
|
||||
<input
|
||||
type="text"
|
||||
bind:this={summaryC}
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingSection title="Language" element="h2">
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-gray-600 italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = lang
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
>
|
||||
<LanguageIcon {lang} />
|
||||
<span class="ml-2 py-2">{label}</span>
|
||||
</Button>
|
||||
{/each}
|
||||
<h2 class="border-b pb-1 mt-8 mb-2">Path</h2>
|
||||
<Path
|
||||
bind:this={pathC}
|
||||
bind:error={pathError}
|
||||
bind:path={script.path}
|
||||
{initialPath}
|
||||
on:enter={() => changeStep(2)}
|
||||
namePlaceholder="script"
|
||||
kind="script"
|
||||
/>
|
||||
<h2 class="border-b pb-1 mt-12 mb-4">Summary</h2>
|
||||
<input
|
||||
type="text"
|
||||
bind:this={summaryC}
|
||||
bind:value={script.summary}
|
||||
placeholder="Short summary to be displayed when listed"
|
||||
/>
|
||||
|
||||
<h2 class="border-b pb-1 mt-12 mb-6">Language</h2>
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-gray-600 italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-row gap-2 flex-wrap">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'pgsql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
color={isPicked ? 'blue' : 'dark'}
|
||||
btnClasses={isPicked ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = lang
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
}}
|
||||
disabled={lockedLanguage}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'pgsql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-2">PostgreSQL</span>
|
||||
<LanguageIcon {lang} />
|
||||
<span class="ml-2 py-2">{label}</span>
|
||||
</Button>
|
||||
<!-- <Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'mysql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'mysql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-2">MySQL</span>
|
||||
</Button> -->
|
||||
</div>
|
||||
</SettingSection>
|
||||
{/each}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'pgsql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'pgsql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
disabled={lockedLanguage}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'pgsql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="pgsql" /><span class="ml-2 py-2">PostgreSQL</span>
|
||||
</Button>
|
||||
<!-- <Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'mysql' ? 'blue' : 'dark'}
|
||||
btnClasses={template == 'mysql' ? '!border-2 !bg-blue-50/75' : 'm-[1px]'}
|
||||
on:click={() => {
|
||||
script.language = Script.language.DENO
|
||||
template = 'mysql'
|
||||
initContent(script.language, script.kind, template)
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="mysql" /><span class="ml-2 py-2">MySQL</span>
|
||||
</Button> -->
|
||||
</div>
|
||||
<SettingSection
|
||||
title="Script kind"
|
||||
element="h3"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let title: string
|
||||
export let tooltip: string = ''
|
||||
export let tooltip: string
|
||||
export let element: `h${IntRange<1, 6>}` = 'h2'
|
||||
export let accordion = false
|
||||
let showContent = !accordion
|
||||
@@ -14,29 +14,25 @@
|
||||
<div class="border-b [&:has(button:hover)]:border-gray-400 duration-200 pb-1 mt-8 mb-2">
|
||||
{#if accordion}
|
||||
<button
|
||||
class="flex w-full justify-start items-center"
|
||||
class="flex w-full justify-between items-center"
|
||||
on:click={() => (showContent = !showContent)}
|
||||
>
|
||||
<span class="rounded-full hover:bg-gray-100 focus:bg-gray-100 p-1 mr-1">
|
||||
<ChevronDown size={22} class="rotate-0 duration-300 {showContent ? '!-rotate-180' : ''}" />
|
||||
</span>
|
||||
<svelte:element this={element}>
|
||||
{title}
|
||||
{#if tooltip}
|
||||
<Tooltip scale={0.9} class="mb-0.5">
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
<Tooltip scale={0.9} class="mb-0.5">
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
</svelte:element>
|
||||
<span class="rounded-full hover:bg-gray-100 focus:bg-gray-100 p-1">
|
||||
<ChevronDown size={22} class="rotate-0 duration-300 {showContent ? '!rotate-180' : ''}" />
|
||||
</span>
|
||||
</button>
|
||||
{:else}
|
||||
<svelte:element this={element}>
|
||||
{title}
|
||||
{#if tooltip}
|
||||
<Tooltip scale={0.9} class="mb-0.5">
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
{/if}
|
||||
<Tooltip scale={0.9} class="mb-0.5">
|
||||
{tooltip}
|
||||
</Tooltip>
|
||||
</svelte:element>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -99,7 +99,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function format() {
|
||||
function format() {
|
||||
if (editor) {
|
||||
code = getCode()
|
||||
editor.getAction('editor.action.formatDocument').run()
|
||||
|
||||
@@ -577,10 +577,6 @@
|
||||
})
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
editor?.focus()
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
loadMonaco()
|
||||
|
||||
@@ -57,8 +57,7 @@
|
||||
decryptSecret: false
|
||||
})
|
||||
can_write =
|
||||
getV.workspace_id == $workspaceStore &&
|
||||
canWrite(edit_path, getV.extra_perms ?? {}, $userStore)
|
||||
getV.workspace_id == $workspaceStore && canWrite(path, getV.extra_perms ?? {}, $userStore)
|
||||
|
||||
variable = {
|
||||
value: getV.value ?? '',
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
...new Set(
|
||||
workers
|
||||
.filter((worker) => {
|
||||
return worker.ip != 'unretrievable IP' && worker.last_ping && worker.last_ping < 300
|
||||
const date = new Date().getTime() - 300 * 60
|
||||
const ping_at = new Date(worker.ping_at).getTime()
|
||||
return worker.ip != 'unretrievable IP' && ping_at > date
|
||||
})
|
||||
.map((worker) => worker.ip)
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import { loadIcon } from '../icon'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { goto } from '$app/navigation'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -24,9 +23,10 @@
|
||||
export let preclickAction: (() => Promise<void>) | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
|
||||
export let render: boolean
|
||||
export let initializing: boolean | undefined = true
|
||||
|
||||
const { worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { runnableComponents, worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let labelValue: string
|
||||
let color: ButtonType.Color
|
||||
@@ -62,10 +62,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<Array<any>>
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: triggerOnAppLoad && runnableComponent?.runComponent()
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
}
|
||||
|
||||
$: outputs?.loading.subscribe({
|
||||
id: 'loading-' + id,
|
||||
next: (value) => {
|
||||
isLoading = value
|
||||
if (ownClick && !value) {
|
||||
@@ -110,15 +109,14 @@
|
||||
} else {
|
||||
await runnableComponent?.runComponent()
|
||||
}
|
||||
|
||||
if (recomputeIds) {
|
||||
await Promise.all(recomputeIds.map((id) => $runnableComponents?.[id]?.()))
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue
|
||||
on:done={() => initializing && (initializing = false)}
|
||||
{id}
|
||||
input={configuration.label}
|
||||
bind:value={labelValue}
|
||||
/>
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.goto} bind:value={gotoUrl} />
|
||||
<InputValue {id} input={configuration.color} bind:value={color} />
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
@@ -137,7 +135,7 @@
|
||||
<InputValue {id} input={configuration.gotoNewTab} bind:value={gotoNewTab} />
|
||||
|
||||
<RunnableWrapper
|
||||
{recomputeIds}
|
||||
flexWrap
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
{id}
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { app, worldStore, stateId } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, runnableComponents, worldStore, stateId } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let color: ButtonType.Color
|
||||
@@ -47,7 +48,6 @@
|
||||
}
|
||||
|
||||
$: outputs?.loading.subscribe({
|
||||
id: 'loading-' + id,
|
||||
next: (value) => {
|
||||
isLoading = value
|
||||
}
|
||||
@@ -62,7 +62,6 @@
|
||||
<InputValue {id} input={configuration.size} bind:value={size} />
|
||||
|
||||
<RunnableWrapper
|
||||
{recomputeIds}
|
||||
{render}
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
@@ -104,6 +103,12 @@
|
||||
}}
|
||||
on:click={() => {
|
||||
runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
$runnableComponents[id]?.()
|
||||
})
|
||||
}
|
||||
}}
|
||||
{size}
|
||||
{color}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, runnableComponents, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let color: ButtonType.Color
|
||||
@@ -57,7 +57,6 @@
|
||||
}
|
||||
|
||||
$: outputs?.loading.subscribe({
|
||||
id: 'loading-' + id,
|
||||
next: (value) => {
|
||||
isLoading = value
|
||||
if (ownClick && !value) {
|
||||
@@ -95,7 +94,6 @@
|
||||
}}
|
||||
>
|
||||
<RunnableWrapper
|
||||
{recomputeIds}
|
||||
{render}
|
||||
bind:runnableComponent
|
||||
{componentInput}
|
||||
@@ -130,6 +128,12 @@
|
||||
on:click={async () => {
|
||||
await runnableComponent?.runComponent()
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.forEach((id) => {
|
||||
$runnableComponents[id]?.()
|
||||
})
|
||||
}
|
||||
|
||||
open = false
|
||||
}}
|
||||
size="xs"
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -27,12 +26,8 @@
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
@@ -95,7 +90,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.line} bind:value={lineChart} />
|
||||
|
||||
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} 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}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import {
|
||||
IS_APP_PUBLIC_CONTEXT_KEY,
|
||||
@@ -18,16 +17,13 @@
|
||||
export let render: boolean
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
let result: any = undefined
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} 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',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
@@ -12,12 +11,8 @@
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let result: string | undefined = undefined
|
||||
let h: number | undefined = undefined
|
||||
@@ -34,7 +29,15 @@
|
||||
bind:clientHeight={h}
|
||||
bind:clientWidth={w}
|
||||
>
|
||||
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper
|
||||
{render}
|
||||
autoRefresh
|
||||
flexWrap
|
||||
{componentInput}
|
||||
{id}
|
||||
bind:initializing
|
||||
bind:result
|
||||
>
|
||||
{#key result}
|
||||
<iframe
|
||||
frameborder="0"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
|
||||
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
|
||||
export let render: boolean
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export const staticOutputs: string[] = ['loading']
|
||||
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer'
|
||||
import { Point } from 'ol/geom'
|
||||
import { defaults as defaultControls } from 'ol/control'
|
||||
import { findGridItem, initOutput } from '../../editor/appUtils'
|
||||
import { findGridItem } from '../../editor/appUtils'
|
||||
import type { Output } from '../../rx'
|
||||
|
||||
interface Marker {
|
||||
lon: number
|
||||
@@ -29,18 +30,19 @@
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export const staticOutputs: string[] = ['mapRegion']
|
||||
export let customCss: ComponentCustomCSS<'map'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
mapRegion: {
|
||||
topLeft: { lat: 0, lon: 0 },
|
||||
bottomRight: { lat: 0, lon: 0 }
|
||||
}
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
mapRegion: Output<{
|
||||
topLeft: { lat: number; lon: number }
|
||||
bottomRight: { lat: number; lon: number }
|
||||
}>
|
||||
}
|
||||
|
||||
let map: Map
|
||||
let mapElement: HTMLDivElement
|
||||
@@ -171,7 +173,6 @@
|
||||
updateRegionOutput()
|
||||
|
||||
if (z) {
|
||||
//@ts-ignore
|
||||
gridItem.data.configuration.zoom.value = z
|
||||
}
|
||||
|
||||
@@ -181,9 +182,7 @@
|
||||
}
|
||||
|
||||
if (gridItem) {
|
||||
//@ts-ignore
|
||||
gridItem.data.configuration.longitude.value = center[0]
|
||||
//@ts-ignore
|
||||
gridItem.data.configuration.latitude.value = center[1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,20 +11,15 @@
|
||||
import { Button } from '../../../common'
|
||||
import { Download, Loader2, MoveHorizontal, ZoomIn, ZoomOut } from 'lucide-svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { findGridItem, initOutput } from '../../editor/appUtils'
|
||||
import { findGridItem } from '../../editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export const staticOutputs: string[] = ['loading']
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, mode, worldStore, selectedComponent } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
const { app, mode, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let source: string | ArrayBuffer | undefined = undefined
|
||||
let wrapper: HTMLDivElement | undefined = undefined
|
||||
@@ -155,9 +150,7 @@
|
||||
function syncZoomValue() {
|
||||
const gridItem = findGridItem($app, id)
|
||||
|
||||
//@ts-ignore
|
||||
if (gridItem && gridItem.data.configuration.zoom.value !== zoom) {
|
||||
//@ts-ignore
|
||||
gridItem.data.configuration.zoom.value = zoom
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -26,12 +25,8 @@
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
@@ -78,7 +73,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
|
||||
|
||||
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} 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}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -32,12 +31,8 @@
|
||||
let zoomable = false
|
||||
let pannable = false
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
ChartJS.register(
|
||||
Title,
|
||||
@@ -84,7 +79,7 @@
|
||||
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} 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} />
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import ResizeWrapper from '../helpers/ResizeWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -22,19 +21,14 @@
|
||||
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore, mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
const { ontextfocus } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let result: string | undefined = undefined
|
||||
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
|
||||
let copyButton: boolean
|
||||
let tooltip: string = ''
|
||||
let fitContent = false
|
||||
|
||||
function getComponent() {
|
||||
switch (style) {
|
||||
@@ -72,27 +66,17 @@
|
||||
|
||||
<InputValue {id} input={configuration.style} bind:value={style} />
|
||||
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
|
||||
<InputValue {id} input={configuration.tooltip} bind:value={tooltip} />
|
||||
<InputValue {id} input={configuration.fitContent} bind:value={fitContent} />
|
||||
|
||||
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<div class="h-full w-full overflow-hidden">
|
||||
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
|
||||
<ResizeWrapper {id} shouldWrap={fitContent}>
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if !result || result === ''}
|
||||
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
|
||||
No text
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="flex flex-wrap gap-2 pb-0.5 {$mode === 'dnd' && componentInput?.type == 'template'
|
||||
? 'cursor-text'
|
||||
: ''}"
|
||||
on:click={() => {
|
||||
if ($mode === 'dnd' && componentInput?.type == 'template') {
|
||||
$ontextfocus?.()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-wrap gap-2 pb-0.5 overflow-x-auto">
|
||||
<svelte:element
|
||||
this={component}
|
||||
class={twMerge(
|
||||
@@ -104,26 +88,17 @@
|
||||
style={[$app.css?.['textcomponent']?.['text']?.style, customCss?.text?.style].join(';')}
|
||||
>
|
||||
{String(result)}
|
||||
{#if tooltip != ''}
|
||||
<Tooltip>{tooltip}</Tooltip>
|
||||
{/if}
|
||||
{#if copyButton && result}
|
||||
<Popover notClickable>
|
||||
<Button
|
||||
variant="border"
|
||||
size="xs"
|
||||
color="dark"
|
||||
btnClasses="!p-1"
|
||||
on:click={() => copyToClipboard(result)}
|
||||
>
|
||||
<Clipboard size={14} strokeWidth={2} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</svelte:element>
|
||||
{#if copyButton && result}
|
||||
<Popover notClickable>
|
||||
<Button size="xs" btnClasses="!px-2" on:click={() => copyToClipboard(result)}>
|
||||
<Clipboard size={14} strokeWidth={2} />
|
||||
</Button>
|
||||
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
|
||||
</Popover>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</AlignWrapper>
|
||||
</div>
|
||||
</ResizeWrapper>
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -32,12 +31,8 @@
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let logarithmicScale = false
|
||||
let zoomable = false
|
||||
@@ -99,7 +94,7 @@
|
||||
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
|
||||
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
|
||||
|
||||
<RunnableWrapper {render} autoRefresh {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} 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} />
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { onMount } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
@@ -11,12 +9,7 @@
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
let result: object | undefined = undefined
|
||||
let divEl: HTMLDivElement | null = null
|
||||
@@ -41,13 +34,13 @@
|
||||
Plotly.newPlot(
|
||||
divEl,
|
||||
[result],
|
||||
{ width: w, height: h, margin: { l: 50, r: 40, b: 40, t: 40, pad: 4 } },
|
||||
{ width: w, height: h, margin: { l: 40, r: 40, b: 40, t: 40, pad: 4 } },
|
||||
{ responsive: true, displayModeBar: false }
|
||||
)
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
|
||||
<div on:pointerdown bind:this={divEl} />
|
||||
</RunnableWrapper>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { onMount } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
@@ -12,16 +10,11 @@
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
let result: object | undefined = undefined
|
||||
let divEl: HTMLDivElement | null = null
|
||||
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
|
||||
let vegaEmbed
|
||||
onMount(async () => {
|
||||
if (divEl) {
|
||||
@@ -60,7 +53,7 @@
|
||||
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
|
||||
|
||||
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
|
||||
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
|
||||
<div on:pointerdown bind:this={divEl} />
|
||||
</RunnableWrapper>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
import { isObject } from '$lib/utils'
|
||||
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import { initOutput } from '$lib/components/apps/editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -20,17 +19,12 @@
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
let result: Record<number, any>[] | undefined = undefined
|
||||
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result', 'selectedRowIndex']
|
||||
|
||||
let result: Record<string, any>[] | undefined = undefined
|
||||
|
||||
const { worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
selectedRowIndex: 0,
|
||||
selectedRow: {},
|
||||
result: [] as Record<number, any>[],
|
||||
loading: false
|
||||
})
|
||||
|
||||
let selectedRowIndex = -1
|
||||
|
||||
function toggleRow(row: Record<string, any>, rowIndex: number) {
|
||||
@@ -54,7 +48,13 @@
|
||||
outputs &&
|
||||
toggleRow({ original: result[0] }, 0)
|
||||
|
||||
$: outputs?.result?.set(result ?? [])
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedRowIndex: Output<number>
|
||||
selectedRow: Output<any>
|
||||
result: Output<any>
|
||||
}
|
||||
|
||||
$: outputs?.result?.set(result)
|
||||
|
||||
let clientHeight
|
||||
let clientWidth
|
||||
@@ -81,7 +81,7 @@
|
||||
<InputValue {id} input={configuration.pagination} bind:value={pagination} />
|
||||
<InputValue {id} input={configuration.pageSize} bind:value={pageSize} />
|
||||
|
||||
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} 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"
|
||||
@@ -109,10 +109,7 @@
|
||||
</div>
|
||||
{:else if result != undefined}
|
||||
<Alert title="Parsing issues" type="error" size="xs">
|
||||
The result should be an array of objects, received:
|
||||
<pre class="overflow-auto">
|
||||
{JSON.stringify(result)}
|
||||
</pre>
|
||||
The result should be an array of objects
|
||||
</Alert>
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
import type { ButtonComponent } from '../../../editor/component'
|
||||
import { concatCustomCss } from '../../../utils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '$lib/components/apps/editor/appUtils'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -28,6 +27,14 @@
|
||||
| undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
export const staticOutputs: string[] = [
|
||||
'selectedRow',
|
||||
'loading',
|
||||
'result',
|
||||
'search',
|
||||
'selectedRowIndex'
|
||||
]
|
||||
|
||||
type T = Record<string, any>
|
||||
|
||||
let result: Record<string, any>[] | undefined = undefined
|
||||
@@ -50,7 +57,11 @@
|
||||
|
||||
let table = createSvelteTable(options)
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const {
|
||||
app,
|
||||
worldStore,
|
||||
staticOutputs: staticOutputsStore
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let selectedRowIndex = -1
|
||||
|
||||
@@ -120,13 +131,11 @@
|
||||
$: filteredResult && setOptions(filteredResult)
|
||||
$: search === 'By Component' && (filteredResult = searchInResult(result ?? [], searchValue))
|
||||
$: (search === 'By Runnable' || search === 'Disabled') && (filteredResult = result ?? [])
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
selectedRowIndex: 0,
|
||||
selectedRow: undefined,
|
||||
loading: false,
|
||||
result: [],
|
||||
search: ''
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedRowIndex: Output<number>
|
||||
selectedRow: Output<any>
|
||||
search: Output<string>
|
||||
}
|
||||
|
||||
function rerender() {
|
||||
table = createSvelteTable(options)
|
||||
@@ -142,7 +151,7 @@
|
||||
|
||||
<InputValue {id} input={configuration.search} bind:value={search} />
|
||||
|
||||
<RunnableWrapper {render} {componentInput} {id} bind:initializing bind:result>
|
||||
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
|
||||
{#if Array.isArray(result) && result.every(isObject)}
|
||||
<div
|
||||
class={twMerge(
|
||||
@@ -234,11 +243,7 @@
|
||||
{/each}
|
||||
|
||||
{#if actionButtons.length > 0}
|
||||
<td
|
||||
class="p-2"
|
||||
on:keypress={() => toggleRow(row, rowIndex)}
|
||||
on:click={() => toggleRow(row, rowIndex)}
|
||||
>
|
||||
<td class="p-2 " on:click={() => toggleRow(row, rowIndex)}>
|
||||
<div class="center-center h-full w-full flex-wrap gap-1">
|
||||
{#each actionButtons as actionButton, actionIndex (actionIndex)}
|
||||
{#if rowIndex == 0}
|
||||
@@ -251,6 +256,7 @@
|
||||
}}
|
||||
extraQueryParams={{ row: row.original }}
|
||||
bind:componentInput={actionButton.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
|
||||
/>
|
||||
{:else}
|
||||
<AppButton
|
||||
@@ -284,10 +290,7 @@
|
||||
</div>
|
||||
{:else if result != undefined}
|
||||
<Alert title="Parsing issues" type="error" size="xs">
|
||||
The result should be an array of objects. Received:
|
||||
<pre class="overflow-auto">
|
||||
{JSON.stringify(result)}
|
||||
</pre>
|
||||
The result should be an array of objects
|
||||
</Alert>
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
|
||||
@@ -1,36 +1,27 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type {
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
StaticAppInput,
|
||||
UserAppInput
|
||||
} from '../../inputType'
|
||||
import type { AppViewerContext, InlineScript } from '../../types'
|
||||
import type { InlineScript } from '../../types'
|
||||
import RunnableComponent from './RunnableComponent.svelte'
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
export let inlineScript: InlineScript | undefined
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
export let autoRefresh: boolean = false
|
||||
|
||||
let result: any = undefined
|
||||
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined,
|
||||
loading: false
|
||||
})
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableComponent
|
||||
render={false}
|
||||
{id}
|
||||
{fields}
|
||||
{autoRefresh}
|
||||
bind:result
|
||||
runnable={{
|
||||
name,
|
||||
@@ -38,7 +29,6 @@
|
||||
type: 'runnableByName'
|
||||
}}
|
||||
wrapperClass="hidden"
|
||||
recomputable
|
||||
>
|
||||
<slot />
|
||||
</RunnableComponent>
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
|
||||
type T = string | number | boolean | Record<string | number, any> | undefined
|
||||
|
||||
export let input: AppInput | undefined
|
||||
export let input: AppInput
|
||||
export let value: T
|
||||
export let id: string | undefined = undefined
|
||||
export let error: string = ''
|
||||
export let extraContext: Record<string, any> = {}
|
||||
|
||||
const { componentControl } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
if (input == undefined) {
|
||||
dispatch('done')
|
||||
}
|
||||
|
||||
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
|
||||
|
||||
$: if (input && !deepEqual(input, lastInput)) {
|
||||
@@ -33,13 +24,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
const { worldStore, state, mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: stateId = $worldStore?.stateId
|
||||
$: state = $worldStore?.state
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
const debounce_ms = 50
|
||||
function debounce(cb: () => Promise<void>) {
|
||||
function debounce(cb: () => void) {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
@@ -49,44 +40,30 @@
|
||||
$: lastInput && $worldStore && debounce(handleConnection)
|
||||
$: lastInput &&
|
||||
lastInput.type == 'template' &&
|
||||
$stateId &&
|
||||
$state &&
|
||||
debounce(async () => {
|
||||
value = await getValue(lastInput)
|
||||
dispatch('done')
|
||||
})
|
||||
debounce(() => (value = getValue(lastInput)))
|
||||
$: lastInput &&
|
||||
lastInput.type == 'eval' &&
|
||||
$stateId &&
|
||||
$state &&
|
||||
debounce(async () => (value = await evalExpr(lastInput)))
|
||||
debounce(() => (value = evalExpr(lastInput)))
|
||||
|
||||
async function handleConnection() {
|
||||
function handleConnection() {
|
||||
if (lastInput.type === 'connected') {
|
||||
$worldStore?.connect<any>(lastInput, onValueChange, id)
|
||||
$worldStore?.connect<any>(lastInput, onValueChange)
|
||||
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
|
||||
value = await getValue(lastInput)
|
||||
value = getValue(lastInput)
|
||||
} else if (lastInput.type == 'eval') {
|
||||
value = await evalExpr(lastInput as EvalAppInput)
|
||||
value = evalExpr(lastInput as EvalAppInput)
|
||||
} else if (lastInput.type == 'upload') {
|
||||
value = (lastInput as UploadAppInput).value
|
||||
} else {
|
||||
value = undefined
|
||||
}
|
||||
dispatch('done')
|
||||
}
|
||||
|
||||
async function evalExpr(input: EvalAppInput) {
|
||||
function evalExpr(input: EvalAppInput) {
|
||||
try {
|
||||
const r = await eval_like(
|
||||
input.expr,
|
||||
computeGlobalContext($worldStore, id, extraContext),
|
||||
true,
|
||||
$state,
|
||||
$mode == 'dnd',
|
||||
$componentControl,
|
||||
$worldStore
|
||||
)
|
||||
const r = eval_like(input.expr, computeGlobalContext())
|
||||
error = ''
|
||||
return r
|
||||
} catch (e) {
|
||||
@@ -95,18 +72,26 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getValue(input: AppInput) {
|
||||
function computeGlobalContext() {
|
||||
return {
|
||||
...Object.fromEntries(
|
||||
Object.entries($worldStore?.outputsById ?? {})
|
||||
.filter(([k, _]) => k != id)
|
||||
.map(([key, value]) => {
|
||||
return [
|
||||
key,
|
||||
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
|
||||
]
|
||||
})
|
||||
),
|
||||
...extraContext
|
||||
}
|
||||
}
|
||||
|
||||
export function getValue(input: AppInput) {
|
||||
if (input.type === 'template' && isCodeInjection(input.eval)) {
|
||||
try {
|
||||
const r = await eval_like(
|
||||
'`' + input.eval + '`',
|
||||
computeGlobalContext($worldStore, id, extraContext),
|
||||
true,
|
||||
$state,
|
||||
$mode == 'dnd',
|
||||
$componentControl,
|
||||
$worldStore
|
||||
)
|
||||
const r = eval_like('`' + input.eval + '`', computeGlobalContext())
|
||||
error = ''
|
||||
return r
|
||||
} catch (e) {
|
||||
@@ -119,6 +104,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
function create_context_function_template(eval_string, context) {
|
||||
return `
|
||||
return function (context) {
|
||||
"use strict";
|
||||
${
|
||||
Object.keys(context).length > 0
|
||||
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
|
||||
: ``
|
||||
}
|
||||
return ${eval_string};
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
function make_context_evaluator(eval_string, context) {
|
||||
let template = create_context_function_template(eval_string, context)
|
||||
let functor = Function(template)
|
||||
return functor()
|
||||
}
|
||||
|
||||
function eval_like(text, context = {}) {
|
||||
let evaluator = make_context_evaluator(text, context)
|
||||
return evaluator(context)
|
||||
}
|
||||
|
||||
function onValueChange(newValue: any): void {
|
||||
if (lastInput.type === 'connected' && newValue !== undefined && newValue !== null) {
|
||||
const { connection } = lastInput
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// Sync the result to the output
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: outputs = $worldStore?.outputsById?.[id] as {
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
loading: Output<boolean>
|
||||
result: Output<any>
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
}
|
||||
|
||||
function setOutput(v: any) {
|
||||
outputs?.result?.set(v, true)
|
||||
outputs.result?.set(v, true)
|
||||
}
|
||||
|
||||
$: result && outputs && setOutput(result)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { RefreshCw } from 'lucide-svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faRefresh } from '@fortawesome/free-solid-svg-icons'
|
||||
import { RefreshCcw, RefreshCw } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
|
||||
@@ -14,7 +16,6 @@
|
||||
}
|
||||
let loading = false
|
||||
$: $worldStore?.outputsById[componentId]?.['loading']?.subscribe({
|
||||
id: 'refresh-' + componentId,
|
||||
next: (value) => {
|
||||
loading = value
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
export let id: string
|
||||
export let shouldWrap: boolean = false
|
||||
const { app, breakpoint, mode } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, breakpoint } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: gridItem = findGridItem($app, id)
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
const wrapperHeight = wrapper.getBoundingClientRect().height
|
||||
const width = $breakpoint === 'sm' ? 3 : 12
|
||||
gridItem[width].h = Math.ceil(wrapperHeight / 36)
|
||||
gridItem = gridItem
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if shouldWrap && $mode !== 'preview'}
|
||||
<div class="h-full w-full" bind:this={wrapper}>
|
||||
{#if shouldWrap}
|
||||
<div bind:this={wrapper}>
|
||||
<slot />
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
import { goto } from '$app/navigation'
|
||||
import type { Schema } from '$lib/common'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import LightweightSchemaForm from '$lib/components/LightweightSchemaForm.svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import { AppService, type CompletedJob } from '$lib/gen'
|
||||
import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { Bug } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import { Bug, Loader2 } from 'lucide-svelte'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { AppInputs, Runnable } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { computeGlobalContext, eval_like } from './eval'
|
||||
import InputValue from './InputValue.svelte'
|
||||
import RefreshButton from './RefreshButton.svelte'
|
||||
|
||||
@@ -25,14 +24,13 @@
|
||||
export let autoRefresh: boolean = true
|
||||
export let result: any = undefined
|
||||
export let forceSchemaDisplay: boolean = 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
|
||||
export let render: boolean
|
||||
export let recomputable: boolean = false
|
||||
export let recomputeIds: string[] = []
|
||||
|
||||
const {
|
||||
worldStore,
|
||||
@@ -44,28 +42,17 @@
|
||||
noBackend,
|
||||
errorByComponent,
|
||||
mode,
|
||||
stateId,
|
||||
state,
|
||||
componentControl
|
||||
stateId
|
||||
} = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: autoRefresh && handleAutorefresh()
|
||||
|
||||
if (recomputable) {
|
||||
$runnableComponents[id] = async () => {
|
||||
await executeComponent(true)
|
||||
}
|
||||
$runnableComponents = $runnableComponents
|
||||
}
|
||||
|
||||
function handleAutorefresh() {
|
||||
if (autoRefresh && $worldStore) {
|
||||
onMount(() => {
|
||||
if (autoRefresh) {
|
||||
$runnableComponents[id] = async () => {
|
||||
await executeComponent(true)
|
||||
}
|
||||
executeComponent(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let args: Record<string, any> | undefined = undefined
|
||||
let testIsLoading = false
|
||||
@@ -93,27 +80,30 @@
|
||||
$: fields && (currentStaticValues = computeStaticValues())
|
||||
$: if (JSON.stringify(currentStaticValues) != JSON.stringify(lazyStaticValues)) {
|
||||
lazyStaticValues = currentStaticValues
|
||||
refreshIfAutoRefresh()
|
||||
}
|
||||
|
||||
$: fields && (lazyStaticValues = computeStaticValues())
|
||||
$: (runnableInputValues || extraQueryParams || args) && testJobLoader && refreshIfAutoRefresh()
|
||||
$: runnable?.type == 'runnableByName' &&
|
||||
runnable.inlineScript?.language == 'frontend' &&
|
||||
($stateId || $state) &&
|
||||
refreshIfAutoRefresh()
|
||||
|
||||
function refreshIfAutoRefresh() {
|
||||
if (autoRefresh) {
|
||||
setDebouncedExecute()
|
||||
}
|
||||
}
|
||||
|
||||
$: fields && (lazyStaticValues = computeStaticValues())
|
||||
$: (runnableInputValues || extraQueryParams || args) &&
|
||||
autoRefresh &&
|
||||
testJobLoader &&
|
||||
setDebouncedExecute()
|
||||
|
||||
// Test job internal state
|
||||
let testJob: CompletedJob | undefined = undefined
|
||||
let testJobLoader: TestJobLoader | undefined = undefined
|
||||
|
||||
let outputs = initOutput($worldStore, id, { result: undefined, loading: false })
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<Array<any>>
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
$: outputs?.loading?.set(testIsLoading)
|
||||
$: schemaStripped = stripSchema(fields, $stateId)
|
||||
@@ -143,27 +133,17 @@
|
||||
return schemaStripped as Schema
|
||||
}
|
||||
|
||||
async function executeComponent(noToast = false) {
|
||||
if (runnable?.type === 'runnableByName' && runnable.inlineScript?.language === 'frontend') {
|
||||
outputs?.loading?.set(true)
|
||||
try {
|
||||
const r = await eval_like(
|
||||
runnable.inlineScript?.content,
|
||||
computeGlobalContext($worldStore, id, {}),
|
||||
false,
|
||||
$state,
|
||||
$mode == 'dnd',
|
||||
$componentControl,
|
||||
$worldStore
|
||||
)
|
||||
setResult(r)
|
||||
$state = $state
|
||||
} catch (e) {
|
||||
sendUserToast('Error running frontend script: ' + e.message, true)
|
||||
$: disabledArgs = Object.keys(fields ?? {}).reduce(
|
||||
(disabledArgsAccumulator: string[], inputName: string) => {
|
||||
if (fields[inputName].type === 'static') {
|
||||
disabledArgsAccumulator = [...disabledArgsAccumulator, inputName]
|
||||
}
|
||||
outputs?.loading?.set(false)
|
||||
return
|
||||
}
|
||||
return disabledArgsAccumulator
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
async function executeComponent(noToast = false) {
|
||||
if (noBackend) {
|
||||
if (!noToast) {
|
||||
sendUserToast('This app is not connected to a windmill backend, it is a static preview')
|
||||
@@ -236,31 +216,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function setResult(res: any) {
|
||||
outputs.result?.set(res)
|
||||
|
||||
result = res
|
||||
|
||||
const previousJobId = Object.keys($errorByComponent).find(
|
||||
(key) => $errorByComponent[key].componentId === id
|
||||
)
|
||||
|
||||
if (previousJobId && !result?.error) {
|
||||
delete $errorByComponent[previousJobId]
|
||||
$errorByComponent = $errorByComponent
|
||||
}
|
||||
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
|
||||
if (gotoNewTab) {
|
||||
window.open(gotoUrl, '_blank')
|
||||
} else {
|
||||
goto(gotoUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (recomputeIds) {
|
||||
recomputeIds.map((id) => $runnableComponents?.[id]?.())
|
||||
}
|
||||
}
|
||||
$: result?.error && recordError(result.error)
|
||||
</script>
|
||||
|
||||
@@ -277,7 +232,24 @@
|
||||
const startedAt = new Date(testJob.started_at).getTime()
|
||||
if (startedAt > lastStartedAt) {
|
||||
lastStartedAt = startedAt
|
||||
setResult(e.detail.result)
|
||||
outputs.result?.set(testJob?.result)
|
||||
result = testJob.result
|
||||
|
||||
const previousJobId = Object.keys($errorByComponent).find(
|
||||
(key) => $errorByComponent[key].componentId === id
|
||||
)
|
||||
|
||||
if (previousJobId && !result?.error) {
|
||||
delete $errorByComponent[previousJobId]
|
||||
$errorByComponent = $errorByComponent
|
||||
}
|
||||
if (gotoUrl && gotoUrl != '' && result?.error == undefined) {
|
||||
if (gotoNewTab) {
|
||||
window.open(gotoUrl, '_blank')
|
||||
} else {
|
||||
goto(gotoUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -290,7 +262,14 @@
|
||||
<div class="h-full flex relative flex-row flex-wrap {wrapperClass}" style={wrapperStyle}>
|
||||
{#if schemaStripped && Object.keys(schemaStripped?.properties ?? {}).length > 0 && (autoRefresh || forceSchemaDisplay)}
|
||||
<div class="px-2 h-fit min-h-0">
|
||||
<LightweightSchemaForm schema={schemaStripped} bind:args />
|
||||
<SchemaForm
|
||||
{flexWrap}
|
||||
schema={schemaStripped}
|
||||
bind:args
|
||||
{disabledArgs}
|
||||
shouldHideNoInputs
|
||||
noVariablePicker
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -334,4 +313,6 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full h-full" />
|
||||
{/if}
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
export let autoRefresh: boolean = true
|
||||
export let runnableComponent: RunnableComponent | undefined = undefined
|
||||
export let forceSchemaDisplay: boolean = false
|
||||
export let flexWrap = false
|
||||
export let runnableClass = ''
|
||||
export let runnableStyle = ''
|
||||
export let goto: string | undefined = undefined
|
||||
export let gotoNewTab: boolean | undefined = undefined
|
||||
export let render: boolean
|
||||
export let recomputeIds: string[] = []
|
||||
|
||||
const { staticExporter, noBackend } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -44,9 +44,9 @@
|
||||
<slot />
|
||||
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
|
||||
<RunnableComponent
|
||||
{recomputeIds}
|
||||
gotoUrl={goto}
|
||||
{gotoNewTab}
|
||||
{flexWrap}
|
||||
bind:this={runnableComponent}
|
||||
fields={componentInput.fields}
|
||||
bind:result
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import type { World } from '../../rx'
|
||||
|
||||
export function computeGlobalContext(
|
||||
world: World | undefined,
|
||||
id: string | undefined,
|
||||
extraContext: any = {}
|
||||
) {
|
||||
return {
|
||||
...Object.fromEntries(
|
||||
Object.entries(world?.outputsById ?? {})
|
||||
.filter(([k, _]) => k != id && k != 'state')
|
||||
.map(([key, value]) => {
|
||||
return [
|
||||
key,
|
||||
Object.fromEntries(Object.entries(value ?? {}).map((x) => [x[0], x[1].peak()]))
|
||||
]
|
||||
})
|
||||
),
|
||||
...extraContext
|
||||
}
|
||||
}
|
||||
|
||||
function create_context_function_template(eval_string, context, noReturn: boolean) {
|
||||
return `
|
||||
return async function (context, state, goto, setTab) {
|
||||
"use strict";
|
||||
${
|
||||
Object.keys(context).length > 0
|
||||
? `let ${Object.keys(context).map((key) => ` ${key} = context['${key}']`)};`
|
||||
: ``
|
||||
}
|
||||
${noReturn ? `return ${eval_string}` : eval_string}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
function make_context_evaluator(
|
||||
eval_string,
|
||||
context,
|
||||
noReturn: boolean
|
||||
): (context, state, goto, setTab) => Promise<any> {
|
||||
let template = create_context_function_template(eval_string, context, noReturn)
|
||||
let functor = Function(template)
|
||||
return functor()
|
||||
}
|
||||
|
||||
export async function eval_like(
|
||||
text: string,
|
||||
context = {},
|
||||
noReturn: boolean,
|
||||
state: Record<string, any>,
|
||||
editor: boolean,
|
||||
controlComponents: Record<string, { setTab?: (index: number) => void }>,
|
||||
worldStore: World | undefined
|
||||
) {
|
||||
const proxiedState = new Proxy(state, {
|
||||
set(target, key, value) {
|
||||
if (typeof key !== 'string') {
|
||||
throw new Error('Invalid key')
|
||||
}
|
||||
let o = worldStore?.newOutput('state', key, value)
|
||||
o?.set(value)
|
||||
target[key] = value
|
||||
return true
|
||||
}
|
||||
})
|
||||
let evaluator = make_context_evaluator(text, context, noReturn)
|
||||
return await evaluator(
|
||||
context,
|
||||
proxiedState,
|
||||
async (x, newTab) => {
|
||||
if (newTab || editor) {
|
||||
if (!newTab) {
|
||||
sendUserToast(
|
||||
'In editor mode, `goto` opens a new tab to prevent losing your work. To test the redirection , use the preview mode.'
|
||||
)
|
||||
}
|
||||
window.open(x, '_blank')
|
||||
} else {
|
||||
await newTab(x)
|
||||
}
|
||||
},
|
||||
(id, index) => {
|
||||
controlComponents[id]?.setTab?.(index)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
@@ -18,15 +17,17 @@
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
export const staticOutputs: string[] = ['result']
|
||||
|
||||
let defaultValue: boolean | undefined = undefined
|
||||
let labelValue: string = 'Default label'
|
||||
|
||||
// As the checkbox is a special case and has no input
|
||||
// we need to manually set the output
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: false
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<boolean>
|
||||
}
|
||||
|
||||
$: defaultValue != undefined && outputs?.result.set(defaultValue)
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
@@ -13,6 +12,7 @@
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let inputType: 'date'
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export const staticOutputs: string[] = ['result']
|
||||
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
|
||||
let value: string | undefined = undefined
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: undefined as string | undefined
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<string | undefined>
|
||||
}
|
||||
|
||||
$: handleDefault(defaultValue)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { FileInput } from '../../../common'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
@@ -11,6 +10,7 @@
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export const staticOutputs: string[] = ['result']
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
let allowMultiple: boolean | undefined = undefined
|
||||
let text: string | undefined = undefined
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: [] as string[] | undefined
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<string[] | undefined>
|
||||
}
|
||||
|
||||
// Receives Base64 encoded strings from the input component
|
||||
async function handleChange(files: string[] | undefined) {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
export const staticOutputs: string[] = ['result']
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
@@ -22,9 +22,9 @@
|
||||
let items: { label: string; value: string }[]
|
||||
let placeholder: string = 'Select an item'
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: [] as string[]
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<string[] | undefined>
|
||||
}
|
||||
|
||||
// $: outputs && handleOutputs()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
@@ -12,6 +11,7 @@
|
||||
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<'input'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
let max: number | undefined = undefined
|
||||
let step = 1
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: undefined as number | undefined
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<number | undefined>
|
||||
}
|
||||
|
||||
$: handleDefault(defaultValue)
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
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
|
||||
export let render: boolean
|
||||
@@ -25,11 +25,9 @@
|
||||
|
||||
let values: [number, number] = [0, 0]
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: null as [number, number] | null
|
||||
})
|
||||
|
||||
console.log('reset')
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<[number, number] | null>
|
||||
}
|
||||
|
||||
$: outputs?.result.set(values)
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
export const staticOutputs: string[] = ['result']
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
@@ -22,9 +22,9 @@
|
||||
let items: { label: string; value: any; created?: boolean }[]
|
||||
let placeholder: string = 'Select an item'
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: undefined
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<any | undefined>
|
||||
}
|
||||
|
||||
let value: string | undefined = outputs?.result.peak()
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
|
||||
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' | 'bar'> | undefined =
|
||||
undefined
|
||||
export let render: boolean
|
||||
@@ -23,9 +23,9 @@
|
||||
let step = 1
|
||||
let slider: HTMLElement
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: (0 as number) || null
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<number | null>
|
||||
}
|
||||
|
||||
let values: [number] = [outputs?.result.peak() ?? 0]
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
@@ -13,6 +12,7 @@
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let inputType = 'text'
|
||||
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' | 'emailinputcomponent' =
|
||||
'textinputcomponent'
|
||||
@@ -24,13 +24,13 @@
|
||||
let defaultValue: string | undefined = undefined
|
||||
let value: string | undefined = undefined
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
result: ''
|
||||
})
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<string | undefined>
|
||||
}
|
||||
|
||||
$: handleDefault(defaultValue)
|
||||
|
||||
$: outputs?.result.set(value ?? '')
|
||||
$: outputs?.result.set(value)
|
||||
|
||||
function handleDefault(defaultValue: string | undefined) {
|
||||
value = defaultValue
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { initOutput } from '$lib/components/apps/editor/appUtils'
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
@@ -13,15 +12,12 @@
|
||||
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<'input'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
initOutput($worldStore, id, {
|
||||
result: undefined
|
||||
})
|
||||
|
||||
let defaultValue: number | undefined = undefined
|
||||
|
||||
let isNegativeAllowed: boolean | undefined = undefined
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS, GridItem } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
@@ -11,6 +12,9 @@
|
||||
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
|
||||
export let render: boolean
|
||||
|
||||
let noPadding: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = []
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
function onFocus() {
|
||||
@@ -25,10 +29,13 @@
|
||||
$: css = concatCustomCss($app.css?.containercomponent, customCss)
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full">
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
|
||||
<div>
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
{noPadding}
|
||||
{id}
|
||||
class={css?.container.class}
|
||||
style={css?.container.style}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let gridContent: string[] | undefined = undefined
|
||||
let noPadding: boolean | undefined = undefined
|
||||
let drawerTitle: string | undefined = undefined
|
||||
let appDrawer: Drawer
|
||||
|
||||
@@ -61,6 +62,7 @@
|
||||
</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} />
|
||||
@@ -79,6 +81,7 @@
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
visible={open && render}
|
||||
{noPadding}
|
||||
{id}
|
||||
class={css?.container.class}
|
||||
style={css?.container.style}
|
||||
|
||||
@@ -16,8 +16,11 @@
|
||||
export let panes: number[]
|
||||
export let render: boolean
|
||||
|
||||
const { app, focusedGrid, selectedComponent, componentControl } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let noPadding: boolean | undefined = undefined
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
@@ -63,6 +66,8 @@
|
||||
}
|
||||
</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)}
|
||||
@@ -71,6 +76,7 @@
|
||||
<SubGridEditor
|
||||
visible={render}
|
||||
noYPadding
|
||||
{noPadding}
|
||||
{id}
|
||||
shouldHighlight={$focusedGrid?.subGridIndex === index}
|
||||
class={css?.container.class}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
import { initOutput } from '../../editor/appUtils'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
|
||||
import { concatCustomCss } from '../../utils'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
@@ -16,18 +17,22 @@
|
||||
| ComponentCustomCSS<'tabRow' | 'tabs' | 'selectedTab' | 'container'>
|
||||
| undefined = undefined
|
||||
export let render: boolean
|
||||
export let initializing: boolean | undefined = configuration.tabsKind != undefined
|
||||
|
||||
const { app, worldStore, focusedGrid, selectedComponent, mode, componentControl } =
|
||||
export const staticOutputs: string[] = ['selectedTabIndex']
|
||||
const { app, worldStore, focusedGrid, selectedComponent } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let selected: string = tabs[0]
|
||||
let kind: 'tabs' | 'sidebar' | 'invisibleOnView' | undefined = undefined
|
||||
let noPadding: boolean | undefined = undefined
|
||||
let tabHeight: number = 0
|
||||
|
||||
let outputs = initOutput($worldStore, id, {
|
||||
selectedTabIndex: 0
|
||||
})
|
||||
$: outputs = $worldStore
|
||||
? initOutput($worldStore, id, {
|
||||
selectedTabIndex: 0
|
||||
})
|
||||
: undefined
|
||||
|
||||
function handleTabSelection() {
|
||||
const selectedIndex = tabs?.indexOf(selected)
|
||||
@@ -73,9 +78,6 @@
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
setTab: (tab: number) => {
|
||||
selected = tabs[tab]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,64 +86,40 @@
|
||||
$: selectedIndex = tabs?.indexOf(selected) ?? -1
|
||||
|
||||
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
|
||||
|
||||
let subgridWidth: number | undefined = undefined
|
||||
</script>
|
||||
|
||||
<InputValue
|
||||
on:done={() => initializing && (initializing = false)}
|
||||
{id}
|
||||
input={configuration.tabsKind}
|
||||
bind:value={kind}
|
||||
/>
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
|
||||
<div class={kind == 'sidebar' ? 'flex gap-4 w-full' : 'w-full'}>
|
||||
{#if !kind || kind == 'tabs' || (kind == 'invisibleOnView' && $mode == 'dnd')}
|
||||
<div bind:clientHeight={tabHeight}>
|
||||
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
|
||||
{#each tabs ?? [] as 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}
|
||||
</Tabs>
|
||||
</div>
|
||||
{:else if kind == 'sidebar'}
|
||||
<div
|
||||
class="flex gap-y-2 flex-col w-1/6 max-w-[160px] bg-white text-[#2e3440] opacity-80 px-4 pt-4 border-r border-gray-400"
|
||||
>
|
||||
<div>
|
||||
<div bind:clientHeight={tabHeight}>
|
||||
<Tabs bind:selected class={css?.tabRow?.class} style={css?.tabRow?.style}>
|
||||
{#each tabs ?? [] as res}
|
||||
<button
|
||||
class="rounded-sm !truncate text-sm hover:bg-gray-100 hover:border hover:text-black px-1 py-2 {selected ==
|
||||
res
|
||||
? 'outline outline-gray-500 outline-1 bg-white text-black'
|
||||
: ''}"
|
||||
on:pointerdown|stopPropagation
|
||||
on:click={() => (selected = res)}>{res}</button
|
||||
<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}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div bind:clientWidth={subgridWidth} class="w-full">
|
||||
{#if $app.subgrids}
|
||||
{#each tabs ?? [] as res, i}<SubGridEditor
|
||||
{id}
|
||||
visible={render && i === selectedIndex}
|
||||
bind:subGrid={$app.subgrids[`${id}-${i}`]}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
containerHeight={componentContainerHeight - tabHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>{/each}
|
||||
{/if}
|
||||
</Tabs>
|
||||
</div>
|
||||
{#if $app.subgrids}
|
||||
{#each tabs ?? [] as res, i}
|
||||
<SubGridEditor
|
||||
{id}
|
||||
visible={render && i === selectedIndex}
|
||||
bind:subGrid={$app.subgrids[`${id}-${i}`]}
|
||||
{noPadding}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
containerHeight={componentContainerHeight - tabHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
id={component.id}
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={component.componentInput.fields}
|
||||
userInputEnabled={component.type === 'formcomponent' ||
|
||||
component.type === 'formbuttoncomponent'}
|
||||
userInputEnabled={component.type !== 'buttoncomponent'}
|
||||
{resourceOnly}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { AppComponent } from './component'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
export let component: AppComponent
|
||||
export let resourceOnly: boolean = false
|
||||
|
||||
$: fields = resourceOnly ? fields : fields
|
||||
</script>
|
||||
|
||||
<InputsSpecsEditor
|
||||
id={component.id}
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={fields}
|
||||
userInputEnabled={component.type !== 'buttoncomponent'}
|
||||
/>
|
||||
@@ -49,6 +49,8 @@
|
||||
export let fromHub: boolean = false
|
||||
|
||||
const appStore = writable<App>(app)
|
||||
const worldStore = writable<World | undefined>(undefined)
|
||||
const staticOutputs = writable<Record<string, string[]>>({})
|
||||
const selectedComponent = writable<string | undefined>(undefined)
|
||||
const mode = writable<EditorMode>(initialMode)
|
||||
const breakpoint = writable<EditorBreakpoint>('lg')
|
||||
@@ -64,15 +66,10 @@
|
||||
const errorByComponent = writable<Record<string, { error: string; componentId: string }>>({})
|
||||
const focusedGrid = writable<FocusedGrid | undefined>(undefined)
|
||||
const pickVariableCallback: Writable<((path: string) => void) | undefined> = writable(undefined)
|
||||
let context = {
|
||||
email: $userStore?.email,
|
||||
username: $userStore?.username,
|
||||
query: Object.fromEntries($page.url.searchParams.entries()),
|
||||
hash: $page.url.hash
|
||||
}
|
||||
|
||||
setContext<AppViewerContext>('AppViewerContext', {
|
||||
worldStore: buildWorld(context),
|
||||
worldStore,
|
||||
staticOutputs,
|
||||
app: appStore,
|
||||
summary: summaryStore,
|
||||
selectedComponent,
|
||||
@@ -91,15 +88,13 @@
|
||||
openDebugRun: writable(undefined),
|
||||
focusedGrid,
|
||||
stateId: writable(0),
|
||||
parentWidth: writable(0),
|
||||
state: writable({}),
|
||||
componentControl: writable({})
|
||||
parentWidth: writable(0)
|
||||
})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
history,
|
||||
pickVariableCallback,
|
||||
ontextfocus: writable(undefined)
|
||||
componentControl: writable({}),
|
||||
pickVariableCallback
|
||||
})
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
@@ -123,11 +118,19 @@
|
||||
mounted = true
|
||||
})
|
||||
|
||||
let context = {
|
||||
email: $userStore?.email,
|
||||
username: $userStore?.username,
|
||||
query: Object.fromEntries($page.url.searchParams.entries()),
|
||||
hash: $page.url.hash
|
||||
}
|
||||
|
||||
function hashchange(e: HashChangeEvent) {
|
||||
context.hash = e.newURL.split('#')[1]
|
||||
context = context
|
||||
}
|
||||
|
||||
$: mounted && ($worldStore = buildWorld($staticOutputs, $worldStore, context))
|
||||
$: previewing = $mode === 'preview'
|
||||
$: width = $breakpoint === 'sm' ? 'min-w-[400px] max-w-[656px]' : 'min-w-[710px] w-full'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { setContext } from 'svelte'
|
||||
import { onMount, setContext } from 'svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import { buildWorld } from '../rx'
|
||||
import { buildWorld, type World } from '../rx'
|
||||
import type {
|
||||
App,
|
||||
AppEditorContext,
|
||||
@@ -10,16 +10,11 @@
|
||||
EditorBreakpoint,
|
||||
EditorMode
|
||||
} from '../types'
|
||||
import GridEditor from './GridEditor.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import type { Policy } from '$lib/gen'
|
||||
import Button from '../../common/button/Button.svelte'
|
||||
import { Unlock } from 'lucide-svelte'
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
import GridViewer from './GridViewer.svelte'
|
||||
import { Component } from './component'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { columnConfiguration } from '../gridUtils'
|
||||
import { HiddenComponent } from '../components'
|
||||
|
||||
export let app: App
|
||||
export let appPath: string
|
||||
@@ -33,6 +28,8 @@
|
||||
export let isLocked = false
|
||||
|
||||
const appStore = writable<App>(app)
|
||||
const worldStore = writable<World | undefined>(undefined)
|
||||
const staticOutputs = writable<Record<string, string[]>>({})
|
||||
const selectedComponent = writable<string | undefined>(undefined)
|
||||
const mode = writable<EditorMode>('preview')
|
||||
|
||||
@@ -44,9 +41,9 @@
|
||||
|
||||
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
|
||||
|
||||
const parentWidth = writable(0)
|
||||
setContext<AppViewerContext>('AppViewerContext', {
|
||||
worldStore: buildWorld(context),
|
||||
worldStore,
|
||||
staticOutputs,
|
||||
app: appStore,
|
||||
summary: writable(summary),
|
||||
selectedComponent,
|
||||
@@ -65,15 +62,19 @@
|
||||
openDebugRun: writable(undefined),
|
||||
focusedGrid: writable(undefined),
|
||||
stateId: writable(0),
|
||||
parentWidth,
|
||||
state: writable({}),
|
||||
componentControl: writable({})
|
||||
parentWidth: writable(0)
|
||||
})
|
||||
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
history: undefined,
|
||||
pickVariableCallback: writable(undefined),
|
||||
ontextfocus: writable(undefined)
|
||||
componentControl: writable({}),
|
||||
pickVariableCallback: writable(undefined)
|
||||
})
|
||||
|
||||
let mounted = false
|
||||
|
||||
onMount(() => {
|
||||
mounted = true
|
||||
})
|
||||
|
||||
let ncontext = context
|
||||
@@ -83,6 +84,7 @@
|
||||
ncontext = ncontext
|
||||
}
|
||||
|
||||
$: mounted && ($worldStore = buildWorld($staticOutputs, $worldStore, ncontext))
|
||||
$: width = $breakpoint === 'sm' ? 'max-w-[640px]' : 'w-full '
|
||||
$: lockedClasses = isLocked ? '!max-h-[400px] overflow-hidden pointer-events-none' : ''
|
||||
</script>
|
||||
@@ -98,48 +100,9 @@
|
||||
>
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('mx-auto', width)}>
|
||||
<div
|
||||
class="w-full sticky top-0 flex justify-between border-b bg-gray-50 px-4 py-1 items-center gap-4"
|
||||
style="z-index: 1000;"
|
||||
>
|
||||
<h2 class="truncate">{summary}</h2>
|
||||
<RecomputeAllComponents />
|
||||
<div class="text-2xs text-gray-600">
|
||||
{policy.on_behalf_of ? `on behalf of ${policy.on_behalf_of_email}` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<GridEditor {policy} />
|
||||
</div>
|
||||
{/if}
|
||||
<div
|
||||
style={app.css?.['app']?.['grid']?.style}
|
||||
class={twMerge('px-4 pt-4 pb-2 overflow-visible', app.css?.['app']?.['grid']?.class ?? '')}
|
||||
bind:clientWidth={$parentWidth}
|
||||
>
|
||||
<div>
|
||||
<GridViewer
|
||||
onTopId={$selectedComponent}
|
||||
items={app.grid}
|
||||
let:dataItem
|
||||
rowHeight={36}
|
||||
cols={columnConfiguration}
|
||||
gap={[4, 2]}
|
||||
>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={'h-full w-full center-center'}
|
||||
on:pointerdown={() => ($selectedComponent = dataItem.data.id)}
|
||||
>
|
||||
<Component
|
||||
render={true}
|
||||
pointerdown={false}
|
||||
component={dataItem.data}
|
||||
selected={false}
|
||||
locked={true}
|
||||
/>
|
||||
</div>
|
||||
</GridViewer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if isLocked}
|
||||
@@ -155,17 +118,3 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if app.hiddenInlineScripts}
|
||||
{#each app.hiddenInlineScripts as script, index}
|
||||
{#if script}
|
||||
<HiddenComponent
|
||||
id={`bg_${index}`}
|
||||
inlineScript={script.inlineScript}
|
||||
name={script.name}
|
||||
fields={script.fields}
|
||||
autoRefresh={script.autoRefresh ?? false}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { getContext, afterUpdate } from 'svelte'
|
||||
import type { App, AppEditorContext, AppViewerContext } from '../types'
|
||||
import type { App, AppEditorContext, AppViewerContext, GridItem } from '../types'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
|
||||
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
@@ -21,6 +21,7 @@
|
||||
app,
|
||||
mode,
|
||||
connectingInput,
|
||||
staticOutputs,
|
||||
runnableComponents,
|
||||
summary,
|
||||
focusedGrid,
|
||||
@@ -30,6 +31,31 @@
|
||||
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
// The drag is disabled when the user is connecting an input
|
||||
// or when the user is previewing the app
|
||||
// or when the focused grid is a subgrid
|
||||
$: setAllDrags($mode === 'preview' || $connectingInput.opened)
|
||||
|
||||
function setAllDrags(enable: boolean) {
|
||||
const fct = enable ? disableDrag : enableDrag
|
||||
|
||||
$app.grid.map((gridItem) => {
|
||||
const disabledGridItem = fct(gridItem)
|
||||
|
||||
if (disabledGridItem?.data?.subGrids) {
|
||||
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map(
|
||||
(subgrid: GridItem[]) => subgrid?.map((subgridItem: GridItem) => fct(subgridItem)) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
return disabledGridItem
|
||||
})
|
||||
|
||||
Object.values($app.subgrids ?? {}).map(
|
||||
(subgrid: GridItem[]) => subgrid?.map((subgridItem: GridItem) => fct(subgridItem)) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
function removeGridElement(component) {
|
||||
if (component) {
|
||||
$app.grid = $app.grid.filter((gridComponent) => {
|
||||
@@ -57,6 +83,10 @@
|
||||
return gridComponent.data.id !== component?.id
|
||||
})
|
||||
|
||||
// Delete static inputs
|
||||
delete $staticOutputs[component.id]
|
||||
$staticOutputs = $staticOutputs
|
||||
|
||||
delete $runnableComponents[component.id]
|
||||
$runnableComponents = $runnableComponents
|
||||
|
||||
@@ -65,11 +95,12 @@
|
||||
}
|
||||
|
||||
function selectComponent(id: string) {
|
||||
if (!$connectingInput.opened) {
|
||||
if ($selectedComponent !== id) {
|
||||
$selectedComponent = id
|
||||
}
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -138,62 +169,64 @@
|
||||
<div class={!$focusedGrid && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
|
||||
<Grid
|
||||
onTopId={$selectedComponent}
|
||||
items={$app.grid}
|
||||
on:redraw={(e) => {
|
||||
push(history, $app)
|
||||
$app.grid = e.detail
|
||||
}}
|
||||
fillSpace={false}
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
rowHeight={36}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
gap={[4, 2]}
|
||||
>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
on:pointerenter={() => ($connectingInput.hoveredComponent = dataItem.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:click|stopPropagation
|
||||
on:pointerdown={() => selectComponent(dataItem.data.id)}
|
||||
class={classNames(
|
||||
'h-full w-full center-center',
|
||||
$selectedComponent === dataItem.data.id ? 'active-grid-item' : ''
|
||||
)}
|
||||
>
|
||||
<Component
|
||||
render={true}
|
||||
{pointerdown}
|
||||
component={dataItem.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(dataItem)}
|
||||
on:delete={() => removeGridElement(dataItem.data)}
|
||||
on:lock={() => {
|
||||
const gridItem = findGridItem($app, dataItem.data.id)
|
||||
if (gridItem) {
|
||||
toggleFixed(gridItem)
|
||||
}
|
||||
$app = $app
|
||||
}}
|
||||
on:expand={() => {
|
||||
push(history, $app)
|
||||
$selectedComponent = dataItem.data.id
|
||||
expandGriditem($app.grid, dataItem, $breakpoint)
|
||||
$app = $app
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{#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: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
|
||||
render={true}
|
||||
{pointerdown}
|
||||
component={gridComponent.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(gridComponent)}
|
||||
on:delete={() => removeGridElement(gridComponent.data)}
|
||||
on:lock={() => {
|
||||
const gridItem = findGridItem($app, gridComponent.data.id)
|
||||
if (gridItem) {
|
||||
toggleFixed(gridItem)
|
||||
}
|
||||
$app = $app
|
||||
}}
|
||||
on:expand={() => {
|
||||
push(history, $app)
|
||||
$selectedComponent = gridComponent.data.id
|
||||
expandGriditem($app.grid, gridComponent, $breakpoint)
|
||||
$app = $app
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
@@ -207,7 +240,7 @@
|
||||
inlineScript={script.inlineScript}
|
||||
name={script.name}
|
||||
fields={script.fields}
|
||||
autoRefresh={script.autoRefresh ?? false}
|
||||
bind:staticOutputs={$staticOutputs[`bg_${index}`]}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -6,11 +6,9 @@
|
||||
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
|
||||
export let gridItems: GridItem[]
|
||||
export let parent: string | undefined
|
||||
|
||||
//$: sortedItems = gridItems.sort((a, b) => a.id.localeCompare - b.id)
|
||||
</script>
|
||||
|
||||
{#each gridItems as gridItem (gridItem.id)}
|
||||
{#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'}
|
||||
@@ -23,7 +21,6 @@
|
||||
bind:component={actionButton}
|
||||
duplicateMoveAllowed={false}
|
||||
onDelete={() => {
|
||||
//@ts-ignore
|
||||
gridItem.data.actionButtons = gridItem.data.actionButtons.filter(
|
||||
(c) => c.id !== actionButton.id
|
||||
)
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount, createEventDispatcher } from 'svelte'
|
||||
|
||||
import type { FilledItem } from '../svelte-grid/types'
|
||||
import { getColumn, throttle } from '../svelte-grid/utils/other'
|
||||
import { getContainerHeight } from '../svelte-grid/utils/container'
|
||||
import { specifyUndefinedColumns } from '../svelte-grid/utils/item'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
type T = $$Generic
|
||||
|
||||
export let items: FilledItem<T>[]
|
||||
export let rowHeight: number
|
||||
export let cols: [number, number][]
|
||||
export let gap = [10, 10]
|
||||
export let throttleUpdate = 100
|
||||
|
||||
export let onTopId: string | undefined = undefined
|
||||
export let containerWidth: number | undefined = undefined
|
||||
|
||||
export let parentWidth: number | undefined = undefined
|
||||
|
||||
let getComputedCols
|
||||
|
||||
let container
|
||||
|
||||
$: [gapX, gapY] = gap
|
||||
|
||||
let xPerPx = 0
|
||||
let yPerPx = rowHeight
|
||||
|
||||
$: containerHeight = getContainerHeight(items, yPerPx, getComputedCols)
|
||||
|
||||
const onResize = throttle(() => {
|
||||
items = specifyUndefinedColumns(items, getComputedCols, cols)
|
||||
dispatch('resize', {
|
||||
cols: getComputedCols,
|
||||
xPerPx,
|
||||
yPerPx,
|
||||
width: containerWidth
|
||||
})
|
||||
}, throttleUpdate)
|
||||
|
||||
onMount(() => {
|
||||
const sizeObserver = new ResizeObserver((entries) => {
|
||||
requestAnimationFrame(() => {
|
||||
let width = entries[0].contentRect.width
|
||||
|
||||
if (width === containerWidth) return
|
||||
|
||||
getComputedCols = getColumn(parentWidth ?? width, cols)
|
||||
|
||||
xPerPx = width / getComputedCols
|
||||
|
||||
if (!containerWidth) {
|
||||
items = specifyUndefinedColumns(items, getComputedCols, cols)
|
||||
|
||||
dispatch('mount', {
|
||||
cols: getComputedCols,
|
||||
xPerPx,
|
||||
yPerPx // same as rowHeight
|
||||
})
|
||||
} else {
|
||||
onResize()
|
||||
}
|
||||
|
||||
containerWidth = width
|
||||
})
|
||||
})
|
||||
|
||||
sizeObserver.observe(container)
|
||||
|
||||
return () => sizeObserver.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="svlt-grid-container" style="height: {containerHeight}px" bind:this={container}>
|
||||
{#if xPerPx}
|
||||
{#each items as item (item.id)}
|
||||
{@const onTop = item.id == onTopId}
|
||||
{@const width =
|
||||
Math.min(getComputedCols, item[getComputedCols] && item[getComputedCols].w) * xPerPx -
|
||||
gapX * 2}
|
||||
{@const height = (item[getComputedCols] && item[getComputedCols].h) * yPerPx - gapY * 2}
|
||||
{@const top = (item[getComputedCols] && item[getComputedCols].y) * yPerPx + gapY}
|
||||
{@const left = (item[getComputedCols] && item[getComputedCols].x) * xPerPx + gapX}
|
||||
<div
|
||||
class="svlt-grid-item"
|
||||
style="width: {width}px; height:{height}px; {onTop
|
||||
? 'z-index: 1000;'
|
||||
: ''} top: {top}px; left: {left}px;"
|
||||
>
|
||||
{#if item[getComputedCols]}
|
||||
<slot dataItem={item} item={item[getComputedCols]} />
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.svlt-grid-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.svlt-grid-item {
|
||||
touch-action: none;
|
||||
position: absolute;
|
||||
will-change: auto;
|
||||
backface-visibility: hidden;
|
||||
-webkit-backface-visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@
|
||||
import Button from '../../common/button/Button.svelte'
|
||||
import type { AppViewerContext } from '../types'
|
||||
|
||||
const { runnableComponents, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
|
||||
let loading: boolean = false
|
||||
let timeout: NodeJS.Timer | undefined = undefined
|
||||
let interval: number | undefined = undefined
|
||||
@@ -36,12 +36,6 @@
|
||||
loading = true
|
||||
Promise.all(
|
||||
Object.keys($runnableComponents).map((id) => {
|
||||
if (id.startsWith('bg_')) {
|
||||
let index = parseInt(id.split('_')[1])
|
||||
if (!$app.hiddenInlineScripts[index]?.autoRefresh) {
|
||||
return
|
||||
}
|
||||
}
|
||||
return $runnableComponents?.[id]?.()
|
||||
})
|
||||
).finally(() => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '../types'
|
||||
import GridPanel from './GridPanel.svelte'
|
||||
@@ -21,27 +20,15 @@
|
||||
|
||||
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
|
||||
{#if $selectedComponent === `bg_${index}`}
|
||||
<div class="min-h-full flex flex-col divide-y">
|
||||
<PanelSection title={`Configuration`}>
|
||||
<Toggle
|
||||
bind:checked={script.autoRefresh}
|
||||
options={{ right: 'Run on start and app refresh' }}
|
||||
<PanelSection title={`Background script inputs`}>
|
||||
{#key $stateId}
|
||||
<InputsSpecsEditor
|
||||
id={`bg_${index}`}
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={script.fields}
|
||||
userInputEnabled={false}
|
||||
/>
|
||||
</PanelSection>
|
||||
|
||||
{#if Object.keys(script.fields).length > 0}
|
||||
<PanelSection title={`Inputs`}>
|
||||
{#key $stateId}
|
||||
<InputsSpecsEditor
|
||||
id={`bg_${index}`}
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={script.fields}
|
||||
userInputEnabled={false}
|
||||
/>
|
||||
{/key}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
<div class="grow shrink" />
|
||||
</div>
|
||||
{/key}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
@@ -5,13 +5,11 @@
|
||||
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
|
||||
import type { AppEditorContext, AppViewerContext, GridItem } from '../types'
|
||||
import Component from './component/Component.svelte'
|
||||
import { expandGriditem, findGridItem, sortGridItemsPosition } from './appUtils'
|
||||
import { expandGriditem, findGridItem } from './appUtils'
|
||||
import { push } from '$lib/history'
|
||||
import Grid from '../svelte-grid/Grid.svelte'
|
||||
import GridViewer from './GridViewer.svelte'
|
||||
|
||||
export let containerHeight: number
|
||||
export let containerWidth: number | undefined = undefined
|
||||
let classes = ''
|
||||
export { classes as class }
|
||||
export let style = ''
|
||||
@@ -50,14 +48,18 @@
|
||||
function selectComponent(id: string) {
|
||||
onComponent = id
|
||||
if (!$connectingInput.opened) {
|
||||
if (id !== $selectedComponent) {
|
||||
$selectedComponent = id
|
||||
$selectedComponent = id
|
||||
/*
|
||||
$focusedGrid = {
|
||||
parentComponentId: parentId,
|
||||
subGridIndex: index
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
function lock(dataItem: GridItem) {
|
||||
let fComponent = findGridItem($app, dataItem.data.id)
|
||||
function lock(gridComponent: GridItem) {
|
||||
let fComponent = findGridItem($app, gridComponent.data.id)
|
||||
if (fComponent) {
|
||||
fComponent = toggleFixed(fComponent)
|
||||
}
|
||||
@@ -69,9 +71,7 @@
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="translate-x-0 translate-y-0 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
|
||||
@@ -86,94 +86,68 @@
|
||||
on:pointerup={onpointerup}
|
||||
style="height: {containerHeight}px; {style ?? ''}"
|
||||
>
|
||||
{#if $mode !== 'preview'}
|
||||
<div class={highlight ? 'border-gray-400 border border-dashed' : ''}>
|
||||
<Grid
|
||||
items={subGrid}
|
||||
on:redraw={(e) => {
|
||||
push(history, $app)
|
||||
subGrid = e.detail
|
||||
}}
|
||||
let:dataItem
|
||||
rowHeight={36}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
gap={[4, 2]}
|
||||
scroller={container}
|
||||
parentWidth={$parentWidth - 17}
|
||||
{containerWidth}
|
||||
>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
on:pointerenter={() => ($connectingInput.hoveredComponent = dataItem.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:pointerdown={() => selectComponent(dataItem.data.id)}
|
||||
class={classNames(
|
||||
'h-full w-full center-center',
|
||||
$selectedComponent === dataItem.data.id ? 'active-grid-item' : '',
|
||||
'top-0'
|
||||
)}
|
||||
>
|
||||
<Component
|
||||
render={visible}
|
||||
{pointerdown}
|
||||
component={dataItem.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(dataItem)}
|
||||
on:lock={() => lock(dataItem)}
|
||||
on:expand={() => {
|
||||
const parentGridItem = findGridItem($app, id)
|
||||
|
||||
if (!parentGridItem) {
|
||||
return
|
||||
}
|
||||
$selectedComponent = dataItem.data.id
|
||||
push(history, $app)
|
||||
|
||||
expandGriditem(subGrid, dataItem, $breakpoint, parentGridItem)
|
||||
$app = $app
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
</div>
|
||||
{:else}
|
||||
<GridViewer
|
||||
items={subGrid}
|
||||
<div class={highlight && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
|
||||
<Grid
|
||||
bind:items={subGrid}
|
||||
let:dataItem
|
||||
rowHeight={36}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
gap={[4, 2]}
|
||||
scroller={container}
|
||||
parentWidth={$parentWidth - 17}
|
||||
{containerWidth}
|
||||
>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:pointerdown={() => selectComponent(dataItem.data.id)}
|
||||
class={classNames('h-full w-full center-center', 'top-0')}
|
||||
>
|
||||
<Component
|
||||
render={visible}
|
||||
{pointerdown}
|
||||
component={dataItem.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(dataItem)}
|
||||
/>
|
||||
</div>
|
||||
</GridViewer>
|
||||
{/if}
|
||||
{#each subGrid 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: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' : '',
|
||||
'top-0'
|
||||
)}
|
||||
>
|
||||
<Component
|
||||
render={visible}
|
||||
{pointerdown}
|
||||
component={gridComponent.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(gridComponent)}
|
||||
on:lock={() => lock(gridComponent)}
|
||||
on:expand={() => {
|
||||
const parentGridItem = findGridItem($app, id)
|
||||
|
||||
if (!parentGridItem) {
|
||||
return
|
||||
}
|
||||
$selectedComponent = gridComponent.data.id
|
||||
push(history, $app)
|
||||
|
||||
expandGriditem(subGrid, gridComponent, $breakpoint, parentGridItem)
|
||||
$app = $app
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</Grid>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { gridColumns } from '../gridUtils'
|
||||
import { allItems } from '../utils'
|
||||
import type { Output, World } from '../rx'
|
||||
import gridHelp from '../svelte-grid/utils/helper'
|
||||
import type { FilledItem } from '../svelte-grid/types'
|
||||
|
||||
function findGridItemById(
|
||||
root: GridItem[],
|
||||
@@ -35,12 +34,15 @@ export function findGridItemParentGrid(app: App, id: string): string | undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function allsubIds(app: App, parentId: string): string[] {
|
||||
let item = findGridItem(app, parentId)
|
||||
if (!item?.data.numberOfSubgrids) {
|
||||
return [parentId]
|
||||
export function isIdInsideGriditem(app: App, gridItem: GridItem, id: string | undefined): boolean {
|
||||
const path: string[] = []
|
||||
let currentId = id
|
||||
while (currentId) {
|
||||
path.push(currentId)
|
||||
currentId = findGridItemParentGrid(app, currentId)?.split('-')[0]
|
||||
}
|
||||
return getAllSubgridsAndComponentIds(app, item?.data)[1]
|
||||
|
||||
return path.includes(gridItem.id)
|
||||
}
|
||||
|
||||
export function findGridItem(app: App, id: string): GridItem | undefined {
|
||||
@@ -296,16 +298,19 @@ type Outputtable<Type> = {
|
||||
}
|
||||
|
||||
export function initOutput<I extends Record<string, any>>(
|
||||
world: World | undefined,
|
||||
world: World,
|
||||
id: string,
|
||||
init: I
|
||||
): Outputtable<I> {
|
||||
if (!world) {
|
||||
return {} as any
|
||||
const output = world.outputsById[id] as Outputtable<I>
|
||||
if (init) {
|
||||
for (const key in init) {
|
||||
if (output && output[key] && output[key].peak() == undefined) {
|
||||
output[key].set(init[key] as any)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries(init).map(([key, value]) => [key, world.newOutput(id, key, value)])
|
||||
) as Outputtable<I>
|
||||
return output
|
||||
}
|
||||
export function expandGriditem(
|
||||
grid: GridItem[],
|
||||
@@ -329,11 +334,13 @@ export function expandGriditem(
|
||||
item.h = item.h + top + bottom
|
||||
}
|
||||
|
||||
export function sortGridItemsPosition<T>(
|
||||
gridItems: FilledItem<T>[],
|
||||
width: number
|
||||
): FilledItem<T>[] {
|
||||
return gridItems.sort((a: FilledItem<T>, b: FilledItem<T>) => {
|
||||
export function sortGridItemsPosition(
|
||||
gridItems: GridItem[],
|
||||
breakpoint: EditorBreakpoint
|
||||
): GridItem[] {
|
||||
return gridItems.sort((a: GridItem, b: GridItem) => {
|
||||
const width = breakpoint === 'lg' ? 12 : 3
|
||||
|
||||
const aX = a[width].x
|
||||
const aY = a[width].y
|
||||
const bX = b[width].x
|
||||
@@ -376,32 +383,3 @@ export function connectInput(
|
||||
|
||||
return connectingInput
|
||||
}
|
||||
|
||||
export function recursivelyFilterKeyInJSON(
|
||||
json: object,
|
||||
search: string,
|
||||
extraSearch?: string | undefined
|
||||
): object {
|
||||
if (!search || search == '') {
|
||||
return json
|
||||
}
|
||||
let filteredJSON = {}
|
||||
Object.keys(json).forEach((key) => {
|
||||
if (
|
||||
key.toLowerCase().includes(search.toLowerCase()) ||
|
||||
extraSearch?.toLowerCase().includes(search.toLowerCase()) ||
|
||||
(typeof json[key] === 'string' && json[key].toLowerCase().includes(search.toLowerCase())) ||
|
||||
(typeof json[key] === 'number' &&
|
||||
json[key].toString().toLowerCase().includes(search.toLowerCase()))
|
||||
) {
|
||||
filteredJSON[key] = json[key]
|
||||
} else if (typeof json[key] === 'object') {
|
||||
const res = recursivelyFilterKeyInJSON(json[key], search, extraSearch)
|
||||
|
||||
if (Object.keys(res).length !== 0) {
|
||||
filteredJSON[key] = res
|
||||
}
|
||||
}
|
||||
})
|
||||
return filteredJSON
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
@@ -47,12 +48,17 @@
|
||||
export let pointerdown: boolean = false
|
||||
export let render: boolean
|
||||
|
||||
const { mode, connectingInput, app, errorByComponent } =
|
||||
const { staticOutputs, mode, connectingInput, app, errorByComponent } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
let hover = false
|
||||
let initializing: boolean | undefined = undefined
|
||||
let componentContainerHeight: number = 0
|
||||
|
||||
// let component = JSON.parse(JSON.stringify({ conf }))
|
||||
// $: if (!deepEqual(component, pComponent)) {
|
||||
// component = JSON.parse(JSON.stringify(pComponent))
|
||||
// }
|
||||
|
||||
$: componentWithErrors = Object.values($errorByComponent).map((e) => e.componentId)
|
||||
$: hasError = componentWithErrors.includes(component.id)
|
||||
</script>
|
||||
@@ -60,7 +66,7 @@
|
||||
<div
|
||||
on:pointerenter={() => (hover = true)}
|
||||
on:pointerleave={() => (hover = false)}
|
||||
class="h-full flex flex-col w-full component {initializing ? 'overflow-hidden h-0' : ''}"
|
||||
class="h-full flex flex-col w-full component"
|
||||
>
|
||||
{#if $mode !== 'preview'}
|
||||
<ComponentHeader
|
||||
@@ -76,10 +82,18 @@
|
||||
{/if}
|
||||
|
||||
<div
|
||||
on:pointerdown={(e) => {
|
||||
// Removed in https://github.com/windmill-labs/windmill/pull/1171
|
||||
// In case of a bug, try stopping propagation on the native event
|
||||
// and dispatch a custom event: `e?.stopPropagation(); dispatch('select');`
|
||||
// if ($mode === 'preview') {
|
||||
// e?.stopPropagation()
|
||||
// }
|
||||
}}
|
||||
class={twMerge(
|
||||
'h-full bg-white/40',
|
||||
selected && $mode !== 'preview' ? 'border border-blue-500' : '',
|
||||
!selected && $mode !== 'preview' ? 'border-gray-100' : '',
|
||||
!selected && $mode !== 'preview' && !component.card ? 'border-gray-100' : '',
|
||||
$mode !== 'preview' && !$connectingInput.opened ? 'hover:border-blue-500' : '',
|
||||
component.softWrap || hasError ? '' : 'overflow-auto',
|
||||
$mode != 'preview' ? 'cursor-pointer' : '',
|
||||
@@ -95,6 +109,7 @@
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'barchartcomponent'}
|
||||
@@ -104,6 +119,7 @@
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'timeseriescomponent'}
|
||||
@@ -113,6 +129,7 @@
|
||||
configuration={component.configuration}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'htmlcomponent'}
|
||||
@@ -121,6 +138,7 @@
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'vegalitecomponent'}
|
||||
@@ -129,6 +147,7 @@
|
||||
id={component.id}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'plotlycomponent'}
|
||||
@@ -136,6 +155,7 @@
|
||||
id={component.id}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'scatterchartcomponent'}
|
||||
@@ -145,6 +165,7 @@
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'piechartcomponent'}
|
||||
@@ -153,6 +174,7 @@
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
componentInput={component.componentInput}
|
||||
{render}
|
||||
/>
|
||||
@@ -162,6 +184,7 @@
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
componentInput={component.componentInput}
|
||||
bind:actionButtons={component.actionButtons}
|
||||
{render}
|
||||
@@ -171,6 +194,7 @@
|
||||
id={component.id}
|
||||
configuration={component.configuration}
|
||||
bind:initializing
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
componentInput={component.componentInput}
|
||||
{render}
|
||||
/>
|
||||
@@ -183,6 +207,7 @@
|
||||
customCss={component.customCss}
|
||||
bind:initializing
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'buttoncomponent'}
|
||||
@@ -193,8 +218,8 @@
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
recomputeIds={component.recomputeIds}
|
||||
bind:initializing
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'}
|
||||
@@ -204,6 +229,7 @@
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'multiselectcomponent'}
|
||||
@@ -213,6 +239,7 @@
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'formcomponent'}
|
||||
@@ -222,6 +249,7 @@
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
recomputeIds={component.recomputeIds}
|
||||
{render}
|
||||
/>
|
||||
@@ -233,6 +261,7 @@
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
componentInput={component.componentInput}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
recomputeIds={component.recomputeIds}
|
||||
{render}
|
||||
/>
|
||||
@@ -243,6 +272,7 @@
|
||||
horizontalAlignment={component.horizontalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'textinputcomponent'}
|
||||
@@ -251,6 +281,7 @@
|
||||
verticalAlignment={component.verticalAlignment}
|
||||
configuration={component.configuration}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'emailinputcomponent'}
|
||||
@@ -261,6 +292,7 @@
|
||||
appCssKey="emailinputcomponent"
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'passwordinputcomponent'}
|
||||
@@ -271,6 +303,7 @@
|
||||
appCssKey="passwordinputcomponent"
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'dateinputcomponent'}
|
||||
@@ -280,6 +313,7 @@
|
||||
inputType="date"
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'numberinputcomponent'}
|
||||
@@ -288,6 +322,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'currencycomponent'}
|
||||
@@ -296,6 +331,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'slidercomponent'}
|
||||
@@ -304,6 +340,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'horizontaldividercomponent'}
|
||||
@@ -332,6 +369,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'tabscomponent'}
|
||||
@@ -340,15 +378,16 @@
|
||||
id={component.id}
|
||||
tabs={component.tabs}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{componentContainerHeight}
|
||||
{render}
|
||||
bind:initializing
|
||||
/>
|
||||
{:else if component.type === 'containercomponent'}
|
||||
<AppContainer
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{componentContainerHeight}
|
||||
{render}
|
||||
/>
|
||||
@@ -378,6 +417,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'fileinputcomponent'}
|
||||
@@ -385,6 +425,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'imagecomponent'}
|
||||
@@ -392,6 +433,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'drawercomponent'}
|
||||
@@ -408,6 +450,7 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{:else if component.type === 'pdfcomponent'}
|
||||
@@ -415,13 +458,17 @@
|
||||
configuration={component.configuration}
|
||||
id={component.id}
|
||||
customCss={component.customCss}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
{render}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if initializing}
|
||||
<div class="absolute inset-0 center-center flex-col bg-white text-gray-600 border">
|
||||
<div
|
||||
out:fade|local={{ duration: 200 }}
|
||||
class="absolute inset-0 center-center flex-col bg-white text-gray-600 border"
|
||||
>
|
||||
<Loader2 class="animate-spin" size={16} />
|
||||
<span class="text-xs mt-1">Loading</span>
|
||||
</div>
|
||||
|
||||
@@ -4,51 +4,56 @@
|
||||
findGridItem,
|
||||
findGridItemParentGrid,
|
||||
getAllSubgridsAndComponentIds,
|
||||
insertNewGridItem
|
||||
insertNewGridItem,
|
||||
sortGridItemsPosition
|
||||
} from '../appUtils'
|
||||
import type { AppEditorContext, AppViewerContext, GridItem } from '../../types'
|
||||
import type { AppEditorContext, AppViewerContext, EditorBreakpoint, GridItem } from '../../types'
|
||||
import { push } from '$lib/history'
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
|
||||
const { app, selectedComponent, worldStore, focusedGrid, componentControl } =
|
||||
const { app, selectedComponent, breakpoint, focusedGrid } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { componentControl, history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let tempGridItem: GridItem | undefined = undefined
|
||||
let copiedGridItem: GridItem | undefined = undefined
|
||||
|
||||
function getSortedGridItemsOfChildren(): GridItem[] {
|
||||
if (!$focusedGrid) {
|
||||
return $app.grid
|
||||
return sortGridItemsPosition($app.grid, $breakpoint)
|
||||
}
|
||||
|
||||
if (!$app.subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return $app.subgrids[`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`] ?? []
|
||||
return sortGridItemsPosition(
|
||||
$app.subgrids[`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`],
|
||||
$breakpoint
|
||||
)
|
||||
}
|
||||
|
||||
function getGridItems(): GridItem[] {
|
||||
function getSortedGridItemsOfGrid(): GridItem[] {
|
||||
if ($app.grid.find((item) => item.id === $selectedComponent)) {
|
||||
return $app.grid
|
||||
return sortGridItemsPosition($app.grid, $breakpoint)
|
||||
}
|
||||
|
||||
if (!$app.subgrids) {
|
||||
return []
|
||||
}
|
||||
|
||||
return (
|
||||
return sortGridItemsPosition(
|
||||
Object.values($app.subgrids ?? {}).find((grid) =>
|
||||
grid.find((item) => item.id === $selectedComponent)
|
||||
) ?? []
|
||||
) ?? [],
|
||||
$breakpoint
|
||||
)
|
||||
}
|
||||
|
||||
function left(event: KeyboardEvent) {
|
||||
if (!$componentControl[$selectedComponent!]?.left?.()) {
|
||||
const sortedGridItems = getGridItems()
|
||||
const sortedGridItems = getSortedGridItemsOfGrid()
|
||||
const currentIndex = sortedGridItems.findIndex((item) => item.id === $selectedComponent)
|
||||
|
||||
if (currentIndex !== -1 && currentIndex > 0) {
|
||||
@@ -62,7 +67,7 @@
|
||||
function right(event: KeyboardEvent) {
|
||||
let r = $componentControl[$selectedComponent!]?.right?.()
|
||||
if (!r) {
|
||||
const sortedGridItems = getGridItems()
|
||||
const sortedGridItems = getSortedGridItemsOfGrid()
|
||||
const currentIndex = sortedGridItems.findIndex((item) => item.id === $selectedComponent)
|
||||
|
||||
if (currentIndex !== -1 && currentIndex < sortedGridItems.length - 1) {
|
||||
@@ -85,8 +90,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (subgrid) {
|
||||
$selectedComponent = subgrid[0].id
|
||||
const sortedGridItems = sortGridItemsPosition(subgrid, $breakpoint)
|
||||
|
||||
if (sortedGridItems) {
|
||||
$selectedComponent = sortedGridItems[0].id
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
@@ -164,16 +171,10 @@
|
||||
insertNewGridItem($app, copiedGridItem.data, $focusedGrid, false)
|
||||
}
|
||||
|
||||
$worldStore = $worldStore
|
||||
$app = $app
|
||||
}
|
||||
|
||||
function keydown(event: KeyboardEvent) {
|
||||
// Ignore keydown events if the user is typing in monaco
|
||||
let classes = event.target?.['className']
|
||||
if (typeof classes === 'string' && classes.includes('inputarea')) {
|
||||
return
|
||||
}
|
||||
switch (event.key) {
|
||||
case 'Escape':
|
||||
handleEscape(event)
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
FileText,
|
||||
AtSignIcon
|
||||
} from 'lucide-svelte'
|
||||
import type { BaseAppComponent, GridItem } from '../../types'
|
||||
import type { BaseAppComponent } from '../../types'
|
||||
import type { Size } from '../../svelte-grid/types'
|
||||
|
||||
type BaseComponent<T extends string> = {
|
||||
@@ -68,7 +68,7 @@ export type BarChartComponent = BaseComponent<'barchartcomponent'>
|
||||
export type PieChartComponent = BaseComponent<'piechartcomponent'>
|
||||
export type ScatterChartComponent = BaseComponent<'scatterchartcomponent'>
|
||||
export type TableComponent = BaseComponent<'tablecomponent'> & {
|
||||
actionButtons: (BaseAppComponent & ButtonComponent & GridItem)[]
|
||||
actionButtons: (BaseAppComponent & ButtonComponent)[]
|
||||
}
|
||||
export type AggridComponent = BaseComponent<'aggridcomponent'>
|
||||
export type DisplayComponent = BaseComponent<'displaycomponent'>
|
||||
@@ -180,7 +180,8 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
customCss: {
|
||||
header: { class: '', style: '' },
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
containercomponent: {
|
||||
@@ -191,12 +192,19 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
softWrap: true,
|
||||
id: '',
|
||||
type: 'containercomponent',
|
||||
configuration: {},
|
||||
configuration: {
|
||||
noPadding: {
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const,
|
||||
componentInput: undefined,
|
||||
|
||||
card: false,
|
||||
numberOfSubgrids: 1
|
||||
}
|
||||
},
|
||||
@@ -205,7 +213,7 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
icon: Type,
|
||||
dims: '1:1-3:1',
|
||||
data: {
|
||||
softWrap: true,
|
||||
softWrap: false,
|
||||
horizontalAlignment: 'left',
|
||||
verticalAlignment: 'top',
|
||||
id: '',
|
||||
@@ -229,17 +237,17 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
fieldType: 'boolean',
|
||||
onlyStatic: true
|
||||
},
|
||||
tooltip: {
|
||||
fitContent: {
|
||||
type: 'static',
|
||||
value: '',
|
||||
fieldType: 'text',
|
||||
onlyStatic: true,
|
||||
tooltip: 'Tooltip text if not empty'
|
||||
value: false,
|
||||
fieldType: 'boolean',
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
text: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
buttoncomponent: {
|
||||
@@ -296,7 +304,7 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
value: ''
|
||||
},
|
||||
gotoNewTab: {
|
||||
tooltip: 'Go to an url on a new tab',
|
||||
tooltip: 'Go to create a new tab',
|
||||
fieldType: 'boolean',
|
||||
type: 'static',
|
||||
value: true
|
||||
@@ -322,7 +330,8 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
},
|
||||
customCss: {
|
||||
button: { style: '', class: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
formcomponent: {
|
||||
@@ -370,7 +379,8 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
customCss: {
|
||||
container: { class: '', style: '' },
|
||||
button: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: true
|
||||
}
|
||||
},
|
||||
formbuttoncomponent: {
|
||||
@@ -413,7 +423,8 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
customCss: {
|
||||
button: { class: '', style: '' },
|
||||
popup: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: true
|
||||
}
|
||||
},
|
||||
piechartcomponent: {
|
||||
@@ -445,7 +456,8 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: true
|
||||
}
|
||||
},
|
||||
barchartcomponent: {
|
||||
@@ -477,7 +489,8 @@ export const components: Record<AppComponent['type'], AppComponentConfig> = {
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: true
|
||||
}
|
||||
},
|
||||
htmlcomponent: {
|
||||
@@ -501,7 +514,8 @@ Hello \${ctx.username}
|
||||
configuration: {},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
vegalitecomponent: {
|
||||
@@ -537,10 +551,11 @@ Hello \${ctx.username}
|
||||
onlyStatic: true,
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
tooltip: 'Use the canvas renderer instead of the svg one for more interactive plots'
|
||||
tooltip: 'use the canvas renderer instead of the svg one for more interactive plots'
|
||||
}
|
||||
},
|
||||
customCss: {}
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
plotlycomponent: {
|
||||
@@ -567,7 +582,8 @@ Hello \${ctx.username}
|
||||
}
|
||||
},
|
||||
configuration: {},
|
||||
customCss: {}
|
||||
customCss: {},
|
||||
card: false
|
||||
}
|
||||
},
|
||||
timeseriescomponent: {
|
||||
@@ -642,7 +658,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: true
|
||||
}
|
||||
},
|
||||
scatterchartcomponent: {
|
||||
@@ -693,7 +710,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: true
|
||||
}
|
||||
},
|
||||
tablecomponent: {
|
||||
@@ -735,7 +753,7 @@ Hello \${ctx.username}
|
||||
tableBody: { class: '', style: '' },
|
||||
tableFooter: { class: '', style: '' }
|
||||
} as const,
|
||||
|
||||
card: true,
|
||||
actionButtons: []
|
||||
}
|
||||
},
|
||||
@@ -757,8 +775,7 @@ Hello \${ctx.username}
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true,
|
||||
tooltip: 'Configure all columns as Editable by users'
|
||||
onlyStatic: true
|
||||
},
|
||||
pagination: {
|
||||
type: 'static',
|
||||
@@ -770,8 +787,7 @@ Hello \${ctx.username}
|
||||
type: 'static',
|
||||
fieldType: 'number',
|
||||
value: 10,
|
||||
onlyStatic: true,
|
||||
tooltip: 'Number of rows per page'
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
@@ -791,7 +807,8 @@ Hello \${ctx.username}
|
||||
}
|
||||
]
|
||||
},
|
||||
customCss: {}
|
||||
customCss: {},
|
||||
card: true
|
||||
}
|
||||
},
|
||||
checkboxcomponent: {
|
||||
@@ -818,7 +835,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
text: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
textinputcomponent: {
|
||||
@@ -846,7 +864,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
input: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
selectcomponent: {
|
||||
@@ -872,8 +891,7 @@ Hello \${ctx.username}
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true,
|
||||
tooltip: 'Allows user to manually add new value'
|
||||
onlyStatic: true
|
||||
},
|
||||
placeholder: {
|
||||
type: 'static',
|
||||
@@ -890,7 +908,7 @@ Hello \${ctx.username}
|
||||
customCss: {
|
||||
input: { style: '' }
|
||||
} as const,
|
||||
|
||||
card: false,
|
||||
softWrap: true
|
||||
}
|
||||
},
|
||||
@@ -920,7 +938,7 @@ Hello \${ctx.username}
|
||||
customCss: {
|
||||
input: { style: '' }
|
||||
} as const,
|
||||
|
||||
card: false,
|
||||
softWrap: true
|
||||
}
|
||||
},
|
||||
@@ -950,7 +968,7 @@ Hello \${ctx.username}
|
||||
customCss: {
|
||||
input: { style: '' }
|
||||
} as const,
|
||||
|
||||
card: false,
|
||||
softWrap: true
|
||||
}
|
||||
},
|
||||
@@ -989,13 +1007,13 @@ Hello \${ctx.username}
|
||||
step: {
|
||||
type: 'static',
|
||||
value: 1,
|
||||
fieldType: 'number',
|
||||
tooltip: 'Spread between each number suggestion'
|
||||
fieldType: 'number'
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
input: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
currencycomponent: {
|
||||
@@ -1032,13 +1050,13 @@ Hello \${ctx.username}
|
||||
value: 'en-US',
|
||||
fieldType: 'select',
|
||||
onlyStatic: true,
|
||||
optionValuesKey: 'localeOptions',
|
||||
tooltip: 'Currency format'
|
||||
optionValuesKey: 'localeOptions'
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
input: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
slidercomponent: {
|
||||
@@ -1071,8 +1089,7 @@ Hello \${ctx.username}
|
||||
step: {
|
||||
type: 'static',
|
||||
value: 1,
|
||||
fieldType: 'number',
|
||||
tooltip: 'Spread between each number suggestion'
|
||||
fieldType: 'number'
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
@@ -1080,7 +1097,8 @@ Hello \${ctx.username}
|
||||
handle: { style: '' },
|
||||
limits: { class: '', style: '' },
|
||||
value: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
rangecomponent: {
|
||||
@@ -1117,8 +1135,7 @@ Hello \${ctx.username}
|
||||
step: {
|
||||
type: 'static',
|
||||
value: 1,
|
||||
fieldType: 'number',
|
||||
tooltip: 'Spread between each number suggestion'
|
||||
fieldType: 'number'
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
@@ -1126,7 +1143,8 @@ Hello \${ctx.username}
|
||||
bar: { style: '' },
|
||||
limits: { class: '', style: '' },
|
||||
values: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
passwordinputcomponent: {
|
||||
@@ -1149,7 +1167,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
input: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
emailinputcomponent: {
|
||||
@@ -1177,7 +1196,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
input: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
dateinputcomponent: {
|
||||
@@ -1209,7 +1229,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
input: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
tabscomponent: {
|
||||
@@ -1221,12 +1242,11 @@ Hello \${ctx.username}
|
||||
id: '',
|
||||
type: 'tabscomponent',
|
||||
configuration: {
|
||||
tabsKind: {
|
||||
fieldType: 'select',
|
||||
noPadding: {
|
||||
type: 'static',
|
||||
onlyStatic: true,
|
||||
optionValuesKey: 'tabsKindOptions',
|
||||
value: 'tabs'
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true
|
||||
}
|
||||
},
|
||||
customCss: {
|
||||
@@ -1236,7 +1256,7 @@ Hello \${ctx.username}
|
||||
container: { class: '', style: '' }
|
||||
} as const,
|
||||
componentInput: undefined,
|
||||
|
||||
card: false,
|
||||
numberOfSubgrids: 2,
|
||||
tabs: ['First tab', 'Second tab']
|
||||
}
|
||||
@@ -1279,7 +1299,8 @@ Hello \${ctx.username}
|
||||
customCss: {
|
||||
container: { class: '', style: '' },
|
||||
icon: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
horizontaldividercomponent: {
|
||||
@@ -1308,7 +1329,8 @@ Hello \${ctx.username}
|
||||
customCss: {
|
||||
container: { class: '', style: '' },
|
||||
divider: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
verticaldividercomponent: {
|
||||
@@ -1337,7 +1359,8 @@ Hello \${ctx.username}
|
||||
customCss: {
|
||||
container: { class: '', style: '' },
|
||||
divider: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
fileinputcomponent: {
|
||||
@@ -1371,7 +1394,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
imagecomponent: {
|
||||
@@ -1409,7 +1433,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
image: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
drawercomponent: {
|
||||
@@ -1423,6 +1448,12 @@ Hello \${ctx.username}
|
||||
horizontalAlignment: 'center',
|
||||
verticalAlignment: 'center',
|
||||
configuration: {
|
||||
noPadding: {
|
||||
type: 'static',
|
||||
fieldType: 'boolean',
|
||||
value: false,
|
||||
onlyStatic: true
|
||||
},
|
||||
drawerTitle: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
@@ -1461,7 +1492,7 @@ Hello \${ctx.username}
|
||||
}
|
||||
},
|
||||
componentInput: undefined,
|
||||
|
||||
card: false,
|
||||
numberOfSubgrids: 1
|
||||
}
|
||||
},
|
||||
@@ -1517,7 +1548,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
map: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
},
|
||||
verticalsplitpanescomponent: {
|
||||
@@ -1528,12 +1560,19 @@ Hello \${ctx.username}
|
||||
softWrap: true,
|
||||
id: '',
|
||||
type: 'verticalsplitpanescomponent',
|
||||
configuration: {},
|
||||
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
|
||||
}
|
||||
@@ -1546,12 +1585,19 @@ Hello \${ctx.username}
|
||||
softWrap: true,
|
||||
id: '',
|
||||
type: 'horizontalsplitpanescomponent',
|
||||
configuration: {},
|
||||
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
|
||||
}
|
||||
@@ -1582,7 +1628,8 @@ Hello \${ctx.username}
|
||||
},
|
||||
customCss: {
|
||||
container: { class: '', style: '' }
|
||||
} as const
|
||||
} as const,
|
||||
card: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
import { push } from '$lib/history'
|
||||
import { flip } from 'svelte/animate'
|
||||
|
||||
const { app, selectedComponent, focusedGrid, worldStore } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, selectedComponent, focusedGrid } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const { history } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
@@ -24,7 +23,6 @@
|
||||
const id = insertNewGridItem($app, data, $focusedGrid)
|
||||
|
||||
$selectedComponent = id
|
||||
$worldStore = $worldStore
|
||||
}
|
||||
|
||||
let search = ''
|
||||
@@ -39,7 +37,7 @@
|
||||
}))
|
||||
</script>
|
||||
|
||||
<section class="p-2 sticky bg-white w-full z-10 top-0">
|
||||
<section class="p-2 sticky bg-white w-full z-20 top-0">
|
||||
<div class="relative">
|
||||
<input
|
||||
bind:value={search}
|
||||
@@ -70,12 +68,12 @@
|
||||
<div in:fade|local={{ duration: 50, delay: 50 }} out:fade|local={{ duration: 50 }}>
|
||||
{#each componentsFiltered as { title, components }, index (index)}
|
||||
{#if components.length}
|
||||
<div transition:slide|local={{ duration: 100 }}>
|
||||
<div transition:slide|local={{ duration: 300 }}>
|
||||
<ListItem title={`${title} (${components.length})`}>
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
{#each components as item (item)}
|
||||
<button
|
||||
animate:flip={{ duration: 100 }}
|
||||
animate:flip={{ duration: 300 }}
|
||||
on:click={() => addComponent(item)}
|
||||
title={componentsRecord[item].name}
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
|
||||
@@ -4,7 +4,6 @@ const buttonColorOptions = [...BUTTON_COLORS]
|
||||
|
||||
export const staticValues = {
|
||||
buttonColorOptions,
|
||||
tabsKindOptions: ['tabs', 'sidebar', 'invisibleOnView'],
|
||||
buttonSizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
|
||||
tableSearchOptions: ['By Component', 'By Runnable', 'Disabled'],
|
||||
chartThemeOptions: ['theme1', 'theme2', 'theme3'],
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext, GridItem } from '../../types'
|
||||
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
|
||||
import { connectInput } from '../appUtils'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { ChevronDown, ChevronRight, Lock } from 'lucide-svelte'
|
||||
import { connectInput, isIdInsideGriditem } from '../appUtils'
|
||||
import { slide } from 'svelte/transition'
|
||||
import SubGridOutput from './SubGridOutput.svelte'
|
||||
import OutputHeader from './components/OutputHeader.svelte'
|
||||
import TableActionsOutput from './components/TableActionsOutput.svelte'
|
||||
|
||||
export let gridItem: GridItem
|
||||
export let first: boolean = false
|
||||
@@ -14,7 +15,8 @@
|
||||
export let parentId: string | undefined = undefined
|
||||
export let expanded: boolean = false
|
||||
|
||||
const { selectedComponent, connectingInput } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, staticOutputs, selectedComponent, connectingInput } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
const name = getComponentNameById(gridItem.id)
|
||||
|
||||
function getComponentNameById(componentId: string) {
|
||||
@@ -29,43 +31,83 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: subGrids = Array.from({ length: gridItem.data.numberOfSubgrids ?? 0 }).map(
|
||||
let manuallyOpened = false
|
||||
|
||||
$: if (expanded) {
|
||||
manuallyOpened = true
|
||||
} else {
|
||||
manuallyOpened = false
|
||||
}
|
||||
|
||||
$: subGrids = Array.from({ length: gridItem.data.numberOfSubgrids }).map(
|
||||
(_, i) => `${gridItem.id}-${i}`
|
||||
)
|
||||
|
||||
function onHeaderClick(manuallyOpen: boolean) {
|
||||
if (manuallyOpen) {
|
||||
$: insideGrid = isIdInsideGriditem($app, gridItem, $selectedComponent)
|
||||
$: isSelected = $selectedComponent === gridItem.id
|
||||
$: opened = insideGrid || isSelected || manuallyOpened
|
||||
|
||||
function onHeaderClick() {
|
||||
if (manuallyOpened) {
|
||||
if (parentId) {
|
||||
$selectedComponent = parentId
|
||||
} else {
|
||||
$selectedComponent = undefined
|
||||
}
|
||||
manuallyOpened = false
|
||||
} else {
|
||||
$selectedComponent = gridItem.id
|
||||
manuallyOpened = true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<OutputHeader
|
||||
on:handleClick={(e) => {
|
||||
if (!$connectingInput.opened) {
|
||||
onHeaderClick(e.detail.manuallyOpen)
|
||||
}
|
||||
}}
|
||||
id={gridItem.id}
|
||||
name={getComponentNameById(gridItem.id)}
|
||||
{first}
|
||||
{nested}
|
||||
>
|
||||
<ComponentOutputViewer
|
||||
componentId={gridItem.id}
|
||||
on:select={({ detail }) => {
|
||||
if ($connectingInput.opened) {
|
||||
$connectingInput = connectInput($connectingInput, gridItem.id, detail)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if $staticOutputs[gridItem.id] || gridItem.data.numberOfSubgrids > 1}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'flex items-center justify-between p-1 cursor-pointer hover:bg-indigo-100 hover:text-indigo-500 border-b',
|
||||
isSelected ? 'bg-indigo-200' : 'bg-white',
|
||||
first ? 'border-t' : '',
|
||||
nested ? 'border-l' : ''
|
||||
)}
|
||||
on:click={onHeaderClick}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'text-2xs ml-0.5 font-bold px-2 py-0.5 rounded-sm',
|
||||
isSelected ? 'bg-indigo-500 text-white' : ' bg-indigo-50'
|
||||
)}
|
||||
>
|
||||
{gridItem.id}
|
||||
</div>
|
||||
<div class="text-2xs font-bold flex flex-row gap-2 items-center">
|
||||
{getComponentNameById(gridItem.id)}
|
||||
{#if !opened && !manuallyOpened}
|
||||
<ChevronRight size={14} />
|
||||
{:else if manuallyOpened}
|
||||
<Lock size={14} class="text-orange-600" />
|
||||
{:else}
|
||||
<ChevronDown size={14} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SubGridOutput {name} {expanded} {subGrids} parentId={gridItem.id} />
|
||||
<TableActionsOutput {gridItem} />
|
||||
</OutputHeader>
|
||||
{#if opened}
|
||||
<div class={classNames('border-b', nested ? 'border-l' : '')} transition:slide|local>
|
||||
<div class="py-1">
|
||||
<ComponentOutputViewer
|
||||
componentId={gridItem.id}
|
||||
outputs={$staticOutputs[gridItem.id]}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, gridItem.id, detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<SubGridOutput {name} {expanded} {subGrids} parentId={gridItem.id} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -1,43 +1,33 @@
|
||||
<script lang="ts">
|
||||
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext, ContextPanelContext } from '../../types'
|
||||
import { recursivelyFilterKeyInJSON as recursivelyFilterInJSON } from '../appUtils'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
|
||||
export let outputs: string[] = []
|
||||
export let componentId: string
|
||||
|
||||
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { search, hasResult } = getContext<ContextPanelContext>('ContextPanel')
|
||||
|
||||
let object = {}
|
||||
|
||||
function subscribeToAllOutputs(observableOutputs: Record<string, Output<any>> | undefined) {
|
||||
function subscribeToAllOutputs(observableOutputs: Record<string, Output<any>>) {
|
||||
if (observableOutputs) {
|
||||
Object.entries(observableOutputs).forEach(([k, output]) => {
|
||||
object[k] = undefined
|
||||
output?.subscribe({
|
||||
id: 'alloutputs' + componentId + '-' + k,
|
||||
outputs.forEach((output: string) => {
|
||||
object[output] = undefined
|
||||
observableOutputs[output]?.subscribe({
|
||||
next: (value) => {
|
||||
object[k] = value
|
||||
object[output] = value
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$: subscribeToAllOutputs($worldStore?.outputsById?.[componentId])
|
||||
|
||||
$: filtered = recursivelyFilterInJSON(object, $search, componentId)
|
||||
|
||||
$: $hasResult[componentId] = Object.keys(filtered).length > 0
|
||||
$: $worldStore?.outputsById[componentId] &&
|
||||
subscribeToAllOutputs($worldStore.outputsById[componentId])
|
||||
</script>
|
||||
|
||||
{#if $hasResult[componentId] || $search == ''}
|
||||
<ObjectViewer json={filtered} on:select topBrackets={false} />
|
||||
{:else if $search.length > 0}
|
||||
<div class="text-xs pl-2 text-gray-600">No results</div>
|
||||
{:else}
|
||||
<div class="text-xs pl-2 text-gray-600">No outputs</div>
|
||||
{#if Object.keys(object).length > 0}
|
||||
<ObjectViewer json={object} on:select topBrackets={false} />
|
||||
{/if}
|
||||
|
||||
@@ -1,45 +1,69 @@
|
||||
<script lang="ts">
|
||||
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
|
||||
import { X } from 'lucide-svelte'
|
||||
import { getContext, setContext } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { Maximize, Minimize, X } from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
import type { AppViewerContext, ContextPanelContext } from '../../types'
|
||||
import { connectInput } from '../appUtils'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { connectInput, findGridItem, sortGridItemsPosition } from '../appUtils'
|
||||
import { components } from '../component'
|
||||
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
|
||||
import ComponentOutput from './ComponentOutput.svelte'
|
||||
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
|
||||
import BackgroundScriptsOutput from './components/BackgroundScriptsOutput.svelte'
|
||||
import MinMaxButton from './components/MinMaxButton.svelte'
|
||||
import OutputHeader from './components/OutputHeader.svelte'
|
||||
|
||||
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { staticOutputs, app, breakpoint, connectingInput, selectedComponent } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let search = writable<string>('')
|
||||
let expanded = writable(false)
|
||||
function getComponentNameById(componentId: string) {
|
||||
const component = findGridItem($app, componentId)
|
||||
|
||||
setContext<ContextPanelContext>('ContextPanel', {
|
||||
search,
|
||||
manuallyOpened: writable<Record<string, boolean>>({}),
|
||||
hasResult: writable<Record<string, boolean>>({}),
|
||||
expanded
|
||||
if (component?.data?.type) {
|
||||
return components[component?.data.type].name
|
||||
} else if (componentId == 'ctx') {
|
||||
return 'Context'
|
||||
} else if (componentId.startsWith('bg_')) {
|
||||
return 'Background'
|
||||
} else {
|
||||
return 'Table action'
|
||||
}
|
||||
}
|
||||
|
||||
function toggleExpanded() {
|
||||
expanded = !expanded
|
||||
}
|
||||
|
||||
let search = ''
|
||||
let expanded = false
|
||||
|
||||
$: panels = [['ctx', ['email', 'username', 'query', 'hash']] as [string, string[]]].concat(
|
||||
Object.entries($staticOutputs)
|
||||
)
|
||||
|
||||
// filter out outputs that don't match the search by name (computed by getComponentNameById) and id
|
||||
// The output should be [string, string[]][]
|
||||
$: filteredPanels = panels.filter(([componentId, outputs]) => {
|
||||
const name = getComponentNameById(componentId)
|
||||
return (
|
||||
name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
componentId.toLowerCase().includes(search.toLowerCase())
|
||||
)
|
||||
})
|
||||
</script>
|
||||
|
||||
<PanelSection noPadding titlePadding="px-4 pt-2 pb-0.5" title="Outputs">
|
||||
<div class="bg-white w-full h-full z-30">
|
||||
<div class="min-w-[150px]">
|
||||
<div class="sticky top-0 left-0 w-full bg-white p-2">
|
||||
<div style="z-index:1000;" class="bg-white">
|
||||
<div class="overflow-auto h-full min-w-[150px] w-full relative flex flex-col">
|
||||
<div class="sticky z-50 top-0 left-0 w-full bg-white px-2 pb-2">
|
||||
<div class="relative">
|
||||
<input
|
||||
bind:value={$search}
|
||||
bind:value={search}
|
||||
class="px-2 py-1 border border-gray-300 rounded-sm {search ? 'pr-8' : ''}"
|
||||
placeholder="Search outputs..."
|
||||
/>
|
||||
{#if search}
|
||||
<button
|
||||
class="absolute right-2 top-1/2 transform -translate-y-1/2 hover:bg-gray-200 rounded-full p-0.5"
|
||||
on:click|stopPropagation|preventDefault={() => ($search = '')}
|
||||
on:click|stopPropagation|preventDefault={() => (search = '')}
|
||||
>
|
||||
<X size="14" />
|
||||
</button>
|
||||
@@ -48,45 +72,34 @@
|
||||
</div>
|
||||
|
||||
<div class="p-1 ">
|
||||
<MinMaxButton bind:expanded={$expanded} />
|
||||
<Button on:click={toggleExpanded} color="light" size="xs">
|
||||
{#if !expanded}
|
||||
<Maximize size="14" />
|
||||
{:else}
|
||||
<Minimize size="14" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<span class="text-sm font-bold p-2">State & Context</span>
|
||||
|
||||
<OutputHeader id={'ctx'} name={'App Context'} first color="blue">
|
||||
<ComponentOutputViewer
|
||||
componentId={'ctx'}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, 'ctx', detail)
|
||||
}}
|
||||
/>
|
||||
</OutputHeader>
|
||||
|
||||
<OutputHeader id={'state'} name={'State'} color="blue">
|
||||
<ComponentOutputViewer
|
||||
componentId={'state'}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, 'state', detail)
|
||||
}}
|
||||
/>
|
||||
</OutputHeader>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="text-sm font-bold p-2">Components</span>
|
||||
{#each $app.grid as gridItem, index (gridItem.id)}
|
||||
<ComponentOutput {gridItem} first={index === 0} />
|
||||
{/each}
|
||||
</div>
|
||||
<div>
|
||||
<span class="text-sm font-bold p-2">Background scripts</span>
|
||||
<div class="border-t">
|
||||
<BackgroundScriptsOutput />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class={classNames(
|
||||
'text-2xs ml-0.5 font-bold px-2 py-0.5 rounded-sm',
|
||||
$selectedComponent === 'ctx' ? 'bg-indigo-500 text-white' : ' bg-indigo-50'
|
||||
)}
|
||||
>
|
||||
ctx
|
||||
</div>
|
||||
<ComponentOutputViewer
|
||||
componentId={'ctx'}
|
||||
outputs={['email', 'username', 'query', 'hash']}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, 'ctx', detail)
|
||||
}}
|
||||
/>
|
||||
|
||||
{#each sortGridItemsPosition($app.grid, $breakpoint) as gridItem, index}
|
||||
<ComponentOutput {gridItem} first={index === 0} {expanded} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</PanelSection>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { slide } from 'svelte/transition'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppViewerContext } from '../../types'
|
||||
import { connectInput } from '../appUtils'
|
||||
import { connectInput, sortGridItemsPosition } from '../appUtils'
|
||||
import ComponentOutput from './ComponentOutput.svelte'
|
||||
|
||||
export let name: string | undefined = undefined
|
||||
@@ -12,7 +12,8 @@
|
||||
export let expanded: boolean = false
|
||||
export let subGrids: string[]
|
||||
|
||||
const { app, connectingInput, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, connectingInput, breakpoint, worldStore } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
let selected = 0
|
||||
|
||||
@@ -20,14 +21,8 @@
|
||||
selectedTabIndex: Output<number>
|
||||
}
|
||||
|
||||
$: subgridItems = subGrids.map((k) => ({
|
||||
k,
|
||||
items: $app.subgrids?.[k] ?? []
|
||||
}))
|
||||
|
||||
$: if (outputs?.selectedTabIndex) {
|
||||
outputs.selectedTabIndex.subscribe({
|
||||
id: 'subgridoutput-' + parentId,
|
||||
next: (value) => {
|
||||
selected = value
|
||||
}
|
||||
@@ -35,7 +30,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each subgridItems as { k, items }, index (k)}
|
||||
{#each subGrids as subGridId, index}
|
||||
<div class="ml-2 my-2">
|
||||
{#if subGrids.length > 1}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
@@ -49,19 +44,20 @@
|
||||
}}
|
||||
>
|
||||
<div class="text-xs">
|
||||
{name ? name : 'Should implement name'}
|
||||
{name ? name : 'Should implement'}
|
||||
{index + 1}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if selected === index || name !== 'Tabs'}
|
||||
<div transition:slide|local class="border-l">
|
||||
{#if items.length > 0}
|
||||
{#each items as subGridItem, index (subGridItem.id)}
|
||||
<div transition:slide|local>
|
||||
{#if $app.subgrids && $app.subgrids[subGridId].length > 0}
|
||||
{#each sortGridItemsPosition($app.subgrids[subGridId], $breakpoint) as subGridItem, index}
|
||||
<ComponentOutput
|
||||
gridItem={subGridItem}
|
||||
first={index === 0}
|
||||
nested
|
||||
{expanded}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, subGridItem.id, detail)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import { getContext } from 'svelte'
|
||||
import { connectInput } from '../../appUtils'
|
||||
import ComponentOutputViewer from '../ComponentOutputViewer.svelte'
|
||||
import OutputHeader from './OutputHeader.svelte'
|
||||
|
||||
const { selectedComponent, connectingInput } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
export let first: boolean = false
|
||||
|
||||
function onHeaderClick(manuallyOpen: boolean) {
|
||||
if (manuallyOpen) {
|
||||
if (id) {
|
||||
$selectedComponent = id
|
||||
} else {
|
||||
$selectedComponent = undefined
|
||||
}
|
||||
} else {
|
||||
$selectedComponent = id
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<OutputHeader
|
||||
{id}
|
||||
{name}
|
||||
color="blue"
|
||||
{first}
|
||||
on:handleClick={(e) => {
|
||||
if (!$connectingInput.opened) {
|
||||
onHeaderClick(e.detail.manuallyOpen)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ComponentOutputViewer
|
||||
componentId={id}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, id, detail)
|
||||
}}
|
||||
/>
|
||||
</OutputHeader>
|
||||
@@ -1,13 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import { getContext } from 'svelte'
|
||||
import BackgroundScriptOutput from './BackgroundScriptOutput.svelte'
|
||||
|
||||
const { app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
export let expanded: boolean = false
|
||||
</script>
|
||||
|
||||
{#each $app.hiddenInlineScripts as action, index}
|
||||
<BackgroundScriptOutput id={`bg_${index}`} name={action.name} />
|
||||
{/each}
|
||||
@@ -1,27 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { Maximize, Minimize } from 'lucide-svelte'
|
||||
|
||||
export let expanded: boolean = false
|
||||
</script>
|
||||
|
||||
<Button
|
||||
on:click={() => {
|
||||
expanded = !expanded
|
||||
}}
|
||||
color="light"
|
||||
size="xs"
|
||||
variant="border"
|
||||
>
|
||||
{#if !expanded}
|
||||
<div class="flex flex-row gap-2">
|
||||
<Maximize size="14" />
|
||||
Expand all
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-row gap-2">
|
||||
<Minimize size="14" />
|
||||
Collapse all
|
||||
</div>
|
||||
{/if}
|
||||
</Button>
|
||||
@@ -1,112 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { AppViewerContext, ContextPanelContext } from '$lib/components/apps/types'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { ChevronDown, ChevronUp } from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { allsubIds } from '../../appUtils'
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
export let first: boolean = false
|
||||
export let nested: boolean = false
|
||||
export let color: 'blue' | 'indigo' = 'indigo'
|
||||
|
||||
const { expanded, manuallyOpened, search, hasResult } =
|
||||
getContext<ContextPanelContext>('ContextPanel')
|
||||
|
||||
const { selectedComponent, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
$: subids = allsubIds($app, id)
|
||||
$: inSearch =
|
||||
$search != '' &&
|
||||
($hasResult[id] ||
|
||||
Object.entries($hasResult).some(([key, value]) => value && subids.includes(key)))
|
||||
$: open =
|
||||
$expanded || subids.includes($selectedComponent ?? '') || $manuallyOpened[id] || inSearch
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const hoverColor = {
|
||||
blue: 'hover:bg-blue-300 hover:text-blue-600',
|
||||
indigo: 'hover:bg-indigo-300 hover:text-indigo-600'
|
||||
}
|
||||
|
||||
const openBackground = {
|
||||
blue: 'bg-blue-50',
|
||||
indigo: 'bg-indigo-50'
|
||||
}
|
||||
|
||||
const manuallyOpenColor = {
|
||||
blue: 'text-blue-600',
|
||||
indigo: 'text-indigo-600'
|
||||
}
|
||||
|
||||
const idClass = {
|
||||
blue: 'bg-blue-500 text-white',
|
||||
indigo: 'bg-indigo-500 text-white'
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class={$search == '' || inSearch ? '' : 'invisible h-0 overflow-hidden'}>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex items-center justify-between p-1 cursor-pointer border-b gap-1 truncate',
|
||||
hoverColor[color],
|
||||
$selectedComponent == id ? openBackground[color] : 'bg-white',
|
||||
first ? 'border-t' : '',
|
||||
nested ? 'border-l' : ''
|
||||
)}
|
||||
on:click={() => {
|
||||
dispatch('handleClick', { manuallyOpen: $manuallyOpened[id] })
|
||||
$manuallyOpened[id] = $manuallyOpened[id] != undefined ? !$manuallyOpened[id] : true
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={classNames(
|
||||
'text-2xs ml-0.5 font-bold px-2 py-0.5 rounded-sm',
|
||||
$selectedComponent == id ? idClass[color] : ' bg-gray-100'
|
||||
)}
|
||||
>
|
||||
{id}
|
||||
</div>
|
||||
<div
|
||||
on:click|stopPropagation={() => {
|
||||
$manuallyOpened[id] = $manuallyOpened[id] != undefined ? !$manuallyOpened[id] : true
|
||||
}}
|
||||
class="text-2xs font-bold flex flex-row gap-2 items-center truncate"
|
||||
>
|
||||
{name}
|
||||
{#if !open}
|
||||
<ChevronDown size={14} />
|
||||
{:else if $manuallyOpened[id]}
|
||||
<ChevronUp size={14} class={manuallyOpenColor[color]} strokeWidth={4} />
|
||||
{:else}
|
||||
<ChevronUp size={14} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="scale border-b overflow-hidden {open ? 'py-1 scale-y' : 'scale-0 max-h-0'} ">
|
||||
<div class={classNames(nested ? 'border-l ml-2' : '')}>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scale {
|
||||
transform-origin: top;
|
||||
transition: transform 0.26s ease;
|
||||
}
|
||||
.scale-y {
|
||||
transform: scaleY(1);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.scale-0 {
|
||||
transform: scaleY(0);
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,21 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import { getContext } from 'svelte'
|
||||
import { connectInput } from '../../appUtils'
|
||||
import ComponentOutputViewer from '../ComponentOutputViewer.svelte'
|
||||
import OutputHeader from './OutputHeader.svelte'
|
||||
|
||||
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
export let id: string
|
||||
export let first: boolean = false
|
||||
</script>
|
||||
|
||||
<OutputHeader {id} name={'Table action'} {first}>
|
||||
<ComponentOutputViewer
|
||||
componentId={id}
|
||||
on:select={({ detail }) => {
|
||||
$connectingInput = connectInput($connectingInput, id, detail)
|
||||
}}
|
||||
/>
|
||||
</OutputHeader>
|
||||
@@ -1,16 +0,0 @@
|
||||
<script lang="ts">
|
||||
import type { GridItem } from '$lib/components/apps/types'
|
||||
import TableActionOutput from './TableActionOutput.svelte'
|
||||
|
||||
export let gridItem: GridItem
|
||||
</script>
|
||||
|
||||
<div class="my-1">
|
||||
{#if gridItem.data.type === 'tablecomponent' && gridItem.data.actionButtons.length > 0}
|
||||
<div class="ml-2 border-l">
|
||||
{#each gridItem.data.actionButtons as action, index}
|
||||
<TableActionOutput id={action.id} first={index === 0} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user