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"> <script lang="ts">
import type { Box, Position } from '#shared/canvas.util'; import type { Box, Position } from '#shared/canvas.util';
import type CanvasNodeEditor from './canvas/CanvasNodeEditor.vue';
type Direction = 'bottom' | 'top' | 'left' | 'right'; type Direction = 'bottom' | 'top' | 'left' | 'right';
const rotation: Record<Direction, string> = { const rotation: Record<Direction, string> = {
top: "180", top: "180",
@ -24,7 +25,7 @@ interface HistoryEvent<T extends Action = Action>
} }
interface HistoryAction<T extends Action> interface HistoryAction<T extends Action>
{ {
element: number; element: string;
from: ActionMap[T]; from: ActionMap[T];
to: ActionMap[T]; to: ActionMap[T];
} }
@ -71,9 +72,11 @@ import { labelCenter, getPath } from '#shared/canvas.util';
const canvas = defineModel<CanvasContent>({ required: true, }); const canvas = defineModel<CanvasContent>({ required: true, });
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<number>(), editing = ref<number>(); const focusing = ref<string>(), editing = ref<string>();
const canvasRef = useTemplateRef('canvasRef'); 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(() => { const edges = computed(() => {
return canvas.value.edges.map(e => { return canvas.value.edges.map(e => {
@ -83,8 +86,6 @@ const edges = computed(() => {
}); });
}); });
const history = ref<HistoryEvent[]>([]); const history = ref<HistoryEvent[]>([]);
const historyPos = ref(-1); const historyPos = ref(-1);
const historyCursor = computed(() => history.value.length > 0 && historyPos.value > -1 ? history.value[historyPos.value] : undefined); 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'>[] = []; 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); 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'>[] = []; 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); addAction('resize', actions);
} }
function selectNode(index: number) function selectNode(id: string)
{ {
if(focusing.value !== index) if(focusing.value !== id)
{ {
unselectNode(); unselectNode();
} }
nodes.value![index]?.dom?.addEventListener('click', stopPropagation, { passive: true }); focusing.value = id;
canvasRef.value?.addEventListener('click', unselectNode, { once: true });
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 }); editing.value = id;
nodes.value![index]?.dom?.addEventListener('dblclick', stopPropagation, { passive: true });
canvasRef.value?.addEventListener('click', unselectNode, { once: true });
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()!; 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(); unselectNode();
for(const i of index) for(const id of ids)
{ {
const [node] = canvas.value.nodes.splice(i, 1); const index = canvas.value.nodes.findIndex(e => e.id === id);
actions.push({ element: i, from: node, to: undefined }); 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'>[] = []; 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; const copy = JSON.parse(JSON.stringify(canvas.value.nodes.find(e => e.id === id)!)) as CanvasNode;
canvas.value.nodes[i][property] = value; canvas.value.nodes.find(e => e.id === id)![property] = value;
actions.push({ element: i, from: copy, to: canvas.value.nodes[i] }); actions.push({ element: id, from: copy, to: canvas.value.nodes.find(e => e.id === id)! });
} }
addAction('property', actions); addAction('property', actions);
@ -280,17 +284,17 @@ function editNodeProperty<T extends keyof CanvasNode>(index: number[], property:
const unselectNode = () => { const unselectNode = () => {
if(focusing.value !== undefined) if(focusing.value !== undefined)
{ {
nodes.value![focusing.value]?.dom?.removeEventListener('click', stopPropagation); focusedNode.value?.dom?.removeEventListener('click', stopPropagation);
nodes.value![focusing.value]?.unselect(); focusedNode.value?.unselect();
} }
focusing.value = undefined; focusing.value = undefined;
if(editing.value !== undefined) if(editing.value !== undefined)
{ {
nodes.value![editing.value]?.dom?.removeEventListener('wheel', stopPropagation); editedNode.value?.dom?.removeEventListener('wheel', stopPropagation);
nodes.value![editing.value]?.dom?.removeEventListener('dblclick', stopPropagation); editedNode.value?.dom?.removeEventListener('dblclick', stopPropagation);
nodes.value![editing.value]?.dom?.removeEventListener('click', stopPropagation); editedNode.value?.dom?.removeEventListener('click', stopPropagation);
nodes.value![editing.value]?.unselect(); editedNode.value?.unselect();
} }
editing.value = undefined; editing.value = undefined;
}; };
@ -300,40 +304,49 @@ const undo = () => {
for(const action of historyCursor.value.actions) for(const action of historyCursor.value.actions)
{ {
const node = canvas.value.nodes.find(e => e.id === action.element)!;
switch(historyCursor.value.event) switch(historyCursor.value.event)
{ {
case 'move': case 'move':
{ {
const a = action as HistoryAction<'move'>; const a = action as HistoryAction<'move'>;
canvas.value.nodes[action.element].x = a.from.x; node.x = a.from.x;
canvas.value.nodes[action.element].y = a.from.y; node.y = a.from.y;
break; break;
} }
case 'resize': case 'resize':
{ {
const a = action as HistoryAction<'resize'>; const a = action as HistoryAction<'resize'>;
canvas.value.nodes[action.element].x = a.from.x; node.x = a.from.x;
canvas.value.nodes[action.element].y = a.from.y; node.y = a.from.y;
canvas.value.nodes[action.element].width = a.from.w; node.width = a.from.w;
canvas.value.nodes[action.element].height = a.from.h; node.height = a.from.h;
break; break;
} }
case 'edit': case 'edit':
{ {
const a = action as HistoryAction<'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; break;
} }
case 'remove': case 'remove':
{ {
const a = action as HistoryAction<'remove'>; const a = action as HistoryAction<'remove'>;
canvas.value.nodes.splice(action.element, 0, a.from!); canvas.value.nodes.push(a.from!);
break; break;
} }
case 'property': case 'property':
{ {
const a = action as HistoryAction<'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; break;
} }
} }
@ -357,40 +370,49 @@ const redo = () => {
for(const action of historyCursor.value.actions) for(const action of historyCursor.value.actions)
{ {
const node = canvas.value.nodes.find(e => e.id === action.element)!;
switch(historyCursor.value.event) switch(historyCursor.value.event)
{ {
case 'move': case 'move':
{ {
const a = action as HistoryAction<'move'>; const a = action as HistoryAction<'move'>;
canvas.value.nodes[action.element].x = a.to.x; node.x = a.to.x;
canvas.value.nodes[action.element].y = a.to.y; node.y = a.to.y;
break; break;
} }
case 'resize': case 'resize':
{ {
const a = action as HistoryAction<'resize'>; const a = action as HistoryAction<'resize'>;
canvas.value.nodes[action.element].x = a.to.x; node.x = a.to.x;
canvas.value.nodes[action.element].y = a.to.y; node.y = a.to.y;
canvas.value.nodes[action.element].width = a.to.w; node.width = a.to.w;
canvas.value.nodes[action.element].height = a.to.h; node.height = a.to.h;
break; break;
} }
case 'edit': case 'edit':
{ {
const a = action as HistoryAction<'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; break;
} }
case 'remove': case 'remove':
{ {
const a = action as HistoryAction<'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; break;
} }
case 'property': case 'property':
{ {
const a = action as HistoryAction<'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; break;
} }
} }
@ -407,7 +429,7 @@ useShortcuts({
</script> </script>
<template> <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="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"> <div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10">
<Tooltip message="Zoom avant" side="right"> <Tooltip message="Zoom avant" side="right">
@ -483,7 +505,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 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"> <div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 flex flex-row">
<PopoverRoot> <PopoverRoot>
<PopoverTrigger asChild> <PopoverTrigger asChild>

View File

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

Binary file not shown.