Compare commits

..

52 Commits

Author SHA1 Message Date
Ruben Fiszel
4b1bfed090 push 2023-02-24 19:16:11 +01:00
Faton Ramadani
2125d119ff fix(frontend): Fix findGridItem 2023-02-24 19:04:33 +01:00
Faton Ramadani
d3f1ef92ea Merge branch 'rf/flatten' of https://github.com/windmill-labs/windmill into rf/flatten 2023-02-24 19:01:01 +01:00
Faton Ramadani
26296ee05c fix(frontend): Fix findGridItem 2023-02-24 19:00:41 +01:00
Ruben Fiszel
7e4179a741 flatten 2023-02-24 18:52:05 +01:00
Faton Ramadani
52adaa4cb5 fix(frontend): Fix next id 2023-02-24 18:51:50 +01:00
Faton Ramadani
a94f266743 fix(frontend): wip 2023-02-24 18:49:06 +01:00
Faton Ramadani
9d680e6744 Merge branch 'rf/flatten' of https://github.com/windmill-labs/windmill into rf/flatten 2023-02-24 18:44:19 +01:00
Faton Ramadani
5060c37fed fix(frontend): gridtab 2023-02-24 18:44:16 +01:00
Ruben Fiszel
15c2bdcdae pushed 2023-02-24 18:43:44 +01:00
Faton Ramadani
45ce630a75 fix(frontend): duplicate 2023-02-24 18:04:31 +01:00
Faton Ramadani
d50734cbb3 fix(frontend): delete grid item 2023-02-24 17:56:47 +01:00
Ruben Fiszel
b08b559eec script editor panel 2023-02-24 17:53:26 +01:00
Ruben Fiszel
5742bf2235 settings panel 2023-02-24 17:49:16 +01:00
Ruben Fiszel
c56176ec91 Merge branch 'main' into rf/flatten 2023-02-24 17:38:19 +01:00
Ruben Fiszel
dd7e8c742c fix(deno): remove mysql support waiting for deno fix 2023-02-24 17:19:13 +01:00
Faton Ramadani
640242b713 fix(frontend): rewrote utils 2023-02-24 17:17:11 +01:00
Ruben Fiszel
2f78132e08 fix(deno): remove mysql support waiting for deno fix 2023-02-24 17:11:53 +01:00
Ruben Fiszel
c696f3b6cf flatten 2023-02-24 15:09:38 +01:00
Ruben Fiszel
0041411c06 subgrids editor panel iter 1 2023-02-24 14:48:28 +01:00
Ruben Fiszel
54955b710c fix subgrids final2 2023-02-24 13:11:06 +01:00
Ruben Fiszel
1eb5a0d1d3 fix subgrids final 2023-02-24 11:59:28 +01:00
Ruben Fiszel
ddda14c52b app fixes 2023-02-24 11:39:50 +01:00
Ruben Fiszel
5123c9365c fix subgrids final 2023-02-24 07:27:22 +01:00
Ruben Fiszel
834e7b1d1c todo: improve deleting tab 2023-02-24 01:04:26 +01:00
Ruben Fiszel
6e9a5b026e tab rework v2 2023-02-23 23:39:57 +01:00
Ruben Fiszel
7ad8879b09 fix binding 2023-02-23 22:14:23 +01:00
Ruben Fiszel
27cac3ffe6 fix(frontend): containers and tab fixes v1 2023-02-23 21:42:21 +01:00
Ruben Fiszel
705703a5e2 fix(frontend): containers and tab fixes v1 2023-02-23 21:01:47 +01:00
Ruben Fiszel
fac31c6628 fix(frontend): containers and tab fixes v1 2023-02-23 19:59:23 +01:00
Ruben Fiszel
90c0e140a1 chore(main): release 1.69.0 (#1226)
* chore(main): release 1.69.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-23 18:07:56 +01:00
Ruben Fiszel
d543650b31 fix(cli): .wmillignore whitelist behavior 2023-02-23 18:02:42 +01:00
Faton Ramadani
089a6b6ae5 feat(frontend): Duplicate component (#1228)
* feat(frontend): Duplicate component

* feat(frontend): add missing types
2023-02-23 17:43:27 +01:00
Faton Ramadani
857ee5f318 feat(frontend): Properly delete tab content (#1227) 2023-02-23 16:02:15 +01:00
Faton Ramadani
6ad876ebb4 feat(frontend): Support deeply nested components (#1225) 2023-02-23 15:33:14 +01:00
Ádám Kovács
ab4137640e refactor(frontend): Reorganise app components (#1224)
* refactor(frontend): Reorganise app components

* refactor(frontend): Restructure app component
2023-02-23 14:15:45 +01:00
Ruben Fiszel
2bd8fabcf7 chore(main): release 1.68.0 (#1223)
* chore(main): release 1.68.0

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-23 13:42:42 +01:00
Ruben Fiszel
3b7160e84a fix(cli): better ergonomics around workspaces 2023-02-23 13:31:08 +01:00
Ruben Fiszel
40c12e6139 fix(cli): better ergonomics around workspace add 2023-02-23 13:19:14 +01:00
Ádám Kovács
6044e3b6ef feat(frontend): Add more app component CSS customisation (#1218)
* fix(frontend): Update app style editor

* fix(frontend): Update component custom css typings

* fix(frontend): Generalise custom CSS inputs

* feat(frontend): Add display CSS customization
2023-02-23 11:42:32 +01:00
Ruben Fiszel
18ff5c7cef chore(main): release 1.67.4 (#1222)
* chore(main): release 1.67.4

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-23 09:06:34 +01:00
Ruben Fiszel
e54dc3ff97 fix(backend): workflow check for has_failure_module 2023-02-23 09:01:02 +01:00
Ruben Fiszel
4d5aae69c8 chore(main): release 1.67.3 (#1221)
* chore(main): release 1.67.3

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-23 08:34:22 +01:00
Ruben Fiszel
ec57c5977f fix(cli): ignone non wmill looking files 2023-02-23 08:26:22 +01:00
Ruben Fiszel
df1b724626 do not create default variables on workspace creation 2023-02-23 08:07:35 +01:00
Ruben Fiszel
268dfbf831 chore(main): release 1.67.2 (#1220)
* chore(main): release 1.67.2

* Apply automatic changes

---------

Co-authored-by: rubenfiszel <rubenfiszel@users.noreply.github.com>
2023-02-23 08:00:15 +01:00
Ruben Fiszel
969e89f8bb fix(cli): ignone non wmill looking files 2023-02-23 07:40:04 +01:00
Ruben Fiszel
5997503961 chore(main): release 1.67.1 (#1219) 2023-02-22 23:47:37 +01:00
Ruben Fiszel
3fa24adad0 fix(cli): coloring nits 2023-02-22 23:46:08 +01:00
Ruben Fiszel
7471be1d81 update rustpython parser 2023-02-22 23:43:06 +01:00
Ruben Fiszel
d64e1c116a nits on cli output 2023-02-22 23:28:50 +01:00
Ruben Fiszel
9267b1fb90 nits on webhook sync 2023-02-22 22:44:19 +01:00
113 changed files with 3872 additions and 2432 deletions

View File

@@ -1,6 +1,61 @@
# Changelog
## [1.69.0](https://github.com/windmill-labs/windmill/compare/v1.68.0...v1.69.0) (2023-02-23)
### Features
* **frontend:** Duplicate component ([#1228](https://github.com/windmill-labs/windmill/issues/1228)) ([089a6b6](https://github.com/windmill-labs/windmill/commit/089a6b6ae52e8d28dd15e2f9a6ad900c5853d0a1))
* **frontend:** Properly delete tab content ([#1227](https://github.com/windmill-labs/windmill/issues/1227)) ([857ee5f](https://github.com/windmill-labs/windmill/commit/857ee5f318466d12bf0d41515451798df087ab74))
* **frontend:** Support deeply nested components ([#1225](https://github.com/windmill-labs/windmill/issues/1225)) ([6ad876e](https://github.com/windmill-labs/windmill/commit/6ad876ebb45a934b7a4dc980cf38a5228d7d11f1))
### Bug Fixes
* **cli:** .wmillignore whitelist behavior ([d543650](https://github.com/windmill-labs/windmill/commit/d543650b313c434e794ad800aefe4aeda83c0fed))
## [1.68.0](https://github.com/windmill-labs/windmill/compare/v1.67.4...v1.68.0) (2023-02-23)
### Features
* **frontend:** Add more app component CSS customisation ([#1218](https://github.com/windmill-labs/windmill/issues/1218)) ([6044e3b](https://github.com/windmill-labs/windmill/commit/6044e3b6ef92e89b8f15f38bc2d0986ec64105d5))
### Bug Fixes
* **cli:** better ergonomics around workspace add ([40c12e6](https://github.com/windmill-labs/windmill/commit/40c12e6139c7b42d7ab169bab2dd37f8b43bea06))
* **cli:** better ergonomics around workspaces ([3b7160e](https://github.com/windmill-labs/windmill/commit/3b7160e84aa454bdb5f343da99cfd97a6b319937))
## [1.67.4](https://github.com/windmill-labs/windmill/compare/v1.67.3...v1.67.4) (2023-02-23)
### Bug Fixes
* **backend:** workflow check for has_failure_module ([e54dc3f](https://github.com/windmill-labs/windmill/commit/e54dc3ff97e4454a15b9efe25cc12f6c9e1e176b))
## [1.67.3](https://github.com/windmill-labs/windmill/compare/v1.67.2...v1.67.3) (2023-02-23)
### Bug Fixes
* **cli:** ignone non wmill looking files ([ec57c59](https://github.com/windmill-labs/windmill/commit/ec57c5977f122b629a07e05bc3551662d518ce30))
## [1.67.2](https://github.com/windmill-labs/windmill/compare/v1.67.1...v1.67.2) (2023-02-23)
### Bug Fixes
* **cli:** ignone non wmill looking files ([969e89f](https://github.com/windmill-labs/windmill/commit/969e89f8bbc10f6712920321b70ede35f19ab9ed))
## [1.67.1](https://github.com/windmill-labs/windmill/compare/v1.67.0...v1.67.1) (2023-02-22)
### Bug Fixes
* **cli:** coloring nits ([3fa24ad](https://github.com/windmill-labs/windmill/commit/3fa24adad0a07ba2f469c545b28251b035efdf90))
## [1.67.0](https://github.com/windmill-labs/windmill/compare/v1.66.1...v1.67.0) (2023-02-22)

View File

@@ -37,7 +37,6 @@ https://user-images.githubusercontent.com/275584/218350457-bc2fdc3b-e667-4da5-a2
- [Show me some actual script code](#show-me-some-actual-script-code)
- [CLI](#cli)
- [Running scripts locally](#running-scripts-locally)
- [Layout](#layout)
- [Stack](#stack)
- [Security](#security)
- [Sandboxing](#sandboxing)
@@ -124,13 +123,6 @@ scripts from local files, github repos and to run scripts and flows on the insta
You can run your script locally easily, you simply need to pass the right environment variables for the `wmill` client library to fetch resource and variables from your instance if necessary. See more: <https://docs.windmill.dev/docs/advanced/local_development/>
## Layout
- `backend/`: Rust backend
- `frontend`: Svelte frontend
- `lsp/`: Lsp asssistant for the monaco editor
- `<lang>-client/`: Windmill client for the given `<lang>`
## Stack
- Postgres as the database
@@ -375,4 +367,4 @@ running options.
## Copyright
Windmill Labs, Inc 2022
Windmill Labs, Inc 2023

46
backend/Cargo.lock generated
View File

@@ -1638,9 +1638,9 @@ checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
[[package]]
name = "is-macro"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c068d4c6b922cd6284c609cfa6dec0e41615c9c5a1a4ba729a970d8daba05fb"
checksum = "8a7d079e129b77477a49c5c4f1cfe9ce6c2c909ef52520693e8e811a714c7b20"
dependencies = [
"Inflector",
"pmutil",
@@ -1651,9 +1651,9 @@ dependencies = [
[[package]]
name = "is-terminal"
version = "0.4.3"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e18b0a45d56fe973d6db23972bf5bc46f988a4a2385deac9cc29572f09daef"
checksum = "21b6b32576413a8e69b90e952e4a026476040d81017b80445deda5f2d3921857"
dependencies = [
"hermit-abi 0.3.1",
"io-lifetimes",
@@ -3003,7 +3003,7 @@ dependencies = [
[[package]]
name = "rustpython-ast"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#edf5995a1e4c366976304ca05432dd27c913054e"
source = "git+https://github.com/RustPython/RustPython#c4b67896662b16b5699a779c0e52aa0ca2587fec"
dependencies = [
"num-bigint",
"rustpython-compiler-core",
@@ -3012,7 +3012,7 @@ dependencies = [
[[package]]
name = "rustpython-compiler-core"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#edf5995a1e4c366976304ca05432dd27c913054e"
source = "git+https://github.com/RustPython/RustPython#c4b67896662b16b5699a779c0e52aa0ca2587fec"
dependencies = [
"bincode",
"bitflags",
@@ -3029,7 +3029,7 @@ dependencies = [
[[package]]
name = "rustpython-parser"
version = "0.2.0"
source = "git+https://github.com/RustPython/RustPython#edf5995a1e4c366976304ca05432dd27c913054e"
source = "git+https://github.com/RustPython/RustPython#c4b67896662b16b5699a779c0e52aa0ca2587fec"
dependencies = [
"ahash",
"anyhow",
@@ -3636,9 +3636,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.5"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcac566e671100a4d4c4a2fb55ceb6c3d3554b9d4fb30b70ddacde3c96ac7986"
checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08"
dependencies = [
"new_debug_unreachable",
"once_cell",
@@ -3823,9 +3823,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.107"
version = "1.0.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
checksum = "d56e159d99e6c2b93995d171050271edb50ecc5288fbc7cc17de8fdce4e58c14"
dependencies = [
"proc-macro2",
"quote",
@@ -4766,7 +4766,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"axum",
@@ -4793,7 +4793,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"argon2",
@@ -4848,7 +4848,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"base64 0.21.0",
"chrono",
@@ -4863,7 +4863,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"chrono",
"serde",
@@ -4876,7 +4876,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"axum",
@@ -4901,7 +4901,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"serde",
"serde_json",
@@ -4909,7 +4909,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"itertools",
@@ -4923,7 +4923,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"itertools",
@@ -4935,7 +4935,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"itertools",
@@ -4950,7 +4950,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"deno_core",
@@ -4964,7 +4964,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"chrono",
@@ -4987,7 +4987,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.67.0"
version = "1.69.0"
dependencies = [
"anyhow",
"async-recursion",

View File

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

View File

@@ -17,10 +17,8 @@ use serde_json::json;
use windmill_common::error;
use windmill_parser::{json_to_typ, Arg, MainArgSignature, Typ};
use rustpython_parser::{
ast::{Constant, ExprKind, Located, StmtKind},
parser,
};
use rustpython_parser as parser;
use rustpython_parser::ast::{Constant, ExprKind, Located, StmtKind};
const DEF_MAIN: &str = "def main(";
const FUNCTION_CALL: &str = "<function call>";

View File

@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.67.0
version: 1.69.0
title: Windmill API
contact:

View File

@@ -676,19 +676,19 @@ async fn create_workspace(
.execute(&mut tx)
.await?;
let mc = magic_crypt::new_magic_crypt!(key, 256);
sqlx::query!(
"INSERT INTO variable
(workspace_id, path, value, is_secret, description)
VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'),
($3, 'g/all/not_secret', $4, false, 'This item is not secret')",
nw.id,
crate::variables::encrypt(&mc, "pretty secret value"),
nw.id,
"finland does not actually exist",
)
.execute(&mut tx)
.await?;
// let mc = magic_crypt::new_magic_crypt!(key, 256);
// sqlx::query!(
// "INSERT INTO variable
// (workspace_id, path, value, is_secret, description)
// VALUES ($1, 'g/all/pretty_secret', $2, true, 'This item is secret'),
// ($3, 'g/all/not_secret', $4, false, 'This item is not secret')",
// nw.id,
// crate::variables::encrypt(&mc, "pretty secret value"),
// nw.id,
// "finland does not actually exist",
// )
// .execute(&mut tx)
// .await?;
sqlx::query!(
"INSERT INTO usr

View File

@@ -557,7 +557,7 @@ async fn has_failure_module<'c>(
flow: Uuid,
tx: &mut sqlx::Transaction<'c, sqlx::Postgres>,
) -> Result<bool, Error> {
sqlx::query_scalar(
sqlx::query_scalar::<_, Option<bool>>(
"
SELECT raw_flow->'failure_module' != 'null'::jsonb
FROM queue
@@ -568,6 +568,7 @@ async fn has_failure_module<'c>(
.fetch_one(tx)
.await
.map_err(|e| Error::InternalErr(format!("error during retrieval of has_failure_module: {e}")))
.map(|v| v.unwrap_or(false))
}
fn next_retry(retry: &Retry, status: &RetryStatus) -> Option<(u16, Duration)> {

View File

@@ -142,7 +142,7 @@ async function push(opts: Options, filePath: string, remotePath: string) {
await requireLogin(opts);
await pushFlow(filePath, workspace.remote, remotePath);
console.log(colors.bold.underline.green("Flow successfully pushed"));
console.log(colors.bold.underline.green("Flow pushed"));
}
export async function pushFlow(

View File

@@ -133,7 +133,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
console.log(colors.bold.yellow("Pushing resource..."));
await pushFolder(workspace.workspaceId, filePath, remotePath);
console.log(colors.bold.underline.green("Resource successfully pushed"));
console.log(colors.bold.underline.green("Resource pushed"));
}
export async function pushFolder(

View File

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

View File

@@ -12,7 +12,7 @@ export async function downloadZip(
const zipResponse = await fetch(
workspace.remote + "api/w/" + workspace.workspaceId +
"/workspaces/tarball?archive_type=zip",
"/workspaces/tarball?archive_type=zip",
{
headers: requestHeaders,
method: "GET",
@@ -25,8 +25,7 @@ export async function downloadZip(
"Failed to request tarball from API " + zipResponse.statusText,
),
);
console.log(await zipResponse.text());
return undefined;
throw new Error(await zipResponse.text());
}
const blob = await zipResponse.blob();
return await JSZip.loadAsync(blob);

View File

@@ -137,7 +137,7 @@ async function push(opts: PushOptions, filePath: string, name: string) {
console.log(colors.bold.yellow("Pushing resource..."));
await pushResourceType(workspace.workspaceId, filePath, name);
console.log(colors.bold.underline.green("Resource successfully pushed"));
console.log(colors.bold.underline.green("Resource pushed"));
}
async function list(opts: GlobalOptions) {

View File

@@ -155,7 +155,7 @@ async function push(opts: PushOptions, filePath: string, remotePath: string) {
console.log(colors.bold.yellow("Pushing resource..."));
await pushResource(workspace.workspaceId, filePath, remotePath);
console.log(colors.bold.underline.green("Resource successfully pushed"));
console.log(colors.bold.underline.green(`Resource ${remotePath} pushed`));
}
async function list(opts: GlobalOptions) {

View File

@@ -76,7 +76,7 @@ async function push(
await requireLogin(opts);
await pushScript(filePath, contentPath, workspace.workspaceId, remotePath);
console.log(colors.bold.underline.green("Script successfully pushed"));
console.log(colors.bold.underline.green(`Script ${remotePath} pushed`));
}
export async function handleScriptMetadata(path: string, workspace: string, alreadySynced: string[]): Promise<boolean> {

View File

@@ -112,7 +112,7 @@ function ZipFSElement(zip: JSZip): DynFSElement {
}
async function* readDirRecursiveWithIgnore(
ignore: (path: string) => boolean,
ignore: (path: string, isDirectory: boolean) => boolean,
root: DynFSElement,
): AsyncGenerator<
{
@@ -132,7 +132,7 @@ async function* readDirRecursiveWithIgnore(
getContentText(): Promise<string>;
}[] = [{
path: root.path,
ignored: ignore(root.path),
ignored: ignore(root.path, root.isDirectory),
isDirectory: root.isDirectory,
c: root.getChildren,
getContentBytes(): Promise<Uint8Array> {
@@ -150,7 +150,7 @@ async function* readDirRecursiveWithIgnore(
for await (const e2 of e.c()) {
stack.push({
path: e2.path,
ignored: e.ignored || ignore(e2.path),
ignored: e.ignored || ignore(e2.path, e2.isDirectory),
isDirectory: e2.isDirectory,
getContentBytes: e2.getContentBytes,
getContentText: e2.getContentText,
@@ -166,7 +166,7 @@ type Edit = { name: "edited"; path: string; before: string; after: string; };
type Change = Added | Deleted | Edit;
async function elementsToMap(els: DynFSElement, ignore: (path: string) => boolean): Promise<{ [key: string]: string }> {
async function elementsToMap(els: DynFSElement, ignore: (path: string, isDirectory: boolean) => boolean): Promise<{ [key: string]: string }> {
const map: { [key: string]: string } = {};
for await (const entry of readDirRecursiveWithIgnore(
ignore,
@@ -180,7 +180,7 @@ async function elementsToMap(els: DynFSElement, ignore: (path: string) => boolea
}
async function compareDynFSElement(
els1: DynFSElement, els2: DynFSElement,
ignore: (path: string) => boolean,
ignore: (path: string, isDirectory: boolean) => boolean,
raw: boolean
): Promise<Change[]> {
@@ -211,7 +211,29 @@ async function compareDynFSElement(
return changes
}
const isNotWmillFile = (p: string, isDirectory: boolean) => {
if (p.endsWith("/")) {
return false
}
if (isDirectory) {
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
}
try {
const typ = getTypeStrFromPath(p)
if (typ == 'resource-type') {
return p.includes('/')
} else {
return !p.startsWith("u/") && !p.startsWith("f/") && !p.startsWith("g/")
}
} catch {
return true
}
}
const isWhitelisted = (p: string) => {
return p == "./" || p == "" || p == "u" || p == "f" || p == "g"
}
async function ignoreF() {
try {
const ignore: {
@@ -220,9 +242,12 @@ async function ignoreF() {
} = gitignore_parser.compile(
await Deno.readTextFile(".wmillignore"),
);
return (p: string) => p.startsWith(".wmill") || ignore.denies(p);
return (p: string, isDirectory: boolean) => {
return !isWhitelisted(p) && (isNotWmillFile(p, isDirectory) || ignore.denies(p));
}
} catch (e) {
return (p: string) => p.startsWith(".wmill");
return (p: string, isDirectory: boolean) => !isWhitelisted(p) && isNotWmillFile(p, isDirectory)
}
}
@@ -239,7 +264,7 @@ async function pull(
await requireLogin(opts);
console.log("Computing diff local vs remote ...");
console.log(colors.gray("Computing the files to update locally to match remote (taking .wmillignore into account)"));
const remote = ZipFSElement((await downloadZip(workspace))!)
const local = await FSFSElement(path.join(Deno.cwd(), opts.raw ? "" : ".wmill"))
const changes = await compareDynFSElement(remote, local, await ignoreF(), opts.raw)
@@ -256,7 +281,7 @@ async function pull(
}
const conflicts = []
console.log(`Applying changes to files ...`);
console.log(colors.gray(`Applying changes to files ...`));
for await (const change of changes) {
const target = path.join(Deno.cwd(), change.path);
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
@@ -437,8 +462,10 @@ async function push(opts: GlobalOptions & { raw: boolean, yes: boolean, skipPull
if (!opts.raw) {
if (!opts.skipPull) {
console.log("You need to be up-to-date before pushing, pulling first.")
console.log(colors.gray("You need to be up-to-date before pushing, pulling first."))
await pull(opts)
console.log(colors.green("Pull done, now pushing."))
console.log()
}
}
@@ -446,12 +473,12 @@ async function push(opts: GlobalOptions & { raw: boolean, yes: boolean, skipPull
await requireLogin(opts);
console.log("Computing diff remote vs local ...");
console.log(colors.gray("Computing the files to update on the remote to match local (taking .wmillignore into account)"));
const remote = ZipFSElement((await downloadZip(workspace))!)
const local = await FSFSElement(path.join(Deno.cwd(), ""))
const changes = await compareDynFSElement(local, remote, await ignoreF(), opts.raw)
console.log(`local -> remote: ${changes.length} changes to apply`);
console.log(`remote <- local: ${changes.length} changes to apply`);
if (changes.length > 0) {
prettyChanges(changes)
@@ -460,14 +487,20 @@ async function push(opts: GlobalOptions & { raw: boolean, yes: boolean, skipPull
) {
return
}
console.log(`Applying changes to files ...`);
console.log(colors.gray(`Applying changes to files ...`));
const alreadySynced: string[] = []
for await (const change of changes) {
const stateTarget = path.join(Deno.cwd(), ".wmill", change.path)
if (change.name === "edited") {
if (await handleScriptMetadata(change.path, workspace.workspaceId, alreadySynced)) {
if (!opts.raw) {
await Deno.writeTextFile(stateTarget, change.after);
}
continue
} else if (await handleFile(change.path, change.after, workspace.workspaceId, alreadySynced)) {
if (!opts.raw) {
await Deno.writeTextFile(stateTarget, change.after);
}
continue
}
if (!opts.raw) {

View File

@@ -125,7 +125,7 @@ export function getTypeStrFromPath(
| "folder"
| "app" {
const parsed = path.parse(p);
if (parsed.ext !== ".json") {
if (parsed.ext == ".go" || parsed.ext == ".ts" || parsed.ext == ".sh" || parsed.ext == ".py") {
return 'script'
}

View File

@@ -154,7 +154,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
console.log(colors.bold.yellow("Pushing variable..."));
await pushVariable(workspace.workspaceId, filePath, remotePath);
console.log(colors.bold.underline.green("Variable successfully pushed"));
console.log(colors.bold.underline.green(`Variable ${remotePath} pushed`));
}
export async function pushVariable(

View File

@@ -151,7 +151,11 @@ async function switchC(opts: GlobalOptions, workspaceName: string) {
const all = await allWorkspaces();
if (all.findIndex((x) => x.name === workspaceName) === -1) {
console.log(colors.red.bold("! This workspace name does not exist."));
console.log(colors.red.bold(`! This workspace profile ${workspaceName} does not exist locally.`));
console.log("available workspace profiles:")
for (const w of all) {
console.log(' - ' + w.name)
}
return;
}
@@ -184,12 +188,6 @@ export async function add(
workspaceName = await Input.prompt("Name this workspace:");
}
const all = await allWorkspaces();
if (all.findIndex((x) => x.name === workspaceName) !== -1) {
console.log(colors.red.bold("! Workspace name already exists"));
return;
}
if (!workspaceId) {
workspaceId = await Input.prompt({ message: "Enter the ID of this workspace", default: workspaceName, suggestions: [workspaceName] });
}
@@ -219,18 +217,24 @@ export async function add(
token = await loginInteractive(remote);
}
setClient(
token,
remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote,
);
let alreadyExists = false
try {
alreadyExists = await WorkspaceService.existsWorkspace({
requestBody: { id: workspaceId },
})
} catch (e) {
console.log(colors.red.bold("! Credentials or instance is invalid. Aborting."));
throw e
}
if (opts.create) {
setClient(
token,
remote.endsWith("/") ? remote.substring(0, remote.length - 1) : remote,
);
if (
!await WorkspaceService.existsWorkspace({
requestBody: { id: workspaceId },
})
!alreadyExists
) {
console.log(colors.yellow("Workspace does not exist. Creating..."));
console.log(colors.yellow(`Workspace at id ${workspaceId} on ${remote} does not exist. Creating...`));
await WorkspaceService.createWorkspace({
requestBody: {
id: workspaceId,
@@ -239,6 +243,14 @@ export async function add(
},
});
}
} else if (!alreadyExists) {
console.log(colors.red.bold(`! Workspace at id ${workspaceId} on ${remote} does not exist. Re-run with --create to create it. Aborting.`));
console.log("On that instance and with those credentials, the workspaces that you can access are:")
const workspaces = await WorkspaceService.listWorkspaces()
for (const workspace of workspaces) {
console.log(`- ${workspace.id} (name: ${workspace.name})`)
}
Deno.exit(1);
}
await addWorkspace({
@@ -246,28 +258,40 @@ export async function add(
remote: remote,
workspaceId: workspaceId,
token: token,
});
}, opts);
await Deno.writeTextFile(
(await getRootStore()) + "/activeWorkspace",
workspaceName,
);
console.log(colors.green.underline("Succesfully added workspace!"));
console.log(colors.green.underline(`Added workspace ${workspaceName} for ${workspaceId} on ${remote}!`));
}
export async function addWorkspace(workspace: Workspace) {
export async function addWorkspace(workspace: Workspace, opts: any) {
workspace.remote = new URL(workspace.remote).toString(); // add trailing slash in all cases!
const file = await Deno.open((await getRootStore()) + "remotes.ndjson", {
append: true,
write: true,
read: false,
read: true,
create: true,
});
await removeWorkspace(workspace.name, true, opts);
await file.write(new TextEncoder().encode(JSON.stringify(workspace) + "\n"));
file.close();
}
export async function removeWorkspace(name: string) {
export async function removeWorkspace(name: string, silent: boolean, opts: any) {
const orgWorkspaces = await allWorkspaces();
if (orgWorkspaces.findIndex((x) => x.name === name) === -1) {
if (!silent) {
console.log(colors.red.bold(`! Workspace profile ${name} does not exist locally`));
console.log("available workspace profiles:")
await list(opts)
}
return;
}
if (silent) {
console.log(colors.yellow(`Replacing existing workspace ${name}`))
}
await Deno.writeTextFile(
(await getRootStore()) + "remotes.ndjson",
orgWorkspaces
@@ -275,11 +299,13 @@ export async function removeWorkspace(name: string) {
.map((x) => JSON.stringify(x))
.join("\n"),
);
if (!silent) {
console.log(colors.green.underline(`Succesfully removed workspace ${name}!`));
}
}
async function remove(_opts: GlobalOptions, name: string) {
await removeWorkspace(name);
console.log(colors.green.underline("Succesfully removed workspace!"));
await removeWorkspace(name, false, _opts);
}
const command = new Command()

View File

@@ -14,7 +14,6 @@ BigInt.prototype.toJSON = function () {
};
export { pgSql, pgClient } from './pg.ts'
export { mySql, mysqlClient } from './mysql.ts'
export type Sql = string
export type Email = string

View File

@@ -1,12 +1,12 @@
{
"name": "windmill",
"version": "1.67.0",
"version": "1.69.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "windmill",
"version": "1.67.0",
"version": "1.69.0",
"dependencies": {
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",

View File

@@ -1,6 +1,6 @@
{
"name": "windmill",
"version": "1.67.0",
"version": "1.69.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",

View File

@@ -396,7 +396,7 @@
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder="my_{resource_type}"
namePlaceholder="{resource_type}"
kind="resource"
/>
@@ -433,7 +433,7 @@
{:else}
<Path
initialPath=""
namePlaceholder="my_{resource_type}"
namePlaceholder="{resource_type}"
bind:error={pathError}
bind:path
kind="resource"

View File

@@ -22,6 +22,7 @@
import Tooltip from './Tooltip.svelte'
import FolderEditor from './FolderEditor.svelte'
import GroupEditor from './GroupEditor.svelte'
import { random_adj } from './random_positive_adjetive'
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
let meta: Meta | undefined = undefined
@@ -69,15 +70,23 @@
export async function reset() {
if (path == '' || path == 'u//') {
meta = { ownerKind: 'user', name: namePlaceholder, owner: '' }
meta = {
ownerKind: 'user',
name: random_adj() + '_' + namePlaceholder,
owner: ''
}
meta.owner = $userStore!.username.split('@')[0]
let i = 1
while (await pathExists(metaToPath(meta), kind)) {
meta.name = `${namePlaceholder}_${i}`
i += 1
let newMeta = { ...meta }
while (await pathExists(metaToPath(newMeta), kind)) {
disabled = true
error = 'finding an available name...'
newMeta.name = random_adj() + '_' + namePlaceholder
}
error = ''
disabled = false
meta = newMeta
path = metaToPath(meta)
} else {
meta = pathToMeta(path)

View File

@@ -144,7 +144,7 @@
disabled={!can_write}
bind:path
{initialPath}
namePlaceholder="my_resource"
namePlaceholder="resource"
kind="resource"
/>
</div>

View File

@@ -180,7 +180,7 @@
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder={'my_schedule'}
namePlaceholder="schedule"
kind="schedule"
/>
<div class="mb-8" />

View File

@@ -204,7 +204,7 @@
bind:path={script.path}
{initialPath}
on:enter={() => changeStep(2)}
namePlaceholder="my_script"
namePlaceholder="script"
kind="script"
/>
<h2 class="border-b pb-1 mt-12 mb-4">Summary</h2>
@@ -245,7 +245,7 @@
>
<LanguageIcon lang="pgsql" /><span class="ml-2 py-4">PostgreSQL</span>
</Button>
<Button
<!-- <Button
size="sm"
variant="border"
color={template == 'mysql' ? 'blue' : 'dark'}
@@ -257,7 +257,7 @@
}}
>
<LanguageIcon lang="mysql" /><span class="ml-2 py-4">MySQL</span>
</Button>
</Button> -->
</div>
<div class="mt-16 mb-4">
<Button

View File

@@ -148,7 +148,7 @@
bind:error={pathError}
bind:path
{initialPath}
namePlaceholder="my_variable"
namePlaceholder="variable"
kind="variable"
/>
</div>

View File

@@ -1,43 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../editor/SubGridEditor.svelte'
import type { AppInput } from '../inputType'
import type { AppEditorContext, GridItem } from '../types'
import InputValue from './helpers/InputValue.svelte'
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let configuration: Record<string, AppInput>
export let subGrids: GridItem[][] | undefined = undefined
export let componentContainerHeight: number
export const staticOutputs: string[] = ['loading', 'result']
const { focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let result: string[] | undefined = undefined
let gridContent: string[] | undefined = undefined
function onFocus() {
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
$: $selectedComponent === id && onFocus()
</script>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
{#if subGrids && subGrids[0]}
<SubGridEditor
bind:subGrid={subGrids[0]}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</RunnableWrapper>

View File

@@ -1,25 +0,0 @@
<script lang="ts">
import DisplayResult from '$lib/components/DisplayResult.svelte'
import { getContext } from 'svelte'
import type { AppInput } from '../inputType'
import { IS_APP_PUBLIC_CONTEXT_KEY } from '../types'
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
let result: any = undefined
export const staticOutputs: string[] = ['result', 'loading']
</script>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<div class="w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm">
Results
</div>
<div class="p-2">
<DisplayResult {result} {requireHtmlApproval} />
</div>
</RunnableWrapper>

View File

@@ -1,9 +1,9 @@
<script lang="ts">
import { Button, type ButtonType } from '$lib/components/common'
import { getContext, onMount } from 'svelte'
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext } from '../../types'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type RunnableComponent from '../helpers/RunnableComponent.svelte'
@@ -20,7 +20,7 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let customCss: Record<'button', { class: string; style: string }> | undefined = undefined
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
export const staticOutputs: string[] = ['loading', 'result']
@@ -119,10 +119,10 @@
<Button
btnClasses={twMerge(
$app.css?.['buttoncomponent']?.['button']?.class,
customCss?.button?.class,
customCss?.button.class,
fillContainer ? 'w-full h-full' : ''
)}
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button?.style].join(';')}
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button.style].join(';')}
{disabled}
on:pointerdown={(e) => {
e?.stopPropagation()

View File

@@ -0,0 +1,3 @@
export { default as AppButton } from './AppButton.svelte'
export { default as AppForm } from './AppForm.svelte'
export { default as AppFormButton } from './AppFormButton.svelte'

View File

@@ -0,0 +1,36 @@
<script lang="ts">
import DisplayResult from '$lib/components/DisplayResult.svelte'
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppInput } from '../../inputType'
import { IS_APP_PUBLIC_CONTEXT_KEY, type AppEditorContext, type ComponentCustomCSS } from '../../types'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'header' | 'container'> | undefined = undefined
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
const { app } = getContext<AppEditorContext>('AppEditorContext')
let result: any = undefined
export const staticOutputs: string[] = ['result', 'loading']
</script>
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<div class={twMerge(
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
$app.css?.['displaycomponent']?.['header']?.class,
customCss?.header?.class
)}>
Results
</div>
<div class={twMerge(
'p-2',
$app.css?.['displaycomponent']?.['container']?.class,
customCss?.container?.class
)}>
<DisplayResult {result} {requireHtmlApproval} />
</div>
</RunnableWrapper>

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 } from '../../types'
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
import { getContext } from 'svelte'
export let id: string
@@ -17,7 +17,7 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
export let initializing: boolean | undefined = undefined
export let customCss: Record<'text', { class: string; style: string }> | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export const staticOutputs: string[] = ['result', 'loading']

View File

@@ -0,0 +1,12 @@
export { default as AppTable } from './table/AppTable.svelte'
export { default as AppBarChart } from './AppBarChart.svelte'
export { default as AppDisplayComponent } from './AppDisplayComponent.svelte'
export { default as AppHtml } from './AppHtml.svelte'
export { default as AppIcon } from './AppIcon.svelte'
export { default as AppImage } from './AppImage.svelte'
export { default as AppPieChart } from './AppPieChart.svelte'
export { default as AppScatterChart } from './AppScatterChart.svelte'
export { default as AppText } from './AppText.svelte'
export { default as AppTimeseries } from './AppTimeseries.svelte'
export { default as PlotlyHtml } from './PlotlyHtml.svelte'
export { default as VegaLiteHtml } from './VegaLiteHtml.svelte'

View File

@@ -1,19 +1,19 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import type { Output } from '../../rx'
import type { AppEditorContext, BaseAppComponent } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import type { AppInput } from '../../inputType'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { Output } from '../../../rx'
import type { AppEditorContext, BaseAppComponent } from '../../../types'
import InputValue from '../../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
import { writable } from 'svelte/store'
import { createSvelteTable, flexRender, type TableOptions } from '@tanstack/svelte-table'
import AppButton from '../buttons/AppButton.svelte'
import AppButton from '../../buttons/AppButton.svelte'
import { classNames, isObject } from '$lib/utils'
import DebouncedInput from '../helpers/DebouncedInput.svelte'
import DebouncedInput from '../../helpers/DebouncedInput.svelte'
import AppTableFooter from './AppTableFooter.svelte'
import { tableOptions } from './tableOptions'
import Alert from '$lib/components/common/alert/Alert.svelte'
import type { ButtonComponent } from '../../editor/Component.svelte'
import type { ButtonComponent } from '../../../editor/component'
export let id: string
export let componentInput: AppInput | undefined

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
import type { InlineScript } from '../types'
import RunnableComponent from './helpers/RunnableComponent.svelte'
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../../inputType'
import type { InlineScript } from '../../types'
import RunnableComponent from './RunnableComponent.svelte'
export let id: string
export let name: string

View File

@@ -1,5 +1,6 @@
export { default as AlignWrapper } from './AlignWrapper.svelte'
export { default as DebouncedInput } from './DebouncedInput.svelte'
export { default as HiddenComponent } from './HiddenComponent.svelte'
export { default as InputDefaultValue } from './InputDefaultValue.svelte'
export { default as InputValue } from './InputValue.svelte'
export { default as MissingConnectionWarning } from './MissingConnectionWarning.svelte'

View File

@@ -0,0 +1,6 @@
export * from './buttons'
export * from './display'
export * from './inputs'
export * from './layout'
export * from './helpers'

View File

@@ -47,7 +47,11 @@
function onChange(e: CustomEvent) {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
outputs?.result.set(JSON.parse(e.detail?.['value']) || undefined)
let result: any = undefined
try {
result = JSON.parse(e.detail?.['value'])
} catch (_) {}
outputs?.result.set(result)
}
</script>
@@ -76,6 +80,6 @@
<style global>
.app-select .value-container {
@apply p-0 !important;
padding: 0 !important;
}
</style>

View File

@@ -1,10 +1,10 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type { AppInput } from '../../../inputType'
import type { Output } from '../../../rx'
import type { AppEditorContext } from '../../../types'
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
import InputValue from '../../helpers/InputValue.svelte'
import CurrencyInput from './CurrencyInput.svelte'
export let id: string

View File

@@ -0,0 +1,9 @@
export { default as AppCheckbox } from './AppCheckbox.svelte'
export { default as AppCurrencyInput } from './currency/AppCurrencyInput.svelte'
export { default as AppDateInput } from './AppDateInput.svelte'
export { default as AppFileInput } from './AppFileInput.svelte'
export { default as AppNumberInput } from './AppNumberInput.svelte'
export { default as AppRangeInput } from './AppRangeInput.svelte'
export { default as AppSelect } from './AppSelect.svelte'
export { default as AppSliderInputs } from './AppSliderInputs.svelte'
export { default as AppTextInput } from './AppTextInput.svelte'

View File

@@ -0,0 +1,41 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppEditorContext, GridItem } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>
export let componentContainerHeight: number
let noPadding: boolean | undefined = undefined
export const staticOutputs: string[] = []
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let gridContent: string[] | undefined = undefined
function onFocus() {
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
$: $selectedComponent === id && onFocus()
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
{noPadding}
bind:subGrid={$app.subgrids[`${id}-0`]}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import type { AppInput } from '../inputType'
import type { HorizontalAlignment, VerticalAlignment } from '../types'
import AlignWrapper from './helpers/AlignWrapper.svelte'
import InputValue from './helpers/InputValue.svelte'
import type { AppInput } from '../../inputType'
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: Record<string, AppInput>

View File

@@ -1,32 +1,29 @@
<script lang="ts">
import { Tab, Tabs } from '$lib/components/common'
import { getContext } from 'svelte'
import SubGridEditor from '../editor/SubGridEditor.svelte'
import type { AppInput } from '../inputType'
import type { Output } from '../rx'
import type { AppEditorContext, GridItem } from '../types'
import InputValue from './helpers/InputValue.svelte'
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, GridItem } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let configuration: Record<string, AppInput>
export let subGrids: GridItem[][] | undefined = undefined
export let componentContainerHeight: number
export let tabs: string[]
export const staticOutputs: string[] = ['loading', 'result', 'selectedTabIndex']
const { worldStore, focusedGrid, selectedComponent } =
export const staticOutputs: string[] = ['selectedTabIndex']
const { app, worldStore, focusedGrid, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
let result: string[] | undefined = undefined
let gridContent: string[] | undefined = undefined
let selected: string = ''
let noPadding: boolean | undefined = undefined
$: selectedIndex = result?.indexOf(selected) ?? -1
$: selectedIndex = tabs?.indexOf(selected) ?? -1
$: if (result && selected === '') {
selected = result[0]
$: if ((tabs && selected === '') || !tabs.includes(selected)) {
selected = tabs[0]
}
$: outputs = $worldStore?.outputsById[id] as {
@@ -38,6 +35,7 @@
}
$: selectedIndex >= 0 && handleTabSelection()
let tabHeight: number = 0
function onFocus() {
@@ -50,24 +48,27 @@
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
</script>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<InputValue {id} input={configuration.tabs} bind:value={tabs} />
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
<div>
<div bind:clientHeight={tabHeight}>
<Tabs bind:selected>
{#each result ?? [] as res}
{#each tabs ?? [] as res}
<Tab value={res}>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
</Tabs>
</div>
{#if subGrids && subGrids[selectedIndex]}
{#if $app.subgrids?.[`${id}-${selectedIndex}`]}
<SubGridEditor
bind:subGrid={subGrids[selectedIndex]}
bind:subGrid={$app.subgrids[`${id}-${selectedIndex}`]}
{noPadding}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</RunnableWrapper>
</div>

View File

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

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { components, type AppComponent } from './Component.svelte'
import { components, type AppComponent } from './component'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
export let component: AppComponent

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
import type { AppComponent } from './Component.svelte'
import type { AppComponent } from './component'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>

View File

@@ -36,6 +36,7 @@
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import { page } from '$app/stores'
import CssSettings from './componentsPanel/CssSettings.svelte'
import { findGridItem } from './appUtils'
export let app: App
export let path: string
@@ -194,21 +195,21 @@
</Pane>
<Pane size={21} minSize={5} maxSize={33}>
<div class="relative flex flex-col h-full">
<Tabs bind:selected={selectedTab}>
<Tab value="insert" size="xs">
<div class="m-1 flex flex-row gap-2">
<Tabs bind:selected={selectedTab} class="!border-b-2 !border-gray-200">
<Tab value="insert" size="xs" class="grow">
<div class="m-1 center-center gap-2">
<Icon data={faPlus} />
<span>Insert</span>
</div>
</Tab>
<Tab value="settings" size="xs">
<div class="m-1 flex flex-row gap-2">
<Tab value="settings" size="xs" class="grow">
<div class="m-1 center-center gap-2">
<Icon data={faSliders} />
<span>Settings</span>
</div>
</Tab>
<Tab value="css" size="xs">
<div class="m-1 flex flex-row gap-2">
<Tab value="css" size="xs" class="grow">
<div class="m-1 center-center gap-2">
<Icon data={faCode} />
<span>CSS</span>
</div>
@@ -218,7 +219,9 @@
{#if $selectedComponent !== undefined}
<SettingsPanel />
{:else}
<div class="p-2 min-w-[150px] text-sm">No component selected.</div>
<div class="min-w-[150px] text-sm text-gray-500 text-center py-8 px-2">
Select a component to see the settings&nbsp;for&nbsp;it
</div>
{/if}
</TabContent>
<TabContent value="insert">

View File

@@ -54,7 +54,7 @@
import { toStatic } from '../utils'
import AppExportButton from './AppExportButton.svelte'
import AppInputs from './AppInputs.svelte'
import type { AppComponent } from './Component.svelte'
import type { AppComponent } from './component/components'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
async function hash(message) {
@@ -241,7 +241,7 @@
bind:error={pathError}
bind:path={newPath}
initialPath=""
namePlaceholder="my_app"
namePlaceholder="app"
kind="app"
/>
@@ -467,8 +467,8 @@
<div>{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}</div>
<button
on:click={() => {
$focusedGrid = undefined
$selectedComponent = undefined
$focusedGrid = undefined
}}
>
<X size={14} />

File diff suppressed because it is too large Load Diff

View File

@@ -5,8 +5,7 @@
import { createEventDispatcher, getContext } from 'svelte'
import Popover from '$lib/components/Popover.svelte'
import { Alert, Button } from '$lib/components/common'
import type { AppComponent } from './Component.svelte'
import type { Output } from '../rx'
import type { AppComponent } from './component'
export let component: AppComponent
export let selected: boolean

View File

@@ -8,8 +8,8 @@
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
import type { Policy } from '$lib/gen'
import HiddenComponent from '../components/HiddenComponent.svelte'
import Component from './Component.svelte'
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
import Component from './component/Component.svelte'
export let policy: Policy
@@ -37,8 +37,8 @@
const disabledGridItem = fct(gridItem)
if (disabledGridItem.data.subGrids) {
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map((subgrid: GridItem[]) =>
subgrid.map((subgridItem: GridItem) => fct(subgridItem))
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map(
(subgrid: GridItem[]) => subgrid?.map((subgridItem: GridItem) => fct(subgridItem)) ?? []
)
}
@@ -87,6 +87,7 @@
function selectComponent(id: string) {
if (!$connectingInput.opened) {
$selectedComponent = id
$focusedGrid = undefined
}
}

View File

@@ -1,36 +1,36 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext } from '../types'
import { allItems, allItemsWithParent } from '../utils'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
import TablePanel from './TablePanel.svelte'
const { selectedComponent, lazyGrid, app } = getContext<AppEditorContext>('AppEditorContext')
const { selectedComponent, app } = getContext<AppEditorContext>('AppEditorContext')
$: subgridKeys = Object.keys($app.subgrids ?? {})
</script>
{#each $lazyGrid as gridItem (gridItem.data.id)}
{#each $app.grid as gridItem (gridItem.data.id)}
{#if gridItem.data.id === $selectedComponent}
<ComponentPanel bind:component={gridItem.data} />
<ComponentPanel parent={undefined} bind:component={gridItem.data} />
{:else if gridItem.data.type === 'tablecomponent'}
<TablePanel bind:component={gridItem.data} />
{:else if gridItem.data.subGrids}
{#each gridItem.data.subGrids as subGrid}
{#each subGrid as subGridItem, index (subGridItem.data.id)}
{#if subGridItem.data.id === $selectedComponent}
<ComponentPanel
bind:component={subGridItem.data}
onDelete={() => {
subGrid.splice(index, 1)
subGrid = subGrid
}}
/>
{/if}
{/each}
{/each}
{/if}
{/each}
{#if $app.subgrids}
{#each subgridKeys as key (key)}
{#each $app.subgrids[key] as gridItem (gridItem.data.id)}
{#if gridItem.data.id === $selectedComponent}
<ComponentPanel parent={key} bind:component={gridItem.data} />
{:else if gridItem.data.type === 'tablecomponent'}
<TablePanel bind:component={gridItem.data} />
{/if}
{/each}
{/each}
{/if}
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
{#if $selectedComponent === `bg_${index}`}
<PanelSection title={`Background script inputs`}>

View File

@@ -4,10 +4,12 @@
import Grid from '@windmill-labs/svelte-grid'
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
import type { AppEditorContext, GridItem } from '../types'
import Component from './Component.svelte'
import Component from './component/Component.svelte'
export let subGrid: GridItem[]
export let containerHeight: number
export let noPadding = false
//export let id: string
export let subGrid: GridItem[] = []
const dispatch = createEventDispatcher()
@@ -35,6 +37,12 @@
onComponent = id
if (!$connectingInput.opened) {
$selectedComponent = id
/*
$focusedGrid = {
parentComponentId: parentId,
subGridIndex: index
}
*/
}
}
@@ -47,33 +55,12 @@
// @ts-ignore
let container
$: if ($focusedGrid?.parentComponentId !== $selectedComponent) {
const gridItemIds = $app.grid
.map((gridItem: GridItem) => {
if (gridItem.data.id === $focusedGrid?.parentComponentId) {
const subGrids = gridItem.data.subGrids ?? []
return [
gridItem.data.id,
...subGrids.map((subGrid: GridItem[]) =>
subGrid.map((gridItem: GridItem) => gridItem.data.id)
)
]
} else {
return []
}
})
.flat(2)
if (!gridItemIds.includes($selectedComponent)) {
$focusedGrid = undefined
}
}
</script>
<div class="relative w-full subgrid " bind:this={container}>
<div
class="px-4 pt-4 overflow-auto {$connectingInput?.opened ? '' : ''}"
class:px-2={!noPadding}
class="py-2 overflow-auto {$connectingInput?.opened ? '' : ''}"
on:pointerdown|stopPropagation={onpointerdown}
on:pointerleave={onpointerup}
on:pointerup={onpointerup}

View File

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

View File

@@ -0,0 +1,182 @@
import { getNextId } from '$lib/components/flows/flowStateUtils'
import type { App, FocusedGrid, GridItem } from '../types'
import { Component, getRecommendedDimensionsByComponent, type AppComponent } from './component'
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
import { gridColumns } from '../gridUtils'
import { allItems } from '../utils'
function findGridItemById(
root: GridItem[],
subGrids: Record<string, GridItem[]> | undefined,
id: string
): GridItem | undefined {
for (const gridItem of root) {
if (gridItem.id === id) {
return gridItem
}
if (subGrids) {
const numberOfSubgrids = gridItem.data.numberOfSubgrids
if (numberOfSubgrids) {
for (let i = 0; i < numberOfSubgrids; i++) {
const subgrid = subGrids[`${gridItem.id}-${i}`] ?? []
const found = findGridItemById(subgrid, subGrids, id)
if (found) {
return found
}
}
}
}
}
return undefined
}
export function findGridItem(app: App, id: string): GridItem | undefined {
return findGridItemById(app.grid, app.subgrids, id)
}
export function getNextGridItemId(app: App): string {
const subgridsKeys = allItems(app.grid, app.subgrids).map((x) => x.id)
const withoutDash = subgridsKeys.map((element) => element.split('-')[0])
const id = getNextId([...new Set(withoutDash)])
return id
}
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
const appComponent = data
appComponent.id = id
const newComponent = {
fixed: false,
resizable: true,
draggable: true,
customDragger: false,
customResizer: false,
x: 0,
y: 0
}
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
const newItem: GridItem = {
data: newData,
id: id
}
gridColumns.forEach((column) => {
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
newItem[column] = {
...newComponent,
min: { w: 1, h: 1 },
max: { w: column, h: 100 },
w: rec.w,
h: rec.h
}
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
newItem[column] = { ...newItem[column], ...position }
})
return newItem
}
export function insertNewGridItem(
app: App,
data: AppComponent,
focusedGrid: FocusedGrid | undefined
) {
const id = getNextGridItemId(app)
if (!focusedGrid) {
const newItem = createNewGridItem(app.grid, id, data)
app.grid.push(newItem)
} else {
const { parentComponentId, subGridIndex } = focusedGrid
if (!app.subgrids) {
app.subgrids = {}
}
const subGrid = app.subgrids[`${parentComponentId}-${subGridIndex}`] ?? []
const newItem = createNewGridItem(subGrid, id, data)
const key = `${parentComponentId}-${subGridIndex ?? 0}`
if (!app.subgrids[key]) {
app.subgrids[key] = [newItem]
} else {
app.subgrids[key].push(newItem)
}
}
return id
}
export function getAllSubgridsAndComponentIds(
app: App,
component: AppComponent
): [string[], string[]] {
const subgrids: string[] = []
let components: string[] = [component.id]
if (app.subgrids && component.numberOfSubgrids) {
for (let i = 0; i < component.numberOfSubgrids; i++) {
const subgrid = app.subgrids[`${component.id}-${i}`]
if (subgrid) {
for (const item of subgrid) {
let [recSubgrids, recComponents] = getAllSubgridsAndComponentIds(app, item.data)
subgrids.push(...recSubgrids)
components.push(...recComponents)
}
}
}
}
return [subgrids, components]
}
export function deleteGridItem(
app: App,
component: AppComponent,
parent: string | undefined
): string[] {
let [subgrids, components] = getAllSubgridsAndComponentIds(app, component)
if (app.subgrids) {
subgrids.forEach((id) => {
delete app.subgrids![id]
})
}
if (!parent) {
let index = app.grid.findIndex((x) => x.id == component.id)
if (index > -1) {
app.grid.splice(index, 1)
}
} else {
let grid = app.subgrids![parent]
let index = grid.findIndex((x) => x.id == component.id)
if (index > -1) {
grid.splice(index, 1)
}
}
return components
}
export function duplicateGridItem(
app: App,
focusedGrid: FocusedGrid | undefined,
id: string
): string | undefined {
const gridItem = findGridItem(app, id)
if (gridItem) {
const newId = getNextGridItemId(app)
const newItem = JSON.parse(JSON.stringify(gridItem))
newItem.id = newId
newItem.data.id = newId
return insertNewGridItem(app, newItem.data, focusedGrid)
}
return undefined
}

View File

@@ -0,0 +1,230 @@
<script lang="ts">
import { getContext } from 'svelte'
import { fade } from 'svelte/transition'
import { Loader2 } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import type { AppEditorContext } from '../../types'
import ComponentHeader from '../ComponentHeader.svelte'
import {
AppBarChart,
AppDisplayComponent,
AppTable,
AppText,
AppButton,
AppPieChart,
AppSelect,
AppCheckbox,
AppTextInput,
AppNumberInput,
AppDateInput,
AppForm,
AppScatterChart,
AppTimeseries,
AppHtml,
AppSliderInputs,
AppFormButton,
VegaLiteHtml,
PlotlyHtml,
AppRangeInput,
AppTabs,
AppIcon,
AppCurrencyInput,
AppDivider,
AppFileInput,
AppImage,
AppContainer
} from '../../components'
import type { AppComponent } from './components'
export let component: AppComponent
export let selected: boolean
export let locked: boolean = false
export let pointerdown: boolean = false
const { staticOutputs, mode, connectingInput, app } =
getContext<AppEditorContext>('AppEditorContext')
let hover = false
let initializing: boolean | undefined = undefined
let componentContainerHeight: number = 0
</script>
<div
on:pointerenter={() => (hover = true)}
on:pointerleave={() => (hover = false)}
class="h-full flex flex-col w-full component"
>
{#if $mode !== 'preview'}
<ComponentHeader {hover} {pointerdown} {component} {selected} on:delete on:lock {locked} />
{/if}
<div
on:pointerdown={(e) => {
// Removed in https://github.com/windmill-labs/windmill/pull/1171
// In case of a bug, try stopping propagation on the native event
// and dispatch a custom event: `e?.stopPropagation(); dispatch('select');`
// if ($mode === 'preview') {
// e?.stopPropagation()
// }
}}
class={twMerge(
'h-full bg-white/40',
selected && $mode !== 'preview' ? 'border border-blue-500' : '',
!selected && $mode !== 'preview' && !component.card ? 'border-gray-100' : '',
$mode !== 'preview' && !$connectingInput.opened ? 'hover:border-blue-500' : '',
component.softWrap ? '' : 'overflow-auto',
$mode != 'preview' ? 'cursor-pointer' : '',
'relative z-auto',
$app.css?.['app']?.['component']?.class
)}
style={$app.css?.['app']?.['component']?.style}
bind:clientHeight={componentContainerHeight}
>
{#if component.type === 'displaycomponent'}
<AppDisplayComponent
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'barchartcomponent'}
<AppBarChart
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'timeseriescomponent'}
<AppTimeseries
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'htmlcomponent'}
<AppHtml
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'vegalitecomponent'}
<VegaLiteHtml
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'plotlycomponent'}
<PlotlyHtml
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'scatterchartcomponent'}
<AppScatterChart
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'piechartcomponent'}
<AppPieChart
{...component}
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
bind:componentInput={component.componentInput}
/>
{:else if component.type === 'tablecomponent'}
<AppTable
{...component}
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
bind:componentInput={component.componentInput}
bind:actionButtons={component.actionButtons}
/>
{:else if component.type === 'textcomponent'}
<AppText
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'buttoncomponent'}
<AppButton
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'selectcomponent'}
<AppSelect {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'formcomponent'}
<AppForm
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'formbuttoncomponent'}
<AppFormButton
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'checkboxcomponent'}
<AppCheckbox {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'textinputcomponent'}
<AppTextInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'passwordinputcomponent'}
<AppTextInput
inputType="password"
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'dateinputcomponent'}
<AppDateInput
inputType="date"
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'numberinputcomponent'}
<AppNumberInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'currencycomponent'}
<AppCurrencyInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'slidercomponent'}
<AppSliderInputs {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'horizontaldividercomponent'}
<AppDivider {...component} position="horizontal" />
{:else if component.type === 'verticaldividercomponent'}
<AppDivider {...component} position="vertical" />
{:else if component.type === 'rangecomponent'}
<AppRangeInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'tabscomponent'}
<AppTabs
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
/>
{:else if component.type === 'containercomponent'}
<AppContainer
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
/>
{:else if component.type === 'iconcomponent'}
<AppIcon {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'fileinputcomponent'}
<AppFileInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'imagecomponent'}
<AppImage {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{/if}
</div>
</div>
{#if initializing}
<div
out:fade={{ duration: 200 }}
class="absolute inset-0 center-center flex-col bg-white text-gray-600 border"
>
<Loader2 class="animate-spin" size={16} />
<span class="text-xs mt-1">Loading</span>
</div>
{/if}

View File

@@ -0,0 +1,15 @@
# How to add new components
1. [**`components.ts`**](./components.ts):
1. Create a type for the new component and add it to the `AppComponent` union
type
1. Add the component to the `components` record
1. [**`sets.ts`**](./sets.ts):
1. Add the component to one of the component sets: `layout`, `buttons`,
`inputs` or `display` _(this controls which group the component will be
placed in in the **Insert** menu)_
1. [**`Component.svelte`**](./Component.svelte):
1. Add the new component in the Svelte `if` statement
1. [**`default-codes.ts`**](./default-codes.ts):
1. _(optional)_ Add the default code associated with the new component to the
`DEFAULT_CODES` record

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,325 @@
import type { AppComponent } from "."
export function defaultCode(component: string, language: string): string | undefined {
return DEFAULT_CODES[component]?.[language]
}
export const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'python3', string>>> = {
tablecomponent: {
deno: `export async function main() {
return [
{
"id": 1,
"name": "A cell with a long name",
"age": 42
},
{
"id": 2,
"name": "A briefer cell",
"age": 84
}
]
}`,
python3: `def main():
return [
{
"id": 1,
"name": "A cell with a long name",
"age": 42
},
{
"id": 2,
"name": "A briefer cell",
"age": 84
}
]`
},
textcomponent: {
deno: `export async function main() {
return "foo"
}`,
python3: `def main():
return "foo"`
},
barchartcomponent: {
deno: `export async function main() {
return {
"data": [
25,
50,
25
],
"labels": [
"Bar",
"Charts",
"<3"
]
}
}`,
python3: `def main():
return {
"data": [
25,
50,
25
],
"labels": [
"Bar",
"Charts",
"<3"
]
}`
},
displaycomponent: {
deno: `export async function main() {
return {
"foo": 42
}
}`,
python3: `def main():
return {
"foo": 42
}`
},
htmlcomponent: {
deno: `export async function main() {
return \`<img
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
>
<h1 class="absolute top-4 left-2 text-white">
Hello world
</h1>\`
}`,
python3: `def main():
return '''<img
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
>
<h1 class="absolute top-4 left-2 text-white">
Hello world
</h1>'''`
},
vegalitecomponent: {
deno: `export async function main() {
return {
data: {
values: [
{ a: "A", b: 28 },
{ a: "B", b: 55 },
{ a: "C", b: 43 },
{ a: "D", b: 91 },
],
},
mark: "bar",
encoding: {
x: { field: "a", type: "ordinal" },
y: { field: "b", type: "quantitative" },
},
}
}`,
python3: `def main():
return {
"data": {
"values": [
{ "a": "A", "b": 28 },
{ "a": "B", "b": 55 },
{ "a": "C", "b": 43 },
{ "a": "D", "b": 91 },
],
},
"mark": "bar",
"encoding": {
"x": { "field": "a", "type": "ordinal" },
"y": { "field": "b", "type": "quantitative" },
},
}`
},
plotlycomponent: {
deno: `export async function main() {
return {
type: 'bar',
x: [1, 2, 3, 4],
y: [5, 10, 2, 8],
marker: {
color: '#C8A2C8',
line: {
width: 2.5
}
}
};
}`,
python3: `def main():
return {
"type": "bar",
"x": [1, 2, 3, 4],
"y": [5, 10, 2, 8],
"marker": {
"color": "#C8A2C8",
"line": {
"width": 2.5
}
}
}`
},
piechartcomponent: {
deno: `export async function main() {
return {
"data": [
25,
50,
25
],
"labels": [
"Pie",
"Charts",
"<3"
]
}
}`,
python3: `def main():
return {
"data": [
25,
50,
25
],
"labels": [
"Pie",
"Charts",
"<3"
]
}`
},
scatterchartcomponent: {
deno: `export async function main() {
return [
{
"label": "foo",
"data": [
{ "x": 25, "y": 50 },
{ "x": 23, "y": 23 },
{ "x": 12, "y": 37 }
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{ "x": 32, "y": 32 },
{ "x": 25, "y": 42 },
{ "x": 3, "y": 27 }
],
"backgroundColor": "orange"
}
]
}`,
python3: `def main():
return [
{
"label": "foo",
"data": [
{ "x": 25, "y": 50 },
{ "x": 23, "y": 23 },
{ "x": 12, "y": 37 }
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{ "x": 32, "y": 32 },
{ "x": 25, "y": 42 },
{ "x": 3, "y": 27 }
],
"backgroundColor": "orange"
}
]`
},
timeseriescomponent: {
deno: `export async function main() {
return [
{
"label": "foo",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 50
},
{
"x": "2021-11-07 01:00:28",
"y": 60
},
{
"x": "2021-11-07 09:00:28",
"y": 20
}
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 20
},
{
"x": "2021-11-07 01:00:28",
"y": 13
},
{
"x": "2021-11-07 09:00:28",
"y": 45
}
],
"backgroundColor": "orange"
}
]
}`,
python3: `def main():
return [
{
"label": "foo",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 50
},
{
"x": "2021-11-07 01:00:28",
"y": 60
},
{
"x": "2021-11-07 09:00:28",
"y": 20
}
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 20
},
{
"x": "2021-11-07 01:00:28",
"y": 13
},
{
"x": "2021-11-07 09:00:28",
"y": 45
}
],
"backgroundColor": "orange"
}
]`
},
iconcomponent: {
deno: `export async function main() {
return "smile";
}`,
python3: `def main():
return "smile"`
}
} as const

View File

@@ -0,0 +1,5 @@
export { default as Component } from './Component.svelte'
export * from './components'
export * from './default-codes'
export * from './sets'

View File

@@ -0,0 +1,52 @@
import type { ComponentSet } from '../../types'
const layout: ComponentSet = {
title: 'Layout',
components: [
'tabscomponent',
'containercomponent',
'horizontaldividercomponent',
'verticaldividercomponent'
]
} as const
const buttons: ComponentSet = {
title: 'Buttons',
components: ['buttoncomponent', 'formcomponent', 'formbuttoncomponent']
} as const
const inputs: ComponentSet = {
title: 'Inputs',
components: [
'textinputcomponent',
'passwordinputcomponent',
'numberinputcomponent',
'currencycomponent',
'slidercomponent',
'rangecomponent',
'dateinputcomponent',
'fileinputcomponent',
'checkboxcomponent',
'selectcomponent'
]
} as const
const display: ComponentSet = {
title: 'Display',
components: [
'textcomponent',
'iconcomponent',
'imagecomponent',
'htmlcomponent',
'tablecomponent',
'barchartcomponent',
'piechartcomponent',
'vegalitecomponent',
'plotlycomponent',
'scatterchartcomponent',
'timeseriescomponent',
'displaycomponent',
]
} as const
export const COMPONENT_SETS = [layout, buttons, inputs, display] as const

View File

@@ -1,142 +1,49 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import Icon from 'svelte-awesome'
import type { AppEditorContext, GridItem } from '../../types'
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
import type { AppEditorContext } from '../../types'
import { getContext, onMount } from 'svelte'
import { getNextId } from '$lib/components/flows/flowStateUtils'
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
import { isOpenStore } from './store'
import { gridColumns } from '../../gridUtils'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import {
components as componentsRecord,
componentSets,
getRecommendedDimensionsByComponent,
type AppComponent
} from '../Component.svelte'
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
import ListItem from './ListItem.svelte'
import { insertNewGridItem } from '../appUtils'
const TITLE_PREFIX = 'Component.' as const
const { app, selectedComponent, focusedGrid } = getContext<AppEditorContext>('AppEditorContext')
// The grid is needed to find a space for the new component
function createNewGridItem(grid: GridItem[], id: string, appComponentType: string): GridItem {
const appComponent = componentsRecord[appComponentType].data
appComponent.id = id
const newComponent = {
fixed: false,
resizable: true,
draggable: true,
customDragger: false,
customResizer: false,
x: 0,
y: 0
}
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
const newItem: GridItem = {
data: newData,
id: id
}
gridColumns.forEach((column) => {
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
newItem[column] = {
...newComponent,
min: { w: 1, h: 1 },
max: { w: column, h: 100 },
w: rec.w,
h: rec.h
}
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
newItem[column] = { ...newItem[column], ...position }
})
return newItem
}
function addComponent(appComponentType: AppComponent['type']): void {
// When a new component is added, we need to mark the app as dirty,
// so a confirmation modal will appear if the user tries to leave the page
$dirtyStore = true
const grid = $app.grid ?? []
const data = componentsRecord[appComponentType].data
const id = insertNewGridItem($app, data, $focusedGrid)
const gridItemIds = grid
.map((gridItem: GridItem) => {
const subGrids = gridItem.data.subGrids ?? []
return [
gridItem.data.id,
...subGrids.map((subGrid: GridItem[]) =>
subGrid.map((gridItem: GridItem) => gridItem.data.id)
)
]
})
.flat(2)
const id = getNextId(gridItemIds)
if ($focusedGrid) {
const { parentComponentId, subGridIndex } = $focusedGrid
const gridItemIndex = $app.grid.findIndex((gridItem) => gridItem.id === parentComponentId)
const subGrids = $app.grid[gridItemIndex].data.subGrids ?? []
const newItem = createNewGridItem(subGrids[subGridIndex] ?? [], id, appComponentType)
const subGrid = subGrids[subGridIndex] ?? []
$app.grid[gridItemIndex].data.subGrids[subGridIndex] = [...subGrid, newItem]
$selectedComponent = id
} else {
const newItem = createNewGridItem(grid, id, appComponentType)
$app.grid = [...grid, newItem]
$selectedComponent = id
}
$selectedComponent = id
}
onMount(() => {
isOpenStore.addItems(componentSets.map((set) => ({ [set.title]: true })))
isOpenStore.addItems(COMPONENT_SETS.map((set) => ({ [TITLE_PREFIX + set.title]: true })))
})
</script>
{#each componentSets as { title, components }, index (index)}
{@const isOpen = $isOpenStore[title]}
<section class="mt-1 mb-2 px-2">
<button
on:click|preventDefault={() => isOpenStore.toggle(title)}
class="w-full flex justify-between items-center text-gray-700 px-2 py-1
rounded-sm duration-200 hover:bg-gray-100"
>
<h1 class="text-sm font-semibold text-left">{title}</h1>
<Icon data={faAngleDown} class="rotate-0 duration-300 {isOpen ? '!rotate-180' : ''}" />
</button>
{#if isOpen}
<div transition:slide|local={{ duration: 300 }}>
{#if components.length}
<div class="flex flex-wrap gap-2 py-2">
{#each components as item}
<button
on:click={() => addComponent(item)}
title={componentsRecord[item].name}
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
>
<svelte:component this={componentsRecord[item].icon} />
<div class="text-xs w-full text-center ellipsize">
{componentsRecord[item].name}
</div>
</button>
{/each}
</div>
{:else}
<div class="text-xs text-gray-500 py-1 px-2">
There are no components in this group yet
</div>
{/if}
{#each COMPONENT_SETS as { title, components }, index (index)}
<ListItem {title} prefix={TITLE_PREFIX}>
{#if components.length}
<div class="flex flex-wrap gap-2 py-2">
{#each components as item}
<button
on:click={() => addComponent(item)}
title={componentsRecord[item].name}
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
>
<svelte:component this={componentsRecord[item].icon} />
<div class="text-xs w-full text-center ellipsize">
{componentsRecord[item].name}
</div>
</button>
{/each}
</div>
{:else}
<div class="text-xs text-gray-500 py-1 px-2"> There are no components in this group yet </div>
{/if}
</section>
</ListItem>
{/each}

View File

@@ -0,0 +1,34 @@
<script lang="ts">
import type { ComponentCssProperty } from "../../types"
export let name: string
export let value: ComponentCssProperty | undefined
</script>
<div class="text-sm font-semibold text-gray-500 capitalize pt-2">
{name}
</div>
{#if value}
<div class="border-l border-gray-400/80 py-1 pl-3.5 mt-1 ml-0.5">
<label class="block pb-2">
<div class="text-xs font-medium pb-0.5">
Style
</div>
<input
type="text"
bind:value={value.style}
on:focus
/>
</label>
<label class="block">
<div class="text-xs font-medium pb-0.5">
Tailwind classes
</div>
<input
type="text"
bind:value={value.class}
on:focus
/>
</label>
</div>
{/if}

View File

@@ -1,17 +1,23 @@
<script lang="ts">
import { onMount, getContext } from 'svelte'
import { LayoutDashboardIcon, MousePointer2, CurlyBraces } from 'lucide-svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { emptyString } from '$lib/utils'
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
import { LayoutDashboardIcon } from 'lucide-svelte'
import { getContext } from 'svelte'
import Icon from 'svelte-awesome'
import { slide } from 'svelte/transition'
import { Tab, TabContent, Tabs } from '../../../common'
import type { AppEditorContext } from '../../types'
import { components, type AppComponent } from '../Component.svelte'
import { isOpenStoreCss } from './store'
import ListItem from './ListItem.svelte'
import { isOpenStore } from './store'
import CssProperty from './CssProperty.svelte'
import { components, type AppComponent } from '../component'
interface CustomCSSEntry {
type: 'app' | AppComponent['type'];
name: string;
icon: any;
ids: string[];
}
const TITLE_PREFIX = 'Css.' as const
const { app } = getContext<AppEditorContext>('AppEditorContext')
let rawCode = ''
@@ -38,28 +44,23 @@
}
}
const entries: { type: 'app' | AppComponent['type']; name: string; icon: any; ids: string[] }[] =
[
{
type: 'app' as 'app' | AppComponent['type'],
name: 'App',
icon: LayoutDashboardIcon,
ids: ['viewer', 'grid', 'component']
}
].concat(
Object.entries(components).map((c) => ({
type: c[1].data.type as 'app' | AppComponent['type'],
name: c[1].name,
icon: c[1].icon,
ids: c[1].cssIds ?? []
}))
)
const entries: CustomCSSEntry[] = [
{
type: 'app',
name: 'App',
icon: LayoutDashboardIcon,
ids: ['viewer', 'grid', 'component']
},
...Object.values(components).map(({ name, icon, data: { type, customCss }}) => ({
type,
name,
icon,
ids: Object.keys(customCss ?? {}) ?? []
}))
]
let isCustom: Record<string, boolean> = Object.fromEntries(
Object.keys(entries).map((k) => [k, false])
)
if (Object.keys($isOpenStoreCss).length == 0) {
$isOpenStoreCss = Object.fromEntries(Object.keys(entries).map((k) => [k, false]))
}
let newCss = $app.css ?? {}
entries.forEach((e) => {
@@ -82,73 +83,78 @@
})
//@ts-ignore
$app.css = newCss
onMount(() => {
isOpenStore.addItems(
[{ name: 'App' }, ...Object.values(components)].map(component => {
return { [TITLE_PREFIX + component.name]: false }
})
)
})
</script>
<div class="flex flex-row-reverse px-2">
<Toggle
on:change={(e) => switchTab(e.detail)}
options={{
right: 'As JSON'
}}
/>
</div>
{#if !viewJsonSchema}
<div class="flex flex-col gap-2 p-1">
{#each entries as { type, name, icon, ids }}
{#if ids.length > 0}
<div>
<button
on:click|preventDefault={() => ($isOpenStoreCss[type] = !$isOpenStoreCss[type])}
class="w-full flex justify-between items-center px-1 py-1
rounded-sm duration-200 hover:bg-gray-100"
>
<h3 class="inline-flex gap-2 {isCustom[type] ? 'text-gray-800' : 'text-gray-500'}"
>{name} <svelte:component this={icon} />
</h3>
<Icon
data={faAngleDown}
class="rotate-0 duration-300 {$isOpenStoreCss[type] ? '!rotate-180' : ''}"
/>
</button>
{#if $isOpenStoreCss[type]}
<div transition:slide|local={{ duration: 300 }} class="flex flex-col px-2 border">
<Tabs
selected="ui"
on:selected={(e) => switchTab(e.detail === 'json')}
class="relative"
>
<Tab value="ui" size="xs" class="grow">
<div class="m-1 center-center">
<MousePointer2 size={16} />
<span class="pl-1">UI</span>
</div>
</Tab>
<Tab value="json" size="xs" class="grow">
<div class="m-1 center-center">
<CurlyBraces size={16} />
<span class="pl-1">JSON</span>
</div>
</Tab>
<div slot="content" class="h-full overflow-y-auto">
<TabContent value="ui">
{#each entries as { type, name, icon, ids }}
{#if ids.length > 0}
<ListItem title={name} prefix={TITLE_PREFIX}>
<div slot="title" class="flex items-center">
<svelte:component this={icon} size={18} />
<span class="ml-1">
{name}
</span>
</div>
<div class="pb-2">
{#each ids as id}
<div class="mb-2">
<div class="mt-1 font-semibold">{id}</div>
{#if $app?.css?.[type]?.[id]}
<span class="text-xs">Style</span>
<input
type="text"
<div class="mb-3">
{#if $app?.css?.[type][id]}
<CssProperty
name={id}
bind:value={$app.css[type][id]}
on:focus={() => (isCustom[type] = true)}
bind:value={$app.css[type][id].style}
/>
<span class="text-xs">Tailwind classes</span>
<input
type="text"
on:focus={() => (isCustom[type] = true)}
bind:value={$app.css[type][id].class}
/>
{/if}
</div>
{/each}
</div>
{/if}
</div>
</ListItem>
{/if}
{/each}
</TabContent>
<TabContent value="json">
{#if !emptyString(jsonError)}
<span class="text-red-400 text-xs mb-1 flex flex-row-reverse">
{jsonError}
</span>
{:else}
<div class="py-2" />
{/if}
{/each}
<div class="h-full w-full py-1">
<SimpleEditor
autoHeight
class="editor"
lang="json"
bind:code={rawCode}
fixedOverflowWidgets={false}
/>
</div>
</TabContent>
</div>
{:else}
{#if !emptyString(jsonError)}<span class="text-red-400 text-xs mb-1 flex flex-row-reverse"
>{jsonError}</span
>{:else}<div class="py-2" />{/if}
<div class="h-full w-full border p-1 rounded">
<SimpleEditor
autoHeight
class="editor"
lang="json"
bind:code={rawCode}
fixedOverflowWidgets={false}
/>
</div>
{/if}
</Tabs>

View File

@@ -0,0 +1,31 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import { ChevronDown } from 'lucide-svelte'
import { isOpenStore } from './store'
export let title: string
export let prefix: string = ''
$: storeTitle = prefix + title
$: isOpen = $isOpenStore[storeTitle]
</script>
<section class="mt-1 mb-2 px-1">
<button
on:click|preventDefault={() => isOpenStore.toggle(storeTitle)}
class="w-full flex justify-between items-center text-gray-700 px-2 py-1
rounded-sm duration-200 hover:bg-gray-100"
>
<h1 class="text-sm font-semibold text-left">
<slot name="title">
{title}
</slot>
</h1>
<ChevronDown class="rotate-0 duration-300 {isOpen ? '!rotate-180' : ''}" />
</button>
{#if isOpen}
<div transition:slide|local={{ duration: 300 }} class="px-2">
<slot />
</div>
{/if}
</section>

View File

@@ -19,5 +19,3 @@ export const isOpenStore = {
},
reset: () => store.set({})
}
export const isOpenStoreCss = writable<Record<string, boolean>>({})

View File

@@ -2,7 +2,8 @@
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import { components } from '../Component.svelte'
import { findGridItem } from '../appUtils'
import { components } from '../component'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
@@ -26,7 +27,7 @@
}
function getComponentNameById(componentId: string) {
const component = $app.grid.find((c) => c?.data?.id === componentId)
const component = findGridItem($app, componentId)
if (component?.data.type) {
return components[component?.data.type].name

View File

@@ -12,7 +12,7 @@
import { createEventDispatcher, getContext } from 'svelte'
import { fly } from 'svelte/transition'
import type { AppEditorContext } from '../../types'
import { defaultCode } from '../Component.svelte'
import { defaultCode } from '../component'
import InlineScriptList from '../settingsPanel/mainInput/InlineScriptList.svelte'
import WorkspaceScriptList from '../settingsPanel/mainInput/WorkspaceScriptList.svelte'
@@ -164,13 +164,13 @@
createInlineScriptByLanguage(Script.language.DENO, name, 'pgsql')
}}
/>
<FlowScriptPicker
<!-- <FlowScriptPicker
label={`MySQL`}
lang="mysql"
on:click={() => {
createInlineScriptByLanguage(Script.language.DENO, name, 'mysql')
}}
/>
/> -->
</div>
<div class="mt-4">

View File

@@ -8,7 +8,7 @@
import { emptySchema, getScriptByPath } from '$lib/utils'
import { faCodeBranch, faExternalLinkAlt, faEye, faPen } from '@fortawesome/free-solid-svg-icons'
import type { AppInput, RunnableByPath } from '../../inputType'
import { clearResultAppInput, schemaToInputsSpec } from '../../utils'
import { clearResultAppInput } from '../../utils'
import EmptyInlineScript from './EmptyInlineScript.svelte'
import InlineScriptEditor from './InlineScriptEditor.svelte'
import { computeFields } from './utils'

View File

@@ -4,11 +4,12 @@
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import InlineScriptsPanelList from './InlineScriptsPanelList.svelte'
import InlineScriptEditorPanel from './InlineScriptEditorPanel.svelte'
import InlineScriptEditor from './InlineScriptEditor.svelte'
import EmptyInlineScript from './EmptyInlineScript.svelte'
import { allItems } from '../../utils'
import InlineScriptEditorPanel from './InlineScriptEditorPanel.svelte'
const { lazyGrid, app, staticOutputs, runnableComponents } =
const { app, staticOutputs, runnableComponents } =
getContext<AppEditorContext>('AppEditorContext')
let selectedScriptComponentId: string | undefined = undefined
@@ -23,16 +24,17 @@
{#if !selectedScriptComponentId}
<span class="p-2 text-sm text-gray-600">Select a script on the left panel</span>
{/if}
{#each $lazyGrid as gridComponent, index (index)}
{#if gridComponent.data.id === selectedScriptComponentId}
{#each allItems($app.grid, $app.subgrids) as gridItem (gridItem.data.id)}
{#if gridItem.data.id === selectedScriptComponentId}
<InlineScriptEditorPanel
id={gridComponent.data.id}
bind:componentInput={gridComponent.data.componentInput}
id={gridItem.data.id}
bind:componentInput={gridItem.data.componentInput}
/>
{/if}
{#if gridComponent.data.type === 'tablecomponent'}
{#each gridComponent.data.actionButtons as actionButton, index (index)}
{#if gridItem.data.type === 'tablecomponent'}
{#each gridItem.data.actionButtons as actionButton, index (index)}
{#if actionButton.id === selectedScriptComponentId}
<InlineScriptEditorPanel
id={actionButton.id}

View File

@@ -2,7 +2,7 @@ import type { Schema } from "$lib/common";
import type { AppInputs, Runnable } from "../../inputType"
import type { GridItem } from "../../types"
import { fieldTypeToTsType, schemaToInputsSpec } from "../../utils";
import type { AppComponent } from "../Component.svelte";
import type { AppComponent } from "../component";
export interface AppScriptsList {
inline: { name: string; id: string }[],

View File

@@ -8,7 +8,7 @@
AlignStartHorizontal,
AlignStartVertical
} from 'lucide-svelte'
import type { AppComponent } from '../Component.svelte'
import type { AppComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
export let component: AppComponent

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import type { StaticAppInput } from '../../inputType'
import { staticValues } from '../componentsPanel/componentStaticValues'
import SubTypeEditor from './SubTypeEditor.svelte'
@@ -8,6 +9,7 @@
type ArrayComponentInput = Extract<StaticAppInput, { fieldType: 'array' }>
export let componentInput: ArrayComponentInput
const dispatch = createEventDispatcher()
function addElementByType() {
if (componentInput.subFieldType && componentInput.value) {
@@ -36,6 +38,9 @@
function deleteElementByType(index: number) {
if (componentInput.value) {
componentInput.value.splice(index, 1)
dispatch('deleteArrayItem', { index })
componentInput = componentInput
}
}
@@ -47,7 +52,7 @@
<div class="flex flex-row gap-2 items-center relative">
<SubTypeEditor bind:componentInput bind:value />
<div class="absolute top-4 right-4">
<div class="absolute top-1 right-1">
<Button
size="xs"
color="light"

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import Button from '$lib/components/common/button/Button.svelte'
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext } from '../../types'
import type { AppEditorContext, GridItem } from '../../types'
import PanelSection from './common/PanelSection.svelte'
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
import TableActions from './TableActions.svelte'
@@ -17,57 +17,46 @@
import AlignmentEditor from './AlignmentEditor.svelte'
import RunnableInputEditor from './inputEditor/RunnableInputEditor.svelte'
import TemplateEditor from '$lib/components/TemplateEditor.svelte'
import type { AppComponent } from '../Component.svelte'
import type { AppComponent } from '../component'
import CssProperty from '../componentsPanel/CssProperty.svelte'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import GridTab from './GridTab.svelte'
import { duplicateGridItem } from '../appUtils'
import { deleteGridItem } from '../appUtils'
export let component: AppComponent | undefined
export let onDelete: (() => void) | undefined = undefined
export let component: AppComponent
export let rowColumns = false
export let onDelete: (() => void) | undefined = undefined
export let parent: string | undefined
export let noGrid = false
const { app, staticOutputs, runnableComponents, selectedComponent, worldStore } =
const { app, staticOutputs, runnableComponents, selectedComponent, worldStore, focusedGrid } =
getContext<AppEditorContext>('AppEditorContext')
function duplicateElement(id: string) {
$dirtyStore = true
const newId = duplicateGridItem($app, $focusedGrid, id)
$selectedComponent = newId
}
function removeGridElement() {
$selectedComponent = undefined
if (onDelete && component) {
delete $staticOutputs[component.id]
$staticOutputs = $staticOutputs
delete $runnableComponents[component.id]
$runnableComponents = $runnableComponents
onDelete()
// Delete static inputs
} else {
if (component) {
$app.grid = $app.grid.filter((gridComponent) => gridComponent.data.id !== component?.id)
// Delete static inputs
delete $staticOutputs[component.id]
$staticOutputs = $staticOutputs
delete $runnableComponents[component.id]
$runnableComponents = $runnableComponents
$focusedGrid = undefined
if (component && !noGrid) {
let ids = deleteGridItem($app, component, parent)
for (const key in ids) {
delete $staticOutputs[key]
delete $runnableComponents[key]
}
}
if (
component &&
component.componentInput?.type === 'runnable' &&
component.componentInput?.runnable?.type === 'runnableByName'
) {
const { name, inlineScript } = component.componentInput.runnable
delete $staticOutputs[component.id]
delete $runnableComponents[component.id]
$app = $app
$staticOutputs = $staticOutputs
$runnableComponents = $runnableComponents
if (inlineScript) {
if (!$app.unusedInlineScripts) {
$app.unusedInlineScripts = []
}
$app.unusedInlineScripts.push({
name,
inlineScript
})
}
}
onDelete?.()
}
$: extraLib =
@@ -159,6 +148,10 @@
</PanelSection>
{/if}
{#if component.type === 'tabscomponent'}
<GridTab bind:tabs={component.tabs} {component} />
{/if}
{#if component.type === 'tablecomponent' && Array.isArray(component.actionButtons)}
<TableActions id={component.id} bind:components={component.actionButtons} />
{/if}
@@ -170,18 +163,32 @@
{#if Object.keys(component.customCss ?? {}).length > 0}
<PanelSection title="Custom CSS">
{#each Object.entries(component.customCss ?? {}) as [key, value]}
<div class="mb-2">
<div class="mt-1 font-semibold">{key}</div>
<span class="text-xs">Style</span>
<input type="text" bind:value={value.style} />
<span class="text-xs">Tailwind classes</span>
<input type="text" bind:value={value.class} />
</div>
{#each Object.keys(component.customCss ?? {}) as name}
{#if component?.customCss?.[name]}
<div class="mb-2">
<CssProperty {name} bind:value={component.customCss[name]} />
</div>
{/if}
{/each}
</PanelSection>
{/if}
<PanelSection title="Duplicate">
<Button
size="xs"
color="blue"
variant="border"
startIcon={{ icon: faCopy }}
on:click={() => {
if (component) {
duplicateElement(component.id)
}
}}
>
Duplicate component: {component.id}
</Button>
</PanelSection>
<PanelSection title="Danger zone">
<Button
size="xs"

View File

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

View File

@@ -7,7 +7,7 @@
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
import type { AppEditorContext, BaseAppComponent } from '../../types'
import type { ButtonComponent } from '../Component.svelte'
import type { ButtonComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
import TableActionLabel from './TableActionLabel.svelte'
@@ -95,17 +95,28 @@
}}
on:keypress
>
<Badge color="dark-indigo">
{component.id}
</Badge>
<div>
<TableActionLabel componentInput={component.configuration.label} />
</div>
<div>
<Button variant="border" color="red" on:click={() => deleteComponent(component.id)}>
<Icon class="h-3" data={faTrashAlt} />
</Button>
</div>
<div>
<TableActionLabel componentInput={component.configuration.label} />
</div>
<Badge color="dark-indigo">
{component.id}
</Badge>
</div>
{/each}
<div class="w-full">
<Button
btnClasses="w-full"
color="light"
variant="border"
startIcon={{ icon: faPlus }}
on:click={addComponent}
iconOnly
/>
</div>
</PanelSection>

View File

@@ -59,7 +59,7 @@
</div>
{/if}
{:else if componentInput.fieldType === 'array'}
<ArrayStaticInputEditor bind:componentInput />
<ArrayStaticInputEditor bind:componentInput on:deleteArrayItem />
{:else}
<input type="text" placeholder="Static value" bind:value={componentInput.value} />
{/if}

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