mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
App bugfix (#1049)
* adapt static adapter to cloudflare * fix(frontend): Various bug fix * fix typecheck * numerous fixes * fix all connects * fix all connects * more improvements * more improvements * everything work? Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
Vendored
+2
-2
@@ -37,7 +37,7 @@ declare module 'svelte-grid' {
|
||||
|
||||
export interface Props<T> {
|
||||
fillSpace?: boolean
|
||||
items: FilledItem<T>[]
|
||||
items: FilledItem<T>[],
|
||||
rowHeight: number
|
||||
cols: [number, number][]
|
||||
gap?: [number, number]
|
||||
@@ -59,7 +59,7 @@ declare module 'svelte-grid' {
|
||||
pointerup: CustomEvent<{ id: string }>
|
||||
},
|
||||
Slots<T>
|
||||
> {}
|
||||
> { }
|
||||
}
|
||||
|
||||
declare module 'svelte-grid/build/helper/index.mjs' {
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
export let pickForField: string | undefined = undefined
|
||||
export let variableEditor: VariableEditor | undefined = undefined
|
||||
export let itemPicker: ItemPicker | undefined = undefined
|
||||
export let noVariablePicker = false
|
||||
|
||||
let monaco: SimpleEditor | undefined = undefined
|
||||
let monacoTemplate: TemplateEditor | undefined = undefined
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let schema: Schema = emptySchema()
|
||||
if (!schema) {
|
||||
schema = emptySchema()
|
||||
}
|
||||
|
||||
let schemaModal: SchemaModal
|
||||
let schemaString: string = ''
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
export let shouldHideNoInputs: boolean = false
|
||||
export let compact = false
|
||||
export let password: string | undefined = undefined
|
||||
export let noVariablePicker = false
|
||||
|
||||
let clazz: string = ''
|
||||
export { clazz as class }
|
||||
@@ -48,8 +49,8 @@
|
||||
$: schema?.properties && removeExtraKey()
|
||||
|
||||
let pickForField: string | undefined
|
||||
let itemPicker: ItemPicker
|
||||
let variableEditor: VariableEditor
|
||||
let itemPicker: ItemPicker | undefined = undefined
|
||||
let variableEditor: VariableEditor | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="w-full {clazz}">
|
||||
@@ -66,6 +67,7 @@
|
||||
bind:extraLib
|
||||
{variableEditor}
|
||||
{itemPicker}
|
||||
{noVariablePicker}
|
||||
bind:pickForField
|
||||
/>
|
||||
{:else if typeof args == 'object'}
|
||||
@@ -102,40 +104,42 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<ItemPicker
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, _) => {
|
||||
if (pickForField) {
|
||||
if (inputTransform) {
|
||||
args[pickForField].value = '$var:' + path
|
||||
} else {
|
||||
args[pickForField] = '$var:' + path
|
||||
{#if !noVariablePicker}
|
||||
<ItemPicker
|
||||
bind:this={itemPicker}
|
||||
pickCallback={(path, _) => {
|
||||
if (pickForField) {
|
||||
if (inputTransform) {
|
||||
args[pickForField].value = '$var:' + path
|
||||
} else {
|
||||
args[pickForField] = '$var:' + path
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
itemName="Variable"
|
||||
extraField="name"
|
||||
loadItems={async () =>
|
||||
(await VariableService.listVariable({ workspace: $workspaceStore ?? '' })).map((x) => ({
|
||||
name: x.path,
|
||||
...x
|
||||
}))}
|
||||
>
|
||||
<div
|
||||
slot="submission"
|
||||
class="flex flex-row-reverse w-full p-5 bg-white border-t border-gray-200 rounded-bl-lg rounded-br-lg"
|
||||
}}
|
||||
itemName="Variable"
|
||||
extraField="name"
|
||||
loadItems={async () =>
|
||||
(await VariableService.listVariable({ workspace: $workspaceStore ?? '' })).map((x) => ({
|
||||
name: x.path,
|
||||
...x
|
||||
}))}
|
||||
>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
on:click={() => {
|
||||
variableEditor?.initNew?.()
|
||||
}}
|
||||
<div
|
||||
slot="submission"
|
||||
class="flex flex-row-reverse w-full p-5 bg-white border-t border-gray-200 rounded-bl-lg rounded-br-lg"
|
||||
>
|
||||
Create a new variable
|
||||
</Button>
|
||||
</div>
|
||||
</ItemPicker>
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
size="sm"
|
||||
on:click={() => {
|
||||
variableEditor?.initNew?.()
|
||||
}}
|
||||
>
|
||||
Create a new variable
|
||||
</Button>
|
||||
</div>
|
||||
</ItemPicker>
|
||||
|
||||
<VariableEditor bind:this={variableEditor} on:create={itemPicker.openDrawer} />
|
||||
<VariableEditor bind:this={variableEditor} on:create={itemPicker.openDrawer} />
|
||||
{/if}
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
notfound = false
|
||||
} catch (err) {
|
||||
intervalId && clearIntervalAsync(intervalId!)
|
||||
isLoading = false
|
||||
clearCurrentJob()
|
||||
if (err.status === 404) {
|
||||
notfound = true
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let options: {
|
||||
@@ -8,20 +9,30 @@
|
||||
export let checked: boolean = false
|
||||
export let disabled = false
|
||||
|
||||
export let size: 'sm' | 'xs' = 'sm'
|
||||
const id = (Math.random() + 1).toString(36).substring(10)
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<span class={$$props.class}>
|
||||
<label for={id} class="inline-flex items-center mt-2 duration-200 {disabled ? 'grayscale opacity-50' : 'cursor-pointer'}">
|
||||
<span class="{$$props.class} z-auto">
|
||||
<label
|
||||
for={id}
|
||||
class="inline-flex items-center mt-2 duration-200 {disabled
|
||||
? 'grayscale opacity-50'
|
||||
: 'cursor-pointer'}"
|
||||
>
|
||||
{#if Boolean(options?.left)}
|
||||
<span class="mr-2 text-sm font-medium duration-200 {disabled ? 'text-gray-600' : 'text-gray-900'}">
|
||||
<span
|
||||
class="mr-2 text-sm font-medium duration-200 {disabled ? 'text-gray-600' : 'text-gray-900'}"
|
||||
>
|
||||
{options?.left}
|
||||
</span>
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="relative" on:click|stopPropagation={() => {}}>
|
||||
<div class="relative" on:pointerdown on:click|stopPropagation>
|
||||
<input
|
||||
on:focus
|
||||
on:click
|
||||
{disabled}
|
||||
type="checkbox"
|
||||
value={false}
|
||||
@@ -40,7 +51,11 @@
|
||||
/>
|
||||
</div>
|
||||
{#if Boolean(options?.right)}
|
||||
<span class="ml-2 text-sm font-medium duration-200 {disabled ? 'text-gray-500' : 'text-gray-900'}">
|
||||
<span
|
||||
class="ml-2 text-sm font-medium duration-200
|
||||
{disabled ? 'text-gray-500' : 'text-gray-900'}
|
||||
{size === 'xs' ? 'text-xs' : 'text-sm'}"
|
||||
>
|
||||
{options?.right}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import { faArrowRight, faRefresh, faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faSpinner } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
@@ -17,12 +17,13 @@
|
||||
export let extraQueryParams: Record<string, any> = {}
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
const { runnableComponents, worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let labelValue: string
|
||||
let color: ButtonType.Color
|
||||
let size: ButtonType.Size
|
||||
let runnableComponent: RunnableComponent
|
||||
@@ -35,6 +36,10 @@
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
$: outputs?.loading.subscribe({
|
||||
next: (value) => {
|
||||
isLoading = value
|
||||
@@ -58,9 +63,15 @@
|
||||
{extraQueryParams}
|
||||
autoRefresh={false}
|
||||
>
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<Button
|
||||
on:click={() => {
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={(e) => {
|
||||
e?.stopPropagation()
|
||||
e?.preventDefault()
|
||||
ownClick = true
|
||||
runnableComponent?.runComponent()
|
||||
|
||||
|
||||
@@ -50,7 +50,12 @@
|
||||
const options = {
|
||||
responsive: true,
|
||||
animation: false,
|
||||
maintainAspectRatio: false
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$: data = {
|
||||
@@ -67,7 +72,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.labels} bind:value={labels} />
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper autoRefresh bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
<Bar {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
<InputValue {id} input={configuration.theme} bind:value={theme} />
|
||||
<InputValue {id} input={configuration.labels} bind:value={labels} />
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result>
|
||||
<RunnableWrapper autoRefresh bind:componentInput {id} bind:result>
|
||||
{#if result}
|
||||
<Pie {data} {options} />
|
||||
{/if}
|
||||
|
||||
@@ -34,6 +34,10 @@
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
$: outputs?.loading.subscribe({
|
||||
next: (value) => {
|
||||
isLoading = value
|
||||
@@ -60,6 +64,10 @@
|
||||
>
|
||||
<AlignWrapper {horizontalAlignment}>
|
||||
<Button
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
on:click={() => {
|
||||
runnableComponent?.runComponent()
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
export let noWFull = false
|
||||
|
||||
function tailwindHorizontalAlignment(horizontalAlignment) {
|
||||
switch (horizontalAlignment) {
|
||||
@@ -27,7 +28,8 @@
|
||||
}
|
||||
|
||||
$: classes = classNames(
|
||||
'flex w-full',
|
||||
'flex z-auto',
|
||||
noWFull ? '' : 'w-full',
|
||||
tailwindHorizontalAlignment(horizontalAlignment),
|
||||
tailwindVerticalAlignment(verticalAlignment),
|
||||
verticalAlignment ? 'h-full' : ''
|
||||
|
||||
@@ -10,16 +10,26 @@
|
||||
export let input: AppInput
|
||||
export let value: T
|
||||
export let id: string | undefined = undefined
|
||||
export let row: Record<string, any> = {}
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: input && setDefault()
|
||||
$: state = $worldStore?.state
|
||||
$: input && $worldStore && handleConnection()
|
||||
$: input && $worldStore && row && handleConnection()
|
||||
$: input && $state && input.type == 'template' && setValue()
|
||||
|
||||
function setDefault() {
|
||||
if (!value && input.defaultValue) {
|
||||
value = input.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
function handleConnection() {
|
||||
if (input.type === 'connected') {
|
||||
$worldStore?.connect<any>(input, onValueChange, value)
|
||||
$worldStore?.connect<any>(input, onValueChange)
|
||||
} else if (input.type === 'row') {
|
||||
value = row[input.column]
|
||||
} else if (input.type === 'static' || input.type == 'template') {
|
||||
setValue()
|
||||
} else {
|
||||
@@ -93,9 +103,11 @@
|
||||
const hasSubPath = ['.', '['].some((x) => path.includes(x))
|
||||
|
||||
if (hasSubPath) {
|
||||
// Must remove top level property from path
|
||||
// Which was manually added, i.e. result
|
||||
const realPath = path.split('.').slice(1).join('.')
|
||||
const realPath = path
|
||||
.replace(/\[(\w+)\]/g, '.$1')
|
||||
.split('.')
|
||||
.slice(1)
|
||||
.join('.')
|
||||
|
||||
value = accessPropertyByPath<T>(newValue, realPath)
|
||||
} else {
|
||||
|
||||
@@ -13,9 +13,14 @@
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
loading: Output<boolean>
|
||||
result: Output<any>
|
||||
}
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
function setOutput() {
|
||||
if (outputs) {
|
||||
outputs.result?.set(result)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
|
||||
import { AppService, type CompletedJob } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import type { AppInputs, Runnable } from '../../inputType'
|
||||
import type { Output } from '../../rx'
|
||||
@@ -39,16 +40,15 @@
|
||||
let testIsLoading = false
|
||||
let runnableInputValues: Record<string, any> = {}
|
||||
|
||||
$: mergedArgs = { ...extraQueryParams, ...runnableInputValues, ...args }
|
||||
|
||||
function setStaticInputsToArgs() {
|
||||
let nargs = {}
|
||||
Object.entries(fields ?? {}).forEach(([key, value]) => {
|
||||
if (value.type === 'static') {
|
||||
args[key] = value.value
|
||||
nargs[key] = value.value
|
||||
}
|
||||
})
|
||||
|
||||
args = args
|
||||
args = nargs
|
||||
}
|
||||
|
||||
$: fields && setStaticInputsToArgs()
|
||||
@@ -76,7 +76,10 @@
|
||||
return areAllArgsValid
|
||||
}
|
||||
|
||||
$: isValid = argMergedArgsValid(mergedArgs, testJobLoader)
|
||||
$: isValid = argMergedArgsValid(
|
||||
{ ...extraQueryParams, ...runnableInputValues, ...args },
|
||||
testJobLoader
|
||||
)
|
||||
|
||||
// Test job internal state
|
||||
let testJob: CompletedJob | undefined = undefined
|
||||
@@ -87,12 +90,16 @@
|
||||
loading: Output<boolean>
|
||||
}
|
||||
|
||||
$: if (outputs?.loading != undefined) {
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
async function loadSchemaFromTriggerable(
|
||||
workspace: string,
|
||||
path: string,
|
||||
runType: 'script' | 'flow' | 'hubscript'
|
||||
): Promise<Schema> {
|
||||
return loadSchema(workspace, path, runType)
|
||||
return loadSchema(workspace, path, runType) ?? emptySchema()
|
||||
}
|
||||
|
||||
$: runnable && loadSchemaAndInputsByName()
|
||||
@@ -181,7 +188,7 @@
|
||||
Object.keys(inputs ?? {}).forEach((key: string) => {
|
||||
const input = inputs[key]
|
||||
|
||||
if (input.type === 'static' && !input.visible && schemaStripped !== undefined) {
|
||||
if (input.type === 'static' && schemaStripped !== undefined) {
|
||||
delete schemaStripped.properties[key]
|
||||
}
|
||||
|
||||
@@ -223,7 +230,7 @@
|
||||
|
||||
await testJobLoader?.abstractRun(() => {
|
||||
const requestBody = {
|
||||
args: mergedArgs,
|
||||
args: { ...extraQueryParams, ...args, ...runnableInputValues },
|
||||
force_viewer_static_fields: {}
|
||||
}
|
||||
|
||||
@@ -256,7 +263,12 @@
|
||||
</script>
|
||||
|
||||
{#each Object.keys(fields ?? {}) as key}
|
||||
<InputValue {id} input={fields[key]} bind:value={runnableInputValues[key]} />
|
||||
<InputValue
|
||||
{id}
|
||||
input={fields[key]}
|
||||
bind:value={runnableInputValues[key]}
|
||||
row={extraQueryParams['row'] ?? {}}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
<TestJobLoader
|
||||
@@ -274,7 +286,14 @@
|
||||
|
||||
<div class="h-full flex flex-col">
|
||||
{#if schemaStripped !== undefined && (autoRefresh || forceSchemaDisplay)}
|
||||
<SchemaForm schema={schemaStripped} bind:args {isValid} {disabledArgs} shouldHideNoInputs />
|
||||
<SchemaForm
|
||||
schema={schemaStripped}
|
||||
bind:args
|
||||
{isValid}
|
||||
{disabledArgs}
|
||||
shouldHideNoInputs
|
||||
noVariablePicker
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if !runnable && autoRefresh}
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
|
||||
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
|
||||
<Toggle
|
||||
on:pointerdown={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
bind:value
|
||||
options={{ right: labelValue }}
|
||||
on:change={(e) => {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
$: result = [] as Array<Record<string, any>>
|
||||
|
||||
let search: 'Frontend' | 'Backend' | 'Disabled' = 'Disabled'
|
||||
let search: 'Runnable' | 'Component' | 'Disabled' = 'Disabled'
|
||||
let searchValue = ''
|
||||
|
||||
let pagination: boolean | undefined = undefined
|
||||
@@ -86,9 +86,9 @@
|
||||
let filteredResult: Array<Record<string, any>> = []
|
||||
|
||||
$: filteredResult && setOptions(filteredResult)
|
||||
$: extraQueryParams = search === 'Backend' ? { search: searchValue, page } : { page, search: '' }
|
||||
$: search === 'Frontend' && (filteredResult = searchInResult(result, searchValue))
|
||||
$: (search === 'Backend' || search === 'Disabled') && (filteredResult = result)
|
||||
$: extraQueryParams = search === 'Runnable' ? { search: searchValue, page } : { page, search: '' }
|
||||
$: search === 'Runnable' && (filteredResult = searchInResult(result, searchValue))
|
||||
$: (search === 'Runnable' || search === 'Disabled') && (filteredResult = result)
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedRow: Output<any>
|
||||
}
|
||||
@@ -149,10 +149,12 @@
|
||||
? 'divide-blue-200 hover:divide-blue-300'
|
||||
: 'divide-gray-200'
|
||||
)}
|
||||
on:click={() => toggleRow(row, rowIndex)}
|
||||
>
|
||||
{#each row.getVisibleCells() as cell, index (index)}
|
||||
<td class="p-4 whitespace-nowrap text-xs text-gray-900">
|
||||
<td
|
||||
on:click={() => toggleRow(row, rowIndex)}
|
||||
class="p-4 whitespace-nowrap text-xs text-gray-900"
|
||||
>
|
||||
<svelte:component
|
||||
this={flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
/>
|
||||
@@ -160,11 +162,15 @@
|
||||
{/each}
|
||||
|
||||
{#if actionButtons.length > 0}
|
||||
<td class="flex flex-row gap-2 p-4">
|
||||
<td
|
||||
class="flex w-full flex-row gap-2 p-4"
|
||||
on:click={() => toggleRow(row, rowIndex)}
|
||||
>
|
||||
{#each actionButtons as actionButton, actionIndex (actionIndex)}
|
||||
<AppButton
|
||||
noWFull
|
||||
{...actionButton}
|
||||
extraQueryParams={{ row: row.original, rowIndex }}
|
||||
extraQueryParams={{ row: row.original }}
|
||||
bind:componentInput={actionButton.componentInput}
|
||||
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
let input: HTMLInputElement
|
||||
let labelValue: string = 'Title'
|
||||
|
||||
let placeholder: string | undefined = undefined
|
||||
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
result: Output<string>
|
||||
@@ -25,14 +26,18 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InputValue {id} input={configuration.label} bind:value={labelValue} />
|
||||
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
|
||||
|
||||
<AlignWrapper {verticalAlignment}>
|
||||
<input
|
||||
on:focus={(e) => {
|
||||
e?.stopPropagation()
|
||||
window.dispatchEvent(new Event('pointerup'))
|
||||
}}
|
||||
type={inputType}
|
||||
bind:this={input}
|
||||
on:input={handleInput}
|
||||
placeholder="Type..."
|
||||
{placeholder}
|
||||
class="h-full"
|
||||
/>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
|
||||
import TabContent from '$lib/components/common/tabs/TabContent.svelte'
|
||||
import { Tab } from '$lib/components/common'
|
||||
import { Alert, Button, Tab } from '$lib/components/common'
|
||||
import ComponentList from './componentsPanel/ComponentList.svelte'
|
||||
import Icon from 'svelte-awesome'
|
||||
import { faPlus, faSliders } from '@fortawesome/free-solid-svg-icons'
|
||||
@@ -31,6 +31,7 @@
|
||||
import TablePanel from './TablePanel.svelte'
|
||||
import { grid } from 'd3-dag'
|
||||
import SettingsPanel from './SettingsPanel.svelte'
|
||||
import { fly } from 'svelte/transition'
|
||||
|
||||
export let app: App
|
||||
export let path: string
|
||||
@@ -54,6 +55,7 @@
|
||||
worldStore,
|
||||
staticOutputs,
|
||||
app: appStore,
|
||||
lazyGrid: writable([]),
|
||||
selectedComponent,
|
||||
mode,
|
||||
connectingInput,
|
||||
@@ -95,51 +97,90 @@
|
||||
<Pane size={55}>
|
||||
<SplitPanesWrapper horizontal>
|
||||
<Pane size={70}>
|
||||
<div class={classNames('bg-gray-100 mx-auto relative min-h-full', width)}>
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('mx-auto p-4 w-full h-full bg-gray-100', width)}>
|
||||
<GridEditor />
|
||||
</div>
|
||||
{/if}
|
||||
<div class="bg-gray-100 w-full p-4 h-full overflow-auto">
|
||||
<div class={classNames('bg-gray-100 mx-auto relative min-h-full', width)}>
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('w-full p-2 h-full bg-white', width)}>
|
||||
<GridEditor />
|
||||
</div>
|
||||
{/if}
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full bg-black border-2 bg-opacity-25 z-1 flex justify-center items-center"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={30}>
|
||||
<div class="relative h-full w-full">
|
||||
<InlineScriptsPanel />
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full bg-black border-2 bg-opacity-25 z-1 flex justify-center items-center"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={30}>
|
||||
<InlineScriptsPanel />
|
||||
{/if}</div
|
||||
>
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
</Pane>
|
||||
<Pane size={25} minSize={20} maxSize={33}>
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faPlus} />
|
||||
<span>Insert</span>
|
||||
<div class="relative">
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faPlus} />
|
||||
<span>Insert</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="settings" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faSliders} />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
<SettingsPanel />
|
||||
{:else}
|
||||
<div class="p-4 text-sm">No component selected.</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="insert">
|
||||
<ComponentList />
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="absolute top-0 left-0 w-full h-full bg-black border-2 bg-opacity-25 z-1 flex justify-center items-center"
|
||||
/>
|
||||
<div
|
||||
class="fixed top-32 p-2 z-10 flex justify-center items-center"
|
||||
transition:fly={{ duration: 100, y: -100 }}
|
||||
>
|
||||
<Alert title="Connecting" type="info">
|
||||
<div class="flex gap-2 flex-col">
|
||||
Click on the output of the component you want to connect to on the left panel.
|
||||
<div>
|
||||
<Button
|
||||
color="blue"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
$connectingInput.opened = false
|
||||
$connectingInput.input = undefined
|
||||
}}
|
||||
>
|
||||
Stop connecting
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="settings" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faSliders} />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
<SettingsPanel />
|
||||
{:else}
|
||||
<div class="p-4 text-sm">No component selected.</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="insert">
|
||||
<ComponentList />
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
{/if}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
faMobileAlt
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import { sendUserToast } from '../../../utils'
|
||||
import type { AppEditorContext, EditorBreakpoint, EditorMode } from '../types'
|
||||
|
||||
@@ -57,10 +58,12 @@
|
||||
<div class="flex gap-4 items-center">
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={mode}>
|
||||
<ToggleButton position="left" value="dnd" startIcon={{ icon: faHand }} size="xs">
|
||||
<ToggleButton position="left" value="dnd" size="xs">
|
||||
<Icon data={faHand} class="h-3 mr-1" />
|
||||
Editor
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="preview" startIcon={{ icon: faDisplay }} size="xs">
|
||||
<ToggleButton position="right" value="preview" size="xs">
|
||||
<Icon data={faDisplay} class="h-4 mr-1" />
|
||||
Preview
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
setContext<AppEditorContext>('AppEditorContext', {
|
||||
worldStore,
|
||||
staticOutputs,
|
||||
lazyGrid: writable(app.grid),
|
||||
app: appStore,
|
||||
selectedComponent,
|
||||
mode,
|
||||
|
||||
@@ -19,26 +19,24 @@
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
export let locked: boolean = false
|
||||
|
||||
$: shouldDisplayOverlay = selected && $mode !== 'preview'
|
||||
export let pointerdown: boolean = false
|
||||
|
||||
const { staticOutputs, mode, connectingInput } = getContext<AppEditorContext>('AppEditorContext')
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col w-full">
|
||||
{#if shouldDisplayOverlay}
|
||||
<ComponentHeader {component} {selected} on:delete on:lock {locked} />
|
||||
<div class="h-full flex flex-col w-full component">
|
||||
{#if $mode !== 'preview'}
|
||||
<ComponentHeader {pointerdown} {component} {selected} on:delete on:lock {locked} />
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'border cursor-pointer h-full bg-white',
|
||||
shouldDisplayOverlay ? 'border-blue-500' : 'border-white',
|
||||
selected && $mode !== 'preview' ? 'border-blue-500' : 'border-white',
|
||||
!selected && $mode !== 'preview' && !component.card ? 'border-gray-100' : '',
|
||||
$mode !== 'preview' && !$connectingInput.opened ? 'hover:border-blue-500' : '',
|
||||
component.card ? 'p-2' : '',
|
||||
component.softWrap ? '' : 'overflow-auto',
|
||||
'relative'
|
||||
'relative z-auto'
|
||||
)}
|
||||
>
|
||||
{#if component.type === 'displaycomponent'}
|
||||
|
||||
@@ -1,49 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
import type { AppComponent } from '../types'
|
||||
import { Anchor, Lock, X } from 'lucide-svelte'
|
||||
import { Anchor, Lock, Move, X } from 'lucide-svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let component: AppComponent
|
||||
export let selected: boolean
|
||||
export let locked: boolean = false
|
||||
export let pointerdown: boolean = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<span
|
||||
class={classNames(
|
||||
'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'
|
||||
'px-2 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute z-50',
|
||||
selected ? 'bg-indigo-500 text-white' : 'bg-gray-200/60 text-gray-500'
|
||||
)}
|
||||
>
|
||||
{component.id}
|
||||
</span>
|
||||
|
||||
<button
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute -top-5 right-8 cursor-pointer',
|
||||
'bg-gray-600 hover:bg-gray-800'
|
||||
)}
|
||||
on:click={() => {
|
||||
dispatch('lock')
|
||||
}}
|
||||
>
|
||||
{#if locked}
|
||||
<Anchor size={16} class="text-orange-500" />
|
||||
{:else}
|
||||
<Anchor size={16} />
|
||||
{/if}
|
||||
</button>
|
||||
{#if pointerdown || selected}
|
||||
<button
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-8 z-50 cursor-pointer',
|
||||
' hover:bg-gray-800',
|
||||
selected ? 'bg-gray-600/90' : 'bg-gray-600/80'
|
||||
)}
|
||||
on:click={() => {
|
||||
dispatch('lock')
|
||||
}}
|
||||
>
|
||||
{#if locked}
|
||||
<Anchor size={16} class="text-orange-500" />
|
||||
{:else}
|
||||
<Anchor size={16} />
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
<button
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute -top-5 right-0 cursor-pointer',
|
||||
'bg-gray-600 hover:bg-gray-800'
|
||||
)}
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
{#if selected}
|
||||
<span
|
||||
on:mousedown|stopPropagation|capture
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-16 z-50 cursor-move',
|
||||
'bg-gray-600/80'
|
||||
)}><Move size={16} /></span
|
||||
>
|
||||
<button
|
||||
class={classNames(
|
||||
'text-white px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute right-0 z-50 cursor-pointer',
|
||||
'bg-gray-600/80 hover:bg-gray-800'
|
||||
)}
|
||||
on:click={() => {
|
||||
dispatch('delete')
|
||||
}}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, onDestroy, onMount } from 'svelte'
|
||||
import type { AppEditorContext } from '../types'
|
||||
import Grid from 'svelte-grid'
|
||||
import ComponentEditor from './ComponentEditor.svelte'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
|
||||
import { Alert } from '$lib/components/common'
|
||||
import { fly } from 'svelte/transition'
|
||||
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
|
||||
|
||||
const { selectedComponent, app, mode, connectingInput, staticOutputs, runnableComponents } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
const {
|
||||
selectedComponent,
|
||||
app,
|
||||
mode,
|
||||
connectingInput,
|
||||
staticOutputs,
|
||||
runnableComponents,
|
||||
lazyGrid
|
||||
} = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
// The drag is disabled when the user is connecting an input
|
||||
$: if ($mode === 'preview' || $connectingInput.opened) {
|
||||
$app.grid.map((gridItem) => disableDrag(gridItem))
|
||||
} else {
|
||||
$app.grid.map((gridItem) => enableDrag(gridItem))
|
||||
$: setAllDrags($mode === 'preview' || $connectingInput.opened)
|
||||
|
||||
function setAllDrags(enable: boolean) {
|
||||
if (enable) {
|
||||
$app.grid.map((gridItem) => disableDrag(gridItem))
|
||||
} else {
|
||||
$app.grid.map((gridItem) => enableDrag(gridItem))
|
||||
}
|
||||
}
|
||||
|
||||
function removeGridElement(component) {
|
||||
@@ -58,31 +67,65 @@
|
||||
$selectedComponent = undefined
|
||||
}
|
||||
}
|
||||
|
||||
function selectComponent(id: string) {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = id
|
||||
}
|
||||
}
|
||||
|
||||
$: $app.grid && recomputeLazyGrid()
|
||||
|
||||
let intervalId: NodeJS.Timeout | undefined = undefined
|
||||
function recomputeLazyGrid() {
|
||||
{
|
||||
if (intervalId) {
|
||||
clearTimeout(intervalId)
|
||||
}
|
||||
intervalId = setTimeout(() => {
|
||||
lazyGrid.set($app.grid)
|
||||
intervalId = undefined
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
let pointerdown = false
|
||||
const onpointerdown = () => {
|
||||
pointerdown = true
|
||||
}
|
||||
const onpointerup = () => {
|
||||
pointerdown = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="bg-white h-full relative">
|
||||
<div
|
||||
on:pointerdown={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
class="bg-white h-full relative"
|
||||
>
|
||||
<RecomputeAllComponents />
|
||||
<Grid
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
rowHeight={32}
|
||||
rowHeight={46}
|
||||
cols={columnConfiguration}
|
||||
fastStart={true}
|
||||
on:pointerup={({ detail }) => {
|
||||
if (!$connectingInput.opened) {
|
||||
$selectedComponent = detail.id
|
||||
}
|
||||
}}
|
||||
on:pointerup={({ detail }) => selectComponent(detail.id)}
|
||||
gap={[4, 2]}
|
||||
>
|
||||
{#each $app.grid as gridComponent (gridComponent.id)}
|
||||
{#each $lazyGrid as gridComponent (gridComponent.id)}
|
||||
{#if gridComponent.data.id === dataItem.data.id}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'h-full w-full flex justify-center align-center',
|
||||
gridComponent.data.card ? 'border border-gray-100' : ''
|
||||
)}
|
||||
on:click|preventDefault|capture|once
|
||||
>
|
||||
<ComponentEditor
|
||||
{pointerdown}
|
||||
bind:component={gridComponent.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
locked={isFixed(gridComponent)}
|
||||
@@ -95,32 +138,6 @@
|
||||
{/if}
|
||||
{/each}
|
||||
</Grid>
|
||||
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="fixed top-32 z-10 flex justify-center items-center"
|
||||
transition:fly={{ duration: 100, y: -100 }}
|
||||
>
|
||||
<Alert title="Connecting" type="info">
|
||||
<div class="flex gap-2 flex-col">
|
||||
Click on the output of the component you want to connect to on the left panel.
|
||||
<div>
|
||||
<Button
|
||||
color="blue"
|
||||
variant="border"
|
||||
size="xs"
|
||||
on:click={() => {
|
||||
$connectingInput.opened = false
|
||||
$connectingInput.input = undefined
|
||||
}}
|
||||
>
|
||||
Stop connecting
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -18,16 +18,13 @@
|
||||
)
|
||||
loading = false
|
||||
}
|
||||
|
||||
$: disabled = Object.keys($runnableComponents).length === 0
|
||||
</script>
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
btnClasses="m-2 mb-4"
|
||||
btnClasses="m-2 mb-6"
|
||||
startIcon={{ icon: faRefresh, classes: classNames(loading ? 'animate-spin' : '', 'mr-2') }}
|
||||
color="dark"
|
||||
{disabled}
|
||||
on:click={onRefresh}
|
||||
>
|
||||
Recompute all ({Object.keys($runnableComponents).length})
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
|
||||
import TablePanel from './TablePanel.svelte'
|
||||
|
||||
const { selectedComponent, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { selectedComponent, lazyGrid } = getContext<AppEditorContext>('AppEditorContext')
|
||||
</script>
|
||||
|
||||
{#each $app.grid as gridItem (gridItem.data.id)}
|
||||
{#each $lazyGrid as gridItem (gridItem.data.id)}
|
||||
{#if gridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel bind:component={gridItem.data} />
|
||||
{:else if gridItem.data.type === 'tablecomponent'}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
{#each component.actionButtons as actionButton (actionButton.id)}
|
||||
{#if actionButton.id === $selectedComponent}
|
||||
<ComponentPanel
|
||||
rowColumns
|
||||
bind:component={actionButton}
|
||||
onDelete={() => {
|
||||
component.actionButtons = component.actionButtons.filter((c) => c.id !== actionButton.id)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
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:2-2:2': [
|
||||
'1:1-2:1': [
|
||||
'buttoncomponent',
|
||||
'textcomponent',
|
||||
'checkboxcomponent',
|
||||
@@ -47,7 +47,8 @@
|
||||
'numberinputcomponent',
|
||||
'selectcomponent',
|
||||
'dateinputcomponent',
|
||||
'passwordinputcomponent'
|
||||
'passwordinputcomponent',
|
||||
'buttoncomponent'
|
||||
].includes(componentType)
|
||||
) {
|
||||
return { w: column, h: 1 }
|
||||
@@ -57,20 +58,7 @@
|
||||
|
||||
function addComponent(appComponent: AppComponent) {
|
||||
const grid = $app.grid ?? []
|
||||
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()
|
||||
)
|
||||
const id = getNextId(grid.map((gridItem) => gridItem.data.id))
|
||||
|
||||
appComponent.id = id
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@ const buttonColorOptions = [...BUTTON_COLORS]
|
||||
export const staticValues = {
|
||||
buttonColorOptions,
|
||||
buttonSizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
|
||||
tableSearchOptions: ['Frontend', 'Backend', 'Disabled'],
|
||||
tableSearchOptions: ['Component', 'Runnable', 'Disabled'],
|
||||
chartThemeOptions: ['theme1', 'theme2', 'theme3']
|
||||
} as const
|
||||
|
||||
@@ -9,7 +9,14 @@ const inputs: ComponentSet = {
|
||||
id: 'textinputcomponent',
|
||||
type: 'textinputcomponent',
|
||||
componentInput: undefined,
|
||||
configuration: {},
|
||||
configuration: {
|
||||
placeholder: {
|
||||
type: 'static',
|
||||
value: 'Type...',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Type...'
|
||||
},
|
||||
},
|
||||
card: false
|
||||
},
|
||||
{
|
||||
@@ -36,14 +43,12 @@ const inputs: ComponentSet = {
|
||||
configuration: {
|
||||
minDate: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
value: '',
|
||||
fieldType: 'date',
|
||||
defaultValue: ''
|
||||
},
|
||||
maxDate: {
|
||||
type: 'static',
|
||||
visible: false,
|
||||
value: '',
|
||||
fieldType: 'date',
|
||||
defaultValue: ''
|
||||
@@ -60,9 +65,8 @@ const inputs: ComponentSet = {
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Label',
|
||||
fieldType: 'textarea',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Label'
|
||||
}
|
||||
},
|
||||
@@ -118,15 +122,13 @@ const buttons: ComponentSet = {
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Lorem ipsum',
|
||||
fieldType: 'textarea',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Lorem ipsum'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'blue',
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'blue'
|
||||
@@ -134,7 +136,6 @@ const buttons: ComponentSet = {
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'xs',
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'xs'
|
||||
@@ -158,15 +159,13 @@ const buttons: ComponentSet = {
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Submit',
|
||||
fieldType: 'textarea',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'formcomponent'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'dark',
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'dark'
|
||||
@@ -174,7 +173,6 @@ const buttons: ComponentSet = {
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'xs',
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'xs'
|
||||
@@ -195,7 +193,6 @@ const display: ComponentSet = {
|
||||
type: 'textcomponent',
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Lorem ipsum',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Lorem ipsum'
|
||||
@@ -218,7 +215,6 @@ const display: ComponentSet = {
|
||||
type: 'static',
|
||||
value: false,
|
||||
fieldType: 'boolean',
|
||||
visible: true,
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
function subscribeToAllOutputs(observableOutputs: Record<string, Output<any>>) {
|
||||
if (observableOutputs) {
|
||||
outputs.forEach((output: string) => {
|
||||
object[output] = undefined
|
||||
observableOutputs[output]?.subscribe({
|
||||
next: (value) => {
|
||||
object[output] = value
|
||||
|
||||
@@ -35,50 +35,53 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title="Outputs">
|
||||
{#each Object.entries($staticOutputs) as [componentId, outputs] (componentId)}
|
||||
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
|
||||
<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>
|
||||
<PanelSection noPadding titlePadding="px-4 pt-2" title="Outputs">
|
||||
<div class="overflow-auto border-t w-full relative flex flex-col gap-2 px-4">
|
||||
{#each Object.entries($staticOutputs) as [componentId, outputs] (componentId)}
|
||||
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
|
||||
<div class="flex flex-row justify-between w-full -mb-2">
|
||||
<button
|
||||
on:click|stopPropagation|preventDefault={$connectingInput.opened
|
||||
? undefined
|
||||
: () => ($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 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}
|
||||
on:select={({ detail }) => {
|
||||
connectInput(componentId, detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full py-2 border relative',
|
||||
$selectedComponent === componentId ? 'border border-blue-500 ' : ''
|
||||
)}
|
||||
>
|
||||
{#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}
|
||||
on:select={({ detail }) => {
|
||||
connectInput(componentId, detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</PanelSection>
|
||||
|
||||
+62
-19
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { Drawer, DrawerContent } from '$lib/components/common'
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import FlowModuleScript from '$lib/components/flows/content/FlowModuleScript.svelte'
|
||||
import FlowPathViewer from '$lib/components/flows/content/FlowPathViewer.svelte'
|
||||
import { getScriptByPath } from '$lib/utils'
|
||||
import { faCodeBranch } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faCodeBranch, faExternalLinkAlt, faEye, faPen } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { AppInput, ResultAppInput } from '../../inputType'
|
||||
import { clearResultAppInput } from '../../utils'
|
||||
import EmptyInlineScript from './EmptyInlineScript.svelte'
|
||||
@@ -28,9 +30,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
let drawerFlowViewer: Drawer
|
||||
let flowPath: string = ''
|
||||
|
||||
// $: inlineScript && (componentInput = componentInput)
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={drawerFlowViewer} size="1200px">
|
||||
<DrawerContent title="Flow {flowPath}" on:close={drawerFlowViewer.closeDrawer}>
|
||||
<FlowPathViewer path={flowPath ?? ''} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
{#if componentInput && componentInput.type == 'runnable'}
|
||||
{#if componentInput?.runnable?.type === 'runnableByName' && componentInput?.runnable?.name !== undefined}
|
||||
{#if componentInput.runnable.inlineScript}
|
||||
@@ -59,25 +70,57 @@
|
||||
{/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>
|
||||
{#if componentInput.runnable.runType == 'script' || componentInput.runnable.runType == 'hubscript'}
|
||||
<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>
|
||||
{/if}
|
||||
<div class="border w-full">
|
||||
<FlowModuleScript path={componentInput.runnable.path} />
|
||||
{#if componentInput.runnable.runType == 'script' || componentInput.runnable.runType == 'hubscript'}
|
||||
<FlowModuleScript path={componentInput.runnable.path} />
|
||||
{:else if componentInput.runnable.runType == 'flow'}
|
||||
<div class="py-1 flex gap-2 w-full flex-row-reverse">
|
||||
<Button
|
||||
size="xs"
|
||||
startIcon={{ icon: faEye }}
|
||||
on:click={() => {
|
||||
flowPath = componentInput?.['runnable']?.path
|
||||
drawerFlowViewer.openDrawer()
|
||||
}}>Expand</Button
|
||||
>
|
||||
<Button
|
||||
size="xs"
|
||||
startIcon={{ icon: faPen }}
|
||||
endIcon={{ icon: faExternalLinkAlt }}
|
||||
target="_blank"
|
||||
href="/flows/edit/{componentInput?.['runnable']?.path}">Edit</Button
|
||||
>
|
||||
<Button
|
||||
size="xs"
|
||||
startIcon={{ icon: faEye }}
|
||||
endIcon={{ icon: faExternalLinkAlt }}
|
||||
target="_blank"
|
||||
href="/flows/get/{componentInput?.['runnable']?.path}">Details page</Button
|
||||
>
|
||||
</div>
|
||||
<FlowPathViewer path={componentInput.runnable.path} />
|
||||
{:else}
|
||||
Unrecognized runType {componentInput.runnable.runType}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
import InlineScriptEditorPanel from './InlineScriptEditorPanel.svelte'
|
||||
import InlineScriptEditor from './InlineScriptEditor.svelte'
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { lazyGrid, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let selectedScriptComponentId: string | undefined = undefined
|
||||
</script>
|
||||
@@ -17,7 +17,7 @@
|
||||
<InlineScriptsPanelList bind:selectedScriptComponentId />
|
||||
</Pane>
|
||||
<Pane size={75}>
|
||||
{#each $app.grid as gridComponent, index (index)}
|
||||
{#each $lazyGrid as gridComponent, index (index)}
|
||||
{#if gridComponent.data.id === selectedScriptComponentId}
|
||||
<InlineScriptEditorPanel bind:componentInput={gridComponent.data.componentInput} />
|
||||
{/if}
|
||||
|
||||
+75
-78
@@ -7,14 +7,14 @@
|
||||
|
||||
export let selectedScriptComponentId: string | undefined = undefined
|
||||
|
||||
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { app, selectedComponent, lazyGrid } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function selectInlineScript(id: string, subId?: string) {
|
||||
selectedScriptComponentId = subId ? subId : id
|
||||
function selectInlineScript(id: string) {
|
||||
selectedScriptComponentId = id
|
||||
$selectedComponent = selectedScriptComponentId
|
||||
}
|
||||
|
||||
$: runnablesByName = $app.grid.reduce((acc, gridComponent) => {
|
||||
$: runnablesByName = $lazyGrid.reduce((acc, gridComponent) => {
|
||||
const component: AppComponent = gridComponent.data
|
||||
|
||||
if (component.type === 'tablecomponent') {
|
||||
@@ -23,8 +23,7 @@
|
||||
if (actionButton.componentInput.runnable?.type === 'runnableByName') {
|
||||
acc.push({
|
||||
name: actionButton.componentInput.runnable.name,
|
||||
id: gridComponent.id,
|
||||
subId: actionButton.id
|
||||
id: actionButton.id
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -42,9 +41,9 @@
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [] as { name: string; id: string; subId?: string }[])
|
||||
}, [] as { name: string; id: string }[])
|
||||
|
||||
$: runnablesByPath = $app.grid.reduce((acc, gridComponent) => {
|
||||
$: runnablesByPath = $lazyGrid.reduce((acc, gridComponent) => {
|
||||
const component: AppComponent = gridComponent.data
|
||||
|
||||
if (component.type === 'tablecomponent') {
|
||||
@@ -53,8 +52,7 @@
|
||||
if (actionButton.componentInput.runnable?.type === 'runnableByPath') {
|
||||
acc.push({
|
||||
name: actionButton.componentInput.runnable.path,
|
||||
id: gridComponent.id,
|
||||
subId: actionButton.id
|
||||
id: actionButton.id
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -72,7 +70,7 @@
|
||||
}
|
||||
}
|
||||
return acc
|
||||
}, [] as { name: string; id: string; subId?: string }[])
|
||||
}, [] as { name: string; id: string }[])
|
||||
|
||||
// When seleced component changes, update selectedScriptComponentId
|
||||
$: {
|
||||
@@ -82,73 +80,72 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title="Inline scripts" smallPadding>
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if runnablesByName.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each runnablesByName as { name, id, subId }, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-sm cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptComponentId === (subId ? subId : id) ? 'border-blue-500 border' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(id, subId)}
|
||||
>
|
||||
<span class="text-xs truncate">{name}</span>
|
||||
<div>
|
||||
<Badge color="dark-indigo">{id}</Badge>
|
||||
{#if subId}
|
||||
<Badge color="dark-indigo">{subId}</Badge>
|
||||
{/if}
|
||||
<div class="h-full flex flex-col gap-4">
|
||||
<PanelSection title="Inline scripts" smallPadding>
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if runnablesByName.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each runnablesByName as { name, id }, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-sm cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptComponentId === id ? 'border-blue-500 border' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(id)}
|
||||
>
|
||||
<span class="text-xs truncate">{name}</span>
|
||||
<div>
|
||||
<Badge color="dark-indigo">{id}</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-gray-500">No inline scripts</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-gray-500">No inline scripts</div>
|
||||
{/if}
|
||||
|
||||
{#if Array.isArray($app.unusedInlineScripts) && $app.unusedInlineScripts.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptComponentId === '' ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(unusedInlineScript.name)}
|
||||
>
|
||||
<span class="text-xs truncate">{unusedInlineScript.name}</span>
|
||||
<Badge color="red">Unused</Badge>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
{#if Array.isArray($app.unusedInlineScripts) && $app.unusedInlineScripts.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptComponentId === '' ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(unusedInlineScript.name)}
|
||||
>
|
||||
<span class="text-xs truncate">{unusedInlineScript.name}</span>
|
||||
<Badge color="red">Detached</Badge>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
|
||||
<PanelSection title="Runnable by path" smallPadding>
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if runnablesByPath.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each runnablesByPath as { name, id }, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptComponentId === id ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(id)}
|
||||
>
|
||||
<span class="text-xs truncate">{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>
|
||||
<PanelSection title="Others" smallPadding>
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
{#if runnablesByPath.length > 0}
|
||||
<div class="flex gap-2 flex-col ">
|
||||
{#each runnablesByPath as { name, id }, index (index)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="{classNames(
|
||||
'border flex gap-1 truncate justify-between flex-row w-full items-center p-2 rounded-md cursor-pointer hover:bg-blue-50 hover:text-blue-400',
|
||||
selectedScriptComponentId === id ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(id)}
|
||||
>
|
||||
<span class="text-xs truncate">{name}</span>
|
||||
<Badge color="dark-indigo">{id}</Badge>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-sm text-gray-500">No items</div>
|
||||
{/if}
|
||||
</div>
|
||||
</PanelSection>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
type ArrayComponentInput = Extract<StaticAppInput, { fieldType: 'array' }>
|
||||
|
||||
export let canHide: boolean = false
|
||||
export let componentInput: ArrayComponentInput
|
||||
|
||||
function addElementByType() {
|
||||
@@ -46,7 +45,7 @@
|
||||
{#if componentInput.value}
|
||||
{#each componentInput.value as value, index (index)}
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<SubTypeEditor bind:componentInput bind:value {canHide} />
|
||||
<SubTypeEditor bind:componentInput bind:value />
|
||||
|
||||
<div>
|
||||
<Button
|
||||
|
||||
+5
-5
@@ -15,11 +15,11 @@
|
||||
</script>
|
||||
|
||||
{#if componentInput.fieldType !== 'any'}
|
||||
<div class="w-full">
|
||||
<div class="w-full overflow-x-auto">
|
||||
<ToggleButtonGroup bind:selected={componentInput.type}>
|
||||
{#if componentInput.fieldType === 'textarea'}
|
||||
<ToggleButton position="left" value="template" size="xs" disable={disableStatic}>
|
||||
{brackets} Templatable
|
||||
{brackets} <span class="hidden lg:block">Templatable</span>
|
||||
</ToggleButton>
|
||||
{:else}
|
||||
<ToggleButton
|
||||
@@ -29,7 +29,7 @@
|
||||
size="xs"
|
||||
disable={disableStatic}
|
||||
>
|
||||
Static
|
||||
<span class="hidden lg:block"> Static </span>
|
||||
</ToggleButton>
|
||||
{/if}
|
||||
|
||||
@@ -39,10 +39,10 @@
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
>
|
||||
Connected
|
||||
<span class="hidden lg:block"> Connected </span>
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="runnable" startIcon={{ icon: faCode }} size="xs">
|
||||
Computed
|
||||
<span class="hidden lg:block"> Computed </span>
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -18,10 +18,10 @@
|
||||
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
|
||||
export let rowColumns = false
|
||||
|
||||
const { app, staticOutputs, runnableComponents, worldStore } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
@@ -96,6 +96,14 @@ declare const ${k} = ${JSON.stringify(v)};
|
||||
title={component.componentInput.fieldType === 'any' ? 'Runnable' : 'Component input'}
|
||||
>
|
||||
<svelte:fragment slot="action">
|
||||
<span
|
||||
class={classNames(
|
||||
'text-white px-2 text-2xs py-0.5 font-bold rounded-sm w-fit',
|
||||
'bg-indigo-500'
|
||||
)}
|
||||
>
|
||||
{`${component.id}`}
|
||||
</span>
|
||||
{#if component.componentInput.fieldType !== 'any'}
|
||||
<Badge color="blue">
|
||||
{component.componentInput.fieldType === 'array' &&
|
||||
@@ -105,25 +113,9 @@ 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} />
|
||||
@@ -156,6 +148,7 @@ declare const ${k} = ${JSON.stringify(v)};
|
||||
shouldCapitalize={false}
|
||||
bind:inputSpecs={component.componentInput.fields}
|
||||
userInputEnabled={component.type !== 'buttoncomponent'}
|
||||
{rowColumns}
|
||||
/>
|
||||
</PanelSection>
|
||||
</div>
|
||||
@@ -170,7 +163,7 @@ declare const ${k} = ${JSON.stringify(v)};
|
||||
{/if}
|
||||
|
||||
{#if component.type === 'tablecomponent' && Array.isArray(component.actionButtons)}
|
||||
<TableActions bind:components={component.actionButtons} />
|
||||
<TableActions id={component.id} bind:components={component.actionButtons} />
|
||||
{/if}
|
||||
|
||||
<AlignmentEditor bind:component />
|
||||
|
||||
@@ -1,14 +1,36 @@
|
||||
<script lang="ts">
|
||||
import type { ConnectedAppInput, StaticAppInput, UserAppInput } from '../../inputType'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import type {
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
StaticAppInput,
|
||||
UserAppInput
|
||||
} from '../../inputType'
|
||||
import ConnectedInputEditor from './inputEditor/ConnectedInputEditor.svelte'
|
||||
import RowInputEditor from './inputEditor/RowInputEditor.svelte'
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
|
||||
export let componentInput: StaticAppInput | ConnectedAppInput | UserAppInput
|
||||
export let canHide: boolean = false
|
||||
export let componentInput: StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput
|
||||
export let userInputEnabled: boolean = false
|
||||
</script>
|
||||
|
||||
{#if componentInput.type === 'static'}
|
||||
<StaticInputEditor bind:componentInput {canHide} />
|
||||
{:else if componentInput.type === 'connected'}
|
||||
{#if componentInput.type === 'connected'}
|
||||
<ConnectedInputEditor bind:componentInput />
|
||||
{:else if componentInput.type === 'row'}
|
||||
<RowInputEditor bind:componentInput />
|
||||
{:else}
|
||||
{#if userInputEnabled}
|
||||
<Toggle
|
||||
class="-mt-2 -mb-1"
|
||||
checked={componentInput.type === 'user'}
|
||||
on:change={({ detail }) => {
|
||||
componentInput.type = detail ? 'user' : 'static'
|
||||
}}
|
||||
options={{ right: 'User input' }}
|
||||
size="xs"
|
||||
/>
|
||||
{/if}
|
||||
{#if componentInput.type === 'static'}
|
||||
<StaticInputEditor bind:componentInput />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { Badge, ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import { capitalize } from '$lib/utils'
|
||||
import { faArrowRight, faPen, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faArrowRight, faPen, faTableCells, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import { fieldTypeToTsType } from '../../utils'
|
||||
import InputsSpecEditor from './InputsSpecEditor.svelte'
|
||||
import type { ConnectedAppInput, StaticAppInput, UserAppInput } from '../../inputType'
|
||||
import type {
|
||||
ConnectedAppInput,
|
||||
RowAppInput,
|
||||
StaticAppInput,
|
||||
UserAppInput
|
||||
} from '../../inputType'
|
||||
|
||||
export let inputSpecs: Record<string, StaticAppInput | ConnectedAppInput | UserAppInput>
|
||||
export let inputSpecs: Record<
|
||||
string,
|
||||
StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput
|
||||
>
|
||||
export let userInputEnabled: boolean = true
|
||||
export let staticOnly: boolean = false
|
||||
export let shouldCapitalize: boolean = true
|
||||
export let rowColumns = false
|
||||
</script>
|
||||
|
||||
{#if inputSpecs}
|
||||
@@ -17,8 +26,8 @@
|
||||
{#each Object.keys(inputSpecs) as inputSpecKey, index (index)}
|
||||
{@const input = inputSpecs[inputSpecKey]}
|
||||
{#if true}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex justify-between items-center gap-1">
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex justify-between items-end gap-1">
|
||||
<span class="text-xs font-semibold">
|
||||
{shouldCapitalize ? capitalize(inputSpecKey) : inputSpecKey}
|
||||
</span>
|
||||
@@ -38,31 +47,31 @@
|
||||
size="xs"
|
||||
iconOnly
|
||||
/>
|
||||
{#if rowColumns}
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="row"
|
||||
startIcon={{ icon: faTableCells }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
disabled={staticOnly}
|
||||
/>
|
||||
{/if}
|
||||
<ToggleButton
|
||||
position={userInputEnabled && input.format === undefined ? 'center' : 'right'}
|
||||
position="right"
|
||||
value="connected"
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
disabled={staticOnly}
|
||||
/>
|
||||
{#if userInputEnabled && input.format === undefined}
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="user"
|
||||
startIcon={{ icon: faUser }}
|
||||
size="xs"
|
||||
iconOnly
|
||||
disabled={staticOnly && !userInputEnabled}
|
||||
/>
|
||||
{/if}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InputsSpecEditor
|
||||
bind:componentInput={inputSpecs[inputSpecKey]}
|
||||
canHide={userInputEnabled && input.format === undefined}
|
||||
userInputEnabled={userInputEnabled && input.format === undefined}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title="Recompute">
|
||||
<PanelSection title="Recompute after this compute">
|
||||
{#if Object.keys($runnableComponents ?? {}).filter((id) => id !== ownId).length > 0}
|
||||
<table class="divide-y divide-gray-300 border w-full">
|
||||
<thead class="bg-gray-50">
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { faClose, faEdit } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { ResultAppInput } from '../../inputType'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { clearResultAppInput } from '../../utils'
|
||||
import InlineScriptEditorDrawer from '../inlineScriptsPanel/InlineScriptEditorDrawer.svelte'
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
export let appInput: ResultAppInput
|
||||
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
|
||||
|
||||
@@ -14,6 +19,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
function detach() {
|
||||
if (appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript) {
|
||||
$app.unusedInlineScripts.push({
|
||||
name: appInput.runnable.name,
|
||||
inlineScript: appInput.runnable.inlineScript
|
||||
})
|
||||
$app = $app
|
||||
appInput = clearResultAppInput(appInput)
|
||||
}
|
||||
}
|
||||
|
||||
function clear() {
|
||||
appInput = clearResultAppInput(appInput)
|
||||
}
|
||||
@@ -25,19 +41,24 @@
|
||||
bind:inlineScript={appInput.runnable.inlineScript}
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex justify-between w-full items-center gap-1">
|
||||
<span class="text-xs font-semibold">
|
||||
<div class="flex flex-col xl:flex-row justify-between w-full items-center gap-1">
|
||||
<span class="text-xs font-semibold truncate">
|
||||
{#if appInput.runnable?.type === 'runnableByName'}
|
||||
{appInput.runnable.name}
|
||||
{:else if appInput.runnable?.type === 'runnableByPath'}
|
||||
{appInput.runnable.path}
|
||||
{/if}
|
||||
</span>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<div class="flex gap-1">
|
||||
{#if appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript}
|
||||
<Button size="xs" color="light" variant="border" startIcon={{ icon: faEdit }} on:click={edit}>
|
||||
Edit
|
||||
</Button>
|
||||
<Button size="xs" color="light" variant="border" on:click={detach}
|
||||
>Detach <Tooltip
|
||||
>Detaching an inline script keep it for later to be reused by another component</Tooltip
|
||||
></Button
|
||||
>
|
||||
{/if}
|
||||
<Button size="xs" color="red" variant="border" startIcon={{ icon: faClose }} on:click={clear}>
|
||||
Clear
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
|
||||
|
||||
export let value: any
|
||||
export let canHide: boolean = false
|
||||
export let componentInput: StaticAppInput
|
||||
|
||||
let fakeComponentInput: StaticAppInput = {
|
||||
...componentInput,
|
||||
value,
|
||||
visible: componentInput.visible,
|
||||
// We don't support array of arrays
|
||||
// @ts-ignore
|
||||
fieldType: componentInput.subFieldType
|
||||
@@ -19,4 +17,4 @@
|
||||
$: fakeComponentInput && (value = fakeComponentInput.value)
|
||||
</script>
|
||||
|
||||
<StaticInputEditor bind:componentInput={fakeComponentInput} {canHide} />
|
||||
<StaticInputEditor bind:componentInput={fakeComponentInput} />
|
||||
|
||||
@@ -3,35 +3,34 @@
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import type { ButtonComponent, AppEditorContext, BaseAppComponent } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import TableActionLabel from './TableActionLabel.svelte'
|
||||
|
||||
export let components: (BaseAppComponent & ButtonComponent)[]
|
||||
export let id: string
|
||||
|
||||
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { selectedComponent, staticOutputs } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function addComponent() {
|
||||
const grid = $app.grid ?? []
|
||||
const id = getNextId(grid.map((gridItem) => gridItem.data.id))
|
||||
const actionId = getNextId(components.map((x) => x.id.split('-')[1]))
|
||||
|
||||
const newComponent: BaseAppComponent & ButtonComponent = {
|
||||
id,
|
||||
id: `${id}-${actionId}`,
|
||||
type: 'buttoncomponent',
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Action',
|
||||
fieldType: 'textarea',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Action'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'dark',
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'dark'
|
||||
@@ -39,7 +38,6 @@
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'xs',
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'xs'
|
||||
@@ -58,6 +56,13 @@
|
||||
|
||||
components = [...components, newComponent]
|
||||
}
|
||||
|
||||
function deleteComponent(cid: string) {
|
||||
components = components.filter((x) => x.id !== cid)
|
||||
delete $staticOutputs[cid]
|
||||
$staticOutputs = $staticOutputs
|
||||
$selectedComponent = id
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title={`Table actions ${components.length > 0 ? `(${components.length})` : ''}`}>
|
||||
@@ -84,6 +89,11 @@
|
||||
}}
|
||||
on:keypress
|
||||
>
|
||||
<div>
|
||||
<Button variant="border" color="red" on:click={() => deleteComponent(component.id)}>
|
||||
<Icon class="h-3" data={faTrashAlt} />
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<TableActionLabel componentInput={component.configuration.label} />
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,18 @@
|
||||
|
||||
export let title: string
|
||||
export let smallPadding: boolean = false
|
||||
export let noPadding: boolean = false
|
||||
export let titlePadding: string = ''
|
||||
</script>
|
||||
|
||||
<div class={classNames('flex flex-col gap-2 items-start', smallPadding ? 'p-2' : 'p-4')}>
|
||||
<div
|
||||
class={classNames(
|
||||
'flex flex-col h-full gap-2 items-start',
|
||||
noPadding ? '' : smallPadding ? 'p-2' : 'p-4'
|
||||
)}
|
||||
>
|
||||
<div class="flex justify-between items-center w-full gap-1">
|
||||
<div class="text-sm font-extrabold">{title}</div>
|
||||
<div class="text-sm font-extrabold {titlePadding}">{title}</div>
|
||||
<slot name="action" />
|
||||
</div>
|
||||
<slot />
|
||||
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { RowAppInput } from '../../../inputType'
|
||||
|
||||
export let componentInput: RowAppInput | undefined
|
||||
</script>
|
||||
|
||||
{#if componentInput}
|
||||
<input type="text" placeholder="column" bind:value={componentInput.column} />
|
||||
{/if}
|
||||
+2
-7
@@ -7,14 +7,9 @@
|
||||
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
|
||||
|
||||
export let componentInput: StaticAppInput | undefined
|
||||
export let canHide: boolean = false
|
||||
</script>
|
||||
|
||||
{#if componentInput?.type === 'static'}
|
||||
{#if canHide}
|
||||
<Toggle bind:checked={componentInput.visible} options={{ right: 'Visible' }} />
|
||||
{/if}
|
||||
|
||||
{#if componentInput.fieldType === 'number'}
|
||||
<input type="number" bind:value={componentInput.value} />
|
||||
{:else if componentInput.fieldType === 'textarea'}
|
||||
@@ -59,8 +54,8 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else if componentInput.fieldType === 'array'}
|
||||
<ArrayStaticInputEditor bind:componentInput {canHide} />
|
||||
<ArrayStaticInputEditor bind:componentInput />
|
||||
{:else}
|
||||
<input bind:value={componentInput.value} />
|
||||
<input type="text" placeholder="Static value" bind:value={componentInput.value} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
+1
-2
@@ -28,8 +28,7 @@
|
||||
|
||||
{#if inlineScripts.length === 0}
|
||||
<div class="flex flex-col w-full h-full">
|
||||
<div class="text-md ">No inline scripts</div>
|
||||
<div class="text-sm">Add inline scripts to your app</div>
|
||||
<div class="text-md">No detached inline scripts</div>
|
||||
</div>
|
||||
{:else if filteredItems.length === 0}
|
||||
<NoItemFound />
|
||||
|
||||
+7
-6
@@ -64,11 +64,12 @@
|
||||
|
||||
$app.unusedInlineScripts.splice(unusedInlineScriptIndex, 1)
|
||||
}
|
||||
$app = $app
|
||||
}
|
||||
|
||||
function createScript(): string {
|
||||
let index = 0
|
||||
let newScriptPath = `inline_script_${index}`
|
||||
let newScriptPath = `Inline Script ${index}`
|
||||
|
||||
const names = $app.grid.reduce((acc, gridItem: GridItem) => {
|
||||
const { componentInput } = gridItem.data
|
||||
@@ -83,11 +84,11 @@
|
||||
return acc
|
||||
}, [] as string[])
|
||||
|
||||
const unusedNames = Object.keys($app.unusedInlineScripts ?? {})
|
||||
const unusedNames = $app.unusedInlineScripts.map((x) => x.name)
|
||||
|
||||
// Find a name that is not used by any other inline script
|
||||
while (names.includes(newScriptPath) || unusedNames.includes(newScriptPath)) {
|
||||
newScriptPath = `inline_script_${++index}`
|
||||
newScriptPath = `Inline Script ${++index}`
|
||||
}
|
||||
|
||||
appInput.runnable = {
|
||||
@@ -103,14 +104,14 @@
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={picker} size="1000px">
|
||||
<DrawerContent title="Picker" on:close={picker.closeDrawer}>
|
||||
<DrawerContent title="Script/Flow Picker" on:close={picker.closeDrawer}>
|
||||
<div>
|
||||
<div class="max-w-6xl">
|
||||
<Tabs bind:selected={tab}>
|
||||
<Tab size="sm" value="inlinescripts">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
Inline Scripts
|
||||
Detached Inline Scripts
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab size="sm" value="workspacescripts">
|
||||
@@ -172,6 +173,6 @@
|
||||
color="blue"
|
||||
startIcon={{ icon: faMousePointer }}
|
||||
>
|
||||
Select a script
|
||||
Select a script or flow
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -33,11 +33,16 @@ export type UserInput<U> = {
|
||||
value: U | undefined
|
||||
}
|
||||
|
||||
|
||||
export type RowInput = {
|
||||
type: 'row'
|
||||
column: string
|
||||
}
|
||||
|
||||
// Static input, set by the developer in the component panel
|
||||
export type StaticInput<U> = {
|
||||
value: U | undefined
|
||||
type: 'static'
|
||||
visible?: boolean | undefined
|
||||
}
|
||||
|
||||
export type TemplateInput = {
|
||||
@@ -62,7 +67,7 @@ export type Runnable = RunnableByPath | RunnableByName | undefined
|
||||
// Runnable input, set by the developer in the component panel
|
||||
export type ResultInput = {
|
||||
runnable: Runnable
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput>
|
||||
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput>
|
||||
type: 'runnable'
|
||||
}
|
||||
|
||||
@@ -70,6 +75,7 @@ type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
| StaticInput<U>
|
||||
| ConnectedInput
|
||||
| UserInput<U>
|
||||
| RowInput
|
||||
| ResultInput
|
||||
| TemplateInput
|
||||
) &
|
||||
@@ -94,11 +100,11 @@ export type AppInput =
|
||||
| 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'>
|
||||
@@ -108,9 +114,10 @@ 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 RowAppInput = Extract<AppInput, { type: 'row' }>
|
||||
export type StaticAppInput = Extract<AppInput, { type: 'static' }>
|
||||
export type ConnectedAppInput = Extract<AppInput, { type: 'connected' }>
|
||||
export type UserAppInput = Extract<AppInput, { type: 'user' }>
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface Input<T> extends Subscriber<T> {
|
||||
|
||||
export type World = {
|
||||
outputsById: Record<string, Record<string, Output<any>>>
|
||||
connect: <T>(inputSpec: AppInput, next: (x: T) => void, previousValue: T) => Input<T>
|
||||
connect: <T>(inputSpec: AppInput, next: (x: T) => void) => Input<T>
|
||||
state: Writable<number>
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ export function buildWorld(
|
||||
export function buildObservableWorld() {
|
||||
const observables: Record<string, Output<any>> = {}
|
||||
|
||||
function connect<T>(inputSpec: AppInput, next: (x: T) => void, previousValue: T): Input<T> {
|
||||
function connect<T>(inputSpec: AppInput, next: (x: T) => void): Input<T> {
|
||||
if (inputSpec.type === 'static') {
|
||||
return {
|
||||
peak: () => inputSpec.value,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
} else if (inputSpec.type === 'connected') {
|
||||
const input = cachedInput(next)
|
||||
@@ -64,13 +64,13 @@ export function buildObservableWorld() {
|
||||
if (!connection) {
|
||||
return {
|
||||
peak: () => undefined,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
}
|
||||
|
||||
const { componentId, path } = connection
|
||||
|
||||
const [p] = path ? path.split('.') : [undefined]
|
||||
const [p] = path ? path.split('.')[0].split('[') : [undefined]
|
||||
|
||||
let obs = observables[`${componentId}.${p}`]
|
||||
|
||||
@@ -78,16 +78,17 @@ export function buildObservableWorld() {
|
||||
console.warn('Observable at ' + componentId + '.' + p + ' not found')
|
||||
return {
|
||||
peak: () => undefined,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
}
|
||||
|
||||
obs.subscribe(input)
|
||||
input.next(obs.peak())
|
||||
return input
|
||||
} else if (inputSpec.type === 'user') {
|
||||
return {
|
||||
peak: () => inputSpec.value,
|
||||
next: () => {}
|
||||
next: () => { }
|
||||
}
|
||||
} else {
|
||||
throw Error('Unknown input type ' + inputSpec)
|
||||
|
||||
@@ -126,7 +126,8 @@ export type ConnectingInput = {
|
||||
|
||||
export type AppEditorContext = {
|
||||
worldStore: Writable<World | undefined>
|
||||
staticOutputs: Writable<Record<string, string[]>>
|
||||
staticOutputs: Writable<Record<string, string[]>>,
|
||||
lazyGrid: Writable<GridItem[]>,
|
||||
app: Writable<App>
|
||||
selectedComponent: Writable<string | undefined>
|
||||
mode: Writable<EditorMode>
|
||||
|
||||
@@ -183,7 +183,7 @@ export function isScriptByNameDefined(appInput: AppInput | undefined): boolean {
|
||||
}
|
||||
|
||||
if (appInput.type === 'runnable' && appInput.runnable?.type == 'runnableByName') {
|
||||
return Boolean(appInput.runnable?.name)
|
||||
return appInput.runnable?.name != undefined
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
ButtonType.SpacingClasses[spacingSize],
|
||||
'focus:ring-2 font-semibold',
|
||||
'duration-200 rounded-md',
|
||||
'justify-center items-center text-center whitespace-nowrap inline-flex',
|
||||
'justify-center items-center text-center whitespace-nowrap inline-flex truncate',
|
||||
btnClasses,
|
||||
disabled ? 'bg-gray-300' : ''
|
||||
),
|
||||
@@ -82,6 +82,7 @@
|
||||
async function onClick(event: MouseEvent) {
|
||||
if (!nonCaptureEvent) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
dispatch('click', event)
|
||||
if (href) {
|
||||
if (href.startsWith('http') || target == '_blank') {
|
||||
@@ -106,6 +107,7 @@
|
||||
<svelte:element
|
||||
this={href ? 'a' : 'button'}
|
||||
bind:this={element}
|
||||
on:pointerdown
|
||||
on:click={onClick}
|
||||
on:focus
|
||||
on:blur
|
||||
|
||||
@@ -89,6 +89,6 @@
|
||||
{/if}
|
||||
{:else}
|
||||
{#each Array(10).fill(0) as _}
|
||||
<Skeleton layout={[[4], 0.5]} />
|
||||
<Skeleton layout={[0.5, [4]]} />
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user