Compare commits

...
Author SHA1 Message Date
Ruben Fiszel a9ef31f314 iterate 2025-06-14 09:55:51 +02:00
Ruben Fiszel 355a92a3df appSvelte5 2025-06-13 17:34:23 +02:00
88 changed files with 2058 additions and 1600 deletions
@@ -4,9 +4,9 @@
import BackgroundRunnablesTutorial from './tutorials/app/BackgroundRunnablesTutorial.svelte'
import ConnectionTutorial from './tutorials/app/ConnectionTutorial.svelte'
let backgroundRunnablesTutorial: BackgroundRunnablesTutorial | undefined = undefined
let connectionTutorial: ConnectionTutorial | undefined = undefined
let appTutorial: AppTutorial | undefined = undefined
let backgroundRunnablesTutorial: BackgroundRunnablesTutorial | undefined = $state(undefined)
let connectionTutorial: ConnectionTutorial | undefined = $state(undefined)
let appTutorial: AppTutorial | undefined = $state(undefined)
export function runTutorialById(id: string, options?: { skipStepsCount?: number }) {
if (id === 'backgroundrunnables') {
@@ -23,6 +23,7 @@
}
</script>
P
<AppTutorial
bind:this={appTutorial}
on:error
@@ -3,10 +3,15 @@
import type { Writable } from 'svelte/store'
import type { GroupContext } from '../types'
export let context: Writable<Record<string, any>>
export let id: string
interface Props {
context: Writable<Record<string, any>>
id: string
children?: import('svelte').Snippet
}
let { context, id, children }: Props = $props()
setContext<GroupContext>('GroupContext', { id, context })
</script>
<slot />
{@render children?.()}
@@ -71,7 +71,7 @@
let runnableComponent: RunnableComponent | undefined = $state()
let loading = $state(false)
let css = $state(initCss($app.css?.formcomponent, customCss))
let css = $state(initCss(app.css?.formcomponent, customCss))
let wrapper: RunnableWrapper | undefined = $state()
$effect(() => {
@@ -98,7 +98,7 @@
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.formcomponent}
componentStyle={app.css?.formcomponent}
/>
{/each}
@@ -70,7 +70,7 @@
let errors: Record<string, string> = {}
let css = $state(initCss($app?.css?.formbuttoncomponent, customCss))
let css = $state(initCss(app?.css?.formbuttoncomponent, customCss))
let runnableWrapper: RunnableWrapper | undefined = $state()
let loading = $state(false)
let modal: AlwaysMountedModal | undefined = $state()
@@ -93,7 +93,7 @@
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.formbuttoncomponent}
componentStyle={app.css?.formbuttoncomponent}
/>
{/each}
@@ -102,7 +102,7 @@
}
}
let css = $state(initCss($app.css?.schemaformcomponent, customCss))
let css = $state(initCss(app.css?.schemaformcomponent, customCss))
const resolvedConfig = $state(
initConfig(components['schemaformcomponent'].initialData.configuration, configuration)
@@ -125,7 +125,15 @@
return policy
}
$effect(() => {
args && untrack(() => handleArgsChange())
if (!args) return
{
if (args && typeof args === 'object') {
for (const key in args) {
args[key]
}
}
}
untrack(() => handleArgsChange())
})
$effect(() => {
outputs.valid.set(valid)
@@ -152,7 +160,7 @@
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.schemaformcomponent}
componentStyle={app.css?.schemaformcomponent}
/>
{/each}
@@ -164,8 +172,7 @@
>
<div
onpointerdown={stopPropagation(
(e) =>
!$connectingInput.opened && selectId(e as PointerEvent, id, selectedComponent, $app)
(e) => !$connectingInput.opened && selectId(e as PointerEvent, id, selectedComponent, app)
)}
>
<SchemaForm
@@ -18,18 +18,27 @@
import DisplayResult from '$lib/components/DisplayResult.svelte'
import { userStore } from '$lib/stores'
export let id: string
export let initializing: boolean | undefined = false
export let customCss: ComponentCustomCSS<'jobiddisplaycomponent'> | undefined = undefined
export let configuration: RichConfigurations
export let render: boolean
interface Props {
id: string
initializing?: boolean | undefined
customCss?: ComponentCustomCSS<'jobiddisplaycomponent'> | undefined
configuration: RichConfigurations
render: boolean
}
let {
id,
initializing = $bindable(false),
customCss = undefined,
configuration,
render
}: Props = $props()
const { app, worldStore, workspace, appPath } = getContext<AppViewerContext>('AppViewerContext')
const requireHtmlApproval = getContext<boolean | undefined>(IS_APP_PUBLIC_CONTEXT_KEY)
let resolvedConfig = initConfig(
components['jobiddisplaycomponent'].initialData.configuration,
configuration
let resolvedConfig = $state(
initConfig(components['jobiddisplaycomponent'].initialData.configuration, configuration)
)
const outputs = initOutput($worldStore, id, {
@@ -40,18 +49,20 @@
initializing = false
let css = initCss($app.css?.jobiddisplaycomponent, customCss)
let css = $state(initCss(app.css?.jobiddisplaycomponent, customCss))
let testJobLoader: TestJobLoader | undefined = undefined
let testIsLoading: boolean = false
let testJob: Job | undefined = undefined
let testJobLoader: TestJobLoader | undefined = $state(undefined)
let testIsLoading: boolean = $state(false)
let testJob: Job | undefined = $state(undefined)
$: if (resolvedConfig.jobId) {
outputs.loading.set(true)
testJobLoader?.watchJob(resolvedConfig?.['jobId'])
}
$effect(() => {
if (resolvedConfig.jobId) {
outputs.loading.set(true)
testJobLoader?.watchJob(resolvedConfig?.['jobId'])
}
})
let result: any = undefined
let result: any = $state(undefined)
</script>
{#each Object.keys(components['jobiddisplaycomponent'].initialData.configuration) as key (key)}
@@ -69,7 +80,7 @@
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.jobiddisplaycomponent}
componentStyle={app.css?.jobiddisplaycomponent}
/>
{/each}
@@ -101,13 +112,13 @@
</div>
<div
style={twMerge(
$app.css?.['displaycomponent']?.['container']?.style,
app.css?.['displaycomponent']?.['container']?.style,
customCss?.container?.style,
'wm-rich-result-container'
)}
class={twMerge(
'p-2 grow overflow-auto',
$app.css?.['displaycomponent']?.['container']?.class,
app.css?.['displaycomponent']?.['container']?.class,
customCss?.container?.class
)}
>
@@ -224,9 +224,7 @@
//@ts-ignore
gridItem.data.configuration.longitude.value = center[0]
//@ts-ignore
gridItem.data.configuration.latitude.value = center[1]
$app = $app
gridItem.data.configuration.latitude.value = center[1] // $app = $app
}
}
</script>
@@ -35,7 +35,7 @@
initializing = false
let css = initCss($app.css?.recomputeallcomponent, customCss)
let css = initCss(app.css?.recomputeallcomponent, customCss)
$: resolvedConfig.defaultRefreshInterval && handleRefreshInterval()
@@ -68,7 +68,7 @@
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.recomputeallcomponent}
componentStyle={app.css?.recomputeallcomponent}
/>
{/each}
@@ -1,4 +1,7 @@
<script lang="ts">
import { run, createBubbler, stopPropagation } from 'svelte/legacy'
const bubble = createBubbler()
import Tooltip from '$lib/components/Tooltip.svelte'
import { Clipboard } from 'lucide-svelte'
import { getContext } from 'svelte'
@@ -15,23 +18,34 @@
import { initCss } from '../../utils'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = 'left'
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let configuration: RichConfigurations
export let initializing: boolean | undefined = undefined
export let customCss: ComponentCustomCSS<'textcomponent'> | undefined = undefined
export let render: boolean
export let editorMode: boolean = false
interface Props {
id: string
componentInput: AppInput | undefined
horizontalAlignment?: 'left' | 'center' | 'right' | undefined
verticalAlignment?: 'top' | 'center' | 'bottom' | undefined
configuration: RichConfigurations
initializing?: boolean | undefined
customCss?: ComponentCustomCSS<'textcomponent'> | undefined
render: boolean
editorMode?: boolean
}
let resolvedConfig = initConfig(
components['textcomponent'].initialData.configuration,
configuration
let {
id,
componentInput,
horizontalAlignment = 'left',
verticalAlignment = undefined,
configuration,
initializing = $bindable(undefined),
customCss = undefined,
render,
editorMode = $bindable(false)
}: Props = $props()
let resolvedConfig = $state(
initConfig(components['textcomponent'].initialData.configuration, configuration)
)
$: editorMode && onEditorMode()
function onEditorMode() {
autosize()
setTimeout(() => autosize(), 50)
@@ -39,9 +53,9 @@
const { app, worldStore, mode, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
let css = initCss($app.css?.textcomponent, customCss)
let css = $state(initCss(app.css?.textcomponent, customCss))
let result: string | undefined = undefined
let result: string | undefined = $state(undefined)
if (
componentInput?.type == 'template' ||
@@ -108,16 +122,8 @@
}
}
let component = 'p'
let classes = ''
$: resolvedConfig.style && (component = getComponent())
$: resolvedConfig.style && (classes = getClasses())
$: initialValue =
componentInput?.type == 'template' || componentInput?.type == 'templatev2'
? componentInput.eval
: ''
$: editableValue = initialValue ?? ''
let component = $state('p')
let classes = $state('')
let rows = 1
@@ -140,6 +146,21 @@
// console.log(el, el?.scrollHeight)
}, 0)
}
run(() => {
editorMode && onEditorMode()
})
run(() => {
resolvedConfig.style && (component = getComponent())
})
run(() => {
resolvedConfig.style && (classes = getClasses())
})
let initialValue = $derived(
componentInput?.type == 'template' || componentInput?.type == 'templatev2'
? componentInput.eval
: ''
)
let editableValue = $derived(initialValue ?? '')
</script>
{#each Object.keys(components['textcomponent'].initialData.configuration) as key (key)}
@@ -157,22 +178,22 @@
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.textcomponent}
componentStyle={app.css?.textcomponent}
/>
{/each}
<RunnableWrapper {outputs} {render} {componentInput} {id} bind:initializing bind:result>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class={twMerge('h-full w-full overflow-hidden', css.container?.class, 'wm-text-container')}
style={css?.container?.style}
on:dblclick={() => {
ondblclick={() => {
if (!editorMode) {
editorMode = true
document.getElementById(`text-${id}`)?.focus()
}
}}
on:keydown|stopPropagation
onkeydown={stopPropagation(bubble('keydown'))}
>
{#if $mode == 'dnd' && editorMode && (componentInput?.type == 'template' || componentInput?.type == 'templatev2')}
<AlignWrapper {verticalAlignment}>
@@ -187,20 +208,20 @@
horizontalAlignment === 'center'
? 'text-center'
: horizontalAlignment === 'right'
? 'text-right'
: 'text-left'
? 'text-right'
: 'text-left'
)}
on:pointerdown|stopPropagation
onpointerdown={stopPropagation(bubble('pointerdown'))}
style={css?.text?.style}
id={`text-${id}`}
on:pointerenter={() => {
onpointerenter={() => {
const elem = document.getElementById(`text-${id}`)
if (elem) {
elem.focus()
}
}}
{rows}
on:input={onInput}
oninput={onInput}
value={editableValue}
></textarea>
</AlignWrapper>
@@ -215,7 +236,7 @@
{/if}
</div>
{:else}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class="flex flex-wrap gap-0.5 pb-0.5 w-full {$mode === 'dnd' &&
(componentInput?.type == 'template' || componentInput?.type == 'templatev2')
@@ -232,8 +253,8 @@
horizontalAlignment === 'center'
? 'text-center'
: horizontalAlignment === 'right'
? 'text-right'
: 'text-left'
? 'text-right'
: 'text-left'
)}
style={css?.text?.style}
>
@@ -380,8 +380,7 @@
//@ts-ignore
gridItem.data.configuration.columnDefs.loading = true
gridItem.data = gridItem.data
$app = $app
gridItem.data = gridItem.data // $app = $app
let tableMetadata = await loadTableMetaData(
resolvedConfig.type.configuration[selected].resource,
@@ -404,9 +403,7 @@
if (shouldReturnEarly(newMap, oldMap)) {
//@ts-ignore
gridItem.data.configuration.columnDefs.loading = false
gridItem.data = gridItem.data
$app = $app
gridItem.data = gridItem.data // $app = $app
return
}
@@ -457,9 +454,7 @@
//@ts-ignore
gridItem.data.configuration.columnDefs = { value: ncols, type: 'static', loading: false }
gridItem.data = gridItem.data
$app = $app
gridItem.data = gridItem.data // $app = $app
let oldS = $selectedComponent
$selectedComponent = []
await tick()
@@ -369,7 +369,7 @@
const uuid = await executeRunnable(
runnable,
workspace,
$app.version,
app.version,
$userStore?.username,
$appPath,
id,
@@ -436,7 +436,7 @@
}
}
const oneOfRunnableInputs = isEditor ? collectOneOfFields(fields, $app) : {}
const oneOfRunnableInputs = isEditor ? collectOneOfFields(fields, app) : {}
const requestBody: ExecuteComponentData['requestBody'] = {
args: nonStaticRunnableInputs,
@@ -608,7 +608,7 @@
function handleInputClick(e: CustomEvent) {
const event = e as unknown as PointerEvent
!$connectingInput.opened && selectId(event, id, selectedComponent, $app)
!$connectingInput.opened && selectId(event, id, selectedComponent, app)
}
let cancellableRun: ((inlineScript?: InlineScript) => CancelablePromise<void>) | undefined =
@@ -12,23 +12,37 @@
import GroupWrapper from '../GroupWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
export let render: boolean
export let groupFields: RichConfigurations | undefined = undefined
interface Props {
id: string
componentContainerHeight: number
customCss?: ComponentCustomCSS<'containercomponent'> | undefined
render: boolean
groupFields?: RichConfigurations | undefined
}
let {
id,
componentContainerHeight,
customCss = undefined,
render,
groupFields = undefined
}: Props = $props()
const { app, focusedGrid, selectedComponent, worldStore, connectingInput, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
let everRender = render
$: render && !everRender && (everRender = true)
let everRender = $state(render)
$effect(() => {
render && !everRender && (everRender = true)
})
let groupContext = writable({})
let outputs = initOutput($worldStore, id, { group: $groupContext })
$: outputs.group.set($groupContext, true)
$effect(() => {
outputs.group.set($groupContext, true)
})
function onFocus() {
$focusedGrid = {
@@ -48,7 +62,7 @@
}
})
let css = initCss($app.css?.containercomponent, customCss)
let css = $state(initCss(app.css?.containercomponent, customCss))
</script>
<InitializeComponent {id} />
@@ -59,7 +73,7 @@
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.containercomponent}
componentStyle={app.css?.containercomponent}
/>
{/each}
@@ -71,7 +85,7 @@
{#if everRender}
<div class="w-full h-full">
{#if $app.subgrids?.[`${id}-0`]}
{#if app.subgrids?.[`${id}-0`]}
<GroupWrapper {id} context={groupContext}>
<SubGridEditor
visible={render}
@@ -90,7 +104,7 @@
</GroupWrapper>
{/if}
</div>
{:else if $app.subgrids?.[`${id}-0`]}
{:else if app.subgrids?.[`${id}-0`]}
<GroupWrapper {id} context={groupContext}>
<SubGridEditor visible={false} {id} subGridId={`${id}-0`} />
</GroupWrapper>
@@ -100,7 +100,7 @@
migrateApp(app)
const appStore = writable<App>(app)
const appStore = $state(app)
const selectedComponent = writable<string[] | undefined>(undefined)
// $: selectedComponent.subscribe((s) => {
@@ -242,7 +242,7 @@
timeout && clearTimeout(timeout)
timeout = setTimeout(() => {
try {
localStorage.setItem(path != '' ? `app-${path}` : 'app', encodeState($appStore))
localStorage.setItem(path != '' ? `app-${path}` : 'app', encodeState(appStore))
} catch (err) {
console.error('Error storing frontend draft in localStorage', err)
}
@@ -262,7 +262,7 @@
selectedTab = 'settings'
if (befSelected) {
if (!['ctx', 'state'].includes(befSelected) && !befSelected?.startsWith(BG_PREFIX)) {
let item = findGridItem($appStore, befSelected)
let item = findGridItem(appStore, befSelected)
if (item?.data.type === 'containercomponent' || item?.data.type === 'listcomponent') {
$focusedGrid = {
parentComponentId: befSelected,
@@ -284,7 +284,7 @@
($worldStore.outputsById?.[befSelected]?.selectedTabIndex?.peak() as number) ?? 0
}
} else {
let subgrid = findGridItemParentGrid($appStore, befSelected)
let subgrid = findGridItemParentGrid(appStore, befSelected)
if (subgrid) {
try {
$focusedGrid = {
@@ -388,22 +388,25 @@
let css: string | undefined = $state(undefined)
let lastTheme: string | undefined = undefined
appStore.subscribe(async (currentAppStore) => {
if (!currentAppStore.theme) {
return
}
if (JSON.stringify(currentAppStore.theme) != lastTheme) {
if (currentAppStore.theme.type === 'inlined') {
css = currentAppStore.theme.css
} else if (currentAppStore.theme.type === 'path' && currentAppStore.theme?.path) {
let loadedCss = await getTheme($workspaceStore!, currentAppStore.theme.path)
if (loadedCss) {
css = loadedCss.value
}
$effect(() => {
appStore.theme
untrack(async () => {
if (!appStore.theme) {
return
}
lastTheme = JSON.stringify(currentAppStore.theme)
}
if (JSON.stringify(appStore.theme) != lastTheme) {
if (appStore.theme.type === 'inlined') {
css = appStore.theme.css
} else if (appStore.theme.type === 'path' && appStore.theme?.path) {
let loadedCss = await getTheme($workspaceStore!, appStore.theme.path)
if (loadedCss) {
css = loadedCss.value
}
}
lastTheme = JSON.stringify(appStore.theme)
}
})
})
function addOrRemoveCss(isPremium: boolean, isPreview: boolean = false) {
@@ -760,15 +763,15 @@
path && untrack(() => onPathChange())
})
$effect(() => {
$appStore && untrack(() => saveFrontendDraft())
appStore && untrack(() => saveFrontendDraft())
})
$effect(() => {
context.mode = $mode == 'dnd' ? 'editor' : 'viewer'
})
let width = $derived(
$breakpoint === 'sm' && $appStore?.mobileViewOnSmallerScreens !== false
$breakpoint === 'sm' && appStore?.mobileViewOnSmallerScreens !== false
? 'min-w-[400px] max-w-[656px]'
: `min-w-[710px] ${$appStore.fullscreen ? 'w-full' : 'max-w-7xl'}`
: `min-w-[710px] ${appStore.fullscreen ? 'w-full' : 'max-w-7xl'}`
)
$effect(() => {
if ($selectedComponent?.[0] != befSelected) {
@@ -846,7 +849,7 @@
/>
{#if !$userStore?.operator}
{#if $appStore}
{#if appStore}
<AppEditorHeader
{newPath}
{newApp}
@@ -885,15 +888,15 @@
<div
class={twMerge(
'h-full w-full relative',
$appStore.css?.['app']?.['viewer']?.class,
appStore.css?.['app']?.['viewer']?.class,
'wm-app-viewer'
)}
style={$appStore.css?.['app']?.['viewer']?.style}
style={appStore.css?.['app']?.['viewer']?.style}
>
<AppPreview
workspace={$workspaceStore ?? ''}
summary={$summaryStore}
app={$appStore}
app={appStore}
appPath={path}
{breakpoint}
{policy}
@@ -951,11 +954,11 @@
}}
class={twMerge(
'bg-surface-secondary h-full w-full relative',
$appStore.css?.['app']?.['viewer']?.class,
appStore.css?.['app']?.['viewer']?.class,
'wm-app-viewer h-full overflow-visible',
$panzoomActive ? 'cursor-grab' : ''
)}
style={$appStore.css?.['app']?.['viewer']?.style}
style={appStore.css?.['app']?.['viewer']?.style}
bind:clientWidth={centerPanelWidth}
>
{#if leftPanelSize === 0}
@@ -1085,7 +1088,7 @@
)}
style={$componentActive ? `top: -${$yTop}px;` : ''}
>
{#if $appStore.grid}
{#if appStore.grid}
{#if !$connectingInput?.opened}
<ComponentNavigation />
{/if}
@@ -1102,7 +1105,7 @@
<GridEditor {policy} />
</div>
{/if}
{#if !$appStore?.mobileViewOnSmallerScreens && $breakpoint === 'sm'}
{#if !appStore?.mobileViewOnSmallerScreens && $breakpoint === 'sm'}
<div
class="absolute inset-0 flex bg-surface center-center z-10000 bg-opacity-60"
>
@@ -1119,7 +1122,7 @@
variant="border"
size="xs"
on:click={() => {
$appStore.mobileViewOnSmallerScreens = true
appStore.mobileViewOnSmallerScreens = true
}}
startIcon={{
icon: Smartphone
@@ -187,7 +187,7 @@
}
async function computeTriggerables() {
const items = allItems($app.grid, $app.subgrids)
const items = allItems(app.grid, app.subgrids)
console.debug('items', items)
@@ -277,7 +277,7 @@
return processed as Promise<[string, TriggerableV2] | undefined>[]
})
.concat(
Object.values($app.hiddenInlineScripts ?? {}).map(async (v, i) => {
Object.values(app.hiddenInlineScripts ?? {}).map(async (v, i) => {
return await processRunnable(BG_PREFIX + i, v, v.fields)
}) as Promise<[string, TriggerableV2] | undefined>[]
)
@@ -294,7 +294,7 @@
.map((x) => {
const c = x.data as AppComponent
const config = c.configuration as any
return computeS3FileInputPolicy(config?.type?.configuration?.s3, $app)
return computeS3FileInputPolicy(config?.type?.configuration?.s3, app)
})
.filter(Boolean) as {
allowed_resources: string[]
@@ -345,7 +345,7 @@
fields: Record<string, any>
): Promise<[string, TriggerableV2] | undefined> {
const staticInputs = collectStaticFields(fields)
const oneOfInputs = collectOneOfFields(fields, $app)
const oneOfInputs = collectOneOfFields(fields, app)
const allowUserResources: string[] = Object.entries(fields)
.map(([k, v]) => {
return v['allowUserResources'] ? k : undefined
@@ -382,7 +382,7 @@
await AppService.createApp({
workspace: $workspaceStore!,
requestBody: {
value: $app,
value: app,
path,
summary: $summary,
policy,
@@ -392,7 +392,7 @@
})
savedApp = {
summary: $summary,
value: structuredClone($app),
value: structuredClone($state.snapshot(app)),
path: path,
policy: policy,
custom_path: customPath
@@ -425,12 +425,12 @@
if (
deployedValue &&
savedApp &&
$app &&
app &&
orderedJsonStringify(deployedValue) ===
orderedJsonStringify(
replaceFalseWithUndefined({
summary: $summary,
value: $app,
value: app,
path: newEditedPath || savedApp.draft?.path || savedApp.path,
policy,
custom_path: customPath
@@ -475,7 +475,7 @@
workspace: $workspaceStore!,
path: $appPath!,
requestBody: {
value: $app!,
value: app!,
summary: $summary,
policy,
path: npath,
@@ -488,7 +488,7 @@
})
savedApp = {
summary: $summary,
value: structuredClone($app),
value: structuredClone($state.snapshot(app)),
path: npath,
policy,
custom_path: customPath
@@ -548,7 +548,7 @@
await AppService.createApp({
workspace: $workspaceStore!,
requestBody: {
value: $app,
value: app,
path: newEditedPath,
summary: $summary,
policy,
@@ -562,7 +562,7 @@
path: newEditedPath,
typ: 'app',
value: {
value: $app,
value: app,
path: newEditedPath,
summary: $summary,
policy,
@@ -572,13 +572,13 @@
})
savedApp = {
summary: $summary,
value: structuredClone($app),
value: structuredClone($state.snapshot(app)),
path: newEditedPath,
policy,
draft_only: true,
draft: {
summary: $summary,
value: structuredClone($app),
value: structuredClone($state.snapshot(app)),
path: newEditedPath,
policy,
custom_path: customPath
@@ -606,7 +606,7 @@
const draftOrDeployed = cleanValueProperties(savedApp.draft || savedApp)
const current = cleanValueProperties({
summary: $summary,
value: $app,
value: app,
path: newEditedPath || savedApp.draft?.path || savedApp.path,
policy
})
@@ -633,7 +633,7 @@
await AppService.createApp({
workspace: $workspaceStore!,
requestBody: {
value: $app!,
value: app!,
summary: $summary,
policy,
path: newEditedPath || path,
@@ -648,7 +648,7 @@
path: savedApp.draft_only ? newEditedPath || path : path,
typ: 'app',
value: {
value: $app!,
value: app!,
summary: $summary,
policy,
path: newEditedPath || path
@@ -660,7 +660,7 @@
...(savedApp?.draft_only
? {
summary: $summary,
value: structuredClone($app),
value: structuredClone($state.snapshot(app)),
path: savedApp.draft_only ? newEditedPath || path : path,
policy,
draft_only: true,
@@ -669,7 +669,7 @@
: savedApp),
draft: {
summary: $summary,
value: structuredClone($app),
value: structuredClone($state.snapshot(app)),
path: newEditedPath || path,
policy,
custom_path: customPath
@@ -726,13 +726,13 @@
switch (event.key) {
case 'Z':
if (event.ctrlKey || event.metaKey) {
$app = redo(history)
// app = redo(history)
event.preventDefault()
}
break
case 'z':
if (event.ctrlKey || event.metaKey) {
$app = undo(history, $app)
// app = undo(history, app)
event.preventDefault()
}
@@ -781,7 +781,7 @@
displayName: 'Export',
icon: FileJson,
action: () => {
appExport?.open($app)
appExport?.open(app)
}
},
// {
@@ -796,7 +796,7 @@
displayName: 'Hub compatible JSON',
icon: FileUp,
action: () => {
appExport?.open(toStatic($app, $staticExporter, $summary).app)
appExport?.open(toStatic(app, $staticExporter, $summary).app)
}
},
{
@@ -832,7 +832,7 @@
draft: savedApp.draft,
current: {
summary: $summary,
value: $app,
value: app,
path: newEditedPath || savedApp.draft?.path || savedApp.path,
policy,
custom_path: customPath
@@ -885,7 +885,7 @@
}
let priorDarkMode = document.documentElement.classList.contains('dark')
setTheme($app?.darkMode)
setTheme(app?.darkMode)
let customPath = $state(savedApp?.custom_path)
let dirtyCustomPath = $state(false)
@@ -951,7 +951,7 @@
savedValue: savedApp,
modifiedValue: {
summary: $summary,
value: $app,
value: app,
path: newEditedPath || savedApp?.draft?.path || savedApp?.path,
policy,
custom_path: customPath
@@ -967,7 +967,7 @@
bind:deployedValue
currentValue={{
summary: $summary,
value: $app,
value: app,
path: newEditedPath || savedApp?.draft?.path || savedApp?.path,
policy,
custom_path: customPath
@@ -1123,7 +1123,7 @@
draft: savedApp.draft,
current: {
summary: $summary,
value: $app,
value: app,
path: newEditedPath || savedApp.draft?.path || savedApp.path,
policy,
custom_path: customPath
@@ -1313,19 +1313,19 @@
undoProps={{ disabled: $history?.index === 0 }}
redoProps={{ disabled: $history && $history?.index === $history.history.length - 1 }}
on:undo={() => {
$app = undo(history, $app)
// app = undo(history, app)
}}
on:redo={() => {
$app = redo(history)
// app = redo(history)
}}
/>
{#if $app}
{#if app}
<ToggleButtonGroup
class="h-[30px]"
selected={$app.fullscreen ? 'true' : 'false'}
selected={app.fullscreen ? 'true' : 'false'}
on:selected={({ detail }) => {
$app.fullscreen = detail === 'true'
app.fullscreen = detail === 'true'
}}
>
{#snippet children({ item })}
@@ -1346,14 +1346,14 @@
{/snippet}
</ToggleButtonGroup>
{/if}
{#if $app}
{#if app}
<ToggleButtonGroup
class="h-[30px]"
on:selected={({ detail }) => {
const theme = detail === 'dark' ? true : detail === 'sun' ? false : undefined
setTheme(theme)
}}
selected={$app.darkMode === undefined ? 'auto' : $app.darkMode ? 'dark' : 'sun'}
selected={app.darkMode === undefined ? 'auto' : app.darkMode ? 'dark' : 'sun'}
>
{#snippet children({ item })}
<ToggleButton
@@ -1406,7 +1406,7 @@
'Desktop view is enabled by default. Enable this to customize the layout of the components for the mobile view'
}}
textClass="text-2xs whitespace-nowrap white !w-full"
bind:checked={$app.mobileViewOnSmallerScreens}
bind:checked={app.mobileViewOnSmallerScreens}
class="flex flex-row px-2 items-center"
/>
{/if}
@@ -11,8 +11,8 @@
import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte'
import { isAppTainted } from '$lib/components/tutorials/utils'
let appTutorials: AppTutorials | undefined = undefined
let targetTutorial: string | undefined = undefined
let appTutorials: AppTutorials | undefined = $state(undefined)
let targetTutorial: string | undefined = $state(undefined)
const { app } = getContext<AppViewerContext>('AppViewerContext')
@@ -23,7 +23,7 @@
const forkedFromTemplate = urlParams.get('template')
if (
!isAppTainted($app) &&
!isAppTainted(app) &&
!$ignoredTutorials.includes(7) &&
$tutorialsToDo.includes(7) &&
!forkedFromTheHub &&
@@ -81,7 +81,7 @@
{#key $tutorialsToDo}
<Dropdown items={getTutorialItems}>
<svelte:fragment slot="buttonReplacement">
{#snippet buttonReplacement()}
<Button
nonCaptureEvent
size="xs"
@@ -92,7 +92,7 @@
icon: BookOpen
}}
/>
</svelte:fragment>
{/snippet}
</Dropdown>
{/key}
@@ -9,7 +9,7 @@
const { app } = getContext<AppViewerContext>('AppViewerContext')
let resourceOnly: boolean = true
let resourceOnly: boolean = $state(true)
</script>
<Alert type="info" title="Configurations">
@@ -19,7 +19,7 @@
<Toggle bind:checked={resourceOnly} options={{ right: 'Resource only' }} />
</div>
<div class="gap-4 flex flex-col pt-4">
{#each allItems($app.grid, $app.subgrids) as gridItem (gridItem.data.id)}
{#each allItems(app.grid, app.subgrids) as gridItem (gridItem.data.id)}
{#if gridItem?.data?.type === 'tablecomponent'}
<div>
<AppComponentInput bind:component={gridItem.data} {resourceOnly} />
@@ -52,10 +52,10 @@
{/each}
</div>
{#if $app?.hiddenInlineScripts?.length > 0}
{#if app?.hiddenInlineScripts?.length > 0}
<div class="font-bold text-lg">Background runnable inputs</div>
<div class="gap-4 flex flex-col pt-4">
{#each $app?.hiddenInlineScripts ?? [] as script, index (script.name)}
{#each app?.hiddenInlineScripts ?? [] as script, index (script.name)}
<div class="border p-2">
<div class="text-sm font-bold">{script.name}</div>
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext, onDestroy, setContext } from 'svelte'
import { getContext, onDestroy, setContext, untrack } from 'svelte'
import { get, writable, type Writable } from 'svelte/store'
import { buildWorld } from '../rx'
import type {
@@ -24,28 +24,43 @@
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
import RecomputeAllComponents from './RecomputeAllComponents.svelte'
export let app: App
export let appPath: string = ''
export let breakpoint: Writable<EditorBreakpoint> = writable('lg')
export let policy: Policy = {}
export let summary: string = ''
export let workspace: string = $workspaceStore!
export let isEditor: boolean = false
export let context: Record<string, any>
export let noBackend: boolean = false
export let isLocked = false
export let hideRefreshBar = false
interface Props {
app: App
appPath?: string
breakpoint?: Writable<EditorBreakpoint>
policy?: Policy
summary?: string
workspace?: string
isEditor?: boolean
context: Record<string, any>
noBackend?: boolean
isLocked?: boolean
hideRefreshBar?: boolean
className?: string
replaceStateFn?: (path: string) => void
gotoFn?: (path: string, opt?: Record<string, any> | undefined) => void
}
export let replaceStateFn: (path: string) => void = (path: string) =>
window.history.replaceState(null, '', path)
export let gotoFn: (path: string, opt?: Record<string, any> | undefined) => void = (
path: string,
opt?: Record<string, any>
) => window.history.pushState(null, '', path)
let {
app,
appPath = '',
breakpoint = writable('lg'),
policy = {},
summary = '',
workspace = $workspaceStore!,
isEditor = false,
context,
noBackend = false,
isLocked = $bindable(false),
hideRefreshBar = false,
className = '',
replaceStateFn = (path: string) => window.history.replaceState(null, '', path),
gotoFn = (path: string, opt?: Record<string, any>) => window.history.pushState(null, '', path)
}: Props = $props()
migrateApp(app)
const appStore = writable<App>(app)
const appStore = $state(app)
const selectedComponent = writable<string[] | undefined>(undefined)
const mode = writable<EditorMode>('preview')
@@ -93,12 +108,11 @@
setTheme($darkMode)
const state = writable({})
const appState = writable({})
let parentContext = getContext<AppViewerContext>('AppViewerContext')
let worldStore = buildWorld(ncontext)
$: onContextChange(context)
function onContextChange(context: any) {
Object.assign(ncontext, context)
@@ -118,7 +132,6 @@
}
let writablePath = writable(appPath)
$: appPath && onPathChange()
function onPathChange() {
writablePath.set(appPath)
@@ -152,7 +165,7 @@
focusedGrid: writable(undefined),
stateId: writable(0),
parentWidth,
state: state,
state: appState,
componentControl: writable({}),
hoverStore: writable(undefined),
allIdsInPath,
@@ -172,19 +185,7 @@
panzoomActive: writable(false)
})
let previousSelectedIds: string[] | undefined = undefined
$: if (!deepEqual(previousSelectedIds, $selectedComponent)) {
previousSelectedIds = $selectedComponent
$allIdsInPath = ($selectedComponent ?? [])
.flatMap((id) => dfs(app.grid, id, app.subgrids ?? {}))
.filter((x) => x != undefined) as string[]
}
$: width =
$breakpoint === 'sm' && $appStore?.mobileViewOnSmallerScreens !== false
? 'max-w-[640px]'
: 'w-full min-w-[768px]'
$: lockedClasses = isLocked ? '!max-h-[400px] overflow-hidden pointer-events-none' : ''
let previousSelectedIds: string[] | undefined = $state(undefined)
function onThemeChange() {
$darkMode = app?.darkMode ?? document.documentElement.classList.contains('dark')
@@ -192,9 +193,14 @@
const cssId = 'wm-global-style'
let css: string | undefined = undefined
let css: string | undefined = $state(undefined)
appStore.subscribe(loadTheme)
$effect(() => {
appStore.theme
untrack(() => {
loadTheme(appStore)
})
})
async function loadTheme(currentAppStore: App) {
if (!currentAppStore.theme) {
@@ -211,8 +217,6 @@
}
}
$: addOrRemoveCss($enterpriseLicense !== undefined || isEditor, css)
function addOrRemoveCss(isPremium: boolean, cssString: string | undefined) {
const existingElement = document.getElementById(cssId)
@@ -237,33 +241,63 @@
}
}
let appHeight: number = 0
let appHeight: number = $state(0)
$: maxRow = maxHeight($appStore.grid, appHeight, $breakpoint)
$effect(() => {
context
untrack(() => {
onContextChange(context)
})
})
$effect(() => {
appPath && onPathChange()
})
$effect(() => {
if (!deepEqual(previousSelectedIds, $selectedComponent)) {
untrack(() => {
previousSelectedIds = $selectedComponent
$allIdsInPath = ($selectedComponent ?? [])
.flatMap((id) => dfs(app.grid, id, app.subgrids ?? {}))
.filter((x) => x != undefined) as string[]
})
}
})
let width = $derived(
$breakpoint === 'sm' && appStore?.mobileViewOnSmallerScreens !== false
? 'max-w-[640px]'
: 'w-full min-w-[768px]'
)
let lockedClasses = $derived(isLocked ? '!max-h-[400px] overflow-hidden pointer-events-none' : '')
$effect(() => {
;[$enterpriseLicense, isEditor, css]
untrack(() => {
addOrRemoveCss($enterpriseLicense !== undefined || isEditor, css)
})
})
let maxRow = $derived(maxHeight(appStore.grid, appHeight, $breakpoint))
</script>
<svelte:head>
</svelte:head>
<svelte:head></svelte:head>
<DarkModeObserver on:change={onThemeChange} />
<svelte:window on:hashchange={hashchange} on:resize={resizeWindow} />
<svelte:window onhashchange={hashchange} onresize={resizeWindow} />
<div class="relative min-h-full grow" bind:clientHeight={appHeight}>
<div id="app-editor-top-level-drawer"></div>
<div id="app-editor-select"></div>
<div
class="{$$props.class} {lockedClasses} {width} h-full bg-surface {app.fullscreen
class="{className} {lockedClasses} {width} h-full bg-surface {app.fullscreen
? ''
: 'max-w-7xl'} mx-auto"
id="app-content"
>
{#if $appStore.grid}
{#if appStore.grid}
<div
class={twMerge(
'mx-auto',
hideRefreshBar || $appStore?.norefreshbar || $appStore.hideLegacyTopBar === true
hideRefreshBar || appStore?.norefreshbar || appStore.hideLegacyTopBar === true
? 'invisible h-0 overflow-hidden'
: ''
)}
@@ -290,36 +324,32 @@
bind:clientWidth={$parentWidth}
>
<div>
<GridViewer
allIdsInPath={$allIdsInPath}
items={app.grid}
let:dataItem
{maxRow}
breakpoint={$breakpoint}
>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={'h-full w-full center-center'}
on:pointerdown={() => ($selectedComponent = [dataItem.id])}
>
<Component
render={true}
component={dataItem.data}
selected={false}
locked={true}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
/>
</div>
<GridViewer allIdsInPath={$allIdsInPath} items={app.grid} {maxRow} breakpoint={$breakpoint}>
{#snippet children({ dataItem })}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
class={'h-full w-full center-center'}
onpointerdown={() => ($selectedComponent = [dataItem.id])}
>
<Component
render={true}
component={dataItem.data}
selected={false}
locked={true}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
/>
</div>
{/snippet}
</GridViewer>
</div>
</div>
</div>
{#if isLocked}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
on:click={() => (isLocked = false)}
onclick={() => (isLocked = false)}
class="absolute inset-0 center-center bg-black/20 z-50 backdrop-blur-[1px] cursor-pointer"
>
<Button on:click={() => (isLocked = false)}>
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext } from 'svelte'
import { getContext, untrack } from 'svelte'
import type { AppEditorContext, AppViewerContext } from '../types'
import { gridColumns, isFixed, toggleFixed } from '../gridUtils'
import { twMerge } from 'tailwind-merge'
@@ -29,7 +29,11 @@
import Popover from '$lib/components/Popover.svelte'
import type { Policy } from '$lib/gen'
export let policy: Policy
interface Props {
policy: Policy
}
let { policy }: Props = $props()
const {
selectedComponent,
@@ -42,33 +46,37 @@
allIdsInPath,
bgRuns,
worldStore
} = getContext<AppViewerContext>('AppViewerContext')
} = $state(getContext<AppViewerContext>('AppViewerContext'))
const { history, componentActive } = getContext<AppEditorContext>('AppEditorContext')
let previousSelectedIds: string[] | undefined = undefined
$: if (!deepEqual(previousSelectedIds, $selectedComponent)) {
previousSelectedIds = $selectedComponent
$allIdsInPath = ($selectedComponent ?? [])
.flatMap((id) => dfs($app.grid, id, $app.subgrids ?? {}))
.filter((x) => x != undefined) as string[]
}
let previousSelectedIds: string[] | undefined = $state(undefined)
$effect(() => {
if (!deepEqual(previousSelectedIds, $selectedComponent)) {
untrack(() => {
previousSelectedIds = $selectedComponent
$allIdsInPath = ($selectedComponent ?? [])
.flatMap((id) => dfs(app.grid, id, app.subgrids ?? {}))
.filter((x) => x != undefined) as string[]
})
}
})
function handleLock(id: string) {
const gridItem = findGridItem($app, id)
const gridItem = findGridItem(app, id)
if (gridItem) {
toggleFixed(gridItem)
}
$app = $app
// app = app
}
function handleFillHeight(id: string) {
const gridItem = findGridItem($app, id)
const gridItem = findGridItem(app, id)
const b = $breakpoint === 'sm' ? 3 : 12
if (gridItem?.[b]) {
gridItem[b].fullHeight = !gridItem[b].fullHeight
}
$app = $app
// app = app
}
export function moveComponentBetweenSubgrids(
@@ -78,25 +86,25 @@
position?: { x: number; y: number }
) {
// Find the component in the source subgrid
const component = findGridItem($app, componentId)
const component = findGridItem(app, componentId)
if (!component) {
return
}
let parentGrid = findGridItemParentGrid($app, component.id)
let parentGrid = findGridItemParentGrid(app, component.id)
if (parentGrid) {
$app.subgrids &&
($app.subgrids[parentGrid] = $app.subgrids[parentGrid].filter(
app.subgrids &&
(app.subgrids[parentGrid] = app.subgrids[parentGrid].filter(
(item) => item.id !== component?.id
))
} else {
$app.grid = $app.grid.filter((item) => item.id !== component?.id)
app.grid = app.grid.filter((item) => item.id !== component?.id)
}
const gridItem = component
insertNewGridItem(
$app,
app,
(id) => ({ ...gridItem.data, id }),
{ parentComponentId: parentComponentId, subGridIndex: subGridIndex },
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
@@ -108,9 +116,6 @@
true
)
// Update the app state
$app = { ...$app }
$selectedComponent = [parentComponentId]
$focusedGrid = {
parentComponentId,
@@ -120,7 +125,7 @@
</script>
<div class="w-full z-[1000] overflow-visible h-full">
<div class={$app.hideLegacyTopBar ? 'hidden' : ''}>
<div class={app.hideLegacyTopBar ? 'hidden' : ''}>
<div
class="w-full sticky top-0 flex justify-between border-b {$componentActive
? 'invisible'
@@ -138,15 +143,17 @@
<span class="!text-2xs text-tertiary inline-flex gap-1 items-center"
><Loader2 size={10} class="animate-spin" /> {$bgRuns.length}
</span>
<span slot="text"
><div class="flex flex-col">
{#each $bgRuns as bgRun}
<div class="flex gap-2 items-center">
<div class="text-2xs text-tertiary">{bgRun}</div>
</div>
{/each}
</div></span
>
{#snippet text()}
<span
><div class="flex flex-col">
{#each $bgRuns as bgRun}
<div class="flex gap-2 items-center">
<div class="text-2xs text-tertiary">{bgRun}</div>
</div>
{/each}
</div></span
>
{/snippet}
</Popover>
{:else}
<span class="w-9"></span>
@@ -155,7 +162,7 @@
<div class="flex text-2xs gap-8 items-center">
<div class="py-2 pr-2 text-secondary flex gap-1 items-center">
Hide bar on view
<Toggle size="xs" bind:checked={$app.norefreshbar} />
<Toggle size="xs" bind:checked={app.norefreshbar} />
</div>
<div>
{policy.on_behalf_of ? `Author ${policy.on_behalf_of_email}` : ''}
@@ -167,15 +174,15 @@
</div>
</div>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
style={$app.css?.['app']?.['grid']?.style}
style={app.css?.['app']?.['grid']?.style}
class={twMerge(
'p-2 overflow-visible z-50',
$app.css?.['app']?.['grid']?.class ?? '',
app.css?.['app']?.['grid']?.class ?? '',
'wm-app-grid !static h-full w-full'
)}
on:pointerdown={() => {
onpointerdown={() => {
$selectedComponent = undefined
$focusedGrid = undefined
}}
@@ -188,20 +195,16 @@
<Grid
allIdsInPath={$allIdsInPath}
selectedIds={$selectedComponent}
items={$app.grid}
items={app.grid}
on:redraw={(e) => {
push(history, $app)
$app.grid = e.detail
push(history, app)
app.grid = e.detail
}}
root
let:dataItem
let:overlapped
let:moveMode
let:componentDraggedId
on:dropped={(e) => {
const { id, overlapped, x, y } = e.detail
const overlappedComponent = findGridItem($app, overlapped)
const overlappedComponent = findGridItem(app, overlapped)
if (overlappedComponent && !isContainer(overlappedComponent.data.type)) {
return
@@ -224,62 +227,62 @@
}}
disableMove={!!$connectingInput.opened}
>
<ComponentWrapper
id={dataItem.id}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center outline outline-surface-secondary',
Boolean($selectedComponent?.includes(dataItem.id)) ? 'active-grid-item' : ''
)}
>
<GridEditorMenu
{#snippet children({ dataItem, overlapped, moveMode, componentDraggedId })}
<ComponentWrapper
id={dataItem.id}
on:expand={() => {
push(history, $app)
$selectedComponent = [dataItem.id]
expandGriditem($app.grid, dataItem.id, $breakpoint)
$app = $app
}}
on:lock={() => {
handleLock(dataItem.id)
}}
on:fillHeight={() => {
handleFillHeight(dataItem.id)
}}
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center outline outline-surface-secondary',
Boolean($selectedComponent?.includes(dataItem.id)) ? 'active-grid-item' : ''
)}
>
<Component
render={true}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
<GridEditorMenu
id={dataItem.id}
on:expand={() => {
push(history, app)
$selectedComponent = [dataItem.id]
expandGriditem(app.grid, dataItem.id, $breakpoint) // app = app
}}
on:lock={() => {
handleLock(dataItem.id)
}}
on:fillHeight={() => {
handleFillHeight(dataItem.id)
}}
on:expand={() => {
push(history, $app)
$selectedComponent = [dataItem.id]
expandGriditem($app.grid, dataItem.id, $breakpoint)
$app = $app
}}
{overlapped}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
>
<Component
render={true}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
on:lock={() => {
handleLock(dataItem.id)
}}
on:fillHeight={() => {
handleFillHeight(dataItem.id)
}}
on:expand={() => {
push(history, app)
$selectedComponent = [dataItem.id]
expandGriditem(app.grid, dataItem.id, $breakpoint) // app = app
}}
{overlapped}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
{/snippet}
</Grid>
</div>
</div>
</div>
{#if $app.hiddenInlineScripts}
{#each $app.hiddenInlineScripts as runnable, index}
{#if app.hiddenInlineScripts}
{#each app.hiddenInlineScripts as runnable, index}
{#if runnable}
<HiddenComponent id={BG_PREFIX + index} {runnable} />
{/if}
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import { getContext, onMount, untrack } from 'svelte'
import type { App, AppEditorContext, AppViewerContext } from '../types'
import { allItems, BG_PREFIX } from '../utils'
import RecomputeAllButton from './RecomputeAllButton.svelte'
@@ -10,13 +10,9 @@
let timeout: NodeJS.Timeout | undefined = undefined
let shouldRefresh = false
let firstLoad = false
let firstLoad = $state(false)
let progressTimer: NodeJS.Timeout | undefined = undefined
$: !firstLoad &&
canInitializeAll($initialized?.initializedComponents, $app) &&
refresh('all initialized')
// $: console.log('canInitializeAll', firstLoad, $initialized?.initializedComponents)
function canInitializeAll(initialized: string[] | undefined, app: App) {
// console.log(
@@ -53,9 +49,6 @@
}
}
$: $recomputeAllContext.componentNumber =
Object.values($runnableComponents).filter((x) => x.autoRefresh).length ?? 0
onMount(() => {
if (appEditorContext) {
appEditorContext.refreshComponents.set(() => refresh('onClick global'))
@@ -112,7 +105,7 @@
onRefresh(!inter, 'setInter ' + source)
}
let refreshing: string[] = []
let refreshing: string[] = $state([])
function refresh(reason: string, excludeId: string | undefined = undefined) {
let isFirstLoad = false
if (!firstLoad && reason == 'all initialized') {
@@ -182,6 +175,19 @@
setInter: (n) => setInter(n, 'all context')
}
})
$effect(() => {
;[$initialized?.initializedComponents]
if (!firstLoad) {
untrack(() => {
canInitializeAll($initialized?.initializedComponents, app) && refresh('all initialized')
})
}
})
$effect(() => {
$recomputeAllContext.componentNumber =
Object.values($runnableComponents).filter((x) => x.autoRefresh).length ?? 0
})
</script>
<RecomputeAllButton
@@ -10,20 +10,11 @@
import EventHandlerItem from './settingsPanel/EventHandlerItem.svelte'
import type { TableAction } from './component'
const { selectedComponent, app, stateId, runnableComponents } =
const { selectedComponent, app, stateId, runnableComponents } = $state(
getContext<AppViewerContext>('AppViewerContext')
)
let firstComponent = $selectedComponent?.[0]
$: $selectedComponent?.[0] != firstComponent && (firstComponent = $selectedComponent?.[0])
$: hiddenInlineScript = $app?.hiddenInlineScripts
?.map((x, i) => ({ script: x, index: i }))
.find(({ script, index }) => $selectedComponent?.includes(BG_PREFIX + index))
$: gridItemWithLocation = findGridItemWithLocation($app, firstComponent)
$: tableActionSettings = findTableActionSettings($app, firstComponent)
$: menuItemsSettings = findMenuItemsSettings($app, firstComponent)
let firstComponent = $state($selectedComponent?.[0])
function findTableActionSettings(app: App, id: string | undefined) {
return allItemsWithLocation(app.grid, app.subgrids)
@@ -107,6 +98,17 @@
}
const dispatch = createEventDispatcher()
$effect(() => {
$selectedComponent?.[0] != firstComponent && (firstComponent = $selectedComponent?.[0])
})
let hiddenInlineScript = $derived(
app?.hiddenInlineScripts
?.map((x, i) => ({ script: x, index: i }))
.find(({ script, index }) => $selectedComponent?.includes(BG_PREFIX + index))
)
let gridItemWithLocation = $derived(findGridItemWithLocation(app, firstComponent))
let tableActionSettings = $derived(findTableActionSettings(app, firstComponent))
let menuItemsSettings = $derived(findMenuItemsSettings(app, firstComponent))
</script>
{#if gridItemWithLocation}
@@ -116,17 +118,17 @@
() => gridItemWithLocation,
(cs) => {
if (gridItemWithLocation?.location.type === 'grid') {
$app.grid[gridItemWithLocation.location.gridItemIndex] = cs.item
app.grid[gridItemWithLocation.location.gridItemIndex] = cs.item
} else if (
gridItemWithLocation?.location.type === 'subgrid' &&
Array.isArray($app.subgrids?.[gridItemWithLocation.location.subgridKey])
Array.isArray(app.subgrids?.[gridItemWithLocation.location.subgridKey])
) {
if (
$app.subgrids[gridItemWithLocation.location.subgridKey][
app.subgrids[gridItemWithLocation.location.subgridKey][
gridItemWithLocation.location.subgridItemIndex
]
) {
$app.subgrids[gridItemWithLocation.location.subgridKey][
app.subgrids[gridItemWithLocation.location.subgridKey][
gridItemWithLocation.location.subgridItemIndex
] = cs.item
}
@@ -149,14 +151,14 @@
if (tableActionSettings.gridItemLocation.type === 'grid') {
const { gridItemIndex } = tableActionSettings.gridItemLocation
const { key, index } = tableActionSettings.location
if ($app.grid[gridItemIndex]?.data?.[key]) {
$app.grid[gridItemIndex].data[key][index] = cs.item.data
if (app.grid[gridItemIndex]?.data?.[key]) {
app.grid[gridItemIndex].data[key][index] = cs.item.data
}
} else if (tableActionSettings.gridItemLocation.type === 'subgrid') {
const { subgridKey, subgridItemIndex } = tableActionSettings.gridItemLocation
const { key, index } = tableActionSettings.location
if ($app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.[key]) {
$app.subgrids[subgridKey][subgridItemIndex].data[key][index] = cs.item.data
if (app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.[key]) {
app.subgrids[subgridKey][subgridItemIndex].data[key][index] = cs.item.data
}
}
}
@@ -165,7 +167,7 @@
duplicateMoveAllowed={false}
onDelete={() => {
if (tableActionSettings) {
const item = findGridItemWithLocation($app, tableActionSettings.parent)
const item = findGridItemWithLocation(app, tableActionSettings.parent)
if (!item) return
const { item: parent, location } = item
if (parent.data.type === 'tablecomponent') {
@@ -174,15 +176,13 @@
)
if (location.type === 'grid') {
const { gridItemIndex } = location
if ($app.grid[gridItemIndex]?.data?.type === 'tablecomponent') {
$app.grid[gridItemIndex].data.actionButtons = newActionButtons
if (app.grid[gridItemIndex]?.data?.type === 'tablecomponent') {
app.grid[gridItemIndex].data.actionButtons = newActionButtons
}
} else if (location.type === 'subgrid') {
const { subgridKey, subgridItemIndex } = location
if (
$app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.type === 'tablecomponent'
) {
$app.subgrids[subgridKey][subgridItemIndex].data.actionButtons = newActionButtons
if (app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.type === 'tablecomponent') {
app.subgrids[subgridKey][subgridItemIndex].data.actionButtons = newActionButtons
}
}
}
@@ -192,13 +192,13 @@
)
if (location.type === 'grid') {
const { gridItemIndex } = location
if (itemHasActions($app.grid[gridItemIndex])) {
$app.grid[gridItemIndex].data.actions = newActions
if (itemHasActions(app.grid[gridItemIndex])) {
app.grid[gridItemIndex].data.actions = newActions
}
} else {
const { subgridKey, subgridItemIndex } = location
if (itemHasActions($app.subgrids?.[subgridKey]?.[subgridItemIndex])) {
$app.subgrids[subgridKey][subgridItemIndex].data.actions = newActions
if (itemHasActions(app.subgrids?.[subgridKey]?.[subgridItemIndex])) {
app.subgrids[subgridKey][subgridItemIndex].data.actions = newActions
}
}
}
@@ -216,13 +216,13 @@
if (menuItemsSettings) {
if (menuItemsSettings.gridItemLocation.type === 'grid') {
const { gridItemIndex } = menuItemsSettings.gridItemLocation
if ($app.grid[gridItemIndex]?.data?.type === 'menucomponent') {
$app.grid[gridItemIndex].data.menuItems[cs.index] = cs.item.data
if (app.grid[gridItemIndex]?.data?.type === 'menucomponent') {
app.grid[gridItemIndex].data.menuItems[cs.index] = cs.item.data
}
} else if (menuItemsSettings.gridItemLocation.type === 'subgrid') {
const { subgridKey, subgridItemIndex } = menuItemsSettings.gridItemLocation
if ($app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.type === 'menucomponent') {
$app.subgrids[subgridKey][subgridItemIndex].data.menuItems[cs.index] = cs.item.data
if (app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.type === 'menucomponent') {
app.subgrids[subgridKey][subgridItemIndex].data.menuItems[cs.index] = cs.item.data
}
}
}
@@ -230,7 +230,7 @@
}
onDelete={() => {
if (menuItemsSettings) {
const item = findGridItemWithLocation($app, menuItemsSettings.parent)
const item = findGridItemWithLocation(app, menuItemsSettings.parent)
if (!item) return
const { item: parent, location } = item
if (parent.data.type === 'menucomponent') {
@@ -239,13 +239,13 @@
)
if (location.type === 'grid') {
const { gridItemIndex } = location
if ($app.grid[gridItemIndex]?.data?.type === 'menucomponent') {
$app.grid[gridItemIndex].data.menuItems = newItems
if (app.grid[gridItemIndex]?.data?.type === 'menucomponent') {
app.grid[gridItemIndex].data.menuItems = newItems
}
} else if (location.type === 'subgrid') {
const { subgridKey, subgridItemIndex } = location
if ($app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.type === 'menucomponent') {
$app.subgrids[subgridKey][subgridItemIndex].data.menuItems = newItems
if (app.subgrids?.[subgridKey]?.[subgridItemIndex]?.data?.type === 'menucomponent') {
app.subgrids[subgridKey][subgridItemIndex].data.menuItems = newItems
}
}
}
@@ -260,7 +260,7 @@
bind:runnable={
() => hiddenInlineScript.script,
(r) => {
$app.hiddenInlineScripts[hiddenInlineScript.index] = r
app.hiddenInlineScripts[hiddenInlineScript.index] = r
}
}
{id}
@@ -276,8 +276,8 @@
bind:inputSpecs={
() => hiddenInlineScript.script.fields,
(is) => {
if ($app.hiddenInlineScripts[hiddenInlineScript.index]) {
$app.hiddenInlineScripts[hiddenInlineScript.index].fields = is
if (app.hiddenInlineScripts[hiddenInlineScript.index]) {
app.hiddenInlineScripts[hiddenInlineScript.index].fields = is
}
}
}
@@ -301,8 +301,8 @@
bind:value={
() => hiddenInlineScript.script.recomputeIds ?? [],
(v) => {
if ($app.hiddenInlineScripts[hiddenInlineScript.index]) {
$app.hiddenInlineScripts[hiddenInlineScript.index].recomputeIds = v
if (app.hiddenInlineScripts[hiddenInlineScript.index]) {
app.hiddenInlineScripts[hiddenInlineScript.index].recomputeIds = v
}
}
}
@@ -1,4 +1,6 @@
<script lang="ts">
import { run } from 'svelte/legacy'
import { push } from '$lib/history'
import { classNames } from '$lib/utils'
import { createEventDispatcher, getContext, onDestroy } from 'svelte'
@@ -21,18 +23,31 @@
import GridViewer from './GridViewer.svelte'
import GridEditorMenu from './GridEditorMenu.svelte'
export let containerHeight: number | undefined = undefined
export let containerWidth: number | undefined = undefined
let classes = ''
interface Props {
containerHeight?: number | undefined
containerWidth?: number | undefined
class?: string
style?: string
noPadding?: boolean
noYPadding?: boolean
subGridId: string
visible?: boolean
id: string
shouldHighlight?: boolean
}
export { classes as class }
export let style = ''
export let noPadding = false
export let noYPadding = false
export let subGridId: string
export let visible: boolean = true
export let id: string
export let shouldHighlight: boolean = true
let {
containerHeight = undefined,
containerWidth = undefined,
class: classes = '',
style = '',
noPadding = false,
noYPadding = false,
subGridId,
visible = true,
id,
shouldHighlight = true
}: Props = $props()
const dispatch = createEventDispatcher()
@@ -50,17 +65,19 @@
const editorContext = getContext<AppEditorContext>('AppEditorContext')
let isActive = false
let isActive = $state(false)
let sber = editorContext?.componentActive?.subscribe((x) => (isActive = x))
let everVisible = visible
let everVisible = $state(visible)
$: visible && !everVisible && (everVisible = true)
run(() => {
visible && !everVisible && (everVisible = true)
})
onDestroy(() => {
sber?.()
})
$: highlight = id === $focusedGrid?.parentComponentId && shouldHighlight
let highlight = $derived(id === $focusedGrid?.parentComponentId && shouldHighlight)
const onpointerdown = (e) => {
dispatch('focus')
@@ -68,21 +85,23 @@
function selectComponent(e: PointerEvent, id: string) {
if (!$connectingInput.opened) {
selectId(e, id, selectedComponent, $app)
selectId(e, id, selectedComponent, app)
}
}
function lock(dataItem: GridItem) {
let fComponent = findGridItem($app, dataItem.id)
let fComponent = findGridItem(app, dataItem.id)
if (fComponent) {
fComponent = toggleFixed(fComponent)
}
$app = $app
// $app = $app
}
let container: HTMLElement | undefined = undefined
let container: HTMLElement | undefined = $state(undefined)
$: maxRow = maxHeight($app.subgrids?.[subGridId] ?? [], containerHeight ?? 0, $breakpoint)
let maxRow = $derived(
maxHeight(app.subgrids?.[subGridId] ?? [], containerHeight ?? 0, $breakpoint)
)
export function moveComponentBetweenSubgrids(
componentId: string,
@@ -91,27 +110,27 @@
position?: { x: number; y: number }
) {
// Find the component in the source subgrid
const component = findGridItem($app, componentId)
const component = findGridItem(app, componentId)
if (!component) {
return
}
let parentGrid = findGridItemParentGrid($app, component.id)
let parentGrid = findGridItemParentGrid(app, component.id)
if (parentGrid) {
$app.subgrids &&
($app.subgrids[parentGrid] = $app.subgrids[parentGrid].filter(
app.subgrids &&
(app.subgrids[parentGrid] = app.subgrids[parentGrid].filter(
(item) => item.id !== component?.id
))
} else {
$app.grid = $app.grid.filter((item) => item.id !== component?.id)
app.grid = app.grid.filter((item) => item.id !== component?.id)
}
const gridItem = component
insertNewGridItem(
$app,
app,
(id) => ({ ...gridItem.data, id }),
{ parentComponentId: parentComponentId, subGridIndex: subGridIndex },
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
@@ -123,8 +142,8 @@
true
)
// Update the app state
$app = { ...$app }
// // Update the app state
// $app = { ...$app }
if (parentGrid) {
$focusedGrid = {
@@ -140,27 +159,27 @@
export function moveToRoot(componentId: string, position?: { x: number; y: number }) {
// Find the component in the source subgrid
const component = findGridItem($app, componentId)
const component = findGridItem(app, componentId)
if (!component) {
return
}
let parentGrid = findGridItemParentGrid($app, component.id)
let parentGrid = findGridItemParentGrid(app, component.id)
if (parentGrid) {
$app.subgrids &&
($app.subgrids[parentGrid] = $app.subgrids[parentGrid].filter(
app.subgrids &&
(app.subgrids[parentGrid] = app.subgrids[parentGrid].filter(
(item) => item.id !== component?.id
))
} else {
$app.grid = $app.grid.filter((item) => item.id !== component?.id)
app.grid = app.grid.filter((item) => item.id !== component?.id)
}
const gridItem = component
insertNewGridItem(
$app,
app,
(id) => ({ ...gridItem.data, id }),
undefined,
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
@@ -173,17 +192,17 @@
)
// Update the app state
$app = { ...$app }
// $app = { ...$app }
}
</script>
{#if everVisible || $app.eagerRendering}
{#if everVisible || app.eagerRendering}
<div
class="translate-x-0 translate-y-0 w-full subgrid {visible
? 'visible'
: 'invisible h-0 overflow-hidden'}"
bind:this={container}
on:pointerdown={onpointerdown}
{onpointerdown}
>
<div
class={twMerge(
@@ -201,23 +220,19 @@
isActive && !$selectedComponent?.includes(id)
? 'outline-orange-600'
: 'outline-gray-400 dark:outline-gray-600'
}`
}`
: ''}
>
<Grid
allIdsInPath={$allIdsInPath}
items={$app.subgrids?.[subGridId] ?? []}
items={app.subgrids?.[subGridId] ?? []}
on:redraw={(e) => {
push(editorContext?.history, $app)
if ($app.subgrids) {
$app.subgrids[subGridId] = e.detail
push(editorContext?.history, app)
if (app.subgrids) {
app.subgrids[subGridId] = e.detail
}
}}
selectedIds={$selectedComponent}
let:dataItem
let:overlapped
let:moveMode
let:componentDraggedId
scroller={container}
parentWidth={$parentWidth - 17}
{containerWidth}
@@ -227,7 +242,7 @@
if (!overlapped) {
moveToRoot(id, { x, y })
} else {
const overlappedComponent = findGridItem($app, overlapped)
const overlappedComponent = findGridItem(app, overlapped)
if (overlappedComponent && !isContainer(overlappedComponent.data.type)) {
return
@@ -250,87 +265,93 @@
}}
disableMove={!!$connectingInput.opened}
>
<ComponentWrapper
id={dataItem.id}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center',
$selectedComponent?.includes(dataItem.id) ? 'active-grid-item' : '',
'top-0'
)}
>
<GridEditorMenu id={dataItem.id}>
<Component
{overlapped}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
render={visible}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
on:lock={() => lock(dataItem)}
on:expand={() => {
const parentGridItem = findGridItem($app, id)
{#snippet children({ dataItem, overlapped, moveMode, componentDraggedId })}
<ComponentWrapper
id={dataItem.id}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center',
$selectedComponent?.includes(dataItem.id) ? 'active-grid-item' : '',
'top-0'
)}
>
<GridEditorMenu id={dataItem.id}>
<Component
{overlapped}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
render={visible}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
on:lock={() => lock(dataItem)}
on:expand={() => {
const parentGridItem = findGridItem(app, id)
if (!parentGridItem) {
return
}
if (!parentGridItem) {
return
}
$selectedComponent = [dataItem.id]
push(editorContext?.history, $app)
$selectedComponent = [dataItem.id]
push(editorContext?.history, app)
expandGriditem(
$app.subgrids?.[subGridId] ?? [],
dataItem.id,
$breakpoint,
parentGridItem
)
$app = $app
}}
on:fillHeight={() => {
const gridItem = findGridItem($app, dataItem.id)
const b = $breakpoint === 'sm' ? 3 : 12
expandGriditem(
app.subgrids?.[subGridId] ?? [],
dataItem.id,
$breakpoint,
parentGridItem
)
// $app = $app
}}
on:fillHeight={() => {
const gridItem = findGridItem(app, dataItem.id)
const b = $breakpoint === 'sm' ? 3 : 12
if (gridItem?.[b]) {
gridItem[b].fullHeight = !gridItem[b].fullHeight
}
$app = $app
}}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
if (gridItem?.[b]) {
gridItem[b].fullHeight = !gridItem[b].fullHeight
}
// $app = $app
}}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
{/snippet}
</Grid>
</div>
{:else}
<GridViewer
allIdsInPath={$allIdsInPath}
items={$app.subgrids?.[subGridId] ?? []}
let:dataItem
items={app.subgrids?.[subGridId] ?? []}
breakpoint={$breakpoint}
parentWidth={$parentWidth - 17}
{containerWidth}
{maxRow}
>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:pointerdown|stopPropagation={(e) => selectComponent(e, dataItem.id)}
class={classNames('h-full w-full center-center', 'top-0')}
>
<Component
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
render={visible}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
/>
</div>
{#snippet children({ dataItem })}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
onpointerdown={(e) => {
e.stopPropagation()
selectComponent(e, dataItem.id)
}}
class={classNames('h-full w-full center-center', 'top-0')}
>
<Component
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
render={visible}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
/>
</div>
{/snippet}
</GridViewer>
{/if}
</div>
</div>
{:else if $app.lazyInitRequire == undefined}
{#each $app?.subgrids?.[subGridId] ?? [] as item}
{:else if app.lazyInitRequire == undefined}
{#each app?.subgrids?.[subGridId] ?? [] as item}
<Component selected={false} fullHeight={false} render={false} component={item.data} />
{/each}
{/if}
@@ -1,14 +1,13 @@
import { parseOutputs } from '$lib/infer'
import { deepEqual } from 'fast-equals'
import type { EvalV2AppInput, TemplateV2Input } from '../inputType'
import type { Writable } from 'svelte/store'
import type { App } from '../types'
export async function inferDeps(
code: string,
worldOutputs: Record<string, any>,
componentInput: EvalV2AppInput | TemplateV2Input,
app: Writable<App>
app: App
) {
const outputs = await parseOutputs(code, true)
if (outputs && componentInput) {
@@ -28,7 +27,7 @@ export async function inferDeps(
}))
if (!deepEqual(noutputs, componentInput.connections)) {
componentInput.connections = noutputs
app.update((old) => old)
// app.update((old) => old)
}
}
}
@@ -24,27 +24,27 @@
const ITEM_TYPE = 'wm-grid-items'
function getSortedGridItemsOfChildren(): GridItem[] {
if (!$focusedGrid) {
return $app.grid
return app.grid
}
if (!$app.subgrids) {
if (!app.subgrids) {
return []
}
return $app.subgrids[`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`] ?? []
return app.subgrids[`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`] ?? []
}
function getGridItems(): GridItem[] {
if ($app.grid.find((item) => item.id === $selectedComponent?.[0])) {
return $app.grid
if (app.grid.find((item) => item.id === $selectedComponent?.[0])) {
return app.grid
}
if (!$app.subgrids) {
if (!app.subgrids) {
return []
}
return (
Object.values($app.subgrids ?? {}).find((grid) =>
Object.values(app.subgrids ?? {}).find((grid) =>
grid.find((item) => item.id === $selectedComponent?.[0])
) ?? []
)
@@ -105,9 +105,9 @@
if (!$focusedGrid) {
$selectedComponent = [getSortedGridItemsOfChildren()[0]?.id]
event.preventDefault()
} else if ($app.subgrids) {
} else if (app.subgrids) {
const index = $focusedGrid?.subGridIndex ?? 0
const subgrid = $app.subgrids[`${$selectedComponent}-${index}`]
const subgrid = app.subgrids[`${$selectedComponent}-${index}`]
if (!subgrid || subgrid.length === 0) {
return
@@ -128,7 +128,7 @@
export function handleArrowUp(event: KeyboardEvent) {
if (!$selectedComponent) return
let parentId = findGridItemParentGrid($app, $selectedComponent?.[0])?.split('-')[0]
let parentId = findGridItemParentGrid(app, $selectedComponent?.[0])?.split('-')[0]
if (parentId) {
$selectedComponent = [parentId]
@@ -144,7 +144,7 @@
}
tempGridItems = undefined
const copiedGridItems = $selectedComponent
.map((x) => findGridItem($app, x))
.map((x) => findGridItem(app, x))
.filter((x) => x != undefined) as GridItem[]
copyGridItemsToClipboard(copiedGridItems, 'copy')
@@ -156,9 +156,9 @@
) {
let allSubgrids = {}
for (let item of items) {
let subgrids = getAllSubgridsAndComponentIds($app, item.data)[0]
let subgrids = getAllSubgridsAndComponentIds(app, item.data)[0]
for (let key of subgrids) {
allSubgrids[key] = $app.subgrids?.[key]
allSubgrids[key] = app.subgrids?.[key]
}
}
let success = await copyToClipboard(
@@ -179,10 +179,10 @@
return
}
$movingcomponents = JSON.parse(JSON.stringify($selectedComponent))
push(history, $app)
push(history, app)
let gridItems = $selectedComponent
.map((x) => findGridItem($app, x))
.map((x) => findGridItem(app, x))
.filter((x) => x != undefined) as GridItem[]
copyGridItemsToClipboard(gridItems, 'cut')
@@ -204,10 +204,10 @@
}
event.preventDefault()
push(history, $app)
push(history, app)
$movingcomponents = undefined
let copiedGridItems: GridItem[] | undefined = undefined
let subgrids = $app.subgrids ?? {}
let subgrids = app.subgrids ?? {}
const txt = event?.clipboardData?.getData('text')
if (txt) {
try {
@@ -225,26 +225,26 @@
for (let tempGridItem of tempGridItems) {
if (
$focusedGrid &&
getAllSubgridsAndComponentIds($app, tempGridItem.data)[0].includes(
getAllSubgridsAndComponentIds(app, tempGridItem.data)[0].includes(
`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`
)
) {
sendUserToast('Cannot paste a component into itself', true)
return
}
let parentGrid = findGridItemParentGrid($app, tempGridItem.id)
let parentGrid = findGridItemParentGrid(app, tempGridItem.id)
if (parentGrid) {
$app.subgrids &&
($app.subgrids[parentGrid] = $app.subgrids[parentGrid].filter(
app.subgrids &&
(app.subgrids[parentGrid] = app.subgrids[parentGrid].filter(
(item) => item.id !== tempGridItem?.id
))
} else {
$app.grid = $app.grid.filter((item) => item.id !== tempGridItem?.id)
app.grid = app.grid.filter((item) => item.id !== tempGridItem?.id)
}
const gridItem = tempGridItem
insertNewGridItem(
$app,
app,
(id) => ({ ...gridItem.data, id }),
$focusedGrid,
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
@@ -258,12 +258,12 @@
} else if (copiedGridItems) {
let nitems: string[] = []
for (let copiedGridItem of copiedGridItems) {
let newItem = copyComponent($app, copiedGridItem, $focusedGrid, subgrids, [])
let newItem = copyComponent(app, copiedGridItem, $focusedGrid, subgrids, [])
newItem && nitems.push(newItem)
}
$selectedComponent = nitems.map((x) => x)
}
$app = $app
// app = app
}
</script>
@@ -75,15 +75,30 @@
import type { AppComponent } from './components'
import { Button } from '$lib/components/common'
export let component: AppComponent
export let render: boolean
export let componentContainerHeight: number
export let errorHandledByComponent: boolean
export let inlineEditorOpened: boolean
export let initializing: boolean | undefined = undefined
interface Props {
component: AppComponent
render: boolean
componentContainerHeight: number
errorHandledByComponent: boolean
inlineEditorOpened: boolean
initializing?: boolean | undefined
}
let {
component,
render,
componentContainerHeight,
errorHandledByComponent = $bindable(),
inlineEditorOpened = $bindable(),
initializing = $bindable(undefined)
}: Props = $props()
</script>
<svelte:boundary>
<svelte:boundary
onerror={(e) => {
console.log(`error in rendering component ${component.id}`, e)
}}
>
{#if component.type === 'displaycomponent'}
<AppDisplayComponent
id={component.id}
@@ -797,7 +812,9 @@
>{error}</pre
>
<div class="flex mt-4">
<Button wrapperClasses="border rounded !border-gray-400" color="dark" on:click={reset}>reset</Button>
<Button wrapperClasses="border rounded !border-gray-400" color="dark" on:click={reset}
>reset</Button
>
</div>
</div>
{/snippet}
@@ -1,8 +1,10 @@
<script lang="ts" context="module">
<script lang="ts" module>
let outTimeout: NodeJS.Timeout | undefined = undefined
</script>
<script lang="ts">
import { run, stopPropagation } from 'svelte/legacy'
import { getContext, onMount } from 'svelte'
import { twMerge } from 'tailwind-merge'
import type { AppEditorContext, AppViewerContext } from '../../types'
@@ -13,16 +15,29 @@
import { findGridItemParentGrid, isContainer } from '../appUtils'
import ComponentInner from './ComponentInner.svelte'
export let component: AppComponent
export let selected: boolean
export let locked: boolean = false
export let fullHeight: boolean
export let overlapped: string | undefined = undefined
export let moveMode: string | undefined = undefined
export let componentDraggedId: string | undefined = undefined
export let render: boolean = false
interface Props {
component: AppComponent
selected: boolean
locked?: boolean
fullHeight: boolean
overlapped?: string | undefined
moveMode?: string | undefined
componentDraggedId?: string | undefined
render?: boolean
}
let initializing: boolean | undefined
let {
component,
selected,
locked = false,
fullHeight,
overlapped = undefined,
moveMode = undefined,
componentDraggedId = undefined,
render = false
}: Props = $props()
let initializing: boolean | undefined = $state()
const { mode, app, hoverStore, connectingInput } =
getContext<AppViewerContext>('AppViewerContext')
@@ -31,15 +46,16 @@
const componentActive = editorContext?.componentActive
const movingcomponents = editorContext?.movingcomponents
$: ismoving =
let ismoving = $derived(
movingcomponents != undefined && $mode == 'dnd' && $movingcomponents?.includes(component.id)
)
let errorHandledByComponent: boolean = false
let componentContainerHeight: number = 0
let componentContainerWidth: number = 0
let errorHandledByComponent: boolean = $state(false)
let componentContainerHeight: number = $state(0)
let componentContainerWidth: number = $state(0)
let inlineEditorOpened: boolean = false
let showSkeleton = false
let inlineEditorOpened: boolean = $state(false)
let showSkeleton = $state(false)
onMount(() => {
setTimeout(() => {
@@ -63,19 +79,19 @@
}
function componentDraggedIsNotChild(componentDraggedId: string, componentId: string) {
let parentGrid = findGridItemParentGrid($app, componentDraggedId)
let parentGrid = findGridItemParentGrid(app, componentDraggedId)
return !parentGrid?.startsWith(`${componentId}-`)
}
function areOnTheSameSubgrid(componentDraggedId: string, componentId: string) {
return (
findGridItemParentGrid($app, componentDraggedId) === findGridItemParentGrid($app, componentId)
findGridItemParentGrid(app, componentDraggedId) === findGridItemParentGrid(app, componentId)
)
}
let cachedComponentDraggedIsNotChild: boolean | undefined
let cachedAreOnTheSameSubgrid: boolean | undefined
let cachedComponentDraggedIsNotChild: boolean | undefined = $state()
let cachedAreOnTheSameSubgrid: boolean | undefined = $state()
function updateCache(componentDraggedId: string | undefined) {
if (componentDraggedId) {
@@ -90,19 +106,21 @@
}
}
$: updateCache(componentDraggedId)
run(() => {
updateCache(componentDraggedId)
})
</script>
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
on:mouseover|stopPropagation={() => {
onmouseover={stopPropagation(() => {
outTimeout && clearTimeout(outTimeout)
if (component.id !== $hoverStore) {
$hoverStore = component.id
}
}}
on:mouseout|stopPropagation={mouseOut}
})}
onmouseout={stopPropagation(mouseOut)}
class={twMerge(
'h-full flex flex-col w-full component relative',
initializing ? 'overflow-hidden h-0' : ''
@@ -162,7 +180,7 @@
<div class="absolute -top-8 w-40">
<button
class="border p-0.5 text-xs"
on:click={() => {
onclick={() => {
$movingcomponents = undefined
}}
>
@@ -183,11 +201,11 @@
selected && $mode !== 'preview' ? 'outline outline-blue-600' : '',
$mode != 'preview' ? 'cursor-pointer' : '',
'relative z-auto',
$app.css?.['app']?.['component']?.class,
app.css?.['app']?.['component']?.class,
'wm-app-component',
ismoving ? 'animate-pulse' : ''
)}
style={$app.css?.['app']?.['component']?.style}
style={app.css?.['app']?.['component']?.style}
bind:clientHeight={componentContainerHeight}
bind:clientWidth={componentContainerWidth}
>
@@ -202,19 +220,19 @@
</div>
</div>
{#if initializing && render && showSkeleton}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
on:mouseover|stopPropagation={() => {
onmouseover={stopPropagation(() => {
if (component.id !== $hoverStore) {
$hoverStore = component.id
}
}}
on:mouseout|stopPropagation={() => {
})}
onmouseout={stopPropagation(() => {
if ($hoverStore !== undefined) {
$hoverStore = undefined
}
}}
})}
class="absolute inset-0 center-center flex-col border animate-skeleton dark:bg-frost-900/50 [animation-delay:1000ms]"
></div>
{/if}
@@ -13,7 +13,7 @@
function selectComponent(e: PointerEvent, id: string) {
if (!$connectingInput.opened) {
selectId(e, id, selectedComponent, $app)
selectId(e, id, selectedComponent, app)
}
}
@@ -29,7 +29,7 @@
if (!$connectingInput.opened) {
selectComponent(e, id)
} else {
const allIdsInPath = dfs($app.grid, id, $app.subgrids ?? {}) ?? []
const allIdsInPath = dfs(app.grid, id, app.subgrids ?? {}) ?? []
allIdsInPath.forEach((id) => {
$manuallyOpened[id] = true
@@ -1,6 +1,6 @@
<script lang="ts">
import type { AppEditorContext, AppViewerContext } from '../../types'
import { getContext, tick } from 'svelte'
import { getContext, tick, untrack } from 'svelte'
import {
components as componentsRecord,
presets as presetsRecord,
@@ -34,12 +34,12 @@
let groups: Array<{
name: string
path: string
}> = []
}> = $state([])
let customComponents: Array<{
name: string
path: string
}> = []
}> = $state([])
async function fetchGroups() {
groups = await listGroups($workspaceStore ?? '')
@@ -53,16 +53,16 @@
}
function addComponent(appComponentType: TypedComponent['type']): string {
push(history, $app)
push(history, app)
const id = insertNewGridItem(
$app,
app,
appComponentFromType(appComponentType) as (id: string) => AppComponent,
$focusedGrid
)
$selectedComponent = [id]
$app = $app
// app = app
return id
}
@@ -72,21 +72,21 @@
if (!res) return
push(history, $app)
push(history, app)
const id = copyComponent($app, res.value.item, $focusedGrid, res.value.subgrids, [])
const id = copyComponent(app, res.value.item, $focusedGrid, res.value.subgrids, [])
if (id) {
$selectedComponent = [id]
$app = $app
// app = app
}
}
async function addNewGroup() {
push(history, $app)
push(history, app)
const id = insertNewGridItem(
$app,
app,
appComponentFromType('containercomponent', undefined, { groupFields: {} }) as (
id: string
) => AppComponent,
@@ -95,7 +95,7 @@
if (id) {
$selectedComponent = [id]
$app = $app
// app = app
}
}
@@ -114,10 +114,10 @@
if (!res) return
push(history, $app)
push(history, app)
const id = insertNewGridItem(
$app,
app,
appComponentFromType('customcomponent', undefined, {
customComponent: {
name: cc.name.replace(/-/g, '_').replace(/\s/g, '_'),
@@ -131,17 +131,17 @@
if (id) {
$selectedComponent = [id]
$app = $app
// app = app
}
}
function addPresetComponent(appComponentType: string): void {
const preset = presetsRecord[appComponentType]
push(history, $app)
push(history, app)
const id = insertNewGridItem(
$app,
app,
appComponentFromType(preset.targetComponent, preset.configuration, undefined) as (
id: string
) => AppComponent,
@@ -158,36 +158,40 @@
$selectedComponent = [id]
if (appComponentType === 'topbarcomponent') {
setUpTopBarComponentContent(id, $app)
setUpTopBarComponentContent(id, app)
}
$app = $app
// app = app
}
let search = ''
let search = $state('')
$: componentsFiltered = COMPONENT_SETS.map((set) => ({
...set,
components: set.components?.filter((component) => {
const name = componentsRecord[component].name.toLowerCase()
return name.includes(search.toLowerCase().trim())
}),
presets: set.presets?.filter((preset) => {
const presetName = presetsRecord[preset].name.toLowerCase()
return presetName.includes(search.toLowerCase().trim())
})
}))
let componentsFiltered = $derived(
COMPONENT_SETS.map((set) => ({
...set,
components: set.components?.filter((component) => {
const name = componentsRecord[component].name.toLowerCase()
return name.includes(search.toLowerCase().trim())
}),
presets: set.presets?.filter((preset) => {
const presetName = presetsRecord[preset].name.toLowerCase()
return presetName.includes(search.toLowerCase().trim())
})
}))
)
$: {
$effect(() => {
if ($workspaceStore) {
fetchGroups()
fetchCustomComponents()
untrack(() => {
fetchGroups()
fetchCustomComponents()
})
}
}
})
let dndTimeout: NodeJS.Timeout | undefined = undefined
let dndTimeout: NodeJS.Timeout | undefined = $state(undefined)
let ccDrawer: Drawer
let ccDrawer: Drawer | undefined = $state()
</script>
<Drawer bind:this={ccDrawer}>
@@ -217,22 +221,25 @@
<ListItem title={`${title}`} subtitle={`(${components.length})`}>
<div class="flex flex-wrap gap-3 py-2">
{#each components as item (item)}
{@const SvelteComponent = componentsRecord[item].icon}
<div class="w-[64px] relative">
{#if DEPRECATED_COMPONENTS[item]}
<div
class="absolute -top-2 -right-2 bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-100 rounded-md py-0.5 px-1 flex flex-row gap-1 items-center"
>
<Popover>
<div slot="text">
{DEPRECATED_COMPONENTS[item]}
</div>
{#snippet text()}
<div>
{DEPRECATED_COMPONENTS[item]}
</div>
{/snippet}
<div class="font-normal text-2xs"> Deprecated </div>
</Popover>
</div>
{/if}
<button
id={item}
on:pointerdown={async (e) => {
onpointerdown={async (e) => {
const id = addComponent(item)
dndTimeout && clearTimeout(dndTimeout)
dndTimeout = setTimeout(async () => {
@@ -248,7 +255,7 @@
class="cursor-move transition-all border w-[64px] shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-surface rounded-md hover:bg-blue-50 dark:hover:bg-blue-900 duration-200 hover:border-blue-500"
>
<svelte:component this={componentsRecord[item].icon} class="text-primary" />
<SvelteComponent class="text-primary" />
</button>
<div class="text-xs text-center flex-wrap text-secondary mt-1">
{componentsRecord[item].name}
@@ -257,17 +264,15 @@
{/each}
{#if presets}
{#each presets as presetItem (presetItem)}
{@const SvelteComponent_1 = presetsRecord[presetItem].icon}
<div class="w-[64px]">
<button
on:click={() => addPresetComponent(presetItem)}
onclick={() => addPresetComponent(presetItem)}
title={presetsRecord[presetItem].name}
class="transition-all border w-[64px] shadow-sm h-16 p-2 flex flex-col gap-2 items-center
justify-center bg-surface rounded-md hover:bg-blue-50 dark:hover:bg-blue-900 duration-200 hover:border-blue-500"
>
<svelte:component
this={presetsRecord[presetItem].icon}
class="text-secondary"
/>
<SvelteComponent_1 class="text-secondary" />
</button>
<div class="text-xs text-center flex-wrap text-secondary mt-1">
{presetsRecord[presetItem].name}
@@ -286,7 +291,7 @@
{#each groups as group (group.path)}
<div class="w-[64px]">
<button
on:click={() => {
onclick={() => {
addGroup(group)
}}
title={group.name}
@@ -303,7 +308,7 @@
{/if}
<div class="w-[64px]">
<button
on:click={() => {
onclick={() => {
addNewGroup()
}}
title=""
@@ -322,7 +327,7 @@
{#each customComponents as cc (cc.path)}
<div class="w-[64px]">
<button
on:click={() => {
onclick={() => {
addCustomComponent(cc)
}}
title={cc.name}
@@ -339,11 +344,11 @@
{/if}
<div class="w-[64px]">
<button
on:click={() => {
onclick={() => {
if (!$enterpriseLicense) {
sendUserToast('Custom components are only available on the EE', true)
} else {
ccDrawer.openDrawer()
ccDrawer?.openDrawer()
}
}}
title=""
@@ -91,9 +91,9 @@
title={name}
prefix={TITLE_PREFIX}
on:open={(e) => {
if ($app.css != undefined) {
if (type && e.detail && $app.css[type] == undefined) {
$app.css[type] = Object.fromEntries((ids ?? []).map(({ id }) => [id, {}]))
if (app.css != undefined) {
if (type && e.detail && app.css[type] == undefined) {
app.css[type] = Object.fromEntries((ids ?? []).map(({ id }) => [id, {}]))
}
}
}}
@@ -16,13 +16,13 @@
const { app } = getContext<AppViewerContext>('AppViewerContext')
let cssEditor: SimpleEditor | undefined = undefined
let alertHeight: number | undefined = undefined
let themeViewer: any = undefined
let selectedTab: 'css' | 'theme' = 'css'
let cssEditor: SimpleEditor | undefined = $state(undefined)
let alertHeight: number | undefined = $state(undefined)
let themeViewer: any = $state(undefined)
let selectedTab: 'css' | 'theme' = $state('css')
function insertSelector(selector: string) {
if ($app?.theme?.type === 'path') {
if (app?.theme?.type === 'path') {
sendUserToast(
'You cannot edit the theme because it is a path theme. Fork the theme to edit it.',
true
@@ -31,8 +31,7 @@
}
const code = cssEditor?.getCode()
cssEditor?.setCode(code + '\n' + selector)
$app = $app
cssEditor?.setCode(code + '\n' + selector) // $app = $app
}
</script>
@@ -71,25 +70,25 @@
</div>
{/if}
<div style="height: calc(100% - {alertHeight || 0}px);">
{#if $app.theme?.type === 'inlined'}
{#if app.theme?.type === 'inlined'}
<SimpleEditor
class="h-full"
lang="css"
bind:code={$app.theme.css}
bind:code={app.theme.css}
fixedOverflowWidgets={true}
small
automaticLayout
bind:this={cssEditor}
/>
{:else}
<ThemeCodePreview theme={$app.theme}>
<ThemeCodePreview theme={app.theme}>
<div class="p-2 w-min">
<Button
size="xs"
color="dark"
on:click={async () => {
const theme = await resolveTheme($app.theme, $workspaceStore)
$app.theme = {
const theme = await resolveTheme(app.theme, $workspaceStore)
app.theme = {
type: 'inlined',
css: theme
}
@@ -32,9 +32,9 @@
function getSubgrids(item: GridItem) {
let allSubgrids = {}
let subgrids = getAllSubgridsAndComponentIds($app, item.data)[0]
let subgrids = getAllSubgridsAndComponentIds(app, item.data)[0]
for (let key of subgrids) {
allSubgrids[key] = $app.subgrids?.[key]
allSubgrids[key] = app.subgrids?.[key]
}
return allSubgrids
}
@@ -116,17 +116,17 @@
function collectStyles() {
const styles: string[] = []
// Getting global app styles
Object.values($app.css || {}).forEach((element) => {
Object.values(app.css || {}).forEach((element) => {
Object.values(element).filter(({ style }) => style && styles.push(style))
})
// Getting styles from individual components
$app.grid.map((component) => {
app.grid.map((component) => {
Object.values(component.data.customCss || {}).forEach(({ style }) => {
style && styles.push(style)
})
})
// Getting style from subgrids
Object.values($app.subgrids || {}).forEach((grid) => {
Object.values(app.subgrids || {}).forEach((grid) => {
grid.map((component) => {
Object.values(component.data.customCss || {}).forEach(({ style }) => {
style && styles.push(style)
@@ -193,7 +193,7 @@
mounted && (!value || value) && untrack(() => parseStyle())
})
$effect(() => {
$app && untrack(() => setTopColors())
app && untrack(() => setTopColors())
})
</script>
@@ -18,15 +18,15 @@
const { previewTheme, app } = getContext<AppViewerContext>('AppViewerContext')
let cssString: string | undefined = $app?.theme?.type === 'inlined' ? $app.theme.css : undefined
$: type = $app?.theme?.type
let cssString: string | undefined = app?.theme?.type === 'inlined' ? app.theme.css : undefined
let type = $derived(app?.theme?.type)
let themes: Array<{
name: string
path: string
}> = []
}> = $state([])
let loading: boolean = false
let loading: boolean = $state(false)
async function getThemes() {
loading = true
@@ -56,14 +56,14 @@
sendUserToast('Theme created:' + message)
$app.theme = {
app.theme = {
type: 'path',
path: theme.path
}
}
let nameField: string = ''
let previewThemePath: string | undefined = undefined
let nameField: string = $state('')
let previewThemePath: string | undefined = $state(undefined)
onMount(() => {
getThemes()
@@ -16,17 +16,20 @@
import ThemeDrawer from './ThemeDrawer.svelte'
import Dropdown from '$lib/components/DropdownV2.svelte'
export let previewThemePath: string | undefined = undefined
export let row: {
name: string
path: string
interface Props {
previewThemePath?: string | undefined
row: {
name: string
path: string
}
}
const { previewTheme, app } = getContext<AppViewerContext>('AppViewerContext')
let { previewThemePath = $bindable(undefined), row }: Props = $props()
let cssString: string | undefined = $app?.theme?.type === 'inlined' ? $app.theme.css : undefined
$: type = $app?.theme?.type
const { previewTheme, app } = $state(getContext<AppViewerContext>('AppViewerContext'))
let cssString: string | undefined = app?.theme?.type === 'inlined' ? app.theme.css : undefined
let type = $derived(app?.theme?.type)
const dispatch = createEventDispatcher()
@@ -41,7 +44,7 @@
}
})
$app.theme = {
app.theme = {
type: 'path',
path: row.path
}
@@ -71,7 +74,7 @@
})
stopPreview()
$app.theme = {
app.theme = {
type: 'path',
path: DEFAULT_THEME
}
@@ -111,7 +114,7 @@
const resolvedTheme = await resolveTheme(theme, $workspaceStore)
$app.theme = {
app.theme = {
type: 'inlined',
css: resolvedTheme
}
@@ -126,7 +129,7 @@
function apply() {
stopPreview()
$app.theme = {
app.theme = {
type: 'path',
path: row.path ?? ''
}
@@ -162,7 +165,7 @@
}
]
}
let themeDrawer: ThemeDrawer
let themeDrawer: ThemeDrawer | undefined = $state(undefined)
</script>
<tr class={twMerge(previewThemePath === row.path ? 'bg-blue-200' : '', 'transition-all')}>
@@ -179,7 +182,7 @@
<Badge color="blue" small>Default</Badge>
{/if}
{#if $app?.theme?.type === 'path' && $app.theme.path === row.path}
{#if app?.theme?.type === 'path' && app.theme.path === row.path}
<Badge color="green" small>Active</Badge>
{/if}
@@ -193,7 +196,7 @@
Update
</Button>
{/if}
{#if $app?.theme?.type !== 'path' || $app.theme.path !== row.path}
{#if app?.theme?.type !== 'path' || app.theme.path !== row.path}
<Button color="light" size="xs" on:click={preview} startIcon={{ icon: Eye }}>
Preview
</Button>
@@ -11,18 +11,29 @@
// @ts-ignore
import MenuItemsOutput from './components/MenuItemsOutput.svelte'
export let gridItem: GridItem
export let first: boolean = false
export let nested: boolean = false
export let expanded: boolean = false
export let renderRec: boolean = true
interface Props {
gridItem: GridItem
first?: boolean
nested?: boolean
expanded?: boolean
renderRec?: boolean
}
let {
gridItem,
first = false,
nested = false,
expanded = false,
renderRec = true
}: Props = $props()
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
const name = getComponentNameById(gridItem.id)
$: nameOverrides =
let nameOverrides = $derived(
gridItem?.data?.type === 'decisiontreecomponent'
? gridItem.data.nodes.map((n, i) => `${n.label} (Tab index ${i})`)
: undefined
)
function getComponentNameById(componentId: string) {
if (gridItem?.data?.type) {
@@ -36,27 +47,30 @@
}
}
$: subGrids = Array.from({ length: gridItem.data?.numberOfSubgrids ?? 0 }).map(
(_, i) => `${gridItem.id}-${i}`
let subGrids = $derived(
Array.from({ length: gridItem.data?.numberOfSubgrids ?? 0 }).map(
(_, i) => `${gridItem.id}-${i}`
)
)
</script>
<OutputHeader
render={renderRec}
let:render
id={gridItem.id}
name={getComponentNameById(gridItem.id)}
{first}
{nested}
>
<ComponentOutputViewer
{render}
componentId={gridItem.id}
on:select={({ detail }) => {
connectOutput(connectingInput, gridItem?.data?.type, gridItem.data.id, detail)
}}
/>
<SubGridOutput {render} {name} {nameOverrides} {expanded} {subGrids} parentId={gridItem.id} />
<TableActionsOutput {render} {gridItem} />
<MenuItemsOutput {render} {gridItem} />
{#snippet children({ render })}
<ComponentOutputViewer
{render}
componentId={gridItem.id}
on:select={({ detail }) => {
connectOutput(connectingInput, gridItem?.data?.type, gridItem.data.id, detail)
}}
/>
<SubGridOutput {render} {name} {nameOverrides} {expanded} {subGrids} parentId={gridItem.id} />
<TableActionsOutput {render} {gridItem} />
<MenuItemsOutput {render} {gridItem} />
{/snippet}
</OutputHeader>
@@ -19,11 +19,11 @@
const dispatch = createEventDispatcher()
let hasState: boolean = false
let hasState: boolean = $state(false)
</script>
<PanelSection noPadding titlePadding="px-2 pt-2" title="Outputs">
<svelte:fragment slot="action">
{#snippet action()}
<div class="p-0.5">
<HideButton
on:click={() => {
@@ -33,7 +33,7 @@
/>
<DocLink docLink="https://www.windmill.dev/docs/apps/outputs" />
</div>
</svelte:fragment>
{/snippet}
<AnimatedButton
animate={$connectingInput.opened}
baseRadius="0px"
@@ -57,45 +57,41 @@
<div>
<span class="text-xs font-semibold text-secondary p-2">State & Context</span>
<OutputHeader
let:render
selectable={false}
id={'ctx'}
name={'App Context'}
first
color="blue"
>
<ComponentOutputViewer
{render}
componentId={'ctx'}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, 'ctx', detail)
}}
/>
<OutputHeader selectable={false} id={'ctx'} name={'App Context'} first color="blue">
{#snippet children({ render })}
<ComponentOutputViewer
{render}
componentId={'ctx'}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, 'ctx', detail)
}}
/>
{/snippet}
</OutputHeader>
<OutputHeader
let:render
selectable={false}
id={'state'}
name={'State'}
color="blue"
disabled={!hasState}
>
<ComponentOutputViewer
{render}
bind:hasContent={hasState}
componentId={'state'}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, 'state', detail)
}}
/>
{#snippet children({ render })}
<ComponentOutputViewer
{render}
bind:hasContent={hasState}
componentId={'state'}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, 'state', detail)
}}
/>
{/snippet}
</OutputHeader>
</div>
<div>
<span class="text-xs font-semibold text-secondary p-2">Components</span>
{#each $app.grid as gridItem, index (gridItem.id)}
{#each app.grid as gridItem, index (gridItem.id)}
<ComponentOutput {gridItem} first={index === 0} />
{/each}
</div>
@@ -12,34 +12,38 @@
const { app, initialized } = getContext<AppViewerContext>('AppViewerContext')
$: unintitializedComponents = allItems($app.grid, $app.subgrids)
.map((x) => x.id)
.filter((x) => !$initialized.initializedComponents?.includes(x))
.sort()
let unintitializedComponents = $derived(
allItems(app.grid, app.subgrids)
.map((x) => x.id)
.filter((x) => !$initialized.initializedComponents?.includes(x))
.sort()
)
$: subgridsErrors = Object.keys($app.subgrids ?? {})
.map((x) => {
const parentId = x.split('-')[0]
const parent = findGridItem($app, parentId)
const subgrid = x.replace(`${parentId}-`, '')
if (subgrid == '-1') {
return {
subGridId: x,
error: 'Invalid subgrid index -1 '
let subgridsErrors = $derived(
Object.keys(app.subgrids ?? {})
.map((x) => {
const parentId = x.split('-')[0]
const parent = findGridItem(app, parentId)
const subgrid = x.replace(`${parentId}-`, '')
if (subgrid == '-1') {
return {
subGridId: x,
error: 'Invalid subgrid index -1 '
}
} else if (parent === undefined) {
return {
subGridId: x,
error: 'Parent not found'
}
} else if (parent?.data?.numberOfSubgrids === undefined) {
return {
subGridId: x,
error: 'Parent is not a container'
}
}
} else if (parent === undefined) {
return {
subGridId: x,
error: 'Parent not found'
}
} else if (parent?.data?.numberOfSubgrids === undefined) {
return {
subGridId: x,
error: 'Parent is not a container'
}
}
})
.filter(Boolean)
})
.filter(Boolean)
)
</script>
<div class="flex flex-col gap-8" style="all:none;">
@@ -74,49 +78,47 @@
>Status</div
>
<div class="font-semibold bg-gray-100 dark:bg-gray-900 px-2 py-1 text-xs border-b"
>Action</div>
>Action</div
>
<!-- Iterate over uninitializedComponents to display each component in the grid -->
{#each unintitializedComponents as c}
{@const item = findGridItem($app, c)}
{@const item = findGridItem(app, c)}
{#if !item}
<div>Item {c} not found</div>
{:else}
<!-- Component Id -->
<div class="text-xs flex items-center px-2 py-2">
<Badge>
{c}
</Badge>
</div>
<!-- Component Id -->
<div class="text-xs flex items-center px-2 py-2">
<Badge>
{c}
</Badge>
</div>
<div class="text-xs flex items-center px-2 py-2">
<Badge color="blue">
{item?.data?.type || 'Unknown'}
</Badge>
</div>
<div class="text-xs flex items-center px-2 py-2">
<Badge color="blue">
{item?.data?.type || 'Unknown'}
</Badge>
</div>
<div class="text-xs flex items-center px-2 py-2">
<Badge color="red">Uninitialized</Badge>
</div>
<div class="text-xs flex items-center px-2 py-2">
<Button
color="light"
startIcon={{
icon: Trash
}}
size="xs2"
on:click={() => {
let parent = findGridItemParentGrid($app, c)
deleteGridItem($app, item.data, parent)
$app = $app
}}
>
Remove
</Button>
</div>
<div class="text-xs flex items-center px-2 py-2">
<Badge color="red">Uninitialized</Badge>
</div>
<div class="text-xs flex items-center px-2 py-2">
<Button
color="light"
startIcon={{
icon: Trash
}}
size="xs2"
on:click={() => {
let parent = findGridItemParentGrid(app, c)
deleteGridItem(app, item.data, parent) // $app = $app
}}
>
Remove
</Button>
</div>
{/if}
{/each}
</div>
</div>
@@ -164,9 +166,8 @@
}}
size="xs2"
on:click={() => {
if ($app.subgrids && s) {
delete $app.subgrids[s.subGridId]
$app = { ...$app }
if (app.subgrids && s) {
delete app.subgrids[s.subGridId]
}
}}
>
@@ -9,10 +9,10 @@
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
const { app } = getContext<AppViewerContext>('AppViewerContext')
let code = $state(JSON.stringify($app.lazyInitRequire))
let code = $state(JSON.stringify(app.lazyInitRequire))
let selectedRendering = $state(
$app.eagerRendering ? 'eager' : $app.lazyInitRequire ? 'lazy' : 'semi-lazy'
app.eagerRendering ? 'eager' : app.lazyInitRequire ? 'lazy' : 'semi-lazy'
)
</script>
@@ -32,15 +32,15 @@
bind:selected={selectedRendering}
on:selected={(e) => {
if (e.detail == 'eager') {
$app.eagerRendering = true
$app.lazyInitRequire = undefined
app.eagerRendering = true
app.lazyInitRequire = undefined
} else if (e.detail == 'semi-lazy') {
$app.eagerRendering = undefined
$app.lazyInitRequire = undefined
app.eagerRendering = undefined
app.lazyInitRequire = undefined
} else {
$app.eagerRendering = undefined
$app.lazyInitRequire = []
code = JSON.stringify($app.lazyInitRequire)
app.eagerRendering = undefined
app.lazyInitRequire = []
code = JSON.stringify(app.lazyInitRequire)
}
}}
>
@@ -65,7 +65,7 @@
</Section>
{:else if selectedRendering == 'lazy'}
<Section label="Component ids to wait the initialization of before the initial refresh">
<JsonEditor bind:value={$app.lazyInitRequire} {code} />
<JsonEditor bind:value={app.lazyInitRequire} {code} />
<span class="text-tertiary text-xs">
{'e.g: ["a", "b"]'}, no need to put background runnables ids
</span>
@@ -1,33 +1,31 @@
<script lang="ts">
import { classNames } from '$lib/utils'
import { getContext } from 'svelte'
import { getContext, untrack } from 'svelte'
import type { Output } from '../../rx'
import type { AppViewerContext } from '../../types'
import { connectInput } from '../appUtils'
import ComponentOutput from './ComponentOutput.svelte'
export let name: string | undefined = undefined
export let parentId: string
export let expanded: boolean = false
export let subGrids: string[]
export let nameOverrides: string[] | undefined = undefined
export let render: boolean
interface Props {
name?: string | undefined
parentId: string
expanded?: boolean
subGrids: string[]
nameOverrides?: string[] | undefined
render: boolean
}
let {
name = undefined,
parentId,
expanded = false,
subGrids,
nameOverrides = undefined,
render
}: Props = $props()
const { app, connectingInput, worldStore } = getContext<AppViewerContext>('AppViewerContext')
let selected = 0
$: outputs = $worldStore?.outputsById[parentId] as {
selectedTabIndex: Output<number>
}
$: subgridItems = subGrids.map((k) => ({
k,
items: $app.subgrids?.[k] ?? []
}))
$: if (outputs?.selectedTabIndex) {
subscribeToOutput()
}
let selected = $state(0)
function subscribeToOutput() {
outputs.selectedTabIndex.subscribe(
@@ -40,19 +38,37 @@
selected
)
}
let outputs = $derived(
$worldStore?.outputsById[parentId] as {
selectedTabIndex: Output<number>
}
)
let subgridItems = $derived(
subGrids.map((k) => ({
k,
items: app.subgrids?.[k] ?? []
}))
)
$effect(() => {
if (outputs?.selectedTabIndex) {
untrack(() => {
subscribeToOutput()
})
}
})
</script>
{#each subgridItems as { k, items }, index (k)}
<div class="ml-2 my-2">
{#if subGrids.length > 1 && render}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class={classNames(
'px-1 py-0.5 flex justify-between items-center font-semibold text-xs border-l border-y w-full cursor-pointer',
selected === index ? 'bg-surface-selected' : 'bg-surface'
)}
on:click={() => {
onclick={() => {
selected = index
}}
>
@@ -78,9 +94,6 @@
gridItem={subGridItem}
first={index === 0}
{expanded}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, subGridItem.id, detail)
}}
/>
{/each}
{:else}
@@ -7,17 +7,23 @@
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
export let id: string
export let name: string
export let first: boolean = false
interface Props {
id: string
name: string
first?: boolean
}
let { id, name, first = false }: Props = $props()
</script>
<OutputHeader let:render renamable={false} selectable={true} {id} {name} color="blue" {first}>
<ComponentOutputViewer
{render}
componentId={id}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, id, detail)
}}
/>
<OutputHeader renamable={false} selectable={true} {id} {name} color="blue" {first}>
{#snippet children({ render })}
<ComponentOutputViewer
{render}
componentId={id}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, id, detail)
}}
/>
{/snippet}
</OutputHeader>
@@ -7,7 +7,7 @@
const { app } = getContext<AppViewerContext>('AppViewerContext')
</script>
{#each $app.hiddenInlineScripts ?? [] as action, index}
{#each app.hiddenInlineScripts ?? [] as action, index}
{#if !action.hidden}
<BackgroundScriptOutput id={BG_PREFIX + index} name={action.name} first={index === 0} />
{/if}
@@ -8,11 +8,15 @@
const { app, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
export let id: string
interface Props {
id: string
}
let { id }: Props = $props()
const dispatch = createEventDispatcher()
$: reservedIds = allItems($app.grid, $app.subgrids).map((item) => item.id)
let reservedIds = $derived(allItems(app.grid, app.subgrids).map((item) => item.id))
</script>
<Popover
@@ -20,9 +24,9 @@
closeOnOtherPopoverOpen
contentClasses="p-4"
>
<svelte:fragment slot="trigger">
{#snippet trigger()}
<button
on:click={() => {
onclick={() => {
$selectedComponent = [id]
}}
title="Edit ID"
@@ -31,8 +35,8 @@
>
<Pencil size={14} />
</button>
</svelte:fragment>
<svelte:fragment slot="content" let:close>
{/snippet}
{#snippet content({ close })}
<IdEditorInput
initialId={id}
on:close={() => close()}
@@ -42,5 +46,5 @@
}}
{reservedIds}
/>
</svelte:fragment>
{/snippet}
</Popover>
@@ -7,18 +7,24 @@
const { connectingInput } = getContext<AppViewerContext>('AppViewerContext')
export let id: string
export let first: boolean = false
export let label: string
export let renderRec: boolean
interface Props {
id: string
first?: boolean
label: string
renderRec: boolean
}
let { id, first = false, label, renderRec }: Props = $props()
</script>
<OutputHeader render={renderRec} let:render renamable={false} {id} name={label} {first}>
<ComponentOutputViewer
{render}
componentId={id}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, id, detail)
}}
/>
<OutputHeader render={renderRec} renamable={false} {id} name={label} {first}>
{#snippet children({ render })}
<ComponentOutputViewer
{render}
componentId={id}
on:select={({ detail }) => {
$connectingInput = connectInput($connectingInput, id, detail)
}}
/>
{/snippet}
</OutputHeader>
@@ -1,4 +1,6 @@
<script lang="ts">
import { stopPropagation } from 'svelte/legacy'
import type { AppViewerContext, ContextPanelContext } from '$lib/components/apps/types'
import { allItems } from '$lib/components/apps/utils'
import { classNames } from '$lib/utils'
@@ -10,28 +12,46 @@
import type { Runnable } from '$lib/components/apps/inputType'
import DocLink from '../../settingsPanel/DocLink.svelte'
export let id: string
export let name: string
export let first: boolean = false
export let nested: boolean = false
export let color: 'blue' | 'indigo' = 'indigo'
export let selectable: boolean = true
export let renamable: boolean = true
export let disabled: boolean = false
export let render: boolean = true
interface Props {
id: string
name: string
first?: boolean
nested?: boolean
color?: 'blue' | 'indigo'
selectable?: boolean
renamable?: boolean
disabled?: boolean
render?: boolean
children?: import('svelte').Snippet<[any]>
}
let {
id,
name,
first = false,
nested = false,
color = 'indigo',
selectable = true,
renamable = true,
disabled = false,
render = true,
children
}: Props = $props()
const { manuallyOpened, search, hasResult } = getContext<ContextPanelContext>('ContextPanel')
const { selectedComponent, app, hoverStore, allIdsInPath, connectingInput, worldStore } =
getContext<AppViewerContext>('AppViewerContext')
$: subids = $search != '' ? allsubIds($app, id) : []
$: inSearch =
let subids = $derived($search != '' ? allsubIds(app, id) : [])
let inSearch = $derived(
$search != '' &&
($hasResult[id] ||
Object.entries($hasResult).some(([key, value]) => value && subids.includes(key)))
$: open =
($hasResult[id] ||
Object.entries($hasResult).some(([key, value]) => value && subids.includes(key)))
)
let open = $derived(
$allIdsInPath.includes(id) || id == $selectedComponent?.[0] || $manuallyOpened[id] || inSearch
)
const hoverColor = {
blue: 'hover:bg-blue-100 hover:text-blue-500 dark:hover:bg-frost-900 dark:hover:text-frost-100',
@@ -55,7 +75,7 @@
}
function renameId(newId: string): void {
const item = findGridItem($app, id)
const item = findGridItem(app, id)
if (!item) {
return
@@ -63,23 +83,23 @@
item.data.id = newId
item.id = newId
const oldSubgrids = Object.keys($app.subgrids ?? {}).filter((subgrid) =>
const oldSubgrids = Object.keys(app.subgrids ?? {}).filter((subgrid) =>
subgrid.startsWith(id + '-')
)
oldSubgrids.forEach((subgrid) => {
if ($app.subgrids) {
$app.subgrids[subgrid.replace(id, newId)] = $app.subgrids[subgrid]
delete $app.subgrids[subgrid]
if (app.subgrids) {
app.subgrids[subgrid.replace(id, newId)] = app.subgrids[subgrid]
delete app.subgrids[subgrid]
}
})
function propagateRename(from: string, to: string) {
allItems($app.grid, $app.subgrids).forEach((item) => {
allItems(app.grid, app.subgrids).forEach((item) => {
renameComponent(from, to, item.data)
})
$app.hiddenInlineScripts?.forEach((x) => {
app.hiddenInlineScripts?.forEach((x) => {
processRunnable(from, to, x)
})
}
@@ -114,7 +134,7 @@
}
}
$app = $app
// $app = $app
$selectedComponent = [newId]
delete $worldStore.outputsById[id]
@@ -199,35 +219,35 @@
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div class={render && ($search == '' || inSearch) ? '' : 'invisible h-0 overflow-hidden'}>
{#if render && ($search == '' || inSearch)}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
on:mouseenter|stopPropagation={() => {
onmouseenter={stopPropagation(() => {
if (id !== $hoverStore) {
$hoverStore = id
}
}}
on:mouseleave|stopPropagation={() => {
})}
onmouseleave={stopPropagation(() => {
if ($hoverStore !== undefined) {
$hoverStore = undefined
}
}}
})}
class={classNames(
'flex items-center justify-between p-1 cursor-pointer gap-1 truncate',
hoverColor[color],
$selectedComponent?.includes(id)
? openBackground[color]
: $connectingInput.hoveredComponent === id
? 'bg-[#fab157]'
: 'bg-surface-secondary',
? 'bg-[#fab157]'
: 'bg-surface-secondary',
first ? 'border-t' : '',
nested ? 'border-l' : '',
'transition-all'
)}
on:click={() => {
onclick={() => {
if (!disabled) {
$manuallyOpened[id] = $manuallyOpened[id] != undefined ? !$manuallyOpened[id] : true
}
@@ -238,7 +258,7 @@
<button
disabled={!(selectable && !$selectedComponent?.includes(id)) || $connectingInput?.opened}
title="Select component"
on:click|stopPropagation={() => ($selectedComponent = [id])}
onclick={stopPropagation(() => ($selectedComponent = [id]))}
class="flex items-center ml-0.5 rounded-sm bg-surface-selected hover:text-primary text-tertiary"
>
<div
@@ -271,8 +291,8 @@
docLink={id === 'state'
? 'https://www.windmill.dev/docs/apps/outputs#state'
: id === 'ctx'
? 'https://www.windmill.dev/docs/apps/outputs#app-context'
: ''}
? 'https://www.windmill.dev/docs/apps/outputs#app-context'
: ''}
size="xs2"
/>
{/if}
@@ -299,7 +319,7 @@
: ''}"
>
<div class={classNames(nested ? 'border-l ml-2' : '', open ? 'border-t' : '')}>
<slot render={open && render} />
{@render children?.({ render: open && render })}
</div>
</div>
</div>
@@ -20,14 +20,23 @@
import type { Preview } from '$lib/gen'
import type { InlineScript } from '../../types'
export let componentType: string | undefined = undefined
export let showScriptPicker = false
export let rawApps = false
export let unusedInlineScripts: { name: string; inlineScript: InlineScript }[]
interface Props {
componentType?: string | undefined
showScriptPicker?: boolean
rawApps?: boolean
unusedInlineScripts: { name: string; inlineScript: InlineScript }[]
}
let tab = 'workspacescripts'
let filter: string = ''
let picker: Drawer
let {
componentType = undefined,
showScriptPicker = false,
rawApps = false,
unusedInlineScripts
}: Props = $props()
let tab = $state('workspacescripts')
let filter: string = $state('')
let picker: Drawer | undefined = $state()
const dispatch = createEventDispatcher()
@@ -78,13 +87,15 @@
newInlineScript(script.content, script.language)
}
$: langs = processLangs(undefined, $defaultScripts?.order ?? Object.keys(defaultScriptLanguages))
.map((l) => [defaultScriptLanguages[l], l])
.filter(
(x) =>
x[1] != 'docker' &&
($defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1]))
) as [string, Preview['language']][]
let langs = $derived(
processLangs(undefined, $defaultScripts?.order ?? Object.keys(defaultScriptLanguages))
.map((l) => [defaultScriptLanguages[l], l])
.filter(
(x) =>
x[1] != 'docker' &&
($defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1]))
) as [string, Preview['language']][]
)
</script>
<Drawer bind:this={picker} size="1000px">
@@ -30,8 +30,7 @@
stateId,
worldStore,
state: stateStore,
appPath,
app
appPath
} = getContext<AppViewerContext>('AppViewerContext')
interface Props {
@@ -196,7 +195,7 @@
$stateId++
}
}
$app = $app
// $app = $app
}
}
@@ -271,7 +270,7 @@
placeholder="Inline script name"
class="!text-xs !rounded-sm !shadow-none"
onkeyup={() => {
$app = $app
// $app = $app
if (stateId) {
$stateId++
}
@@ -385,7 +384,7 @@
loadSchemaAndInputsByName()
}
}
$app = $app
// $app = $app
}}
args={Object.entries(fields).reduce((acc, [key, obj]) => {
acc[key] = obj.type === 'static' ? obj.value : undefined
@@ -413,7 +412,7 @@
}}
on:change={async (e) => {
inferSuggestions(e.detail.code)
$app = $app
// $app = $app
}}
/>
{/if}
@@ -18,8 +18,7 @@
function clear() {
if (componentInput && componentInput.type == 'runnable') {
componentInput = clearResultAppInput(componentInput)
$app = $app
componentInput = clearResultAppInput(componentInput) // $app = $app
}
}
@@ -76,7 +75,7 @@
/>
{:else}
<EmptyInlineScript
unusedInlineScripts={$app?.unusedInlineScripts}
unusedInlineScripts={app?.unusedInlineScripts}
{componentType}
on:delete={clear}
on:new={(e) => {
@@ -87,8 +86,7 @@
) {
componentInput.runnable.inlineScript = e.detail
componentInput.autoRefresh = true
componentInput.recomputeOnInputChanged = true
$app = $app
componentInput.recomputeOnInputChanged = true // $app = $app
}
}}
/>
@@ -6,9 +6,13 @@
import type { Runnable, StaticAppInput } from '../../inputType'
import { createEventDispatcher, getContext } from 'svelte'
export let runnable: HiddenRunnable
export let id: string
export let transformer: boolean
interface Props {
runnable: HiddenRunnable
id: string
transformer: boolean
}
let { runnable = $bindable(), id, transformer }: Props = $props()
const { runnableComponents, app } = getContext<AppViewerContext>('AppViewerContext')
async function fork(nrunnable: Runnable) {
@@ -69,7 +73,7 @@
/>
{:else}
<EmptyInlineScript
unusedInlineScripts={$app?.unusedInlineScripts}
unusedInlineScripts={app?.unusedInlineScripts}
on:pick={(e) => onPick(e.detail)}
on:delete
showScriptPicker
@@ -18,11 +18,11 @@
function deleteBackgroundScript(index: number) {
// remove the script from the array at the index
if ($app.hiddenInlineScripts.length - 1 == index) {
$app.hiddenInlineScripts.splice(index, 1)
$app.hiddenInlineScripts = [...$app.hiddenInlineScripts]
if (app.hiddenInlineScripts.length - 1 == index) {
app.hiddenInlineScripts.splice(index, 1)
app.hiddenInlineScripts = [...app.hiddenInlineScripts]
} else {
$app.hiddenInlineScripts[index] = {
app.hiddenInlineScripts[index] = {
hidden: true,
inlineScript: undefined,
name: `Background Runnable ${index}`,
@@ -30,7 +30,7 @@
type: 'runnableByName',
recomputeIds: undefined
}
$app.hiddenInlineScripts = $app.hiddenInlineScripts
app.hiddenInlineScripts = app.hiddenInlineScripts
}
$selectedComponentInEditor = undefined
@@ -40,24 +40,33 @@
}
}
$: gridItem =
let gridItem = $derived(
$selectedComponentInEditor && !$selectedComponentInEditor.startsWith(BG_PREFIX)
? findGridItem($app, $selectedComponentInEditor?.split('_')?.[0])
? findGridItem(app, $selectedComponentInEditor?.split('_')?.[0])
: undefined
$: hiddenInlineScript = $app?.hiddenInlineScripts?.findIndex((k_, index) => {
const [prefix, id] = $selectedComponentInEditor?.split('_') || []
if (prefix !== 'bg') return false
return Number(id) === index
})
$: unusedInlineScript = $app?.unusedInlineScripts?.findIndex(
(k_, index) => `unused-${index}` === $selectedComponentInEditor
)
export let width: number | undefined = undefined
let hiddenInlineScript = $derived(
app?.hiddenInlineScripts?.findIndex((k_, index) => {
const [prefix, id] = $selectedComponentInEditor?.split('_') || []
if (prefix !== 'bg') return false
return Number(id) === index
})
)
let unusedInlineScript = $derived(
app?.unusedInlineScripts?.findIndex(
(k_, index) => `unused-${index}` === $selectedComponentInEditor
)
)
interface Props {
width?: number | undefined
}
let { width = undefined }: Props = $props()
</script>
<Splitpanes
@@ -86,24 +95,24 @@
bind:gridItem
/>
{/key}
{:else if unusedInlineScript > -1 && $app.unusedInlineScripts?.[unusedInlineScript]}
{:else if unusedInlineScript > -1 && app.unusedInlineScripts?.[unusedInlineScript]}
{#key unusedInlineScript}
<InlineScriptEditor
on:createScriptFromInlineScript={() =>
sendUserToast('Cannot save to workspace unused scripts', true)}
id={`unused-${unusedInlineScript}`}
bind:name={$app.unusedInlineScripts[unusedInlineScript].name}
bind:inlineScript={$app.unusedInlineScripts[unusedInlineScript].inlineScript}
bind:name={app.unusedInlineScripts[unusedInlineScript].name}
bind:inlineScript={app.unusedInlineScripts[unusedInlineScript].inlineScript}
on:delete={() => {
// remove the script from the array at the index
$app.unusedInlineScripts.splice(unusedInlineScript, 1)
$app.unusedInlineScripts = [...$app.unusedInlineScripts]
app.unusedInlineScripts.splice(unusedInlineScript, 1)
app.unusedInlineScripts = [...app.unusedInlineScripts]
}}
/>
{/key}
{:else if hiddenInlineScript > -1}
{#key hiddenInlineScript}
{#if $app.hiddenInlineScripts?.[hiddenInlineScript]}
{#if app.hiddenInlineScripts?.[hiddenInlineScript]}
<InlineScriptHiddenRunnable
on:createScriptFromInlineScript={(e) => {
createScriptFromInlineScript(
@@ -111,13 +120,12 @@
e.detail,
$workspaceStore ?? '',
$appPath
)
$app = $app
) // app = app
}}
transformer={$selectedComponentInEditor?.endsWith('_transformer')}
on:delete={() => deleteBackgroundScript(hiddenInlineScript)}
id={BG_PREFIX + hiddenInlineScript}
bind:runnable={$app.hiddenInlineScripts[hiddenInlineScript]}
bind:runnable={app.hiddenInlineScripts[hiddenInlineScript]}
/>{/if}{/key}
{:else}
<div class="text-sm text-tertiary text-center py-8 px-2">
@@ -1,7 +1,7 @@
<script lang="ts">
import { Badge, Button } from '$lib/components/common'
import { Plus } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import { createEventDispatcher, getContext, untrack } from 'svelte'
import Tooltip from '../../../Tooltip.svelte'
import type { AppEditorContext, AppViewerContext } from '../../types'
import { BG_PREFIX, getAllScriptNames } from '../../utils'
@@ -16,7 +16,7 @@
const PREFIX = 'script-selector-' as const
const { app, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
const { app, selectedComponent } = $state(getContext<AppViewerContext>('AppViewerContext'))
const { selectedComponentInEditor } = getContext<AppEditorContext>('AppEditorContext')
function selectScript(id: string) {
@@ -26,11 +26,6 @@
}
}
$: runnables = getAppScripts($app.grid, $app.subgrids)
// When selected component changes, update selectedScriptComponentId
$: selectedComponent && handleSelectedComponent($selectedComponent)
function handleSelectedComponent(selectedComponent: string[] | undefined) {
if (
selectedComponent != $selectedComponentInEditor &&
@@ -45,10 +40,10 @@
appTutorials?.runTutorialById('backgroundrunnables', { skipStepsCount: 2 })
}
for (const [index, script] of $app.hiddenInlineScripts.entries()) {
for (const [index, script] of app.hiddenInlineScripts.entries()) {
if (script.hidden) {
delete script.hidden
$app.hiddenInlineScripts = $app.hiddenInlineScripts
app.hiddenInlineScripts = app.hiddenInlineScripts
selectScript(BG_PREFIX + index)
return
}
@@ -56,18 +51,18 @@
let index = 0
let newScriptPath = `Background Runnable ${index}`
const names = getAllScriptNames($app)
const names = getAllScriptNames(app)
// Find a name that is not used by any other inline script
while (names.includes(newScriptPath)) {
newScriptPath = `Background Runnable ${++index}`
}
if (!$app.hiddenInlineScripts) {
$app.hiddenInlineScripts = []
if (!app.hiddenInlineScripts) {
app.hiddenInlineScripts = []
}
$app.hiddenInlineScripts.push({
app.hiddenInlineScripts.push({
name: newScriptPath,
inlineScript: undefined,
autoRefresh: true,
@@ -75,16 +70,21 @@
fields: {},
recomputeIds: undefined
})
$app.hiddenInlineScripts = $app.hiddenInlineScripts
selectScript(`${BG_PREFIX}${$app.hiddenInlineScripts.length - 1}`)
app.hiddenInlineScripts = app.hiddenInlineScripts
selectScript(`${BG_PREFIX}${app.hiddenInlineScripts.length - 1}`)
}
let appTutorials: AppTutorials | undefined = undefined
let appTutorials: AppTutorials | undefined = $state(undefined)
const dispatch = createEventDispatcher()
let runnables = $derived(getAppScripts(app.grid, app.subgrids))
// When selected component changes, update selectedScriptComponentId
$effect(() => {
selectedComponent && untrack(() => handleSelectedComponent($selectedComponent))
})
</script>
<PanelSection title="Runnables" id="app-editor-runnable-panel">
<svelte:fragment slot="action">
{#snippet action()}
<div class="flex flex-row gap-1">
<HideButton
direction="bottom"
@@ -96,7 +96,7 @@
docLink="https://www.windmill.dev/docs/apps/app-runnable-panel#creating-a-runnable"
/>
</div>
</svelte:fragment>
{/snippet}
<div class="w-full flex flex-col gap-6 py-1">
<div>
<div class="flex flex-col gap-2 w-full">
@@ -109,7 +109,7 @@
{$selectedComponentInEditor === id
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50 dark:hover:bg-frost-900/50'}"
on:click={() => selectScript(id)}
onclick={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<div>
@@ -124,7 +124,7 @@
{$selectedComponentInEditor === id + '_transformer'
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50 dark:hover:bg-frost-900/50'}"
on:click={() => selectScript(id + '_transformer')}
onclick={() => selectScript(id + '_transformer')}
>
<span class="text-2xs truncate">Transformer</span>
</button>
@@ -140,7 +140,7 @@
{$selectedComponentInEditor === id
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50'}"
on:click={() => selectScript(id)}
onclick={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<Badge color="indigo">{id}</Badge>
@@ -153,7 +153,7 @@
{$selectedComponentInEditor === id + '_transformer'
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50 dark:hover:bg-frost-900/50'}"
on:click={() => selectScript(id + '_transformer')}
onclick={() => selectScript(id + '_transformer')}
>
<span class="text-2xs truncate">Transformer</span>
</button>
@@ -161,9 +161,9 @@
{/if}
{/each}
{#if $app.unusedInlineScripts?.length > 0}
{#if app.unusedInlineScripts?.length > 0}
<div class="flex gap-1 flex-col">
{#each $app.unusedInlineScripts as unusedInlineScript, index (index)}
{#each app.unusedInlineScripts as unusedInlineScript, index (index)}
{@const id = `unused-${index}`}
<button
id={PREFIX + id}
@@ -171,7 +171,7 @@
{$selectedComponentInEditor === id
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50 dark:hover:bg-frost-900/50'}"
on:click={() => selectScript(id)}
onclick={() => selectScript(id)}
>
<span class="text-2xs truncate">{unusedInlineScript.name}</span>
<Badge color="red">Detached</Badge>
@@ -179,7 +179,7 @@
{/each}
</div>
{/if}
{#if runnables.inline.length == 0 && $app.unusedInlineScripts?.length == 0 && runnables.imported.length == 0}
{#if runnables.inline.length == 0 && app.unusedInlineScripts?.length == 0 && runnables.imported.length == 0}
<div class="text-xs text-tertiary">No scripts/flows</div>
{/if}
</div>
@@ -210,8 +210,8 @@
</Button>
</div>
<div class="flex flex-col gap-1 w-full">
{#if $app.hiddenInlineScripts?.length > 0}
{#each $app.hiddenInlineScripts as { name, hidden, transformer }, index (index)}
{#if app.hiddenInlineScripts?.length > 0}
{#each app.hiddenInlineScripts as { name, hidden, transformer }, index (index)}
{#if !hidden}
{@const id = BG_PREFIX + index}
<button
@@ -220,7 +220,7 @@
{$selectedComponentInEditor === id
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50 dark:hover:bg-frost-900/50'}"
on:click={() => selectScript(id)}
onclick={() => selectScript(id)}
>
<span class="text-2xs truncate">{name}</span>
<Badge color="indigo">{id}</Badge>
@@ -233,7 +233,7 @@
{$selectedComponentInEditor === id + '_transformer'
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50 dark:hover:bg-frost-900/50'}"
on:click={() => selectScript(id + '_transformer')}
onclick={() => selectScript(id + '_transformer')}
>
<span class="text-2xs truncate">Transformer</span>
</button>
@@ -7,14 +7,10 @@
AlignStartHorizontal,
AlignStartVertical
} from 'lucide-svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '../../types'
import type { AppComponent } from '../component'
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
const { app } = getContext<AppViewerContext>('AppViewerContext')
interface Props {
component: AppComponent
}
@@ -26,11 +22,7 @@
<div class="flex flex-wrap gap-x-4 gap-y-1 w-full justify-end items-center">
<div class="text-tertiary text-xs">Alignment</div>
{#if component.horizontalAlignment}
<ToggleButtonGroup
noWFull
on:selected={() => ($app = $app)}
bind:selected={component.horizontalAlignment}
>
<ToggleButtonGroup noWFull bind:selected={component.horizontalAlignment}>
{#snippet children({ item })}
<ToggleButton value="left" icon={AlignStartVertical} {item} />
<ToggleButton value="center" icon={AlignCenterVertical} {item} />
@@ -39,11 +31,7 @@
</ToggleButtonGroup>
{/if}
{#if component.type !== 'formcomponent' && component.verticalAlignment}
<ToggleButtonGroup
noWFull
on:selected={() => ($app = $app)}
bind:selected={component.verticalAlignment}
>
<ToggleButtonGroup noWFull bind:selected={component.verticalAlignment}>
{#snippet children({ item })}
<ToggleButton value="top" icon={AlignStartHorizontal} {item} />
<ToggleButton value="center" icon={AlignCenterHorizontal} {item} />
@@ -71,7 +71,7 @@
}
function appendMigrationsToCss(migrations: Map<string, string[]>) {
const theme = $app.theme
const theme = app.theme
if (theme?.type === 'path') {
sendUserToast(
@@ -111,7 +111,7 @@
theme.css = cssString
$app.theme = theme
app.theme = theme
}
}
@@ -167,7 +167,7 @@
</Badge>
</div>
{/if}
{#if component?.type && $app.css}
{#if component?.type && app.css}
{#each Object.keys(component.customCss ?? {}) as cssKey}
{#if component.customCss?.[cssKey].style != undefined && component.customCss[cssKey].style !== ''}
<div class="grid grid-cols-2 gap-2">
@@ -215,15 +215,15 @@
{/each}
{/if}
{#if hasStyles(component?.type ? $app.css?.[component?.type] : undefined)}
{#if hasStyles(component?.type ? app.css?.[component?.type] : undefined)}
<div class="leading-6 text-xs font-semibold">
Global: {component?.type ? ccomponents[component.type]?.name : ''}
</div>
{/if}
{#if component?.type && $app.css}
{#each Object.keys($app.css[component?.type] ?? {}) as cssKey}
{#if type && $app.css?.[type]?.[cssKey].style != undefined && $app.css[type]?.[cssKey].style !== ''}
{#if component?.type && app.css}
{#each Object.keys(app.css[component?.type] ?? {}) as cssKey}
{#if type && app.css?.[type]?.[cssKey].style != undefined && app.css[type]?.[cssKey].style !== ''}
<div class="grid grid-cols-2 gap-2">
<div>
<div class="flex flex-row justify-between items-center py-0.5">
@@ -236,9 +236,9 @@
color="dark"
size="xs"
on:click={() => {
if (type && $app.css?.[type]) {
setOrUpdateMigration(cssKey, $app.css[type][cssKey].style)
$app.css[type][cssKey].style = ''
if (type && app.css?.[type]) {
setOrUpdateMigration(cssKey, app.css[type][cssKey].style)
app.css[type][cssKey].style = ''
}
}}
endIcon={{ icon: MoveRight }}
@@ -247,14 +247,14 @@
</Button>
</div>
<div class="border p-2 rounded-md">
<Highlight code={$app.css[type][cssKey].style} language={css} />
<Highlight code={app.css[type][cssKey].style} language={css} />
</div>
</div>
<div class="">
<div class="leading-6 text-xs font-semibold my-1">Preview</div>
<div class="border rounded-md p-2">
<Highlight
code={`${getSelector(cssKey)} {\n\t${$app.css[type][cssKey].style}\n}`}
code={`${getSelector(cssKey)} {\n\t${app.css[type][cssKey].style}\n}`}
language={css}
/>
</div>
@@ -7,29 +7,35 @@
import typescript from 'svelte-highlight/languages/typescript'
import { Button } from '$lib/components/common'
import HighlightTheme from '$lib/components/HighlightTheme.svelte'
export let type: keyof typeof components
interface Props {
type: keyof typeof components
}
let { type }: Props = $props()
const componentControls = getComponentControl(type)
let collapsed: boolean = true
let collapsed: boolean = $state(true)
</script>
<HighlightTheme />
{#if componentControls?.length > 0}
<PanelSection title="Controls">
<div slot="action" class="flex justify-end flex-wrap gap-1">
<Button
color="light"
size="xs2"
btnClasses="text-2xs font-normal"
on:click={() => {
collapsed = !collapsed
}}
>
{collapsed ? 'Show' : 'Hide'} details
</Button>
</div>
{#snippet action()}
<div class="flex justify-end flex-wrap gap-1">
<Button
color="light"
size="xs2"
btnClasses="text-2xs font-normal"
on:click={() => {
collapsed = !collapsed
}}
>
{collapsed ? 'Show' : 'Hide'} details
</Button>
</div>
{/snippet}
{#if collapsed}
<div class="flex flex-row gap-1 flex-wrap">
@@ -17,7 +17,7 @@
let { componentInput = $bindable(), disableStatic = false, evalV2editor }: Props = $props()
const { onchange, connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
const { onchange, connectingInput } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
@@ -45,8 +45,7 @@
}
]
}
evalV2editor?.setCode(expr)
$app = $app
evalV2editor?.setCode(expr) // $app = $app
}
let clientWidth: number = $state(0)
@@ -46,11 +46,19 @@
import { twMerge } from 'tailwind-merge'
import Popover from '$lib/components/Popover.svelte'
export let componentSettings: { item: GridItem; parent: string | undefined } | undefined =
undefined
export let onDelete: (() => void) | undefined = undefined
export let noGrid = false
export let duplicateMoveAllowed = true
interface Props {
componentSettings?: { item: GridItem; parent: string | undefined } | undefined
onDelete?: (() => void) | undefined
noGrid?: boolean
duplicateMoveAllowed?: boolean
}
let {
componentSettings = $bindable(undefined),
onDelete = undefined,
noGrid = false,
duplicateMoveAllowed = true
}: Props = $props()
const {
app,
@@ -67,7 +75,7 @@
const { history, movingcomponents } = getContext<AppEditorContext>('AppEditorContext')
function removeGridElement() {
push(history, $app)
push(history, app)
const id = componentSettings?.item?.id
const onDeleteComponentControl = id ? $componentControl[id]?.onDelete : undefined
@@ -91,7 +99,7 @@
$selectedComponent = undefined
$focusedGrid = undefined
if (componentSettings?.item && !noGrid) {
let ids = deleteGridItem($app, componentSettings?.item.data, componentSettings?.parent)
let ids = deleteGridItem(app, componentSettings?.item.data, componentSettings?.parent)
for (const key of ids) {
delete $runnableComponents[key]
}
@@ -100,7 +108,7 @@
if (componentSettings?.item?.data?.id) {
delete $runnableComponents[componentSettings?.item?.data?.id]
}
$app = $app
// $app = $app
$runnableComponents = $runnableComponents
onDelete?.()
@@ -108,10 +116,10 @@
let viewCssOptions = false
$: extraLib =
let extraLib = $derived(
(componentSettings?.item?.data?.componentInput?.type === 'template' ||
componentSettings?.item?.data?.componentInput?.type === 'templatev2') &&
$worldStore
$worldStore
? buildExtraLib(
$worldStore?.outputsById ?? {},
componentSettings?.item?.data?.id,
@@ -119,6 +127,7 @@
false
)
: undefined
)
// `
// /** The current's app state */
@@ -144,7 +153,7 @@
? isTriggerable(componentSettings?.item.data.type)
: false
let evalV2editor: EvalV2InputEditor | undefined = undefined
let evalV2editor: EvalV2InputEditor | undefined = $state(undefined)
function transformToFrontend() {
if (componentSettings?.item.data.componentInput) {
@@ -173,17 +182,17 @@
}
</script>
<svelte:window on:keydown={keydown} />
<svelte:window onkeydown={keydown} />
{#if componentSettings?.item?.id && isTableAction(componentSettings?.item?.id, $app)}
{#if componentSettings?.item?.id && isTableAction(componentSettings?.item?.id, app)}
<div
class="flex items-center px-3 py-2 bg-surface border-b text-xs font-semibold gap-2 justify-between"
>
<div class="flex flex-row items-center gap-2">
<Popover>
<svelte:fragment slot="text">
{#snippet text()}
<div class="flex flex-row gap-1"> Back to table component </div>
</svelte:fragment>
{/snippet}
<Button
iconOnly
startIcon={{
@@ -238,7 +247,7 @@
: 'Data source'}
id={'component-input'}
>
<svelte:fragment slot="action">
{#snippet action()}
<div class="flex flex-row gap-1 justify-center items-center">
<DocLink
docLink={'https://www.windmill.dev/docs/apps/app-runnable-panel#creating-a-runnable'}
@@ -252,7 +261,7 @@
{`${component.id}`}
</div>
</div>
</svelte:fragment>
{/snippet}
{#if componentSettings.item.data.componentInput}
<ComponentInputTypeEditor
@@ -314,7 +323,7 @@
id={component.id}
bind:componentInput={componentSettings.item.data.componentInput}
/>
<a class="text-2xs" on:click={transformToFrontend} href={undefined}>
<a class="text-2xs" onclick={transformToFrontend} href={undefined}>
transform to a frontend script
</a>
{:else if componentSettings.item.data.componentInput?.type === 'runnable' && component.componentInput !== undefined}
@@ -470,17 +479,19 @@
{#if Object.keys(ccomponents[component.type]?.customCss ?? {}).length > 0}
<PanelSection title="Styling">
<div slot="action" class="flex justify-end flex-wrap gap-1">
<Button
color="light"
size="xs"
variant="border"
startIcon={{ icon: ChevronLeft }}
on:click={() => secondaryMenuLeft.toggle(StylePanel, { type: 'style' })}
>
Show
</Button>
</div>
{#snippet action()}
<div class="flex justify-end flex-wrap gap-1">
<Button
color="light"
size="xs"
variant="border"
startIcon={{ icon: ChevronLeft }}
on:click={() => secondaryMenuLeft.toggle(StylePanel, { type: 'style' })}
>
Show
</Button>
</div>
{/snippet}
<div class="flex gap-2 items-center flex-wrap">
<div class="!text-2xs">Full height</div>
{#if componentSettings?.item?.[12]?.fullHeight !== undefined}
@@ -525,20 +536,22 @@
{#if duplicateMoveAllowed}
<PanelSection title="Copy/Move">
<div slot="action">
<Button
size="xs"
color="red"
variant="border"
on:click={removeGridElement}
shortCut={{
key: isMac() ? getModifierKey() + 'Del' : 'Del',
withoutModifier: true
}}
>
Delete
</Button>
</div>
{#snippet action()}
<div>
<Button
size="xs"
color="red"
variant="border"
on:click={removeGridElement}
shortCut={{
key: isMac() ? getModifierKey() + 'Del' : 'Del',
withoutModifier: true
}}
>
Delete
</Button>
</div>
{/snippet}
<div class="overflow-auto grid grid-cols-2 gap-1 text-tertiary">
<div>
@@ -47,7 +47,7 @@
function addGridItemContext(parentId: string) {
if (!parentId) return
const gridItem = findGridItem($app, parentId)
const gridItem = findGridItem(app, parentId)
addParentContextVariable(gridItem)
}
@@ -62,7 +62,7 @@
function findParentsContextVariables(id: string): void {
if (!id) return
const allParents = dfs($app.grid, id, $app.subgrids ?? {})
const allParents = dfs(app.grid, id, app.subgrids ?? {})
if (!allParents) return
@@ -21,8 +21,8 @@
export function removeGridElement() {
const id = $selectedComponent?.[0]
const componentSetting = findComponentSettings($app, id)
push(history, $app)
const componentSetting = findComponentSettings(app, id)
push(history, app)
const onDeleteComponentControl = id ? $componentControl[id]?.onDelete : undefined
if (onDeleteComponentControl) {
@@ -44,7 +44,7 @@
$selectedComponent = undefined
$focusedGrid = undefined
if (componentSetting?.item && !noGrid) {
let ids = deleteGridItem($app, componentSetting?.item.data, componentSetting?.parent)
let ids = deleteGridItem(app, componentSetting?.item.data, componentSetting?.parent)
for (const key of ids) {
delete $runnableComponents[key]
}
@@ -53,7 +53,7 @@
if (componentSetting?.item?.data?.id) {
delete $runnableComponents[componentSetting?.item?.data?.id]
}
$app = $app
// $app = $app
$runnableComponents = $runnableComponents
onDelete?.()
@@ -13,24 +13,32 @@
import { deleteGridItem } from '../appUtils'
import type { AppComponent } from '../component'
export let conditions: RichConfiguration[] = []
export let component: AppComponent
interface Props {
conditions?: RichConfiguration[]
component: AppComponent
}
let items = conditions.slice(0, -1).map((condition, index) => {
return { value: condition, id: generateRandomString(), originalIndex: index }
let { conditions = $bindable([]), component = $bindable() }: Props = $props()
let items = $state(
conditions.slice(0, -1).map((condition, index) => {
return { value: condition, id: generateRandomString(), originalIndex: index }
})
)
$effect.pre(() => {
conditions = items
.map((item) => item.value)
.concat([
{
type: 'evalv2',
expr: 'true',
fieldType: 'boolean',
connections: []
}
])
})
$: conditions = items
.map((item) => item.value)
.concat([
{
type: 'evalv2',
expr: 'true',
fieldType: 'boolean',
connections: []
}
])
const { app, runnableComponents, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
@@ -49,7 +57,7 @@
const newSubgrids = {}
for (let i = 0; i < items.length; i++) {
newSubgrids[`${component.id}-${i}`] =
$app!.subgrids![`${component.id}-${items[i].originalIndex}`] ?? []
app!.subgrids![`${component.id}-${items[i].originalIndex}`] ?? []
}
// update originalIndex
@@ -57,11 +65,10 @@
item.originalIndex = i
})
$app!.subgrids = {
...$app!.subgrids,
app!.subgrids = {
...app!.subgrids,
...newSubgrids
}
$app = $app
} // $app = $app
tick().then(() => {
const targetIndex = items.findIndex((i) => i.id === e.detail.info.id)
@@ -72,8 +79,8 @@
function deleteSubgrid(index: number) {
let subgrid = `${component.id}-${index}`
for (const item of $app!.subgrids![subgrid]) {
const components = deleteGridItem($app, item.data, subgrid)
for (const item of app!.subgrids![subgrid]) {
const components = deleteGridItem(app, item.data, subgrid)
for (const key in components) {
delete $runnableComponents[key]
}
@@ -81,7 +88,7 @@
$runnableComponents = $runnableComponents
for (let i = index; i < items.length; i++) {
$app!.subgrids![`${component.id}-${i}`] = $app!.subgrids![`${component.id}-${i + 1}`]
app!.subgrids![`${component.id}-${i}`] = app!.subgrids![`${component.id}-${i + 1}`]
}
// Remove the corresponding item from the items array
@@ -94,21 +101,20 @@
})
items = nitems
delete $app!.subgrids![`${component.id}-${items.length + 1}`]
$app = $app
delete app!.subgrids![`${component.id}-${items.length + 1}`] // $app = $app
}
function addCondition(): void {
const numberOfConditions = conditions.length
if (!$app.subgrids) {
$app.subgrids = {}
if (!app.subgrids) {
app.subgrids = {}
}
$app.subgrids[`${component.id}-${numberOfConditions}`] =
$app.subgrids[`${component.id}-${numberOfConditions - 1}`]
app.subgrids[`${component.id}-${numberOfConditions}`] =
app.subgrids[`${component.id}-${numberOfConditions - 1}`]
$app.subgrids[`${component.id}-${numberOfConditions - 1}`] = []
app.subgrids[`${component.id}-${numberOfConditions - 1}`] = []
const newCondition: AppInputSpec<'boolean', boolean> = {
type: 'evalv2',
@@ -141,8 +147,8 @@
flipDurationMs: 200,
dropTargetStyle: {}
}}
on:consider={handleConsider}
on:finalize={handleFinalize}
onconsider={handleConsider}
onfinalize={handleFinalize}
>
{#each items as item, index (item.id)}
{@const condition = item.value}
@@ -168,14 +174,14 @@
</div>
<div class="flex flex-col justify-center gap-2">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div on:click={() => deleteSubgrid(index)}>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div onclick={() => deleteSubgrid(index)}>
<X size={16} />
</div>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div use:dragHandle class="w-4 h-4 handle" aria-label="drag-handle">
<GripVertical size={16} />
</div>
@@ -41,8 +41,7 @@
delete $app!.subgrids![`${component.id}-${panes.length}`]
panes = panes
component.numberOfSubgrids = panes.length
$app = $app
component.numberOfSubgrids = panes.length // $app = $app
}
</script>
@@ -1,4 +1,7 @@
<script lang="ts">
import { createBubbler, stopPropagation } from 'svelte/legacy'
const bubble = createBubbler()
import Button from '$lib/components/common/button/Button.svelte'
import CloseButton from '$lib/components/common/CloseButton.svelte'
import { getContext, tick } from 'svelte'
@@ -11,27 +14,40 @@
import { GripVertical, Plus } from 'lucide-svelte'
import GridTabDisabled from './GridTabDisabled.svelte'
export let tabs: string[] = []
export let disabledTabs: RichConfiguration[] = []
export let canDisableTabs: boolean = false
export let word: string = 'Tab'
export let component: AppComponent
$: if (disabledTabs == undefined) {
disabledTabs = [
{ type: 'static', value: false, fieldType: 'boolean' },
{ type: 'static', value: false, fieldType: 'boolean' }
]
interface Props {
tabs?: string[]
disabledTabs?: RichConfiguration[]
canDisableTabs?: boolean
word?: string
component: AppComponent
}
let items = tabs.map((tab, index) => {
return { value: tab, id: generateRandomString(), originalIndex: index }
let {
tabs = $bindable([]),
disabledTabs = $bindable([]),
canDisableTabs = false,
word = 'Tab',
component = $bindable()
}: Props = $props()
$effect.pre(() => {
if (disabledTabs == undefined) {
disabledTabs = [
{ type: 'static', value: false, fieldType: 'boolean' },
{ type: 'static', value: false, fieldType: 'boolean' }
]
}
})
$: tabs = items.map((item) => item.value)
let items = $state(
tabs.map((tab, index) => {
return { value: tab, id: generateRandomString(), originalIndex: index }
})
)
$effect.pre(() => {
tabs = items.map((item) => item.value)
})
const { app, runnableComponents, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
@@ -39,11 +55,11 @@
function addTab() {
const numberOfTabs = items.length
if (!$app.subgrids) {
$app.subgrids = {}
if (!app.subgrids) {
app.subgrids = {}
}
$app.subgrids[`${component.id}-${numberOfTabs}`] = []
app.subgrids[`${component.id}-${numberOfTabs}`] = []
items = [
...items,
{
@@ -59,8 +75,8 @@
function deleteSubgrid(index: number) {
let subgrid = `${component.id}-${index}`
for (const item of $app!.subgrids![subgrid]) {
const components = deleteGridItem($app, item.data, subgrid)
for (const item of app!.subgrids![subgrid]) {
const components = deleteGridItem(app, item.data, subgrid)
for (const key in components) {
delete $runnableComponents[key]
}
@@ -68,7 +84,7 @@
$runnableComponents = $runnableComponents
for (let i = index; i < items.length - 1; i++) {
$app!.subgrids![`${component.id}-${i}`] = $app!.subgrids![`${component.id}-${i + 1}`]
app!.subgrids![`${component.id}-${i}`] = app!.subgrids![`${component.id}-${i + 1}`]
}
// Remove the corresponding item from the items array
@@ -84,8 +100,7 @@
})
items = items
delete $app!.subgrids![`${component.id}-${items.length}`]
$app = $app
delete app!.subgrids![`${component.id}-${items.length}`] // $app = $app
}
function handleConsider(e: CustomEvent): void {
@@ -110,7 +125,7 @@
const newSubgrids = {}
for (let i = 0; i < items.length; i++) {
newSubgrids[`${component.id}-${i}`] =
$app!.subgrids![`${component.id}-${items[i].originalIndex}`] ?? []
app!.subgrids![`${component.id}-${items[i].originalIndex}`] ?? []
}
const newDisabledTabs: RichConfiguration[] = []
@@ -124,11 +139,10 @@
item.originalIndex = i
})
$app!.subgrids = {
...$app!.subgrids,
app!.subgrids = {
...app!.subgrids,
...newSubgrids
}
$app = $app
} // $app = $app
tick().then(() => {
const targetIndex = items.findIndex((i) => i.id === e.detail.info.id)
@@ -149,15 +163,15 @@
flipDurationMs: 200,
dropTargetStyle: {}
}}
on:consider={handleConsider}
on:finalize={handleFinalize}
onconsider={handleConsider}
onfinalize={handleFinalize}
>
{#each items as item, index (item.id)}
<div class="border rounded-md p-2 mb-2 bg-surface">
<div class="w-full flex flex-row gap-2 items-center relative my-1">
<input
on:keydown|stopPropagation
on:input={(e) => updateItemValue(index, e)}
onkeydown={stopPropagation(bubble('keydown'))}
oninput={(e) => updateItemValue(index, e)}
type="text"
bind:value={items[index].value}
/>
@@ -166,9 +180,9 @@
</div>
<div class="flex flex-col justify-center gap-2">
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div use:dragHandle class="handle w-4 h-4" aria-label="drag-handle">
<GripVertical size={16} />
</div>
@@ -97,7 +97,7 @@
}
})
const { connectingInput, app, workspace } = getContext<AppViewerContext>('AppViewerContext')
const { connectingInput, workspace } = getContext<AppViewerContext>('AppViewerContext')
const dispatch = createEventDispatcher()
@@ -129,8 +129,7 @@
{ componentId: connection.componentId, id: connection.path.split('.')[0].split('[')[0] }
]
}
evalV2editor?.setCode(expr)
$app = $app
evalV2editor?.setCode(expr) // $app = $app
}
function closeConnection() {
@@ -13,8 +13,7 @@
export let components: (BaseAppComponent & ButtonComponent)[]
export let id: string
const { selectedComponent, app, errorByComponent } =
getContext<AppViewerContext>('AppViewerContext')
const { selectedComponent, errorByComponent } = getContext<AppViewerContext>('AppViewerContext')
function addComponent() {
const actionId = getNextId(components.map((x) => x.id.split('_')[1]))
@@ -23,8 +22,7 @@
...appComponentFromType('buttoncomponent')(`${id}_${actionId}`),
recomputeIds: []
}
components = [...components, newComponent]
$app = $app
components = [...components, newComponent] // $app = $app
}
function deleteComponent(cid: string) {
@@ -32,8 +30,7 @@
delete $errorByComponent[cid]
$selectedComponent = [id]
$app = $app
$selectedComponent = [id] // $app = $app
}
</script>
@@ -9,20 +9,25 @@
const { app } = getContext<AppViewerContext>('AppViewerContext')
export let appInput: ResultAppInput
export let appComponent: AppComponent
$: if (appInput.autoRefresh === undefined) {
appInput.autoRefresh = true
interface Props {
appInput: ResultAppInput
appComponent: AppComponent
}
let { appInput = $bindable(), appComponent = $bindable() }: Props = $props()
$effect.pre(() => {
if (appInput.autoRefresh === undefined) {
appInput.autoRefresh = true
}
})
function detach() {
if (appInput.runnable?.type === 'runnableByName' && appInput.runnable.inlineScript) {
$app.unusedInlineScripts.push({
app.unusedInlineScripts.push({
name: appInput.runnable.name,
inlineScript: appInput.runnable.inlineScript
})
$app = $app
}) // $app = $app
appInput = clearResultAppInput(appInput)
}
}
@@ -31,7 +36,7 @@
appInput = clearResultAppInput(appInput)
}
$: {
$effect.pre(() => {
if (appInput.recomputeOnInputChanged === undefined) {
appInput.recomputeOnInputChanged = true
}
@@ -40,11 +45,13 @@
appInput.recomputeOnInputChanged = !appInput.doNotRecomputeOnInputChanged
appInput.doNotRecomputeOnInputChanged = undefined
}
}
})
$: hasScript =
let hasScript = $derived(
appInput?.runnable?.type === 'runnableByPath' ||
(appInput?.runnable?.type === 'runnableByName' && appInput.runnable?.inlineScript !== undefined)
(appInput?.runnable?.type === 'runnableByName' &&
appInput.runnable?.inlineScript !== undefined)
)
function getActions(_hasScript: boolean): ActionType[] {
return [
@@ -56,7 +63,7 @@
color: 'light',
callback: detach
}
] as const)
] as const)
: []),
{
label: 'Clear',
@@ -67,7 +74,7 @@
]
}
$: actions = getActions(hasScript)
let actions = $derived(getActions(hasScript))
</script>
<ComponentScriptSettings bind:appInput bind:appComponent {hasScript} {actions} />
@@ -20,28 +20,29 @@
const { app, cssEditorOpen, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let component: AppComponent | undefined
$: {
const newComponent = findComponentSettings($app, $selectedComponent?.[0])?.item?.data
let component: AppComponent | undefined = $state()
$effect.pre(() => {
const newComponent = findComponentSettings(app, $selectedComponent?.[0])?.item?.data
if (component != newComponent) {
component = newComponent
}
}
})
let tab: 'local' | 'global' = 'local'
let overrideGlobalCSS: (() => void) | undefined = undefined
let overrideLocalCSS: (() => void) | undefined = undefined
$: type = component?.type
let migrationModal: CssMigrationModal | undefined = undefined
let tab: 'local' | 'global' = $state('local')
let overrideGlobalCSS: (() => void) | undefined = $state(undefined)
let overrideLocalCSS: (() => void) | undefined = $state(undefined)
let type = $derived(component?.type)
let migrationModal: CssMigrationModal | undefined = $state(undefined)
$: customCssByComponentType =
component?.type && $app.css
? Object.entries($app.css[component.type] || {}).map(([id, v]) => ({
let customCssByComponentType = $derived(
component?.type && app.css
? Object.entries(app.css[component.type] || {}).map(([id, v]) => ({
id,
forceStyle: v?.style != undefined,
forceClass: v?.['class'] != undefined
}))
: undefined
)
function copyLocalToGlobal(name: string, value: ComponentCssProperty | undefined) {
if (!value) {
@@ -51,18 +52,16 @@
if (!type) return
if (hasStyleValue($app.css?.[type]?.[name])) {
if (hasStyleValue(app.css?.[type]?.[name])) {
overrideGlobalCSS = () => {
$app.css![type]![name] = JSON.parse(JSON.stringify(value))
app.set($app)
app.css![type]![name] = JSON.parse(JSON.stringify(value))
}
} else {
if (!$app.css![type]) {
if (!app.css![type]) {
initGlobalCss()
}
$app.css![type]![name] = JSON.parse(JSON.stringify(value))
app.set($app)
app.css![type]![name] = JSON.parse(JSON.stringify(value))
sendUserToast('Global CSS copied')
}
}
@@ -75,11 +74,9 @@
if (hasStyleValue(value)) {
overrideLocalCSS = () => {
component!.customCss![id] = JSON.parse(JSON.stringify(value))
app.set($app)
}
} else {
component!.customCss![id] = JSON.parse(JSON.stringify(value))
app.set($app)
sendUserToast('Local CSS copied')
}
}
@@ -88,21 +85,20 @@
function initGlobalCss() {
// If the global css is not initialised, we initialise it.
// Should only happen once per app
if (!$app.css) {
$app.css = {}
if (!app.css) {
app.css = {}
}
// If the global css for this component type is not initialised, we initialise it.
// Should only happen once per component type
if (
$app.css &&
app.css &&
component &&
!$app.css[component.type]?.style &&
!app.css[component.type]?.style &&
components[component.type] &&
$app.css[component.type] === undefined
app.css[component.type] === undefined
) {
$app.css[component.type] = JSON.parse(JSON.stringify(components[component.type].customCss))
app.set($app)
app.css[component.type] = JSON.parse(JSON.stringify(components[component.type].customCss))
}
}
@@ -196,13 +192,12 @@
wmClass={getSelector(name)}
componentType={component.type}
bind:value={component.customCss[name]}
on:change={() => app.set($app)}
shouldDisplayRight={hasStyleValue(component.customCss[name])}
on:right={() => {
copyLocalToGlobal(name, component?.customCss?.[name])
tab = 'global'
}}
overridding={hasStyleValue($app.css?.[component.type]?.[name]) &&
overridding={hasStyleValue(app.css?.[component.type]?.[name]) &&
hasStyleValue(component.customCss[name])}
/>
</div>
@@ -214,16 +209,16 @@
{#if type}
{#each customCssByComponentType ?? [] as { id, forceStyle, forceClass }}
<div class="w-full">
{#if $app.css && type && $app.css[type] && component?.customCss}
{#if app.css && type && app.css[type] && component?.customCss}
<CssPropertyWrapper
{forceStyle}
{forceClass}
{id}
bind:property={$app.css[type]}
bind:property={app.css[type]}
on:left={() => {
copyGlobalToLocal(
id,
component?.type ? $app?.css?.[component?.type]?.[id] : undefined
component?.type ? app?.css?.[component?.type]?.[id] : undefined
)
tab = 'local'
}}
@@ -1,4 +1,7 @@
<script lang="ts">
import { createBubbler } from 'svelte/legacy'
const bubble = createBubbler()
import { Badge } from '$lib/components/common'
import Button from '$lib/components/common/button/Button.svelte'
import { getNextId } from '$lib/components/flows/idUtils'
@@ -15,11 +18,15 @@
import TableActionsWizard from '$lib/components/wizards/TableActionsWizard.svelte'
import Alert from '$lib/components/common/alert/Alert.svelte'
export let components:
| (BaseAppComponent & (ButtonComponent | CheckboxComponent | SelectComponent))[]
| undefined
interface Props {
components:
| (BaseAppComponent & (ButtonComponent | CheckboxComponent | SelectComponent))[]
| undefined
actionsOrder?: RichConfiguration | undefined
id: string
}
export let actionsOrder: RichConfiguration | undefined = undefined
let { components = $bindable(), actionsOrder = $bindable(undefined), id }: Props = $props()
// Migration code:
onMount(() => {
@@ -28,16 +35,17 @@
}
})
let items =
let items = $state(
components?.map((tab, index) => {
return { value: tab, id: generateRandomString(), originalIndex: index }
}) ?? []
)
$: components = items.map((item) => item.value)
$effect.pre(() => {
components = items.map((item) => item.value)
})
export let id: string
const { selectedComponent, app, errorByComponent, hoverStore } =
const { selectedComponent, errorByComponent, hoverStore } =
getContext<AppViewerContext>('AppViewerContext')
function addComponent(typ: 'buttoncomponent' | 'checkboxcomponent' | 'selectcomponent') {
@@ -68,8 +76,7 @@
}
]
components = [...components, newComponent]
$app = $app
components = [...components, newComponent] // $app = $app
}
function deleteComponent(cid: string, index: number) {
@@ -79,8 +86,7 @@
components = components.filter((x) => x.id !== cid)
delete $errorByComponent[cid]
$selectedComponent = [id]
$app = $app
$selectedComponent = [id] // $app = $app
// Remove the corresponding item from the items array
items = items.filter((item) => item.originalIndex !== index)
}
@@ -99,9 +105,9 @@
{#if components}
<PanelSection title={`Table Actions`}>
<svelte:fragment slot="action">
{#snippet action()}
<TableActionsWizard bind:actionsOrder selectedId={$selectedComponent?.[0] ?? ''} {components}>
<svelte:fragment slot="trigger">
{#snippet trigger()}
<Button
color="light"
size="xs2"
@@ -113,9 +119,9 @@
<ListOrdered size={16} />
</div>
</Button>
</svelte:fragment>
{/snippet}
</TableActionsWizard>
</svelte:fragment>
{/snippet}
{#if components.length == 0}
<span class="text-xs text-tertiary">No action buttons</span>
{/if}
@@ -126,27 +132,27 @@
flipDurationMs: 200,
dropTargetStyle: {}
}}
on:consider={handleConsider}
on:finalize={handleFinalize}
onconsider={handleConsider}
onfinalize={handleFinalize}
>
{#each items as item, index (item.id)}
{@const component = items[index].value}
<div animate:flip={{ duration: 200 }} class="flex flex-row gap-2 items-center mb-2">
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
<div
class={classNames(
'w-full text-xs text-semibold truncate py-1.5 px-2 cursor-pointer justify-between flex items-center border rounded-md',
'bg-surface hover:bg-surface-hover focus:border-primary text-secondary'
)}
on:click={() => {
onclick={() => {
$selectedComponent = [component.id]
}}
on:mouseover={() => {
onmouseover={() => {
$hoverStore = component.id
}}
on:keypress
onkeypress={bubble('keypress')}
>
<div class="flex flex-row gap-2 items-center">
<Badge color="dark-indigo">
@@ -2,18 +2,36 @@
import { classNames } from '$lib/utils'
import Tooltip from '../../../../Tooltip.svelte'
export let title: string
export let noPadding: boolean = false
export let fullHeight: boolean = true
export let titlePadding: string = ''
export let tooltip = ''
export let documentationLink: string | undefined = undefined
export let id: string | undefined = undefined
interface Props {
title: string
noPadding?: boolean
fullHeight?: boolean
titlePadding?: string
tooltip?: string
documentationLink?: string | undefined
id?: string | undefined
class?: string
action?: import('svelte').Snippet
children?: import('svelte').Snippet
}
let {
title,
noPadding = false,
fullHeight = true,
titlePadding = '',
tooltip = '',
documentationLink = undefined,
id = undefined,
class: className = '',
action,
children
}: Props = $props()
</script>
<div
class={classNames(
$$props.class,
className,
'flex flex-col gap-2 items-start',
noPadding ? '' : 'p-3',
fullHeight ? 'h-full' : ''
@@ -31,7 +49,7 @@
</Tooltip>
{/if}
</div>
<slot name="action" />
{@render action?.()}
</div>
<slot />
{@render children?.()}
</div>
@@ -57,10 +57,10 @@
function addSubGrid() {
const numberOfPanes = nodes.length
if (!$app.subgrids) {
$app.subgrids = {}
if (!app.subgrids) {
app.subgrids = {}
}
$app.subgrids[`${component.id}-${numberOfPanes}`] = []
app.subgrids[`${component.id}-${numberOfPanes}`] = []
component.numberOfSubgrids = nodes.length + 1
}
@@ -68,26 +68,25 @@
function deleteSubgrid(index: number) {
let subgrid = `${component.id}-${index}`
if (!$app.subgrids![subgrid]) {
if (!app.subgrids![subgrid]) {
return
}
for (const item of $app!.subgrids![subgrid]) {
const components = deleteGridItem($app, item.data, subgrid)
for (const item of app!.subgrids![subgrid]) {
const components = deleteGridItem(app, item.data, subgrid)
for (const key in components) {
delete $runnableComponents[key]
}
}
$runnableComponents = $runnableComponents
for (let i = index; i < nodes.length - 1; i++) {
$app!.subgrids![`${component.id}-${i}`] = $app!.subgrids![`${component.id}-${i + 1}`]
app!.subgrids![`${component.id}-${i}`] = app!.subgrids![`${component.id}-${i + 1}`]
}
nodes.splice(index, 1)
delete $app!.subgrids![`${component.id}-${nodes.length}`]
delete app!.subgrids![`${component.id}-${nodes.length}`]
nodes = nodes
component.numberOfSubgrids = nodes.length
$app = $app
component.numberOfSubgrids = nodes.length // $app = $app
}
function nodeCallbackHandler(
@@ -11,7 +11,7 @@
function applyConnection(connection: InputConnection) {
componentInput.connection = connection
$app = $app
// $app = $app
}
</script>
@@ -33,7 +33,7 @@
if (componentInput.type === 'connected') {
componentInput.connection = undefined
}
$app = $app
// $app = $app
}}
>
Disconnect
@@ -126,9 +126,9 @@
bind:this={editor}
lang="javascript"
bind:code={
() => componentInput.expr ?? '',
() => componentInput?.expr ?? '',
(e) => {
if (componentInput.expr != e) {
if (componentInput?.expr != e) {
componentInput.expr = e
}
}
@@ -7,9 +7,13 @@
import SelectedRunnable from '../SelectedRunnable.svelte'
import type { AppEditorContext, AppViewerContext } from '$lib/components/apps/types'
export let appInput: ResultAppInput
export let defaultUserInput = false
export let appComponent: AppComponent
interface Props {
appInput: ResultAppInput
defaultUserInput?: boolean
appComponent: AppComponent
}
let { appInput = $bindable(), defaultUserInput = false, appComponent }: Props = $props()
const { selectedComponentInEditor } = getContext<AppEditorContext>('AppEditorContext')
const { app } = getContext<AppViewerContext>('AppViewerContext')
@@ -34,7 +38,7 @@
<SelectedRunnable {appComponent} bind:appInput />
{:else if appInput !== undefined}
<RunnableSelector
unusedInlineScripts={$app.unusedInlineScripts}
unusedInlineScripts={app.unusedInlineScripts}
hideCreateScript={appComponent.type === 'flowstatuscomponent'}
onlyFlow={appComponent.type === 'flowstatuscomponent'}
{defaultUserInput}
@@ -1,4 +1,7 @@
<script lang="ts">
import { run, createBubbler, stopPropagation } from 'svelte/legacy';
const bubble = createBubbler();
import type { InputType, StaticInput, StaticOptions } from '../../../inputType'
import ArrayStaticInputEditor from '../ArrayStaticInputEditor.svelte'
import ResourcePicker from '$lib/components/ResourcePicker.svelte'
@@ -26,32 +29,46 @@
import S3FilePicker from '$lib/components/S3FilePicker.svelte'
import FileUpload from '$lib/components/common/fileUpload/FileUpload.svelte'
export let componentInput: StaticInput<any> | undefined
export let fieldType: InputType | undefined = undefined
export let subFieldType: InputType | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
export let placeholder: string | undefined = undefined
export let format: string | undefined = undefined
export let id: string | undefined
interface Props {
componentInput: StaticInput<any> | undefined;
fieldType?: InputType | undefined;
subFieldType?: InputType | undefined;
selectOptions?: StaticOptions['selectOptions'] | undefined;
placeholder?: string | undefined;
format?: string | undefined;
id: string | undefined;
}
let {
componentInput = $bindable(),
fieldType = undefined,
subFieldType = undefined,
selectOptions = undefined,
placeholder = undefined,
format = undefined,
id
}: Props = $props();
const appContext = getContext<AppViewerContext>('AppViewerContext')
$: componentInput && appContext?.onchange?.()
let s3FileUploadRawMode = false
let s3FilePicker: S3FilePicker | undefined = undefined
run(() => {
componentInput && appContext?.onchange?.()
});
let s3FileUploadRawMode = $state(false)
let s3FilePicker: S3FilePicker | undefined = $state(undefined)
</script>
{#key subFieldType}
{#if componentInput?.type === 'static'}
{#if fieldType === 'number' || fieldType === 'integer'}
<input on:keydown|stopPropagation type="number" bind:value={componentInput.value} />
<input onkeydown={stopPropagation(bubble('keydown'))} type="number" bind:value={componentInput.value} />
{:else if fieldType === 'textarea'}
<textarea use:autosize on:keydown|stopPropagation bind:value={componentInput.value}
<textarea use:autosize onkeydown={stopPropagation(bubble('keydown'))} bind:value={componentInput.value}
></textarea>
{:else if fieldType === 'date'}
<input on:keydown|stopPropagation type="date" bind:value={componentInput.value} />
<input onkeydown={stopPropagation(bubble('keydown'))} type="date" bind:value={componentInput.value} />
{:else if fieldType === 'time'}
<input on:keydown|stopPropagation type="time" bind:value={componentInput.value} />
<input onkeydown={stopPropagation(bubble('keydown'))} type="time" bind:value={componentInput.value} />
{:else if fieldType === 'datetime'}
<DateTimeInput bind:value={componentInput.value} />
{:else if fieldType === 'boolean'}
@@ -60,7 +77,7 @@
{#if subFieldType === 'db-table'}
<DBTableSelect bind:componentInput {selectOptions} {id} />
{:else}
<select on:keydown|stopPropagation bind:value={componentInput.value}>
<select onkeydown={stopPropagation(bubble('keydown'))} bind:value={componentInput.value}>
{#each selectOptions ?? [] as option}
{#if typeof option == 'string'}
<option value={option}>
@@ -114,7 +131,7 @@
{#if componentInput?.value && typeof componentInput?.value == 'object' && 'label' in componentInput?.value && (componentInput.value?.['value'] == undefined || typeof componentInput.value?.['value'] == 'string')}
<div class="flex flex-col gap-1 w-full">
<input
on:keydown|stopPropagation
onkeydown={stopPropagation(bubble('keydown'))}
placeholder="Label"
type="text"
bind:value={componentInput.value['label']}
@@ -257,13 +274,15 @@
/>
<div class="absolute top-1 right-1">
<AgGridWizard bind:value={componentInput.value}>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
</svelte:fragment>
{#snippet trigger()}
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
{/snippet}
</AgGridWizard>
</div>
</div>
@@ -279,13 +298,15 @@
/>
<div class="absolute top-1 right-1">
<DBExplorerWizard bind:value={componentInput.value}>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
</svelte:fragment>
{#snippet trigger()}
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
{/snippet}
</DBExplorerWizard>
</div>
</div>
@@ -300,13 +321,15 @@
/>
<div class="absolute top-1 right-1">
<TableColumnWizard bind:column={componentInput.value}>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
</svelte:fragment>
{#snippet trigger()}
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
{/snippet}
</TableColumnWizard>
</div>
</div>
@@ -321,13 +344,15 @@
/>
<div class="absolute top-1 right-1">
<PlotlyWizard bind:value={componentInput.value} on:remove>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
</svelte:fragment>
{#snippet trigger()}
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
{/snippet}
</PlotlyWizard>
</div>
</div>
@@ -342,13 +367,15 @@
/>
<div class="absolute top-1 right-1">
<ChartJSWizard bind:value={componentInput.value} on:remove>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
</svelte:fragment>
{#snippet trigger()}
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
{/snippet}
</ChartJSWizard>
</div>
</div>
@@ -364,13 +391,15 @@
<div class="absolute top-1 right-1">
<AgChartWizard bind:value={componentInput.value} on:remove>
<svelte:fragment slot="trigger">
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
</svelte:fragment>
{#snippet trigger()}
<Button color="light" size="xs2" nonCaptureEvent={true}>
<div class="flex flex-row items-center gap-2 text-xs font-normal">
<Settings size={16} />
</div>
</Button>
{/snippet}
</AgChartWizard>
</div>
</div>
@@ -401,7 +430,7 @@
<textarea
rows="1"
use:autosize
on:keydown|stopPropagation
onkeydown={stopPropagation(bubble('keydown'))}
placeholder={placeholder ?? 'Static value'}
bind:value={componentInput.value}
class="!pr-12"
@@ -4,11 +4,15 @@
import { allItems } from '$lib/components/apps/utils'
import { getContext } from 'svelte'
export let componentInput: StaticInput<{ id: string; index: number }>
interface Props {
componentInput: StaticInput<{ id: string; index: number }>
}
let { componentInput = $bindable() }: Props = $props()
const { app } = getContext<AppViewerContext>('AppViewerContext')
const tabComponents = allItems($app.grid, $app.subgrids).filter(
const tabComponents = allItems(app.grid, app.subgrids).filter(
(component) =>
component.data.type === 'tabscomponent' ||
component.data.type === 'conditionalwrapper' ||
@@ -4,24 +4,33 @@
import { classNames, itemsExists } from '$lib/utils'
import { Plus, X } from 'lucide-svelte'
import { Button } from '$lib/components/common'
import { getContext } from 'svelte'
import { getContext, untrack } from 'svelte'
import type { App, AppViewerContext, InlineScript } from '$lib/components/apps/types'
import Tooltip from '$lib/components/Tooltip.svelte'
import { deepEqual } from 'fast-equals'
import { getAllGridItems } from '../../../appUtils'
export let triggerEvents: string[] = []
export let inlineScript: InlineScript | undefined = undefined
export let isFrontend: boolean = false
export let dependencies: string[] = []
export let shoudlDisplayChangeEvents: boolean = false
export let id: string
interface Props {
triggerEvents?: string[]
inlineScript?: InlineScript | undefined
isFrontend?: boolean
dependencies?: string[]
shoudlDisplayChangeEvents?: boolean
id: string
}
let {
triggerEvents = [],
inlineScript = $bindable(undefined),
isFrontend = false,
dependencies = [],
shoudlDisplayChangeEvents = false,
id
}: Props = $props()
const { connectingInput, app, stateId } = getContext<AppViewerContext>('AppViewerContext')
let onSuccessEvents: string[] = []
$: computeOnSuccessEvents($app, id)
let onSuccessEvents: string[] = $state([])
function computeOnSuccessEvents(app: App, _id: string) {
const nr: string[] = []
@@ -47,16 +56,6 @@
})
onSuccessEvents = nr
}
$: changeEvents = isFrontend
? inlineScript?.refreshOn
? inlineScript.refreshOn.map((x) => `${x.id}.${x.key}`)
: []
: dependencies
$: hasNoTriggers =
triggerEvents.length === 0 &&
(changeEvents.length === 0 || !shoudlDisplayChangeEvents) &&
onSuccessEvents.length == 0
const badgeClass = 'inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium border'
const colors = {
@@ -84,9 +83,26 @@
} else if (!itemsExists(inlineScript.refreshOn, refresh)) {
inlineScript.refreshOn = [...inlineScript.refreshOn, refresh]
}
inlineScript = inlineScript
$app = $app
inlineScript = inlineScript // $app = $app
}
$effect.pre(() => {
;[app, id]
untrack(() => {
computeOnSuccessEvents(app, id)
})
})
let changeEvents = $derived(
isFrontend
? inlineScript?.refreshOn
? inlineScript.refreshOn.map((x) => `${x.id}.${x.key}`)
: []
: dependencies
)
let hasNoTriggers = $derived(
triggerEvents.length === 0 &&
(changeEvents.length === 0 || !shoudlDisplayChangeEvents) &&
onSuccessEvents.length == 0
)
</script>
{#if hasNoTriggers}
@@ -118,7 +134,7 @@
{#if isFrontend}
<button
class="bg-blue-300 ml-2 p-0.5 rounded-md hover:bg-blue-400 cursor-pointer"
on:click={() => {
onclick={() => {
if (inlineScript?.refreshOn) {
inlineScript.refreshOn = inlineScript.refreshOn.filter(
(x) => `${x.id}.${x.key}` !== changeEvent
@@ -173,7 +189,7 @@
'p-0.5 rounded-md hover:bg-blue-400 cursor-pointer !text-2xs text-secondary',
badgeClass
)}
on:click={() => {
onclick={() => {
if (inlineScript) {
if (!itemsExists(inlineScript.refreshOn, suggestion)) {
inlineScript.refreshOn = [...(inlineScript.refreshOn ?? []), suggestion]
@@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
import { writable } from 'svelte/store'
const componentDraggedIdStore = writable<string | undefined>(undefined)
@@ -17,7 +17,7 @@
import { getContainerHeight } from './utils/container'
import { moveItem, getItemById, specifyUndefinedColumns } from './utils/item'
import { onMount, createEventDispatcher, getContext } from 'svelte'
import { onMount, createEventDispatcher, getContext, untrack } from 'svelte'
import { getColumn, throttle } from './utils/other'
import MoveResize from './MoveResize.svelte'
import type { FilledItem } from './types'
@@ -41,33 +41,49 @@
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
export let items: FilledItem<T>[]
export let rowHeight: number = ROW_HEIGHT
interface Props {
items: FilledItem<T>[]
rowHeight?: number
gap?: any
throttleUpdate?: number
throttleResize?: number
selectedIds: string[] | undefined
allIdsInPath: string[] | undefined
containerWidth?: number | undefined
scroller?: HTMLElement | undefined
sensor?: number
root?: boolean
parentWidth?: number | undefined
disableMove?: boolean
children?: import('svelte').Snippet<[any]>
}
export let gap = [ROW_GAP_X, ROW_GAP_Y]
export let throttleUpdate = 100
export let throttleResize = 100
export let selectedIds: string[] | undefined
export let allIdsInPath: string[] | undefined
export let containerWidth: number | undefined = undefined
export let scroller: HTMLElement | undefined = undefined
export let sensor = 20
export let root: boolean = false
export let parentWidth: number | undefined = undefined
export let disableMove: boolean = false
let {
items = $bindable(),
rowHeight = ROW_HEIGHT,
gap = [ROW_GAP_X, ROW_GAP_Y],
throttleUpdate = 100,
throttleResize = 100,
selectedIds,
allIdsInPath,
containerWidth = $bindable(undefined),
scroller = undefined,
sensor = 20,
root = false,
parentWidth = undefined,
disableMove = false,
children
}: Props = $props()
const cols = columnConfiguration
let getComputedCols: 3 | 12 | undefined =
$app.mobileViewOnSmallerScreens == false ? WIDE_GRID_COLUMNS : undefined
let container
let getComputedCols: 3 | 12 | undefined = $state(
app.mobileViewOnSmallerScreens == false ? WIDE_GRID_COLUMNS : undefined
)
let container = $state() as Element | undefined
$: [gapX, gapY] = gap
let xPerPx = 0
let xPerPx = $state(0)
let yPerPx = rowHeight
$: containerHeight = getContainerHeight(items, yPerPx, getComputedCols)
const onResize = throttle(() => {
items = specifyUndefinedColumns(items, getComputedCols, cols)
dispatch('resize', {
@@ -78,7 +94,7 @@
})
}, throttleUpdate)
let mounted = false
let mounted = $state(false)
onMount(() => {
const sizeObserver = new ResizeObserver((entries) => {
@@ -92,7 +108,7 @@
return
}
if ($app.mobileViewOnSmallerScreens != false || !getComputedCols) {
if (app.mobileViewOnSmallerScreens != false || !getComputedCols) {
getComputedCols = getColumn(parentWidth ?? width, cols)
}
xPerPx = width / getComputedCols!
@@ -119,10 +135,9 @@
return () => sizeObserver.disconnect()
})
let sortedItems: FilledItem<T>[] = []
$: sortedItems = smartCopy(items).sort((a, b) => a.id.localeCompare(b.id))
let sortedItems: FilledItem<T>[] = $state([])
let resizing: boolean = false
let resizing: boolean = $state(false)
function handleKeyUp(event) {
if ((event.key === 'Control' || event.key === 'Meta') && $isCtrlOrMetaPressedStore) {
@@ -142,9 +157,11 @@
? items.map((item) => {
return {
...item,
[getComputedCols as number]: structuredClone(item[getComputedCols as number])
[getComputedCols as number]: structuredClone(
$state.snapshot(item[getComputedCols as number])
)
}
})
})
: []
}
const updateMatrix = ({ detail }) => {
@@ -236,7 +253,7 @@
//let hiddenComponents = writable({})
let lastDetail: { isPointerUp: false; activate: false; id: string | undefined } | undefined =
undefined
$state(undefined)
const handleRepaint = ({ detail }) => {
if (!detail.isPointerUp) {
throttleMatrix({ detail })
@@ -269,13 +286,15 @@
}
}
let moveResizes: Record<string, MoveResize> = {}
let shadows: Record<string, { x: number; y: number; w: number; h: number } | undefined> = {}
let moveResizes: Record<string, MoveResize> = $state({})
let shadows: Record<string, { x: number; y: number; w: number; h: number } | undefined> = $state(
{}
)
export function handleMove({ detail }) {
Object.entries(moveResizes).forEach(([id, moveResize]) => {
if (selectedIds?.includes(id)) {
moveResize?.updateMove(structuredClone(detail.cordDiff), detail.eventY)
moveResize?.updateMove(structuredClone($state.snapshot(detail.cordDiff)), detail.eventY)
}
})
@@ -307,15 +326,15 @@
const div = document.getElementById(`component-${$overlappedStore}`)
const type = div?.getAttribute('data-componenttype')
if (!$app.subgrids) {
if (!app.subgrids) {
return
}
const index = type ? subGridIndexKey(type, $overlappedStore, $worldStore) : 0
items = $app.subgrids[`${$overlappedStore}-${index}`] ?? []
items = app.subgrids[`${$overlappedStore}-${index}`] ?? []
} else {
items = $app.grid ?? []
items = app.grid ?? []
}
if (!draggedItem) {
@@ -346,7 +365,7 @@
export function handleInitMove(id: string) {
$componentDraggedIdStore = id
$componentDraggedParentIdStore = findGridItemParentGrid($app, id)?.split('-')[0] ?? undefined
$componentDraggedParentIdStore = findGridItemParentGrid(app, id)?.split('-')[0] ?? undefined
Object.entries(moveResizes).forEach(([id, moveResize]) => {
if (selectedIds?.includes(id)) {
@@ -354,16 +373,24 @@
}
})
}
let [gapX, gapY] = $derived(gap)
let containerHeight = $derived(getContainerHeight(items, yPerPx, getComputedCols))
$effect(() => {
items
untrack(() => {
sortedItems = smartCopy(items).sort((a, b) => a.id.localeCompare(b.id))
})
})
</script>
<svelte:window
on:focus={() => {
onfocus={() => {
if ($isCtrlOrMetaPressedStore) {
$isCtrlOrMetaPressedStore = false
}
}}
on:keydown={handleKeyDown}
on:keyup={handleKeyUp}
onkeydown={handleKeyDown}
onkeyup={handleKeyUp}
/>
<div
@@ -482,7 +509,7 @@
width={xPerPx == 0
? 0
: Math.min(getComputedCols, item[getComputedCols] && item[getComputedCols].w) * xPerPx -
gapX * 2}
gapX * 2}
height={(item[getComputedCols] && item[getComputedCols].h) * yPerPx - gapY * 2}
top={(item[getComputedCols] && item[getComputedCols].y) * yPerPx + gapY}
left={(item[getComputedCols] && item[getComputedCols].x) * xPerPx + gapX}
@@ -499,13 +526,13 @@
{disableMove}
>
{#if item[getComputedCols]}
<slot
dataItem={item}
hidden={false}
overlapped={$overlappedStore}
moveMode={$isCtrlOrMetaPressedStore ? 'insert' : 'move'}
componentDraggedId={$componentDraggedIdStore}
/>
{@render children?.({
dataItem: item,
hidden: false,
overlapped: $overlappedStore,
moveMode: $isCtrlOrMetaPressedStore ? 'insert' : 'move',
componentDraggedId: $componentDraggedIdStore
})}
{/if}
</MoveResize>
{/if}
@@ -1,4 +1,6 @@
<script lang="ts">
import { preventDefault, stopPropagation } from 'svelte/legacy'
import { createEventDispatcher, getContext, onMount } from 'svelte'
import type { AppEditorContext, AppViewerContext } from '../types'
import { writable } from 'svelte/store'
@@ -13,33 +15,57 @@
const dispatch = createEventDispatcher()
export let sensor
export let width
export let height
export let left
export let top
interface Props {
sensor: any
width: any
height: any
left: any
top: any
id: any
container: any
xPerPx: any
yPerPx: any
gapX: any
gapY: any
item: any
cols: any
nativeContainer: any
onTop: any
shadow?: { x: number; y: number; w: number; h: number } | undefined
overlapped?: string | undefined
moveMode?: 'move' | 'insert'
type?: string | undefined
fakeShadow?: GridShadow | undefined
disableMove?: boolean
mounted?: boolean
children?: import('svelte').Snippet
}
export let id
export let container
export let xPerPx
export let yPerPx
export let gapX
export let gapY
export let item
export let cols
export let nativeContainer
export let onTop
export let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
export let overlapped: string | undefined = undefined
export let moveMode: 'move' | 'insert' = 'move'
export let type: string | undefined = undefined
export let fakeShadow: GridShadow | undefined = undefined
export let disableMove: boolean = true
export let mounted: boolean = false
let {
sensor,
width,
height,
left,
top,
id,
container,
xPerPx,
yPerPx,
gapX,
gapY,
item,
cols,
nativeContainer,
onTop,
shadow = $bindable(undefined),
overlapped = undefined,
moveMode = 'move',
type = undefined,
fakeShadow = undefined,
disableMove = true,
mounted = false,
children
}: Props = $props()
const ctx = getContext<AppEditorContext>('AppEditorContext')
const { mode, app } = getContext<AppViewerContext>('AppViewerContext')
@@ -47,9 +73,9 @@
const scale = ctx ? ctx.scale : writable(100)
const divId = `component-${id}`
let shadowElement
let shadowElement = $state(undefined) as HTMLElement | undefined
let active = false
let active = $state(false)
let initX, initY
@@ -58,10 +84,10 @@
y: 0
}
let cordDiff = { x: 0, y: 0 }
let cordDiff = $state({ x: 0, y: 0 })
let newSize = { width, height }
let trans = false
let newSize = $state({ width, height })
let trans = $state(false)
let anima
@@ -114,6 +140,7 @@
shadowBound = irect
}
if (!rect) return
const xdragBound = rect.left + cordDiff.x
const ydragBound = rect.top + cordDiff.y
@@ -142,7 +169,7 @@
// Autoscroll
let _scrollTop = 0
let containerFrame
let rect
let rect = $state() as { top: number; left: number } | undefined
let scrollElement
const getContainerFrame = (element) => {
@@ -317,7 +344,7 @@
}
}
let element: HTMLElement | undefined = undefined
let element: HTMLElement | undefined = $state(undefined)
function computeShadow(clientX: number, clientY: number) {
const elementsAtPoint = document.elementsFromPoint(clientX, clientY)
@@ -384,7 +411,7 @@
return
}
const parent = findGridItemParentGrid($app, id)
const parent = findGridItemParentGrid(app, id)
if (overlapped && (overlapped === parent || parent?.startsWith(overlapped))) {
return
@@ -469,7 +496,7 @@
return true
}
const parent = findGridItemParentGrid($app, id)
const parent = findGridItemParentGrid(app, id)
if (parent === undefined) {
return overlapped === undefined
@@ -481,12 +508,12 @@
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={element}
draggable="false"
on:pointerdown|stopPropagation|preventDefault={pointerdown}
onpointerdown={(e) => stopPropagation(preventDefault((_) => pointerdown(e)))}
id={divId}
class="svlt-grid-item"
data-iscontainer={type ? isContainer(type) : false}
@@ -495,30 +522,26 @@
style="width: {xPerPx == 0 ? 0 : active ? newSize.width : width}px; height:{xPerPx == 0
? 0
: active
? newSize.height
: height}px;
? newSize.height
: height}px;
{xPerPx == 0 ? 'overflow: hidden;' : ''}
{onTop ? 'z-index: 1000;' : ''}
{active && rect
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px);top:${rect.top}px;left:${rect.left}px;z-index:10000;`
: trans
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
: `${
xPerPx > 0 && mounted ? 'transition: transform 0.1s, opacity 0.1s;' : ''
} transform: translate(${left}px, ${top}px); `} "
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
: `${
xPerPx > 0 && mounted ? 'transition: transform 0.1s, opacity 0.1s;' : ''
} transform: translate(${left}px, ${top}px); `} "
>
<slot />
{@render children?.()}
{#if moveMode === 'move' && !disableMove}
<div
class="svlt-grid-resizer-bottom"
on:pointerdown={(e) => resizePointerDown(e, 'vertical')}
<div class="svlt-grid-resizer-bottom" onpointerdown={(e) => resizePointerDown(e, 'vertical')}
></div>
<div
class="svlt-grid-resizer-side"
on:pointerdown={(e) => resizePointerDown(e, 'horizontal')}
<div class="svlt-grid-resizer-side" onpointerdown={(e) => resizePointerDown(e, 'horizontal')}
></div>
<div class="svlt-grid-resizer" on:pointerdown={(e) => resizePointerDown(e, 'both')}></div>
<div class="svlt-grid-resizer" onpointerdown={(e) => resizePointerDown(e, 'both')}></div>
{/if}
</div>
+8 -8
View File
@@ -23,7 +23,7 @@ import type {
TemplateV2AppInput,
UploadAppInput,
UploadS3AppInput,
UserAppInput,
UserAppInput
} from './inputType'
import type { World } from './rx'
import type { FilledItem } from './svelte-grid/types'
@@ -141,13 +141,13 @@ export type HiddenRunnable = {
export type AppTheme =
| {
type: 'path'
path: string
}
type: 'path'
path: string
}
| {
type: 'inlined'
css: string
}
type: 'inlined'
css: string
}
export type App = {
grid: GridItem[]
@@ -209,7 +209,7 @@ export type JobById = {
export type AppViewerContext = {
worldStore: Writable<World>
app: Writable<App>
app: App
summary: Writable<string>
initialized: Writable<{
initializedComponents: string[]
@@ -1,12 +1,26 @@
<script lang="ts">
import AnimatedButtonInner from './AnimatedButtonInner.svelte'
export let marginWidth = '2px'
export let animationDuration = '2s'
export let baseRadius = '4px'
export let animate = true
export let wrapperClasses = ''
export let ringColor = 'transparent'
export let darkMode = false
interface Props {
marginWidth?: string
animationDuration?: string
baseRadius?: string
animate?: boolean
wrapperClasses?: string
ringColor?: string
darkMode?: boolean
children?: import('svelte').Snippet
}
let {
marginWidth = '2px',
animationDuration = '2s',
baseRadius = '4px',
animate = true,
wrapperClasses = '',
ringColor = 'transparent',
darkMode = false,
children
}: Props = $props()
</script>
{#if animate}
@@ -19,10 +33,10 @@
{ringColor}
{darkMode}
>
<slot />
{@render children?.()}
</AnimatedButtonInner>
{:else}
<div class={wrapperClasses}>
<slot />
{@render children?.()}
</div>
{/if}
@@ -100,7 +100,7 @@
placeholder="Inline script name"
class="!text-xs !rounded-sm !shadow-none"
on:keyup={() => {
// $app = $app
// // $app = $app
// if (stateId) {
// $stateId++
// }
@@ -197,7 +197,7 @@
syncFields()
}
}
// $app = $app
// // $app = $app
}}
args={Object.entries(fields ?? {}).reduce((acc, [key, obj]) => {
acc[key] = obj.type === 'static' ? obj.value : undefined
@@ -9,8 +9,12 @@
import type { Runnable } from '../apps/inputType'
import { getNextId } from '$lib/components/flows/idUtils'
export let selectedRunnable: string | undefined
export let runnables: Writable<Record<string, Runnable>>
interface Props {
selectedRunnable: string | undefined
runnables: Writable<Record<string, Runnable>>
}
let { selectedRunnable = $bindable(), runnables }: Props = $props()
function createBackgroundScript() {
const nid = getNextId(Object.keys($runnables ?? {}))
@@ -23,7 +27,6 @@
}
return r
})
console.log('BAR 2')
selectedRunnable = nid
}
@@ -31,7 +34,7 @@
</script>
<PanelSection title="Backend Runnables" id="app-editor-runnable-panel">
<svelte:fragment slot="action">
{#snippet action()}
<div class="flex flex-row gap-1">
<HideButton
direction="bottom"
@@ -55,7 +58,7 @@
<Plus size={14} class="!text-primary" />
</Button>
</div>
</svelte:fragment>
{/snippet}
<div class="w-full flex flex-col gap-6 py-1">
<div>
<div class="flex flex-col gap-1 w-full">
@@ -68,7 +71,7 @@
{selectedRunnable === id
? 'border-blue-500 bg-blue-100 dark:bg-frost-900/50'
: 'hover:bg-blue-50 dark:hover:bg-frost-900/50'}"
on:click={() => (selectedRunnable = id)}
onclick={() => (selectedRunnable = id)}
>
<span class="text-2xs truncate">{runnable?.name}</span>
<Badge color="indigo">{id}</Badge>
@@ -1,6 +1,4 @@
<script lang="ts">
import { run } from 'svelte/legacy'
import { addWhitespaceBeforeCapitals, capitalize, classNames } from '$lib/utils'
import Tooltip from '$lib/components/Tooltip.svelte'
@@ -54,7 +52,7 @@
markdownTooltip = undefined
}: Props = $props()
run(() => {
$effect(() => {
if (componentInput == undefined) {
//@ts-ignore
componentInput = {
@@ -14,10 +14,14 @@
} from '../utils'
import { updateProgress } from '$lib/tutorialUtils'
export let name: string
export let index: number
interface Props {
name: string
index: number
}
let tutorial: Tutorial | undefined = undefined
let { name, index }: Props = $props()
let tutorial: Tutorial | undefined = $state(undefined)
const { app, selectedComponent, focusedGrid, connectingInput } =
getContext<AppViewerContext>('AppViewerContext')
@@ -28,16 +32,15 @@
}
function addComponent(appComponentType: TypedComponent['type']): void {
push(history, $app)
push(history, app)
const id = insertNewGridItem(
$app,
app,
appComponentFromType(appComponentType) as (id: string) => AppComponent,
$focusedGrid
)
$selectedComponent = [id]
$app = $app
$selectedComponent = [id] // $app = $app
}
</script>
@@ -47,7 +50,7 @@
{name}
on:error
on:skipAll
tainted={isAppTainted($app)}
tainted={isAppTainted(app)}
getSteps={(driver) => {
const steps = [
{
@@ -186,7 +189,7 @@
setTimeout(() => {
if ($selectedComponent?.[0]) {
updateInlineRunnableCode(
$app,
app,
$selectedComponent[0],
`export async function main(x: string) {
return x?.toLocaleUpperCase();
@@ -265,9 +268,7 @@
description:
'We can now type in the text input and see the result in the display component',
onNextClick: () => {
connectInlineRunnableInputToComponentOutput($app, 'e', 'x', 'd', 'result', 'integer')
$app = $app
connectInlineRunnableInputToComponentOutput(app, 'e', 'x', 'd', 'result', 'integer') // $app = $app
updateProgress(7)
@@ -8,10 +8,14 @@
import { clickButtonBySelector } from '../utils'
import { updateProgress } from '$lib/tutorialUtils'
export let name: string
export let index: number
interface Props {
name: string
index: number
}
let tutorial: Tutorial | undefined = undefined
let { name, index }: Props = $props()
let tutorial: Tutorial | undefined = $state(undefined)
const { app, selectedComponent, focusedGrid } = getContext<AppViewerContext>('AppViewerContext')
const { history } = getContext<AppEditorContext>('AppEditorContext')
@@ -21,16 +25,15 @@
}
function addComponent(): void {
push(history, $app)
push(history, app)
const id = insertNewGridItem(
$app,
app,
appComponentFromType('textcomponent') as (id: string) => AppComponent,
$focusedGrid
)
$selectedComponent = [id]
$app = $app
$selectedComponent = [id] // $app = $app
}
</script>
@@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher, getContext } from 'svelte'
import { createEventDispatcher, getContext, untrack } from 'svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Label from '../Label.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
@@ -16,21 +16,32 @@
type: 'bar' | 'scatter' | 'line' | 'area' | 'range-bar' | 'range-area'
}
let component: GridItem | undefined = undefined
let component = $state(undefined) as GridItem | undefined
$: if (component === undefined && $selectedComponent && $app) {
component = findGridItem($app, $selectedComponent[0])
$effect.pre(() => {
if (component === undefined && $selectedComponent && untrack(() => app)) {
untrack(() => {
component = findGridItem(app, $selectedComponent[0])
})
}
})
let isEE = $derived(component?.data.type === 'agchartscomponentee')
interface Props {
value?: Dataset | undefined
trigger?: import('svelte').Snippet
}
$: isEE = component?.data.type === 'agchartscomponentee'
export let value: Dataset | undefined = undefined
let { value = $bindable(undefined), trigger }: Props = $props()
const dispatch = createEventDispatcher()
function removeDataset() {
dispatch('remove')
}
const trigger_render = $derived(trigger)
</script>
<Popover
@@ -41,10 +52,10 @@
}}
closeOnOtherPopoverOpen
>
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<svelte:fragment slot="content">
{#snippet trigger()}
{@render trigger_render?.()}
{/snippet}
{#snippet content()}
{#if value}
<div class="flex flex-col w-96 gap-4 p-4 max-h-[70vh] overflow-y-auto">
<Label label="Name">
@@ -66,5 +77,5 @@
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</svelte:fragment>
{/snippet}
</Popover>
@@ -29,7 +29,12 @@
cellRendererType: 'text' | 'badge' | 'link'
}
export let value: Column | undefined
interface Props {
value: Column | undefined
trigger?: import('svelte').Snippet
}
let { value = $bindable(), trigger }: Props = $props()
const presets = [
{
@@ -82,11 +87,15 @@
}
]
let renderCount = 0
let renderCount = $state(0)
$: if (value && value.cellRendererType === null) {
value.cellRendererType = 'text'
}
$effect.pre(() => {
if (value && value.cellRendererType === null) {
value.cellRendererType = 'text'
}
})
const trigger_render = $derived(trigger)
</script>
<Popover
@@ -97,10 +106,10 @@
}}
closeOnOtherPopoverOpen
>
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<svelte:fragment slot="content">
{#snippet trigger()}
{@render trigger_render?.()}
{/snippet}
{#snippet content()}
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[70vh] overflow-y-auto">
<span class="text-sm mb-2 leading-6 font-semibold">
@@ -132,7 +141,7 @@
</Label>
<Label label="Flex">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-sizing/#column-flex"
>
@@ -146,7 +155,7 @@
The column with flex: 2 has twice the size with flex: 1. So final sizes will be:
150px, 100px, 200px.
</Tooltip>
</svelte:fragment>
{/snippet}
<input type="range" step="1" bind:value={value.flex} min={1} max={12} />
<div class="text-xs">{value.flex}</div>
@@ -164,7 +173,7 @@
</Label>
<Label label="Value formatter">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/value-formatters/"
>
@@ -172,8 +181,8 @@
one type (e.g. numeric) but needs to be converted for human reading (e.g. putting in
currency symbols and number formatting).
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
{/snippet}
{#snippet action()}
<Button
size="xs"
color="light"
@@ -186,21 +195,20 @@
>
Clear
</Button>
</svelte:fragment>
{/snippet}
</Label>
<div>
{#key renderCount}
<div class="flex flex-col gap-4">
<div class="relative">
{#if !presets.find((preset) => preset.value === value?.valueFormatter)}
<div
class="z-50 absolute bg-opacity-50 bg-surface top-0 left-0 bottom-0 right-0"
<div class="z-50 absolute bg-opacity-50 bg-surface top-0 left-0 bottom-0 right-0"
></div>
{/if}
<div class="text-xs font-semibold">Presets</div>
<select
bind:value={value.valueFormatter}
on:change={() => {
onchange={() => {
renderCount++
}}
placeholder="Code"
@@ -231,12 +239,12 @@
</Label>
<Label label="Filter">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip documentationLink="https://www.ag-grid.com/javascript-data-grid/filtering/">
Filtering allows you to limit the rows displayed in your grid to those that match
criteria you specify.
</Tooltip>
</svelte:fragment>
{/snippet}
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
@@ -248,36 +256,36 @@
</Label>
<!--
EE only
EE only
<Label label="Aggregation function">
<SimpleEditor autoHeight lang="javascript" bind:code={value.aggFunc} />
</Label>
<Label label="Aggregation function">
<SimpleEditor autoHeight lang="javascript" bind:code={value.aggFunc} />
</Label>
<Label label="Pivot">
<Toggle bind:checked={value.pivot} size="xs" />
</Label>
<Label label="Pivot">
<Toggle bind:checked={value.pivot} size="xs" />
</Label>
<Label label="Pivot index">
<input type="number" placeholder="pivot index" bind:value={value.pivotIndex} />
</Label>
<Label label="Pivot index">
<input type="number" placeholder="pivot index" bind:value={value.pivotIndex} />
</Label>
<Label label="Pinned">
<select bind:value={value.pinned}>
<option value={null}>None</option>
<option value="left">Left</option>
<option value="right">Right</option>
</select>
</Label>
<Label label="Pinned">
<select bind:value={value.pinned}>
<option value={null}>None</option>
<option value="left">Left</option>
<option value="right">Right</option>
</select>
</Label>
<Label label="Row group">
<Toggle bind:checked={value.rowGroup} size="xs" />
</Label>
<Label label="Row group">
<Toggle bind:checked={value.rowGroup} size="xs" />
</Label>
<Label label="Row group index">
<input type="number" placeholder="row group index" bind:value={value.rowGroupIndex} />
</Label>
-->
<Label label="Row group index">
<input type="number" placeholder="row group index" bind:value={value.rowGroupIndex} />
</Label>
-->
<Label label="Type">
<select bind:value={value.cellRendererType}>
@@ -302,5 +310,5 @@
{/if}
</div>
{/if}
</svelte:fragment>
{/snippet}
</Popover>
@@ -13,7 +13,12 @@
name: string
}
export let value: Dataset | undefined = undefined
interface Props {
value?: Dataset | undefined
trigger?: import('svelte').Snippet
}
let { value = $bindable(undefined), trigger: trigger_render }: Props = $props()
const dispatch = createEventDispatcher()
@@ -30,10 +35,10 @@
}}
closeOnOtherPopoverOpen
>
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<svelte:fragment slot="content">
{#snippet trigger()}
{@render trigger_render?.()}
{/snippet}
{#snippet content()}
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[70vh] overflow-y-auto">
<Label label="Name">
@@ -61,5 +66,5 @@
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</svelte:fragment>
{/snippet}
</Popover>
@@ -11,9 +11,13 @@
import { ColumnIdentity, type ColumnDef } from '../apps/components/display/dbtable/utils'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
export let value: ColumnDef | undefined
import Alert from '../common/alert/Alert.svelte'
interface Props {
value: ColumnDef | undefined
trigger?: import('svelte').Snippet
}
let { value = $bindable(), trigger: trigger_render }: Props = $props()
const presets = [
{
@@ -71,7 +75,7 @@
}
]
let renderCount = 0
let renderCount = $state(0)
function computeWarning(columnMetadata, value) {
if (columnMetadata?.isnullable === 'NO' && !columnMetadata?.defaultvalue) {
@@ -118,7 +122,7 @@
return null
}
$: warning = computeWarning(value, value)
let warning = $derived(computeWarning(value, value))
</script>
<Popover
@@ -130,27 +134,27 @@
contentClasses="max-h-[70vh] overflow-y-auto p-4 flex flex-col gap-4 w-96"
closeOnOtherPopoverOpen
>
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
{#snippet trigger()}
{@render trigger_render?.()}
{/snippet}
<svelte:fragment slot="content">
{#snippet content()}
{#if value}
<Section label="Column settings">
<svelte:fragment slot="header">
{#snippet header()}
<Badge color="blue">
{value.field}
</Badge>
</svelte:fragment>
{/snippet}
<Label label="Skip for select and update">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip>
By default, all columns are included in the select and update queries. If you want to
exclude a column from the select and update queries, you can set this property to
true.
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
{/snippet}
{#snippet action()}
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
@@ -159,7 +163,7 @@
size="xs"
disabled={value?.isprimarykey}
/>
</svelte:fragment>
{/snippet}
{#if value?.isprimarykey}
<Alert type="warning" size="xs" title="Primary key" class="my-1">
You cannot skip a primary key.
@@ -168,14 +172,14 @@
</Label>
<Label label="Hide from insert">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip>
By default, all columns are used to generate the submit form. If you want to exclude a
column from the submit form, you can set this property to true. If the column is not
nullable or doesn't have a default value, a default value will be required.
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
{/snippet}
{#snippet action()}
<Toggle
disabled={value?.isidentity === ColumnIdentity.Always}
on:pointerdown={(e) => {
@@ -184,7 +188,7 @@
bind:checked={value.hideInsert}
size="xs"
/>
</svelte:fragment>
{/snippet}
</Label>
{#if value?.isidentity === ColumnIdentity.Always}
<Alert type="warning" size="xs" title="Identity column" class="my-1">
@@ -216,13 +220,13 @@
/>
{/if}
<Label label="Default input">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip>
By default, all columns are used to generate the submit form. If you want to exclude a
column from the submit form, you can set this property to true. If the column is not
nullable or doesn't have a default value, a default value will be required.
</Tooltip>
</svelte:fragment>
{/snippet}
{#if value?.datatype}
{@const type = value?.datatype}
@@ -266,7 +270,7 @@
<Section label="AG Grid configuration">
<div
class={twMerge('flex flex-col gap-4', value.ignored ? 'opacity-50 cursor-none ' : '')}
on:pointerdown={(e) => {
onpointerdown={(e) => {
if (value?.ignored) {
e?.stopPropagation()
}
@@ -292,7 +296,7 @@
</Label>
<Label label="Flex">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/column-sizing/#column-flex"
>
@@ -306,7 +310,7 @@
remaining. The column with flex: 2 has twice the size with flex: 1. So final sizes
will be: 150px, 100px, 200px.
</Tooltip>
</svelte:fragment>
{/snippet}
<input type="range" step="1" bind:value={value.flex} min={1} max={12} />
<div class="text-xs">{value.flex}</div>
@@ -324,7 +328,7 @@
</Label>
<Label label="Value formatter">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip
documentationLink="https://www.ag-grid.com/javascript-data-grid/value-formatters/"
>
@@ -332,8 +336,8 @@
one type (e.g. numeric) but needs to be converted for human reading (e.g. putting in
currency symbols and number formatting).
</Tooltip>
</svelte:fragment>
<svelte:fragment slot="action">
{/snippet}
{#snippet action()}
<Button
size="xs"
color="light"
@@ -346,7 +350,7 @@
>
Clear
</Button>
</svelte:fragment>
{/snippet}
</Label>
<div>
{#key renderCount}
@@ -360,7 +364,7 @@
<div class="text-xs font-semibold">Presets</div>
<select
bind:value={value.valueFormatter}
on:change={() => {
onchange={() => {
renderCount++
}}
placeholder="Code"
@@ -392,5 +396,5 @@
</div>
</Section>
{/if}
</svelte:fragment>
{/snippet}
</Popover>
@@ -11,7 +11,7 @@
const { selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let closeOnOutsideClick = true
let closeOnOutsideClick = $state(true)
type Dataset = {
value: RichConfiguration
@@ -23,13 +23,20 @@
extraOptions?: { mode: 'markers' | 'lines' | 'lines+markers' } | undefined
}
export let value: Dataset | undefined = undefined
interface Props {
value?: Dataset | undefined
trigger?: import('svelte').Snippet
}
let { value = $bindable(undefined), trigger }: Props = $props()
const dispatch = createEventDispatcher()
function removeDataset() {
dispatch('remove')
}
const trigger_render = $derived(trigger)
</script>
<Popover
@@ -40,10 +47,10 @@
}}
{closeOnOutsideClick}
>
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<svelte:fragment slot="content">
{#snippet trigger()}
{@render trigger_render?.()}
{/snippet}
{#snippet content()}
{#if value}
<div class="flex flex-col w-96 p-4 gap-4 max-h-[70vh] overflow-y-auto">
<Label label="Name">
@@ -55,7 +62,7 @@
<option value="bar">Bar</option>
<option
value="scatter"
on:click={() => {
onclick={() => {
if (value && value?.extraOptions === undefined) {
value.extraOptions = { mode: 'markers' }
}
@@ -77,13 +84,13 @@
{/if}
<Label label="Aggregation method">
<svelte:fragment slot="header">
{#snippet header()}
<Tooltip>
A method to aggregate the data. For example, if you have multiple x data points with
the same value, you can choose to sum them up or take the mean. If you don't have
multiple x data points with the same value, this option will have no effect.
</Tooltip>
</svelte:fragment>
{/snippet}
<select bind:value={value.aggregation_method}>
<option value="sum">Sum</option>
<option value="mean">Mean</option>
@@ -128,5 +135,5 @@
<Button color="red" size="xs" on:click={removeDataset}>Remove dataset</Button>
</div>
{/if}
</svelte:fragment>
{/snippet}
</Popover>
@@ -12,11 +12,23 @@
CheckboxComponent,
SelectComponent
} from '../apps/editor/component'
export let actionsOrder: RichConfiguration | undefined = undefined
export let selectedId: string | undefined = undefined
export let components:
| (BaseAppComponent & (ButtonComponent | CheckboxComponent | SelectComponent))[]
| undefined
interface Props {
actionsOrder?: RichConfiguration | undefined
selectedId?: string | undefined
components:
| (BaseAppComponent & (ButtonComponent | CheckboxComponent | SelectComponent))[]
| undefined
trigger?: import('svelte').Snippet
}
let {
actionsOrder = $bindable(undefined),
selectedId = undefined,
components,
trigger
}: Props = $props()
const trigger_render = $derived(trigger)
</script>
<Popover
@@ -27,15 +39,15 @@
}}
closeButton
>
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<svelte:fragment slot="content">
{#snippet trigger()}
{@render trigger_render?.()}
{/snippet}
{#snippet content()}
<div class="w-96">
<PanelSection
title={`Manage actions programmatically`}
tooltip="
You can manage the order of the actions programmatically: You need to return an array of action ids in the order you want them to appear in the table. You can also hide actions by not including them in the array."
You can manage the order of the actions programmatically: You need to return an array of action ids in the order you want them to appear in the table. You can also hide actions by not including them in the array."
>
<div class="w-full flex gap-2 flex-col mt-2">
{#if actionsOrder}
@@ -93,5 +105,5 @@
</div>
</PanelSection>
</div>
</svelte:fragment>
{/snippet}
</Popover>
@@ -5,11 +5,18 @@
import Toggle from '../Toggle.svelte'
import Tooltip from '../Tooltip.svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
export let column: {
headerName: string
hideColumn: boolean
type: 'text' | 'badge' | 'link'
interface Props {
column: {
headerName: string
hideColumn: boolean
type: 'text' | 'badge' | 'link'
}
trigger?: import('svelte').Snippet
}
let { column = $bindable(), trigger }: Props = $props()
const trigger_render = $derived(trigger)
</script>
<Popover
@@ -21,10 +28,10 @@
closeButton
closeOnOtherPopoverOpen
>
<svelte:fragment slot="trigger">
<slot name="trigger" />
</svelte:fragment>
<svelte:fragment slot="content">
{#snippet trigger()}
{@render trigger_render?.()}
{/snippet}
{#snippet content()}
<div class="flex flex-col w-96 p-4 gap-4">
<span class="text-sm mb-2 leading-6 font-semibold">
Table Column
@@ -73,5 +80,5 @@
</Alert>
{/if}
</div>
</svelte:fragment>
{/snippet}
</Popover>