refactor components to ease adding custom components

This commit is contained in:
Ruben Fiszel
2023-02-03 02:11:06 +01:00
parent 9f7d31f46f
commit cd4023cdb5
30 changed files with 1396 additions and 1421 deletions
@@ -7,7 +7,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let initializing
export let initializing: boolean | undefined = undefined
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
let result: any = undefined
@@ -18,7 +18,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = ['loading', 'result']
@@ -4,7 +4,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = ['result', 'loading']
@@ -18,7 +18,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = ['loading', 'result']
@@ -19,7 +19,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing
export let initializing: boolean | undefined = undefined
let zoomable = false
let pannable = false
@@ -13,7 +13,7 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: Record<string, AppInput>
export let initializing
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = ['result', 'loading']
@@ -68,25 +68,15 @@
</div>
{:else}
<div class="flex flex-wrap gap-2">
<svelte:element
this={component}
class="whitespace-pre-wrap {classes}"
style={extraStyle}
>
<svelte:element this={component} class="whitespace-pre-wrap {classes}" style={extraStyle}>
{String(result)}
</svelte:element>
{#if copyButton && result}
<Popover notClickable>
<Button
size="xs"
btnClasses="!px-2"
on:click={() => copyToClipboard(result)}
>
<Button size="xs" btnClasses="!px-2" on:click={() => copyToClipboard(result)}>
<Clipboard size={14} strokeWidth={2} />
</Button>
<svelte:fragment slot="text">
Copy to clipboard
</svelte:fragment>
<svelte:fragment slot="text">Copy to clipboard</svelte:fragment>
</Popover>
{/if}
</div>
@@ -22,7 +22,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = ['loading', 'result']
@@ -7,7 +7,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = ['result', 'loading']
@@ -8,7 +8,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let initializing
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = ['result', 'loading']
@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import type { Output } from '../../rx'
import type { AppEditorContext, BaseAppComponent, ButtonComponent } from '../../types'
import type { AppEditorContext, BaseAppComponent } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import type { AppInput } from '../../inputType'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
@@ -13,12 +13,13 @@
import AppTableFooter from './AppTableFooter.svelte'
import { tableOptions } from './tableOptions'
import Alert from '$lib/components/common/alert/Alert.svelte'
import type { ButtonComponent } from '../../editor/Component.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: Record<string, AppInput>
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
export let initializing
export let initializing: boolean | undefined = undefined
export const staticOutputs: string[] = [
'selectedRow',
@@ -211,21 +212,18 @@
{/each}
{#if actionButtons.length > 0}
<td
class="p-2 "
on:click={() => toggleRow(row, rowIndex)}
>
<div class="center-center h-full w-full flex-wrap gap-1">
{#each actionButtons as actionButton, actionIndex (actionIndex)}
<AppButton
noWFull
{...actionButton}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
/>
{/each}
</div>
<td class="p-2 " on:click={() => toggleRow(row, rowIndex)}>
<div class="center-center h-full w-full flex-wrap gap-1">
{#each actionButtons as actionButton, actionIndex (actionIndex)}
<AppButton
noWFull
{...actionButton}
extraQueryParams={{ row: row.original }}
bind:componentInput={actionButton.componentInput}
bind:staticOutputs={$staticOutputsStore[actionButton.id]}
/>
{/each}
</div>
</td>
{/if}
</tr>
@@ -1,6 +1,5 @@
<script lang="ts">
import type { AppComponent } from '../types'
import { displayData } from '../utils'
import { components, type AppComponent } from './Component.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
export let component: AppComponent
@@ -11,7 +10,7 @@
<div class="mb-8">
<div class="flex justify-between mb-4">
<span class="text-sm font-bold">{component.id}</span>
<span class="text-sm font-bold">{displayData[component.type].name}</span>
<span class="text-sm font-bold">{components[component.type].name}</span>
</div>
{#if resourceOnly && Object.keys(component.componentInput.fields).filter((fieldKey) => {
@@ -1,6 +1,6 @@
<script lang="ts">
import type { ConnectedAppInput, RowAppInput, StaticAppInput, UserAppInput } from '../inputType'
import type { AppComponent } from '../types'
import type { AppComponent } from './Component.svelte'
import InputsSpecsEditor from './settingsPanel/InputsSpecsEditor.svelte'
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
@@ -49,10 +49,11 @@
StaticAppInput,
UserAppInput
} from '../inputType'
import type { AppComponent, AppEditorContext } from '../types'
import type { AppEditorContext } from '../types'
import { toStatic } from '../utils'
import AppExportButton from './AppExportButton.svelte'
import AppInputs from './AppInputs.svelte'
import type { AppComponent } from './Component.svelte'
import PanelSection from './settingsPanel/common/PanelSection.svelte'
async function hash(message) {
File diff suppressed because it is too large Load Diff
@@ -1,189 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
import { fade } from 'svelte/transition';
import { classNames } from '$lib/utils'
import type { AppComponent, AppEditorContext } from '../types'
import { Loader2 } from 'lucide-svelte'
import BarChartComponent from '../components/dataDisplay/AppBarChart.svelte'
import DisplayComponent from '../components/DisplayComponent.svelte'
import TableComponent from '../components/table/AppTable.svelte'
import TextComponent from '../components/dataDisplay/AppText.svelte'
import ButtonComponent from '../components/buttons/AppButton.svelte'
import PieChartComponent from '../components/dataDisplay/AppPieChart.svelte'
import SelectComponent from '../components/selectInputs/AppSelect.svelte'
import CheckboxComponent from '../components/selectInputs/AppCheckbox.svelte'
import TextInputComponent from '../components/textInputs/AppTextInput.svelte'
import NumberInputComponent from '../components/numberInputs/AppNumberInput.svelte'
import DateInputComponent from '../components/dateInputs/AppDateInput.svelte'
import ComponentHeader from './ComponentHeader.svelte'
import AppForm from '../components/form/AppForm.svelte'
import AppScatterChart from '../components/dataDisplay/AppScatterChart.svelte'
import AppTimeseries from '../components/dataDisplay/AppTimeseries.svelte'
import AppHtml from '../components/dataDisplay/AppHtml.svelte'
import AppSliderInputs from '../components/numberInputs/AppSliderInputs.svelte'
import AppFormButton from '../components/form/AppFormButton.svelte'
import VegaLiteHtml from '../components/dataDisplay/VegaLiteHtml.svelte'
import PlotlyHtml from '../components/dataDisplay/PlotlyHtml.svelte'
export let component: AppComponent
export let selected: boolean
export let locked: boolean = false
export let pointerdown: boolean = false
const { staticOutputs, mode, connectingInput } = getContext<AppEditorContext>('AppEditorContext')
let hover = false
let initializing
</script>
<div
on:pointerenter={() => (hover = true)}
on:pointerleave={() => (hover = false)}
class="h-full flex flex-col w-full component"
>
{#if $mode !== 'preview'}
<ComponentHeader {hover} {pointerdown} {component} {selected} on:delete on:lock {locked} />
{/if}
<div
on:pointerdown={(e) => {
// Removed in https://github.com/windmill-labs/windmill/pull/1171
// In case of a bug, try stopping propagation on the native event
// and dispatch a custom event: `e?.stopPropagation(); dispatch('select');`
// if ($mode === 'preview') {
// e?.stopPropagation()
// }
}}
class={classNames(
'border h-full bg-white',
selected && $mode !== 'preview' ? 'border-blue-500' : 'border-white',
!selected && $mode !== 'preview' && !component.card ? 'border-gray-100' : '',
$mode !== 'preview' && !$connectingInput.opened ? 'hover:border-blue-500' : '',
component.softWrap ? '' : 'overflow-auto',
$mode != 'preview' ? 'cursor-pointer' : '',
'relative z-auto'
)}
>
{#if component.type === 'displaycomponent'}
<DisplayComponent
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'barchartcomponent'}
<BarChartComponent
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'timeseriescomponent'}
<AppTimeseries
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'htmlcomponent'}
<AppHtml
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'vegalitecomponent'}
<VegaLiteHtml
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'plotlycomponent'}
<PlotlyHtml
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'scatterchartcomponent'}
<AppScatterChart
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'piechartcomponent'}
<PieChartComponent
{...component}
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
bind:componentInput={component.componentInput}
/>
{:else if component.type === 'tablecomponent'}
<TableComponent
{...component}
bind:initializing
bind:staticOutputs={$staticOutputs[component.id]}
bind:componentInput={component.componentInput}
bind:actionButtons={component.actionButtons}
/>
{:else if component.type === 'textcomponent'}
<TextComponent
{...component}
bind:initializing
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'buttoncomponent'}
<ButtonComponent
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'selectcomponent'}
<SelectComponent {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'formcomponent'}
<AppForm
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'formbuttoncomponent'}
<AppFormButton
{...component}
bind:componentInput={component.componentInput}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'checkboxcomponent'}
<CheckboxComponent {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'textinputcomponent'}
<TextInputComponent {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'passwordinputcomponent'}
<TextInputComponent
inputType="password"
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'dateinputcomponent'}
<DateInputComponent
inputType="date"
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'numberinputcomponent'}
<NumberInputComponent {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'slidercomponent'}
<AppSliderInputs {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{/if}
</div>
</div>
{#if initializing}
<div
out:fade={{ duration: 200 }}
class="absolute inset-0 center-center flex-col bg-white text-gray-600 border"
>
<Loader2 class="animate-spin" size={16} />
<span class="text-xs mt-1">Loading</span>
</div>
{/if}
@@ -1,10 +1,11 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import type { AppComponent, AppEditorContext } from '../types'
import type { AppEditorContext } from '../types'
import { Anchor, Bug, Move } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import Popover from '$lib/components/Popover.svelte'
import { Alert, Button } from '$lib/components/common'
import type { AppComponent } from './Component.svelte'
export let component: AppComponent
export let selected: boolean
@@ -2,13 +2,13 @@
import { getContext, afterUpdate } from 'svelte'
import type { AppEditorContext } from '../types'
import Grid from 'svelte-grid'
import ComponentEditor from './ComponentEditor.svelte'
import { classNames } from '$lib/utils'
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
import type { Policy } from '$lib/gen'
import HiddenComponent from '../components/HiddenComponent.svelte'
import Component from './Component.svelte'
export let policy: Policy
@@ -102,11 +102,11 @@
}
afterUpdate(() => {
if($selectedComponent) {
if ($selectedComponent) {
const parents = document.querySelectorAll<HTMLElement>('.svlt-grid-item')
parents.forEach((parent) => {
const hasActiveChild = !!parent.querySelector('.active-grid-item')
if(hasActiveChild) {
if (hasActiveChild) {
parent.style.setProperty('z-index', '100')
} else {
parent.style.removeProperty('z-index')
@@ -170,7 +170,7 @@
gridComponent.data.card ? 'border border-gray-100' : ''
)}
>
<ComponentEditor
<Component
{pointerdown}
bind:component={gridComponent.data}
selected={$selectedComponent === dataItem.data.id}
@@ -1,6 +1,7 @@
<script lang="ts">
import { getContext } from 'svelte'
import type { AppEditorContext, TableComponent } from '../types'
import type { AppEditorContext } from '../types'
import type { TableComponent } from './Component.svelte'
import ComponentPanel from './settingsPanel/ComponentPanel.svelte'
export let component: TableComponent
@@ -1,59 +1,25 @@
<script lang="ts">
import { slide } from 'svelte/transition'
import Icon from 'svelte-awesome'
import type { AppComponent, AppEditorContext, GridItem } from '../../types'
import { displayData } from '../../utils'
import { componentSets } from './data'
import type { AppEditorContext, GridItem } from '../../types'
import gridHelp from 'svelte-grid/build/helper/index.mjs'
import { getContext, onMount } from 'svelte'
import { getNextId } from '$lib/components/flows/flowStateUtils'
import type { Size } from 'svelte-grid'
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
import { isOpenStore } from './store'
import { gridColumns } from '../../gridUtils'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import {
components as componentsRecord,
componentSets,
getRecommendedDimensionsByComponent,
type AppComponent
} from '../Component.svelte'
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
function getRecommendedDimensionsByComponent(
componentType: AppComponent['type'],
column: number
): Size {
// Dimensions key formula: <mobile width>:<mobile height>-<desktop width>:<desktop height>
const dimensions: Record<`${number}:${number}-${number}:${number}`, AppComponent['type'][]> = {
'1:1-3:1': ['textcomponent'],
'1:1-2:1': ['buttoncomponent', 'checkboxcomponent'],
'1:2-1:2': ['htmlcomponent'],
'2:1-3:1': [
'textinputcomponent',
'numberinputcomponent',
'selectcomponent',
'passwordinputcomponent',
'dateinputcomponent'
],
'3:5-6:5': ['formcomponent'],
'2:8-6:8': [
'timeseriescomponent',
'barchartcomponent',
'piechartcomponent',
'displaycomponent',
'scatterchartcomponent',
'vegalitecomponent',
'plotlycomponent'
],
'3:10-6:10': ['tablecomponent']
}
// Finds the key that is associated with the component type and extracts the dimensions from it
const [dimension] = Object.entries(dimensions).find(([_, value]) =>
value.includes(componentType)
) || ['2:1-2:1']
const size = dimension.split('-')[column === 3 ? 0 : 1].split(':')
return { w: +size[0], h: +size[1] }
}
function addComponent(appComponent: AppComponent) {
function addComponent(appComponentType: AppComponent['type']) {
$dirtyStore = true
const grid = $app.grid ?? []
const id = getNextId(
@@ -61,6 +27,8 @@
true
)
const appComponent = componentsRecord[appComponentType].data
appComponent.id = id
const newComponent = {
@@ -122,13 +90,13 @@
{#each components as item}
<button
on:click={() => addComponent(item)}
title={displayData[item.type].name}
title={componentsRecord[item].name}
class="border w-24 shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-white rounded-md hover:bg-gray-100 duration-200"
>
<svelte:component this={displayData[item.type].icon} />
<svelte:component this={componentsRecord[item].icon} />
<div class="text-xs w-full text-center ellipsize">
{displayData[item.type].name}
{componentsRecord[item].name}
</div>
</button>
{/each}
@@ -1,597 +0,0 @@
import type { ComponentSet } from '../../types'
import { defaultAlignement } from './componentDefaultProps'
const inputs: ComponentSet = {
title: 'Inputs',
components: [
{
softWrap: true,
verticalAlignment: 'center',
id: 'textinputcomponent',
type: 'textinputcomponent',
componentInput: undefined,
configuration: {
placeholder: {
type: 'static',
value: 'Type...',
fieldType: 'text',
onlyStatic: true
},
defaultValue: {
type: 'static',
value: undefined,
fieldType: 'text'
}
},
card: false
},
{
softWrap: true,
verticalAlignment: 'center',
id: 'passwordinputcomponent',
type: 'passwordinputcomponent',
componentInput: undefined,
configuration: {
placeholder: {
type: 'static',
value: 'Password',
fieldType: 'text',
onlyStatic: true
}
},
card: false
},
{
softWrap: true,
verticalAlignment: 'center',
id: 'numberinputcomponent',
type: 'numberinputcomponent',
componentInput: undefined,
configuration: {
placeholder: {
type: 'static',
value: 'Type...',
fieldType: 'text',
onlyStatic: true
},
defaultValue: {
type: 'static',
value: undefined,
fieldType: 'number'
}
},
card: false
},
{
softWrap: true,
verticalAlignment: 'center',
id: 'slidercomponent',
type: 'slidercomponent',
componentInput: undefined,
configuration: {
min: {
type: 'static',
value: 0,
fieldType: 'number',
onlyStatic: true
},
max: {
type: 'static',
value: 42,
fieldType: 'number',
onlyStatic: true
}
},
card: false
},
{
softWrap: true,
verticalAlignment: 'center',
id: 'dateinputcomponent',
type: 'dateinputcomponent',
componentInput: undefined,
configuration: {
minDate: {
type: 'static',
value: '',
fieldType: 'date'
},
maxDate: {
type: 'static',
value: '',
fieldType: 'date'
},
defaultValue: {
type: 'static',
value: undefined,
fieldType: 'date'
}
},
card: false
},
{
...defaultAlignement,
softWrap: true,
id: 'checkboxcomponent',
type: 'checkboxcomponent',
componentInput: undefined,
configuration: {
label: {
type: 'static',
value: 'Label',
fieldType: 'text'
},
defaultValue: {
type: 'static',
value: undefined,
fieldType: 'boolean'
}
},
card: false
},
{
verticalAlignment: 'center',
id: 'selectcomponent',
type: 'selectcomponent',
componentInput: undefined,
configuration: {
items: {
type: 'static',
fieldType: 'array',
subFieldType: 'object',
value: [
{ value: 'foo', label: 'Foo' },
{ value: 'bar', label: 'Bar' }
]
},
itemKey: {
type: 'static',
fieldType: 'text',
value: 'value'
}
},
card: false,
softWrap: true
}
]
}
const buttons: ComponentSet = {
title: 'Buttons',
components: [
{
...defaultAlignement,
softWrap: true,
id: 'buttoncomponent',
type: 'buttoncomponent',
componentInput: {
type: 'runnable',
fieldType: 'any',
fields: {},
runnable: undefined
},
recomputeIds: undefined,
configuration: {
label: {
type: 'static',
fieldType: 'text',
value: 'Press me'
},
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonColorOptions',
value: 'blue'
},
size: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions',
value: 'xs'
},
disabled: {
fieldType: 'boolean',
type: 'eval',
expr: 'false'
}
},
card: false
},
{
horizontalAlignment: 'center',
id: 'formcomponent',
type: 'formcomponent',
componentInput: {
type: 'runnable',
fieldType: 'any',
fields: {},
runnable: undefined
},
recomputeIds: undefined,
configuration: {
label: {
type: 'static',
value: 'Submit',
fieldType: 'text'
},
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
value: 'dark',
optionValuesKey: 'buttonColorOptions'
},
size: {
fieldType: 'select',
type: 'static',
value: 'xs',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions'
}
},
card: true
},
{
horizontalAlignment: 'center',
verticalAlignment: 'center',
id: 'formbuttoncomponent',
type: 'formbuttoncomponent',
componentInput: {
type: 'runnable',
fieldType: 'any',
fields: {},
runnable: undefined
},
recomputeIds: undefined,
configuration: {
label: {
type: 'static',
value: 'Open popup',
fieldType: 'text'
},
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
value: 'dark',
optionValuesKey: 'buttonColorOptions'
},
size: {
fieldType: 'select',
type: 'static',
value: 'xs',
onlyStatic: true,
optionValuesKey: 'buttonSizeOptions'
}
},
card: true
}
]
}
const display: ComponentSet = {
title: 'Display',
components: [
{
softWrap: false,
id: 'htmlcomponent',
type: 'htmlcomponent',
componentInput: {
type: 'static',
fieldType: 'template',
value: `<img
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
>
<h1 class="absolute top-4 left-2 text-white">
Hello \${ctx.username}
</h1>`
},
configuration: {},
card: false
},
{
softWrap: false,
id: 'vegalitecomponent',
type: 'vegalitecomponent',
componentInput: {
type: 'static',
fieldType: 'object',
value: {
data: {
values: [
{ a: 'A', b: 28 },
{ a: 'B', b: 55 },
{ a: 'C', b: 43 },
{ a: 'D', b: 91 }
]
},
mark: 'bar',
encoding: {
x: { field: 'a', type: 'ordinal' },
y: { field: 'b', type: 'quantitative' }
}
}
},
configuration: {
canvas: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false,
tooltip: 'use the canvas renderer instead of the svg one for more interactive plots'
}
},
card: false
},
{
softWrap: false,
id: 'plotlycomponent',
type: 'plotlycomponent',
componentInput: {
type: 'static',
fieldType: 'object',
value: {
type: 'bar',
x: [1, 2, 3, 4],
y: [5, 10, 2, 8],
marker: {
color: '#C8A2C8',
line: {
width: 2.5
}
}
}
},
configuration: {},
card: false
},
{
softWrap: true,
horizontalAlignment: 'left',
verticalAlignment: 'top',
id: 'textcomponent',
type: 'textcomponent',
componentInput: {
type: 'static',
fieldType: 'template',
value: 'Hello ${ctx.username}'
},
configuration: {
style: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'textStyleOptions',
value: 'Body'
},
extraStyle: {
type: 'static',
fieldType: 'text',
value: '',
tooltip: 'CSS rules like "color: blue;"'
},
copyButton: {
type: 'static',
value: false,
fieldType: 'boolean',
onlyStatic: true
}
},
card: false
},
{
id: 'tablecomponent',
type: 'tablecomponent',
configuration: {
search: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
optionValuesKey: 'tableSearchOptions',
value: 'Disabled'
}
},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'object',
value: [
{
id: 1,
name: 'A cell with a long name',
age: 42
},
{
id: 2,
name: 'A briefer cell',
age: 84
}
]
},
card: true,
actionButtons: []
},
{
id: 'piechartcomponent',
type: 'piechartcomponent',
configuration: {
theme: {
type: 'static',
onlyStatic: true,
fieldType: 'select',
optionValuesKey: 'chartThemeOptions',
value: 'theme1'
},
doughnutStyle: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
}
},
componentInput: {
type: 'static',
fieldType: 'object',
value: { data: [25, 50, 25], labels: ['Pie', 'Charts', '<3'] }
},
card: true
},
{
id: 'barchartcomponent',
type: 'barchartcomponent',
configuration: {
theme: {
type: 'static',
onlyStatic: true,
fieldType: 'select',
optionValuesKey: 'chartThemeOptions',
value: 'theme1'
},
line: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
}
},
componentInput: {
type: 'static',
fieldType: 'object',
value: { data: [25, 50, 25], labels: ['Bar', 'Charts', '<3'] }
},
card: true
},
{
id: 'scatterchartcomponent',
type: 'scatterchartcomponent',
configuration: {
zoomable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
},
pannable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
}
},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'object',
value: [
{
label: 'foo',
data: [
{ x: 25, y: 50 },
{ x: 23, y: 23 },
{ x: 12, y: 37 }
],
backgroundColor: 'rgb(255, 12, 137)'
},
{
label: 'foobar',
data: [
{ x: 32, y: 32 },
{ x: 25, y: 42 },
{ x: 3, y: 27 }
],
backgroundColor: 'orange'
}
]
},
card: true
},
{
id: 'timeseriescomponent',
type: 'timeseriescomponent',
configuration: {
logarithmicScale: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
},
zoomable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
},
pannable: {
type: 'static',
onlyStatic: true,
fieldType: 'boolean',
value: false
}
},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'object',
value: [
{
label: 'foo',
data: [
{
x: '2021-11-06 23:39:30',
y: 50
},
{
x: '2021-11-07 01:00:28',
y: 60
},
{
x: '2021-11-07 09:00:28',
y: 20
}
],
backgroundColor: 'rgb(255, 12, 137)'
},
{
label: 'foobar',
data: [
{
x: '2021-11-06 23:39:30',
y: 20
},
{
x: '2021-11-07 01:00:28',
y: 13
},
{
x: '2021-11-07 09:00:28',
y: 45
}
],
backgroundColor: 'orange'
}
]
},
card: true
},
{
id: 'displaycomponent',
type: 'displaycomponent',
componentInput: {
type: 'static',
fieldType: 'object',
value: { foo: 42 }
},
configuration: {},
card: false
}
]
}
const componentSets = [buttons, inputs, display]
export { componentSets }
@@ -1,10 +1,8 @@
<script lang="ts">
import { page } from '$app/stores'
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import { key } from 'svelte-awesome/icons'
import type { AppEditorContext } from '../../types'
import { displayData } from '../../utils'
import { components } from '../Component.svelte'
import PanelSection from '../settingsPanel/common/PanelSection.svelte'
import ComponentOutputViewer from './ComponentOutputViewer.svelte'
@@ -31,7 +29,7 @@
const component = $app.grid.find((c) => c?.data?.id === componentId)
if (component?.data.type) {
return displayData[component?.data.type].name
return components[component?.data.type].name
} else if (componentId == 'ctx') {
return 'Context'
} else if (componentId.startsWith('bg_')) {
@@ -7,8 +7,8 @@
import { capitalize, emptySchema } from '$lib/utils'
import { createEventDispatcher, getContext } from 'svelte'
import { fly } from 'svelte/transition'
import { defaultCode } from '../../editorUtils'
import type { AppEditorContext } from '../../types'
import { defaultCode } from '../Component.svelte'
export let name: string
export let id: string
@@ -1,5 +1,6 @@
import type { Runnable } from "../../inputType"
import type { AppComponent, GridItem } from "../../types"
import type { GridItem } from "../../types"
import type { AppComponent } from "../Component.svelte";
export interface AppScriptsList {
inline: { name: string; id: string }[],
@@ -30,11 +31,11 @@ export function getAppScripts(grid: GridItem[]) {
}
return acc
}, {inline: [], imported: []} as AppScriptsList)
}, { inline: [], imported: [] } as AppScriptsList)
}
function processRunnable(runnable: Runnable, id: string, list: AppScriptsList) {
if(runnable?.type === undefined) { return }
if (runnable?.type === undefined) { return }
const type: keyof AppScriptsList = runnable.type === 'runnableByPath' ? 'imported' : 'inline'
list[type].push({
name: runnable[runnable.type === 'runnableByPath' ? 'path' : 'name'],
@@ -8,7 +8,7 @@
AlignStartHorizontal,
AlignStartVertical
} from 'lucide-svelte'
import type { AppComponent } from '../../types'
import type { AppComponent } from '../Component.svelte'
import PanelSection from './common/PanelSection.svelte'
export let component: AppComponent
@@ -2,7 +2,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import { faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppComponent, AppEditorContext } from '../../types'
import type { AppEditorContext } from '../../types'
import PanelSection from './common/PanelSection.svelte'
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
import TableActions from './TableActions.svelte'
@@ -17,6 +17,7 @@
import AlignmentEditor from './AlignmentEditor.svelte'
import RunnableInputEditor from './inputEditor/RunnableInputEditor.svelte'
import TemplateEditor from '$lib/components/TemplateEditor.svelte'
import type { AppComponent } from '../Component.svelte'
export let component: AppComponent | undefined
export let onDelete: (() => void) | undefined = undefined
@@ -6,7 +6,8 @@
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
import type { ButtonComponent, AppEditorContext, BaseAppComponent } from '../../types'
import type { AppEditorContext, BaseAppComponent } from '../../types'
import type { ButtonComponent } from '../Component.svelte'
import PanelSection from './common/PanelSection.svelte'
import TableActionLabel from './TableActionLabel.svelte'
@@ -1,338 +0,0 @@
import type { AppComponent } from './types'
export function defaultCode(component: string, language: string): string | undefined {
return DEFAULT_CODES[component]?.[language]
}
const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'python3', string>>> = {
tablecomponent: {
deno:
`export async function main() {
return [
{
"id": 1,
"name": "A cell with a long name",
"age": 42
},
{
"id": 2,
"name": "A briefer cell",
"age": 84
}
]
}`,
python3:
`def main():
return [
{
"id": 1,
"name": "A cell with a long name",
"age": 42
},
{
"id": 2,
"name": "A briefer cell",
"age": 84
}
]`,
},
textcomponent: {
deno:
`export async function main() {
return "foo"
}`,
python3:
`def main():
return "foo"`,
},
barchartcomponent: {
deno:
`export async function main() {
return {
"data": [
25,
50,
25
],
"labels": [
"Bar",
"Charts",
"<3"
]
}
}`,
python3:
`def main():
return {
"data": [
25,
50,
25
],
"labels": [
"Bar",
"Charts",
"<3"
]
}`,
},
displaycomponent: {
deno:
`export async function main() {
return {
"foo": 42
}
}`,
python3:
`def main():
return {
"foo": 42
}`,
},
htmlcomponent: {
deno:
`export async function main() {
return \`<img
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
>
<h1 class="absolute top-4 left-2 text-white">
Hello world
</h1>\`
}`,
python3:
`def main():
return '''<img
src="https://images.unsplash.com/photo-1554629947-334ff61d85dc?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&amp;ixlib=rb-1.2.1&amp;auto=format&amp;fit=crop&amp;w=1024&amp;h=1280&amp;q=80"
>
<h1 class="absolute top-4 left-2 text-white">
Hello world
</h1>'''`,
},
vegalitecomponent: {
deno:
`export async function main() {
return {
data: {
values: [
{ a: "A", b: 28 },
{ a: "B", b: 55 },
{ a: "C", b: 43 },
{ a: "D", b: 91 },
],
},
mark: "bar",
encoding: {
x: { field: "a", type: "ordinal" },
y: { field: "b", type: "quantitative" },
},
}
}`,
python3:
`def main():
return {
"data": {
"values": [
{ "a": "A", "b": 28 },
{ "a": "B", "b": 55 },
{ "a": "C", "b": 43 },
{ "a": "D", "b": 91 },
],
},
"mark": "bar",
"encoding": {
"x": { "field": "a", "type": "ordinal" },
"y": { "field": "b", "type": "quantitative" },
},
}`
},
plotlycomponent: {
deno:
`export async function main() {
return {
type: 'bar',
x: [1, 2, 3, 4],
y: [5, 10, 2, 8],
marker: {
color: '#C8A2C8',
line: {
width: 2.5
}
}
};
}`,
python3:
`def main():
return {
"type": "bar",
"x": [1, 2, 3, 4],
"y": [5, 10, 2, 8],
"marker": {
"color": "#C8A2C8",
"line": {
"width": 2.5
}
}
}`
},
piechartcomponent: {
deno:
`export async function main() {
return {
"data": [
25,
50,
25
],
"labels": [
"Pie",
"Charts",
"<3"
]
}
}`,
python3:
`def main():
return {
"data": [
25,
50,
25
],
"labels": [
"Pie",
"Charts",
"<3"
]
}`,
},
scatterchartcomponent: {
deno:
`export async function main() {
return [
{
"label": "foo",
"data": [
{ "x": 25, "y": 50 },
{ "x": 23, "y": 23 },
{ "x": 12, "y": 37 }
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{ "x": 32, "y": 32 },
{ "x": 25, "y": 42 },
{ "x": 3, "y": 27 }
],
"backgroundColor": "orange"
}
]
}`,
python3:
`def main():
return [
{
"label": "foo",
"data": [
{ "x": 25, "y": 50 },
{ "x": 23, "y": 23 },
{ "x": 12, "y": 37 }
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{ "x": 32, "y": 32 },
{ "x": 25, "y": 42 },
{ "x": 3, "y": 27 }
],
"backgroundColor": "orange"
}
]`,
},
timeseriescomponent: {
deno:
`export async function main() {
return [
{
"label": "foo",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 50
},
{
"x": "2021-11-07 01:00:28",
"y": 60
},
{
"x": "2021-11-07 09:00:28",
"y": 20
}
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 20
},
{
"x": "2021-11-07 01:00:28",
"y": 13
},
{
"x": "2021-11-07 09:00:28",
"y": 45
}
],
"backgroundColor": "orange"
}
]
}`,
python3:
`def main():
return [
{
"label": "foo",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 50
},
{
"x": "2021-11-07 01:00:28",
"y": 60
},
{
"x": "2021-11-07 09:00:28",
"y": 20
}
],
"backgroundColor": "rgb(255, 12, 137)"
},
{
"label": "bar",
"data": [
{
"x": "2021-11-06 23:39:30",
"y": 20
},
{
"x": "2021-11-07 01:00:28",
"y": 13
},
{
"x": "2021-11-07 09:00:28",
"y": 45
}
],
"backgroundColor": "orange"
}
]`,
},
} as const
+3 -69
View File
@@ -2,6 +2,7 @@ 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 { AppComponent } from './editor/Component.svelte'
import type {
AppInput,
ConnectedAppInput,
@@ -13,46 +14,6 @@ import type {
} from './inputType'
import type { World } from './rx'
type BaseComponent<T extends string> = {
type: T
}
export type TextComponent = BaseComponent<'textcomponent'>
export type TextInputComponent = BaseComponent<'textinputcomponent'>
export type PasswordInputComponent = BaseComponent<'passwordinputcomponent'>
export type DateInputComponent = BaseComponent<'dateinputcomponent'>
export type NumberInputComponent = BaseComponent<'numberinputcomponent'>
export type SliderComponent = BaseComponent<'slidercomponent'>
export type HtmlComponent = BaseComponent<'htmlcomponent'>
export type VegaLiteComponent = BaseComponent<'vegalitecomponent'>
export type PlotlyComponent = BaseComponent<'plotlycomponent'>
export type TimeseriesComponent = BaseComponent<'timeseriescomponent'>
export type ButtonComponent = BaseComponent<'buttoncomponent'> & {
recomputeIds: string[] | undefined
}
export type FormComponent = BaseComponent<'formcomponent'> & {
recomputeIds: string[] | undefined
}
export type FormButtonComponent = BaseComponent<'formbuttoncomponent'> & {
recomputeIds: string[] | undefined
}
export type RunFormComponent = BaseComponent<'runformcomponent'>
export type BarChartComponent = BaseComponent<'barchartcomponent'>
export type PieChartComponent = BaseComponent<'piechartcomponent'>
export type ScatterChartComponent = BaseComponent<'scatterchartcomponent'>
export type TableComponent = BaseComponent<'tablecomponent'> & {
actionButtons: (BaseAppComponent & ButtonComponent)[]
}
export type DisplayComponent = BaseComponent<'displaycomponent'>
export type ImageComponent = BaseComponent<'imagecomponent'>
export type InputComponent = BaseComponent<'inputcomponent'>
export type SelectComponent = BaseComponent<'selectcomponent'>
export type CheckboxComponent = BaseComponent<'checkboxcomponent'>
export type RadioComponent = BaseComponent<'radiocomponent'>
export type HorizontalAlignment = 'left' | 'center' | 'right'
export type VerticalAlignment = 'top' | 'center' | 'bottom'
@@ -82,38 +43,11 @@ export interface BaseAppComponent extends Partial<Aligned> {
softWrap?: boolean
}
export type AppComponent = BaseAppComponent &
(
| RunFormComponent
| DisplayComponent
| TextInputComponent
| PasswordInputComponent
| DateInputComponent
| NumberInputComponent
| SliderComponent
| BarChartComponent
| TimeseriesComponent
| HtmlComponent
| TableComponent
| TextComponent
| TableComponent
| ButtonComponent
| PieChartComponent
| ScatterChartComponent
| ImageComponent
| InputComponent
| SelectComponent
| CheckboxComponent
| RadioComponent
| FormComponent
| FormButtonComponent
| VegaLiteComponent
| PlotlyComponent
)
export type ComponentSet = {
title: string
components: AppComponent[]
components: AppComponent['type'][]
}
type SectionID = string
+4 -121
View File
@@ -2,31 +2,11 @@ import type { Schema } from '$lib/common'
import { FlowService, ScriptService } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { emptySchema } from '$lib/utils'
import {
BarChart4,
Binary,
CircleDot,
FormInput,
Inspect,
List,
Monitor,
PieChart,
Play,
Table2,
Image,
TextCursorInput,
Type,
Lock,
Calendar,
ToggleLeft,
GripHorizontal,
Code2,
SlidersHorizontal,
PlusSquare
} from 'lucide-svelte'
import type { AppComponent } from './editor/Component.svelte'
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
import type { Output } from './rx'
import type { App, AppComponent, GridItem } from './types'
import type { App, GridItem } from './types'
export async function loadSchema(
workspace: string,
@@ -85,104 +65,7 @@ export function schemaToInputsSpec(
}, {})
}
export const displayData: Record<AppComponent['type'], { name: string; icon: any }> = {
displaycomponent: {
name: 'Result',
icon: Monitor
},
textcomponent: {
name: 'Text',
icon: Type
},
buttoncomponent: {
name: 'Button',
icon: Inspect
},
formcomponent: {
name: 'Form',
icon: FormInput
},
formbuttoncomponent: {
name: 'Modal Form',
icon: PlusSquare
},
piechartcomponent: {
name: 'Pie Chart',
icon: PieChart
},
barchartcomponent: {
name: 'Bar/Line Chart',
icon: BarChart4
},
htmlcomponent: {
name: 'HTML',
icon: Code2
},
vegalitecomponent: {
name: 'Vega Lite',
icon: PieChart
},
plotlycomponent: {
name: 'Plotly',
icon: PieChart
},
timeseriescomponent: {
name: 'Timeseries',
icon: GripHorizontal
},
scatterchartcomponent: {
name: 'Scatter Chart',
icon: GripHorizontal
},
tablecomponent: {
name: 'Table',
icon: Table2
},
checkboxcomponent: {
name: 'Toggle',
icon: ToggleLeft
},
textinputcomponent: {
name: 'Text Input',
icon: TextCursorInput
},
imagecomponent: {
name: 'Image',
icon: Image
},
inputcomponent: {
name: 'Input',
icon: FormInput
},
radiocomponent: {
name: 'Radio Button',
icon: CircleDot
},
runformcomponent: {
name: 'Run Form',
icon: Play
},
selectcomponent: {
name: 'Select',
icon: List
},
numberinputcomponent: {
name: 'Number',
icon: Binary
},
slidercomponent: {
name: 'Slider',
icon: SlidersHorizontal
},
passwordinputcomponent: {
name: 'Password',
icon: Lock
},
dateinputcomponent: {
name: 'Date Input',
icon: Calendar
}
}
export function accessPropertyByPath<T>(object: T, path: string): T | undefined {
// convert indexes to properties
+1 -1
View File
@@ -46,7 +46,7 @@ declare module 'svelte-grid' {
default: { item: ItemLayout; dataItem: Item<T> & { data: AppComponent } }
}
export default class Grid<T = {}> extends SvelteComponentTyped<Props<T>, {}, Slots<T>> {}
export default class Grid<T = {}> extends SvelteComponentTyped<Props<T>, {}, Slots<T>> { }
}
declare module 'svelte-grid/build/helper/index.mjs' {