improve type definition of apps and sync them with static components + purge app content of unecessary data

This commit is contained in:
Ruben Fiszel
2023-03-18 15:29:42 +01:00
parent cf2d031e8e
commit d570ef58ac
65 changed files with 845 additions and 944 deletions
@@ -23,7 +23,7 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let customCss: ComponentCustomCSS<'button'> | undefined = undefined
export let customCss: ComponentCustomCSS<'buttoncomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined = true
@@ -158,10 +158,10 @@
<Button
btnClasses={twMerge(
$app.css?.['buttoncomponent']?.['button']?.class,
customCss?.button.class,
customCss?.button?.class,
resolvedConfig.fillContainer ? 'w-full h-full' : ''
)}
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button.style].join(';')}
style={[$app.css?.['buttoncomponent']?.['button']?.style, customCss?.button?.style].join(';')}
disabled={resolvedConfig.disabled}
on:pointerdown={(e) => {
e?.stopPropagation()
@@ -18,7 +18,7 @@
export let recomputeIds: string[] | undefined = undefined
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'button'> | undefined = undefined
export let customCss: ComponentCustomCSS<'formcomponent'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
@@ -72,7 +72,7 @@
autoRefresh={false}
forceSchemaDisplay={true}
runnableClass="!block"
runnableStyle={css?.container.style}
runnableStyle={css?.container?.style}
>
<AlignWrapper {horizontalAlignment}>
<div
@@ -21,7 +21,7 @@
export let extraQueryParams: Record<string, any> = {}
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'button' | 'popup'> | undefined = undefined
export let customCss: ComponentCustomCSS<'formbuttoncomponent'> | undefined = undefined
export let render: boolean
export const staticOutputs: string[] = ['loading', 'result']
@@ -85,8 +85,8 @@
<Modal
{open}
title={labelValue}
class={css?.popup.class}
style={css?.popup.style}
class={css?.popup?.class}
style={css?.popup?.style}
on:canceled={() => {
open = false
}}
@@ -29,7 +29,7 @@
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'barchartcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -14,7 +14,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'header' | 'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'displaycomponent'> | undefined = undefined
export let render: boolean
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
@@ -9,7 +9,7 @@
export let id: string
export let componentInput: AppInput | undefined
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'htmlcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -14,7 +14,7 @@
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'container' | 'icon'> | undefined = undefined
export let customCss: ComponentCustomCSS<'iconcomponent'> | undefined = undefined
export let render: boolean
const { app } = getContext<AppViewerContext>('AppViewerContext')
@@ -10,7 +10,7 @@
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'image'> | undefined = undefined
export let customCss: ComponentCustomCSS<'imagecomponent'> | undefined = undefined
export let render: boolean
const { app } = getContext<AppViewerContext>('AppViewerContext')
@@ -33,7 +33,7 @@
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'map'> | undefined = undefined
export let customCss: ComponentCustomCSS<'mapcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, selectedComponent, connectingInput, focusedGrid, mode } =
@@ -3,7 +3,12 @@
import { twMerge } from 'tailwind-merge'
import { getDocument, type PDFDocumentProxy, type PDFPageProxy } from 'pdfjs-dist'
import 'pdfjs-dist/build/pdf.worker.entry'
import type { AppViewerContext, ComponentCustomCSS, RichConfiguration, RichConfigurations } from '../../types'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { throttle } from '../../../../utils'
@@ -14,14 +19,13 @@
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'pdfcomponent'> | undefined = undefined
export let render: boolean
const { app, mode, worldStore, selectedComponent } =
getContext<AppViewerContext>('AppViewerContext')
initOutput($worldStore, id, {
result: undefined,
const outputs = initOutput($worldStore, id, {
loading: false
})
@@ -63,6 +67,7 @@
return
}
try {
outputs.loading.set(true)
await resetDoc()
doc = await getDocument(src).promise
pageNumber = 1
@@ -73,6 +78,7 @@
error = err?.message ?? (typeof err === 'string' ? err : 'Error loading PDF')
console.log(err)
}
outputs.loading.set(false)
}
async function renderPdf(scaleToViewport = true, resizing = false) {
@@ -28,7 +28,7 @@
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'piechartcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -19,19 +19,14 @@
import type { ChartOptions, ChartData } from 'chart.js'
import { concatCustomCss } from '../../utils'
import { getContext } from 'svelte'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { initOutput } from '../../editor/appUtils'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'scatterchartcomponent'> | undefined = undefined
export let render: boolean
let zoomable = false
@@ -12,7 +12,6 @@
AppEditorContext,
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import { getContext } from 'svelte'
@@ -25,7 +24,7 @@
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export let customCss: ComponentCustomCSS<'textcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, mode } = getContext<AppViewerContext>('AppViewerContext')
@@ -20,12 +20,7 @@
import { Scatter } from 'svelte-chartjs'
import InputValue from '../helpers/InputValue.svelte'
import type { ChartOptions, ChartData } from 'chart.js'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { getContext } from 'svelte'
import { concatCustomCss } from '../../utils'
import { initOutput } from '../../editor/appUtils'
@@ -34,7 +29,7 @@
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'timeseriescomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -28,9 +28,7 @@
export let configuration: RichConfigurations
export let actionButtons: (BaseAppComponent & ButtonComponent)[]
export let initializing: boolean | undefined = undefined
export let customCss:
| ComponentCustomCSS<'container' | 'tableHeader' | 'tableBody' | 'tableFooter'>
| undefined = undefined
export let customCss: ComponentCustomCSS<'tablecomponent'> | undefined = undefined
export let render: boolean
type T = Record<string, any>
@@ -339,8 +337,8 @@
paginationEnabled={pagination}
{result}
{table}
class={css?.tableFooter.class}
style={css?.tableFooter.style}
class={css?.tableFooter?.class}
style={css?.tableFooter?.style}
/>
</div>
{:else if result != undefined}
@@ -16,7 +16,6 @@
export let extraContext: Record<string, any> = {}
export let key: string = ''
$: console.log(value)
const { componentControl } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
@@ -27,7 +26,6 @@
let lastInput = input ? JSON.parse(JSON.stringify(input)) : undefined
$: console.log(input)
$: if (input && !deepEqual(input, lastInput)) {
lastInput = JSON.parse(JSON.stringify(input))
// Needed because of file uploads
@@ -65,7 +63,6 @@
debounce(async () => (value = await evalExpr(lastInput)))
async function handleConnection() {
console.log('handleConnection', lastInput)
if (lastInput.type === 'connected') {
$worldStore?.connect<any>(lastInput, onValueChange, `${id}-${key}`)
} else if (lastInput.type === 'static' || lastInput.type == 'template') {
@@ -20,10 +20,6 @@
result: Output<any>
}
$: if (outputs?.loading != undefined) {
outputs.loading.set(false, true)
}
function setOutput(v: any) {
outputs?.result?.set(v, true)
}
@@ -4,7 +4,6 @@
import Alert from '$lib/components/common/alert/Alert.svelte'
import LightweightSchemaForm from '$lib/components/LightweightSchemaForm.svelte'
import Popover from '$lib/components/Popover.svelte'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import TestJobLoader from '$lib/components/TestJobLoader.svelte'
import { AppService, type CompletedJob } from '$lib/gen'
import { classNames, defaultIfEmptyString, emptySchema, sendUserToast } from '$lib/utils'
@@ -51,7 +50,7 @@
$: autoRefresh && handleAutorefresh()
if (recomputable) {
if (recomputable || autoRefresh) {
$runnableComponents[id] = async () => {
await executeComponent(true)
}
@@ -60,9 +59,6 @@
function handleAutorefresh() {
if (autoRefresh && $worldStore) {
$runnableComponents[id] = async () => {
await executeComponent(true)
}
executeComponent(true)
}
}
@@ -193,7 +189,7 @@
if (runnable?.type === 'runnableByName') {
const { inlineScript } = runnable
// console.log(inlineScript?.content)
if (inlineScript) {
requestBody['raw_code'] = {
content: inlineScript.content,
@@ -260,6 +256,10 @@
$: result?.error && recordError(result.error)
</script>
<!-- {#if runnable?.type == 'runnableByName'}
{runnable?.inlineScript?.content}
{/if} -->
{#each Object.entries(fields ?? {}) as [key, v] (key)}
{#if v.type != 'static' && v.type != 'user'}
<InputValue {key} {id} input={fields[key]} bind:value={runnableInputValues[key]} />
@@ -35,14 +35,14 @@
$staticExporter[id] = () => result
})
function isRunnableDefined() {
function isRunnableDefined(componentInput) {
return isScriptByNameDefined(componentInput) || isScriptByPathDefined(componentInput)
}
</script>
{#if componentInput === undefined}
<slot />
{:else if componentInput.type === 'runnable' && isRunnableDefined()}
{:else if componentInput.type === 'runnable' && isRunnableDefined(componentInput)}
<RunnableComponent
{recomputeIds}
gotoUrl={goto}
@@ -4,12 +4,7 @@
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type {
AppViewerContext,
ComponentCustomCSS,
RichConfiguration,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -18,7 +13,7 @@
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'text'> | undefined = undefined
export let customCss: ComponentCustomCSS<'checkboxcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -11,7 +11,7 @@
export let configuration: RichConfigurations
export let inputType: 'date'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let customCss: ComponentCustomCSS<'dateinputcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
@@ -3,15 +3,13 @@
import { twMerge } from 'tailwind-merge'
import { FileInput } from '../../../common'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -14,7 +14,7 @@
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, connectingInput, selectedComponent } =
@@ -66,7 +66,6 @@
multiple
on:change={(e) => e.stopPropagation()}
{items}
class={css?.input?.class ?? ''}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={'border-color: #999;' +
SELECT_INPUT_DEFAULT_STYLE.containerStyles +
@@ -10,7 +10,7 @@
export let id: string
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let customCss: ComponentCustomCSS<'numberinputcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
@@ -13,8 +13,7 @@
export let id: string
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'handles' | 'bar' | 'limits' | 'values'> | undefined =
undefined
export let customCss: ComponentCustomCSS<'rangecomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -13,7 +13,7 @@
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let customCss: ComponentCustomCSS<'selectcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, connectingInput, selectedComponent } =
@@ -13,8 +13,7 @@
export let id: string
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'handle' | 'limits' | 'value' | 'bar'> | undefined =
undefined
export let customCss: ComponentCustomCSS<'slidercomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
@@ -13,7 +13,7 @@
export let configuration: RichConfigurations
export let inputType = 'text'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let customCss: ComponentCustomCSS<'textinputcomponent'> | undefined = undefined
export let appCssKey: 'textinputcomponent' | 'passwordinputcomponent' | 'emailinputcomponent' =
'textinputcomponent'
export let render: boolean
@@ -13,7 +13,7 @@
export let id: string
export let configuration: RichConfigurations
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
export let customCss: ComponentCustomCSS<'currencycomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
@@ -1,13 +1,12 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS, GridItem } from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
export let id: string
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
export let render: boolean
const { app, focusedGrid, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
@@ -29,8 +28,8 @@
<SubGridEditor
visible={render}
{id}
class={css?.container.class}
style={css?.container.style}
class={css?.container?.class}
style={css?.container?.style}
bind:subGrid={$app.subgrids[`${id}-0`]}
containerHeight={componentContainerHeight}
on:focus={() => {
@@ -17,7 +17,9 @@
export let configuration: RichConfigurations
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let customCss: ComponentCustomCSS<'container' | 'divider'> | undefined = undefined
export let customCss:
| ComponentCustomCSS<'verticaldividercomponent' | 'horizontaldividercomponent'>
| undefined = undefined
export let position: 'horizontal' | 'vertical'
export let render: boolean
@@ -1,7 +1,6 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import InputValue from '../helpers/InputValue.svelte'
import Portal from 'svelte-portal'
@@ -10,7 +9,7 @@
import { twMerge } from 'tailwind-merge'
import { AlignWrapper } from '../helpers'
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss: ComponentCustomCSS<'drawercomponent'> | undefined = undefined
export let id: string
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
@@ -80,8 +79,8 @@
<SubGridEditor
visible={open && render}
{id}
class={css?.container.class}
style={css?.container.style}
class={css?.container?.class}
style={css?.container?.style}
bind:subGrid={$app.subgrids[`${id}-0`]}
containerHeight={1200}
on:focus={() => {
@@ -1,21 +1,16 @@
<script lang="ts">
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppInput } from '../../inputType'
import type {
AppEditorContext,
AppViewerContext,
ComponentCustomCSS,
RichConfigurations
} from '../../types'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { concatCustomCss } from '../../utils'
import InputValue from '../helpers/InputValue.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { deepEqual } from 'fast-equals'
export let id: string
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'container'> | undefined = undefined
export let customCss:
| ComponentCustomCSS<'horizontalsplitpanescomponent' | 'verticalsplitpanescomponent'>
| undefined = undefined
export let horizontal: boolean = false
export let panes: number[]
export let render: boolean
@@ -76,8 +71,8 @@
visible={render}
{id}
shouldHighlight={$focusedGrid?.subGridIndex === index}
class={css?.container.class}
style={css?.container.style}
class={css?.container?.class}
style={css?.container?.style}
bind:subGrid={$app.subgrids[`${id}-${index}`]}
containerHeight={horizontal ? undefined : componentContainerHeight - 8}
on:focus={() => {
@@ -12,9 +12,7 @@
export let configuration: RichConfigurations
export let componentContainerHeight: number
export let tabs: string[]
export let customCss:
| ComponentCustomCSS<'tabRow' | 'tabs' | 'selectedTab' | 'container'>
| undefined = undefined
export let customCss: ComponentCustomCSS<'tabscomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined = configuration.tabsKind != undefined
@@ -100,8 +98,8 @@
{#each tabs ?? [] as res}
<Tab
value={res}
class={css?.tabs?.class}
style={css?.tabs?.style}
class={css?.allTabs?.class}
style={css?.allTabs?.style}
selectedClass={css?.selectedTab?.class}
selectedStyle={css?.selectedTab?.style}
>
@@ -11,7 +11,7 @@
import Component from './component/Component.svelte'
import { deepEqual } from 'fast-equals'
import { push } from '$lib/history'
import { expandGriditem, findGridItem } from './appUtils'
import { expandGriditem} from './appUtils'
import Grid from '../svelte-grid/Grid.svelte'
export let policy: Policy
@@ -180,9 +180,8 @@
locked={isFixed(dataItem)}
on:delete={() => removeGridElement(dataItem.data)}
on:lock={() => {
const gridItem = findGridItem($app, dataItem.data.id)
if (gridItem) {
toggleFixed(gridItem)
if (dataItem.data) {
toggleFixed(dataItem)
}
$app = $app
}}
@@ -190,7 +189,6 @@
push(history, $app)
$selectedComponent = dataItem.data.id
expandGriditem($app.grid, dataItem, $breakpoint)
$app = $app
}}
/>
</div>
@@ -57,9 +57,8 @@
}
function lock(dataItem: GridItem) {
let fComponent = findGridItem($app, dataItem.data.id)
if (fComponent) {
fComponent = toggleFixed(fComponent)
if (dataItem) {
toggleFixed(dataItem)
}
$app = $app
}
@@ -1,6 +1,21 @@
import { getNextId } from '$lib/components/flows/flowStateUtils'
import type { App, ConnectingInput, EditorBreakpoint, FocusedGrid, GridItem } from '../types'
import { getRecommendedDimensionsByComponent, type AppComponent } from './component'
import type {
App,
BaseAppComponent,
ConnectingInput,
EditorBreakpoint,
FocusedGrid,
GridItem,
StaticRichConfigurations
} from '../types'
import {
ccomponents,
components,
getRecommendedDimensionsByComponent,
type AppComponent,
type BaseComponent,
type TypedComponent
} from './component'
import { gridColumns } from '../gridUtils'
import { allItems } from '../utils'
import type { Output, World } from '../rx'
@@ -73,8 +88,7 @@ export function getAllRecomputeIdsForComponent(app: App, id: string) {
export function createNewGridItem(grid: GridItem[], id: string, data: AppComponent): GridItem {
const newComponent = {
resizable: true,
draggable: true,
fixed: false,
x: 0,
y: 0
}
@@ -92,13 +106,8 @@ export function createNewGridItem(grid: GridItem[], id: string, data: AppCompone
newItem[column] = {
...newComponent,
min: { w: 1, h: 1 },
max: { w: column, h: 100 },
w: rec.w,
h: rec.h,
customDragger: false,
customResizer: false,
fixed: false
h: rec.h
}
const position = gridHelp.findSpace(newItem, grid, column) as { x: number; y: number }
newItem[column] = { ...newItem[column], ...position }
@@ -117,14 +126,48 @@ export function getGridItems(app: App, focusedGrid: FocusedGrid | undefined): Gr
}
}
export function appComponentFromType<T extends keyof typeof components>(
type: T
): (id: string) => BaseAppComponent & BaseComponent<T> {
return (id: string) => {
const init = ccomponents[type].initialData
return {
type,
//TODO remove tooltip and onlyStatic from there
configuration: Object.fromEntries(
Object.entries(init.configuration).map(([key, value]) => {
if (value.ctype === undefined) {
if (value.type === 'static') {
return [key, { type: value.type, value: value.value }]
} else if (value.type === 'eval') {
return [key, { type: value.type, expr: value.expr }]
}
}
return [key, value]
})
),
componentInput: init.componentInput,
panes: init.panes,
tabs: init.tabs,
customCss: {},
recomputeIds: init.recomputeIds ? [] : undefined,
actionButtons: init.actionButtons ? [] : undefined,
numberOfSubgrids: init.numberOfSubgrids,
horizontalAlignment: init.horizontalAlignment,
verticalAlignment: init.verticalAlignment,
id
}
}
}
export function insertNewGridItem(
app: App,
data: AppComponent,
builddata: (id: string) => AppComponent,
focusedGrid: FocusedGrid | undefined,
keepId?: boolean
keepId?: string
) {
const id = keepId ? data.id : getNextGridItemId(app)
const id = keepId ?? getNextGridItemId(app)
const data = builddata(id)
if (!app.subgrids) {
app.subgrids = {}
}
@@ -4,7 +4,7 @@
import { twMerge } from 'tailwind-merge'
import type { AppViewerContext } from '../../types'
import ComponentHeader from '../ComponentHeader.svelte'
import type { AppComponent } from './components'
import { ccomponents, components, type AppComponent } from './components'
import {
AppBarChart,
AppDisplayComponent,
@@ -54,6 +54,8 @@
$: componentWithErrors = Object.values($errorByComponent).map((e) => e.componentId)
$: hasError = componentWithErrors.includes(component.id)
console.log('component', component)
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
@@ -88,7 +90,7 @@
'h-full bg-white/40 outline-1',
$hoverStore === component.id && $mode !== 'preview' ? 'outline outline-blue-600' : '',
selected && $mode !== 'preview' ? 'outline outline-indigo-600' : '',
component.softWrap || hasError ? '' : 'overflow-auto',
ccomponents[component.type].softWrap || hasError ? '' : 'overflow-auto',
$mode != 'preview' ? 'cursor-pointer' : '',
'relative z-auto',
$app.css?.['app']?.['component']?.class
@@ -150,7 +150,7 @@
function handlePaste(event: KeyboardEvent) {
push(history, $app)
if (tempGridItem) {
if (tempGridItem != undefined) {
if (
$focusedGrid &&
getAllSubgridsAndComponentIds($app, tempGridItem.data)[0].includes(
@@ -169,11 +169,13 @@
grid.splice(idx, 1)
}
insertNewGridItem($app, tempGridItem.data, $focusedGrid, true)
const gridItem = tempGridItem
insertNewGridItem($app, (id) => ({ ...gridItem.data, id }), $focusedGrid, tempGridItem.id)
copiedGridItem = tempGridItem
tempGridItem = undefined
} else if (copiedGridItem) {
insertNewGridItem($app, copiedGridItem.data, $focusedGrid, false)
const gridItem = copiedGridItem
insertNewGridItem($app, (id) => ({ ...gridItem.data, id }), $focusedGrid)
}
$worldStore = $worldStore
File diff suppressed because it is too large Load Diff
@@ -1,38 +1,40 @@
import type { AppComponent } from "."
import type { AppComponent } from '.'
export function defaultCode(component: string, language: string): string | undefined {
return DEFAULT_CODES[component]?.[language]
}
export const DEFAULT_CODES: Partial<Record<AppComponent['type'], Record<'deno' | 'python3', string>>> = {
export 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
}
]
}`,
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
}
]`
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() {
@@ -322,4 +324,4 @@ return "smile";
python3: `def main():
return "smile"`
}
} as const
} as const
@@ -3,9 +3,14 @@
import { getContext } from 'svelte'
import { fade, slide } from 'svelte/transition'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import { components as componentsRecord, COMPONENT_SETS, type AppComponent } from '../component'
import {
components as componentsRecord,
COMPONENT_SETS,
type AppComponent,
type TypedComponent
} from '../component'
import ListItem from './ListItem.svelte'
import { insertNewGridItem } from '../appUtils'
import { appComponentFromType, insertNewGridItem } from '../appUtils'
import { X } from 'lucide-svelte'
import { push } from '$lib/history'
import { flip } from 'svelte/animate'
@@ -15,13 +20,16 @@
const { history } = getContext<AppEditorContext>('AppEditorContext')
function addComponent(appComponentType: AppComponent['type']): void {
function addComponent(appComponentType: TypedComponent['type']): void {
push(history, $app)
$dirtyStore = true
const data = componentsRecord[appComponentType].data
const id = insertNewGridItem($app, data, $focusedGrid)
const id = insertNewGridItem(
$app,
appComponentFromType(appComponentType) as (id: string) => AppComponent,
$focusedGrid
)
$selectedComponent = id
$worldStore = $worldStore
@@ -5,7 +5,9 @@
import type { ComponentCssProperty } from '../../types'
export let name: string
export let value: ComponentCssProperty | undefined
export let value: ComponentCssProperty = {}
export let forceStyle: boolean = false
export let forceClass: boolean = false
function reset(property: keyof ComponentCssProperty) {
if (value) {
@@ -19,7 +21,7 @@
</div>
{#if value}
<div class="border-l border-gray-400/80 py-1 pl-3.5 mt-1 ml-0.5">
{#if value.style !== undefined}
{#if value.style !== undefined || forceStyle}
<label class="block pb-2">
<div class="text-xs font-medium pb-0.5"> Style </div>
<div class="relative">
@@ -43,7 +45,7 @@
</div>
</label>
{/if}
{#if value.class !== undefined}
{#if value.class !== undefined || forceClass}
<label class="block">
<div class="text-xs font-medium pb-0.5"> Tailwind classes </div>
<div class="relative">
@@ -1,27 +1,24 @@
<script lang="ts">
import { onMount, getContext } from 'svelte'
import { LayoutDashboardIcon, MousePointer2, CurlyBraces } from 'lucide-svelte'
import { getContext } from 'svelte'
import { LayoutDashboardIcon, MousePointer2, CurlyBraces, X } from 'lucide-svelte'
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { emptyString } from '$lib/utils'
import { Tab, TabContent, Tabs } from '../../../common'
import type { AppViewerContext } from '../../types'
import ListItem from './ListItem.svelte'
import { isOpenStore } from './store'
import CssProperty from './CssProperty.svelte'
import { components, type AppComponent } from '../component'
import { slide } from 'svelte/transition'
import { ccomponents, components, type AppComponent, type TypedComponent } from '../component'
const STATIC_ELEMENTS = ['app'] as const
const TITLE_PREFIX = 'Css.' as const
const SHOW_UNUSED_ID = 'Setting.ShowUnused' as const
type CustomCSSType = (typeof STATIC_ELEMENTS)[number] | AppComponent['type']
type CustomCSSType = (typeof STATIC_ELEMENTS)[number] | keyof typeof components
interface CustomCSSEntry {
type: CustomCSSType
name: string
icon: any
ids: string[]
ids: { id: string; forceStyle: boolean; forceClass: boolean }[]
}
const { app } = getContext<AppViewerContext>('AppViewerContext')
@@ -55,64 +52,21 @@
type: 'app',
name: 'App',
icon: LayoutDashboardIcon,
ids: ['viewer', 'grid', 'component']
ids: ['viewer', 'grid', 'component'].map((id) => ({ id, forceStyle: true, forceClass: true }))
},
...Object.values(components).map(({ name, icon, data: { type, customCss } }) => ({
type,
...Object.entries(ccomponents).map(([type, { name, icon, customCss }]) => ({
type: type as keyof typeof components,
name,
icon,
ids: Object.keys(customCss ?? {}) ?? []
ids: Object.entries(customCss).map(([id, v]) => ({
id,
forceStyle: v?.style != undefined,
forceClass: v?.['class'] != undefined
}))
}))
]
let isCustom: Record<string, boolean> = Object.fromEntries(
Object.keys(entries).map((k) => [k, false])
)
let newCss = $app.css ?? {}
entries.forEach((e) => {
if (!newCss[e.type]) {
isCustom[e.type] = true
newCss[e.type] = {}
}
e.ids.forEach((id) => {
if (!newCss[e.type][id]) {
newCss[e.type][id] = components?.[e.type]?.data?.customCss?.[id] ?? { class: '', style: '' }
}
})
e.ids
.map((id) => newCss[e.type][id].class != '' || newCss[e.type][id].style != '')
.forEach((c) => {
if (c) {
isCustom[e.type] = true
}
})
})
//@ts-ignore
$app.css = newCss
$: staticComponents = entries.filter(({ type }) => STATIC_ELEMENTS.includes(type as any))
$: usedComponents = Object.keys(
$app.grid.reduce((acc, { data }) => {
acc[data.type] = true
return acc
}, {})
)
.map((type) => entries.find((entry) => entry.type === type))
.filter(Boolean) as CustomCSSEntry[]
$: unusedComponents = entries.filter(({ type }) => {
return (
!STATIC_ELEMENTS.includes(type as any) && !usedComponents.find((entry) => entry.type === type)
)
})
onMount(() => {
isOpenStore.addItems(
[{ name: 'App' }, ...Object.values(components)].map((component) => {
return { [TITLE_PREFIX + component.name]: false }
})
)
isOpenStore.addItems([{ [SHOW_UNUSED_ID]: false }])
})
entries.sort((a, b) => a.name.localeCompare(b.name))
let search = ''
</script>
<Tabs selected="ui" on:selected={(e) => switchTab(e.detail === 'json')} class="relative">
@@ -130,9 +84,38 @@
</Tab>
<div slot="content" class="h-full overflow-y-auto">
<TabContent value="ui">
{#each [...staticComponents, ...usedComponents] as { type, name, icon, ids }}
<div class="sticky top-0 left-0 w-full bg-white p-2">
<div class="relative">
<input
bind:value={search}
class="px-2 pb-1 border border-gray-300 rounded-sm {search ? 'pr-8' : ''}"
placeholder="Search..."
/>
{#if search}
<button
class="absolute right-2 top-1/2 transform -translate-y-1/2 hover:bg-gray-200 rounded-full p-0.5"
on:click|stopPropagation|preventDefault={() => (search = '')}
>
<X size="14" />
</button>
{/if}
</div>
</div>
{#each search != '' ? entries.filter((x) => x.name
.toLowerCase()
.includes(search.toLowerCase())) : entries as { type, name, icon, ids } (name)}
{#if ids.length > 0}
<ListItem title={name} prefix={TITLE_PREFIX}>
<ListItem
title={name}
prefix={TITLE_PREFIX}
on:open={(e) => {
if ($app.css != undefined) {
if (e.detail && $app.css[type] == undefined) {
$app.css[type] = Object.fromEntries(ids.map(({ id }) => [id, {}]))
}
}
}}
>
<div slot="title" class="flex items-center">
<svelte:component this={icon} size={18} />
<span class="ml-1">
@@ -140,13 +123,14 @@
</span>
</div>
<div class="pb-2">
{#each ids as id}
{#each ids as { id, forceStyle, forceClass }}
<div class="mb-3">
{#if $app?.css?.[type][id]}
{#if $app?.css?.[type]}
<CssProperty
{forceClass}
{forceStyle}
name={id}
bind:value={$app.css[type][id]}
on:focus={() => (isCustom[type] = true)}
/>
{/if}
</div>
@@ -155,43 +139,6 @@
</ListItem>
{/if}
{/each}
<div class="px-1 my-4">
<button
on:click|preventDefault|stopPropagation={() => isOpenStore.toggle(SHOW_UNUSED_ID)}
class="w-full text-xs text-gray-500 text-center font-medium hover:underline p-2"
>
{$isOpenStore[SHOW_UNUSED_ID] ? 'Hide' : 'Show'} unused components
</button>
</div>
{#if $isOpenStore[SHOW_UNUSED_ID]}
<div transition:slide|local={{ duration: 300 }}>
{#each unusedComponents as { type, name, icon, ids }}
{#if ids.length > 0}
<ListItem title={name} prefix={TITLE_PREFIX}>
<div slot="title" class="flex items-center">
<svelte:component this={icon} size={18} />
<span class="ml-1">
{name}
</span>
</div>
<div class="pb-2">
{#each ids as id}
<div class="mb-3">
{#if $app?.css?.[type][id]}
<CssProperty
name={id}
bind:value={$app.css[type][id]}
on:focus={() => (isCustom[type] = true)}
/>
{/if}
</div>
{/each}
</div>
</ListItem>
{/if}
{/each}
</div>
{/if}
</TabContent>
<TabContent value="json">
{#if !emptyString(jsonError)}
@@ -2,12 +2,17 @@
import { slide } from 'svelte/transition'
import { ChevronDown } from 'lucide-svelte'
import { isOpenStore } from './store'
import { createEventDispatcher } from 'svelte'
export let title: string
export let prefix: string | undefined = undefined
const dispatch = createEventDispatcher()
$: storeTitle = prefix + title
$: isOpen = prefix ? $isOpenStore[storeTitle] : true
$: dispatch('open', isOpen)
</script>
<section class="pt-1 pb-2 px-1">
@@ -206,6 +206,8 @@
</div>
</div>
<!-- {inlineScript.content} -->
<div class="border h-full">
{#if inlineScript.language != 'frontend'}
<Editor
@@ -215,6 +217,7 @@
bind:code={inlineScript.content}
fixedOverflowWidgets={true}
cmdEnterAction={async () => {
inlineScript.content = editor?.getCode() ?? ''
runLoading = true
await $runnableComponents[id]?.()
runLoading = false
@@ -2,41 +2,48 @@
import { Button } from '$lib/components/common'
import { faPlus, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import type { StaticAppInput } from '../../inputType'
import type { InputType, StaticAppInput, StaticInput } from '../../inputType'
import { staticValues } from '../componentsPanel/componentStaticValues'
import SubTypeEditor from './SubTypeEditor.svelte'
type ArrayComponentInput = Extract<StaticAppInput, { fieldType: 'array' }>
export let componentInput: StaticInput<any[]>
export let subFieldType: InputType | undefined = undefined
export let optionValuesKey: keyof typeof staticValues | undefined = undefined
export let componentInput: ArrayComponentInput
const dispatch = createEventDispatcher()
function addElementByType() {
console.log('ADD')
if (!Array.isArray(componentInput.value)) {
componentInput.value = []
}
if (componentInput.subFieldType && componentInput.value) {
if (componentInput.subFieldType === 'boolean') {
componentInput.value.push(false)
} else if (componentInput.subFieldType === 'number') {
componentInput.value.push(0)
} else if (componentInput.subFieldType === 'object') {
componentInput.value.push({})
} else if (componentInput.subFieldType === 'labeledresource') {
componentInput.value.push({ value: '', label: '' })
let value: any[] = componentInput.value
if (subFieldType) {
if (subFieldType === 'boolean') {
value.push(false)
} else if (subFieldType === 'number') {
value.push(0)
} else if (subFieldType === 'object') {
value.push({})
} else if (subFieldType === 'labeledresource') {
value.push({ value: '', label: '' })
} else if (
componentInput.subFieldType === 'text' ||
componentInput.subFieldType === 'textarea' ||
subFieldType === 'text' ||
subFieldType === 'textarea' ||
// TODO: Add support for these types
componentInput.subFieldType === 'date' ||
componentInput.subFieldType === 'time' ||
componentInput.subFieldType === 'datetime'
subFieldType === 'date' ||
subFieldType === 'time' ||
subFieldType === 'datetime'
) {
componentInput.value.push('')
} else if (componentInput.subFieldType === 'select') {
componentInput.value.push(staticValues[componentInput.optionValuesKey][0])
value.push('')
} else if (subFieldType === 'select' && optionValuesKey) {
value.push(staticValues[optionValuesKey][0])
}
} else {
value.push('')
}
console.log(value, componentInput.value)
componentInput = componentInput
}
@@ -55,7 +62,7 @@
{#if Array.isArray(componentInput.value)}
{#each componentInput.value as value, index (index)}
<div class="flex flex-row gap-2 items-center relative">
<SubTypeEditor bind:componentInput bind:value />
<SubTypeEditor {subFieldType} bind:componentInput bind:value />
<div class="absolute top-1 right-1">
<Button
@@ -2,7 +2,7 @@
import Button from '$lib/components/common/button/Button.svelte'
import { faChevronDown, faChevronUp, faCopy, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
import { getContext } from 'svelte'
import type { AppEditorContext, AppViewerContext } from '../../types'
import type { AppEditorContext, AppViewerContext, RichConfiguration } from '../../types'
import PanelSection from './common/PanelSection.svelte'
import InputsSpecsEditor from './InputsSpecsEditor.svelte'
import TableActions from './TableActions.svelte'
@@ -17,7 +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'
import { ccomponents, components, type AppComponent } from '../component'
import CssProperty from '../componentsPanel/CssProperty.svelte'
import GridTab from './GridTab.svelte'
import { deleteGridItem } from '../appUtils'
@@ -76,6 +76,10 @@
}
}
}
const initialConfiguration = ccomponents[component.type].initialData.configuration
const componentInput: RichConfiguration | undefined =
ccomponents[component.type].initialData.componentInput
</script>
<svelte:window on:keydown={keydown} />
@@ -107,7 +111,13 @@
<div class="flex flex-col w-full gap-2 my-2">
{#if component.componentInput.type === 'static'}
<StaticInputEditor bind:componentInput={component.componentInput} />
<StaticInputEditor
fieldType={componentInput?.fieldType}
subFieldType={componentInput?.subFieldType}
format={componentInput?.format}
bind:componentInput={component.componentInput}
noVariablePicker
/>
{:else if component.componentInput.type === 'template' && component.componentInput !== undefined}
<div class="py-1 min-h-[28px] rounded border border-1 border-gray-500">
<TemplateEditor
@@ -121,6 +131,7 @@
<ConnectedInputEditor bind:componentInput={component.componentInput} />
{:else if component.componentInput?.type === 'runnable' && component.componentInput !== undefined}
<RunnableInputEditor
appComponent={component}
bind:appInput={component.componentInput}
defaultUserInput={component.type == 'formcomponent' ||
component.type == 'formbuttoncomponent'}
@@ -158,11 +169,12 @@
{/key}
</PanelSection>
{/if}
{#if Object.values(component.configuration).length > 0}
<PanelSection title={`Configuration (${Object.values(component.configuration).length})`}>
{#if Object.values(initialConfiguration).length > 0}
<PanelSection title={`Configuration (${Object.values(initialConfiguration).length})`}>
<InputsSpecsEditor
{rowColumns}
id={component.id}
inputSpecsConfiguration={initialConfiguration}
bind:inputSpecs={component.configuration}
userInputEnabled={false}
/>
@@ -184,7 +196,7 @@
<div class="grow shrink" />
{#if Object.keys(component.customCss ?? {}).length > 0}
{#if Object.keys(ccomponents[component.type].customCss ?? {}).length > 0}
<PanelSection title="Custom CSS">
<div slot="action">
<Button
@@ -198,10 +210,15 @@
</div>
{#if viewCssOptions}
<div transition:slide|local>
{#each Object.keys(component.customCss ?? {}) as name}
{#if component?.customCss?.[name]}
{#each Object.keys(ccomponents[component.type].customCss ?? {}) as name}
{#if component?.customCss != undefined}
<div class="w-full mb-2">
<CssProperty {name} bind:value={component.customCss[name]} />
<CssProperty
forceStyle={ccomponents[component.type].customCss[name].style != undefined}
forceClass={ccomponents[component.type].customCss[name].class != undefined}
{name}
bind:value={component.customCss[name]}
/>
</div>
{/if}
{/each}
@@ -13,6 +13,8 @@
import UploadInputEditor from './inputEditor/UploadInputEditor.svelte'
import { getContext } from 'svelte'
import type { AppViewerContext, RichConfiguration } from '../../types'
import type { InputType } from '../../inputType'
import type { staticValues } from '../componentsPanel/componentStaticValues'
export let id: string
export let componentInput: RichConfiguration
@@ -21,31 +23,37 @@
export let userInputEnabled: boolean = false
export let shouldCapitalize: boolean = true
export let resourceOnly = false
export let tooltip: string | undefined = undefined
export let onlyStatic: boolean = false
export let fieldType: InputType
export let subFieldType: InputType | undefined
export let format: string | undefined
export let optionValuesKeys: keyof typeof staticValues | undefined
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
</script>
{#if componentInput.ctype == undefined}
{#if !(resourceOnly && (componentInput.fieldType !== 'object' || !componentInput.format?.startsWith('resource-')))}
{#if !(resourceOnly && (fieldType !== 'object' || !format?.startsWith('resource-')))}
<div class="flex flex-col gap-1">
<div class="flex justify-between items-end gap-1">
<span class="text-sm font-semibold truncate">
{shouldCapitalize ? capitalize(addWhitespaceBeforeCapitals(key)) : key}
{#if componentInput.tooltip}
{#if tooltip}
<Tooltip>
{componentInput.tooltip}
{tooltip}
</Tooltip>
{/if}
</span>
<div class="flex gap-x-2 gap-y-1 flex-wrap justify-end items-center">
<Badge color="blue">
{componentInput.fieldType === 'array' && componentInput.subFieldType
? `${capitalize(fieldTypeToTsType(componentInput.subFieldType))}[]`
: capitalize(fieldTypeToTsType(componentInput.fieldType))}
{fieldType === 'array' && subFieldType
? `${capitalize(fieldTypeToTsType(subFieldType))}[]`
: capitalize(fieldTypeToTsType(fieldType))}
</Badge>
{#if !componentInput.onlyStatic && componentInput.type != 'eval'}
{#if !onlyStatic && componentInput.type != 'eval'}
<ToggleButtonGroup
bind:selected={componentInput.type}
on:selected={(e) => {
@@ -68,7 +76,7 @@
/>
<svelte:fragment slot="text">Static</svelte:fragment>
</Popover>
{#if userInputEnabled && !componentInput.format?.startsWith('resource-')}
{#if userInputEnabled && !format?.startsWith('resource-')}
<Popover placement="bottom" notClickable disapperTimoout={0}>
<ToggleButton
position="center"
@@ -112,7 +120,13 @@
{:else if componentInput.type === 'row'}
<RowInputEditor bind:componentInput />
{:else if componentInput.type === 'static'}
<StaticInputEditor bind:componentInput />
<StaticInputEditor
{fieldType}
{subFieldType}
{optionValuesKeys}
{format}
bind:componentInput
/>
{:else if componentInput.type === 'eval'}
<EvalInputEditor {hasRows} {id} bind:componentInput />
{:else if componentInput.type === 'upload'}
@@ -1,15 +1,11 @@
<script lang="ts">
import type {
ConnectedAppInput,
RowAppInput,
StaticAppInput,
UserAppInput
} from '../../inputType'
import type { RichConfigurations } from '../../types'
import InputsSpecEditor from './InputsSpecEditor.svelte'
export let id: string
export let inputSpecs: RichConfigurations
export let inputSpecsConfiguration: RichConfigurations = inputSpecs
export let userInputEnabled: boolean = false
export let shouldCapitalize: boolean = true
export let rowColumns = false
@@ -18,8 +14,10 @@
{#if inputSpecs}
<div class="w-full flex flex-col gap-4">
{#each Object.keys(inputSpecs) as k}
{#each Object.keys(inputSpecsConfiguration) as k}
{#if inputSpecs[k].ctype == undefined}
{@const meta = inputSpecsConfiguration?.[k]}
<!-- {JSON.stringify(meta)} -->
<InputsSpecEditor
key={k}
bind:componentInput={inputSpecs[k]}
@@ -28,6 +26,12 @@
{shouldCapitalize}
{resourceOnly}
hasRows={rowColumns}
fieldType={meta?.['fieldType']}
subFieldType={meta?.['subFieldType']}
format={meta?.['format']}
optionValuesKeys={meta?.['optionValuesKeys']}
tooltip={meta?.['tooltip']}
onlyStatic={meta?.['onlyStatic']}
/>
{/if}
{/each}
@@ -6,6 +6,7 @@
import type { ResultAppInput } from '../../inputType'
import type { AppViewerContext } from '../../types'
import { clearResultAppInput } from '../../utils'
import type { AppComponent } from '../component'
import InlineScriptEditorDrawer from '../inlineScriptsPanel/InlineScriptEditorDrawer.svelte'
import ComponentTriggerList from './triggerLists/ComponentTriggerList.svelte'
@@ -13,6 +14,7 @@
export let appInput: ResultAppInput
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
export let appComponent: AppComponent
function edit() {
if (appInput.runnable?.type === 'runnableByName') {
@@ -68,7 +70,7 @@
</div>
</div>
{#if appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript}
<ComponentTriggerList fields={appInput.fields} />
<ComponentTriggerList {appComponent} fields={appInput.fields} />
{/if}
{#if appInput.runnable?.type === 'runnableByName' && !appInput.runnable.inlineScript}
<span class="text-xs text-gray-500">
@@ -1,20 +1,22 @@
<script lang="ts">
import type { StaticAppInput } from '../../inputType'
import type { InputType, StaticInput } from '../../inputType'
import StaticInputEditor from './inputEditor/StaticInputEditor.svelte'
export let value: any
export let componentInput: StaticAppInput
export let componentInput: StaticInput<any>
export let subFieldType: InputType | undefined
let fakeComponentInput: StaticAppInput = {
let fakeComponentInput: StaticInput<any> = {
...componentInput,
value,
// We don't support array of arrays
// @ts-ignore
fieldType: componentInput.subFieldType
value
}
// Bubble up changes to the real componentInput
$: fakeComponentInput && (value = fakeComponentInput.value)
</script>
<StaticInputEditor bind:componentInput={fakeComponentInput} />
<StaticInputEditor
noVariablePicker
fieldType={subFieldType}
bind:componentInput={fakeComponentInput}
/>
@@ -7,6 +7,7 @@
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
import type { AppViewerContext, BaseAppComponent } from '../../types'
import { appComponentFromType } from '../appUtils'
import type { ButtonComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
import TableActionLabel from './TableActionLabel.svelte'
@@ -19,71 +20,10 @@
function addComponent() {
const actionId = getNextId(components.map((x) => x.id.split('_')[1]))
const newComponent: BaseAppComponent & ButtonComponent = {
id: `${id}_${actionId}`,
type: 'buttoncomponent',
configuration: {
label: {
type: 'static',
value: 'Action',
fieldType: 'text'
},
color: {
fieldType: 'select',
type: 'static',
value: 'dark',
optionValuesKey: 'buttonColorOptions'
},
size: {
fieldType: 'select',
type: 'static',
value: 'xs',
optionValuesKey: 'buttonSizeOptions'
},
disabled: {
fieldType: 'boolean',
type: 'eval',
expr: 'false',
tooltip: "Disabled based on a predicate. Use `row` to access the current row's data"
},
goto: {
tooltip: 'Go to an url on success if not empty',
fieldType: 'text',
type: 'static',
value: ''
},
gotoNewTab: {
tooltip: 'Go to create a new tab',
fieldType: 'boolean',
type: 'static',
value: true
},
beforeIcon: {
type: 'static',
value: undefined,
fieldType: 'icon-select',
onlyStatic: true
},
afterIcon: {
type: 'static',
value: undefined,
fieldType: 'icon-select',
onlyStatic: true
}
},
componentInput: {
type: 'runnable',
fieldType: 'any',
fields: {},
runnable: undefined,
value: undefined
},
recomputeIds: undefined,
customCss: {
button: { style: '', class: '' }
} as const
const newComponent = {
...appComponentFromType('buttoncomponent')(`${id}_${actionId}`),
recomputeIds: []
}
components = [...components, newComponent]
}
@@ -1,8 +1,8 @@
<script lang="ts">
import ColorPicker, { ChromeVariant } from 'svelte-awesome-color-picker'
import type { AppInput, StaticInput } from '../../../inputType'
import type { StaticInput } from '../../../inputType'
export let componentInput: StaticInput<string> & Extract<AppInput, { fieldType: 'color' }>
export let componentInput: StaticInput<string>
let isOpen = false
</script>
@@ -4,7 +4,7 @@
import { Popup } from '../../../../common'
import { fade } from 'svelte/transition'
export let componentInput: StaticInput<string> & Extract<AppInput, { fieldType: 'icon-select' }>
export let componentInput: StaticInput<string>
let anchor: HTMLElement
let loading = false
let items: { label: string; icon: any }[]
@@ -1,17 +1,19 @@
<script lang="ts">
import type { ResultAppInput } from '$lib/components/apps/inputType'
import { isScriptByNameDefined, isScriptByPathDefined } from '$lib/components/apps/utils'
import type { AppComponent } from '../../component'
import RunnableSelector from '../mainInput/RunnableSelector.svelte'
import SelectedRunnable from '../SelectedRunnable.svelte'
export let appInput: ResultAppInput
export let defaultUserInput = false
export let appComponent: AppComponent
$: isRunnableSelected = isScriptByPathDefined(appInput) || isScriptByNameDefined(appInput)
</script>
{#if isRunnableSelected}
<SelectedRunnable bind:appInput />
<SelectedRunnable {appComponent} bind:appInput />
{:else}
<RunnableSelector {defaultUserInput} bind:appInput />
{/if}
@@ -1,7 +1,7 @@
<script lang="ts">
import Toggle from '$lib/components/Toggle.svelte'
import { staticValues } from '../../componentsPanel/componentStaticValues'
import type { StaticAppInput } from '../../../inputType'
import type { InputType, StaticInput } from '../../../inputType'
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
import JsonEditor from './JsonEditor.svelte'
@@ -12,7 +12,12 @@
import { Icon } from 'svelte-awesome'
import { faDollarSign } from '@fortawesome/free-solid-svg-icons'
export let componentInput: StaticAppInput | undefined
export let componentInput: StaticInput<any> | undefined
export let fieldType: InputType | undefined = undefined
export let subFieldType: InputType | undefined = undefined
export let optionValuesKeys: keyof typeof staticValues | undefined = undefined
export let format: string | undefined = undefined
export let noVariablePicker: boolean = false
const { onchange } = getContext<AppViewerContext>('AppViewerContext')
const { pickVariableCallback } = getContext<AppEditorContext>('AppEditorContext')
@@ -21,25 +26,25 @@
</script>
{#if componentInput?.type === 'static'}
{#if componentInput.fieldType === 'number'}
{#if fieldType === 'number'}
<input on:keydown|stopPropagation type="number" bind:value={componentInput.value} />
{:else if componentInput.fieldType === 'textarea'}
{:else if fieldType === 'textarea'}
<textarea on:keydown|stopPropagation bind:value={componentInput.value} />
{:else if componentInput.fieldType === 'date'}
{:else if fieldType === 'date'}
<input on:keydown|stopPropagation type="date" bind:value={componentInput.value} />
{:else if componentInput.fieldType === 'boolean'}
{:else if fieldType === 'boolean'}
<Toggle bind:checked={componentInput.value} />
{:else if componentInput.fieldType === 'select'}
{:else if fieldType === 'select' && optionValuesKeys}
<select on:keydown|stopPropagation on:keydown|stopPropagation bind:value={componentInput.value}>
{#each staticValues[componentInput.optionValuesKey] || [] as option}
{#each staticValues[optionValuesKeys] || [] as option}
<option value={option}>
{option}
</option>
{/each}
</select>
{:else if componentInput.fieldType === 'icon-select'}
{:else if fieldType === 'icon-select'}
<IconSelectInput bind:componentInput />
{:else if componentInput.fieldType === 'labeledresource'}
{:else if fieldType === 'labeledresource'}
{#if componentInput?.value && typeof componentInput?.value == 'object' && 'label' in componentInput?.value}
<div class="flex flex-col gap-1 w-full">
<input
@@ -65,10 +70,10 @@
{:else}
Inconsistent labeled resource object
{/if}
{:else if componentInput.fieldType === 'color'}
{:else if fieldType === 'color'}
<ColorInput bind:componentInput />
{:else if componentInput.fieldType === 'object'}
{#if componentInput?.format?.startsWith('resource-')}
{:else if fieldType === 'object'}
{#if format?.startsWith('resource-')}
<ResourcePicker
initialValue={componentInput.value?.split('$res:')[1] || ''}
on:change={(e) => {
@@ -81,8 +86,8 @@
}
}
}}
resourceType={componentInput.format.split('-').length > 1
? componentInput.format.substring('resource-'.length)
resourceType={format.split('-').length > 1
? format.substring('resource-'.length)
: undefined}
/>
{:else}
@@ -93,8 +98,8 @@
/>
</div>
{/if}
{:else if componentInput.fieldType === 'array'}
<ArrayStaticInputEditor bind:componentInput on:deleteArrayItem />
{:else if fieldType === 'array'}
<ArrayStaticInputEditor {subFieldType} bind:componentInput on:deleteArrayItem />
{:else}
<div class="flex gap-1">
<input
@@ -103,18 +108,20 @@
placeholder="Static value"
bind:value={componentInput.value}
/>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="min-w-min min-h-[34px] items-center leading-4 px-3 text-gray-600 cursor-pointer border border-gray-700 rounded center-center"
on:click={() => {
$pickVariableCallback = (variable) => {
if (componentInput) {
componentInput.value = `$var:${variable}`
{#if !noVariablePicker}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="min-w-min min-h-[34px] items-center leading-4 px-3 text-gray-600 cursor-pointer border border-gray-700 rounded center-center"
on:click={() => {
$pickVariableCallback = (variable) => {
if (componentInput) {
componentInput.value = `$var:${variable}`
}
}
}
}}
><Icon data={faDollarSign} />
</div>
}}
><Icon data={faDollarSign} />
</div>
{/if}
</div>
{/if}
{/if}
@@ -7,29 +7,23 @@
} from '$lib/components/apps/inputType'
import type { AppViewerContext } from '$lib/components/apps/types'
import { getContext } from 'svelte'
import { findGridItem } from '../../appUtils'
import type { AppComponent } from '../../component'
import TriggerBadgesList from './TriggerBadgesList.svelte'
import { getDependencies } from './triggerListUtils'
const { selectedComponent, app } = getContext<AppViewerContext>('AppViewerContext')
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let appComponent: AppComponent
$: gridItem = $selectedComponent ? findGridItem($app, $selectedComponent) : undefined
$: onClick =
gridItem?.data.type &&
['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(gridItem?.data.type)
$: if (gridItem) {
console.log(gridItem?.data.configuration.triggerOnAppLoad)
}
const onClick = ['buttoncomponent', 'formbuttoncomponent', 'formcomponent'].includes(
appComponent.type
)
$: onLoad =
!onClick ||
(gridItem?.data.configuration.triggerOnAppLoad.ctype === undefined &&
// TODO: Type and value are not in sync.
// @ts-ignore
gridItem?.data.configuration.triggerOnAppLoad.value)
(appComponent.configuration.triggerOnAppLoad.ctype === undefined &&
appComponent.configuration.triggerOnAppLoad.type == 'static' &&
appComponent.configuration.triggerOnAppLoad.value)
</script>
<TriggerBadgesList
@@ -156,8 +156,6 @@
on:pointerup={pointerup}
onTop={item.id == onTopId}
id={item.id}
resizable={item[getComputedCols] && item[getComputedCols].resizable}
draggable={item[getComputedCols] && item[getComputedCols].draggable}
{xPerPx}
{yPerPx}
width={Math.min(getComputedCols, item[getComputedCols] && item[getComputedCols].w) *
@@ -167,8 +165,6 @@
top={(item[getComputedCols] && item[getComputedCols].y) * yPerPx + gapY}
left={(item[getComputedCols] && item[getComputedCols].x) * xPerPx + gapX}
item={item[getComputedCols]}
min={item[getComputedCols] && item[getComputedCols].min}
max={item[getComputedCols] && item[getComputedCols].max}
cols={getComputedCols}
{gapX}
{gapY}
@@ -9,9 +9,6 @@
export let left
export let top
export let resizable
export let draggable
export let id
export let container
@@ -22,9 +19,6 @@
export let gapY
export let item
export let max
export let min
export let cols
export let nativeContainer
@@ -195,10 +189,6 @@
if (shadow) {
shadow.x = Math.max(Math.min(gridX, cols - shadow.w), 0)
shadow.y = Math.max(gridY, 0)
if (max.y) {
shadow.y = Math.min(shadow.y, max.y)
}
}
repaint(undefined, false)
}
@@ -283,19 +273,13 @@
// Get max col number
let maxWidth = cols - shadow.x
maxWidth = Math.min(max.w, maxWidth) || maxWidth
maxWidth = maxWidth
// Limit bound
newSize.width = Math.max(
Math.min(newSize.width, maxWidth * xPerPx - gapX * 2),
min.w * xPerPx - gapX * 2
)
newSize.width = Math.min(newSize.width, maxWidth * xPerPx - gapX * 2)
newSize.height = Math.max(newSize.height, min.h * yPerPx - gapY * 2)
newSize.height = newSize.height
if (max.h) {
newSize.height = Math.min(newSize.height, max.h * yPerPx - gapY * 2)
}
// Limit col & row
shadow.w = Math.round((newSize.width + gapX * 2) / xPerPx)
shadow.h = Math.round((newSize.height + gapY * 2) / yPerPx)
@@ -316,7 +300,7 @@
<div
draggable={false}
on:pointerdown={item && item.customDragger ? null : draggable && pointerdown}
on:pointerdown={pointerdown}
class="svlt-grid-item"
class:svlt-grid-active={active || (trans && rect)}
style="width: {active ? newSize.width : width}px; height:{active
@@ -329,9 +313,7 @@
: `transition: transform 0.1s, opacity 0.1s; transform: translate(${left}px, ${top}px); `} "
>
<slot movePointerDown={pointerdown} {resizePointerDown} />
{#if resizable && !item.customResizer}
<div class="svlt-grid-resizer" on:pointerdown={resizePointerDown} />
{/if}
<div class="svlt-grid-resizer" on:pointerdown={resizePointerDown} />
</div>
{#if (active || trans) && shadow}
+1 -7
View File
@@ -9,13 +9,7 @@ export interface Positon {
}
interface ItemLayout extends Size, Positon {
fixed?: boolean
resizable?: boolean
draggable?: boolean
customDragger?: boolean
customResizer?: boolean
min?: Size
max?: Size
fixed: boolean
}
export type FilledItem<T> = { [width: number]: Required<ItemLayout>; data: T; id: string }
@@ -1,43 +1,39 @@
import { makeMatrixFromItems } from "./matrix";
import { findFreeSpaceForItem, normalize, adjust } from "./item";
import { getRowsCount } from "./other";
import { makeMatrixFromItems } from './matrix'
import { findFreeSpaceForItem, normalize, adjust } from './item'
import { getRowsCount } from './other'
function makeItem(item) {
const { min = { w: 1, h: 1 }, max } = item;
return {
fixed: false,
resizable: !item.fixed,
draggable: !item.fixed,
customDragger: false,
customResizer: false,
min: {
w: Math.max(1, min.w),
h: Math.max(1, min.h),
},
max: { ...max },
...item,
};
const { min = { w: 1, h: 1 }, max } = item
return {
fixed: false,
min: {
w: Math.max(1, min.w),
h: Math.max(1, min.h)
},
max: { ...max },
...item
}
}
const gridHelp = {
normalize(items, col) {
return normalize(items, col);
},
normalize(items, col) {
return normalize(items, col)
},
adjust(items, col) {
return adjust(items, col);
},
adjust(items, col) {
return adjust(items, col)
},
item(obj) {
return makeItem(obj);
},
item(obj) {
return makeItem(obj)
},
findSpace(item, items, cols) {
let matrix = makeMatrixFromItems(items, getRowsCount(items, cols), cols);
findSpace(item, items, cols) {
let matrix = makeMatrixFromItems(items, getRowsCount(items, cols), cols)
let position = findFreeSpaceForItem(matrix, item[cols]);
return position;
},
};
let position = findFreeSpaceForItem(matrix, item[cols])
return position
}
}
export default gridHelp;
export default gridHelp
+28 -17
View File
@@ -3,12 +3,13 @@ import type { Preview } from '$lib/gen'
import type { History } from '$lib/history'
import type { Writable } from 'svelte/store'
import type { AppComponent } from './editor/component/components'
import type { AppComponent, components } from './editor/component/components'
import type {
AppInput,
ConnectedAppInput,
ConnectedInput,
EvalAppInput,
ResultAppInput,
RowAppInput,
StaticAppInput,
UploadAppInput,
@@ -27,7 +28,6 @@ export type Aligned = {
export interface GeneralAppInput {
onlyStatic?: boolean
evaluatedValue?: boolean
tooltip?: string
}
@@ -36,33 +36,42 @@ export type ComponentCssProperty = {
style?: string
}
export type ComponentCustomCSS<T extends string = string> = Record<T, ComponentCssProperty>
export type ComponentCustomCSS<T extends keyof typeof components> = Partial<
(typeof components)[T]['customCss']
>
export type Configuration = GeneralAppInput &
(StaticAppInput | ConnectedAppInput | UserAppInput | RowAppInput | EvalAppInput | UploadAppInput)
export type Configuration =
| StaticAppInput
| ConnectedAppInput
| UserAppInput
| RowAppInput
| EvalAppInput
| UploadAppInput
| ResultAppInput
export type RichConfiguration =
| (Configuration & { ctype?: undefined })
export type StaticConfiguration = GeneralAppInput & StaticAppInput
export type RichConfigurationT<T> =
| (T & { ctype?: undefined })
| {
ctype: 'oneOf'
selected: string
configuration: Record<string, Record<string, Configuration>>
configuration: Record<string, Record<string, T>>
}
| { ctype: 'group'; title: string; configuration: Record<string, Configuration> }
| { ctype: 'group'; title: string; configuration: Record<string, T> }
export type RichConfiguration = RichConfigurationT<Configuration>
export type RichConfigurations = Record<string, RichConfiguration>
export type StaticRichConfigurations = Record<
string,
RichConfigurationT<GeneralAppInput & (StaticAppInput | EvalAppInput)>
>
export interface BaseAppComponent extends Partial<Aligned> {
id: ComponentID
componentInput: AppInput | undefined
configuration: RichConfigurations
customCss?: ComponentCustomCSS
/**
* 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
customCss?: Record<string, ComponentCssProperty>
// Number of subgrids
numberOfSubgrids?: number
}
@@ -102,7 +111,9 @@ export type App = {
fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
autoRefresh?: boolean
}>
css?: Record<'viewer' | 'grid' | AppComponent['type'], ComponentCustomCSS>
css?: Partial<
Record<'viewer' | 'grid' | AppComponent['type'], Record<string, ComponentCssProperty>>
>
subgrids?: Record<string, GridItem[]>
}
+22 -22
View File
@@ -2,7 +2,7 @@ import type { Schema } from '$lib/common'
import { FlowService, ScriptService } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { emptySchema } from '$lib/utils'
import type { AppComponent } from './editor/component'
import type { AppComponent, components } from './editor/component'
import type { App, ComponentCssProperty, ComponentCustomCSS, GridItem } from './types'
import { twMerge } from 'tailwind-merge'
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
@@ -230,29 +230,29 @@ export function toKebabCase(text: string) {
return text.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
}
export function concatCustomCss<T extends string = string>(
appCss?: ComponentCustomCSS<T>,
componentCss?: ComponentCustomCSS<T>
): Record<T, ComponentCssProperty> | undefined {
export function concatCustomCss<T extends Record<string, ComponentCssProperty>>(
appCss?: Record<string, ComponentCssProperty>,
componentCss?: T
): T | undefined {
if (!componentCss) return undefined
const customStyle = {} as Record<T, ComponentCssProperty>
Object.keys(componentCss).forEach((key) => {
const k = key as keyof typeof componentCss
return Object.fromEntries(
Object.entries(componentCss).map(([key, v]) => {
// This is the general style of the component type
const appStyle = appCss?.[key]?.style?.trim() || ''
const appEnding = appStyle?.endsWith(';') || !appStyle ? ' ' : '; '
// This is the general style of the component type
const appStyle = appCss?.[k]?.style?.trim() || ''
const appEnding = appStyle?.endsWith(';') || !appStyle ? ' ' : '; '
// This is the custom style of the component instance
const compStyle = v?.style?.trim() || ''
const compEnding = compStyle?.endsWith(';') || !compStyle ? ' ' : ';'
// This is the custom style of the component instance
const compStyle = componentCss[k]?.style?.trim() || ''
const compEnding = compStyle?.endsWith(';') || !compStyle ? ' ' : ';'
customStyle[k] = {
style: (appStyle + appEnding + compStyle + compEnding).trim(),
class: twMerge(appCss?.[k]?.class, componentCss[k]?.class)
}
})
return customStyle
return [
key,
{
style: (appStyle + appEnding + compStyle + compEnding).trim(),
class: twMerge(appCss?.[key]?.class, v?.class)
}
]
})
) as T
}
@@ -26,7 +26,8 @@
grid: [],
fullscreen: false,
unusedInlineScripts: [],
hiddenInlineScripts: []
hiddenInlineScripts: [],
css: {}
}
if (nodraft) {