feat(frontend): Add app sub grids (#1208)

* feat(frontend): Add app sub grids

* feat(frontend): remove temp file

* feat(frontend): update package.json

* feat(frontend): fix build

* fixes

* feat(frontend): wip

* feat(frontend): wip

* feat(frontend): update dependencies

* feat(frontend): update dependencies

* feat(frontend): fix scrolling issues

* feat(frontend): fix preview + delete

* feat(frontend): done

* feat(frontend): remove dead code

* feat(frontend): fix selection

* feat(frontend): add container

* feat(frontend): fix package.json

* feat(frontend): fix package.json

* feat(frontend): fix package.json

* feat(frontend): fix package.json

* feat(frontend): fix package.json

* feat(frontend): restore npm package

* feat(frontend): add missing dependencies

* feat(frontend): remove missing dependencies

* feat(frontend): fix package.json

* feat(frontend): fix package.json

* feat(frontend): fix package.json

* feat(frontend): revert

* feat(frontend): fix package-lock

* feat(frontend): fix package-lock

* fix package-lock.json

* update

---------

Co-authored-by: Ruben Fiszel <ruben@rubenfiszel.com>
This commit is contained in:
Faton Ramadani
2023-02-22 17:52:10 +01:00
committed by GitHub
parent 6ede24635b
commit 0ec271d9ba
23 changed files with 565 additions and 121 deletions
+18 -22
View File
@@ -11,9 +11,11 @@
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@leeoniya/ufuzzy": "^0.9.1",
"@popperjs/core": "^2.11.6",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.7.6",
"async-mutex": "^0.4.0",
"chart.js": "^3.9.1",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"date-fns": "^2.29.3",
@@ -41,6 +43,7 @@
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelte-grid": "^5.1.3",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
@@ -60,7 +63,6 @@
"svelte": "^3.55.1",
"svelte-awesome": "^3.0.0",
"svelte-check": "^3.0.2",
"svelte-grid": "^5.1.1",
"svelte-highlight": "^6.2.1",
"svelte-overlay": "^1.4.1",
"svelte-popperjs": "^1.3.2",
@@ -775,8 +777,6 @@
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
"dev": true,
"peer": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
@@ -1262,6 +1262,12 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@windmill-labs/svelte-grid": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.3.tgz",
"integrity": "sha512-SzfuJ2qYkBYC73x3l+x9l7WcgU+kMlRoFkCgSCOfggi3z5zJU9eX1VYsV1wDjtQVU9OUaAciv1xlUR4OPxrFlg==",
"dev": true
},
"node_modules/@windmill-labs/svelvet": {
"version": "4.0.20",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelvet/-/svelvet-4.0.20.tgz",
@@ -1865,8 +1871,7 @@
"node_modules/chart.js": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==",
"peer": true
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="
},
"node_modules/chartjs-adapter-date-fns": {
"version": "3.0.0",
@@ -6198,12 +6203,6 @@
"typescript": "^4.1.2"
}
},
"node_modules/svelte-grid": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/svelte-grid/-/svelte-grid-5.1.1.tgz",
"integrity": "sha512-PJTC4PUejb8RzcUjymWnf1HALqseUBwxl+p+i5uJlDIC1+zXKok1J16IDoHQxXhNj+INcF9KHE7hIm++NurSAQ==",
"dev": true
},
"node_modules/svelte-highlight": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-6.2.1.tgz",
@@ -7597,9 +7596,7 @@
"@popperjs/core": {
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
"dev": true,
"peer": true
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw=="
},
"@redocly/json-to-json-schema": {
"version": "0.0.1",
@@ -7906,6 +7903,12 @@
"eslint-visitor-keys": "^3.3.0"
}
},
"@windmill-labs/svelte-grid": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.3.tgz",
"integrity": "sha512-SzfuJ2qYkBYC73x3l+x9l7WcgU+kMlRoFkCgSCOfggi3z5zJU9eX1VYsV1wDjtQVU9OUaAciv1xlUR4OPxrFlg==",
"dev": true
},
"@windmill-labs/svelvet": {
"version": "4.0.20",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelvet/-/svelvet-4.0.20.tgz",
@@ -8321,8 +8324,7 @@
"chart.js": {
"version": "3.9.1",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==",
"peer": true
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w=="
},
"chartjs-adapter-date-fns": {
"version": "3.0.0",
@@ -11513,12 +11515,6 @@
}
}
},
"svelte-grid": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/svelte-grid/-/svelte-grid-5.1.1.tgz",
"integrity": "sha512-PJTC4PUejb8RzcUjymWnf1HALqseUBwxl+p+i5uJlDIC1+zXKok1J16IDoHQxXhNj+INcF9KHE7hIm++NurSAQ==",
"dev": true
},
"svelte-highlight": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/svelte-highlight/-/svelte-highlight-6.2.1.tgz",
+3 -1
View File
@@ -25,6 +25,7 @@
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelte-grid": "^5.1.3",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
@@ -44,7 +45,6 @@
"svelte": "^3.55.1",
"svelte-awesome": "^3.0.0",
"svelte-check": "^3.0.2",
"svelte-grid": "^5.1.1",
"svelte-highlight": "^6.2.1",
"svelte-overlay": "^1.4.1",
"svelte-popperjs": "^1.3.2",
@@ -63,9 +63,11 @@
"@fortawesome/free-brands-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@leeoniya/ufuzzy": "^0.9.1",
"@popperjs/core": "^2.11.6",
"@redocly/json-to-json-schema": "^0.0.1",
"@tanstack/svelte-table": "^8.7.6",
"async-mutex": "^0.4.0",
"chart.js": "^3.9.1",
"chartjs-adapter-date-fns": "^3.0.0",
"chartjs-plugin-zoom": "^2.0.0",
"date-fns": "^2.29.3",
+5 -5
View File
@@ -9,7 +9,7 @@ declare namespace svelte.JSX {
}
}
declare module 'svelte-grid' {
declare module '@windmill-labs/svelte-grid' {
import type { SvelteComponentTyped } from 'svelte'
export interface Size {
@@ -37,7 +37,7 @@ declare module 'svelte-grid' {
export interface Props<T> {
fillSpace?: boolean
items: FilledItem<T>[],
items: FilledItem<T>[]
rowHeight: number
cols: [number, number][]
gap?: [number, number]
@@ -60,11 +60,11 @@ declare module 'svelte-grid' {
mount: CustomEvent<>
},
Slots<T>
> { }
> {}
}
declare module 'svelte-grid/build/helper/index.mjs' {
import { ItemLayout } from 'svelte-grid'
declare module '@windmill-labs/svelte-grid/build/helper/index.mjs' {
import { ItemLayout } from '@windmill-labs/svelte-grid'
const x: {
normalize(items: any[], col: any): unknown[]
@@ -0,0 +1,43 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../editor/SubGridEditor.svelte'
import type { AppInput } from '../inputType'
import type { AppEditorContext, GridItem } from '../types'
import InputValue from './helpers/InputValue.svelte'
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let configuration: Record<string, AppInput>
export let subGrids: GridItem[][] | undefined = undefined
export let componentContainerHeight: number
export const staticOutputs: string[] = ['loading', 'result']
const { focusedGrid, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
let result: string[] | undefined = undefined
let gridContent: string[] | undefined = undefined
function onFocus() {
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
$: $selectedComponent === id && onFocus()
</script>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
{#if subGrids && subGrids[0]}
<SubGridEditor
bind:subGrid={subGrids[0]}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</RunnableWrapper>
@@ -0,0 +1,73 @@
<script lang="ts">
import { Tab, Tabs } from '$lib/components/common'
import { getContext } from 'svelte'
import SubGridEditor from '../editor/SubGridEditor.svelte'
import type { AppInput } from '../inputType'
import type { Output } from '../rx'
import type { AppEditorContext, GridItem } from '../types'
import InputValue from './helpers/InputValue.svelte'
import RunnableWrapper from './helpers/RunnableWrapper.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let configuration: Record<string, AppInput>
export let subGrids: GridItem[][] | undefined = undefined
export let componentContainerHeight: number
export const staticOutputs: string[] = ['loading', 'result', 'selectedTabIndex']
const { worldStore, focusedGrid, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
let result: string[] | undefined = undefined
let gridContent: string[] | undefined = undefined
let selected: string = ''
$: selectedIndex = result?.indexOf(selected) ?? -1
$: if (result && selected === '') {
selected = result[0]
}
$: outputs = $worldStore?.outputsById[id] as {
selectedTabIndex: Output<number | null>
}
function handleTabSelection() {
outputs?.selectedTabIndex.set(selectedIndex)
}
$: selectedIndex >= 0 && handleTabSelection()
let tabHeight: number = 0
function onFocus() {
$focusedGrid = {
parentComponentId: id,
subGridIndex: selectedIndex ?? 0
}
}
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
</script>
<InputValue {id} input={configuration.gridContent} bind:value={gridContent} />
<RunnableWrapper flexWrap bind:componentInput {id} bind:initializing bind:result>
<div bind:clientHeight={tabHeight}>
<Tabs bind:selected>
{#each result ?? [] as res}
<Tab value={res}>
<span class="font-semibold">{res}</span>
</Tab>
{/each}
</Tabs>
</div>
{#if subGrids && subGrids[selectedIndex]}
<SubGridEditor
bind:subGrid={subGrids[selectedIndex]}
containerHeight={componentContainerHeight - tabHeight}
on:focus={() => {
$selectedComponent = id
}}
/>
{/if}
</RunnableWrapper>
@@ -11,6 +11,7 @@
CategoryScale,
BarElement
} from 'chart.js'
import type { ChartOptions } from 'chart.js'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import InputValue from '../helpers/InputValue.svelte'
@@ -45,7 +46,18 @@
theme3: ['#e74a3b', '#4e73df', '#1cc88a', '#36b9cc', '#f6c23e']
}[theme]
const options = {
const lineOptions: ChartOptions<'line'> = {
responsive: true,
animation: false,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
}
}
const barOptions: ChartOptions<'bar'> = {
responsive: true,
animation: false,
maintainAspectRatio: false,
@@ -73,9 +85,9 @@
<RunnableWrapper flexWrap autoRefresh bind:componentInput {id} bind:initializing bind:result>
{#if result}
{#if lineChart}
<Line {data} {options} />
<Line {data} options={lineOptions} />
{:else}
<Bar {data} {options} />
<Bar {data} options={barOptions} />
{/if}
{/if}
</RunnableWrapper>
@@ -9,12 +9,14 @@
LinearScale,
PointElement,
CategoryScale,
BarElement
BarElement,
type Point
} from 'chart.js'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import Scatter from 'svelte-chartjs/Scatter.svelte'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
export let id: string
export let componentInput: AppInput | undefined
@@ -59,11 +61,11 @@
}
}
}
}
} as ChartOptions<'scatter'>
$: data = {
datasets: result ?? []
}
} as ChartData<'scatter', (number | Point)[], unknown>
</script>
<InputValue {id} input={configuration.zoomable} bind:value={zoomable} />
@@ -12,12 +12,14 @@
CategoryScale,
BarElement,
TimeScale,
LogarithmicScale
LogarithmicScale,
type Point
} from 'chart.js'
import RunnableWrapper from '../helpers/RunnableWrapper.svelte'
import type { AppInput } from '../../inputType'
import Scatter from 'svelte-chartjs/Scatter.svelte'
import { Scatter } from 'svelte-chartjs'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
export let id: string
export let componentInput: AppInput | undefined
@@ -73,11 +75,11 @@
type: logarithmicScale ? 'logarithmic' : 'linear'
}
}
}
} as ChartOptions<'scatter'>
$: data = {
datasets: result ?? []
}
} as ChartData<'scatter', (number | Point)[], unknown>
</script>
<InputValue {id} input={configuration.logarithmicScale} bind:value={logarithmicScale} />
@@ -35,7 +35,9 @@
accept={acceptedFileTypes?.length ? acceptedFileTypes?.join(', ') : undefined}
multiple={allowMultiple}
convertToBase64
on:change={({detail}) => {handleChange(detail)}}
on:change={({ detail }) => {
handleChange(detail)
}}
class="w-full h-full"
>
{text}
@@ -26,7 +26,7 @@
result: Output<boolean>
}
$: defaultValue != undefined && outputs.result.set(defaultValue)
$: defaultValue != undefined && outputs?.result.set(defaultValue)
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
@@ -11,7 +11,8 @@
AppEditorContext,
ConnectingInput,
EditorBreakpoint,
EditorMode
EditorMode,
FocusedGrid
} from '../types'
import AppEditorHeader from './AppEditorHeader.svelte'
import GridEditor from './GridEditor.svelte'
@@ -59,6 +60,8 @@
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
const errorByComponent = writable<Record<string, { error: string; componentId: string }>>({})
const focusedGrid = writable<FocusedGrid | undefined>(undefined)
setContext<AppEditorContext>('AppEditorContext', {
worldStore,
staticOutputs,
@@ -78,7 +81,8 @@
staticExporter: writable({}),
noBackend: false,
errorByComponent,
openDebugRun: writable(undefined)
openDebugRun: writable(undefined),
focusedGrid
})
let timeout: NodeJS.Timeout | undefined = undefined
@@ -22,7 +22,6 @@
import {
faBug,
faClipboard,
faCode,
faExternalLink,
faFileExport,
faGlobe,
@@ -37,7 +36,8 @@
MoreVertical,
Pencil,
SlidersHorizontal,
Smartphone
Smartphone,
X
} from 'lucide-svelte'
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
@@ -77,7 +77,9 @@
jobs,
staticExporter,
errorByComponent,
openDebugRun
openDebugRun,
focusedGrid,
selectedComponent
} = getContext<AppEditorContext>('AppEditorContext')
const loading = {
@@ -459,6 +461,22 @@
</ToggleButtonGroup>
</div>
</div>
{#if $focusedGrid !== undefined}
<Badge color="indigo">
<div class="flex flex-row gap-2 justify-center items-center">
<div>{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}</div>
<button
on:click={() => {
$focusedGrid = undefined
$selectedComponent = undefined
}}
>
<X size={14} />
</button>
</div>
</Badge>
{/if}
<div class="flex flex-row gap-2 justify-end items-center overflow-visible">
<Dropdown
placement="bottom-end"
@@ -61,7 +61,8 @@
staticExporter: writable({}),
noBackend,
errorByComponent: writable({}),
openDebugRun: writable(undefined)
openDebugRun: writable(undefined),
focusedGrid: writable(undefined)
})
let mounted = false
@@ -17,6 +17,11 @@
Code2,
SlidersHorizontal,
PlusSquare,
ListOrdered,
Box
} from 'lucide-svelte'
import type { Size } from '@windmill-labs/svelte-grid'
import {
Smile,
DollarSign,
SeparatorHorizontal,
@@ -24,7 +29,6 @@
Paperclip,
Image
} from 'lucide-svelte'
import type { Size } from 'svelte-grid'
import { twMerge } from 'tailwind-merge'
type BaseComponent<T extends string> = {
@@ -78,6 +82,8 @@
export type HorizontalDividerComponent = BaseComponent<'horizontaldividercomponent'>
export type VerticalDividerComponent = BaseComponent<'verticaldividercomponent'>
export type FileInputComponent = BaseComponent<'fileinputcomponent'>
export type TabsComponent = BaseComponent<'tabscomponent'>
export type ContainerComponent = BaseComponent<'containercomponent'>
export type AppComponent = BaseAppComponent &
(
@@ -104,6 +110,8 @@
| FormButtonComponent
| VegaLiteComponent
| PlotlyComponent
| TabsComponent
| ContainerComponent
| IconComponent
| HorizontalDividerComponent
| VerticalDividerComponent
@@ -111,22 +119,24 @@
| ImageComponent
)
export type AppComponentDimensions =
`${IntRange<1, typeof NARROW_GRID_COLUMNS>}:${number}-${IntRange<1, typeof WIDE_GRID_COLUMNS>}:${number}`
export type AppComponentDimensions = `${IntRange<
1,
typeof NARROW_GRID_COLUMNS
>}:${number}-${IntRange<1, typeof WIDE_GRID_COLUMNS>}:${number}`
export type AppComponentConfig = {
name: string
icon: any
/**
* Dimensions key formula:
* [**mobile width**]:[**mobile height**]-[**desktop width**]:[**desktop height**]
*/
dims: AppComponentDimensions
data: AppComponent
cssIds?: string[]
}
export type AppComponentConfig = {
name: string
icon: any
/**
* Dimensions key formula:
* [**mobile width**]:[**mobile height**]-[**desktop width**]:[**desktop height**]
*/
dims: AppComponentDimensions
data: AppComponent
cssIds?: string[]
}
export const components: Record<AppComponent['type'], AppComponentConfig> = {
export const components: Record<AppComponent['type'], AppComponentConfig> = {
displaycomponent: {
name: 'Rich Result',
icon: Monitor,
@@ -144,6 +154,26 @@
card: false
}
},
containercomponent: {
name: 'Container',
icon: Box,
dims: '2:8-6:8',
data: {
softWrap: true,
verticalAlignment: 'center',
id: '',
type: 'containercomponent',
configuration: {},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: ['First Tab', 'Second Tab']
},
card: false,
subGrids: [[]]
}
},
textcomponent: {
name: 'Text',
icon: Type,
@@ -977,6 +1007,26 @@
card: false
}
},
tabscomponent: {
name: 'Tabs',
icon: ListOrdered,
dims: '2:8-6:8',
data: {
softWrap: true,
verticalAlignment: 'center',
id: '',
type: 'tabscomponent',
configuration: {},
componentInput: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: ['First Tab', 'Second Tab']
},
card: false,
subGrids: [[], []]
}
},
iconcomponent: {
name: 'Icon',
icon: Smile,
@@ -1081,10 +1131,7 @@
configuration: {
acceptedFileTypes: {
type: 'static',
value: [
'image/*',
'application/pdf'
],
value: ['image/*', 'application/pdf'],
fieldType: 'array',
onlyStatic: true
},
@@ -1100,7 +1147,7 @@
value: 'Drag and drop files or click to select them',
fieldType: 'text',
onlyStatic: true
},
}
},
customCss: {},
card: false
@@ -1136,19 +1183,19 @@
value: '',
fieldType: 'text',
onlyStatic: true,
tooltip: 'This text will appear if the image can\'t be loaded for any reason'
tooltip: "This text will appear if the image can't be loaded for any reason"
},
customStyles: {
type: 'static',
value: '',
fieldType: 'textarea',
onlyStatic: true,
},
onlyStatic: true
}
},
customCss: {},
card: false
}
},
}
}
const inputs: ComponentSet = {
@@ -1187,6 +1234,8 @@
'scatterchartcomponent',
'timeseriescomponent',
'displaycomponent',
'tabscomponent',
'containercomponent',
'horizontaldividercomponent',
'verticaldividercomponent'
]
@@ -1556,6 +1605,7 @@
import PlotlyHtml from '../components/dataDisplay/PlotlyHtml.svelte'
import { defaultAlignement } from './componentsPanel/componentDefaultProps'
import AppRangeInput from '../components/numberInputs/AppRangeInput.svelte'
import AppTabs from '../components/AppTabs.svelte'
import AppIcon from '../components/dataDisplay/AppIcon.svelte'
import AppCurrencyInput from '../components/numberInputs/AppCurrencyInput.svelte'
import AppDivider from '../components/AppDivider.svelte'
@@ -1563,6 +1613,8 @@
import type { IntRange } from '../../../common'
import type { NARROW_GRID_COLUMNS, WIDE_GRID_COLUMNS } from '../gridUtils'
import AppImage from '../components/dataDisplay/AppImage.svelte'
import { set_data } from 'svelte/internal'
import AppContainer from '../components/AppContainer.svelte'
export let component: AppComponent
export let selected: boolean
@@ -1573,6 +1625,7 @@
getContext<AppEditorContext>('AppEditorContext')
let hover = false
let initializing: boolean | undefined = undefined
let componentContainerHeight: number = 0
</script>
<div
@@ -1604,6 +1657,7 @@
$app.css?.['app']?.['component']?.class
)}
style={$app.css?.['app']?.['component']?.style}
bind:clientHeight={componentContainerHeight}
>
{#if component.type === 'displaycomponent'}
<AppDisplayComponent
@@ -1724,6 +1778,18 @@
<AppDivider {...component} position="vertical" />
{:else if component.type === 'rangecomponent'}
<AppRangeInput {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'tabscomponent'}
<AppTabs
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
/>
{:else if component.type === 'containercomponent'}
<AppContainer
{...component}
bind:staticOutputs={$staticOutputs[component.id]}
{componentContainerHeight}
/>
{:else if component.type === 'iconcomponent'}
<AppIcon {...component} bind:staticOutputs={$staticOutputs[component.id]} />
{:else if component.type === 'fileinputcomponent'}
@@ -6,6 +6,7 @@
import Popover from '$lib/components/Popover.svelte'
import { Alert, Button } from '$lib/components/common'
import type { AppComponent } from './Component.svelte'
import type { Output } from '../rx'
export let component: AppComponent
export let selected: boolean
@@ -15,7 +16,8 @@
const dispatch = createEventDispatcher()
const { errorByComponent, openDebugRun } = getContext<AppEditorContext>('AppEditorContext')
const { errorByComponent, openDebugRun, focusedGrid, worldStore } =
getContext<AppEditorContext>('AppEditorContext')
$: error = Object.values($errorByComponent).find((e) => e.componentId === component.id)
@@ -1,7 +1,7 @@
<script lang="ts">
import { getContext, afterUpdate } from 'svelte'
import type { AppEditorContext } from '../types'
import Grid from 'svelte-grid'
import type { AppEditorContext, GridItem } from '../types'
import Grid from '@windmill-labs/svelte-grid'
import { classNames } from '$lib/utils'
import { columnConfiguration, disableDrag, enableDrag, isFixed, toggleFixed } from '../gridUtils'
import { twMerge } from 'tailwind-merge'
@@ -21,18 +21,29 @@
staticOutputs,
runnableComponents,
lazyGrid,
summary
summary,
focusedGrid
} = getContext<AppEditorContext>('AppEditorContext')
// The drag is disabled when the user is connecting an input
// or when the user is previewing the app
// or when the focused grid is a subgrid
$: setAllDrags($mode === 'preview' || $connectingInput.opened)
function setAllDrags(enable: boolean) {
if (enable) {
$app.grid.map((gridItem) => disableDrag(gridItem))
} else {
$app.grid.map((gridItem) => enableDrag(gridItem))
}
const fct = enable ? disableDrag : enableDrag
$app.grid.map((gridItem) => {
const disabledGridItem = fct(gridItem)
if (disabledGridItem.data.subGrids) {
disabledGridItem.data.subGrids = disabledGridItem.data.subGrids.map((subgrid: GridItem[]) =>
subgrid.map((subgridItem: GridItem) => fct(subgridItem))
)
}
return disabledGridItem
})
}
function removeGridElement(component) {
@@ -129,10 +140,11 @@
{#if !$connectingInput.opened}
<RecomputeAllComponents />
{/if}
<div class="text-2xs text-gray-600"
>{policy.on_behalf_of ? `on behalf of ${policy.on_behalf_of_email}` : ''}</div
>
<div class="text-2xs text-gray-600">
{policy.on_behalf_of ? `on behalf of ${policy.on_behalf_of_email}` : ''}
</div>
</div>
<div
style={$app.css?.['app']?.['grid']?.style}
class={twMerge('px-4 pt-4 pb-2 overflow-visible', $app.css?.['app']?.['grid']?.class ?? '')}
@@ -14,6 +14,20 @@
<ComponentPanel bind:component={gridItem.data} />
{:else if gridItem.data.type === 'tablecomponent'}
<TablePanel bind:component={gridItem.data} />
{:else if gridItem.data.subGrids}
{#each gridItem.data.subGrids as subGrid}
{#each subGrid as subGridItem, index (subGridItem.data.id)}
{#if subGridItem.data.id === $selectedComponent}
<ComponentPanel
bind:component={subGridItem.data}
onDelete={() => {
subGrid.splice(index, 1)
subGrid = subGrid
}}
/>
{/if}
{/each}
{/each}
{/if}
{/each}
@@ -0,0 +1,135 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import { createEventDispatcher, getContext } from 'svelte'
import Grid from '@windmill-labs/svelte-grid'
import { columnConfiguration, isFixed, toggleFixed } from '../gridUtils'
import type { AppEditorContext, GridItem } from '../types'
import Component from './Component.svelte'
export let subGrid: GridItem[]
export let containerHeight: number
const dispatch = createEventDispatcher()
const { app, connectingInput, selectedComponent, focusedGrid } =
getContext<AppEditorContext>('AppEditorContext')
let pointerdown = false
let onComponent: string | undefined = undefined
const onpointerdown = (e) => {
if (onComponent === undefined) {
dispatch('focus')
} else {
onComponent = undefined
}
pointerdown = true
}
const onpointerup = () => {
pointerdown = false
}
function selectComponent(id: string) {
onComponent = id
if (!$connectingInput.opened) {
$selectedComponent = id
}
}
function lock(gridComponent: GridItem) {
let fComponent = $app.grid.find((c) => c.id === gridComponent.id)
if (fComponent) {
fComponent = toggleFixed(fComponent)
}
}
// @ts-ignore
let container
$: if ($focusedGrid?.parentComponentId !== $selectedComponent) {
const gridItemIds = $app.grid
.map((gridItem: GridItem) => {
if (gridItem.data.id === $focusedGrid?.parentComponentId) {
const subGrids = gridItem.data.subGrids ?? []
return [
gridItem.data.id,
...subGrids.map((subGrid: GridItem[]) =>
subGrid.map((gridItem: GridItem) => gridItem.data.id)
)
]
} else {
return []
}
})
.flat(2)
if (!gridItemIds.includes($selectedComponent)) {
$focusedGrid = undefined
}
}
</script>
<div class="relative w-full subgrid " bind:this={container}>
<div
class="px-4 pt-4 overflow-auto {$connectingInput?.opened ? '' : ''}"
on:pointerdown|stopPropagation={onpointerdown}
on:pointerleave={onpointerup}
on:pointerup={onpointerup}
style="height: {containerHeight}px;"
>
<div>
<Grid
bind:items={subGrid}
let:dataItem
rowHeight={36}
cols={columnConfiguration}
fastStart={true}
gap={[4, 2]}
scroller={container}
>
{#each subGrid as gridComponent (gridComponent.id)}
{#if gridComponent.data.id === dataItem.data.id}
<!-- svelte-ignore a11y-click-events-have-key-events -->
{#if $connectingInput.opened}
{#if $connectingInput.opened}
<div
on:pointerenter={() =>
($connectingInput.hoveredComponent = gridComponent.data.id)}
on:pointerleave={() => ($connectingInput.hoveredComponent = undefined)}
class="absolute w-full h-full bg-black border-2 bg-opacity-25 z-20 flex justify-center items-center"
/>
{/if}
<div
style="transform: translate(-50%, -50%);"
class="absolute w-fit justify-center bg-indigo-500/90 left-[50%] top-[50%] z-50 px-6 rounded border text-white py-2 text-5xl center-center"
>
{dataItem.data.id}
</div>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:pointerdown={() => selectComponent(dataItem.data.id)}
class={classNames(
'h-full w-full center-center',
$selectedComponent === dataItem.data.id ? 'active-grid-item' : '',
gridComponent.data.card ? 'border border-gray-100' : '',
'top-0'
)}
>
<Component
{pointerdown}
bind:component={gridComponent.data}
selected={$selectedComponent === dataItem.data.id}
locked={isFixed(gridComponent)}
on:lock={() => lock(gridComponent)}
/>
</div>
{/if}
{/each}
</Grid>
</div>
</div>
</div>
@@ -3,7 +3,7 @@
import Icon from 'svelte-awesome'
import type { AppEditorContext, GridItem } from '../../types'
import gridHelp from 'svelte-grid/build/helper/index.mjs'
import gridHelp from '@windmill-labs/svelte-grid/src/utils/helper'
import { getContext, onMount } from 'svelte'
import { getNextId } from '$lib/components/flows/flowStateUtils'
import { faAngleDown } from '@fortawesome/free-solid-svg-icons'
@@ -17,13 +17,10 @@
type AppComponent
} from '../Component.svelte'
const { app, selectedComponent } = getContext<AppEditorContext>('AppEditorContext')
function addComponent(appComponentType: AppComponent['type']) {
$dirtyStore = true
const grid = $app.grid ?? []
const id = getNextId(grid.map((gridItem) => gridItem.data.id))
const { app, selectedComponent, focusedGrid } = getContext<AppEditorContext>('AppEditorContext')
// The grid is needed to find a space for the new component
function createNewGridItem(grid: GridItem[], id: string, appComponentType: string): GridItem {
const appComponent = componentsRecord[appComponentType].data
appComponent.id = id
@@ -59,9 +56,45 @@
newItem[column] = { ...newItem[column], ...position }
})
$app.grid = [...grid, newItem]
return newItem
}
$selectedComponent = id
function addComponent(appComponentType: AppComponent['type']): void {
// When a new component is added, we need to mark the app as dirty,
// so a confirmation modal will appear if the user tries to leave the page
$dirtyStore = true
const grid = $app.grid ?? []
const gridItemIds = grid
.map((gridItem: GridItem) => {
const subGrids = gridItem.data.subGrids ?? []
return [
gridItem.data.id,
...subGrids.map((subGrid: GridItem[]) =>
subGrid.map((gridItem: GridItem) => gridItem.data.id)
)
]
})
.flat(2)
const id = getNextId(gridItemIds)
if ($focusedGrid) {
const { parentComponentId, subGridIndex } = $focusedGrid
const gridItemIndex = $app.grid.findIndex((gridItem) => gridItem.id === parentComponentId)
const subGrids = $app.grid[gridItemIndex].data.subGrids ?? []
const newItem = createNewGridItem(subGrids[subGridIndex] ?? [], id, appComponentType)
const subGrid = subGrids[subGridIndex] ?? []
$app.grid[gridItemIndex].data.subGrids[subGridIndex] = [...subGrid, newItem]
$selectedComponent = id
} else {
const newItem = createNewGridItem(grid, id, appComponentType)
$app.grid = [...grid, newItem]
$selectedComponent = id
}
}
onMount(() => {
+13 -5
View File
@@ -46,7 +46,6 @@ export type EvalInput = {
expr: string
}
export type RowInput = {
type: 'row'
column: string
@@ -81,7 +80,7 @@ export type Runnable = RunnableByPath | RunnableByName | undefined
// Runnable input, set by the developer in the component panel
export type ResultInput = {
runnable: Runnable
fields: Record<string, (StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput)>
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
type: 'runnable'
value?: any
}
@@ -101,16 +100,16 @@ type AppInputSpec<T extends InputType, U, V extends InputType = never> = (
type InputConfiguration<T extends InputType, U, V extends InputType> = {
fieldType: T
subFieldType?: V
format?: string | undefined,
format?: string | undefined
fileUpload?: {
/** Use `*` to accept anything. */
accept: string
/**
/**
* Controls if user is allowed to select multiple files.
* @default false
*/
multiple?: boolean
/**
/**
* Controls if the uploaded file(s) will be returned as `Base64` strings.
* @default false
*/
@@ -137,6 +136,12 @@ export type AppInput =
| AppInputSpec<'any', any>
| AppInputSpec<'object', Record<string | number, any>>
| AppInputSpec<'object', string>
| (AppInputSpec<'select', string> & {
/**
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
*/
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'select', string> & StaticOptions)
| AppInputSpec<'icon-select', string>
| AppInputSpec<'array', string[], 'text'>
@@ -147,6 +152,9 @@ export type AppInput =
| AppInputSpec<'array', string[], 'time'>
| AppInputSpec<'array', string[], 'datetime'>
| AppInputSpec<'array', object[], 'object'>
| (AppInputSpec<'array', string[], 'select'> & {
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
export type RowAppInput = Extract<AppInput, { type: 'row' }>
+25 -17
View File
@@ -1,6 +1,6 @@
import type { Schema } from '$lib/common'
import type { Preview } from '$lib/gen'
import type { FilledItem } from 'svelte-grid'
import type { FilledItem } from '@windmill-labs/svelte-grid'
import type { Writable } from 'svelte/store'
import type { AppComponent } from './editor/Component.svelte'
import type {
@@ -15,7 +15,6 @@ import type {
} from './inputType'
import type { World } from './rx'
export type HorizontalAlignment = 'left' | 'center' | 'right'
export type VerticalAlignment = 'top' | 'center' | 'bottom'
@@ -35,30 +34,33 @@ export interface BaseAppComponent extends Partial<Aligned> {
componentInput: AppInput | undefined
configuration: Record<
string,
GeneralAppInput & (
StaticAppInput
| ConnectedAppInput
| UserAppInput
| RowAppInput
| EvalAppInput
| UploadAppInput
)
GeneralAppInput &
(
| StaticAppInput
| ConnectedAppInput
| UserAppInput
| RowAppInput
| EvalAppInput
| UploadAppInput
)
>
card: boolean | undefined
customCss?: Record<string, {
class: string
style: string
}>
customCss?: Record<
string,
{
class: string
style: string
}
>
/**
* If `true` then the wrapper will allow items to flow outside of it's borders.
*
* *For example when the component has a popup like `Select`*
*/
softWrap?: boolean
subGrids?: GridItem[][]
}
export type ComponentSet = {
title: string
components: AppComponent['type'][]
@@ -95,7 +97,10 @@ export type App = {
inlineScript: InlineScript | undefined
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
}>
css?: Record<'viewer' | 'grid' | AppComponent['type'], Record<string, { style?: string, class?: string }>>
css?: Record<
'viewer' | 'grid' | AppComponent['type'],
Record<string, { style?: string; class?: string }>
>
}
export type ConnectingInput = {
@@ -125,8 +130,11 @@ export type AppEditorContext = {
noBackend: boolean
errorByComponent: Writable<Record<string, { error: string; componentId: string }>>
openDebugRun: Writable<((componentID: string) => void) | undefined>
focusedGrid: Writable<FocusedGrid | undefined>
}
export type FocusedGrid = { parentComponentId: string; subGridIndex: number }
export type EditorMode = 'dnd' | 'preview'
export type EditorBreakpoint = 'sm' | 'lg'
@@ -1,5 +1,12 @@
import type { Schema } from '$lib/common'
import { Script, ScriptService, type FlowModule, type PathFlow, type PathScript, type RawScript } from '$lib/gen'
import {
Script,
ScriptService,
type FlowModule,
type PathFlow,
type PathScript,
type RawScript
} from '$lib/gen'
import { initialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/utils'
@@ -20,7 +27,11 @@ export async function loadFlowModuleState(flowModule: FlowModule): Promise<FlowM
try {
const { input_transforms, schema } = await loadSchemaFromModule(flowModule)
if (flowModule.value.type == 'script' || flowModule.value.type == 'rawscript' || flowModule.value.type == 'flow') {
if (
flowModule.value.type == 'script' ||
flowModule.value.type == 'rawscript' ||
flowModule.value.type == 'flow'
) {
flowModule.value.input_transforms = input_transforms
}
return { schema, previewResult: NEVER_TESTED_THIS_FAR }
@@ -77,7 +88,7 @@ export async function pickScript(
export async function pickFlow(
path: string,
summary: string,
id: string,
id: string
): Promise<[FlowModule & { value: PathFlow }, FlowModuleState]> {
const flowModule: FlowModule & { value: PathFlow } = {
id,
@@ -156,7 +167,7 @@ export async function createFlow(id: string): Promise<[FlowModule, FlowModuleSta
value: {
type: 'flow',
path: '',
input_transforms: {},
input_transforms: {}
},
summary: ''
}
+4 -4
View File
@@ -2,7 +2,7 @@ import type { AppComponent } from '$lib/components/apps/types'
declare module 'simple-svelte-autocomplete'
declare module 'svelte-grid' {
declare module '@windmill-labs/svelte-grid' {
import type { SvelteComponentTyped } from 'svelte'
export interface Size {
@@ -46,11 +46,11 @@ 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' {
import { ItemLayout } from 'svelte-grid'
declare module '@windmill-labs/svelte-grid/build/helper/index.mjs' {
import { ItemLayout } from '@windmill-labs/svelte-grid'
const x: {
normalize(items: any[], col: any): unknown[]