mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-07 08:02:40 +00:00
refactor(raw_apps): per-pane tab bars with mirrored single-mode lists
Replace the single tab bar above the inner Splitpanes with one DraggableTabs per pane. Splitter now goes floor-to-ceiling through tabs AND content in split mode. In single mode both bars mirror the full tab list, so the visible pane always carries every tab — fixes the bug where activating Preview hid the tab strip. Clicking Preview while in split mode is a no-op (Preview is permanently visible in the right pane). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
f3e01dd4e3
commit
3fef52cbdb
@@ -213,13 +213,23 @@
|
||||
? 'file'
|
||||
: 'runnable'
|
||||
)
|
||||
// When split is on, the Preview tab moves out of the bar and lives in
|
||||
// the right pane. The user only sees file/runnable tabs in the bar;
|
||||
// the right pane is always preview. When split is off, the Preview
|
||||
// tab is back in the bar like any other tab.
|
||||
const displayedTabs = $derived(
|
||||
// Per-pane tab lists.
|
||||
// - Single mode: BOTH panes mirror the FULL tab list — whichever pane is
|
||||
// visible (paneA when source is active, paneB when preview is active)
|
||||
// carries every tab so the user can always switch. This is the fix
|
||||
// for the bug where activating Preview hid every tab.
|
||||
// - Split mode: each pane shows its own subset (VS Code-style):
|
||||
// left = file/runnable tabs, right = just the Preview pseudo-header.
|
||||
const leftPaneTabs = $derived<TabItem[]>(
|
||||
splitWithPreview ? tabs.filter((t) => t.id !== PREVIEW_TAB_ID) : tabs
|
||||
)
|
||||
const rightPaneTabs = $derived<TabItem[]>(
|
||||
splitWithPreview ? tabs.filter((t) => t.id === PREVIEW_TAB_ID) : tabs
|
||||
)
|
||||
// In split mode the Preview tab is permanently visible in the right pane;
|
||||
// the right bar always shows it as the active tab regardless of which
|
||||
// file/runnable tab is logically active in the left pane.
|
||||
const rightPaneActiveId = $derived(splitWithPreview ? PREVIEW_TAB_ID : activeTabId)
|
||||
const showSource = $derived(activeTabKind === 'file')
|
||||
const showRunnable = $derived(activeTabKind === 'runnable')
|
||||
|
||||
@@ -274,6 +284,11 @@
|
||||
function activateTab(id: string) {
|
||||
const tab = tabs.find((t) => t.id === id)
|
||||
if (!tab) return
|
||||
// Clicking the Preview tab while in split mode is a visual no-op —
|
||||
// Preview is already permanently shown in the right pane, and
|
||||
// promoting it to active would trigger the pane-sizing $effect and
|
||||
// collapse the left pane (the bug being fixed here).
|
||||
if (splitWithPreview && id === PREVIEW_TAB_ID) return
|
||||
activeTabId = id
|
||||
if (tab.id === PREVIEW_TAB_ID) {
|
||||
selectedRunnable = undefined
|
||||
@@ -1330,56 +1345,49 @@
|
||||
</Pane>
|
||||
{/if}
|
||||
<Pane>
|
||||
<div class="flex flex-col h-full w-full min-h-0">
|
||||
<!--
|
||||
Main tab bar — always visible above the Splitpanes, full
|
||||
width. The user can switch tabs (including back from
|
||||
Preview to a file) without losing visibility on the rest
|
||||
of the tab list.
|
||||
-->
|
||||
<DraggableTabs
|
||||
tabs={displayedTabs}
|
||||
activeId={activeTabId}
|
||||
onSelect={(id) => activateTab(id)}
|
||||
onClose={(id) => closeTab(id)}
|
||||
onReorder={(next) => reorderTabs(next)}
|
||||
>
|
||||
{#snippet trailing()}
|
||||
<div class="flex items-center gap-1 px-2">
|
||||
<button
|
||||
title={splitWithPreview
|
||||
? 'Move preview back into a tab'
|
||||
: 'Pin preview to the right'}
|
||||
aria-label="Toggle split with preview"
|
||||
aria-pressed={splitWithPreview}
|
||||
class={splitWithPreview
|
||||
? 'cursor-pointer bg-surface-accent-selected text-accent border border-border-selected w-7 h-7 rounded-md inline-flex items-center justify-center'
|
||||
: 'cursor-pointer bg-surface hover:bg-surface-hover border border-border-light text-primary w-7 h-7 rounded-md inline-flex items-center justify-center'}
|
||||
onclick={toggleSplit}
|
||||
<!--
|
||||
Per-pane tab bars (VS Code-style). Each pane carries its own
|
||||
DraggableTabs instance so the splitter goes floor-to-ceiling
|
||||
through tabs AND content in split mode. In single mode both
|
||||
bars mirror the FULL tab list — whichever pane is visible
|
||||
keeps every tab accessible, fixing the bug where activating
|
||||
Preview previously hid every tab.
|
||||
-->
|
||||
<div
|
||||
class="h-full w-full min-h-0 {splitWithPreview &&
|
||||
activeTabKind !== 'preview'
|
||||
? 'tabs-content-split'
|
||||
: 'tabs-content-single'}"
|
||||
>
|
||||
<Splitpanes>
|
||||
<Pane bind:size={paneALeftSize} minSize={0}>
|
||||
<div class="flex flex-col h-full w-full min-h-0">
|
||||
<DraggableTabs
|
||||
tabs={leftPaneTabs}
|
||||
activeId={activeTabId}
|
||||
onSelect={(id) => activateTab(id)}
|
||||
onClose={(id) => closeTab(id)}
|
||||
onReorder={(next) => reorderTabs(next)}
|
||||
>
|
||||
<Columns2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</DraggableTabs>
|
||||
<!--
|
||||
Content area: Splitpanes below the main tab bar. Both
|
||||
iframes + RawAppInlineScriptsPanel stay mounted always;
|
||||
`display` + pane sizes do the swapping. The right pane
|
||||
carries its own Preview pseudo-header with the
|
||||
preview-affecting toolbar (bundler / inspector /
|
||||
rebuild). The header is rendered whenever the right
|
||||
pane is visible — width 0 hides it implicitly.
|
||||
-->
|
||||
<div
|
||||
class="flex-1 min-h-0 w-full {splitWithPreview &&
|
||||
activeTabKind !== 'preview'
|
||||
? 'tabs-content-split'
|
||||
: 'tabs-content-single'}"
|
||||
>
|
||||
<Splitpanes>
|
||||
<Pane bind:size={paneALeftSize} minSize={0}>
|
||||
<div class="relative h-full w-full">
|
||||
{#snippet trailing()}
|
||||
<div class="flex items-center gap-1 px-2">
|
||||
<button
|
||||
title={splitWithPreview
|
||||
? 'Move preview back into a tab'
|
||||
: 'Pin preview to the right'}
|
||||
aria-label="Toggle split with preview"
|
||||
aria-pressed={splitWithPreview}
|
||||
class={splitWithPreview
|
||||
? 'cursor-pointer bg-surface-accent-selected text-accent border border-border-selected w-7 h-7 rounded-md inline-flex items-center justify-center'
|
||||
: 'cursor-pointer bg-surface hover:bg-surface-hover border border-border-light text-primary w-7 h-7 rounded-md inline-flex items-center justify-center'}
|
||||
onclick={toggleSplit}
|
||||
>
|
||||
<Columns2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
</DraggableTabs>
|
||||
<div class="flex-1 min-h-0 relative">
|
||||
<div
|
||||
class="absolute inset-0"
|
||||
style="display: {showSource ? 'block' : 'none'}"
|
||||
@@ -1424,18 +1432,19 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane bind:size={paneBRightSize} minSize={0}>
|
||||
<div class="flex flex-col h-full w-full min-h-0">
|
||||
<div
|
||||
class="flex items-stretch bg-surface-secondary flex-shrink-0 h-8"
|
||||
>
|
||||
<div
|
||||
class="inline-flex items-center gap-1.5 px-3 h-8 text-xs bg-surface text-primary"
|
||||
>
|
||||
<span>Preview</span>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-1 px-2">
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane bind:size={paneBRightSize} minSize={0}>
|
||||
<div class="flex flex-col h-full w-full min-h-0">
|
||||
<DraggableTabs
|
||||
tabs={rightPaneTabs}
|
||||
activeId={rightPaneActiveId}
|
||||
onSelect={(id) => activateTab(id)}
|
||||
onClose={(id) => closeTab(id)}
|
||||
onReorder={(next) => reorderTabs(next)}
|
||||
>
|
||||
{#snippet trailing()}
|
||||
<div class="flex items-center gap-1 px-2">
|
||||
<button
|
||||
class="cursor-pointer bg-surface hover:bg-surface-hover border border-border-light text-primary px-2 h-7 rounded-md text-xs"
|
||||
title="Switch bundler"
|
||||
@@ -1490,18 +1499,31 @@
|
||||
>
|
||||
<RefreshCw size={14} />
|
||||
</button>
|
||||
<button
|
||||
title={splitWithPreview
|
||||
? 'Move preview back into a tab'
|
||||
: 'Pin preview to the right'}
|
||||
aria-label="Toggle split with preview"
|
||||
aria-pressed={splitWithPreview}
|
||||
class={splitWithPreview
|
||||
? 'cursor-pointer bg-surface-accent-selected text-accent border border-border-selected w-7 h-7 rounded-md inline-flex items-center justify-center'
|
||||
: 'cursor-pointer bg-surface hover:bg-surface-hover border border-border-light text-primary w-7 h-7 rounded-md inline-flex items-center justify-center'}
|
||||
onclick={toggleSplit}
|
||||
>
|
||||
<Columns2 size={14} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<iframe
|
||||
bind:this={previewIframe}
|
||||
title="App preview"
|
||||
src="/ui_builder/app-preview.html"
|
||||
class="w-full flex-1 block"
|
||||
></iframe>
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
{/snippet}
|
||||
</DraggableTabs>
|
||||
<iframe
|
||||
bind:this={previewIframe}
|
||||
title="App preview"
|
||||
src="/ui_builder/app-preview.html"
|
||||
class="w-full flex-1 block"
|
||||
></iframe>
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
|
||||
Reference in New Issue
Block a user