feat: add editor bar to inline scripts of flows

This commit is contained in:
Ruben Fiszel
2022-07-23 11:20:22 +02:00
parent d95128e681
commit 7a6a2c982d
7 changed files with 253 additions and 201 deletions
+13
View File
@@ -33,6 +33,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"openapi-typescript-codegen": "^0.23.0",
"path-browserify": "^1.0.1",
"postcss": "^8.4.5",
"postcss-load-config": "^4.0.1",
"prettier": "^2.7.1",
@@ -2886,6 +2887,12 @@
"tslib": "^2.0.3"
}
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true
},
"node_modules/path-exists": {
"version": "4.0.0",
"dev": true,
@@ -6585,6 +6592,12 @@
"tslib": "^2.0.3"
}
},
"path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
"integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
"dev": true
},
"path-exists": {
"version": "4.0.0",
"dev": true,
+1
View File
@@ -30,6 +30,7 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-svelte3": "^4.0.0",
"openapi-typescript-codegen": "^0.23.0",
"path-browserify": "^1.0.1",
"postcss": "^8.4.5",
"postcss-load-config": "^4.0.1",
"prettier": "^2.7.1",
@@ -0,0 +1,213 @@
<script lang="ts">
import { ResourceService, ScriptService, VariableService } from '$lib/gen'
import { sendUserToast } from '$lib/utils'
import Icon from 'svelte-awesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
import { workspaceStore } from '$lib/stores'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
import ResourceEditor from './ResourceEditor.svelte'
import { Highlight } from 'svelte-highlight'
import { python, typescript } from 'svelte-highlight/languages'
import github from 'svelte-highlight/styles/github'
import Modal from './Modal.svelte'
import type Editor from './Editor.svelte'
export let lang: 'python3' | 'deno'
export let editor: Editor
export let websocketAlive: { pyright: boolean; black: boolean; deno: boolean }
let variablePicker: ItemPicker
let resourcePicker: ItemPicker
let scriptPicker: ItemPicker
let variableEditor: VariableEditor
let resourceEditor: ResourceEditor
let codeViewer: Modal
let codeContent: string = ''
async function loadVariables() {
let r: { name: string; path?: string; description?: string }[] = []
const variables = (
await VariableService.listVariable({ workspace: $workspaceStore ?? 'NO_W' })
).map((x) => {
return { name: x.path, ...x }
})
const rvariables = await VariableService.listContextualVariables({
workspace: $workspaceStore ?? 'NO_W'
})
r = r.concat(variables).concat(rvariables)
return r
}
async function loadScripts(): Promise<{ path: string; summary?: string }[]> {
return await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' })
}
</script>
<svelte:head>
{@html github}
</svelte:head>
<ItemPicker
bind:this={scriptPicker}
pickCallback={async (path, _) => {
codeContent = (
await ScriptService.getScriptByPath({
workspace: $workspaceStore ?? '',
path
})
).content
codeViewer.openModal()
}}
closeOnClick={false}
itemName="script"
extraField="summary"
loadItems={loadScripts}
/>
<Modal bind:this={codeViewer}>
<div slot="title">Code</div>
<div slot="content">
{#if lang == 'python3'}
<Highlight language={python} code={codeContent} />
{:else if lang == 'deno'}
<Highlight language={typescript} code={codeContent} />
{/if}
</div></Modal
>
<ItemPicker
bind:this={variablePicker}
pickCallback={(path, name) => {
if (!path) {
if (lang == 'deno') {
editor.insertAtCursor(`Deno.env.get('${name}')`)
} else {
if (!editor.getCode().includes('import os')) {
editor.insertAtBeginning('import os\n')
}
editor.insertAtCursor(`os.environ.get("${name}")`)
}
sendUserToast(`${name} inserted at cursor`)
} else {
if (lang == 'deno') {
if (!editor.getCode().includes('import * as wmill from')) {
editor.insertAtBeginning(
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
)
}
editor.insertAtCursor(`(await wmill.getVariable('${path}'))`)
} else {
if (!editor.getCode().includes('import wmill')) {
editor.insertAtBeginning('import wmill\n')
}
editor.insertAtCursor(`wmill.get_variable("${path}")`)
}
sendUserToast(`${name} inserted at cursor`)
}
}}
itemName="Variable"
extraField="name"
loadItems={loadVariables}
>
<div slot="submission" class="flex flex-row">
<div class="text-xs mr-2 align-middle">
The variable you were looking for does not exist yet?
</div>
<button
class="default-button-secondary"
type="button"
on:click={() => {
variableEditor.initNew()
}}
>
Create a new variable
</button>
</div>
</ItemPicker>
<ItemPicker
bind:this={resourcePicker}
pickCallback={(path, _) => {
if (lang == 'deno') {
if (!editor.getCode().includes('import * as wmill from')) {
editor.insertAtBeginning(
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
)
}
editor.insertAtCursor(`(await wmill.getResource('${path}'))`)
} else {
if (!editor.getCode().includes('import wmill')) {
editor.insertAtBeginning('import wmill\n')
}
editor.insertAtCursor(`wmill.get_resource("${path}")`)
}
sendUserToast(`${path} inserted at cursor`)
}}
itemName="Resource"
extraField="resource_type"
loadItems={async () =>
await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
>
<div slot="submission" class="flex flex-row">
<div class="text-xs mr-2 align-middle">
The resource you were looking for does not exist yet?
</div>
<button
class="default-button-secondary"
type="button"
on:click={() => {
resourceEditor.initNew()
}}
>
Create a new resource
</button>
</div>
</ItemPicker>
<ResourceEditor bind:this={resourceEditor} on:refresh={resourcePicker.openModal} />
<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={() => {
variablePicker.openModal()
}}
>Variable 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={() => {
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={() => {
scriptPicker.openModal()
}}
>Script explorer <Icon data={faSearch} scale={0.7} />
</button>
<button
class="default-button-secondary py-px max-h-8 text-xs"
on:click|stopPropagation={() => {
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>
</div>
@@ -5,6 +5,7 @@
import { faRobot } 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 FlowInputs from './flows/FlowInputs.svelte'
import FlowModuleHeader from './flows/FlowModuleHeader.svelte'
@@ -25,6 +26,9 @@
export let mod: FlowModule
export let args: Record<string, any> = {}
let editor: Editor
let websocketAlive = { pyright: false, black: false, deno: false }
$: schema = $schemasStore[i]
$: shouldPick = mod.value.path === '' && mod.value.language === undefined
$: previousSchema = i === 0 ? schemaToObject($flowStore?.schema) : $previewResults[i]
@@ -78,7 +82,11 @@
/>
{/if}
{#if mod.value.type === FlowModuleValue.type.RAWSCRIPT}
<div class="p-1">
<EditorBar {editor} {websocketAlive} lang={mod.value.language ?? 'deno'} />
</div>
<Editor
bind:this={editor}
class="h-80 border p-2 rounded"
bind:code={mod.value.content}
deno={mod.value.language === FlowModuleValue.language.DENO}
+12 -200
View File
@@ -1,13 +1,6 @@
<script lang="ts">
import {
JobService,
Job,
CompletedJob,
VariableService,
ResourceService,
ScriptService
} from '$lib/gen'
import { sendUserToast, emptySchema, displayDate } from '$lib/utils'
import { JobService, Job, CompletedJob, VariableService, ScriptService } 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'
@@ -17,7 +10,6 @@
faChevronUp,
faExclamationTriangle,
faMagic,
faSearch,
faSpinner,
faTimes
} from '@fortawesome/free-solid-svg-icons'
@@ -28,12 +20,7 @@
import TableCustom from './TableCustom.svelte'
import { check } from 'svelte-awesome/icons'
import Modal from './Modal.svelte'
import { Highlight } from 'svelte-highlight'
import { json, python, typescript } from 'svelte-highlight/languages'
import github from 'svelte-highlight/styles/github'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
import ResourceEditor from './ResourceEditor.svelte'
import { inferArgs } from '$lib/infer'
// @ts-ignore
@@ -41,6 +28,9 @@
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 { json, python, typescript } from 'svelte-highlight/languages'
// Exported
export let schema: Schema = emptySchema()
@@ -55,6 +45,10 @@
let websocketAlive = { pyright: false, black: false, deno: false }
let modalViewerTitle: string = ''
let modalViewerContent: any
let modalViewerMode: 'logs' | 'result' | 'code' = 'logs'
// Internal state
let editor: Editor
@@ -69,15 +63,6 @@
let pastPreviews: CompletedJob[] = []
let modalViewer: Modal
let modalViewerTitle: string = ''
let modalViewerContent: any
let modalViewerMode: 'logs' | 'result' | 'code' = 'logs'
let variablePicker: ItemPicker
let resourcePicker: ItemPicker
let scriptPicker: ItemPicker
let variableEditor: VariableEditor
let resourceEditor: ResourceEditor
let syncIteration: number = 0
let ITERATIONS_BEFORE_SLOW_REFRESH = 100
@@ -196,25 +181,6 @@
}
}
async function loadVariables() {
let r: { name: string; path?: string; description?: string }[] = []
const variables = (
await VariableService.listVariable({ workspace: $workspaceStore ?? 'NO_W' })
).map((x) => {
return { name: x.path, ...x }
})
const rvariables = await VariableService.listContextualVariables({
workspace: $workspaceStore ?? 'NO_W'
})
r = r.concat(variables).concat(rvariables)
return r
}
async function loadScripts(): Promise<{ path: string; summary?: string }[]> {
return await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' })
}
let syncCode: NodeJS.Timer
onMount(() => {
syncCode = setInterval(() => {
@@ -237,29 +203,6 @@
})
</script>
<svelte:head>
{@html github}
</svelte:head>
<ItemPicker
bind:this={scriptPicker}
pickCallback={async (path, _) => {
modalViewerMode = 'code'
modalViewerTitle = 'Script ' + path
modalViewerContent = (
await ScriptService.getScriptByPath({
workspace: $workspaceStore ?? '',
path
})
).content
modalViewer.openModal()
}}
closeOnClick={false}
itemName="script"
extraField="summary"
loadItems={loadScripts}
/>
<Modal bind:this={modalViewer}>
<div slot="title">{modalViewerTitle}</div>
<div slot="content">
@@ -279,99 +222,6 @@
</div></Modal
>
<ItemPicker
bind:this={variablePicker}
pickCallback={(path, name) => {
if (!path) {
if (lang == 'deno') {
getEditor().insertAtCursor(`Deno.env.get('${name}')`)
} else {
if (!getEditor().getCode().includes('import os')) {
getEditor().insertAtBeginning('import os\n')
}
getEditor().insertAtCursor(`os.environ.get("${name}")`)
}
sendUserToast(`${name} inserted at cursor`)
} else {
if (lang == 'deno') {
if (!getEditor().getCode().includes('import * as wmill from')) {
getEditor().insertAtBeginning(
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
)
}
getEditor().insertAtCursor(`(await wmill.getVariable('${path}'))`)
} else {
if (!getEditor().getCode().includes('import wmill')) {
getEditor().insertAtBeginning('import wmill\n')
}
getEditor().insertAtCursor(`wmill.get_variable("${path}")`)
}
sendUserToast(`${name} inserted at cursor`)
}
}}
itemName="Variable"
extraField="name"
loadItems={loadVariables}
>
<div slot="submission" class="flex flex-row">
<div class="text-xs mr-2 align-middle">
The variable you were looking for does not exist yet?
</div>
<button
class="default-button-secondary"
type="button"
on:click={() => {
variableEditor.initNew()
}}
>
Create a new variable
</button>
</div>
</ItemPicker>
<ItemPicker
bind:this={resourcePicker}
pickCallback={(path, _) => {
if (lang == 'deno') {
if (!getEditor().getCode().includes('import * as wmill from')) {
getEditor().insertAtBeginning(
`import * as wmill from 'https://deno.land/x/windmill@v${__pkg__.version}/index.ts'\n`
)
}
getEditor().insertAtCursor(`(await wmill.getResource('${path}'))`)
} else {
if (!getEditor().getCode().includes('import wmill')) {
getEditor().insertAtBeginning('import wmill\n')
}
getEditor().insertAtCursor(`wmill.get_resource("${path}")`)
}
sendUserToast(`${path} inserted at cursor`)
}}
itemName="Resource"
extraField="resource_type"
loadItems={async () =>
await ResourceService.listResource({ workspace: $workspaceStore ?? 'NO_W' })}
>
<div slot="submission" class="flex flex-row">
<div class="text-xs mr-2 align-middle">
The resource you were looking for does not exist yet?
</div>
<button
class="default-button-secondary"
type="button"
on:click={() => {
resourceEditor.initNew()
}}
>
Create a new resource
</button>
</div>
</ItemPicker>
<ResourceEditor bind:this={resourceEditor} on:refresh={resourcePicker.openModal} />
<VariableEditor bind:this={variableEditor} on:create={variablePicker.openModal} />
<VSplitPane
class="h-full"
topPanelSize={viewPreview ? '75%' : '90%'}
@@ -385,46 +235,7 @@
<top slot="top">
<div class="flex flex-col h-full">
<div class="header">
<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={() => {
variablePicker.openModal()
}}
>Variable 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={() => {
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={() => {
scriptPicker.openModal()
}}
>Script explorer <Icon data={faSearch} scale={0.7} />
</button>
<button
class="default-button-secondary py-px max-h-8 text-xs"
on:click|stopPropagation={() => {
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>
</div>
<EditorBar {editor} {lang} {websocketAlive} />
</div>
<div class="flex-1 overflow-hidden">
<Editor
@@ -438,6 +249,7 @@
formatAction={() => {
code = getEditor().getCode()
localStorage.setItem(path ?? 'last_save', code)
lastSave = code
}}
class="h-full"
deno={lang == 'deno'}
+1 -1
View File
@@ -183,7 +183,7 @@
{/if}
</div>
<h2 class="mt-2 md:mt-6">
Schedule<Tooltip class="mx-2">Schedules use CRON syntax. Milliseconds are mandatory.</Tooltip>
Schedule<Tooltip>Schedules use CRON syntax. Milliseconds are mandatory.</Tooltip>
</h2>
<div class="text-purple-500 text-2xs grow">{cronError}</div>
<div class="flex flex-row items-end max-w-5xl">
+5
View File
@@ -24,6 +24,11 @@ const config = {
'highlight.js/lib/core',
]
},
resolve: {
alias: {
path: "path-browserify",
},
},
};
export default config;