Add custom Pane and Splipanes for layout saving
This commit is contained in:
@@ -83,6 +83,11 @@
|
||||
} from './triggers/utils'
|
||||
import DraftTriggersConfirmationModal from './common/confirmationModal/DraftTriggersConfirmationModal.svelte'
|
||||
import { Triggers } from './triggers/triggers.svelte'
|
||||
import {
|
||||
SplitPanesLayout,
|
||||
setSplitPanesLayoutContext,
|
||||
type PanesLayout
|
||||
} from './splitPanes/SplitPanesLayout.svelte'
|
||||
|
||||
export let initialPath: string = ''
|
||||
export let pathStoreInit: string | undefined = undefined
|
||||
@@ -102,6 +107,7 @@
|
||||
export let setSavedraftCb: ((cb: () => void) => void) | undefined = undefined
|
||||
export let draftTriggersFromUrl: Trigger[] | undefined = undefined
|
||||
export let selectedTriggerIndexFromUrl: number | undefined = undefined
|
||||
export let savedSplitPanesLayout: Record<string, PanesLayout> | undefined = undefined
|
||||
|
||||
let initialPathStore = writable(initialPath)
|
||||
$: initialPathStore.set(initialPath)
|
||||
@@ -134,6 +140,15 @@
|
||||
|
||||
$: setContext('customUi', customUi)
|
||||
|
||||
const splitPanesLayout = new SplitPanesLayout(savedSplitPanesLayout ?? {}, saveSessionDraft)
|
||||
setSplitPanesLayoutContext(splitPanesLayout)
|
||||
|
||||
export function setSplitPanesLayout(layout: Record<string, PanesLayout> | undefined) {
|
||||
if (layout !== undefined) {
|
||||
splitPanesLayout.layout = layout
|
||||
}
|
||||
}
|
||||
|
||||
export function getInitialAndModifiedValues(): SavedAndModifiedValue {
|
||||
return {
|
||||
savedValue: savedFlow,
|
||||
@@ -490,7 +505,8 @@
|
||||
path: $pathStore,
|
||||
selectedId: $selectedIdStore,
|
||||
draft_triggers: triggersState.getDraftTriggersSnapshot(),
|
||||
selected_trigger: triggersState.getSelectedTriggerSnapshot()
|
||||
selected_trigger: triggersState.getSelectedTriggerSnapshot(),
|
||||
split_panes_layout: splitPanesLayout.getLayoutSnapshot()
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import FlowEditorPanel from './content/FlowEditorPanel.svelte'
|
||||
import FlowModuleSchemaMap from './map/FlowModuleSchemaMap.svelte'
|
||||
import WindmillIcon from '../icons/WindmillIcon.svelte'
|
||||
@@ -8,12 +7,12 @@
|
||||
import type { FlowEditorContext } from './types'
|
||||
import type { FlowCopilotContext } from '../copilot/flow'
|
||||
import { classNames } from '$lib/utils'
|
||||
|
||||
import { writable } from 'svelte/store'
|
||||
import type { PropPickerContext, FlowPropPickerConfig } from '$lib/components/prop_picker'
|
||||
import type { PickableProperties } from '$lib/components/flows/previousResults'
|
||||
import type { Flow } from '$lib/gen'
|
||||
import type { Trigger } from '$lib/components/triggers/utils'
|
||||
import { Splitpanes, Pane } from '$lib/components/splitPanes/index'
|
||||
const { flowStore } = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
export let loading: boolean
|
||||
@@ -31,8 +30,6 @@
|
||||
| undefined = undefined
|
||||
export let onDeployTrigger: (trigger: Trigger) => void = () => {}
|
||||
|
||||
let size = 50
|
||||
|
||||
const { currentStepStore: copilotCurrentStepStore } =
|
||||
getContext<FlowCopilotContext>('FlowCopilotContext')
|
||||
|
||||
@@ -49,8 +46,8 @@
|
||||
$copilotCurrentStepStore !== undefined ? 'border-gray-500/75' : ''
|
||||
)}
|
||||
>
|
||||
<Splitpanes>
|
||||
<Pane {size} minSize={15} class="h-full relative z-0">
|
||||
<Splitpanes id="flow-editor">
|
||||
<Pane minSize={15} class="h-full relative z-0" index={0} defaultSize={60}>
|
||||
<div class="grow overflow-hidden bg-gray h-full bg-surface-secondary relative">
|
||||
{#if loading}
|
||||
<div class="p-2 pt-10">
|
||||
@@ -72,7 +69,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane class="relative z-10" size={100 - size} minSize={40}>
|
||||
<Pane class="relative z-10" index={1} defaultSize={40}>
|
||||
{#if loading}
|
||||
<div class="w-full h-full">
|
||||
<div class="block m-auto pt-40 w-10">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { Splitpanes, Pane } from '$lib/components/splitPanes/index'
|
||||
import Tab from '$lib/components/common/tabs/Tab.svelte'
|
||||
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
|
||||
import Editor from '$lib/components/Editor.svelte'
|
||||
@@ -202,8 +202,6 @@
|
||||
}
|
||||
|
||||
let forceReload = 0
|
||||
let editorPanelSize = noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50
|
||||
let editorSettingsPanelSize = 100 - editorPanelSize
|
||||
|
||||
$: $selectedId && onSelectedIdChange()
|
||||
|
||||
@@ -260,13 +258,6 @@
|
||||
$: parentLoop =
|
||||
$flowStore && flowModule ? checkIfParentLoop($flowStore, flowModule.id) : undefined
|
||||
|
||||
let leftPanelSize = 0
|
||||
$: if (selected === 'test') {
|
||||
leftPanelSize = 50
|
||||
} else {
|
||||
leftPanelSize = 100
|
||||
}
|
||||
|
||||
function showDiffMode() {
|
||||
diffMode = true
|
||||
diffEditor?.setOriginal((savedModule?.value as RawScript).content ?? '')
|
||||
@@ -280,6 +271,11 @@
|
||||
diffEditor?.hide()
|
||||
editor?.show()
|
||||
}
|
||||
|
||||
let panesSizes = [
|
||||
noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50,
|
||||
100 - (noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50)
|
||||
]
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={onKeyDown} />
|
||||
@@ -380,8 +376,8 @@
|
||||
{/if}
|
||||
|
||||
<div class="min-h-0 flex-grow" id="flow-editor-editor">
|
||||
<Splitpanes horizontal>
|
||||
<Pane bind:size={editorPanelSize} minSize={10} class="relative">
|
||||
<Splitpanes id={`flow-editor-split-${flowModule.id}`} horizontal>
|
||||
<Pane minSize={10} class="relative" index={0} defaultSize={panesSizes[0]}>
|
||||
{#if flowModule.value.type === 'rawscript'}
|
||||
{#if !noEditor}
|
||||
{#key flowModule.id}
|
||||
@@ -457,9 +453,9 @@
|
||||
{/key}
|
||||
{/if}
|
||||
</Pane>
|
||||
<Pane bind:size={editorSettingsPanelSize} minSize={20}>
|
||||
<Splitpanes>
|
||||
<Pane minSize={36} bind:size={leftPanelSize}>
|
||||
<Pane minSize={20} index={1} defaultSize={panesSizes[1]}>
|
||||
<Splitpanes id={`flow-editor-step-input-${flowModule.id}`}>
|
||||
<Pane minSize={36} index={0} defaultSize={selected === 'inputs' ? 100 : 50}>
|
||||
<Tabs bind:selected>
|
||||
{#if !preprocessorModule}
|
||||
<Tab value="inputs">Step Input</Tab>
|
||||
@@ -480,6 +476,7 @@
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
error={failureModule}
|
||||
noPadding
|
||||
moduleId={flowModule.id}
|
||||
>
|
||||
{#if reloadError}
|
||||
<div
|
||||
@@ -781,7 +778,7 @@
|
||||
</div>
|
||||
</Pane>
|
||||
{#if selected === 'test'}
|
||||
<Pane minSize={20}>
|
||||
<Pane minSize={20} index={1} defaultSize={50}>
|
||||
<ModulePreviewResultViewer
|
||||
lang={flowModule.value['language'] ?? 'deno'}
|
||||
{editor}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
import PropPickerResult from '$lib/components/propertyPicker/PropPickerResult.svelte'
|
||||
import { clickOutside } from '$lib/utils'
|
||||
import { createEventDispatcher, getContext, setContext } from 'svelte'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { Pane, Splitpanes } from '$lib/components/splitPanes/index'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
import type { PickableProperties } from '../previousResults'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -44,6 +44,7 @@
|
||||
export let alwaysOn: boolean = false
|
||||
export let paneClass: string = ''
|
||||
export let noFlowPlugConnect = false
|
||||
export let moduleId: string = ''
|
||||
|
||||
const propPickerConfig: Writable<PropPickerConfig | undefined> = writable<
|
||||
PropPickerConfig | undefined
|
||||
@@ -100,17 +101,22 @@
|
||||
flowPropPickerConfig.set(undefined)
|
||||
}}
|
||||
>
|
||||
<Splitpanes class={$propPickerConfig ? 'splitpanes-remove-splitter' : ''}>
|
||||
<Splitpanes
|
||||
class={$propPickerConfig ? 'splitpanes-remove-splitter' : ''}
|
||||
id={`prop-picker-wrapper-${moduleId}`}
|
||||
>
|
||||
<Pane
|
||||
index={0}
|
||||
minSize={20}
|
||||
size={60}
|
||||
defaultSize={60}
|
||||
class={twMerge('relative !transition-none ', noPadding ? '' : 'p-2')}
|
||||
>
|
||||
<slot />
|
||||
</Pane>
|
||||
<Pane
|
||||
index={1}
|
||||
minSize={20}
|
||||
size={40}
|
||||
defaultSize={40}
|
||||
class="!transition-none z-1000 {$propPickerConfig ? 'ml-[-1px]' : ''} {paneClass}"
|
||||
>
|
||||
<AnimatedButton
|
||||
|
||||
32
frontend/src/lib/components/splitPanes/Pane.svelte
Normal file
32
frontend/src/lib/components/splitPanes/Pane.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { Pane } from 'svelte-splitpanes'
|
||||
import { getSplitPanesLayout } from './SplitPanesLayout.svelte'
|
||||
import type { Snippet } from 'svelte'
|
||||
import { type ComponentProps, getContext, onDestroy, onMount } from 'svelte'
|
||||
|
||||
type SplitpanesProps = ComponentProps<Pane>
|
||||
|
||||
type Props = SplitpanesProps & {
|
||||
index: number
|
||||
defaultSize: number
|
||||
children?: Snippet
|
||||
}
|
||||
|
||||
let { index, defaultSize, children, ...rest }: Props = $props()
|
||||
|
||||
const splitPanesId = getContext<string>('splitPanesId')
|
||||
|
||||
const splitPanesLayout = getSplitPanesLayout()
|
||||
|
||||
onMount(() => {
|
||||
splitPanesLayout.mountPane(splitPanesId, index, defaultSize)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
splitPanesLayout.unmountPane(splitPanesId, index)
|
||||
})
|
||||
</script>
|
||||
|
||||
<Pane size={splitPanesLayout.layout[splitPanesId]?.[index]?.size ?? defaultSize} {...rest}>
|
||||
{@render children?.()}
|
||||
</Pane>
|
||||
@@ -0,0 +1,127 @@
|
||||
import { setContext, getContext, tick } from 'svelte'
|
||||
|
||||
const KEY = 'splitPanesLayout'
|
||||
|
||||
export type Pane = {
|
||||
size: number | undefined
|
||||
active: boolean
|
||||
}
|
||||
|
||||
export type PanesLayout = Pane[]
|
||||
|
||||
export class SplitPanesLayout {
|
||||
#layout: Record<string, PanesLayout> = $state({})
|
||||
#changeCb: (() => void) | undefined = undefined
|
||||
#readyPanes: Record<string, boolean> = $state({})
|
||||
|
||||
constructor(initialPaneLayouts?: Record<string, PanesLayout>, changeCb?: () => void) {
|
||||
this.#layout = initialPaneLayouts ?? {}
|
||||
this.#changeCb = changeCb
|
||||
}
|
||||
|
||||
get layout() {
|
||||
return this.#layout
|
||||
}
|
||||
|
||||
set layout(layout: Record<string, PanesLayout>) {
|
||||
this.#layout = layout
|
||||
this.#changeCb?.()
|
||||
}
|
||||
|
||||
mountPane(layoutId: string, paneIndex: number, defaultSize: number) {
|
||||
if (!this.#layout[layoutId]) {
|
||||
this.#layout[layoutId] = [{ size: defaultSize, active: true }]
|
||||
} else if (this.#readyPanes[layoutId]) {
|
||||
// Ensure array is long enough
|
||||
while (this.#layout[layoutId].length <= paneIndex) {
|
||||
this.#layout[layoutId].push({ size: undefined, active: false })
|
||||
}
|
||||
this.#layout[layoutId][paneIndex].active = true
|
||||
this.#layout[layoutId][paneIndex].size = this.#layout[layoutId][paneIndex].size ?? defaultSize
|
||||
this.#scalePanes(layoutId, paneIndex)
|
||||
} else {
|
||||
// Ensure array is long enough
|
||||
while (this.#layout[layoutId].length <= paneIndex) {
|
||||
this.#layout[layoutId].push({ size: undefined, active: false })
|
||||
}
|
||||
|
||||
const currentPane = this.#layout[layoutId][paneIndex]
|
||||
currentPane.active = true
|
||||
currentPane.size = currentPane.size ?? defaultSize
|
||||
}
|
||||
this.#changeCb?.()
|
||||
}
|
||||
|
||||
#scalePanes(layoutId: string, paneIndex: number) {
|
||||
// Scale other active panes while keeping the current pane size
|
||||
const otherPanesTotal = this.#layout[layoutId]
|
||||
.filter((pane, idx) => idx !== paneIndex && pane.active)
|
||||
.reduce((sum, pane) => sum + (pane.size ?? 0), 0)
|
||||
|
||||
if (otherPanesTotal > 0) {
|
||||
const scale = (100 - (this.#layout[layoutId][paneIndex].size ?? 0)) / otherPanesTotal
|
||||
this.#layout[layoutId] = this.#layout[layoutId].map((pane, idx) =>
|
||||
idx === paneIndex ? pane : { ...pane, size: (pane.size ?? 0) * scale }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#scalePanesTo100(layoutId: string) {
|
||||
const activePanesTotal = this.#layout[layoutId]
|
||||
.filter((pane) => pane.active)
|
||||
.reduce((sum, pane) => sum + (pane.size ?? 0), 0)
|
||||
if (activePanesTotal > 0) {
|
||||
const scale = 100 / activePanesTotal
|
||||
this.#layout[layoutId] = this.#layout[layoutId].map((pane) =>
|
||||
pane.active ? { ...pane, size: (pane.size ?? 0) * scale } : pane
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async unmountPane(layoutId: string, paneIndex: number) {
|
||||
// Wait for the dom refresh to avoid update in the case the splitpane has been removed
|
||||
await tick()
|
||||
if (this.#readyPanes[layoutId]) {
|
||||
this.#layout[layoutId][paneIndex].active = false
|
||||
this.#scalePanesTo100(layoutId)
|
||||
}
|
||||
this.#changeCb?.()
|
||||
}
|
||||
|
||||
handleResize(layoutId: string, newSizes: { size: number; index: number }[]) {
|
||||
if (this.#layout[layoutId]) {
|
||||
this.#layout[layoutId] = this.#layout[layoutId].map((pane, idx) => {
|
||||
const newSize = newSizes.find((s) => s.index === idx)
|
||||
return newSize ? { ...pane, size: newSize.size } : pane
|
||||
})
|
||||
}
|
||||
this.#changeCb?.()
|
||||
}
|
||||
|
||||
handleSplitPaneReady(layoutId: string) {
|
||||
this.#readyPanes[layoutId] = true
|
||||
this.#scalePanesTo100(layoutId)
|
||||
this.#changeCb?.()
|
||||
}
|
||||
|
||||
handleSplitPaneDestroy(layoutId: string) {
|
||||
this.#readyPanes[layoutId] = false
|
||||
this.#changeCb?.()
|
||||
}
|
||||
|
||||
setChangeCb(cb: () => void) {
|
||||
this.#changeCb = cb
|
||||
}
|
||||
|
||||
getLayoutSnapshot(): Record<string, PanesLayout> {
|
||||
return $state.snapshot(this.#layout)
|
||||
}
|
||||
}
|
||||
|
||||
export function getSplitPanesLayout(): SplitPanesLayout {
|
||||
return getContext<SplitPanesLayout>(KEY)
|
||||
}
|
||||
|
||||
export function setSplitPanesLayoutContext(layout: SplitPanesLayout) {
|
||||
setContext<SplitPanesLayout>(KEY, layout)
|
||||
}
|
||||
41
frontend/src/lib/components/splitPanes/Splitpanes.svelte
Normal file
41
frontend/src/lib/components/splitPanes/Splitpanes.svelte
Normal file
@@ -0,0 +1,41 @@
|
||||
<script lang="ts">
|
||||
import { Splitpanes } from 'svelte-splitpanes'
|
||||
import { getSplitPanesLayout } from './SplitPanesLayout.svelte'
|
||||
import { onDestroy, onMount, setContext, type Snippet } from 'svelte'
|
||||
import type { ComponentProps } from 'svelte'
|
||||
|
||||
type SplitpanesProps = ComponentProps<Splitpanes>
|
||||
|
||||
type Props = SplitpanesProps & {
|
||||
id: string
|
||||
children?: Snippet
|
||||
}
|
||||
|
||||
let { id, children, ...rest }: Props = $props()
|
||||
let timeout: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
setContext<string>('splitPanesId', id)
|
||||
|
||||
const splitPanesLayout = getSplitPanesLayout()
|
||||
|
||||
onMount(() => {
|
||||
splitPanesLayout.handleSplitPaneReady(id)
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
splitPanesLayout.handleSplitPaneDestroy(id)
|
||||
clearTimeout(timeout)
|
||||
})
|
||||
</script>
|
||||
|
||||
<Splitpanes
|
||||
on:resized={({ detail }) => {
|
||||
splitPanesLayout.handleResize(
|
||||
id,
|
||||
detail.map((d, index) => ({ size: d.size, index }))
|
||||
)
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
{@render children?.()}
|
||||
</Splitpanes>
|
||||
2
frontend/src/lib/components/splitPanes/index.ts
Normal file
2
frontend/src/lib/components/splitPanes/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as Splitpanes } from './Splitpanes.svelte'
|
||||
export { default as Pane } from './Pane.svelte'
|
||||
@@ -16,6 +16,7 @@
|
||||
import type { ScheduleTrigger } from '$lib/components/triggers'
|
||||
import type { GetInitialAndModifiedValues } from '$lib/components/common/confirmationModal/unsavedTypes'
|
||||
import type { Trigger } from '$lib/components/triggers/utils'
|
||||
import type { PanesLayout } from '$lib/components/splitPanes/SplitPanesLayout.svelte'
|
||||
|
||||
let version: undefined | number = undefined
|
||||
let nodraft = $page.url.searchParams.get('nodraft')
|
||||
@@ -63,6 +64,7 @@
|
||||
|
||||
let draftTriggersFromUrl: Trigger[] | undefined = undefined
|
||||
let selectedTriggerIndexFromUrl: number | undefined = undefined
|
||||
let splitPanesLayoutFromUrl: Record<string, PanesLayout> | undefined = undefined
|
||||
|
||||
let flowBuilder: FlowBuilder | undefined = undefined
|
||||
|
||||
@@ -93,6 +95,8 @@
|
||||
flow = stateLoadedFromUrl.flow
|
||||
draftTriggersFromUrl = stateLoadedFromUrl.draft_triggers
|
||||
selectedTriggerIndexFromUrl = stateLoadedFromUrl.selected_trigger
|
||||
splitPanesLayoutFromUrl = stateLoadedFromUrl.split_panes_layout
|
||||
flowBuilder?.setSplitPanesLayout(splitPanesLayoutFromUrl)
|
||||
flowBuilder?.setDraftTriggers(draftTriggersFromUrl)
|
||||
flowBuilder?.setSelectedTriggerIndex(selectedTriggerIndexFromUrl)
|
||||
const selectedId = stateLoadedFromUrl?.selectedId ?? 'settings-metadata'
|
||||
@@ -262,6 +266,7 @@
|
||||
initialPath={$page.params.path}
|
||||
newFlow={false}
|
||||
{selectedId}
|
||||
savedSplitPanesLayout={splitPanesLayoutFromUrl}
|
||||
{initialArgs}
|
||||
{loading}
|
||||
bind:this={flowBuilder}
|
||||
|
||||
Reference in New Issue
Block a user