Expose current dragging and receiving nodes

This commit is contained in:
thepaperpilot 2023-04-23 00:02:53 -05:00
parent 8e8879f586
commit a8815f9eb5
3 changed files with 82 additions and 58 deletions

View file

@ -17,9 +17,9 @@
@touchmove="drag" @touchmove="drag"
@mousedown="(e: MouseEvent) => mouseDown(e)" @mousedown="(e: MouseEvent) => mouseDown(e)"
@touchstart="(e: TouchEvent) => mouseDown(e)" @touchstart="(e: TouchEvent) => mouseDown(e)"
@mouseup="() => endDragging(dragging)" @mouseup="() => endDragging(unref(draggingNode))"
@touchend.passive="() => endDragging(dragging)" @touchend.passive="() => endDragging(unref(draggingNode))"
@mouseleave="() => endDragging(dragging)" @mouseleave="() => endDragging(unref(draggingNode))"
> >
<svg class="stage" width="100%" height="100%"> <svg class="stage" width="100%" height="100%">
<g class="g1"> <g class="g1">
@ -36,10 +36,10 @@
<BoardNodeVue <BoardNodeVue
:node="node" :node="node"
:nodeType="types[node.type]" :nodeType="types[node.type]"
:dragging="draggingNode" :dragging="unref(draggingNode)"
:dragged="draggingNode === node ? dragged : undefined" :dragged="unref(draggingNode) === node ? dragged : undefined"
:hasDragged="hasDragged" :hasDragged="hasDragged"
:receivingNode="receivingNode?.id === node.id" :receivingNode="unref(receivingNode)?.id === node.id"
:selectedNode="unref(selectedNode)" :selectedNode="unref(selectedNode)"
:selectedAction="unref(selectedAction)" :selectedAction="unref(selectedAction)"
@mouseDown="mouseDown" @mouseDown="mouseDown"
@ -65,7 +65,7 @@ import { getNodeProperty } from "features/boards/board";
import type { StyleValue } from "features/feature"; import type { StyleValue } from "features/feature";
import { Visibility, isVisible } from "features/feature"; import { Visibility, isVisible } from "features/feature";
import type { ProcessedComputable } from "util/computed"; import type { ProcessedComputable } from "util/computed";
import { Ref, computed, ref, toRefs, unref } from "vue"; import { Ref, computed, ref, toRefs, unref, watchEffect } from "vue";
import BoardLinkVue from "./BoardLink.vue"; import BoardLinkVue from "./BoardLink.vue";
import BoardNodeVue from "./BoardNode.vue"; import BoardNodeVue from "./BoardNode.vue";
@ -81,32 +81,31 @@ const _props = defineProps<{
links: Ref<BoardNodeLink[] | null>; links: Ref<BoardNodeLink[] | null>;
selectedAction: Ref<GenericBoardNodeAction | null>; selectedAction: Ref<GenericBoardNodeAction | null>;
selectedNode: Ref<BoardNode | null>; selectedNode: Ref<BoardNode | null>;
draggingNode: Ref<BoardNode | null>;
receivingNode: Ref<BoardNode | null>;
mousePosition: Ref<{ x: number; y: number } | null>; mousePosition: Ref<{ x: number; y: number } | null>;
setReceivingNode: (node: BoardNode | null) => void;
setDraggingNode: (node: BoardNode | null) => void;
}>(); }>();
const props = toRefs(_props); const props = toRefs(_props);
const lastMousePosition = ref({ x: 0, y: 0 }); const lastMousePosition = ref({ x: 0, y: 0 });
const dragged = ref({ x: 0, y: 0 }); const dragged = ref({ x: 0, y: 0 });
const dragging = ref<number | null>(null);
const hasDragged = ref(false); const hasDragged = ref(false);
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
const stage = ref<any>(null); const stage = ref<any>(null);
const draggingNode = computed(() =>
dragging.value == null ? undefined : props.nodes.value.find(node => node.id === dragging.value)
);
const sortedNodes = computed(() => { const sortedNodes = computed(() => {
const nodes = props.nodes.value.slice(); const nodes = props.nodes.value.slice();
if (draggingNode.value) { if (props.draggingNode.value) {
const node = nodes.splice(nodes.indexOf(draggingNode.value), 1)[0]; const node = nodes.splice(nodes.indexOf(props.draggingNode.value), 1)[0];
nodes.push(node); nodes.push(node);
} }
return nodes; return nodes;
}); });
const receivingNode = computed(() => { watchEffect(() => {
const node = draggingNode.value; const node = props.draggingNode.value;
if (node == null) { if (node == null) {
return null; return null;
} }
@ -116,26 +115,30 @@ const receivingNode = computed(() => {
y: node.position.y + dragged.value.y y: node.position.y + dragged.value.y
}; };
let smallestDistance = Number.MAX_VALUE; let smallestDistance = Number.MAX_VALUE;
return props.nodes.value.reduce((smallest: BoardNode | null, curr: BoardNode) => {
if (curr.id === node.id) {
return smallest;
}
const nodeType = props.types.value[curr.type];
const canAccept = getNodeProperty(nodeType.canAccept, curr);
if (!canAccept) {
return smallest;
}
const distanceSquared = props.setReceivingNode.value(
Math.pow(position.x - curr.position.x, 2) + Math.pow(position.y - curr.position.y, 2); props.nodes.value.reduce((smallest: BoardNode | null, curr: BoardNode) => {
let size = getNodeProperty(nodeType.size, curr); if (curr.id === node.id) {
if (distanceSquared > smallestDistance || distanceSquared > size * size) { return smallest;
return smallest; }
} const nodeType = props.types.value[curr.type];
const canAccept = getNodeProperty(nodeType.canAccept, curr);
if (!canAccept) {
return smallest;
}
smallestDistance = distanceSquared; const distanceSquared =
return curr; Math.pow(position.x - curr.position.x, 2) +
}, null); Math.pow(position.y - curr.position.y, 2);
let size = getNodeProperty(nodeType.size, curr);
if (distanceSquared > smallestDistance || distanceSquared > size * size) {
return smallest;
}
smallestDistance = distanceSquared;
return curr;
}, null)
);
}); });
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -144,8 +147,8 @@ function onInit(panzoomInstance: any) {
panzoomInstance.moveTo(stage.value.$el.clientWidth / 2, stage.value.$el.clientHeight / 2); panzoomInstance.moveTo(stage.value.$el.clientWidth / 2, stage.value.$el.clientHeight / 2);
} }
function mouseDown(e: MouseEvent | TouchEvent, nodeID: number | null = null, draggable = false) { function mouseDown(e: MouseEvent | TouchEvent, node: BoardNode | null = null, draggable = false) {
if (dragging.value == null) { if (props.draggingNode.value == null) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -169,10 +172,10 @@ function mouseDown(e: MouseEvent | TouchEvent, nodeID: number | null = null, dra
hasDragged.value = false; hasDragged.value = false;
if (draggable) { if (draggable) {
dragging.value = nodeID; props.setDraggingNode.value(node);
} }
} }
if (nodeID != null) { if (node != null) {
props.state.value.selectedNode = null; props.state.value.selectedNode = null;
props.state.value.selectedAction = null; props.state.value.selectedAction = null;
} }
@ -187,7 +190,7 @@ function drag(e: MouseEvent | TouchEvent) {
clientX = e.touches[0].clientX; clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY; clientY = e.touches[0].clientY;
} else { } else {
endDragging(dragging.value); endDragging(props.draggingNode.value);
props.mousePosition.value = null; props.mousePosition.value = null;
return; return;
} }
@ -214,28 +217,28 @@ function drag(e: MouseEvent | TouchEvent) {
hasDragged.value = true; hasDragged.value = true;
} }
if (dragging.value != null) { if (props.draggingNode.value != null) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
} }
} }
function endDragging(nodeID: number | null) { function endDragging(node: BoardNode | null) {
if (dragging.value != null && dragging.value === nodeID && draggingNode.value != null) { if (props.draggingNode.value != null && props.draggingNode.value === node) {
draggingNode.value.position.x += Math.round(dragged.value.x / 25) * 25; props.draggingNode.value.position.x += Math.round(dragged.value.x / 25) * 25;
draggingNode.value.position.y += Math.round(dragged.value.y / 25) * 25; props.draggingNode.value.position.y += Math.round(dragged.value.y / 25) * 25;
const nodes = props.nodes.value; const nodes = props.nodes.value;
nodes.push(nodes.splice(nodes.indexOf(draggingNode.value), 1)[0]); nodes.push(nodes.splice(nodes.indexOf(props.draggingNode.value), 1)[0]);
if (receivingNode.value) { if (props.receivingNode.value) {
props.types.value[receivingNode.value.type].onDrop?.( props.types.value[props.receivingNode.value.type].onDrop?.(
receivingNode.value, props.receivingNode.value,
draggingNode.value props.draggingNode.value
); );
} }
dragging.value = null; props.setDraggingNode.value(null);
} else if (!hasDragged.value) { } else if (!hasDragged.value) {
props.state.value.selectedNode = null; props.state.value.selectedNode = null;
props.state.value.selectedAction = null; props.state.value.selectedAction = null;

View file

@ -156,7 +156,7 @@ const sqrtTwo = Math.sqrt(2);
const _props = defineProps<{ const _props = defineProps<{
node: BoardNode; node: BoardNode;
nodeType: GenericNodeType; nodeType: GenericNodeType;
dragging?: BoardNode; dragging: BoardNode | null;
dragged?: { dragged?: {
x: number; x: number;
y: number; y: number;
@ -168,8 +168,8 @@ const _props = defineProps<{
}>(); }>();
const props = toRefs(_props); const props = toRefs(_props);
const emit = defineEmits<{ const emit = defineEmits<{
(e: "mouseDown", event: MouseEvent | TouchEvent, node: number, isDraggable: boolean): void; (e: "mouseDown", event: MouseEvent | TouchEvent, node: BoardNode, isDraggable: boolean): void;
(e: "endDragging", node: number): void; (e: "endDragging", node: BoardNode): void;
(e: "clickAction", actionId: string): void; (e: "clickAction", actionId: string): void;
}>(); }>();
@ -181,7 +181,7 @@ const isDraggable = computed(() =>
watch(isDraggable, value => { watch(isDraggable, value => {
const node = unref(props.node); const node = unref(props.node);
if (unref(props.dragging) === node && !value) { if (unref(props.dragging) === node && !value) {
emit("endDragging", node.id); emit("endDragging", node);
} }
}); });
@ -265,12 +265,12 @@ const style = computed(() => getNodeProperty(props.nodeType.value.style, unref(p
const classes = computed(() => getNodeProperty(props.nodeType.value.classes, unref(props.node))); const classes = computed(() => getNodeProperty(props.nodeType.value.classes, unref(props.node)));
function mouseDown(e: MouseEvent | TouchEvent) { function mouseDown(e: MouseEvent | TouchEvent) {
emit("mouseDown", e, props.node.value.id, isDraggable.value); emit("mouseDown", e, props.node.value, isDraggable.value);
} }
function mouseUp(e: MouseEvent | TouchEvent) { function mouseUp(e: MouseEvent | TouchEvent) {
if (!props.hasDragged?.value) { if (!props.hasDragged?.value) {
emit("endDragging", props.node.value.id); emit("endDragging", props.node.value);
props.nodeType.value.onClick?.(props.node.value); props.nodeType.value.onClick?.(props.node.value);
e.stopPropagation(); e.stopPropagation();
} }

View file

@ -261,6 +261,10 @@ export interface BaseBoard {
selectedNode: Ref<BoardNode | null>; selectedNode: Ref<BoardNode | null>;
/** The currently selected action, if any. */ /** The currently selected action, if any. */
selectedAction: Ref<GenericBoardNodeAction | null>; selectedAction: Ref<GenericBoardNodeAction | null>;
/** The currently being dragged node, if any. */
draggingNode: Ref<BoardNode | null>;
/** If dragging a node, the node it's currently being hovered over, if any. */
receivingNode: Ref<BoardNode | null>;
/** The current mouse position, if over the board. */ /** The current mouse position, if over the board. */
mousePosition: Ref<{ x: number; y: number } | null>; mousePosition: Ref<{ x: number; y: number } | null>;
/** A symbol that helps identify features of the same type. */ /** A symbol that helps identify features of the same type. */
@ -375,6 +379,8 @@ export function createBoard<T extends BoardOptions>(
return null; return null;
}); });
} }
board.draggingNode = ref(null);
board.receivingNode = ref(null);
processComputable(board as T, "visibility"); processComputable(board as T, "visibility");
setDefault(board, "visibility", Visibility.Visible); setDefault(board, "visibility", Visibility.Visible);
processComputable(board as T, "width"); processComputable(board as T, "width");
@ -431,6 +437,15 @@ export function createBoard<T extends BoardOptions>(
} }
} }
function setDraggingNode(node: BoardNode | null) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
board.draggingNode!.value = node;
}
function setReceivingNode(node: BoardNode | null) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
board.receivingNode!.value = node;
}
board[GatherProps] = function (this: GenericBoard) { board[GatherProps] = function (this: GenericBoard) {
const { const {
nodes, nodes,
@ -444,7 +459,9 @@ export function createBoard<T extends BoardOptions>(
links, links,
selectedAction, selectedAction,
selectedNode, selectedNode,
mousePosition mousePosition,
draggingNode,
receivingNode
} = this; } = this;
return { return {
nodes, nodes,
@ -458,7 +475,11 @@ export function createBoard<T extends BoardOptions>(
links, links,
selectedAction, selectedAction,
selectedNode, selectedNode,
mousePosition mousePosition,
draggingNode,
receivingNode,
setDraggingNode,
setReceivingNode
}; };
}; };