mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-04 13:30:48 +00:00
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:
parent
5c7e04a725
commit
7bfe0ce55b
73 changed files with 532 additions and 798 deletions
|
@ -4,36 +4,35 @@ use graphite_editor::application::Editor;
|
|||
use graphite_editor::messages::frontend::utility_types::FrontendImageData;
|
||||
use graphite_editor::messages::prelude::*;
|
||||
|
||||
use axum::body::StreamBody;
|
||||
use axum::extract::Path;
|
||||
use axum::http;
|
||||
use axum::response::IntoResponse;
|
||||
// use axum::body::StreamBody;
|
||||
// use axum::extract::Path;
|
||||
// use axum::http;
|
||||
// use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use fern::colors::{Color, ColoredLevelConfig};
|
||||
use http::{Response, StatusCode};
|
||||
// use http::{Response, StatusCode};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
// use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
// use std::sync::Mutex;
|
||||
|
||||
static IMAGES: Mutex<Option<HashMap<String, FrontendImageData>>> = Mutex::new(None);
|
||||
thread_local! {
|
||||
static EDITOR: RefCell<Option<Editor>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
async fn respond_to(id: Path<String>) -> impl IntoResponse {
|
||||
let builder = Response::builder().header("Access-Control-Allow-Origin", "*").status(StatusCode::OK);
|
||||
// async fn respond_to(id: Path<String>) -> impl IntoResponse {
|
||||
// let builder = Response::builder().header("Access-Control-Allow-Origin", "*").status(StatusCode::OK);
|
||||
|
||||
let guard = IMAGES.lock().unwrap();
|
||||
let images = guard;
|
||||
let image = images.as_ref().unwrap().get(&id.0).unwrap();
|
||||
// let guard = IMAGES.lock().unwrap();
|
||||
// let images = guard;
|
||||
// let image = images.as_ref().unwrap().get(&id.0).unwrap();
|
||||
|
||||
println!("image: {:#?}", image.path);
|
||||
let result: Result<Vec<u8>, &str> = Ok((*image.image_data).clone());
|
||||
let stream = futures::stream::once(async move { result });
|
||||
builder.body(StreamBody::new(stream)).unwrap()
|
||||
}
|
||||
// println!("image: {:#?}", image.path);
|
||||
// let result: Result<Vec<u8>, &str> = Ok((*image.image_data).clone());
|
||||
// let stream = futures::stream::once(async move { result });
|
||||
// builder.body(StreamBody::new(stream)).unwrap()
|
||||
// }
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
|
@ -56,10 +55,10 @@ async fn main() {
|
|||
.apply()
|
||||
.unwrap();
|
||||
|
||||
*(IMAGES.lock().unwrap()) = Some(HashMap::new());
|
||||
// *(IMAGES.lock().unwrap()) = Some(HashMap::new());
|
||||
graphite_editor::application::set_uuid_seed(0);
|
||||
EDITOR.with(|editor| editor.borrow_mut().replace(Editor::new()));
|
||||
let app = Router::new().route("/", get(|| async { "Hello, World!" })).route("/image/:id", get(respond_to));
|
||||
let app = Router::new().route("/", get(|| async { "Hello, World!" }))/*.route("/image/:id", get(respond_to))*/;
|
||||
|
||||
// run it with hyper on localhost:3000
|
||||
tauri::async_runtime::spawn(async {
|
||||
|
@ -78,8 +77,7 @@ async fn main() {
|
|||
}
|
||||
#[tauri::command]
|
||||
fn set_random_seed(seed: f64) {
|
||||
let seed = seed as u64;
|
||||
graphite_editor::application::set_uuid_seed(seed);
|
||||
graphite_editor::application::set_uuid_seed(seed as u64);
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
@ -96,20 +94,11 @@ fn handle_message(message: String) -> String {
|
|||
fn send_frontend_message_to_js(message: FrontendMessage) -> FrontendMessage {
|
||||
// Special case for update image data to avoid serialization times.
|
||||
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
|
||||
let mut guard = IMAGES.lock().unwrap();
|
||||
let images = (*guard).as_mut().unwrap();
|
||||
let mut stub_data = Vec::with_capacity(image_data.len());
|
||||
for image in image_data {
|
||||
let path = image.path.clone();
|
||||
let mime = image.mime.clone();
|
||||
let transform = image.transform;
|
||||
images.insert(format!("{:?}_{}", image.path, document_id), image);
|
||||
stub_data.push(FrontendImageData {
|
||||
path,
|
||||
node_id: None,
|
||||
mime,
|
||||
mime: image.mime.clone(),
|
||||
image_data: Arc::new(Vec::new()),
|
||||
transform,
|
||||
});
|
||||
}
|
||||
FrontendMessage::UpdateImageData { document_id, image_data: stub_data }
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -12,7 +12,6 @@ use editor::consts::{FILE_SAVE_SUFFIX, GRAPHITE_DOCUMENT_VERSION};
|
|||
use editor::messages::input_mapper::utility_types::input_keyboard::ModifierKeys;
|
||||
use editor::messages::input_mapper::utility_types::input_mouse::{EditorMouseState, ScrollDelta, ViewportBounds};
|
||||
use editor::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use editor::messages::portfolio::document::utility_types::LayerId;
|
||||
use editor::messages::portfolio::utility_types::Platform;
|
||||
use editor::messages::prelude::*;
|
||||
use graph_craft::document::NodeId;
|
||||
|
@ -35,8 +34,6 @@ pub fn set_random_seed(seed: u64) {
|
|||
/// This avoids creating a json with a list millions of numbers long.
|
||||
#[wasm_bindgen(module = "/../src/wasm-communication/editor.ts")]
|
||||
extern "C" {
|
||||
fn updateImage(path: Vec<u64>, nodeId: Option<u64>, mime: String, imageData: &[u8], transform: js_sys::Float64Array, document_id: u64);
|
||||
fn fetchImage(path: Vec<u64>, nodeId: Option<u64>, mime: String, document_id: u64, identifier: String);
|
||||
//fn dispatchTauri(message: String) -> String;
|
||||
fn dispatchTauri(message: String);
|
||||
}
|
||||
|
@ -160,29 +157,28 @@ impl JsEditorHandle {
|
|||
// Sends a FrontendMessage to JavaScript
|
||||
fn send_frontend_message_to_js(&self, mut message: FrontendMessage) {
|
||||
// Special case for update image data to avoid serialization times.
|
||||
if let FrontendMessage::UpdateImageData { document_id, image_data } = message {
|
||||
for image in image_data {
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
{
|
||||
let transform = if let Some(transform_val) = image.transform {
|
||||
let transform = js_sys::Float64Array::new_with_length(6);
|
||||
transform.copy_from(&transform_val);
|
||||
transform
|
||||
} else {
|
||||
js_sys::Float64Array::default()
|
||||
};
|
||||
updateImage(image.path, image.node_id, image.mime, &image.image_data, transform, document_id);
|
||||
}
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let identifier = format!("http://localhost:3001/image/{:?}_{}", image.path, document_id);
|
||||
fetchImage(image.path.clone(), image.node_id, image.mime, document_id, identifier);
|
||||
}
|
||||
}
|
||||
if let FrontendMessage::UpdateImageData { document_id: _, image_data: _ } = message {
|
||||
// for image in image_data {
|
||||
// #[cfg(not(feature = "tauri"))]
|
||||
// {
|
||||
// let transform = if let Some(transform_val) = image.transform {
|
||||
// let transform = js_sys::Float64Array::new_with_length(6);
|
||||
// transform.copy_from(&transform_val);
|
||||
// transform
|
||||
// } else {
|
||||
// js_sys::Float64Array::default()
|
||||
// };
|
||||
// }
|
||||
// #[cfg(feature = "tauri")]
|
||||
// {
|
||||
// let identifier = format!("http://localhost:3001/image/{:?}_{}", image.path, document_id);
|
||||
// fetchImage(image.path.clone(), image.node_id, image.mime, document_id, identifier);
|
||||
// }
|
||||
// }
|
||||
return;
|
||||
}
|
||||
if let FrontendMessage::UpdateDocumentLayerTreeStructure { data_buffer } = message {
|
||||
message = FrontendMessage::UpdateDocumentLayerTreeStructureJs { data_buffer: data_buffer.into() };
|
||||
if let FrontendMessage::UpdateDocumentLayerStructure { data_buffer } = message {
|
||||
message = FrontendMessage::UpdateDocumentLayerStructureJs { data_buffer: data_buffer.into() };
|
||||
}
|
||||
|
||||
let message_type = message.to_discriminant().local_name();
|
||||
|
@ -298,7 +294,7 @@ impl JsEditorHandle {
|
|||
}
|
||||
|
||||
#[wasm_bindgen(js_name = selectDocument)]
|
||||
pub fn select_document(&self, document_id: u64) {
|
||||
pub fn select_document(&self, document_id: DocumentId) {
|
||||
let message = PortfolioMessage::SelectDocument { document_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
@ -331,7 +327,7 @@ impl JsEditorHandle {
|
|||
}
|
||||
|
||||
#[wasm_bindgen(js_name = openAutoSavedDocument)]
|
||||
pub fn open_auto_saved_document(&self, document_id: u64, document_name: String, document_is_saved: bool, document_serialized_content: String) {
|
||||
pub fn open_auto_saved_document(&self, document_id: DocumentId, document_name: String, document_is_saved: bool, document_serialized_content: String) {
|
||||
let message = PortfolioMessage::OpenDocumentFileWithId {
|
||||
document_id,
|
||||
document_name,
|
||||
|
@ -343,13 +339,13 @@ impl JsEditorHandle {
|
|||
}
|
||||
|
||||
#[wasm_bindgen(js_name = triggerAutoSave)]
|
||||
pub fn trigger_auto_save(&self, document_id: u64) {
|
||||
pub fn trigger_auto_save(&self, document_id: DocumentId) {
|
||||
let message = PortfolioMessage::AutoSaveDocument { document_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = closeDocumentWithConfirmation)]
|
||||
pub fn close_document_with_confirmation(&self, document_id: u64) {
|
||||
pub fn close_document_with_confirmation(&self, document_id: DocumentId) {
|
||||
let message = PortfolioMessage::CloseDocumentWithConfirmation { document_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
@ -532,8 +528,8 @@ impl JsEditorHandle {
|
|||
|
||||
/// Modify the layer selection based on the layer which is clicked while holding down the <kbd>Ctrl</kbd> and/or <kbd>Shift</kbd> modifier keys used for range selection behavior
|
||||
#[wasm_bindgen(js_name = selectLayer)]
|
||||
pub fn select_layer(&self, layer_path: Vec<LayerId>, ctrl: bool, shift: bool) {
|
||||
let message = DocumentMessage::SelectLayer { layer_path, ctrl, shift };
|
||||
pub fn select_layer(&self, id: NodeId, ctrl: bool, shift: bool) {
|
||||
let message = DocumentMessage::SelectLayer { id, ctrl, shift };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
@ -544,20 +540,23 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Move a layer to be next to the specified neighbor
|
||||
/// Move a layer to within a folder and placed down at the given index.
|
||||
/// If the folder is `None`, it is inserted into the document root.
|
||||
/// If the insert index is `None`, it is inserted at the end of the folder (equivalent to index infinity).
|
||||
#[wasm_bindgen(js_name = moveLayerInTree)]
|
||||
pub fn move_layer_in_tree(&self, folder_path: Vec<LayerId>, insert_index: isize) {
|
||||
let parent = folder_path.last().copied().map(LayerNodeIdentifier::new_unchecked).unwrap_or(LayerNodeIdentifier::ROOT);
|
||||
|
||||
let message = DocumentMessage::MoveSelectedLayersTo { parent, insert_index };
|
||||
pub fn move_layer_in_tree(&self, insert_parent_id: Option<NodeId>, insert_index: Option<usize>) {
|
||||
let parent = insert_parent_id.map(|id| LayerNodeIdentifier::new_unchecked(id)).unwrap_or(LayerNodeIdentifier::default());
|
||||
let message = DocumentMessage::MoveSelectedLayersTo {
|
||||
parent,
|
||||
insert_index: insert_index.map(|x| x as isize).unwrap_or(-1),
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Set the name for the layer
|
||||
#[wasm_bindgen(js_name = setLayerName)]
|
||||
pub fn set_layer_name(&self, layer_path: Vec<LayerId>, name: String) {
|
||||
let node_id = *layer_path.last().unwrap();
|
||||
let message = NodeGraphMessage::SetName { node_id, name };
|
||||
pub fn set_layer_name(&self, id: NodeId, name: String) {
|
||||
let message = NodeGraphMessage::SetName { node_id: id, name };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
@ -575,23 +574,9 @@ impl JsEditorHandle {
|
|||
self.dispatch(message);
|
||||
}
|
||||
|
||||
// /// Sends the blob URL generated by JS to the Image layer
|
||||
// #[wasm_bindgen(js_name = setImageBlobURL)]
|
||||
// pub fn set_image_blob_url(&self, document_id: u64, layer_path: Vec<LayerId>, node_id: Option<NodeId>, blob_url: String, width: f64, height: f64, _transform: Option<js_sys::Float64Array>) {
|
||||
// let resolution = (width, height);
|
||||
// let message = PortfolioMessage::SetImageBlobUrl {
|
||||
// document_id,
|
||||
// layer_path: layer_path.clone(),
|
||||
// node_id,
|
||||
// blob_url,
|
||||
// resolution,
|
||||
// };
|
||||
// self.dispatch(message);
|
||||
// }
|
||||
|
||||
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs
|
||||
#[wasm_bindgen(js_name = connectNodesByLink)]
|
||||
pub fn connect_nodes_by_link(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) {
|
||||
pub fn connect_nodes_by_link(&self, output_node: NodeId, output_node_connector_index: usize, input_node: NodeId, input_node_connector_index: usize) {
|
||||
let message = NodeGraphMessage::ConnectNodesByLink {
|
||||
output_node,
|
||||
output_node_connector_index,
|
||||
|
@ -603,14 +588,14 @@ impl JsEditorHandle {
|
|||
|
||||
/// Shifts the node and its children to stop nodes going on top of each other
|
||||
#[wasm_bindgen(js_name = shiftNode)]
|
||||
pub fn shift_node(&self, node_id: u64) {
|
||||
pub fn shift_node(&self, node_id: NodeId) {
|
||||
let message = NodeGraphMessage::ShiftNode { node_id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Notifies the backend that the user disconnected a node
|
||||
#[wasm_bindgen(js_name = disconnectNodes)]
|
||||
pub fn disconnect_nodes(&self, node_id: u64, input_index: usize) {
|
||||
pub fn disconnect_nodes(&self, node_id: NodeId, input_index: usize) {
|
||||
let message = NodeGraphMessage::DisconnectNodes { node_id, input_index };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
@ -629,7 +614,7 @@ impl JsEditorHandle {
|
|||
|
||||
/// Creates a new document node in the node graph
|
||||
#[wasm_bindgen(js_name = createNode)]
|
||||
pub fn create_node(&self, node_type: String, x: i32, y: i32) -> u64 {
|
||||
pub fn create_node(&self, node_type: String, x: i32, y: i32) -> NodeId {
|
||||
let id = generate_uuid();
|
||||
let message = NodeGraphMessage::CreateNode { node_id: Some(id), node_type, x, y };
|
||||
self.dispatch(message);
|
||||
|
@ -638,7 +623,7 @@ impl JsEditorHandle {
|
|||
|
||||
/// Notifies the backend that the user selected a node in the node graph
|
||||
#[wasm_bindgen(js_name = selectNodes)]
|
||||
pub fn select_nodes(&self, nodes: Option<Vec<u64>>) {
|
||||
pub fn select_nodes(&self, nodes: Option<Vec<NodeId>>) {
|
||||
let nodes = nodes.unwrap_or_default();
|
||||
let message = NodeGraphMessage::SelectedNodesSet { nodes };
|
||||
self.dispatch(message);
|
||||
|
@ -653,7 +638,7 @@ impl JsEditorHandle {
|
|||
|
||||
/// Notifies the backend that the user double clicked a node
|
||||
#[wasm_bindgen(js_name = doubleClickNode)]
|
||||
pub fn double_click_node(&self, node: u64) {
|
||||
pub fn double_click_node(&self, node: NodeId) {
|
||||
let message = NodeGraphMessage::DoubleClickNode { node };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
@ -686,15 +671,15 @@ impl JsEditorHandle {
|
|||
|
||||
/// Toggle visibility of a layer from the layer list
|
||||
#[wasm_bindgen(js_name = toggleLayerVisibility)]
|
||||
pub fn toggle_layer_visibility(&self, layer_path: Vec<LayerId>) {
|
||||
let message = NodeGraphMessage::ToggleHidden { node_id: *layer_path.last().unwrap() };
|
||||
pub fn toggle_layer_visibility(&self, id: NodeId) {
|
||||
let message = NodeGraphMessage::ToggleHidden { node_id: id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
/// Toggle expansions state of a layer from the layer list
|
||||
#[wasm_bindgen(js_name = toggleLayerExpansion)]
|
||||
pub fn toggle_layer_expansion(&self, layer_path: Vec<LayerId>) {
|
||||
let message = DocumentMessage::ToggleLayerExpansion { layer: *layer_path.last().unwrap() };
|
||||
pub fn toggle_layer_expansion(&self, id: NodeId) {
|
||||
let message = DocumentMessage::ToggleLayerExpansion { id };
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ use wasm_bindgen::prelude::*;
|
|||
pub static EDITOR_HAS_CRASHED: AtomicBool = AtomicBool::new(false);
|
||||
pub static LOGGER: WasmLog = WasmLog;
|
||||
thread_local! {
|
||||
// TODO: Remove the concept of multiple editor instances to simplify all of this
|
||||
pub static EDITOR_INSTANCES: RefCell<HashMap<u64, editor::application::Editor>> = RefCell::new(HashMap::new());
|
||||
pub static JS_EDITOR_HANDLES: RefCell<HashMap<u64, editor_api::JsEditorHandle>> = RefCell::new(HashMap::new());
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue