mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 07:28:12 +08:00
Desktop: Add the transparent viewport hole punch and hook up window button plumbing (#2949)
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import { createLocalizationManager } from "@graphite/io-managers/localization";
|
||||
import { createPanicManager } from "@graphite/io-managers/panic";
|
||||
import { createPersistenceManager } from "@graphite/io-managers/persistence";
|
||||
import { createAppWindowState } from "@graphite/state-providers/app-window";
|
||||
import { createDialogState } from "@graphite/state-providers/dialog";
|
||||
import { createDocumentState } from "@graphite/state-providers/document";
|
||||
import { createFontsState } from "@graphite/state-providers/fonts";
|
||||
@@ -36,6 +37,8 @@
|
||||
setContext("nodeGraph", nodeGraph);
|
||||
let portfolio = createPortfolioState(editor);
|
||||
setContext("portfolio", portfolio);
|
||||
let appWindow = createAppWindowState(editor);
|
||||
setContext("appWindow", appWindow);
|
||||
|
||||
// Initialize managers, which are isolated systems that subscribe to backend messages to link them to browser API functionality (like JS events, IndexedDB, etc.)
|
||||
createClipboardManager(editor);
|
||||
@@ -58,10 +61,11 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<MainWindow />
|
||||
<MainWindow platform={$appWindow.platform} maximized={$appWindow.maximized} viewportHolePunch={$appWindow.viewportHolePunch} />
|
||||
|
||||
<style lang="scss" global>
|
||||
// Disable the spinning loading indicator
|
||||
body::before,
|
||||
body::after {
|
||||
content: none !important;
|
||||
}
|
||||
@@ -206,10 +210,16 @@
|
||||
height: 100%;
|
||||
background: var(--color-2-mildblack);
|
||||
overscroll-behavior: none;
|
||||
-webkit-user-select: none; // Required as of Safari 15.0 (Graphite's minimum version) through the latest release
|
||||
-webkit-user-select: none; // Still required by Safari as of 2025
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
// Needed for the viewport hole punch on desktop
|
||||
html:has(body > .viewport-hole-punch),
|
||||
body:has(> .viewport-hole-punch) {
|
||||
background: none;
|
||||
}
|
||||
|
||||
// The default value of `auto` from the CSS spec is a footgun with flexbox layouts:
|
||||
// https://stackoverflow.com/questions/36247140/why-dont-flex-items-shrink-past-content-size
|
||||
* {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
UpdateMouseCursor,
|
||||
isWidgetSpanRow,
|
||||
} from "@graphite/messages";
|
||||
import type { AppWindowState } from "@graphite/state-providers/app-window";
|
||||
import type { DocumentState } from "@graphite/state-providers/document";
|
||||
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
||||
import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
||||
@@ -34,6 +35,7 @@
|
||||
let viewport: HTMLDivElement | undefined;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const appWindow = getContext<AppWindowState>("appWindow");
|
||||
const document = getContext<DocumentState>("document");
|
||||
|
||||
// Interactive text editing
|
||||
@@ -514,13 +516,13 @@
|
||||
<RulerInput origin={rulerOrigin.x} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Horizontal" bind:this={rulerHorizontal} />
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
<LayoutRow class="viewport-container-inner">
|
||||
<LayoutRow class="viewport-container-inner-1">
|
||||
{#if rulersVisible}
|
||||
<LayoutCol class="ruler-or-scrollbar">
|
||||
<RulerInput origin={rulerOrigin.y} majorMarkSpacing={rulerSpacing} numberInterval={rulerInterval} direction="Vertical" bind:this={rulerVertical} />
|
||||
</LayoutCol>
|
||||
{/if}
|
||||
<LayoutCol class="viewport-container-inner" styles={{ cursor: canvasCursor }}>
|
||||
<LayoutCol class="viewport-container-inner-2" styles={{ cursor: canvasCursor }} data-viewport-container>
|
||||
{#if cursorEyedropper}
|
||||
<EyedropperPreview
|
||||
colorChoice={cursorEyedropperPreviewColorChoice}
|
||||
@@ -531,25 +533,27 @@
|
||||
y={cursorTop}
|
||||
/>
|
||||
{/if}
|
||||
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} bind:this={viewport} data-viewport>
|
||||
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
||||
{@html artworkSvg}
|
||||
</svg>
|
||||
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
|
||||
{#if showTextInput}
|
||||
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" on:scroll={preventTextEditingScroll} />
|
||||
{/if}
|
||||
{#if !$appWindow.viewportHolePunch}
|
||||
<div class="viewport" on:pointerdown={(e) => canvasPointerDown(e)} bind:this={viewport} data-viewport>
|
||||
<svg class="artboards" style:width={canvasWidthCSS} style:height={canvasHeightCSS}>
|
||||
{@html artworkSvg}
|
||||
</svg>
|
||||
<div class="text-input" style:width={canvasWidthCSS} style:height={canvasHeightCSS} style:pointer-events={showTextInput ? "auto" : ""}>
|
||||
{#if showTextInput}
|
||||
<div bind:this={textInput} style:transform="matrix({textInputMatrix})" on:scroll={preventTextEditingScroll} />
|
||||
{/if}
|
||||
</div>
|
||||
<canvas
|
||||
class="overlays"
|
||||
width={canvasWidthScaledRoundedToEven}
|
||||
height={canvasHeightScaledRoundedToEven}
|
||||
style:width={canvasWidthCSS}
|
||||
style:height={canvasHeightCSS}
|
||||
data-overlays-canvas
|
||||
>
|
||||
</canvas>
|
||||
</div>
|
||||
<canvas
|
||||
class="overlays"
|
||||
width={canvasWidthScaledRoundedToEven}
|
||||
height={canvasHeightScaledRoundedToEven}
|
||||
style:width={canvasWidthCSS}
|
||||
style:height={canvasHeightCSS}
|
||||
data-overlays-canvas
|
||||
>
|
||||
</canvas>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="graph-view" class:open={$document.graphViewOverlayOpen} style:--fade-artwork={`${$document.fadeArtwork}%`} data-graph>
|
||||
<Graph />
|
||||
</div>
|
||||
@@ -593,7 +597,8 @@
|
||||
.control-bar {
|
||||
height: 32px;
|
||||
flex: 0 0 auto;
|
||||
margin: 0 4px;
|
||||
padding: 0 4px; // Padding (instead of margin) is needed for the viewport hole punch on desktop
|
||||
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop
|
||||
|
||||
.spacer {
|
||||
min-width: 40px;
|
||||
@@ -632,6 +637,7 @@
|
||||
.tool-shelf {
|
||||
flex: 0 0 auto;
|
||||
justify-content: space-between;
|
||||
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop
|
||||
|
||||
.tools {
|
||||
flex: 0 1 auto;
|
||||
@@ -713,6 +719,7 @@
|
||||
|
||||
.ruler-or-scrollbar {
|
||||
flex: 0 0 auto;
|
||||
background: var(--color-3-darkgray); // Needed for the viewport hole punch on desktop
|
||||
}
|
||||
|
||||
.ruler-corner {
|
||||
@@ -743,7 +750,8 @@
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.viewport-container-inner {
|
||||
.viewport-container-inner-1,
|
||||
.viewport-container-inner-2 {
|
||||
flex: 1 1 100%;
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<script lang="ts" context="module">
|
||||
export type ApplicationPlatform = "Windows" | "Mac" | "Linux" | "Web";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { AppWindowPlatform } from "@graphite/messages";
|
||||
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import StatusBar from "@graphite/components/window/status-bar/StatusBar.svelte";
|
||||
import TitleBar from "@graphite/components/window/title-bar/TitleBar.svelte";
|
||||
import Workspace from "@graphite/components/window/workspace/Workspace.svelte";
|
||||
|
||||
let platform: ApplicationPlatform = "Web";
|
||||
let maximized: true;
|
||||
export let platform: AppWindowPlatform;
|
||||
export let maximized: boolean;
|
||||
export let viewportHolePunch: boolean;
|
||||
</script>
|
||||
|
||||
<LayoutCol class="main-window">
|
||||
<LayoutCol class="main-window" classes={{ "viewport-hole-punch": viewportHolePunch }}>
|
||||
<TitleBar {platform} {maximized} />
|
||||
|
||||
<Workspace />
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
<script lang="ts" context="module">
|
||||
export type Platform = "Windows" | "Mac" | "Linux" | "Web";
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, UpdateMenuBarLayout } from "@graphite/messages";
|
||||
import { type KeyRaw, type LayoutKeysGroup, type MenuBarEntry, type MenuListEntry, type AppWindowPlatform, UpdateMenuBarLayout } from "@graphite/messages";
|
||||
import type { PortfolioState } from "@graphite/state-providers/portfolio";
|
||||
import { platformIsMac } from "@graphite/utility-functions/platform";
|
||||
|
||||
@@ -17,7 +13,7 @@
|
||||
import WindowButtonsWindows from "@graphite/components/window/title-bar/WindowButtonsWindows.svelte";
|
||||
import WindowTitle from "@graphite/components/window/title-bar/WindowTitle.svelte";
|
||||
|
||||
export let platform: Platform;
|
||||
export let platform: AppWindowPlatform;
|
||||
export let maximized: boolean;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
@@ -73,7 +69,7 @@
|
||||
<!-- Menu bar (or on Mac: window buttons) -->
|
||||
<LayoutRow class="left">
|
||||
{#if platform === "Mac"}
|
||||
<WindowButtonsMac {maximized} />
|
||||
<WindowButtonsMac />
|
||||
{:else}
|
||||
{#each entries as entry}
|
||||
<TextButton label={entry.label} icon={entry.icon} menuListChildren={entry.children} action={entry.action} flush={true} />
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
|
||||
export let maximized = false;
|
||||
const editor = getContext<Editor>("editor");
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-buttons-mac">
|
||||
<div class="close" title="Close" />
|
||||
<div class="minimize" title={maximized ? "Minimize" : "Maximize"} />
|
||||
<div class="zoom" title="Zoom" />
|
||||
<div class="close" on:click={() => editor.handle.appWindowClose()} />
|
||||
<div class="minimize" on:click={() => editor.handle.appWindowMinimize()} />
|
||||
<div class="zoom" on:click={() => editor.handle.appWindowMaximize()} />
|
||||
</LayoutRow>
|
||||
|
||||
<style lang="scss" global>
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { FullscreenState } from "@graphite/state-providers/fullscreen";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const fullscreen = getContext<FullscreenState>("fullscreen");
|
||||
|
||||
$: requestFullscreenHotkeys = fullscreen.keyboardLockApiSupported && !$fullscreen.keyboardLocked;
|
||||
|
||||
async function handleClick() {
|
||||
async function handleClick(e: MouseEvent) {
|
||||
// TODO: Remove this debugging option to switch from web to desktop window buttons
|
||||
if (e.ctrlKey && e.shiftKey && e.altKey) {
|
||||
editor.handle.appWindowMinimize();
|
||||
editor.handle.appWindowMinimize();
|
||||
return;
|
||||
}
|
||||
|
||||
if ($fullscreen.windowFullscreen) fullscreen.exitFullscreen();
|
||||
else fullscreen.enterFullscreen();
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-buttons-web" on:click={() => handleClick()} tooltip={($fullscreen.windowFullscreen ? "Exit" : "Enter") + " Fullscreen (F11)"}>
|
||||
<LayoutRow class="window-buttons-web" on:click={handleClick} tooltip={$fullscreen.windowFullscreen ? "Exit Fullscreen (F11)" : "Enter Fullscreen (F11)"}>
|
||||
{#if requestFullscreenHotkeys}
|
||||
<TextLabel italic={true}>Go fullscreen to access all hotkeys</TextLabel>
|
||||
{/if}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
|
||||
export let maximized = false;
|
||||
export let maximized;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
</script>
|
||||
|
||||
<LayoutRow class="window-button windows minimize" tooltip="Minimize">
|
||||
<LayoutRow class="window-button windows" tooltip="Minimize" on:click={() => editor.handle.appWindowMinimize()}>
|
||||
<IconLabel icon={"WindowButtonWinMinimize"} />
|
||||
</LayoutRow>
|
||||
{#if !maximized}
|
||||
<LayoutRow class="window-button windows maximize" tooltip="Maximize">
|
||||
<IconLabel icon={"WindowButtonWinMaximize"} />
|
||||
</LayoutRow>
|
||||
{:else}
|
||||
<LayoutRow class="window-button windows restore-down" tooltip="Restore Down">
|
||||
<IconLabel icon={"WindowButtonWinRestoreDown"} />
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
<LayoutRow class="window-button windows close" tooltip="Close">
|
||||
<LayoutRow class="window-button windows" tooltip={maximized ? "Restore Down" : "Maximize"} on:click={() => editor.handle.appWindowMaximize()}>
|
||||
<IconLabel icon={maximized ? "WindowButtonWinRestoreDown" : "WindowButtonWinMaximize"} />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="window-button windows" tooltip="Close" on:click={() => editor.handle.appWindowClose()}>
|
||||
<IconLabel icon={"WindowButtonWinClose"} />
|
||||
</LayoutRow>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.close:hover {
|
||||
&:last-of-type:hover {
|
||||
background: #e81123;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@
|
||||
export let clickAction: ((index: number) => void) | undefined = undefined;
|
||||
export let closeAction: ((index: number) => void) | undefined = undefined;
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
let styleName = "";
|
||||
export { styleName as style };
|
||||
export let styles: Record<string, string | number | undefined> = {};
|
||||
|
||||
let tabElements: (LayoutRow | undefined)[] = [];
|
||||
|
||||
function platformModifiers(reservedKey: boolean): LayoutKeysGroup {
|
||||
@@ -90,7 +97,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<LayoutCol class="panel" on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)}>
|
||||
<LayoutCol on:pointerdown={() => panelType && editor.handle.setActivePanel(panelType)} class={`panel ${className}`.trim()} {classes} style={styleName} {styles}>
|
||||
<LayoutRow class="tab-bar" classes={{ "min-widths": tabMinWidths }}>
|
||||
<LayoutRow class="tab-group" scrollableX={true}>
|
||||
{#each tabLabels as tabLabel, tabIndex}
|
||||
@@ -194,6 +201,7 @@
|
||||
.tab-bar {
|
||||
height: 28px;
|
||||
min-height: auto;
|
||||
background: var(--color-1-nearblack); // Needed for the viewport hole punch on desktop
|
||||
|
||||
&.min-widths .tab-group .tab {
|
||||
min-width: 120px;
|
||||
@@ -336,5 +344,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Needed for the viewport hole punch on desktop
|
||||
.viewport-hole-punch &.document-panel,
|
||||
.viewport-hole-punch &.document-panel .panel-body:not(:has(.empty-panel)) {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -137,6 +137,7 @@
|
||||
<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
|
||||
class="document-panel"
|
||||
panelType={$portfolio.documents.length > 0 ? "Document" : undefined}
|
||||
tabCloseButtons={true}
|
||||
tabMinWidths={true}
|
||||
@@ -176,8 +177,9 @@
|
||||
flex: 1 1 100%;
|
||||
|
||||
.workspace-grid-subdivision {
|
||||
min-height: 28px;
|
||||
position: relative;
|
||||
flex: 1 1 0;
|
||||
min-height: 28px;
|
||||
|
||||
&.folded {
|
||||
flex-grow: 0;
|
||||
@@ -196,5 +198,15 @@
|
||||
cursor: ew-resize;
|
||||
}
|
||||
}
|
||||
|
||||
// Needed for the viewport hole punch on desktop
|
||||
.viewport-hole-punch & .workspace-grid-subdivision:has(.panel.document-panel)::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 6px;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 0 calc(100vw + 100vh) var(--color-2-mildblack);
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -169,7 +169,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
potentiallyRestoreCanvasFocus(e);
|
||||
|
||||
const { target } = e;
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-node-graph]");
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||
const inDialog = target instanceof Element && target.closest("[data-dialog] [data-floating-menu-content]");
|
||||
const inContextMenu = target instanceof Element && target.closest("[data-context-menu]");
|
||||
const inTextInput = target === textToolInteractiveInputElement;
|
||||
@@ -219,7 +219,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
|
||||
// Allow only events within the viewport or node graph boundaries
|
||||
const { target } = e;
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-node-graph]");
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||
if (!(isTargetingCanvas instanceof Element)) return;
|
||||
|
||||
// Allow only repeated increments of double-clicks (not 1, 3, 5, etc.)
|
||||
@@ -256,7 +256,7 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
|
||||
function onWheelScroll(e: WheelEvent) {
|
||||
const { target } = e;
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-node-graph]");
|
||||
const isTargetingCanvas = target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-node-graph]");
|
||||
|
||||
// Redirect vertical scroll wheel movement into a horizontal scroll on a horizontally scrollable element
|
||||
// There seems to be no possible way to properly employ the browser's smooth scrolling interpolation
|
||||
@@ -502,7 +502,9 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
|
||||
function potentiallyRestoreCanvasFocus(e: Event) {
|
||||
const { target } = e;
|
||||
const newInCanvasArea = (target instanceof Element && target.closest("[data-viewport], [data-graph]")) instanceof Element && !targetIsTextField(window.document.activeElement || undefined);
|
||||
const newInCanvasArea =
|
||||
(target instanceof Element && target.closest("[data-viewport], [data-viewport-container], [data-graph]")) instanceof Element &&
|
||||
!targetIsTextField(window.document.activeElement || undefined);
|
||||
if (!canvasFocused && newInCanvasArea) {
|
||||
canvasFocused = true;
|
||||
app?.focus();
|
||||
|
||||
@@ -349,6 +349,21 @@ export class TriggerIndexedDbRemoveDocument extends JsMessage {
|
||||
documentId!: string;
|
||||
}
|
||||
|
||||
export type AppWindowPlatform = "Web" | "Windows" | "Mac" | "Linux";
|
||||
|
||||
export class UpdatePlatform extends JsMessage {
|
||||
@Transform(({ value }: { value: AppWindowPlatform }) => value)
|
||||
readonly platform!: AppWindowPlatform;
|
||||
}
|
||||
|
||||
export class UpdateMaximized extends JsMessage {
|
||||
readonly maximized!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateViewportHolePunch extends JsMessage {
|
||||
readonly active!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateInputHints extends JsMessage {
|
||||
@Type(() => HintInfo)
|
||||
readonly hintData!: HintData;
|
||||
@@ -1670,29 +1685,32 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
UpdateEyedropperSamplingState,
|
||||
UpdateGraphFadeArtwork,
|
||||
UpdateGraphViewOverlay,
|
||||
UpdateSpreadsheetState,
|
||||
UpdateImportReorderIndex,
|
||||
UpdateImportsExports,
|
||||
UpdateInputHints,
|
||||
UpdateInSelectedNetwork,
|
||||
UpdateLayersPanelBottomBarLayout,
|
||||
UpdateLayersPanelControlBarLeftLayout,
|
||||
UpdateLayersPanelControlBarRightLayout,
|
||||
UpdateLayersPanelBottomBarLayout,
|
||||
UpdateLayerWidths,
|
||||
UpdateMaximized,
|
||||
UpdateMenuBarLayout,
|
||||
UpdateMouseCursor,
|
||||
UpdateNodeGraphNodes,
|
||||
UpdateVisibleNodes,
|
||||
UpdateNodeGraphWires,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeGraphControlBarLayout,
|
||||
UpdateNodeGraphNodes,
|
||||
UpdateNodeGraphSelection,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeGraphWires,
|
||||
UpdateNodeThumbnail,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdatePlatform,
|
||||
UpdatePropertyPanelSectionsLayout,
|
||||
UpdateSpreadsheetLayout,
|
||||
UpdateSpreadsheetState,
|
||||
UpdateToolOptionsLayout,
|
||||
UpdateToolShelfLayout,
|
||||
UpdateViewportHolePunch,
|
||||
UpdateVisibleNodes,
|
||||
UpdateWirePathInProgress,
|
||||
UpdateWorkingColorsLayout,
|
||||
} as const;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/* eslint-disable max-classes-per-file */
|
||||
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
import { type Editor } from "@graphite/editor";
|
||||
import { type AppWindowPlatform, UpdatePlatform, UpdateMaximized, UpdateViewportHolePunch } from "@graphite/messages";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export function createAppWindowState(editor: Editor) {
|
||||
const { subscribe, update } = writable({
|
||||
platform: "Web" as AppWindowPlatform,
|
||||
maximized: false,
|
||||
viewportHolePunch: false,
|
||||
});
|
||||
|
||||
// Set up message subscriptions on creation
|
||||
editor.subscriptions.subscribeJsMessage(UpdatePlatform, (updatePlatform) => {
|
||||
update((state) => {
|
||||
state.platform = updatePlatform.platform;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateMaximized, (maximized) => {
|
||||
update((state) => {
|
||||
state.maximized = maximized.maximized;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateViewportHolePunch, (viewportHolePunch) => {
|
||||
update((state) => {
|
||||
state.viewportHolePunch = viewportHolePunch.active;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
};
|
||||
}
|
||||
export type AppWindowState = ReturnType<typeof createAppWindowState>;
|
||||
@@ -103,7 +103,6 @@ export function createPortfolioState(editor: Editor) {
|
||||
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
|
||||
}
|
||||
});
|
||||
|
||||
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetState, async (updateSpreadsheetState) => {
|
||||
update((state) => {
|
||||
state.spreadsheetOpen = updateSpreadsheetState.open;
|
||||
@@ -111,7 +110,6 @@ export function createPortfolioState(editor: Editor) {
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
||||
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetLayout, (updateSpreadsheetLayout) => {
|
||||
update((state) => {
|
||||
patchWidgetLayout(state.spreadsheetWidgets, updateSpreadsheetLayout);
|
||||
|
||||
Reference in New Issue
Block a user