Add edge dragging, autofocus on inputs and limit neighbor distance lookup during snap fetching
This commit is contained in:
parent
939b9cbd28
commit
6abc467a43
|
|
@ -4,6 +4,7 @@ import type CanvasNodeEditor from './canvas/CanvasNodeEditor.vue';
|
||||||
import type CanvasEdgeEditor from './canvas/CanvasEdgeEditor.vue';
|
import type CanvasEdgeEditor from './canvas/CanvasEdgeEditor.vue';
|
||||||
import { SnapFinder, type SnapHint } from '#shared/physics.util';
|
import { SnapFinder, type SnapHint } from '#shared/physics.util';
|
||||||
import type { CanvasPreferences } from '~/types/general';
|
import type { CanvasPreferences } from '~/types/general';
|
||||||
|
import Id from '~/pages/user/[id].vue';
|
||||||
export type Element = { type: 'node' | 'edge', id: string };
|
export type Element = { type: 'node' | 'edge', id: string };
|
||||||
|
|
||||||
interface ActionMap {
|
interface ActionMap {
|
||||||
|
|
@ -68,7 +69,7 @@ const props = defineProps<{
|
||||||
|
|
||||||
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
||||||
const focusing = ref<Element>(), editing = ref<Element>();
|
const focusing = ref<Element>(), editing = ref<Element>();
|
||||||
const canvasRef = useTemplateRef('canvasRef'), transformRef = useTemplateRef('transformRef'), toolbarRef = useTemplateRef('toolbarRef'), viewportSize = useElementSize(canvasRef);
|
const canvasRef = useTemplateRef('canvasRef'), transformRef = useTemplateRef('transformRef'), toolbarRef = useTemplateRef('toolbarRef'), viewportSize = useElementBounding(canvasRef);
|
||||||
const nodes = useTemplateRef<NodeEditor[]>('nodes'), edges = useTemplateRef<EdgeEditor[]>('edges');
|
const nodes = useTemplateRef<NodeEditor[]>('nodes'), edges = useTemplateRef<EdgeEditor[]>('edges');
|
||||||
const canvasSettings = useCookie<CanvasPreferences>('canvasPreference', { default: () => ({ gridSnap: true, neighborSnap: true }) });
|
const canvasSettings = useCookie<CanvasPreferences>('canvasPreference', { default: () => ({ gridSnap: true, neighborSnap: true }) });
|
||||||
const hints = ref<SnapHint[]>([]);
|
const hints = ref<SnapHint[]>([]);
|
||||||
|
|
@ -78,7 +79,8 @@ const viewport = computed<Box>(() => {
|
||||||
return { x: -dispX.value + movementX / 2, y: -dispY.value + movementY / 2, w: width, h: height };
|
return { x: -dispX.value + movementX / 2, y: -dispY.value + movementY / 2, w: width, h: height };
|
||||||
});
|
});
|
||||||
|
|
||||||
const fakeEdge = ref<{ original?: string, from?: Position, fromSide?: Direction, to?: Position, toSide?: Direction, path?: Path, style?: { stroke: string, fill: string }, hex?: string, dragFrom?: string, snapped?: { node: string, side: Direction } }>({});
|
type DragOrigin = { type: 'edge', id: string, destination: 'from' | 'to', node: string } | { type: 'node', id: string };
|
||||||
|
const fakeEdge = ref<{ from?: Position, fromSide?: Direction, to?: Position, toSide?: Direction, path?: Path, style?: { stroke: string, fill: string }, hex?: string, drag?: DragOrigin, snapped?: { node: string, side: Direction } }>({});
|
||||||
|
|
||||||
const focused = computed(() => focusing.value ? focusing.value?.type === 'node' ? nodes.value?.find(e => !!e && e.id === focusing.value!.id) : edges.value?.find(e => !!e && e.id === focusing.value!.id) : undefined), edited = computed(() => editing.value ? editing.value?.type === 'node' ? nodes.value?.find(e => !!e && e.id === editing.value!.id) : edges.value?.find(e => !!e && e.id === editing.value!.id) : undefined);
|
const focused = computed(() => focusing.value ? focusing.value?.type === 'node' ? nodes.value?.find(e => !!e && e.id === focusing.value!.id) : edges.value?.find(e => !!e && e.id === focusing.value!.id) : undefined), edited = computed(() => editing.value ? editing.value?.type === 'node' ? nodes.value?.find(e => !!e && e.id === editing.value!.id) : edges.value?.find(e => !!e && e.id === editing.value!.id) : undefined);
|
||||||
let snapFinder: SnapFinder;
|
let snapFinder: SnapFinder;
|
||||||
|
|
@ -118,7 +120,6 @@ function addAction<T extends Action = Action>(event: T, actions: HistoryAction<T
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
let lastX = 0, lastY = 0, lastDistance = 0;
|
let lastX = 0, lastY = 0, lastDistance = 0;
|
||||||
let box = canvasRef.value?.getBoundingClientRect()!;
|
|
||||||
const dragMove = (e: MouseEvent) => {
|
const dragMove = (e: MouseEvent) => {
|
||||||
dispX.value = dispX.value - (lastX - e.clientX) / zoom.value;
|
dispX.value = dispX.value - (lastX - e.clientX) / zoom.value;
|
||||||
dispY.value = dispY.value - (lastY - e.clientY) / zoom.value;
|
dispY.value = dispY.value - (lastY - e.clientY) / zoom.value;
|
||||||
|
|
@ -143,7 +144,6 @@ onMounted(() => {
|
||||||
document.removeEventListener('gesturechange', cancelEvent);
|
document.removeEventListener('gesturechange', cancelEvent);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
window.addEventListener('resize', () => box = canvasRef.value?.getBoundingClientRect()!);
|
|
||||||
canvasRef.value?.addEventListener('mousedown', (e) => {
|
canvasRef.value?.addEventListener('mousedown', (e) => {
|
||||||
if(e.button === 1)
|
if(e.button === 1)
|
||||||
{
|
{
|
||||||
|
|
@ -159,7 +159,7 @@ onMounted(() => {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const diff = Math.exp(e.deltaY * -0.001);
|
const diff = Math.exp(e.deltaY * -0.001);
|
||||||
const centerX = (box.x + box.width / 2), centerY = (box.y + box.height / 2);
|
const centerX = (viewportSize.x.value + viewportSize.width.value / 2), centerY = (viewportSize.y.value + viewportSize.height.value / 2);
|
||||||
const mousex = centerX - e.clientX, mousey = centerY - e.clientY;
|
const mousex = centerX - e.clientX, mousey = centerY - e.clientY;
|
||||||
|
|
||||||
dispX.value = dispX.value - (mousex / (diff * zoom.value) - mousex / zoom.value);
|
dispX.value = dispX.value - (mousex / (diff * zoom.value) - mousex / zoom.value);
|
||||||
|
|
@ -364,7 +364,7 @@ function dragEdgeTo(e: MouseEvent): void
|
||||||
(fakeEdge.value.to as Position).x += e.movementX / zoom.value;
|
(fakeEdge.value.to as Position).x += e.movementX / zoom.value;
|
||||||
(fakeEdge.value.to as Position).y += e.movementY / zoom.value;
|
(fakeEdge.value.to as Position).y += e.movementY / zoom.value;
|
||||||
|
|
||||||
const result = snapFinder.findEdgeSnapPosition(fakeEdge.value.dragFrom!, fakeEdge.value.to!.x, fakeEdge.value.to!.y);
|
const result = snapFinder.findEdgeSnapPosition(fakeEdge.value.drag!.id, fakeEdge.value.to!.x, fakeEdge.value.to!.y);
|
||||||
|
|
||||||
fakeEdge.value.snapped = result ? { node: result.node, side: result.direction } : undefined;
|
fakeEdge.value.snapped = result ? { node: result.node, side: result.direction } : undefined;
|
||||||
fakeEdge.value.path = bezier((fakeEdge.value.from as Position), fakeEdge.value.fromSide!, result ?? (fakeEdge.value.to as Position), result?.direction ?? fakeEdge.value.toSide!);
|
fakeEdge.value.path = bezier((fakeEdge.value.from as Position), fakeEdge.value.fromSide!, result ?? (fakeEdge.value.to as Position), result?.direction ?? fakeEdge.value.toSide!);
|
||||||
|
|
@ -376,8 +376,8 @@ function dragEndEdgeTo(e: MouseEvent): void
|
||||||
|
|
||||||
if(fakeEdge.value.snapped)
|
if(fakeEdge.value.snapped)
|
||||||
{
|
{
|
||||||
const node = canvas.value.nodes!.find(e => e.id === fakeEdge.value.dragFrom)!;
|
const node = canvas.value.nodes!.find(e => e.id === fakeEdge.value.drag!.id)!;
|
||||||
const edge: CanvasEdge = { fromNode: fakeEdge.value.dragFrom!, fromSide: fakeEdge.value.fromSide!, toNode: fakeEdge.value.snapped.node, toSide: fakeEdge.value.snapped.side, id: getID(16), color: node.color };
|
const edge: CanvasEdge = { fromNode: fakeEdge.value.drag!.id, fromSide: fakeEdge.value.fromSide!, toNode: fakeEdge.value.snapped.node, toSide: fakeEdge.value.snapped.side, id: getID(16), color: node.color };
|
||||||
canvas.value.edges?.push(edge);
|
canvas.value.edges?.push(edge);
|
||||||
|
|
||||||
addAction('create', [{ from: undefined, to: edge, element: { id: edge.id, type: 'edge' } }]);
|
addAction('create', [{ from: undefined, to: edge, element: { id: edge.id, type: 'edge' } }]);
|
||||||
|
|
@ -393,12 +393,84 @@ function dragStartEdgeTo(id: string, e: MouseEvent, direction: Direction): void
|
||||||
window.addEventListener('mousemove', dragEdgeTo, { passive: true });
|
window.addEventListener('mousemove', dragEdgeTo, { passive: true });
|
||||||
window.addEventListener('mouseup', dragEndEdgeTo, { passive: true });
|
window.addEventListener('mouseup', dragEndEdgeTo, { passive: true });
|
||||||
}
|
}
|
||||||
|
function dragEdgeSide(e: MouseEvent): void
|
||||||
|
{
|
||||||
|
if(fakeEdge.value.drag?.type === 'node')
|
||||||
|
return;
|
||||||
|
|
||||||
|
const destination = fakeEdge.value.drag!.destination;
|
||||||
|
const pos = fakeEdge.value[destination]!;
|
||||||
|
|
||||||
|
pos.x += e.movementX / zoom.value;
|
||||||
|
pos.y += e.movementY / zoom.value;
|
||||||
|
|
||||||
|
const result = snapFinder.findEdgeSnapPosition(fakeEdge.value.drag!.node, pos.x, pos.y);
|
||||||
|
|
||||||
|
fakeEdge.value.snapped = result ? { node: result.node, side: result.direction } : undefined;
|
||||||
|
fakeEdge.value.path = bezier(destination === 'from' ? (result ?? pos) : fakeEdge.value.from!, destination === 'from' ? result?.direction ?? fakeEdge.value.fromSide! : fakeEdge.value.fromSide!, destination === 'to' ? (result ?? pos) : fakeEdge.value.to!, destination === 'to' ? result?.direction ?? fakeEdge.value.toSide! : fakeEdge.value.toSide!);
|
||||||
|
}
|
||||||
|
function dragEndEdgeSide(e: MouseEvent): void
|
||||||
|
{
|
||||||
|
if(fakeEdge.value.drag?.type === 'node')
|
||||||
|
return;
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove', dragEdgeSide);
|
||||||
|
window.removeEventListener('mouseup', dragEndEdgeSide);
|
||||||
|
|
||||||
|
if(fakeEdge.value.snapped)
|
||||||
|
{
|
||||||
|
const edge = canvas.value.edges!.find(e => e.id === fakeEdge.value.drag?.id)!
|
||||||
|
const old = { ... edge };
|
||||||
|
|
||||||
|
const destination = fakeEdge.value.drag!.destination;
|
||||||
|
|
||||||
|
edge.fromNode = destination === 'to' ? fakeEdge.value.drag!.node : fakeEdge.value.snapped.node;
|
||||||
|
edge.fromSide = destination === 'to' ? fakeEdge.value.fromSide! : fakeEdge.value.snapped.side;
|
||||||
|
|
||||||
|
edge.toNode = destination === 'from' ? fakeEdge.value.drag!.node : fakeEdge.value.snapped.node;
|
||||||
|
edge.toSide = destination === 'from' ? fakeEdge.value.toSide! : fakeEdge.value.snapped.side;
|
||||||
|
|
||||||
|
addAction('property', [{ from: old, to: edge, element: { id: edge.id, type: 'edge' } }]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fakeEdge.value = {};
|
||||||
|
}
|
||||||
|
function dragStartEdgeSide(id: string, e: MouseEvent, direction: 'from' | 'to'): void
|
||||||
|
{
|
||||||
|
const edge = canvas.value.edges!.find(e => e.id === id)!;
|
||||||
|
fakeEdgeFromEdge(edge, direction);
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', dragEdgeSide, { passive: true });
|
||||||
|
window.addEventListener('mouseup', dragEndEdgeSide, { passive: true });
|
||||||
|
}
|
||||||
|
function fakeEdgeFromEdge(edge: CanvasEdge, direction: 'from' | 'to'): void
|
||||||
|
{
|
||||||
|
fakeEdge.value.drag = { type: 'edge', id: edge.id, destination: direction, node: direction === 'to' ? edge.fromNode : edge.toNode };
|
||||||
|
|
||||||
|
const destinationNode = direction === 'from' ? canvas.value.nodes!.find(e => e.id === edge.fromNode)! : canvas.value.nodes!.find(e => e.id === edge.toNode)!;
|
||||||
|
const otherNode = direction === 'from' ? canvas.value.nodes!.find(e => e.id === edge.toNode)! : canvas.value.nodes!.find(e => e.id === edge.fromNode)!;
|
||||||
|
const destinationPos = posFromDir(getBbox(destinationNode), direction === 'from' ? edge.fromSide : edge.toSide);
|
||||||
|
const otherPos = posFromDir(getBbox(otherNode), direction === 'from' ? edge.toSide : edge.fromSide);
|
||||||
|
|
||||||
|
fakeEdge.value.from = direction === 'from' ? destinationPos : otherPos;
|
||||||
|
fakeEdge.value.fromSide = edge.fromSide;
|
||||||
|
|
||||||
|
fakeEdge.value.to = direction === 'to' ? destinationPos : otherPos;
|
||||||
|
fakeEdge.value.toSide = edge.toSide;
|
||||||
|
|
||||||
|
fakeEdge.value.path = bezier(destinationPos, edge.fromSide, otherPos, edge.toSide);
|
||||||
|
fakeEdge.value.hex = edge.color?.hex;
|
||||||
|
|
||||||
|
fakeEdge.value.style = edge?.color ? edge.color?.class ?
|
||||||
|
{ fill: `fill-light-${edge.color?.class} dark:fill-dark-${edge.color?.class}`, stroke: `stroke-light-${edge.color?.class} dark:stroke-dark-${edge.color?.class}` } :
|
||||||
|
{ fill: `fill-colored`, stroke: `stroke-[color:var(--canvas-color)]` } :
|
||||||
|
{ stroke: `stroke-light-40 dark:stroke-dark-40`, fill: `fill-light-40 dark:fill-dark-40` };
|
||||||
|
}
|
||||||
function fakeEdgeFromNode(node: CanvasNode, direction: Direction): void
|
function fakeEdgeFromNode(node: CanvasNode, direction: Direction): void
|
||||||
{
|
{
|
||||||
const pos = posFromDir(getBbox(node), direction);
|
const pos = posFromDir(getBbox(node), direction);
|
||||||
|
|
||||||
fakeEdge.value.original = undefined;
|
fakeEdge.value.drag = { type: 'node', id: node.id };
|
||||||
fakeEdge.value.dragFrom = node.id;
|
|
||||||
|
|
||||||
fakeEdge.value.from = { ... pos };
|
fakeEdge.value.from = { ... pos };
|
||||||
fakeEdge.value.fromSide = direction;
|
fakeEdge.value.fromSide = direction;
|
||||||
|
|
@ -678,7 +750,7 @@ useShortcuts({
|
||||||
'transform-origin': 'center center',
|
'transform-origin': 'center center',
|
||||||
}" class="h-full">
|
}" class="h-full">
|
||||||
<div class="absolute top-0 left-0 w-full h-full pointer-events-none *:pointer-events-auto *:select-none touch-none">
|
<div class="absolute top-0 left-0 w-full h-full pointer-events-none *:pointer-events-auto *:select-none touch-none">
|
||||||
<div class="absolute z-20 origin-bottom" ref="toolbarRef">
|
<div class="absolute z-20 destination-bottom" ref="toolbarRef">
|
||||||
<template v-if="focusing">
|
<template v-if="focusing">
|
||||||
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 flex flex-row" v-if="focusing.type === 'node'">
|
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 flex flex-row" v-if="focusing.type === 'node'">
|
||||||
<PopoverRoot>
|
<PopoverRoot>
|
||||||
|
|
@ -787,7 +859,7 @@ useShortcuts({
|
||||||
@select="select" @edit="edit" @move="(i, x, y) => moveNode([i], x, y)" @resize="(i, x, y, w, h) => resizeNode([i], x, y, w, h)" @input="(id, text) => editNodeProperty([id], node.type === 'group' ? 'label' : 'text', text)" :snap="snapFinder.findNodeSnapPosition.bind(snapFinder)" @edge="dragStartEdgeTo" />
|
@select="select" @edit="edit" @move="(i, x, y) => moveNode([i], x, y)" @resize="(i, x, y, w, h) => resizeNode([i], x, y, w, h)" @input="(id, text) => editNodeProperty([id], node.type === 'group' ? 'label' : 'text', text)" :snap="snapFinder.findNodeSnapPosition.bind(snapFinder)" @edge="dragStartEdgeTo" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<CanvasEdgeEditor v-for="edge of canvas.edges" :key="edge.id" ref="edges" :edge="edge" :nodes="canvas.nodes!" @select="select" @edit="edit" @input="(id, text) => editEdgeProperty([id], 'label', text)" />
|
<CanvasEdgeEditor v-for="edge of canvas.edges" :key="edge.id" ref="edges" :edge="edge" :nodes="canvas.nodes!" @select="select" @edit="edit" @input="(id, text) => editEdgeProperty([id], 'label', text)" @drag="dragStartEdgeSide" />
|
||||||
<div v-if="fakeEdge.path" class="absolute overflow-visible">
|
<div v-if="fakeEdge.path" class="absolute overflow-visible">
|
||||||
<svg class="absolute top-0 overflow-visible h-px w-px">
|
<svg class="absolute top-0 overflow-visible h-px w-px">
|
||||||
<g :style="{'--canvas-color': fakeEdge.hex}" class="z-0">
|
<g :style="{'--canvas-color': fakeEdge.hex}" class="z-0">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute overflow-visible">
|
<div class="absolute overflow-visible group">
|
||||||
<input v-if="editing" @click="e => e.stopImmediatePropagation()" :style="{ transform: `${labelPos} translate(-50%, -50%)` }" class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 z-20" v-model="edge.label" />
|
<input v-autofocus v-if="editing" @click="e => e.stopImmediatePropagation()" :style="{ transform: `${labelPos} translate(-50%, -50%)` }" class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 z-20" v-model="edge.label" />
|
||||||
<div v-else-if="edge.label" :style="{ transform: `${labelPos} translate(-50%, -50%)` }" class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 z-20" @click.left="select" @dblclick.left="edit">{{ edge.label }}</div>
|
<div v-else-if="edge.label" :style="{ transform: `${labelPos} translate(-50%, -50%)` }" class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 z-20" @click.left="select" @dblclick.left="edit">{{ edge.label }}</div>
|
||||||
<svg ref="dom" class="absolute top-0 overflow-visible h-px w-px">
|
<svg ref="dom" class="absolute top-0 overflow-visible h-px w-px">
|
||||||
<g :style="{'--canvas-color': edge.color?.hex}" class="z-0">
|
<g :style="{'--canvas-color': edge.color?.hex}" class="z-0">
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
<path style="stroke-width: calc(22px * var(--zoom-multiplier));" class="fill-none transition-opacity z-30 opacity-0 hover:opacity-25" :class="[style.stroke, { 'opacity-25': focusing }]" :d="path.path" @click="select" @dblclick="edit"></path>
|
<path style="stroke-width: calc(22px * var(--zoom-multiplier));" class="fill-none transition-opacity z-30 opacity-0 hover:opacity-25" :class="[style.stroke, { 'opacity-25': focusing }]" :d="path.path" @click="select" @dblclick="edit"></path>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
<span v-if="focusing && !editing" :style="`transform: translate(${path.from.x}px, ${path.from.y}px) translate(-50%, -50%) scale(var(--zoom-multiplier))`" @mousedown.left="(e) => dragEdge(e, 'from')" :class="style.fill" class="hidden group-hover:block z-[31] absolute rounded-full border-2 border-light-70 dark:border-dark-70 bg-light-30 dark:bg-dark-30 w-6 h-6"></span>
|
||||||
|
<span v-if="focusing && !editing" :style="`transform: translate(${path.to.x}px, ${path.to.y}px) translate(-50%, -50%) scale(var(--zoom-multiplier))`" @mousedown.left="(e) => dragEdge(e, 'to')" :class="style.fill" class="hidden group-hover:block z-[31] absolute rounded-full border-2 border-light-70 dark:border-dark-70 bg-light-30 dark:bg-dark-30 w-6 h-6"></span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -34,7 +36,7 @@ const { edge, nodes } = defineProps<{
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'select', id: Element): void,
|
(e: 'select', id: Element): void,
|
||||||
(e: 'edit', id: Element): void,
|
(e: 'edit', id: Element): void,
|
||||||
(e: 'move', id: string, from: string, to: string): void,
|
(e: 'drag', id: string, _e: MouseEvent, origin: 'from' | 'to'): void,
|
||||||
(e: 'input', id: string, text?: string): void,
|
(e: 'input', id: string, text?: string): void,
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
@ -64,6 +66,11 @@ function edit(e: Event) {
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
emit('edit', { type: 'edge', id: edge.id });
|
emit('edit', { type: 'edge', id: edge.id });
|
||||||
}
|
}
|
||||||
|
function dragEdge(e: MouseEvent, origin: 'from' | 'to') {
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
|
emit('drag', edge.id, e, origin);
|
||||||
|
}
|
||||||
function unselect() {
|
function unselect() {
|
||||||
if(editing.value)
|
if(editing.value)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
<Editor v-model="node.text" autofocus />
|
<Editor v-model="node.text" autofocus />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!editing && node.type === 'group' && node.label !== undefined" @click.left="(e) => selectNode(e)" @dblclick.left="(e) => editNode(e)" :class="style.border" style="max-width: 100%; font-size: calc(18px * var(--zoom-multiplier))" class="origin-bottom-left tracking-wider border-4 truncate inline-block text-light-100 dark:text-dark-100 absolute bottom-[100%] mb-2 px-2 py-1 font-thin">{{ node.label }}</div>
|
<div v-if="!editing && node.type === 'group' && node.label !== undefined" @click.left="(e) => selectNode(e)" @dblclick.left="(e) => editNode(e)" :class="style.border" style="max-width: 100%; font-size: calc(18px * var(--zoom-multiplier))" class="origin-bottom-left tracking-wider border-4 truncate inline-block text-light-100 dark:text-dark-100 absolute bottom-[100%] mb-2 px-2 py-1 font-thin">{{ node.label }}</div>
|
||||||
<input v-else-if="editing && node.type === 'group'" v-model="node.label" @click="e => e.stopImmediatePropagation()" autofocus :class="[style.border, style.outline]" style="max-width: 100%; font-size: calc(18px * var(--zoom-multiplier))" class="origin-bottom-left tracking-wider border-4 truncate inline-block text-light-100 dark:text-dark-100 absolute bottom-[100%] appearance-none bg-transparent outline-4 mb-2 px-2 py-1 font-thin min-w-4" />
|
<input v-else-if="editing && node.type === 'group'" v-model="node.label" @click="e => e.stopImmediatePropagation()" v-autofocus :class="[style.border, style.outline]" style="max-width: 100%; font-size: calc(18px * var(--zoom-multiplier))" class="origin-bottom-left tracking-wider border-4 truncate inline-block text-light-100 dark:text-dark-100 absolute bottom-[100%] appearance-none bg-transparent outline-4 mb-2 px-2 py-1 font-thin min-w-4" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -119,7 +119,7 @@ function resizeNode(e: MouseEvent, x: number, y: number, w: number, h: number) {
|
||||||
window.addEventListener('mousemove', resizemove);
|
window.addEventListener('mousemove', resizemove);
|
||||||
window.addEventListener('mouseup', resizeend);
|
window.addEventListener('mouseup', resizeend);
|
||||||
}
|
}
|
||||||
function dragEdge(e: Event, direction: Direction) {
|
function dragEdge(e: MouseEvent, direction: Direction) {
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
emit('edge', node.id, e, direction)
|
emit('edge', node.id, e, direction)
|
||||||
|
|
|
||||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.vueApp.directive('autofocus', {
|
||||||
|
mounted(el, binding) {
|
||||||
|
el.focus();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -125,10 +125,10 @@ class SpatialGrid {
|
||||||
const endX = Math.ceil((node.x + node.width) / this.cellSize);
|
const endX = Math.ceil((node.x + node.width) / this.cellSize);
|
||||||
const endY = Math.ceil((node.y + node.height) / this.cellSize);
|
const endY = Math.ceil((node.y + node.height) / this.cellSize);
|
||||||
|
|
||||||
const minX = viewport ? Math.max(this.minx, Math.floor(viewport.x / this.cellSize)) : this.minx;
|
const minX = Math.max(viewport ? Math.max(this.minx, Math.floor(viewport.x / this.cellSize)) : this.minx, startX - 8);
|
||||||
const minY = viewport ? Math.max(this.miny, Math.floor(viewport.y / this.cellSize)) : this.miny;
|
const minY = Math.max(viewport ? Math.max(this.miny, Math.floor(viewport.y / this.cellSize)) : this.miny, startY - 8);
|
||||||
const maxX = viewport ? Math.min(this.maxx, Math.ceil((viewport.x + viewport.w) / this.cellSize)) : this.maxx;
|
const maxX = Math.min(viewport ? Math.min(this.maxx, Math.ceil((viewport.x + viewport.w) / this.cellSize)) : this.maxx, endX + 8);
|
||||||
const maxY = viewport ? Math.min(this.maxy, Math.ceil((viewport.y + viewport.h) / this.cellSize)) : this.maxy;
|
const maxY = Math.min(viewport ? Math.min(this.maxy, Math.ceil((viewport.y + viewport.h) / this.cellSize)) : this.maxy, endY + 8);
|
||||||
|
|
||||||
for (let dx = minX; dx <= maxX; dx++) {
|
for (let dx = minX; dx <= maxX; dx++) {
|
||||||
const gridX = this.cells.get(dx);
|
const gridX = this.cells.get(dx);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue