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:
Faton Ramadani
2022-11-27 17:47:19 +01:00
committed by GitHub
parent 56204a511c
commit 1069105451
22 changed files with 362 additions and 219 deletions
@@ -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>
@@ -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" />
+49 -50
View File
@@ -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'
+19 -1
View File
@@ -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}
+10 -4
View File
@@ -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)
}