mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 00:03:07 +00:00
feat(frontend): Add actions to tables (#951)
* fix(frontend): WIP * fix(frontend): WIP * fix(frontend): Sub runnable * fix(frontend): revert changes * fix(frontend): fix build
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import DisplayResult from '$lib/components/DisplayResult.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, ComponentInputsSpec } from '../types'
|
||||
import type { AppEditorContext, InputsSpec } from '../types'
|
||||
import InputValue from './helpers/InputValue.svelte'
|
||||
|
||||
export let componentInputs: ComponentInputsSpec
|
||||
export let componentInputs: InputsSpec
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
|
||||
@@ -3,19 +3,22 @@
|
||||
import { classNames } from '$lib/utils'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Output } from '../rx'
|
||||
import type { AppEditorContext, ComponentInputsSpec, InputsSpec } from '../types'
|
||||
import type { AppComponent, AppEditorContext, InputsSpec } from '../types'
|
||||
import InputValue from './helpers/InputValue.svelte'
|
||||
import DebouncedInput from './helpers/DebouncedInput.svelte'
|
||||
import RunnableComponent from './helpers/RunnableComponent.svelte'
|
||||
import ButtonComponent from './common/ButtonComponent.svelte'
|
||||
|
||||
export let id: string
|
||||
export let inputs: InputsSpec
|
||||
export let path: string | undefined = undefined
|
||||
export let runType: 'script' | 'flow' | undefined = undefined
|
||||
export let inlineScriptName: string | undefined = undefined
|
||||
export let componentInputs: ComponentInputsSpec
|
||||
export let componentInputs: InputsSpec
|
||||
export let components: AppComponent[]
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { worldStore, staticOutputs: staticOutputsStore } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
export const staticOutputs: string[] = ['selectedRow', 'loading', 'result']
|
||||
|
||||
@@ -102,8 +105,17 @@
|
||||
</td>
|
||||
{/each}
|
||||
<td class="relative whitespace-nowrap px-4 py-2 text-right ">
|
||||
{#if false}
|
||||
<Button color="blue" size="xs" variant="contained">Edit</Button>
|
||||
{#if components?.length > 0}
|
||||
<div class="flex gap-2">
|
||||
{#each components as component}
|
||||
<ButtonComponent
|
||||
{...component}
|
||||
extraQueryParams={{ row }}
|
||||
bind:inputs={component.inputs}
|
||||
bind:staticOutputs={$staticOutputsStore[component.id]}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Button, type ButtonType } from '$lib/components/common'
|
||||
import type { ComponentInputsSpec, InputsSpec } from '../../types'
|
||||
import type { InputsSpec } from '../../types'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
import RunnableComponent from '../helpers/RunnableComponent.svelte'
|
||||
|
||||
@@ -9,17 +9,20 @@
|
||||
export let path: string | undefined = undefined
|
||||
export let runType: 'script' | 'flow' | undefined = undefined
|
||||
export let inlineScriptName: string | undefined = undefined
|
||||
export let componentInputs: ComponentInputsSpec
|
||||
export let componentInputs: InputsSpec
|
||||
export let extraQueryParams: Record<string, any> = {}
|
||||
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
let color: ButtonType.Color
|
||||
let size: ButtonType.Size
|
||||
let runnableComponent: RunnableComponent
|
||||
</script>
|
||||
|
||||
<InputValue input={componentInputs.label} bind:value={labelValue} />
|
||||
<InputValue input={componentInputs.color} bind:value={color} />
|
||||
<InputValue input={componentInputs.size} bind:value={size} />
|
||||
|
||||
<RunnableComponent
|
||||
bind:this={runnableComponent}
|
||||
@@ -29,12 +32,14 @@
|
||||
{inlineScriptName}
|
||||
{id}
|
||||
autoRefresh={false}
|
||||
{extraQueryParams}
|
||||
>
|
||||
<Button
|
||||
on:click={() => {
|
||||
runnableComponent?.runComponent()
|
||||
}}
|
||||
btnClasses="w-full h-full"
|
||||
{size}
|
||||
{color}
|
||||
>
|
||||
{labelValue}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import SvelteMarkdown from 'svelte-markdown'
|
||||
import type { ComponentInputsSpec } from '../../types'
|
||||
import type { InputsSpec } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let componentInputs: ComponentInputsSpec
|
||||
export let componentInputs: InputsSpec
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { StaticInput, DynamicInput, AppEditorContext, UserInput } from '../../types'
|
||||
import type { AppEditorContext, AppInputTransform } from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
|
||||
type T = string | number | boolean | Record<string | number, any> | undefined
|
||||
|
||||
export let input: DynamicInput | StaticInput | UserInput
|
||||
export let input: AppInputTransform
|
||||
export let value: T
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
@@ -17,6 +17,8 @@
|
||||
$worldStore?.connect<any>(input, onValueChange)
|
||||
} else if (input.type === 'static') {
|
||||
setValue()
|
||||
} else {
|
||||
value = undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -87,10 +87,10 @@
|
||||
}
|
||||
|
||||
// Only loads the schema
|
||||
$: if ($workspaceStore && path && runType) {
|
||||
$: if ($workspaceStore && path && runType && !schema) {
|
||||
// Remote schema needs to be loaded
|
||||
loadSchemaFromTriggerable($workspaceStore, path, runType)
|
||||
} else if (inlineScriptName && $app.inlineScripts[inlineScriptName]) {
|
||||
} else if (inlineScriptName && $app.inlineScripts[inlineScriptName] && !schema) {
|
||||
// Inline scripts directly provide the schema
|
||||
schema = $app.inlineScripts[inlineScriptName].schema
|
||||
}
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
let schemaStripped: Schema | undefined = undefined
|
||||
|
||||
function stripSchema(schema: Schema) {
|
||||
function stripSchema(schema: Schema, inputs: InputsSpec) {
|
||||
schemaStripped = JSON.parse(JSON.stringify(schema))
|
||||
|
||||
// Remove hidden static inputs
|
||||
@@ -134,7 +134,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
$: schema && stripSchema(schema)
|
||||
$: schema && inputs && stripSchema(schema, inputs)
|
||||
|
||||
$: disabledArgs = Object.keys(inputs).reduce(
|
||||
(disabledArgsAccumulator: string[], inputName: string) => {
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext, ComponentInputsSpec } from '../../types'
|
||||
import type { AppEditorContext, InputsSpec } from '../../types'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import InputValue from '../helpers/InputValue.svelte'
|
||||
|
||||
export let id: string
|
||||
export let componentInputs: ComponentInputsSpec
|
||||
export let componentInputs: InputsSpec
|
||||
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
|
||||
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { Pane } from 'svelte-splitpanes'
|
||||
import { writable } from 'svelte/store'
|
||||
import { buildWorld, type World } from '../rx'
|
||||
import type { App, AppEditorContext, ConnectingInput, EditorMode } from '../types'
|
||||
import type { App, AppEditorContext, ConnectingInput, EditorMode, InputType } from '../types'
|
||||
import AppEditorHeader from './AppEditorHeader.svelte'
|
||||
import GridEditor from './GridEditor.svelte'
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
const staticOutputs = writable<Record<string, string[]>>({})
|
||||
const selectedComponent = writable<string | undefined>(undefined)
|
||||
const mode = writable<EditorMode>('dnd')
|
||||
const connectingInput = writable<ConnectingInput>({
|
||||
const connectingInput = writable<ConnectingInput<InputType, any>>({
|
||||
opened: false,
|
||||
input: undefined
|
||||
})
|
||||
@@ -80,7 +80,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={20} minSize={20} maxSize={40}>
|
||||
<Pane size={25} minSize={20} maxSize={40}>
|
||||
{#if $mode === 'dnd'}
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { BUTTON_COLORS } from "../../../common";
|
||||
import { BUTTON_COLORS } from '../../../common'
|
||||
|
||||
const buttonColorOptions = [...BUTTON_COLORS]
|
||||
|
||||
export const staticValues = {
|
||||
buttonColorOptions,
|
||||
} as const
|
||||
buttonSizeOptions: ['xs', 'sm', 'md', 'lg', 'xl']
|
||||
} as const
|
||||
|
||||
@@ -13,7 +13,8 @@ const windmillComponents = {
|
||||
id: undefined,
|
||||
name: undefined,
|
||||
type: 'output',
|
||||
defaultValue: undefined
|
||||
defaultValue: {},
|
||||
fieldType: 'object'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +34,8 @@ const plainComponents: ComponentSet = {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Lorem ipsum',
|
||||
fieldType: 'textarea'
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Lorem ipsum'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -46,14 +48,24 @@ const plainComponents: ComponentSet = {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Lorem ipsum',
|
||||
fieldType: 'textarea'
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Lorem ipsum'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'blue',
|
||||
optionValuesKey: 'buttonColorOptions'
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'blue'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'md',
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'md'
|
||||
}
|
||||
},
|
||||
runnable: true
|
||||
@@ -67,8 +79,9 @@ const plainComponents: ComponentSet = {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Lorem ipsum',
|
||||
fieldType: 'textarea'
|
||||
value: undefined,
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Lorem ipsum'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,16 +119,22 @@ const tableComponents = {
|
||||
searchEnabled: {
|
||||
type: 'static',
|
||||
value: false,
|
||||
fieldType: 'boolean'
|
||||
fieldType: 'boolean',
|
||||
visible: true,
|
||||
defaultValue: false
|
||||
},
|
||||
paginationEnabled: {
|
||||
type: 'static',
|
||||
value: false,
|
||||
fieldType: 'boolean'
|
||||
fieldType: 'boolean',
|
||||
visible: true,
|
||||
defaultValue: false
|
||||
}
|
||||
},
|
||||
|
||||
runnable: true,
|
||||
card: true
|
||||
card: true,
|
||||
components: []
|
||||
}
|
||||
] as AppComponent[]
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@
|
||||
id,
|
||||
name,
|
||||
type: 'output',
|
||||
defaultValue: undefined
|
||||
defaultValue: undefined,
|
||||
fieldType: 'any'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,25 +80,18 @@
|
||||
scriptCreationDrawer.closeDrawer()
|
||||
}}
|
||||
>
|
||||
<label for="pathInput" class="text-sm font-semibold">
|
||||
Script name
|
||||
</label>
|
||||
<label for="pathInput" class="text-sm font-semibold"> Script name </label>
|
||||
<div class="flex justify-between items-center gap-4">
|
||||
<input id="pathInput" class="grow min-w-[150px]" bind:value={newScriptPath} />
|
||||
<Button
|
||||
on:click={createScript}
|
||||
size="sm"
|
||||
disabled={isTakenPath}
|
||||
startIcon={{icon: faPlus}}
|
||||
>
|
||||
<Button on:click={createScript} size="sm" disabled={isTakenPath} startIcon={{ icon: faPlus }}>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
{#if isTakenPath && !ignorePathError}
|
||||
<div transition:fade={{ duration: 100 }} class="text-sm text-red-600 h-5 mt-1">
|
||||
This name is already used.
|
||||
</div>
|
||||
{/if}
|
||||
{#if isTakenPath && !ignorePathError}
|
||||
<div transition:fade={{ duration: 100 }} class="text-sm text-red-600 h-5 mt-1">
|
||||
This name is already used.
|
||||
</div>
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { faBolt, faLink, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { ComponentInputsSpec } from '../../types'
|
||||
import InputsSpecEditor from './InputsSpecEditor.svelte'
|
||||
|
||||
export let componentInputSpecs: ComponentInputsSpec
|
||||
|
||||
let openedProp = Object.keys(componentInputSpecs)[0]
|
||||
</script>
|
||||
|
||||
<div class="w-full flex flex-col gap-4">
|
||||
{#each Object.keys(componentInputSpecs) as inputSpecKey, index (index)}
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full text-xs font-bold border rounded-md py-1 px-2 cursor-pointer hover:bg-gray-800 hover:text-white transition-all',
|
||||
openedProp !== inputSpecKey ? 'bg-gray-200 ' : 'bg-gray-600 text-gray-300'
|
||||
)}
|
||||
on:keypress
|
||||
on:click={() => {
|
||||
openedProp = inputSpecKey
|
||||
}}
|
||||
>
|
||||
{inputSpecKey}
|
||||
</div>
|
||||
{#if inputSpecKey === openedProp}
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<ToggleButtonGroup bind:selected={componentInputSpecs[inputSpecKey].type}>
|
||||
<ToggleButton position="left" value="static" startIcon={{ icon: faBolt }} size="xs">
|
||||
Static
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="output" startIcon={{ icon: faLink }} size="xs">
|
||||
Dynamic
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<InputsSpecEditor bind:appInputTransform={componentInputSpecs[inputSpecKey]} />
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
@@ -14,25 +14,34 @@
|
||||
import Icon from 'svelte-awesome'
|
||||
import type { AppComponent, AppEditorContext } from '../../types'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import ComponentInputsSpecsEditor from './ComponentInputsSpecsEditor.svelte'
|
||||
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
|
||||
import PickFlow from './PickFlow.svelte'
|
||||
import gridHelp from 'svelte-grid/build/helper/index.mjs'
|
||||
import PickInlineScript from './PickInlineScript.svelte'
|
||||
import TableActions from './TableActions.svelte'
|
||||
|
||||
export let component: AppComponent | undefined
|
||||
export let onDelete: (() => void) | undefined = undefined
|
||||
|
||||
const { app, staticOutputs } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function removeGridElement() {
|
||||
if (component) {
|
||||
const COLS = 6
|
||||
$app.grid = $app.grid.filter((gridComponent) => gridComponent.data.id !== component?.id)
|
||||
$app.grid = gridHelp.adjust($app.grid, COLS)
|
||||
|
||||
// Delete static inputs
|
||||
if (onDelete && component) {
|
||||
delete $staticOutputs[component.id]
|
||||
$staticOutputs = $staticOutputs
|
||||
|
||||
onDelete()
|
||||
// Delete static inputs
|
||||
} else {
|
||||
if (component) {
|
||||
const COLS = 6
|
||||
$app.grid = $app.grid.filter((gridComponent) => gridComponent.data.id !== component?.id)
|
||||
$app.grid = gridHelp.adjust($app.grid, COLS)
|
||||
|
||||
// Delete static inputs
|
||||
delete $staticOutputs[component.id]
|
||||
$staticOutputs = $staticOutputs
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -72,7 +81,7 @@
|
||||
{#if component.runnable && component['path'] === undefined && component['inlineScriptName'] === undefined}
|
||||
<span class="text-sm">Select a script or a flow to continue</span>
|
||||
<PickInlineScript
|
||||
scripts={(Object.keys($app.inlineScripts) || []).map((summary) => ({summary}))}
|
||||
scripts={(Object.keys($app.inlineScripts) || []).map((summary) => ({ summary }))}
|
||||
on:pick={({ detail }) => {
|
||||
if (component?.runnable) {
|
||||
// @ts-ignore
|
||||
@@ -107,11 +116,17 @@
|
||||
{/if}
|
||||
|
||||
{#if Object.values(component.componentInputs).length > 0}
|
||||
<PanelSection title="Component parameters">
|
||||
<ComponentInputsSpecsEditor bind:componentInputSpecs={component.componentInputs} />
|
||||
<PanelSection
|
||||
title={`Component parameters (${Object.values(component.componentInputs).length})`}
|
||||
>
|
||||
<InputsSpecsEditor bind:inputSpecs={component.componentInputs} userInputEnabled={false} />
|
||||
</PanelSection>
|
||||
{/if}
|
||||
|
||||
{#if component.type === 'tablecomponent' && Array.isArray(component.components)}
|
||||
<TableActions bind:components={component.components} />
|
||||
{/if}
|
||||
|
||||
{#if component.verticalAlignment !== undefined}
|
||||
<PanelSection title="Alignment">
|
||||
<div class="w-full text-xs font-bold">Horizontal alignment</div>
|
||||
@@ -150,7 +165,7 @@
|
||||
startIcon={{ icon: faTrashAlt }}
|
||||
on:click={removeGridElement}
|
||||
>
|
||||
Delete component
|
||||
Delete component: {component.id}
|
||||
</Button>
|
||||
</PanelSection>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
<script lang="ts">
|
||||
import type { AppEditorContext, DynamicInput } from '../../types'
|
||||
import type { AppEditorContext, DynamicInput, InputType } from '../../types'
|
||||
import { Badge, Button } from '$lib/components/common'
|
||||
import { faLink } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
export let input: DynamicInput
|
||||
export let input: DynamicInput<InputType, any>
|
||||
|
||||
const { connectingInput, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function applyConnection() {
|
||||
if (!$connectingInput.opened && $connectingInput.input !== undefined) {
|
||||
input = $connectingInput.input
|
||||
input.id = $connectingInput.input.id
|
||||
input.name = $connectingInput.input.name
|
||||
|
||||
// TODO: CHeck whether types are ok
|
||||
|
||||
// TODO: Check whether this is needed
|
||||
$selectedComponent = $selectedComponent
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import { classNames } from '$lib/utils'
|
||||
import { Badge, ToggleButton, ToggleButtonGroup } from '$lib/components/common'
|
||||
import { capitalize, classNames } from '$lib/utils'
|
||||
import { faBolt, faLink, faUser } from '@fortawesome/free-solid-svg-icons'
|
||||
import type { InputsSpec } from '../../types'
|
||||
import { fieldTypeToTsType } from '../../utils'
|
||||
import InputsSpecEditor from './InputsSpecEditor.svelte'
|
||||
|
||||
export let inputSpecs: InputsSpec
|
||||
export let userInputEnabled: boolean = true
|
||||
|
||||
const userTypeKeys = ['schemaProperty', 'defaultValue', 'value']
|
||||
let openedProp: string | undefined = Object.keys(inputSpecs)[0]
|
||||
|
||||
const userTypeKeys = ['value']
|
||||
const staticTypeKeys = ['value']
|
||||
const dynamicTypeKeys = ['id', 'name', 'defaultValue']
|
||||
const dynamicTypeKeys = ['id', 'name']
|
||||
|
||||
function sanitizeInputSpec(type: 'user' | 'static' | 'output', inputSpecKey: string) {
|
||||
const inputSpec = inputSpecs[inputSpecKey]
|
||||
@@ -38,42 +42,65 @@
|
||||
|
||||
inputSpecs[inputSpecKey] = inputSpec
|
||||
}
|
||||
|
||||
let openedProp = Object.keys(inputSpecs)[0]
|
||||
</script>
|
||||
|
||||
<div class="w-full flex flex-col gap-4">
|
||||
{#each Object.keys(inputSpecs) as inputSpecKey}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full text-xs font-bold border rounded-md py-1 px-2 cursor-pointer hover:bg-gray-800 hover:text-white transition-all',
|
||||
openedProp !== inputSpecKey ? 'bg-gray-200 ' : 'bg-gray-600 text-gray-300'
|
||||
)}
|
||||
on:click={() => {
|
||||
openedProp = inputSpecKey
|
||||
}}
|
||||
>
|
||||
{inputSpecKey}
|
||||
</div>
|
||||
{#if inputSpecKey === openedProp}
|
||||
<div class="flex flex-col w-full gap-2">
|
||||
<ToggleButtonGroup
|
||||
bind:selected={inputSpecs[inputSpecKey].type}
|
||||
on:select={(x) => sanitizeInputSpec(x.detail, inputSpecKey)}
|
||||
>
|
||||
<ToggleButton position="left" value="static" startIcon={{ icon: faBolt }} size="xs">
|
||||
Static
|
||||
</ToggleButton>
|
||||
<ToggleButton position="center" value="output" startIcon={{ icon: faLink }} size="xs">
|
||||
Dynamic
|
||||
</ToggleButton>
|
||||
<ToggleButton position="right" value="user" startIcon={{ icon: faUser }} size="xs">
|
||||
User
|
||||
</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<InputsSpecEditor bind:appInputTransform={inputSpecs[inputSpecKey]} canHide />
|
||||
<div class="w-full flex flex-col gap-2">
|
||||
{#each Object.keys(inputSpecs) as inputSpecKey, index (index)}
|
||||
{@const input = inputSpecs[inputSpecKey]}
|
||||
|
||||
<div>
|
||||
<div
|
||||
class={classNames(
|
||||
'w-full text-xs font-bold py-1.5 px-2 cursor-pointer transition-all justify-between flex items-center border border-gray-3 rounded-md',
|
||||
openedProp === inputSpecKey
|
||||
? 'bg-gray-700 hover:bg-gray-900 focus:bg-gray-900 text-white'
|
||||
: 'bg-white border-gray-300 hover:bg-gray-100 focus:bg-gray-100 text-gray-700'
|
||||
)}
|
||||
on:keypress
|
||||
on:click={() => {
|
||||
if (openedProp === inputSpecKey) {
|
||||
openedProp = undefined
|
||||
} else {
|
||||
openedProp = inputSpecKey
|
||||
}
|
||||
}}
|
||||
>
|
||||
{inputSpecKey}
|
||||
{#if input.fieldType}
|
||||
<Badge color={openedProp === inputSpecKey ? 'dark-blue' : 'blue'}>
|
||||
{capitalize(fieldTypeToTsType(input.fieldType))}
|
||||
</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if inputSpecKey === openedProp}
|
||||
<div class="flex flex-col w-full gap-2 my-2">
|
||||
<ToggleButtonGroup
|
||||
bind:selected={inputSpecs[inputSpecKey].type}
|
||||
on:selected={({ detail }) => sanitizeInputSpec(detail, inputSpecKey)}
|
||||
>
|
||||
<ToggleButton position="left" value="static" startIcon={{ icon: faBolt }} size="xs">
|
||||
Static
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
position={userInputEnabled ? 'center' : 'right'}
|
||||
value="output"
|
||||
startIcon={{ icon: faLink }}
|
||||
size="xs"
|
||||
>
|
||||
Dynamic
|
||||
</ToggleButton>
|
||||
{#if userInputEnabled}
|
||||
<ToggleButton position="right" value="user" startIcon={{ icon: faUser }} size="xs">
|
||||
User
|
||||
</ToggleButton>
|
||||
{/if}
|
||||
</ToggleButtonGroup>
|
||||
<InputsSpecEditor
|
||||
bind:appInputTransform={inputSpecs[inputSpecKey]}
|
||||
canHide={userInputEnabled}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
<script lang="ts">
|
||||
import type { StaticInput } from '../../types'
|
||||
import type { AppInputTransform } from '../../types'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { staticValues } from '../componentsPanel/componentStaticValues'
|
||||
|
||||
export let input: StaticInput
|
||||
export let input: AppInputTransform
|
||||
export let canHide: boolean
|
||||
</script>
|
||||
|
||||
{#if canHide}
|
||||
<Toggle bind:checked={input.visible} options={{ right: 'Visible' }} />
|
||||
{/if}
|
||||
{#if input.type === 'static'}
|
||||
{#if canHide}
|
||||
<Toggle bind:checked={input.visible} options={{ right: 'Visible' }} />
|
||||
{/if}
|
||||
|
||||
{#if input.fieldType === 'number'}
|
||||
<input type="number" bind:value={input.value} />
|
||||
{:else if input.fieldType === 'textarea'}
|
||||
<textarea bind:value={input.value} />
|
||||
{:else if input.fieldType === 'boolean'}
|
||||
<Toggle bind:checked={input.value} />
|
||||
{:else if input.fieldType === 'select'}
|
||||
<select bind:value={input.value}>
|
||||
{#each staticValues[input.optionValuesKey] || [] as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<input bind:value={input.value} />
|
||||
{#if input.fieldType === 'number'}
|
||||
<input type="number" bind:value={input.value} />
|
||||
{:else if input.fieldType === 'textarea'}
|
||||
<textarea bind:value={input.value} />
|
||||
{:else if input.fieldType === 'boolean'}
|
||||
<Toggle bind:checked={input.value} />
|
||||
{:else if input.fieldType === 'select'}
|
||||
<select bind:value={input.value}>
|
||||
{#each staticValues[input.optionValuesKey] || [] as option}
|
||||
<option value={option}>
|
||||
{option}
|
||||
</option>
|
||||
{/each}
|
||||
</select>
|
||||
{:else}
|
||||
<input bind:value={input.value} />
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { getNextId } from '$lib/components/flows/flowStateUtils'
|
||||
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppComponent, AppEditorContext } from '../../types'
|
||||
import { defaultProps } from '../componentsPanel/componentDefaultProps'
|
||||
import PanelSection from './common/PanelSection.svelte'
|
||||
import ComponentPanel from './ComponentPanel.svelte'
|
||||
|
||||
export let components: AppComponent[]
|
||||
|
||||
const { app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function addComponent() {
|
||||
const grid = $app.grid ?? []
|
||||
const id = getNextId(grid.map((gridItem) => gridItem.data.id))
|
||||
|
||||
const newComponent: AppComponent = {
|
||||
...defaultProps,
|
||||
id,
|
||||
type: 'buttoncomponent',
|
||||
componentInputs: {
|
||||
label: {
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'Action',
|
||||
fieldType: 'textarea',
|
||||
defaultValue: 'Action'
|
||||
},
|
||||
color: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'dark',
|
||||
optionValuesKey: 'buttonColorOptions',
|
||||
defaultValue: 'dark'
|
||||
},
|
||||
size: {
|
||||
fieldType: 'select',
|
||||
type: 'static',
|
||||
visible: true,
|
||||
value: 'xs',
|
||||
optionValuesKey: 'buttonSizeOptions',
|
||||
defaultValue: 'xs'
|
||||
}
|
||||
},
|
||||
runnable: true
|
||||
}
|
||||
|
||||
components = [...components, newComponent]
|
||||
}
|
||||
</script>
|
||||
|
||||
<PanelSection title="Table actions">
|
||||
<svelte:fragment slot="action">
|
||||
<Button size="xs" color="dark" startIcon={{ icon: faPlus }} on:click={addComponent}>
|
||||
Create an action
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
{#each components as component}
|
||||
<div class="w-full border">
|
||||
<div class="border-b py-1 px-2 text-sm text-white bg-gray-500">Component: {component.id}</div>
|
||||
<ComponentPanel
|
||||
bind:component
|
||||
onDelete={() => {
|
||||
components = components.filter((c) => c.id !== component.id)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</PanelSection>
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { classNames } from '$lib/utils'
|
||||
|
||||
export let title: string
|
||||
</script>
|
||||
|
||||
<div class="p-4 flex flex-col gap-2 items-start">
|
||||
<div class={classNames('flex flex-col gap-2 items-start p-4')}>
|
||||
<div class="flex justify-between items-center w-full">
|
||||
<div class="text-xs font-bold">{title}</div>
|
||||
<slot name="action" />
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
import type { Schema, SchemaProperty } from '$lib/common'
|
||||
import type { Schema } from '$lib/common'
|
||||
import type { Preview } from '$lib/gen'
|
||||
import type { FilledItem } from 'svelte-grid'
|
||||
import type { Writable } from 'svelte/store'
|
||||
import type { staticValues } from './editor/componentsPanel/componentStaticValues'
|
||||
import type { World } from './rx'
|
||||
|
||||
export type UserInput = {
|
||||
export type UserInput<T, V> = {
|
||||
type: 'user'
|
||||
schemaProperty: SchemaProperty
|
||||
// Default value override
|
||||
defaultValue: any
|
||||
value: any
|
||||
value: V | undefined
|
||||
defaultValue: V
|
||||
fieldType: T
|
||||
}
|
||||
|
||||
export type DynamicInput = {
|
||||
export type DynamicInput<T, V> = {
|
||||
type: 'output'
|
||||
id: FieldID | undefined
|
||||
name: string | undefined
|
||||
// Before any connection occures
|
||||
defaultValue: any
|
||||
defaultValue: V
|
||||
fieldType: T
|
||||
}
|
||||
|
||||
export type StaticInputType =
|
||||
('text'
|
||||
export type InputType =
|
||||
| 'text'
|
||||
| 'textarea'
|
||||
| 'number'
|
||||
| 'boolean'
|
||||
@@ -30,39 +29,36 @@ export type StaticInputType =
|
||||
| 'date'
|
||||
| 'time'
|
||||
| 'datetime'
|
||||
| 'object')
|
||||
| 'object'
|
||||
|
||||
type BaseStaticInput<T extends StaticInputType, V> = {
|
||||
fieldType: T
|
||||
export type StaticInput<T, V> = {
|
||||
value: V | undefined
|
||||
type: 'static'
|
||||
visible?: boolean
|
||||
defaultValue: V
|
||||
fieldType: T
|
||||
}
|
||||
|
||||
export type SelectStaticInput = BaseStaticInput<'select', string> & {
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
}
|
||||
type AppInput<T extends InputType, V> = StaticInput<T, V> | DynamicInput<T, V> | UserInput<T, V>
|
||||
|
||||
export type StaticInput =
|
||||
BaseStaticInput<'text', string>
|
||||
| BaseStaticInput<'textarea', string>
|
||||
| BaseStaticInput<'number', number>
|
||||
| BaseStaticInput<'boolean', boolean>
|
||||
| SelectStaticInput
|
||||
| BaseStaticInput<'date', string>
|
||||
| BaseStaticInput<'time', string>
|
||||
| BaseStaticInput<'datetime', string>
|
||||
| BaseStaticInput<'object', Record<string | number, any>>
|
||||
|
||||
|
||||
export type AppInputTransform = DynamicInput | StaticInput | UserInput
|
||||
export type AppInputTransform =
|
||||
| AppInput<'text', string>
|
||||
| AppInput<'textarea', string>
|
||||
| AppInput<'number', number>
|
||||
| AppInput<'boolean', boolean>
|
||||
| (AppInput<'select', string> & {
|
||||
/**
|
||||
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
|
||||
*/
|
||||
optionValuesKey: keyof typeof staticValues
|
||||
})
|
||||
| AppInput<'date', string>
|
||||
| AppInput<'time', string>
|
||||
| AppInput<'datetime', string>
|
||||
| AppInput<'object', Record<string | number, any>>
|
||||
|
||||
// Inner inputs, (search, filter, page, inputs of a script or flow)
|
||||
export type InputsSpec = Record<FieldID, AppInputTransform>
|
||||
export type ComponentInputsSpec = Record<FieldID, DynamicInput | StaticInput>
|
||||
|
||||
type Runnable = {
|
||||
inlineScriptName?: string
|
||||
@@ -76,11 +72,15 @@ type BaseComponent<T extends string> = {
|
||||
|
||||
export type TextComponent = BaseComponent<'textcomponent'>
|
||||
export type TextInputComponent = BaseComponent<'textinputcomponent'>
|
||||
export type ButtonComponent = BaseComponent<'buttoncomponent'>
|
||||
export type ButtonComponent = Runnable & BaseComponent<'buttoncomponent'>
|
||||
export type RunFormComponent = Runnable & BaseComponent<'runformcomponent'>
|
||||
export type BarChartComponent = BaseComponent<'barchartcomponent'>
|
||||
export type PieChartComponent = Runnable & BaseComponent<'piechartcomponent'>
|
||||
export type TableComponent = Runnable & BaseComponent<'tablecomponent'>
|
||||
export type TableComponent = Runnable &
|
||||
BaseComponent<'tablecomponent'> & {
|
||||
components: AppComponent[]
|
||||
}
|
||||
|
||||
export type DisplayComponent = BaseComponent<'displaycomponent'>
|
||||
export type ImageComponent = BaseComponent<'imagecomponent'>
|
||||
export type InputComponent = BaseComponent<'inputcomponent'>
|
||||
@@ -98,17 +98,16 @@ export type Aligned = {
|
||||
export interface BaseAppComponent extends Partial<Aligned> {
|
||||
id: ComponentID
|
||||
inputs: InputsSpec
|
||||
// Only dynamic inputs (Result of display)
|
||||
componentInputs: ComponentInputsSpec
|
||||
componentInputs: InputsSpec
|
||||
runnable?: boolean | undefined
|
||||
card?: boolean | undefined
|
||||
|
||||
// TODO: add min/max width/height
|
||||
}
|
||||
|
||||
export type AppComponent =
|
||||
BaseAppComponent & (
|
||||
RunFormComponent
|
||||
export type AppComponent = BaseAppComponent &
|
||||
(
|
||||
| RunFormComponent
|
||||
| DisplayComponent
|
||||
| TextInputComponent
|
||||
| BarChartComponent
|
||||
@@ -117,15 +116,15 @@ export type AppComponent =
|
||||
| TableComponent
|
||||
| ButtonComponent
|
||||
| PieChartComponent
|
||||
| ImageComponent
|
||||
| InputComponent
|
||||
| SelectComponent
|
||||
| CheckboxComponent
|
||||
| RadioComponent
|
||||
| ImageComponent
|
||||
| InputComponent
|
||||
| SelectComponent
|
||||
| CheckboxComponent
|
||||
| RadioComponent
|
||||
)
|
||||
|
||||
export type ComponentSet = {
|
||||
title: string,
|
||||
title: string
|
||||
components: AppComponent[]
|
||||
}
|
||||
|
||||
@@ -150,9 +149,9 @@ export type App = {
|
||||
title: string
|
||||
}
|
||||
|
||||
export type ConnectingInput = {
|
||||
export type ConnectingInput<T, V> = {
|
||||
opened: boolean
|
||||
input?: DynamicInput
|
||||
input?: DynamicInput<T, V>
|
||||
}
|
||||
|
||||
export type AppEditorContext = {
|
||||
@@ -161,7 +160,7 @@ export type AppEditorContext = {
|
||||
app: Writable<App>
|
||||
selectedComponent: Writable<string | undefined>
|
||||
mode: Writable<EditorMode>
|
||||
connectingInput: Writable<ConnectingInput>
|
||||
connectingInput: Writable<ConnectingInput<any, any>>
|
||||
}
|
||||
|
||||
export type EditorMode = 'dnd' | 'preview'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { InputsSpec } from './types'
|
||||
import type { InputsSpec, InputType } from './types'
|
||||
import type { Schema } from '$lib/common'
|
||||
|
||||
import { FlowService, ScriptService } from '$lib/gen'
|
||||
@@ -97,3 +97,21 @@ export function accessPropertyByPath<T>(object: T, path: string): T | undefined
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
export function fieldTypeToTsType(InputType: InputType): string {
|
||||
switch (InputType) {
|
||||
case 'text':
|
||||
case 'textarea':
|
||||
case 'date':
|
||||
case 'time':
|
||||
case 'datetime':
|
||||
case 'select':
|
||||
return 'string'
|
||||
case 'number':
|
||||
return 'number'
|
||||
case 'boolean':
|
||||
return 'boolean'
|
||||
case 'object':
|
||||
return 'object'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { truncate } from '$lib/utils'
|
||||
import { pluralize, truncate } from '$lib/utils'
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { Badge } from '../common'
|
||||
@@ -45,6 +45,7 @@
|
||||
{#if keys.length > 0}
|
||||
<span class:hidden={collapsed}>
|
||||
{#if level != 0}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class="cursor-pointer border border-gray-300 hover:bg-gray-200 px-1 rounded"
|
||||
on:click={collapse}
|
||||
@@ -100,6 +101,7 @@
|
||||
</ul>
|
||||
{#if level == 0 && topBrackets}<span class="h-0">{closeBracket}</span>{/if}
|
||||
</span>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<span
|
||||
class="border border-blue-600 rounded px-1 cursor-pointer hover:bg-gray-200"
|
||||
class:hidden={!collapsed}
|
||||
@@ -107,6 +109,11 @@
|
||||
>
|
||||
{openBracket}{collapsedSymbol}{closeBracket}
|
||||
</span>
|
||||
{#if collapsed}
|
||||
<span class="text-gray-500 text-xs">
|
||||
{pluralize(Object.keys(json).length, Array.isArray(json) ? 'item' : 'key')}
|
||||
</span>
|
||||
{/if}
|
||||
{#if !isLast && collapsed}
|
||||
<span class="text-black">,</span>
|
||||
{/if}
|
||||
|
||||
@@ -59,8 +59,9 @@ export function displayDate(dateString: string | undefined): string {
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return ''
|
||||
} else {
|
||||
return `${date.getFullYear()}/${date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
return `${date.getFullYear()}/${
|
||||
date.getMonth() + 1
|
||||
}/${date.getDate()} at ${date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,7 +377,6 @@ export function schemaToObject(schema: Schema, args: Record<string, any>): Objec
|
||||
return object
|
||||
}
|
||||
|
||||
|
||||
export type InputCat =
|
||||
| 'string'
|
||||
| 'number'
|
||||
@@ -536,7 +536,9 @@ export function classNames(...classes: Array<string | undefined>): string {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
export function scriptLangToEditorLang(lang: Script.language): 'typescript' | 'python' | 'go' | 'shell' {
|
||||
export function scriptLangToEditorLang(
|
||||
lang: Script.language
|
||||
): 'typescript' | 'python' | 'go' | 'shell' {
|
||||
if (lang == 'deno') {
|
||||
return 'typescript'
|
||||
} else if (lang == 'python3') {
|
||||
@@ -570,3 +572,7 @@ export function pluralize(quantity: number, word: string, customPlural?: string)
|
||||
return `${quantity} ${word}s`
|
||||
}
|
||||
}
|
||||
|
||||
export function capitalize(word: string): string {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user