Files
DemonGatanjieu-Anomalous_Mo…/web/modules/material_drag.js
T

93 lines
5.3 KiB
JavaScript

import { app } from '../../../scripts/app.js';
import { translate as t } from './locales.js';
import { text } from './ui_dom.js';
import { materialNodeHeading } from './material_inspector.js';
import { anomalousAlert } from './ui_dialog.js';
let activeDrag = null;
export function materialDropNode(event, canvas, graph) {
const surface = canvas?.canvas;
if (!surface || !graph || canvas.graph !== graph) return null;
const rect = surface.getBoundingClientRect();
if (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom) return null;
// DOM text widgets overlay the canvas, so accept their drop surface too.
if (event.target !== surface && !event.target?.closest?.('.dom-widget')) return null;
let position;
if (canvas.convertEventToCanvasOffset) position = canvas.convertEventToCanvasOffset(event);
else if (canvas.adjustMouseEvent) { canvas.adjustMouseEvent(event); position = [event.canvasX, event.canvasY]; }
if (!position?.every(Number.isFinite)) return null;
const node = graph.getNodeOnPos?.(position[0], position[1]);
return node && graph.getNodeById(node.id) === node ? node : null;
}
/** Only this page's active drag can mutate a node; transfer data is never trusted. */
export function bindMaterialDrag(element, owner, { payload, accepts, drop, dropOnCanvas }) {
element.draggable = true;
element.addEventListener('dragstart', event => {
if (event.target !== element && event.target?.closest?.('button, input, textarea, select')) { event.preventDefault(); return; }
const data = payload();
const graph = app.graph;
const canvas = app.canvas;
if (!data || !event.dataTransfer || !canvas?.canvas) { event.preventDefault(); return; }
activeDrag?.();
event.stopPropagation();
event.dataTransfer.setData('application/x-anomalous-material', 'local-drag');
event.dataTransfer.effectAllowed = 'copy';
const defaultHint = data.dragHint || t('materialDropHint');
const hint = text(document.body, 'div', defaultHint, 'anomalous-material-drag-hint');
hint.setAttribute('role', 'status');
const cleanup = () => {
clearTimeout(reveal);
owner.modal?.classList.remove('anomalous-material-dragging');
hint.remove();
for (const [name, fn] of listeners) window.removeEventListener(name, fn, true);
if (activeDrag === cleanup) activeDrag = null;
};
const isOverCanvasSurface = event => {
const surface = canvas?.canvas;
if (!surface || app.graph !== graph || app.canvas !== canvas || canvas.graph !== graph) return false;
if (event.target !== surface && !event.target?.closest?.('.dom-widget')) return false;
const rect = surface.getBoundingClientRect();
return event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom;
};
const target = event => app.graph === graph && app.canvas === canvas ? materialDropNode(event, canvas, graph) : null;
const over = event => {
event.preventDefault(); event.stopImmediatePropagation();
if (dropOnCanvas && isOverCanvasSurface(event)) {
event.dataTransfer.dropEffect = 'copy';
hint.classList.add('is-target-valid');
hint.textContent = data.dragTargetHint || defaultHint;
} else {
const node = target(event);
const valid = !!node && accepts?.(node, data);
event.dataTransfer.dropEffect = valid ? 'copy' : 'none';
hint.classList.toggle('is-target-valid', valid);
hint.textContent = valid ? t('materialDropTarget', { name: materialNodeHeading(node) }) : defaultHint;
}
hint.style.left = `${Math.max(8, Math.min(event.clientX + 16, window.innerWidth - 250))}px`;
hint.style.top = `${Math.max(8, event.clientY - 48)}px`;
};
const finish = async event => {
event.preventDefault(); event.stopImmediatePropagation();
const overCanvas = isOverCanvasSurface(event);
const node = target(event);
cleanup();
if (dropOnCanvas && overCanvas) {
try { await dropOnCanvas(event, data, graph); }
catch (error) { await anomalousAlert(t(error.message) === error.message ? t('recipeOpenError') : t(error.message)); }
return;
}
if (!node || !accepts?.(node, data)) return;
try { await drop(node, data, graph); }
catch (error) { await anomalousAlert(t(error.message) === error.message ? t('materialApplyFailed') : t(error.message)); }
};
const escape = event => { if (event.key === 'Escape') cleanup(); };
const listeners = [['dragover', over], ['drop', finish], ['dragend', cleanup], ['keydown', escape], ['blur', cleanup]];
// Let the browser capture the card's drag image before revealing the canvas.
const reveal = setTimeout(() => owner.modal?.classList.add('anomalous-material-dragging'), 0);
activeDrag = cleanup;
for (const [name, fn] of listeners) window.addEventListener(name, fn, true);
});
}