Compare commits
40 Commits
v1.67.4
...
rf/flatten
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b1bfed090 | ||
|
|
2125d119ff | ||
|
|
d3f1ef92ea | ||
|
|
26296ee05c | ||
|
|
7e4179a741 | ||
|
|
52adaa4cb5 | ||
|
|
a94f266743 | ||
|
|
9d680e6744 | ||
|
|
5060c37fed | ||
|
|
15c2bdcdae | ||
|
|
45ce630a75 | ||
|
|
d50734cbb3 | ||
|
|
b08b559eec | ||
|
|
5742bf2235 | ||
|
|
c56176ec91 | ||
|
|
dd7e8c742c | ||
|
|
640242b713 | ||
|
|
2f78132e08 | ||
|
|
c696f3b6cf | ||
|
|
0041411c06 | ||
|
|
54955b710c | ||
|
|
1eb5a0d1d3 | ||
|
|
ddda14c52b | ||
|
|
5123c9365c | ||
|
|
834e7b1d1c | ||
|
|
6e9a5b026e | ||
|
|
7ad8879b09 | ||
|
|
27cac3ffe6 | ||
|
|
705703a5e2 | ||
|
|
fac31c6628 | ||
|
|
90c0e140a1 | ||
|
|
d543650b31 | ||
|
|
089a6b6ae5 | ||
|
|
857ee5f318 | ||
|
|
6ad876ebb4 | ||
|
|
ab4137640e | ||
|
|
2bd8fabcf7 | ||
|
|
3b7160e84a | ||
|
|
40c12e6139 | ||
|
|
6044e3b6ef |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,6 +1,33 @@
|
||||
# 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)
|
||||
|
||||
|
||||
|
||||
34
backend/Cargo.lock
generated
34
backend/Cargo.lock
generated
@@ -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#4a579b5752355de4e9efc63f9c1351c12ae90556"
|
||||
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#4a579b5752355de4e9efc63f9c1351c12ae90556"
|
||||
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#4a579b5752355de4e9efc63f9c1351c12ae90556"
|
||||
source = "git+https://github.com/RustPython/RustPython#c4b67896662b16b5699a779c0e52aa0ca2587fec"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"anyhow",
|
||||
@@ -4766,7 +4766,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windmill"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4793,7 +4793,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
@@ -4848,7 +4848,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-api-client"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"base64 0.21.0",
|
||||
"chrono",
|
||||
@@ -4863,7 +4863,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-audit"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
@@ -4876,7 +4876,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-common"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -4901,7 +4901,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4909,7 +4909,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-bash"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4923,7 +4923,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-go"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4935,7 +4935,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-py"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools",
|
||||
@@ -4950,7 +4950,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-parser-ts"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_core",
|
||||
@@ -4964,7 +4964,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-queue"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@@ -4987,7 +4987,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windmill-worker"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "windmill"
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
@@ -19,7 +19,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "1.67.4"
|
||||
version = "1.69.0"
|
||||
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
openapi: "3.0.3"
|
||||
|
||||
info:
|
||||
version: 1.67.4
|
||||
version: 1.69.0
|
||||
title: Windmill API
|
||||
|
||||
contact:
|
||||
|
||||
@@ -142,7 +142,7 @@ async function push(opts: Options, filePath: string, remotePath: string) {
|
||||
await requireLogin(opts);
|
||||
|
||||
await pushFlow(filePath, workspace.remote, remotePath);
|
||||
console.log(colors.bold.underline.green("Flow successfully pushed"));
|
||||
console.log(colors.bold.underline.green("Flow pushed"));
|
||||
}
|
||||
|
||||
export async function pushFlow(
|
||||
|
||||
@@ -133,7 +133,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushFolder(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
console.log(colors.bold.underline.green("Resource pushed"));
|
||||
}
|
||||
|
||||
export async function pushFolder(
|
||||
|
||||
@@ -13,7 +13,7 @@ import sync from "./sync.ts";
|
||||
import { tryResolveVersion } from "./context.ts";
|
||||
import { GlobalOptions } from "./types.ts";
|
||||
|
||||
const VERSION = "v1.67.4";
|
||||
const VERSION = "v1.69.0";
|
||||
|
||||
let command: any = new Command()
|
||||
.name("wmill")
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function downloadZip(
|
||||
|
||||
const zipResponse = await fetch(
|
||||
workspace.remote + "api/w/" + workspace.workspaceId +
|
||||
"/workspaces/tarball?archive_type=zip",
|
||||
"/workspaces/tarball?archive_type=zip",
|
||||
{
|
||||
headers: requestHeaders,
|
||||
method: "GET",
|
||||
@@ -25,8 +25,7 @@ export async function downloadZip(
|
||||
"Failed to request tarball from API " + zipResponse.statusText,
|
||||
),
|
||||
);
|
||||
console.log(await zipResponse.text());
|
||||
return undefined;
|
||||
throw new Error(await zipResponse.text());
|
||||
}
|
||||
const blob = await zipResponse.blob();
|
||||
return await JSZip.loadAsync(blob);
|
||||
|
||||
@@ -137,7 +137,7 @@ async function push(opts: PushOptions, filePath: string, name: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushResourceType(workspace.workspaceId, filePath, name);
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
console.log(colors.bold.underline.green("Resource pushed"));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
|
||||
@@ -155,7 +155,7 @@ async function push(opts: PushOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing resource..."));
|
||||
|
||||
await pushResource(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green("Resource successfully pushed"));
|
||||
console.log(colors.bold.underline.green(`Resource ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
async function list(opts: GlobalOptions) {
|
||||
|
||||
@@ -76,7 +76,7 @@ async function push(
|
||||
|
||||
await requireLogin(opts);
|
||||
await pushScript(filePath, contentPath, workspace.workspaceId, remotePath);
|
||||
console.log(colors.bold.underline.green("Script successfully pushed"));
|
||||
console.log(colors.bold.underline.green(`Script ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
export async function handleScriptMetadata(path: string, workspace: string, alreadySynced: string[]): Promise<boolean> {
|
||||
|
||||
12
cli/sync.ts
12
cli/sync.ts
@@ -212,7 +212,7 @@ async function compareDynFSElement(
|
||||
}
|
||||
|
||||
const isNotWmillFile = (p: string, isDirectory: boolean) => {
|
||||
if (p == "./" || p == "" || p == "u" || p == "f" || p == "g" || p.endsWith("/")) {
|
||||
if (p.endsWith("/")) {
|
||||
return false
|
||||
}
|
||||
if (isDirectory) {
|
||||
@@ -231,6 +231,9 @@ const isNotWmillFile = (p: string, isDirectory: boolean) => {
|
||||
}
|
||||
}
|
||||
|
||||
const isWhitelisted = (p: string) => {
|
||||
return p == "./" || p == "" || p == "u" || p == "f" || p == "g"
|
||||
}
|
||||
async function ignoreF() {
|
||||
try {
|
||||
const ignore: {
|
||||
@@ -239,9 +242,12 @@ async function ignoreF() {
|
||||
} = gitignore_parser.compile(
|
||||
await Deno.readTextFile(".wmillignore"),
|
||||
);
|
||||
return (p: string, isDirectory: boolean) => isNotWmillFile(p, isDirectory) || ignore.denies(p);
|
||||
|
||||
return (p: string, isDirectory: boolean) => {
|
||||
return !isWhitelisted(p) && (isNotWmillFile(p, isDirectory) || ignore.denies(p));
|
||||
}
|
||||
} catch (e) {
|
||||
return isNotWmillFile
|
||||
return (p: string, isDirectory: boolean) => !isWhitelisted(p) && isNotWmillFile(p, isDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ async function push(opts: GlobalOptions, filePath: string, remotePath: string) {
|
||||
console.log(colors.bold.yellow("Pushing variable..."));
|
||||
|
||||
await pushVariable(workspace.workspaceId, filePath, remotePath);
|
||||
console.log(colors.bold.underline.green("Variable successfully pushed"));
|
||||
console.log(colors.bold.underline.green(`Variable ${remotePath} pushed`));
|
||||
}
|
||||
|
||||
export async function pushVariable(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.67.4",
|
||||
"version": "1.69.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill",
|
||||
"version": "1.67.4",
|
||||
"version": "1.69.0",
|
||||
"dependencies": {
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "windmill",
|
||||
"version": "1.67.4",
|
||||
"version": "1.69.0",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import GroupEditor from './GroupEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -69,15 +70,23 @@
|
||||
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
meta = { ownerKind: 'user', name: namePlaceholder, owner: '' }
|
||||
meta = {
|
||||
ownerKind: 'user',
|
||||
name: random_adj() + '_' + namePlaceholder,
|
||||
owner: ''
|
||||
}
|
||||
|
||||
meta.owner = $userStore!.username.split('@')[0]
|
||||
|
||||
let i = 1
|
||||
while (await pathExists(metaToPath(meta), kind)) {
|
||||
meta.name = `${namePlaceholder}_${i}`
|
||||
i += 1
|
||||
let newMeta = { ...meta }
|
||||
while (await pathExists(metaToPath(newMeta), kind)) {
|
||||
disabled = true
|
||||
error = 'finding an available name...'
|
||||
newMeta.name = random_adj() + '_' + namePlaceholder
|
||||
}
|
||||
error = ''
|
||||
disabled = false
|
||||
meta = newMeta
|
||||
path = metaToPath(meta)
|
||||
} else {
|
||||
meta = pathToMeta(path)
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
disabled={!can_write}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="my_resource"
|
||||
namePlaceholder="resource"
|
||||
kind="resource"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder={'my_schedule'}
|
||||
namePlaceholder="schedule"
|
||||
kind="schedule"
|
||||
/>
|
||||
<div class="mb-8" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path
|
||||
{initialPath}
|
||||
namePlaceholder="my_variable"
|
||||
namePlaceholder="variable"
|
||||
kind="variable"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import type { AppEditorContext, GridItem } from '../types'
|
||||
import InputValue from './helpers/InputValue.svelte'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let subGrids: GridItem[][] | undefined = undefined
|
||||
export let componentContainerHeight: number
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
const { focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let result: string[] | undefined = undefined
|
||||
let gridContent: string[] | undefined = undefined
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedComponent === id && onFocus()
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
{#if subGrids && subGrids[0]}
|
||||
<SubGridEditor
|
||||
bind:subGrid={subGrids[0]}
|
||||
containerHeight={componentContainerHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</RunnableWrapper>
|
||||
@@ -1,25 +0,0 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY } from '../types'
|
||||
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<div class="w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm">
|
||||
Results
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<DisplayResult {result} {requireHtmlApproval} />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -1,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()
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as AppButton } from './AppButton.svelte'
|
||||
export { default as AppForm } from './AppForm.svelte'
|
||||
export { default as AppFormButton } from './AppFormButton.svelte'
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import { IS_APP_PUBLIC_CONTEXT_KEY, type AppEditorContext, type ComponentCustomCSS } from '../../types'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'header' | 'container'> | undefined = undefined
|
||||
|
||||
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
|
||||
<div class={twMerge(
|
||||
'w-full border-b px-2 text-xs p-1 font-semibold bg-gray-500 text-white rounded-t-sm',
|
||||
$app.css?.['displaycomponent']?.['header']?.class,
|
||||
customCss?.header?.class
|
||||
)}>
|
||||
Results
|
||||
</div>
|
||||
<div class={twMerge(
|
||||
'p-2',
|
||||
$app.css?.['displaycomponent']?.['container']?.class,
|
||||
customCss?.container?.class
|
||||
)}>
|
||||
<DisplayResult {result} {requireHtmlApproval} />
|
||||
</div>
|
||||
</RunnableWrapper>
|
||||
@@ -8,7 +8,7 @@
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext, ComponentCustomCSS } from '../../types'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
export let id: string
|
||||
@@ -17,7 +17,7 @@
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let initializing: boolean | undefined = undefined
|
||||
export let customCss: Record<'text', { class: string; style: string }> | undefined = undefined
|
||||
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
|
||||
12
frontend/src/lib/components/apps/components/display/index.ts
Normal file
12
frontend/src/lib/components/apps/components/display/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export { default as AppTable } from './table/AppTable.svelte'
|
||||
export { default as AppBarChart } from './AppBarChart.svelte'
|
||||
export { default as AppDisplayComponent } from './AppDisplayComponent.svelte'
|
||||
export { default as AppHtml } from './AppHtml.svelte'
|
||||
export { default as AppIcon } from './AppIcon.svelte'
|
||||
export { default as AppImage } from './AppImage.svelte'
|
||||
export { default as AppPieChart } from './AppPieChart.svelte'
|
||||
export { default as AppScatterChart } from './AppScatterChart.svelte'
|
||||
export { default as AppText } from './AppText.svelte'
|
||||
export { default as AppTimeseries } from './AppTimeseries.svelte'
|
||||
export { default as PlotlyHtml } from './PlotlyHtml.svelte'
|
||||
export { default as VegaLiteHtml } from './VegaLiteHtml.svelte'
|
||||
@@ -1,19 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext, BaseAppComponent } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
|
||||
import type { Output } from '../../../rx'
|
||||
import type { AppEditorContext, BaseAppComponent } from '../../../types'
|
||||
import InputValue from '../../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { createSvelteTable, flexRender, type TableOptions } from '@tanstack/svelte-table'
|
||||
import AppButton from '../buttons/AppButton.svelte'
|
||||
import AppButton from '../../buttons/AppButton.svelte'
|
||||
import { classNames, isObject } from '$lib/utils'
|
||||
import DebouncedInput from '../helpers/DebouncedInput.svelte'
|
||||
import DebouncedInput from '../../helpers/DebouncedInput.svelte'
|
||||
import AppTableFooter from './AppTableFooter.svelte'
|
||||
import { tableOptions } from './tableOptions'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import type { ButtonComponent } from '../../editor/Component.svelte'
|
||||
import type { ButtonComponent } from '../../../editor/component'
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { InlineScript } from '../types'
|
||||
import RunnableComponent from './helpers/RunnableComponent.svelte'
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../../inputType'
|
||||
import type { InlineScript } from '../../types'
|
||||
import RunnableComponent from './RunnableComponent.svelte'
|
||||
|
||||
export let id: string
|
||||
export let name: string
|
||||
@@ -1,5 +1,6 @@
|
||||
export { default as AlignWrapper } from './AlignWrapper.svelte'
|
||||
export { default as DebouncedInput } from './DebouncedInput.svelte'
|
||||
export { default as HiddenComponent } from './HiddenComponent.svelte'
|
||||
export { default as InputDefaultValue } from './InputDefaultValue.svelte'
|
||||
export { default as InputValue } from './InputValue.svelte'
|
||||
export { default as MissingConnectionWarning } from './MissingConnectionWarning.svelte'
|
||||
|
||||
6
frontend/src/lib/components/apps/components/index.ts
Normal file
6
frontend/src/lib/components/apps/components/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './buttons'
|
||||
export * from './display'
|
||||
export * from './inputs'
|
||||
export * from './layout'
|
||||
|
||||
export * from './helpers'
|
||||
@@ -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>
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
import type { Output } from '../../../rx'
|
||||
import type { AppEditorContext } from '../../../types'
|
||||
import AlignWrapper from '../../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../../helpers/InputValue.svelte'
|
||||
import CurrencyInput from './CurrencyInput.svelte'
|
||||
|
||||
export let id: string
|
||||
@@ -0,0 +1,9 @@
|
||||
export { default as AppCheckbox } from './AppCheckbox.svelte'
|
||||
export { default as AppCurrencyInput } from './currency/AppCurrencyInput.svelte'
|
||||
export { default as AppDateInput } from './AppDateInput.svelte'
|
||||
export { default as AppFileInput } from './AppFileInput.svelte'
|
||||
export { default as AppNumberInput } from './AppNumberInput.svelte'
|
||||
export { default as AppRangeInput } from './AppRangeInput.svelte'
|
||||
export { default as AppSelect } from './AppSelect.svelte'
|
||||
export { default as AppSliderInputs } from './AppSliderInputs.svelte'
|
||||
export { default as AppTextInput } from './AppTextInput.svelte'
|
||||
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import SubGridEditor from '../../editor/SubGridEditor.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext, GridItem } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let componentContainerHeight: number
|
||||
|
||||
let noPadding: boolean | undefined = undefined
|
||||
|
||||
export const staticOutputs: string[] = []
|
||||
const { app, focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let gridContent: string[] | undefined = undefined
|
||||
|
||||
function onFocus() {
|
||||
$focusedGrid = {
|
||||
parentComponentId: id,
|
||||
subGridIndex: 0
|
||||
}
|
||||
}
|
||||
|
||||
$: $selectedComponent === id && onFocus()
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
|
||||
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
|
||||
|
||||
{#if $app.subgrids?.[`${id}-0`]}
|
||||
<SubGridEditor
|
||||
{noPadding}
|
||||
bind:subGrid={$app.subgrids[`${id}-0`]}
|
||||
containerHeight={componentContainerHeight}
|
||||
on:focus={() => {
|
||||
$selectedComponent = id
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { AppInput } from '../inputType'
|
||||
import type { HorizontalAlignment, VerticalAlignment } from '../types'
|
||||
import AlignWrapper from './helpers/AlignWrapper.svelte'
|
||||
import InputValue from './helpers/InputValue.svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let configuration: Record<string, AppInput>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,3 @@
|
||||
export { default as AppContainer } from './AppContainer.svelte'
|
||||
export { default as AppDivider } from './AppDivider.svelte'
|
||||
export { default as AppTabs } from './AppTabs.svelte'
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { components, type AppComponent } from './Component.svelte'
|
||||
import { components, type AppComponent } from './component'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
|
||||
import type { AppComponent } from './Component.svelte'
|
||||
import type { AppComponent } from './component'
|
||||
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
|
||||
|
||||
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
|
||||
@@ -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 for it
|
||||
</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="insert">
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
import { toStatic } from '../utils'
|
||||
import AppExportButton from './AppExportButton.svelte'
|
||||
import AppInputs from './AppInputs.svelte'
|
||||
import type { AppComponent } from './Component.svelte'
|
||||
import type { AppComponent } from './component/components'
|
||||
import PanelSection from './settingsPanel/common/PanelSection.svelte'
|
||||
|
||||
async function hash(message) {
|
||||
@@ -241,7 +241,7 @@
|
||||
bind:error={pathError}
|
||||
bind:path={newPath}
|
||||
initialPath=""
|
||||
namePlaceholder="my_app"
|
||||
namePlaceholder="app"
|
||||
kind="app"
|
||||
/>
|
||||
|
||||
@@ -467,8 +467,8 @@
|
||||
<div>{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}</div>
|
||||
<button
|
||||
on:click={() => {
|
||||
$focusedGrid = undefined
|
||||
$selectedComponent = undefined
|
||||
$focusedGrid = undefined
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,8 +5,7 @@
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
import { Alert, Button } from '$lib/components/common'
|
||||
import type { AppComponent } from './Component.svelte'
|
||||
import type { Output } from '../rx'
|
||||
import type { AppComponent } from './component'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
import type { Policy } from '$lib/gen'
|
||||
import HiddenComponent from '../components/HiddenComponent.svelte'
|
||||
import Component from './Component.svelte'
|
||||
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
|
||||
import Component from './component/Component.svelte'
|
||||
|
||||
export let policy: Policy
|
||||
|
||||
@@ -37,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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`}>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
182
frontend/src/lib/components/apps/editor/appUtils.ts
Normal file
182
frontend/src/lib/components/apps/editor/appUtils.ts
Normal 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
|
||||
}
|
||||
@@ -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}
|
||||
15
frontend/src/lib/components/apps/editor/component/README.md
Normal file
15
frontend/src/lib/components/apps/editor/component/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# How to add new components
|
||||
|
||||
1. [**`components.ts`**](./components.ts):
|
||||
1. Create a type for the new component and add it to the `AppComponent` union
|
||||
type
|
||||
1. Add the component to the `components` record
|
||||
1. [**`sets.ts`**](./sets.ts):
|
||||
1. Add the component to one of the component sets: `layout`, `buttons`,
|
||||
`inputs` or `display` _(this controls which group the component will be
|
||||
placed in in the **Insert** menu)_
|
||||
1. [**`Component.svelte`**](./Component.svelte):
|
||||
1. Add the new component in the Svelte `if` statement
|
||||
1. [**`default-codes.ts`**](./default-codes.ts):
|
||||
1. _(optional)_ Add the default code associated with the new component to the
|
||||
`DEFAULT_CODES` record
|
||||
1203
frontend/src/lib/components/apps/editor/component/components.ts
Normal file
1203
frontend/src/lib/components/apps/editor/component/components.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,325 @@
|
||||
import type { AppComponent } from "."
|
||||
|
||||
export function defaultCode(component: string, language: string): string | undefined {
|
||||
return DEFAULT_CODES[component]?.[language]
|
||||
}
|
||||
|
||||
export const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'python3', string>>> = {
|
||||
tablecomponent: {
|
||||
deno: `export async function main() {
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "A cell with a long name",
|
||||
"age": 42
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "A briefer cell",
|
||||
"age": 84
|
||||
}
|
||||
]
|
||||
}`,
|
||||
python3: `def main():
|
||||
return [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "A cell with a long name",
|
||||
"age": 42
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "A briefer cell",
|
||||
"age": 84
|
||||
}
|
||||
]`
|
||||
},
|
||||
textcomponent: {
|
||||
deno: `export async function main() {
|
||||
return "foo"
|
||||
}`,
|
||||
python3: `def main():
|
||||
return "foo"`
|
||||
},
|
||||
barchartcomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Bar",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Bar",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}`
|
||||
},
|
||||
displaycomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
"foo": 42
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"foo": 42
|
||||
}`
|
||||
},
|
||||
htmlcomponent: {
|
||||
deno: `export async function main() {
|
||||
return \`<img
|
||||
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&h=1280&q=80"
|
||||
>
|
||||
<h1 class="absolute top-4 left-2 text-white">
|
||||
Hello world
|
||||
</h1>\`
|
||||
}`,
|
||||
python3: `def main():
|
||||
return '''<img
|
||||
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1024&h=1280&q=80"
|
||||
>
|
||||
<h1 class="absolute top-4 left-2 text-white">
|
||||
Hello world
|
||||
</h1>'''`
|
||||
},
|
||||
vegalitecomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
data: {
|
||||
values: [
|
||||
{ a: "A", b: 28 },
|
||||
{ a: "B", b: 55 },
|
||||
{ a: "C", b: 43 },
|
||||
{ a: "D", b: 91 },
|
||||
],
|
||||
},
|
||||
mark: "bar",
|
||||
encoding: {
|
||||
x: { field: "a", type: "ordinal" },
|
||||
y: { field: "b", type: "quantitative" },
|
||||
},
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"data": {
|
||||
"values": [
|
||||
{ "a": "A", "b": 28 },
|
||||
{ "a": "B", "b": 55 },
|
||||
{ "a": "C", "b": 43 },
|
||||
{ "a": "D", "b": 91 },
|
||||
],
|
||||
},
|
||||
"mark": "bar",
|
||||
"encoding": {
|
||||
"x": { "field": "a", "type": "ordinal" },
|
||||
"y": { "field": "b", "type": "quantitative" },
|
||||
},
|
||||
}`
|
||||
},
|
||||
plotlycomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
type: 'bar',
|
||||
x: [1, 2, 3, 4],
|
||||
y: [5, 10, 2, 8],
|
||||
marker: {
|
||||
color: '#C8A2C8',
|
||||
line: {
|
||||
width: 2.5
|
||||
}
|
||||
}
|
||||
};
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"type": "bar",
|
||||
"x": [1, 2, 3, 4],
|
||||
"y": [5, 10, 2, 8],
|
||||
"marker": {
|
||||
"color": "#C8A2C8",
|
||||
"line": {
|
||||
"width": 2.5
|
||||
}
|
||||
}
|
||||
}`
|
||||
},
|
||||
piechartcomponent: {
|
||||
deno: `export async function main() {
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Pie",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}
|
||||
}`,
|
||||
python3: `def main():
|
||||
return {
|
||||
"data": [
|
||||
25,
|
||||
50,
|
||||
25
|
||||
],
|
||||
"labels": [
|
||||
"Pie",
|
||||
"Charts",
|
||||
"<3"
|
||||
]
|
||||
}`
|
||||
},
|
||||
scatterchartcomponent: {
|
||||
deno: `export async function main() {
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{ "x": 25, "y": 50 },
|
||||
{ "x": 23, "y": 23 },
|
||||
{ "x": 12, "y": 37 }
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{ "x": 32, "y": 32 },
|
||||
{ "x": 25, "y": 42 },
|
||||
{ "x": 3, "y": 27 }
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
python3: `def main():
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{ "x": 25, "y": 50 },
|
||||
{ "x": 23, "y": 23 },
|
||||
{ "x": 12, "y": 37 }
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{ "x": 32, "y": 32 },
|
||||
{ "x": 25, "y": 42 },
|
||||
{ "x": 3, "y": 27 }
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]`
|
||||
},
|
||||
timeseriescomponent: {
|
||||
deno: `export async function main() {
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 50
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 60
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 20
|
||||
}
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 20
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 13
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 45
|
||||
}
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]
|
||||
}`,
|
||||
python3: `def main():
|
||||
return [
|
||||
{
|
||||
"label": "foo",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 50
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 60
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 20
|
||||
}
|
||||
],
|
||||
"backgroundColor": "rgb(255, 12, 137)"
|
||||
},
|
||||
{
|
||||
"label": "bar",
|
||||
"data": [
|
||||
{
|
||||
"x": "2021-11-06 23:39:30",
|
||||
"y": 20
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 01:00:28",
|
||||
"y": 13
|
||||
},
|
||||
{
|
||||
"x": "2021-11-07 09:00:28",
|
||||
"y": 45
|
||||
}
|
||||
],
|
||||
"backgroundColor": "orange"
|
||||
}
|
||||
]`
|
||||
},
|
||||
iconcomponent: {
|
||||
deno: `export async function main() {
|
||||
return "smile";
|
||||
}`,
|
||||
python3: `def main():
|
||||
return "smile"`
|
||||
}
|
||||
} as const
|
||||
@@ -0,0 +1,5 @@
|
||||
export { default as Component } from './Component.svelte'
|
||||
|
||||
export * from './components'
|
||||
export * from './default-codes'
|
||||
export * from './sets'
|
||||
52
frontend/src/lib/components/apps/editor/component/sets.ts
Normal file
52
frontend/src/lib/components/apps/editor/component/sets.ts
Normal 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
|
||||
@@ -1,142 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { slide } from 'svelte/transition'
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { AppEditorContext, GridItem } from '../../types'
|
||||
|
||||
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
|
||||
import { isOpenStore } from './store'
|
||||
import { gridColumns } from '../../gridUtils'
|
||||
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
|
||||
import {
|
||||
components as componentsRecord,
|
||||
componentSets,
|
||||
getRecommendedDimensionsByComponent,
|
||||
type AppComponent
|
||||
} from '../Component.svelte'
|
||||
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
|
||||
import ListItem from './ListItem.svelte'
|
||||
import { insertNewGridItem } from '../appUtils'
|
||||
|
||||
const TITLE_PREFIX = 'Component.' as const
|
||||
const { app, selectedComponent, focusedGrid } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
// The grid is needed to find a space for the new component
|
||||
function createNewGridItem(grid: GridItem[], id: string, appComponentType: string): GridItem {
|
||||
const appComponent = componentsRecord[appComponentType].data
|
||||
|
||||
appComponent.id = id
|
||||
|
||||
const newComponent = {
|
||||
fixed: false,
|
||||
resizable: true,
|
||||
draggable: true,
|
||||
customDragger: false,
|
||||
customResizer: false,
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
|
||||
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
|
||||
|
||||
const newItem: GridItem = {
|
||||
data: newData,
|
||||
id: id
|
||||
}
|
||||
|
||||
gridColumns.forEach((column) => {
|
||||
const rec = getRecommendedDimensionsByComponent(appComponent.type, column)
|
||||
|
||||
newItem[column] = {
|
||||
...newComponent,
|
||||
min: { w: 1, h: 1 },
|
||||
max: { w: column, h: 100 },
|
||||
w: rec.w,
|
||||
h: rec.h
|
||||
}
|
||||
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
|
||||
newItem[column] = { ...newItem[column], ...position }
|
||||
})
|
||||
|
||||
return newItem
|
||||
}
|
||||
|
||||
function addComponent(appComponentType: AppComponent['type']): void {
|
||||
// When a new component is added, we need to mark the app as dirty,
|
||||
// so a confirmation modal will appear if the user tries to leave the page
|
||||
$dirtyStore = true
|
||||
|
||||
const grid = $app.grid ?? []
|
||||
const data = componentsRecord[appComponentType].data
|
||||
const id = insertNewGridItem($app, data, $focusedGrid)
|
||||
|
||||
const gridItemIds = grid
|
||||
.map((gridItem: GridItem) => {
|
||||
const subGrids = gridItem.data.subGrids ?? []
|
||||
return [
|
||||
gridItem.data.id,
|
||||
...subGrids.map((subGrid: GridItem[]) =>
|
||||
subGrid.map((gridItem: GridItem) => gridItem.data.id)
|
||||
)
|
||||
]
|
||||
})
|
||||
.flat(2)
|
||||
|
||||
const id = getNextId(gridItemIds)
|
||||
|
||||
if ($focusedGrid) {
|
||||
const { parentComponentId, subGridIndex } = $focusedGrid
|
||||
|
||||
const gridItemIndex = $app.grid.findIndex((gridItem) => gridItem.id === parentComponentId)
|
||||
const subGrids = $app.grid[gridItemIndex].data.subGrids ?? []
|
||||
const newItem = createNewGridItem(subGrids[subGridIndex] ?? [], id, appComponentType)
|
||||
const subGrid = subGrids[subGridIndex] ?? []
|
||||
|
||||
$app.grid[gridItemIndex].data.subGrids[subGridIndex] = [...subGrid, newItem]
|
||||
$selectedComponent = id
|
||||
} else {
|
||||
const newItem = createNewGridItem(grid, id, appComponentType)
|
||||
$app.grid = [...grid, newItem]
|
||||
$selectedComponent = id
|
||||
}
|
||||
$selectedComponent = id
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
isOpenStore.addItems(componentSets.map((set) => ({ [set.title]: true })))
|
||||
isOpenStore.addItems(COMPONENT_SETS.map((set) => ({ [TITLE_PREFIX + set.title]: true })))
|
||||
})
|
||||
</script>
|
||||
|
||||
{#each componentSets as { title, components }, index (index)}
|
||||
{@const isOpen = $isOpenStore[title]}
|
||||
<section class="mt-1 mb-2 px-2">
|
||||
<button
|
||||
on:click|preventDefault={() => isOpenStore.toggle(title)}
|
||||
class="w-full flex justify-between items-center text-gray-700 px-2 py-1
|
||||
rounded-sm duration-200 hover:bg-gray-100"
|
||||
>
|
||||
<h1 class="text-sm font-semibold text-left">{title}</h1>
|
||||
<Icon data={faAngleDown} class="rotate-0 duration-300 {isOpen ? '!rotate-180' : ''}" />
|
||||
</button>
|
||||
{#if isOpen}
|
||||
<div transition:slide|local={{ duration: 300 }}>
|
||||
{#if components.length}
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
{#each components as item}
|
||||
<button
|
||||
on:click={() => addComponent(item)}
|
||||
title={componentsRecord[item].name}
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
|
||||
>
|
||||
<svelte:component this={componentsRecord[item].icon} />
|
||||
<div class="text-xs w-full text-center ellipsize">
|
||||
{componentsRecord[item].name}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-gray-500 py-1 px-2">
|
||||
There are no components in this group yet
|
||||
</div>
|
||||
{/if}
|
||||
{#each COMPONENT_SETS as { title, components }, index (index)}
|
||||
<ListItem {title} prefix={TITLE_PREFIX}>
|
||||
{#if components.length}
|
||||
<div class="flex flex-wrap gap-2 py-2">
|
||||
{#each components as item}
|
||||
<button
|
||||
on:click={() => addComponent(item)}
|
||||
title={componentsRecord[item].name}
|
||||
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
|
||||
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
|
||||
>
|
||||
<svelte:component this={componentsRecord[item].icon} />
|
||||
<div class="text-xs w-full text-center ellipsize">
|
||||
{componentsRecord[item].name}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-xs text-gray-500 py-1 px-2"> There are no components in this group yet </div>
|
||||
{/if}
|
||||
</section>
|
||||
</ListItem>
|
||||
{/each}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import type { ComponentCssProperty } from "../../types"
|
||||
|
||||
export let name: string
|
||||
export let value: ComponentCssProperty | undefined
|
||||
</script>
|
||||
|
||||
<div class="text-sm font-semibold text-gray-500 capitalize pt-2">
|
||||
{name}
|
||||
</div>
|
||||
{#if value}
|
||||
<div class="border-l border-gray-400/80 py-1 pl-3.5 mt-1 ml-0.5">
|
||||
<label class="block pb-2">
|
||||
<div class="text-xs font-medium pb-0.5">
|
||||
Style
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={value.style}
|
||||
on:focus
|
||||
/>
|
||||
</label>
|
||||
<label class="block">
|
||||
<div class="text-xs font-medium pb-0.5">
|
||||
Tailwind classes
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={value.class}
|
||||
on:focus
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -19,5 +19,3 @@ export const isOpenStore = {
|
||||
},
|
||||
reset: () => store.set({})
|
||||
}
|
||||
|
||||
export const isOpenStoreCss = writable<Record<string, boolean>>({})
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 }[],
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Schema } from '$lib/common'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import type { FilledItem } from '@windmill-labs/svelte-grid'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { AppComponent } from './editor/Component.svelte'
|
||||
import type { AppComponent } from './editor/component/components'
|
||||
import type {
|
||||
AppInput,
|
||||
ConnectedAppInput,
|
||||
@@ -29,6 +29,13 @@ export interface GeneralAppInput {
|
||||
tooltip?: string
|
||||
}
|
||||
|
||||
export type ComponentCssProperty = {
|
||||
class: string
|
||||
style: string
|
||||
}
|
||||
|
||||
export type ComponentCustomCSS<T extends string = string> = Record<T, ComponentCssProperty>
|
||||
|
||||
export interface BaseAppComponent extends Partial<Aligned> {
|
||||
id: ComponentID
|
||||
componentInput: AppInput | undefined
|
||||
@@ -45,25 +52,20 @@ export interface BaseAppComponent extends Partial<Aligned> {
|
||||
)
|
||||
>
|
||||
card: boolean | undefined
|
||||
customCss?: Record<
|
||||
string,
|
||||
{
|
||||
class: string
|
||||
style: string
|
||||
}
|
||||
>
|
||||
customCss?: ComponentCustomCSS
|
||||
/**
|
||||
* If `true` then the wrapper will allow items to flow outside of it's borders.
|
||||
*
|
||||
* *For example when the component has a popup like `Select`*
|
||||
*/
|
||||
softWrap?: boolean
|
||||
subGrids?: GridItem[][]
|
||||
// Number of subgrids
|
||||
numberOfSubgrids?: number
|
||||
}
|
||||
|
||||
export type ComponentSet = {
|
||||
title: string
|
||||
components: AppComponent['type'][]
|
||||
components: Readonly<AppComponent['type'][]>
|
||||
}
|
||||
|
||||
type SectionID = string
|
||||
@@ -97,10 +99,8 @@ export type App = {
|
||||
inlineScript: InlineScript | undefined
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
|
||||
}>
|
||||
css?: Record<
|
||||
'viewer' | 'grid' | AppComponent['type'],
|
||||
Record<string, { style?: string; class?: string }>
|
||||
>
|
||||
css?: Record<'viewer' | 'grid' | AppComponent['type'], ComponentCustomCSS>
|
||||
subgrids?: Record<string, GridItem[]>
|
||||
}
|
||||
|
||||
export type ConnectingInput = {
|
||||
@@ -134,7 +134,6 @@ export type AppEditorContext = {
|
||||
}
|
||||
|
||||
export type FocusedGrid = { parentComponentId: string; subGridIndex: number }
|
||||
|
||||
export type EditorMode = 'dnd' | 'preview'
|
||||
export type EditorBreakpoint = 'sm' | 'lg'
|
||||
|
||||
|
||||
@@ -2,12 +2,66 @@ import type { Schema } from '$lib/common'
|
||||
import { FlowService, ScriptService } from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import type { AppComponent } from './editor/Component.svelte'
|
||||
import type { AppComponent } from './editor/component'
|
||||
|
||||
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
|
||||
import type { Output } from './rx'
|
||||
import type { App, GridItem } from './types'
|
||||
|
||||
/*
|
||||
export function deleteComponent(
|
||||
subgrid: string | undefined,
|
||||
component: AppComponent,
|
||||
app: App,
|
||||
staticOutputs: Record<string, any>,
|
||||
runnableComponents: Record<string, any>
|
||||
) {
|
||||
if (parentItems) {
|
||||
let index = parentItems.findIndex((item) => item.data?.id === component.id)
|
||||
if (index != -1) {
|
||||
parentItems.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
delete staticOutputs[component.id]
|
||||
delete runnableComponents[component.id]
|
||||
|
||||
if (
|
||||
component.componentInput?.type === 'runnable' &&
|
||||
component.componentInput?.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
const { name, inlineScript } = component.componentInput.runnable
|
||||
|
||||
if (inlineScript) {
|
||||
if (!app.unusedInlineScripts) {
|
||||
app.unusedInlineScripts = []
|
||||
}
|
||||
|
||||
app.unusedInlineScripts.push({
|
||||
name,
|
||||
inlineScript
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
export function allItems(grid: GridItem[], subgrids: Record<string, GridItem[]> | undefined): GridItem[] {
|
||||
if (subgrids == undefined) {
|
||||
return grid
|
||||
}
|
||||
return [...grid, ...Object.values(subgrids).flat()]
|
||||
}
|
||||
|
||||
export function allItemsWithParent(grid: GridItem[], subgrids: Record<string, GridItem[]> | undefined): [GridItem, string | undefined][] {
|
||||
const items: [GridItem, string | undefined][] = grid.map((item) => [item, undefined])
|
||||
if (subgrids == undefined) {
|
||||
return items
|
||||
}
|
||||
return [...items, ...Object.entries(subgrids).flatMap(([k, v]) => v.map((g) => [g, k] as [GridItem, string]))]
|
||||
}
|
||||
|
||||
export async function loadSchema(
|
||||
workspace: string,
|
||||
path: string,
|
||||
@@ -65,8 +119,6 @@ export function schemaToInputsSpec(
|
||||
}, {})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function accessPropertyByPath<T>(object: T, path: string): T | undefined {
|
||||
// convert indexes to properties
|
||||
path = path.replace(/\[(\w+)\]/g, '.$1')
|
||||
@@ -202,13 +254,13 @@ export function getAllScriptNames(app: App): string[] {
|
||||
}
|
||||
|
||||
function clearAndUpper(text: string) {
|
||||
return text.replace(/-/, "").toUpperCase();
|
||||
return text.replace(/-/, '').toUpperCase()
|
||||
}
|
||||
|
||||
export function toPascalCase(text: string) {
|
||||
return text.replace(/(^\w|-\w)/g, clearAndUpper);
|
||||
return text.replace(/(^\w|-\w)/g, clearAndUpper)
|
||||
}
|
||||
|
||||
export function toKebabCase(text: string) {
|
||||
return text.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
||||
return text.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
$$props.class
|
||||
)}
|
||||
>
|
||||
<slot />
|
||||
<slot {selected} />
|
||||
</div>
|
||||
</div>
|
||||
<slot name="content" />
|
||||
|
||||
@@ -142,12 +142,12 @@
|
||||
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'pgsql' })}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
<!-- <FlowScriptPicker
|
||||
label={`MySQL`}
|
||||
lang="mysql"
|
||||
on:click={() =>
|
||||
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'mysql' })}
|
||||
/>
|
||||
/> -->
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -42,12 +42,7 @@
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="settings-metadata" class="p-4 h-full">
|
||||
<div class="overflow-auto h-full">
|
||||
<Path
|
||||
bind:path={$flowStore.path}
|
||||
{initialPath}
|
||||
namePlaceholder="my_flow"
|
||||
kind="flow"
|
||||
/>
|
||||
<Path bind:path={$flowStore.path} {initialPath} namePlaceholder="flow" kind="flow" />
|
||||
|
||||
<label class="block my-4">
|
||||
<span class="text-gray-700 text-sm">Summary <Required required={false} /></span>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user