Compare commits

...

16 Commits

Author SHA1 Message Date
Guilhem
74795bec71 Merge remote-tracking branch 'origin/main' into feat/frontend/save-flow-builder-layout
# Conflicts:
#	frontend/src/lib/components/FlowBuilder.svelte
2025-06-23 15:03:56 +01:00
Guilhem
99ae6ff5be Update frontend/src/lib/components/splitPanes/SplitPanesLayout.svelte.ts
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-06-20 12:58:55 +01:00
Guilhem
d97a6dde21 fix potential null pointer issue 2025-06-20 08:07:41 +01:00
Guilhem
4c90d1730d fix rebase issue 2025-06-19 16:49:58 +01:00
Guilhem
3167b707e3 Merge remote-tracking branch 'origin/main' into feat/frontend/save-flow-builder-layout 2025-06-19 16:35:40 +01:00
Guilhem
51caf46636 add tab state to tabs V2 2025-06-18 11:47:49 +01:00
Guilhem
24e020f091 Merge remote-tracking branch 'origin/main' into feat/frontend/save-flow-builder-layout
# Conflicts:
#	frontend/src/lib/components/FlowBuilder.svelte
#	frontend/src/lib/components/common/tabs/Tabs.svelte
#	frontend/src/lib/components/flows/FlowEditor.svelte
#	frontend/src/lib/components/flows/content/FlowBranchesAllWrapper.svelte
#	frontend/src/lib/components/flows/content/FlowBranchesOneWrapper.svelte
#	frontend/src/lib/components/flows/content/FlowLoop.svelte
#	frontend/src/lib/components/flows/content/FlowModuleComponent.svelte
#	frontend/src/lib/components/flows/content/FlowWhileLoop.svelte
#	frontend/src/routes/(root)/(logged)/flows/add/+page.svelte
#	frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte
2025-06-18 11:39:12 +01:00
Guilhem
b6bffaccc9 Merge remote-tracking branch 'origin/main' into feat/frontend/save-flow-builder-layout 2025-06-02 12:44:11 +01:00
Guilhem
f11e997cce save flow step result layout 2025-06-02 12:40:28 +01:00
Guilhem
e90572975c add tabs and layout save to flow-add 2025-06-02 12:40:10 +01:00
Guilhem
365ab12118 simplify panes addition logic 2025-06-02 12:23:48 +01:00
Guilhem
bd40be76a4 add tab memory for all modules 2025-06-02 08:42:37 +02:00
Guilhem
5b43fe3cc4 clean 2025-05-28 20:49:30 +02:00
Guilhem
520c9e7016 handle context unavailable 2025-05-28 17:59:41 +02:00
Guilhem
44aba60614 Allow tabs to save state 2025-05-28 17:55:04 +02:00
Guilhem
d600cd0ec7 Add custom Pane and Splipanes for layout saving 2025-05-28 17:40:16 +02:00
21 changed files with 417 additions and 52 deletions

View File

@@ -72,6 +72,12 @@
} from './triggers/utils'
import DraftTriggersConfirmationModal from './common/confirmationModal/DraftTriggersConfirmationModal.svelte'
import { Triggers } from './triggers/triggers.svelte'
import {
SplitPanesLayout,
setSplitPanesLayoutContext
} from './splitPanes/SplitPanesLayout.svelte'
import type { PanesLayout } from './splitPanes/types'
import { setTabStateContext, TabsState } from './common/tabs/tabsState.svelte'
import { TestSteps } from './flows/testSteps.svelte'
import { aiChatManager } from './copilot/chat/AIChatManager.svelte'
@@ -94,6 +100,8 @@
setSavedraftCb?: ((cb: () => void) => void) | undefined
draftTriggersFromUrl?: Trigger[] | undefined
selectedTriggerIndexFromUrl?: number | undefined
savedSplitPanesLayout?: Record<string, PanesLayout> | undefined
tabsStateFromUrl?: Record<string, string> | undefined
children?: import('svelte').Snippet
}
@@ -116,6 +124,8 @@
setSavedraftCb = undefined,
draftTriggersFromUrl = undefined,
selectedTriggerIndexFromUrl = undefined,
savedSplitPanesLayout = undefined,
tabsStateFromUrl = undefined,
children
}: Props = $props()
@@ -147,6 +157,24 @@
confirmDeploymentCallback(selectedTriggers)
}
const splitPanesLayout = new SplitPanesLayout(savedSplitPanesLayout ?? {}, saveSessionDraft)
setSplitPanesLayoutContext(splitPanesLayout)
const tabsState = new TabsState(tabsStateFromUrl ?? {}, saveSessionDraft)
setTabStateContext(tabsState)
export function setSplitPanesLayout(layout: Record<string, PanesLayout> | undefined) {
if (layout !== undefined) {
splitPanesLayout.layout = layout
}
}
export function setTabsState(newTabsState: Record<string, string> | undefined) {
if (newTabsState !== undefined) {
tabsState.selected = newTabsState
}
}
export function getInitialAndModifiedValues(): SavedAndModifiedValue {
return {
savedValue: savedFlow,
@@ -198,7 +226,7 @@
}
export function setSelectedTriggerIndex(index: number | undefined) {
triggersState.selectedTriggerIndex = index
selectedTriggerIndexFromUrl = index
}
let loadingSave = $state(false)
@@ -499,7 +527,9 @@
path: $pathStore,
selectedId: $selectedIdStore,
draft_triggers: triggersState.getDraftTriggersSnapshot(),
selected_trigger: triggersState.getSelectedTriggerSnapshot()
selected_trigger: triggersState.getSelectedTriggerSnapshot(),
split_panes_layout: splitPanesLayout.getLayoutSnapshot(),
tabs_state: tabsState.getTabsStateSnapshot()
})
)
} catch (err) {
@@ -596,6 +626,8 @@
if (savedFlow && savedFlow.draft) {
savedFlow = filterDraftTriggers(savedFlow, triggersState) as FlowWithDraftAndDraftTriggers
}
triggersState.selectedTriggerIndex = selectedTriggerIndexFromUrl
}
function onKeyDown(event: KeyboardEvent) {

View File

@@ -6,7 +6,7 @@
import type Editor from './Editor.svelte'
import type { Script, Job, FlowModule } from '$lib/gen'
import OutputPickerInner from '$lib/components/flows/propPicker/OutputPickerInner.svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { Pane, Splitpanes } from '$lib/components/splitPanes'
import type { FlowEditorContext } from './flows/types'
import { getContext } from 'svelte'
import { getStringError } from './copilot/chat/utils'
@@ -61,8 +61,8 @@
let forceJson = $state(false)
</script>
<Splitpanes horizontal>
<Pane size={50} minSize={10} class="text-sm text-tertiary">
<Splitpanes horizontal id={`module-preview-result-viewer-${mod.id}`} defaultSizes={[50, 50]}>
<Pane index={0} minSize={10} class="text-sm text-tertiary">
{#if scriptProgress}
<JobProgressBar
job={testJob}
@@ -96,7 +96,7 @@
{/snippet}
</OutputPickerInner>
</Pane>
<Pane size={50} minSize={10}>
<Pane index={1} minSize={10}>
{#if (mod.mock?.enabled && preview != 'job') || preview == 'mock'}
<LogViewer
small

View File

@@ -1,8 +1,9 @@
<script lang="ts">
import { setContext, untrack } from 'svelte'
import { onMount, setContext, untrack } from 'svelte'
import { writable } from 'svelte/store'
import { createEventDispatcher } from 'svelte'
import { twMerge } from 'tailwind-merge'
import { getTabStateContext, type TabsState } from './tabsState.svelte'
import type { TabsContext } from '$lib/components/apps/editor/settingsPanel/inputEditor/tabs.svelte'
const dispatch = createEventDispatcher<{ selected: string }>()
@@ -17,6 +18,7 @@
values?: string[] | undefined
children?: import('svelte').Snippet<[any]>
content?: import('svelte').Snippet
id?: string
}
let {
@@ -28,9 +30,11 @@
hashNavigation = false,
values = undefined,
children,
content
content,
id
}: Props = $props()
let tabsState: TabsState | undefined = $state(undefined)
const selectedStore = writable(selected)
setContext<TabsContext>('Tabs', {
@@ -44,6 +48,9 @@
function updateSelected() {
selectedStore.set(selected)
if (tabsState && id) {
tabsState.setSelected(id, selected)
}
}
let hashValues = $derived(values ? values.map((x) => '#' + x) : undefined)
@@ -58,6 +65,17 @@
}
}
}
onMount(() => {
if (id) {
tabsState = getTabStateContext()
const tabState = tabsState?.getSelected(id)
if (tabState) {
selected = tabState
}
}
})
$effect(() => {
selected && untrack(() => updateSelected())
})

View File

@@ -1,8 +1,9 @@
<script lang="ts">
import { setContext, untrack } from 'svelte'
import { onMount, setContext, untrack } from 'svelte'
import { writable } from 'svelte/store'
import { twMerge } from 'tailwind-merge'
import type { TabsContext } from '$lib/components/apps/editor/settingsPanel/inputEditor/tabs.svelte'
import { getTabStateContext, type TabsState } from './tabsState.svelte'
interface Props {
selected: string
@@ -15,6 +16,7 @@
children?: import('svelte').Snippet<[any]>
content?: import('svelte').Snippet
onSelectedChange?: (value: string) => void
id?: string
}
let {
@@ -27,10 +29,12 @@
values = undefined,
children,
content,
onSelectedChange
onSelectedChange,
id
}: Props = $props()
const selectedStore = writable(selected)
let tabsState: TabsState | undefined = $state(undefined)
setContext<TabsContext>('Tabs', {
selected: selectedStore,
@@ -43,6 +47,9 @@
function updateSelected() {
selectedStore.set(selected)
if (tabsState && id) {
tabsState.setSelected(id, selected)
}
}
function hashChange() {
@@ -63,6 +70,16 @@
})
let hashValues = $derived(values ? values.map((x) => '#' + x) : undefined)
onMount(() => {
if (id) {
tabsState = getTabStateContext()
const tabStateInitial = tabsState?.getSelected(id)
if (tabStateInitial) {
selected = tabStateInitial
}
}
})
</script>
<svelte:window onhashchange={hashChange} />

View File

@@ -0,0 +1,47 @@
import { getContext, setContext } from 'svelte'
const KEY = 'tabsState'
export class TabsState {
#selected: Record<string, string> = $state({})
#changeCb: (() => void) | undefined = undefined
constructor(selected: Record<string, string>, changeCb?: () => void) {
this.#selected = selected
this.#changeCb = changeCb
}
get selected() {
return this.#selected
}
set selected(value: Record<string, string>) {
this.#selected = value
this.#changeCb?.()
}
setChangeCb(cb: () => void) {
this.#changeCb = cb
}
getSelected(id: string) {
return this.#selected[id]
}
setSelected(id: string, value: string) {
this.#selected[id] = value
this.#changeCb?.()
}
getTabsStateSnapshot() {
return $state.snapshot(this.#selected)
}
}
export function setTabStateContext(tabsState: TabsState) {
setContext<TabsState>(KEY, tabsState)
}
export function getTabStateContext(): TabsState | undefined {
return getContext<TabsState>(KEY)
}

View File

@@ -1,17 +1,16 @@
<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'
import { Skeleton } from '../common'
import { getContext, onDestroy, onMount, setContext } from 'svelte'
import type { FlowEditorContext } from './types'
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'
import FlowAIChat from '../copilot/chat/flow/FlowAIChat.svelte'
import { aiChatManager, AIMode } from '../copilot/chat/AIChatManager.svelte'
import TriggerableByAI from '../TriggerableByAI.svelte'
@@ -76,8 +75,8 @@
class={'h-full overflow-hidden transition-colors duration-[400ms] ease-linear border-t'}
>
<TriggerableByAI id="flow-editor" description="Component to edit a flow" />
<Splitpanes>
<Pane size={50} minSize={15} class="h-full relative z-0">
<Splitpanes id="flow-editor" defaultSizes={[60, 40]}>
<Pane minSize={15} class="h-full relative z-0" index={0}>
<div class="grow overflow-hidden bg-gray h-full bg-surface-secondary relative">
{#if loading}
<div class="p-2 pt-10">
@@ -107,7 +106,7 @@
{/if}
</div>
</Pane>
<Pane class="relative z-10" size={50} minSize={20}>
<Pane class="relative z-10" index={1} minSize={20}>
{#if loading}
<div class="w-full h-full">
<div class="block m-auto pt-40 w-10">

View File

@@ -83,7 +83,7 @@
</Pane>
{#if flowModule}
<Pane size={40}>
<TabsV2 bind:selected>
<TabsV2 bind:selected id={`flow-editor-branch-all-${flowModule.id}`}>
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="skip">Skip</Tab>
<Tab value="suspend">Suspend/Approval/Prompt</Tab>

View File

@@ -101,7 +101,7 @@
</Pane>
{#if flowModule}
<Pane size={40}>
<TabsV2 bind:selected>
<TabsV2 bind:selected id={`flow-editor-branch-one-${flowModule.id}`}>
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="skip">Skip</Tab>
<Tab value="suspend">Suspend/Approval/Prompt</Tab>

View File

@@ -264,7 +264,7 @@
{/if}
</Pane>
<Pane size={40} minSize={20} class="flex flex-col flex-1">
<TabsV2 bind:selected>
<TabsV2 bind:selected id={`flow-editor-loop-${mod.id}`}>
<!-- <Tab value="retries">Retries</Tab> -->
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="skip">Skip</Tab>

View File

@@ -1,8 +1,8 @@
<script lang="ts">
import { Splitpanes, Pane } from '$lib/components/splitPanes/index'
import { createBubbler } from 'svelte/legacy'
const bubble = createBubbler()
import { Pane, Splitpanes } from 'svelte-splitpanes'
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'
@@ -184,8 +184,6 @@
}
let forceReload = $state(0)
let editorPanelSize = $state(noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50)
let editorSettingsPanelSize = $state(100 - untrack(() => editorPanelSize))
function onSelectedIdChange() {
if (!$flowStateStore?.[$selectedId]?.schema && flowModule) {
@@ -212,8 +210,6 @@
}
}
let leftPanelSize = $state(0)
function showDiffMode() {
diffMode = true
diffEditor?.setOriginal((savedModule?.value as RawScript).content ?? '')
@@ -227,6 +223,11 @@
diffEditor?.hide()
editor?.show()
}
let panesSizes = $derived([
noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50,
100 - (noEditor ? 0 : flowModule.value.type == 'script' ? 30 : 50)
])
let lastDeployedCode = $derived(onModulesChange(savedModule, flowModule))
let stepPropPicker = $derived(
@@ -254,13 +255,6 @@
let parentLoop = $derived(
flowStore.val && flowModule ? checkIfParentLoop(flowStore.val, flowModule.id) : undefined
)
$effect(() => {
if (selected === 'test') {
leftPanelSize = 50
} else {
leftPanelSize = 100
}
})
$effect(() => {
editor &&
@@ -393,8 +387,12 @@
{/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
defaultSizes={panesSizes}
>
<Pane minSize={10} class="relative" index={0}>
{#if flowModule.value.type === 'rawscript'}
{#if !noEditor}
{#key flowModule.id}
@@ -477,10 +475,10 @@
{/key}
{/if}
</Pane>
<Pane bind:size={editorSettingsPanelSize} minSize={20}>
<Splitpanes>
<Pane minSize={36} bind:size={leftPanelSize}>
<Tabs bind:selected>
<Pane minSize={20} index={1}>
<Splitpanes id={`flow-editor-step-input-${flowModule.id}`} defaultSizes={[50, 50]}>
<Pane minSize={36} index={0}>
<Tabs bind:selected id={`flow-editor-step-input-${flowModule.id}`}>
{#if !preprocessorModule}
<Tab value="inputs">Step Input</Tab>
{/if}
@@ -500,6 +498,7 @@
pickableProperties={stepPropPicker.pickableProperties}
error={failureModule}
noPadding
moduleId={flowModule.id}
>
{#if reloadError}
<div
@@ -546,7 +545,10 @@
focusArg={highlightArg}
/>
{:else if selected === 'advanced'}
<Tabs bind:selected={advancedSelected}>
<Tabs
bind:selected={advancedSelected}
id={`flow-editor-step-advanced-${flowModule.id}`}
>
<Tab value="retries" active={flowModule.retry !== undefined}>Retries</Tab>
{#if !$selectedId.includes('failure')}
<Tab value="runtime">Runtime</Tab>
@@ -570,7 +572,10 @@
{/if}
</Tabs>
{#if advancedSelected === 'runtime'}
<Tabs bind:selected={advancedRuntimeSelected}>
<Tabs
bind:selected={advancedRuntimeSelected}
id={`flow-editor-step-advanced-runtime-${flowModule.id}`}
>
<Tab value="concurrency">Concurrency</Tab>
<Tab value="timeout">Timeout</Tab>
<Tab value="priority">Priority</Tab>
@@ -819,7 +824,7 @@
</div>
</Pane>
{#if selected === 'test'}
<Pane minSize={20}>
<Pane minSize={20} index={1}>
<ModulePreviewResultViewer
lang={flowModule.value['language'] ?? 'deno'}
{editor}

View File

@@ -110,7 +110,7 @@
{/if}
</Pane>
<Pane size={40} minSize={20} class="flex flex-col flex-1">
<TabsV2 bind:selected>
<TabsV2 bind:selected id={`flow-editor-while-loop-${mod.id}`}>
<!-- <Tab value="retries">Retries</Tab> -->
<Tab value="early-stop">Early Stop/Break</Tab>
<Tab value="skip">Skip</Tab>

View File

@@ -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,21 @@
flowPropPickerConfig.set(undefined)
}}
>
<Splitpanes class={$propPickerConfig ? 'splitpanes-remove-splitter' : ''}>
<Splitpanes
class={$propPickerConfig ? 'splitpanes-remove-splitter' : ''}
id={`prop-picker-wrapper-${moduleId}`}
defaultSizes={[60, 40]}
>
<Pane
index={0}
minSize={20}
size={60}
class={twMerge('relative !transition-none ', noPadding ? '' : 'p-2')}
>
<slot />
</Pane>
<Pane
index={1}
minSize={20}
size={40}
class="!transition-none z-1000 {$propPickerConfig ? 'ml-[-1px]' : ''} {paneClass}"
>
<AnimatedButton

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import { Pane } from 'svelte-splitpanes'
import type { Snippet } from 'svelte'
import { getContext, onDestroy, onMount, type ComponentProps } from 'svelte'
import type { SplitPanesContext } from './types'
type SplitpanesProps = ComponentProps<Pane>
export type Props = SplitpanesProps & {
index: number
children?: Snippet
}
let { index, children, ...rest }: Props = $props()
const { sizes, setActivePane, removeActivePane } =
getContext<SplitPanesContext>('splitPanesContext') ?? {}
onMount(() => {
setActivePane(index)
})
onDestroy(() => {
removeActivePane(index)
})
</script>
<Pane size={sizes?.(index)} {...rest}>
{@render children?.()}
</Pane>

View File

@@ -0,0 +1,104 @@
import { setContext, getContext } from 'svelte'
import type { Pane, PanesLayout } from './types'
const KEY = 'splitPanesLayout'
export class SplitPanesLayout {
#layout: Record<string, PanesLayout> = $state({})
#changeCb: (() => void) | undefined = undefined
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?.()
}
setActivePane(layoutId: string, paneIndex: number) {
if (this.#layout[layoutId]?.[paneIndex]?.size == null) {
return
} else {
this.#layout[layoutId][paneIndex].active = true
this.#scalePanes(layoutId, paneIndex)
}
this.#changeCb?.()
}
setPanes(layoutId: string, panes: Pane[]) {
if (!this.#layout[layoutId]) {
this.#layout[layoutId] = []
}
this.#layout[layoutId] = panes
this.#scalePanesTo100(layoutId)
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 removePane(layoutId: string, paneIndex: number) {
if (this.#layout[layoutId][paneIndex].active) {
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?.()
}
setChangeCb(cb: () => void) {
this.#changeCb = cb
}
getLayoutSnapshot(): Record<string, PanesLayout> {
return $state.snapshot(this.#layout)
}
}
export function getSplitPanesLayout(): SplitPanesLayout | undefined {
return getContext<SplitPanesLayout>(KEY)
}
export function setSplitPanesLayoutContext(layout: SplitPanesLayout) {
setContext<SplitPanesLayout>(KEY, layout)
}

View File

@@ -0,0 +1,75 @@
<script lang="ts">
import { Splitpanes } from 'svelte-splitpanes'
import { getSplitPanesLayout } from './SplitPanesLayout.svelte'
import { onDestroy, onMount, setContext, tick, type Snippet } from 'svelte'
import type { ComponentProps } from 'svelte'
import type { Pane, SplitPanesContext } from './types'
type SplitpanesProps = ComponentProps<Splitpanes>
type Props = SplitpanesProps & {
defaultSizes: number[]
id: string
children?: Snippet
}
const splitPanesLayout = getSplitPanesLayout()
let { id, children, defaultSizes, ...rest }: Props = $props()
let ready = $state(false)
let isDestroying = $state(false)
let initialPanes: Pane[] = $state(
splitPanesLayout?.layout[id] ??
defaultSizes.map((size) => ({
size,
active: false
}))
)
const sizes = $derived(splitPanesLayout?.layout[id]?.map((pane) => pane.size) ?? [])
setContext<SplitPanesContext>('splitPanesContext', {
sizes: (index: number) => sizes[index],
setActivePane: (index: number) => {
if (!ready) {
initialPanes[index].active = true
return
}
splitPanesLayout?.setActivePane(id, index)
},
removeActivePane: async (index: number) => {
await tick()
if (!ready || isDestroying) {
initialPanes[index].active = false
return
}
splitPanesLayout?.removePane(id, index)
}
})
function initPanes() {
splitPanesLayout?.setPanes(id, initialPanes)
}
onDestroy(() => {
isDestroying = true
ready = false
})
onMount(() => {
initPanes()
ready = true
})
</script>
<Splitpanes
on:resized={({ detail }) => {
splitPanesLayout?.handleResize(
id,
detail.map((d, index) => ({ size: d.size, index }))
)
}}
{...rest}
>
{@render children?.()}
</Splitpanes>

View File

@@ -0,0 +1,2 @@
export { default as Splitpanes } from './Splitpanes.svelte'
export { default as Pane } from './Pane.svelte'

View File

@@ -0,0 +1,12 @@
export type Pane = {
size: number | undefined
active: boolean
}
export type PanesLayout = Pane[]
export type SplitPanesContext = {
sizes: (index: number) => number | undefined
setActivePane: (index: number) => void
removeActivePane: (index: number) => void
}

View File

@@ -23,7 +23,7 @@
import type { CaptureTriggerKind, Capture } from '$lib/gen'
import CaptureIcon from './CaptureIcon.svelte'
import type { TriggerContext } from '$lib/components/triggers'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { Pane, Splitpanes } from '$lib/components/splitPanes'
import { Popover } from '$lib/components/meltComponents'
import { CaptureService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
@@ -259,8 +259,8 @@
})
</script>
<Splitpanes>
<Pane size={50} minSize={30}>
<Splitpanes id={`capture-section`} defaultSizes={[50, 50]}>
<Pane index={0} minSize={30}>
<div
class="flex flex-col gap-1 px-4 py-2 h-full w-full overflow-auto"
style="scrollbar-gutter: stable"
@@ -342,7 +342,7 @@
</div>
</Pane>
<Pane minSize={30} class="flex flex-col">
<Pane index={1} minSize={30} class="flex flex-col">
<div class="flex flex-row gap-1 justify-between min-h-[33.5px] pl-1 pr-4">
<div class="flex flex-row gap-1 items-center">
{#if lastCapture}

View File

@@ -3,7 +3,7 @@
import FlowCard from '../flows/common/FlowCard.svelte'
import { getContext, onDestroy, createEventDispatcher } from 'svelte'
import type { TriggerContext } from '$lib/components/triggers'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { Pane, Splitpanes } from '$lib/components/splitPanes'
import TriggersTable from './TriggersTable.svelte'
import CaptureWrapper from './CaptureWrapper.svelte'
import { fade } from 'svelte/transition'
@@ -214,8 +214,8 @@
<div bind:clientWidth={width} class="h-full w-full">
<FlowCard {noEditor} noHeader>
<Splitpanes horizontal>
<Pane>
<Splitpanes id="triggers-editor" defaultSizes={[60, 40]} horizontal>
<Pane index={0}>
<div class="flex flex-row h-full">
<!-- Left Pane - Triggers List -->
{#if !useVerticalTriggerBar}
@@ -323,7 +323,7 @@
: undefined}
{#if captureKind}
{#key captureKind}
<Pane minSize={20} size={40}>
<Pane minSize={20} index={1}>
<CaptureWrapper
path={initialPath || fakeInitialPath}
{isFlow}

View File

@@ -15,6 +15,7 @@
import { writable } from 'svelte/store'
import { replaceScriptPlaceholderWithItsValues } from '$lib/hub'
import type { Trigger } from '$lib/components/triggers/utils'
import type { PanesLayout } from '$lib/components/splitPanes/types'
let nodraft = $page.url.searchParams.get('nodraft')
@@ -58,8 +59,11 @@
})
const flowStateStore = writable<FlowState>({})
let splitPanesLayoutFromUrl: Record<string, PanesLayout> | undefined = $state(undefined)
let tabsStateFromUrl: Record<string, string> | undefined = $state(undefined)
let draftTriggersFromUrl: Trigger[] | undefined = $state(undefined)
let selectedTriggerIndexFromUrl: number | undefined = $state(undefined)
async function loadFlow() {
loading = true
let flow: Flow = {
@@ -106,6 +110,10 @@
pathStoreInit = state.path
draftTriggersFromUrl = state.draft_triggers
selectedTriggerIndexFromUrl = state.selected_trigger
splitPanesLayoutFromUrl = state.split_panes_layout
tabsStateFromUrl = state.tabs_state
flowBuilder?.setSplitPanesLayout(splitPanesLayoutFromUrl)
flowBuilder?.setTabsState(tabsStateFromUrl)
flowBuilder?.setDraftTriggers(draftTriggersFromUrl)
flowBuilder?.setSelectedTriggerIndex(selectedTriggerIndexFromUrl)
state?.selectedId && (selectedId = state?.selectedId)
@@ -182,6 +190,8 @@
{loading}
{draftTriggersFromUrl}
{selectedTriggerIndexFromUrl}
savedSplitPanesLayout={splitPanesLayoutFromUrl}
{tabsStateFromUrl}
>
<UnsavedConfirmationModal
getInitialAndModifiedValues={flowBuilder?.getInitialAndModifiedValues}

View File

@@ -21,6 +21,7 @@
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import type { ScheduleTrigger } from '$lib/components/triggers'
import type { Trigger } from '$lib/components/triggers/utils'
import type { PanesLayout } from '$lib/components/splitPanes/types'
import { untrack } from 'svelte'
let version: undefined | number = $state(undefined)
@@ -71,6 +72,8 @@
stateLoadedFromUrl?.primarySchedule
)
let splitPanesLayoutFromUrl: Record<string, PanesLayout> | undefined = $state(undefined)
let tabsStateFromUrl: Record<string, string> | undefined = $state(undefined)
let draftTriggersFromUrl: Trigger[] | undefined = $state(undefined)
let selectedTriggerIndexFromUrl: number | undefined = $state(undefined)
@@ -106,8 +109,12 @@
flow = stateLoadedFromUrl.flow
draftTriggersFromUrl = stateLoadedFromUrl.draft_triggers
selectedTriggerIndexFromUrl = stateLoadedFromUrl.selected_trigger
splitPanesLayoutFromUrl = stateLoadedFromUrl.split_panes_layout
tabsStateFromUrl = stateLoadedFromUrl.tabs_state
flowBuilder?.setSplitPanesLayout(splitPanesLayoutFromUrl)
flowBuilder?.setDraftTriggers(draftTriggersFromUrl)
flowBuilder?.setSelectedTriggerIndex(selectedTriggerIndexFromUrl)
flowBuilder?.setTabsState(stateLoadedFromUrl.tabs_state)
const selectedId = stateLoadedFromUrl?.selectedId ?? 'settings-metadata'
const reloadAction = () => {
stateLoadedFromUrl = undefined
@@ -274,6 +281,7 @@
initialPath={$page.params.path}
newFlow={false}
{selectedId}
savedSplitPanesLayout={splitPanesLayoutFromUrl}
{initialArgs}
{loading}
bind:this={flowBuilder}
@@ -282,6 +290,7 @@
{savedPrimarySchedule}
{draftTriggersFromUrl}
{selectedTriggerIndexFromUrl}
{tabsStateFromUrl}
{version}
>
<UnsavedConfirmationModal