Compare commits

..

54 Commits

Author SHA1 Message Date
Ádám Kovács
b76426ba83 faet(frontend): Add tour manager store 2023-03-10 20:11:25 +01:00
Ádám Kovács
1cb2a177c6 Merge branch 'main' into tutorials 2023-03-10 19:13:04 +01:00
Ádám Kovács
db87577a1b feat(frontend): Add app editor tour 2023-03-10 19:11:34 +01:00
Ruben Fiszel
bc440f8d41 feat(frontend-apps): add variable picker for static string input on apps 2023-03-10 18:41:27 +01:00
Ádám Kovács
c219eb0ee9 feat(frontend): Add flow editor tour 2023-03-10 18:36:53 +01:00
Ruben Fiszel
1d5c194f09 feat(bash): add default argument handling for bash 2023-03-10 15:04:34 +01:00
Ruben Fiszel
7a9d230459 disable playwright for now 2023-03-10 12:54:00 +01:00
Ruben Fiszel
4d5e2499cf cleanup .workflows 2023-03-10 12:48:02 +01:00
Ruben Fiszel
686275fd46 trim tailwindcss 2023-03-10 12:44:08 +01:00
Ruben Fiszel
99399f4f77 fix serde test 2023-03-10 12:19:56 +01:00
Ruben Fiszel
6e09194313 fix compile 2023-03-10 12:12:10 +01:00
Ruben Fiszel
7c825c212d fix(backend): add killpill for lines reading 2023-03-10 12:04:05 +01:00
Ádám Kovács
bfe5b56c99 feat(frontend): Add script editor tour 2023-03-10 09:38:34 +01:00
Ruben Fiszel
480fd781b6 worker ping at least every 5s even when running long jobs 2023-03-10 01:38:12 +01:00
Ruben Fiszel
4f2079f624 trim tailwind safelist 2023-03-10 01:06:55 +01:00
Ruben Fiszel
43c45d930c chore(main): release 1.74.2 (#1277)
* chore(main): release 1.74.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-03-10 00:55:13 +01:00
Faton Ramadani
8d5c5b88a3 fix(frontend): fix splitpanes navigation (#1276) 2023-03-10 00:32:33 +01:00
Ruben Fiszel
cc8bedd0c7 make frontend configurable through consts.ts 2023-03-09 22:54:25 +01:00
Ruben Fiszel
74c3d6443c chore(main): release 1.74.1 (#1275)
* chore(main): release 1.74.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-03-09 22:45:53 +01:00
Ruben Fiszel
c7be313210 fix importjson 2023-03-09 22:42:47 +01:00
Ruben Fiszel
ae53bafaf6 fix(apps): proper reactivity for non rendered static components 2023-03-09 22:29:19 +01:00
Ruben Fiszel
2ea15d5035 fix(ci): make windmill compile again by pinning swc deps 2023-03-09 22:20:31 +01:00
Ruben Fiszel
0f187d66dd show backtrace for cook 2023-03-09 21:22:36 +01:00
Ádám Kovács
4453690521 feat(frontend): Add onboarding tour to home page 2023-03-09 20:11:27 +01:00
Ruben Fiszel
6691b19b24 chore(main): release 1.74.0 (#1269)
* chore(main): release 1.74.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-03-09 19:58:21 +01:00
Ruben Fiszel
2f9ccff65c app nits 2023-03-09 19:56:47 +01:00
Ruben Fiszel
09db6fd867 fix key navigation 2023-03-09 19:41:12 +01:00
Ruben Fiszel
fd52740d5d improve reactivity check for big objects on app 2023-03-09 18:34:36 +01:00
Faton Ramadani
6b0fb75d23 feat(frontend): Add key navigation in app editor (#1273)
* feat(frontend): add expand

* feat(frontend): fix container height

* feat(frontend): remove code duplication

* feat(frontend): add historic

* feat(frontend): add key navigation

* feat(frontend): simplfiy

* feat(frontend): add support for subgrids

* feat(frontend): update key navigation

* feat(frontend): update key navigation

* feat(frontend): fix nested component

* feat(frontend): fix build

* feat(frontend): remove code duplication

* feat(frontend): support tabs

* feat(frontend): support tabs

* feat(frontend): Fix AppTabs + handle tab navigation

* feat(frontend): support splitpanes
2023-03-09 18:23:12 +01:00
Ruben Fiszel
b1a45b1e70 feat(frontend): add hash to ctx in apps 2023-03-09 14:57:43 +01:00
Ruben Fiszel
b2de531a46 fix(frontend): simplify input bindings 2023-03-09 14:25:08 +01:00
Ruben Fiszel
a4adcb5192 fix(frontend): add confirmation modal to delete script/flow/app 2023-03-09 13:19:03 +01:00
Ruben Fiszel
0c2cf92dd3 feat: add delete by path for scripts 2023-03-09 12:44:49 +01:00
Ruben Fiszel
e6344dac6d fix(cli): improve visibility of the active workspace 2023-03-09 11:21:16 +01:00
Ruben Fiszel
8fb2454e83 enforce on_behalf_of by the backend, not frontend 2023-03-09 11:12:28 +01:00
Ádám Kovács
3b6ae0cc49 fix(frontend): Minor changes (#1272)
* fix(frontend): Output seach fixed on top

* fix(frontend): Use undo-redo component in flows
2023-03-09 09:42:51 +01:00
Ruben Fiszel
96ff2eebc1 fix publishing app as a superadmin 2023-03-09 02:08:52 +01:00
Ruben Fiszel
ed29d51c36 fix app json import 2023-03-09 01:16:57 +01:00
Ádám Kovács
88e537ad1f feat(frontend): Add color picker input to app (#1270)
* feat(frontend): Add color picker input to app

* fix(frontend): Add color input to dividers
2023-03-08 22:57:34 +01:00
Faton Ramadani
b854ee3439 feat(frontend): add expand (#1268)
* feat(frontend): add expand

* feat(frontend): fix container height

* feat(frontend): remove code duplication

* feat(frontend): add historic
2023-03-08 22:56:30 +01:00
Ádám Kovács
0a5e181a3a fix(frontend): Clean up app editor (#1267)
* fix(frontend): Clean up app editor

* fix(frontend): Add outputs search empty state

* fix(frontend): Add remove button to icon input

* label

* fix(frontend): Iconed app button
2023-03-08 19:02:19 +01:00
Ruben Fiszel
8cc59225d8 improve resource picker 2023-03-08 19:00:26 +01:00
Ruben Fiszel
9c41346dde fix subtle plotly import bug 2023-03-08 18:27:19 +01:00
Ruben Fiszel
41a398f50e fix frontend build error 2023-03-08 16:59:14 +01:00
Ruben Fiszel
3436061ad4 make windmill compatible with arm64 2023-03-08 16:55:00 +01:00
Ruben Fiszel
569b5d2516 improve rendering performances for non visible elements 2023-03-08 16:39:16 +01:00
Ruben Fiszel
a08cdd7b86 chore(main): release 1.73.1 (#1266)
* chore(main): release 1.73.1

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-03-08 00:41:42 +01:00
Ruben Fiszel
719d475262 fix(frontend): load flow is not initialized 2023-03-08 00:37:58 +01:00
Ruben Fiszel
5b3e1183e5 revert import on tailwind colors for faster builds 2023-03-08 00:20:12 +01:00
Ruben Fiszel
7ed301b186 chore(main): release 1.73.0 (#1257)
* chore(main): release 1.73.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-03-07 22:35:56 +01:00
Ruben Fiszel
46b6e4371b improve undo/redo + keybindings 2023-03-07 21:16:15 +01:00
Ruben Fiszel
e0d3465b07 fix z-stacking on chrome for flow builder 2023-03-07 19:27:35 +01:00
Ruben Fiszel
7f8fe8dc17 fix z-stacking on chrome for flow builder 2023-03-07 19:23:41 +01:00
Faton Ramadani
24f58efd99 feat(frontend): add a way to automatically resize (#1259)
* feat(frontend): add a way to automatically resize (wip) + add automatic resizable component

* feat(frontend): fix text resize

* feat(frontend): remvove useless softWrap

* feat(frontend): remove useless softWrap

* feat(frontend): Fix recomputeIds + app table

* feat(frontend): Fix app preview error display
2023-03-07 16:53:29 +01:00
158 changed files with 3465 additions and 1306 deletions

View File

@@ -1,20 +0,0 @@
name: Deploy to windmill.dev
on:
push:
branches: [main]
paths:
- "community/**"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Deploy to windmill.dev
uses: windmill-labs/windmill-gh-action-deploy@v2.0.0
with:
dry_run: false
input_dir: community
windmill_workspace: starter
windmill_token: ${{ secrets.WINDMILL_API_TOKEN }}

View File

@@ -109,38 +109,38 @@ jobs:
${{ steps.meta-ee-public.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
playwright:
runs-on: [self-hosted, new]
needs: [build]
services:
postgres:
image: postgres
env:
POSTGRES_DB: windmill
POSTGRES_USER: admin
POSTGRES_PASSWORD: changeme
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: "Docker"
run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest)"
id: docker-container
- uses: actions/setup-node@v3
with:
node-version: 16
- name: "Playwright run"
timeout-minutes: 2
run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=http://localhost:8000 && npm run test
- name: "Clean up"
run: docker kill ${{ steps.docker-container.outputs.id }}
if: always()
# disabled until we make it 100% reliable and add more meaningful tests
# playwright:
# runs-on: [self-hosted, new]
# needs: [build]
# services:
# postgres:
# image: postgres
# env:
# POSTGRES_DB: windmill
# POSTGRES_USER: admin
# POSTGRES_PASSWORD: changeme
# ports:
# - 5432:5432
# options: >-
# --health-cmd pg_isready
# --health-interval 10s
# --health-timeout 5s
# --health-retries 5
# steps:
# - uses: actions/checkout@v3
# - name: "Docker"
# run: echo "::set-output name=id::$(docker run --network=host --rm -d -p 8000:8000 --privileged -it -e DATABASE_URL=postgres://admin:changeme@localhost:5432/windmill -e BASE_INTERNAL_URL=http://localhost:8000 ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest)"
# id: docker-container
# - uses: actions/setup-node@v3
# with:
# node-version: 16
# - name: "Playwright run"
# timeout-minutes: 2
# run: cd frontend && npm ci @playwright/test && npx playwright install && export BASE_URL=http://localhost:8000 && npm run test
# - name: "Clean up"
# run: docker kill ${{ steps.docker-container.outputs.id }}
# if: always()
publish_privately_heavy:

View File

@@ -1,6 +1,69 @@
# Changelog
## [1.74.2](https://github.com/windmill-labs/windmill/compare/v1.74.1...v1.74.2) (2023-03-09)
### Bug Fixes
* **frontend:** fix splitpanes navigation ([#1276](https://github.com/windmill-labs/windmill/issues/1276)) ([8d5c5b8](https://github.com/windmill-labs/windmill/commit/8d5c5b88a35d7a3bad1d8ddf2d940026825241eb))
## [1.74.1](https://github.com/windmill-labs/windmill/compare/v1.74.0...v1.74.1) (2023-03-09)
### Bug Fixes
* **apps:** proper reactivity for non rendered static components ([ae53baf](https://github.com/windmill-labs/windmill/commit/ae53bafaf6777f928113f84b2c6ed6a2ed341844))
* **ci:** make windmill compile again by pinning swc deps ([2ea15d5](https://github.com/windmill-labs/windmill/commit/2ea15d5035e5e15473968db3c0501a4dddff5cd0))
## [1.74.0](https://github.com/windmill-labs/windmill/compare/v1.73.1...v1.74.0) (2023-03-09)
### Features
* add delete by path for scripts ([0c2cf92](https://github.com/windmill-labs/windmill/commit/0c2cf92dd3df9610e649f15e23921a4ca0d94e6a))
* **frontend:** Add color picker input to app ([#1270](https://github.com/windmill-labs/windmill/issues/1270)) ([88e537a](https://github.com/windmill-labs/windmill/commit/88e537ad1fb4c207f38fbe951c82106bef6491a3))
* **frontend:** add expand ([#1268](https://github.com/windmill-labs/windmill/issues/1268)) ([b854ee3](https://github.com/windmill-labs/windmill/commit/b854ee34393534bde104e2e6f606108fd66d38dc))
* **frontend:** add hash to ctx in apps ([b1a45b1](https://github.com/windmill-labs/windmill/commit/b1a45b1e708aa6f19f8be9c949507083e044f2d8))
* **frontend:** Add key navigation in app editor ([#1273](https://github.com/windmill-labs/windmill/issues/1273)) ([6b0fb75](https://github.com/windmill-labs/windmill/commit/6b0fb75d23e2151c88b07814139d203c1bd0578d))
### Bug Fixes
* **cli:** improve visibility of the active workspace ([e6344da](https://github.com/windmill-labs/windmill/commit/e6344dac6d1be04b46231fa8ef8579fd12ca8f37))
* **frontend:** add confirmation modal to delete script/flow/app ([a4adcb5](https://github.com/windmill-labs/windmill/commit/a4adcb5192c11f7bf47a0d259825e474779378d7))
* **frontend:** Clean up app editor ([#1267](https://github.com/windmill-labs/windmill/issues/1267)) ([0a5e181](https://github.com/windmill-labs/windmill/commit/0a5e181a3aa966fb8211bee0d9174fc16353b31f))
* **frontend:** Minor changes ([#1272](https://github.com/windmill-labs/windmill/issues/1272)) ([3b6ae0c](https://github.com/windmill-labs/windmill/commit/3b6ae0cc49461b858d9cfff79eae9a7569465235))
* **frontend:** simplify input bindings ([b2de531](https://github.com/windmill-labs/windmill/commit/b2de531a46e4b120d7106d361b727746bec516dd))
## [1.73.1](https://github.com/windmill-labs/windmill/compare/v1.73.0...v1.73.1) (2023-03-07)
### Bug Fixes
* **frontend:** load flow is not initialized ([719d475](https://github.com/windmill-labs/windmill/commit/719d4752621d462b1cfaa0d27930fba7586be779))
## [1.73.0](https://github.com/windmill-labs/windmill/compare/v1.72.0...v1.73.0) (2023-03-07)
### Features
* **frontend:** add a way to automatically resize ([#1259](https://github.com/windmill-labs/windmill/issues/1259)) ([24f58ef](https://github.com/windmill-labs/windmill/commit/24f58efd9994a2201c1b1d9bbfb11734c57068e3))
* **frontend:** add ability to move nodes ([614fb50](https://github.com/windmill-labs/windmill/commit/614fb5022aa7d5428fb96b7ee3a20794edd1e9d3))
* **frontend:** Add app PDF viewer ([#1254](https://github.com/windmill-labs/windmill/issues/1254)) ([3e5d09e](https://github.com/windmill-labs/windmill/commit/3e5d09ef0b5619186bee5ec6d442cbfd12a6e8d5))
* **frontend:** add fork/save buttons + consistent styling for slider/range ([9e9f8ef](https://github.com/windmill-labs/windmill/commit/9e9f8efb8ee389ea75e99b67ef720756959ca737))
* **frontend:** add history to flows and apps ([9e4d90a](https://github.com/windmill-labs/windmill/commit/9e4d90ad37a57ff1f515eea0c82cf603649e915d))
* **frontend:** Fix object viewer style ([#1255](https://github.com/windmill-labs/windmill/issues/1255)) ([94f1aad](https://github.com/windmill-labs/windmill/commit/94f1aadef2b09ac1962478f11b27cc708b8328f1))
* **frontend:** refactor entire flow builder UX ([2ac51b0](https://github.com/windmill-labs/windmill/commit/2ac51b0af08bdef7ce3c7e874e9983b9fc00478a))
### Bug Fixes
* **frontend:** arginput + apppreview fixes ([e2c4545](https://github.com/windmill-labs/windmill/commit/e2c45452401022b00285b21551ffaf35a114be33))
* **frontend:** fix app map reactivity ([#1260](https://github.com/windmill-labs/windmill/issues/1260)) ([2557e13](https://github.com/windmill-labs/windmill/commit/2557e136bd0df1a023819b7d9b2235e30d7140b6))
* **frontend:** fix branch deletion ([#1261](https://github.com/windmill-labs/windmill/issues/1261)) ([a999eb2](https://github.com/windmill-labs/windmill/commit/a999eb21121a7c0010621448324e0c77caf2b3f6))
* **frontend:** Side menu z-index issue ([#1265](https://github.com/windmill-labs/windmill/issues/1265)) ([c638897](https://github.com/windmill-labs/windmill/commit/c638897fdcd58f55b0929f91641b21a6f9d25ead))
## [1.72.0](https://github.com/windmill-labs/windmill/compare/v1.71.0...v1.72.0) (2023-03-02)

View File

@@ -73,7 +73,7 @@ ARG features=""
COPY --from=planner /windmill/recipe.json recipe.json
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo chef cook --release --features "$features" --recipe-path recipe.json
RUN CARGO_NET_GIT_FETCH_WITH_CLI=true RUST_BACKTRACE=1 cargo chef cook --release --features "$features" --recipe-path recipe.json
COPY ./openflow.openapi.yaml /openflow.openapi.yaml
COPY ./backend ./
@@ -86,6 +86,7 @@ RUN CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build --release --features "$feature
FROM python:3.11.2-slim-buster
ARG TARGETPLATFORM
ARG APP=/usr/src/app
@@ -129,6 +130,10 @@ COPY --from=nsjail /nsjail/nsjail /bin/nsjail
COPY --from=denoland/deno:latest /usr/bin/deno /usr/bin/deno
# docker does not support conditional COPY and we want to use the same Dockerfile for both amd64 and arm64 and privilege the official image
COPY --from=lukechannings/deno:latest /usr/bin/deno /usr/bin/deno-arm
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then rm /usr/bin/deno-arm; elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then mv /usr/bin/deno-arm /usr/bin/deno; fi
RUN mkdir -p ${APP}
WORKDIR ${APP}

245
backend/Cargo.lock generated
View File

@@ -67,9 +67,9 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
[[package]]
name = "argon2"
version = "0.4.1"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73"
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
dependencies = [
"base64ct",
"blake2",
@@ -202,9 +202,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.64"
version = "0.1.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
dependencies = [
"proc-macro2",
"quote",
@@ -254,9 +254,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "axum"
version = "0.6.9"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6137c6234afb339e75e764c866e3594900f0211e1315d33779f269bbe2ec6967"
checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591"
dependencies = [
"async-trait",
"axum-core",
@@ -281,16 +281,16 @@ dependencies = [
"sync_wrapper",
"tokio",
"tower",
"tower-http",
"tower-http 0.3.5",
"tower-layer",
"tower-service",
]
[[package]]
name = "axum-core"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34"
checksum = "b2f958c80c248b34b9a877a643811be8dbca03ca5ba827f2b63baf3a81e5fc4e"
dependencies = [
"async-trait",
"bytes",
@@ -378,9 +378,9 @@ dependencies = [
[[package]]
name = "block-buffer"
version = "0.10.3"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
@@ -719,9 +719,9 @@ dependencies = [
[[package]]
name = "cxx"
version = "1.0.91"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
checksum = "9a140f260e6f3f79013b8bfc65e7ce630c9ab4388c6a89c71e07226f49487b72"
dependencies = [
"cc",
"cxxbridge-flags",
@@ -731,9 +731,9 @@ dependencies = [
[[package]]
name = "cxx-build"
version = "1.0.91"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
checksum = "da6383f459341ea689374bf0a42979739dc421874f112ff26f829b8040b8e613"
dependencies = [
"cc",
"codespan-reporting",
@@ -746,15 +746,15 @@ dependencies = [
[[package]]
name = "cxxbridge-flags"
version = "1.0.91"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
checksum = "90201c1a650e95ccff1c8c0bb5a343213bdd317c6e600a93075bca2eff54ec97"
[[package]]
name = "cxxbridge-macro"
version = "1.0.91"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
checksum = "0b75aed41bb2e6367cae39e6326ef817a851db13c13e4f3263714ca3cfb8de56"
dependencies = [
"proc-macro2",
"quote",
@@ -895,7 +895,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer 0.10.3",
"block-buffer 0.10.4",
"const-oid",
"crypto-common",
"subtle",
@@ -1612,9 +1612,9 @@ dependencies = [
[[package]]
name = "io-lifetimes"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1abeb7a0dd0f8181267ff8adc397075586500b81b28a73e8a0208b00fc170fb3"
checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
dependencies = [
"libc",
"windows-sys 0.45.0",
@@ -1662,9 +1662,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "jobserver"
@@ -1815,9 +1815,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.139"
version = "0.2.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
[[package]]
name = "libgit2-sys"
@@ -2263,9 +2263,9 @@ dependencies = [
[[package]]
name = "password-hash"
version = "0.4.2"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
dependencies = [
"base64ct",
"rand_core 0.6.4",
@@ -2274,9 +2274,9 @@ dependencies = [
[[package]]
name = "paste"
version = "1.0.11"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pem-rfc7468"
@@ -2295,9 +2295,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
version = "2.5.5"
version = "2.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660"
checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7"
dependencies = [
"thiserror",
"ucd-trie",
@@ -2525,7 +2525,7 @@ dependencies = [
[[package]]
name = "progenitor"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"anyhow",
"built",
@@ -2543,7 +2543,7 @@ dependencies = [
[[package]]
name = "progenitor-client"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"bytes",
"futures-core",
@@ -2557,7 +2557,7 @@ dependencies = [
[[package]]
name = "progenitor-impl"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"getopts",
"heck",
@@ -2579,7 +2579,7 @@ dependencies = [
[[package]]
name = "progenitor-macro"
version = "0.2.1-dev"
source = "git+https://github.com/oxidecomputer/progenitor#cbe875ad1a4c650cf3af595c90df6fd7421b47c2"
source = "git+https://github.com/oxidecomputer/progenitor#e9b6970d4485ad5813f93e95570480195528b0a9"
dependencies = [
"openapiv3",
"proc-macro2",
@@ -2846,9 +2846,9 @@ dependencies = [
[[package]]
name = "rust-embed"
version = "6.4.2"
version = "6.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "283ffe2f866869428c92e0d61c2f35dfb4355293cdfdc48f49e895c15f1333d1"
checksum = "cb133b9a38b5543fad3807fb2028ea47c5f2b566f4f5e28a11902f1a358348b6"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -2857,9 +2857,9 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "6.3.1"
version = "6.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31ab23d42d71fb9be1b643fe6765d292c5e14d46912d13f3ae2815ca048ea04d"
checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7"
dependencies = [
"proc-macro2",
"quote",
@@ -2870,9 +2870,9 @@ dependencies = [
[[package]]
name = "rust-embed-utils"
version = "7.3.0"
version = "7.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1669d81dfabd1b5f8e2856b8bbe146c6192b0ba22162edc738ac0a5de18f054"
checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731"
dependencies = [
"sha2 0.10.6",
"walkdir",
@@ -2917,9 +2917,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.36.8"
version = "0.36.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f43abb88211988493c1abb44a70efa56ff0ce98f233b7b276146f1f3f7ba9644"
checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
dependencies = [
"bitflags",
"errno",
@@ -2953,7 +2953,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -2962,7 +2962,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
dependencies = [
"bitflags",
"bstr",
@@ -2975,7 +2975,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#6e4c2fe7866f705bb427200a36d6c9fb46508de3"
source = "git+https://github.com/RustPython/RustPython#87728c44527272ece739cddbb4942ad7e176dd79"
dependencies = [
"ahash",
"anyhow",
@@ -2998,15 +2998,15 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.11"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "ryu"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "same-file"
@@ -3066,9 +3066,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.3"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1"
[[package]]
name = "sct"
@@ -3147,9 +3147,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "8cdd151213925e7f1ab45a9bbfb129316bd00799784b174b7cc7bcd16961c49e"
dependencies = [
"serde_derive",
]
@@ -3176,9 +3176,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.152"
version = "1.0.154"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "4fc80d722935453bcafdc2c9a73cd6fac4dc1938f0346035d84bf99fa9e33217"
dependencies = [
"proc-macro2",
"quote",
@@ -3198,9 +3198,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.93"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"indexmap",
"itoa",
@@ -3210,9 +3210,9 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
version = "0.1.9"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341"
checksum = "db0969fff533976baadd92e08b1d102c5a3d8a8049eadfd69d4d1e3c5b2ed189"
dependencies = [
"serde",
]
@@ -3241,9 +3241,9 @@ dependencies = [
[[package]]
name = "serde_tokenstream"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "274f512d6748a01e67cbcde5b4307ab2c9d52a98a2b870a980ef0793a351deff"
checksum = "797ba1d80299b264f3aac68ab5d12e5825a561749db4df7cd7c8083900c5d4e9"
dependencies = [
"proc-macro2",
"serde",
@@ -3278,9 +3278,9 @@ dependencies = [
[[package]]
name = "serde_yaml"
version = "0.9.17"
version = "0.9.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fb06d4b6cdaef0e0c51fa881acb721bed3c924cfaa71d9c94a3b771dfdf6567"
checksum = "f82e6c8c047aa50a7328632d067bcae6ef38772a79e28daf32f735e0e4f3dd10"
dependencies = [
"indexmap",
"itoa",
@@ -3384,6 +3384,17 @@ dependencies = [
"syn",
]
[[package]]
name = "smartstring"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
dependencies = [
"autocfg",
"static_assertions",
"version_check",
]
[[package]]
name = "smol_str"
version = "0.1.24"
@@ -3395,9 +3406,9 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.4.7"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
@@ -3581,9 +3592,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.4"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08"
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
dependencies = [
"new_debug_unreachable",
"once_cell",
@@ -3642,9 +3653,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "swc_atoms"
version = "0.4.38"
version = "0.4.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a172f2e444ae1378286cd27ff2a5cb26eadfd7a77c98ccb0edde8992857be1e"
checksum = "2ebef84c2948cd0d1ba25acbf1b4bd9d80ab6f057efdbe35d8449b8d54699401"
dependencies = [
"once_cell",
"rustc-hash",
@@ -3656,9 +3667,9 @@ dependencies = [
[[package]]
name = "swc_common"
version = "0.29.33"
version = "0.29.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a75c46065858a37cdda2c1c6fd056986e3b7752d5ec332e91ce312c6a22749"
checksum = "2d515be281f603cb97afaa89896aca5e5c748fde11c7f926e35cdaa8ff8da705"
dependencies = [
"ahash",
"ast_node",
@@ -3683,9 +3694,9 @@ dependencies = [
[[package]]
name = "swc_ecma_ast"
version = "0.98.0"
version = "0.98.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1f6bc913d0f1daf0fa713a64660aed27848e0047ee671ab2206ad602730d1f"
checksum = "305d2aa5e36a775e0d376552b35b325fccf0d7d8c633da1e4aec8e16460251cf"
dependencies = [
"bitflags",
"is-macro",
@@ -3700,9 +3711,9 @@ dependencies = [
[[package]]
name = "swc_ecma_parser"
version = "0.127.0"
version = "0.128.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3875db58a1515382c670679062133294e5ec88aff08776f5fb6e4f3c09c0f5f9"
checksum = "35e349e3f4c5561645b9042caae162dbaf55502be7b583ac99f3ccf3e65bccb7"
dependencies = [
"either",
"enum_kind",
@@ -3710,6 +3721,7 @@ dependencies = [
"num-bigint",
"serde",
"smallvec",
"smartstring",
"stacker",
"swc_atoms",
"swc_common",
@@ -3818,18 +3830,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.38"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
"proc-macro2",
"quote",
@@ -4095,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"
@@ -4109,7 +4140,6 @@ dependencies = [
"http-body",
"http-range-header",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
"tracing",
@@ -4244,7 +4274,7 @@ checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "typify"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
dependencies = [
"typify-impl",
"typify-macro",
@@ -4253,7 +4283,7 @@ dependencies = [
[[package]]
name = "typify-impl"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
dependencies = [
"heck",
"log",
@@ -4271,7 +4301,7 @@ dependencies = [
[[package]]
name = "typify-macro"
version = "0.0.11-dev"
source = "git+https://github.com/oxidecomputer/typify#05d65ea62be9061c1abd0c31d955d0248120d301"
source = "git+https://github.com/oxidecomputer/typify#18e7faf1957812626e3bf459e20fbe16785de2d4"
dependencies = [
"proc-macro2",
"quote",
@@ -4362,9 +4392,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.10"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
checksum = "524b68aca1d05e03fdf03fcdce2c6c94b6daf6d16861ddaa7e4f2b6638a9052c"
[[package]]
name = "unicode-general-category"
@@ -4380,9 +4410,9 @@ checksum = "d70b6494226b36008c8366c288d77190b3fad2eb4c10533139c1c1f461127f1a"
[[package]]
name = "unicode-ident"
version = "1.0.6"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-normalization"
@@ -4420,16 +4450,16 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "unicode_names2"
version = "0.6.0"
source = "git+https://github.com/youknowone/unicode_names2.git?tag=v0.6.0+character-alias#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
source = "git+https://github.com/youknowone/unicode_names2.git?rev=4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde#4ce16aa85cbcdd9cc830410f1a72ef9a235f2fde"
dependencies = [
"phf",
]
[[package]]
name = "unsafe-libyaml"
version = "0.2.5"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2"
checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c"
[[package]]
name = "untrusted"
@@ -4712,7 +4742,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"axum",
@@ -4739,7 +4769,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"argon2",
@@ -4778,7 +4808,7 @@ dependencies = [
"tokio-util",
"tower",
"tower-cookies",
"tower-http",
"tower-http 0.4.0",
"tracing",
"tracing-subscriber",
"urlencoding",
@@ -4794,7 +4824,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"base64 0.21.0",
"chrono",
@@ -4809,7 +4839,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"chrono",
"serde",
@@ -4822,7 +4852,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"axum",
@@ -4847,7 +4877,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"serde",
"serde_json",
@@ -4855,13 +4885,14 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"itertools",
"lazy_static",
"phf",
"regex",
"serde_json",
"unicode-general-category",
"windmill-common",
"windmill-parser",
@@ -4869,7 +4900,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"itertools",
@@ -4881,7 +4912,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"itertools",
@@ -4896,7 +4927,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"deno_core",
@@ -4910,7 +4941,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"chrono",
@@ -4933,7 +4964,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.72.0"
version = "1.74.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -5049,9 +5080,9 @@ checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.3"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658"
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
dependencies = [
"memchr",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.72.0"
version = "1.74.2"
authors.workspace = true
edition.workspace = true
@@ -19,7 +19,7 @@ members = [
]
[workspace.package]
version = "1.72.0"
version = "1.74.2"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -123,8 +123,8 @@ regex = "^1"
deno_core = "^0"
async-recursion = "^1"
swc_common = "^0"
swc_ecma_parser = "^0"
swc_ecma_ast = "^0"
swc_ecma_parser = "0.128.2"
swc_ecma_ast = "0.98.1"
base64 = "0.21.0"
unicode-general-category = "^0"
hmac = "0.12.1"

View File

@@ -16,4 +16,5 @@ unicode-general-category.workspace = true
itertools.workspace = true
anyhow.workspace = true
regex.workspace = true
lazy_static.workspace = true
lazy_static.workspace = true
serde_json.workspace = true

View File

@@ -1,6 +1,8 @@
#![allow(non_snake_case)] // TODO: switch to parse_* function naming
use anyhow::anyhow;
use regex::Regex;
use serde_json::json;
use std::collections::HashMap;
use windmill_parser::{Arg, MainArgSignature, Typ};
@@ -17,19 +19,32 @@ pub fn parse_bash_sig(code: &str) -> windmill_common::error::Result<MainArgSigna
}
}
lazy_static::lazy_static! {
static ref RE: Regex = Regex::new(r#"(?m)^(\w+)="\$(?:(\d+)|\{(\d+):-(.*)\})"$"#).unwrap();
}
fn parse_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
let mut hm = HashMap::new();
let re = Regex::new(r#"(?m)^(\w+)="\$(\d+)"$"#).unwrap();
for cap in re.captures_iter(code) {
hm.insert(cap[2].parse::<i32>()?, cap[1].to_string());
let mut hm: HashMap<i32, (String, Option<String>)> = HashMap::new();
for cap in RE.captures_iter(code) {
hm.insert(
cap.get(2)
.or(cap.get(3))
.and_then(|x| x.as_str().parse::<i32>().ok())
.ok_or_else(|| anyhow!("Impossible to parse arg digit"))?,
(
cap[1].to_string(),
cap.get(4).map(|x| x.as_str().to_string()),
),
);
}
let mut args = vec![];
for i in 1..20 {
if hm.contains_key(&i) {
let (name, default) = hm.get(&i).unwrap();
args.push(Arg {
name: hm[&i].clone(),
name: name.clone(),
typ: Typ::Str(None),
default: None,
default: default.clone().map(|x| json!(x)),
otyp: None,
has_default: false,
});
@@ -43,6 +58,8 @@ fn parse_file(code: &str) -> anyhow::Result<Option<Vec<Arg>>> {
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
@@ -50,8 +67,7 @@ mod tests {
let code = r#"
token="$1"
image="$2"
digest="${3:-latest}"
foo="$4"
digest="${3:-latest with spaces}"
"#;
//println!("{}", serde_json::to_string()?);
@@ -74,6 +90,13 @@ foo="$4"
typ: Typ::Str(None),
default: None,
has_default: false
},
Arg {
otyp: None,
name: "digest".to_string(),
typ: Typ::Str(None),
default: Some(json!("latest with spaces")),
has_default: false
}
]
}

View File

@@ -468,6 +468,27 @@
},
"query": "DELETE FROM workspace_settings WHERE workspace_id = $1"
},
"0e7d95f4913e5775651971d741a3b5c1ef5dfe079be5325abe2866d39a7fe5fb": {
"describe": {
"columns": [
{
"name": "path",
"ordinal": 0,
"type_info": "Varchar"
}
],
"nullable": [
false
],
"parameters": {
"Left": [
"Text",
"Text"
]
}
},
"query": "DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path"
},
"11b1586acdfc180c5a077861ee1f7201fcbcec9d0ebada464f9d952c9c3e400d": {
"describe": {
"columns": [],
@@ -5019,6 +5040,18 @@
},
"query": "\n SELECT id, flow_status, suspend, script_path\n FROM queue\n WHERE id = ( SELECT parent_job FROM queue WHERE id = $1 UNION ALL SELECT parent_job FROM completed_job WHERE id = $1)\n FOR UPDATE\n "
},
"c9d97800eb0ec87df8e8959b283dacb2c6cce422365ed394375641488ceb6b65": {
"describe": {
"columns": [],
"nullable": [],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "UPDATE worker_ping SET ping_at = now() WHERE worker = $1"
},
"cac594031a21b4806de9c4616317d3541522ef9712a83ecff7bd8b5f6e870748": {
"describe": {
"columns": [],

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.72.0
version: 1.74.2
title: Windmill API
contact:
@@ -2278,7 +2278,7 @@ paths:
/w/{workspace}/scripts/delete/h/{hash}:
post:
summary: delete script by hash (erase content but keep hash)
summary: delete script by hash (erase content but keep hash, require admin)
operationId: deleteScriptByHash
tags:
- script
@@ -2293,6 +2293,23 @@ paths:
schema:
$ref: "#/components/schemas/Script"
/w/{workspace}/scripts/delete/p/{path}:
post:
summary: delete all scripts at a given path (require admin)
operationId: deleteScriptByPath
tags:
- script
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/ScriptPath"
responses:
"200":
description: script path
content:
application/json:
schema:
type: string
/w/{workspace}/scripts/get/p/{path}:
get:
summary: get script by path

View File

@@ -313,10 +313,13 @@ async fn create_app(
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Path(w_id): Path<String>,
Json(app): Json<CreateApp>,
Json(mut app): Json<CreateApp>,
) -> Result<(StatusCode, String)> {
let mut tx = user_db.begin(&authed).await?;
app.policy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
app.policy.on_behalf_of_email = Some(authed.email);
let id = sqlx::query_scalar!(
"INSERT INTO app
(workspace_id, path, summary, policy, versions)
@@ -463,7 +466,9 @@ async fn update_app(
sqlb.set_str("summary", nsummary);
}
if let Some(npolicy) = ns.policy {
if let Some(mut npolicy) = ns.policy {
npolicy.on_behalf_of = Some(username_to_permissioned_as(&authed.username));
npolicy.on_behalf_of_email = Some(authed.email);
sqlb.set(
"policy",
&format!(
@@ -728,6 +733,9 @@ fn build_args(
path: String,
args: &Map<String, Value>,
) -> Result<Map<String, Value>> {
// disallow var and res access in args coming from the user for security reasons
args.into_iter()
.try_for_each(|x| disallow_var_res_access(x.1))?;
let static_args = policy
.triggerables
.get(&path)
@@ -748,3 +756,20 @@ fn build_args(
}
Ok(args)
}
fn disallow_var_res_access(args: &serde_json::Value) -> Result<()> {
match args {
Value::Object(v) => v.into_iter().try_for_each(|x| disallow_var_res_access(x.1)),
Value::Array(arr) => arr.into_iter().try_for_each(|v| disallow_var_res_access(v)),
Value::String(s) => {
if s.starts_with("$var:") || s.starts_with("$res:") {
Err(Error::BadRequest(format!(
"For security reasons, variable or resource access is not allowed as dynamic argument"
)))
} else {
Ok(())
}
}
_ => Ok(()),
}
}

View File

@@ -640,8 +640,6 @@ mod tests {
"type": "script",
"path": "test"
},
"stop_after_if": null,
"summary": null
},
{
"id": "b",
@@ -650,15 +648,12 @@ mod tests {
"input_transforms": {},
"type": "rawscript",
"content": "test",
"lock": null,
"path": null,
"language": "deno"
},
"stop_after_if": {
"expr": "foo = 'bar'",
"skip_if_stopped": false
},
"summary": null
}
},
{
"id": "c",
@@ -680,8 +675,7 @@ mod tests {
"stop_after_if": {
"expr": "previous.isEmpty()",
"skip_if_stopped": false,
},
"summary": null
}
}
],
"failure_module": {
@@ -695,8 +689,7 @@ mod tests {
"stop_after_if": {
"expr": "previous.isEmpty()",
"skip_if_stopped": false
},
"summary": null
}
}
});
assert_eq!(dbg!(serde_json::json!(fv)), dbg!(expect));

View File

@@ -70,6 +70,7 @@ pub fn workspaced_service() -> Router {
.route("/exists/p/*path", get(exists_script_by_path))
.route("/archive/h/:hash", post(archive_script_by_hash))
.route("/delete/h/:hash", post(delete_script_by_hash))
.route("/delete/p/*path", post(delete_script_by_path))
.route("/get/h/:hash", get(get_script_by_hash))
.route("/raw/h/:hash", get(raw_script_by_hash))
.route("/deployment_status/h/:hash", get(get_deployment_status))
@@ -720,6 +721,46 @@ async fn delete_script_by_hash(
Ok(Json(script))
}
async fn delete_script_by_path(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Extension(webhook): Extension<WebhookShared>,
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<String> {
let mut tx = user_db.begin(&authed).await?;
let path = path.to_path();
require_admin(authed.is_admin, &authed.username)?;
let script = sqlx::query_scalar!(
"DELETE FROM script WHERE path = $1 AND workspace_id = $2 RETURNING path",
path,
w_id
)
.fetch_one(&db)
.await
.map_err(|e| Error::InternalErr(format!("deleting script by path {w_id}: {e}")))?;
audit_log(
&mut tx,
&authed.username,
"scripts.delete",
ActionKind::Delete,
&w_id,
Some(&path),
Some([("workspace", w_id.as_str())].into()),
)
.await?;
tx.commit().await?;
webhook.send_message(
w_id.clone(),
WebhookMessage::DeleteScriptPath { workspace: w_id, path: path.to_string() },
);
Ok(Json(script))
}
async fn parse_python_code_to_jsonschema(
Json(code): Json<String>,
) -> JsonResult<windmill_parser::MainArgSignature> {

View File

@@ -37,6 +37,7 @@ pub enum WebhookMessage {
CreateScript { workspace: String, path: String, hash: String },
UpdateScript { workspace: String, path: String, hash: String },
DeleteScript { workspace: String, hash: String },
DeleteScriptPath { workspace: String, path: String },
CreateVariable { workspace: String, path: String },
UpdateVariable { workspace: String, old_path: String, new_path: String },
DeleteVariable { workspace: String, path: String },

View File

@@ -34,7 +34,7 @@ use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader},
process::{Child, Command},
sync::{
mpsc::{self, Sender}, watch,
mpsc::{self, Sender}, watch, broadcast,
},
time::{interval, sleep, Instant, MissedTickBehavior},
};
@@ -840,7 +840,7 @@ async fn handle_queued_job(
job_dir: &str,
metrics: Metrics,
same_worker_tx: Sender<Uuid>,
base_internal_url: &str
base_internal_url: &str,
) -> windmill_common::error::Result<()> {
if job.canceled {
return Err(Error::JsonErr(canceled_job_to_result(&job)))?;
@@ -888,10 +888,10 @@ async fn handle_queued_job(
logs.push_str(&format!("job {} on worker {}\n", &job.id, &worker_name));
let result = match job.job_kind {
JobKind::Dependencies => {
handle_dependency_job(&job, &mut logs, job_dir, db).await
handle_dependency_job(&job, &mut logs, job_dir, db, worker_name).await
}
JobKind::FlowDependencies => {
handle_flow_dependency_job(&job, &mut logs, job_dir, db)
handle_flow_dependency_job(&job, &mut logs, job_dir, db, worker_name)
.await
.map(|()| Value::Null)
}
@@ -912,7 +912,8 @@ async fn handle_queued_job(
job_dir,
worker_dir,
&mut logs,
base_internal_url
base_internal_url,
worker_name
)
.await
}
@@ -1064,7 +1065,9 @@ async fn handle_code_execution_job(
job_dir: &str,
worker_dir: &str,
logs: &mut String,
base_internal_url: &str
base_internal_url: &str,
worker_name: &str
) -> error::Result<serde_json::Value> {
let (inner_content, requirements_o, language) = match job.job_kind {
JobKind::Preview | JobKind::Script_Hub => (
@@ -1086,7 +1089,6 @@ async fn handle_code_execution_job(
"handle_code_execution_job should never be reachable with a non-code execution job"
),
};
let worker_name = worker_dir.split("/").last().unwrap_or("unknown");
let lang_str = job
.language
.as_ref()
@@ -1136,7 +1138,7 @@ mount {{
token,
&inner_content,
&shared_mount,
base_internal_url
base_internal_url,
)
.await
}
@@ -1151,7 +1153,8 @@ mount {{
&inner_content,
&shared_mount,
requirements_o,
base_internal_url
base_internal_url,
worker_name
)
.await
}
@@ -1166,7 +1169,8 @@ mount {{
job_dir,
requirements_o,
&shared_mount,
base_internal_url
base_internal_url,
worker_name
)
.await
}
@@ -1179,7 +1183,8 @@ mount {{
&inner_content,
job_dir,
&shared_mount,
base_internal_url
base_internal_url,
worker_name
)
.await
}
@@ -1208,6 +1213,7 @@ async fn handle_go_job(
requirements_o: Option<String>,
shared_mount: &str,
base_internal_url: &str,
worker_name: &str,
) -> Result<serde_json::Value, Error> {
//go does not like executing modules at temp root
let job_dir = &format!("{job_dir}/go");
@@ -1236,6 +1242,7 @@ async fn handle_go_job(
db,
true,
skip_go_mod,
worker_name
)
.await?;
@@ -1353,7 +1360,7 @@ func Run(req Req) (interface{{}}, error){{
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(&job.id, db, logs, build_go, false).await?;
handle_child(&job.id, db, logs, build_go, false, worker_name).await?;
Command::new(NSJAIL_PATH.as_str())
.current_dir(job_dir)
@@ -1379,7 +1386,7 @@ func Run(req Req) (interface{{}}, error){{
.stderr(Stdio::piped())
.spawn()?
};
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
read_result(job_dir).await
}
@@ -1393,6 +1400,7 @@ async fn handle_bash_job(
job_dir: &str,
shared_mount: &str,
base_internal_url: &str,
worker_name: &str,
) -> Result<serde_json::Value, Error> {
logs.push_str("\n\n--- BASH CODE EXECUTION ---\n");
set_logs(logs, &job.id, db).await;
@@ -1456,7 +1464,7 @@ async fn handle_bash_job(
.stderr(Stdio::piped())
.spawn()?
};
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
//for now bash jobs have an empty result object
Ok(serde_json::json!(logs
.lines()
@@ -1484,7 +1492,8 @@ async fn handle_deno_job(
inner_content: &String,
shared_mount: &str,
lockfile: Option<String>,
base_internal_url: &str
base_internal_url: &str,
worker_name: &str
) -> error::Result<serde_json::Value> {
logs.push_str("\n\n--- DENO CODE EXECUTION ---\n");
set_logs(logs, &job.id, db).await;
@@ -1619,7 +1628,7 @@ run().catch(async (e) => {{
}
.instrument(trace_span!("create_deno_jail"))
.await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
read_result(job_dir).await
}
@@ -1657,7 +1666,7 @@ async fn handle_python_job(
token: String,
inner_content: &String,
shared_mount: &str,
base_internal_url: &str
base_internal_url: &str,
) -> error::Result<serde_json::Value> {
create_dependencies_dir(job_dir).await;
@@ -1671,7 +1680,7 @@ async fn handle_python_job(
if requirements.is_empty() {
"".to_string()
} else {
pip_compile(&job.id, &requirements, logs, job_dir, db)
pip_compile(&job.id, &requirements, logs, job_dir, db, worker_name)
.await
.map_err(|e| {
Error::ExecutionErr(format!("pip compile failed: {}", e.to_string()))
@@ -1888,7 +1897,7 @@ mount {{
.spawn()?
};
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL).await?;
handle_child(&job.id, db, logs, child, !*DISABLE_NSJAIL, worker_name).await?;
read_result(job_dir).await
}
@@ -1918,6 +1927,7 @@ async fn handle_dependency_job(
logs: &mut String,
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>,
worker_name: &str,
) -> error::Result<serde_json::Value> {
let content = capture_dependency_job(
&job.id,
@@ -1932,7 +1942,8 @@ async fn handle_dependency_job(
.unwrap_or_else(|| "no raw code"),
logs,
job_dir,
db
db,
worker_name
)
.await;
match content {
@@ -1967,6 +1978,7 @@ async fn handle_flow_dependency_job(
logs: &mut String,
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>,
worker_name: &str,
) -> error::Result<()> {
let path = job.script_path.clone().ok_or_else(|| {
error::Error::InternalErr(
@@ -1997,6 +2009,7 @@ async fn handle_flow_dependency_job(
logs,
job_dir,
db,
worker_name
)
.await;
match new_lock {
@@ -2111,12 +2124,13 @@ async fn capture_dependency_job(
job_raw_code: &str,
logs: &mut String,
job_dir: &str,
db: &sqlx::Pool<sqlx::Postgres>
db: &sqlx::Pool<sqlx::Postgres>,
worker_name: &str
) -> error::Result<String> {
match job_language {
ScriptLang::Python3 => {
create_dependencies_dir(job_dir).await;
pip_compile(job_id, job_raw_code, logs, job_dir, db ).await
pip_compile(job_id, job_raw_code, logs, job_dir, db, worker_name).await
}
ScriptLang::Go => {
install_go_dependencies(
@@ -2127,6 +2141,7 @@ async fn capture_dependency_job(
db,
false,
false,
worker_name
)
.await
}
@@ -2144,6 +2159,7 @@ async fn pip_compile(
logs: &mut String,
job_dir: &str,
db: &Pool<Postgres>,
worker_name: &str
) -> error::Result<String> {
logs.push_str(&format!("\nresolving dependencies..."));
set_logs(logs, job_id, db).await;
@@ -2176,7 +2192,7 @@ async fn pip_compile(
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, child, false)
handle_child(job_id, db, logs, child, false, worker_name)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
let path_lock = format!("{job_dir}/requirements.txt");
@@ -2199,6 +2215,7 @@ async fn install_go_dependencies(
db: &sqlx::Pool<sqlx::Postgres>,
preview: bool,
skip_go_mod: bool,
worker_name: &str
) -> error::Result<String> {
if !skip_go_mod {
gen_go_mymod(code, job_dir).await?;
@@ -2209,7 +2226,7 @@ async fn install_go_dependencies(
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, child, false).await?;
handle_child(job_id, db, logs, child, false, worker_name).await?;
}
let child = Command::new(GO_PATH.as_str())
.current_dir(job_dir)
@@ -2217,7 +2234,7 @@ async fn install_go_dependencies(
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
handle_child(job_id, db, logs, child, false)
handle_child(job_id, db, logs, child, false, worker_name)
.await
.map_err(|e| Error::ExecutionErr(format!("Lock file generation failed: {e:?}")))?;
@@ -2323,6 +2340,7 @@ async fn handle_child(
logs: &mut String,
mut child: Child,
nsjail: bool,
worker_name: &str,
) -> error::Result<()> {
let update_job_interval = Duration::from_millis(500);
let write_logs_delay = Duration::from_millis(500);
@@ -2336,11 +2354,14 @@ async fn handle_child(
tracing::info!("could not get child pid");
}
let (set_too_many_logs, mut too_many_logs) = watch::channel::<bool>(false);
let (tx, mut rx) = broadcast::channel::<()>(3);
let mut rx2 = tx.subscribe();
let output = child_joined_output_stream(&mut child);
let job_id = job_id.clone();
let (tx, mut rx) = mpsc::channel::<()>(1);
/* the cancellation future is polled on by `wait_on_child` while
* waiting for the child to exit normally */
@@ -2350,10 +2371,22 @@ async fn handle_child(
let mut interval = interval(update_job_interval);
interval.set_missed_tick_behavior(MissedTickBehavior::Skip);
let mut i = 1;
loop {
tokio::select!(
_ = rx.recv() => break,
_ = interval.tick() => {
// update the last_ping column every 5 seconds
i+=1;
if i % 10 == 0 {
sqlx::query!(
"UPDATE worker_ping SET ping_at = now() WHERE worker = $1",
&worker_name
)
.execute(&db)
.await
.expect("update worker ping");
}
let mem_peak = get_mem_peak(pid, nsjail).await;
tracing::info!("{job_id} still running. mem peak: {}kB", mem_peak);
let mem_peak = if mem_peak > 0 { Some(mem_peak) } else { None };
@@ -2387,10 +2420,10 @@ async fn handle_child(
biased;
result = child.wait() => return result.map(Ok),
Ok(()) = too_many_logs.changed() => KillReason::TooManyLogs,
_ = update_job => KillReason::Cancelled,
_ = sleep(*TIMEOUT_DURATION) => KillReason::Timeout,
_ = update_job => KillReason::Cancelled,
};
tx.send(()).await.expect("rx should never be dropped");
tx.send(()).expect("rx should never be dropped");
drop(tx);
let set_reason = async {
@@ -2413,7 +2446,7 @@ async fn handle_child(
}
}
};
/* send SIGKILL and reap child process */
let (_, kill) = future::join(set_reason, child.kill()).await;
kill.map(|()| Err(kill_reason))
@@ -2429,12 +2462,13 @@ async fn handle_child(
/* log_remaining is zero when output limit was reached */
let mut log_remaining = max_log_size.saturating_sub(logs.chars().count());
let mut result = io::Result::Ok(());
let mut output = output;
let mut output = output.take_until(rx2.recv()).boxed();
/* `do_write` resolves the task, but does not contain the Result.
* It's useful to know if the task completed. */
let (mut do_write, mut write_result) = tokio::spawn(ready(())).remote_handle();
while let Some(line) = output.by_ref().next().await {
while let Some(line) = output.by_ref().next().await {
let do_write_ = do_write.shared();
let mut read_lines = stream::once(async { line })
@@ -2449,11 +2483,14 @@ async fn handle_child(
let mut joined = String::new();
while let Some(line) = read_lines.next().await {
match line {
Ok(_) if log_remaining == 0 => (),
Ok(line) => {
if line.is_empty() {
continue;
}
append_with_limit(&mut joined, &line, &mut log_remaining);
if log_remaining == 0 {
tracing::info!(%job_id, "Too many logs lines for job {job_id}");
let _ = set_too_many_logs.send(true);
@@ -2473,6 +2510,7 @@ async fn handle_child(
logs.push_str(&joined);
/* Ensure the last flush completed before starting a new one.
*
* This shouldn't pause since `take_until()` reads lines until `do_write`
@@ -2489,8 +2527,7 @@ async fn handle_child(
panic::resume_unwind(p);
}
(do_write, write_result) =
tokio::spawn(append_logs(job_id, joined, db.clone())).remote_handle();
(do_write, write_result) = tokio::spawn(append_logs(job_id, joined, db.clone())).remote_handle();
if let Err(err) = result {
tracing::error!(%job_id, %err, "error reading output for job {job_id}: {err}");
@@ -2500,6 +2537,7 @@ async fn handle_child(
if *set_too_many_logs.borrow() {
break;
}
}
/* drop our end of the pipe */
@@ -2813,7 +2851,7 @@ async fn handle_python_reqs(
.spawn()?
};
let child = handle_child(&job.id, db, logs, child, false).await;
let child = handle_child(&job.id, db, logs, child, false, worker_name).await;
tracing::info!(
worker_name = %worker_name,
job_id = %job.id,

View File

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

View File

@@ -139,6 +139,8 @@ async function list(opts: GlobalOptions) {
}),
)
.render();
console.log('Active: ' + colors.green.bold(activeName || 'none'))
}
async function switchC(opts: GlobalOptions, workspaceName: string) {
@@ -313,6 +315,8 @@ async function remove(_opts: GlobalOptions, name: string) {
async function whoami(_opts: GlobalOptions) {
await requireLogin(_opts)
console.log(await UserService.globalWhoami())
const activeName = await getActiveWorkspaceName(_opts);
console.log('Active: ' + colors.green.bold(activeName || 'none'))
}
const command = new Command()

View File

@@ -1,12 +1,12 @@
{
"name": "windmill",
"version": "1.72.0",
"version": "1.74.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "windmill",
"version": "1.72.0",
"version": "1.74.2",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
@@ -64,10 +64,12 @@
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"shepherd.js": "^10.0.1",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
"svelte": "^3.55.1",
"svelte-awesome": "^3.0.0",
"svelte-awesome-color-picker": "^2.4.1",
"svelte-check": "^3.0.2",
"svelte-highlight": "^6.2.1",
"svelte-overlay": "^1.4.1",
@@ -6652,6 +6654,24 @@
"node": ">=8"
}
},
"node_modules/shepherd.js": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
"dev": true,
"dependencies": {
"@popperjs/core": "^2.11.5",
"deepmerge": "^4.2.2",
"smoothscroll-polyfill": "^0.4.4"
},
"engines": {
"node": "12.* || 14.* || >= 16"
},
"funding": {
"type": "individual",
"url": "https://github.com/sponsors/rwwagner90"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -6738,6 +6758,12 @@
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
}
},
"node_modules/smoothscroll-polyfill": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
"dev": true
},
"node_modules/sorcery": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
@@ -7128,6 +7154,15 @@
"svelte": "^3.43.1"
}
},
"node_modules/svelte-awesome-color-picker": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/svelte-awesome-color-picker/-/svelte-awesome-color-picker-2.4.1.tgz",
"integrity": "sha512-IzICGgaMRMgyK4uukm27UdTFfN7s0QN3eoFyIGiXXY8FCPumS9T+vffVG4rNhiuUUP4ga5SoGUE+KXuNj5dsrw==",
"dev": true,
"dependencies": {
"colord": "^2.9.3"
}
},
"node_modules/svelte-chartjs": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/svelte-chartjs/-/svelte-chartjs-3.1.0.tgz",
@@ -12979,6 +13014,17 @@
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true
},
"shepherd.js": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/shepherd.js/-/shepherd.js-10.0.1.tgz",
"integrity": "sha512-R32v4b4b0N1gK/vxvRtdhty+SBZlBPcPTSYCwcaAQvFd0n6Xki7cRH6Sx0oD9WB/HCkqCNM1msyIyylXmq635w==",
"dev": true,
"requires": {
"@popperjs/core": "^2.11.5",
"deepmerge": "^4.2.2",
"smoothscroll-polyfill": "^0.4.4"
}
},
"signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -13039,6 +13085,12 @@
"is-fullwidth-code-point": "^3.0.0"
}
},
"smoothscroll-polyfill": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/smoothscroll-polyfill/-/smoothscroll-polyfill-0.4.4.tgz",
"integrity": "sha512-TK5ZA9U5RqCwMpfoMq/l1mrH0JAR7y7KRvOBx0n2869aLxch+gT9GhN3yUfjiw+d/DiF1mKo14+hd62JyMmoBg==",
"dev": true
},
"sorcery": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz",
@@ -13338,6 +13390,15 @@
"dev": true,
"requires": {}
},
"svelte-awesome-color-picker": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/svelte-awesome-color-picker/-/svelte-awesome-color-picker-2.4.1.tgz",
"integrity": "sha512-IzICGgaMRMgyK4uukm27UdTFfN7s0QN3eoFyIGiXXY8FCPumS9T+vffVG4rNhiuUUP4ga5SoGUE+KXuNj5dsrw==",
"dev": true,
"requires": {
"colord": "^2.9.3"
}
},
"svelte-chartjs": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/svelte-chartjs/-/svelte-chartjs-3.1.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.72.0",
"version": "1.74.2",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -43,10 +43,12 @@
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.3",
"prettier-plugin-svelte": "^2.9.0",
"shepherd.js": "^10.0.1",
"simple-svelte-autocomplete": "^2.5.1",
"stylelint-config-recommended": "^9.0.0",
"svelte": "^3.55.1",
"svelte-awesome": "^3.0.0",
"svelte-awesome-color-picker": "^2.4.1",
"svelte-check": "^3.0.2",
"svelte-highlight": "^6.2.1",
"svelte-overlay": "^1.4.1",

View File

@@ -232,8 +232,14 @@
<Badge color={validCode ? 'green' : 'red'} class="min-w-[60px] mr-3">
{validCode ? 'Valid' : 'Invalid'}
</Badge>
<div class="flex items-center divide-x">
<Popover notClickable placement="bottom" disapperTimoout={0} class="pr-1" disablePopup={!iconOnly}>
<div id="script-tutorial-3" class="flex items-center divide-x">
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="pr-1"
disablePopup={!iconOnly}
>
<Button
color="light"
btnClasses="!font-medium !h-full"
@@ -245,11 +251,15 @@
>
+Context Var
</Button>
<svelte:fragment slot="text">
Add context variable
</svelte:fragment>
<svelte:fragment slot="text">Add context variable</svelte:fragment>
</Popover>
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="px-1"
disablePopup={!iconOnly}
>
<Button
color="light"
btnClasses="!font-medium !h-full"
@@ -261,11 +271,15 @@
>
+Variable
</Button>
<svelte:fragment slot="text">
Add variable
</svelte:fragment>
<svelte:fragment slot="text">Add variable</svelte:fragment>
</Popover>
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="px-1"
disablePopup={!iconOnly}
>
<Button
btnClasses="!font-medium !h-full"
size="xs"
@@ -277,11 +291,15 @@
>
+Resource
</Button>
<svelte:fragment slot="text">
Add resource
</svelte:fragment>
<svelte:fragment slot="text">Add resource</svelte:fragment>
</Popover>
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="px-1"
disablePopup={!iconOnly}
>
<Button
btnClasses="!font-medium !h-full"
size="xs"
@@ -293,11 +311,15 @@
>
Reset
</Button>
<svelte:fragment slot="text">
Reset
</svelte:fragment>
<svelte:fragment slot="text">Reset</svelte:fragment>
</Popover>
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="px-1"
disablePopup={!iconOnly}
>
<Button
btnClasses="!font-medium !h-full"
size="xs"
@@ -320,11 +342,15 @@
{/if}
</span>
</Button>
<svelte:fragment slot="text">
Reload assistant
</svelte:fragment>
<svelte:fragment slot="text">Reload assistant</svelte:fragment>
</Popover>
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="px-1"
disablePopup={!iconOnly}
>
<Button
btnClasses="!font-medium"
size="xs"
@@ -342,7 +368,13 @@
</Popover>
</div>
</div>
<Popover notClickable placement="bottom" disapperTimoout={0} class="px-1" disablePopup={!iconOnly}>
<Popover
notClickable
placement="bottom"
disapperTimoout={0}
class="px-1"
disablePopup={!iconOnly}
>
<Button
btnClasses="!font-medium"
size="xs"
@@ -354,9 +386,7 @@
>
Script
</Button>
<svelte:fragment slot="text">
Script
</svelte:fragment>
<svelte:fragment slot="text">Script</svelte:fragment>
</Popover>
</div>

View File

@@ -4,23 +4,23 @@
import { initHistory, redo, undo } from '$lib/history'
import { userStore, workspaceStore } from '$lib/stores'
import { encodeState, formatCron, loadHubScripts, sendUserToast } from '$lib/utils'
import { faCalendarAlt, faEye, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
import { Redo, Undo } from 'lucide-svelte'
import { faCalendarAlt, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
import { setContext } from 'svelte'
import { writable, type Writable } from 'svelte/store'
import CenteredPage from './CenteredPage.svelte'
import { Button, ButtonPopup, ButtonPopupItem } from './common'
import { Button, ButtonPopup, ButtonPopupItem, UndoRedo } from './common'
import { dirtyStore } from './common/confirmationModal/dirtyStore'
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
import { OFFSET } from './CronInput.svelte'
import ScriptEditorDrawer from './flows/content/ScriptEditorDrawer.svelte'
import FlowEditor from './flows/FlowEditor.svelte'
import type { FlowState } from './flows/flowState'
import { dfs } from './flows/flowStore'
import FlowImportExportMenu from './flows/header/FlowImportExportMenu.svelte'
import FlowPreviewButtons from './flows/header/FlowPreviewButtons.svelte'
import { loadFlowSchedule, type Schedule } from './flows/scheduleUtils'
import type { FlowEditorContext } from './flows/types'
import { cleanInputs } from './flows/utils'
import { Tour } from './tutorial'
export let initialPath: string = ''
export let selectedId: string | undefined
@@ -28,6 +28,7 @@
export let loading = false
export let flowStore: Writable<Flow>
export let flowStateStore: Writable<FlowState>
export let tour = false
async function createSchedule(path: string) {
const { cron, args, enabled } = $scheduleStore
@@ -193,11 +194,66 @@
$: initialPath && $workspaceStore && loadSchedule()
loadHubScripts()
function onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case 'Z':
if (event.ctrlKey) {
$flowStore = redo(history)
event.preventDefault()
}
break
case 'z':
if (event.ctrlKey) {
$flowStore = undo(history, $flowStore)
$selectedIdStore = 'Input'
event.preventDefault()
}
break
case 's':
if (event.ctrlKey) {
saveFlow(false)
event.preventDefault()
}
break
case 'ArrowDown': {
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > -1 && idx < ids.length - 1) {
$selectedIdStore = ids[idx + 1]
event.preventDefault()
}
break
}
case 'ArrowUp': {
let ids = generateIds()
let idx = ids.indexOf($selectedIdStore)
if (idx > 0 && idx < ids.length) {
$selectedIdStore = ids[idx - 1]
event.preventDefault()
}
break
}
}
}
function generateIds() {
return [
'settings-metadata',
'constants',
...dfs($flowStore.value.modules, (module) => module.id)
]
}
</script>
<svelte:window on:keydown={onKeyDown} />
{#if tour}
<Tour tutorial="flow" />
{/if}
{#if !$userStore?.operator}
<ScriptEditorDrawer bind:this={$scriptEditorDrawer} />
<UnsavedConfirmationModal />
<div class="flex flex-col flex-1 h-screen">
<!-- Nav between steps-->
@@ -206,32 +262,17 @@
>
<div class="flex flex-row gap-4 items-center">
<FlowImportExportMenu />
<div class="flex gap-1">
<Button
title="Undo"
disabled={$history.index == 0}
variant="border"
color="dark"
size="xs"
on:click={async () => {
$flowStore = undo(history, $flowStore)
}}
>
<Undo size={14} />
</Button>
<Button
title="Redo"
disabled={$history.index == $history.history.length - 1}
variant="border"
color="dark"
size="xs"
on:click={async () => {
$flowStore = redo(history)
}}
>
<Redo size={14} />
</Button>
</div>
<UndoRedo
undoProps={{ disabled: $history.index === 0 }}
redoProps={{ disabled: $history.index === $history.history.length - 1 }}
on:undo={() => {
$flowStore = undo(history, $flowStore)
$selectedIdStore = 'Input'
}}
on:redo={() => {
$flowStore = redo(history)
}}
/>
</div>
<div class="gap-1 flex-row hidden md:flex shrink overflow-hidden">

View File

@@ -409,8 +409,8 @@
{/if}
</div>
<div class="flex-row flex justify-between">
<div><span class="font-mono text-sm">{path}</span></div>
<div class="flex-row flex justify-between w-full">
<div><span class="font-mono text-sm break-all">{path}</span></div>
<div class="text-red-600 text-2xs">{error}</div>
</div>
</div>

View File

@@ -11,17 +11,38 @@
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
const dispatch = createEventDispatcher()
let resources: Resource[] = []
export let initialValue: string | undefined = undefined
export let value: string | undefined = initialValue
export let resourceType: string | undefined = undefined
let valueSelect =
initialValue || value
? {
value: value ?? initialValue,
label: value ?? initialValue
}
: undefined
let collection = [valueSelect]
async function loadResources(resourceType: string | undefined) {
const v = value
resources = await ResourceService.listResource({ workspace: $workspaceStore!, resourceType })
value = v
const nc = (
await ResourceService.listResource({
workspace: $workspaceStore!,
resourceType
})
).map((x) => ({
value: x.path,
label: x.path
}))
if (!nc.find((x) => x.value == value) && (initialValue || value)) {
nc.push({ value: value ?? initialValue!, label: value ?? initialValue! })
}
collection = nc
}
$: {
if ($workspaceStore) {
loadResources(resourceType)
@@ -29,10 +50,6 @@
}
$: dispatch('change', value)
$: collection = resources.map((x) => ({
value: x.path,
label: x.path
}))
let appConnect: AppConnect
let resourceEditor: ResourceEditor
</script>
@@ -41,6 +58,7 @@
on:refresh={async (e) => {
await loadResources(resourceType)
value = e.detail
valueSelect = { value: e.detail, label: e.detail }
}}
newPageOAuth
bind:this={appConnect}
@@ -50,18 +68,24 @@
bind:this={resourceEditor}
on:refresh={async (e) => {
await loadResources(resourceType)
console.log(e)
if (e.detail) {
value = e.detail
valueSelect = { value: e.detail, label: e.detail }
}
}}
/>
<div class="flex flex-row gap-x-1 w-full">
<Select
listAutoWidth={false}
value={collection.find((x) => x.value == value)}
bind:justValue={value}
value={valueSelect}
on:change={(e) => {
value = e.detail.value
valueSelect = e.detail
}}
on:clear={() => {
value = undefined
valueSelect = undefined
}}
items={collection}
class="text-clip grow min-w-0"
placeholder="{resourceType} resource"

View File

@@ -12,7 +12,6 @@
import ScriptEditor from './ScriptEditor.svelte'
import ScriptSchema from './ScriptSchema.svelte'
import CenteredPage from './CenteredPage.svelte'
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
import { dirtyStore } from './common/confirmationModal/dirtyStore'
import { Button, ButtonPopup, ButtonPopupItem, Kbd } from './common'
import { faChevronDown, faChevronUp, faPen, faSave } from '@fortawesome/free-solid-svg-icons'
@@ -121,10 +120,7 @@
}
</script>
<svelte:window on:keydown={onKeyDown} />
{#if !$userStore?.operator}
<UnsavedConfirmationModal />
<div class="flex flex-col h-screen">
<!-- Nav between steps-->
<div class="flex flex-col w-full px-2 py-1 border-b shadow-sm">
@@ -350,6 +346,7 @@
lang={script.language}
{initialArgs}
{kind}
tour
/>
{:else if step === 3}
<CenteredPage>
@@ -360,3 +357,5 @@
{:else}
Script Builder not available to operators
{/if}
<svelte:window on:keydown={onKeyDown} />

View File

@@ -17,6 +17,7 @@
import { Button, Kbd } from './common'
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
import WindmillIcon from './icons/WindmillIcon.svelte'
import { Tour } from './tutorial'
// Exported
export let schema: Schema = emptySchema()
@@ -27,6 +28,7 @@
export let initialArgs: Record<string, any> = {}
export let fixedOverflowWidgets = true
export let noSyncFromGithub = false
export let tour = false
let websocketAlive = { pyright: false, black: false, deno: false, go: false }
@@ -114,6 +116,10 @@
<svelte:window on:keydown={onKeyDown} />
{#if tour}
<Tour tutorial="script" />
{/if}
<div class="border-b-2 shadow-sm px-1 pr-4" bind:clientWidth={width}>
<div class="flex justify-between space-x-2">
<EditorBar
@@ -146,6 +152,7 @@
<Splitpanes class="!overflow-visible">
<Pane size={60} minSize={10} class="!overflow-visible">
<div
id="script-tutorial-1"
class="pl-2 h-full !overflow-visible"
on:mouseleave={() => {
inferSchema(code)
@@ -180,7 +187,7 @@
</Pane>
<Pane size={40} minSize={10}>
<div class="flex flex-col h-full">
<div class="px-2 w-full border-b py-1">
<div id="script-tutorial-4" class="px-2 w-full border-b py-1">
{#if testIsLoading}
<Button on:click={testJobLoader?.cancelJob} btnClasses="w-full" color="red" size="xs">
<WindmillIcon
@@ -211,7 +218,7 @@
</div>
<Splitpanes horizontal class="!max-h-[calc(100%-43px)]">
<Pane size={33}>
<div class="px-2">
<div id="script-tutorial-2" class="px-2">
<div class="break-words relative font-sans">
<SchemaForm compact {schema} bind:args bind:isValid />
</div>

View File

@@ -186,9 +186,9 @@
syncIteration++
await loadTestJob(id)
let nextIteration = 50
if (syncIteration == ITERATIONS_BEFORE_SLOW_REFRESH) {
if (syncIteration > ITERATIONS_BEFORE_SLOW_REFRESH) {
nextIteration = 500
} else if (syncIteration == ITERATIONS_BEFORE_SUPER_SLOW_REFRESH) {
} else if (syncIteration > ITERATIONS_BEFORE_SUPER_SLOW_REFRESH) {
nextIteration = 2000
}
setTimeout(() => syncer(id), nextIteration)

View File

@@ -16,7 +16,7 @@
const dispatch = createEventDispatcher()
</script>
<span class="{$$props.class} z-auto">
<span class="{$$props.class ?? ''} z-auto">
<label
for={id}
class="inline-flex items-center mt-2 duration-200 {disabled

View File

@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -22,10 +22,11 @@
export let noWFull = false
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { runnableComponents, worldStore, app } = getContext<AppEditorContext>('AppEditorContext')
const { runnableComponents, worldStore, app } = getContext<AppViewerContext>('AppViewerContext')
let labelValue: string
let color: ButtonType.Color
@@ -141,6 +142,7 @@
autoRefresh={false}
goto={gotoUrl}
{gotoNewTab}
{render}
>
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
{#if errorsMessage}
@@ -164,11 +166,11 @@
{loading}
>
<span class="truncate inline-flex gap-2 items-center">
{#if beforeIconComponent}
{#if beforeIcon && beforeIconComponent}
<svelte:component this={beforeIconComponent} size={14} />
{/if}
<div>{labelValue}</div>
{#if afterIconComponent}
{#if afterIcon && afterIconComponent}
<svelte:component this={afterIconComponent} size={14} />
{/if}
</span>

View File

@@ -5,7 +5,7 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -19,11 +19,12 @@
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'button'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app, runnableComponents, worldStore, stateId } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -61,6 +62,7 @@
<InputValue {id} input={configuration.size} bind:value={size} />
<RunnableWrapper
{render}
bind:runnableComponent
{componentInput}
{id}

View File

@@ -5,7 +5,7 @@
import { Icon } from 'svelte-awesome'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -22,10 +22,11 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'button' | 'popup'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app, runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, runnableComponents, worldStore } = getContext<AppViewerContext>('AppViewerContext')
let labelValue: string = 'Default label'
let color: ButtonType.Color
@@ -93,6 +94,7 @@
}}
>
<RunnableWrapper
{render}
bind:runnableComponent
{componentInput}
{id}

View File

@@ -17,16 +17,17 @@
import InputValue from '../helpers/InputValue.svelte'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -89,7 +90,7 @@
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.line} bind:value={lineChart} />
<RunnableWrapper flexWrap 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}

View File

@@ -5,7 +5,7 @@
import type { AppInput } from '../../inputType'
import {
IS_APP_PUBLIC_CONTEXT_KEY,
type AppEditorContext,
type AppViewerContext,
type ComponentCustomCSS
} from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
@@ -14,15 +14,16 @@
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'header' | 'container'> | undefined = undefined
export let render: boolean
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let result: any = undefined
export const staticOutputs: string[] = ['result', 'loading']
</script>
<RunnableWrapper flexWrap {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',

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
@@ -9,9 +9,10 @@
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let result: string | undefined = undefined
let h: number | undefined = undefined
@@ -28,7 +29,15 @@
bind:clientHeight={h}
bind:clientWidth={w}
>
<RunnableWrapper autoRefresh flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper
{render}
autoRefresh
flexWrap
{componentInput}
{id}
bind:initializing
bind:result
>
{#key result}
<iframe
frameborder="0"

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import { AlignWrapper, InputValue } from '../helpers'
import { loadIcon } from '../icon'
@@ -12,8 +12,9 @@
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = []
export let customCss: ComponentCustomCSS<'container' | 'icon'> | undefined = undefined
export let render: boolean
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let icon: string | undefined = undefined
let size: number
@@ -21,12 +22,10 @@
let strokeWidth: number
let iconComponent: any
$: icon && handleIcon()
$: handleIcon(icon)
async function handleIcon() {
if (icon) {
iconComponent = await loadIcon(icon)
}
async function handleIcon(i?: string) {
iconComponent = i ? await loadIcon(i) : undefined
}
$: css = concatCustomCss($app.css?.iconcomponent, customCss)
@@ -38,12 +37,13 @@
<InputValue {id} input={configuration.strokeWidth} bind:value={strokeWidth} />
<AlignWrapper
{render}
{horizontalAlignment}
{verticalAlignment}
class={css?.container?.class ?? ''}
style={css?.container?.style ?? ''}
>
{#if iconComponent}
{#if icon && iconComponent}
<svelte:component
this={iconComponent}
size={size || 24}

View File

@@ -3,7 +3,7 @@
import { twMerge } from 'tailwind-merge'
import type { staticValues } from '../../editor/componentsPanel/componentStaticValues'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
@@ -13,8 +13,9 @@
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['loading']
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
export let render: boolean
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
const fit: Record<FitOption, string> = {
cover: 'object-cover',
contain: 'object-contain',
@@ -32,10 +33,12 @@
<InputValue {id} input={configuration.imageFit} bind:value={imageFit} />
<InputValue {id} input={configuration.altText} bind:value={altText} />
<img
on:pointerdown|preventDefault
src={source}
alt={altText}
style={css?.image?.style ?? ''}
class={twMerge(`w-full h-full ${fit[imageFit || 'cover']}`, css?.image?.class ?? '')}
/>
{#if render}
<img
on:pointerdown|preventDefault
src={source}
alt={altText}
style={css?.image?.style ?? ''}
class={twMerge(`w-full h-full ${fit[imageFit || 'cover']}`, css?.image?.class ?? '')}
/>
{/if}

View File

@@ -2,7 +2,7 @@
import { getContext, onMount } from 'svelte'
import { concatCustomCss } from '../../utils'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { InputValue } from '../helpers'
import { twMerge } from 'tailwind-merge'
import { Map, View, Feature } from 'ol'
@@ -32,9 +32,10 @@
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<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
$: outputs = $worldStore?.outputsById[id] as {
mapRegion: Output<{
@@ -192,23 +193,25 @@
<InputValue {id} input={configuration.zoom} bind:value={zoom} />
<InputValue {id} input={configuration.markers} bind:value={markers} />
<div class="relative h-full w-full">
<div
on:pointerdown|stopPropagation={selectComponent}
bind:this={mapElement}
class={twMerge(`w-full h-full`, css?.map?.class ?? '')}
style={css?.map?.style ?? ''}
/>
{#if $mode !== 'preview'}
{#if render}
<div class="relative h-full w-full">
<div
class="absolute bottom-0 left-0 px-1 py-0.5 bg-indigo-500 text-white text-2xs"
on:pointerdown={handleSyncRegion}
>
Set region
</div>
{/if}
</div>
on:pointerdown|stopPropagation={selectComponent}
bind:this={mapElement}
class={twMerge(`w-full h-full`, css?.map?.class ?? '')}
style={css?.map?.style ?? ''}
/>
{#if $mode !== 'preview'}
<div
class="absolute bottom-0 left-0 px-1 py-0.5 bg-indigo-500 text-white text-2xs"
on:pointerdown={handleSyncRegion}
>
Set region
</div>
{/if}
</div>
{/if}
<style global lang="postcss">
.ol-overlaycontainer-stopevent {

View File

@@ -4,7 +4,7 @@
import { getDocument, type PDFDocumentProxy, type PDFPageProxy } from 'pdfjs-dist'
import 'pdfjs-dist/build/pdf.worker.entry'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { throttle } from '../../../../utils'
@@ -17,8 +17,9 @@
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, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
const { app, mode, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let source: string | ArrayBuffer | undefined = undefined
let wrapper: HTMLDivElement | undefined = undefined
@@ -184,135 +185,137 @@
<InputValue {id} input={configuration.source} bind:value={source} />
<InputValue {id} input={configuration.zoom} bind:value={zoom} />
<div class="relative w-full h-full bg-gray-100">
{#if source && zoom}
{#if pages?.length}
<div
bind:clientWidth={controlsWidth}
bind:clientHeight={controlsHeight}
class="fixed flex {$mode !== 'preview'
? 'w-[calc(100%-2px)] top-[1px]'
: 'w-full top-0'} {wideView
? 'justify-center gap-14'
: '!justify-between'} overflow-x-auto bg-white border mx-auto py-1"
>
<div class="flex justify-start items-center px-2 text-gray-600 text-sm">
<Button
on:click={() => zoom && (zoom -= 10)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Zoom out"
aria-label="Zoom out"
btnClasses="!rounded-r-none !px-2"
>
<ZoomOut size={16} />
</Button>
{#if wideView}
{#if render}
<div class="relative w-full h-full bg-gray-100">
{#if source && zoom}
{#if pages?.length}
<div
bind:clientWidth={controlsWidth}
bind:clientHeight={controlsHeight}
class="fixed flex {$mode !== 'preview'
? 'w-[calc(100%-2px)] top-[1px]'
: 'w-full top-0'} {wideView
? 'justify-center gap-14'
: '!justify-between'} overflow-x-auto bg-white border mx-auto py-1"
>
<div class="flex justify-start items-center px-2 text-gray-600 text-sm">
<Button
on:click={() => (zoom = 100)}
on:click={() => zoom && (zoom -= 10)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Reset zoom"
aria-label="Reset zoom"
btnClasses="!w-[50px] !font-medium !rounded-none !border-l-0 !px-1"
title="Zoom out"
aria-label="Zoom out"
btnClasses="!rounded-r-none !px-2"
>
{zoom.toFixed(0)}%
<ZoomOut size={16} />
</Button>
{/if}
<Button
on:click={() => renderPdf(true, true)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Scale to viewport"
aria-label="Scale to viewport"
btnClasses="!rounded-none !border-l-0 !px-2"
>
<MoveHorizontal size={16} />
</Button>
<Button
on:click={() => zoom && (zoom += 10)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Zoom in"
aria-label="Zoom in"
btnClasses="!rounded-l-none !px-2 !border-l-0"
>
<ZoomIn size={16} />
</Button>
</div>
<div class="center-center px-2 text-gray-600 text-sm">
<input
on:input={({ currentTarget }) => {
scrollToPage(currentTarget.valueAsNumber)
}}
min="1"
max={pages.length}
value={pageNumber}
disabled={!doc}
type="number"
class="!w-[45px] !px-1 !py-0"
/>
<span class="whitespace-nowrap pl-1">
/ {pages.length}
</span>
</div>
<div class="flex justify-end items-center px-2 text-gray-600 text-sm">
<Button
on:click={downloadPdf}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Download PDF"
aria-label="Download PDF"
btnClasses="!font-medium !px-2"
>
{#if wideView}
<span class="mr-1"> Download </span>
<Button
on:click={() => (zoom = 100)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Reset zoom"
aria-label="Reset zoom"
btnClasses="!w-[50px] !font-medium !rounded-none !border-l-0 !px-1"
>
{zoom.toFixed(0)}%
</Button>
{/if}
<Download size={16} />
</Button>
<Button
on:click={() => renderPdf(true, true)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Scale to viewport"
aria-label="Scale to viewport"
btnClasses="!rounded-none !border-l-0 !px-2"
>
<MoveHorizontal size={16} />
</Button>
<Button
on:click={() => zoom && (zoom += 10)}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Zoom in"
aria-label="Zoom in"
btnClasses="!rounded-l-none !px-2 !border-l-0"
>
<ZoomIn size={16} />
</Button>
</div>
<div class="center-center px-2 text-gray-600 text-sm">
<input
on:input={({ currentTarget }) => {
scrollToPage(currentTarget.valueAsNumber)
}}
min="1"
max={pages.length}
value={pageNumber}
disabled={!doc}
type="number"
class="!w-[45px] !px-1 !py-0"
/>
<span class="whitespace-nowrap pl-1">
/ {pages.length}
</span>
</div>
<div class="flex justify-end items-center px-2 text-gray-600 text-sm">
<Button
on:click={downloadPdf}
disabled={!doc}
size="xs"
color="light"
variant="border"
title="Download PDF"
aria-label="Download PDF"
btnClasses="!font-medium !px-2"
>
{#if wideView}
<span class="mr-1"> Download </span>
{/if}
<Download size={16} />
</Button>
</div>
</div>
</div>
{:else}
{:else}
<div
out:fade={{ duration: 200 }}
class="absolute inset-0 center-center flex-col text-center text-sm bg-white text-gray-600"
>
<Loader2 class="animate-spin mb-2" />
Loading PDF
</div>
{/if}
<div
out:fade={{ duration: 200 }}
class="absolute inset-0 center-center flex-col text-center text-sm bg-white text-gray-600"
bind:this={wrapper}
on:scroll={throttledScroll}
class={twMerge('w-full h-full overflow-auto', css?.container?.class ?? '', 'bg-gray-100')}
style="padding-top: {controlsHeight ?? 0}px; {css?.container?.style ?? ''}"
/>
{/if}
{#if $mode !== 'preview' && $selectedComponent === id}
<button
class="fixed z-10 bottom-0 left-0 px-2 py-0.5 bg-indigo-500/90
hover:bg-indigo-500 focus:bg-indigo-500 duration-200 text-white text-2xs"
on:click={() => syncZoomValue()}
>
<Loader2 class="animate-spin mb-2" />
Loading PDF
Sync zoom value
</button>
{/if}
{#if error}
<div
class="absolute inset-0 z-20 center-center
bg-gray-100 text-center text-gray-600 text-sm"
>
{error}
</div>
{/if}
<div
bind:this={wrapper}
on:scroll={throttledScroll}
class={twMerge('w-full h-full overflow-auto', css?.container?.class ?? '', 'bg-gray-100')}
style="padding-top: {controlsHeight ?? 0}px; {css?.container?.style ?? ''}"
/>
{/if}
{#if $mode !== 'preview' && $selectedComponent === id}
<button
class="fixed z-10 bottom-0 left-0 px-2 py-0.5 bg-indigo-500/90
hover:bg-indigo-500 focus:bg-indigo-500 duration-200 text-white text-2xs"
on:click={() => syncZoomValue()}
>
Sync zoom value
</button>
{/if}
{#if error}
<div
class="absolute inset-0 z-20 center-center
bg-gray-100 text-center text-gray-600 text-sm"
>
{error}
</div>
{/if}
</div>
</div>
{/if}

View File

@@ -14,7 +14,7 @@
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
@@ -23,9 +23,10 @@
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -72,7 +73,7 @@
<InputValue {id} input={configuration.theme} bind:value={theme} />
<InputValue {id} input={configuration.doughnutStyle} bind:value={doughnut} />
<RunnableWrapper flexWrap 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}

View File

@@ -19,19 +19,20 @@
import type { ChartOptions, ChartData } from 'chart.js'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
let zoomable = false
let pannable = false
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
ChartJS.register(
Title,
@@ -78,7 +79,7 @@
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper flexWrap 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} />

View File

@@ -8,7 +8,7 @@
import InputValue from '../helpers/InputValue.svelte'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import { twMerge } from 'tailwind-merge'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { getContext } from 'svelte'
import ResizeWrapper from '../helpers/ResizeWrapper.svelte'
@@ -19,10 +19,11 @@
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let result: string | undefined = undefined
let style: 'Title' | 'Subtitle' | 'Body' | 'Caption' | 'Label' | undefined = undefined
@@ -67,7 +68,7 @@
<InputValue {id} input={configuration.copyButton} bind:value={copyButton} />
<InputValue {id} input={configuration.fitContent} bind:value={fitContent} />
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<ResizeWrapper {id} shouldWrap={fitContent}>
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
{#if !result || result === ''}

View File

@@ -20,7 +20,7 @@
import { Scatter } from 'svelte-chartjs'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { getContext } from 'svelte'
import { concatCustomCss } from '../../utils'
@@ -29,9 +29,10 @@
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let logarithmicScale = false
let zoomable = false
@@ -93,7 +94,7 @@
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
<InputValue {id} input={configuration.pannable} bind:value={pannable} />
<RunnableWrapper flexWrap 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} />

View File

@@ -7,6 +7,7 @@
export let componentInput: AppInput | undefined
// export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
@@ -15,18 +16,17 @@
let Plotly
onMount(async () => {
if (divEl) {
//@ts-ignore
await import('https://cdn.plot.ly/plotly-2.18.0.min.js')
//@ts-ignore
await import('https://cdn.plot.ly/plotly-2.18.0.min.js')
Plotly = window['Plotly']
}
Plotly = window['Plotly']
})
let h: number | undefined = undefined
let w: number | undefined = undefined
$: Plotly &&
render &&
result &&
divEl &&
h &&
@@ -40,7 +40,7 @@
</script>
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -8,6 +8,7 @@
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['result', 'loading']
@@ -52,7 +53,7 @@
<InputValue {id} input={configuration.canvas} bind:value={canvas} />
<div class="w-full h-full" bind:clientHeight={h} bind:clientWidth={w}>
<RunnableWrapper flexWrap {componentInput} {id} bind:initializing bind:result>
<RunnableWrapper {render} flexWrap {componentInput} {id} bind:initializing bind:result>
<div on:pointerdown bind:this={divEl} />
</RunnableWrapper>
</div>

View File

@@ -5,7 +5,7 @@
import { getContext, onMount } from 'svelte'
import type { Output } from '../../../rx'
import type { AppEditorContext } from '../../../types'
import type { AppViewerContext } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
@@ -17,16 +17,13 @@
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result', 'selectedRowIndex']
let result: Record<string, any>[] | undefined = undefined
const {
worldStore,
staticOutputs: staticOutputsStore,
selectedComponent
} = getContext<AppEditorContext>('AppEditorContext')
const { worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let selectedRowIndex = -1
@@ -84,7 +81,7 @@
<InputValue {id} input={configuration.pagination} bind:value={pagination} />
<InputValue {id} input={configuration.pageSize} bind:value={pageSize} />
<RunnableWrapper flexWrap {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"

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import type { Output } from '../../../rx'
import type { AppEditorContext, BaseAppComponent, ComponentCustomCSS } from '../../../types'
import type { AppViewerContext, BaseAppComponent, ComponentCustomCSS } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
@@ -25,6 +25,7 @@
export let customCss:
| ComponentCustomCSS<'container' | 'tableHeader' | 'tableBody' | 'tableFooter'>
| undefined = undefined
export let render: boolean
export const staticOutputs: string[] = [
'selectedRow',
@@ -60,7 +61,7 @@
app,
worldStore,
staticOutputs: staticOutputsStore
} = getContext<AppEditorContext>('AppEditorContext')
} = getContext<AppViewerContext>('AppViewerContext')
let selectedRowIndex = -1
@@ -150,7 +151,7 @@
<InputValue {id} input={configuration.search} bind:value={search} />
<RunnableWrapper flexWrap {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(
@@ -247,6 +248,7 @@
{#each actionButtons as actionButton, actionIndex (actionIndex)}
{#if rowIndex == 0}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {
@@ -258,6 +260,7 @@
/>
{:else}
<AppButton
{render}
noWFull
{...actionButton}
preclickAction={async () => {

View File

@@ -8,6 +8,7 @@
let c = ''
export { c as class }
export let style = ''
export let render: boolean = true
function tailwindHorizontalAlignment(alignment?: HorizontalAlignment) {
if (!alignment) return ''
@@ -39,6 +40,8 @@
)
</script>
<div class={classes} {style}>
<slot />
</div>
{#if render}
<div class={classes} {style}>
<slot />
</div>
{/if}

View File

@@ -19,6 +19,7 @@
</script>
<RunnableComponent
render={false}
{id}
{fields}
bind:result

View File

@@ -3,7 +3,7 @@
import { deepEqual } from 'fast-equals'
import { getContext } from 'svelte'
import type { AppInput, EvalAppInput, UploadAppInput } from '../../inputType'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import { accessPropertyByPath } from '../../utils'
type T = string | number | boolean | Record<string | number, any> | undefined
@@ -23,7 +23,7 @@
}
}
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
$: state = $worldStore?.state

View File

@@ -1,16 +1,19 @@
<script lang="ts">
import { Loader2 } from 'lucide-svelte'
import { getContext } from 'svelte'
import { fade } from 'svelte/transition'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import InputValue from './InputValue.svelte'
export let componentInput: AppInput
export let id: string
export let result: any
export let render: boolean
// Sync the result to the output
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
$: outputs = $worldStore?.outputsById[id] as {
loading: Output<boolean>
@@ -32,4 +35,16 @@
<InputValue {id} input={componentInput} bind:value={result} />
{/if}
<slot />
{#if render}
<slot />
{:else}
<div class="w-full h-full">
<div
out:fade|local={{ duration: 50 }}
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>
</div>
{/if}

View File

@@ -3,11 +3,11 @@
import { faRefresh } from '@fortawesome/free-solid-svg-icons'
import { RefreshCcw, RefreshCw } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
export let componentId: string
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { runnableComponents, worldStore } = getContext<AppViewerContext>('AppViewerContext')
async function refresh() {
window.dispatchEvent(new Event('pointerup'))

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { getContext } from 'svelte'
import { findGridItem } from '../../editor/appUtils'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
export let id: string
export let shouldWrap: boolean = false
const { app, breakpoint } = getContext<AppEditorContext>('AppEditorContext')
const { app, breakpoint } = getContext<AppViewerContext>('AppViewerContext')
$: gridItem = findGridItem($app, id)

View File

@@ -7,11 +7,12 @@
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import { AppService, type CompletedJob } from '$lib/gen'
import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
import { Bug } from 'lucide-svelte'
import { 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 { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import InputValue from './InputValue.svelte'
import RefreshButton from './RefreshButton.svelte'
@@ -29,6 +30,7 @@
export let initializing: boolean | undefined = undefined
export let gotoUrl: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
export let render: boolean
const {
worldStore,
@@ -41,7 +43,7 @@
errorByComponent,
mode,
stateId
} = getContext<AppEditorContext>('AppEditorContext')
} = getContext<AppViewerContext>('AppViewerContext')
onMount(() => {
if (autoRefresh) {
@@ -58,7 +60,6 @@
let executeTimeout: NodeJS.Timeout | undefined = undefined
function setDebouncedExecute() {
console.log('EXECUTE')
executeTimeout && clearTimeout(executeTimeout)
executeTimeout = setTimeout(() => {
executeComponent(true)
@@ -86,7 +87,6 @@
}
$: fields && (lazyStaticValues = computeStaticValues())
$: console.log(runnableInputValues, extraQueryParams, args, autoRefresh, testJobLoader)
$: (runnableInputValues || extraQueryParams || args) &&
autoRefresh &&
testJobLoader &&
@@ -258,57 +258,61 @@
bind:this={testJobLoader}
/>
<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">
<SchemaForm
{flexWrap}
schema={schemaStripped}
bind:args
{disabledArgs}
shouldHideNoInputs
noVariablePicker
/>
</div>
{/if}
{#if render}
<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">
<SchemaForm
{flexWrap}
schema={schemaStripped}
bind:args
{disabledArgs}
shouldHideNoInputs
noVariablePicker
/>
</div>
{/if}
{#if !runnable && autoRefresh}
<Alert type="warning" size="xs" class="mt-2 px-1" title="Missing runnable">
Please select a runnable
</Alert>
{:else if result?.error && $mode === 'preview'}
<div
title="Error"
class={classNames(
'text-red-500 px-1 text-2xs py-0.5 font-bold w-fit absolute border border-red-500 -bottom-2 shadow left-1/2 transform -translate-x-1/2 z-50 cursor-pointer',
'bg-red-100/80'
)}
>
<Popover notClickable placement="bottom" popupClass="!bg-white border w-96">
<Bug size={14} />
<span slot="text">
<div class="bg-white">
<Alert type="error" title="Error during execution">
<div class="flex flex-col gap-2">
An error occured, please contact the app author.
<span class="font-semibold">Job id: {testJob?.id}</span>
</div>
</Alert>
</div>
</span>
</Popover>
</div>
<div class="block grow w-full max-h-full border border-red-300 bg-red-50 relative">
<slot />
</div>
{:else}
<div class="block grow w-full max-h-full">
<slot />
</div>
{/if}
{#if !initializing && autoRefresh === true}
<div class="flex absolute top-1 right-1 z-50">
<RefreshButton componentId={id} />
</div>
{/if}
</div>
{#if !runnable && autoRefresh}
<Alert type="warning" size="xs" class="mt-2 px-1" title="Missing runnable">
Please select a runnable
</Alert>
{:else if result?.error && $mode === 'preview'}
<div
title="Error"
class={classNames(
'text-red-500 px-1 text-2xs py-0.5 font-bold w-fit absolute border border-red-500 -bottom-2 shadow left-1/2 transform -translate-x-1/2 z-50 cursor-pointer',
'bg-red-100/80'
)}
>
<Popover notClickable placement="bottom" popupClass="!bg-white border w-96">
<Bug size={14} />
<span slot="text">
<div class="bg-white">
<Alert type="error" title="Error during execution">
<div class="flex flex-col gap-2">
An error occured, please contact the app author.
<span class="font-semibold">Job id: {testJob?.id}</span>
</div>
</Alert>
</div>
</span>
</Popover>
</div>
<div class="block grow w-full max-h-full border border-red-300 bg-red-50 relative">
<slot />
</div>
{:else}
<div class="block grow w-full max-h-full">
<slot />
</div>
{/if}
{#if !initializing && autoRefresh === true}
<div class="flex absolute top-1 right-1 z-50">
<RefreshButton componentId={id} />
</div>
{/if}
</div>
{:else}
<div class="w-full h-full" />
{/if}

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import { isScriptByNameDefined, isScriptByPathDefined } from '../../utils'
import NonRunnableComponent from './NonRunnableComponent.svelte'
import RunnableComponent from './RunnableComponent.svelte'
@@ -20,8 +20,9 @@
export let runnableStyle = ''
export let goto: string | undefined = undefined
export let gotoNewTab: boolean | undefined = undefined
export let render: boolean
const { staticExporter, noBackend } = getContext<AppEditorContext>('AppEditorContext')
const { staticExporter, noBackend } = getContext<AppViewerContext>('AppViewerContext')
$: if (initializing && result) {
initializing = false
@@ -57,11 +58,12 @@
{initializing}
wrapperClass={runnableClass}
wrapperStyle={runnableStyle}
{render}
>
<slot />
</RunnableComponent>
{:else}
<NonRunnableComponent bind:result {id} {componentInput}>
<NonRunnableComponent {render} bind:result {id} {componentInput}>
<slot />
</NonRunnableComponent>
{/if}

View File

@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -13,8 +13,9 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
export const staticOutputs: string[] = ['result']
@@ -36,7 +37,7 @@
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()

View File

@@ -3,36 +3,38 @@
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputDefaultValue from '../helpers/InputDefaultValue.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let inputType: 'date' | 'time' | 'datetime-local'
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
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let input: HTMLInputElement
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let labelValue: string = 'Title'
let minValue: string = ''
let maxValue: string = ''
let defaultValue: string | undefined = undefined
let value: string | undefined = undefined
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string>
result: Output<string | undefined>
}
function handleInput() {
outputs?.result.set(input.value)
$: handleDefault(defaultValue)
$: outputs?.result.set(value)
function handleDefault(defaultValue: string | undefined) {
value = defaultValue
}
$: input && handleInput()
$: css = concatCustomCss($app.css?.dateinputcomponent, customCss)
</script>
@@ -41,17 +43,18 @@
<InputValue {id} input={configuration.maxDate} bind:value={maxValue} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<InputDefaultValue bind:input {defaultValue} />
<AlignWrapper {verticalAlignment}>
<input
type={inputType}
bind:this={input}
on:input={handleInput}
min={minValue}
max={maxValue}
placeholder="Type..."
class={twMerge('mx-0.5', css?.input?.class ?? '')}
style={css?.input?.style ?? ''}
/>
<AlignWrapper {render} {verticalAlignment}>
{#if inputType === 'date'}
<input
on:focus={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation
type="date"
bind:value
min={minValue}
max={maxValue}
placeholder="Type..."
class={twMerge('mx-0.5', css?.input?.class ?? '')}
style={css?.input?.style ?? ''}
/>
{/if}
</AlignWrapper>

View File

@@ -4,7 +4,7 @@
import { FileInput } from '../../../common'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
@@ -12,8 +12,9 @@
export let configuration: Record<string, AppInput>
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
let acceptedFileTypes: string[] | undefined = undefined
let allowMultiple: boolean | undefined = undefined
@@ -35,17 +36,19 @@
<InputValue {id} input={configuration.allowMultiple} bind:value={allowMultiple} />
<InputValue {id} input={configuration.text} bind:value={text} />
<div class="w-full h-full p-1">
<FileInput
accept={acceptedFileTypes?.length ? acceptedFileTypes?.join(', ') : undefined}
multiple={allowMultiple}
convertTo="base64"
on:change={({ detail }) => {
handleChange(detail)
}}
class={twMerge('w-full h-full', css?.container?.class)}
style={css?.container?.style}
>
{text}
</FileInput>
</div>
{#if render}
<div class="w-full h-full p-1">
<FileInput
accept={acceptedFileTypes?.length ? acceptedFileTypes?.join(', ') : undefined}
multiple={allowMultiple}
convertTo="base64"
on:change={({ detail }) => {
handleChange(detail)
}}
class={twMerge('w-full h-full', css?.container?.class)}
style={css?.container?.style}
>
{text}
</FileInput>
</div>
{/if}

View File

@@ -3,12 +3,11 @@
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { stopPropagation } from 'ol/events/Event'
export const staticOutputs: string[] = ['result']
export let id: string
@@ -16,9 +15,10 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let render: boolean
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
let items: { label: string; value: string }[]
let placeholder: string = 'Select an item'
@@ -57,7 +57,7 @@
<InputValue {id} input={configuration.items} bind:value={labels} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px" on:pointerdown|stopPropagation>
{#if !value || Array.isArray(value)}
<Select

View File

@@ -3,10 +3,9 @@
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputDefaultValue from '../helpers/InputDefaultValue.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
@@ -14,28 +13,29 @@
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<AppEditorContext>('AppEditorContext')
let input: HTMLInputElement
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let defaultValue: number | undefined = undefined
let placeholder: string | undefined = undefined
let value: number | undefined = undefined
let min: number | undefined = undefined
let max: number | undefined = undefined
let step = 1
$: outputs = $worldStore?.outputsById[id] as {
result: Output<number | null>
result: Output<number | undefined>
}
function handleInput() {
const value = input?.value
const num = isNaN(+value) ? null : +value
outputs?.result.set(num)
}
$: handleDefault(defaultValue)
$: input && handleInput()
$: outputs?.result.set(value)
function handleDefault(defaultValue: number | undefined) {
value = defaultValue
}
$: css = concatCustomCss($app.css?.numberinputcomponent, customCss)
</script>
@@ -45,21 +45,17 @@
<InputValue {id} input={configuration.max} bind:value={max} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<InputDefaultValue bind:input {defaultValue} />
<AlignWrapper {verticalAlignment}>
<AlignWrapper {render} {verticalAlignment}>
<input
on:pointerdown|stopPropagation
on:focus={() => ($selectedComponent = id)}
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
bind:this={input}
on:input={handleInput}
on:focus={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
bind:value
{min}
{max}
{step}

View File

@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { concatCustomCss } from '../../utils'
@@ -15,8 +15,9 @@
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'handles' | 'bar' | 'limits' | 'values'> | undefined =
undefined
export let render: boolean
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
let min = 0
let max = 42
let step = 1
@@ -48,7 +49,7 @@
<InputValue {id} input={configuration.defaultLow} bind:value={values[0]} />
<InputValue {id} input={configuration.defaultHigh} bind:value={values[1]} />
<AlignWrapper {verticalAlignment}>
<AlignWrapper {render} {verticalAlignment}>
<div class="flex flex-col w-full">
<div class="flex items-center w-full gap-1 px-1">
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>

View File

@@ -3,13 +3,11 @@
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { twMerge } from 'tailwind-merge'
import Portal from 'svelte-portal'
export const staticOutputs: string[] = ['result']
export let id: string
@@ -17,9 +15,10 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let render: boolean
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
let items: { label: string; value: any; created?: boolean }[]
let placeholder: string = 'Select an item'
@@ -98,7 +97,7 @@
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<InputValue {id} input={configuration.create} bind:value={create} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px;" on:pointerdown|stopPropagation>
<Select
--border-radius="0"

View File

@@ -3,7 +3,7 @@
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { twMerge } from 'tailwind-merge'
@@ -15,8 +15,9 @@
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'handle' | 'limits' | 'value' | 'bar'> | undefined =
undefined
export let render: boolean
const { app, worldStore, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let min = 0
let max = 42
let step = 1
@@ -56,7 +57,7 @@
<InputValue {id} input={configuration.max} bind:value={max} />
<InputValue {id} input={configuration.defaultValue} bind:value={values[0]} />
<AlignWrapper {verticalAlignment}>
<AlignWrapper {render} {verticalAlignment}>
<div class="flex items-center w-full gap-1 px-1">
<span class={css?.limits?.class ?? ''} style={css?.limits?.style ?? ''}>
{+min}

View File

@@ -3,10 +3,9 @@
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputDefaultValue from '../helpers/InputDefaultValue.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
@@ -15,45 +14,73 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export const staticOutputs: string[] = ['result']
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let appCssKey: 'textinputcomponent' | 'passwordinputcomponent' = 'textinputcomponent'
export let appCssKey: 'textinputcomponent' | 'passwordinputcomponent' | 'emailinputcomponent' =
'textinputcomponent'
export let render: boolean
const { app, worldStore } = getContext<AppEditorContext>('AppEditorContext')
let input: HTMLInputElement
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let placeholder: string | undefined = undefined
let defaultValue: string | undefined = undefined
let value: string | undefined = undefined
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string>
result: Output<string | undefined>
}
function handleInput() {
outputs?.result.set(input.value)
}
$: handleDefault(defaultValue)
$: input && handleInput()
$: outputs?.result.set(value)
function handleDefault(defaultValue: string | undefined) {
value = defaultValue
}
$: css = concatCustomCss($app.css?.[appCssKey], customCss)
</script>
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<InputDefaultValue bind:input {defaultValue} />
<AlignWrapper {verticalAlignment}>
<input
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
on:focus={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
type={inputType}
bind:this={input}
on:input={handleInput}
{placeholder}
/>
<AlignWrapper {render} {verticalAlignment}>
{#if inputType === 'password'}
<input
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
on:pointerdown|stopPropagation
on:focus={() => ($selectedComponent = id)}
type="password"
bind:value
{placeholder}
/>
{:else if inputType === 'text'}
<input
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
on:pointerdown|stopPropagation
on:focus={() => ($selectedComponent = id)}
type="text"
bind:value
{placeholder}
/>
{:else if inputType === 'email'}
<input
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
on:pointerdown|stopPropagation
on:focus={() => ($selectedComponent = id)}
type="email"
bind:value
{placeholder}
/>
{/if}
</AlignWrapper>

View File

@@ -3,7 +3,7 @@
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../../inputType'
import type { Output } from '../../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../../types'
import { concatCustomCss } from '../../../utils'
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
import InputValue from '../../helpers/InputValue.svelte'
@@ -14,8 +14,9 @@
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<AppEditorContext>('AppEditorContext')
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
let defaultValue: number | undefined = undefined
@@ -49,7 +50,7 @@
<InputValue {id} input={configuration.currency} bind:value={currency} />
<InputValue {id} input={configuration.locale} bind:value={locale} />
<AlignWrapper {verticalAlignment}>
<AlignWrapper {render} {verticalAlignment}>
{#key isNegativeAllowed}
{#key locale}
{#key currency}

View File

@@ -2,7 +2,7 @@
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS, GridItem } from '../../types'
import type { AppViewerContext, ComponentCustomCSS, GridItem } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
@@ -10,11 +10,12 @@
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
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<AppEditorContext>('AppEditorContext')
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
function onFocus() {
$focusedGrid = {
@@ -30,9 +31,10 @@
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div class="border">
<div>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={render}
{noPadding}
{id}
class={css?.container.class}

View File

@@ -3,7 +3,7 @@
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import type {
AppEditorContext,
AppViewerContext,
ComponentCustomCSS,
HorizontalAlignment,
VerticalAlignment
@@ -18,8 +18,9 @@
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'divider'> | undefined = undefined
export let position: 'horizontal' | 'vertical'
export let render: boolean
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let size = 2
let color = '#00000060'
@@ -34,6 +35,7 @@
{verticalAlignment}
class={twMerge(css?.container?.class, 'h-full')}
style={css?.container?.style}
{render}
>
<div
class={twMerge(

View File

@@ -2,7 +2,7 @@
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import Portal from 'svelte-portal'
import { concatCustomCss } from '../../utils'
@@ -16,8 +16,9 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
export let render: boolean
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let gridContent: string[] | undefined = undefined
let noPadding: boolean | undefined = undefined
@@ -69,7 +70,7 @@
<InputValue {id} input={configuration.fillContainer} bind:value={fillContainer} />
<Portal target="#app-editor-top-level-drawer">
<Drawer bind:this={appDrawer} size="800px" alwaysOpen positionClass="!absolute">
<Drawer let:open bind:this={appDrawer} size="800px" alwaysOpen positionClass="!absolute">
<DrawerContent
title={drawerTitle}
on:close={() => {
@@ -79,6 +80,7 @@
>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={open && render}
{noPadding}
{id}
class={css?.container.class}

View File

@@ -2,7 +2,7 @@
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
@@ -14,10 +14,11 @@
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let horizontal: boolean = false
export let panes: number[]
export let render: boolean
export const staticOutputs: string[] = []
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
let noPadding: boolean | undefined = undefined
@@ -31,6 +32,31 @@
$: $selectedComponent === id && onFocus()
$: css = concatCustomCss($app.css?.containercomponent, customCss)
$componentControl[id] = {
left: () => {
if ($focusedGrid?.subGridIndex) {
const index = $focusedGrid?.subGridIndex ?? 0
if (index > 0) {
$focusedGrid.subGridIndex = index - 1
return true
}
}
return false
},
right: () => {
// subGridIndex can be 0
if ($focusedGrid?.subGridIndex !== undefined) {
const index = $focusedGrid?.subGridIndex ?? 0
if (index < panes.length - 1) {
$focusedGrid.subGridIndex = index + 1
return true
}
}
return false
}
}
let sumedup = [50, 50]
$: {
let ns = panes.map((x) => (x / panes.reduce((a, b) => a + b, 0)) * 100)
@@ -48,6 +74,7 @@
<Pane size={paneSize} minSize={20}>
{#if $app.subgrids?.[`${id}-${index}`]}
<SubGridEditor
visible={render}
noYPadding
{noPadding}
{id}

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { Tab, Tabs } from '$lib/components/common'
import { getContext } from 'svelte'
import { getContext, onMount } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import type { AppEditorContext, AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
@@ -15,31 +15,26 @@
export let customCss:
| ComponentCustomCSS<'tabRow' | 'tabs' | 'selectedTab' | 'container'>
| undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['selectedTabIndex']
const { app, worldStore, focusedGrid, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
let selected: string = ''
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
let selected: string = tabs[0]
let noPadding: boolean | undefined = undefined
$: selectedIndex = tabs?.indexOf(selected) ?? -1
$: if ((tabs && selected === '') || !tabs.includes(selected)) {
selected = tabs[0]
}
$: outputs = $worldStore?.outputsById[id] as {
selectedTabIndex: Output<number | null>
}
$: outputs?.selectedTabIndex && handleOutputs()
let tabHeight: number = 0
function handleTabSelection() {
outputs?.selectedTabIndex.set(selectedIndex)
const selectedIndex = tabs?.indexOf(selected)
outputs?.selectedTabIndex.set(selectedIndex, true)
if ($selectedComponent != id) {
$selectedComponent = id
}
if ($focusedGrid?.parentComponentId != id || $focusedGrid?.subGridIndex != selectedIndex) {
$focusedGrid = {
parentComponentId: id,
@@ -48,24 +43,46 @@
}
}
$: selectedIndex >= 0 && handleTabSelection()
$: $selectedComponent === id && focusGrid()
let tabHeight: number = 0
function onFocus() {
$focusedGrid = {
parentComponentId: id,
subGridIndex: selectedIndex ?? 0
function focusGrid() {
const selectedIndex = tabs?.indexOf(selected)
if ($focusedGrid?.parentComponentId != id || $focusedGrid?.subGridIndex != selectedIndex) {
$focusedGrid = {
parentComponentId: id,
subGridIndex: selectedIndex
}
}
}
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
$componentControl[id] = {
left: () => {
const index = tabs.indexOf(selected)
if (index > 0) {
selected = tabs[index - 1]
return true
}
return false
},
right: () => {
const index = tabs.indexOf(selected)
if (index < tabs.length - 1) {
selected = tabs[index + 1]
return true
}
return false
}
}
$: selected && handleTabSelection()
$: outputs = $worldStore?.outputsById[id] as {
selectedTabIndex: Output<number>
}
$: selectedIndex = tabs?.indexOf(selected) ?? -1
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
function handleOutputs() {
outputs?.selectedTabIndex.set(outputs.selectedTabIndex.peak())
}
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
@@ -90,7 +107,7 @@
{#each tabs ?? [] as res, i}
<SubGridEditor
{id}
visible={i === selectedIndex}
visible={render && i === selectedIndex}
bind:subGrid={$app.subgrids[`${id}-${i}`]}
{noPadding}
class={css?.container?.class}

View File

@@ -4,11 +4,12 @@
import { twMerge } from 'tailwind-merge'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { writable } from 'svelte/store'
import { writable, type Writable } from 'svelte/store'
import { buildWorld, type World } from '../rx'
import type {
App,
AppEditorContext,
AppViewerContext,
ConnectingInput,
EditorBreakpoint,
EditorMode,
@@ -32,11 +33,14 @@
import SettingsPanel from './SettingsPanel.svelte'
import { fly } from 'svelte/transition'
import type { Policy } from '$lib/gen'
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import { VariableService, type Policy } from '$lib/gen'
import { page } from '$app/stores'
import CssSettings from './componentsPanel/CssSettings.svelte'
import { initHistory } from '$lib/history'
import { Tour } from '../../tutorial'
import ComponentNavigation from './component/ComponentNavigation.svelte'
import ItemPicker from '$lib/components/ItemPicker.svelte'
import VariableEditor from '$lib/components/VariableEditor.svelte'
export let app: App
export let path: string
@@ -44,6 +48,7 @@
export let policy: Policy
export let summary: string
export let fromHub: boolean = false
export let tour: boolean = false
const appStore = writable<App>(app)
const worldStore = writable<World | undefined>(undefined)
@@ -62,8 +67,9 @@
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
const errorByComponent = writable<Record<string, { error: string; componentId: string }>>({})
const focusedGrid = writable<FocusedGrid | undefined>(undefined)
const pickVariableCallback: Writable<((path: string) => void) | undefined> = writable(undefined)
setContext<AppEditorContext>('AppEditorContext', {
setContext<AppViewerContext>('AppViewerContext', {
worldStore,
staticOutputs,
app: appStore,
@@ -84,8 +90,13 @@
openDebugRun: writable(undefined),
focusedGrid,
stateId: writable(0),
parentWidth: writable(0),
history
parentWidth: writable(0)
})
setContext<AppEditorContext>('AppEditorContext', {
history,
componentControl: writable({}),
pickVariableCallback
})
let timeout: NodeJS.Timeout | undefined = undefined
@@ -109,10 +120,16 @@
mounted = true
})
$: context = {
let context = {
email: $userStore?.email,
username: $userStore?.username,
query: Object.fromEntries($page.url.searchParams.entries())
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))
@@ -125,15 +142,28 @@
} else {
selectedTab = 'insert'
}
let itemPicker: ItemPicker | undefined = undefined
$: if ($pickVariableCallback) {
itemPicker?.openDrawer()
}
let variableEditor: VariableEditor | undefined = undefined
</script>
<svelte:window on:hashchange={hashchange} />
{#if tour}
<Tour tutorial="app" />
{/if}
{#if $connectingInput.opened}
<div
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
/>
{/if}
{#if !$userStore?.operator}
<UnsavedConfirmationModal />
{#if initialMode !== 'preview'}
<AppEditorHeader {policy} {fromHub} />
{/if}
@@ -172,6 +202,7 @@
$selectedComponent = undefined
$focusedGrid = undefined
}}
id="app-tutorial-3"
class={twMerge(
'bg-gray-100 h-full w-full',
$appStore.css?.['app']?.['viewer']?.class
@@ -185,6 +216,8 @@
)}
>
{#if $appStore.grid}
<ComponentNavigation />
<div on:pointerdown|stopPropagation class={width}>
<GridEditor {policy} />
</div>
@@ -195,7 +228,7 @@
</div>
</Pane>
<Pane size={$connectingInput?.opened ? 0 : 30}>
<div class="relative h-full w-full">
<div id="app-tutorial-4" class="relative h-full w-full">
<InlineScriptsPanel />
</div>
</Pane>
@@ -204,7 +237,7 @@
</Pane>
<Pane size={21} minSize={5} maxSize={33}>
<div class="relative flex flex-col h-full">
<Tabs bind:selected={selectedTab} class="!border-b-2 !border-gray-200 !h-full">
<Tabs bind:selected={selectedTab} wrapperClass="!h-[40px]" class="!h-full">
<Tab value="insert" size="xs">
<div class="m-1 center-center gap-2">
<Icon data={faPlus} />
@@ -274,3 +307,36 @@
{:else}
App editor not available to operators
{/if}
<ItemPicker
bind:this={itemPicker}
pickCallback={(path, _) => {
$pickVariableCallback?.(path)
}}
itemName="Variable"
extraField="path"
loadItems={async () =>
(await VariableService.listVariable({ workspace: $workspaceStore ?? '' })).map((x) => ({
name: x.path,
...x
}))}
>
<div
slot="submission"
class="flex flex-row-reverse w-full bg-white border-t border-gray-200 rounded-bl-lg rounded-br-lg"
>
<Button
variant="border"
color="blue"
size="sm"
startIcon={{ icon: faPlus }}
on:click={() => {
variableEditor?.initNew?.()
}}
>
New variable
</Button>
</div>
</ItemPicker>
<VariableEditor bind:this={variableEditor} />

View File

@@ -7,7 +7,8 @@
ButtonPopup,
ButtonPopupItem,
Drawer,
DrawerContent
DrawerContent,
UndoRedo
} from '$lib/components/common'
import Button from '$lib/components/common/button/Button.svelte'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
@@ -26,7 +27,7 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import { AppService, Job, Policy } from '$lib/gen'
import { redo, undo } from '$lib/history'
import { userStore, workspaceStore } from '$lib/stores'
import { workspaceStore } from '$lib/stores'
import {
faBug,
faClipboard,
@@ -60,7 +61,7 @@
StaticAppInput,
UserAppInput
} from '../inputType'
import type { AppEditorContext } from '../types'
import type { AppEditorContext, AppViewerContext } from '../types'
import { allItems, toStatic } from '../utils'
import AppExportButton from './AppExportButton.svelte'
import AppInputs from './AppInputs.svelte'
@@ -89,9 +90,10 @@
errorByComponent,
openDebugRun,
focusedGrid,
selectedComponent,
history
} = getContext<AppEditorContext>('AppEditorContext')
selectedComponent
} = getContext<AppViewerContext>('AppViewerContext')
const { history } = getContext<AppEditorContext>('AppEditorContext')
const loading = {
publish: false,
@@ -159,7 +161,7 @@
return []
})
.concat(
Object.values($app.hiddenInlineScripts).map(async (v) => {
Object.values($app.hiddenInlineScripts ?? {}).map(async (v) => {
let hex = await hash(v.inlineScript?.content)
const staticInputs = collectStaticFields(v.fields)
return [`rawscript/${hex}`, staticInputs]
@@ -167,9 +169,6 @@
)
)
policy.triggerables = Object.fromEntries(allTriggers.filter((x) => x.length > 0))
policy.on_behalf_of = `u/${$userStore?.username}`
policy.on_behalf_of_email = $userStore?.email
}
async function createApp(path: string) {
await computeTriggerables()
@@ -242,8 +241,51 @@
$: selectedJobId && testJobLoader?.watchJob(selectedJobId)
$: hasErrors = Object.keys($errorByComponent).length > 0
function onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case 'Z':
if (event.ctrlKey) {
$app = redo(history)
event.preventDefault()
}
break
case 'z':
if (event.ctrlKey) {
$app = undo(history, $app)
event.preventDefault()
}
break
case 's':
if (event.ctrlKey) {
save()
event.preventDefault()
}
break
// case 'ArrowDown': {
// let ids = generateIds()
// let idx = ids.indexOf($selectedIdStore)
// if (idx > -1 && idx < ids.length - 1) {
// $selectedIdStore = ids[idx + 1]
// event.preventDefault()
// }
// break
// }
// case 'ArrowUp': {
// let ids = generateIds()
// let idx = ids.indexOf($selectedIdStore)
// if (idx > 0 && idx < ids.length) {
// $selectedIdStore = ids[idx - 1]
// event.preventDefault()
// }
// break
// }
}
}
</script>
<svelte:window on:keydown={onKeyDown} />
<TestJobLoader bind:this={testJobLoader} bind:isLoading={testIsLoading} bind:job />
<Drawer bind:open={saveDrawerOpen} size="800px">
@@ -428,32 +470,16 @@
<div class="min-w-64 w-64">
<input type="text" placeholder="App summary" class="text-sm w-full" bind:value={$summary} />
</div>
<div class="flex gap-1">
<Button
title="Undo"
disabled={$history.index == 0}
variant="border"
color="dark"
size="xs"
on:click={async () => {
$app = undo(history, $app)
}}
>
<Undo size={14} />
</Button>
<Button
title="Redo"
disabled={$history.index == $history.history.length - 1}
variant="border"
color="dark"
size="xs"
on:click={async () => {
$app = redo(history)
}}
>
<Redo size={14} />
</Button>
</div>
<UndoRedo
undoProps={{ disabled: $history.index === 0 }}
redoProps={{ disabled: $history.index === $history.history.length - 1 }}
on:undo={() => {
$app = undo(history, $app)
}}
on:redo={() => {
$app = redo(history)
}}
/>
<div class="flex gap-4 items-center grow justify-center">
<div>
<ToggleButtonGroup bind:selected={$mode}>
@@ -486,7 +512,7 @@
<ToggleButton position="left" value={false} size="xs">
<div class="flex gap-1 justify-start items-center">
<AlignHorizontalSpaceAround size={14} />
<Tooltip light>
<Tooltip light class="mb-0.5">
The max width is 1168px and the content stay centered instead of taking the full page
width
</Tooltip>

View File

@@ -2,11 +2,11 @@
import { Alert } from '$lib/components/common'
import Toggle from '$lib/components/Toggle.svelte'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../types'
import type { AppViewerContext } from '../types'
import AppComponentInput from './AppComponentInput.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let resourceOnly: boolean = true
</script>

View File

@@ -1,12 +1,10 @@
<script lang="ts">
import { onMount, setContext } from 'svelte'
import { fade } from 'svelte/transition'
import { cubicOut } from 'svelte/easing'
import { writable, type Writable } from 'svelte/store'
import { buildWorld, type World } from '../rx'
import type {
App,
AppEditorContext,
AppViewerContext,
ConnectingInput,
EditorBreakpoint,
EditorMode
@@ -42,7 +40,7 @@
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
setContext<AppEditorContext>('AppEditorContext', {
setContext<AppViewerContext>('AppViewerContext', {
worldStore,
staticOutputs,
app: appStore,
@@ -63,8 +61,7 @@
openDebugRun: writable(undefined),
focusedGrid: writable(undefined),
stateId: writable(0),
parentWidth: writable(0),
history: writable<any>(undefined)
parentWidth: writable(0)
})
let mounted = false
@@ -73,11 +70,20 @@
mounted = true
})
$: mounted && ($worldStore = buildWorld($staticOutputs, $worldStore, context))
let ncontext = context
function hashchange(e: HashChangeEvent) {
ncontext.hash = e.newURL.split('#')[1]
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>
<svelte:window on:hashchange={hashchange} />
<div id="app-editor-top-level-drawer" />
<div class="relative">

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import type { AppEditorContext } from '../types'
import { Anchor, Bug, Move } from 'lucide-svelte'
import type { AppViewerContext } from '../types'
import { Anchor, Bug, Expand, Move } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import Popover from '$lib/components/Popover.svelte'
import { Alert, Button } from '$lib/components/common'
@@ -15,7 +15,7 @@
const dispatch = createEventDispatcher()
const { errorByComponent, openDebugRun } = getContext<AppEditorContext>('AppEditorContext')
const { errorByComponent, openDebugRun } = getContext<AppViewerContext>('AppViewerContext')
$: error = Object.values($errorByComponent).find((e) => e.componentId === component.id)
@@ -55,6 +55,17 @@
<Anchor aria-label="Lock position" size={14} />
{/if}
</button>
<button
title="Position locking"
class={classNames(
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[0.5rem] z-50 cursor-pointer',
' hover:bg-gray-300',
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
)}
on:click={() => dispatch('expand')}
>
<Expand aria-label="Lock position" size={14} />
</button>
{/if}
{#if selected || hover}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { getContext, afterUpdate } from 'svelte'
import type { AppEditorContext, GridItem } from '../types'
import type { App, AppEditorContext, AppViewerContext, GridItem } from '../types'
import Grid from '@windmill-labs/svelte-grid'
import { classNames } from '$lib/utils'
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
@@ -10,6 +10,9 @@
import type { Policy } from '$lib/gen'
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
import Component from './component/Component.svelte'
import { deepEqual } from 'fast-equals'
import { push } from '$lib/history'
import { expandGriditem, findGridItem } from './appUtils'
export let policy: Policy
@@ -22,8 +25,11 @@
runnableComponents,
summary,
focusedGrid,
parentWidth
} = getContext<AppEditorContext>('AppEditorContext')
parentWidth,
breakpoint
} = getContext<AppViewerContext>('AppViewerContext')
const { history } = getContext<AppEditorContext>('AppEditorContext')
// The drag is disabled when the user is connecting an input
// or when the user is previewing the app
@@ -100,11 +106,16 @@
}
let pointerdown = false
let lastapp: App | undefined = undefined
const onpointerdown = () => {
lastapp = JSON.parse(JSON.stringify($app))
pointerdown = true
}
const onpointerup = () => {
pointerdown = false
if (!deepEqual(lastapp, $app)) {
push(history, lastapp, false, true)
}
}
afterUpdate(() => {
@@ -169,13 +180,14 @@
<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"
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
>
{dataItem.data.id}
</div>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
@@ -188,17 +200,23 @@
)}
>
<Component
render={true}
{pointerdown}
component={gridComponent.data}
selected={$selectedComponent === dataItem.data.id}
locked={isFixed(gridComponent)}
on:delete={() => removeGridElement(gridComponent.data)}
on:lock={() => {
let fComponent = $app.grid.find((c) => c.id === gridComponent.id)
if (fComponent) {
fComponent = toggleFixed(fComponent)
const gridItem = findGridItem($app, gridComponent.data.id)
if (gridItem) {
toggleFixed(gridItem)
}
}}
on:expand={() => {
push(history, $app)
expandGriditem($app.grid, gridComponent, $breakpoint)
$app = { ...$app }
}}
/>
</div>
{/if}

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext, GridItem } from '../types'
import type { AppViewerContext, GridItem } from '../types'
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
const { selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
export let gridItems: GridItem[]
export let parent: string | undefined
</script>

View File

@@ -3,9 +3,9 @@
import { ChevronDown, RefreshCw } from 'lucide-svelte'
import { getContext, onMount } from 'svelte'
import Button from '../../common/button/Button.svelte'
import type { AppEditorContext } from '../types'
import type { AppViewerContext } from '../types'
const { runnableComponents } = getContext<AppEditorContext>('AppEditorContext')
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
let loading: boolean = false
let timeout: NodeJS.Timer | undefined = undefined
let interval: number | undefined = undefined
@@ -14,14 +14,14 @@
$: componentNumber = Object.keys($runnableComponents).length
function onClick(stopAfterClear = true) {
if(timeout) {
if (timeout) {
clearInterval(timeout)
timeout = undefined
shouldRefresh = false
if(stopAfterClear) return;
if (stopAfterClear) return
}
refresh()
if(interval) {
if (interval) {
shouldRefresh = true
timeout = setInterval(refresh, interval)
}
@@ -44,12 +44,12 @@
}
function visChange() {
if(document.visibilityState === 'hidden') {
if(timeout) {
if (document.visibilityState === 'hidden') {
if (timeout) {
clearInterval(timeout)
timeout = undefined
}
} else if(shouldRefresh) {
} else if (shouldRefresh) {
timeout = setInterval(refresh, interval)
}
}
@@ -58,7 +58,7 @@
document.addEventListener('visibilitychange', visChange)
return () => {
document.removeEventListener('visibilitychange', visChange)
if(timeout) clearInterval(timeout)
if (timeout) clearInterval(timeout)
}
})
</script>
@@ -66,17 +66,19 @@
<div class="flex items-center">
<Button
on:click={() => onClick()}
color="{timeout ? 'blue' : 'light'}"
variant="{timeout ? 'contained' : 'border'}"
color={timeout ? 'blue' : 'light'}
variant={timeout ? 'contained' : 'border'}
size="xs"
btnClasses="!rounded-r-none {timeout ? '!border !border-blue-500' : ''}"
title="Refresh {componentNumber} component{componentNumber > 1 ? 's' : ''} {interval ? `every ${interval / 1000} seconds` : 'once'}"
title="Refresh {componentNumber} component{componentNumber > 1 ? 's' : ''} {interval
? `every ${interval / 1000} seconds`
: 'once'}"
>
<RefreshCw class={loading ? 'animate-spin' : ''} size={16} />
</Button>
<Dropdown
btnClasses="!rounded-l-none !border-l-0 min-w-[4rem] !px-2"
color="{timeout ? 'blue' : 'light'}"
color={timeout ? 'blue' : 'light'}
variant="border"
dropdownItems={[
{
@@ -86,7 +88,7 @@
...[1, 2, 3, 4, 5, 6].map((i) => ({
displayName: `Every ${i * 5} seconds`,
action: () => setInter(i * 5000)
})),
}))
]}
>
<span class="grow text center">{interval ? `${interval / 1000}s` : 'once'}</span>

View File

@@ -1,11 +1,11 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext } from '../types'
import type { AppViewerContext } from '../types'
import GridPanel from './GridPanel.svelte'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
const { selectedComponent, app, stateId } = getContext<AppEditorContext>('AppEditorContext')
const { selectedComponent, app, stateId } = getContext<AppViewerContext>('AppViewerContext')
</script>
{#if $app.grid}

View File

@@ -4,9 +4,10 @@
import Grid from '@windmill-labs/svelte-grid'
import { twMerge } from 'tailwind-merge'
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
import type { AppEditorContext, GridItem } from '../types'
import type { AppEditorContext, AppViewerContext, GridItem } from '../types'
import Component from './component/Component.svelte'
import { findGridItem } from './appUtils'
import { expandGriditem, findGridItem } from './appUtils'
import { push } from '$lib/history'
export let containerHeight: number
let classes = ''
@@ -21,8 +22,10 @@
const dispatch = createEventDispatcher()
const { app, connectingInput, selectedComponent, focusedGrid, mode, parentWidth } =
getContext<AppEditorContext>('AppEditorContext')
const { app, connectingInput, selectedComponent, focusedGrid, mode, parentWidth, breakpoint } =
getContext<AppViewerContext>('AppViewerContext')
const { history } = getContext<AppEditorContext>('AppEditorContext')
$: highlight = id === $focusedGrid?.parentComponentId && shouldHighlight
@@ -64,8 +67,6 @@
// @ts-ignore
let container
// $: containerHeight = subGrid.map((item) => columnConfiguration.map((c) => item[c[1]].h))
</script>
<div
@@ -123,11 +124,24 @@
)}
>
<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
}
push(history, $app)
expandGriditem(subGrid, gridComponent, $breakpoint, parentGridItem)
$app = { ...$app }
}}
/>
</div>
{/if}

View File

@@ -1,5 +1,5 @@
import { getNextId } from '$lib/components/flows/flowStateUtils'
import type { App, FocusedGrid, GridItem } from '../types'
import type { App, EditorBreakpoint, FocusedGrid, GridItem } from '../types'
import { getRecommendedDimensionsByComponent, type AppComponent } from './component'
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
import { gridColumns } from '../gridUtils'
@@ -18,6 +18,21 @@ function findGridItemById(
return undefined
}
export function findGridItemParentId(app: App, id: string): string | undefined {
const gridItem = app.grid.find((x) => x.id === id)
if (gridItem) {
return undefined
} else {
for (const key in app.subgrids) {
const subGrid = app.subgrids[key]
const gridItem = subGrid.find((x) => x.id === id)
if (gridItem) {
return key.split('-')[0]
}
}
}
}
export function findGridItem(app: App, id: string): GridItem | undefined {
return findGridItemById(app.grid, app.subgrids, id)
}
@@ -31,7 +46,6 @@ export function getNextGridItemId(app: App): string {
}
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
const appComponent = data
appComponent.id = id
@@ -176,3 +190,131 @@ export function duplicateGridItem(
}
return undefined
}
type AvailableSpace = {
left: number
right: number
top: number
bottom: number
}
export function findAvailableSpace(
grid: GridItem[],
gridItem: GridItem,
editorBreakpoint: EditorBreakpoint,
parentGridItem: GridItem | undefined = undefined
): AvailableSpace | undefined {
if (gridItem) {
const breakpoint = editorBreakpoint === 'sm' ? 3 : 12
const maxHeight = parentGridItem ? parentGridItem[breakpoint].h - 1 : 12
const maxWidth = 12
const availableSpace = {
left: 0,
right: 0,
top: 0,
bottom: 0
}
const items = grid.map((item) => {
return {
id: item.id,
x: item[breakpoint].x,
y: item[breakpoint].y,
w: item[breakpoint].w,
h: item[breakpoint].h
}
})
const item = items.find((item) => item.id === gridItem.id)
if (!item) {
return availableSpace
}
if (item.x > 0) {
for (let x = item.x - 1; x >= 0; x--) {
const itemToCheck = { ...item, x, w: 1 }
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
if (isItemInWay) {
break
} else {
availableSpace.left++
}
}
}
if (item.x + item.w < maxWidth) {
for (let x = item.x + item.w; x < maxWidth; x++) {
const itemToCheck = { ...item, x, w: 1 }
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
if (isItemInWay) {
break
} else {
availableSpace.right++
}
}
}
if (item.y > 0) {
for (let y = item.y - 1; y >= 0; y--) {
const itemToCheck = { ...item, h: 1, y }
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
if (isItemInWay) {
break
} else {
availableSpace.top++
}
}
}
if (item.y + item.h < maxHeight) {
for (let y = item.y + item.h; y < maxHeight; y++) {
const itemToCheck = { ...item, h: 1, y }
const isItemInWay = items.some((item) => isOverlapping(item, itemToCheck))
if (isItemInWay) {
break
} else {
availableSpace.bottom++
}
}
}
return availableSpace
}
}
function isOverlapping(item1: any, item2: any) {
return (
item1.x < item2.x + item2.w &&
item1.x + item1.w > item2.x &&
item1.y < item2.y + item2.h &&
item1.y + item1.h > item2.y
)
}
export function expandGriditem(
grid: GridItem[],
gridComponent: GridItem,
$breakpoint: EditorBreakpoint,
parentGridItem: GridItem | undefined = undefined
) {
const availableSpace = findAvailableSpace(grid, gridComponent, $breakpoint, parentGridItem)
if (!availableSpace) {
return
}
const { left, right, top, bottom } = availableSpace
const width = $breakpoint === 'sm' ? 3 : 12
const previousGridItem = JSON.parse(JSON.stringify(gridComponent[width]))
gridComponent[width].x = previousGridItem.x - left
gridComponent[width].y = previousGridItem.y - top
gridComponent[width].w = previousGridItem.w + left + right
gridComponent[width].h = previousGridItem.h + top + bottom
}

View File

@@ -3,7 +3,7 @@
import { fade } from 'svelte/transition'
import { Loader2 } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import ComponentHeader from '../ComponentHeader.svelte'
import type { AppComponent } from './components'
import {
@@ -46,9 +46,10 @@
export let selected: boolean
export let locked: boolean = false
export let pointerdown: boolean = false
export let render: boolean
const { staticOutputs, mode, connectingInput, app, errorByComponent } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
let hover = false
let initializing: boolean | undefined = undefined
let componentContainerHeight: number = 0
@@ -68,7 +69,16 @@
class="h-full flex flex-col w-full component"
>
{#if $mode !== 'preview'}
<ComponentHeader {hover} {pointerdown} {component} {selected} on:delete on:lock {locked} />
<ComponentHeader
{hover}
{pointerdown}
{component}
{selected}
on:delete
on:lock
on:expand
{locked}
/>
{/if}
<div
@@ -100,6 +110,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'barchartcomponent'}
<AppBarChart
@@ -109,6 +120,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'timeseriescomponent'}
<AppTimeseries
@@ -118,6 +130,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'htmlcomponent'}
<AppHtml
@@ -126,6 +139,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'vegalitecomponent'}
<VegaLiteHtml
@@ -134,6 +148,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'plotlycomponent'}
<PlotlyHtml
@@ -141,6 +156,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'scatterchartcomponent'}
<AppScatterChart
@@ -150,6 +166,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'piechartcomponent'}
<AppPieChart
@@ -159,6 +176,7 @@
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
componentInput={component.componentInput}
{render}
/>
{:else if component.type === 'tablecomponent'}
<AppTable
@@ -169,6 +187,7 @@
bind:staticOutputs={$staticOutputs[component.id]}
componentInput={component.componentInput}
bind:actionButtons={component.actionButtons}
{render}
/>
{:else if component.type === 'aggridcomponent'}
<AppAggridTable
@@ -177,6 +196,7 @@
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
componentInput={component.componentInput}
{render}
/>
{:else if component.type === 'textcomponent'}
<AppText
@@ -188,6 +208,7 @@
bind:initializing
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'buttoncomponent'}
<AppButton
@@ -199,6 +220,7 @@
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
recomputeIds={component.recomputeIds}
{render}
/>
{:else if component.type === 'selectcomponent'}
<AppSelect
@@ -208,6 +230,7 @@
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'multiselectcomponent'}
<AppMultiSelect
@@ -217,6 +240,7 @@
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'formcomponent'}
<AppForm
@@ -227,6 +251,7 @@
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
recomputeIds={component.recomputeIds}
{render}
/>
{:else if component.type === 'formbuttoncomponent'}
<AppFormButton
@@ -238,6 +263,7 @@
componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
recomputeIds={component.recomputeIds}
{render}
/>
{:else if component.type === 'checkboxcomponent'}
<AppCheckbox
@@ -247,6 +273,7 @@
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'textinputcomponent'}
<AppTextInput
@@ -255,6 +282,18 @@
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'emailinputcomponent'}
<AppTextInput
verticalAlignment={component.verticalAlignment}
configuration={component.configuration}
inputType="email"
appCssKey="emailinputcomponent"
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'passwordinputcomponent'}
<AppTextInput
@@ -265,6 +304,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'dateinputcomponent'}
<AppDateInput
@@ -274,6 +314,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'numberinputcomponent'}
<AppNumberInput
@@ -282,6 +323,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'currencycomponent'}
<AppCurrencyInput
@@ -290,6 +332,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'slidercomponent'}
<AppSliderInputs
@@ -298,6 +341,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'horizontaldividercomponent'}
<AppDivider
@@ -307,6 +351,7 @@
id={component.id}
customCss={component.customCss}
position="horizontal"
{render}
/>
{:else if component.type === 'verticaldividercomponent'}
<AppDivider
@@ -316,6 +361,7 @@
id={component.id}
customCss={component.customCss}
position="vertical"
{render}
/>
{:else if component.type === 'rangecomponent'}
<AppRangeInput
@@ -324,6 +370,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'tabscomponent'}
<AppTabs
@@ -333,6 +380,7 @@
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
{render}
/>
{:else if component.type === 'containercomponent'}
<AppContainer
@@ -341,6 +389,7 @@
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
{render}
/>
{:else if component.type === 'verticalsplitpanescomponent'}
<AppSplitpanes
@@ -348,8 +397,8 @@
id={component.id}
customCss={component.customCss}
panes={component.panes}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
{render}
/>
{:else if component.type === 'horizontalsplitpanescomponent'}
<AppSplitpanes
@@ -357,9 +406,9 @@
id={component.id}
customCss={component.customCss}
panes={component.panes}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
horizontal={true}
{render}
/>
{:else if component.type === 'iconcomponent'}
<AppIcon
@@ -369,6 +418,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'fileinputcomponent'}
<AppFileInput
@@ -376,6 +426,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'imagecomponent'}
<AppImage
@@ -383,6 +434,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'drawercomponent'}
<AppDrawer
@@ -391,6 +443,7 @@
configuration={component.configuration}
id={component.id}
customCss={component.customCss}
{render}
/>
{:else if component.type === 'mapcomponent'}
<AppMap
@@ -398,6 +451,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{:else if component.type === 'pdfcomponent'}
<AppPdf
@@ -405,6 +459,7 @@
id={component.id}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
{render}
/>
{/if}
</div>

View File

@@ -0,0 +1,156 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext, AppViewerContext, EditorBreakpoint, GridItem } from '../../types'
import { findGridItemParentId } from '../appUtils'
const { app, selectedComponent, breakpoint, focusedGrid } =
getContext<AppViewerContext>('AppViewerContext')
const { componentControl } = getContext<AppEditorContext>('AppEditorContext')
function getSortedGridItemsOfChildren(): GridItem[] {
if (!$focusedGrid) {
return sortGridItems($app.grid, $breakpoint)
}
if (!$app.subgrids) {
return []
}
return sortGridItems(
$app.subgrids[`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`],
$breakpoint
)
}
function getSortedGridItemsOfGrid(): GridItem[] {
if ($app.grid.find((item) => item.id === $selectedComponent)) {
return sortGridItems($app.grid, $breakpoint)
}
if (!$app.subgrids) {
return []
}
return sortGridItems(
Object.values($app.subgrids ?? {}).find((grid) =>
grid.find((item) => item.id === $selectedComponent)
) ?? [],
$breakpoint
)
}
function left(event: KeyboardEvent) {
if (!$componentControl[$selectedComponent!]?.left?.()) {
const sortedGridItems = getSortedGridItemsOfGrid()
const currentIndex = sortedGridItems.findIndex((item) => item.id === $selectedComponent)
if (currentIndex !== -1 && currentIndex > 0) {
$selectedComponent = sortedGridItems[currentIndex - 1].id
}
}
event.preventDefault()
}
function right(event: KeyboardEvent) {
let r = $componentControl[$selectedComponent!]?.right?.()
if (!r) {
const sortedGridItems = getSortedGridItemsOfGrid()
const currentIndex = sortedGridItems.findIndex((item) => item.id === $selectedComponent)
if (currentIndex !== -1 && currentIndex < sortedGridItems.length - 1) {
$selectedComponent = sortedGridItems[currentIndex + 1].id
}
}
event.preventDefault()
}
function down(event: KeyboardEvent) {
if (!$focusedGrid) {
$selectedComponent = getSortedGridItemsOfChildren()[0]?.id
event.preventDefault()
} else if ($app.subgrids) {
const index = $focusedGrid?.subGridIndex ?? 0
const subgrid = $app.subgrids[`${$selectedComponent}-${index}`]
if (!subgrid || subgrid.length === 0) {
return
}
const sortedGridItems = sortGridItems(subgrid, $breakpoint)
if (sortedGridItems) {
$selectedComponent = sortedGridItems[0].id
}
event.preventDefault()
}
}
function keydown(event: KeyboardEvent) {
switch (event.key) {
case 'Escape':
$selectedComponent = undefined
event.preventDefault()
break
case 'ArrowUp': {
if (!$selectedComponent) return
let parentId = findGridItemParentId($app, $selectedComponent)
if (parentId) {
$selectedComponent = parentId
} else {
$selectedComponent = undefined
$focusedGrid = undefined
}
break
}
case 'ArrowDown': {
down(event)
break
}
case 'ArrowRight': {
right(event)
break
}
case 'ArrowLeft': {
left(event)
break
}
default:
break
}
}
function sortGridItems(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
const bY = b[width].y
if (aY < bY) {
return -1
} else if (aY > bY) {
return 1
} else {
if (aX < bX) {
return -1
} else if (aX > bX) {
return 1
} else {
return 0
}
}
})
}
</script>
<svelte:window on:keydown={keydown} />

View File

@@ -32,7 +32,8 @@ import {
MapPin,
FlipHorizontal,
FlipVertical,
FileText
FileText,
AtSignIcon
} from 'lucide-svelte'
import type { BaseAppComponent } from '../../types'
@@ -43,6 +44,7 @@ type BaseComponent<T extends string> = {
export type TextComponent = BaseComponent<'textcomponent'>
export type TextInputComponent = BaseComponent<'textinputcomponent'>
export type PasswordInputComponent = BaseComponent<'passwordinputcomponent'>
export type EmailInputComponent = BaseComponent<'emailinputcomponent'>
export type DateInputComponent = BaseComponent<'dateinputcomponent'>
export type NumberInputComponent = BaseComponent<'numberinputcomponent'>
export type CurrencyComponent = BaseComponent<'currencycomponent'>
@@ -99,6 +101,7 @@ export type AppComponent = BaseAppComponent &
| DisplayComponent
| TextInputComponent
| PasswordInputComponent
| EmailInputComponent
| DateInputComponent
| NumberInputComponent
| CurrencyComponent
@@ -1136,6 +1139,35 @@ Hello \${ctx.username}
card: false
}
},
emailinputcomponent: {
name: 'Email Input',
icon: AtSignIcon,
dims: '2:1-3:1',
data: {
softWrap: true,
verticalAlignment: 'center',
id: '',
type: 'emailinputcomponent',
componentInput: undefined,
configuration: {
placeholder: {
type: 'static',
value: 'Email',
fieldType: 'text',
onlyStatic: true
},
defaultValue: {
type: 'static',
value: undefined,
fieldType: 'text'
}
},
customCss: {
input: { class: '', style: '' }
} as const,
card: false
}
},
dateinputcomponent: {
name: 'Date',
icon: Calendar,
@@ -1217,7 +1249,7 @@ Hello \${ctx.username}
color: {
type: 'static',
value: 'currentColor',
fieldType: 'text'
fieldType: 'color'
},
size: {
type: 'static',
@@ -1258,7 +1290,7 @@ Hello \${ctx.username}
color: {
type: 'static',
value: '#00000060',
fieldType: 'text',
fieldType: 'color',
onlyStatic: true
}
},
@@ -1288,7 +1320,7 @@ Hello \${ctx.username}
color: {
type: 'static',
value: '#00000060',
fieldType: 'text',
fieldType: 'color',
onlyStatic: true
}
},

View File

@@ -72,14 +72,14 @@ return {
},
displaycomponent: {
deno: `export async function main() {
return {
"foo": 42
}
return {
"foo": 42
}
}`,
python3: `def main():
return {
"foo": 42
}`
return {
"foo": 42
}`
},
htmlcomponent: {
deno: `export async function main() {

View File

@@ -23,6 +23,7 @@ const inputs: ComponentSet = {
components: [
'textinputcomponent',
'passwordinputcomponent',
'emailinputcomponent',
'numberinputcomponent',
'currencycomponent',
'slidercomponent',

View File

@@ -1,15 +1,18 @@
<script lang="ts">
import type { AppEditorContext } from '../../types'
import type { AppEditorContext, AppViewerContext } from '../../types'
import { getContext } from 'svelte'
import { fade, slide } from 'svelte/transition'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
import ListItem from './ListItem.svelte'
import { insertNewGridItem } from '../appUtils'
import { X } from 'lucide-svelte'
import { push } from '$lib/history'
import { flip } from 'svelte/animate'
const { app, selectedComponent, focusedGrid, history } =
getContext<AppEditorContext>('AppEditorContext')
const { app, selectedComponent, focusedGrid } = getContext<AppViewerContext>('AppViewerContext')
const { history } = getContext<AppEditorContext>('AppEditorContext')
function addComponent(appComponentType: AppComponent['type']): void {
push(history, $app)
@@ -34,7 +37,7 @@
}))
</script>
<section class="p-2 sticky bg-white border-b w-full h-12 z-20 top-0">
<section class="p-2 sticky bg-white w-full z-20 top-0">
<div class="relative">
<input
bind:value={search}
@@ -52,32 +55,45 @@
</div>
</section>
{#if componentsFiltered.reduce((acc, { components }) => acc + components.length, 0) === 0}
<div class="text-xs text-gray-500 py-1 px-2"> No components found </div>
{:else}
{#each componentsFiltered as { title, components }, index (index)}
<ListItem title={`${title} (${components.length})`}>
{#if components.length}
<div class="flex flex-wrap gap-2 py-2">
{#each components as item}
<button
on:click={() => addComponent(item)}
title={componentsRecord[item].name}
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
>
<svelte:component this={componentsRecord[item].icon} />
<div class="text-xs w-full text-center ellipsize">
{componentsRecord[item].name}
<div class="relative">
{#if componentsFiltered.reduce((acc, { components }) => acc + components.length, 0) === 0}
<div
in:fade|local={{ duration: 50, delay: 50 }}
out:fade|local={{ duration: 50 }}
class="absolute left-0 top-0 w-full text-sm text-gray-500 text-center py-6 px-2"
>
No components found
</div>
{:else}
<div
id="app-tutorial-1"
in:fade|local={{ duration: 50, delay: 50 }}
out:fade|local={{ duration: 50 }}
>
{#each componentsFiltered as { title, components }, index (index)}
{#if components.length}
<div transition:slide|local={{ duration: 300 }}>
<ListItem title={`${title} (${components.length})`}>
<div class="flex flex-wrap gap-2 py-2">
{#each components as item (item)}
<button
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
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
>
<svelte:component this={componentsRecord[item].icon} />
<div class="text-xs w-full text-center ellipsize">
{componentsRecord[item].name}
</div>
</button>
{/each}
</div>
</button>
{/each}
</div>
{:else}
<div class="text-xs text-gray-500 py-1 px-2">
There are no components in this group yet
</div>
{/if}
</ListItem>
{/each}
{/if}
</ListItem>
</div>
{/if}
{/each}
</div>
{/if}
</div>

View File

@@ -4,7 +4,7 @@
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { emptyString } from '$lib/utils'
import { Tab, TabContent, Tabs } from '../../../common'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import ListItem from './ListItem.svelte'
import { isOpenStore } from './store'
import CssProperty from './CssProperty.svelte'
@@ -24,7 +24,7 @@
ids: string[]
}
const { app } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
let rawCode = ''
let viewJsonSchema = false

View File

@@ -2,12 +2,12 @@
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
import { getContext } from 'svelte'
import type { Output } from '../../rx'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
export let outputs: string[] = []
export let componentId: string
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
const { worldStore } = getContext<AppViewerContext>('AppViewerContext')
let object = {}

View File

@@ -2,14 +2,16 @@
import { classNames } from '$lib/utils'
import { X } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import { flip } from 'svelte/animate'
import { fade } from 'svelte/transition'
import type { AppViewerContext } from '../../types'
import { findGridItem } from '../appUtils'
import { components } from '../component'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
const { connectingInput, staticOutputs, worldStore, selectedComponent, app } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
function connectInput(componentId: string, path: string) {
if ($connectingInput) {
@@ -40,7 +42,7 @@
return 'Table action'
}
}
$: panels = [['ctx', ['email', 'username', 'query']] as [string, string[]]].concat(
$: panels = [['ctx', ['email', 'username', 'query', 'hash']] as [string, string[]]].concat(
Object.entries($staticOutputs)
)
@@ -58,83 +60,100 @@
</script>
<PanelSection noPadding titlePadding="px-4 pt-2 pb-0.5" title="Outputs">
<div
class="overflow-auto min-w-[150px] border-t w-full relative flex flex-col gap-4 px-2 pt-4 pb-2"
>
<div class="relative {$connectingInput?.opened ? 'bg-white z-50' : ''}">
<input
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 = '')}
>
<X size="14" />
</button>
{/if}
<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}
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 = '')}
>
<X size="14" />
</button>
{/if}
</div>
</div>
{#each filteredPanels as [componentId, outputs] (componentId)}
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
{@const name = getComponentNameById(componentId)}
<div>
<div
class="flex {$connectingInput?.opened
? 'bg-white z-50'
: ''} flex-row justify-between w-full"
>
<button
on:click|stopPropagation|preventDefault={$connectingInput.opened
? undefined
: () => ($selectedComponent = componentId)}
class={classNames(
'px-2 text-2xs py-0.5 border-t border-x font-bold rounded-t-sm w-fit',
$selectedComponent === componentId
? ' bg-blue-500 text-white border-blue-500'
: 'bg-gray-100 text-gray-500 border-gray-200'
)}
>
{componentId}
</button>
<span
class={classNames(
'px-1 text-2xs py-0.5 font-semibold rounded-t-sm w-fit',
'bg-gray-700 text-white'
)}
>
{name}
</span>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={classNames(
$connectingInput?.opened ? 'bg-white z-50' : '',
`w-full py-2 grow border relative break-all `,
$selectedComponent === componentId ? 'border border-blue-500 ' : '',
$connectingInput.hoveredComponent === componentId ? 'outline outline-blue-500' : ''
)}
>
{#key $selectedComponent}
{#key $connectingInput?.opened}
<ComponentOutputViewer
outputs={$connectingInput?.opened && $selectedComponent === componentId
? name == 'Table'
? ['search']
: []
: outputs}
<div id="app-tutorial-2" class="relative p-2">
{#each filteredPanels as [componentId, outputs] (componentId)}
<div
animate:flip={{ duration: 300 }}
in:fade|local={{ duration: 100, delay: 50 }}
out:fade|local={{ duration: 100 }}
class="pb-2"
>
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
{@const name = getComponentNameById(componentId)}
<div>
<div
class="flex {$connectingInput?.opened
? 'bg-white z-50'
: ''} flex-row justify-between w-full"
>
<button
on:click|stopPropagation|preventDefault={$connectingInput.opened
? undefined
: () => ($selectedComponent = componentId)}
class={classNames(
'px-2 text-2xs py-0.5 border-t border-x font-bold rounded-t-sm w-fit',
$selectedComponent === componentId
? ' bg-indigo-500/90 text-white border-indigo-500/90'
: 'bg-gray-100 text-gray-500 border-gray-200'
)}
>
{componentId}
on:select={({ detail }) => {
connectInput(componentId, detail)
}}
/>
{/key}
{/key}
</div>
</button>
<span
class={classNames(
'px-1 text-2xs py-0.5 font-semibold rounded-t-sm w-fit',
'bg-gray-700 text-white'
)}
>
{name}
</span>
</div>
<div
class={classNames(
$connectingInput?.opened ? 'bg-white z-50' : '',
`w-full py-2 grow border relative break-all `,
$selectedComponent === componentId ? 'border border-indigo-500/90 ' : '',
$connectingInput.hoveredComponent === componentId
? 'outline outline-indigo-500/90'
: ''
)}
>
{#key $selectedComponent}
{#key $connectingInput?.opened}
<ComponentOutputViewer
outputs={$connectingInput?.opened && $selectedComponent === componentId
? name == 'Table'
? ['search']
: []
: outputs}
{componentId}
on:select={({ detail }) => {
connectInput(componentId, detail)
}}
/>
{/key}
{/key}
</div>
</div>
{/if}
</div>
{/if}
{/each}
{:else}
<div
in:fade|local={{ duration: 50, delay: 100 }}
out:fade|local={{ duration: 50 }}
class="absolute left-0 top-0 w-full text-sm text-gray-500 text-center py-4 px-2"
>
No outputs found
</div>
{/each}
</div>
</div>
</PanelSection>

View File

@@ -11,7 +11,7 @@
import { Building, Globe2 } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import { fly } from 'svelte/transition'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import { defaultCode } from '../component'
import InlineScriptList from '../settingsPanel/mainInput/InlineScriptList.svelte'
import WorkspaceScriptList from '../settingsPanel/mainInput/WorkspaceScriptList.svelte'
@@ -23,7 +23,7 @@
let filter: string = ''
let picker: Drawer
const { appPath, app } = getContext<AppEditorContext>('AppEditorContext')
const { appPath, app } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
async function inferInlineScriptSchema(

View File

@@ -2,7 +2,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import type { Preview } from '$lib/gen'
import { createEventDispatcher, getContext, onMount } from 'svelte'
import type { AppEditorContext, InlineScript } from '../../types'
import type { AppViewerContext, InlineScript } from '../../types'
import { CornerDownLeft, Maximize2, Plus, Trash2, X } from 'lucide-svelte'
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
import { inferArgs } from '$lib/infer'
@@ -26,7 +26,7 @@
export let fields: Record<string, AppInput> = {}
export let syncFields: boolean = false
const { runnableComponents, stateId } = getContext<AppEditorContext>('AppEditorContext')
const { runnableComponents, stateId } = getContext<AppViewerContext>('AppViewerContext')
let editor: Editor
let validCode = true

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import InlineScriptsPanelList from './InlineScriptsPanelList.svelte'
@@ -9,7 +9,7 @@
import InlineScriptEditorPanel from './InlineScriptEditorPanel.svelte'
const { app, staticOutputs, runnableComponents } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
let selectedScriptComponentId: string | undefined = undefined

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { Badge, Button } from '$lib/components/common'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { Plus } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import Tooltip from '../../../Tooltip.svelte'
import type { AppViewerContext } from '../../types'
import { getAllScriptNames } from '../../utils'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
import { getAppScripts } from './utils'
@@ -11,8 +11,7 @@
const PREFIX = 'script-selector-' as const
export let selectedScriptComponentId: string | undefined = undefined
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let list: HTMLElement
const { app, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
function selectScript(id: string) {
selectedScriptComponentId = id
@@ -53,106 +52,110 @@
}
</script>
<div
class="sticky top-0 py-0.5 px-2 text-left bg-gray-50 text-gray-800 text-xs font-semibold tracking-wide border-b border-gray-300"
>
Runnables
</div>
<div bind:this={list} class="grow flex flex-col gap-4">
<PanelSection title="Inline scripts" smallPadding>
<div class="flex flex-col gap-2 w-full">
{#if runnables.inline.length > 0}
<div class="flex gap-1 flex-col ">
{#each runnables.inline as { name, id }, index (index)}
<PanelSection title="Runnables" smallPadding>
<div class="w-full flex flex-col gap-6 py-1">
<div>
<div class="text-sm font-semibold truncate mb-1"> Inline scripts </div>
<div class="flex flex-col gap-2 w-full">
{#if runnables.inline.length > 0}
<div class="flex gap-1 flex-col ">
{#each runnables.inline as { name, id }, index (index)}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<div>
<Badge color="dark-indigo">{id}</Badge>
</div>
</button>
{/each}
</div>
{/if}
{#if $app.unusedInlineScripts?.length > 0}
<div class="flex gap-1 flex-col ">
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
{@const id = `unused-${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{unusedInlineScript.name}</span>
<Badge color="red">Detached</Badge>
</button>
{/each}
</div>
{/if}
{#if runnables.inline.length == 0 && $app.unusedInlineScripts?.length == 0}
<div class="text-xs text-gray-500">No inline scripts</div>
{/if}
</div>
</div>
<div>
<div class="text-sm font-semibold truncate mb-1"> Workspace/Hub </div>
<div class="flex flex-col gap-1 w-full">
{#if runnables.imported.length > 0}
{#each runnables.imported as { name, id }, index (index)}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<div>
<Badge color="dark-indigo">{id}</Badge>
</div>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
</div>
{/if}
{:else}
<div class="text-xs text-gray-500">No imported scripts/flows</div>
{/if}
</div>
</div>
{#if $app.unusedInlineScripts?.length > 0}
<div class="flex gap-1 flex-col ">
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
{@const id = `unused-${index}`}
<div>
<div class="w-full flex justify-between items-center">
<div class="text-sm font-semibold truncate mb-1">
Background scripts
<Tooltip class="mb-0.5">
Background scripts are triggered upon global refresh or when their input changes. The
result of a background script can be shared among many components.
</Tooltip>
</div>
<Button
size="xs"
color="light"
variant="border"
btnClasses="!rounded-full !p-1"
title="Create a new background script"
aria-label="Create a new background script"
on:click={createBackgroundScript}
>
<Plus size={14} class="text-gray-500" />
</Button>
</div>
<div class="flex flex-col gap-1 w-full">
{#if $app.hiddenInlineScripts?.length > 0}
{#each $app.hiddenInlineScripts as { name }, index (index)}
{@const id = `bg_${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{unusedInlineScript.name}</span>
<Badge color="red">Detached</Badge>
<span class="text-2xs truncate">{name}</span>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
</div>
{/if}
{#if runnables.inline.length == 0 && $app.unusedInlineScripts?.length == 0}
<div class="text-sm text-gray-500">No inline scripts</div>
{/if}
{:else}
<div class="text-xs text-gray-500">No background scripts</div>
{/if}
</div>
</div>
</PanelSection>
<PanelSection title="Workspace/Hub" smallPadding>
<div class="flex flex-col gap-1 w-full">
{#if runnables.imported.length > 0}
{#each runnables.imported as { name, id }, index (index)}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
{:else}
<div class="text-sm text-gray-500">No imported scripts/flows</div>
{/if}
</div>
</PanelSection>
<PanelSection
title="Background scripts"
tooltip="Background scripts are triggered upon global refresh or when their input changes. The result
of a background script can be shared among many components."
smallPadding
>
<svelte:fragment slot="action">
<button
class="rounded-full bg-gray-100 hover:bg-gray-200 p-1 border border-gray-200"
on:click={createBackgroundScript}
>
<Plus size={14} class="text-gray-500" />
</button>
</svelte:fragment>
<div class="flex flex-col gap-1 w-full">
{#if $app.hiddenInlineScripts?.length > 0}
{#each $app.hiddenInlineScripts as { name }, index (index)}
{@const id = `bg_${index}`}
<button
id={PREFIX + id}
class="border flex gap-1 truncate font-normal justify-between w-full items-center py-1 px-2 rounded-sm duration-200
{selectedScriptComponentId === id ? 'border-blue-500 bg-blue-100' : 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<Badge color="dark-indigo">{id}</Badge>
</button>
{/each}
{:else}
<div class="text-sm text-gray-500">No items</div>
{/if}
</div>
</PanelSection>
</div>
</div>
</PanelSection>

View File

@@ -3,12 +3,12 @@
import { faArrowRight, faCode, faPen } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
export let componentInput: AppInput
export let disableStatic: boolean = false
const { onchange } = getContext<AppEditorContext>('AppEditorContext')
const { onchange } = getContext<AppViewerContext>('AppViewerContext')
$: if (componentInput.fieldType == 'template' && componentInput.type == 'static') {
//@ts-ignore

View File

@@ -2,7 +2,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import { faChevronDown, faChevronUp, faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import type { AppEditorContext, AppViewerContext } from '../../types'
import PanelSection from './common/PanelSection.svelte'
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
import TableActions from './TableActions.svelte'
@@ -42,9 +42,10 @@
selectedComponent,
worldStore,
focusedGrid,
stateId,
history
} = getContext<AppEditorContext>('AppEditorContext')
stateId
} = getContext<AppViewerContext>('AppViewerContext')
const { history } = getContext<AppEditorContext>('AppEditorContext')
function duplicateElement(id: string) {
push(history, $app)
@@ -80,8 +81,20 @@
component?.componentInput?.type === 'template' && $worldStore
? buildExtraLib($worldStore?.outputsById ?? {}, component?.id, false)
: undefined
function keydown(event: KeyboardEvent) {
switch (event.key) {
case 'Delete': {
removeGridElement()
event.stopPropagation()
break
}
}
}
</script>
<svelte:window on:keydown={keydown} />
{#if component}
<div class="flex flex-col min-w-[150px] w-full divide-y">
{#if component.componentInput}

View File

@@ -2,7 +2,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import { deleteGridItem } from '../appUtils'
import type { AppComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
@@ -11,7 +11,7 @@
export let component: AppComponent
const { app, staticOutputs, runnableComponents } =
getContext<AppEditorContext>('AppEditorContext')
getContext<AppViewerContext>('AppViewerContext')
function addTab() {
const numberOfPanes = panes.length
@@ -53,9 +53,9 @@
{#if panes.length == 0}
<span class="text-xs text-gray-500">No panes</span>
{/if}
<div class="flex gap-2 flex-col mt-2">
<div class="w-full flex gap-2 flex-col mt-2">
{#each panes as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<div class="w-full flex flex-row gap-2 items-center relative">
<input type="number" bind:value />
<div class="absolute top-1 right-1">

View File

@@ -2,7 +2,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import type { AppViewerContext } from '../../types'
import { deleteGridItem } from '../appUtils'
import type { AppComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
@@ -10,8 +10,8 @@
export let tabs: string[]
export let component: AppComponent
const { app, staticOutputs, runnableComponents } =
getContext<AppEditorContext>('AppEditorContext')
const { app, staticOutputs, runnableComponents, focusedGrid } =
getContext<AppViewerContext>('AppViewerContext')
function addTab() {
const numberOfTabs = tabs.length
@@ -51,9 +51,9 @@
{#if tabs.length == 0}
<span class="text-xs text-gray-500">No Tabs</span>
{/if}
<div class="flex gap-2 flex-col mt-2">
<div class="w-full flex gap-2 flex-col mt-2">
{#each tabs as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<div class="w-full flex flex-row gap-2 items-center relative">
<input type="text" bind:value />
<div class="absolute top-1 right-1">

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import VariableEditor from '$lib/components/VariableEditor.svelte'
import type {
ConnectedAppInput,
EvalAppInput,

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