|
<script lang="ts"> |
|
import { getContext } from "svelte"; |
|
|
|
import type { Editor } from "@graphite/editor"; |
|
import type { FrontendDocumentDetails } from "@graphite/messages"; |
|
import type { DialogState } from "@graphite/state-providers/dialog"; |
|
import type { PortfolioState } from "@graphite/state-providers/portfolio"; |
|
|
|
import Dialog from "@graphite/components/floating-menus/Dialog.svelte"; |
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte"; |
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; |
|
import Panel from "@graphite/components/window/workspace/Panel.svelte"; |
|
|
|
const MIN_PANEL_SIZE = 100; |
|
const PANEL_SIZES = { |
|
root: 100, |
|
content: 80, |
|
document: 70, |
|
spreadsheet: 30, |
|
details: 20, |
|
properties: 45, |
|
layers: 55, |
|
}; |
|
|
|
let panelSizes = PANEL_SIZES; |
|
let documentPanel: Panel | undefined; |
|
let gutterResizeRestore: [number, number] | undefined = undefined; |
|
let pointerCaptureId: number | undefined = undefined; |
|
|
|
$: documentPanel?.scrollTabIntoView($portfolio.activeDocumentIndex); |
|
|
|
$: documentTabLabels = $portfolio.documents.map((doc: FrontendDocumentDetails) => { |
|
const name = doc.displayName; |
|
|
|
if (!editor.handle.inDevelopmentMode()) return { name }; |
|
|
|
const tooltip = `Document ID: ${doc.id}`; |
|
return { name, tooltip }; |
|
}); |
|
|
|
const editor = getContext<Editor>("editor"); |
|
const portfolio = getContext<PortfolioState>("portfolio"); |
|
const dialog = getContext<DialogState>("dialog"); |
|
|
|
function resizePanel(e: PointerEvent) { |
|
const gutter = (e.target || undefined) as HTMLDivElement | undefined; |
|
const nextSibling = (gutter?.nextElementSibling || undefined) as HTMLDivElement | undefined; |
|
const prevSibling = (gutter?.previousElementSibling || undefined) as HTMLDivElement | undefined; |
|
const parentElement = (gutter?.parentElement || undefined) as HTMLDivElement | undefined; |
|
|
|
const nextSiblingName = (nextSibling?.getAttribute("data-subdivision-name") || undefined) as keyof typeof PANEL_SIZES; |
|
const prevSiblingName = (prevSibling?.getAttribute("data-subdivision-name") || undefined) as keyof typeof PANEL_SIZES; |
|
|
|
if (!gutter || !nextSibling || !prevSibling || !parentElement || !nextSiblingName || !prevSiblingName) return; |
|
|
|
|
|
const isHorizontal = gutter.getAttribute("data-gutter-horizontal") !== null; |
|
|
|
|
|
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; |
|
|
|
|
|
pointerCaptureId = e.pointerId; |
|
gutter.setPointerCapture(pointerCaptureId); |
|
|
|
const mouseStart = isHorizontal ? e.clientX : e.clientY; |
|
|
|
const abortResize = () => { |
|
if (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId); |
|
removeListeners(); |
|
|
|
pointerCaptureId = e.pointerId; |
|
gutter.setPointerCapture(pointerCaptureId); |
|
|
|
if (gutterResizeRestore !== undefined) { |
|
panelSizes[nextSiblingName] = gutterResizeRestore[0]; |
|
panelSizes[prevSiblingName] = gutterResizeRestore[1]; |
|
gutterResizeRestore = undefined; |
|
} |
|
}; |
|
|
|
const onPointerMove = (e: PointerEvent) => { |
|
const mouseCurrent = isHorizontal ? e.clientX : e.clientY; |
|
let mouseDelta = mouseStart - mouseCurrent; |
|
|
|
mouseDelta = Math.max(nextSiblingSize + mouseDelta, MIN_PANEL_SIZE) - nextSiblingSize; |
|
mouseDelta = prevSiblingSize - Math.max(prevSiblingSize - mouseDelta, MIN_PANEL_SIZE); |
|
|
|
if (gutterResizeRestore === undefined) gutterResizeRestore = [panelSizes[nextSiblingName], panelSizes[prevSiblingName]]; |
|
|
|
panelSizes[nextSiblingName] = ((nextSiblingSize + mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100; |
|
panelSizes[prevSiblingName] = ((prevSiblingSize - mouseDelta) / totalResizingSpaceOccupied) * proportionBeingResized * 100; |
|
}; |
|
|
|
const onPointerUp = () => { |
|
gutterResizeRestore = undefined; |
|
if (pointerCaptureId) gutter.releasePointerCapture(pointerCaptureId); |
|
removeListeners(); |
|
}; |
|
|
|
const onMouseDown = (e: MouseEvent) => { |
|
const BUTTONS_RIGHT = 0b0000_0010; |
|
if (e.buttons & BUTTONS_RIGHT) abortResize(); |
|
}; |
|
|
|
const onKeyDown = (e: KeyboardEvent) => { |
|
if (e.key === "Escape") abortResize(); |
|
}; |
|
|
|
const addListeners = () => { |
|
document.addEventListener("pointermove", onPointerMove); |
|
document.addEventListener("pointerup", onPointerUp); |
|
document.addEventListener("mousedown", onMouseDown); |
|
document.addEventListener("keydown", onKeyDown); |
|
}; |
|
|
|
const removeListeners = () => { |
|
document.removeEventListener("pointermove", onPointerMove); |
|
document.removeEventListener("pointerup", onPointerUp); |
|
document.removeEventListener("mousedown", onMouseDown); |
|
document.removeEventListener("keydown", onKeyDown); |
|
}; |
|
|
|
addListeners(); |
|
} |
|
</script> |
|
|
|
<LayoutRow class="workspace" data-workspace> |
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["root"] }} data-subdivision-name="root"> |
|
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["content"] }} data-subdivision-name="content"> |
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["document"] }} data-subdivision-name="document"> |
|
<Panel |
|
panelType={$portfolio.documents.length > 0 ? "Document" : undefined} |
|
tabCloseButtons={true} |
|
tabMinWidths={true} |
|
tabLabels={documentTabLabels} |
|
clickAction={(tabIndex) => editor.handle.selectDocument($portfolio.documents[tabIndex].id)} |
|
closeAction={(tabIndex) => editor.handle.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)} |
|
tabActiveIndex={$portfolio.activeDocumentIndex} |
|
bind:this={documentPanel} |
|
/> |
|
</LayoutRow> |
|
{#if $portfolio.spreadsheetOpen} |
|
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} /> |
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["spreadsheet"] }} data-subdivision-name="spreadsheet"> |
|
<Panel panelType="Spreadsheet" tabLabels={[{ name: "Spreadsheet" }]} tabActiveIndex={0} /> |
|
</LayoutRow> |
|
{/if} |
|
</LayoutCol> |
|
<LayoutCol class="workspace-grid-resize-gutter" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} /> |
|
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["details"] }} data-subdivision-name="details"> |
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["properties"] }} data-subdivision-name="properties"> |
|
<Panel panelType="Properties" tabLabels={[{ name: "Properties" }]} tabActiveIndex={0} /> |
|
</LayoutRow> |
|
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} /> |
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["layers"] }} data-subdivision-name="layers"> |
|
<Panel panelType="Layers" tabLabels={[{ name: "Layers" }]} tabActiveIndex={0} /> |
|
</LayoutRow> |
|
</LayoutCol> |
|
</LayoutRow> |
|
{#if $dialog.visible} |
|
<Dialog /> |
|
{/if} |
|
</LayoutRow> |
|
|
|
<style lang="scss" global> |
|
.workspace { |
|
position: relative; |
|
flex: 1 1 100%; |
|
|
|
.workspace-grid-subdivision { |
|
min-height: 28px; |
|
flex: 1 1 0; |
|
|
|
&.folded { |
|
flex-grow: 0; |
|
height: 0; |
|
} |
|
} |
|
|
|
.workspace-grid-resize-gutter { |
|
flex: 0 0 4px; |
|
|
|
&.layout-row { |
|
cursor: ns-resize; |
|
} |
|
|
|
&.layout-col { |
|
cursor: ew-resize; |
|
} |
|
} |
|
} |
|
</style> |
|
|