mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 08:08:13 +08:00
Fix Eyedropper tool and make Svelte's bind:this more robust
This commit is contained in:
@@ -28,9 +28,9 @@
|
||||
import type { Editor } from "@/wasm-communication/editor";
|
||||
import type { DocumentState } from "@/state-providers/document";
|
||||
|
||||
let rulerHorizontal: CanvasRuler;
|
||||
let rulerVertical: CanvasRuler;
|
||||
let canvasContainer: HTMLDivElement;
|
||||
let rulerHorizontal: CanvasRuler | undefined;
|
||||
let rulerVertical: CanvasRuler | undefined;
|
||||
let canvasContainer: HTMLDivElement | undefined;
|
||||
|
||||
const editor = getContext<Editor>("editor");
|
||||
const document = getContext<DocumentState>("document");
|
||||
@@ -125,8 +125,8 @@
|
||||
await tick();
|
||||
|
||||
if (textInput) {
|
||||
const foreignObject = canvasContainer.getElementsByTagName("foreignObject")[0] as SVGForeignObjectElement;
|
||||
if (foreignObject.children.length > 0) return;
|
||||
const foreignObject = canvasContainer?.getElementsByTagName("foreignObject")[0] as SVGForeignObjectElement | undefined;
|
||||
if (!foreignObject || foreignObject.children.length > 0) return;
|
||||
|
||||
const addedInput = foreignObject.appendChild(textInput);
|
||||
window.dispatchEvent(new CustomEvent("modifyinputfield", { detail: addedInput }));
|
||||
@@ -282,6 +282,8 @@
|
||||
|
||||
// Resize elements to render the new viewport size
|
||||
export function viewportResize() {
|
||||
if (!canvasContainer) return;
|
||||
|
||||
// Resize the canvas
|
||||
canvasSvgWidth = Math.ceil(parseFloat(getComputedStyle(canvasContainer).width));
|
||||
canvasSvgHeight = Math.ceil(parseFloat(getComputedStyle(canvasContainer).height));
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
entry: LayerPanelEntry;
|
||||
};
|
||||
|
||||
let list: LayoutCol;
|
||||
let list: LayoutCol | undefined;
|
||||
|
||||
const RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT = 20;
|
||||
const LAYER_INDENT = 16;
|
||||
@@ -105,7 +105,7 @@
|
||||
|
||||
await tick();
|
||||
|
||||
const textInput = (list?.div().querySelector("[data-text-input]:not([disabled])") || undefined) as HTMLInputElement | undefined;
|
||||
const textInput = (list?.div()?.querySelector("[data-text-input]:not([disabled])") || undefined) as HTMLInputElement | undefined;
|
||||
textInput?.select();
|
||||
}
|
||||
|
||||
@@ -153,8 +153,8 @@
|
||||
}
|
||||
|
||||
function calculateDragIndex(tree: LayoutCol, clientY: number, select?: () => void): DraggingData {
|
||||
const treeChildren = tree.div().children;
|
||||
const treeOffset = tree.div().getBoundingClientRect().top;
|
||||
const treeChildren = tree.div()?.children;
|
||||
const treeOffset = tree.div()?.getBoundingClientRect().top;
|
||||
|
||||
// Closest distance to the middle of the row along the Y axis
|
||||
let closest = Infinity;
|
||||
@@ -171,45 +171,47 @@
|
||||
let markerHeight = 0;
|
||||
let previousHeight = undefined as undefined | number;
|
||||
|
||||
Array.from(treeChildren).forEach((treeChild, index) => {
|
||||
const layerComponents = treeChild.getElementsByClassName("layer");
|
||||
if (layerComponents.length !== 1) return;
|
||||
const child = layerComponents[0];
|
||||
if (treeChildren !== undefined && treeOffset !== undefined) {
|
||||
Array.from(treeChildren).forEach((treeChild, index) => {
|
||||
const layerComponents = treeChild.getElementsByClassName("layer");
|
||||
if (layerComponents.length !== 1) return;
|
||||
const child = layerComponents[0];
|
||||
|
||||
const indexAttribute = child.getAttribute("data-index");
|
||||
if (!indexAttribute) return;
|
||||
const { folderIndex, entry: layer } = layers[parseInt(indexAttribute, 10)];
|
||||
const indexAttribute = child.getAttribute("data-index");
|
||||
if (!indexAttribute) return;
|
||||
const { folderIndex, entry: layer } = layers[parseInt(indexAttribute, 10)];
|
||||
|
||||
const rect = child.getBoundingClientRect();
|
||||
const position = rect.top + rect.height / 2;
|
||||
const distance = position - clientY;
|
||||
const rect = child.getBoundingClientRect();
|
||||
const position = rect.top + rect.height / 2;
|
||||
const distance = position - clientY;
|
||||
|
||||
// Inserting above current row
|
||||
if (distance > 0 && distance < closest) {
|
||||
insertFolder = layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = folderIndex;
|
||||
highlightFolder = false;
|
||||
closest = distance;
|
||||
markerHeight = previousHeight || treeOffset + INSERT_MARK_OFFSET;
|
||||
}
|
||||
// Inserting below current row
|
||||
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
|
||||
insertFolder = layer.layerType === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = layer.layerType === "Folder" ? 0 : folderIndex + 1;
|
||||
highlightFolder = layer.layerType === "Folder";
|
||||
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;
|
||||
// Inserting above current row
|
||||
if (distance > 0 && distance < closest) {
|
||||
insertFolder = layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = folderIndex;
|
||||
highlightFolder = false;
|
||||
closest = distance;
|
||||
markerHeight = previousHeight || treeOffset + INSERT_MARK_OFFSET;
|
||||
}
|
||||
// Inserting below current row
|
||||
else if (distance > -closest && distance > -RANGE_TO_INSERT_WITHIN_BOTTOM_FOLDER_NOT_ROOT && distance < 0) {
|
||||
insertFolder = layer.layerType === "Folder" ? layer.path : layer.path.slice(0, layer.path.length - 1);
|
||||
insertIndex = layer.layerType === "Folder" ? 0 : folderIndex + 1;
|
||||
highlightFolder = layer.layerType === "Folder";
|
||||
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;
|
||||
|
||||
markerHeight = rect.bottom - INSERT_MARK_OFFSET;
|
||||
}
|
||||
previousHeight = rect.bottom;
|
||||
});
|
||||
markerHeight = rect.bottom - INSERT_MARK_OFFSET;
|
||||
}
|
||||
previousHeight = rect.bottom;
|
||||
});
|
||||
}
|
||||
|
||||
markerHeight -= treeOffset;
|
||||
markerHeight -= (treeOffset || 0);
|
||||
|
||||
return {
|
||||
select,
|
||||
|
||||
@@ -22,9 +22,10 @@
|
||||
const editor = getContext<Editor>("editor");
|
||||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
let graph: LayoutRow;
|
||||
let nodesContainer: HTMLDivElement;
|
||||
let nodeSearchInput: TextInput;
|
||||
let graph: LayoutRow | undefined;
|
||||
let nodesContainer: HTMLDivElement | undefined;
|
||||
let nodeSearchInput: TextInput | undefined;
|
||||
|
||||
let transform = { scale: 1, x: 0, y: 0 };
|
||||
let panning = false;
|
||||
let selected: bigint[] = [];
|
||||
@@ -75,7 +76,7 @@
|
||||
}
|
||||
|
||||
function createLinkPathInProgress(linkInProgressFromConnector?: HTMLDivElement, linkInProgressToConnector?: HTMLDivElement | DOMRect): [string, string] | undefined {
|
||||
if (linkInProgressFromConnector && linkInProgressToConnector) {
|
||||
if (linkInProgressFromConnector && linkInProgressToConnector && nodesContainer) {
|
||||
return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, false, false);
|
||||
}
|
||||
return undefined;
|
||||
@@ -107,9 +108,12 @@
|
||||
async function refreshLinks(): Promise<void> {
|
||||
await tick();
|
||||
|
||||
if (!nodesContainer) return;
|
||||
const theNodesContainer = nodesContainer;
|
||||
|
||||
const links = $nodeGraph.links;
|
||||
nodeLinkPaths = links.flatMap((link, index) => {
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, nodesContainer);
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, theNodesContainer);
|
||||
if (!nodePrimaryInput || !nodePrimaryOutput) return [];
|
||||
if (disconnecting?.linkIndex === index) return [];
|
||||
|
||||
@@ -129,6 +133,8 @@
|
||||
}
|
||||
|
||||
function buildWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
||||
if (!nodesContainer) return [];
|
||||
|
||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
||||
|
||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
||||
@@ -193,7 +199,9 @@
|
||||
let zoomFactor = 1 + Math.abs(scrollY) * WHEEL_RATE;
|
||||
if (scrollY > 0) zoomFactor = 1 / zoomFactor;
|
||||
|
||||
const { x, y, width, height } = graph.div().getBoundingClientRect();
|
||||
const bounds = graph?.div()?.getBoundingClientRect();
|
||||
if (!bounds) return;
|
||||
const { x, y, width, height } = bounds;
|
||||
|
||||
transform.scale *= zoomFactor;
|
||||
|
||||
@@ -238,14 +246,15 @@
|
||||
|
||||
// Create the add node popup on right click, then exit
|
||||
if (rmb) {
|
||||
const graphBounds = graph.div().getBoundingClientRect();
|
||||
const graphBounds = graph?.div()?.getBoundingClientRect();
|
||||
if (!graphBounds) return;
|
||||
nodeListLocation = {
|
||||
x: Math.round(((e.clientX - graphBounds.x) / transform.scale - transform.x) / GRID_SIZE),
|
||||
y: Math.round(((e.clientY - graphBounds.y) / transform.scale - transform.y) / GRID_SIZE),
|
||||
};
|
||||
|
||||
// Find actual relevant child and focus it (setTimeout is required to actually focus the input element)
|
||||
setTimeout(() => nodeSearchInput.focus(), 0);
|
||||
setTimeout(() => nodeSearchInput?.focus(), 0);
|
||||
|
||||
document.addEventListener("keydown", keydown);
|
||||
return;
|
||||
@@ -277,9 +286,9 @@
|
||||
const inputIndexInt = BigInt(inputIndex);
|
||||
const links = $nodeGraph.links;
|
||||
const linkIndex = links.findIndex((value) => value.linkEnd === nodeIdInt && value.linkEndInputIndex === inputIndexInt);
|
||||
const nodeOutputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
|
||||
const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
|
||||
linkInProgressFromConnector = nodeOutputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
const nodeInputConnectors = nodesContainer.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String(links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
|
||||
linkInProgressToConnector = nodeInputConnectors?.[Number(links[linkIndex].linkEndInputIndex)] as HTMLDivElement | undefined;
|
||||
disconnecting = { nodeId: nodeIdInt, inputIndex, linkIndex };
|
||||
refreshLinks();
|
||||
@@ -400,24 +409,26 @@
|
||||
// Check if this node should be inserted between two other nodes
|
||||
if (selected.length === 1) {
|
||||
const selectedNodeId = selected[0];
|
||||
const selectedNode = nodesContainer.querySelector(`[data-node="${String(selectedNodeId)}"]`);
|
||||
const selectedNode = nodesContainer?.querySelector(`[data-node="${String(selectedNodeId)}"]`) || undefined;
|
||||
|
||||
// Check that neither the input or output of the selected node are already connected.
|
||||
const notConnected = $nodeGraph.links.findIndex((link) => link.linkStart === selectedNodeId || (link.linkEnd === selectedNodeId && link.linkEndInputIndex === BigInt(0))) === -1;
|
||||
const input = selectedNode?.querySelector(`[data-port="input"]`);
|
||||
const output = selectedNode?.querySelector(`[data-port="output"]`);
|
||||
const input = selectedNode?.querySelector(`[data-port="input"]`) || undefined;
|
||||
const output = selectedNode?.querySelector(`[data-port="output"]`) || undefined;
|
||||
|
||||
// TODO: Make sure inputs are correctly typed
|
||||
if (selectedNode && notConnected && input && output) {
|
||||
if (selectedNode && notConnected && input && output && nodesContainer) {
|
||||
const theNodesContainer = nodesContainer;
|
||||
|
||||
// Find the link that the node has been dragged on top of
|
||||
const link = $nodeGraph.links.find((link): boolean => {
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, nodesContainer);
|
||||
const { nodePrimaryInput, nodePrimaryOutput } = resolveLink(link, theNodesContainer);
|
||||
if (!nodePrimaryInput || !nodePrimaryOutput) return false;
|
||||
|
||||
const wireCurveLocations = buildWirePathLocations(nodePrimaryOutput.getBoundingClientRect(), nodePrimaryInput.getBoundingClientRect(), false, false);
|
||||
|
||||
const selectedNodeBounds = selectedNode.getBoundingClientRect();
|
||||
const containerBoundsBounds = nodesContainer.getBoundingClientRect();
|
||||
const containerBoundsBounds = theNodesContainer.getBoundingClientRect();
|
||||
|
||||
return editor.instance.rectangleIntersects(
|
||||
new Float64Array(wireCurveLocations.map((loc) => loc.x)),
|
||||
@@ -428,6 +439,7 @@
|
||||
selectedNodeBounds.right - containerBoundsBounds.x
|
||||
);
|
||||
});
|
||||
|
||||
// If the node has been dragged on top of the link then connect it into the middle.
|
||||
if (link) {
|
||||
editor.instance.connectNodesByLink(link.linkStart, 0, selectedNodeId, 0);
|
||||
|
||||
Reference in New Issue
Block a user