mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-22 07:28:11 +08:00
Replace the Spreadsheet panel with an improved Data panel (#3037)
* Improve the table data panel * Add the "Window" menu bar section and polish everything
This commit is contained in:
@@ -39,6 +39,7 @@
|
||||
export let colorOrGradient: FillChoice;
|
||||
export let allowNone = false;
|
||||
// export let allowTransparency = false; // TODO: Implement
|
||||
export let disabled = false;
|
||||
export let direction: MenuDirection = "Bottom";
|
||||
// TODO: See if this should be made to follow the pattern of DropdownInput.svelte so this could be removed
|
||||
export let open: boolean;
|
||||
@@ -133,6 +134,8 @@
|
||||
}
|
||||
|
||||
function onPointerDown(e: PointerEvent) {
|
||||
if (disabled) return;
|
||||
|
||||
const target = (e.target || undefined) as HTMLElement | undefined;
|
||||
draggingPickerTrack = target?.closest("[data-saturation-value-picker], [data-hue-picker], [data-alpha-picker]") || undefined;
|
||||
|
||||
@@ -403,7 +406,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<FloatingMenu class="color-picker" {open} on:open {strayCloses} escapeCloses={strayCloses && !gradientSpectrumDragging} {direction} type="Popover" bind:this={self}>
|
||||
<FloatingMenu class="color-picker" classes={{ disabled }} {open} on:open {strayCloses} escapeCloses={strayCloses && !gradientSpectrumDragging} {direction} type="Popover" bind:this={self}>
|
||||
<LayoutRow
|
||||
styles={{
|
||||
"--new-color": newColor.toHexOptionalAlpha(),
|
||||
@@ -418,7 +421,7 @@
|
||||
>
|
||||
<LayoutCol class="pickers-and-gradient">
|
||||
<LayoutRow class="pickers">
|
||||
<LayoutCol class="saturation-value-picker" on:pointerdown={onPointerDown} data-saturation-value-picker>
|
||||
<LayoutCol class="saturation-value-picker" title={disabled ? "Saturation and value (disabled)" : "Saturation and value"} on:pointerdown={onPointerDown} data-saturation-value-picker>
|
||||
{#if !isNone}
|
||||
<div class="selection-circle" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`} />
|
||||
{/if}
|
||||
@@ -432,12 +435,12 @@
|
||||
/>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
<LayoutCol class="hue-picker" on:pointerdown={onPointerDown} data-hue-picker>
|
||||
<LayoutCol class="hue-picker" title={disabled ? "Hue (disabled)" : "Hue"} on:pointerdown={onPointerDown} data-hue-picker>
|
||||
{#if !isNone}
|
||||
<div class="selection-needle" style:top={`${(1 - hue) * 100}%`} />
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
<LayoutCol class="alpha-picker" on:pointerdown={onPointerDown} data-alpha-picker>
|
||||
<LayoutCol class="alpha-picker" title={disabled ? "Alpha (disabled)" : "Alpha"} on:pointerdown={onPointerDown} data-alpha-picker>
|
||||
{#if !isNone}
|
||||
<div class="selection-needle" style:top={`${(1 - alpha) * 100}%`} />
|
||||
{/if}
|
||||
@@ -447,6 +450,7 @@
|
||||
<LayoutRow class="gradient">
|
||||
<SpectrumInput
|
||||
{gradient}
|
||||
{disabled}
|
||||
on:gradient={() => {
|
||||
gradient = gradient;
|
||||
if (gradient) dispatch("colorOrGradient", gradient);
|
||||
@@ -459,6 +463,7 @@
|
||||
{#if gradientSpectrumInputWidget && activeIndex !== undefined}
|
||||
<NumberInput
|
||||
value={(gradient.positionAtIndex(activeIndex) || 0) * 100}
|
||||
{disabled}
|
||||
on:value={({ detail }) => {
|
||||
if (gradientSpectrumInputWidget && activeIndex !== undefined && detail !== undefined) gradientSpectrumInputWidget.setPosition(activeIndex, detail / 100);
|
||||
}}
|
||||
@@ -478,7 +483,7 @@
|
||||
styles={{ "--outline-amount": outlineFactor }}
|
||||
tooltip={!newColor.equals(oldColor) ? "Comparison between the present color choice (left) and the color before any change was made (right)" : "The present color choice"}
|
||||
>
|
||||
{#if !newColor.equals(oldColor)}
|
||||
{#if !newColor.equals(oldColor) && !disabled}
|
||||
<div class="swap-button-background"></div>
|
||||
<IconButton class="swap-button" icon="SwapHorizontal" size={16} action={swapNewWithOld} tooltip="Swap" />
|
||||
{/if}
|
||||
@@ -500,6 +505,7 @@
|
||||
<LayoutRow>
|
||||
<TextInput
|
||||
value={newColor.toHexOptionalAlpha() || "-"}
|
||||
{disabled}
|
||||
on:commitText={({ detail }) => {
|
||||
dispatch("startHistoryTransaction");
|
||||
setColorCode(detail);
|
||||
@@ -520,6 +526,7 @@
|
||||
{/if}
|
||||
<NumberInput
|
||||
value={strength}
|
||||
{disabled}
|
||||
on:value={({ detail }) => {
|
||||
strength = detail;
|
||||
setColorRGB(channel, detail);
|
||||
@@ -547,6 +554,7 @@
|
||||
{/if}
|
||||
<NumberInput
|
||||
value={strength}
|
||||
{disabled}
|
||||
on:value={({ detail }) => {
|
||||
strength = detail;
|
||||
setColorHSV(channel, detail);
|
||||
@@ -573,6 +581,7 @@
|
||||
<Separator type="Related" />
|
||||
<NumberInput
|
||||
value={!isNone ? alpha * 100 : undefined}
|
||||
{disabled}
|
||||
on:value={({ detail }) => {
|
||||
if (detail !== undefined) alpha = detail / 100;
|
||||
setColorAlphaPercent(detail);
|
||||
@@ -593,14 +602,14 @@
|
||||
<LayoutRow class="leftover-space" />
|
||||
<LayoutRow>
|
||||
{#if allowNone && !gradient}
|
||||
<button class="preset-color none" on:click={() => setColorPreset("none")} title="Set to no color" tabindex="0"></button>
|
||||
<button class="preset-color none" {disabled} on:click={() => setColorPreset("none")} title="Set to no color" tabindex="0"></button>
|
||||
<Separator type="Related" />
|
||||
{/if}
|
||||
<button class="preset-color black" on:click={() => setColorPreset("black")} title="Set to black" tabindex="0"></button>
|
||||
<button class="preset-color black" {disabled} on:click={() => setColorPreset("black")} title="Set to black" tabindex="0"></button>
|
||||
<Separator type="Related" />
|
||||
<button class="preset-color white" on:click={() => setColorPreset("white")} title="Set to white" tabindex="0"></button>
|
||||
<button class="preset-color white" {disabled} on:click={() => setColorPreset("white")} title="Set to white" tabindex="0"></button>
|
||||
<Separator type="Related" />
|
||||
<button class="preset-color pure" on:click={setColorPresetSubtile} tabindex="-1">
|
||||
<button class="preset-color pure" {disabled} on:click={setColorPresetSubtile} tabindex="-1">
|
||||
<div data-pure-tile="red" style="--pure-color: #ff0000; --pure-color-gray: #4c4c4c" title="Set to red" />
|
||||
<div data-pure-tile="yellow" style="--pure-color: #ffff00; --pure-color-gray: #e3e3e3" title="Set to yellow" />
|
||||
<div data-pure-tile="green" style="--pure-color: #00ff00; --pure-color-gray: #969696" title="Set to green" />
|
||||
@@ -609,7 +618,7 @@
|
||||
<div data-pure-tile="magenta" style="--pure-color: #ff00ff; --pure-color-gray: #696969" title="Set to magenta" />
|
||||
</button>
|
||||
<Separator type="Related" />
|
||||
<IconButton icon="Eyedropper" size={24} action={activateEyedropperSample} tooltip="Sample a pixel color from the document" />
|
||||
<IconButton icon="Eyedropper" size={24} {disabled} action={activateEyedropperSample} tooltip="Sample a pixel color from the document" />
|
||||
</LayoutRow>
|
||||
</LayoutCol>
|
||||
</LayoutRow>
|
||||
@@ -954,7 +963,7 @@
|
||||
// For the least jarring luminance conversion, these colors are derived by placing a black layer with the "desaturate" blend mode over the colors.
|
||||
// We don't use the CSS `filter: grayscale(1);` property because it produces overly dark tones for bright colors with a noticeable jump on hover.
|
||||
background: var(--pure-color-gray);
|
||||
transition: background-color 0.2s ease;
|
||||
transition: background-color 0.1s;
|
||||
}
|
||||
|
||||
&:hover div {
|
||||
@@ -963,5 +972,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled .pickers-and-gradient .pickers :is(.saturation-value-picker, .hue-picker, .alpha-picker),
|
||||
&.disabled .details .preset-color,
|
||||
&.disabled .details .choice-preview {
|
||||
transition: opacity 0.1s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled .details .preset-color.pure:hover div {
|
||||
background: var(--pure-color-gray);
|
||||
}
|
||||
}
|
||||
|
||||
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, onDestroy } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import { defaultWidgetLayout, patchWidgetLayout, UpdateDataPanelLayout } from "@graphite/messages";
|
||||
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let dataPanelLayout = defaultWidgetLayout();
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDataPanelLayout, (updateDataPanelLayout) => {
|
||||
patchWidgetLayout(dataPanelLayout, updateDataPanelLayout);
|
||||
dataPanelLayout = dataPanelLayout;
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdateDataPanelLayout);
|
||||
});
|
||||
</script>
|
||||
|
||||
<LayoutCol class="data-panel">
|
||||
<LayoutCol class="body" scrollableY={true}>
|
||||
<WidgetLayout layout={dataPanelLayout} />
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
|
||||
<style lang="scss" global>
|
||||
.data-panel {
|
||||
flex-grow: 1;
|
||||
padding: 4px;
|
||||
|
||||
table {
|
||||
margin: -4px;
|
||||
width: calc(100% + 2 * 4px);
|
||||
|
||||
.text-label {
|
||||
white-space: wrap;
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -675,7 +675,7 @@
|
||||
|
||||
&[title^="Coming Soon"] {
|
||||
opacity: 0.25;
|
||||
transition: opacity 0.2s;
|
||||
transition: opacity 0.1s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
@@ -825,7 +825,7 @@
|
||||
|
||||
.graph-view {
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s;
|
||||
opacity: 0;
|
||||
|
||||
&.open {
|
||||
|
||||
@@ -98,6 +98,12 @@
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarLeftLayout);
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarRightLayout);
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelBottomBarLayout);
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerStructureJs);
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerDetails);
|
||||
|
||||
removeEventListener("pointermove", clippingHover);
|
||||
removeEventListener("keydown", clippingKeyPress);
|
||||
removeEventListener("keyup", clippingKeyPress);
|
||||
@@ -489,7 +495,9 @@
|
||||
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
||||
<LayoutRow class="control-bar" scrollableX={true}>
|
||||
<WidgetLayout layout={layersPanelControlBarLeftLayout} />
|
||||
<Separator />
|
||||
{#if layersPanelControlBarLeftLayout?.layout?.length > 0 && layersPanelControlBarRightLayout?.layout?.length > 0}
|
||||
<Separator />
|
||||
{/if}
|
||||
<WidgetLayout layout={layersPanelControlBarRightLayout} />
|
||||
</LayoutRow>
|
||||
<LayoutRow class="list-area" scrollableY={true}>
|
||||
@@ -605,6 +613,10 @@
|
||||
.widget-span:first-child {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&:not(:has(*)) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom bar
|
||||
@@ -619,6 +631,10 @@
|
||||
.widget-span > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&:not(:has(*)) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Layer hierarchy
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from "svelte";
|
||||
import { getContext, onMount, onDestroy } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelSectionsLayout } from "@graphite/messages";
|
||||
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertiesPanelLayout } from "@graphite/messages";
|
||||
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
|
||||
let propertiesSectionsLayout = defaultWidgetLayout();
|
||||
let propertiesPanelLayout = defaultWidgetLayout();
|
||||
|
||||
onMount(() => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelSectionsLayout, (updatePropertyPanelSectionsLayout) => {
|
||||
patchWidgetLayout(propertiesSectionsLayout, updatePropertyPanelSectionsLayout);
|
||||
propertiesSectionsLayout = propertiesSectionsLayout;
|
||||
editor.subscriptions.subscribeJsMessage(UpdatePropertiesPanelLayout, (updatePropertiesPanelLayout) => {
|
||||
patchWidgetLayout(propertiesPanelLayout, updatePropertiesPanelLayout);
|
||||
propertiesPanelLayout = propertiesPanelLayout;
|
||||
});
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
editor.subscriptions.unsubscribeJsMessage(UpdatePropertiesPanelLayout);
|
||||
});
|
||||
</script>
|
||||
|
||||
<LayoutCol class="properties">
|
||||
<LayoutCol class="sections" scrollableY={true}>
|
||||
<WidgetLayout layout={propertiesSectionsLayout} />
|
||||
<WidgetLayout layout={propertiesPanelLayout} />
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<script lang="ts">
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
import { getContext } from "svelte";
|
||||
|
||||
import type { PortfolioState } from "/src/state-providers/portfolio";
|
||||
|
||||
const portfolio = getContext<PortfolioState>("portfolio");
|
||||
</script>
|
||||
|
||||
<LayoutCol class="spreadsheet">
|
||||
<LayoutRow class="control-bar">
|
||||
<TextLabel>Spreadsheet Data for Node ID {$portfolio.spreadsheetNode}:</TextLabel>
|
||||
</LayoutRow>
|
||||
<LayoutCol class="body" scrollableY={true}>
|
||||
<WidgetLayout layout={$portfolio.spreadsheetWidgets} />
|
||||
</LayoutCol>
|
||||
</LayoutCol>
|
||||
|
||||
<style lang="scss" global>
|
||||
.spreadsheet {
|
||||
flex-grow: 1;
|
||||
padding: 4px;
|
||||
|
||||
.control-bar {
|
||||
height: 32px;
|
||||
--widget-height: 24px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
.text-label {
|
||||
margin: calc((24px - var(--widget-height)) / 2 + 4px) 0;
|
||||
min-height: var(--widget-height);
|
||||
line-height: var(--widget-height);
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0 -4px;
|
||||
width: calc(100% + 2 * 4px);
|
||||
margin-top: 8px;
|
||||
|
||||
.text-label {
|
||||
white-space: wrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1008,7 +1008,7 @@
|
||||
border-radius: 4px;
|
||||
bottom: calc(100% + 12px);
|
||||
z-index: -1;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
transition: opacity 0.2s;
|
||||
opacity: 0.5;
|
||||
|
||||
// Tail
|
||||
@@ -1040,7 +1040,7 @@
|
||||
-webkit-user-select: text;
|
||||
user-select: text;
|
||||
transition:
|
||||
opacity 0.2s ease-in-out,
|
||||
opacity 0.2s,
|
||||
z-index 0s 0.2s;
|
||||
|
||||
&::selection {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||
import WorkingColorsInput from "@graphite/components/widgets/inputs/WorkingColorsInput.svelte";
|
||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||
import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte";
|
||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||
@@ -124,6 +125,10 @@
|
||||
{#if iconLabel}
|
||||
<IconLabel {...exclude(iconLabel)} />
|
||||
{/if}
|
||||
{@const imageLabel = narrowWidgetProps(component.props, "ImageLabel")}
|
||||
{#if imageLabel}
|
||||
<ImageLabel {...exclude(imageLabel)} />
|
||||
{/if}
|
||||
{@const imageButton = narrowWidgetProps(component.props, "ImageButton")}
|
||||
{#if imageButton}
|
||||
<ImageButton {...exclude(imageButton)} action={() => widgetValueCommitAndUpdate(index, undefined)} />
|
||||
|
||||
@@ -17,14 +17,15 @@
|
||||
.join(" ");
|
||||
</script>
|
||||
|
||||
<img src={IMAGE_BASE64_STRINGS[image]} style:width style:height class={`image-label ${className} ${extraClasses}`.trim()} title={tooltip} alt="" on:click={action} />
|
||||
<img src={IMAGE_BASE64_STRINGS[image]} style:width style:height class={`image-button ${className} ${extraClasses}`.trim()} title={tooltip} alt="" on:click={action} />
|
||||
|
||||
<style lang="scss" global>
|
||||
.image-label {
|
||||
.image-button {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 2px;
|
||||
|
||||
+ .image-label.image-label {
|
||||
+ .image-button.image-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
|
||||
import type { FillChoice } from "@graphite/messages";
|
||||
import type { FillChoice, MenuDirection } from "@graphite/messages";
|
||||
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
||||
|
||||
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||
|
||||
const dispatch = createEventDispatcher<{ value: FillChoice; startHistoryTransaction: undefined }>();
|
||||
|
||||
@@ -15,6 +14,7 @@
|
||||
export let value: FillChoice;
|
||||
export let disabled = false;
|
||||
export let allowNone = false;
|
||||
export let menuDirection: MenuDirection = "Bottom";
|
||||
// export let allowTransparency = false; // TODO: Implement
|
||||
export let tooltip: string | undefined = undefined;
|
||||
|
||||
@@ -25,16 +25,18 @@
|
||||
$: transparency = value instanceof Gradient ? value.stops.some((stop) => stop.color.alpha < 1) : value.alpha < 1;
|
||||
</script>
|
||||
|
||||
<LayoutCol class="color-button" classes={{ open, disabled, none, transparency, outlined }} {tooltip}>
|
||||
<button {disabled} style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
|
||||
{#if disabled && value instanceof Color && !value.none}
|
||||
<LayoutCol class="color-button" classes={{ open, disabled, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltip}>
|
||||
<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
|
||||
<!-- {#if disabled && value instanceof Color && !value.none}
|
||||
<TextLabel>sRGB</TextLabel>
|
||||
{/if}
|
||||
{/if} -->
|
||||
</button>
|
||||
<ColorPicker
|
||||
{open}
|
||||
on:open={({ detail }) => (open = detail)}
|
||||
{disabled}
|
||||
colorOrGradient={value}
|
||||
direction={menuDirection || "Bottom"}
|
||||
on:open={({ detail }) => (open = detail)}
|
||||
on:colorOrGradient={({ detail }) => {
|
||||
value = detail;
|
||||
dispatch("value", detail);
|
||||
@@ -137,5 +139,9 @@
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
&.direction-top > .floating-menu {
|
||||
bottom: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
const dispatch = createEventDispatcher<{ activeMarkerIndexChange: number | undefined; gradient: Gradient; dragging: boolean }>();
|
||||
|
||||
export let gradient: Gradient;
|
||||
export let disabled = false;
|
||||
export let activeMarkerIndex = 0 as number | undefined;
|
||||
// export let disabled = false;
|
||||
// export let tooltip: string | undefined = undefined;
|
||||
@@ -22,6 +23,8 @@
|
||||
let deletionRestore: boolean | undefined = undefined;
|
||||
|
||||
function markerPointerDown(e: PointerEvent, index: number) {
|
||||
if (disabled) return;
|
||||
|
||||
// Left-click to select and begin potentially dragging
|
||||
if (e.button === BUTTON_LEFT) {
|
||||
activeMarkerIndex = index;
|
||||
@@ -47,6 +50,8 @@
|
||||
}
|
||||
|
||||
function insertStop(e: MouseEvent) {
|
||||
if (disabled) return;
|
||||
|
||||
if (e.button !== BUTTON_LEFT) return;
|
||||
|
||||
let position = markerPosition(e);
|
||||
@@ -79,6 +84,8 @@
|
||||
}
|
||||
|
||||
function deleteStop(e: KeyboardEvent) {
|
||||
if (disabled) return;
|
||||
|
||||
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
||||
if (activeMarkerIndex === undefined) return;
|
||||
|
||||
@@ -88,6 +95,8 @@
|
||||
}
|
||||
|
||||
function deleteStopByIndex(index: number) {
|
||||
if (disabled) return;
|
||||
|
||||
if (gradient.stops.length <= 2) return;
|
||||
|
||||
gradient.stops.splice(index, 1);
|
||||
@@ -103,6 +112,8 @@
|
||||
}
|
||||
|
||||
function moveMarker(e: PointerEvent, index: number) {
|
||||
if (disabled) return;
|
||||
|
||||
// Just in case the mouseup event is lost
|
||||
if (e.buttons === 0) stopDrag();
|
||||
|
||||
@@ -120,6 +131,8 @@
|
||||
}
|
||||
|
||||
export function setPosition(index: number, position: number) {
|
||||
if (disabled) return;
|
||||
|
||||
const active = gradient.stops[index];
|
||||
active.position = position;
|
||||
gradient.stops.sort((a, b) => a.position - b.position);
|
||||
@@ -131,6 +144,8 @@
|
||||
}
|
||||
|
||||
function abortDrag() {
|
||||
if (disabled) return;
|
||||
|
||||
if (activeMarkerIndex === undefined) return;
|
||||
|
||||
if (deletionRestore) {
|
||||
@@ -143,6 +158,8 @@
|
||||
}
|
||||
|
||||
function stopDrag() {
|
||||
if (disabled) return;
|
||||
|
||||
removeEvents();
|
||||
|
||||
positionRestore = undefined;
|
||||
@@ -152,19 +169,27 @@
|
||||
}
|
||||
|
||||
function onPointerMove(e: PointerEvent) {
|
||||
if (disabled) return;
|
||||
|
||||
if (activeMarkerIndex !== undefined) moveMarker(e, activeMarkerIndex);
|
||||
}
|
||||
|
||||
function onPointerUp() {
|
||||
if (disabled) return;
|
||||
|
||||
stopDrag();
|
||||
}
|
||||
|
||||
function onMouseDown(e: MouseEvent) {
|
||||
if (disabled) return;
|
||||
|
||||
const BUTTONS_RIGHT = 0b0000_0010;
|
||||
if (e.buttons & BUTTONS_RIGHT) abortDrag();
|
||||
}
|
||||
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
if (disabled) return;
|
||||
|
||||
if (e.key === "Escape") {
|
||||
const element = markerTrack?.div();
|
||||
if (element) preventEscapeClosingParentFloatingMenu(element);
|
||||
@@ -193,25 +218,28 @@
|
||||
document.removeEventListener("keydown", deleteStop);
|
||||
});
|
||||
|
||||
// Future design notes:
|
||||
//
|
||||
// # Backend -> Frontend
|
||||
// Populate(gradient, { position, color }[], active) // The only way indexes get changed. Frontend drops marker if it's being dragged.
|
||||
// UpdateGradient(gradient)
|
||||
// UpdateMarkers({ index, position, color }[])
|
||||
|
||||
//
|
||||
// # Frontend -> Backend
|
||||
// SendNewActive(index)
|
||||
// SendPositions({ index, position }[])
|
||||
// AddMarker(position)
|
||||
// RemoveMarkers(index[])
|
||||
// ResetMarkerToDefault(index)
|
||||
|
||||
// // We need a way to encode constraints on some markers, like locking them in place or preventing reordering
|
||||
// // We need a way to encode the allowability of adding new markers between certain markers, or preventing the deletion of certain markers
|
||||
// // We need the ability to multi-select markers and move them all at once
|
||||
//
|
||||
// We need a way to encode constraints on some markers, like locking them in place or preventing reordering
|
||||
// We need a way to encode the allowability of adding new markers between certain markers, or preventing the deletion of certain markers
|
||||
// We need the ability to multi-select markers and move them all at once
|
||||
</script>
|
||||
|
||||
<LayoutCol
|
||||
class="spectrum-input"
|
||||
classes={{ disabled }}
|
||||
styles={{
|
||||
"--gradient-start": gradient.firstColor()?.toHexOptionalAlpha() || "black",
|
||||
"--gradient-end": gradient.lastColor()?.toHexOptionalAlpha() || "black",
|
||||
@@ -232,6 +260,9 @@
|
||||
viewBox="0 0 12 12"
|
||||
>
|
||||
<path class="inner-fill" d="M10,11.5H2c-0.8,0-1.5-0.7-1.5-1.5V6.8c0-0.4,0.2-0.8,0.4-1.1L6,0.7l5.1,5.1c0.3,0.3,0.4,0.7,0.4,1.1V10C11.5,10.8,10.8,11.5,10,11.5z" />
|
||||
{#if disabled}
|
||||
<path class="disabled-fill" d="M10,11.5H2c-0.8,0-1.5-0.7-1.5-1.5V6.8c0-0.4,0.2-0.8,0.4-1.1L6,0.7l5.1,5.1c0.3,0.3,0.4,0.7,0.4,1.1V10C11.5,10.8,10.8,11.5,10,11.5z" />
|
||||
{/if}
|
||||
<path
|
||||
class="outer-border"
|
||||
d="M6,1.4L1.3,6.1C1.1,6.3,1,6.6,1,6.8V10c0,0.6,0.4,1,1,1h8c0.6,0,1-0.4,1-1V6.8c0-0.3-0.1-0.5-0.3-0.7L6,1.4M6,0l5.4,5.4C11.8,5.8,12,6.3,12,6.8V10c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2V6.8c0-0.5,0.2-1,0.6-1.4L6,0z"
|
||||
@@ -269,6 +300,14 @@
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
&.disabled .gradient-strip {
|
||||
transition: opacity 0.1s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.marker-track {
|
||||
margin-top: calc(24px - 16px - 12px);
|
||||
margin-left: var(--marker-half-width);
|
||||
@@ -294,28 +333,40 @@
|
||||
.outer-border {
|
||||
fill: var(--color-5-dullgray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
.inner-fill:hover + .outer-border,
|
||||
.outer-border:hover {
|
||||
fill: var(--color-6-lowergray);
|
||||
}
|
||||
&.disabled .marker-track .marker {
|
||||
.disabled-fill {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.outer-border {
|
||||
fill: var(--color-4-dimgray);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.disabled) .marker-track .marker {
|
||||
&:not(.active) {
|
||||
.inner-fill:hover + .outer-border,
|
||||
.outer-border:hover {
|
||||
fill: var(--color-6-lowergray);
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.inner-fill {
|
||||
filter: drop-shadow(0 0 1px var(--color-2-mildblack)) drop-shadow(0 0 1px var(--color-2-mildblack));
|
||||
}
|
||||
|
||||
&.active {
|
||||
.inner-fill {
|
||||
filter: drop-shadow(0 0 1px var(--color-2-mildblack)) drop-shadow(0 0 1px var(--color-2-mildblack));
|
||||
}
|
||||
// Outer border when active
|
||||
.outer-border {
|
||||
fill: var(--color-e-nearwhite);
|
||||
}
|
||||
|
||||
// Outer border when active
|
||||
.outer-border {
|
||||
fill: var(--color-e-nearwhite);
|
||||
}
|
||||
|
||||
.inner-fill:hover + .outer-border,
|
||||
.outer-border:hover {
|
||||
fill: var(--color-f-white);
|
||||
}
|
||||
.inner-fill:hover + .outer-border,
|
||||
.outer-border:hover {
|
||||
fill: var(--color-f-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// Styling
|
||||
export let centered = false;
|
||||
export let minWidth = 0;
|
||||
export let maxWidth = 0;
|
||||
|
||||
let className = "";
|
||||
export { className as class };
|
||||
@@ -63,7 +64,10 @@
|
||||
<FieldInput
|
||||
class={`text-input ${className}`.trim()}
|
||||
classes={{ centered, ...classes }}
|
||||
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}
|
||||
styles={{
|
||||
...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}),
|
||||
...(maxWidth > 0 ? { "max-width": `${maxWidth}px` } : {}),
|
||||
}}
|
||||
{value}
|
||||
on:value
|
||||
on:textFocused={onTextFocused}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
let className = "";
|
||||
export { className as class };
|
||||
export let classes: Record<string, boolean> = {};
|
||||
|
||||
export let url: string;
|
||||
export let width: string | undefined;
|
||||
export let height: string | undefined;
|
||||
export let tooltip: string | undefined = undefined;
|
||||
|
||||
$: extraClasses = Object.entries(classes)
|
||||
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
||||
.join(" ");
|
||||
</script>
|
||||
|
||||
<img src={url} style:width style:height class={`image-label ${className} ${extraClasses}`.trim()} title={tooltip} alt="" />
|
||||
|
||||
<style lang="scss" global>
|
||||
.image-label {
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 2px;
|
||||
background-image: var(--color-transparent-checkered-background);
|
||||
background-size: var(--color-transparent-checkered-background-size);
|
||||
background-position: var(--color-transparent-checkered-background-position);
|
||||
background-repeat: var(--color-transparent-checkered-background-repeat);
|
||||
|
||||
+ .image-label.image-label {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts" context="module">
|
||||
import Data from "@graphite/components/panels/Data.svelte";
|
||||
import Document from "@graphite/components/panels/Document.svelte";
|
||||
import Layers from "@graphite/components/panels/Layers.svelte";
|
||||
import Properties from "@graphite/components/panels/Properties.svelte";
|
||||
import Spreadsheet from "@graphite/components/panels/Spreadsheet.svelte";
|
||||
|
||||
const PANEL_COMPONENTS = {
|
||||
Document,
|
||||
Layers,
|
||||
Properties,
|
||||
Spreadsheet,
|
||||
Data,
|
||||
};
|
||||
type PanelType = keyof typeof PANEL_COMPONENTS;
|
||||
</script>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
/**/ root: 100,
|
||||
/* ├─ */ content: 80,
|
||||
/* │ ├─ */ document: 70,
|
||||
/* │ └─ */ spreadsheet: 30,
|
||||
/* │ └─ */ data: 30,
|
||||
/* └─ */ details: 20,
|
||||
/* ├─ */ properties: 45,
|
||||
/* └─ */ layers: 55,
|
||||
@@ -148,23 +148,31 @@
|
||||
bind:this={documentPanel}
|
||||
/>
|
||||
</LayoutRow>
|
||||
{#if $portfolio.spreadsheetOpen}
|
||||
{#if $portfolio.dataPanelOpen}
|
||||
<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 class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["data"] }} data-subdivision-name="data">
|
||||
<Panel panelType="Data" tabLabels={[{ name: "Data" }]} 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>
|
||||
{#if $portfolio.propertiesPanelOpen || $portfolio.layersPanelOpen}
|
||||
<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">
|
||||
{#if $portfolio.propertiesPanelOpen}
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["properties"] }} data-subdivision-name="properties">
|
||||
<Panel panelType="Properties" tabLabels={[{ name: "Properties" }]} tabActiveIndex={0} />
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
{#if $portfolio.propertiesPanelOpen && $portfolio.layersPanelOpen}
|
||||
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />
|
||||
{/if}
|
||||
{#if $portfolio.layersPanelOpen}
|
||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["layers"] }} data-subdivision-name="layers">
|
||||
<Panel panelType="Layers" tabLabels={[{ name: "Layers" }]} tabActiveIndex={0} />
|
||||
</LayoutRow>
|
||||
{/if}
|
||||
</LayoutCol>
|
||||
{/if}
|
||||
</LayoutRow>
|
||||
{#if $dialog.visible}
|
||||
<Dialog />
|
||||
|
||||
+52
-15
@@ -763,10 +763,16 @@ export class UpdateGraphFadeArtwork extends JsMessage {
|
||||
readonly percentage!: number;
|
||||
}
|
||||
|
||||
export class UpdateSpreadsheetState extends JsMessage {
|
||||
export class UpdateDataPanelState extends JsMessage {
|
||||
readonly open!: boolean;
|
||||
}
|
||||
|
||||
readonly node!: bigint | undefined;
|
||||
export class UpdatePropertiesPanelState extends JsMessage {
|
||||
readonly open!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateLayersPanelState extends JsMessage {
|
||||
readonly open!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateMouseCursor extends JsMessage {
|
||||
@@ -981,9 +987,11 @@ export class ColorInput extends WidgetProps {
|
||||
})
|
||||
value!: FillChoice;
|
||||
|
||||
allowNone!: boolean;
|
||||
|
||||
disabled!: boolean;
|
||||
|
||||
allowNone!: boolean;
|
||||
menuDirection!: MenuDirection | undefined;
|
||||
|
||||
// allowTransparency!: boolean; // TODO: Implement
|
||||
|
||||
@@ -1140,6 +1148,19 @@ export class ImageButton extends WidgetProps {
|
||||
tooltip!: string | undefined;
|
||||
}
|
||||
|
||||
export class ImageLabel extends WidgetProps {
|
||||
url!: string;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value || undefined)
|
||||
width!: string | undefined;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value || undefined)
|
||||
height!: string | undefined;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value || undefined)
|
||||
tooltip!: string | undefined;
|
||||
}
|
||||
|
||||
export type NumberInputIncrementBehavior = "Add" | "Multiply" | "Callback" | "None";
|
||||
export type NumberInputMode = "Increment" | "Range";
|
||||
|
||||
@@ -1332,6 +1353,8 @@ export class TextInput extends WidgetProps {
|
||||
|
||||
minWidth!: number;
|
||||
|
||||
maxWidth!: number;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value || undefined)
|
||||
tooltip!: string | undefined;
|
||||
}
|
||||
@@ -1383,6 +1406,7 @@ const widgetSubTypes = [
|
||||
{ value: FontInput, name: "FontInput" },
|
||||
{ value: IconButton, name: "IconButton" },
|
||||
{ value: ImageButton, name: "ImageButton" },
|
||||
{ value: ImageLabel, name: "ImageLabel" },
|
||||
{ value: IconLabel, name: "IconLabel" },
|
||||
{ value: NodeCatalog, name: "NodeCatalog" },
|
||||
{ value: NumberInput, name: "NumberInput" },
|
||||
@@ -1472,11 +1496,11 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
|
||||
|
||||
updates.diff.forEach((update) => {
|
||||
// Find the object where the diff applies to
|
||||
const diffObject = update.widgetPath.reduce((targetLayout, index) => {
|
||||
if ("columnWidgets" in targetLayout) return targetLayout.columnWidgets[index];
|
||||
if ("rowWidgets" in targetLayout) return targetLayout.rowWidgets[index];
|
||||
if ("tableWidgets" in targetLayout) return targetLayout.tableWidgets[index];
|
||||
if ("layout" in targetLayout) return targetLayout.layout[index];
|
||||
const diffObject = update.widgetPath.reduce((targetLayout: UIItem | undefined, index: number): UIItem | undefined => {
|
||||
if (targetLayout && "columnWidgets" in targetLayout) return targetLayout.columnWidgets[index];
|
||||
if (targetLayout && "rowWidgets" in targetLayout) return targetLayout.rowWidgets[index];
|
||||
if (targetLayout && "tableWidgets" in targetLayout) return targetLayout.tableWidgets[index];
|
||||
if (targetLayout && "layout" in targetLayout) return targetLayout.layout[index];
|
||||
if (targetLayout instanceof Widget) {
|
||||
if (targetLayout.props.kind === "PopoverButton" && targetLayout.props instanceof PopoverButton && targetLayout.props.popoverLayout) {
|
||||
return targetLayout.props.popoverLayout[index];
|
||||
@@ -1487,10 +1511,21 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
|
||||
}
|
||||
// This is a path traversal so we can assume from the backend that it exists
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
if ("action" in targetLayout) return targetLayout.children![index];
|
||||
return targetLayout[index];
|
||||
if (targetLayout && "action" in targetLayout) return targetLayout.children![index];
|
||||
|
||||
return targetLayout?.[index];
|
||||
}, layout.layout as UIItem);
|
||||
|
||||
// Exit if we failed to produce a valid patch for the existing layout.
|
||||
// This means that the backend assumed an existing layout that doesn't exist in the frontend. This can happen, for
|
||||
// example, if a panel is destroyed in the frontend but was never cleared in the backend, so the next time the backend
|
||||
// tries to update the layout, it attempts to insert only the changes against the old layout that no longer exists.
|
||||
if (diffObject === undefined) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("In `patchWidgetLayout`, the `diffObject` is undefined. The layout has not been updated. See the source code comment above this error for hints.");
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a list with a length, then set the length to 0 to clear the list
|
||||
if ("length" in diffObject) {
|
||||
diffObject.length = 0;
|
||||
@@ -1613,9 +1648,9 @@ export class UpdateMenuBarLayout extends JsMessage {
|
||||
|
||||
export class UpdateNodeGraphControlBarLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate {}
|
||||
export class UpdatePropertiesPanelLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateSpreadsheetLayout extends WidgetDiffUpdate {}
|
||||
export class UpdateDataPanelLayout extends WidgetDiffUpdate {}
|
||||
|
||||
export class UpdateToolOptionsLayout extends WidgetDiffUpdate {}
|
||||
|
||||
@@ -1712,9 +1747,11 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||
UpdateNodeThumbnail,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdatePlatform,
|
||||
UpdatePropertyPanelSectionsLayout,
|
||||
UpdateSpreadsheetLayout,
|
||||
UpdateSpreadsheetState,
|
||||
UpdatePropertiesPanelLayout,
|
||||
UpdateDataPanelLayout,
|
||||
UpdateDataPanelState,
|
||||
UpdatePropertiesPanelState,
|
||||
UpdateLayersPanelState,
|
||||
UpdateToolOptionsLayout,
|
||||
UpdateToolShelfLayout,
|
||||
UpdateViewportHolePunch,
|
||||
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
TriggerOpenDocument,
|
||||
UpdateActiveDocument,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdateSpreadsheetState,
|
||||
defaultWidgetLayout,
|
||||
patchWidgetLayout,
|
||||
UpdateSpreadsheetLayout,
|
||||
UpdateDataPanelState,
|
||||
UpdatePropertiesPanelState,
|
||||
UpdateLayersPanelState,
|
||||
} from "@graphite/messages";
|
||||
import { downloadFile, downloadFileBlob, upload } from "@graphite/utility-functions/files";
|
||||
import { extractPixelData, rasterizeSVG } from "@graphite/utility-functions/rasterization";
|
||||
@@ -27,9 +26,9 @@ export function createPortfolioState(editor: Editor) {
|
||||
unsaved: false,
|
||||
documents: [] as FrontendDocumentDetails[],
|
||||
activeDocumentIndex: 0,
|
||||
spreadsheetOpen: false,
|
||||
spreadsheetNode: BigInt(0) as bigint | undefined,
|
||||
spreadsheetWidgets: defaultWidgetLayout(),
|
||||
dataPanelOpen: false,
|
||||
propertiesPanelOpen: true,
|
||||
layersPanelOpen: true,
|
||||
});
|
||||
|
||||
// Set up message subscriptions on creation
|
||||
@@ -107,16 +106,21 @@ 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) => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateDataPanelState, async (updateDataPanelState) => {
|
||||
update((state) => {
|
||||
state.spreadsheetOpen = updateSpreadsheetState.open;
|
||||
state.spreadsheetNode = updateSpreadsheetState.node;
|
||||
state.dataPanelOpen = updateDataPanelState.open;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetLayout, (updateSpreadsheetLayout) => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdatePropertiesPanelState, async (updatePropertiesPanelState) => {
|
||||
update((state) => {
|
||||
patchWidgetLayout(state.spreadsheetWidgets, updateSpreadsheetLayout);
|
||||
state.propertiesPanelOpen = updatePropertiesPanelState.open;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelState, async (updateLayersPanelState) => {
|
||||
update((state) => {
|
||||
state.layersPanelOpen = updateLayersPanelState.open;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,6 +17,10 @@ export function createSubscriptionRouter() {
|
||||
subscriptions[messageType.name] = callback;
|
||||
};
|
||||
|
||||
const unsubscribeJsMessage = <T extends JsMessage>(messageType: new () => T) => {
|
||||
delete subscriptions[messageType.name];
|
||||
};
|
||||
|
||||
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WebAssembly.Memory, handle: EditorHandle) => {
|
||||
// Find the message maker for the message type, which can either be a JS class constructor or a function that returns an instance of the JS class
|
||||
const messageMaker = messageMakers[messageType];
|
||||
@@ -68,6 +72,7 @@ export function createSubscriptionRouter() {
|
||||
|
||||
return {
|
||||
subscribeJsMessage,
|
||||
unsubscribeJsMessage,
|
||||
handleJsMessage,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user