Rework the structure to handle suppression (using ID instead of index). Add create history and removing.

This commit is contained in:
Peaceultime 2025-01-13 00:27:14 +01:00
parent 9439dd2d95
commit 4433cf0e00
3 changed files with 95 additions and 74 deletions

View File

@ -1,5 +1,6 @@
<script lang="ts">
import type { Box, Position } from '#shared/canvas.util';
import type CanvasNodeEditor from './canvas/CanvasNodeEditor.vue';
type Direction = 'bottom' | 'top' | 'left' | 'right';
const rotation: Record<Direction, string> = {
top: "180",
@ -24,7 +25,7 @@ interface HistoryEvent<T extends Action = Action>
}
interface HistoryAction<T extends Action>
{
element: number;
element: string;
from: ActionMap[T];
to: ActionMap[T];
}
@ -71,9 +72,11 @@ import { labelCenter, getPath } from '#shared/canvas.util';
const canvas = defineModel<CanvasContent>({ required: true, });
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
const focusing = ref<number>(), editing = ref<number>();
const focusing = ref<string>(), editing = ref<string>();
const canvasRef = useTemplateRef('canvasRef');
const nodes = useTemplateRef('nodes');
const nodes = useTemplateRef<InstanceType<typeof CanvasNodeEditor>[]>('nodes');
const focusedNode = computed(() => nodes.value?.find(e => !!e && e.id === focusing.value)), editedNode = computed(() => nodes.value?.find(e => !!e && e.id === editing.value));
const edges = computed(() => {
return canvas.value.edges.map(e => {
@ -83,8 +86,6 @@ const edges = computed(() => {
});
});
const history = ref<HistoryEvent[]>([]);
const historyPos = ref(-1);
const historyCursor = computed(() => history.value.length > 0 && historyPos.value > -1 ? history.value[historyPos.value] : undefined);
@ -199,79 +200,82 @@ onMounted(() => {
};
});
function moveNode(index: number[], deltax: number, deltay: number)
function moveNode(ids: string[], deltax: number, deltay: number)
{
const actions: HistoryAction<'move'>[] = [];
for(const i of index)
for(const id of ids)
{
const node = canvas.value.nodes[i];
const node = canvas.value.nodes.find(e => e.id === id)!;
actions.push({ element: i, from: { x: node.x - deltax, y: node.y - deltay }, to: { x: node.x, y: node.y } });
actions.push({ element: id, from: { x: node.x - deltax, y: node.y - deltay }, to: { x: node.x, y: node.y } });
}
addAction('move', actions);
}
function resizeNode(index: number[], deltax: number, deltay: number, deltaw: number, deltah: number)
function resizeNode(ids: string[], deltax: number, deltay: number, deltaw: number, deltah: number)
{
const actions: HistoryAction<'resize'>[] = [];
for(const i of index)
for(const id of ids)
{
const node = canvas.value.nodes[i];
const node = canvas.value.nodes.find(e => e.id === id)!;
actions.push({ element: i, from: { x: node.x - deltax, y: node.y - deltay, w: node.width - deltaw, h: node.height - deltah }, to: { x: node.x, y: node.y, w: node.width, h: node.height } });
actions.push({ element: id, from: { x: node.x - deltax, y: node.y - deltay, w: node.width - deltaw, h: node.height - deltah }, to: { x: node.x, y: node.y, w: node.width, h: node.height } });
}
addAction('resize', actions);
}
function selectNode(index: number)
function selectNode(id: string)
{
if(focusing.value !== index)
if(focusing.value !== id)
{
unselectNode();
}
nodes.value![index]?.dom?.addEventListener('click', stopPropagation, { passive: true });
canvasRef.value?.addEventListener('click', unselectNode, { once: true });
focusing.value = id;
focusing.value = index;
focusedNode.value?.dom?.addEventListener('click', stopPropagation, { passive: true });
canvasRef.value?.addEventListener('click', unselectNode, { once: true });
}
function editNode(index: number)
function editNode(id: string)
{
nodes.value![index]?.dom?.addEventListener('wheel', stopPropagation, { passive: true });
nodes.value![index]?.dom?.addEventListener('dblclick', stopPropagation, { passive: true });
canvasRef.value?.addEventListener('click', unselectNode, { once: true });
editing.value = id;
editing.value = index;
focusedNode.value?.dom?.addEventListener('wheel', stopPropagation, { passive: true });
focusedNode.value?.dom?.addEventListener('dblclick', stopPropagation, { passive: true });
canvasRef.value?.addEventListener('click', unselectNode, { once: true });
}
function addNode(e: MouseEvent)
function createNode(e: MouseEvent)
{
let box = canvasRef.value?.getBoundingClientRect()!;
canvas.value.nodes.push({ id: getID(16), x: (e.layerX / zoom.value) - box.width / 2 - 50, y: (e.layerY / zoom.value) - box.height / 2 - 25, width: 100, height: 50, type: 'text' });
const node: CanvasNode = { id: getID(16), x: (e.layerX / zoom.value) - box.width / 2 - 50, y: (e.layerY / zoom.value) - box.height / 2 - 25, width: 100, height: 50, type: 'text' };
canvas.value.nodes.push(node);
addAction('create', [{ element: node.id, from: undefined, to: node }]);
}
function removeNode(index: number[])
function removeNode(ids: string[])
{
/*const actions: HistoryAction<'remove'>[] = [];
const actions: HistoryAction<'remove'>[] = [];
unselectNode();
for(const i of index)
for(const id of ids)
{
const [node] = canvas.value.nodes.splice(i, 1);
actions.push({ element: i, from: node, to: undefined });
const index = canvas.value.nodes.findIndex(e => e.id === id);
actions.push({ element: id, from: canvas.value.nodes.splice(index, 1)[0], to: undefined });
console.log("Removing %s", i);
console.log("Removing %s", id);
}
addAction('remove', actions);*/
addAction('remove', actions);
}
function editNodeProperty<T extends keyof CanvasNode>(index: number[], property: T, value: CanvasNode[T])
function editNodeProperty<T extends keyof CanvasNode>(ids: string[], property: T, value: CanvasNode[T])
{
const actions: HistoryAction<'remove'>[] = [];
for(const i of index)
for(const id of ids)
{
const copy = JSON.parse(JSON.stringify(canvas.value.nodes[i])) as CanvasNode;
canvas.value.nodes[i][property] = value;
actions.push({ element: i, from: copy, to: canvas.value.nodes[i] });
const copy = JSON.parse(JSON.stringify(canvas.value.nodes.find(e => e.id === id)!)) as CanvasNode;
canvas.value.nodes.find(e => e.id === id)![property] = value;
actions.push({ element: id, from: copy, to: canvas.value.nodes.find(e => e.id === id)! });
}
addAction('property', actions);
@ -280,17 +284,17 @@ function editNodeProperty<T extends keyof CanvasNode>(index: number[], property:
const unselectNode = () => {
if(focusing.value !== undefined)
{
nodes.value![focusing.value]?.dom?.removeEventListener('click', stopPropagation);
nodes.value![focusing.value]?.unselect();
focusedNode.value?.dom?.removeEventListener('click', stopPropagation);
focusedNode.value?.unselect();
}
focusing.value = undefined;
if(editing.value !== undefined)
{
nodes.value![editing.value]?.dom?.removeEventListener('wheel', stopPropagation);
nodes.value![editing.value]?.dom?.removeEventListener('dblclick', stopPropagation);
nodes.value![editing.value]?.dom?.removeEventListener('click', stopPropagation);
nodes.value![editing.value]?.unselect();
editedNode.value?.dom?.removeEventListener('wheel', stopPropagation);
editedNode.value?.dom?.removeEventListener('dblclick', stopPropagation);
editedNode.value?.dom?.removeEventListener('click', stopPropagation);
editedNode.value?.unselect();
}
editing.value = undefined;
};
@ -300,40 +304,49 @@ const undo = () => {
for(const action of historyCursor.value.actions)
{
const node = canvas.value.nodes.find(e => e.id === action.element)!;
switch(historyCursor.value.event)
{
case 'move':
{
const a = action as HistoryAction<'move'>;
canvas.value.nodes[action.element].x = a.from.x;
canvas.value.nodes[action.element].y = a.from.y;
node.x = a.from.x;
node.y = a.from.y;
break;
}
case 'resize':
{
const a = action as HistoryAction<'resize'>;
canvas.value.nodes[action.element].x = a.from.x;
canvas.value.nodes[action.element].y = a.from.y;
canvas.value.nodes[action.element].width = a.from.w;
canvas.value.nodes[action.element].height = a.from.h;
node.x = a.from.x;
node.y = a.from.y;
node.width = a.from.w;
node.height = a.from.h;
break;
}
case 'edit':
{
const a = action as HistoryAction<'edit'>;
canvas.value.nodes[action.element].label = a.from;
node.label = a.from;
break;
}
case 'create':
{
const a = action as HistoryAction<'create'>;
const index = canvas.value.nodes.findIndex(e => e.id === action.element);
canvas.value.nodes.splice(index, 1);
break;
}
case 'remove':
{
const a = action as HistoryAction<'remove'>;
canvas.value.nodes.splice(action.element, 0, a.from!);
canvas.value.nodes.push(a.from!);
break;
}
case 'property':
{
const a = action as HistoryAction<'property'>;
canvas.value.nodes[action.element] = a.from;
const index = canvas.value.nodes.findIndex(e => e.id === action.element);
canvas.value.nodes[index] = a.from;
break;
}
}
@ -357,40 +370,49 @@ const redo = () => {
for(const action of historyCursor.value.actions)
{
const node = canvas.value.nodes.find(e => e.id === action.element)!;
switch(historyCursor.value.event)
{
case 'move':
{
const a = action as HistoryAction<'move'>;
canvas.value.nodes[action.element].x = a.to.x;
canvas.value.nodes[action.element].y = a.to.y;
node.x = a.to.x;
node.y = a.to.y;
break;
}
case 'resize':
{
const a = action as HistoryAction<'resize'>;
canvas.value.nodes[action.element].x = a.to.x;
canvas.value.nodes[action.element].y = a.to.y;
canvas.value.nodes[action.element].width = a.to.w;
canvas.value.nodes[action.element].height = a.to.h;
node.x = a.to.x;
node.y = a.to.y;
node.width = a.to.w;
node.height = a.to.h;
break;
}
case 'edit':
{
const a = action as HistoryAction<'edit'>;
canvas.value.nodes[action.element].label = a.to;
node.label = a.to;
break;
}
case 'create':
{
const a = action as HistoryAction<'remove'>;
canvas.value.nodes.push(a.to!);
break;
}
case 'remove':
{
const a = action as HistoryAction<'remove'>;
canvas.value.nodes.splice(action.element, 1);
const index = canvas.value.nodes.findIndex(e => e.id === action.element);
canvas.value.nodes.splice(index, 1);
break;
}
case 'property':
{
const a = action as HistoryAction<'property'>;
canvas.value.nodes[action.element] = a.to;
const index = canvas.value.nodes.findIndex(e => e.id === action.element);
canvas.value.nodes[index] = a.to;
break;
}
}
@ -407,7 +429,7 @@ useShortcuts({
</script>
<template>
<div ref="canvasRef" class="absolute top-0 left-0 overflow-hidden w-full h-full touch-none" :style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }" @dblclick.left="addNode">
<div ref="canvasRef" class="absolute top-0 left-0 overflow-hidden w-full h-full touch-none" :style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }" @dblclick.left="createNode">
<div class="flex flex-col absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden gap-4">
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10">
<Tooltip message="Zoom avant" side="right">
@ -483,7 +505,7 @@ useShortcuts({
'transform-origin': 'center center',
}" 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 v-if="focusing !== undefined" class="absolute z-20 origin-bottom" :style="{transform: `translate(${canvas.nodes[focusing].x}px, ${canvas.nodes[focusing].y}px) translateY(-100%) translateY(-12px) translateX(-50%) translateX(${canvas.nodes[focusing].width / 2}px) scale(calc(1 / var(--tw-scale)))`}">
<div v-if="focusing !== undefined && focusedNode !== undefined" class="absolute z-20 origin-bottom" :style="{transform: `translate(${focusedNode.x}px, ${focusedNode.y}px) translateY(-100%) translateY(-12px) translateX(-50%) translateX(${focusedNode.width / 2}px) scale(calc(1 / var(--tw-scale)))`}">
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 flex flex-row">
<PopoverRoot>
<PopoverTrigger asChild>

View File

@ -38,17 +38,16 @@ import type { Direction } from '~/shared/canvas.util';
import FakeA from '../prose/FakeA.vue';
import type { CanvasNode } from '~/types/canvas';
const { node, index, zoom } = defineProps<{
const { node, zoom } = defineProps<{
node: CanvasNode
index: number
zoom: number
}>();
const emit = defineEmits<{
(e: 'select', index: number): void,
(e: 'edit', index: number): void,
(e: 'move', index: number, x: number, y: number): void,
(e: 'resize', index: number, x: number, y: number, w: number, h: number): void,
(e: 'select', id: string): void,
(e: 'edit', id: string): void,
(e: 'move', id: string, x: number, y: number): void,
(e: 'resize', id: string, x: number, y: number, w: number, h: number): void,
}>();
const dom = useTemplateRef('dom');
@ -59,7 +58,7 @@ function selectNode(e: Event) {
return;
focusing.value = true;
emit('select', index);
emit('select', node.id);
dom.value?.addEventListener('mousedown', dragstart, { passive: true });
}
@ -70,7 +69,7 @@ function editNode(e: Event) {
e.stopImmediatePropagation();
dom.value?.removeEventListener('mousedown', dragstart);
emit('edit', index);
emit('edit', node.id);
}
function resizeNode(e: MouseEvent, x: number, y: number, w: number, h: number) {
e.stopImmediatePropagation();
@ -88,7 +87,7 @@ function resizeNode(e: MouseEvent, x: number, y: number, w: number, h: number) {
const resizeend = (e: MouseEvent) => {
if(e.button !== 0)
return;
emit('resize', index, node.x - startx, node.y - starty, node.width - startw, node.height - starth);
emit('resize', node.id, node.x - startx, node.y - starty, node.width - startw, node.height - starth);
window.removeEventListener('mousemove', resizemove);
window.removeEventListener('mouseup', resizeend);
@ -123,7 +122,7 @@ const dragend = (e: MouseEvent) => {
window.removeEventListener('mouseup', dragend);
if(node.x - lastx !== 0 && node.y - lasty !== 0)
emit('move', index, node.x - lastx, node.y - lasty);
emit('move', node.id, node.x - lastx, node.y - lasty);
};
const dragstart = (e: MouseEvent) => {
if(e.button !== 0)
@ -135,7 +134,7 @@ const dragstart = (e: MouseEvent) => {
window.addEventListener('mouseup', dragend, { passive: true });
};
defineExpose({ unselect, dom });
defineExpose({ unselect, dom, ...node });
const style = computed(() => {
return node.color ? node.color?.class ?

Binary file not shown.