Retire layer paths used throughout the code (#1531)

* Part 1

* Part 2

* Part 3

* Part 4

* Part 5

* Part 6

* Part 7

* Part 8
This commit is contained in:
Keavon Chambers 2023-12-21 19:32:46 -08:00 committed by GitHub
parent 5c7e04a725
commit 7bfe0ce55b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 532 additions and 798 deletions

View file

@ -4,7 +4,7 @@
import { beginDraggingElement } from "@graphite/io-managers/drag";
import { platformIsMac } from "@graphite/utility-functions/platform";
import type { Editor } from "@graphite/wasm-communication/editor";
import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerTreeStructureJs, UpdateLayersPanelOptionsLayout } from "@graphite/wasm-communication/messages";
import { defaultWidgetLayout, patchWidgetLayout, UpdateDocumentLayerDetails, UpdateDocumentLayerStructureJs, UpdateLayersPanelOptionsLayout } from "@graphite/wasm-communication/messages";
import type { LayerClassification, LayerPanelEntry } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
@ -27,8 +27,9 @@
type DraggingData = {
select?: () => void;
insertFolder: BigUint64Array;
insertIndex: number;
insertParentId: bigint | undefined;
insertDepth: number;
insertIndex: number | undefined;
highlightFolder: boolean;
markerHeight: number;
};
@ -42,7 +43,7 @@
// Interactive dragging
let draggable = true;
let draggingData: undefined | DraggingData = undefined;
let fakeHighlight: undefined | BigUint64Array[] = undefined;
let fakeHighlight: undefined | bigint = undefined;
let dragInPanel = false;
// Layouts
@ -54,24 +55,24 @@
layersPanelOptionsLayout = layersPanelOptionsLayout;
});
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerTreeStructureJs, (updateDocumentLayerTreeStructure) => {
rebuildLayerTree(updateDocumentLayerTreeStructure);
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerStructureJs, (updateDocumentLayerStructure) => {
rebuildLayerHierarchy(updateDocumentLayerStructure);
});
editor.subscriptions.subscribeJsMessage(UpdateDocumentLayerDetails, (updateDocumentLayerDetails) => {
const targetLayer = updateDocumentLayerDetails.data;
const targetPath = targetLayer.path;
const targetId = targetLayer.id;
updateLayerInTree(targetPath, targetLayer);
updateLayerInTree(targetId, targetLayer);
});
});
function toggleLayerVisibility(path: BigUint64Array) {
editor.instance.toggleLayerVisibility(path);
function toggleLayerVisibility(id: bigint) {
editor.instance.toggleLayerVisibility(id);
}
function handleExpandArrowClick(path: BigUint64Array) {
editor.instance.toggleLayerExpansion(path);
function handleExpandArrowClick(id: bigint) {
editor.instance.toggleLayerExpansion(id);
}
async function onEditLayerName(listing: LayerListingInfo) {
@ -97,7 +98,7 @@
layers = layers;
const name = (e.target instanceof HTMLInputElement && e.target.value) || "";
editor.instance.setLayerName(listing.entry.path, name);
editor.instance.setLayerName(listing.entry.id, name);
listing.entry.name = name;
}
@ -120,16 +121,16 @@
const [accel, oppositeAccel] = platformIsMac() ? [meta, ctrl] : [ctrl, meta];
// Select the layer only if the accel and/or shift keys are pressed
if (!oppositeAccel && !alt) selectLayer(accel, shift, listing);
if (!oppositeAccel && !alt) selectLayer(listing, accel, shift);
e.stopPropagation();
}
function selectLayer(accel: boolean, shift: boolean, listing: LayerListingInfo) {
function selectLayer(listing: LayerListingInfo, accel: boolean, shift: boolean) {
// Don't select while we are entering text to rename the layer
if (listing.editingName) return;
editor.instance.selectLayer(listing.entry.path, accel, shift);
editor.instance.selectLayer(listing.entry.id, accel, shift);
}
async function deselectAllLayers() {
@ -148,10 +149,11 @@
let closest = Infinity;
// Folder to insert into
let insertFolder = new BigUint64Array();
let insertParentId: bigint | undefined = undefined;
let insertDepth = 0;
// Insert index
let insertIndex = -1;
// Insert index (starts at the end, essentially infinity)
let insertIndex = undefined;
// Whether you are inserting into a folder and should show the folder outline
let highlightFolder = false;
@ -171,7 +173,8 @@
// Inserting above current row
if (distance > 0 && distance < closest) {
insertFolder = layer.path.slice(0, layer.path.length - 1);
insertParentId = layer.parentId;
insertDepth = layer.depth - 1;
insertIndex = folderIndex;
highlightFolder = false;
closest = distance;
@ -179,15 +182,24 @@
}
// Inserting below current row
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
insertFolder = isNestingLayer(layer.layerClassification) ? layer.path : layer.path.slice(0, layer.path.length - 1);
insertIndex = isNestingLayer(layer.layerClassification) ? 0 : folderIndex + 1;
highlightFolder = isNestingLayer(layer.layerClassification);
if (isNestingLayer(layer.layerClassification)) {
insertParentId = layer.id;
insertDepth = layer.depth;
insertIndex = 0;
highlightFolder = true;
} else {
insertParentId = layer.parentId;
insertDepth = layer.depth - 1;
insertIndex = folderIndex + 1;
highlightFolder = false;
}
closest = -distance;
markerHeight = index === treeChildren.length - 1 ? rect.bottom - INSERT_MARK_OFFSET : rect.bottom;
}
// Inserting with no nesting at the end of the panel
else if (closest === Infinity) {
if (layer.path.length === 1) insertIndex = folderIndex + 1;
if (layer.parentId === undefined) insertIndex = folderIndex + 1;
markerHeight = rect.bottom - INSERT_MARK_OFFSET;
}
@ -199,7 +211,8 @@
return {
select,
insertFolder,
insertParentId,
insertDepth,
insertIndex,
highlightFolder,
markerHeight,
@ -210,10 +223,10 @@
const layer = listing.entry;
dragInPanel = true;
if (!layer.selected) {
fakeHighlight = [layer.path];
fakeHighlight = layer.id;
}
const select = () => {
if (!layer.selected) selectLayer(false, false, listing);
if (!layer.selected) selectLayer(listing, false, false);
};
const target = (event.target instanceof HTMLElement && event.target) || undefined;
@ -240,58 +253,49 @@
async function drop() {
if (draggingData && dragInPanel) {
const { select, insertFolder, insertIndex } = draggingData;
const { select, insertParentId, insertIndex } = draggingData;
select?.();
editor.instance.moveLayerInTree(insertFolder, insertIndex);
editor.instance.moveLayerInTree(insertParentId, insertIndex);
}
draggingData = undefined;
fakeHighlight = undefined;
dragInPanel = false;
}
function rebuildLayerTree(updateDocumentLayerTreeStructure: UpdateDocumentLayerTreeStructureJs) {
function rebuildLayerHierarchy(updateDocumentLayerStructure: UpdateDocumentLayerStructureJs) {
const layerWithNameBeingEdited = layers.find((layer: LayerListingInfo) => layer.editingName);
const layerPathWithNameBeingEdited = layerWithNameBeingEdited?.entry.path;
const layerIdWithNameBeingEdited = layerPathWithNameBeingEdited?.slice(-1)[0];
const path: bigint[] = [];
const layerIdWithNameBeingEdited = layerWithNameBeingEdited?.entry.id;
// Clear the layer tree before rebuilding it
// Clear the layer hierarchy before rebuilding it
layers = [];
// Build the new layer tree
const recurse = (folder: UpdateDocumentLayerTreeStructureJs) => {
// Build the new layer hierarchy
const recurse = (folder: UpdateDocumentLayerStructureJs) => {
folder.children.forEach((item, index) => {
// TODO: fix toString
const layerId = BigInt(item.layerId.toString());
path.push(layerId);
const mapping = layerCache.get([path[path.length - 1]].toString());
const mapping = layerCache.get(String(item.layerId));
if (mapping) {
mapping.path = new BigUint64Array(path);
mapping.id = item.layerId;
layers.push({
folderIndex: index,
bottomLayer: index === folder.children.length - 1,
entry: mapping,
editingName: layerIdWithNameBeingEdited === layerId,
editingName: layerIdWithNameBeingEdited === item.layerId,
});
}
// Call self recursively if there are any children
if (item.children.length >= 1) recurse(item);
path.pop();
});
};
recurse(updateDocumentLayerTreeStructure);
recurse(updateDocumentLayerStructure);
layers = layers;
}
function updateLayerInTree(targetPath: BigUint64Array, targetLayer: LayerPanelEntry) {
const path = targetPath.toString();
layerCache.set(path, targetLayer);
function updateLayerInTree(targetId: bigint, targetLayer: LayerPanelEntry) {
layerCache.set(String(targetId), targetLayer);
const layer = layers.find((layer: LayerListingInfo) => layer.entry.path.toString() === path);
const layer = layers.find((layer: LayerListingInfo) => layer.entry.id === targetId);
if (layer) {
layer.entry = targetLayer;
layers = layers;
@ -305,15 +309,15 @@
</LayoutRow>
<LayoutRow class="list-area" scrollableY={true}>
<LayoutCol class="list" bind:this={list} on:click={() => deselectAllLayers()} on:dragover={(e) => draggable && updateInsertLine(e)} on:dragend={() => draggable && drop()}>
{#each layers as listing, index (String(listing.entry.path.slice(-1)))}
{#each layers as listing, index (String(listing.entry.id))}
<LayoutRow
class="layer"
classes={{
selected: fakeHighlight ? fakeHighlight.includes(listing.entry.path) : listing.entry.selected,
"insert-folder": (draggingData?.highlightFolder || false) && draggingData?.insertFolder === listing.entry.path,
selected: fakeHighlight !== undefined ? fakeHighlight === listing.entry.id : listing.entry.selected,
"insert-folder": (draggingData?.highlightFolder || false) && draggingData?.insertParentId === listing.entry.id,
}}
styles={{ "--layer-indent-levels": `${listing.entry.path.length - 1}` }}
data-layer={String(listing.entry.path)}
styles={{ "--layer-indent-levels": `${listing.entry.depth - 1}` }}
data-layer
data-index={index}
tooltip={listing.entry.tooltip}
{draggable}
@ -321,7 +325,7 @@
on:click={(e) => selectLayerWithModifiers(e, listing)}
>
{#if isNestingLayer(listing.entry.layerClassification)}
<button class="expand-arrow" class:expanded={listing.entry.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.path)} tabindex="0" />
<button class="expand-arrow" class:expanded={listing.entry.expanded} on:click|stopPropagation={() => handleExpandArrowClick(listing.entry.id)} tabindex="0" />
{#if listing.entry.layerClassification === "Artboard"}
<IconLabel icon="Artboard" class={"layer-type-icon"} />
{:else if listing.entry.layerClassification === "Folder"}
@ -347,7 +351,7 @@
</LayoutRow>
<IconButton
class={"visibility"}
action={(e) => (toggleLayerVisibility(listing.entry.path), e?.stopPropagation())}
action={(e) => (toggleLayerVisibility(listing.entry.id), e?.stopPropagation())}
size={24}
icon={(() => true)() ? "EyeVisible" : "EyeHidden"}
tooltip={(() => true)() ? "Visible" : "Hidden"}
@ -356,7 +360,7 @@
{/each}
</LayoutCol>
{#if draggingData && !draggingData.highlightFolder && dragInPanel}
<div class="insert-mark" style:left={`${4 + draggingData.insertFolder.length * 16}px`} style:top={`${draggingData.markerHeight}px`} />
<div class="insert-mark" style:left={`${4 + draggingData.insertDepth * 16}px`} style:top={`${draggingData.markerHeight}px`} />
{/if}
</LayoutRow>
</LayoutCol>
@ -387,7 +391,7 @@
}
}
// Layer tree
// Layer hierarchy
.list-area {
margin: 4px 0;
position: relative;

View file

@ -116,8 +116,8 @@
const from = connectorToNodeIndex(linkInProgressFromConnector);
const to = linkInProgressToConnector instanceof SVGSVGElement ? connectorToNodeIndex(linkInProgressToConnector) : undefined;
const linkStart = $nodeGraph.nodes.find((node) => node.id === from?.nodeId)?.isLayer;
const linkEnd = $nodeGraph.nodes.find((node) => node.id === to?.nodeId)?.isLayer && to?.index !== 0;
const linkStart = $nodeGraph.nodes.find((node) => node.id === from?.nodeId)?.isLayer || false;
const linkEnd = ($nodeGraph.nodes.find((node) => node.id === to?.nodeId)?.isLayer && to?.index !== 0) || false;
return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, linkStart, linkEnd);
}
return undefined;
@ -158,8 +158,8 @@
const { nodeInput, nodeOutput } = resolveLink(link);
if (!nodeInput || !nodeOutput) return [];
if (disconnecting?.linkIndex === index) return [];
const linkStart = $nodeGraph.nodes.find((node) => node.id === link.linkStart)?.isLayer;
const linkEnd = $nodeGraph.nodes.find((node) => node.id === link.linkEnd)?.isLayer && link.linkEndInputIndex !== 0n;
const linkStart = $nodeGraph.nodes.find((node) => node.id === link.linkStart)?.isLayer || false;
const linkEnd = ($nodeGraph.nodes.find((node) => node.id === link.linkEnd)?.isLayer && link.linkEndInputIndex !== 0n) || false;
return [createWirePath(nodeOutput, nodeInput.getBoundingClientRect(), linkStart, linkEnd)];
});
@ -673,7 +673,7 @@
<div class="layers-and-nodes" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`} bind:this={nodesContainer}>
<!-- Layers -->
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
{@const clipPathId = `${Math.random()}`.substring(2)}
{@const clipPathId = String(Math.random()).substring(2)}
{@const stackDatainput = node.exposedInputs[0]}
<div
class="layer"
@ -756,7 +756,7 @@
<!-- Nodes -->
{#each $nodeGraph.nodes.flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
{@const exposedInputsOutputs = [...node.exposedInputs, ...node.exposedOutputs]}
{@const clipPathId = `${Math.random()}`.substring(2)}
{@const clipPathId = String(Math.random()).substring(2)}
<div
class="node"
class:selected={selected.includes(node.id)}

View file

@ -15,7 +15,7 @@
let inputElement: HTMLInputElement | undefined;
let id = `${Math.random()}`.substring(2);
let id = String(Math.random()).substring(2);
$: displayIcon = (!checked && icon === "Checkmark" ? "Empty12px" : icon) as IconName;

View file

@ -29,7 +29,7 @@
export let hideContextMenu = false;
let inputOrTextarea: HTMLInputElement | HTMLTextAreaElement | undefined;
let id = `${Math.random()}`.substring(2);
let id = String(Math.random()).substring(2);
let macKeyboardLayout = platformIsMac();
$: inputValue = value;

View file

@ -176,7 +176,7 @@
function onTextFocused() {
if (value === undefined) text = "";
else if (unitIsHiddenWhenEditing) text = `${value}`;
else if (unitIsHiddenWhenEditing) text = String(value);
else text = `${value}${unPluralize(unit, value)}`;
editing = true;

View file

@ -14,7 +14,7 @@ export function createLocalizationManager(editor: Editor) {
const dateString = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
const timeString = `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
const timezoneNameString = timezoneName?.value;
return { timestamp: `${dateString} ${timeString} ${timezoneNameString}`, year: `${date.getFullYear()}` };
return { timestamp: `${dateString} ${timeString} ${timezoneNameString}`, year: String(date.getFullYear()) };
}
// Subscribe to process backend event

View file

@ -33,7 +33,7 @@ export function panicProxy<T extends object>(module: T): T {
result = targetValue.apply(this, args);
} catch (err) {
// Suppress `unreachable` WebAssembly.RuntimeError exceptions
if (!`${err}`.startsWith("RuntimeError: unreachable")) throw err;
if (!String(err).startsWith("RuntimeError: unreachable")) throw err;
}
return result;
};

View file

@ -10,35 +10,6 @@ export type Editor = Readonly<ReturnType<typeof createEditor>>;
// `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()`
let wasmImport: WebAssembly.Memory | undefined;
export async function updateImage(path: BigUint64Array, nodeId: bigint, mime: string, imageData: Uint8Array, _transform: Float64Array, _documentId: bigint) {
const blob = new Blob([imageData], { type: mime });
const blobURL = URL.createObjectURL(blob);
// Pre-decode the image so it is ready to be drawn instantly once it's placed into the viewport SVG
const image = new Image();
image.src = blobURL;
await image.decode();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// (window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, transform);
}
export async function fetchImage(_path: BigUint64Array, _nodeId: bigint, _mime: string, _documentId: bigint, url: string) {
const data = await fetch(url);
const blob = await data.blob();
const blobURL = URL.createObjectURL(blob);
// Pre-decode the image so it is ready to be drawn instantly once it's placed into the viewport SVG
const image = new Image();
image.src = blobURL;
await image.decode();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
// (window as any).editorInstance?.setImageBlobURL(documentId, path, nodeId, blobURL, image.naturalWidth, image.naturalHeight, undefined);
}
const tauri = "__TAURI_METADATA__" in window && import("@tauri-apps/api");
export async function dispatchTauri(message: unknown) {
if (!tauri) return;

View file

@ -560,10 +560,10 @@ export class TriggerSavePreferences extends JsMessage {
export class DocumentChanged extends JsMessage {}
export class UpdateDocumentLayerTreeStructureJs extends JsMessage {
export class UpdateDocumentLayerStructureJs extends JsMessage {
constructor(
readonly layerId: bigint,
readonly children: UpdateDocumentLayerTreeStructureJs[],
readonly children: UpdateDocumentLayerStructureJs[],
) {
super();
}
@ -574,7 +574,7 @@ type DataBuffer = {
length: bigint;
};
export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerTreeStructureJs {
export function newUpdateDocumentLayerStructure(input: { dataBuffer: DataBuffer }, wasm: WasmRawInstance): UpdateDocumentLayerStructureJs {
const pointerNum = Number(input.dataBuffer.pointer);
const lengthNum = Number(input.dataBuffer.length);
@ -591,7 +591,7 @@ export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuf
const layerIdsSection = new DataView(wasmMemoryBuffer, pointerNum + 8 + structureSectionLength * 8);
let layersEncountered = 0;
let currentFolder = new UpdateDocumentLayerTreeStructureJs(BigInt(-1), []);
let currentFolder = new UpdateDocumentLayerStructureJs(BigInt(-1), []);
const currentFolderStack = [currentFolder];
for (let i = 0; i < structureSectionLength; i += 1) {
@ -606,7 +606,7 @@ export function newUpdateDocumentLayerTreeStructure(input: { dataBuffer: DataBuf
const layerId = layerIdsSection.getBigUint64(layersEncountered * 8, true);
layersEncountered += 1;
const childLayer = new UpdateDocumentLayerTreeStructureJs(layerId, []);
const childLayer = new UpdateDocumentLayerStructureJs(layerId, []);
currentFolder.children.push(childLayer);
}
@ -650,8 +650,8 @@ export class DisplayEditableTextboxTransform extends JsMessage {
export class UpdateImageData extends JsMessage {
readonly documentId!: bigint;
@Type(() => RenderedImageData)
readonly imageData!: RenderedImageData[];
@Type(() => FrontendImageData)
readonly imageData!: FrontendImageData[];
}
export class DisplayRemoveEditableTextbox extends JsMessage {}
@ -669,8 +669,12 @@ export class LayerPanelEntry {
layerClassification!: LayerClassification;
@Transform(({ value }: { value: bigint[] }) => new BigUint64Array(value))
path!: BigUint64Array;
parentId!: bigint | undefined;
id!: bigint;
@Transform(({ value }: { value: bigint }) => Number(value))
depth!: number;
expanded!: boolean;
@ -681,16 +685,10 @@ export class LayerPanelEntry {
export type LayerClassification = "Folder" | "Artboard" | "Layer";
export class RenderedImageData {
readonly path!: BigUint64Array;
readonly nodeId!: bigint;
export class FrontendImageData {
readonly mime!: string;
readonly imageData!: Uint8Array;
readonly transform!: Float64Array;
}
export class DisplayDialogDismiss extends JsMessage {}
@ -1381,7 +1379,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateDocumentArtwork,
UpdateDocumentBarLayout,
UpdateDocumentLayerDetails,
UpdateDocumentLayerTreeStructureJs: newUpdateDocumentLayerTreeStructure,
UpdateDocumentLayerStructureJs: newUpdateDocumentLayerStructure,
UpdateDocumentModeLayout,
UpdateDocumentRulers,
UpdateDocumentScrollbars,