mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
Add support for dragging panel tabs docked into other panel tab bars (#4006)
* Add support for dragging panel tabs docked into other panel tab bars * Fix terminology * Add Group suffix to PanelGroupId enums variants * Code review
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
.data-panel {
|
||||
flex-grow: 1;
|
||||
padding: 4px;
|
||||
padding-top: 0;
|
||||
|
||||
table {
|
||||
margin: -4px;
|
||||
@@ -42,7 +43,7 @@
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 0;
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
tr:first-child:has(td:first-child label:empty) ~ tr td:first-child {
|
||||
|
||||
@@ -72,7 +72,10 @@
|
||||
.widget-section {
|
||||
flex: 0 0 auto;
|
||||
margin: 0 4px;
|
||||
margin-top: 4px;
|
||||
|
||||
+ .widget-section {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: left;
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
import Welcome from "/src/components/panels/Welcome.svelte";
|
||||
import IconButton from "/src/components/widgets/buttons/IconButton.svelte";
|
||||
import TextLabel from "/src/components/widgets/labels/TextLabel.svelte";
|
||||
import type { EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
type PanelType = keyof typeof PANEL_COMPONENTS;
|
||||
import { panelDrag, startCrossPanelDrag, endCrossPanelDrag, updateCrossPanelHover } from "/src/stores/panel-drag";
|
||||
import type { EditorWrapper, PanelType, PanelGroupId } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
const PANEL_COMPONENTS = {
|
||||
Welcome,
|
||||
@@ -31,11 +30,13 @@
|
||||
export let tabCloseButtons = false;
|
||||
export let tabLabels: { name: string; unsaved?: boolean; tooltipLabel?: string; tooltipDescription?: string; tooltipShortcut?: string }[];
|
||||
export let tabActiveIndex: number;
|
||||
export let panelType: PanelType | undefined = undefined;
|
||||
export let panelTypes: PanelType[];
|
||||
export let panelId: PanelGroupId;
|
||||
export let clickAction: ((index: number) => void) | undefined = undefined;
|
||||
export let closeAction: ((index: number) => void) | undefined = undefined;
|
||||
export let reorderAction: ((oldIndex: number, newIndex: number) => void) | undefined = undefined;
|
||||
export let emptySpaceAction: (() => void) | undefined = undefined;
|
||||
export let crossPanelDropAction: ((sourcePanelId: string, targetPanelId: string, insertIndex: number) => void) | undefined = undefined;
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
@@ -55,7 +56,7 @@
|
||||
let tabGroupElement: LayoutRow | undefined = undefined;
|
||||
|
||||
onDestroy(() => {
|
||||
removeDragListeners();
|
||||
endDrag();
|
||||
});
|
||||
|
||||
function onEmptySpaceAction(e: MouseEvent) {
|
||||
@@ -77,7 +78,10 @@
|
||||
// Activate the tab upon pointer down
|
||||
clickAction?.(tabIndex);
|
||||
|
||||
if (!reorderAction || tabLabels.length < 2) return;
|
||||
// Allow within-panel reorder if there are multiple tabs, or cross-panel drag if this panel supports docking
|
||||
const canReorder = reorderAction && tabLabels.length > 1;
|
||||
const canCrossPanelDrag = crossPanelDropAction !== undefined;
|
||||
if (!canReorder && !canCrossPanelDrag) return;
|
||||
|
||||
dragStartState = { tabIndex, pointerX: e.clientX, pointerY: e.clientY };
|
||||
dragging = false;
|
||||
@@ -95,30 +99,72 @@
|
||||
const deltaX = Math.abs(e.clientX - dragStartState.pointerX);
|
||||
const deltaY = Math.abs(e.clientY - dragStartState.pointerY);
|
||||
if (deltaX < DRAG_ACTIVATION_DISTANCE && deltaY < DRAG_ACTIVATION_DISTANCE) return;
|
||||
|
||||
dragging = true;
|
||||
|
||||
if (crossPanelDropAction) {
|
||||
// Notify the shared store that a cross-panel drag has started
|
||||
startCrossPanelDrag(panelId, tabLabels[dragStartState.tabIndex].name, dragStartState.tabIndex);
|
||||
}
|
||||
}
|
||||
|
||||
lastPointerX = e.clientX;
|
||||
|
||||
// Only show insertion line while the cursor is within the tab bar
|
||||
// Exit early in here after we show the insertion marker, if we're within our own tab bar
|
||||
if (pointerIsInsideTabBar(e)) {
|
||||
calculateInsertionIndex(lastPointerX);
|
||||
} else {
|
||||
insertionIndex = undefined;
|
||||
insertionMarkerLeft = undefined;
|
||||
updateCrossPanelHover(undefined, undefined, undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear local insertion marker since we're outside our own tab bar
|
||||
insertionIndex = undefined;
|
||||
insertionMarkerLeft = undefined;
|
||||
|
||||
// Check if the pointer is over any other dockable panel's tab bar
|
||||
if (crossPanelDropAction) {
|
||||
const target = Array.from(document.querySelectorAll("[data-panel-tab-bar]")).find((element) => {
|
||||
const targetPanelId = element.getAttribute("data-panel-tab-bar");
|
||||
if (!targetPanelId || targetPanelId === panelId) return false;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
return e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
||||
});
|
||||
|
||||
const targetPanelId = target?.getAttribute("data-panel-tab-bar");
|
||||
if (target instanceof HTMLDivElement && targetPanelId) {
|
||||
calculateForeignInsertionIndex(e.clientX, targetPanelId, target);
|
||||
} else {
|
||||
updateCrossPanelHover(undefined, undefined, undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dragPointerUp() {
|
||||
if (dragging && dragStartState && insertionIndex !== undefined) {
|
||||
const oldIndex = dragStartState.tabIndex;
|
||||
if (dragging && dragStartState) {
|
||||
const crossPanelState = $panelDrag;
|
||||
|
||||
// Adjust for the fact that removing the dragged tab shifts indices
|
||||
let newIndex = insertionIndex;
|
||||
if (newIndex > oldIndex) newIndex -= 1;
|
||||
// Cross-panel drop: the pointer is over a different panel's tab bar
|
||||
if (
|
||||
crossPanelDropAction &&
|
||||
crossPanelState.active &&
|
||||
crossPanelState.hoverTargetPanelId &&
|
||||
crossPanelState.hoverTargetPanelId !== panelId &&
|
||||
crossPanelState.hoverInsertionIndex !== undefined
|
||||
) {
|
||||
crossPanelDropAction?.(panelId, crossPanelState.hoverTargetPanelId, crossPanelState.hoverInsertionIndex);
|
||||
}
|
||||
// Within-panel reorder
|
||||
else if (insertionIndex !== undefined) {
|
||||
const oldIndex = dragStartState.tabIndex;
|
||||
|
||||
if (oldIndex !== newIndex) {
|
||||
reorderAction?.(oldIndex, newIndex);
|
||||
// Adjust for the fact that removing the dragged tab shifts indices
|
||||
let newIndex = insertionIndex;
|
||||
if (newIndex > oldIndex) newIndex -= 1;
|
||||
|
||||
if (oldIndex !== newIndex) {
|
||||
reorderAction?.(oldIndex, newIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +187,7 @@
|
||||
dragging = false;
|
||||
insertionIndex = undefined;
|
||||
insertionMarkerLeft = undefined;
|
||||
if (crossPanelDropAction) endCrossPanelDrag();
|
||||
removeDragListeners();
|
||||
}
|
||||
|
||||
@@ -152,6 +199,30 @@
|
||||
return e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
||||
}
|
||||
|
||||
// Calculate the insertion position for a foreign panel's tab bar
|
||||
function calculateForeignInsertionIndex(pointerX: number, targetPanelId: string, tabBarDiv: HTMLDivElement) {
|
||||
const tabBarRect = tabBarDiv.getBoundingClientRect();
|
||||
const tabs = tabBarDiv.querySelectorAll(":scope > [data-tab]");
|
||||
let bestIndex = 0;
|
||||
let bestMarkerLeft = 0;
|
||||
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tabRect = tabs[i].getBoundingClientRect();
|
||||
const tabCenter = tabRect.left + tabRect.width / 2;
|
||||
|
||||
if (pointerX > tabCenter) {
|
||||
bestIndex = i + 1;
|
||||
bestMarkerLeft = tabRect.right - tabBarRect.left;
|
||||
} else {
|
||||
bestMarkerLeft = tabRect.left - tabBarRect.left;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Must be at least 2px from the left so its left half doesn't get cut off along the left of the tab bar
|
||||
updateCrossPanelHover(targetPanelId, bestIndex, Math.max(2, bestMarkerLeft));
|
||||
}
|
||||
|
||||
function calculateInsertionIndex(pointerX: number) {
|
||||
const groupDiv = tabGroupElement?.div?.();
|
||||
if (!dragStartState || !groupDiv) return;
|
||||
@@ -199,13 +270,21 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutCol on:pointerdown={() => panelType && editor.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>
|
||||
<LayoutCol on:pointerdown={() => panelTypes[tabActiveIndex] && editor.setActivePanel(panelTypes[tabActiveIndex])} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>
|
||||
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
|
||||
<LayoutRow class="tab-group" scrollableX={true} on:click={onEmptySpaceAction} on:auxclick={onEmptySpaceAction} bind:this={tabGroupElement}>
|
||||
<LayoutRow
|
||||
class="tab-group"
|
||||
scrollableX={true}
|
||||
data-panel-tab-bar={crossPanelDropAction ? panelId : undefined}
|
||||
on:click={onEmptySpaceAction}
|
||||
on:auxclick={onEmptySpaceAction}
|
||||
bind:this={tabGroupElement}
|
||||
>
|
||||
{#each tabLabels as tabLabel, tabIndex}
|
||||
<LayoutRow
|
||||
class="tab"
|
||||
classes={{ active: tabIndex === tabActiveIndex }}
|
||||
data-tab
|
||||
tooltipLabel={tabLabel.tooltipLabel}
|
||||
tooltipDescription={tabLabel.tooltipDescription}
|
||||
on:pointerdown={(e) => tabPointerDown(e, tabIndex)}
|
||||
@@ -242,10 +321,13 @@
|
||||
{#if dragging && insertionMarkerLeft !== undefined}
|
||||
<div class="tab-insertion-mark" style:left={`${insertionMarkerLeft}px`}></div>
|
||||
{/if}
|
||||
{#if !dragging && crossPanelDropAction && $panelDrag.active && $panelDrag.hoverTargetPanelId === panelId && $panelDrag.hoverInsertionMarkerLeft !== undefined}
|
||||
<div class="tab-insertion-mark" style:left={`${$panelDrag.hoverInsertionMarkerLeft}px`}></div>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
<LayoutCol class="panel-body">
|
||||
{#if panelType}
|
||||
<svelte:component this={PANEL_COMPONENTS[panelType]} />
|
||||
{#if panelTypes[tabActiveIndex]}
|
||||
<svelte:component this={PANEL_COMPONENTS[panelTypes[tabActiveIndex]]} />
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
|
||||
@@ -23,9 +23,11 @@
|
||||
let pointerCaptureId: number | undefined = undefined;
|
||||
let activeResizeCleanup: (() => void) | undefined = undefined;
|
||||
|
||||
onDestroy(() => {
|
||||
activeResizeCleanup?.();
|
||||
});
|
||||
// Reactive panel layout derived from backend state
|
||||
$: panelLayout = $portfolio.panelLayout;
|
||||
$: propertiesGroup = panelLayout.propertiesGroup;
|
||||
$: layersGroup = panelLayout.layersGroup;
|
||||
$: dataGroup = panelLayout.dataGroup;
|
||||
|
||||
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex);
|
||||
|
||||
@@ -41,6 +43,10 @@
|
||||
const editor = getContext<EditorWrapper>("editor");
|
||||
const portfolio = getContext<PortfolioStore>("portfolio");
|
||||
|
||||
function crossPanelDrop(sourcePanelId: string, targetPanelId: string, insertIndex: number) {
|
||||
editor.movePanelTab(sourcePanelId, targetPanelId, insertIndex);
|
||||
}
|
||||
|
||||
function isPanelName(name: string): name is keyof typeof PANEL_SIZES {
|
||||
return name in PANEL_SIZES;
|
||||
}
|
||||
@@ -155,6 +161,10 @@
|
||||
addListeners();
|
||||
activeResizeCleanup = removeListeners;
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
activeResizeCleanup?.();
|
||||
});
|
||||
</script>
|
||||
|
||||
<LayoutRow class="workspace" data-workspace>
|
||||
@@ -163,7 +173,8 @@
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["document"] }} data-subdivision-name="document">
|
||||
<Panel
|
||||
class="document-panel"
|
||||
panelType={$portfolio.documents.length > 0 ? "Document" : "Welcome"}
|
||||
panelId="DocumentGroup"
|
||||
panelTypes={$portfolio.documents.length > 0 ? $portfolio.documents.map(() => "Document") : ["Welcome"]}
|
||||
tabCloseButtons={true}
|
||||
tabMinWidths={true}
|
||||
tabLabels={documentTabLabels}
|
||||
@@ -175,27 +186,51 @@
|
||||
bind:this={documentPanel}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{#if $portfolio.dataPanelOpen}
|
||||
{#if dataGroup.tabs.length > 0}
|
||||
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} on:dblclick={(e) => resetPanelSizes(e)} />
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["data"] }} data-subdivision-name="data">
|
||||
<Panel panelType="Data" tabLabels={[{ name: "Data" }]} tabActiveIndex={0} />
|
||||
<Panel
|
||||
panelId="DataGroup"
|
||||
panelTypes={dataGroup.tabs}
|
||||
tabLabels={dataGroup.tabs.map((name) => ({ name }))}
|
||||
tabActiveIndex={dataGroup.activeTabIndex}
|
||||
clickAction={(tabIndex) => editor.setPanelGroupActiveTab("DataGroup", tabIndex)}
|
||||
reorderAction={(oldIndex, newIndex) => editor.reorderPanelGroupTab("DataGroup", oldIndex, newIndex)}
|
||||
crossPanelDropAction={crossPanelDrop}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
{#if $portfolio.propertiesPanelOpen || $portfolio.layersPanelOpen}
|
||||
{#if propertiesGroup.tabs.length > 0 || layersGroup.tabs.length > 0}
|
||||
<LayoutCol class="workspace-grid-resize-gutter" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} on:dblclick={(e) => resetPanelSizes(e)} />
|
||||
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["details"] }} data-subdivision-name="details">
|
||||
{#if $portfolio.propertiesPanelOpen}
|
||||
{#if propertiesGroup.tabs.length > 0}
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["properties"] }} data-subdivision-name="properties">
|
||||
<Panel panelType="Properties" tabLabels={[{ name: "Properties" }]} tabActiveIndex={0} />
|
||||
<Panel
|
||||
panelId="PropertiesGroup"
|
||||
panelTypes={propertiesGroup.tabs}
|
||||
tabLabels={propertiesGroup.tabs.map((name) => ({ name }))}
|
||||
tabActiveIndex={propertiesGroup.activeTabIndex}
|
||||
clickAction={(tabIndex) => editor.setPanelGroupActiveTab("PropertiesGroup", tabIndex)}
|
||||
reorderAction={(oldIndex, newIndex) => editor.reorderPanelGroupTab("PropertiesGroup", oldIndex, newIndex)}
|
||||
crossPanelDropAction={crossPanelDrop}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
{#if $portfolio.propertiesPanelOpen && $portfolio.layersPanelOpen}
|
||||
{#if propertiesGroup.tabs.length > 0 && layersGroup.tabs.length > 0}
|
||||
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} on:dblclick={(e) => resetPanelSizes(e)} />
|
||||
{/if}
|
||||
{#if $portfolio.layersPanelOpen}
|
||||
{#if layersGroup.tabs.length > 0}
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["layers"] }} data-subdivision-name="layers">
|
||||
<Panel panelType="Layers" tabLabels={[{ name: "Layers" }]} tabActiveIndex={0} />
|
||||
<Panel
|
||||
panelId="LayersGroup"
|
||||
panelTypes={layersGroup.tabs}
|
||||
tabLabels={layersGroup.tabs.map((name) => ({ name }))}
|
||||
tabActiveIndex={layersGroup.activeTabIndex}
|
||||
clickAction={(tabIndex) => editor.setPanelGroupActiveTab("LayersGroup", tabIndex)}
|
||||
reorderAction={(oldIndex, newIndex) => editor.reorderPanelGroupTab("LayersGroup", oldIndex, newIndex)}
|
||||
crossPanelDropAction={crossPanelDrop}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
|
||||
61
frontend/src/stores/panel-drag.ts
Normal file
61
frontend/src/stores/panel-drag.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { writable } from "svelte/store";
|
||||
import type { Writable } from "svelte/store";
|
||||
|
||||
export type PanelDragState = {
|
||||
active: boolean;
|
||||
sourcePanelId: string | undefined;
|
||||
draggedTabLabel: string | undefined;
|
||||
sourceTabIndex: number;
|
||||
// Which panel's tab bar the pointer is currently hovering over (undefined if none)
|
||||
hoverTargetPanelId: string | undefined;
|
||||
hoverInsertionIndex: number | undefined;
|
||||
hoverInsertionMarkerLeft: number | undefined;
|
||||
};
|
||||
|
||||
const initialState: PanelDragState = {
|
||||
active: false,
|
||||
sourcePanelId: undefined,
|
||||
draggedTabLabel: undefined,
|
||||
sourceTabIndex: 0,
|
||||
hoverTargetPanelId: undefined,
|
||||
hoverInsertionIndex: undefined,
|
||||
hoverInsertionMarkerLeft: undefined,
|
||||
};
|
||||
|
||||
// Store state persisted across HMR to maintain reactive subscriptions in the component tree
|
||||
const store: Writable<PanelDragState> = import.meta.hot?.data?.store || writable<PanelDragState>(initialState);
|
||||
if (import.meta.hot) import.meta.hot.data.store = store;
|
||||
|
||||
export const panelDrag = store;
|
||||
|
||||
export function startCrossPanelDrag(sourcePanelId: string, draggedTabLabel: string, sourceTabIndex: number) {
|
||||
store.update((state) => {
|
||||
state.active = true;
|
||||
state.sourcePanelId = sourcePanelId;
|
||||
state.draggedTabLabel = draggedTabLabel;
|
||||
state.sourceTabIndex = sourceTabIndex;
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
export function endCrossPanelDrag() {
|
||||
store.update((state) => {
|
||||
state.active = false;
|
||||
state.sourcePanelId = undefined;
|
||||
state.draggedTabLabel = undefined;
|
||||
state.sourceTabIndex = 0;
|
||||
state.hoverTargetPanelId = undefined;
|
||||
state.hoverInsertionIndex = undefined;
|
||||
state.hoverInsertionMarkerLeft = undefined;
|
||||
return state;
|
||||
});
|
||||
}
|
||||
|
||||
export function updateCrossPanelHover(hoverTargetPanelId: string | undefined, hoverInsertionIndex: number | undefined, hoverInsertionMarkerLeft: number | undefined) {
|
||||
store.update((state) => {
|
||||
state.hoverTargetPanelId = hoverTargetPanelId;
|
||||
state.hoverInsertionIndex = hoverInsertionIndex;
|
||||
state.hoverInsertionMarkerLeft = hoverInsertionMarkerLeft;
|
||||
return state;
|
||||
});
|
||||
}
|
||||
@@ -4,25 +4,36 @@ import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||
import { downloadFile, downloadFileBlob, upload } from "/src/utility-functions/files";
|
||||
import { storeDocumentTabOrder } from "/src/utility-functions/persistence";
|
||||
import { rasterizeSVG } from "/src/utility-functions/rasterization";
|
||||
import type { EditorWrapper, OpenDocument } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { EditorWrapper, OpenDocument, PanelType } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
export type PortfolioStore = ReturnType<typeof createPortfolioStore>;
|
||||
|
||||
export type PanelGroupState = {
|
||||
tabs: PanelType[];
|
||||
activeTabIndex: number;
|
||||
};
|
||||
|
||||
export type WorkspacePanelLayout = {
|
||||
propertiesGroup: PanelGroupState;
|
||||
layersGroup: PanelGroupState;
|
||||
dataGroup: PanelGroupState;
|
||||
};
|
||||
|
||||
type PortfolioStoreState = {
|
||||
unsaved: boolean;
|
||||
documents: OpenDocument[];
|
||||
activeDocumentIndex: number;
|
||||
dataPanelOpen: boolean;
|
||||
propertiesPanelOpen: boolean;
|
||||
layersPanelOpen: boolean;
|
||||
panelLayout: WorkspacePanelLayout;
|
||||
};
|
||||
const initialState: PortfolioStoreState = {
|
||||
unsaved: false,
|
||||
documents: [],
|
||||
activeDocumentIndex: 0,
|
||||
dataPanelOpen: false,
|
||||
propertiesPanelOpen: true,
|
||||
layersPanelOpen: true,
|
||||
panelLayout: {
|
||||
propertiesGroup: { tabs: ["Properties"], activeTabIndex: 0 },
|
||||
layersGroup: { tabs: ["Layers"], activeTabIndex: 0 },
|
||||
dataGroup: { tabs: [], activeTabIndex: 0 },
|
||||
},
|
||||
};
|
||||
|
||||
let subscriptionsRouter: SubscriptionsRouter | undefined = undefined;
|
||||
@@ -103,23 +114,15 @@ export function createPortfolioStore(subscriptions: SubscriptionsRouter, editor:
|
||||
}
|
||||
});
|
||||
|
||||
subscriptions.subscribeFrontendMessage("UpdateDataPanelState", async (data) => {
|
||||
update((state) => {
|
||||
state.dataPanelOpen = data.open;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
subscriptions.subscribeFrontendMessage("UpdateWorkspacePanelLayout", (data) => {
|
||||
// Coerce activeTabIndex from BigInt (produced by serde_wasm_bindgen for usize) to number
|
||||
const layout = data.panelLayout;
|
||||
layout.propertiesGroup.activeTabIndex = Number(layout.propertiesGroup.activeTabIndex);
|
||||
layout.layersGroup.activeTabIndex = Number(layout.layersGroup.activeTabIndex);
|
||||
layout.dataGroup.activeTabIndex = Number(layout.dataGroup.activeTabIndex);
|
||||
|
||||
subscriptions.subscribeFrontendMessage("UpdatePropertiesPanelState", async (data) => {
|
||||
update((state) => {
|
||||
state.propertiesPanelOpen = data.open;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
||||
subscriptions.subscribeFrontendMessage("UpdateLayersPanelState", async (data) => {
|
||||
update((state) => {
|
||||
state.layersPanelOpen = data.open;
|
||||
state.panelLayout = layout;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
@@ -139,7 +142,5 @@ export function destroyPortfolioStore() {
|
||||
subscriptions.unsubscribeFrontendMessage("TriggerSaveDocument");
|
||||
subscriptions.unsubscribeFrontendMessage("TriggerSaveFile");
|
||||
subscriptions.unsubscribeFrontendMessage("TriggerExportImage");
|
||||
subscriptions.unsubscribeFrontendMessage("UpdateDataPanelState");
|
||||
subscriptions.unsubscribeFrontendMessage("UpdatePropertiesPanelState");
|
||||
subscriptions.unsubscribeFrontendMessage("UpdateLayersPanelState");
|
||||
subscriptions.unsubscribeFrontendMessage("UpdateWorkspacePanelLayout");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user