Compare commits
16 Commits
rf/pythonI
...
feat/front
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74795bec71 | ||
|
|
99ae6ff5be | ||
|
|
d97a6dde21 | ||
|
|
4c90d1730d | ||
|
|
3167b707e3 | ||
|
|
51caf46636 | ||
|
|
24e020f091 | ||
|
|
b6bffaccc9 | ||
|
|
f11e997cce | ||
|
|
e90572975c | ||
|
|
365ab12118 | ||
|
|
bd40be76a4 | ||
|
|
5b43fe3cc4 | ||
|
|
520c9e7016 | ||
|
|
44aba60614 | ||
|
|
d600cd0ec7 |
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
|
||||
@@ -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} />
|
||||
|
||||
47
frontend/src/lib/components/common/tabs/tabsState.svelte.ts
Normal file
47
frontend/src/lib/components/common/tabs/tabsState.svelte.ts
Normal 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)
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
30
frontend/src/lib/components/splitPanes/Pane.svelte
Normal file
30
frontend/src/lib/components/splitPanes/Pane.svelte
Normal 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>
|
||||
@@ -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)
|
||||
}
|
||||
75
frontend/src/lib/components/splitPanes/Splitpanes.svelte
Normal file
75
frontend/src/lib/components/splitPanes/Splitpanes.svelte
Normal 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>
|
||||
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'
|
||||
12
frontend/src/lib/components/splitPanes/types.ts
Normal file
12
frontend/src/lib/components/splitPanes/types.ts
Normal 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
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user