mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 00:01:49 +00:00
improve Flow UI (#297)
* Improve flow ui * Finish reworking * Fix formating * Fix naviation * Fix navigaion + adjust icon sizes * Remove duplicate code Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
co-authored by
Ruben Fiszel
parent
e2788a2003
commit
6b30969ae5
Generated
+105
-3
@@ -32,6 +32,8 @@
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"flowbite": "^1.5.1",
|
||||
"flowbite-svelte": "^0.22.19",
|
||||
"openapi-typescript-codegen": "^0.23.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.4.5",
|
||||
@@ -188,6 +190,21 @@
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
|
||||
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.2.tgz",
|
||||
@@ -319,7 +336,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/popperjs"
|
||||
@@ -1028,6 +1044,12 @@
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/clone-regexp": {
|
||||
"version": "2.2.0",
|
||||
"dev": true,
|
||||
@@ -1886,6 +1908,32 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/flowbite": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-1.5.1.tgz",
|
||||
"integrity": "sha512-kMQ2o/aLV629WqUVubw2KZGYcN7W5rD0tFVqxvK5/45EFIgMAlTmT+XxdIjKwzyTlb3HgmUj5h5NaqN3TJc9Zw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/flowbite-svelte": {
|
||||
"version": "0.22.19",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.22.19.tgz",
|
||||
"integrity": "sha512-HhLhQlHiCP6K37z3OhrDGgoiiNqq6DMmbsa8hL+1TgK0WilokTuC4iLbnaBTTNg5lN+Od4w/KmX1PYKiBUrzow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@floating-ui/dom": "^0.5.3",
|
||||
"classnames": "^2.3.1",
|
||||
"flowbite": "^1.4.2",
|
||||
"svelte-heros": "^2.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"dev": true,
|
||||
@@ -4188,6 +4236,12 @@
|
||||
"svelte": "^3.24.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-heros": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-heros/-/svelte-heros-2.3.2.tgz",
|
||||
"integrity": "sha512-OlfD/TzHytc9/J9ij/YrFKS2Lj0i0N0qkOm/5F7s9zdb/YGZ9tHY4PujSXrNxEyVWjjiaLTJGVgMkXlIrvVNNg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/svelte-highlight": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-6.2.0.tgz",
|
||||
@@ -4905,6 +4959,21 @@
|
||||
"strip-json-comments": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"@floating-ui/core": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
|
||||
"integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==",
|
||||
"dev": true
|
||||
},
|
||||
"@floating-ui/dom": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz",
|
||||
"integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@floating-ui/core": "^0.7.3"
|
||||
}
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.2.tgz",
|
||||
@@ -4993,8 +5062,7 @@
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"@rollup/pluginutils": {
|
||||
"version": "4.2.1",
|
||||
@@ -5405,6 +5473,12 @@
|
||||
"readdirp": "~3.6.0"
|
||||
}
|
||||
},
|
||||
"classnames": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz",
|
||||
"integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==",
|
||||
"dev": true
|
||||
},
|
||||
"clone-regexp": {
|
||||
"version": "2.2.0",
|
||||
"dev": true,
|
||||
@@ -5963,6 +6037,28 @@
|
||||
"version": "3.2.5",
|
||||
"dev": true
|
||||
},
|
||||
"flowbite": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/flowbite/-/flowbite-1.5.1.tgz",
|
||||
"integrity": "sha512-kMQ2o/aLV629WqUVubw2KZGYcN7W5rD0tFVqxvK5/45EFIgMAlTmT+XxdIjKwzyTlb3HgmUj5h5NaqN3TJc9Zw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@popperjs/core": "^2.9.3",
|
||||
"mini-svg-data-uri": "^1.4.3"
|
||||
}
|
||||
},
|
||||
"flowbite-svelte": {
|
||||
"version": "0.22.19",
|
||||
"resolved": "https://registry.npmjs.org/flowbite-svelte/-/flowbite-svelte-0.22.19.tgz",
|
||||
"integrity": "sha512-HhLhQlHiCP6K37z3OhrDGgoiiNqq6DMmbsa8hL+1TgK0WilokTuC4iLbnaBTTNg5lN+Od4w/KmX1PYKiBUrzow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@floating-ui/dom": "^0.5.3",
|
||||
"classnames": "^2.3.1",
|
||||
"flowbite": "^1.4.2",
|
||||
"svelte-heros": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"fraction.js": {
|
||||
"version": "4.2.0",
|
||||
"dev": true
|
||||
@@ -7356,6 +7452,12 @@
|
||||
"typescript": "*"
|
||||
}
|
||||
},
|
||||
"svelte-heros": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/svelte-heros/-/svelte-heros-2.3.2.tgz",
|
||||
"integrity": "sha512-OlfD/TzHytc9/J9ij/YrFKS2Lj0i0N0qkOm/5F7s9zdb/YGZ9tHY4PujSXrNxEyVWjjiaLTJGVgMkXlIrvVNNg==",
|
||||
"dev": true
|
||||
},
|
||||
"svelte-highlight": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-6.2.0.tgz",
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"flowbite": "^1.5.1",
|
||||
"flowbite-svelte": "^0.22.19",
|
||||
"openapi-typescript-codegen": "^0.23.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"postcss": "^8.4.5",
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
|
||||
/* Avoid content shifting */
|
||||
overflow-y: overlay;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
h1 {
|
||||
@apply text-2xl;
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
import { ResourceService, ScriptService, VariableService } from '$lib/gen'
|
||||
import { getScriptByPath, loadHubScripts, sendUserToast } from '$lib/utils'
|
||||
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faSearch } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
|
||||
import { workspaceStore, hubScripts } from '$lib/stores'
|
||||
import ItemPicker from './ItemPicker.svelte'
|
||||
import VariableEditor from './VariableEditor.svelte'
|
||||
import ResourceEditor from './ResourceEditor.svelte'
|
||||
import { hubScripts, workspaceStore } from '$lib/stores'
|
||||
import { Button } from 'flowbite-svelte'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import { python, typescript } from 'svelte-highlight/languages'
|
||||
import Modal from './Modal.svelte'
|
||||
import type Editor from './Editor.svelte'
|
||||
import ItemPicker from './ItemPicker.svelte'
|
||||
import Modal from './Modal.svelte'
|
||||
import ResourceEditor from './ResourceEditor.svelte'
|
||||
import VariableEditor from './VariableEditor.svelte'
|
||||
|
||||
export let lang: 'python3' | 'deno'
|
||||
export let editor: Editor
|
||||
@@ -172,41 +173,57 @@
|
||||
|
||||
<VariableEditor bind:this={variableEditor} on:create={variablePicker.openModal} />
|
||||
|
||||
<div class="flex flex-row justify-around w-full">
|
||||
<button
|
||||
class="default-button-secondary font-semibold py-px mr-2 text-xs align-middle max-h-8"
|
||||
on:click|stopPropagation={() => {
|
||||
<div class="flex flex-row justify-end w-full space-x-2">
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
outline
|
||||
on:click={() => {
|
||||
variablePicker.openModal()
|
||||
}}
|
||||
>Variable picker <Icon data={faSearch} scale={0.7} />
|
||||
</button>
|
||||
>
|
||||
<Icon data={faSearch} scale={0.7} class="mr-2" />
|
||||
Variable picker
|
||||
</Button>
|
||||
|
||||
<button
|
||||
class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
|
||||
on:click|stopPropagation={() => {
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
outline
|
||||
on:click={() => {
|
||||
resourcePicker.openModal()
|
||||
}}
|
||||
>Resource picker <Icon data={faSearch} scale={0.7} />
|
||||
</button>
|
||||
<button
|
||||
class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
|
||||
on:click|stopPropagation={() => {
|
||||
>
|
||||
<Icon data={faSearch} scale={0.7} class="mr-2" />
|
||||
Resource picker
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
outline
|
||||
on:click={() => {
|
||||
scriptPicker.openModal()
|
||||
}}
|
||||
>Script explorer <Icon data={faSearch} scale={0.7} />
|
||||
</button>
|
||||
>
|
||||
<Icon data={faSearch} scale={0.7} class="mr-2" />
|
||||
Script explorer
|
||||
</Button>
|
||||
|
||||
<button
|
||||
class="default-button-secondary py-px max-h-8 text-xs"
|
||||
on:click|stopPropagation={() => {
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
outline
|
||||
on:click={() => {
|
||||
editor.reloadWebsocket()
|
||||
}}
|
||||
>
|
||||
Reload assistants (status: {#if lang == 'deno'}<span
|
||||
class={websocketAlive.deno ? 'text-green-600' : 'text-red-600'}>deno</span
|
||||
>{:else if lang == 'python3'}<span
|
||||
class={websocketAlive.pyright ? 'text-green-600' : 'text-red-600'}>pyright</span
|
||||
>
|
||||
<span class={websocketAlive.black ? 'text-green-600' : 'text-red-600'}> black</span>{/if})
|
||||
</button>
|
||||
Reload assistants (status:
|
||||
{#if lang == 'deno'}
|
||||
<span class={websocketAlive.deno ? 'text-green-600' : 'text-red-600'}>Deno</span>
|
||||
{:else if lang == 'python3'}
|
||||
<span class={websocketAlive.pyright ? 'text-green-600' : 'text-red-600'}>Pyright</span>
|
||||
<span class={websocketAlive.black ? 'text-green-600' : 'text-red-600'}>Black</span>
|
||||
{/if})
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
sendUserToast,
|
||||
setQueryWithoutLoad
|
||||
} from '$lib/utils'
|
||||
import { Breadcrumb, BreadcrumbItem } from 'flowbite-svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import { OFFSET } from './CronInput.svelte'
|
||||
import FlowEditor from './FlowEditor.svelte'
|
||||
@@ -126,61 +127,48 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-screen max-w-screen-lg xl:-ml-20 xl:pl-4 w-full -mt-4 pt-4 md:mx-10 ">
|
||||
<div class="flex flex-col max-w-screen-lg w-full mb-96 px-8">
|
||||
<!-- Nav between steps-->
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="justify-between flex flex-row drop-shadow-sm w-full">
|
||||
<div class="wizard-nav flex flex-row w-full">
|
||||
<div class="justify-between flex flex-row w-full">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem>
|
||||
<button on:click={() => changeStep(1)} class={step === 1 ? 'font-bold' : null}
|
||||
>Flow Editor</button
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem>
|
||||
<button on:click={() => changeStep(2)} class={step === 2 ? 'font-bold' : null}
|
||||
>UI customisation</button
|
||||
>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
<div class="flex flex-row-reverse ml-2">
|
||||
{#if step == 1}
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="{step === 1
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(1)
|
||||
}}>Step 1: Flow</button
|
||||
class="default-button px-6 max-h-8"
|
||||
on:click={() => changeStep(2)}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="{step === 2
|
||||
? 'default-button-disabled text-gray-700'
|
||||
: 'default-button-secondary'} min-w-max ml-2"
|
||||
on:click={() => {
|
||||
changeStep(2)
|
||||
}}>Step 2: UI customisation</button
|
||||
class="default-button-secondary px-6 max-h-8 mr-2"
|
||||
on:click={saveFlow}
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-row-reverse ml-2">
|
||||
{#if step == 1}
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="default-button px-6 max-h-8"
|
||||
on:click={() => {
|
||||
changeStep(step + 1)
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
disabled={pathError != ''}
|
||||
class="default-button-secondary px-6 max-h-8 mr-2"
|
||||
on:click={saveFlow}
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
{:else}
|
||||
<button class="default-button px-6 self-end" on:click={saveFlow}>Save</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row-reverse">
|
||||
<span class="my-1 text-sm text-gray-500 italic">
|
||||
{#if initialPath && initialPath != $flowStore?.path} {initialPath} → {/if}
|
||||
{$flowStore?.path}
|
||||
</span>
|
||||
Save
|
||||
</button>
|
||||
{:else}
|
||||
<button class="default-button px-6 self-end" on:click={saveFlow}>Save</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-row-reverse">
|
||||
<span class="my-1 text-sm text-gray-500 italic">
|
||||
{#if initialPath && initialPath != $flowStore?.path} {initialPath} → {/if}
|
||||
{$flowStore?.path}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- metadata -->
|
||||
|
||||
@@ -205,13 +193,3 @@
|
||||
<p>Loading</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* .wizard-nav {
|
||||
@apply w-1/2 sm:w-1/4;
|
||||
} */
|
||||
|
||||
.wizard-nav button {
|
||||
max-height: 30px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { ScheduleService } from '$lib/gen'
|
||||
|
||||
import { faFileExport, faFileImport, faGlobe, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import FlowPreview from './FlowPreview.svelte'
|
||||
import CopyFirstStepSchema from './flows/CopyFirstStepSchema.svelte'
|
||||
import { addModule, flowStore, initFlow, mode } from './flows/flowStore'
|
||||
import ModuleStep from './ModuleStep.svelte'
|
||||
import Path from './Path.svelte'
|
||||
import RadioButtonV2 from './RadioButtonV2.svelte'
|
||||
import SchemaEditor from './SchemaEditor.svelte'
|
||||
import Required from './Required.svelte'
|
||||
import { flowToHubUrl, pathIsEmpty, sendUserToast } from '$lib/utils'
|
||||
import Dropdown from './Dropdown.svelte'
|
||||
import Editor from './Editor.svelte'
|
||||
import Modal from './Modal.svelte'
|
||||
import FlowViewer from './FlowViewer.svelte'
|
||||
import { flowToMode } from './flows/utils'
|
||||
import CronInput from './CronInput.svelte'
|
||||
import CollapseLink from './CollapseLink.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { pathIsEmpty } from '$lib/utils'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Button } from 'flowbite-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import FlowInput from './flows/FlowInput.svelte'
|
||||
import FlowSettings from './flows/FlowSettings.svelte'
|
||||
import { addModule, flowStore, mode } from './flows/flowStore'
|
||||
import ModuleStep from './ModuleStep.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
export let pathError = ''
|
||||
export let initialPath: string = ''
|
||||
@@ -31,11 +19,6 @@
|
||||
export let scheduleEnabled = false
|
||||
export let scheduleCron: string = '0 */5 * * *'
|
||||
|
||||
let jsonSetter: Modal
|
||||
let jsonViewer: Modal
|
||||
|
||||
let jsonValue: string = ''
|
||||
|
||||
async function loadSchedule() {
|
||||
const existsSchedule = await ScheduleService.existsSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
@@ -46,7 +29,7 @@
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: initialPath
|
||||
})
|
||||
scheduleEnabled = schedule.enabled
|
||||
scheduleEnabled = schedule.enabled!
|
||||
scheduleCron = schedule.schedule
|
||||
scheduleArgs = schedule.args ?? {}
|
||||
}
|
||||
@@ -60,221 +43,51 @@
|
||||
let args: Record<string, any> = {}
|
||||
</script>
|
||||
|
||||
<Modal bind:this={jsonSetter}>
|
||||
<div slot="title">Import JSON</div>
|
||||
<div slot="content" class="h-full">
|
||||
<Editor bind:code={jsonValue} lang={'json'} class="h-full" />
|
||||
</div>
|
||||
<div slot="submission">
|
||||
<button
|
||||
class="default-button px-4 py-2 font-semibold"
|
||||
{#if $flowStore}
|
||||
<div class="flex space-y-8 flex-col items-center line">
|
||||
<FlowSettings
|
||||
bind:pathError
|
||||
bind:initialPath
|
||||
bind:scheduleArgs
|
||||
bind:scheduleCron
|
||||
bind:scheduleEnabled
|
||||
bind:open
|
||||
/>
|
||||
<FlowInput />
|
||||
{#each $flowStore?.value.modules as mod, i}
|
||||
<ModuleStep bind:open bind:mod bind:args {i} mode={$mode} />
|
||||
{#if i == 0 && $mode == 'pull'}
|
||||
<div class="flex justify-center bg-white shadow p-2">
|
||||
Starting from here, the flow for loop over items from step 1's result above
|
||||
<Tooltip>
|
||||
This flow being in 'Pull' mode, the rest of the flow will for loop over the list of
|
||||
items returned by the trigger script right above. Retrieve the item value using
|
||||
`flow_input._value`
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
<Button
|
||||
disabled={pathIsEmpty($flowStore.path)}
|
||||
color="blue"
|
||||
on:click={() => {
|
||||
Object.assign($flowStore, JSON.parse(jsonValue))
|
||||
initFlow($flowStore)
|
||||
open = -1
|
||||
sendUserToast('OpenFlow imported from JSON')
|
||||
jsonSetter.closeModal()
|
||||
addModule()
|
||||
open = $flowStore?.value.modules.length - 1
|
||||
}}
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
</div></Modal
|
||||
>
|
||||
|
||||
<Modal bind:this={jsonViewer}>
|
||||
<div slot="title">See JSON</div>
|
||||
<div slot="content" class="h-full">
|
||||
<FlowViewer flow={flowToMode($flowStore, $mode)} tab="json" />
|
||||
<Icon class="text-white mr-2" data={faPlus} />
|
||||
Add step {pathIsEmpty($flowStore?.path) ? '(pick a name first!)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
{#if $flowStore}
|
||||
<div class="flow-root bg-gray-50 rounded-xl border border-gray-200">
|
||||
<ul class="relative -mt-10">
|
||||
<span class="absolute top-0 left-1/2 h-full w-1 bg-gray-400" aria-hidden="true" />
|
||||
<div class="relative">
|
||||
<li class="flex flex-row flex-shrink max-w-full mx-auto mt-20">
|
||||
<div
|
||||
class="bg-white border border-gray xl-rounded shadow-lg w-full max-w-4xl mx-4 md:mx-auto p-4"
|
||||
>
|
||||
<div class="flex flex-row-reverse mr-4">
|
||||
<Dropdown
|
||||
dropdownItems={[
|
||||
{
|
||||
displayName: 'Import from a JSON OpenFlow',
|
||||
icon: faFileImport,
|
||||
action: () => {
|
||||
jsonSetter.openModal()
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Export to a JSON OpenFlow',
|
||||
icon: faFileExport,
|
||||
action: () => {
|
||||
jsonViewer.openModal()
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: 'Publish to Hub',
|
||||
icon: faGlobe,
|
||||
action: () => {
|
||||
const url = flowToHubUrl(flowToMode($flowStore, $mode))
|
||||
window.open(url, '_blank')?.focus()
|
||||
}
|
||||
}
|
||||
]}
|
||||
relative={false}
|
||||
/>
|
||||
</div>
|
||||
<div class="mb-8 p-4">
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path={$flowStore.path}
|
||||
{initialPath}
|
||||
namePlaceholder="my_flow"
|
||||
kind="flow"
|
||||
>
|
||||
<div slot="ownerToolkit">
|
||||
Flow permissions depend on their path. Select the group <span class="font-mono"
|
||||
>all</span
|
||||
>
|
||||
to share your flow, and <span class="font-mono">user</span> to keep it private.
|
||||
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
|
||||
</div>
|
||||
</Path>
|
||||
|
||||
<label class="block mt-4">
|
||||
<span class="text-gray-700">Summary <Required required={false} /></span>
|
||||
<textarea
|
||||
bind:value={$flowStore.summary}
|
||||
class="
|
||||
mt-1
|
||||
block
|
||||
w-full
|
||||
rounded-md
|
||||
border-gray-300
|
||||
shadow-sm
|
||||
focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
|
||||
"
|
||||
placeholder="A very short summary of the flow displayed when the flow is listed"
|
||||
rows="1"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<RadioButtonV2
|
||||
options={[
|
||||
[
|
||||
{
|
||||
title: 'UI or webhook triggered',
|
||||
desc: 'Trigger this flow through the generated UI, a manual schedule or by calling the associated webhook'
|
||||
},
|
||||
'push'
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'Watching changes regularly',
|
||||
desc: 'The first module of this flow is a trigger script whose purpose is to pull data from an external source and return all new items since last run. This flow is meant to be scheduled very regularly to reduce latency to react to new events. It will trigger the rest of the flow once per item. If no new items, the flow will be skipped.'
|
||||
},
|
||||
'pull'
|
||||
]
|
||||
]}
|
||||
bind:value={$mode}
|
||||
/>
|
||||
{#if $mode == 'pull'}
|
||||
<div class="p-4">
|
||||
<CollapseLink text="set primary schedule" open={true}>
|
||||
<Tooltip
|
||||
>The primary schedule of a flow is simply a schedule that has the same name as a
|
||||
flow. It can be set and enabled directly within the flow editor. "Watching for
|
||||
new changes" flows are meant to be watching regularly for new items in an
|
||||
external systems. The primary schedule purpose is there to set the periodicity
|
||||
at which you want this watcher to operate.
|
||||
</Tooltip>
|
||||
<Toggle
|
||||
bind:checked={scheduleEnabled}
|
||||
options={{
|
||||
left: { label: 'disabled', value: false },
|
||||
right: { label: 'enabled', value: true }
|
||||
}}
|
||||
/>
|
||||
<div class="p-2 mt-2 rounded" class:bg-gray-300={!scheduleEnabled}>
|
||||
{#if !scheduleEnabled}
|
||||
<span class="font-black">No next scheduled run when disabled</span>
|
||||
{/if}
|
||||
<CronInput bind:schedule={scheduleCron} />
|
||||
</div>
|
||||
<SchemaForm schema={$flowStore.schema} bind:args={scheduleArgs} />
|
||||
</CollapseLink>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
<li class="flex flex-row flex-shrink max-w-full mx-auto mt-20">
|
||||
<div class="bg-white border border-gray xl-rounded shadow-lg w-full mx-4 xl:mx-20">
|
||||
<div
|
||||
class="flex items-center justify-between flex-wra px-4 py-5 border-b border-gray-200 sm:px-6"
|
||||
>
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">Flow Input</h3>
|
||||
<CopyFirstStepSchema />
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<SchemaEditor
|
||||
on:change={() => {
|
||||
$flowStore = $flowStore
|
||||
}}
|
||||
schema={$flowStore.schema}
|
||||
/>
|
||||
<div class="my-4" />
|
||||
<FlowPreview
|
||||
mode={$mode}
|
||||
flow={$flowStore}
|
||||
i={$flowStore?.value.modules.length}
|
||||
bind:args
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{#each $flowStore?.value.modules as mod, i}
|
||||
<li class="relative mt-16">
|
||||
<div class="relative flex justify-center">
|
||||
<button
|
||||
class="default-button h-10 w-10 shadow-blue-600/40 border-blue-600 shadow"
|
||||
on:click={() => {
|
||||
addModule(i)
|
||||
open = i
|
||||
}}
|
||||
>
|
||||
<Icon class="text-white mb-1" data={faPlus} />
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
<ModuleStep bind:open bind:mod bind:args {i} mode={$mode} />
|
||||
{/each}
|
||||
<li class="relative m-20 ">
|
||||
<div class="relative flex justify-center">
|
||||
<button
|
||||
disabled={pathIsEmpty($flowStore.path)}
|
||||
class="default-button h-10 w-10 shadow"
|
||||
on:click={() => {
|
||||
addModule()
|
||||
open = $flowStore?.value.modules.length - 1
|
||||
}}
|
||||
>
|
||||
<Icon class="text-white mb-1" data={faPlus} />
|
||||
Add step {pathIsEmpty($flowStore?.path) ? '(pick a name first!)' : ''}
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="py-10 bg-white" />
|
||||
{:else}
|
||||
<h3>Loading flow</h3>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.shadow:not([disabled]) {
|
||||
@apply border-blue-600 shadow-blue-600/40;
|
||||
.line {
|
||||
background-image: linear-gradient(#e5e7eb, #e5e7eb);
|
||||
background-size: 2px 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -97,7 +97,8 @@
|
||||
>
|
||||
<h2 class="flex justify-center text-gray-600">
|
||||
<div>
|
||||
Preview<Icon class="ml-1" data={viewPreview ? faChevronUp : faChevronDown} scale={1} />
|
||||
Preview
|
||||
<Icon class="ml-1" data={viewPreview ? faChevronUp : faChevronDown} scale={1} />
|
||||
</div>
|
||||
</h2>
|
||||
</button>
|
||||
|
||||
@@ -37,10 +37,10 @@
|
||||
|
||||
<div class="fixed top-0 w-screen h-screen {z}">
|
||||
<div
|
||||
class="fixed right-0 flex flex-col w-3/4 sm:w-2/3 lg:w-1/2 h-screen border border-gray-300 shadow-xl"
|
||||
class="fixed right-0 top-0 flex flex-col w-3/4 sm:w-2/3 lg:w-1/2 h-screen border border-gray-300 shadow-xl"
|
||||
>
|
||||
{#if open}
|
||||
<div class="flex flex-row justify-between p-2 bg-white border-b border-gray-200">
|
||||
<div class="flex flex-row justify-between p-2 bg-white border-b border-gray-200">
|
||||
<button
|
||||
on:click={() => {
|
||||
open = false
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { type FlowModule, RawScript } from '$lib/gen'
|
||||
import { RawScript, type FlowModule } from '$lib/gen'
|
||||
import { previewResults } from '$lib/stores'
|
||||
import { buildExtraLib, objectToTsType, schemaToTsType } from '$lib/utils'
|
||||
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faChevronDown, faChevronUp, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Editor from './Editor.svelte'
|
||||
import EditorBar from './EditorBar.svelte'
|
||||
import FlowPreview from './FlowPreview.svelte'
|
||||
import FlowBox from './flows/FlowBox.svelte'
|
||||
import FlowInputs from './flows/FlowInputs.svelte'
|
||||
import FlowModuleHeader from './flows/FlowModuleHeader.svelte'
|
||||
import {
|
||||
addModule,
|
||||
createInlineScriptModule,
|
||||
flowStore,
|
||||
loadSchema,
|
||||
@@ -42,62 +44,69 @@
|
||||
)
|
||||
|
||||
function toResults(jobs: any) {
|
||||
return jobs.map((x) => {
|
||||
if (x.result) {
|
||||
return x.result
|
||||
} else if (x.length) {
|
||||
return toResults(x)
|
||||
return jobs.map((job) => {
|
||||
if (job.result) {
|
||||
return job.result
|
||||
} else if (Array.isArray(job)) {
|
||||
return toResults(job)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const isTrigger = mode === 'pull' && i === 0
|
||||
</script>
|
||||
|
||||
<li class="flex flex-row flex-shrink max-w-full mx-auto mt-16">
|
||||
<div
|
||||
class="bg-white border border-gray xl-rounded shadow-lg w-full max-w-4xl mx-4 md:mx-auto"
|
||||
id="module-{i}"
|
||||
>
|
||||
<div class="flex items-center justify-between flex-wra p-4 sm:px-6 z-10">
|
||||
<FlowModuleHeader bind:open {mod} {i} {shouldPick}>
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-gray-900">Step {i + 1}</h3>
|
||||
{#if i == 0 && mode == 'pull'}
|
||||
<h3 class="font-bold">
|
||||
Trigger Script <Tooltip
|
||||
>When a flow is 'Pull', the first step is a trigger script. Trigger scripts are
|
||||
scripts that must return a list which are the new items to be treated one by one by
|
||||
the rest of the flow, usually the list of new items since last time the flow was
|
||||
run. One can retrieve the item in the next step using `previous_result._value`. To
|
||||
easily compute the diff, windmill provides some helpers under the form of
|
||||
`getInternalState` and `setInternalState`.</Tooltip
|
||||
>
|
||||
</h3>{/if}
|
||||
<p>
|
||||
{#if 'path' in mod.value && mod.value.path}
|
||||
{mod.value.path}
|
||||
{/if}
|
||||
{#if 'language' in mod.value && mod.value.language}
|
||||
Inline {mod.value.language}
|
||||
{/if}
|
||||
{#if !('path' in mod.value) && !('language' in mod.value)}
|
||||
Select a script
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</FlowModuleHeader>
|
||||
</div>
|
||||
<div class="border-b border-gray-200" />
|
||||
<button
|
||||
class="rounded-full h-10 w-10 bg-white border-2 border-gray-400"
|
||||
on:click={() => {
|
||||
addModule(i)
|
||||
open = i
|
||||
}}
|
||||
>
|
||||
<Icon class="text-gray-400 mb-1" data={faPlus} />
|
||||
</button>
|
||||
<FlowBox>
|
||||
<div id="module-{i}">
|
||||
<FlowModuleHeader bind:open {mod} {i} {shouldPick}>
|
||||
<div>
|
||||
<h3 class="text-lg font-bold text-gray-900">Step {i + 1}</h3>
|
||||
{#if isTrigger}
|
||||
<h3 class="font-bold">
|
||||
Trigger Script
|
||||
<Tooltip>
|
||||
When a flow is 'Pull', the first step is a trigger script. Trigger scripts are scripts
|
||||
that must return a list which are the new items to be treated one by one by the rest
|
||||
of the flow, usually the list of new items since last time the flow was run. One can
|
||||
retrieve the item in the next step using `previous_result._value`. To easily compute
|
||||
the diff, windmill provides some helpers under the form of `getInternalState` and
|
||||
`setInternalState`.
|
||||
</Tooltip>
|
||||
</h3>
|
||||
{/if}
|
||||
<p>
|
||||
{#if 'path' in mod.value && mod.value.path}
|
||||
{mod.value.path}
|
||||
{/if}
|
||||
{#if 'language' in mod.value && mod.value.language}
|
||||
Inline {mod.value.language}
|
||||
{/if}
|
||||
{#if !('path' in mod.value) && !('language' in mod.value)}
|
||||
Select a script
|
||||
{/if}
|
||||
</p>
|
||||
</div>
|
||||
</FlowModuleHeader>
|
||||
{#if open == i}
|
||||
<div class="p-6">
|
||||
{#if shouldPick}
|
||||
<FlowInputs
|
||||
isTrigger={mode == 'pull' && i == 0}
|
||||
{isTrigger}
|
||||
on:pick={(e) => pickScript(e.detail.path, i)}
|
||||
on:new={(e) => createInlineScriptModule(e.detail.language, i, mode)}
|
||||
/>
|
||||
{/if}
|
||||
{#if mod.value.type === 'rawscript'}
|
||||
<div class="p-1 overflow-hidden">
|
||||
<div class="mb-2 overflow-hidden">
|
||||
<EditorBar {editor} {websocketAlive} lang={mod.value.language ?? 'deno'} />
|
||||
</div>
|
||||
<div>
|
||||
@@ -156,27 +165,16 @@
|
||||
{/if}
|
||||
{#if open == i}
|
||||
<div>
|
||||
<button class="w-full h-full" on:click={() => (open = -1)}
|
||||
><Icon data={faChevronUp} scale={1.0} /></button
|
||||
>
|
||||
<button class="w-full h-full" on:click={() => (open = -1)}>
|
||||
<Icon data={faChevronUp} scale={1.0} />
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<button class="w-full h-full" on:click={() => (open = i)}
|
||||
><Icon data={faChevronDown} scale={1.0} /></button
|
||||
>
|
||||
<button class="w-full h-full" on:click={() => (open = i)}>
|
||||
<Icon data={faChevronDown} scale={1.0} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</li>
|
||||
{#if i == 0 && mode == 'pull'}
|
||||
<li class="relative m-20 ">
|
||||
<div class="relative flex justify-center bg-white shadow p-2">
|
||||
Starting from here, the flow for loop over items from step 1's result above <Tooltip
|
||||
>This flow being in 'Pull' mode, the rest of the flow will for loop over the list of items
|
||||
returned by the trigger script right above. Retrieve the item value using
|
||||
`flow_input._value`</Tooltip
|
||||
>
|
||||
</div>
|
||||
</li>
|
||||
{/if}
|
||||
</FlowBox>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
<script lang="ts">
|
||||
import SchemaModal, { DEFAULT_PROPERTY, schemaToModal } from './SchemaModal.svelte'
|
||||
import type { ModalSchemaProperty } from './SchemaModal.svelte'
|
||||
import type { Schema, SchemaProperty } from '$lib/common'
|
||||
import Editor from './Editor.svelte'
|
||||
import { emptySchema, sendUserToast, truncate } from '$lib/utils'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import { emptySchema, sendUserToast } from '$lib/utils'
|
||||
import { faPen, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Badge, Button } from 'flowbite-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Editor from './Editor.svelte'
|
||||
import type { ModalSchemaProperty } from './SchemaModal.svelte'
|
||||
import SchemaModal, { DEFAULT_PROPERTY, schemaToModal } from './SchemaModal.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -101,6 +105,13 @@
|
||||
try {
|
||||
if (Object.keys(schema.properties).includes(argName)) {
|
||||
delete schema.properties[argName]
|
||||
|
||||
const requiredIndex = schema.required.findIndex((arg) => arg === argName)
|
||||
|
||||
if (requiredIndex > -1) {
|
||||
schema.required = schema.required.slice(requiredIndex, 1)
|
||||
}
|
||||
|
||||
schema = schema
|
||||
schemaString = JSON.stringify(schema, null, '\t')
|
||||
dispatch('change', schema)
|
||||
@@ -126,95 +137,89 @@
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
<div class="w-full">
|
||||
<div class="flex flex-row text-base">
|
||||
<button
|
||||
class="text-xs sm:text-base py-1 px-6 block hover:text-blue-500 focus:outline-noneborder-gray-200 {viewJsonSchema
|
||||
? 'text-gray-500 '
|
||||
: 'text-gray-700 font-semibold '}"
|
||||
on:click={() => (viewJsonSchema ? switchTab() : null)}
|
||||
>
|
||||
arguments
|
||||
</button><button
|
||||
class="py-1 px-6 block hover:text-blue-500 focus:outline-none border-gray-200 {viewJsonSchema
|
||||
? 'text-gray-700 font-semibold '
|
||||
: 'text-gray-500'}"
|
||||
on:click={() => (viewJsonSchema ? null : switchTab())}
|
||||
>
|
||||
advanced <Tooltip
|
||||
>Arguments can be edited either using the wizard, or by editing their json-schema <a
|
||||
href="https://docs.windmill.dev/docs/reference/script_arguments_reference">docs</a
|
||||
></Tooltip
|
||||
>
|
||||
</button>
|
||||
<button
|
||||
class="default-button-secondary grow mb-1"
|
||||
on:click={() => {
|
||||
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
|
||||
schemaModal.openModal()
|
||||
}}>Add argument</button
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<Button
|
||||
on:click={() => {
|
||||
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
|
||||
schemaModal.openModal()
|
||||
}}
|
||||
color="blue"
|
||||
>
|
||||
<Icon data={faPlus} class="mr-1" />
|
||||
Add argument
|
||||
</Button>
|
||||
|
||||
<div class="flex items-center">
|
||||
<Toggle
|
||||
on:change={() => switchTab()}
|
||||
options={{
|
||||
left: { label: '', value: false },
|
||||
right: { label: 'Json Schema Editor', value: true }
|
||||
}}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<Tooltip>
|
||||
Arguments can be edited either using the wizard, or by editing their json-schema
|
||||
<a href="https://docs.windmill.dev/docs/reference/script_arguments_reference">docs</a>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--json schema or table view-->
|
||||
<div class="border-t py-1 h-full overflow-y-auto">
|
||||
<div class="h-full overflow-y-auto">
|
||||
{#if !viewJsonSchema}
|
||||
<div class="h-full">
|
||||
{#if schema.properties && Object.keys(schema.properties).length > 0 && schema.required}
|
||||
<TableCustom class="w-full min-h-full">
|
||||
<tr slot="header-row" class="underline">
|
||||
<th>name</th>
|
||||
<th>type</th>
|
||||
<th>description</th>
|
||||
<th>default</th>
|
||||
<th>required</th>
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
<th>Default</th>
|
||||
<th>Required</th>
|
||||
<th />
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each Object.entries(schema.properties) as [name, property] (name)}
|
||||
<tr>
|
||||
<td>{name}</td>
|
||||
<td
|
||||
>{property.type ?? 'any'}
|
||||
{property.format ? `(format: ${property.format})` : ''}
|
||||
{property.contentEncoding ? `(encoding: ${property.contentEncoding})` : ''}
|
||||
<td class="font-bold">{name}</td>
|
||||
<td>
|
||||
{#if !property.type}
|
||||
<Badge color="red">ANY</Badge>
|
||||
{:else}
|
||||
<Badge color="blue">{property.type.toLocaleUpperCase()}</Badge>
|
||||
{/if}
|
||||
</td>
|
||||
<td>{truncate(property.description, 20)}</td>
|
||||
<td>{truncate(JSON.stringify(property.default) ?? '', 20)}</td>
|
||||
<td>{schema.required.includes(name) ? 'required' : 'optional'}</td>
|
||||
<td class="">
|
||||
<button class="mr-2" on:click={() => handleDeleteArgument(name)}
|
||||
><svg
|
||||
class="w-4 h-4"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 14"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
/>
|
||||
</svg></button
|
||||
<td>{property.description}</td>
|
||||
<td>{JSON.stringify(property.default) ?? ''}</td>
|
||||
<td>{schema.required.includes(name) ? 'Required' : 'Optional'}</td>
|
||||
<td class="justify-end flex">
|
||||
<Button
|
||||
color="red"
|
||||
outline
|
||||
class="mr-2"
|
||||
size="xs"
|
||||
on:click={() => handleDeleteArgument(name)}
|
||||
>
|
||||
<button
|
||||
class="default-button-secondary text-xs inline-flex"
|
||||
on:click={() => {
|
||||
startEditArgument(name)
|
||||
}}>edit</button
|
||||
></td
|
||||
>
|
||||
<Icon data={faTrash} class="mr-2" scale={0.8} />
|
||||
Delete
|
||||
</Button>
|
||||
<Button color="alternative" size="xs" on:click={() => startEditArgument(name)}>
|
||||
<Icon data={faPen} class="mr-2" scale={0.8} />
|
||||
Edit
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</TableCustom>
|
||||
{:else}
|
||||
<div class="text-gray-700 text-xs italic">This script has no argument</div>
|
||||
<div class="text-gray-700 text-xs italic mt-2">This schema has no arguments.</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<div class="border rounded mt-4 p-2">
|
||||
<Editor
|
||||
on:change={() => {
|
||||
try {
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { ScriptService, type Script } from '$lib/gen'
|
||||
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { DENO_INIT_CODE, DENO_INIT_CODE_TRIGGER, initialCode } from '$lib/script_helpers'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { emptySchema, encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
|
||||
import { onDestroy } from 'svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import { page } from '$app/stores'
|
||||
import { goto } from '$app/navigation'
|
||||
import Path from './Path.svelte'
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import Required from './Required.svelte'
|
||||
import { DENO_INIT_CODE, DENO_INIT_CODE_TRIGGER, initialCode } from '$lib/script_helpers'
|
||||
import Path from './Path.svelte'
|
||||
import RadioButtonV3 from './RadioButtonV3.svelte'
|
||||
import Required from './Required.svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
|
||||
let editor: ScriptEditor
|
||||
let scriptSchema: ScriptSchema
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { JobService, Job, CompletedJob } from '$lib/gen'
|
||||
import { emptySchema, displayDate } from '$lib/utils'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { CompletedJob, Job, JobService } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { displayDate, emptySchema } from '$lib/utils'
|
||||
import {
|
||||
faCheck,
|
||||
faChevronDown,
|
||||
@@ -12,26 +11,25 @@
|
||||
faSpinner,
|
||||
faTimes
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import Editor from './Editor.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { check } from 'svelte-awesome/icons'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Editor from './Editor.svelte'
|
||||
import Modal from './Modal.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
|
||||
import { inferArgs } from '$lib/infer'
|
||||
|
||||
// @ts-ignore
|
||||
import { VSplitPane } from 'svelte-split-pane'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import type { Preview } from '$lib/gen/models/Preview'
|
||||
import EditorBar from './EditorBar.svelte'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import typescript from 'svelte-highlight/languages/typescript'
|
||||
import python from 'svelte-highlight/languages/python'
|
||||
import json from 'svelte-highlight/languages/json'
|
||||
import { json, python, typescript } from 'svelte-highlight/languages'
|
||||
import { VSplitPane } from 'svelte-split-pane'
|
||||
import DisplayResult from './DisplayResult.svelte'
|
||||
import EditorBar from './EditorBar.svelte'
|
||||
import SchemaForm from './SchemaForm.svelte'
|
||||
|
||||
// Exported
|
||||
export let schema: Schema = emptySchema()
|
||||
@@ -423,7 +421,7 @@
|
||||
{:else}No local save{/if}
|
||||
</div>
|
||||
{:else if previewTab === 'history'}
|
||||
<TableCustom paginated={false}>
|
||||
<TableCustom>
|
||||
<tr slot="header-row">
|
||||
<th class="text-xs">id</th>
|
||||
<th class="text-xs">created at</th>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
- the second slot should be a <tbody>, containing th elements
|
||||
-->
|
||||
<div class="flex flex-col {$$props.class} min-w-full">
|
||||
<div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle">
|
||||
<table id="table-custom" class="min-w-full divide-y divide-gray-300 table-auto">
|
||||
<thead>
|
||||
<slot name="header-row" />
|
||||
@@ -25,19 +25,16 @@
|
||||
<div class="flex flex-row-reverse text-gray-500 mb-6">
|
||||
<button
|
||||
class="ml-2 drop-shadow-md {showNext ? 'visible' : 'invisible'}"
|
||||
on:click={() => {
|
||||
dispatch('next')
|
||||
}}>Next</button
|
||||
on:click={() => dispatch('next')}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
<button
|
||||
class="mx-2 drop-shadow-md {currentPage === 1 ? 'hidden' : ''}"
|
||||
on:click={() => {
|
||||
dispatch('previous')
|
||||
}}>Previous</button
|
||||
on:click={() => dispatch('previous')}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { createPopperActions } from 'svelte-popperjs'
|
||||
import { fade } from 'svelte/transition'
|
||||
const [popperRef, popperContent] = createPopperActions({
|
||||
@@ -50,6 +50,7 @@
|
||||
use:popperContent={extraOpts}
|
||||
on:mouseenter={open}
|
||||
on:mouseleave={close}
|
||||
class="w-96 text-left"
|
||||
>
|
||||
<slot />
|
||||
<div id="arrow" data-popper-arrow />
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { Button } from 'flowbite-svelte'
|
||||
import { copyFirstStepSchema, isCopyFirstStepSchemaDisabled } from './flowStore'
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="default-secondary-button-v2"
|
||||
<Button
|
||||
color="alternative"
|
||||
size="sm"
|
||||
disabled={$isCopyFirstStepSchemaDisabled}
|
||||
on:click={copyFirstStepSchema}
|
||||
>
|
||||
Copy from step 1's schema
|
||||
</button>
|
||||
Copy from first step schema
|
||||
</Button>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="bg-white border border-gray-300 rounded-lg w-full shadow-md">
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<script lang="ts">
|
||||
export let title: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="flex items-center justify-between flex-wra px-6 py-4 border-b border-gray-300">
|
||||
{#if title}
|
||||
<h3 class="text-lg leading-6 font-bold text-gray-900">{title}</h3>
|
||||
{/if}
|
||||
<slot />
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import SchemaEditor from '../SchemaEditor.svelte'
|
||||
import { flowStore } from './../flows/flowStore'
|
||||
import CopyFirstStepSchema from './CopyFirstStepSchema.svelte'
|
||||
import FlowBox from './FlowBox.svelte'
|
||||
import FlowBoxHeader from './FlowBoxHeader.svelte'
|
||||
</script>
|
||||
|
||||
<FlowBox>
|
||||
<FlowBoxHeader title="Flow Inputs">
|
||||
<CopyFirstStepSchema />
|
||||
</FlowBoxHeader>
|
||||
<div class="p-6 ">
|
||||
<SchemaEditor
|
||||
on:change={() => {
|
||||
$flowStore = $flowStore
|
||||
}}
|
||||
schema={$flowStore.schema}
|
||||
/>
|
||||
</div>
|
||||
</FlowBox>
|
||||
@@ -2,11 +2,13 @@
|
||||
import type { FlowModule } from '$lib/gen'
|
||||
import { getScriptByPath } from '$lib/utils'
|
||||
import { faCode, faCodeBranch, faSave, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Button } from 'flowbite-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { Highlight } from 'svelte-highlight'
|
||||
import typescript from 'svelte-highlight/languages/typescript'
|
||||
import python from 'svelte-highlight/languages/python'
|
||||
import typescript from 'svelte-highlight/languages/typescript'
|
||||
import Modal from '../Modal.svelte'
|
||||
import FlowBoxHeader from './FlowBoxHeader.svelte'
|
||||
import { createScriptFromInlineScript, fork, removeModule } from './flowStore'
|
||||
import { scrollIntoView } from './utils'
|
||||
|
||||
@@ -36,54 +38,54 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-row w-full space-x-2">
|
||||
<slot />
|
||||
<a href="#module-{i}" class="grow" on:click={() => (open = i)} on:click|preventDefault={scrollTo}
|
||||
><div class="w-full" /></a
|
||||
<FlowBoxHeader>
|
||||
<a
|
||||
href="#module-{i}"
|
||||
class="grow text-inherit"
|
||||
on:click={() => (open = i)}
|
||||
on:click|preventDefault={scrollTo}
|
||||
>
|
||||
<slot />
|
||||
</a>
|
||||
|
||||
<div class="flex flex-row space-x-2">
|
||||
{#if mod.value.type === 'script' && !shouldPick}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
on:click={() => {
|
||||
open = i
|
||||
fork(i)
|
||||
}}
|
||||
class="default-secondary-button-v2 text-xs"
|
||||
size="sm"
|
||||
color="alternative"
|
||||
>
|
||||
<Icon data={faCodeBranch} class={`w-4 mr-2 h-4`} />
|
||||
<Icon data={faCodeBranch} class="mr-2" />
|
||||
Fork
|
||||
</button>
|
||||
<button type="button" on:click={viewCode} class="default-secondary-button-v2 text-xs">
|
||||
<Icon data={faCode} class="w-4 mr-2 h-4" />
|
||||
</Button>
|
||||
<Button size="sm" color="alternative" on:click={viewCode}>
|
||||
<Icon data={faCode} class="mr-2" />
|
||||
View code
|
||||
</button>
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
{#if mod.value.type === 'rawscript' && !shouldPick}
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => createScriptFromInlineScript(i)}
|
||||
class="default-secondary-button-v2 text-sm"
|
||||
>
|
||||
<Icon data={faSave} class="w-4 mr-4" />
|
||||
<Button size="sm" color="alternative" on:click={() => createScriptFromInlineScript(i)}>
|
||||
<Icon data={faSave} class="mr-2" />
|
||||
Save to workspace
|
||||
</button>
|
||||
</Button>
|
||||
{/if}
|
||||
<button
|
||||
type="button"
|
||||
<Button
|
||||
size="sm"
|
||||
color="alternative"
|
||||
on:click={() => {
|
||||
open = -1
|
||||
removeModule(i)
|
||||
}}
|
||||
class="default-secondary-button-v2 text-xs"
|
||||
>
|
||||
<Icon data={faTrashAlt} class="w-4 mr-2 h-4" />
|
||||
<Icon data={faTrashAlt} class="mr-2" />
|
||||
Remove step
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FlowBoxHeader>
|
||||
|
||||
<Modal bind:this={modalViewer}>
|
||||
<div slot="title">Script {'path' in mod?.value ? mod?.value.path : ''}</div>
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
<script lang="ts">
|
||||
import { sendUserToast } from '$lib/utils'
|
||||
import { faFileExport, faFileImport, faGlobe } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Dropdown, DropdownItem } from 'flowbite-svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import Editor from '../Editor.svelte'
|
||||
import FlowPreview from '../FlowPreview.svelte'
|
||||
import FlowViewer from '../FlowViewer.svelte'
|
||||
import Modal from '../Modal.svelte'
|
||||
import CollapseLink from './../CollapseLink.svelte'
|
||||
import CronInput from './../CronInput.svelte'
|
||||
import FlowBox from './../flows/FlowBox.svelte'
|
||||
import { flowStore, initFlow, mode } from './../flows/flowStore'
|
||||
import { flowToMode } from './../flows/utils'
|
||||
import Path from './../Path.svelte'
|
||||
import RadioButtonV2 from './../RadioButtonV2.svelte'
|
||||
import Required from './../Required.svelte'
|
||||
import SchemaForm from './../SchemaForm.svelte'
|
||||
import Toggle from './../Toggle.svelte'
|
||||
import Tooltip from './../Tooltip.svelte'
|
||||
import FlowBoxHeader from './FlowBoxHeader.svelte'
|
||||
|
||||
export let pathError = ''
|
||||
export let initialPath: string = ''
|
||||
|
||||
export let scheduleArgs: Record<string, any> = {}
|
||||
export let scheduleEnabled = false
|
||||
export let scheduleCron: string = '0 */5 * * *'
|
||||
export let open: number
|
||||
|
||||
let jsonSetter: Modal
|
||||
let jsonViewer: Modal
|
||||
|
||||
let jsonValue: string = ''
|
||||
</script>
|
||||
|
||||
<Modal bind:this={jsonSetter}>
|
||||
<div slot="title">Import JSON</div>
|
||||
<div slot="content" class="h-full">
|
||||
<Editor bind:code={jsonValue} lang={'json'} class="h-full" />
|
||||
</div>
|
||||
<div slot="submission">
|
||||
<button
|
||||
class="default-button px-4 py-2 font-semibold"
|
||||
on:click={() => {
|
||||
Object.assign($flowStore, JSON.parse(jsonValue))
|
||||
initFlow($flowStore)
|
||||
open = -1
|
||||
sendUserToast('OpenFlow imported from JSON')
|
||||
jsonSetter.closeModal()
|
||||
}}
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={jsonViewer}>
|
||||
<div slot="title">See JSON</div>
|
||||
<div slot="content" class="h-full">
|
||||
<FlowViewer flow={flowToMode($flowStore, $mode)} tab="json" />
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<FlowBox>
|
||||
<FlowBoxHeader title="Flow Settings">
|
||||
<div class="flex flex-row-reverse">
|
||||
<Dropdown class="w-fit" placement="bottom-end">
|
||||
<button slot="trigger" class="text-gray-900 bg-white dark:text-white dark:bg-gray-800">
|
||||
...
|
||||
</button>
|
||||
<DropdownItem
|
||||
on:click={() => {
|
||||
jsonSetter.openModal()
|
||||
}}
|
||||
>
|
||||
<Icon data={faFileImport} scale={0.6} class="inline mr-2" />
|
||||
Import from a JSON OpenFlow
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
on:click={() => {
|
||||
jsonViewer.openModal()
|
||||
}}
|
||||
>
|
||||
<Icon data={faFileExport} scale={0.6} class="inline mr-2" />
|
||||
Export to a JSON OpenFlow
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
on:click={() => {
|
||||
const url = new URL('https://hub.windmill.dev/flows/add')
|
||||
const openFlow = {
|
||||
value: $flowStore.value,
|
||||
summary: $flowStore.summary,
|
||||
description: $flowStore.description,
|
||||
schema: $flowStore.schema
|
||||
}
|
||||
url.searchParams.append('flow', btoa(JSON.stringify(flowToMode(openFlow, $mode))))
|
||||
window.open(url, '_blank')?.focus()
|
||||
}}
|
||||
>
|
||||
<Icon data={faGlobe} scale={0.6} class="inline mr-2" />
|
||||
Publish to Hub
|
||||
</DropdownItem>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</FlowBoxHeader>
|
||||
|
||||
<div class="p-6">
|
||||
<Path
|
||||
bind:error={pathError}
|
||||
bind:path={$flowStore.path}
|
||||
{initialPath}
|
||||
namePlaceholder="my_flow"
|
||||
kind="flow"
|
||||
>
|
||||
<div slot="ownerToolkit">
|
||||
Flow permissions depend on their path. Select the group <span class="font-mono">all</span>
|
||||
to share your flow, and <span class="font-mono">user</span> to keep it private.
|
||||
<a href="https://docs.windmill.dev/docs/reference/namespaces">docs</a>
|
||||
</div>
|
||||
</Path>
|
||||
|
||||
<label class="block mt-4">
|
||||
<span class="text-gray-700">Summary <Required required={false} /></span>
|
||||
<textarea
|
||||
bind:value={$flowStore.summary}
|
||||
class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50"
|
||||
placeholder="A very short summary of the flow displayed when the flow is listed"
|
||||
rows="1"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<RadioButtonV2
|
||||
options={[
|
||||
[
|
||||
{
|
||||
title: 'UI or webhook triggered',
|
||||
desc: 'Trigger this flow through the generated UI, a manual schedule or by calling the associated webhook'
|
||||
},
|
||||
'push'
|
||||
],
|
||||
[
|
||||
{
|
||||
title: 'Watching changes regularly',
|
||||
desc: 'The first module of this flow is a trigger script whose purpose is to pull data from an external source and return all new items since last run. This flow is meant to be scheduled very regularly to reduce latency to react to new events. It will trigger the rest of the flow once per item. If no new items, the flow will be skipped.'
|
||||
},
|
||||
'pull'
|
||||
]
|
||||
]}
|
||||
bind:value={$mode}
|
||||
/>
|
||||
{#if $mode == 'pull'}
|
||||
<div class="p-4">
|
||||
<CollapseLink text="set primary schedule" open={true}>
|
||||
<Tooltip>
|
||||
The primary schedule of a flow is simply a schedule that has the same name as a flow. It
|
||||
can be set and enabled directly within the flow editor. "Watching for new changes" flows
|
||||
are meant to be watching regularly for new items in an external systems. The primary
|
||||
schedule purpose is there to set the periodicity at which you want this watcher to
|
||||
operate.
|
||||
</Tooltip>
|
||||
<Toggle
|
||||
bind:checked={scheduleEnabled}
|
||||
options={{
|
||||
left: { label: 'disabled', value: false },
|
||||
right: { label: 'enabled', value: true }
|
||||
}}
|
||||
/>
|
||||
<div class="p-2 mt-2 rounded" class:bg-gray-300={!scheduleEnabled}>
|
||||
{#if !scheduleEnabled}
|
||||
<span class="font-black">No next scheduled run when disabled</span>
|
||||
{/if}
|
||||
<CronInput bind:schedule={scheduleCron} />
|
||||
</div>
|
||||
<SchemaForm schema={$flowStore.schema} bind:args={scheduleArgs} />
|
||||
</CollapseLink>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="p-6">
|
||||
<FlowPreview
|
||||
mode={$mode}
|
||||
flow={$flowStore}
|
||||
i={$flowStore?.value.modules.length}
|
||||
bind:args={scheduleArgs}
|
||||
/>
|
||||
</div>
|
||||
</FlowBox>
|
||||
@@ -1,9 +1,15 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import { type InputTransform, RawScript, ScriptService, type Flow, type FlowModule } from '$lib/gen'
|
||||
import { RawScript, ScriptService, type Flow, type FlowModule } from '$lib/gen'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { derived, get, writable } from 'svelte/store'
|
||||
import { createInlineScriptModuleFromPath, getFirstStepSchema, loadSchemaFromModule, scrollIntoView } from './utils'
|
||||
import {
|
||||
createInlineScriptModuleFromPath,
|
||||
getFirstStepSchema,
|
||||
loadSchemaFromModule,
|
||||
scrollIntoView
|
||||
} from './utils'
|
||||
|
||||
export type FlowMode = 'push' | 'pull'
|
||||
|
||||
@@ -17,7 +23,8 @@ export function initFlow(flow: Flow) {
|
||||
flow = flattenForloopFlows(flow)
|
||||
flow.value.modules.forEach((mod) => {
|
||||
Object.values(mod.input_transform).forEach((inp) => {
|
||||
if (inp.type == 'javascript') { // for now we use the value for dynamic expression when done in the static editor so we have to resort to this
|
||||
if (inp.type == 'javascript') {
|
||||
// for now we use the value for dynamic expression when done in the static editor so we have to resort to this
|
||||
|
||||
//@ts-ignore
|
||||
inp.value = codeToStaticTemplate(inp.expr)
|
||||
@@ -77,7 +84,6 @@ export function addModule(i?: number) {
|
||||
input_transform: {}
|
||||
}
|
||||
|
||||
|
||||
flowStore.update((flow: Flow) => {
|
||||
const insertAt = i ?? flow.value.modules.length
|
||||
|
||||
@@ -87,6 +93,19 @@ export function addModule(i?: number) {
|
||||
|
||||
return flow
|
||||
})
|
||||
|
||||
schemasStore.update((schemas: Schema[]) => {
|
||||
if (typeof i !== 'undefined') {
|
||||
const previousSchema = schemas[i]
|
||||
const hadSchema = Boolean(previousSchema)
|
||||
if (hadSchema) {
|
||||
schemas[i] = emptySchema()
|
||||
schemas[i + 1] = previousSchema
|
||||
}
|
||||
}
|
||||
|
||||
return schemas
|
||||
})
|
||||
}
|
||||
|
||||
export async function pickScript(path: string, step: number) {
|
||||
@@ -101,13 +120,17 @@ export async function pickScript(path: string, step: number) {
|
||||
await loadSchema(step)
|
||||
}
|
||||
|
||||
export async function createInlineScriptModule(language: RawScript.language, step: number, mode: FlowMode) {
|
||||
const code = initialCode(language, (mode === 'pull' && step == 0))
|
||||
export async function createInlineScriptModule(
|
||||
language: RawScript.language,
|
||||
step: number,
|
||||
mode: FlowMode
|
||||
) {
|
||||
const code = initialCode(language, mode === 'pull' && step == 0)
|
||||
flowStore.update((flow: Flow) => {
|
||||
flow.value.modules[step].value = {
|
||||
type: 'rawscript',
|
||||
content: code,
|
||||
language,
|
||||
language
|
||||
}
|
||||
|
||||
return flow
|
||||
@@ -152,7 +175,6 @@ export async function createScriptFromInlineScript(step: number) {
|
||||
const schemas = get(schemasStore)
|
||||
const user = get(userStore)
|
||||
|
||||
|
||||
const flowModuleValue = flow.value.modules[step].value
|
||||
|
||||
if (flowModuleValue.type != 'rawscript') {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
const config = {
|
||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||
content: [
|
||||
'./src/**/*.{html,js,svelte,ts}',
|
||||
"./node_modules/flowbite-svelte/**/*.{html,js,svelte,ts}",
|
||||
],
|
||||
|
||||
theme: {
|
||||
extend: {
|
||||
@@ -26,7 +29,12 @@ const config = {
|
||||
}
|
||||
},
|
||||
|
||||
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')]
|
||||
plugins: [
|
||||
require('@tailwindcss/forms'),
|
||||
require('@tailwindcss/typography'),
|
||||
require('flowbite/plugin')
|
||||
],
|
||||
darkMode: 'class',
|
||||
}
|
||||
|
||||
module.exports = config
|
||||
|
||||
Reference in New Issue
Block a user