From 0ec271d9baa001be032c157fed5efc077831c4bd Mon Sep 17 00:00:00 2001 From: Faton Ramadani Date: Wed, 22 Feb 2023 17:52:10 +0100 Subject: [PATCH] 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 --- frontend/package-lock.json | 40 +++--- frontend/package.json | 4 +- frontend/src/global.d.ts | 10 +- .../apps/components/AppContainer.svelte | 43 ++++++ .../components/apps/components/AppTabs.svelte | 73 ++++++++++ .../components/dataDisplay/AppBarChart.svelte | 18 ++- .../dataDisplay/AppScatterChart.svelte | 8 +- .../dataDisplay/AppTimeseries.svelte | 10 +- .../otherInputs/AppFileInput.svelte | 4 +- .../selectInputs/AppCheckbox.svelte | 2 +- .../components/apps/editor/AppEditor.svelte | 8 +- .../apps/editor/AppEditorHeader.svelte | 24 +++- .../components/apps/editor/AppPreview.svelte | 3 +- .../components/apps/editor/Component.svelte | 114 +++++++++++---- .../apps/editor/ComponentHeader.svelte | 4 +- .../components/apps/editor/GridEditor.svelte | 34 +++-- .../apps/editor/SettingsPanel.svelte | 14 ++ .../apps/editor/SubGridEditor.svelte | 135 ++++++++++++++++++ .../componentsPanel/ComponentList.svelte | 51 +++++-- frontend/src/lib/components/apps/inputType.ts | 18 ++- frontend/src/lib/components/apps/types.ts | 42 +++--- .../lib/components/flows/flowStateUtils.ts | 19 ++- frontend/src/types.d.ts | 8 +- 23 files changed, 565 insertions(+), 121 deletions(-) create mode 100644 frontend/src/lib/components/apps/components/AppContainer.svelte create mode 100644 frontend/src/lib/components/apps/components/AppTabs.svelte create mode 100644 frontend/src/lib/components/apps/editor/SubGridEditor.svelte diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 14d9865966..ce2ae0275a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -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", diff --git a/frontend/package.json b/frontend/package.json index 76159be94a..263e62abca 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -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", diff --git a/frontend/src/global.d.ts b/frontend/src/global.d.ts index 31b4bdc6ee..036deecb9c 100644 --- a/frontend/src/global.d.ts +++ b/frontend/src/global.d.ts @@ -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 { fillSpace?: boolean - items: FilledItem[], + items: FilledItem[] rowHeight: number cols: [number, number][] gap?: [number, number] @@ -60,11 +60,11 @@ declare module 'svelte-grid' { mount: CustomEvent<> }, Slots - > { } + > {} } -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[] diff --git a/frontend/src/lib/components/apps/components/AppContainer.svelte b/frontend/src/lib/components/apps/components/AppContainer.svelte new file mode 100644 index 0000000000..32922c14a1 --- /dev/null +++ b/frontend/src/lib/components/apps/components/AppContainer.svelte @@ -0,0 +1,43 @@ + + + + + {#if subGrids && subGrids[0]} + { + $selectedComponent = id + }} + /> + {/if} + diff --git a/frontend/src/lib/components/apps/components/AppTabs.svelte b/frontend/src/lib/components/apps/components/AppTabs.svelte new file mode 100644 index 0000000000..e264a0cea2 --- /dev/null +++ b/frontend/src/lib/components/apps/components/AppTabs.svelte @@ -0,0 +1,73 @@ + + + + +
+ + {#each result ?? [] as res} + + {res} + + {/each} + +
+ {#if subGrids && subGrids[selectedIndex]} + { + $selectedComponent = id + }} + /> + {/if} +
diff --git a/frontend/src/lib/components/apps/components/dataDisplay/AppBarChart.svelte b/frontend/src/lib/components/apps/components/dataDisplay/AppBarChart.svelte index 2f7f76d720..2426b710ae 100644 --- a/frontend/src/lib/components/apps/components/dataDisplay/AppBarChart.svelte +++ b/frontend/src/lib/components/apps/components/dataDisplay/AppBarChart.svelte @@ -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 @@ {#if result} {#if lineChart} - + {:else} - + {/if} {/if} diff --git a/frontend/src/lib/components/apps/components/dataDisplay/AppScatterChart.svelte b/frontend/src/lib/components/apps/components/dataDisplay/AppScatterChart.svelte index f39e0386c1..0bc10e1def 100644 --- a/frontend/src/lib/components/apps/components/dataDisplay/AppScatterChart.svelte +++ b/frontend/src/lib/components/apps/components/dataDisplay/AppScatterChart.svelte @@ -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> diff --git a/frontend/src/lib/components/apps/components/dataDisplay/AppTimeseries.svelte b/frontend/src/lib/components/apps/components/dataDisplay/AppTimeseries.svelte index d44d2affa4..5c88f1989d 100644 --- a/frontend/src/lib/components/apps/components/dataDisplay/AppTimeseries.svelte +++ b/frontend/src/lib/components/apps/components/dataDisplay/AppTimeseries.svelte @@ -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> diff --git a/frontend/src/lib/components/apps/components/otherInputs/AppFileInput.svelte b/frontend/src/lib/components/apps/components/otherInputs/AppFileInput.svelte index 86a773782a..db8942622d 100644 --- a/frontend/src/lib/components/apps/components/otherInputs/AppFileInput.svelte +++ b/frontend/src/lib/components/apps/components/otherInputs/AppFileInput.svelte @@ -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} diff --git a/frontend/src/lib/components/apps/components/selectInputs/AppCheckbox.svelte b/frontend/src/lib/components/apps/components/selectInputs/AppCheckbox.svelte index 5b86f84de0..7d9447f48f 100644 --- a/frontend/src/lib/components/apps/components/selectInputs/AppCheckbox.svelte +++ b/frontend/src/lib/components/apps/components/selectInputs/AppCheckbox.svelte @@ -26,7 +26,7 @@ result: Output } - $: defaultValue != undefined && outputs.result.set(defaultValue) + $: defaultValue != undefined && outputs?.result.set(defaultValue) diff --git a/frontend/src/lib/components/apps/editor/AppEditor.svelte b/frontend/src/lib/components/apps/editor/AppEditor.svelte index 74f8f92445..a23ee5b0c7 100644 --- a/frontend/src/lib/components/apps/editor/AppEditor.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditor.svelte @@ -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 Promise>>({}) const errorByComponent = writable>({}) + const focusedGrid = writable(undefined) + setContext('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 diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte index 846bbe8b0a..282a37985a 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte @@ -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') const loading = { @@ -459,6 +461,22 @@ + {#if $focusedGrid !== undefined} + +
+
{`Sub grid: ${$focusedGrid.parentComponentId} (${$focusedGrid.subGridIndex})`}
+ +
+
+ {/if} +
= { @@ -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 = { + export const components: Record = { 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') let hover = false let initializing: boolean | undefined = undefined + let componentContainerHeight: number = 0
{#if component.type === 'displaycomponent'} {:else if component.type === 'rangecomponent'} + {:else if component.type === 'tabscomponent'} + + {:else if component.type === 'containercomponent'} + {:else if component.type === 'iconcomponent'} {:else if component.type === 'fileinputcomponent'} diff --git a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte index 5f67baf2c3..29d47b1b49 100644 --- a/frontend/src/lib/components/apps/editor/ComponentHeader.svelte +++ b/frontend/src/lib/components/apps/editor/ComponentHeader.svelte @@ -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') + const { errorByComponent, openDebugRun, focusedGrid, worldStore } = + getContext('AppEditorContext') $: error = Object.values($errorByComponent).find((e) => e.componentId === component.id) diff --git a/frontend/src/lib/components/apps/editor/GridEditor.svelte b/frontend/src/lib/components/apps/editor/GridEditor.svelte index 55795874f1..23180636fa 100644 --- a/frontend/src/lib/components/apps/editor/GridEditor.svelte +++ b/frontend/src/lib/components/apps/editor/GridEditor.svelte @@ -1,7 +1,7 @@ + +
+
+
+ + {#each subGrid as gridComponent (gridComponent.id)} + {#if gridComponent.data.id === dataItem.data.id} + + {#if $connectingInput.opened} + {#if $connectingInput.opened} +
+ ($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} +
+ {dataItem.data.id} +
+ {/if} + + +
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' + )} + > + lock(gridComponent)} + /> +
+ {/if} + {/each} + +
+
+
diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/ComponentList.svelte b/frontend/src/lib/components/apps/editor/componentsPanel/ComponentList.svelte index 4d01709a46..6890d4116e 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/ComponentList.svelte +++ b/frontend/src/lib/components/apps/editor/componentsPanel/ComponentList.svelte @@ -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') - - 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') + // 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(() => { diff --git a/frontend/src/lib/components/apps/inputType.ts b/frontend/src/lib/components/apps/inputType.ts index 3949a21b1d..6631441e9d 100644 --- a/frontend/src/lib/components/apps/inputType.ts +++ b/frontend/src/lib/components/apps/inputType.ts @@ -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 + fields: Record type: 'runnable' value?: any } @@ -101,16 +100,16 @@ type AppInputSpec = ( type InputConfiguration = { 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> | 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 diff --git a/frontend/src/lib/components/apps/types.ts b/frontend/src/lib/components/apps/types.ts index f4d47317d3..d2438c42fd 100644 --- a/frontend/src/lib/components/apps/types.ts +++ b/frontend/src/lib/components/apps/types.ts @@ -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 { 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 + 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 }> - css?: Record<'viewer' | 'grid' | AppComponent['type'], Record> + css?: Record< + 'viewer' | 'grid' | AppComponent['type'], + Record + > } export type ConnectingInput = { @@ -125,8 +130,11 @@ export type AppEditorContext = { noBackend: boolean errorByComponent: Writable> openDebugRun: Writable<((componentID: string) => void) | undefined> + focusedGrid: Writable } +export type FocusedGrid = { parentComponentId: string; subGridIndex: number } + export type EditorMode = 'dnd' | 'preview' export type EditorBreakpoint = 'sm' | 'lg' diff --git a/frontend/src/lib/components/flows/flowStateUtils.ts b/frontend/src/lib/components/flows/flowStateUtils.ts index f7f3415720..1353cc84e3 100644 --- a/frontend/src/lib/components/flows/flowStateUtils.ts +++ b/frontend/src/lib/components/flows/flowStateUtils.ts @@ -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 { 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: '' } diff --git a/frontend/src/types.d.ts b/frontend/src/types.d.ts index ad4304586b..4177bf3748 100644 --- a/frontend/src/types.d.ts +++ b/frontend/src/types.d.ts @@ -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 & { data: AppComponent } } } - export default class Grid extends SvelteComponentTyped, {}, Slots> { } + export default class Grid extends SvelteComponentTyped, {}, Slots> {} } -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[]