mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 10:58:12 +08:00
Fix panel docking bugs and polish its behavior (#4087)
* Fix panel docking bugs and polish its behavior * Fix bug
This commit is contained in:
@@ -1,30 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, onDestroy } from "svelte";
|
||||
import { getContext } from "svelte";
|
||||
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
||||
import WidgetLayout from "/src/components/widgets/WidgetLayout.svelte";
|
||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||
import { patchLayout } from "/src/utility-functions/widgets";
|
||||
import type { Layout } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { PortfolioStore } from "/src/stores/portfolio";
|
||||
|
||||
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||
|
||||
let dataPanelLayout: Layout = [];
|
||||
|
||||
onMount(() => {
|
||||
subscriptions.subscribeLayoutUpdate("DataPanel", (data) => {
|
||||
patchLayout(dataPanelLayout, data);
|
||||
dataPanelLayout = dataPanelLayout;
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
subscriptions.unsubscribeLayoutUpdate("DataPanel");
|
||||
});
|
||||
const portfolio = getContext<PortfolioStore>("portfolio");
|
||||
</script>
|
||||
|
||||
<LayoutCol class="data-panel">
|
||||
<LayoutCol class="body" scrollableY={true}>
|
||||
<WidgetLayout layout={dataPanelLayout} layoutTarget="DataPanel" />
|
||||
<WidgetLayout layout={$portfolio.dataPanelLayout} layoutTarget="DataPanel" />
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||
import { SvelteMap } from "svelte/reactivity";
|
||||
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
||||
import IconButton from "/src/components/widgets/buttons/IconButton.svelte";
|
||||
@@ -8,12 +7,11 @@
|
||||
import Separator from "/src/components/widgets/labels/Separator.svelte";
|
||||
import WidgetLayout from "/src/components/widgets/WidgetLayout.svelte";
|
||||
import type { NodeGraphStore } from "/src/stores/node-graph";
|
||||
import type { PortfolioStore } from "/src/stores/portfolio";
|
||||
import type { TooltipStore } from "/src/stores/tooltip";
|
||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||
import { pasteFile } from "/src/utility-functions/files";
|
||||
import { operatingSystem } from "/src/utility-functions/platform";
|
||||
import { patchLayout } from "/src/utility-functions/widgets";
|
||||
import type { EditorWrapper, LayerPanelEntry, LayerStructureEntry, Layout } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { EditorWrapper, LayerPanelEntry, LayerStructureEntry } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
type LayerListingInfo = {
|
||||
folderIndex: number;
|
||||
@@ -48,15 +46,13 @@
|
||||
startY: number;
|
||||
};
|
||||
|
||||
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||
const editor = getContext<EditorWrapper>("editor");
|
||||
const nodeGraph = getContext<NodeGraphStore>("nodeGraph");
|
||||
const tooltip = getContext<TooltipStore>("tooltip");
|
||||
const portfolio = getContext<PortfolioStore>("portfolio");
|
||||
|
||||
let list: LayoutCol | undefined;
|
||||
|
||||
// Layer data
|
||||
let layerCache = new SvelteMap<string, LayerPanelEntry>(); // TODO: replace with BigUint64Array as index
|
||||
let layers: LayerListingInfo[] = [];
|
||||
|
||||
// Interactive dragging
|
||||
@@ -71,38 +67,9 @@
|
||||
let layerToClipUponClick: LayerListingInfo | undefined = undefined;
|
||||
let layerToClipAltKeyPressed = false;
|
||||
|
||||
// Layouts
|
||||
let layersPanelControlBarLeftLayout: Layout = [];
|
||||
let layersPanelControlBarRightLayout: Layout = [];
|
||||
let layersPanelBottomBarLayout: Layout = [];
|
||||
$: rebuildLayerHierarchy($portfolio.layerStructure, $portfolio.layerCache);
|
||||
|
||||
onMount(() => {
|
||||
subscriptions.subscribeLayoutUpdate("LayersPanelControlLeftBar", (data) => {
|
||||
patchLayout(layersPanelControlBarLeftLayout, data);
|
||||
layersPanelControlBarLeftLayout = layersPanelControlBarLeftLayout;
|
||||
});
|
||||
|
||||
subscriptions.subscribeLayoutUpdate("LayersPanelControlRightBar", (data) => {
|
||||
patchLayout(layersPanelControlBarRightLayout, data);
|
||||
layersPanelControlBarRightLayout = layersPanelControlBarRightLayout;
|
||||
});
|
||||
|
||||
subscriptions.subscribeLayoutUpdate("LayersPanelBottomBar", (data) => {
|
||||
patchLayout(layersPanelBottomBarLayout, data);
|
||||
layersPanelBottomBarLayout = layersPanelBottomBarLayout;
|
||||
});
|
||||
|
||||
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerStructure", (data) => {
|
||||
rebuildLayerHierarchy(data.layerStructure);
|
||||
});
|
||||
|
||||
subscriptions.subscribeFrontendMessage("UpdateDocumentLayerDetails", (data) => {
|
||||
const targetLayer = data.data;
|
||||
const targetId = targetLayer.id;
|
||||
|
||||
updateLayerInTree(targetId, targetLayer);
|
||||
});
|
||||
|
||||
addEventListener("pointerup", draggingPointerUp);
|
||||
addEventListener("pointermove", draggingPointerMove);
|
||||
addEventListener("mousedown", draggingMouseDown);
|
||||
@@ -115,12 +82,6 @@
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlLeftBar");
|
||||
subscriptions.unsubscribeLayoutUpdate("LayersPanelControlRightBar");
|
||||
subscriptions.unsubscribeLayoutUpdate("LayersPanelBottomBar");
|
||||
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerStructure");
|
||||
subscriptions.unsubscribeFrontendMessage("UpdateDocumentLayerDetails");
|
||||
|
||||
removeEventListener("pointerup", draggingPointerUp);
|
||||
removeEventListener("pointermove", draggingPointerMove);
|
||||
removeEventListener("mousedown", draggingMouseDown);
|
||||
@@ -504,7 +465,7 @@
|
||||
dragInPanel = false;
|
||||
}
|
||||
|
||||
function rebuildLayerHierarchy(layerStructure: LayerStructureEntry[]) {
|
||||
function rebuildLayerHierarchy(layerStructure: LayerStructureEntry[], cache: Map<string, LayerPanelEntry>) {
|
||||
// Track the editing state by flat list index, not layer ID, since a layer can appear at multiple positions
|
||||
const editingIndex = layers.findIndex((layer: LayerListingInfo) => layer.editingName);
|
||||
|
||||
@@ -515,7 +476,7 @@
|
||||
const recurse = (children: LayerStructureEntry[], depth: number, parentId: bigint | undefined, parentPath: bigint[], parentsVisible: boolean, parentsUnlocked: boolean) => {
|
||||
children.forEach((item, index) => {
|
||||
const treePath = [...parentPath, item.layerId];
|
||||
const mapping = layerCache.get(String(item.layerId));
|
||||
const mapping = cache.get(String(item.layerId));
|
||||
|
||||
if (mapping) {
|
||||
mapping.id = item.layerId;
|
||||
@@ -544,28 +505,15 @@
|
||||
recurse(layerStructure, 1, undefined, [], true, true);
|
||||
layers = layers;
|
||||
}
|
||||
|
||||
function updateLayerInTree(targetId: bigint, targetLayer: LayerPanelEntry) {
|
||||
layerCache.set(String(targetId), targetLayer);
|
||||
|
||||
let changed = false;
|
||||
layers.forEach((layer) => {
|
||||
if (layer.entry.id === targetId) {
|
||||
layer.entry = targetLayer;
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) layers = layers;
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
||||
<LayoutRow class="control-bar" scrollableX={true}>
|
||||
<WidgetLayout layout={layersPanelControlBarLeftLayout} layoutTarget="LayersPanelControlLeftBar" />
|
||||
{#if layersPanelControlBarLeftLayout?.length > 0 && layersPanelControlBarRightLayout?.length > 0}
|
||||
<WidgetLayout layout={$portfolio.layersPanelControlBarLeftLayout} layoutTarget="LayersPanelControlLeftBar" />
|
||||
{#if $portfolio.layersPanelControlBarLeftLayout?.length > 0 && $portfolio.layersPanelControlBarRightLayout?.length > 0}
|
||||
<Separator />
|
||||
{/if}
|
||||
<WidgetLayout layout={layersPanelControlBarRightLayout} layoutTarget="LayersPanelControlRightBar" />
|
||||
<WidgetLayout layout={$portfolio.layersPanelControlBarRightLayout} layoutTarget="LayersPanelControlRightBar" />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="list-area" classes={{ "drag-ongoing": Boolean(internalDragState?.active && draggingData) }} scrollableY={true}>
|
||||
<LayoutCol
|
||||
@@ -685,7 +633,7 @@
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
<LayoutRow class="bottom-bar" scrollableX={true}>
|
||||
<WidgetLayout layout={layersPanelBottomBarLayout} layoutTarget="LayersPanelBottomBar" />
|
||||
<WidgetLayout layout={$portfolio.layersPanelBottomBarLayout} layoutTarget="LayersPanelBottomBar" />
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
|
||||
|
||||
@@ -1,30 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, onDestroy } from "svelte";
|
||||
import { getContext } from "svelte";
|
||||
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
||||
import WidgetLayout from "/src/components/widgets/WidgetLayout.svelte";
|
||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||
import { patchLayout } from "/src/utility-functions/widgets";
|
||||
import type { Layout } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { PortfolioStore } from "/src/stores/portfolio";
|
||||
|
||||
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||
|
||||
let propertiesPanelLayout: Layout = [];
|
||||
|
||||
onMount(() => {
|
||||
subscriptions.subscribeLayoutUpdate("PropertiesPanel", (data) => {
|
||||
patchLayout(propertiesPanelLayout, data);
|
||||
propertiesPanelLayout = propertiesPanelLayout;
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
subscriptions.unsubscribeLayoutUpdate("PropertiesPanel");
|
||||
});
|
||||
const portfolio = getContext<PortfolioStore>("portfolio");
|
||||
</script>
|
||||
|
||||
<LayoutCol class="properties">
|
||||
<LayoutCol class="sections" scrollableY={true}>
|
||||
<WidgetLayout layout={propertiesPanelLayout} layoutTarget="PropertiesPanel" />
|
||||
<WidgetLayout layout={$portfolio.propertiesPanelLayout} layoutTarget="PropertiesPanel" />
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, onDestroy } from "svelte";
|
||||
import { getContext } from "svelte";
|
||||
import LayoutCol from "/src/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "/src/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "/src/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "/src/components/widgets/labels/TextLabel.svelte";
|
||||
import WidgetLayout from "/src/components/widgets/WidgetLayout.svelte";
|
||||
import type { SubscriptionsRouter } from "/src/subscriptions-router";
|
||||
import type { PortfolioStore } from "/src/stores/portfolio";
|
||||
import { pasteFile } from "/src/utility-functions/files";
|
||||
import { patchLayout } from "/src/utility-functions/widgets";
|
||||
import type { EditorWrapper, Layout } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
import type { EditorWrapper } from "/wrapper/pkg/graphite_wasm_wrapper";
|
||||
|
||||
const subscriptions = getContext<SubscriptionsRouter>("subscriptions");
|
||||
const editor = getContext<EditorWrapper>("editor");
|
||||
|
||||
let welcomePanelButtonsLayout: Layout = [];
|
||||
|
||||
onMount(() => {
|
||||
subscriptions.subscribeLayoutUpdate("WelcomeScreenButtons", (data) => {
|
||||
patchLayout(welcomePanelButtonsLayout, data);
|
||||
welcomePanelButtonsLayout = welcomePanelButtonsLayout;
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
subscriptions.unsubscribeLayoutUpdate("WelcomeScreenButtons");
|
||||
});
|
||||
const portfolio = getContext<PortfolioStore>("portfolio");
|
||||
|
||||
function dropFile(e: DragEvent) {
|
||||
if (!e.dataTransfer) return;
|
||||
@@ -43,7 +29,7 @@
|
||||
<IconLabel icon="GraphiteLogotypeSolid" />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="actions">
|
||||
<WidgetLayout layout={welcomePanelButtonsLayout} layoutTarget="WelcomeScreenButtons" />
|
||||
<WidgetLayout layout={$portfolio.welcomeScreenButtonsLayout} layoutTarget="WelcomeScreenButtons" />
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
// Only start a group drag from the tab bar background (not from a tab or button)
|
||||
if (e.button !== BUTTON_LEFT) return;
|
||||
if (e.target !== e.currentTarget) return;
|
||||
if (!crossPanelDropAction) return;
|
||||
if (!crossPanelDropAction && !splitDropAction) return;
|
||||
|
||||
dragStartState = { tabIndex: tabActiveIndex, pointerX: e.clientX, pointerY: e.clientY, isGroupDrag: true };
|
||||
dragging = false;
|
||||
@@ -142,13 +142,12 @@
|
||||
|
||||
dragging = true;
|
||||
|
||||
if (crossPanelDropAction) {
|
||||
if (dragStartState.isGroupDrag) {
|
||||
startCrossPanelDrag(panelId, [...panelTypes], tabActiveIndex, true);
|
||||
} else {
|
||||
const draggedTab = panelTypes[dragStartState.tabIndex];
|
||||
startCrossPanelDrag(panelId, [draggedTab], dragStartState.tabIndex, false);
|
||||
}
|
||||
// Group drags enter cross-panel state for edge docking even without crossPanelDropAction
|
||||
if (dragStartState.isGroupDrag && (crossPanelDropAction || splitDropAction)) {
|
||||
startCrossPanelDrag(panelId, [...panelTypes], tabActiveIndex, true);
|
||||
} else if (!dragStartState.isGroupDrag && crossPanelDropAction) {
|
||||
const draggedTab = panelTypes[dragStartState.tabIndex];
|
||||
startCrossPanelDrag(panelId, [draggedTab], dragStartState.tabIndex, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +164,9 @@
|
||||
insertionIndex = undefined;
|
||||
insertionMarkerLeft = undefined;
|
||||
|
||||
// Check if the pointer is over any other dockable panel's tab bar
|
||||
// Skip cross-panel hover detection for sources that can't dock anywhere
|
||||
if (!crossPanelDropAction && !splitDropAction) return;
|
||||
|
||||
if (crossPanelDropAction) {
|
||||
const tabBarTarget = Array.from(document.querySelectorAll("[data-panel-tab-bar]")).find((element) => {
|
||||
const targetPanelId = element.getAttribute("data-panel-tab-bar");
|
||||
@@ -180,35 +181,35 @@
|
||||
calculateForeignInsertionIndex(e.clientX, tabBarTargetId, tabBarTarget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the pointer is over any panel body's edge zone for split docking
|
||||
const panelBody = Array.from(document.querySelectorAll("[data-panel-body]")).find((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
||||
});
|
||||
// Check for edge-zone split docking
|
||||
const panelBody = Array.from(document.querySelectorAll("[data-panel-body]")).find((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return e.clientX >= rect.left && e.clientX <= rect.right && e.clientY >= rect.top && e.clientY <= rect.bottom;
|
||||
});
|
||||
|
||||
const bodyPanelId = panelBody && panelBody.getAttribute("data-panel-body");
|
||||
if (bodyPanelId) {
|
||||
const rect = panelBody.getBoundingClientRect();
|
||||
let edge: DockingEdge | undefined = detectDockingEdge(e.clientX, e.clientY, rect);
|
||||
const bodyPanelId = panelBody && panelBody.getAttribute("data-panel-body");
|
||||
if (bodyPanelId) {
|
||||
const rect = panelBody.getBoundingClientRect();
|
||||
let edge: DockingEdge | undefined = detectDockingEdge(e.clientX, e.clientY, rect);
|
||||
|
||||
// Block center drops between document and non-document panels
|
||||
if (edge === "Center") {
|
||||
const targetIsDockable = panelBody.hasAttribute("data-panel-dockable");
|
||||
const sourceIsDockable = crossPanelDropAction !== undefined;
|
||||
if (targetIsDockable !== sourceIsDockable) edge = undefined;
|
||||
}
|
||||
|
||||
if (edge) {
|
||||
updateDockingHover(bodyPanelId, edge);
|
||||
return;
|
||||
}
|
||||
// Center drops between different panels require both to be cross-panel-dockable (self-drops are always allowed as a no-op)
|
||||
if (edge === "Center" && bodyPanelId !== panelId) {
|
||||
const targetIsDockable = panelBody.hasAttribute("data-panel-dockable");
|
||||
const sourceIsDockable = crossPanelDropAction !== undefined;
|
||||
if (!sourceIsDockable || !targetIsDockable) edge = undefined;
|
||||
}
|
||||
|
||||
// Not hovering any drop target
|
||||
updateCrossPanelHover(undefined, undefined, undefined);
|
||||
updateDockingHover(undefined, undefined);
|
||||
if (edge) {
|
||||
updateDockingHover(bodyPanelId, edge);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not hovering any drop target
|
||||
updateCrossPanelHover(undefined, undefined, undefined);
|
||||
updateDockingHover(undefined, undefined);
|
||||
}
|
||||
|
||||
function dragPointerUp() {
|
||||
@@ -273,7 +274,7 @@
|
||||
dragging = false;
|
||||
insertionIndex = undefined;
|
||||
insertionMarkerLeft = undefined;
|
||||
if (crossPanelDropAction) endCrossPanelDrag();
|
||||
endCrossPanelDrag();
|
||||
removeDragListeners();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
const MIN_PANEL_SIZE = 100;
|
||||
const DOUBLE_CLICK_MILLISECONDS = 500;
|
||||
// Must match DOCUMENT_PANEL_SHARE / NON_DOCUMENT_PANEL_SHARE in utility_types.rs
|
||||
const DOCUMENT_PANEL_SHARE = 0.8;
|
||||
const EQUAL_PANEL_SHARE = 0.5;
|
||||
|
||||
const editor = getContext<EditorWrapper>("editor");
|
||||
const portfolio = getContext<PortfolioStore>("portfolio");
|
||||
@@ -24,11 +27,15 @@
|
||||
let activeResizeCleanup: (() => void) | undefined = undefined;
|
||||
let lastGutterClickTarget: EventTarget | undefined = undefined;
|
||||
let lastGutterClickTime = 0;
|
||||
let lastSubdivisionRef: PanelLayoutSubdivision | undefined = undefined;
|
||||
|
||||
// At even depths (0, 2, 4...) children are in a row, at odd depths (1, 3, 5...) in a column
|
||||
$: horizontal = depth % 2 === 0;
|
||||
// Reset overrides when the subdivision changes (e.g., backend sends a new layout)
|
||||
$: if (subdivision) sizeOverrides = {};
|
||||
// Compare by reference because `safe_not_equal` treats any store update as changed, which would wipe drag overrides
|
||||
$: if (subdivision !== lastSubdivisionRef) {
|
||||
sizeOverrides = {};
|
||||
lastSubdivisionRef = subdivision;
|
||||
}
|
||||
// Reactive array of resolved sizes (merging backend defaults with local overrides)
|
||||
$: resolvedSizes = subdivision && "Split" in subdivision ? subdivision.Split.children.map((child, index) => sizeOverrides[index] ?? child.size) : [];
|
||||
$: documentTabLabels = $portfolio.documents.map((doc: DocumentInfo) => {
|
||||
@@ -55,26 +62,41 @@
|
||||
const parentElement = gutter.parentElement;
|
||||
if (!(nextSibling instanceof HTMLDivElement) || !(prevSibling instanceof HTMLDivElement) || !(parentElement instanceof HTMLDivElement)) return;
|
||||
|
||||
// Double-click resets both adjacent panels to their default sizes
|
||||
// Double-click resets the two adjacent panels to the default ratio (80:20 near document, otherwise 50:50)
|
||||
const now = Date.now();
|
||||
const isDoubleClick = now - lastGutterClickTime < DOUBLE_CLICK_MILLISECONDS && lastGutterClickTarget === gutter;
|
||||
|
||||
lastGutterClickTime = now;
|
||||
lastGutterClickTarget = gutter;
|
||||
|
||||
if (isDoubleClick) {
|
||||
sizeOverrides = {};
|
||||
editor.resetPanelGroupSizes(splitPath);
|
||||
const children = subdivision.Split.children;
|
||||
const adjacentSum = resolvedSizes[prevIndex] + resolvedSizes[nextIndex];
|
||||
|
||||
const prevHasDocument = subtreeContainsDocument(children[prevIndex].subdivision);
|
||||
const nextHasDocument = subtreeContainsDocument(children[nextIndex].subdivision);
|
||||
|
||||
let prevShare = EQUAL_PANEL_SHARE;
|
||||
if (prevHasDocument && !nextHasDocument) prevShare = DOCUMENT_PANEL_SHARE;
|
||||
else if (!prevHasDocument && nextHasDocument) prevShare = 1 - DOCUMENT_PANEL_SHARE;
|
||||
|
||||
sizeOverrides[prevIndex] = adjacentSum * prevShare;
|
||||
sizeOverrides[nextIndex] = adjacentSum * (1 - prevShare);
|
||||
sizeOverrides = sizeOverrides;
|
||||
|
||||
const allSizes = children.map((child, i) => sizeOverrides[i] ?? child.size);
|
||||
editor.setPanelGroupSizes(splitPath, allSizes);
|
||||
return;
|
||||
}
|
||||
|
||||
const isHorizontal = horizontal;
|
||||
|
||||
const gutterSize = isHorizontal ? gutter.getBoundingClientRect().width : gutter.getBoundingClientRect().height;
|
||||
const nextSiblingSize = isHorizontal ? nextSibling.getBoundingClientRect().width : nextSibling.getBoundingClientRect().height;
|
||||
const prevSiblingSize = isHorizontal ? prevSibling.getBoundingClientRect().width : prevSibling.getBoundingClientRect().height;
|
||||
const parentElementSize = isHorizontal ? parentElement.getBoundingClientRect().width : parentElement.getBoundingClientRect().height;
|
||||
|
||||
const totalResizingSpaceOccupied = gutterSize + nextSiblingSize + prevSiblingSize;
|
||||
const proportionBeingResized = totalResizingSpaceOccupied / parentElementSize;
|
||||
// Only redistribute within the two adjacent panels' combined flex-grow total
|
||||
const adjacentFlexGrowTotal = resolvedSizes[prevIndex] + resolvedSizes[nextIndex];
|
||||
const adjacentPixelTotal = prevSiblingSize + nextSiblingSize;
|
||||
|
||||
pointerCaptureId = e.pointerId;
|
||||
gutter.setPointerCapture(pointerCaptureId);
|
||||
@@ -88,7 +110,9 @@
|
||||
activeResizeCleanup = undefined;
|
||||
|
||||
if (gutterResizeRestore !== undefined) {
|
||||
sizeOverrides = { ...sizeOverrides, [nextIndex]: gutterResizeRestore[0], [prevIndex]: gutterResizeRestore[1] };
|
||||
sizeOverrides[nextIndex] = gutterResizeRestore[0];
|
||||
sizeOverrides[prevIndex] = gutterResizeRestore[1];
|
||||
sizeOverrides = sizeOverrides;
|
||||
gutterResizeRestore = undefined;
|
||||
}
|
||||
};
|
||||
@@ -102,11 +126,9 @@
|
||||
|
||||
if (gutterResizeRestore === undefined) gutterResizeRestore = [resolvedSizes[nextIndex], resolvedSizes[prevIndex]];
|
||||
|
||||
sizeOverrides = {
|
||||
...sizeOverrides,
|
||||
[nextIndex]: ((nextSiblingSize + mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100,
|
||||
[prevIndex]: ((prevSiblingSize - mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100,
|
||||
};
|
||||
sizeOverrides[nextIndex] = (adjacentFlexGrowTotal * (nextSiblingSize + mouseDelta)) / adjacentPixelTotal;
|
||||
sizeOverrides[prevIndex] = (adjacentFlexGrowTotal * (prevSiblingSize - mouseDelta)) / adjacentPixelTotal;
|
||||
sizeOverrides = sizeOverrides;
|
||||
};
|
||||
|
||||
const onPointerUp = () => {
|
||||
@@ -164,6 +186,12 @@
|
||||
function isDocumentGroup(state: PanelGroupState): boolean {
|
||||
return state.tabs.some((t) => t === "Document" || t === "Welcome");
|
||||
}
|
||||
|
||||
function subtreeContainsDocument(node: PanelLayoutSubdivision): boolean {
|
||||
if ("PanelGroup" in node) return isDocumentGroup(node.PanelGroup.state);
|
||||
if ("Split" in node) return node.Split.children.some((child) => subtreeContainsDocument(child.subdivision));
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if subdivision && "PanelGroup" in subdivision}
|
||||
|
||||
Reference in New Issue
Block a user