feat(frontend): multiselect components for apps

This commit is contained in:
Ruben Fiszel
2023-03-25 16:54:50 +01:00
parent 9ab087a20c
commit 577dec5c57
52 changed files with 378 additions and 318 deletions
@@ -235,7 +235,6 @@
<input
{autofocus}
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
{disabled}
@@ -254,7 +253,6 @@
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
{disabled}
class={valid
@@ -334,7 +332,6 @@
<textarea
bind:this={el}
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
{autofocus}
@@ -355,7 +352,6 @@
{:else if inputCat == 'enum'}
<select
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
{disabled}
@@ -372,7 +368,6 @@
<div class="border my-1 mb-4 w-full border-gray-400">
<SimpleEditor
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
on:blur={() => dispatch('blur')}
@@ -411,7 +406,6 @@
rows="1"
bind:this={el}
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
on:blur={() => dispatch('blur')}
@@ -194,6 +194,14 @@
loadHubScripts()
function onKeyDown(event: KeyboardEvent) {
let classes = event.target?.['className']
if (
(typeof classes === 'string' && classes.includes('inputarea')) ||
['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName!)
) {
return
}
switch (event.key) {
case 'Z':
if (event.ctrlKey) {
@@ -161,7 +161,6 @@
{:else}
<input
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
type="number"
@@ -179,7 +178,6 @@
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
class={valid
? ''
@@ -256,7 +254,6 @@
<textarea
bind:this={el}
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
use:autosize
@@ -274,7 +271,6 @@
{:else if inputCat == 'enum'}
<select
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
class="px-6"
@@ -307,7 +303,6 @@
rows="1"
bind:this={el}
on:focus={(e) => {
window.dispatchEvent(new Event('pointerup'))
dispatch('focus')
}}
on:blur={() => dispatch('blur')}
+1 -1
View File
@@ -38,7 +38,7 @@
</span>
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="relative" on:pointerdown on:click|stopPropagation>
<div class="relative" on:click|stopPropagation>
<input
on:focus
on:click
@@ -79,7 +79,7 @@
event?.stopPropagation()
event?.preventDefault()
$selectedComponent = id
$selectedComponent = [id]
if (preclickAction) {
await preclickAction()
@@ -98,7 +98,6 @@
style={css?.button?.style ?? ''}
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={() => {
runnableComponent?.runComponent()
@@ -106,7 +106,6 @@
btnClasses="my-1"
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={async () => {
if (!runnableComponent) {
@@ -69,7 +69,7 @@
function selectComponent() {
if (!$connectingInput.opened) {
$selectedComponent = id
$selectedComponent = [id]
$focusedGrid = undefined
}
}
@@ -312,7 +312,7 @@
style="padding-top: {controlsHeight ?? 0}px; {css?.container?.style ?? ''}"
/>
{/if}
{#if $mode !== 'preview' && $selectedComponent === id}
{#if $mode !== 'preview' && $selectedComponent?.includes(id)}
<button
class="fixed z-10 bottom-0 left-0 px-2 py-0.5 bg-indigo-500/90
hover:bg-indigo-500 focus:bg-indigo-500 duration-200 text-white text-2xs"
@@ -90,7 +90,7 @@
>
<div
on:pointerdown|stopPropagation={() => {
$selectedComponent = id
$selectedComponent = [id]
}}
style:height="{clientHeight}px"
style:width="{clientWidth}px"
@@ -151,7 +151,7 @@
const hasActions = actionButtons.length >= 1
if (hasActions) {
$selectedComponent = actionButtons[0].id
$selectedComponent = [actionButtons[0].id]
return true
}
return false
@@ -273,13 +273,13 @@
$hoverStore = undefined
}
}}
class={(actionButton.id === $selectedComponent ||
class={($selectedComponent?.includes(actionButton.id) ||
$hoverStore === actionButton.id) &&
$mode !== 'preview'
? 'outline outline-indigo-500 outline-1 outline-offset-1 relative '
: ''}
>
{#if actionButton.id === $selectedComponent || $hoverStore === actionButton.id}
{#if $selectedComponent?.includes(actionButton.id) || $hoverStore === actionButton.id}
<span
title={`Id: ${actionButton.id}`}
class={classNames(
@@ -307,10 +307,10 @@
controls={{
left: () => {
if (actionIndex === 0) {
$selectedComponent = id
$selectedComponent = [id]
return true
} else if (actionIndex > 0) {
$selectedComponent = actionButtons[actionIndex - 1].id
$selectedComponent = [actionButtons[actionIndex - 1].id]
return true
}
return false
@@ -319,7 +319,7 @@
if (actionIndex === actionButtons.length - 1) {
return id
} else if (actionIndex < actionButtons.length - 1) {
$selectedComponent = actionButtons[actionIndex + 1].id
$selectedComponent = [actionButtons[actionIndex + 1].id]
return true
}
return false
@@ -14,7 +14,8 @@
export let name: string
export let inlineScript: InlineScript | undefined
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
export let doNotRecomputeOnInputChanged: boolean = false
export let doNotRecomputeOnInputChanged: boolean
export let recomputableByRefreshButton: boolean
let result: any = undefined
@@ -40,8 +41,8 @@
type: 'runnableByName'
}}
wrapperClass="hidden"
recomputable
{outputs}
{recomputableByRefreshButton}
>
<slot />
</RunnableComponent>
@@ -4,21 +4,12 @@
import type { AppViewerContext } from '../../types'
export let componentId: string
const { runnableComponents, worldStore } = getContext<AppViewerContext>('AppViewerContext')
export let loading: boolean
const { runnableComponents } = getContext<AppViewerContext>('AppViewerContext')
async function refresh() {
window.dispatchEvent(new Event('pointerup'))
await $runnableComponents[componentId]?.()
await $runnableComponents[componentId]?.cb?.()
}
let loading = false
$: $worldStore?.outputsById[componentId]?.['loading']?.subscribe({
id: 'refresh-' + componentId,
next: (value) => {
loading = value
}
})
</script>
<button
@@ -29,11 +29,11 @@
export let wrapperStyle = ''
export let initializing: boolean | undefined = undefined
export let render: boolean
export let recomputable: boolean = false
export let outputs: { result: Output<any>; loading: Output<boolean> }
export let extraKey = ''
export let doNotRecomputeOnInputChanged: boolean = false
export let loading = false
export let recomputableByRefreshButton: boolean = true
const {
worldStore,
@@ -52,12 +52,13 @@
const dispatch = createEventDispatcher()
if (recomputable || autoRefresh) {
$runnableComponents[id] = async (inlineScript?: InlineScript) => {
$runnableComponents[id] = {
autoRefresh: autoRefresh && recomputableByRefreshButton,
cb: async (inlineScript?: InlineScript) => {
await executeComponent(true, inlineScript)
}
$runnableComponents = $runnableComponents
}
$runnableComponents = $runnableComponents
let args: Record<string, any> | undefined = undefined
let testIsLoading = false
@@ -377,7 +378,7 @@
{/if}
{#if !initializing && autoRefresh === true}
<div class="flex absolute top-1 right-1 z-50">
<RefreshButton componentId={id} />
<RefreshButton {loading} componentId={id} />
</div>
{/if}
</div>
@@ -60,7 +60,7 @@
export function onSuccess() {
if (recomputeIds) {
recomputeIds.map((id) => $runnableComponents?.[id]?.())
recomputeIds.map((id) => $runnableComponents?.cb?.[id]?.())
}
if (!doOnSuccess) return
@@ -38,10 +38,6 @@
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<Toggle
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
checked={defaultValue}
options={{ right: labelValue }}
textClass={css?.text?.class ?? ''}
@@ -44,14 +44,14 @@
<AlignWrapper {render} {verticalAlignment}>
{#if inputType === 'date'}
<input
on:focus={() => ($selectedComponent = id)}
on:focus={() => ($selectedComponent = [id])}
on:pointerdown|stopPropagation
type="date"
bind:value
min={minValue}
max={maxValue}
placeholder="Type..."
class={twMerge('mx-0.5', css?.input?.class ?? '')}
class={twMerge(css?.input?.class ?? '')}
style={css?.input?.style ?? ''}
/>
{/if}
@@ -58,7 +58,7 @@
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px" on:pointerdown|stopPropagation>
<div class="app-select w-full" style="height: 34px" on:pointerdown|stopPropagation>
{#if !value || Array.isArray(value)}
<Select
--border-radius="0"
@@ -74,11 +74,11 @@
{placeholder}
on:click={() => {
if (!$connectingInput.opened) {
$selectedComponent = id
$selectedComponent = [id]
}
}}
on:focus={() => {
$selectedComponent = id
$selectedComponent = [id]
}}
floatingConfig={{
strategy: 'fixed'
@@ -46,10 +46,10 @@
<AlignWrapper {render} {verticalAlignment}>
<input
on:pointerdown|stopPropagation={() => ($selectedComponent = id)}
on:focus={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={() => ($selectedComponent = [id])}
on:focus={() => ($selectedComponent = [id])}
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
@@ -54,7 +54,6 @@
function onChange(e: CustomEvent) {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
if (create) {
listItems = listItems.map((i) => {
@@ -105,7 +104,7 @@
<InputValue {id} input={configuration.create} bind:value={create} />
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px;" on:pointerdown|stopPropagation>
<div class="app-select w-full" style="height: 34px;" on:pointerdown|stopPropagation>
<Select
--border-radius="0"
--border-color="#999"
@@ -122,11 +121,11 @@
{placeholder}
on:click={() => {
if (!$connectingInput.opened) {
$selectedComponent = id
$selectedComponent = [id]
}
}}
on:focus={() => {
$selectedComponent = id
$selectedComponent = [id]
}}
floatingConfig={{
strategy: 'fixed'
@@ -69,7 +69,7 @@
class="grow"
style="--range-handle-focus: {'#7e9abd'}; --range-handle: {'#7e9abd'}; {css?.bar?.style ??
''}"
on:pointerdown|stopPropagation={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={() => ($selectedComponent = [id])}
>
<RangeSlider bind:slider bind:values {step} min={+min} max={+max} />
</div>
@@ -2,10 +2,8 @@
import { getContext } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { initOutput } from '../../editor/appUtils'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import { concatCustomCss, selectId } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
@@ -46,12 +44,11 @@
{#if inputType === 'password'}
<input
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 ',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
on:focus|stopPropagation={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={(e) => selectId(e, id, selectedComponent, $app)}
type="password"
bind:value
{placeholder}
@@ -59,12 +56,11 @@
{:else if inputType === 'text'}
<input
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 ',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
on:focus={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={(e) => selectId(e, id, selectedComponent, $app)}
type="text"
bind:value
{placeholder}
@@ -72,12 +68,11 @@
{:else if inputType === 'email'}
<input
class={twMerge(
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 mx-0.5',
'windmillapp w-full py-1.5 text-sm focus:ring-indigo-100 px-2 ',
css?.input?.class ?? ''
)}
style={css?.input?.style ?? ''}
on:focus={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={() => ($selectedComponent = id)}
on:pointerdown|stopPropagation={(e) => selectId(e, id, selectedComponent, $app)}
type="email"
bind:value
{placeholder}
@@ -54,7 +54,7 @@
{#key isNegativeAllowed}
{#key locale}
{#key currency}
<div class="w-full" on:pointerdown|stopPropagation={() => ($selectedComponent = id)}>
<div class="w-full" on:pointerdown|stopPropagation={() => ($selectedComponent = [id])}>
<CurrencyInput
inputClasses={{
formatted: twMerge('px-2 w-full py-1.5 windmillapp', css?.input?.class),
@@ -23,7 +23,7 @@
}
}
$: $selectedComponent === id && onFocus()
$: $selectedComponent?.includes(id) && onFocus()
$: css = concatCustomCss($app.css?.containercomponent, customCss)
</script>
@@ -38,7 +38,7 @@
subGridId={`${id}-0`}
containerHeight={componentContainerHeight}
on:focus={() => {
$selectedComponent = id
$selectedComponent = [id]
}}
/>
{/if}
@@ -47,7 +47,6 @@
{disabled}
on:pointerdown={(e) => {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
}}
on:click={async (e) => {
$focusedGrid = {
@@ -89,7 +88,7 @@
subGridId={`${id}-0`}
containerHeight={1200}
on:focus={() => {
$selectedComponent = id
$selectedComponent = [id]
}}
/>
{/if}
@@ -29,7 +29,7 @@
}
}
$: $selectedComponent === id && onFocus()
$: $selectedComponent?.includes(id) && onFocus()
$: css = concatCustomCss($app.css?.containercomponent, customCss)
$componentControl[id] = {
@@ -73,7 +73,7 @@
<div
class="w-full h-full"
on:pointerdown|stopPropagation={() => {
$selectedComponent = id
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: index
@@ -90,7 +90,7 @@
subGridId={`${id}-${index}`}
containerHeight={horizontal ? undefined : componentContainerHeight - 8}
on:focus={() => {
$selectedComponent = id
$selectedComponent = [id]
$focusedGrid = {
parentComponentId: id,
subGridIndex: index
@@ -118,7 +118,7 @@
? componentContainerHeight - tabHeight
: componentContainerHeight}
on:focus={() => {
$selectedComponent = id
$selectedComponent = [id]
}}
/>
{/each}
@@ -51,7 +51,7 @@
export let fromHub: boolean = false
const appStore = writable<App>(app)
const selectedComponent = writable<string | undefined>(undefined)
const selectedComponent = writable<string[] | undefined>(undefined)
const mode = writable<EditorMode>(initialMode)
const breakpoint = writable<EditorBreakpoint>('lg')
const summaryStore = writable(summary)
@@ -83,7 +83,7 @@
mode,
connectingInput,
breakpoint,
runnableComponents,
runnableComponents: writable({}),
appPath: path,
workspace: $workspaceStore ?? '',
onchange: () => saveDraft(),
@@ -105,7 +105,7 @@
history,
pickVariableCallback,
ontextfocus: writable(undefined),
movingcomponent: writable(undefined),
movingcomponents: writable(undefined),
selectedComponentInEditor: writable(undefined)
})
@@ -140,10 +140,10 @@
let selectedTab: 'insert' | 'settings' = 'insert'
$: if ($selectedComponent) {
let befSelected: string | undefined = undefined
$: if ($selectedComponent?.[0] != befSelected) {
befSelected = $selectedComponent?.[0]
selectedTab = 'settings'
} else {
selectedTab = 'insert'
}
let itemPicker: ItemPicker | undefined = undefined
@@ -247,7 +247,17 @@
let lock = false
function onKeyDown(event: KeyboardEvent) {
if (lock) return
let classes = event.target?.['className']
if (
(typeof classes === 'string' && classes.includes('inputarea')) ||
['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName!)
) {
return
}
lock = true
switch (event.key) {
case 'Z':
if (event.ctrlKey) {
@@ -33,7 +33,7 @@
export let isLocked = false
const appStore = writable<App>(app)
const selectedComponent = writable<string | undefined>(undefined)
const selectedComponent = writable<string[] | undefined>(undefined)
const mode = writable<EditorMode>('preview')
const connectingInput = writable<ConnectingInput>({
@@ -42,8 +42,6 @@
hoveredComponent: undefined
})
const runnableComponents = writable<Record<string, () => Promise<void>>>({})
const parentWidth = writable(0)
setContext<AppViewerContext>('AppViewerContext', {
worldStore: buildWorld(context),
@@ -53,7 +51,7 @@
mode,
connectingInput,
breakpoint,
runnableComponents,
runnableComponents: writable({}),
appPath,
workspace,
onchange: undefined,
@@ -118,7 +116,7 @@
>
<div>
<GridViewer
onTopId={$selectedComponent}
onTopId={$selectedComponent?.[0]}
items={app.grid}
let:dataItem
rowHeight={36}
@@ -128,7 +126,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={'h-full w-full center-center'}
on:pointerdown={() => ($selectedComponent = dataItem.id)}
on:pointerdown={() => ($selectedComponent = [dataItem.id])}
>
<Component render={true} component={dataItem.data} selected={false} locked={true} />
</div>
@@ -159,6 +157,8 @@
inlineScript={script.inlineScript}
name={script.name}
fields={script.fields}
doNotRecomputeOnInputChanged={script.doNotRecomputeOnInputChanged ?? false}
recomputableByRefreshButton={script.autoRefresh ?? false}
/>
{/if}
{/each}
@@ -29,6 +29,7 @@
{#if selected || hover}
<span
on:mousedown|stopPropagation|capture
draggable="false"
title={`Id: ${component.id}`}
class={twMerge(
@@ -12,7 +12,7 @@
import { push } from '$lib/history'
import { expandGriditem, findGridItem } from './appUtils'
import Grid from '../svelte-grid/Grid.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { selectId } from '../utils'
export let policy: Policy
@@ -64,9 +64,9 @@
}
}
function selectComponent(id: string) {
function selectComponent(e: PointerEvent, id: string) {
if (!$connectingInput.opened) {
$selectedComponent = id
selectId(e, id, selectedComponent, $app)
if ($focusedGrid?.parentComponentId != id) {
$focusedGrid = undefined
}
@@ -108,7 +108,7 @@
>
<div class={!$focusedGrid && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
<Grid
onTopId={$selectedComponent}
selectedIds={$selectedComponent}
items={$app.grid}
on:redraw={(e) => {
push(history, $app)
@@ -136,16 +136,16 @@
{/if}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:pointerdown={() => selectComponent(dataItem.id)}
on:pointerdown={(e) => selectComponent(e, dataItem.id)}
class={classNames(
'h-full w-full center-center',
$selectedComponent === dataItem.id ? 'active-grid-item' : ''
Boolean($selectedComponent?.includes(dataItem.id)) ? 'active-grid-item' : ''
)}
>
<Component
render={true}
component={dataItem.data}
selected={$selectedComponent === dataItem.id}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
on:delete={() => removeGridElement(dataItem.data)}
on:lock={() => {
@@ -157,7 +157,7 @@
}}
on:expand={() => {
push(history, $app)
$selectedComponent = dataItem.id
$selectedComponent = [dataItem.id]
expandGriditem($app.grid, dataItem.id, $breakpoint)
$app = $app
}}
@@ -176,7 +176,8 @@
inlineScript={script.inlineScript}
name={script.name}
fields={script.fields}
doNotRecomputeOnInputChanged={script.doNotRecomputeOnInputChanged}
doNotRecomputeOnInputChanged={script.doNotRecomputeOnInputChanged ?? false}
recomputableByRefreshButton={script.autoRefresh ?? false}
/>
{/if}
{/each}
@@ -17,7 +17,7 @@
$worldStore.initializedOutputs ==
allItems($app.grid, $app.subgrids).length + $app.hiddenInlineScripts.length &&
refresh()
$: componentNumber = Object.keys($runnableComponents).length
$: componentNumber = Object.values($runnableComponents).filter((x) => x.autoRefresh).length
function onClick(stopAfterClear = true) {
if (timeout) {
@@ -46,13 +46,10 @@
loading = true
Promise.all(
Object.keys($runnableComponents).map((id) => {
if (id.startsWith('bg_')) {
let index = parseInt(id.split('_')[1])
if (!$app.hiddenInlineScripts[index]?.autoRefresh) {
return
}
if (!$runnableComponents?.[id]?.autoRefresh) {
return
}
return $runnableComponents?.[id]?.()
return $runnableComponents?.[id]?.cb?.()
})
).finally(() => {
loading = false
@@ -82,6 +79,7 @@
<div class="flex items-center">
<Button
disabled={componentNumber == 0}
on:click={() => onClick()}
color={timeout ? 'blue' : 'light'}
variant={timeout ? 'contained' : 'border'}
@@ -14,11 +14,11 @@
$: hiddenInlineScript = $app?.hiddenInlineScripts
?.map((x, i) => ({ script: x, index: i }))
.find(({ script, index }) => `bg_${index}` === $selectedComponent)
.find(({ script, index }) => $selectedComponent?.includes(`bg_${index}`))
$: componentSettings = findComponentSettings($app, $selectedComponent)
$: componentSettings = findComponentSettings($app, $selectedComponent?.[0])
$: tableActionSettings = findTableActionSettings($app, $selectedComponent)
$: tableActionSettings = findTableActionSettings($app, $selectedComponent?.[0])
function findTableActionSettings(app: App, id: string | undefined) {
return allItems(app.grid, app.subgrids)
@@ -9,6 +9,7 @@
import { push } from '$lib/history'
import Grid from '../svelte-grid/Grid.svelte'
import GridViewer from './GridViewer.svelte'
import { selectId } from '../utils'
export let containerHeight: number | undefined = undefined
export let containerWidth: number | undefined = undefined
@@ -35,10 +36,9 @@
dispatch('focus')
}
function selectComponent(id: string) {
function selectComponent(e: PointerEvent, id: string) {
if (!$connectingInput.opened) {
dispatch('focus')
$selectedComponent = id
selectId(e, id, selectedComponent, $app)
}
}
@@ -79,7 +79,7 @@
$app.subgrids[subGridId] = e.detail
}
}}
onTopId={$selectedComponent}
selectedIds={$selectedComponent}
let:dataItem
rowHeight={36}
cols={columnConfiguration}
@@ -106,17 +106,17 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:pointerdown={() => selectComponent(dataItem.id)}
on:pointerdown={(e) => selectComponent(e, dataItem.id)}
class={classNames(
'h-full w-full center-center',
$selectedComponent === dataItem.id ? 'active-grid-item' : '',
$selectedComponent?.includes(dataItem.id) ? 'active-grid-item' : '',
'top-0'
)}
>
<Component
render={visible}
component={dataItem.data}
selected={$selectedComponent === dataItem.id}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
on:lock={() => lock(dataItem)}
on:expand={() => {
@@ -125,7 +125,7 @@
if (!parentGridItem) {
return
}
$selectedComponent = dataItem.id
$selectedComponent = [dataItem.id]
push(editorContext?.history, $app)
expandGriditem(
@@ -142,7 +142,7 @@
</div>
{:else}
<GridViewer
onTopId={$selectedComponent}
onTopId={$selectedComponent?.[0]}
items={$app.subgrids?.[subGridId] ?? []}
let:dataItem
rowHeight={36}
@@ -153,13 +153,13 @@
>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
on:pointerdown={() => selectComponent(dataItem.id)}
on:pointerdown|stopPropagation={(e) => selectComponent(e, dataItem.id)}
class={classNames('h-full w-full center-center', 'top-0')}
>
<Component
render={visible}
component={dataItem.data}
selected={$selectedComponent === dataItem.id}
selected={Boolean($selectedComponent?.includes(dataItem.id))}
locked={isFixed(dataItem)}
/>
</div>
@@ -50,8 +50,9 @@
getContext<AppViewerContext>('AppViewerContext')
const editorContext = getContext<AppEditorContext>('AppEditorContext')
const movingcomponent = editorContext?.movingcomponent
$: ismoving = movingcomponent != undefined && $mode == 'dnd' && $movingcomponent === component.id
const movingcomponents = editorContext?.movingcomponents
$: ismoving =
movingcomponents != undefined && $mode == 'dnd' && $movingcomponents?.includes(component.id)
let initializing: boolean | undefined = undefined
let componentContainerHeight: number = 0
@@ -91,7 +92,7 @@
<button
class="border p-0.5 text-xs"
on:click={() => {
$movingcomponent = undefined
$movingcomponents = undefined
}}>Cancel move</button
>
</div>
@@ -14,10 +14,10 @@
const { app, selectedComponent, worldStore, focusedGrid, componentControl } =
getContext<AppViewerContext>('AppViewerContext')
const { history, movingcomponent } = getContext<AppEditorContext>('AppEditorContext')
const { history, movingcomponents } = getContext<AppEditorContext>('AppEditorContext')
let tempGridItem: GridItem | undefined = undefined
let copiedGridItem: GridItem | undefined = undefined
let tempGridItems: GridItem[] | undefined = undefined
let copiedGridItems: GridItem[] | undefined = undefined
function getSortedGridItemsOfChildren(): GridItem[] {
if (!$focusedGrid) {
@@ -32,7 +32,7 @@
}
function getGridItems(): GridItem[] {
if ($app.grid.find((item) => item.id === $selectedComponent)) {
if ($app.grid.find((item) => item.id === $selectedComponent?.[0])) {
return $app.grid
}
@@ -42,23 +42,25 @@
return (
Object.values($app.subgrids ?? {}).find((grid) =>
grid.find((item) => item.id === $selectedComponent)
grid.find((item) => item.id === $selectedComponent?.[0])
) ?? []
)
}
function left(event: KeyboardEvent) {
if (!$componentControl[$selectedComponent!]?.left?.()) {
if (!$componentControl[$selectedComponent?.[0] ?? '']?.left?.()) {
const sortedGridItems = getGridItems()
const currentIndex = sortedGridItems.findIndex((item) => item.id === $selectedComponent)
const currentIndex = sortedGridItems.findIndex(
(item) => item.id === $selectedComponent?.[0] ?? ''
)
if (currentIndex !== -1 && currentIndex > 0) {
const left = sortedGridItems[currentIndex - 1]
if (left.data.type === 'tablecomponent' && left.data.actionButtons.length >= 1) {
$selectedComponent = left.data.actionButtons[left.data.actionButtons.length - 1].id
$selectedComponent = [left.data.actionButtons[left.data.actionButtons.length - 1].id]
} else {
$selectedComponent = left.id
$selectedComponent = [left.id]
}
}
}
@@ -67,19 +69,21 @@
}
function right(event: KeyboardEvent) {
let r = $componentControl[$selectedComponent!]?.right?.()
let r = $componentControl[$selectedComponent?.[0] ?? '']?.right?.()
if (typeof r === 'string') {
$selectedComponent = r
$selectedComponent = [r]
r = $componentControl[r]?.right?.(true)
}
if (!r) {
const sortedGridItems = getGridItems()
const currentIndex = sortedGridItems.findIndex((item) => item.id === $selectedComponent)
const currentIndex = sortedGridItems.findIndex(
(item) => item.id === $selectedComponent?.[0] ?? ''
)
if (currentIndex !== -1 && currentIndex < sortedGridItems.length - 1) {
$selectedComponent = sortedGridItems[currentIndex + 1].id
$selectedComponent = [sortedGridItems[currentIndex + 1].id]
}
}
@@ -88,7 +92,7 @@
function down(event: KeyboardEvent) {
if (!$focusedGrid) {
$selectedComponent = getSortedGridItemsOfChildren()[0]?.id
$selectedComponent = [getSortedGridItemsOfChildren()[0]?.id]
event.preventDefault()
} else if ($app.subgrids) {
const index = $focusedGrid?.subGridIndex ?? 0
@@ -99,7 +103,7 @@
}
if (subgrid) {
$selectedComponent = subgrid[0].id
$selectedComponent = [subgrid[0].id]
}
event.preventDefault()
}
@@ -113,10 +117,10 @@
function handleArrowUp(event: KeyboardEvent) {
if (!$selectedComponent) return
let parentId = findGridItemParentGrid($app, $selectedComponent)?.split('-')[0]
let parentId = findGridItemParentGrid($app, $selectedComponent?.[0])?.split('-')[0]
if (parentId) {
$selectedComponent = parentId
$selectedComponent = [parentId]
} else {
$selectedComponent = undefined
$focusedGrid = undefined
@@ -127,72 +131,82 @@
if (!$selectedComponent) {
return
}
tempGridItem = undefined
copiedGridItem = findGridItem($app, $selectedComponent)
tempGridItems = undefined
copiedGridItems = $selectedComponent
.map((x) => findGridItem($app, x))
.filter((x) => x != undefined) as GridItem[]
}
function handleCut(event: KeyboardEvent) {
if (!$selectedComponent) {
return
}
$movingcomponent = $selectedComponent
$movingcomponents = JSON.parse(JSON.stringify($selectedComponent))
push(history, $app)
const gridItem = findGridItem($app, $selectedComponent)
copiedGridItem = gridItem
let gridItems = $selectedComponent
.map((x) => findGridItem($app, x))
.filter((x) => x != undefined) as GridItem[]
copiedGridItems = gridItems
if (!gridItem) {
if (!gridItems) {
return
}
// Store the grid item in a temp variable so we can paste it later
tempGridItem = gridItem
tempGridItems = gridItems
}
function handlePaste(event: KeyboardEvent) {
push(history, $app)
$movingcomponent = undefined
if (tempGridItem != undefined) {
if (
$focusedGrid &&
getAllSubgridsAndComponentIds($app, tempGridItem.data)[0].includes(
`${$focusedGrid.parentComponentId}-${$focusedGrid.subGridIndex}`
$movingcomponents = undefined
if (tempGridItems != undefined) {
for (let tempGridItem of tempGridItems) {
if (
$focusedGrid &&
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)
if (parentGrid) {
$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)
}
const gridItem = tempGridItem
insertNewGridItem(
$app,
(id) => ({ ...gridItem.data, id }),
$focusedGrid,
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
tempGridItem.id
)
) {
sendUserToast('Cannot paste a component into itself', true)
return
}
let parentGrid = findGridItemParentGrid($app, tempGridItem.id)
if (parentGrid) {
$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)
copiedGridItems = tempGridItems
$selectedComponent = tempGridItems.map((x) => x.id)
tempGridItems = undefined
} else if (copiedGridItems) {
let nitems: string[] = []
for (let copiedGridItem of copiedGridItems) {
nitems.push(
insertNewGridItem(
$app,
(id) => ({ ...copiedGridItem.data, id }),
$focusedGrid,
Object.fromEntries(gridColumns.map((column) => [column, copiedGridItem[column]]))
)
)
}
const gridItem = tempGridItem
insertNewGridItem(
$app,
(id) => ({ ...gridItem.data, id }),
$focusedGrid,
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]])),
tempGridItem.id
)
copiedGridItem = tempGridItem
$selectedComponent = tempGridItem.id
tempGridItem = undefined
} else if (copiedGridItem) {
const gridItem = copiedGridItem
$selectedComponent = insertNewGridItem(
$app,
(id) => ({ ...gridItem.data, id }),
$focusedGrid,
Object.fromEntries(gridColumns.map((column) => [column, gridItem[column]]))
)
$selectedComponent = nitems.map((x) => x)
}
$worldStore = $worldStore
@@ -31,7 +31,7 @@
$focusedGrid
)
$selectedComponent = id
$selectedComponent = [id]
$app = $app
$worldStore = $worldStore
}
@@ -36,12 +36,12 @@
function onHeaderClick(manuallyOpen: boolean) {
if (manuallyOpen) {
if (parentId) {
$selectedComponent = parentId
$selectedComponent = [parentId]
} else {
$selectedComponent = undefined
}
} else {
$selectedComponent = gridItem.id
$selectedComponent = [gridItem.id]
}
}
</script>
@@ -14,12 +14,12 @@
function onHeaderClick(manuallyOpen: boolean) {
if (manuallyOpen) {
if (id) {
$selectedComponent = id
$selectedComponent = [id]
} else {
$selectedComponent = undefined
}
} else {
$selectedComponent = id
$selectedComponent = [id]
}
}
</script>
@@ -22,7 +22,10 @@
($hasResult[id] ||
Object.entries($hasResult).some(([key, value]) => value && subids.includes(key)))
$: open =
$expanded || subids.includes($selectedComponent ?? '') || $manuallyOpened[id] || inSearch
$expanded ||
subids.some((x) => $selectedComponent?.includes(x)) ||
$manuallyOpened[id] ||
inSearch
const dispatch = createEventDispatcher()
@@ -64,7 +67,7 @@
class={classNames(
'flex items-center justify-between p-1 cursor-pointer border-b gap-1 truncate',
hoverColor[color],
$selectedComponent == id ? openBackground[color] : 'bg-white',
$selectedComponent?.includes(id) ? openBackground[color] : 'bg-white',
first ? 'border-t' : '',
nested ? 'border-l' : ''
)}
@@ -76,7 +79,7 @@
<div
class={classNames(
'text-2xs ml-0.5 font-bold px-2 py-0.5 rounded-sm',
$selectedComponent == id ? idClass[color] : ' bg-gray-100'
$selectedComponent?.includes(id) ? idClass[color] : ' bg-gray-100'
)}
>
{id}
@@ -191,7 +191,7 @@
btnClasses="!px-2 !py-1 !bg-gray-700 !text-white hover:!bg-gray-900"
on:click={async () => {
runLoading = true
await $runnableComponents[id]?.(!transformer ? inlineScript : undefined)
await $runnableComponents[id]?.cb?.(!transformer ? inlineScript : undefined)
runLoading = false
}}
>
@@ -227,7 +227,7 @@
inlineScript.content = editor?.getCode() ?? ''
}
runLoading = true
await $runnableComponents[id]?.(inlineScript)
await $runnableComponents[id]?.cb?.(inlineScript)
runLoading = false
}}
on:change={async (e) => {
@@ -250,7 +250,7 @@
bind:this={simpleEditor}
cmdEnterAction={async () => {
runLoading = true
await $runnableComponents[id]?.(!transformer ? inlineScript : undefined)
await $runnableComponents[id]?.cb?.(!transformer ? inlineScript : undefined)
runLoading = false
}}
class="h-full"
@@ -16,7 +16,7 @@
function selectScript(id: string) {
$selectedComponentInEditor = id
if (!id.startsWith('unused-') || !id.startsWith('bg_')) {
$selectedComponent = $selectedComponentInEditor.split('_transformer')[0]
$selectedComponent = [$selectedComponentInEditor.split('_transformer')[0]]
}
}
@@ -27,7 +27,7 @@
$selectedComponent != $selectedComponentInEditor &&
!$selectedComponentInEditor?.includes('_transformer')
) {
$selectedComponentInEditor = $selectedComponent
$selectedComponentInEditor = $selectedComponent?.[0]
}
function createBackgroundScript() {
@@ -300,6 +300,10 @@
<Kbd>&uparrow;</Kbd><Kbd>&rightarrow;</Kbd>
<Kbd>ESC</Kbd>
</div>
<div>
<span class="text-gray-600 text-xs mr-2">Add to selection:</span>
<Kbd>&DoubleUpArrow;</Kbd>+<Kbd>click</Kbd>
</div>
</div>
</PanelSection>
{/if}
@@ -39,7 +39,7 @@
$errorByComponent = clearErrorByComponentId(cid, $errorByComponent)
$jobs = clearJobsByComponentId(cid, $jobs)
$selectedComponent = id
$selectedComponent = [id]
$app = $app
}
</script>
@@ -64,10 +64,10 @@
class={classNames(
'w-full text-xs font-bold gap-1 truncate py-1.5 px-2 cursor-pointer transition-all justify-between flex items-center border border-gray-3 rounded-md',
'bg-white border-gray-300 hover:bg-gray-100 focus:bg-gray-100 text-gray-700',
$selectedComponent === component.id ? 'outline outline-blue-500 bg-red-400' : ''
$selectedComponent?.includes(component.id) ? 'outline outline-blue-500 bg-red-400' : ''
)}
on:click={() => {
$selectedComponent = component.id
$selectedComponent = [component.id]
}}
on:keypress
>
@@ -34,6 +34,6 @@
bind:inlineScript={runnable.inlineScript}
{onLoad}
{doNotRecomputeOnInputChanged}
id={$selectedComponent}
id={$selectedComponent?.[0]}
{onClick}
/>
@@ -18,7 +18,7 @@
export let fastStart = false
export let throttleUpdate = 100
export let throttleResize = 100
export let onTopId: string | undefined = undefined
export let selectedIds: string[] | undefined
export let containerWidth: number | undefined = undefined
export let scroller: HTMLElement | undefined = undefined
@@ -37,13 +37,6 @@
$: containerHeight = getContainerHeight(items, yPerPx, getComputedCols)
const pointerup = (ev) => {
dispatch('pointerup', {
id: ev.detail.id,
cols: getComputedCols
})
}
const onResize = throttle(() => {
items = specifyUndefinedColumns(items, getComputedCols, cols)
dispatch('resize', {
@@ -91,6 +84,7 @@
let initItems: FilledItem<T>[] | undefined = undefined
const updateMatrix = ({ detail }) => {
console.log('updateMatrix', detail)
let isPointerUp = detail.isPointerUp
let citems: FilledItem<T>[]
if (isPointerUp) {
@@ -107,26 +101,32 @@
citems = JSON.parse(JSON.stringify(initItems))
}
let activeItem = getItemById(detail.id, citems)
sortedItems = citems
for (let id of selectedIds ?? []) {
let activeItem = getItemById(id, sortedItems)
if (activeItem) {
activeItem = {
...activeItem,
[getComputedCols]: {
...activeItem[getComputedCols],
...detail.shadow
if (activeItem) {
activeItem = {
...activeItem,
[getComputedCols]: {
...activeItem[getComputedCols],
...shadows[id]
}
}
sortedItems = moveItem(
activeItem,
sortedItems,
getComputedCols,
getItemById(id, sortedItems)
)
}
}
sortedItems = moveItem(activeItem, citems, getComputedCols, getItemById(detail.id, citems))
if (detail.onUpdate) detail.onUpdate()
dispatch('change', {
unsafeItem: activeItem,
id: activeItem.id,
cols: getComputedCols
})
for (let id of selectedIds ?? []) {
if (detail.activate) {
moveResizes?.[id]?.inActivate()
}
}
if (isPointerUp) {
@@ -146,15 +146,37 @@
updateMatrix({ detail })
}
}
let moveResizes: Record<string, MoveResize> = {}
let shadows: Record<string, { x: number; y: number; w: number; h: number } | undefined> = {}
export function handleMove({ detail }) {
Object.entries(moveResizes).forEach(([id, moveResize]) => {
if (selectedIds?.includes(id)) {
moveResize?.updateMove(JSON.parse(JSON.stringify(detail.cordDiff)), detail.eventY)
}
})
throttleMatrix({ detail: { isPointerUp: false, activate: false } })
}
export function handleInitMove({ detail }) {
Object.entries(moveResizes).forEach(([id, moveResize]) => {
if (selectedIds?.includes(id)) {
moveResize?.initmove()
}
})
}
</script>
<div class="svlt-grid-container" style="height: {containerHeight}px" bind:this={container}>
{#if xPerPx || !fastStart}
{#each sortedItems as item (item.id)}
<MoveResize
on:initmove={handleInitMove}
on:move={handleMove}
bind:shadow={shadows[item.id]}
bind:this={moveResizes[item.id]}
on:repaint={handleRepaint}
on:pointerup={pointerup}
onTop={item.id == onTopId}
onTop={Boolean(selectedIds?.includes(item.id))}
id={item.id}
{xPerPx}
{yPerPx}
@@ -191,5 +213,6 @@
.svlt-grid-container {
position: relative;
width: 100%;
user-select: none;
}
</style>
@@ -23,9 +23,10 @@
export let nativeContainer
export let onTop
export let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
const divId = `component-${id}`
let shadowElement
let shadow: { x: number; y: number; w: number; h: number } | undefined = undefined
let active = false
@@ -43,7 +44,7 @@
let anima
const inActivate = () => {
export function inActivate() {
if (shadowElement && shadow != undefined) {
let subgrid = shadowElement.closest('.subgrid')
let irect = shadowElement.getBoundingClientRect()
@@ -80,20 +81,14 @@
anima = setTimeout(() => {
trans = false
}, 100)
dispatch('pointerup', {
id
})
}, 50)
}
}
let repaint = (cb: (() => void) | undefined, isPointerUp: boolean) => {
let repaint = (activate: boolean, isPointerUp: boolean) => {
dispatch('repaint', {
id,
shadow,
isPointerUp,
onUpdate: cb
activate
})
}
@@ -105,7 +100,7 @@
const getContainerFrame = (element) => {
if (element === document.documentElement || !element) {
const { height, top, right, bottom, left } = nativeContainer.getBoundingClientRect()
const { top, bottom } = nativeContainer.getBoundingClientRect()
return {
top: Math.max(0, top),
@@ -118,12 +113,13 @@
const getScroller = (element) => (!element ? document.documentElement : element)
function computeRect(target) {
let gridItem = target.closest('.svlt-grid-item')
function computeRect() {
let gridItem = document.getElementById(divId)
if (!gridItem) return
let subgrid = gridItem.closest('.subgrid')
let irect = gridItem.getBoundingClientRect()
if (subgrid) {
if (subgrid && subgrid.parentElement) {
let subGridParent = subgrid.parentElement
let subGridParentRect = subGridParent.getBoundingClientRect()
@@ -150,25 +146,27 @@
initX = clientX
initY = clientY
capturePos = { x: left, y: top }
shadow = { x: item.x, y: item.y, w: item.w, h: item.h }
newSize = { width, height }
containerFrame = getContainerFrame(container)
scrollElement = getScroller(container)
cordDiff = { x: 0, y: 0 }
active = true
trans = false
computeRect(target)
_scrollTop = scrollElement.scrollTop
dispatch('initmove')
}
window.addEventListener('pointermove', pointermove)
window.addEventListener('pointerup', pointerup)
}
export function initmove() {
computeRect()
newSize = { width, height }
capturePos = { x: left, y: top }
shadow = { x: item.x, y: item.y, w: item.w, h: item.h }
cordDiff = { x: 0, y: 0 }
active = true
trans = false
containerFrame = getContainerFrame(container)
scrollElement = getScroller(container)
_scrollTop = scrollElement.scrollTop
}
let sign = { x: 0, y: 0 }
let vel = { x: 0, y: 0 }
let intervalId: NodeJS.Timer | undefined = undefined
@@ -181,9 +179,8 @@
}
const update = () => {
const _newScrollTop = scrollElement.scrollTop - _scrollTop
const boundX = capturePos.x + cordDiff.x
const _newScrollTop = (scrollElement?.scrollTop ?? 0) - (_scrollTop ?? 0)
const boundY = capturePos.y + (cordDiff.y + _newScrollTop)
let gridX = Math.round(boundX / xPerPx)
@@ -193,7 +190,6 @@
shadow.x = Math.max(Math.min(gridX, cols - shadow.w), 0)
shadow.y = Math.max(gridY, 0)
}
repaint(undefined, false)
}
const pointermove = (event) => {
@@ -203,60 +199,76 @@
event.stopImmediatePropagation()
const { clientX, clientY } = event
cordDiff = { x: clientX - initX, y: clientY - initY }
const cordDiff = { x: clientX - initX, y: clientY - initY }
dispatch('move', { cordDiff, clientY })
}
export function updateMove(newCoordDiff, clientY) {
if (!active) {
active = true
}
if (trans) {
trans = false
}
cordDiff = newCoordDiff
// console.log(cordDiff, id, 'B')
const Y_SENSOR = sensor
let velocityTop = Math.max(0, (containerFrame.top + Y_SENSOR - clientY) / Y_SENSOR)
let velocityBottom = Math.max(0, (clientY - (containerFrame.bottom - Y_SENSOR)) / Y_SENSOR)
if (containerFrame) {
let velocityTop = Math.max(0, (containerFrame.top + Y_SENSOR - clientY) / Y_SENSOR)
let velocityBottom = Math.max(0, (clientY - (containerFrame.bottom - Y_SENSOR)) / Y_SENSOR)
const topSensor = velocityTop > 0 && velocityBottom === 0
const bottomSensor = velocityBottom > 0 && velocityTop === 0
const topSensor = velocityTop > 0 && velocityBottom === 0
const bottomSensor = velocityBottom > 0 && velocityTop === 0
sign.y = topSensor ? -1 : bottomSensor ? 1 : 0
vel.y = sign.y === -1 ? velocityTop : velocityBottom
sign.y = topSensor ? -1 : bottomSensor ? 1 : 0
vel.y = sign.y === -1 ? velocityTop : velocityBottom
if (vel.y > 0) {
if (!intervalId) {
// Start scrolling
// TODO Use requestAnimationFrame
intervalId = setInterval(() => {
scrollElement.scrollTop += 2 * (vel.y + Math.sign(vel.y)) * sign.y
update()
}, 10)
if (vel.y > 0) {
if (!intervalId) {
// Start scrolling
// TODO Use requestAnimationFrame
intervalId = setInterval(() => {
scrollElement.scrollTop += 2 * (vel.y + Math.sign(vel.y)) * sign.y
update()
}, 10)
}
} else if (intervalId) {
stopAutoscroll()
} else {
update()
}
} else if (intervalId) {
stopAutoscroll()
} else {
update()
}
}
const pointerup = (e) => {
dragClosure = undefined
stopAutoscroll()
window.removeEventListener('pointerdown', pointerdown)
window.removeEventListener('pointermove', pointermove)
window.removeEventListener('pointerup', pointerup)
repaint(inActivate, true)
if (!dragClosure) {
repaint(true, true)
} else {
dragClosure = undefined
}
}
// Resize
let resizeInitPos = { x: 0, y: 0 }
let initSize = { width: 0, height: 0 }
const resizePointerDown = (e) => {
e.stopPropagation()
const { pageX, pageY, target } = e
const { pageX, pageY } = e
resizeInitPos = { x: pageX, y: pageY }
initSize = { width, height }
cordDiff = { x: 0, y: 0 }
computeRect(target)
computeRect()
newSize = { width, height }
@@ -287,14 +299,14 @@
shadow.w = Math.round((newSize.width + gapX * 2) / xPerPx)
shadow.h = Math.round((newSize.height + gapY * 2) / yPerPx)
repaint(undefined, false)
repaint(false, false)
}
}
const resizePointerUp = (e) => {
e.stopPropagation()
repaint(inActivate, true)
repaint(true, true)
window.removeEventListener('pointermove', resizePointerMove)
window.removeEventListener('pointerup', resizePointerUp)
@@ -305,12 +317,13 @@
<div
draggable="false"
on:pointerdown|stopPropagation|preventDefault={pointerdown}
id={divId}
class="svlt-grid-item"
class:svlt-grid-active={active || (trans && rect)}
style="width: {active ? newSize.width : width}px; height:{active
? newSize.height
: height}px; {onTop ? 'z-index: 1000;' : ''}
{active
{active && rect
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px);top:${rect.top}px;left:${rect.left}px;`
: trans
? `transform: translate(${cordDiff.x}px, ${cordDiff.y}px); position:absolute; transition: width 0.2s, height 0.2s;`
@@ -16,10 +16,6 @@ function makeItem(item) {
}
const gridHelp = {
normalize(items, col) {
return normalize(items, col)
},
adjust(items, col) {
return adjust(items, col)
},
+5 -3
View File
@@ -131,11 +131,13 @@ export type AppViewerContext = {
worldStore: Writable<World>
app: Writable<App>
summary: Writable<string>
selectedComponent: Writable<string | undefined>
selectedComponent: Writable<string[] | undefined>
mode: Writable<EditorMode>
connectingInput: Writable<ConnectingInput>
breakpoint: Writable<EditorBreakpoint>
runnableComponents: Writable<Record<string, (inlineScript?: InlineScript) => Promise<void>>>
runnableComponents: Writable<
Record<string, { autoRefresh: boolean; cb: (inlineScript?: InlineScript) => Promise<void> }>
>
staticExporter: Writable<Record<string, () => any>>
appPath: string
workspace: string
@@ -167,7 +169,7 @@ export type AppEditorContext = {
pickVariableCallback: Writable<((path: string) => void) | undefined>
ontextfocus: Writable<(() => void) | undefined>
selectedComponentInEditor: Writable<string | undefined>
movingcomponent: Writable<string | undefined>
movingcomponents: Writable<string[] | undefined>
}
export type FocusedGrid = { parentComponentId: string; subGridIndex: number }
+35 -2
View File
@@ -1,12 +1,45 @@
import type { Schema } from '$lib/common'
import { FlowService, ScriptService } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { emptySchema } from '$lib/utils'
import { emptySchema, sendUserToast } from '$lib/utils'
import type { AppComponent, components } from './editor/component'
import type { App, ComponentCssProperty, ComponentCustomCSS, GridItem } from './types'
import type { App, ComponentCssProperty, GridItem } from './types'
import { twMerge } from 'tailwind-merge'
import type { AppInput, InputType, ResultAppInput, StaticAppInput } from './inputType'
import type { Output } from './rx'
import { get, type Writable } from 'svelte/store'
import { findGridItemParentGrid } from './editor/appUtils'
export function selectId(
e: PointerEvent,
id: string,
selectedComponent: Writable<string[] | undefined>,
app: App
) {
if (e.shiftKey) {
selectedComponent.update((old) => {
if (old && old?.[0]) {
if (findGridItemParentGrid(app, old[0]) != findGridItemParentGrid(app, id)) {
sendUserToast('Cannot multi select items from different grids', true)
return old
}
}
if (old == undefined) {
return [id]
}
if (old.includes(id)) {
return old
}
return [...old, id]
})
} else {
if (get(selectedComponent)?.includes(id)) {
return
} else {
selectedComponent.set([id])
}
}
}
export function allItems(
grid: GridItem[],
@@ -15,7 +15,6 @@
import { writable } from 'svelte/store'
let app: AppWithLastVersion | undefined = undefined
let user: GlobalUserInfo | undefined = undefined
let notExists = false
setContext(IS_APP_PUBLIC_CONTEXT_KEY, true)
@@ -31,14 +30,8 @@
}
}
async function loadUser() {
try {
user = await UserService.globalWhoami()
} catch (e) {}
}
if (browser) {
loadApp()
loadUser()
}
const breakpoint = writable<EditorBreakpoint>('lg')
@@ -53,16 +46,7 @@
>Powered by &nbsp;<WindmillIcon />&nbsp;Windmill</a
>
</div>
<div class="z-50 text-xs text-gray-500 fixed top-1 left-2">
<div>
{#if user}
Logged in as {user.email}
{:else}
Not logged in
{/if}
</div>
<a class="text-blue-400" href="/">Go to app</a>
</div>
{#if notExists}
<div class="px-4 mt-20"
><Alert type="error" title="Not found"