mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 16:02:14 +00:00
Inline script editor (#1029)
* feat(frontend): WIP * feat(frontend): WI * feat(frontend): Fix inline scripts * feat(frontend): Fix inline scripts
This commit is contained in:
@@ -9,7 +9,6 @@
|
||||
import { encodeState, sendUserToast, setQueryWithoutLoad } from '$lib/utils'
|
||||
import Path from './Path.svelte'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import Required from './Required.svelte'
|
||||
import ScriptEditor from './ScriptEditor.svelte'
|
||||
import ScriptSchema from './ScriptSchema.svelte'
|
||||
import CenteredPage from './CenteredPage.svelte'
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
let result: string = ''
|
||||
</script>
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper bind:componentInput {id} bind:result autoRefresh={false}>
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
{#if result === ''}
|
||||
<div class="text-gray-400 bg-gray-100 flex justify-center items-center h-full w-full">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
export let result: any = undefined
|
||||
export let forceSchemaDisplay: boolean = false
|
||||
|
||||
const { app, worldStore, runnableComponents } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { worldStore, runnableComponents } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
onMount(() => {
|
||||
$runnableComponents[id] = async () => {
|
||||
@@ -95,9 +95,11 @@
|
||||
|
||||
loadSchemaFromTriggerable($workspaceStore, path, runType)
|
||||
} else if (runnable?.type === 'runnableByName' && !schema) {
|
||||
const { inlineScriptName } = runnable
|
||||
const { inlineScript } = runnable
|
||||
// Inline scripts directly provide the schema
|
||||
schema = $app.inlineScripts[inlineScriptName].schema
|
||||
if (inlineScript) {
|
||||
schema = inlineScript.schema
|
||||
}
|
||||
}
|
||||
|
||||
// When the schema is loaded, we need to update the inputs spec
|
||||
@@ -165,12 +167,14 @@
|
||||
}
|
||||
|
||||
if (runnable?.type === 'runnableByName') {
|
||||
const { inlineScriptName } = runnable
|
||||
const { inlineScript } = runnable
|
||||
|
||||
requestBody['raw_code'] = {
|
||||
content: $app.inlineScripts[inlineScriptName].content,
|
||||
language: $app.inlineScripts[inlineScriptName].language,
|
||||
path: $app.inlineScripts[inlineScriptName].path
|
||||
if (inlineScript) {
|
||||
requestBody['raw_code'] = {
|
||||
content: inlineScript.content,
|
||||
language: inlineScript.language,
|
||||
path: inlineScript.path
|
||||
}
|
||||
}
|
||||
} else if (runnable?.type === 'runnableByPath') {
|
||||
const { path, runType } = runnable
|
||||
|
||||
@@ -11,11 +11,26 @@
|
||||
export let autoRefresh: boolean = true
|
||||
export let runnableComponent: RunnableComponent | undefined = undefined
|
||||
export let forceSchemaDisplay: boolean = false
|
||||
|
||||
function isRunnableDefined() {
|
||||
if (!componentInput) return false
|
||||
if (componentInput.type !== 'runnable') return false
|
||||
|
||||
if (
|
||||
(componentInput.runnable?.type === 'runnableByName' &&
|
||||
componentInput.runnable.inlineScript) ||
|
||||
(componentInput.runnable?.type === 'runnableByPath' && componentInput.runnable.path)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if componentInput === undefined}
|
||||
<slot />
|
||||
{:else if componentInput.type === 'runnable'}
|
||||
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
|
||||
<RunnableComponent
|
||||
bind:this={runnableComponent}
|
||||
bind:inputs={componentInput.fields}
|
||||
|
||||
@@ -55,6 +55,10 @@
|
||||
}
|
||||
|
||||
function setOptions(filteredResult: Array<Record<string, any>>) {
|
||||
if (!Array.isArray(result)) {
|
||||
return
|
||||
}
|
||||
|
||||
const headers = Array.from(new Set(result.flatMap((row) => Object.keys(row))))
|
||||
|
||||
$options = {
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
|
||||
<span
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute -top-5',
|
||||
'text-white px-2 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute -top-5',
|
||||
selected ? 'bg-indigo-500' : 'bg-gray-500'
|
||||
)}
|
||||
>
|
||||
{displayData[component.type].name} - {component.id}
|
||||
{component.id}
|
||||
</span>
|
||||
<button
|
||||
class={classNames(
|
||||
|
||||
@@ -47,7 +47,14 @@
|
||||
>
|
||||
<RecomputeAllComponents />
|
||||
|
||||
<Grid bind:items={$app.grid} rowHeight={64} let:dataItem cols={columnConfiguration}>
|
||||
<Grid
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
rowHeight={64}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
fillSpace={true}
|
||||
>
|
||||
{#each $app.grid as gridComponent (gridComponent.id)}
|
||||
{#if gridComponent.data.id === dataItem.data.id}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
||||
@@ -13,20 +13,22 @@
|
||||
import { isOpenStore } from './store'
|
||||
import { gridColumns } from '../../gridUtils'
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function getMinDimensionsByComponent(componentType: AppComponent['type'], column: number): Size {
|
||||
// Dimensions key formula: <mobile width>:<mobile height>-<desktop width>:<desktop height>
|
||||
const dimensions: Record<`${number}:${number}-${number}:${number}`, AppComponent['type'][]> = {
|
||||
'1:1-3:1': [ 'buttoncomponent', 'textcomponent', 'checkboxcomponent' ],
|
||||
'1:2-3:2': [ 'textinputcomponent', 'numberinputcomponent', 'selectcomponent' ],
|
||||
'2:2-6:4': [ 'displaycomponent' ],
|
||||
'2:3-6:4': [ 'formcomponent' ],
|
||||
'2:4-6:4': [ 'barchartcomponent', 'piechartcomponent' ],
|
||||
'3:4-12:4': [ 'tablecomponent' ],
|
||||
'1:1-3:1': ['buttoncomponent', 'textcomponent', 'checkboxcomponent'],
|
||||
'1:2-3:2': ['textinputcomponent', 'numberinputcomponent', 'selectcomponent'],
|
||||
'2:2-6:4': ['displaycomponent'],
|
||||
'2:3-6:4': ['formcomponent'],
|
||||
'2:4-6:4': ['barchartcomponent', 'piechartcomponent'],
|
||||
'3:4-12:4': ['tablecomponent']
|
||||
}
|
||||
// Finds the key that is associated with the component type and extracts the dimensions from it
|
||||
const [dimension] = Object.entries(dimensions).find(([_, value]) => value.includes(componentType)) || ['2:1-2:1']
|
||||
const [dimension] = Object.entries(dimensions).find(([_, value]) =>
|
||||
value.includes(componentType)
|
||||
) || ['2:1-2:1']
|
||||
const size = dimension.split('-')[column === 3 ? 0 : 1].split(':')
|
||||
return { w: +size[0], h: +size[1] }
|
||||
}
|
||||
@@ -53,19 +55,17 @@
|
||||
}
|
||||
|
||||
gridColumns.forEach((column) => {
|
||||
newItem[column] = newComponent
|
||||
const position = gridHelp.findSpace(newItem, grid, column)
|
||||
const min = getMinDimensionsByComponent(appComponent.type, column)
|
||||
|
||||
const max = { w: 12, h: 12 }
|
||||
|
||||
newItem[column].w = min.w
|
||||
newItem[column].h = min.h
|
||||
|
||||
newItem[column] = { ...newComponent, min, max, w: min.w, h: min.h }
|
||||
const position = gridHelp.findSpace(newItem, grid, column)
|
||||
newItem[column] = { ...newItem[column], ...position, min, max }
|
||||
})
|
||||
|
||||
$app.grid = [...grid, newItem]
|
||||
|
||||
$selectedComponent = id
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -36,27 +36,39 @@
|
||||
<PanelSection title="Outputs">
|
||||
{#each Object.entries($staticOutputs) as [componentId, outputs], index}
|
||||
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
|
||||
<button
|
||||
on:click={() => ($selectedComponent = componentId)}
|
||||
class={classNames(
|
||||
'px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit -mb-2',
|
||||
$selectedComponent === componentId
|
||||
? ' bg-indigo-500 text-white'
|
||||
: 'bg-gray-200 text-gray-500'
|
||||
)}
|
||||
>
|
||||
{getComponentNameById(componentId)} -
|
||||
{componentId}
|
||||
</button>
|
||||
<div class="flex flex-row justify-between w-full -mb-2 ">
|
||||
<button
|
||||
on:click={() => ($selectedComponent = componentId)}
|
||||
class={classNames(
|
||||
'px-2 text-2xs py-0.5 font-bold rounded-t-sm w-fit',
|
||||
$selectedComponent === componentId
|
||||
? ' bg-indigo-500 text-white'
|
||||
: 'bg-gray-200 text-gray-500'
|
||||
)}
|
||||
>
|
||||
{componentId}
|
||||
</button>
|
||||
<span
|
||||
class={classNames(
|
||||
'px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit',
|
||||
'bg-gray-500 text-white'
|
||||
)}
|
||||
>
|
||||
{getComponentNameById(componentId)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
on:click={() => ($selectedComponent = componentId)}
|
||||
class={classNames(
|
||||
'w-full py-2 border',
|
||||
$selectedComponent === componentId ? 'border border-blue-500' : 'cursor-pointer'
|
||||
'w-full py-2 border relative',
|
||||
$selectedComponent === componentId ? 'border border-blue-500 ' : 'cursor-pointer'
|
||||
)}
|
||||
>
|
||||
{#if $selectedComponent === componentId && $connectingInput?.opened}
|
||||
<div class="absolute top-0 left-0 w-full h-full bg-gray-500 bg-opacity-50 z-10" />
|
||||
{/if}
|
||||
<ComponentOutputViewer
|
||||
{outputs}
|
||||
{componentId}
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { Preview, Script } from '$lib/gen'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { Check, CheckCheck, CheckCircle, Code2, X } from 'lucide-svelte'
|
||||
import FlowScriptPicker from '$lib/components/flows/pickers/FlowScriptPicker.svelte'
|
||||
import type { ResultAppInput } from '../../inputType'
|
||||
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import type { Schema } from '$lib/common'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
|
||||
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
export let componentInput: ResultAppInput
|
||||
export let selectedScriptName: string | undefined = undefined
|
||||
|
||||
$: shouldDisplay =
|
||||
componentInput.runnable?.type === 'runnableByName' &&
|
||||
componentInput.runnable?.name === selectedScriptName
|
||||
const { appPath } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let validCode = false
|
||||
|
||||
async function inferInlineScriptSchema(
|
||||
language: Preview.language,
|
||||
content: string,
|
||||
schema: Schema
|
||||
): Promise<Schema> {
|
||||
try {
|
||||
await inferArgs(language, content, schema)
|
||||
validCode = true
|
||||
} catch (e) {
|
||||
console.error("Couldn't infer args", e)
|
||||
validCode = false
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
|
||||
async function createInlineScriptByLanguage(
|
||||
language: Preview.language,
|
||||
path: string,
|
||||
subkind: 'pgsql' | 'mysql' | undefined = undefined
|
||||
) {
|
||||
const fullPath = `${appPath}/inline-script/${path}`
|
||||
|
||||
const content = initialCode(language, Script.kind.SCRIPT, subkind)
|
||||
let schema: Schema = emptySchema()
|
||||
|
||||
schema = await inferInlineScriptSchema(language, content, schema)
|
||||
|
||||
const inlineScript = {
|
||||
content,
|
||||
language,
|
||||
path: fullPath,
|
||||
schema
|
||||
}
|
||||
if (componentInput?.runnable?.type === 'runnableByName') {
|
||||
componentInput.runnable.inlineScript = inlineScript
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if componentInput.runnable && componentInput.runnable.type === 'runnableByName' && componentInput.runnable.inlineScript}
|
||||
<InlineScriptEditorDrawer
|
||||
bind:this={inlineScriptEditorDrawer}
|
||||
bind:inlineScript={componentInput.runnable.inlineScript}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if shouldDisplay}
|
||||
{#if componentInput?.runnable?.type === 'runnableByName' && componentInput?.runnable?.inlineScript}
|
||||
<div class="h-full p-4 flex flex-col gap-2 ">
|
||||
<div class="flex w-full flex-row-reverse gap-2 items-center">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
if (selectedScriptName) {
|
||||
inlineScriptEditorDrawer?.openDrawer()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex gap-1 items-center">
|
||||
<Code2 size={16} />
|
||||
Open full editor
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
iconOnly
|
||||
startIcon={{ icon: faTrash }}
|
||||
on:click={() => {
|
||||
if (componentInput?.runnable?.type === 'runnableByName') {
|
||||
componentInput.runnable = undefined
|
||||
componentInput.fields = {}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if validCode}
|
||||
<Badge color="green">
|
||||
<CheckCircle size={16} />
|
||||
</Badge>
|
||||
{:else}
|
||||
<Badge color="red">
|
||||
<X size={16} />
|
||||
</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if componentInput?.runnable?.type === 'runnableByName' && componentInput?.runnable?.inlineScript}
|
||||
<div class="border h-full">
|
||||
<SimpleEditor
|
||||
class="flex flex-1 grow h-full"
|
||||
lang="typescript"
|
||||
bind:code={componentInput.runnable.inlineScript.content}
|
||||
fixedOverflowWidgets={false}
|
||||
on:change={async () => {
|
||||
if (
|
||||
componentInput?.runnable?.type === 'runnableByName' &&
|
||||
componentInput?.runnable?.inlineScript
|
||||
) {
|
||||
let schema = await inferInlineScriptSchema(
|
||||
componentInput?.runnable?.inlineScript?.language,
|
||||
componentInput.runnable.inlineScript.content,
|
||||
componentInput.runnable.inlineScript.schema
|
||||
)
|
||||
|
||||
componentInput.runnable.inlineScript.schema = schema
|
||||
|
||||
componentInput = componentInput
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col p-4 gap-2 text-sm">
|
||||
Please choose a language:
|
||||
<div class="flex gap-2 flex-row flex-wrap">
|
||||
{#each Object.values(Script.language) as lang}
|
||||
<FlowScriptPicker
|
||||
label={lang}
|
||||
{lang}
|
||||
on:click={() => {
|
||||
if (selectedScriptName) {
|
||||
createInlineScriptByLanguage(lang, selectedScriptName)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<FlowScriptPicker
|
||||
label={`PostgreSQL`}
|
||||
lang="pgsql"
|
||||
on:click={() => {
|
||||
if (selectedScriptName) {
|
||||
createInlineScriptByLanguage(Script.language.DENO, selectedScriptName, 'pgsql')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<FlowScriptPicker
|
||||
label={`MySQL`}
|
||||
lang="mysql"
|
||||
on:click={() => {
|
||||
if (selectedScriptName) {
|
||||
createInlineScriptByLanguage(Script.language.DENO, selectedScriptName, 'mysql')
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
+9
-53
@@ -1,58 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
import { Button, Drawer, DrawerContent } from '$lib/components/common'
|
||||
import ScriptEditor from '$lib/components/ScriptEditor.svelte'
|
||||
import { Preview } from '$lib/gen'
|
||||
import { DENO_INIT_CODE_CLEAR } from '$lib/script_helpers'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { InlineScript } from '../../types'
|
||||
|
||||
type InlineScript = {
|
||||
content: string
|
||||
language: Preview.language
|
||||
path: string
|
||||
schema: Schema
|
||||
}
|
||||
|
||||
const { app, appPath } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let scriptEditorDrawer: Drawer
|
||||
let selectedScript: InlineScript | undefined
|
||||
export let inlineScript: InlineScript
|
||||
|
||||
export function openDrawer(path: string) {
|
||||
if ($app.inlineScripts[path]) {
|
||||
selectedScript = $app.inlineScripts[path]
|
||||
scriptEditorDrawer.openDrawer?.()
|
||||
}
|
||||
}
|
||||
|
||||
export function createScript(): string {
|
||||
let index = 0
|
||||
let newScriptPath = `inline_script_${index}`
|
||||
|
||||
while ($app.inlineScripts?.[newScriptPath]) {
|
||||
newScriptPath = `inline_script_${++index}`
|
||||
}
|
||||
|
||||
const path = `${appPath}/inline-script/inline_script_${index}`
|
||||
|
||||
const inlineScript = {
|
||||
content: DENO_INIT_CODE_CLEAR,
|
||||
language: Preview.language.DENO,
|
||||
path,
|
||||
schema: emptySchema()
|
||||
}
|
||||
|
||||
if ($app.inlineScripts) {
|
||||
$app.inlineScripts[newScriptPath] = inlineScript
|
||||
} else {
|
||||
$app.inlineScripts = {
|
||||
[newScriptPath]: inlineScript
|
||||
}
|
||||
}
|
||||
|
||||
return newScriptPath
|
||||
export function openDrawer() {
|
||||
scriptEditorDrawer.openDrawer?.()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -63,12 +19,12 @@
|
||||
forceOverflowVisible
|
||||
on:close={scriptEditorDrawer.closeDrawer}
|
||||
>
|
||||
{#if selectedScript}
|
||||
{#if inlineScript}
|
||||
<ScriptEditor
|
||||
lang={selectedScript.language}
|
||||
bind:code={selectedScript.content}
|
||||
path={selectedScript.path}
|
||||
bind:schema={selectedScript.schema}
|
||||
lang={inlineScript.language}
|
||||
bind:code={inlineScript.content}
|
||||
path={inlineScript.path}
|
||||
bind:schema={inlineScript.schema}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
+11
-185
@@ -1,200 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { fade } from 'svelte/transition'
|
||||
import type { Schema } from '$lib/common'
|
||||
import { Drawer } from '$lib/components/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
|
||||
import { Preview } from '$lib/gen'
|
||||
import { DENO_INIT_CODE_CLEAR } from '$lib/script_helpers'
|
||||
import { classNames, emptySchema } from '$lib/utils'
|
||||
import { faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
|
||||
import { Pane } from 'svelte-splitpanes'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
|
||||
import { Code2 } from 'lucide-svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
|
||||
import InlineScriptsPanelList from './InlineScriptsPanelList.svelte'
|
||||
import InlineScriptEditor from './InlineScriptEditor.svelte'
|
||||
|
||||
const { app, appPath } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let newScriptPath: string
|
||||
let ignorePathError = false
|
||||
|
||||
$: isTakenPath = Object.keys($app.inlineScripts).includes(newScriptPath)
|
||||
|
||||
function createScript() {
|
||||
// To prevent the error message flashing up just before the drawer is closed
|
||||
ignorePathError = true
|
||||
const path = `${appPath}/inline-script/${newScriptPath}`
|
||||
const inlineScript = {
|
||||
content: DENO_INIT_CODE_CLEAR,
|
||||
language: Preview.language.DENO,
|
||||
path,
|
||||
schema: emptySchema()
|
||||
}
|
||||
|
||||
if ($app.inlineScripts) {
|
||||
$app.inlineScripts[newScriptPath] = inlineScript
|
||||
} else {
|
||||
$app.inlineScripts = {
|
||||
[newScriptPath]: inlineScript
|
||||
}
|
||||
}
|
||||
scriptCreationDrawer?.closeDrawer?.()
|
||||
selectedScript = inlineScript
|
||||
scriptEditorDrawer.openDrawer?.()
|
||||
}
|
||||
|
||||
function afterCreateScript() {
|
||||
newScriptPath = ''
|
||||
ignorePathError = false
|
||||
}
|
||||
|
||||
$: selectedScript = undefined as
|
||||
| { content: string; language: Preview.language; path: string; schema: Schema }
|
||||
| undefined
|
||||
let scriptEditorDrawer: Drawer
|
||||
|
||||
let scriptCreationDrawer: Drawer | undefined = undefined
|
||||
|
||||
$: selectedScriptName = undefined as string | undefined
|
||||
$: scriptsUsedByComponents = new Map<string, string>()
|
||||
$: {
|
||||
scriptsUsedByComponents.clear()
|
||||
|
||||
$app.grid
|
||||
.filter((gridComponent) => gridComponent)
|
||||
.forEach((gridComponent) => {
|
||||
if (
|
||||
gridComponent.data?.componentInput?.type === 'runnable' &&
|
||||
gridComponent.data.componentInput.runnable !== undefined &&
|
||||
gridComponent.data.componentInput.runnable.type === 'runnableByName'
|
||||
) {
|
||||
scriptsUsedByComponents.set(
|
||||
gridComponent.data.componentInput.runnable.inlineScriptName,
|
||||
gridComponent.data.id
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function deleteInlineScript() {
|
||||
const key = Object.keys($app.inlineScripts).find(
|
||||
(key) => $app.inlineScripts[key].path === selectedScript?.path
|
||||
)
|
||||
|
||||
if (key && $app.inlineScripts[key]) {
|
||||
delete $app.inlineScripts[key]
|
||||
$app = $app
|
||||
selectedScript = undefined
|
||||
}
|
||||
}
|
||||
|
||||
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
let selectedScriptName: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={scriptCreationDrawer} size="600px" on:afterClose={afterCreateScript}>
|
||||
<DrawerContent title="Script creation" on:close={scriptCreationDrawer.closeDrawer}>
|
||||
<label for="pathInput" class="text-sm font-semibold"> Script name </label>
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
autofocus
|
||||
id="pathInput"
|
||||
class="grow min-w-[150px]"
|
||||
bind:value={newScriptPath}
|
||||
on:keypress={(e) => e.key === 'Enter' && createScript()}
|
||||
/>
|
||||
<Button on:click={createScript} size="sm" disabled={isTakenPath} startIcon={{ icon: faPlus }}>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
{#if isTakenPath && !ignorePathError}
|
||||
<div transition:fade={{ duration: 100 }} class="text-sm text-red-600 h-5 mt-1">
|
||||
This name is already used.
|
||||
</div>
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<InlineScriptEditorDrawer bind:this={inlineScriptEditorDrawer} />
|
||||
|
||||
<SplitPanesWrapper>
|
||||
<Pane size={25}>
|
||||
<PanelSection title="Inline scripts">
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if $app.inlineScripts && Object.keys($app.inlineScripts).length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each $app.inlineScripts ? Object.entries($app.inlineScripts) : [] as [key, value], index}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScript?.path === value.path ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => {
|
||||
selectedScript = value
|
||||
selectedScriptName = key
|
||||
}}
|
||||
>
|
||||
<span class="text-xs">{key}</span>
|
||||
{#if scriptsUsedByComponents.get(key)}
|
||||
<Badge color="blue">{scriptsUsedByComponents.get(key)}</Badge>
|
||||
{:else}
|
||||
<Badge color="red">Unused</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-gray-500">No inline scripts</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
<InlineScriptsPanelList bind:selectedScriptName />
|
||||
</Pane>
|
||||
<Pane size={75}>
|
||||
{#key selectedScript?.path}
|
||||
{#if selectedScript}
|
||||
<div class="h-full p-4 flex flex-col gap-2 ">
|
||||
<div class="flex w-full flex-row-reverse gap-2 items-center">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
on:click={() => {
|
||||
if (selectedScriptName) {
|
||||
inlineScriptEditorDrawer?.openDrawer(selectedScriptName)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="flex gap-1 items-center">
|
||||
<Code2 size={16} />
|
||||
Open full editor
|
||||
</div>
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
iconOnly
|
||||
startIcon={{ icon: faTrash }}
|
||||
on:click={deleteInlineScript}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border h-full">
|
||||
<SimpleEditor
|
||||
class="flex flex-1 grow h-full"
|
||||
lang="typescript"
|
||||
bind:code={selectedScript.content}
|
||||
fixedOverflowWidgets={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/key}
|
||||
{#each $app.grid as gridComponent, index (index)}
|
||||
<InlineScriptEditor
|
||||
bind:componentInput={gridComponent.data.componentInput}
|
||||
{selectedScriptName}
|
||||
/>
|
||||
{/each}
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { Badge } from '$lib/components/common'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
|
||||
|
||||
export let selectedScriptName: string | undefined = undefined
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function selectInlineScript(name: string) {
|
||||
selectedScriptName = name
|
||||
}
|
||||
|
||||
$: componentInlineScripts = $app.grid.reduce((acc, gridComponent) => {
|
||||
const componentInput: AppInput = gridComponent.data.componentInput
|
||||
|
||||
if (componentInput?.type === 'runnable') {
|
||||
if (componentInput.runnable?.type === 'runnableByName') {
|
||||
acc.push({
|
||||
name: componentInput.runnable.name,
|
||||
id: gridComponent.id
|
||||
})
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
</script>
|
||||
|
||||
<PanelSection title="Inline scripts">
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if componentInlineScripts.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each componentInlineScripts as { name, id }, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptName === name ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(name)}
|
||||
>
|
||||
<span class="text-xs">{name}</span>
|
||||
<Badge color="dark-indigo">{id}</Badge>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-gray-500">No inline scripts</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import {
|
||||
AlignCenterHorizontal,
|
||||
AlignCenterVertical,
|
||||
AlignEndHorizontal,
|
||||
AlignEndVertical,
|
||||
AlignStartHorizontal,
|
||||
AlignStartVertical
|
||||
} from 'lucide-svelte'
|
||||
import type { AppComponent } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
</script>
|
||||
|
||||
{#if component.verticalAlignment !== undefined}
|
||||
<PanelSection title="Alignment">
|
||||
<div class="w-full text-xs font-semibold">Horizontal</div>
|
||||
|
||||
<div class="w-full">
|
||||
<ToggleButtonGroup bind:selected={component.horizontalAlignment}>
|
||||
<ToggleButton position="left" value="left" size="xs">
|
||||
<AlignStartVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="right" size="xs">
|
||||
<AlignEndVertical size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{#if component.type !== 'formcomponent'}
|
||||
<div class="w-full text-xs font-semibold">Vertical</div>
|
||||
<div class="w-full">
|
||||
<ToggleButtonGroup bind:selected={component.verticalAlignment}>
|
||||
<ToggleButton position="left" value="top" size="xs">
|
||||
<AlignStartHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="bottom" size="xs">
|
||||
<AlignEndHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
@@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
import { ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import { faArrowRight, faCode, faPen } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { AppInput } from '../../inputType'
|
||||
|
||||
export let componentInput: AppInput
|
||||
export let disableStatic: boolean = false
|
||||
</script>
|
||||
|
||||
{#if componentInput.fieldType !== 'any'}
|
||||
<div class="w-full">
|
||||
<ToggleButtonGroup bind:selected={componentInput.type}>
|
||||
<ToggleButton
|
||||
position="left"
|
||||
value="static"
|
||||
startIcon={{ icon: faPen }}
|
||||
size="xs"
|
||||
disable={disableStatic}
|
||||
>
|
||||
Static
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="connected"
|
||||
position="center"
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
>
|
||||
Connected
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="runnable" startIcon={{ icon: faCode }} size="xs">
|
||||
Computed
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1,45 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import {
|
||||
faArrowRight,
|
||||
faClose,
|
||||
faCode,
|
||||
faEdit,
|
||||
faMinimize,
|
||||
faPen,
|
||||
faTrashAlt
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
AlignStartHorizontal,
|
||||
AlignStartVertical,
|
||||
AlignCenterHorizontal,
|
||||
AlignCenterVertical,
|
||||
AlignEndHorizontal,
|
||||
AlignEndVertical
|
||||
} from 'lucide-svelte'
|
||||
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppComponent, AppEditorContext } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
|
||||
import TableActions from './TableActions.svelte'
|
||||
import StaticInputEditor from './StaticInputEditor.svelte'
|
||||
import ConnectedInputEditor from './ConnectedInputEditor.svelte'
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
|
||||
import Badge from '$lib/components/common/badge/Badge.svelte'
|
||||
import { capitalize } from '$lib/utils'
|
||||
import { fieldTypeToTsType } from '../../utils'
|
||||
import Recompute from './Recompute.svelte'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import RunnableSelector from './mainInput/RunnableSelector.svelte'
|
||||
import gridHelp from 'svelte-grid/build/helper/index.mjs'
|
||||
import { gridColumns } from '../../gridUtils'
|
||||
import InlineScriptEditorDrawer from '../inlineScriptsPanel/InlineScriptEditorDrawer.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import ComponentInputTypeEditor from './ComponentInputTypeEditor.svelte'
|
||||
import AlignmentEditor from './AlignmentEditor.svelte'
|
||||
import RunnableInputEditor from './inputEditor/RunnableInputEditor.svelte'
|
||||
|
||||
export let component: AppComponent | undefined
|
||||
export let onDelete: (() => void) | undefined = undefined
|
||||
|
||||
const { app, staticOutputs, runnableComponents, appPath } =
|
||||
const { app, staticOutputs, runnableComponents } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function removeGridElement() {
|
||||
@@ -69,12 +52,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
</script>
|
||||
|
||||
{#if component}
|
||||
<InlineScriptEditorDrawer bind:this={inlineScriptEditorDrawer} />
|
||||
|
||||
<div class="flex flex-col w-full divide-y">
|
||||
{#if component.componentInput}
|
||||
<PanelSection
|
||||
@@ -90,92 +70,15 @@
|
||||
</Badge>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{#if component.componentInput.fieldType !== 'any'}
|
||||
<div class="w-full">
|
||||
<ToggleButtonGroup bind:selected={component.componentInput.type}>
|
||||
<ToggleButton
|
||||
position="left"
|
||||
value="static"
|
||||
startIcon={{ icon: faPen }}
|
||||
size="xs"
|
||||
disable={component.type === 'buttoncomponent'}
|
||||
>
|
||||
Static
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
value="connected"
|
||||
position="center"
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
>
|
||||
Connected
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="runnable"
|
||||
startIcon={{ icon: faCode }}
|
||||
size="xs"
|
||||
>
|
||||
Computed
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<ComponentInputTypeEditor bind:componentInput={component.componentInput} />
|
||||
<div class="flex flex-col w-full gap-2 my-2">
|
||||
{#if component.componentInput.type === 'static'}
|
||||
<StaticInputEditor bind:componentInput={component.componentInput} />
|
||||
{:else if component.componentInput.type === 'connected' && component.componentInput !== undefined}
|
||||
<ConnectedInputEditor bind:componentInput={component.componentInput} />
|
||||
{:else if component && component.componentInput?.type === 'runnable' && component.componentInput.runnable}
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<span class="text-xs font-semibold">
|
||||
{component.componentInput.runnable.type === 'runnableByName'
|
||||
? component.componentInput.runnable.inlineScriptName
|
||||
: component.componentInput.runnable.path}
|
||||
</span>
|
||||
<div>
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
startIcon={{ icon: faEdit }}
|
||||
on:click={() => {
|
||||
if (
|
||||
component?.componentInput?.type === 'runnable' &&
|
||||
component?.componentInput?.runnable?.type === 'runnableByName' &&
|
||||
component?.componentInput?.runnable.inlineScriptName
|
||||
) {
|
||||
inlineScriptEditorDrawer.openDrawer(
|
||||
component.componentInput.runnable.inlineScriptName
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button
|
||||
size="xs"
|
||||
color="red"
|
||||
variant="border"
|
||||
startIcon={{ icon: faClose }}
|
||||
on:click={() => {
|
||||
if (component?.componentInput?.type === 'runnable') {
|
||||
component.componentInput.runnable = undefined
|
||||
component.componentInput.fields = {}
|
||||
component = component
|
||||
}
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<RunnableSelector
|
||||
inlineScripts={Object.keys($app.inlineScripts)}
|
||||
bind:componentInput={component.componentInput}
|
||||
{inlineScriptEditorDrawer}
|
||||
/>
|
||||
{:else if component.componentInput?.type === 'runnable' && component.componentInput !== undefined}
|
||||
<RunnableInputEditor bind:appInput={component.componentInput} />
|
||||
{/if}
|
||||
</div>
|
||||
{#if component.componentInput?.type === 'runnable' && Object.keys(component.componentInput.fields ?? {}).length > 0}
|
||||
@@ -213,41 +116,7 @@
|
||||
<TableActions bind:components={component.actionButtons} />
|
||||
{/if}
|
||||
|
||||
{#if component.verticalAlignment !== undefined}
|
||||
<PanelSection title="Alignment">
|
||||
<div class="w-full text-xs font-semibold">Horizontal</div>
|
||||
|
||||
<div class="w-full">
|
||||
<ToggleButtonGroup bind:selected={component.horizontalAlignment}>
|
||||
<ToggleButton position="left" value="left" size="xs">
|
||||
<AlignStartVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterVertical size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="right" size="xs">
|
||||
<AlignEndVertical size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{#if component.type !== 'formcomponent'}
|
||||
<div class="w-full text-xs font-semibold">Vertical</div>
|
||||
<div class="w-full">
|
||||
<ToggleButtonGroup bind:selected={component.verticalAlignment}>
|
||||
<ToggleButton position="left" value="top" size="xs">
|
||||
<AlignStartHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="center" size="xs">
|
||||
<AlignCenterHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="bottom" size="xs">
|
||||
<AlignEndHorizontal size={16} />
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
</PanelSection>
|
||||
{/if}
|
||||
<AlignmentEditor bind:component />
|
||||
{#if component.type === 'buttoncomponent' || component.type === 'formcomponent'}
|
||||
<Recompute bind:recomputeIds={component.recomputeIds} ownId={component.id} />
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, StaticAppInput, UserAppInput } from '../../inputType'
|
||||
import ConnectedInputEditor from './ConnectedInputEditor.svelte'
|
||||
import StaticInputEditor from './StaticInputEditor.svelte'
|
||||
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
|
||||
export let componentInput: StaticAppInput | ConnectedAppInput | UserAppInput
|
||||
export let canHide: boolean = false
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { faClose, faEdit } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { ResultAppInput } from '../../inputType'
|
||||
import InlineScriptEditorDrawer from '../inlineScriptsPanel/InlineScriptEditorDrawer.svelte'
|
||||
|
||||
export let appInput: ResultAppInput
|
||||
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
|
||||
function edit() {
|
||||
if (appInput.runnable?.type === 'runnableByName') {
|
||||
inlineScriptEditorDrawer?.openDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
if (appInput) {
|
||||
appInput.runnable = undefined
|
||||
appInput.fields = {}
|
||||
appInput = appInput
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if appInput.runnable && appInput.runnable.type === 'runnableByName' && appInput.runnable.inlineScript}
|
||||
<InlineScriptEditorDrawer
|
||||
bind:this={inlineScriptEditorDrawer}
|
||||
bind:inlineScript={appInput.runnable.inlineScript}
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex justify-between w-full items-center">
|
||||
<span class="text-xs font-semibold">
|
||||
{#if appInput.runnable?.type === 'runnableByName'}
|
||||
{appInput.runnable.name}
|
||||
{:else if appInput.runnable?.type === 'runnableByPath'}
|
||||
{appInput.runnable.path}
|
||||
{/if}
|
||||
</span>
|
||||
<div>
|
||||
{#if appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript}
|
||||
<Button size="xs" color="light" variant="border" startIcon={{ icon: faEdit }} on:click={edit}>
|
||||
Edit
|
||||
</Button>
|
||||
{/if}
|
||||
<Button size="xs" color="red" variant="border" startIcon={{ icon: faClose }} on:click={clear}>
|
||||
Clear
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{#if appInput.runnable?.type === 'runnableByName' && !appInput.runnable.inlineScript}
|
||||
<span class="text-xs text-gray-500">
|
||||
Please configure the language in the inline script panel
|
||||
</span>
|
||||
{/if}
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { StaticAppInput } from '../../inputType'
|
||||
import StaticInputEditor from './StaticInputEditor.svelte'
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
|
||||
export let value: any
|
||||
export let canHide: boolean = false
|
||||
|
||||
+2
-3
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import type { AppEditorContext } from '../../../types'
|
||||
import { Badge, Button } from '$lib/components/common'
|
||||
import { faArrowRight, faClose } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppInput } from '../../../inputType'
|
||||
|
||||
export let componentInput: AppInput
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
componentInput.type === 'connected' &&
|
||||
!componentInput.connection
|
||||
) {
|
||||
debugger
|
||||
componentInput.connection = $connectingInput.input.connection
|
||||
$connectingInput = {
|
||||
opened: false,
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import type { ResultAppInput } from '$lib/components/apps/inputType'
|
||||
import { isScriptByNameDefined, isScriptByPathDefined } from '$lib/components/apps/utils'
|
||||
import RunnableSelector from '../mainInput/RunnableSelector.svelte'
|
||||
import SelectedRunnable from '../SelectedRunnable.svelte'
|
||||
|
||||
export let appInput: ResultAppInput
|
||||
|
||||
$: isRunnableSelected = isScriptByPathDefined(appInput) || isScriptByNameDefined(appInput)
|
||||
</script>
|
||||
|
||||
{#if isRunnableSelected}
|
||||
<SelectedRunnable bind:appInput />
|
||||
{:else}
|
||||
<RunnableSelector bind:appInput />
|
||||
{/if}
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { staticValues } from '../componentsPanel/componentStaticValues'
|
||||
import type { StaticAppInput } from '../../inputType'
|
||||
import { staticValues } from '../../componentsPanel/componentStaticValues'
|
||||
import type { StaticAppInput } from '../../../inputType'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import ArrayStaticInputEditor from './ArrayStaticInputEditor.svelte'
|
||||
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
|
||||
|
||||
export let componentInput: StaticAppInput | undefined
|
||||
export let canHide: boolean = false
|
||||
+49
-25
@@ -4,24 +4,25 @@
|
||||
import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte'
|
||||
import { Building, Globe2 } from 'lucide-svelte'
|
||||
import InlineScriptList from './InlineScriptList.svelte'
|
||||
import type { AppInput } from '$lib/components/apps/inputType'
|
||||
import type { ResultAppInput } from '$lib/components/apps/inputType'
|
||||
import WorkspaceScriptList from './WorkspaceScriptList.svelte'
|
||||
import WorkspaceFlowList from './WorkspaceFlowList.svelte'
|
||||
import InlineScriptEditorDrawer from '../../inlineScriptsPanel/InlineScriptEditorDrawer.svelte'
|
||||
import type { AppEditorContext, GridItem, InlineScript } from '$lib/components/apps/types'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
type Tab = 'hubscripts' | 'workspacescripts' | 'workspaceflows' | 'inlinescripts'
|
||||
|
||||
export let inlineScripts: string[]
|
||||
export let componentInput: AppInput
|
||||
export let appInput: ResultAppInput
|
||||
|
||||
let tab: Tab = 'inlinescripts'
|
||||
let filter: string = ''
|
||||
|
||||
let picker: Drawer
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function pickScript(path: string) {
|
||||
if (componentInput.type === 'runnable') {
|
||||
componentInput.runnable = {
|
||||
if (appInput.type === 'runnable') {
|
||||
appInput.runnable = {
|
||||
type: 'runnableByPath',
|
||||
path,
|
||||
runType: 'script'
|
||||
@@ -30,8 +31,8 @@
|
||||
}
|
||||
|
||||
function pickFlow(path: string) {
|
||||
if (componentInput.type === 'runnable') {
|
||||
componentInput.runnable = {
|
||||
if (appInput.type === 'runnable') {
|
||||
appInput.runnable = {
|
||||
type: 'runnableByPath',
|
||||
path,
|
||||
runType: 'flow'
|
||||
@@ -39,16 +40,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
function pickInlineScript(inlineScriptName: string) {
|
||||
if (componentInput.type === 'runnable') {
|
||||
componentInput.runnable = {
|
||||
function pickInlineScript(name: string, inlineScript: InlineScript) {
|
||||
if (appInput.type === 'runnable') {
|
||||
appInput.runnable = {
|
||||
type: 'runnableByName',
|
||||
inlineScriptName
|
||||
name,
|
||||
inlineScript
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
function createScript(): string {
|
||||
let index = 0
|
||||
let newScriptPath = `inline_script_${index}`
|
||||
|
||||
const names = $app.grid.reduce((acc, gridItem: GridItem) => {
|
||||
const { componentInput } = gridItem.data
|
||||
|
||||
if (
|
||||
componentInput.type === 'runnable' &&
|
||||
componentInput.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
acc.push(componentInput.runnable.name)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [] as string[])
|
||||
|
||||
while (names.includes(newScriptPath)) {
|
||||
newScriptPath = `inline_script_${++index}`
|
||||
}
|
||||
|
||||
appInput.runnable = {
|
||||
type: 'runnableByName',
|
||||
name: newScriptPath,
|
||||
inlineScript: undefined
|
||||
}
|
||||
|
||||
appInput = appInput
|
||||
|
||||
return newScriptPath
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={picker} size="1000px">
|
||||
@@ -85,7 +117,7 @@
|
||||
<div class="flex flex-col gap-y-16">
|
||||
<div class="flex flex-col">
|
||||
{#if tab == 'inlinescripts'}
|
||||
<InlineScriptList {inlineScripts} on:pick={(e) => pickInlineScript(e.detail)} />
|
||||
<InlineScriptList on:pick={(e) => pickInlineScript('', e.detail)} />
|
||||
{:else if tab == 'workspacescripts'}
|
||||
<WorkspaceScriptList on:pick={(e) => pickScript(e.detail)} />
|
||||
{:else if tab == 'workspaceflows'}
|
||||
@@ -100,17 +132,9 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<InlineScriptEditorDrawer bind:this={inlineScriptEditorDrawer} />
|
||||
<div class="flex flex-row gap-2 justify-between">
|
||||
<div class="flex flex-col gap-2">
|
||||
<Button
|
||||
on:click={() => {
|
||||
const name = inlineScriptEditorDrawer.createScript()
|
||||
inlineScriptEditorDrawer.openDrawer(name)
|
||||
|
||||
setTimeout(() => {
|
||||
pickInlineScript(name)
|
||||
})
|
||||
}}
|
||||
on:click={createScript}
|
||||
size="sm"
|
||||
color="light"
|
||||
variant="border"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { staticValues } from './editor/componentsPanel/componentStaticValues'
|
||||
import type { InlineScript } from './types'
|
||||
|
||||
export type InputType =
|
||||
| 'text'
|
||||
@@ -46,7 +47,8 @@ type RunnableByPath = {
|
||||
}
|
||||
|
||||
type RunnableByName = {
|
||||
inlineScriptName: string
|
||||
name: string
|
||||
inlineScript: InlineScript | undefined
|
||||
type: 'runnableByName'
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,6 @@ export type InlineScript = {
|
||||
|
||||
export type App = {
|
||||
grid: GridItem[]
|
||||
inlineScripts: Record<string, InlineScript>
|
||||
title: string
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { FlowService, ScriptService } from '$lib/gen'
|
||||
import {
|
||||
BarChart4,
|
||||
Binary,
|
||||
BoxSelect,
|
||||
CircleDot,
|
||||
FormInput,
|
||||
Inspect,
|
||||
@@ -16,7 +15,7 @@ import {
|
||||
Type,
|
||||
ToggleLeft
|
||||
} from 'lucide-svelte'
|
||||
import type { AppInputs, InputType } from './inputType'
|
||||
import type { AppInput, AppInputs, InputType } from './inputType'
|
||||
import type { AppComponent } from './types'
|
||||
|
||||
export async function loadSchema(
|
||||
@@ -43,8 +42,6 @@ export async function loadSchema(
|
||||
path
|
||||
})
|
||||
|
||||
debugger
|
||||
|
||||
return script.schema
|
||||
}
|
||||
}
|
||||
@@ -163,3 +160,27 @@ export function fieldTypeToTsType(inputType: InputType): string {
|
||||
return 'string'
|
||||
}
|
||||
}
|
||||
|
||||
export function isScriptByNameDefined(appInput: AppInput | undefined): boolean {
|
||||
if (!appInput) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (appInput.type === 'runnable' && appInput.runnable?.type == 'runnableByName') {
|
||||
return Boolean(appInput.runnable?.name)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
export function isScriptByPathDefined(appInput: AppInput | undefined): boolean {
|
||||
if (!appInput) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (appInput.type === 'runnable' && appInput.runnable?.type == 'runnableByPath') {
|
||||
return Boolean(appInput.runnable?.path)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user