mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 08:01:25 +00:00
feat(frontend): Properly support resource (#1039)
* feat(frontend): Properly support resource * feat(frontend): remove unused import * feat(frontend): Fix build errors * feat(frontend): Fix table actions * feat(frontend): Fix table parameters * feat(frontend): Fix runnable inputs sync * feat(frontend): Done * fix * fix * feat(frontend): Fix typing issues * feat(frontend): Fix id generation Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
Vendored
+67
@@ -8,3 +8,70 @@ declare namespace svelte.JSX {
|
||||
onfinalize?: (event: CustomEvent<DndEvent<ItemType>> & { target: EventTarget & T }) => void
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'svelte-grid' {
|
||||
import type { SvelteComponentTyped } from 'svelte'
|
||||
|
||||
export interface Size {
|
||||
w: number
|
||||
h: number
|
||||
}
|
||||
|
||||
export interface Positon {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
interface ItemLayout extends Size, Positon {
|
||||
fixed?: boolean
|
||||
resizable?: boolean
|
||||
draggable?: boolean
|
||||
customDragger?: boolean
|
||||
customResizer?: boolean
|
||||
min?: Size
|
||||
max?: Size
|
||||
}
|
||||
|
||||
export type Item<T> = T & { [width: number]: ItemLayout; data: any }
|
||||
export type FilledItem<T> = T & { [width: number]: Required<ItemLayout>; data: any }
|
||||
|
||||
export interface Props<T> {
|
||||
fillSpace?: boolean
|
||||
items: FilledItem<T>[]
|
||||
rowHeight: number
|
||||
cols: [number, number][]
|
||||
gap?: [number, number]
|
||||
fastStart?: boolean
|
||||
throttleUpdate?: number
|
||||
throttleResize?: number
|
||||
|
||||
scroller?: undefined
|
||||
sensor?: number
|
||||
}
|
||||
|
||||
export interface Slots<T> {
|
||||
default: { item: ItemLayout; dataItem: Item<T> }
|
||||
}
|
||||
|
||||
export default class Grid<T = {}> extends SvelteComponentTyped<
|
||||
Props<T>,
|
||||
{
|
||||
pointerup: CustomEvent<{ id: string }>
|
||||
},
|
||||
Slots<T>
|
||||
> {}
|
||||
}
|
||||
|
||||
declare module 'svelte-grid/build/helper/index.mjs' {
|
||||
import { ItemLayout } from 'svelte-grid'
|
||||
|
||||
const x: {
|
||||
normalize(items: any[], col: any): unknown[]
|
||||
adjust(items: any[], col: any): unknown[]
|
||||
findSpace(item: any, items: any, cols: any): unknown
|
||||
|
||||
item<T>(obj: ItemLayout): Required<ItemLayout>
|
||||
}
|
||||
|
||||
export default x
|
||||
}
|
||||
|
||||
@@ -431,7 +431,8 @@
|
||||
...editorConfig(model, code, lang, automaticLayout, fixedOverflowWidgets),
|
||||
lineNumbers: 'off',
|
||||
fontSize: 16,
|
||||
suggestOnTriggerCharacters: true
|
||||
suggestOnTriggerCharacters: true,
|
||||
lineDecorationsWidth: 0
|
||||
})
|
||||
|
||||
const stdLib = { content: libStdContent, filePath: 'es5.d.ts' }
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
if (err.status === 404) {
|
||||
notfound = true
|
||||
}
|
||||
console.error(err)
|
||||
console.warn(err)
|
||||
}
|
||||
return isCompleted
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
export let id: string
|
||||
export let componentInput: AppInput | undefined
|
||||
export const staticOutputs: string[] = []
|
||||
|
||||
let result: any = undefined
|
||||
|
||||
export const staticOutputs: string[] = ['result', 'loading']
|
||||
</script>
|
||||
|
||||
<RunnableWrapper bind:result bind:componentInput {id}>
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
export let id: string | undefined = undefined
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: state = $worldStore?.state
|
||||
|
||||
$: input && $worldStore && handleConnection()
|
||||
|
||||
$: input && $state && input.type == 'template' && setValue()
|
||||
|
||||
function handleConnection() {
|
||||
@@ -29,9 +28,6 @@
|
||||
}
|
||||
|
||||
function computeGlobalContext() {
|
||||
Object.prototype.toString = function () {
|
||||
return JSON.stringify(this)
|
||||
}
|
||||
return Object.fromEntries(
|
||||
Object.entries($worldStore?.outputsById ?? {})
|
||||
.filter(([k, _]) => k != id)
|
||||
@@ -45,7 +41,6 @@
|
||||
}
|
||||
|
||||
function setValue() {
|
||||
console.log(computeGlobalContext())
|
||||
if (input.type === 'template' && isCodeInjection(input.eval)) {
|
||||
try {
|
||||
value = eval_like('`' + input.eval + '`', computeGlobalContext())
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<script lang="ts">
|
||||
import type { AppInput } from '../../inputType'
|
||||
|
||||
export let input: AppInput
|
||||
</script>
|
||||
|
||||
{#if input.type === 'connected'}
|
||||
<div class="flex gap-2 flex-col">
|
||||
<div>This component is expecting a connected input: </div>
|
||||
<div>
|
||||
<code class="bg-gray-50 border text-gray-600">{input.connection?.path} </code>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -10,12 +10,13 @@
|
||||
import type { AppInputs, Runnable } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { loadSchema, schemaToInputsSpec } from '../../utils'
|
||||
import { fieldTypeToTsType, loadSchema, schemaToInputsSpec } from '../../utils'
|
||||
import InputValue from './InputValue.svelte'
|
||||
import MissingConnectionWarning from './MissingConnectionWarning.svelte'
|
||||
|
||||
// Component props
|
||||
export let id: string
|
||||
export let inputs: AppInputs
|
||||
export let fields: AppInputs
|
||||
export let runnable: Runnable
|
||||
export let extraQueryParams: Record<string, any> = {}
|
||||
export let autoRefresh: boolean = true
|
||||
@@ -25,8 +26,10 @@
|
||||
const { worldStore, runnableComponents } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
onMount(() => {
|
||||
$runnableComponents[id] = async () => {
|
||||
await executeComponent()
|
||||
if (autoRefresh) {
|
||||
$runnableComponents[id] = async () => {
|
||||
await executeComponent()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -39,7 +42,7 @@
|
||||
$: mergedArgs = { ...extraQueryParams, ...runnableInputValues, ...args }
|
||||
|
||||
function setStaticInputsToArgs() {
|
||||
Object.entries(inputs ?? {}).forEach(([key, value]) => {
|
||||
Object.entries(fields ?? {}).forEach(([key, value]) => {
|
||||
if (value.type === 'static') {
|
||||
args[key] = value.value
|
||||
}
|
||||
@@ -48,15 +51,15 @@
|
||||
args = args
|
||||
}
|
||||
|
||||
$: inputs && setStaticInputsToArgs()
|
||||
$: fields && setStaticInputsToArgs()
|
||||
|
||||
function argMergedArgsValid(mergedArgs: Record<string, any>, testJobLoader) {
|
||||
if (!inputs) {
|
||||
if (!fields) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (
|
||||
Object.keys(inputs).length !==
|
||||
Object.keys(fields).length !==
|
||||
Object.keys(mergedArgs).length - Object.keys(extraQueryParams).length
|
||||
) {
|
||||
return false
|
||||
@@ -88,27 +91,55 @@
|
||||
workspace: string,
|
||||
path: string,
|
||||
runType: 'script' | 'flow' | 'hubscript'
|
||||
) {
|
||||
schema = await loadSchema(workspace, path, runType)
|
||||
): Promise<Schema> {
|
||||
return loadSchema(workspace, path, runType)
|
||||
}
|
||||
|
||||
// Only loads the schema
|
||||
$: if ($workspaceStore && runnable?.type === 'runnableByPath' && !schema) {
|
||||
// Remote schema needs to be loaded
|
||||
const { path, runType } = runnable
|
||||
$: runnable && loadSchemaAndInputsByName()
|
||||
|
||||
loadSchemaFromTriggerable($workspaceStore, path, runType)
|
||||
} else if (runnable?.type === 'runnableByName' && !schema) {
|
||||
const { inlineScript } = runnable
|
||||
// Inline scripts directly provide the schema
|
||||
if (inlineScript) {
|
||||
schema = inlineScript.schema
|
||||
async function loadSchemaAndInputsByName() {
|
||||
if (runnable?.type === 'runnableByName') {
|
||||
const { inlineScript } = runnable
|
||||
// Inline scripts directly provide the schema
|
||||
if (inlineScript) {
|
||||
const newSchema = inlineScript.schema
|
||||
schema = newSchema
|
||||
|
||||
const newFields = reloadInputs()
|
||||
|
||||
if (JSON.stringify(newFields) !== JSON.stringify(fields)) {
|
||||
fields = newFields
|
||||
setTimeout(() => {
|
||||
fields = newFields
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSchemaAndInputsByPath() {
|
||||
if ($workspaceStore && runnable?.type === 'runnableByPath') {
|
||||
// Remote schema needs to be loaded
|
||||
const { path, runType } = runnable
|
||||
const newSchema = await loadSchemaFromTriggerable($workspaceStore, path, runType)
|
||||
schema = newSchema
|
||||
|
||||
let schemaWithoutExtraQueries: Schema = JSON.parse(JSON.stringify(schema))
|
||||
|
||||
// Remove extra query params from the schema, which are not directly configurable by the user
|
||||
Object.keys(extraQueryParams).forEach((key) => {
|
||||
delete schemaWithoutExtraQueries.properties[key]
|
||||
})
|
||||
|
||||
fields = schemaToInputsSpec(schemaWithoutExtraQueries)
|
||||
}
|
||||
}
|
||||
|
||||
$: !schema && runnable?.type === 'runnableByPath' && loadSchemaAndInputsByPath()
|
||||
|
||||
// When the schema is loaded, we need to update the inputs spec
|
||||
// in order to render the inputs the component panel
|
||||
$: if (schema && Object.keys(schema.properties).length !== Object.keys(inputs ?? {}).length) {
|
||||
function reloadInputs() {
|
||||
let schemaWithoutExtraQueries: Schema = JSON.parse(JSON.stringify(schema))
|
||||
|
||||
// Remove extra query params from the schema, which are not directly configurable by the user
|
||||
@@ -116,7 +147,29 @@
|
||||
delete schemaWithoutExtraQueries.properties[key]
|
||||
})
|
||||
|
||||
inputs = schemaToInputsSpec(schemaWithoutExtraQueries)
|
||||
const result = {}
|
||||
const newInputs = schemaToInputsSpec(schemaWithoutExtraQueries)
|
||||
|
||||
if (!fields) {
|
||||
return newInputs
|
||||
}
|
||||
Object.keys(newInputs).forEach((key) => {
|
||||
const newInput = newInputs[key]
|
||||
const oldInput = fields[key]
|
||||
|
||||
// If the input is not present in the old inputs, add it
|
||||
if (oldInput === undefined) {
|
||||
result[key] = newInput
|
||||
} else {
|
||||
if (fieldTypeToTsType(newInput.fieldType) !== fieldTypeToTsType(oldInput.fieldType)) {
|
||||
result[key] = newInput
|
||||
} else {
|
||||
result[key] = oldInput
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
let schemaStripped: Schema | undefined = undefined
|
||||
@@ -145,11 +198,11 @@
|
||||
})
|
||||
}
|
||||
|
||||
$: schema && inputs && stripSchema(schema, inputs)
|
||||
$: schema && fields && stripSchema(schema, fields)
|
||||
|
||||
$: disabledArgs = Object.keys(inputs ?? {}).reduce(
|
||||
$: disabledArgs = Object.keys(fields ?? {}).reduce(
|
||||
(disabledArgsAccumulator: string[], inputName: string) => {
|
||||
if (inputs[inputName].type === 'static') {
|
||||
if (fields[inputName].type === 'static') {
|
||||
disabledArgsAccumulator = [...disabledArgsAccumulator, inputName]
|
||||
}
|
||||
return disabledArgsAccumulator
|
||||
@@ -162,6 +215,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
if (outputs?.loading.peak() === true) {
|
||||
return
|
||||
}
|
||||
|
||||
outputs?.loading?.set(true)
|
||||
|
||||
await testJobLoader?.abstractRun(() => {
|
||||
@@ -182,7 +239,7 @@
|
||||
}
|
||||
} else if (runnable?.type === 'runnableByPath') {
|
||||
const { path, runType } = runnable
|
||||
requestBody['path'] = `${runType}/${path}`
|
||||
requestBody['path'] = runType !== 'hubscript' ? `${runType}/${path}` : `script/${path}`
|
||||
}
|
||||
|
||||
return AppService.executeComponent({
|
||||
@@ -198,8 +255,8 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#each Object.keys(inputs ?? {}) as key}
|
||||
<InputValue {id} input={inputs[key]} bind:value={runnableInputValues[key]} />
|
||||
{#each Object.keys(fields ?? {}) as key}
|
||||
<InputValue {id} input={fields[key]} bind:value={runnableInputValues[key]} />
|
||||
{/each}
|
||||
|
||||
<TestJobLoader
|
||||
@@ -228,8 +285,14 @@
|
||||
{#if isValid}
|
||||
<slot />
|
||||
{:else}
|
||||
<Alert type="warning" size="xs" class="mt-2" title="Missing inputs">
|
||||
<Alert type="info" size="xs" class="mt-2" title="Missing inputs">
|
||||
Please fill in all the inputs
|
||||
|
||||
{#each Object.keys(fields ?? {}) as key}
|
||||
{#if fields[key].type === 'connected'}
|
||||
<MissingConnectionWarning input={fields[key]} />
|
||||
{/if}
|
||||
{/each}
|
||||
</Alert>
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
|
||||
<RunnableComponent
|
||||
bind:this={runnableComponent}
|
||||
bind:inputs={componentInput.fields}
|
||||
bind:fields={componentInput.fields}
|
||||
bind:result
|
||||
runnable={componentInput.runnable}
|
||||
{autoRefresh}
|
||||
|
||||
@@ -161,12 +161,12 @@
|
||||
|
||||
{#if actionButtons.length > 0}
|
||||
<td class="flex flex-row gap-2 p-4">
|
||||
{#each actionButtons as props, actionIndex (actionIndex)}
|
||||
{#each actionButtons as actionButton, actionIndex (actionIndex)}
|
||||
<AppButton
|
||||
{...props}
|
||||
extraQueryParams={{ row }}
|
||||
bind:componentInput={props.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[props.id]}
|
||||
{...actionButton}
|
||||
extraQueryParams={{ row: row.original, rowIndex }}
|
||||
bind:componentInput={actionButton.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
|
||||
/>
|
||||
{/each}
|
||||
</td>
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
import { userStore } from '$lib/stores'
|
||||
|
||||
import InlineScriptsPanel from './inlineScriptsPanel/InlineScriptsPanel.svelte'
|
||||
import TablePanel from './TablePanel.svelte'
|
||||
import { grid } from 'd3-dag'
|
||||
import SettingsPanel from './SettingsPanel.svelte'
|
||||
|
||||
export let app: App
|
||||
export let path: string
|
||||
@@ -127,13 +130,8 @@
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
{#each $appStore.grid as gridItem (gridItem.id)}
|
||||
{#if gridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel bind:component={gridItem.data} />
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#if $selectedComponent === undefined}
|
||||
<SettingsPanel />
|
||||
{:else}
|
||||
<div class="p-4 text-sm">No component selected.</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
{...component}
|
||||
bind:staticOutputs={$staticOutputs[component.id]}
|
||||
bind:componentInput={component.componentInput}
|
||||
bind:actionButtons={component.actionButtons}
|
||||
/>
|
||||
{:else if component.type === 'textcomponent'}
|
||||
<TextComponent
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, InlineScript } from '../types'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import Grid from 'svelte-grid'
|
||||
import ComponentEditor from './ComponentEditor.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
@@ -26,6 +26,7 @@
|
||||
$app.grid = $app.grid.filter((gridComponent) => {
|
||||
if (gridComponent.data.id === component.id) {
|
||||
if (
|
||||
gridComponent.data.componentInput?.type === 'runnable' &&
|
||||
gridComponent.data.componentInput?.runnable?.type === 'runnableByName' &&
|
||||
gridComponent.data.componentInput?.runnable.inlineScript
|
||||
) {
|
||||
@@ -64,7 +65,7 @@
|
||||
<Grid
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
rowHeight={64}
|
||||
rowHeight={32}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
on:pointerup={({ detail }) => {
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
import TablePanel from './TablePanel.svelte'
|
||||
|
||||
const { selectedComponent, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
</script>
|
||||
|
||||
{#each $app.grid as gridItem (gridItem.data.id)}
|
||||
{#if gridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel bind:component={gridItem.data} />
|
||||
{:else if gridItem.data.type === 'tablecomponent'}
|
||||
<TablePanel bind:component={gridItem.data} />
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -0,0 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, TableComponent } from '../types'
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
|
||||
export let component: TableComponent
|
||||
const { selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
</script>
|
||||
|
||||
{#each component.actionButtons as actionButton (actionButton.id)}
|
||||
{#if actionButton.id === $selectedComponent}
|
||||
<ComponentPanel
|
||||
bind:component={actionButton}
|
||||
onDelete={() => {
|
||||
component.actionButtons = component.actionButtons.filter((c) => c.id !== actionButton.id)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -18,8 +18,8 @@
|
||||
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-2:1': ['textinputcomponent', 'numberinputcomponent', 'selectcomponent'],
|
||||
'1:2-3:2': ['buttoncomponent', 'textcomponent', 'checkboxcomponent'],
|
||||
'1:2-2:2': ['textinputcomponent', 'numberinputcomponent', 'selectcomponent'],
|
||||
'2:2-6:4': ['displaycomponent'],
|
||||
'2:3-6:4': ['formcomponent'],
|
||||
'2:4-6:4': ['barchartcomponent', 'piechartcomponent'],
|
||||
@@ -50,7 +50,20 @@
|
||||
|
||||
function addComponent(appComponent: AppComponent) {
|
||||
const grid = $app.grid ?? []
|
||||
const id = getNextId(grid.map((gridItem) => gridItem.data.id))
|
||||
const id = getNextId(
|
||||
grid
|
||||
.map((gridItem) => {
|
||||
if (gridItem.data.type === 'tablecomponent') {
|
||||
return [
|
||||
gridItem.data.id,
|
||||
...gridItem.data.actionButtons.map((actionButton) => actionButton.id)
|
||||
]
|
||||
} else {
|
||||
return [gridItem.data.id]
|
||||
}
|
||||
})
|
||||
.flat()
|
||||
)
|
||||
|
||||
appComponent.id = id
|
||||
|
||||
@@ -74,7 +87,7 @@
|
||||
const max = getMaxDimensionsByComponent(appComponent.type, column)
|
||||
|
||||
newItem[column] = { ...newComponent, min, max, w: min.w, h: min.h }
|
||||
const position = gridHelp.findSpace(newItem, grid, column)
|
||||
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
|
||||
newItem[column] = { ...newItem[column], ...position, min, max }
|
||||
})
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
function subscribeToAllOutputs(observableOutputs: Record<string, Output<any>>) {
|
||||
if (observableOutputs) {
|
||||
outputs.forEach((output: string) => {
|
||||
observableOutputs[output].subscribe({
|
||||
observableOutputs[output]?.subscribe({
|
||||
next: (value) => {
|
||||
object[output] = value
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
if (component?.data.type) {
|
||||
return displayData[component?.data.type].name
|
||||
} else {
|
||||
return 'Table action'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
+1
-1
@@ -4,7 +4,6 @@
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import type { InlineScript } from '../../types'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { CheckCircle, Code2, X } from 'lucide-svelte'
|
||||
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
@@ -103,6 +102,7 @@
|
||||
inlineScript.content,
|
||||
inlineScript.schema
|
||||
)
|
||||
|
||||
inlineScript.schema = schema
|
||||
inlineScript = inlineScript
|
||||
}
|
||||
|
||||
+63
-46
@@ -3,65 +3,82 @@
|
||||
import FlowModuleScript from '$lib/components/flows/content/FlowModuleScript.svelte'
|
||||
import { getScriptByPath } from '$lib/utils'
|
||||
import { faCodeBranch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { r } from 'svelte-highlight/languages'
|
||||
import type { ResultAppInput } from '../../inputType'
|
||||
import type { AppInput, ResultAppInput } from '../../inputType'
|
||||
import { clearResultAppInput } from '../../utils'
|
||||
import EmptyInlineScript from './EmptyInlineScript.svelte'
|
||||
import InlineScriptEditor from './InlineScriptEditor.svelte'
|
||||
|
||||
export let componentInput: ResultAppInput
|
||||
export let componentInput: AppInput | undefined
|
||||
|
||||
async function fork(path: string) {
|
||||
const { content, language, schema } = await getScriptByPath(path)
|
||||
componentInput.runnable = {
|
||||
type: 'runnableByName',
|
||||
name: path,
|
||||
inlineScript: {
|
||||
content,
|
||||
language,
|
||||
schema,
|
||||
path
|
||||
if (componentInput && componentInput.type == 'runnable') {
|
||||
componentInput.runnable = {
|
||||
type: 'runnableByName',
|
||||
name: path,
|
||||
inlineScript: {
|
||||
content,
|
||||
language,
|
||||
schema,
|
||||
path
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('componentInput is undefined')
|
||||
}
|
||||
}
|
||||
|
||||
// $: inlineScript && (componentInput = componentInput)
|
||||
</script>
|
||||
|
||||
{#if componentInput?.runnable?.type === 'runnableByName' && componentInput?.runnable?.name !== undefined}
|
||||
{#if componentInput.runnable.inlineScript}
|
||||
<InlineScriptEditor
|
||||
bind:inlineScript={componentInput.runnable.inlineScript}
|
||||
bind:name={componentInput.runnable.name}
|
||||
on:delete={() => {
|
||||
componentInput = clearResultAppInput(componentInput)
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<EmptyInlineScript
|
||||
name={componentInput.runnable.name}
|
||||
on:new={(e) => {
|
||||
if (componentInput?.runnable?.type === 'runnableByName') {
|
||||
componentInput.runnable.inlineScript = e.detail
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{:else if componentInput?.runnable?.type === 'runnableByPath' && componentInput?.runnable?.path}
|
||||
<div class="p-2 h-full flex flex-col gap-2 ">
|
||||
<div>
|
||||
<Button
|
||||
size="xs"
|
||||
startIcon={{ icon: faCodeBranch }}
|
||||
on:click={() => {
|
||||
if (componentInput.runnable?.type === 'runnableByPath') {
|
||||
fork(componentInput.runnable.path)
|
||||
{#if componentInput && componentInput.type == 'runnable'}
|
||||
{#if componentInput?.runnable?.type === 'runnableByName' && componentInput?.runnable?.name !== undefined}
|
||||
{#if componentInput.runnable.inlineScript}
|
||||
<InlineScriptEditor
|
||||
bind:inlineScript={componentInput.runnable.inlineScript}
|
||||
bind:name={componentInput.runnable.name}
|
||||
on:delete={() => {
|
||||
if (componentInput && componentInput.type == 'runnable') {
|
||||
componentInput = clearResultAppInput(componentInput)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
/>
|
||||
{:else}
|
||||
<EmptyInlineScript
|
||||
name={componentInput.runnable.name}
|
||||
on:new={(e) => {
|
||||
if (
|
||||
componentInput &&
|
||||
componentInput.type == 'runnable' &&
|
||||
componentInput?.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
componentInput.runnable.inlineScript = e.detail
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{:else if componentInput?.runnable?.type === 'runnableByPath' && componentInput?.runnable?.path}
|
||||
<div class="p-2 h-full flex flex-col gap-2 ">
|
||||
<div>
|
||||
<Button
|
||||
size="xs"
|
||||
startIcon={{ icon: faCodeBranch }}
|
||||
on:click={() => {
|
||||
if (
|
||||
componentInput &&
|
||||
componentInput.type == 'runnable' &&
|
||||
componentInput.runnable?.type === 'runnableByPath'
|
||||
) {
|
||||
fork(componentInput.runnable.path)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Fork
|
||||
</Button>
|
||||
</div>
|
||||
<div class="border w-full">
|
||||
<FlowModuleScript path={componentInput.runnable.path} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="border w-full">
|
||||
<FlowModuleScript path={componentInput.runnable.path} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
+20
-5
@@ -2,7 +2,6 @@
|
||||
import { Badge } from '$lib/components/common'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppComponent, AppEditorContext } from '../../types'
|
||||
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
|
||||
|
||||
@@ -12,7 +11,7 @@
|
||||
|
||||
function selectInlineScript(id: string, subId?: string) {
|
||||
selectedScriptComponentId = subId ? subId : id
|
||||
$selectedComponent = id
|
||||
$selectedComponent = selectedScriptComponentId
|
||||
}
|
||||
|
||||
$: runnablesByName = $app.grid.reduce((acc, gridComponent) => {
|
||||
@@ -43,10 +42,26 @@
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}, [] as { name: string; id: string; subId?: string }[])
|
||||
|
||||
$: runnablesByPath = $app.grid.reduce((acc, gridComponent) => {
|
||||
const componentInput: AppInput = gridComponent.data.componentInput
|
||||
const component: AppComponent = gridComponent.data
|
||||
|
||||
if (component.type === 'tablecomponent') {
|
||||
component.actionButtons.forEach((actionButton) => {
|
||||
if (actionButton.componentInput?.type === 'runnable') {
|
||||
if (actionButton.componentInput.runnable?.type === 'runnableByPath') {
|
||||
acc.push({
|
||||
name: actionButton.componentInput.runnable.path,
|
||||
id: gridComponent.id,
|
||||
subId: actionButton.id
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const componentInput = component.componentInput
|
||||
|
||||
if (componentInput?.type === 'runnable') {
|
||||
if (componentInput.runnable?.type === 'runnableByPath') {
|
||||
@@ -57,7 +72,7 @@
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
}, [] as { name: string; id: string; subId?: string }[])
|
||||
|
||||
// When seleced component changes, update selectedScriptComponentId
|
||||
$: {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
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 { capitalize, classNames } from '$lib/utils'
|
||||
import { fieldTypeToTsType } from '../../utils'
|
||||
import Recompute from './Recompute.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
@@ -18,6 +18,7 @@
|
||||
import RunnableInputEditor from './inputEditor/RunnableInputEditor.svelte'
|
||||
import TemplateEditor from '$lib/components/TemplateEditor.svelte'
|
||||
import type { Output } from '../../rx'
|
||||
import { Alert } from '$lib/components/common'
|
||||
|
||||
export let component: AppComponent | undefined
|
||||
export let onDelete: (() => void) | undefined = undefined
|
||||
@@ -47,6 +48,25 @@
|
||||
$runnableComponents = $runnableComponents
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
component &&
|
||||
component.componentInput?.type === 'runnable' &&
|
||||
component.componentInput?.runnable?.type === 'runnableByName'
|
||||
) {
|
||||
const { name, inlineScript } = component.componentInput.runnable
|
||||
|
||||
if (inlineScript) {
|
||||
if (!$app.unusedInlineScripts) {
|
||||
$app.unusedInlineScripts = []
|
||||
}
|
||||
|
||||
$app.unusedInlineScripts.push({
|
||||
name,
|
||||
inlineScript
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function buildExtraLib(components: Record<string, Record<string, Output<any>>>): string {
|
||||
@@ -85,8 +105,25 @@ declare const ${k} = ${JSON.stringify(v)};
|
||||
</Badge>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<span
|
||||
class={classNames(
|
||||
'text-white px-2 text-2xs py-0.5 font-bold rounded-sm w-fit',
|
||||
'bg-indigo-500'
|
||||
)}
|
||||
>
|
||||
{`Selected component: ${component.id}`}
|
||||
</span>
|
||||
|
||||
<ComponentInputTypeEditor bind:componentInput={component.componentInput} />
|
||||
|
||||
{#if onDelete}
|
||||
<div class="w-full">
|
||||
<Alert title="Special arguments" size="xs">
|
||||
The row and the rowIndex are passed as arguments to the runnable.
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col w-full gap-2 my-2">
|
||||
{#if component.componentInput.type === 'static'}
|
||||
<StaticInputEditor bind:componentInput={component.componentInput} />
|
||||
@@ -114,6 +151,7 @@ declare const ${k} = ${JSON.stringify(v)};
|
||||
</svelte:fragment>
|
||||
|
||||
<InputsSpecsEditor
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={component.componentInput.fields}
|
||||
userInputEnabled={component.type !== 'buttoncomponent'}
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
export let inputSpecs: Record<string, StaticAppInput | ConnectedAppInput | UserAppInput>
|
||||
export let userInputEnabled: boolean = true
|
||||
export let staticOnly: boolean = false
|
||||
export let shouldCapitalize: boolean = true
|
||||
</script>
|
||||
|
||||
{#if inputSpecs}
|
||||
@@ -18,7 +19,9 @@
|
||||
{#if true}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex justify-between items-center gap-1">
|
||||
<span class="text-xs font-semibold">{capitalize(inputSpecKey)}</span>
|
||||
<span class="text-xs font-semibold">
|
||||
{shouldCapitalize ? capitalize(inputSpecKey) : inputSpecKey}
|
||||
</span>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
<Badge color="blue">
|
||||
@@ -36,14 +39,14 @@
|
||||
iconOnly
|
||||
/>
|
||||
<ToggleButton
|
||||
position={userInputEnabled ? 'center' : 'right'}
|
||||
position={userInputEnabled && input.format === undefined ? 'center' : 'right'}
|
||||
value="connected"
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
disabled={staticOnly}
|
||||
/>
|
||||
{#if userInputEnabled}
|
||||
{#if userInputEnabled && input.format === undefined}
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="user"
|
||||
@@ -59,7 +62,7 @@
|
||||
|
||||
<InputsSpecEditor
|
||||
bind:componentInput={inputSpecs[inputSpecKey]}
|
||||
canHide={userInputEnabled}
|
||||
canHide={userInputEnabled && input.format === undefined}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
if (event.currentTarget.checked) {
|
||||
recomputeIds = [...(recomputeIds ?? []), id]
|
||||
} else {
|
||||
recomputeIds = recomputeIds?.filter((id) => id !== id)
|
||||
recomputeIds = recomputeIds?.filter((x) => x !== id)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -43,7 +43,11 @@
|
||||
<Badge color="blue">{id}</Badge>
|
||||
</td>
|
||||
<td class="relative whitespace-nowrap px-4 py-2 ">
|
||||
<input type="checkbox" on:change={(event) => onChange(event, id)} />
|
||||
<input
|
||||
type="checkbox"
|
||||
on:change={(event) => onChange(event, id)}
|
||||
checked={recomputeIds?.includes(id)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Badge } from '$lib/components/common'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import { classNames } from '$lib/utils'
|
||||
@@ -8,12 +7,11 @@
|
||||
import { getContext } from 'svelte'
|
||||
import type { ButtonComponent, AppEditorContext, BaseAppComponent } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import ComponentPanel from './ComponentPanel.svelte'
|
||||
import TableActionLabel from './TableActionLabel.svelte'
|
||||
|
||||
export let components: (BaseAppComponent & ButtonComponent)[]
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function addComponent() {
|
||||
const grid = $app.grid ?? []
|
||||
@@ -48,10 +46,11 @@
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: '',
|
||||
value: ''
|
||||
type: 'runnable',
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined,
|
||||
defaultValue: undefined
|
||||
},
|
||||
recomputeIds: undefined,
|
||||
card: false
|
||||
@@ -59,8 +58,6 @@
|
||||
|
||||
components = [...components, newComponent]
|
||||
}
|
||||
|
||||
let openedComponentId: string | undefined = components[0]?.id
|
||||
</script>
|
||||
|
||||
<PanelSection title={`Table actions ${components.length > 0 ? `(${components.length})` : ''}`}>
|
||||
@@ -74,26 +71,16 @@
|
||||
iconOnly
|
||||
/>
|
||||
</svelte:fragment>
|
||||
{#if components.length > 0}
|
||||
<div class="w-full">
|
||||
<Alert title="Special argument" size="xs">
|
||||
The row is passed as an argument to the runnable.
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#each components as component}
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full text-xs font-bold py-1.5 px-2 cursor-pointer transition-all justify-between flex items-center border border-gray-3 rounded-md',
|
||||
'bg-white border-gray-300 hover:bg-gray-100 focus:bg-gray-100 text-gray-700',
|
||||
openedComponentId === component.id ? 'outline outline-gray-500 outline-offset-1' : ''
|
||||
$selectedComponent === component.id ? 'outline outline-blue-500 bg-red-400' : ''
|
||||
)}
|
||||
on:click={() => {
|
||||
if (openedComponentId === component.id) {
|
||||
openedComponentId = undefined
|
||||
} else {
|
||||
openedComponentId = component.id
|
||||
}
|
||||
$selectedComponent = component.id
|
||||
}}
|
||||
on:keypress
|
||||
>
|
||||
@@ -104,16 +91,5 @@
|
||||
Component: {component.id}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
{#if openedComponentId === component.id}
|
||||
<div class="w-full border">
|
||||
<ComponentPanel
|
||||
bind:component
|
||||
onDelete={() => {
|
||||
components = components.filter((c) => c.id !== component.id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</PanelSection>
|
||||
|
||||
+25
-8
@@ -4,6 +4,7 @@
|
||||
import type { StaticAppInput } from '../../../inputType'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
|
||||
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
|
||||
|
||||
export let componentInput: StaticAppInput | undefined
|
||||
export let canHide: boolean = false
|
||||
@@ -29,18 +30,34 @@
|
||||
{/each}
|
||||
</select>
|
||||
{:else if componentInput.fieldType === 'object'}
|
||||
<div class="border rounded-sm w-full">
|
||||
<SimpleEditor
|
||||
lang="json"
|
||||
code={JSON.stringify(componentInput.value, null, 2)}
|
||||
class="few-lines-editor"
|
||||
{#if componentInput.format}
|
||||
<ResourcePicker
|
||||
initialValue={componentInput.value?.split('$res:')[1] || ''}
|
||||
on:change={(e) => {
|
||||
if (componentInput?.type === 'static' && componentInput.value) {
|
||||
componentInput.value = JSON.parse(e.detail.code)
|
||||
let path = e.detail
|
||||
|
||||
if (componentInput && path) {
|
||||
componentInput.value = `$res:${path}`
|
||||
}
|
||||
}}
|
||||
resourceType={componentInput.format.split('-').length > 1
|
||||
? componentInput.format.substring('resource-'.length)
|
||||
: undefined}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="border rounded-sm w-full">
|
||||
<SimpleEditor
|
||||
lang="json"
|
||||
code={JSON.stringify(componentInput.value, null, 2)}
|
||||
class="few-lines-editor"
|
||||
on:change={(e) => {
|
||||
if (componentInput?.type === 'static' && componentInput.value) {
|
||||
componentInput.value = JSON.parse(e.detail.code)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if componentInput.fieldType === 'array'}
|
||||
<ArrayStaticInputEditor bind:componentInput {canHide} />
|
||||
{:else}
|
||||
|
||||
+11
-1
@@ -40,6 +40,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
function pickHubScript(path: string) {
|
||||
if (appInput.type === 'runnable') {
|
||||
appInput.runnable = {
|
||||
type: 'runnableByPath',
|
||||
path,
|
||||
runType: 'hubscript'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function pickInlineScript(name: string) {
|
||||
const unusedInlineScriptIndex = $app.unusedInlineScripts?.findIndex(
|
||||
(script) => script.name === name
|
||||
@@ -137,7 +147,7 @@
|
||||
{:else if tab == 'workspaceflows'}
|
||||
<WorkspaceFlowList on:pick={(e) => pickFlow(e.detail)} />
|
||||
{:else if tab == 'hubscripts'}
|
||||
<PickHubScript bind:filter on:pick={(e) => pickScript(e.detail.path)} />
|
||||
<PickHubScript bind:filter on:pick={(e) => pickHubScript(e.detail.path)} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,6 +79,7 @@ type InputConfiguration<T extends InputType, U, V extends InputType> = {
|
||||
fieldType: T
|
||||
defaultValue: U
|
||||
subFieldType?: V
|
||||
format?: string | undefined
|
||||
}
|
||||
|
||||
export type AppInput =
|
||||
@@ -91,12 +92,13 @@ export type AppInput =
|
||||
| AppInputSpec<'datetime', string>
|
||||
| AppInputSpec<'any', any>
|
||||
| AppInputSpec<'object', Record<string | number, any>>
|
||||
| AppInputSpec<'object', string>
|
||||
| (AppInputSpec<'select', string> & {
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
| AppInputSpec<'array', string[], 'text'>
|
||||
| AppInputSpec<'array', string[], 'textarea'>
|
||||
| AppInputSpec<'array', number[], 'number'>
|
||||
@@ -106,8 +108,8 @@ export type AppInput =
|
||||
| AppInputSpec<'array', string[], 'datetime'>
|
||||
| AppInputSpec<'array', object[], 'object'>
|
||||
| (AppInputSpec<'array', string[], 'select'> & {
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
|
||||
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
|
||||
export type ConnectedAppInput = Extract<AppInput, { type: 'connected' }>
|
||||
|
||||
@@ -17,14 +17,16 @@ export interface Input<T> extends Subscriber<T> {
|
||||
peak(): T | any | undefined
|
||||
}
|
||||
|
||||
|
||||
export type World = {
|
||||
outputsById: Record<string, Record<string, Output<any>>>
|
||||
connect: <T>(inputSpec: AppInput, next: (x: T) => void, previousValue: T) => Input<T>
|
||||
state: Writable<number>
|
||||
}
|
||||
|
||||
export function buildWorld(components: Record<string, string[]>, previousWorld: World | undefined): World {
|
||||
export function buildWorld(
|
||||
components: Record<string, string[]>,
|
||||
previousWorld: World | undefined
|
||||
): World {
|
||||
const newWorld = buildObservableWorld()
|
||||
const outputsById: Record<string, Record<string, Output<any>>> = {}
|
||||
const state = writable(0)
|
||||
@@ -32,7 +34,12 @@ export function buildWorld(components: Record<string, string[]>, previousWorld:
|
||||
outputsById[k] = {}
|
||||
|
||||
for (const o of outputs) {
|
||||
outputsById[k][o] = newWorld.newOutput(k, o, state, previousWorld?.outputsById[k]?.[o].peak())
|
||||
outputsById[k][o] = newWorld.newOutput(
|
||||
k,
|
||||
o,
|
||||
state,
|
||||
previousWorld?.outputsById[k]?.[o]?.peak()
|
||||
)
|
||||
}
|
||||
}
|
||||
state.update((x) => x + 1)
|
||||
@@ -47,7 +54,7 @@ export function buildObservableWorld() {
|
||||
if (inputSpec.type === 'static') {
|
||||
return {
|
||||
peak: () => inputSpec.value,
|
||||
next: () => { }
|
||||
next: () => {}
|
||||
}
|
||||
} else if (inputSpec.type === 'connected') {
|
||||
const input = cachedInput(next)
|
||||
@@ -57,7 +64,7 @@ export function buildObservableWorld() {
|
||||
if (!connection) {
|
||||
return {
|
||||
peak: () => undefined,
|
||||
next: () => { }
|
||||
next: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +78,7 @@ export function buildObservableWorld() {
|
||||
console.warn('Observable at ' + componentId + '.' + p + ' not found')
|
||||
return {
|
||||
peak: () => undefined,
|
||||
next: () => { }
|
||||
next: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,15 +87,19 @@ export function buildObservableWorld() {
|
||||
} else if (inputSpec.type === 'user') {
|
||||
return {
|
||||
peak: () => inputSpec.value,
|
||||
next: () => { }
|
||||
next: () => {}
|
||||
}
|
||||
} else {
|
||||
throw Error('Unknown input type ' + inputSpec)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function newOutput<T>(id: string, name: string, state: Writable<number>, previousValue: T): Output<T> {
|
||||
function newOutput<T>(
|
||||
id: string,
|
||||
name: string,
|
||||
state: Writable<number>,
|
||||
previousValue: T
|
||||
): Output<T> {
|
||||
const output = settableOutput<T>(state, previousValue)
|
||||
observables[`${id}.${name}`] = output
|
||||
return output
|
||||
@@ -123,7 +134,6 @@ export function settableOutput<T>(state: Writable<number>, previousValue: T): Ou
|
||||
|
||||
function subscribe(x: Subscriber<T>) {
|
||||
if (!subscribers.includes(x)) {
|
||||
|
||||
subscribers.push(x)
|
||||
|
||||
// Send the current value to the new subscriber if it already exists
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Schema } from '$lib/common'
|
||||
import { FlowService, ScriptService } from '$lib/gen'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import {
|
||||
BarChart4,
|
||||
Binary,
|
||||
@@ -44,6 +45,8 @@ export async function loadSchema(
|
||||
path
|
||||
})
|
||||
|
||||
await inferArgs(script.language, script.content, script.schema)
|
||||
|
||||
return script.schema
|
||||
}
|
||||
}
|
||||
@@ -56,9 +59,11 @@ export function schemaToInputsSpec(schema: Schema): AppInputs {
|
||||
type: 'static',
|
||||
defaultValue: property.default,
|
||||
value: undefined,
|
||||
visible: true,
|
||||
fieldType: property.type
|
||||
visible: property.format ? false : true,
|
||||
fieldType: property.type,
|
||||
format: property.format
|
||||
}
|
||||
|
||||
return accu
|
||||
}, {})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user