partial app svelte 5 migration (#5945)

This commit is contained in:
Ruben Fiszel
2025-06-15 23:25:34 +02:00
committed by GitHub
parent 855571f359
commit f0f720f490
22 changed files with 849 additions and 777 deletions
@@ -66,6 +66,7 @@
let args: Record<string, unknown> = $state(!iterContext ? (outputs?.values?.peak() ?? {}) : {})
function handleArgsChange() {
// console.log('handleArgsChange', args)
const newArgs: Record<string, unknown> = {}
for (const key in args) {
@@ -1,4 +1,7 @@
<script lang="ts">
import { createBubbler, stopPropagation } from 'svelte/legacy'
const bubble = createBubbler()
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
@@ -11,12 +14,23 @@
import { twMerge } from 'tailwind-merge'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let customCss: ComponentCustomCSS<'accordionlistcomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined
export let componentContainerHeight: number
interface Props {
id: string
componentInput: AppInput | undefined
customCss?: ComponentCustomCSS<'accordionlistcomponent'> | undefined
render: boolean
initializing: boolean | undefined
componentContainerHeight: number
}
let {
id,
componentInput,
customCss = undefined,
render,
initializing = $bindable(),
componentContainerHeight
}: Props = $props()
type AccordionListValue = { header: string; [key: string]: any }
@@ -24,16 +38,18 @@
value: AccordionListValue[]
}
$: accordionInput = componentInput as InternalAccordionListInput
let accordionInput = $derived(componentInput as InternalAccordionListInput)
const { app, focusedGrid, selectedComponent, worldStore, connectingInput } =
getContext<AppViewerContext>('AppViewerContext')
let everRender = render
let everRender = $state(render)
$: render && !everRender && (everRender = true)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
let activeIndex: number = 0
let activeIndex: number = $state(0)
const outputs = initOutput($worldStore, id, {
result: undefined,
@@ -49,17 +65,19 @@
}
}
let css = initCss($app.css?.accordionlistcomponent, customCss)
let result: any[] | undefined = undefined
let css = $state(initCss($app.css?.accordionlistcomponent, customCss))
let result: any[] | undefined = $state(undefined)
let inputs = {}
let inputs = $state({})
$: $selectedComponent?.includes(id) &&
$focusedGrid === undefined &&
($focusedGrid = {
parentComponentId: id,
subGridIndex: 0
})
$effect.pre(() => {
$selectedComponent?.includes(id) &&
$focusedGrid === undefined &&
($focusedGrid = {
parentComponentId: id,
subGridIndex: 0
})
})
function toggleAccordion(index: number) {
activeIndex = activeIndex === index ? -1 : index
@@ -95,8 +113,8 @@
{#each result ?? [] as value, index}
<div class="border-b">
<button
on:pointerdown|stopPropagation
on:click={() => toggleAccordion(index)}
onpointerdown={stopPropagation(bubble('pointerdown'))}
onclick={() => toggleAccordion(index)}
class={twMerge(
'w-full text-left bg-surface !truncate text-sm hover:text-primary px-1 py-2',
'wm-tabs-alltabs',
@@ -16,19 +16,33 @@
import { twMerge } from 'tailwind-merge'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'carousellistcomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined
export let componentContainerHeight: number
interface Props {
id: string
componentInput: AppInput | undefined
configuration: RichConfigurations
customCss?: ComponentCustomCSS<'carousellistcomponent'> | undefined
render: boolean
initializing: boolean | undefined
componentContainerHeight: number
}
let {
id,
componentInput,
configuration,
customCss = undefined,
render,
initializing = $bindable(),
componentContainerHeight
}: Props = $props()
const { app, focusedGrid, selectedComponent, worldStore, connectingInput } =
getContext<AppViewerContext>('AppViewerContext')
let everRender = render
$: render && !everRender && (everRender = true)
let everRender = $state(render)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
const outputs = initOutput($worldStore, id, {
result: undefined,
@@ -36,9 +50,8 @@
inputs: {}
})
const resolvedConfig = initConfig(
components['carousellistcomponent'].initialData.configuration,
configuration
const resolvedConfig = $state(
initConfig(components['carousellistcomponent'].initialData.configuration, configuration)
)
function onFocus() {
@@ -48,19 +61,21 @@
}
}
let css = initCss($app.css?.carousellistcomponent, customCss)
let result: any[] | undefined = undefined
let css = $state(initCss($app.css?.carousellistcomponent, customCss))
let result: any[] | undefined = $state(undefined)
let inputs = {}
let carousel: Carousel
let inputs = $state({})
let carousel: Carousel = $state()
$: $selectedComponent?.includes(id) &&
$focusedGrid === undefined &&
($focusedGrid = {
parentComponentId: id,
subGridIndex: 0
})
let currentPageIndex = 0
$effect.pre(() => {
$selectedComponent?.includes(id) &&
$focusedGrid === undefined &&
($focusedGrid = {
parentComponentId: id,
subGridIndex: 0
})
})
let currentPageIndex = $state(0)
</script>
{#each Object.keys(components['carousellistcomponent'].initialData.configuration) as key (key)}
@@ -116,41 +131,45 @@
}
}}
>
<div slot="prev" class="h-full flex justify-center flex-col p-2">
<div>
<Button
color="light"
on:click={() => {
const pagesCount = result?.length ?? 0
{#snippet prev()}
<div class="h-full flex justify-center flex-col p-2">
<div>
<Button
color="light"
on:click={() => {
const pagesCount = result?.length ?? 0
if (currentPageIndex > 0) {
carousel.goTo(currentPageIndex - 1)
} else {
carousel.goTo(pagesCount - 1)
}
}}
>
<ArrowLeftCircle size={16} />
</Button>
if (currentPageIndex > 0) {
carousel.goTo(currentPageIndex - 1)
} else {
carousel.goTo(pagesCount - 1)
}
}}
>
<ArrowLeftCircle size={16} />
</Button>
</div>
</div>
</div>
<div slot="next" class="h-full flex justify-center flex-col p-2">
<div>
<Button
color="light"
on:click={() => {
const pagesCount = result?.length ?? 0
if (currentPageIndex < pagesCount - 1) {
carousel.goTo(currentPageIndex + 1)
} else {
carousel.goTo(0)
}
}}
>
<ArrowRightCircle size={16} />
</Button>
{/snippet}
{#snippet next()}
<div class="h-full flex justify-center flex-col p-2">
<div>
<Button
color="light"
on:click={() => {
const pagesCount = result?.length ?? 0
if (currentPageIndex < pagesCount - 1) {
carousel.goTo(currentPageIndex + 1)
} else {
carousel.goTo(0)
}
}}
>
<ArrowRightCircle size={16} />
</Button>
</div>
</div>
</div>
{/snippet}
{#each result ?? [] as value, index}
<div class="overflow-auto w-full">
<ListWrapper
@@ -3,27 +3,42 @@
import type { HorizontalAlignment, VerticalAlignment } from '../../types'
import { tailwindHorizontalAlignment, tailwindVerticalAlignment } from '../../utils'
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let noWFull = false
export let hFull = false
let c = ''
export { c as class }
export let style = ''
export let render: boolean = true
interface Props {
horizontalAlignment?: HorizontalAlignment | undefined
verticalAlignment?: VerticalAlignment | undefined
noWFull?: boolean
hFull?: boolean
class?: string
style?: string
render?: boolean
children?: import('svelte').Snippet
}
$: classes = twMerge(
'flex z-auto',
noWFull ? '' : 'w-full',
tailwindHorizontalAlignment(horizontalAlignment),
tailwindVerticalAlignment(verticalAlignment),
verticalAlignment || hFull ? 'h-full' : '',
c
let {
horizontalAlignment = undefined,
verticalAlignment = undefined,
noWFull = false,
hFull = false,
class: c = '',
style = '',
render = true,
children
}: Props = $props()
let classes = $derived(
twMerge(
'flex z-auto',
noWFull ? '' : 'w-full',
tailwindHorizontalAlignment(horizontalAlignment),
tailwindVerticalAlignment(verticalAlignment),
verticalAlignment || hFull ? 'h-full' : '',
c
)
)
</script>
{#if render}
<div class={classes} {style}>
<slot />
{@render children?.()}
</div>
{/if}
@@ -1,206 +1,23 @@
<script lang="ts">
import { getContext } from 'svelte'
import { initConfig, initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { initCss } from '../../utils'
import type { ComponentCustomCSS, RichConfigurations } from '../../types'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import { components } from '../../editor/component'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
// @ts-ignore
import MultiSelect from 'svelte-multiselect'
import Portal from '$lib/components/Portal.svelte'
import { createFloatingActions } from 'svelte-floating-ui'
import { extractCustomProperties } from '$lib/utils'
import { tick } from 'svelte'
import { offset, flip, shift } from 'svelte-floating-ui/dom'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
export let id: string
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
export let render: boolean
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
const [floatingRef, floatingContent] = createFloatingActions({
strategy: 'absolute',
middleware: [offset(5), flip(), shift()]
})
const { app, worldStore, selectedComponent, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
let items: string[]
const resolvedConfig = initConfig(
components['multiselectcomponent'].initialData.configuration,
configuration
)
const outputs = initOutput($worldStore, id, {
result: [] as string[]
})
let value: string[] | undefined = [...new Set(outputs?.result.peak())] as string[]
$componentControl[id] = {
setValue(nvalue: string[]) {
value = [...new Set(nvalue)]
outputs?.result.set([...(value ?? [])])
}
}
$: resolvedConfig.items && handleItems()
function handleItems() {
if (Array.isArray(resolvedConfig.items)) {
items = resolvedConfig.items?.map((label) => {
return typeof label === 'string' ? label : `NOT_STRING`
})
}
}
$: resolvedConfig.defaultItems && handleDefaultItems()
function handleDefaultItems() {
if (Array.isArray(resolvedConfig.defaultItems)) {
const nvalue = resolvedConfig.defaultItems?.map((label) => {
return typeof label === 'string' ? label : `NOT_STRING`
})
value = [...new Set(nvalue)]
outputs?.result.set([...(value ?? [])])
}
}
let css = initCss($app.css?.multiselectcomponent, customCss)
function setOuterDivStyle(outerDiv: HTMLDivElement, portalRef: HTMLDivElement, style: string) {
outerDiv.setAttribute('style', style)
// find ul in portalRef and set style
const ul = portalRef.querySelector('ul')
ul?.setAttribute('style', extractCustomProperties(style))
}
$: outerDiv &&
portalRef &&
css?.multiselect?.style &&
setOuterDivStyle(outerDiv, portalRef, css?.multiselect?.style)
let outerDiv: HTMLDivElement | undefined = undefined
let portalRef: HTMLDivElement | undefined = undefined
function moveOptionsToPortal() {
// Find ul element with class 'options' within the outerDiv
const ul = outerDiv?.querySelector('.options')
if (ul) {
// Move the ul element to the portal
portalRef?.appendChild(ul)
}
}
$: if (render && portalRef && outerDiv && items?.length > 0) {
tick().then(() => {
moveOptionsToPortal()
})
}
let w = 0
let open = false
</script>
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
{#each Object.keys(css ?? {}) as key (key)}
<ResolveStyle
{id}
{customCss}
{key}
bind:css={css[key]}
componentStyle={$app.css?.multiselectcomponent}
/>
{/each}
<InitializeComponent {id} />
<AlignWrapper {render} hFull {verticalAlignment}>
<div
class="w-full app-editor-input"
on:pointerdown={(e) => {
$selectedComponent = [id]
if (!e.shiftKey) {
e.stopPropagation()
}
selectedComponent.set([id])
}}
use:floatingRef
bind:clientWidth={w}
>This version of the multiselect component is not supported anymore. Recreate the multiselect
component to use this new version.</div
>
{#if !value || Array.isArray(value)}
<MultiSelect
bind:outerDiv
outerDivClass={`${resolvedConfig.allowOverflow ? '' : 'h-full'}`}
ulSelectedClass={`${resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full'} `}
ulOptionsClass={'p-2 !bg-surface-secondary'}
--sms-border={'none'}
--sms-min-height={'32px'}
--sms-focus-border={'none'}
bind:selected={value}
options={Array.isArray(items) ? items : []}
placeholder={resolvedConfig.placeholder}
allowUserOptions={resolvedConfig.create}
onchange={(event) => {
if (event?.type === 'removeAll') {
outputs?.result.set([])
} else {
outputs?.result.set([...(value ?? [])])
}
}}
onopen={() => {
$selectedComponent = [id]
open = true
}}
onclose={() => {
open = false
}}
>
{#snippet children({ option })}
<!-- needed because portal doesn't work for mouseup event en mobile -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="w-full"
on:mouseup|stopPropagation
on:pointerdown|stopPropagation={(e) => {
let newe = new MouseEvent('mouseup')
e.target?.['parentElement']?.dispatchEvent(newe)
}}
>
{option}
</div>
{/snippet}
</MultiSelect>
<Portal name="app-multiselect">
<div use:floatingContent class="z5000" hidden={!open}>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
bind:this={portalRef}
class="multiselect"
style={`min-width: ${w}px;`}
on:click|stopPropagation
></div>
</div>
</Portal>
{:else}
Value {value} is not an array
{/if}
</div>
</AlignWrapper>
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext } from 'svelte'
import { getContext, untrack } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppViewerContext, ComponentCustomCSS, RichConfiguration } from '../../types'
@@ -9,12 +9,23 @@
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'conditionalwrapper'> | undefined = undefined
export let render: boolean
export let conditions: RichConfiguration[]
export let onTabChange: string[] | undefined = undefined
interface Props {
id: string
componentContainerHeight: number
customCss?: ComponentCustomCSS<'conditionalwrapper'> | undefined
render: boolean
conditions: RichConfiguration[]
onTabChange?: string[] | undefined
}
let {
id,
componentContainerHeight,
customCss = undefined,
render,
conditions,
onTabChange = undefined
}: Props = $props()
const {
app,
@@ -31,8 +42,10 @@
selectedTabIndex: 0
})
let everRender = render
$: render && !everRender && (everRender = true)
let everRender = $state(render)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
function onFocus() {
$focusedGrid = {
@@ -41,10 +54,10 @@
}
}
let css = initCss($app.css?.conditionalwrapper, customCss)
let css = $state(initCss($app.css?.conditionalwrapper, customCss))
let resolvedConditions: boolean[] = conditions.map((_x) => false)
let selectedConditionIndex = 0
let resolvedConditions: boolean[] = $state(conditions.map((_x) => false))
let selectedConditionIndex = $state(0)
function handleResolvedConditions() {
const slicedArray = resolvedConditions.slice(0, conditions.length)
@@ -66,7 +79,9 @@
onTabChange?.forEach((id) => $runnableComponents?.[id]?.cb?.forEach((cb) => cb?.()))
}
$: resolvedConditions && handleResolvedConditions()
$effect.pre(() => {
resolvedConditions && untrack(() => handleResolvedConditions())
})
$componentControl[id] = {
setTab: (conditionIndex: number) => {
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext, onMount } from 'svelte'
import { getContext, onMount, untrack } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
@@ -12,23 +12,40 @@
import GroupWrapper from '../GroupWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'containercomponent'> | undefined = undefined
export let render: boolean
export let groupFields: RichConfigurations | undefined = undefined
interface Props {
id: string
componentContainerHeight: number
customCss?: ComponentCustomCSS<'containercomponent'> | undefined
render: boolean
groupFields?: RichConfigurations | undefined
}
let {
id,
componentContainerHeight,
customCss = undefined,
render,
groupFields = undefined
}: Props = $props()
const { app, focusedGrid, selectedComponent, worldStore, connectingInput, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
let everRender = render
$: render && !everRender && (everRender = true)
let everRender = $state(render)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
let groupContext = writable({})
let outputs = initOutput($worldStore, id, { group: $groupContext })
$: outputs.group.set($groupContext, true)
$effect.pre(() => {
$groupContext
untrack(() => {
outputs.group.set($groupContext, true)
})
})
function onFocus() {
$focusedGrid = {
@@ -48,7 +65,7 @@
}
})
let css = initCss($app.css?.containercomponent, customCss)
let css = $state(initCss($app.css?.containercomponent, customCss))
</script>
<InitializeComponent {id} />
@@ -1,5 +1,5 @@
<script lang="ts">
import { getContext } from 'svelte'
import { getContext, untrack } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
import { initCss } from '../../utils'
@@ -14,14 +14,34 @@
import { getFirstNode, isDebugging } from '../../editor/settingsPanel/decisionTree/utils'
import InputValue from '../helpers/InputValue.svelte'
export let id: string
export let componentContainerHeight: number
export let customCss: ComponentCustomCSS<'decisiontreecomponent'> | undefined = undefined
export let render: boolean
export let nodes: DecisionTreeNode[]
interface Props {
id: string
componentContainerHeight: number
customCss?: ComponentCustomCSS<'decisiontreecomponent'> | undefined
render: boolean
nodes: DecisionTreeNode[]
}
let everRender = render
$: render && !everRender && (everRender = true)
let { id, componentContainerHeight, customCss = undefined, render, nodes }: Props = $props()
let resolvedConditions = $derived(
nodes.reduce((acc, node) => {
acc[node.id] = acc[node.id] || []
return acc
}, {})
)
let resolvedNext = $derived(
nodes.reduce((acc, node) => {
acc[node.id] = acc[node.id] || false
return acc
}, {})
)
let everRender = $state(render)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
const {
app,
@@ -33,35 +53,27 @@
debuggingComponents
} = getContext<AppViewerContext>('AppViewerContext')
let css = initCss($app.css?.conditionalwrapper, customCss)
let css = $state(initCss($app.css?.conditionalwrapper, customCss))
let selectedConditionIndex = 0
let currentNodeId = getFirstNode(nodes)?.id ?? ''
let currentNodeId = $state(getFirstNode(nodes)?.id ?? '')
let outputs = initOutput($worldStore, id, {
currentNodeId,
currentNodeIndex: selectedConditionIndex
})
$: resolvedConditions = nodes.reduce((acc, node) => {
acc[node.id] = acc[node.id] || []
return acc
}, resolvedConditions || {})
$effect.pre(() => {
if (!nodes.map((n) => n.id).includes(currentNodeId)) {
const firstNode = untrack(() => getFirstNode(nodes)?.id)
$: resolvedNext = nodes.reduce((acc, node) => {
acc[node.id] = acc[node.id] || false
return acc
}, resolvedNext || {})
$: if (!nodes.map((n) => n.id).includes(currentNodeId)) {
const firstNode = getFirstNode(nodes)?.id
if (firstNode) {
currentNodeId = firstNode
if (firstNode) {
currentNodeId = firstNode
}
}
}
})
$: lastNodeId = nodes?.find((node) => node.next.length === 0)?.id
$: isNextDisabled = resolvedNext?.[currentNodeId] === false
let lastNodeId = $derived(nodes?.find((node) => node.next.length === 0)?.id)
let isNextDisabled = $derived(resolvedNext?.[currentNodeId] === false)
const history: string[] = []
@@ -136,17 +148,21 @@
}
}
$: if (currentNodeId) {
outputs.currentNodeId.set(currentNodeId)
outputs.currentNodeIndex.set(nodes.findIndex((next) => next.id == currentNodeId))
}
$: if ($selectedComponent?.[0] === id && !$focusedGrid) {
$focusedGrid = {
parentComponentId: id,
subGridIndex: nodes.findIndex((node) => node.id === currentNodeId)
$effect.pre(() => {
if (currentNodeId) {
outputs.currentNodeId.set(currentNodeId)
outputs.currentNodeIndex.set(nodes.findIndex((next) => next.id == currentNodeId))
}
}
})
$effect.pre(() => {
if ($selectedComponent?.[0] === id && !$focusedGrid) {
$focusedGrid = {
parentComponentId: id,
subGridIndex: nodes.findIndex((node) => node.id === currentNodeId)
}
}
})
</script>
{#if Object.keys(resolvedConditions).length === nodes.length}
@@ -1,4 +1,6 @@
<script lang="ts">
import { run } from 'svelte/legacy'
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
@@ -14,18 +16,34 @@
import ResolveStyle from '../helpers/ResolveStyle.svelte'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
export let customCss: ComponentCustomCSS<'drawercomponent'> | undefined = undefined
export let id: string
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
export let render: boolean
export let onOpenRecomputeIds: string[] | undefined = undefined
export let onCloseRecomputeIds: string[] | undefined = undefined
interface Props {
customCss?: ComponentCustomCSS<'drawercomponent'> | undefined
id: string
configuration: RichConfigurations
horizontalAlignment?: 'left' | 'center' | 'right' | undefined
verticalAlignment?: 'top' | 'center' | 'bottom' | undefined
noWFull?: boolean
render: boolean
onOpenRecomputeIds?: string[] | undefined
onCloseRecomputeIds?: string[] | undefined
}
let everRender = render
$: render && !everRender && (everRender = true)
let {
customCss = undefined,
id,
configuration,
horizontalAlignment = undefined,
verticalAlignment = undefined,
noWFull = false,
render,
onOpenRecomputeIds = undefined,
onCloseRecomputeIds = undefined
}: Props = $props()
let everRender = $state(render)
run(() => {
render && !everRender && (everRender = true)
})
const {
app,
@@ -38,17 +56,16 @@
runnableComponents
} = getContext<AppViewerContext>('AppViewerContext')
const resolvedConfig = initConfig(
components['drawercomponent'].initialData.configuration,
configuration
const resolvedConfig = $state(
initConfig(components['drawercomponent'].initialData.configuration, configuration)
)
const outputs = initOutput($worldStore, id, {
open: false
})
let containerHeight: number = 0
let containerHeight: number = $state(0)
let appDrawer: Drawer
let appDrawer: Drawer | undefined = $state()
$componentControl[id] = {
open: () => {
@@ -59,7 +76,7 @@
}
}
let css = initCss($app.css?.drawercomponent, customCss)
let css = $state(initCss($app.css?.drawercomponent, customCss))
</script>
{#each Object.keys(components['drawercomponent'].initialData.configuration) as key (key)}
@@ -105,7 +122,7 @@
parentComponentId: id,
subGridIndex: 0
}
appDrawer.toggleDrawer()
appDrawer?.toggleDrawer()
}}
size={resolvedConfig.size}
color={resolvedConfig.color}
@@ -122,7 +139,6 @@
{#if everRender}
<Portal target="#app-editor-top-level-drawer" name="app-drawer">
<Drawer
let:open
bind:this={appDrawer}
size="800px"
alwaysOpen
@@ -138,48 +154,50 @@
}}
disableChatOffset={true}
>
<DrawerContent
title={resolvedConfig.drawerTitle}
on:close={() => {
appDrawer?.toggleDrawer()
$focusedGrid = undefined
}}
fullScreen={$mode !== 'dnd'}
>
<div
class={twMerge('h-full', css?.drawer?.class, 'wm-drawer')}
style={css?.drawer?.style}
bind:clientHeight={containerHeight}
on:pointerdown={(e) => {
e?.stopPropagation()
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
{#snippet children({ open })}
<DrawerContent
title={resolvedConfig.drawerTitle}
on:close={() => {
appDrawer?.toggleDrawer()
$focusedGrid = undefined
}}
fullScreen={$mode !== 'dnd'}
>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={open && render}
{id}
{containerHeight}
subGridId={`${id}-0`}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
<div
class={twMerge('h-full', css?.drawer?.class, 'wm-drawer')}
style={css?.drawer?.style}
bind:clientHeight={containerHeight}
onpointerdown={(e) => {
e?.stopPropagation()
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}}
/>
{/if}
</div>
</DrawerContent>
}
}}
>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={open && render}
{id}
{containerHeight}
subGridId={`${id}-0`}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
}}
/>
{/if}
</div>
</DrawerContent>
{/snippet}
</Drawer>
</Portal>
{:else if $app.subgrids?.[`${id}-0`]}
@@ -15,19 +15,32 @@
import { twMerge } from 'tailwind-merge'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
export let id: string
export let componentInput: AppInput | undefined
export let configuration: RichConfigurations
export let customCss: ComponentCustomCSS<'listcomponent'> | undefined = undefined
export let render: boolean
export let initializing: boolean | undefined
interface Props {
id: string
componentInput: AppInput | undefined
configuration: RichConfigurations
customCss?: ComponentCustomCSS<'listcomponent'> | undefined
render: boolean
initializing: boolean | undefined
}
let {
id,
componentInput,
configuration,
customCss = undefined,
render,
initializing = $bindable()
}: Props = $props()
const { app, focusedGrid, selectedComponent, worldStore, connectingInput, allIdsInPath, mode } =
getContext<AppViewerContext>('AppViewerContext')
let page = 0
let page = $state(0)
let everRender = render
$: render && !everRender && (everRender = true)
let everRender = $state(render)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
const outputs = initOutput($worldStore, id, {
result: undefined,
@@ -36,9 +49,8 @@
page: 0
})
let resolvedConfig = initConfig(
components['listcomponent'].initialData.configuration,
configuration
let resolvedConfig = $state(
initConfig(components['listcomponent'].initialData.configuration, configuration)
)
function onFocus() {
@@ -48,20 +60,22 @@
}
}
let css = initCss($app.css?.listcomponent, customCss)
let result: any[] | undefined = undefined
let css = $state(initCss($app.css?.listcomponent, customCss))
let result: any[] | undefined = $state(undefined)
$: isCard = resolvedConfig.width?.selected == 'card'
let isCard = $derived(resolvedConfig.width?.selected == 'card')
let inputs = {}
let loading: boolean = false
let isPreviousLoading = false
let isNextLoading = false
let inputs = $state({})
let loading: boolean = $state(false)
let isPreviousLoading = $state(false)
let isNextLoading = $state(false)
$: if (!loading) {
isPreviousLoading = false
isNextLoading = false
}
$effect.pre(() => {
if (!loading) {
isPreviousLoading = false
isNextLoading = false
}
})
function getPagination(
configuration: {
@@ -99,11 +113,13 @@
}
}
$: pagination = getPagination(
resolvedConfig.pagination?.configuration,
resolvedConfig.pagination?.selected,
result,
page
let pagination = $derived(
getPagination(
resolvedConfig.pagination?.configuration,
resolvedConfig.pagination?.selected,
result,
page
)
)
</script>
@@ -150,8 +166,8 @@
: 'overflow-auto'} {isCard
? 'gap-2 flex-wrap'
: resolvedConfig?.displayBorders
? 'divide-y flex-col'
: 'flex-col'}"
? 'divide-y flex-col'
: 'flex-col'}"
>
{#if $app.subgrids?.[`${id}-0`] && Array.isArray(result) && result.length > 0}
{#each result ?? [] as value, index (index)}
@@ -162,16 +178,16 @@
isCard
? `min-width: ${resolvedConfig.width?.configuration?.card?.minWidthPx}px; `
: ''
} max-height: ${resolvedConfig.heightPx}px;`
} max-height: ${resolvedConfig.heightPx}px;`
: ''}
class={inRange
? `${
$allIdsInPath.includes(id)
? 'overflow-visible'
: resolvedConfig.heightPx
? 'overflow-auto'
: ''
} ${!isCard ? 'w-full' : resolvedConfig?.displayBorders ? 'border' : ''}`
? 'overflow-auto'
: ''
} ${!isCard ? 'w-full' : resolvedConfig?.displayBorders ? 'border' : ''}`
: 'h-0 float overflow-hidden invisible absolute'}
>
<ListWrapper
@@ -1,4 +1,6 @@
<script lang="ts">
import { stopPropagation } from 'svelte/legacy'
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
@@ -17,16 +19,29 @@
import Disposable from '$lib/components/common/drawer/Disposable.svelte'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
export let customCss: ComponentCustomCSS<'modalcomponent'> | undefined = undefined
export let id: string
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let noWFull = false
export let render: boolean
interface Props {
customCss?: ComponentCustomCSS<'modalcomponent'> | undefined
id: string
configuration: RichConfigurations
horizontalAlignment?: 'left' | 'center' | 'right' | undefined
verticalAlignment?: 'top' | 'center' | 'bottom' | undefined
noWFull?: boolean
render: boolean
onOpenRecomputeIds?: string[] | undefined
onCloseRecomputeIds?: string[] | undefined
}
export let onOpenRecomputeIds: string[] | undefined = undefined
export let onCloseRecomputeIds: string[] | undefined = undefined
let {
customCss = undefined,
id,
configuration,
horizontalAlignment = undefined,
verticalAlignment = undefined,
noWFull = false,
render,
onOpenRecomputeIds = undefined,
onCloseRecomputeIds = undefined
}: Props = $props()
const {
app,
@@ -40,23 +55,24 @@
breakpoint
} = getContext<AppViewerContext>('AppViewerContext')
let everRender = render
$: render && !everRender && (everRender = true)
let everRender = $state(render)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
//used so that we can count number of outputs setup for first refresh
const outputs = initOutput($worldStore, id, {
open: false
})
let css = initCss($app.css?.modalcomponent, customCss)
let disposable: Disposable | undefined = undefined
let css = $state(initCss($app.css?.modalcomponent, customCss))
let disposable: Disposable | undefined = $state(undefined)
let resolvedConfig = initConfig(
components['modalcomponent'].initialData.configuration,
configuration
let resolvedConfig = $state(
initConfig(components['modalcomponent'].initialData.configuration, configuration)
)
let unclickableOutside = false
let unclickableOutside = $state(false)
function unclosableModal() {
unclickableOutside = true
setTimeout(() => {
@@ -81,14 +97,16 @@
disposable?.closeDrawer()
}
}
let wrapperHeight: number = 0
let headerHeight: number = 0
let wrapperHeight: number = $state(0)
let headerHeight: number = $state(0)
$: containerHeight = Math.min(
// 8px * 2 of padding
maxHeight($app.subgrids?.[`${id}-0`] ?? [], 0, $breakpoint) * (ROW_HEIGHT + ROW_GAP_Y) + 16,
// 32px (2rem) of top and bottom margin
wrapperHeight - headerHeight - 64
let containerHeight = $derived(
Math.min(
// 8px * 2 of padding
maxHeight($app.subgrids?.[`${id}-0`] ?? [], 0, $breakpoint) * (ROW_HEIGHT + ROW_GAP_Y) + 16,
// 32px (2rem) of top and bottom margin
wrapperHeight - headerHeight - 64
)
)
async function getMenuElements(): Promise<HTMLElement[]> {
@@ -154,9 +172,6 @@
<Portal target="#app-editor-top-level-drawer" name="app-modal">
<Disposable
{id}
let:handleClickAway
let:zIndex
let:open
bind:this={disposable}
on:open={() => {
outputs?.open.set(true)
@@ -167,80 +182,82 @@
onCloseRecomputeIds?.forEach((id) => $runnableComponents?.[id]?.cb?.map((cb) => cb?.()))
}}
>
<div
class={twMerge(
`${
$mode == 'dnd' ? 'absolute' : 'fixed'
} top-0 bottom-0 left-0 right-0 transition-all duration-50`,
open ? ' bg-black bg-opacity-60' : 'h-0 overflow-hidden invisible'
)}
style="z-index: {zIndex}"
bind:clientHeight={wrapperHeight}
>
{#snippet children({ handleClickAway, zIndex, open })}
<div
style={css?.popup?.style}
class={twMerge('mx-24 mt-8 bg-surface wm-modal rounded-lg relative', css?.popup?.class)}
use:clickOutside={{
capture: false,
stopPropagation: false,
exclude: getMenuElements
}}
on:click_outside={(e) => {
if ($mode !== 'dnd' && !unclickableOutside) {
handleClickAway(e)
}
}}
class={twMerge(
`${
$mode == 'dnd' ? 'absolute' : 'fixed'
} top-0 bottom-0 left-0 right-0 transition-all duration-50`,
open ? ' bg-black bg-opacity-60' : 'h-0 overflow-hidden invisible'
)}
style="z-index: {zIndex}"
bind:clientHeight={wrapperHeight}
>
<div
class="px-4 py-2 border-b flex justify-between items-center"
bind:clientHeight={headerHeight}
>
<div>{resolvedConfig.modalTitle}</div>
<div class="w-8">
<button
on:click|stopPropagation={() => {
disposable?.closeDrawer()
}}
class="hover:bg-surface-hover bg-surface-secondary rounded-full w-8 h-8 flex items-center justify-center transition-all"
>
<X class="text-tertiary" />
</button>
</div>
</div>
<div
class={twMerge('wm-modal-container h-full', 'overflow-y-auto', css?.container?.class)}
on:pointerdown={(e) => {
e?.stopPropagation()
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
style={css?.popup?.style}
class={twMerge('mx-24 mt-8 bg-surface wm-modal rounded-lg relative', css?.popup?.class)}
use:clickOutside={{
capture: false,
stopPropagation: false,
exclude: getMenuElements,
onClickOutside: (e: MouseEvent) => {
if ($mode !== 'dnd' && !unclickableOutside) {
handleClickAway(e)
}
}
}}
>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={open && render}
{id}
{containerHeight}
subGridId={`${id}-0`}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
<div
class="px-4 py-2 border-b flex justify-between items-center"
bind:clientHeight={headerHeight}
>
<div>{resolvedConfig.modalTitle}</div>
<div class="w-8">
<button
onclick={stopPropagation(() => {
disposable?.closeDrawer()
})}
class="hover:bg-surface-hover bg-surface-secondary rounded-full w-8 h-8 flex items-center justify-center transition-all"
>
<X class="text-tertiary" />
</button>
</div>
</div>
<div
class={twMerge('wm-modal-container h-full', 'overflow-y-auto', css?.container?.class)}
onpointerdown={(e) => {
e?.stopPropagation()
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}}
/>
{/if}
}
}}
>
{#if $app.subgrids?.[`${id}-0`]}
<SubGridEditor
visible={open && render}
{id}
{containerHeight}
subGridId={`${id}-0`}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: 0
}
}
}}
/>
{/if}
</div>
</div>
</div>
</div>
{/snippet}
</Disposable>
</Portal>
{:else if $app.subgrids?.[`${id}-0`]}
@@ -1,4 +1,6 @@
<script lang="ts">
import { stopPropagation } from 'svelte/legacy'
import { getContext } from 'svelte'
import SubGridEditor from '../../editor/SubGridEditor.svelte'
import type { AppViewerContext, ComponentCustomCSS } from '../../types'
@@ -10,14 +12,25 @@
import { twMerge } from 'tailwind-merge'
import ResolveStyle from '../helpers/ResolveStyle.svelte'
export let id: string
export let componentContainerHeight: number
export let customCss:
| ComponentCustomCSS<'horizontalsplitpanescomponent' | 'verticalsplitpanescomponent'>
| undefined = undefined
export let horizontal: boolean = false
export let panes: number[]
export let render: boolean
interface Props {
id: string
componentContainerHeight: number
customCss?:
| ComponentCustomCSS<'horizontalsplitpanescomponent' | 'verticalsplitpanescomponent'>
| undefined
horizontal?: boolean
panes: number[]
render: boolean
}
let {
id,
componentContainerHeight,
customCss = undefined,
horizontal = false,
panes,
render
}: Props = $props()
const { app, focusedGrid, selectedComponent, componentControl, worldStore, connectingInput } =
getContext<AppViewerContext>('AppViewerContext')
@@ -25,9 +38,11 @@
//used so that we can count number of outputs setup for first refresh
initOutput($worldStore, id, {})
let everRender = render
let everRender = $state(render)
$: render && !everRender && (everRender = true)
$effect.pre(() => {
render && !everRender && (everRender = true)
})
function onFocus() {
$focusedGrid = {
@@ -36,7 +51,7 @@
}
}
let css = initCss($app.css?.containercomponent, customCss)
let css = $state(initCss($app.css?.containercomponent, customCss))
$componentControl[id] = {
left: () => {
@@ -63,13 +78,13 @@
}
}
let sumedup = panes.map((x) => (x / panes.reduce((a, b) => a + b, 0)) * 100)
$: {
let sumedup = $state(panes.map((x) => (x / panes.reduce((a, b) => a + b, 0)) * 100))
$effect.pre(() => {
let ns = panes.map((x) => (x / panes.reduce((a, b) => a + b, 0)) * 100)
if (!deepEqual(ns, sumedup)) {
sumedup = ns
}
}
})
</script>
{#each Object.keys(css ?? {}) as key (key)}
@@ -87,20 +102,20 @@
<InitializeComponent {id} />
{#if everRender}
<div class="h-full w-full border" on:pointerdown={onFocus}>
<div class="h-full w-full border" onpointerdown={onFocus}>
{#key sumedup}
<Splitpanes {horizontal}>
{#each sumedup as paneSize, index (index)}
<Pane size={paneSize} minSize={20}>
<div
class="w-full h-full"
on:pointerdown|stopPropagation={() => {
onpointerdown={stopPropagation(() => {
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: index
}
}}
})}
>
{#if $app.subgrids?.[`${id}-${index}`]}
<SubGridEditor
@@ -443,6 +443,7 @@
let box: HTMLElement | undefined = $state(undefined)
function parseScroll() {
$yTop = box?.scrollTop ?? 0
// console.log('parse scroll', $yTop)
}
let mounted = false
@@ -1,4 +1,6 @@
<script lang="ts">
import { stopPropagation } from 'svelte/legacy'
import { push } from '$lib/history'
import { classNames } from '$lib/utils'
import { createEventDispatcher, getContext, onDestroy } from 'svelte'
@@ -21,18 +23,31 @@
import GridViewer from './GridViewer.svelte'
import GridEditorMenu from './GridEditorMenu.svelte'
export let containerHeight: number | undefined = undefined
export let containerWidth: number | undefined = undefined
let classes = ''
interface Props {
containerHeight?: number | undefined
containerWidth?: number | undefined
class?: string
style?: string
noPadding?: boolean
noYPadding?: boolean
subGridId: string
visible?: boolean
id: string
shouldHighlight?: boolean
}
export { classes as class }
export let style = ''
export let noPadding = false
export let noYPadding = false
export let subGridId: string
export let visible: boolean = true
export let id: string
export let shouldHighlight: boolean = true
let {
containerHeight = undefined,
containerWidth = undefined,
class: classes = '',
style = '',
noPadding = false,
noYPadding = false,
subGridId,
visible = true,
id,
shouldHighlight = true
}: Props = $props()
const dispatch = createEventDispatcher()
@@ -50,17 +65,19 @@
const editorContext = getContext<AppEditorContext>('AppEditorContext')
let isActive = false
let isActive = $state(false)
let sber = editorContext?.componentActive?.subscribe((x) => (isActive = x))
let everVisible = visible
let everVisible = $state(visible)
$: visible && !everVisible && (everVisible = true)
$effect.pre(() => {
visible && !everVisible && (everVisible = true)
})
onDestroy(() => {
sber?.()
})
$: highlight = id === $focusedGrid?.parentComponentId && shouldHighlight
let highlight = $derived(id === $focusedGrid?.parentComponentId && shouldHighlight)
const onpointerdown = (e) => {
dispatch('focus')
@@ -80,9 +97,11 @@
$app = $app
}
let container: HTMLElement | undefined = undefined
let container: HTMLElement | undefined = $state(undefined)
$: maxRow = maxHeight($app.subgrids?.[subGridId] ?? [], containerHeight ?? 0, $breakpoint)
let maxRow = $derived(
maxHeight($app.subgrids?.[subGridId] ?? [], containerHeight ?? 0, $breakpoint)
)
export function moveComponentBetweenSubgrids(
componentId: string,
@@ -177,13 +196,15 @@
}
</script>
<!-- {visible}
{everVisible} -->
{#if everVisible || $app.eagerRendering}
<div
class="translate-x-0 translate-y-0 w-full subgrid {visible
? 'visible'
: 'invisible h-0 overflow-hidden'}"
bind:this={container}
on:pointerdown={onpointerdown}
{onpointerdown}
>
<div
class={twMerge(
@@ -204,6 +225,7 @@
}`
: ''}
>
<!-- A{JSON.stringify($app.subgrids?.[subGridId]) ?? []}B -->
<Grid
allIdsInPath={$allIdsInPath}
items={$app.subgrids?.[subGridId] ?? []}
@@ -214,10 +236,6 @@
}
}}
selectedIds={$selectedComponent}
let:dataItem
let:overlapped
let:moveMode
let:componentDraggedId
scroller={container}
parentWidth={$parentWidth - 17}
{containerWidth}
@@ -250,81 +268,86 @@
}}
disableMove={!!$connectingInput.opened}
>
<ComponentWrapper
id={dataItem.id}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center',
$selectedComponent?.includes(dataItem.id) ? 'active-grid-item' : '',
'top-0'
)}
>
<GridEditorMenu id={dataItem.id}>
<Component
{overlapped}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
render={visible}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
on:lock={() => lock(dataItem)}
on:expand={() => {
const parentGridItem = findGridItem($app, id)
{#snippet children({ dataItem, overlapped, moveMode, componentDraggedId })}
<ComponentWrapper
id={dataItem.id}
type={dataItem.data.type}
class={classNames(
'h-full w-full center-center',
$selectedComponent?.includes(dataItem.id) ? 'active-grid-item' : '',
'top-0'
)}
>
<GridEditorMenu id={dataItem.id}>
<Component
{overlapped}
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
render={visible}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
on:lock={() => lock(dataItem)}
on:expand={() => {
const parentGridItem = findGridItem($app, id)
if (!parentGridItem) {
return
}
if (!parentGridItem) {
return
}
$selectedComponent = [dataItem.id]
push(editorContext?.history, $app)
$selectedComponent = [dataItem.id]
push(editorContext?.history, $app)
expandGriditem(
$app.subgrids?.[subGridId] ?? [],
dataItem.id,
$breakpoint,
parentGridItem
)
$app = $app
}}
on:fillHeight={() => {
const gridItem = findGridItem($app, dataItem.id)
const b = $breakpoint === 'sm' ? 3 : 12
expandGriditem(
$app.subgrids?.[subGridId] ?? [],
dataItem.id,
$breakpoint,
parentGridItem
)
$app = $app
}}
on:fillHeight={() => {
const gridItem = findGridItem($app, dataItem.id)
const b = $breakpoint === 'sm' ? 3 : 12
if (gridItem?.[b]) {
gridItem[b].fullHeight = !gridItem[b].fullHeight
}
$app = $app
}}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
if (gridItem?.[b]) {
gridItem[b].fullHeight = !gridItem[b].fullHeight
}
$app = $app
}}
{moveMode}
{componentDraggedId}
/>
</GridEditorMenu>
</ComponentWrapper>
{/snippet}
</Grid>
</div>
{:else}
<GridViewer
allIdsInPath={$allIdsInPath}
items={$app.subgrids?.[subGridId] ?? []}
let:dataItem
breakpoint={$breakpoint}
parentWidth={$parentWidth - 17}
{containerWidth}
{maxRow}
>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:pointerdown|stopPropagation={(e) => selectComponent(e, dataItem.id)}
class={classNames('h-full w-full center-center', 'top-0')}
>
<Component
fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight}
render={visible}
component={dataItem.data}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
/>
</div>
{#snippet children({ dataItem })}
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div
onpointerdown={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>
@@ -476,7 +476,7 @@ export function appComponentFromType<T extends keyof typeof components>(
),
recomputeIds: init.recomputeIds ? [] : undefined,
actionButtons: init.actionButtons ? [] : undefined,
actions: [],
actions: undefined,
menuItems: init.menuItems ? [] : undefined,
numberOfSubgrids: init.numberOfSubgrids,
horizontalAlignment: override?.horizontalAlignment ?? init.horizontalAlignment,
@@ -1,4 +1,4 @@
<script lang="ts" context="module">
<script lang="ts" module>
import { writable } from 'svelte/store'
const componentDraggedIdStore = writable<string | undefined>(undefined)
@@ -42,35 +42,51 @@
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,
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 =
let getComputedCols: 3 | 12 | undefined = $state(
$app.mobileViewOnSmallerScreens == false ? WIDE_GRID_COLUMNS : undefined
let container
)
let container = $state()
$: [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)
sortedItems = specifyUndefinedColumns(sortedItems, getComputedCols, cols)
dispatch('resize', {
cols: getComputedCols,
xPerPx,
@@ -79,7 +95,7 @@
})
}, throttleUpdate)
let mounted = false
let mounted = $state(false)
onMount(() => {
const sizeObserver = new ResizeObserver((entries) => {
@@ -99,7 +115,7 @@
xPerPx = width / getComputedCols!
if (!containerWidth) {
items = specifyUndefinedColumns(items, getComputedCols, cols)
sortedItems = specifyUndefinedColumns(sortedItems, getComputedCols, cols)
dispatch('mount', {
cols: getComputedCols,
@@ -115,15 +131,14 @@
})
})
sizeObserver.observe(container)
sizeObserver.observe(container as Element)
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) {
@@ -199,9 +214,9 @@
return item
})
let { items } = moveItem(activeItem, fixedContainer, getComputedCols)
let { items: nitems } = moveItem(activeItem, fixedContainer, getComputedCols)
items = items.map((item) => {
nitems = nitems.map((item) => {
if (initialFixedStates.has(item.id)) {
const initialState = initialFixedStates.get(item.id)
@@ -213,12 +228,12 @@
return item
})
sortedItems = items
sortedItems = nitems
}
} else {
let { items } = moveItem(activeItem, sortedItems, getComputedCols)
let { items: nitems } = moveItem(activeItem, sortedItems, getComputedCols)
sortedItems = items
sortedItems = nitems
}
}
}
@@ -238,8 +253,16 @@
//let hiddenComponents = writable({})
let lastDetail: { isPointerUp: false; activate: false; id: string | undefined } | undefined =
undefined
let lastDetail:
| {
cordDiff: { x: number; y: number }
clientY: number
intersectingElement?: string | undefined
shadow?: GridShadow | undefined
overlapped?: string | undefined
}
| undefined = $state(undefined)
const handleRepaint = ({ detail }) => {
if (!detail.isPointerUp) {
throttleMatrix({ detail })
@@ -273,12 +296,20 @@
}
let moveResizes: Record<string, MoveResize> = {}
let shadows: Record<string, { x: number; y: number; w: number; h: number } | undefined> = {}
let shadows: Record<string, { x: number; y: number; w: number; h: number } | undefined> = $state(
{}
)
export function handleMove({ detail }) {
export function handleMove(detail: {
cordDiff: { x: number; y: number }
clientY: number
intersectingElement?: string | undefined
shadow?: GridShadow | undefined
overlapped?: string | undefined
}) {
Object.entries(moveResizes).forEach(([id, moveResize]) => {
if (selectedIds?.includes(id)) {
moveResize?.updateMove(structuredClone(stateSnapshot(detail.cordDiff)), detail.eventY)
moveResize?.updateMove(detail.cordDiff, detail.clientY)
}
})
@@ -304,7 +335,7 @@
draggedItem[getComputedCols].y = detail.shadow.y
}
let items: GridItem[] = []
let nitems: GridItem[] = []
if ($overlappedStore) {
const div = document.getElementById(`component-${$overlappedStore}`)
@@ -316,16 +347,16 @@
const index = type ? subGridIndexKey(type, $overlappedStore, $worldStore) : 0
items = $app.subgrids[`${$overlappedStore}-${index}`] ?? []
nitems = $app.subgrids[`${$overlappedStore}-${index}`] ?? []
} else {
items = $app.grid ?? []
nitems = $app.grid ?? []
}
if (!draggedItem) {
return
}
const freeSpace = gridHelp.findSpace(draggedItem, items, getComputedCols)
const freeSpace = gridHelp.findSpace(draggedItem, nitems, getComputedCols)
$fakeShadowStore = {
x: freeSpace.x,
@@ -357,16 +388,21 @@
}
})
}
let [gapX, gapY] = $derived(gap)
let containerHeight = $derived(getContainerHeight(items, yPerPx, getComputedCols))
$effect.pre(() => {
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
@@ -458,7 +494,7 @@
<MoveResize
{mounted}
on:initmove={() => handleInitMove(item.id)}
on:move={handleMove}
onMove={handleMove}
bind:shadow={shadows[item.id]}
bind:this={moveResizes[item.id]}
on:repaint={handleRepaint}
@@ -502,13 +538,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}
@@ -13,33 +13,65 @@
const dispatch = createEventDispatcher()
export let sensor
export let width
export let height
export let left
export let top
interface Props {
sensor: any
width: any
height: any
left: any
top: any
id: any
container: any
xPerPx: any
yPerPx: any
gapX: any
gapY: any
item: any
cols: any
nativeContainer: any
onTop: any
shadow?: { x: number; y: number; w: number; h: number } | undefined
overlapped?: string | undefined
moveMode?: 'move' | 'insert'
type?: string | undefined
fakeShadow?: GridShadow | undefined
disableMove?: boolean
mounted?: boolean
onMove: (detail: {
cordDiff: { x: number; y: number }
clientY: number
intersectingElement?: string | undefined
shadow?: GridShadow | undefined
overlapped?: string | undefined
}) => void
children?: import('svelte').Snippet
}
export let id
export let container
export let xPerPx
export let yPerPx
export let gapX
export let gapY
export let item
export let cols
export let nativeContainer
export let onTop
export let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
export let overlapped: string | undefined = undefined
export let moveMode: 'move' | 'insert' = 'move'
export let type: string | undefined = undefined
export let fakeShadow: GridShadow | undefined = undefined
export let disableMove: boolean = true
export let mounted: boolean = false
let {
sensor,
width,
height,
left,
top,
id,
container,
xPerPx,
yPerPx,
gapX,
gapY,
item,
cols,
nativeContainer,
onTop,
shadow = $bindable(undefined),
overlapped = undefined,
moveMode = 'move',
type = undefined,
fakeShadow = undefined,
disableMove = true,
mounted = false,
onMove,
children
}: Props = $props()
const ctx = getContext<AppEditorContext>('AppEditorContext')
const { mode, app } = getContext<AppViewerContext>('AppViewerContext')
@@ -47,9 +79,9 @@
const scale = ctx ? ctx.scale : writable(100)
const divId = `component-${id}`
let shadowElement
let shadowElement: HTMLElement | undefined = $state()
let active = false
let active = $state(false)
let initX, initY
@@ -58,10 +90,10 @@
y: 0
}
let cordDiff = { x: 0, y: 0 }
let cordDiff = $state({ x: 0, y: 0 })
let newSize = { width, height }
let trans = false
let newSize = $state({ width, height })
let trans = $state(false)
let anima
@@ -88,7 +120,7 @@
y: (moveY / $scale) * 100 - initY
}
dispatch('move', { cordDiff, clientY: clientY })
onMove({ cordDiff, clientY: clientY })
}
return x
})
@@ -114,6 +146,8 @@
shadowBound = irect
}
if (!rect) return
const xdragBound = rect.left + cordDiff.x
const ydragBound = rect.top + cordDiff.y
@@ -142,7 +176,7 @@
// Autoscroll
let _scrollTop = 0
let containerFrame
let rect
let rect = $state() as { top: number; left: number } | undefined
let scrollElement
const getContainerFrame = (element) => {
@@ -205,7 +239,6 @@
containerFrame = getContainerFrame(container)
scrollElement = getScroller(container)
_scrollTop = scrollElement.scrollTop
}
@@ -256,7 +289,7 @@
const cordDiff = { x: (clientX / $scale) * 100 - initX, y: (clientY / $scale) * 100 - initY }
if (moveMode === 'move') {
dispatch('move', {
onMove({
cordDiff,
clientY,
intersectingElement: undefined,
@@ -268,7 +301,7 @@
throttledComputeShadow(clientX, clientY)
dispatch('move', {
onMove({
cordDiff,
clientY,
intersectingElement: currentIntersectingElementId,
@@ -285,7 +318,6 @@
trans = false
}
cordDiff = newCoordDiff
// console.log(cordDiff, id, 'B')
const Y_SENSOR = sensor
if (containerFrame) {
@@ -317,7 +349,7 @@
}
}
let element: HTMLElement | undefined = undefined
let element: HTMLElement | undefined = $state(undefined)
function computeShadow(clientX: number, clientY: number) {
const elementsAtPoint = document.elementsFromPoint(clientX, clientY)
@@ -481,12 +513,16 @@
}
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
bind:this={element}
draggable="false"
on:pointerdown|stopPropagation|preventDefault={pointerdown}
onpointerdown={(e) => {
e.stopPropagation()
e.preventDefault()
pointerdown(e)
}}
id={divId}
class="svlt-grid-item"
data-iscontainer={type ? isContainer(type) : false}
@@ -495,30 +531,26 @@
style="width: {xPerPx == 0 ? 0 : active ? newSize.width : width}px; height:{xPerPx == 0
? 0
: active
? newSize.height
: height}px;
? newSize.height
: height}px;
{xPerPx == 0 ? 'overflow: hidden;' : ''}
{onTop ? 'z-index: 1000;' : ''}
{active && rect
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px);top:${rect.top}px;left:${rect.left}px;z-index:10000;`
: trans
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
: `${
xPerPx > 0 && mounted ? 'transition: transform 0.1s, opacity 0.1s;' : ''
} transform: translate(${left}px, ${top}px); `} "
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
: `${
xPerPx > 0 && mounted ? 'transition: transform 0.1s, opacity 0.1s;' : ''
} transform: translate(${left}px, ${top}px); `} "
>
<slot />
{@render children?.()}
{#if moveMode === 'move' && !disableMove}
<div
class="svlt-grid-resizer-bottom"
on:pointerdown={(e) => resizePointerDown(e, 'vertical')}
<div class="svlt-grid-resizer-bottom" onpointerdown={(e) => resizePointerDown(e, 'vertical')}
></div>
<div
class="svlt-grid-resizer-side"
on:pointerdown={(e) => resizePointerDown(e, 'horizontal')}
<div class="svlt-grid-resizer-side" onpointerdown={(e) => resizePointerDown(e, 'horizontal')}
></div>
<div class="svlt-grid-resizer" on:pointerdown={(e) => resizePointerDown(e, 'both')}></div>
<div class="svlt-grid-resizer" onpointerdown={(e) => resizePointerDown(e, 'both')}></div>
{/if}
</div>
@@ -141,13 +141,13 @@ export function moveItem(active, items, cols) {
}
// Update items
items = updateItem(items, active, item, cols)
const nitems = updateItem(items, active, item, cols)
// Create matrix of items expect close elements
matrix = makeMatrixFromItemsIgnore(items, closeBlocks, getRowsCount(items, cols), cols)
matrix = makeMatrixFromItemsIgnore(nitems, closeBlocks, getRowsCount(nitems, cols), cols)
// Create temp vars
let tempItems = items
let tempItems = nitems
let tempCloseBlocks = closeBlocks
// Exclude resolved elements ids in array
@@ -54,7 +54,7 @@ export function createAppFromScript(path: string, schema: Record<string, any> |
style: ''
}
},
actions: [],
actions: undefined,
numberOfSubgrids: 1,
id: 'topbar'
},
@@ -478,7 +478,7 @@ export function createAppFromScript(path: string, schema: Record<string, any> |
style: ''
}
},
actions: [],
actions: undefined,
horizontalAlignment: 'left',
verticalAlignment: 'center',
id: 'title'
@@ -511,7 +511,6 @@ export function createAppFromScript(path: string, schema: Record<string, any> |
class: ''
}
},
actions: [],
menuItems: [],
horizontalAlignment: 'right',
verticalAlignment: 'center',
@@ -616,7 +615,6 @@ export function createAppFromFlow(path: string, schema: Record<string, any> | un
style: ''
}
},
actions: [],
numberOfSubgrids: 1,
id: 'topbar'
},
@@ -1041,7 +1039,6 @@ export function createAppFromFlow(path: string, schema: Record<string, any> | un
style: ''
}
},
actions: [],
horizontalAlignment: 'left',
verticalAlignment: 'center',
id: 'title'
@@ -1074,7 +1071,6 @@ export function createAppFromFlow(path: string, schema: Record<string, any> | un
class: ''
}
},
actions: [],
menuItems: [],
horizontalAlignment: 'right',
verticalAlignment: 'center',
@@ -80,7 +80,6 @@ const emptyApp = {
style: ''
}
},
actions: [],
numberOfSubgrids: 1,
id: 'a'
},
@@ -163,7 +162,6 @@ const emptyApp = {
style: ''
}
},
actions: [],
horizontalAlignment: 'left',
verticalAlignment: 'center',
id: 'b'
@@ -190,7 +188,6 @@ const emptyApp = {
data: {
type: 'recomputeallcomponent',
configuration: {},
actions: [],
menuItems: [],
horizontalAlignment: 'right',
verticalAlignment: 'center',
@@ -34,10 +34,10 @@
$importStore = undefined
}
const state = nodraft ? undefined : localStorage.getItem('app')
const appState = nodraft ? undefined : localStorage.getItem('app')
let summary = ''
let value: App = {
let summary = $state('')
let value: App = $state({
grid: [],
fullscreen: false,
unusedInlineScripts: [],
@@ -46,7 +46,7 @@
type: 'path',
path: DEFAULT_THEME
}
}
})
afterNavigate(() => {
if (nodraft) {
let url = new URL($page.url.href)
@@ -54,13 +54,13 @@
replaceState(url.toString(), $page.state)
}
})
let policy: Policy = {
let policy: Policy = $state({
on_behalf_of: $userStore?.username.includes('@')
? $userStore?.username
: `u/${$userStore?.username}`,
on_behalf_of_email: $userStore?.email,
execution_mode: 'publisher'
}
})
loadApp()
@@ -101,7 +101,7 @@
summary = hub.app.summary
sendUserToast('App loaded from Hub')
goto('?', { replaceState: true })
} else if (!templatePath && !hubId && state) {
} else if (!templatePath && !hubId && appState) {
sendUserToast('App restored from browser stored autosave', false, [
{
label: 'Start from blank',
@@ -116,7 +116,7 @@
}
}
])
value = decodeState(state)
value = decodeState(appState)
} else {
const preset = presets['topbarcomponent']
@@ -168,18 +168,17 @@
replaceStateFn={(path) => replaceState(path, $page.state)}
gotoFn={(path, opt) => goto(path, opt)}
>
<svelte:fragment
slot="unsavedConfirmationModal"
let:diffDrawer
let:additionalExitAction
let:getInitialAndModifiedValues
>
{#snippet unsavedConfirmationModal({
diffDrawer,
additionalExitAction,
getInitialAndModifiedValues
})}
<UnsavedConfirmationModal
{diffDrawer}
{additionalExitAction}
{getInitialAndModifiedValues}
/>
</svelte:fragment>
{/snippet}
</AppEditor>
{/key}
</div>
@@ -16,8 +16,11 @@
import type { App } from '$lib/components/apps/types'
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import { stateSnapshot } from '$lib/svelte5Utils.svelte'
import { untrack } from 'svelte'
let app = undefined as (AppWithLastVersion & { draft_only?: boolean; value: any }) | undefined
let app = $state(
undefined as (AppWithLastVersion & { draft_only?: boolean; value: any }) | undefined
)
let savedApp:
| {
value: App
@@ -28,8 +31,8 @@
draft_only?: boolean
custom_path?: string
}
| undefined = undefined
let redraw = 0
| undefined = $state(undefined)
let redraw = $state(0)
let path = $page.params.path
let nodraft = $page.url.searchParams.get('nodraft')
@@ -93,8 +96,8 @@
actions.push({
label: 'Show diff',
callback: async () => {
diffDrawer.openDrawer()
diffDrawer.setDiff({
diffDrawer?.openDrawer()
diffDrawer?.setDiff({
mode: 'simple',
original: draftOrDeployed,
current: urlScript,
@@ -139,8 +142,8 @@
{
label: 'Show diff',
callback: async () => {
diffDrawer.openDrawer()
diffDrawer.setDiff({
diffDrawer?.openDrawer()
diffDrawer?.setDiff({
mode: 'simple',
original: deployed,
current: draft,
@@ -156,18 +159,20 @@
}
}
$: {
$effect(() => {
if ($workspaceStore) {
loadApp()
untrack(() => {
loadApp()
})
}
}
})
async function restoreDraft() {
if (!savedApp || !savedApp.draft) {
sendUserToast('Could not restore to draft', true)
return
}
diffDrawer.closeDrawer()
diffDrawer?.closeDrawer()
goto(`/apps/edit/${savedApp.draft.path}`)
await loadApp()
redraw++
@@ -178,7 +183,7 @@
sendUserToast('Could not restore to deployed', true)
return
}
diffDrawer.closeDrawer()
diffDrawer?.closeDrawer()
if (savedApp.draft) {
await DraftService.deleteDraft({
workspace: $workspaceStore!,
@@ -191,7 +196,7 @@
redraw++
}
let diffDrawer: DiffDrawer
let diffDrawer: DiffDrawer | undefined = $state()
function onRestore(ev: any) {
sendUserToast('App restored from previous deployment')
@@ -233,18 +238,17 @@
replaceStateFn={(path) => replaceState(path, $page.state)}
gotoFn={(path, opt) => goto(path, opt)}
>
<svelte:fragment
slot="unsavedConfirmationModal"
let:diffDrawer
let:additionalExitAction
let:getInitialAndModifiedValues
>
{#snippet unsavedConfirmationModal({
diffDrawer,
additionalExitAction,
getInitialAndModifiedValues
})}
<UnsavedConfirmationModal
{diffDrawer}
{additionalExitAction}
{getInitialAndModifiedValues}
/>
</svelte:fragment>
{/snippet}
</AppEditor>
</div>
{/if}