appSvelte5

This commit is contained in:
Ruben Fiszel
2025-06-13 17:34:23 +02:00
parent 0b3a084c81
commit 355a92a3df
61 changed files with 1284 additions and 1031 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
@@ -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}
@@ -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>
@@ -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()
@@ -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}
@@ -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
}
}
}
@@ -77,7 +77,7 @@
if (fComponent) {
fComponent = toggleFixed(fComponent)
}
$app = $app
// $app = $app
}
let container: HTMLElement | undefined = undefined
@@ -201,7 +201,7 @@
isActive && !$selectedComponent?.includes(id)
? 'outline-orange-600'
: 'outline-gray-400 dark:outline-gray-600'
}`
}`
: ''}
>
<Grid
@@ -284,7 +284,7 @@
$breakpoint,
parentGridItem
)
$app = $app
// $app = $app
}}
on:fillHeight={() => {
const gridItem = findGridItem($app, dataItem.id)
@@ -293,7 +293,7 @@
if (gridItem?.[b]) {
gridItem[b].fullHeight = !gridItem[b].fullHeight
}
$app = $app
// $app = $app
}}
{moveMode}
{componentDraggedId}
@@ -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,12 +75,23 @@
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>
@@ -797,7 +808,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=""
@@ -31,8 +31,7 @@
}
const code = cssEditor?.getCode()
cssEditor?.setCode(code + '\n' + selector)
$app = $app
cssEditor?.setCode(code + '\n' + selector) // $app = $app
}
</script>
@@ -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>
@@ -74,7 +74,8 @@
>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}
@@ -82,41 +83,38 @@
{#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>
@@ -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>
@@ -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">
@@ -196,7 +196,7 @@
$stateId++
}
}
$app = $app
// $app = $app
}
}
@@ -271,7 +271,7 @@
placeholder="Inline script name"
class="!text-xs !rounded-sm !shadow-none"
onkeyup={() => {
$app = $app
// $app = $app
if (stateId) {
$stateId++
}
@@ -385,7 +385,7 @@
loadSchemaAndInputsByName()
}
}
$app = $app
// $app = $app
}}
args={Object.entries(fields).reduce((acc, [key, obj]) => {
acc[key] = obj.type === 'static' ? obj.value : undefined
@@ -413,7 +413,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
}
}}
/>
@@ -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} />
@@ -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>
@@ -53,7 +53,7 @@
if (componentSetting?.item?.data?.id) {
delete $runnableComponents[componentSetting?.item?.data?.id]
}
$app = $app
// $app = $app
$runnableComponents = $runnableComponents
onDelete?.()
@@ -60,8 +60,7 @@
$app!.subgrids = {
...$app!.subgrids,
...newSubgrids
}
$app = $app
} // $app = $app
tick().then(() => {
const targetIndex = items.findIndex((i) => i.id === e.detail.info.id)
@@ -94,8 +93,7 @@
})
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 {
@@ -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>
@@ -84,8 +84,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 {
@@ -127,8 +126,7 @@
$app!.subgrids = {
...$app!.subgrids,
...newSubgrids
}
$app = $app
} // $app = $app
tick().then(() => {
const targetIndex = items.findIndex((i) => i.id === e.detail.info.id)
@@ -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() {
@@ -23,8 +23,7 @@
...appComponentFromType('buttoncomponent')(`${id}_${actionId}`),
recomputeIds: []
}
components = [...components, newComponent]
$app = $app
components = [...components, newComponent] // $app = $app
}
function deleteComponent(cid: string) {
@@ -32,8 +31,7 @@
delete $errorByComponent[cid]
$selectedComponent = [id]
$app = $app
$selectedComponent = [id] // $app = $app
}
</script>
@@ -21,8 +21,7 @@
$app.unusedInlineScripts.push({
name: appInput.runnable.name,
inlineScript: appInput.runnable.inlineScript
})
$app = $app
}) // $app = $app
appInput = clearResultAppInput(appInput)
}
}
@@ -56,7 +55,7 @@
color: 'light',
callback: detach
}
] as const)
] as const)
: []),
{
label: 'Clear',
@@ -68,8 +68,7 @@
}
]
components = [...components, newComponent]
$app = $app
components = [...components, newComponent] // $app = $app
}
function deleteComponent(cid: string, index: number) {
@@ -79,8 +78,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)
}
@@ -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>
@@ -86,8 +86,7 @@
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
@@ -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"
@@ -84,8 +84,7 @@
} else if (!itemsExists(inlineScript.refreshOn, refresh)) {
inlineScript.refreshOn = [...inlineScript.refreshOn, refresh]
}
inlineScript = inlineScript
$app = $app
inlineScript = inlineScript // $app = $app
}
</script>
@@ -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}
+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
@@ -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)
@@ -29,8 +29,7 @@
$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>
@@ -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>