mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 16:00:38 +00:00
apps improvements
This commit is contained in:
@@ -224,7 +224,7 @@
|
||||
<div class="max-w-xs" />
|
||||
|
||||
<div
|
||||
class="relative pb-6 {$propPickerConfig?.propName == argName
|
||||
class="relative mb-6 {$propPickerConfig?.propName == argName
|
||||
? 'outline outline-offset-0 outline-2 outline-blue-500 rounded-md'
|
||||
: ''}"
|
||||
>
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
{#if !loading}
|
||||
<span class="w-5" />
|
||||
{:else}
|
||||
<Loader2 class="animate-spin" />
|
||||
<Loader2 class="animate-spin ml-1" size={14} />
|
||||
{/if}
|
||||
</Button>
|
||||
</AlignWrapper>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { isCodeInjection } from '$lib/components/flows/utils'
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte'
|
||||
import type { AppInput } from '../../inputType'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
@@ -14,26 +14,19 @@
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
$: input && setDefault()
|
||||
$: state = $worldStore?.state
|
||||
$: input && $worldStore && row && handleConnection()
|
||||
$: input && $state && input.type == 'template' && (value = getValue(input))
|
||||
|
||||
function setDefault() {
|
||||
if (!value && input.defaultValue) {
|
||||
value = input.defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
function handleConnection() {
|
||||
if (input.type === 'connected') {
|
||||
$worldStore?.connect<any>(input, onValueChange)
|
||||
} else if (input.type === 'row') {
|
||||
value = row[input.column]
|
||||
setTimeout(() => (value = row[input['column']]), 0)
|
||||
} else if (input.type === 'static' || input.type == 'template') {
|
||||
value = getValue(input)
|
||||
setTimeout(() => (value = getValue(input)), 0)
|
||||
} else {
|
||||
value = undefined
|
||||
setTimeout(() => (value = undefined), 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,11 @@
|
||||
outputs.loading.set(false, true)
|
||||
}
|
||||
|
||||
function setOutput() {
|
||||
if (outputs) {
|
||||
outputs.result?.set(result)
|
||||
}
|
||||
function setOutput(v: any) {
|
||||
outputs.result?.set(v, true)
|
||||
}
|
||||
|
||||
$: result !== undefined && outputs && setOutput()
|
||||
$: result && outputs && setOutput(result)
|
||||
</script>
|
||||
|
||||
{#if componentInput.type !== 'runnable'}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
export let result: any = undefined
|
||||
export let forceSchemaDisplay: boolean = false
|
||||
|
||||
const { worldStore, runnableComponents, workspace, appPath, mode } =
|
||||
const { worldStore, runnableComponents, workspace, appPath, isEditor } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
onMount(() => {
|
||||
@@ -47,7 +47,30 @@
|
||||
}, 200)
|
||||
}
|
||||
|
||||
$: fields && runnableInputValues && args && autoRefresh && testJobLoader && setDebouncedExecute()
|
||||
function computeStaticValues() {
|
||||
return Object.entries(fields ?? {})
|
||||
.filter(([k, v]) => v.type == 'static')
|
||||
.map(([name, field]) => {
|
||||
return [name, field['value']]
|
||||
})
|
||||
}
|
||||
|
||||
let lazyStaticValues = computeStaticValues()
|
||||
let currentStaticValues = lazyStaticValues
|
||||
|
||||
$: fields && (currentStaticValues = computeStaticValues())
|
||||
$: if (JSON.stringify(currentStaticValues) != JSON.stringify(lazyStaticValues)) {
|
||||
lazyStaticValues = currentStaticValues
|
||||
setDebouncedExecute()
|
||||
}
|
||||
|
||||
$: fields && (lazyStaticValues = computeStaticValues())
|
||||
$: runnableInputValues &&
|
||||
extraQueryParams &&
|
||||
args &&
|
||||
autoRefresh &&
|
||||
testJobLoader &&
|
||||
setDebouncedExecute()
|
||||
|
||||
// Test job internal state
|
||||
let testJob: CompletedJob | undefined = undefined
|
||||
@@ -164,17 +187,18 @@
|
||||
await testJobLoader?.abstractRun(() => {
|
||||
const nonStaticRunnableInputs = {}
|
||||
const staticRunnableInputs = {}
|
||||
Object.keys(fields ?? {}).forEach(([k, v]) => {
|
||||
Object.keys(fields ?? {}).forEach((k) => {
|
||||
let field = fields[k]
|
||||
if (field.type == 'static') {
|
||||
if (field?.type == 'static' && fields[k]) {
|
||||
staticRunnableInputs[k] = field.value
|
||||
} else {
|
||||
nonStaticRunnableInputs[k] = runnableInputValues[k]
|
||||
}
|
||||
}, {})
|
||||
})
|
||||
|
||||
const requestBody = {
|
||||
args: { ...nonStaticRunnableInputs, ...args },
|
||||
force_viewer_static_fields: $mode == 'preview' ? undefined : staticRunnableInputs
|
||||
force_viewer_static_fields: !isEditor ? undefined : staticRunnableInputs
|
||||
}
|
||||
|
||||
if (runnable?.type === 'runnableByName') {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
import { classNames, isObject } from '$lib/utils'
|
||||
import DebouncedInput from '../helpers/DebouncedInput.svelte'
|
||||
import AppTableFooter from './AppTableFooter.svelte'
|
||||
import RefreshButton from '../helpers/RefreshButton.svelte'
|
||||
import { tableOptions } from './tableOptions'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
|
||||
@@ -20,17 +19,22 @@
|
||||
export let configuration: Record<string, AppInput>
|
||||
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
|
||||
|
||||
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result']
|
||||
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result', 'search']
|
||||
|
||||
type T = Record<string, any>
|
||||
|
||||
$: result = [] as Array<Record<string, any>>
|
||||
|
||||
let search: 'Runnable' | 'Component' | 'Disabled' | undefined = undefined
|
||||
let search: 'By Runnable' | 'By Component' | 'Disabled' | undefined = undefined
|
||||
let searchValue = ''
|
||||
|
||||
let pagination: boolean | undefined = undefined
|
||||
let page = 1
|
||||
|
||||
$: setSearch(searchValue)
|
||||
|
||||
function setSearch(srch) {
|
||||
outputs?.search?.set(srch)
|
||||
}
|
||||
|
||||
const options = writable<TableOptions<T>>({
|
||||
data: [],
|
||||
@@ -79,7 +83,7 @@
|
||||
return result
|
||||
}
|
||||
return result.filter((row) =>
|
||||
Object.values(row).some((value) => value.toString().includes(searchValue))
|
||||
Object.values(row).some((value) => value?.toString()?.includes(searchValue))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -94,11 +98,11 @@
|
||||
let filteredResult: Array<Record<string, any>> = []
|
||||
|
||||
$: filteredResult && setOptions(filteredResult)
|
||||
$: extraQueryParams = search === 'Runnable' ? { search: searchValue, page } : { page, search: '' }
|
||||
$: search === 'Runnable' && (filteredResult = searchInResult(result, searchValue))
|
||||
$: (search === 'Runnable' || search === 'Disabled') && (filteredResult = result)
|
||||
$: search === 'By Component' && (filteredResult = searchInResult(result, searchValue))
|
||||
$: (search === 'By Runnable' || search === 'Disabled') && (filteredResult = result)
|
||||
$: outputs = $worldStore?.outputsById[id] as {
|
||||
selectedRow: Output<any>
|
||||
search: Output<string>
|
||||
}
|
||||
|
||||
function rerender() {
|
||||
@@ -111,7 +115,7 @@
|
||||
<InputValue {id} input={configuration.search} bind:value={search} />
|
||||
<InputValue {id} input={configuration.pagination} bind:value={pagination} />
|
||||
|
||||
<RunnableWrapper bind:componentInput {id} bind:result {extraQueryParams}>
|
||||
<RunnableWrapper bind:componentInput {id} bind:result>
|
||||
{#if Array.isArray(result) && result.every(isObject)}
|
||||
<div class="border border-gray-300 shadow-sm divide-y divide-gray-300 flex flex-col h-full">
|
||||
{#if search !== 'Disabled'}
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
const summaryStore = writable(summary)
|
||||
const connectingInput = writable<ConnectingInput>({
|
||||
opened: false,
|
||||
input: undefined
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
})
|
||||
|
||||
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
|
||||
@@ -66,7 +67,8 @@
|
||||
runnableComponents,
|
||||
appPath: path,
|
||||
workspace: $workspaceStore ?? '',
|
||||
onchange: () => saveDraft()
|
||||
onchange: () => saveDraft(),
|
||||
isEditor: true
|
||||
})
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined
|
||||
@@ -96,6 +98,11 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
class="absolute w-full h-screen bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
/>
|
||||
{/if}
|
||||
{#if !$userStore?.operator}
|
||||
<UnsavedConfirmationModal />
|
||||
{#if initialMode !== 'preview'}
|
||||
@@ -110,15 +117,16 @@
|
||||
appPath={path}
|
||||
{breakpoint}
|
||||
{policy}
|
||||
isEditor
|
||||
/>
|
||||
{:else}
|
||||
<SplitPanesWrapper class="max-w-full overflow-hidden">
|
||||
<Pane size={15} minSize={5} maxSize={33}>
|
||||
<Pane size={$connectingInput?.opened ? 40 : 15} minSize={5} maxSize={33}>
|
||||
<ContextPanel />
|
||||
</Pane>
|
||||
<Pane size={64}>
|
||||
<SplitPanesWrapper horizontal>
|
||||
<Pane size={70}>
|
||||
<Pane size={$connectingInput?.opened ? 100 : 70}>
|
||||
<div
|
||||
class="bg-gray-100 relative w-full h-full overflow-auto {app.fullscreen
|
||||
? ''
|
||||
@@ -129,22 +137,12 @@
|
||||
<GridEditor {policy} />
|
||||
</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>
|
||||
</Pane>
|
||||
<Pane size={30}>
|
||||
<Pane size={$connectingInput?.opened ? 0 : 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
|
||||
>
|
||||
</div>
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
</Pane>
|
||||
@@ -178,10 +176,7 @@
|
||||
</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"
|
||||
class="fixed top-32 p-2 z-50 flex justify-center items-center"
|
||||
transition:fly={{ duration: 100, y: -100 }}
|
||||
>
|
||||
<Alert title="Connecting" type="info">
|
||||
|
||||
@@ -12,7 +12,14 @@
|
||||
import { AppService, Policy } from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { faClipboard, faExternalLink, faSave } from '@fortawesome/free-solid-svg-icons'
|
||||
import { Eye, Laptop2, Pencil, Smartphone } from 'lucide-svelte'
|
||||
import {
|
||||
AlignHorizontalSpaceAround,
|
||||
Expand,
|
||||
Eye,
|
||||
Laptop2,
|
||||
Pencil,
|
||||
Smartphone
|
||||
} from 'lucide-svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import { Icon } from 'svelte-awesome'
|
||||
import { copyToClipboard, sendUserToast } from '../../../utils'
|
||||
@@ -211,11 +218,11 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="border-b flex flex-row justify-between py-1 gap-1 flex-wrap gap-y-2 px-4 items-center">
|
||||
<div class="border-b flex flex-row justify-between py-1 gap-4 flex-wrap gap-y-2 px-4 items-center">
|
||||
<div class="w-64">
|
||||
<input type="text" placeholder="App summary" class="text-sm w-full" bind:value={$summary} />
|
||||
</div>
|
||||
<div class="flex gap-8 items-center">
|
||||
<div class="flex gap-8 items-center grow justify-center">
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={$mode}>
|
||||
<ToggleButton position="left" value="dnd" size="xs">
|
||||
@@ -240,12 +247,12 @@
|
||||
|
||||
<ToggleButtonGroup bind:selected={$app.fullscreen}>
|
||||
<ToggleButton position="left" value={false} size="xs"
|
||||
>Centered <Tooltip
|
||||
><AlignHorizontalSpaceAround size={14} /> <Tooltip
|
||||
>The max width is 1168px and the content stay centered instead of taking the full page
|
||||
width</Tooltip
|
||||
></ToggleButton
|
||||
>
|
||||
<ToggleButton position="right" value={true} size="xs">Full</ToggleButton>
|
||||
<ToggleButton position="right" value={true} size="xs"><Expand size={14} /></ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
<div class="flex flex-row grow gap-4 justify-end ">
|
||||
|
||||
@@ -16,13 +16,7 @@
|
||||
export let app: App
|
||||
</script>
|
||||
|
||||
<Button
|
||||
btnClasses="pr-4"
|
||||
size="sm"
|
||||
variant="border"
|
||||
color="light"
|
||||
on:click={() => jsonViewerDrawer.toggleDrawer()}
|
||||
>
|
||||
<Button size="sm" variant="border" color="light" on:click={() => jsonViewerDrawer.toggleDrawer()}>
|
||||
<Icon data={faFileExport} scale={0.6} class="inline mr-2" />
|
||||
JSON
|
||||
</Button>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
export let policy: Policy
|
||||
export let summary: string
|
||||
export let workspace: string
|
||||
export let isEditor: boolean
|
||||
|
||||
const appStore = writable<App>(app)
|
||||
const worldStore = writable<World | undefined>(undefined)
|
||||
@@ -30,7 +31,8 @@
|
||||
|
||||
const connectingInput = writable<ConnectingInput>({
|
||||
opened: false,
|
||||
input: undefined
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
})
|
||||
|
||||
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
|
||||
@@ -48,7 +50,8 @@
|
||||
runnableComponents,
|
||||
appPath,
|
||||
workspace,
|
||||
onchange: undefined
|
||||
onchange: undefined,
|
||||
isEditor
|
||||
})
|
||||
|
||||
let mounted = false
|
||||
@@ -61,7 +64,7 @@
|
||||
$: width = $breakpoint === 'sm' ? 'max-w-[640px]' : 'w-full '
|
||||
</script>
|
||||
|
||||
<div class="h-full w-full {app.fullscreen ? '' : 'max-w-6xl'} px-4 mx-auto">
|
||||
<div class="h-full w-full {app.fullscreen ? '' : 'max-w-6xl'} mx-auto">
|
||||
{#if $appStore.grid}
|
||||
<div class={classNames('mx-auto pb-4', width)}>
|
||||
<GridEditor {policy} />
|
||||
|
||||
@@ -101,15 +101,26 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="bg-white px-2 pb-2 relative">
|
||||
<div class="w-full flex justify-between border-b px-4 py-2 mb-4 items-center gap-4">
|
||||
<div class="pb-2 relative z-20">
|
||||
<div
|
||||
class="w-full flex justify-between border-b {$connectingInput?.opened
|
||||
? ''
|
||||
: 'bg-gray-50 '}px-4 py-2 items-center gap-4"
|
||||
>
|
||||
<h2 class="truncate">{$summary}</h2>
|
||||
<RecomputeAllComponents />
|
||||
{#if !$connectingInput.opened}
|
||||
<RecomputeAllComponents />
|
||||
{/if}
|
||||
<div class="text-2xs text-gray-600"
|
||||
>{policy.on_behalf_of ? `on behalf of ${policy.on_behalf_of}` : ''}</div
|
||||
>
|
||||
</div>
|
||||
<div on:pointerdown={onpointerdown} on:pointerleave={onpointerup} on:pointerup={onpointerup}>
|
||||
<div
|
||||
class="px-4 pt-4 {$connectingInput?.opened ? '' : 'bg-gray-50/70'}"
|
||||
on:pointerdown={onpointerdown}
|
||||
on:pointerleave={onpointerup}
|
||||
on:pointerup={onpointerup}
|
||||
>
|
||||
<Grid
|
||||
bind:items={$app.grid}
|
||||
let:dataItem
|
||||
@@ -122,9 +133,23 @@
|
||||
{#each $lazyGrid as gridComponent (gridComponent.id)}
|
||||
{#if gridComponent.data.id === dataItem.data.id}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
{#if $connectingInput.opened}
|
||||
{#if $connectingInput.opened}
|
||||
<div
|
||||
on:pointerenter={() => ($connectingInput.hoveredComponent = gridComponent.data.id)}
|
||||
on:pointerleave={() => ($connectingInput.hoveredComponent = undefined)}
|
||||
class="absolute w-full h-full bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
|
||||
/>
|
||||
{/if}
|
||||
<div
|
||||
style="transform: translate(-50%, -50%);"
|
||||
class="absolute w-fit justify-center bg-indigo-500/90 left-[50%] top-[50%] z-50 px-6 rounded border text-white py-2 text-5xl center-center"
|
||||
>{dataItem.data.id}</div
|
||||
>
|
||||
{/if}
|
||||
<div
|
||||
class={classNames(
|
||||
'h-full w-full flex justify-center align-center',
|
||||
'h-full w-full flex justify-center align-center items-center',
|
||||
gridComponent.data.card ? 'border border-gray-100' : ''
|
||||
)}
|
||||
on:click|preventDefault|capture|once
|
||||
|
||||
@@ -75,14 +75,7 @@
|
||||
}
|
||||
|
||||
let newData: AppComponent = JSON.parse(JSON.stringify(appComponent))
|
||||
Object.values(newData.configuration).forEach((x) => {
|
||||
if (x.type == 'static' && !x.value) {
|
||||
x.value = x.defaultValue
|
||||
}
|
||||
})
|
||||
if (newData?.componentInput?.type == 'static') {
|
||||
newData.componentInput.value = newData.componentInput.defaultValue
|
||||
}
|
||||
|
||||
const newItem: GridItem = {
|
||||
data: newData,
|
||||
id: id
|
||||
|
||||
@@ -5,7 +5,7 @@ const buttonColorOptions = [...BUTTON_COLORS]
|
||||
export const staticValues = {
|
||||
buttonColorOptions,
|
||||
buttonSizeOptions: ['xs', 'sm', 'md', 'lg', 'xl'],
|
||||
tableSearchOptions: ['Component', 'Runnable', 'Disabled'],
|
||||
tableSearchOptions: ['By Component', 'By Runnable', 'Disabled'],
|
||||
chartThemeOptions: ['theme1', 'theme2', 'theme3'],
|
||||
textStyleOptions: ['Title', 'Subtitle', 'Body', 'Label', 'Caption']
|
||||
} as const
|
||||
|
||||
@@ -14,7 +14,6 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
value: 'Type...',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Type...'
|
||||
},
|
||||
},
|
||||
card: false
|
||||
@@ -45,13 +44,11 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
value: '',
|
||||
fieldType: 'date',
|
||||
defaultValue: ''
|
||||
},
|
||||
maxDate: {
|
||||
type: 'static',
|
||||
value: '',
|
||||
fieldType: 'date',
|
||||
defaultValue: ''
|
||||
}
|
||||
},
|
||||
card: false,
|
||||
@@ -67,7 +64,6 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
value: 'Label',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Label'
|
||||
}
|
||||
},
|
||||
card: false
|
||||
@@ -82,10 +78,6 @@ const inputs: ComponentSet = {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'object',
|
||||
defaultValue: [
|
||||
{ value: 'foo', label: 'Foo' },
|
||||
{ value: 'bar', label: 'Bar' }
|
||||
],
|
||||
value: [
|
||||
{ value: 'foo', label: 'Foo' },
|
||||
{ value: 'bar', label: 'Bar' }
|
||||
@@ -94,7 +86,6 @@ const inputs: ComponentSet = {
|
||||
itemKey: {
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'value',
|
||||
value: 'value'
|
||||
}
|
||||
},
|
||||
@@ -116,29 +107,29 @@ const buttons: ComponentSet = {
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined,
|
||||
defaultValue: undefined
|
||||
|
||||
},
|
||||
recomputeIds: undefined,
|
||||
configuration: {
|
||||
label: {
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Lorem ipsum'
|
||||
value: 'Lorem ipsum'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'blue'
|
||||
value: 'blue'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'xs'
|
||||
value: 'xs'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -153,7 +144,7 @@ const buttons: ComponentSet = {
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined,
|
||||
defaultValue: undefined
|
||||
|
||||
},
|
||||
recomputeIds: undefined,
|
||||
configuration: {
|
||||
@@ -161,21 +152,18 @@ const buttons: ComponentSet = {
|
||||
type: 'static',
|
||||
value: 'Submit',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'formcomponent'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: 'dark',
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'dark'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: 'xs',
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'xs'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -194,23 +182,23 @@ const display: ComponentSet = {
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Lorem ipsum',
|
||||
value: undefined
|
||||
value: 'Lorem ipsum',
|
||||
|
||||
},
|
||||
configuration: {
|
||||
style: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
optionValuesKey: 'textStyleOptions',
|
||||
defaultValue: 'Body'
|
||||
value: 'Body'
|
||||
},
|
||||
extraStyle:
|
||||
{
|
||||
type: 'static',
|
||||
fieldType: 'text',
|
||||
defaultValue: '',
|
||||
value: undefined
|
||||
value: '',
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
@@ -223,22 +211,22 @@ const display: ComponentSet = {
|
||||
search: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
optionValuesKey: 'tableSearchOptions',
|
||||
defaultValue: 'Disabled'
|
||||
value: 'Disabled'
|
||||
},
|
||||
pagination: {
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
fieldType: 'boolean',
|
||||
defaultValue: false
|
||||
value: false
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'object',
|
||||
defaultValue: [
|
||||
value: [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Lorem ipsum',
|
||||
@@ -250,7 +238,7 @@ const display: ComponentSet = {
|
||||
age: 42
|
||||
}
|
||||
],
|
||||
value: undefined
|
||||
|
||||
},
|
||||
card: true,
|
||||
actionButtons: []
|
||||
@@ -261,26 +249,24 @@ const display: ComponentSet = {
|
||||
configuration: {
|
||||
theme: {
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
fieldType: 'select',
|
||||
optionValuesKey: 'chartThemeOptions',
|
||||
defaultValue: 'theme1'
|
||||
value: 'theme1'
|
||||
},
|
||||
labels: {
|
||||
type: 'static',
|
||||
value: ['First', 'Second', 'Third'],
|
||||
fieldType: 'array',
|
||||
|
||||
subFieldType: 'text',
|
||||
defaultValue: ['First', 'Second', 'Third']
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
defaultValue: [25, 50, 25],
|
||||
value: undefined
|
||||
value: [25, 50, 25],
|
||||
|
||||
},
|
||||
card: true
|
||||
},
|
||||
@@ -290,25 +276,25 @@ const display: ComponentSet = {
|
||||
configuration: {
|
||||
theme: {
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
fieldType: 'select',
|
||||
optionValuesKey: 'chartThemeOptions',
|
||||
defaultValue: 'theme1'
|
||||
value: 'theme1'
|
||||
},
|
||||
labels: {
|
||||
type: 'static',
|
||||
value: undefined,
|
||||
|
||||
fieldType: 'array',
|
||||
subFieldType: 'text',
|
||||
defaultValue: ['Lorem ipsum', 'Lorem ipsum', 'Lorem ipsum']
|
||||
value: ['Lorem ipsum', 'Lorem ipsum', 'Lorem ipsum']
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'array',
|
||||
subFieldType: 'number',
|
||||
defaultValue: [25, 50, 25],
|
||||
value: undefined
|
||||
value: [25, 50, 25],
|
||||
|
||||
},
|
||||
card: true
|
||||
},
|
||||
@@ -318,8 +304,8 @@ const display: ComponentSet = {
|
||||
componentInput: {
|
||||
type: 'static',
|
||||
fieldType: 'object',
|
||||
defaultValue: { "foo": 42 },
|
||||
value: undefined
|
||||
value: { "foo": 42 },
|
||||
|
||||
},
|
||||
configuration: {},
|
||||
card: false
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
path
|
||||
},
|
||||
type: 'connected'
|
||||
}
|
||||
},
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,10 +37,14 @@
|
||||
</script>
|
||||
|
||||
<PanelSection noPadding titlePadding="px-4 pt-2" title="Outputs">
|
||||
<div class="overflow-auto min-w-[150px] border-t w-full relative flex flex-col gap-2 px-4">
|
||||
<div class="overflow-auto min-w-[150px] border-t w-full relative flex flex-col gap-2 px-4 pb-2">
|
||||
{#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">
|
||||
<div
|
||||
class="flex {$connectingInput?.opened
|
||||
? 'bg-white z-50'
|
||||
: ''} flex-row justify-between w-full -mb-2"
|
||||
>
|
||||
<button
|
||||
on:click|stopPropagation|preventDefault={$connectingInput.opened
|
||||
? undefined
|
||||
@@ -66,13 +71,15 @@
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full py-2 border relative overflow-auto',
|
||||
$selectedComponent === componentId ? 'border border-blue-500 ' : ''
|
||||
$connectingInput?.opened ? 'bg-white z-50' : '',
|
||||
`w-full py-2 border relative overflow-auto`,
|
||||
$selectedComponent === componentId ? 'border border-blue-500 ' : '',
|
||||
$connectingInput.hoveredComponent === componentId ? 'outline outline-blue-500' : ''
|
||||
)}
|
||||
>
|
||||
{#if $selectedComponent === componentId && $connectingInput?.opened}
|
||||
<!-- {#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}
|
||||
{/if} -->
|
||||
<ComponentOutputViewer
|
||||
{outputs}
|
||||
{componentId}
|
||||
|
||||
+15
-10
@@ -2,8 +2,8 @@
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import { faTrash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import type { InlineScript } from '../../types'
|
||||
import { createEventDispatcher, getContext, onMount } from 'svelte'
|
||||
import type { AppEditorContext, InlineScript } from '../../types'
|
||||
import { CheckCircle, Code2, X } from 'lucide-svelte'
|
||||
import InlineScriptEditorDrawer from './InlineScriptEditorDrawer.svelte'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
@@ -18,6 +18,8 @@
|
||||
export let inlineScript: InlineScript
|
||||
export let name: string | undefined = undefined
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
let editor: Editor
|
||||
|
||||
let validCode = false
|
||||
@@ -68,14 +70,17 @@
|
||||
</Badge>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
iconOnly
|
||||
startIcon={{ icon: faTrash }}
|
||||
on:click={() => dispatch('delete')}
|
||||
/>
|
||||
{#if $app.unusedInlineScripts.map((x) => x.name).includes(name ?? '')}
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
variant="border"
|
||||
iconOnly
|
||||
startIcon={{ icon: faTrash }}
|
||||
on:click={() => dispatch('delete')}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
size="xs"
|
||||
color="blue"
|
||||
|
||||
-2
@@ -32,8 +32,6 @@
|
||||
|
||||
let drawerFlowViewer: Drawer
|
||||
let flowPath: string = ''
|
||||
|
||||
// $: inlineScript && (componentInput = componentInput)
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={drawerFlowViewer} size="1200px">
|
||||
|
||||
+13
-14
@@ -30,19 +30,18 @@
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
{#if Array.isArray($app.unusedInlineScripts)}
|
||||
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
|
||||
{#if unusedInlineScript.name === selectedScriptComponentId}
|
||||
<InlineScriptEditor
|
||||
bind:inlineScript={unusedInlineScript.inlineScript}
|
||||
on:delete={() => {
|
||||
// remove the script from the array at the index
|
||||
$app.unusedInlineScripts.splice(index, 1)
|
||||
$app.unusedInlineScripts = [...$app.unusedInlineScripts]
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
|
||||
{#if `unused-${index}` === selectedScriptComponentId}
|
||||
<InlineScriptEditor
|
||||
bind:name={unusedInlineScript.name}
|
||||
bind:inlineScript={unusedInlineScript.inlineScript}
|
||||
on:delete={() => {
|
||||
// remove the script from the array at the index
|
||||
$app.unusedInlineScripts.splice(index, 1)
|
||||
$app.unusedInlineScripts = [...$app.unusedInlineScripts]
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
|
||||
+5
-5
@@ -11,7 +11,9 @@
|
||||
|
||||
function selectInlineScript(id: string) {
|
||||
selectedScriptComponentId = id
|
||||
$selectedComponent = selectedScriptComponentId
|
||||
if (!id.startsWith('unused-')) {
|
||||
$selectedComponent = selectedScriptComponentId
|
||||
}
|
||||
}
|
||||
|
||||
$: runnablesByName = $lazyGrid.reduce((acc, gridComponent) => {
|
||||
@@ -110,11 +112,9 @@
|
||||
<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 === unusedInlineScript.name
|
||||
? 'bg-blue-100 text-blue-600'
|
||||
: ''
|
||||
selectedScriptComponentId === `unused-${index}` ? 'bg-blue-100 text-blue-600' : ''
|
||||
)},"
|
||||
on:click={() => selectInlineScript(unusedInlineScript.name)}
|
||||
on:click={() => selectInlineScript(`unused-${index}`)}
|
||||
>
|
||||
<span class="text-xs truncate">{unusedInlineScript.name}</span>
|
||||
<Badge color="red">Detached</Badge>
|
||||
|
||||
+4
-4
@@ -43,25 +43,25 @@
|
||||
{/if}
|
||||
|
||||
<ToggleButton
|
||||
title="Connected"
|
||||
title="Connect"
|
||||
value="connected"
|
||||
position="center"
|
||||
startIcon={{ icon: faArrowRight }}
|
||||
size="xs"
|
||||
>
|
||||
{#if clientWidth > 250}
|
||||
<span class="hidden lg:block"> Connected </span>
|
||||
<span class="hidden lg:block"> Connect </span>
|
||||
{/if}
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
title="Computed"
|
||||
title="Compute"
|
||||
position="right"
|
||||
value="runnable"
|
||||
startIcon={{ icon: faCode }}
|
||||
size="xs"
|
||||
>
|
||||
{#if clientWidth > 250}
|
||||
<span class="hidden lg:block"> Computed </span>
|
||||
<span class="hidden lg:block"> Compute </span>
|
||||
{/if}
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
|
||||
@@ -23,10 +23,11 @@
|
||||
export let onDelete: (() => void) | undefined = undefined
|
||||
export let rowColumns = false
|
||||
|
||||
const { app, staticOutputs, runnableComponents, worldStore } =
|
||||
const { app, staticOutputs, runnableComponents, selectedComponent, worldStore } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function removeGridElement() {
|
||||
$selectedComponent = undefined
|
||||
if (onDelete && component) {
|
||||
delete $staticOutputs[component.id]
|
||||
$staticOutputs = $staticOutputs
|
||||
@@ -92,7 +93,7 @@ declare const ${k} = ${JSON.stringify(v)};
|
||||
{#if component}
|
||||
<div class="flex flex-col min-w-[150px] w-full divide-y">
|
||||
{#if component.componentInput}
|
||||
<PanelSection title={component.componentInput.fieldType === 'any' ? 'Runnable' : 'Input'}>
|
||||
<PanelSection title={component.componentInput.fieldType === 'any' ? 'By Runnable' : 'Input'}>
|
||||
<svelte:fragment slot="action">
|
||||
<span
|
||||
class={classNames(
|
||||
|
||||
@@ -25,22 +25,19 @@
|
||||
label: {
|
||||
type: 'static',
|
||||
value: 'Action',
|
||||
fieldType: 'text',
|
||||
defaultValue: 'Action'
|
||||
fieldType: 'text'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: 'dark',
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'dark'
|
||||
optionValuesKey: 'buttonColorOptions'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
value: 'xs',
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'xs'
|
||||
optionValuesKey: 'buttonSizeOptions'
|
||||
}
|
||||
},
|
||||
componentInput: {
|
||||
@@ -48,7 +45,7 @@
|
||||
fieldType: 'any',
|
||||
fields: {},
|
||||
runnable: undefined,
|
||||
defaultValue: undefined
|
||||
value: undefined
|
||||
},
|
||||
recomputeIds: undefined,
|
||||
card: false
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
$$props.class,
|
||||
'flex flex-col h-full gap-2 items-start',
|
||||
noPadding ? '' : smallPadding ? 'p-2' : 'p-4'
|
||||
)}
|
||||
|
||||
+4
-2
@@ -19,7 +19,8 @@
|
||||
componentInput.connection = $connectingInput.input.connection
|
||||
$connectingInput = {
|
||||
opened: false,
|
||||
input: undefined
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,7 +68,8 @@
|
||||
if (componentInput.type === 'connected') {
|
||||
$connectingInput = {
|
||||
opened: true,
|
||||
input: undefined
|
||||
input: undefined,
|
||||
hoveredComponent: undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
+2
@@ -186,6 +186,7 @@
|
||||
color="light"
|
||||
variant="border"
|
||||
startIcon={{ icon: faPlus }}
|
||||
btnClasses="truncate"
|
||||
>
|
||||
Create an inline script
|
||||
</Button>
|
||||
@@ -194,6 +195,7 @@
|
||||
size="sm"
|
||||
color="blue"
|
||||
startIcon={{ icon: faMousePointer }}
|
||||
btnClasses="truncate"
|
||||
>
|
||||
Select a script or flow
|
||||
</Button>
|
||||
|
||||
@@ -84,7 +84,6 @@ type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
|
||||
|
||||
type InputConfiguration<T extends InputType, U, V extends InputType> = {
|
||||
fieldType: T
|
||||
defaultValue: U
|
||||
subFieldType?: V
|
||||
format?: string | undefined
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ export type ConnectingInput = {
|
||||
opened: boolean
|
||||
input?: ConnectedInput
|
||||
sourceName?: string
|
||||
hoveredComponent: string | undefined
|
||||
}
|
||||
|
||||
export type AppEditorContext = {
|
||||
@@ -137,7 +138,8 @@ export type AppEditorContext = {
|
||||
runnableComponents: Writable<Record<string, () => Promise<void>>>
|
||||
appPath: string,
|
||||
workspace: string,
|
||||
onchange: (() => void) | undefined
|
||||
onchange: (() => void) | undefined,
|
||||
isEditor: boolean
|
||||
}
|
||||
|
||||
export type EditorMode = 'dnd' | 'preview'
|
||||
|
||||
@@ -58,8 +58,7 @@ export function schemaToInputsSpec(schema: Schema): Record<string, StaticAppInpu
|
||||
|
||||
accu[key] = {
|
||||
type: 'static',
|
||||
defaultValue: property.default,
|
||||
value: undefined,
|
||||
value: property.default,
|
||||
visible: property.format ? false : true,
|
||||
fieldType: property.type,
|
||||
format: property.format
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
>
|
||||
{/if}
|
||||
<h3 class="pb-2 pt-4">
|
||||
Inline new <span class="text-blue-500">{kind == 'script' ? 'common' : kind}</span> script
|
||||
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
|
||||
<Tooltip>
|
||||
Embed a script directly inside a flow instead of saving the script into your workspace for
|
||||
reuse. You can always save an inline script to your workspace later.
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
<script lang="ts">
|
||||
import PropPicker from '$lib/components/propertyPicker/PropPicker.svelte'
|
||||
import PropPickerResult from '$lib/components/propertyPicker/PropPickerResult.svelte'
|
||||
import { clickOutside } from '$lib/utils'
|
||||
import { createEventDispatcher, setContext } from 'svelte'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
@@ -48,34 +49,44 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<Splitpanes>
|
||||
<Pane minSize={20} size={60} class="relative p-4 !transition-none">
|
||||
<slot />
|
||||
</Pane>
|
||||
<Pane minSize={20} size={40} class="py-2 relative !transition-none">
|
||||
{#if result}
|
||||
<PropPickerResult
|
||||
{result}
|
||||
on:select={({ detail }) => {
|
||||
dispatch('select', detail)
|
||||
if ($propPickerConfig?.onSelect(detail)) {
|
||||
propPickerConfig.set(undefined)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if pickableProperties}
|
||||
<PropPicker
|
||||
{displayContext}
|
||||
{error}
|
||||
{pickableProperties}
|
||||
{notSelectable}
|
||||
on:select={({ detail }) => {
|
||||
dispatch('select', detail)
|
||||
if ($propPickerConfig?.onSelect(detail)) {
|
||||
propPickerConfig.set(undefined)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
<div
|
||||
class="h-full w-full"
|
||||
use:clickOutside
|
||||
on:click_outside={() => propPickerConfig.set(undefined)}
|
||||
>
|
||||
<Splitpanes>
|
||||
<Pane minSize={20} size={60} class="relative p-4 !transition-none">
|
||||
<slot />
|
||||
</Pane>
|
||||
<Pane
|
||||
minSize={20}
|
||||
size={40}
|
||||
class="pt-2 relative !transition-none {$propPickerConfig ? 'border-2 border-blue-500' : ''}"
|
||||
>
|
||||
{#if result}
|
||||
<PropPickerResult
|
||||
{result}
|
||||
on:select={({ detail }) => {
|
||||
dispatch('select', detail)
|
||||
if ($propPickerConfig?.onSelect(detail)) {
|
||||
propPickerConfig.set(undefined)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if pickableProperties}
|
||||
<PropPicker
|
||||
{displayContext}
|
||||
{error}
|
||||
{pickableProperties}
|
||||
{notSelectable}
|
||||
on:select={({ detail }) => {
|
||||
dispatch('select', detail)
|
||||
if ($propPickerConfig?.onSelect(detail)) {
|
||||
propPickerConfig.set(undefined)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
|
||||
@@ -117,8 +117,10 @@
|
||||
{#if !isLast && collapsed}
|
||||
<span class="text-black">,</span>
|
||||
{/if}
|
||||
{:else}
|
||||
{:else if topBrackets}
|
||||
<span class="text-black">{openBracket}{closeBracket}</span>
|
||||
{:else}
|
||||
<span class="text-gray-600 text-xs">No items</span>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col h-full">
|
||||
<div class="flex flex-col h-full ">
|
||||
<div class="px-2">
|
||||
{#if !notSelectable}
|
||||
<div class="flex flex-row space-x-1">
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
appPath={app.path}
|
||||
{breakpoint}
|
||||
policy={app.policy}
|
||||
isEditor={false}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
appPath={app.path}
|
||||
{breakpoint}
|
||||
policy={app.policy}
|
||||
isEditor={false}
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
|
||||
Reference in New Issue
Block a user