Compare commits

..

40 Commits

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

* Apply automatic changes

---------

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

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

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

* Apply automatic changes

---------

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

* fix(frontend): Update component custom css typings

* fix(frontend): Generalise custom CSS inputs

* feat(frontend): Add display CSS customization
2023-02-23 11:42:32 +01:00
107 changed files with 3781 additions and 2387 deletions

View File

@@ -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
View File

@@ -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",

View File

@@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)
}
}

View File

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

View File

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

View File

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

View File

@@ -1,12 +1,12 @@
{
"name": "windmill",
"version": "1.67.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",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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'

View File

@@ -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()
}

View File

@@ -41,7 +41,7 @@
$$props.class
)}
>
<slot />
<slot {selected} />
</div>
</div>
<slot name="content" />

View File

@@ -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>

View File

@@ -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