Copy readonly features from canvas editor to canvas reader.
This commit is contained in:
parent
af317cb0e3
commit
154584e175
|
|
@ -1,13 +1,19 @@
|
||||||
<script setup lang="ts">
|
<template>
|
||||||
import type { Direction, Path } from "~/shared/canvas.util";
|
<div class="absolute overflow-visible">
|
||||||
import type { CanvasColor } from "~/types/canvas";
|
<div v-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">{{ edge.label }}</div>
|
||||||
|
<svg class="absolute top-0 overflow-visible h-px w-px">
|
||||||
const props = defineProps<{
|
<g :style="{'--canvas-color': edge.color?.hex}" class="z-0">
|
||||||
path: Path;
|
<g :style="`transform: translate(${path!.to.x}px, ${path!.to.y}px) scale(var(--zoom-multiplier)) rotate(${rotation[path!.side]}deg);`">
|
||||||
color?: CanvasColor;
|
<polygon :class="style.fill" points="0,0 6.5,10.4 -6.5,10.4"></polygon>
|
||||||
label?: string;
|
</g>
|
||||||
}>();
|
<path :style="`stroke-width: calc(3px * var(--zoom-multiplier));`" style="stroke-linecap: butt;" :class="style.stroke" class="fill-none stroke-[4px]" :d="path!.path"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import type { Direction } from '#shared/canvas.util';
|
||||||
const rotation: Record<Direction, string> = {
|
const rotation: Record<Direction, string> = {
|
||||||
top: "180",
|
top: "180",
|
||||||
bottom: "0",
|
bottom: "0",
|
||||||
|
|
@ -15,12 +21,24 @@ const rotation: Record<Direction, string> = {
|
||||||
right: "270"
|
right: "270"
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getPath, labelCenter } from '#shared/canvas.util';
|
||||||
|
import type { CanvasEdge, CanvasNode } from '~/types/canvas';
|
||||||
|
|
||||||
<template>
|
const { edge, nodes } = defineProps<{
|
||||||
<g :style="{'--canvas-color': color?.hex}" class="z-0">
|
edge: CanvasEdge
|
||||||
<path :style="`stroke-linecap: butt; stroke-width: calc(3px * var(--zoom-multiplier));`" :class="color?.class ? `stroke-light-${color.class} dark:stroke-dark-${color.class}` : ((color && color?.hex !== undefined) ? 'stroke-[color:var(--canvas-color)]' : 'stroke-light-40 dark:stroke-dark-40')" class="fill-none stroke-[4px]" :d="path.path"></path>
|
nodes: CanvasNode[]
|
||||||
<g :style="`transform: translate(${path.to.x}px, ${path.to.y}px) scale(var(--zoom-multiplier)) rotate(${rotation[path.side]}deg);`">
|
}>();
|
||||||
<polygon :class="color?.class ? `fill-light-${color.class} dark:fill-dark-${color.class}` : ((color && color?.hex !== undefined) ? 'fill-[color:var(--canvas-color)]' : 'fill-light-40 dark:fill-dark-40')" points="0,0 6.5,10.4 -6.5,10.4"></polygon>
|
|
||||||
</g>
|
const from = computed(() => nodes!.find(f => f.id === edge.fromNode));
|
||||||
</g>
|
const to = computed(() => nodes!.find(f => f.id === edge.toNode));
|
||||||
</template>
|
const path = computed(() => getPath(from.value!, edge.fromSide, to.value!, edge.toSide));
|
||||||
|
const labelPos = computed(() => labelCenter(from.value!, edge.fromSide, to.value!, edge.toSide));
|
||||||
|
|
||||||
|
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}` } :
|
||||||
|
{ 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` }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -1,44 +1,28 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
|
||||||
import type { CanvasNode } from '~/types/canvas';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
node: CanvasNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
|
||||||
|
|
||||||
const size = Math.max(props.node.width, props.node.height);
|
|
||||||
const colors = computed(() => {
|
|
||||||
if(props.node.color)
|
|
||||||
{
|
|
||||||
const color = props.node.color;
|
|
||||||
return color?.class ? { bg: `bg-light-${color?.class} dark:bg-dark-${color?.class}`, border: `border-light-${color?.class} dark:border-dark-${color?.class}`} : { bg: `bg-colored`, border: `border-[color:var(--canvas-color)]` };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return { border: `border-light-40 dark:border-dark-40`, bg: `bg-light-40 dark:bg-dark-40` };
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bg-colored
|
|
||||||
{
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgba(from var(--canvas-color) r g b / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute" :style="{transform: `translate(${node.x}px, ${node.y}px)`, width: `${node.width}px`, height: `${node.height}px`, '--canvas-color': node.color?.hex}" :class="{'-z-10': node.type === 'group', 'z-10': node.type !== 'group'}">
|
<div class="absolute" :style="{transform: `translate(${node.x}px, ${node.y}px)`, width: `${node.width}px`, height: `${node.height}px`, '--canvas-color': node.color?.hex}" :class="{'-z-10': node.type === 'group', 'z-10': node.type !== 'group'}">
|
||||||
<div :class="[colors.border]" class="border-2 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full flex">
|
<div :class="[style.border]" class="outline-0 transition-[outline-width] border-2 bg-light-20 dark:bg-dark-20 w-full h-full hover:outline-4">
|
||||||
<div class="w-full h-full py-2 px-4 flex !bg-opacity-[0.07]" :class="colors.bg">
|
<div class="w-full h-full py-2 px-4 flex !bg-opacity-[0.07] overflow-auto" :class="style.bg">
|
||||||
<div v-if="node.text?.length > 0" class="flex items-center">
|
<div v-if="node.text?.length > 0" class="flex items-center">
|
||||||
<MarkdownRenderer :content="node.text" />
|
<MarkdownRenderer :content="node.text" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="node.type === 'group' && node.label !== undefined" :class="[colors.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="node.type === 'group' && node.label !== undefined" :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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { CanvasNode } from '~/types/canvas';
|
||||||
|
|
||||||
|
const { node } = defineProps<{
|
||||||
|
node: CanvasNode
|
||||||
|
zoom: number
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const style = computed(() => {
|
||||||
|
return node.color ? node.color?.class ?
|
||||||
|
{ bg: `bg-light-${node.color?.class} dark:bg-dark-${node.color?.class}`, border: `border-light-${node.color?.class} dark:border-dark-${node.color?.class}` } :
|
||||||
|
{ bg: `bg-colored`, border: `border-[color:var(--canvas-color)]` } :
|
||||||
|
{ border: `border-light-40 dark:border-dark-40`, bg: `bg-light-40 dark:bg-dark-40` }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -1,41 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none *:pointer-events-auto *:select-none" v-if="canvas">
|
|
||||||
<div>
|
|
||||||
<CanvasNode v-for="node of canvas.nodes" :key="node.id" :node="node" />
|
|
||||||
</div>
|
|
||||||
<template v-for="edge of canvas.edges">
|
|
||||||
<div :key="edge.id" v-if="edge.label" class="absolute z-10"
|
|
||||||
:style="{ transform: labelCenter(getNode(canvas.nodes, edge.fromNode)!, edge.fromSide, getNode(canvas.nodes, edge.toNode)!, edge.toSide) }">
|
|
||||||
<div class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 -translate-x-[50%] -translate-y-[50%]">{{ edge.label }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<svg class="absolute top-0 left-0 overflow-visible w-full h-full origin-top pointer-events-none">
|
|
||||||
<CanvasEdge v-for="edge of canvas.edges" :key="edge.id"
|
|
||||||
:path="getPath(getNode(canvas.nodes, edge.fromNode)!, edge.fromSide, getNode(canvas.nodes, edge.toNode)!, edge.toSide)!"
|
|
||||||
:color="edge.color" :label="edge.label" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { labelCenter, getNode, getPath } from '#shared/canvas.util';
|
|
||||||
import type { CanvasContent } from '~/types/content';
|
|
||||||
import type { CanvasContent as Canvas } from '~/types/canvas';
|
|
||||||
|
|
||||||
const { path } = defineProps<{
|
|
||||||
path: string
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const { content, get } = useContent();
|
|
||||||
const overview = computed(() => content.value.find(e => e.path === path) as CanvasContent | undefined);
|
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
if(overview.value && !overview.value.content)
|
|
||||||
{
|
|
||||||
loading.value = true;
|
|
||||||
await get(path);
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
const canvas = computed(() => overview.value && overview.value.content ? overview.value.content : undefined);
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
@ -1,23 +1,26 @@
|
||||||
<script setup lang="ts">
|
<script lang="ts">
|
||||||
import { useDrag, useHover, usePinch, useWheel } from '@vueuse/gesture';
|
import { type Position } from '#shared/canvas.util';
|
||||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
import { hasPermissions } from '~/shared/auth.util';
|
||||||
import { clamp } from '#shared/general.util';
|
|
||||||
|
|
||||||
const { path } = defineProps<{ path: string }>();
|
const cancelEvent = (e: Event) => e.preventDefault();
|
||||||
const { user } = useUserSession();
|
function center(touches: TouchList): Position
|
||||||
const { content } = useContent();
|
{
|
||||||
const overview = computed(() => content.value.find(e => e.path === path));
|
const pos = { x: 0, y: 0 };
|
||||||
|
|
||||||
const isOwner = computed(() => user.value?.id === overview.value?.owner);
|
for(const touch of touches)
|
||||||
|
{
|
||||||
|
pos.x += touch.clientX;
|
||||||
|
pos.y += touch.clientY;
|
||||||
|
}
|
||||||
|
|
||||||
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
pos.x /= touches.length;
|
||||||
const canvasRef = useTemplateRef('canvasRef');
|
pos.y /= touches.length;
|
||||||
|
return pos;
|
||||||
const reset = (_: MouseEvent) => {
|
}
|
||||||
zoom.value = minZoom.value;
|
function distance(touches: TouchList): number
|
||||||
|
{
|
||||||
dispX.value = 0;
|
const [A, B] = touches;
|
||||||
dispY.value = 0;
|
return Math.hypot(B.clientX - A.clientX, B.clientY - A.clientY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -84,40 +87,157 @@ dark:outline-dark-cyan
|
||||||
dark:outline-dark-purple
|
dark:outline-dark-purple
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
</script>
|
||||||
|
|
||||||
const cancelEvent = (e: Event) => e.preventDefault()
|
<script setup lang="ts">
|
||||||
useHover(({ hovering }) => {
|
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||||
if (!hovering) {
|
import { clamp } from '#shared/general.util';
|
||||||
//@ts-ignore
|
import type { CanvasContent } from '~/types/content';
|
||||||
window.removeEventListener('wheel', cancelEvent, { passive: false });
|
|
||||||
document.removeEventListener('gesturestart', cancelEvent)
|
const { path } = defineProps<{
|
||||||
document.removeEventListener('gesturechange', cancelEvent)
|
path: string
|
||||||
return
|
}>();
|
||||||
|
|
||||||
|
const { user } = useUserSession();
|
||||||
|
const { content, get } = useContent();
|
||||||
|
const overview = computed(() => content.value.find(e => e.path === path) as CanvasContent | undefined);
|
||||||
|
const isOwner = computed(() => user.value?.id === overview.value?.owner);
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
if(overview.value && !overview.value.content)
|
||||||
|
{
|
||||||
|
loading.value = true;
|
||||||
|
await get(path);
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
const canvas = computed(() => overview.value && overview.value.content ? overview.value.content : undefined);
|
||||||
|
|
||||||
|
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
||||||
|
const canvasRef = useTemplateRef('canvasRef'), transformRef = useTemplateRef('transformRef');
|
||||||
|
|
||||||
|
const reset = (_: MouseEvent) => {
|
||||||
|
zoom.value = minZoom.value;
|
||||||
|
|
||||||
|
dispX.value = 0;
|
||||||
|
dispY.value = 0;
|
||||||
|
|
||||||
|
updateTransform();
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
let lastX = 0, lastY = 0, lastDistance = 0;
|
||||||
|
let box = canvasRef.value?.getBoundingClientRect()!;
|
||||||
|
const dragMove = (e: MouseEvent) => {
|
||||||
|
dispX.value = dispX.value - (lastX - e.clientX) / zoom.value;
|
||||||
|
dispY.value = dispY.value - (lastY - e.clientY) / zoom.value;
|
||||||
|
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
|
||||||
|
updateTransform();
|
||||||
|
};
|
||||||
|
const dragEnd = (e: MouseEvent) => {
|
||||||
|
window.removeEventListener('mouseup', dragEnd);
|
||||||
|
window.removeEventListener('mousemove', dragMove);
|
||||||
|
};
|
||||||
|
canvasRef.value?.addEventListener('mouseenter', () => {
|
||||||
|
window.addEventListener('wheel', cancelEvent, { passive: false });
|
||||||
|
document.addEventListener('gesturestart', cancelEvent);
|
||||||
|
document.addEventListener('gesturechange', cancelEvent);
|
||||||
|
|
||||||
|
canvasRef.value?.addEventListener('mouseleave', () => {
|
||||||
|
window.removeEventListener('wheel', cancelEvent);
|
||||||
|
document.removeEventListener('gesturestart', cancelEvent);
|
||||||
|
document.removeEventListener('gesturechange', cancelEvent);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
window.addEventListener('resize', () => box = canvasRef.value?.getBoundingClientRect()!);
|
||||||
|
canvasRef.value?.addEventListener('mousedown', (e) => {
|
||||||
|
if(e.button === 1)
|
||||||
|
{
|
||||||
|
lastX = e.clientX;
|
||||||
|
lastY = e.clientY;
|
||||||
|
|
||||||
|
window.addEventListener('mouseup', dragEnd, { passive: true });
|
||||||
|
window.addEventListener('mousemove', dragMove, { passive: true });
|
||||||
|
}
|
||||||
|
}, { passive: true });
|
||||||
|
canvasRef.value?.addEventListener('wheel', (e) => {
|
||||||
|
if((zoom.value >= 3 && e.deltaY < 0) || (zoom.value <= minZoom.value && e.deltaY > 0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const diff = Math.exp(e.deltaY * -0.001);
|
||||||
|
const centerX = (box.x + box.width / 2), centerY = (box.y + box.height / 2);
|
||||||
|
const mousex = centerX - e.clientX, mousey = centerY - e.clientY;
|
||||||
|
|
||||||
|
dispX.value = dispX.value - (mousex / (diff * zoom.value) - mousex / zoom.value);
|
||||||
|
dispY.value = dispY.value - (mousey / (diff * zoom.value) - mousey / zoom.value);
|
||||||
|
|
||||||
|
zoom.value = clamp(zoom.value * diff, minZoom.value, 3)
|
||||||
|
|
||||||
|
updateTransform();
|
||||||
|
}, { passive: true });
|
||||||
|
canvasRef.value?.addEventListener('touchstart', (e) => {
|
||||||
|
({ x: lastX, y: lastY } = center(e.touches));
|
||||||
|
|
||||||
|
if(e.touches.length > 1)
|
||||||
|
{
|
||||||
|
lastDistance = distance(e.touches);
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasRef.value?.addEventListener('touchend', touchend, { passive: true });
|
||||||
|
canvasRef.value?.addEventListener('touchcancel', touchcancel, { passive: true });
|
||||||
|
canvasRef.value?.addEventListener('touchmove', touchmove, { passive: true });
|
||||||
|
}, { passive: true });
|
||||||
|
const touchend = (e: TouchEvent) => {
|
||||||
|
if(e.touches.length > 1)
|
||||||
|
{
|
||||||
|
({ x: lastX, y: lastY } = center(e.touches));
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasRef.value?.removeEventListener('touchend', touchend);
|
||||||
|
canvasRef.value?.removeEventListener('touchcancel', touchcancel);
|
||||||
|
canvasRef.value?.removeEventListener('touchmove', touchmove);
|
||||||
|
};
|
||||||
|
const touchcancel = (e: TouchEvent) => {
|
||||||
|
if(e.touches.length > 1)
|
||||||
|
{
|
||||||
|
({ x: lastX, y: lastY } = center(e.touches));
|
||||||
|
}
|
||||||
|
|
||||||
|
canvasRef.value?.removeEventListener('touchend', touchend);
|
||||||
|
canvasRef.value?.removeEventListener('touchcancel', touchcancel);
|
||||||
|
canvasRef.value?.removeEventListener('touchmove', touchmove);
|
||||||
|
};
|
||||||
|
const touchmove = (e: TouchEvent) => {
|
||||||
|
const pos = center(e.touches);
|
||||||
|
dispX.value = dispX.value - (lastX - pos.x) / zoom.value;
|
||||||
|
dispY.value = dispY.value - (lastY - pos.y) / zoom.value;
|
||||||
|
lastX = pos.x;
|
||||||
|
lastY = pos.y;
|
||||||
|
|
||||||
|
if(e.touches.length === 2)
|
||||||
|
{
|
||||||
|
const dist = distance(e.touches);
|
||||||
|
const diff = dist / lastDistance;
|
||||||
|
|
||||||
|
zoom.value = clamp(zoom.value * diff, minZoom.value, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTransform();
|
||||||
|
};
|
||||||
|
|
||||||
|
updateTransform();
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateTransform()
|
||||||
|
{
|
||||||
|
if(transformRef.value)
|
||||||
|
{
|
||||||
|
transformRef.value.style.transform = `scale3d(${zoom.value}, ${zoom.value}, 1) translate3d(${dispX.value}px, ${dispY.value}px, 0)`;
|
||||||
|
transformRef.value.style.setProperty('--tw-scale', zoom.value.toString());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
window.addEventListener('wheel', cancelEvent, { passive: false });
|
|
||||||
document.addEventListener('gesturestart', cancelEvent)
|
|
||||||
document.addEventListener('gesturechange', cancelEvent)
|
|
||||||
}, {
|
|
||||||
domTarget: canvasRef,
|
|
||||||
});
|
|
||||||
|
|
||||||
const dragHandler = useDrag(({ delta: [x, y] }: { delta: number[] }) => {
|
|
||||||
dispX.value += x / zoom.value;
|
|
||||||
dispY.value += y / zoom.value;
|
|
||||||
}, {
|
|
||||||
domTarget: canvasRef,
|
|
||||||
});
|
|
||||||
const wheelHandler = useWheel(({ delta: [x, y] }: { delta: number[] }) => {
|
|
||||||
zoom.value = clamp(zoom.value + y * -0.001, minZoom.value, 3);
|
|
||||||
}, {
|
|
||||||
domTarget: canvasRef,
|
|
||||||
});
|
|
||||||
const pinchHandler = usePinch(({ offset: [z] }: { offset: number[] }) => {
|
|
||||||
zoom.value = clamp(z / 2048, minZoom.value, 3);
|
|
||||||
}, {
|
|
||||||
domTarget: canvasRef,
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -125,12 +245,12 @@ const pinchHandler = usePinch(({ offset: [z] }: { offset: number[] }) => {
|
||||||
<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">
|
||||||
<div @click="zoom = clamp(zoom * 1.1, minZoom, 3)" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
<div @click="zoom = clamp(zoom * 1.1, minZoom, 3); updateTransform()" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
||||||
<Icon icon="radix-icons:plus" />
|
<Icon icon="radix-icons:plus" />
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip message="Reset" side="right">
|
<Tooltip message="Reset" side="right">
|
||||||
<div @click="zoom = 1" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
<div @click="zoom = 1; updateTransform();" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
||||||
<Icon icon="radix-icons:reload" />
|
<Icon icon="radix-icons:reload" />
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
@ -140,27 +260,28 @@ const pinchHandler = usePinch(({ offset: [z] }: { offset: number[] }) => {
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip message="Zoom arrière" side="right">
|
<Tooltip message="Zoom arrière" side="right">
|
||||||
<div @click="zoom = clamp(zoom * 0.9, minZoom, 3)" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
<div @click="zoom = clamp(zoom / 1.1, minZoom, 3); updateTransform()" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
||||||
<Icon icon="radix-icons:minus" />
|
<Icon icon="radix-icons:minus" />
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10" v-if="isOwner">
|
<NuxtLink v-if="overview && isOwner || hasPermissions(user?.permissions ?? [], ['admin', 'editor'])" :to="{ name: 'explore-edit', hash: `#${encodeURIComponent(overview!.path)}` }" class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10">
|
||||||
<Tooltip message="Modifier" side="right">
|
<Tooltip message="Modifier" side="right">
|
||||||
<NuxtLink :to="{ name: 'explore-edit', hash: '#' + path }" class="w-8 h-8 flex justify-center items-center p-2 hover:bg-light-30 dark:hover:bg-dark-30 cursor-pointer">
|
<Icon icon="radix-icons:pencil-1" class="w-8 h-8 p-2" />
|
||||||
<Icon icon="radix-icons:pencil-1" />
|
|
||||||
</NuxtLink>
|
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
<div :style="{
|
<div ref="transformRef" :style="{
|
||||||
'--tw-translate-x': `${dispX}px`,
|
'transform-origin': 'center center',
|
||||||
'--tw-translate-y': `${dispY}px`,
|
}" class="h-full">
|
||||||
'--tw-scale-x': `${zoom}`,
|
<div v-if="canvas" class="absolute top-0 left-0 w-full h-full pointer-events-none *:pointer-events-auto *:select-none touch-none">
|
||||||
'--tw-scale-y': `${zoom}`,
|
<div>
|
||||||
transform: 'scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) translate3d(var(--tw-translate-x), var(--tw-translate-y), 0)'
|
<CanvasNode v-for="node of canvas.nodes" :key="node.id" ref="nodes" :node="node" :zoom="zoom" />
|
||||||
}">
|
</div>
|
||||||
<CanvasRenderer :path="path" />
|
<div>
|
||||||
|
<CanvasEdge v-for="edge of canvas.edges" :key="edge.id" ref="edges" :edge="edge" :nodes="canvas.nodes!" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { hasPermissions } from '~/shared/auth.util';
|
||||||
|
|
||||||
|
|
||||||
const { path } = defineProps<{
|
const { path } = defineProps<{
|
||||||
path: string
|
path: string
|
||||||
|
|
@ -26,7 +28,7 @@ if(overview.value && !overview.value.content)
|
||||||
<div v-if="!popover" class="flex flex-1 flex-row justify-between items-center">
|
<div v-if="!popover" class="flex flex-1 flex-row justify-between items-center">
|
||||||
<ProseH1>{{ overview.title }}</ProseH1>
|
<ProseH1>{{ overview.title }}</ProseH1>
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<NuxtLink :href="{ name: 'explore-edit', hash: '#' + overview.path }" v-if="isOwner"><Button>Modifier</Button></NuxtLink>
|
<NuxtLink :href="{ name: 'explore-edit', hash: '#' + overview.path }" v-if="isOwner || hasPermissions(user?.permissions ?? [], ['admin', 'editor'])"><Button>Modifier</Button></NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MarkdownRenderer v-if="overview.content" :content="overview.content" :filter="filter" />
|
<MarkdownRenderer v-if="overview.content" :content="overview.content" :filter="filter" />
|
||||||
|
|
|
||||||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
Loading…
Reference in New Issue