98 lines
4.7 KiB
Vue
98 lines
4.7 KiB
Vue
<template>
|
|
<div class="absolute overflow-visible group">
|
|
<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>
|
|
<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="`transform: translate(${path.to.x}px, ${path.to.y}px) scale(var(--zoom-multiplier)) rotate(${rotation[path.side]}deg);`">
|
|
<polygon :class="style.fill" points="0,0 6.5,10.4 -6.5,10.4"></polygon>
|
|
</g>
|
|
<path :style="`stroke-width: calc(${focusing ? 6 : 3}px * var(--zoom-multiplier));`" style="stroke-linecap: butt;" :class="style.stroke" class="transition-[stroke-width] fill-none stroke-[4px]" :d="path.path"></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>
|
|
</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>
|
|
</template>
|
|
|
|
<style>
|
|
.fill-colored
|
|
{
|
|
--tw-bg-opacity: 1;
|
|
fill: rgba(from var(--canvas-color) r g b / var(--tw-bg-opacity));
|
|
}
|
|
</style>
|
|
<script setup lang="ts">
|
|
import { getPath, labelCenter, rotation } from '#shared/canvas.util';
|
|
import type { Element } from '../CanvasEditor.vue';
|
|
import type { CanvasEdge, CanvasNode } from '~/types/canvas';
|
|
|
|
const { edge, nodes } = defineProps<{
|
|
edge: CanvasEdge
|
|
nodes: CanvasNode[]
|
|
}>();
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'select', id: Element): void,
|
|
(e: 'edit', id: Element): void,
|
|
(e: 'drag', id: string, _e: MouseEvent, origin: 'from' | 'to'): void,
|
|
(e: 'input', id: string, text?: string): void,
|
|
}>();
|
|
|
|
const dom = useTemplateRef('dom');
|
|
const focusing = ref(false), editing = ref(false);
|
|
|
|
const from = computed(() => nodes!.find(f => f.id === edge.fromNode));
|
|
const to = computed(() => nodes!.find(f => f.id === edge.toNode));
|
|
const path = computed(() => getPath(from.value!, edge.fromSide, to.value!, edge.toSide)!);
|
|
const labelPos = computed(() => labelCenter(from.value!, edge.fromSide, to.value!, edge.toSide));
|
|
|
|
let oldText = edge.label;
|
|
|
|
function select(e: Event) {
|
|
if(editing.value)
|
|
return;
|
|
|
|
focusing.value = true;
|
|
emit('select', { type: 'edge', id: edge.id });
|
|
}
|
|
function edit(e: Event) {
|
|
oldText = edge.label;
|
|
|
|
focusing.value = true;
|
|
editing.value = true;
|
|
|
|
e.stopImmediatePropagation();
|
|
emit('edit', { type: 'edge', id: edge.id });
|
|
}
|
|
function dragEdge(e: MouseEvent, origin: 'from' | 'to') {
|
|
e.stopImmediatePropagation();
|
|
|
|
emit('drag', edge.id, e, origin);
|
|
}
|
|
function unselect() {
|
|
if(editing.value)
|
|
{
|
|
const text = edge.label;
|
|
|
|
if(text !== oldText)
|
|
{
|
|
edge.label = oldText;
|
|
|
|
emit('input', edge.id, text);
|
|
}
|
|
}
|
|
focusing.value = false;
|
|
editing.value = false;
|
|
}
|
|
|
|
defineExpose({ unselect, dom, id: edge.id, path });
|
|
|
|
const style = computed(() => {
|
|
return 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}`, outline: `outline-light-${edge.color?.class} dark:outline-dark-${edge.color?.class}` } :
|
|
{ fill: `fill-colored`, stroke: `stroke-[color:var(--canvas-color)]`, outline: `outline-[color:var(--canvas-color)]` } :
|
|
{ stroke: `stroke-light-40 dark:stroke-dark-40`, fill: `fill-light-40 dark:fill-dark-40`, outline: `outline-light-40 dark:outline-dark-40` }
|
|
});
|
|
</script> |