mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-10 08:07:03 +00:00
* Remove $$props.field usage * Rename slots to ensure no hyphen * _props * _trigger * OnSelectedIteration type correct capitalization * rename _content * Remove afterUpdate * Migrate everything to svelte 5 * array bind * Fix popover * type never * nit fixes * Fixed many trivial errors * onClick * Fix errors * use let: * nit typing * fix: wrap state_referenced_locally vars with untrack() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add untrack import * Fix all syntax errors due to untrack migration * Fix undefined errors * Fix more undefined errors * untrack(() => initialOpen) * svelte-ignore * Fix state_descriptors_fixed error in Chart.svelte Use $state.snapshot() to pass plain copies of data/options to Chart.js instead of $state proxies. Chart.js's listenArrayEvents tries to define property descriptors on data arrays, which Svelte 5 proxies reject. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * nit typing * Merge issue * Fix "path is not set" error in resource picker / editor * Fix InputTransformForm error when rerunning some flows * fix npm run check --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
358 lines
9.0 KiB
Svelte
358 lines
9.0 KiB
Svelte
<script lang="ts">
|
|
import { stopPropagation } from 'svelte/legacy'
|
|
|
|
import { push } from '$lib/history.svelte'
|
|
import { classNames } from '$lib/utils'
|
|
import { getContext, onDestroy, untrack } from 'svelte'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import { gridColumns, isFixed, toggleFixed } from '../gridUtils'
|
|
import Grid from '../svelte-grid/Grid.svelte'
|
|
import type { AppEditorContext, AppViewerContext, GridItem } from '../types'
|
|
import {
|
|
expandGriditem,
|
|
findGridItemParentGrid,
|
|
insertNewGridItem,
|
|
isContainer,
|
|
maxHeight,
|
|
selectId,
|
|
subGridIndexKey
|
|
} from './appUtils'
|
|
import Component from './component/Component.svelte'
|
|
import ComponentWrapper from './component/ComponentWrapper.svelte'
|
|
import GridViewer from './GridViewer.svelte'
|
|
import GridEditorMenu from './GridEditorMenu.svelte'
|
|
import { findGridItem } from './appUtilsCore'
|
|
|
|
interface Props {
|
|
containerHeight?: number | undefined
|
|
containerWidth?: number | undefined
|
|
class?: string
|
|
style?: string
|
|
noPadding?: boolean
|
|
noYPadding?: boolean
|
|
subGridId: string
|
|
visible?: boolean
|
|
id: string
|
|
shouldHighlight?: boolean
|
|
onFocus?: () => void
|
|
}
|
|
|
|
let {
|
|
containerHeight = undefined,
|
|
containerWidth = undefined,
|
|
class: classes = '',
|
|
style = '',
|
|
noPadding = false,
|
|
noYPadding = false,
|
|
subGridId,
|
|
visible = true,
|
|
id,
|
|
shouldHighlight = true,
|
|
onFocus
|
|
}: Props = $props()
|
|
|
|
const {
|
|
app,
|
|
connectingInput,
|
|
selectedComponent,
|
|
focusedGrid,
|
|
mode,
|
|
parentWidth,
|
|
breakpoint,
|
|
allIdsInPath,
|
|
worldStore
|
|
} = getContext<AppViewerContext>('AppViewerContext')
|
|
|
|
const editorContext = getContext<AppEditorContext>('AppEditorContext')
|
|
|
|
let isActive = $state(false)
|
|
let sber = editorContext?.componentActive?.subscribe((x) => (isActive = x))
|
|
|
|
let everVisible = $state(untrack(() => visible))
|
|
|
|
$effect.pre(() => {
|
|
visible && !everVisible && (everVisible = true)
|
|
})
|
|
|
|
onDestroy(() => {
|
|
sber?.()
|
|
})
|
|
let highlight = $derived(id === $focusedGrid?.parentComponentId && shouldHighlight)
|
|
|
|
const onpointerdown = (e) => {
|
|
onFocus?.()
|
|
}
|
|
|
|
function selectComponent(e: PointerEvent, id: string) {
|
|
if (!$connectingInput.opened) {
|
|
selectId(e, id, selectedComponent, $app)
|
|
}
|
|
}
|
|
|
|
function lock(dataItem: GridItem) {
|
|
let fComponent = findGridItem($app, dataItem.id)
|
|
if (fComponent) {
|
|
fComponent = toggleFixed(fComponent)
|
|
}
|
|
$app = $app
|
|
}
|
|
|
|
let container: HTMLElement | undefined = $state(undefined)
|
|
|
|
let maxRow = $derived(
|
|
maxHeight($app.subgrids?.[subGridId] ?? [], containerHeight ?? 0, $breakpoint)
|
|
)
|
|
|
|
export function moveComponentBetweenSubgrids(
|
|
componentId: string,
|
|
parentComponentId: string,
|
|
subGridIndex: number,
|
|
position?: { x: number; y: number }
|
|
) {
|
|
// Find the component in the source subgrid
|
|
const component = findGridItem($app, componentId)
|
|
|
|
if (!component) {
|
|
return
|
|
}
|
|
|
|
let parentGrid = findGridItemParentGrid($app, component.id)
|
|
|
|
if (parentGrid) {
|
|
$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)
|
|
}
|
|
|
|
const gridItem = component
|
|
|
|
insertNewGridItem(
|
|
$app,
|
|
(id) => ({ ...gridItem.data, id }),
|
|
{ parentComponentId: parentComponentId, subGridIndex: subGridIndex },
|
|
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
|
|
component.id,
|
|
position,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
true
|
|
)
|
|
|
|
// Update the app state
|
|
$app = $app
|
|
|
|
if (parentGrid) {
|
|
$focusedGrid = {
|
|
parentComponentId,
|
|
subGridIndex
|
|
}
|
|
|
|
$selectedComponent = [parentComponentId]
|
|
} else {
|
|
$focusedGrid = undefined
|
|
}
|
|
}
|
|
|
|
export function moveToRoot(componentId: string, position?: { x: number; y: number }) {
|
|
// Find the component in the source subgrid
|
|
const component = findGridItem($app, componentId)
|
|
|
|
if (!component) {
|
|
return
|
|
}
|
|
|
|
let parentGrid = findGridItemParentGrid($app, component.id)
|
|
|
|
if (parentGrid) {
|
|
$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)
|
|
}
|
|
|
|
const gridItem = component
|
|
|
|
insertNewGridItem(
|
|
$app,
|
|
(id) => ({ ...gridItem.data, id }),
|
|
undefined,
|
|
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
|
|
component.id,
|
|
position,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
true
|
|
)
|
|
|
|
// Update the app state
|
|
$app = $app
|
|
}
|
|
</script>
|
|
|
|
<!-- {visible}
|
|
{everVisible} -->
|
|
{#if everVisible || $app.eagerRendering}
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
class="translate-x-0 translate-y-0 w-full subgrid {visible
|
|
? 'visible'
|
|
: 'invisible h-0 overflow-hidden'}"
|
|
bind:this={container}
|
|
{onpointerdown}
|
|
>
|
|
<div
|
|
class={twMerge(
|
|
$allIdsInPath.includes(id) && $mode == 'dnd' ? 'overflow-visible' : 'overflow-auto',
|
|
noYPadding ? '' : 'py-2',
|
|
classes ?? '',
|
|
noPadding ? 'px-0' : 'px-2'
|
|
)}
|
|
style="{containerHeight ? `height: ${containerHeight - 2}px;` : ''} {style ?? ''}"
|
|
>
|
|
{#if $mode !== 'preview'}
|
|
<div
|
|
class={highlight
|
|
? `!outline-dashed outline-2 min-h-full ${
|
|
isActive && !$selectedComponent?.includes(id)
|
|
? 'outline-orange-600'
|
|
: 'outline-gray-400 dark:outline-gray-600'
|
|
}`
|
|
: ''}
|
|
>
|
|
<!-- A{JSON.stringify($app.subgrids?.[subGridId]) ?? []}B -->
|
|
<Grid
|
|
allIdsInPath={$allIdsInPath}
|
|
items={$app.subgrids?.[subGridId] ?? []}
|
|
onRedraw={(grid) => {
|
|
push(editorContext?.history, $app)
|
|
if ($app.subgrids) {
|
|
$app.subgrids[subGridId] = grid
|
|
}
|
|
}}
|
|
selectedIds={$selectedComponent}
|
|
scroller={container}
|
|
parentWidth={$parentWidth - 17}
|
|
{containerWidth}
|
|
onDropped={({ id, overlapped, x, y }) => {
|
|
if (!overlapped) {
|
|
moveToRoot(id, { x, y })
|
|
} else {
|
|
const overlappedComponent = findGridItem($app, overlapped)
|
|
|
|
if (overlappedComponent && !isContainer(overlappedComponent.data.type)) {
|
|
return
|
|
}
|
|
if (!overlapped) {
|
|
return
|
|
}
|
|
|
|
if (id === overlapped) {
|
|
return
|
|
}
|
|
moveComponentBetweenSubgrids(
|
|
id,
|
|
overlapped,
|
|
subGridIndexKey(overlappedComponent?.data?.type, overlapped, $worldStore),
|
|
{ x, y }
|
|
)
|
|
}
|
|
}}
|
|
disableMove={!!$connectingInput.opened}
|
|
>
|
|
{#snippet children({ dataItem, overlapped, componentDraggedId })}
|
|
<ComponentWrapper
|
|
id={dataItem.id}
|
|
type={dataItem.data.type}
|
|
class={classNames(
|
|
'h-full w-full center-center',
|
|
$selectedComponent?.includes(dataItem.id) ? 'active-grid-item' : '',
|
|
'top-0'
|
|
)}
|
|
>
|
|
<GridEditorMenu id={dataItem.id}>
|
|
<Component
|
|
{overlapped}
|
|
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
|
|
render={visible}
|
|
component={dataItem.data}
|
|
selected={Boolean($selectedComponent?.includes(dataItem.id))}
|
|
locked={isFixed(dataItem)}
|
|
on:lock={() => lock(dataItem)}
|
|
on:expand={() => {
|
|
const parentGridItem = findGridItem($app, id)
|
|
|
|
if (!parentGridItem) {
|
|
return
|
|
}
|
|
|
|
$selectedComponent = [dataItem.id]
|
|
push(editorContext?.history, $app)
|
|
|
|
expandGriditem(
|
|
$app.subgrids?.[subGridId] ?? [],
|
|
dataItem.id,
|
|
$breakpoint,
|
|
parentGridItem
|
|
)
|
|
$app = $app
|
|
}}
|
|
on:fillHeight={() => {
|
|
const gridItem = findGridItem($app, dataItem.id)
|
|
const b = $breakpoint === 'sm' ? 3 : 12
|
|
|
|
if (gridItem?.[b]) {
|
|
gridItem[b].fullHeight = !gridItem[b].fullHeight
|
|
}
|
|
$app = $app
|
|
}}
|
|
{componentDraggedId}
|
|
/>
|
|
</GridEditorMenu>
|
|
</ComponentWrapper>
|
|
{/snippet}
|
|
</Grid>
|
|
</div>
|
|
{:else}
|
|
<GridViewer
|
|
allIdsInPath={$allIdsInPath}
|
|
items={$app.subgrids?.[subGridId] ?? []}
|
|
breakpoint={$breakpoint}
|
|
parentWidth={$parentWidth - 17}
|
|
{containerWidth}
|
|
{maxRow}
|
|
>
|
|
{#snippet children({ dataItem })}
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
onpointerdown={stopPropagation((e) =>
|
|
selectComponent(e as PointerEvent, dataItem.id)
|
|
)}
|
|
class={classNames('h-full w-full center-center', 'top-0')}
|
|
>
|
|
<Component
|
|
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
|
|
render={visible}
|
|
component={dataItem.data}
|
|
selected={Boolean($selectedComponent?.includes(dataItem.id))}
|
|
locked={isFixed(dataItem)}
|
|
/>
|
|
</div>
|
|
{/snippet}
|
|
</GridViewer>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{:else if $app.lazyInitRequire == undefined}
|
|
{#each $app?.subgrids?.[subGridId] ?? [] as item}
|
|
<Component selected={false} fullHeight={false} render={false} component={item.data} />
|
|
{/each}
|
|
{/if}
|