You've already forked obsidian-visualiser
Change explorer pages structure to isolates them from there renderers allowing to fetch data outside of the renderers
This commit is contained in:
@@ -1,216 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useDrag, usePinch, useWheel } from '@vueuse/gesture';
|
||||
import type { CanvasContent, CanvasNode } from '~/types/canvas';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { clamp } from '#shared/general.utils';
|
||||
|
||||
interface Props
|
||||
{
|
||||
canvas: CanvasContent;
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(0.5);
|
||||
const canvas = useTemplateRef('canvas');
|
||||
|
||||
const reset = (_: MouseEvent) => {
|
||||
zoom.value = minZoom.value;
|
||||
|
||||
dispX.value = 0;
|
||||
dispY.value = 0;
|
||||
}
|
||||
function edgePos(side: 'bottom' | 'top' | 'left' | 'right', pos: { x: number, y: number }, offset: number): { x: number, y: number } {
|
||||
switch (side) {
|
||||
case "left":
|
||||
return {
|
||||
x: pos.x - offset,
|
||||
y: pos.y
|
||||
};
|
||||
case "right":
|
||||
return {
|
||||
x: pos.x + offset,
|
||||
y: pos.y
|
||||
};
|
||||
case "top":
|
||||
return {
|
||||
x: pos.x,
|
||||
y: pos.y - offset
|
||||
};
|
||||
case "bottom":
|
||||
return {
|
||||
x: pos.x,
|
||||
y: pos.y + offset
|
||||
}
|
||||
}
|
||||
}
|
||||
function getNode(id: string): CanvasNode | undefined
|
||||
{
|
||||
return props.canvas.nodes.find(e => e.id === id);
|
||||
}
|
||||
function posFromDir(e: { minX: number, minY: number, maxX: number, maxY: number }, t: 'bottom' | 'top' | 'left' | 'right'): { x: number, y: number } {
|
||||
switch (t) {
|
||||
case "top":
|
||||
return { x: (e.minX + e.maxX) / 2, y: e.minY };
|
||||
case "right":
|
||||
return { x: e.maxX, y: (e.minY + e.maxY) / 2 };
|
||||
case "bottom":
|
||||
return { x: (e.minX + e.maxX) / 2, y: e.maxY };
|
||||
case "left":
|
||||
return { x: e.minX, y: (e.minY + e.maxY) / 2 };
|
||||
}
|
||||
}
|
||||
function getBbox(node: CanvasNode): { minX: number, minY: number, maxX: number, maxY: number } {
|
||||
return { minX: node.x, minY: node.y, maxX: node.x + node.width, maxY: node.y + node.height };
|
||||
}
|
||||
function path(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
||||
if(from === undefined || to === undefined)
|
||||
{
|
||||
return {
|
||||
path: '',
|
||||
from: {},
|
||||
to: {},
|
||||
toSide: '',
|
||||
}
|
||||
}
|
||||
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
||||
return bezier(start, fromSide, end, toSide);
|
||||
}
|
||||
function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'left' | 'right', to: { x: number, y: number }, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
||||
const r = Math.hypot(from.x - to.x, from.y - to.y), o = clamp(r / 2, 70, 150), a = edgePos(fromSide, from, o), s = edgePos(toSide, to, o);
|
||||
return {
|
||||
path: `M${from.x},${from.y} C${a.x},${a.y} ${s.x},${s.y} ${to.x},${to.y}`,
|
||||
from: from,
|
||||
to: to,
|
||||
side: toSide,
|
||||
};
|
||||
}
|
||||
function labelCenter(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): string {
|
||||
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
||||
const len = Math.hypot(start.x - end.x, start.y - end.y), offset = clamp(len / 2, 70, 150), b = edgePos(fromSide, start, offset), s = edgePos(toSide, end, offset);
|
||||
const center = getCenter(start, end, b, s, 0.5);
|
||||
return `translate(${center.x}px, ${center.y}px)`;
|
||||
}
|
||||
function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r: { x: number, y: number }, o: { x: number, y: number }, e: number): { x: number, y: number } {
|
||||
const a = 1 - e, s = a * a * a, l = 3 * e * a * a, c = 3 * e * e * a, u = e * e * e;
|
||||
return {
|
||||
x: s * n.x + l * r.x + c * o.x + u * i.x,
|
||||
y: s * n.y + l * r.y + c * o.y + u * i.y
|
||||
};
|
||||
}
|
||||
/*
|
||||
|
||||
stroke-light-red
|
||||
stroke-light-orange
|
||||
stroke-light-yellow
|
||||
stroke-light-green
|
||||
stroke-light-cyan
|
||||
stroke-light-purple
|
||||
dark:stroke-dark-red
|
||||
dark:stroke-dark-orange
|
||||
dark:stroke-dark-yellow
|
||||
dark:stroke-dark-green
|
||||
dark:stroke-dark-cyan
|
||||
dark:stroke-dark-purple
|
||||
fill-light-red
|
||||
fill-light-orange
|
||||
fill-light-yellow
|
||||
fill-light-green
|
||||
fill-light-cyan
|
||||
fill-light-purple
|
||||
dark:fill-dark-red
|
||||
dark:fill-dark-orange
|
||||
dark:fill-dark-yellow
|
||||
dark:fill-dark-green
|
||||
dark:fill-dark-cyan
|
||||
dark:fill-dark-purple
|
||||
bg-light-red
|
||||
bg-light-orange
|
||||
bg-light-yellow
|
||||
bg-light-green
|
||||
bg-light-cyan
|
||||
bg-light-purple
|
||||
dark:bg-dark-red
|
||||
dark:bg-dark-orange
|
||||
dark:bg-dark-yellow
|
||||
dark:bg-dark-green
|
||||
dark:bg-dark-cyan
|
||||
dark:bg-dark-purple
|
||||
border-light-red
|
||||
border-light-orange
|
||||
border-light-yellow
|
||||
border-light-green
|
||||
border-light-cyan
|
||||
border-light-purple
|
||||
dark:border-dark-red
|
||||
dark:border-dark-orange
|
||||
dark:border-dark-yellow
|
||||
dark:border-dark-green
|
||||
dark:border-dark-cyan
|
||||
dark:border-dark-purple
|
||||
|
||||
*/
|
||||
|
||||
const pinchHandler = usePinch(({ event, offset: [z] }: { event: Event, offset: number[] }) => {
|
||||
zoom.value = clamp(z / 2048, minZoom.value, 3);
|
||||
}, {
|
||||
domTarget: canvas,
|
||||
})
|
||||
const dragHandler = useDrag(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
||||
dispX.value += x / zoom.value;
|
||||
dispY.value += y / zoom.value;
|
||||
}, {
|
||||
domTarget: canvas,
|
||||
})
|
||||
const wheelHandler = useWheel(({ event, delta: [x, y] }: { event: Event, delta: number[] }) => {
|
||||
zoom.value = clamp(zoom.value + y * -0.001, minZoom.value, 3);
|
||||
}, {
|
||||
domTarget: canvas,
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div id="canvas" ref="canvas" class="absolute top-0 left-0 overflow-hidden w-full h-full touch-none"
|
||||
:style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }">
|
||||
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden">
|
||||
<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">
|
||||
<Icon icon="radix-icons:plus" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<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">
|
||||
<Icon icon="radix-icons:reload" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<Tooltip message="Tout contenir" side="right">
|
||||
<div @click="reset" 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:corners" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
<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">
|
||||
<Icon icon="radix-icons:minus" />
|
||||
</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none *:pointer-events-auto *:select-none"
|
||||
:style="{transform: `scale(${zoom}) translate(${dispX}px, ${dispY}px)`}">
|
||||
<div>
|
||||
<CanvasNode v-for="node of props.canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
|
||||
</div>
|
||||
<template v-for="edge of props.canvas.edges">
|
||||
<div :key="edge.id" v-if="edge.label" class="absolute z-10"
|
||||
:style="{ transform: labelCenter(getNode(edge.fromNode)!, edge.fromSide, getNode(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 props.canvas.edges" :key="edge.id"
|
||||
:path="path(getNode(edge.fromNode)!, edge.fromSide, getNode(edge.toNode)!, edge.toSide)"
|
||||
:color="edge.color" :label="edge.label" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -37,7 +37,7 @@ const colors = computed(() => {
|
||||
<div class="w-full h-full py-2 px-4 flex !bg-opacity-[0.07]" :class="colors.bg">
|
||||
<template v-if="node.type === 'group' || zoom > Math.min(0.4, 1000 / size)">
|
||||
<div v-if="node.text?.length > 0" class="flex items-center">
|
||||
<Markdown :content="node.text" />
|
||||
<MarkdownRenderer :content="node.text" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
109
components/canvas/CanvasRenderer.vue
Normal file
109
components/canvas/CanvasRenderer.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none *:pointer-events-auto *:select-none">
|
||||
<div>
|
||||
<CanvasNode v-for="node of canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
|
||||
</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="path(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">
|
||||
const { canvas, zoom } = defineProps<{
|
||||
canvas: CanvasContent
|
||||
zoom: number
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { clamp } from '~/shared/general.utils';
|
||||
import type { CanvasContent, CanvasNode } from '~/types/canvas';
|
||||
|
||||
function edgePos(side: 'bottom' | 'top' | 'left' | 'right', pos: { x: number, y: number }, offset: number): { x: number, y: number } {
|
||||
switch (side) {
|
||||
case "left":
|
||||
return {
|
||||
x: pos.x - offset,
|
||||
y: pos.y
|
||||
};
|
||||
case "right":
|
||||
return {
|
||||
x: pos.x + offset,
|
||||
y: pos.y
|
||||
};
|
||||
case "top":
|
||||
return {
|
||||
x: pos.x,
|
||||
y: pos.y - offset
|
||||
};
|
||||
case "bottom":
|
||||
return {
|
||||
x: pos.x,
|
||||
y: pos.y + offset
|
||||
}
|
||||
}
|
||||
}
|
||||
function getNode(nodes: CanvasNode[], id: string): CanvasNode | undefined
|
||||
{
|
||||
return nodes.find(e => e.id === id);
|
||||
}
|
||||
function posFromDir(e: { minX: number, minY: number, maxX: number, maxY: number }, t: 'bottom' | 'top' | 'left' | 'right'): { x: number, y: number } {
|
||||
switch (t) {
|
||||
case "top":
|
||||
return { x: (e.minX + e.maxX) / 2, y: e.minY };
|
||||
case "right":
|
||||
return { x: e.maxX, y: (e.minY + e.maxY) / 2 };
|
||||
case "bottom":
|
||||
return { x: (e.minX + e.maxX) / 2, y: e.maxY };
|
||||
case "left":
|
||||
return { x: e.minX, y: (e.minY + e.maxY) / 2 };
|
||||
}
|
||||
}
|
||||
function getBbox(node: CanvasNode): { minX: number, minY: number, maxX: number, maxY: number } {
|
||||
return { minX: node.x, minY: node.y, maxX: node.x + node.width, maxY: node.y + node.height };
|
||||
}
|
||||
function path(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
||||
if(from === undefined || to === undefined)
|
||||
{
|
||||
return {
|
||||
path: '',
|
||||
from: {},
|
||||
to: {},
|
||||
toSide: '',
|
||||
}
|
||||
}
|
||||
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
||||
return bezier(start, fromSide, end, toSide);
|
||||
}
|
||||
function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'left' | 'right', to: { x: number, y: number }, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
|
||||
const r = Math.hypot(from.x - to.x, from.y - to.y), o = clamp(r / 2, 70, 150), a = edgePos(fromSide, from, o), s = edgePos(toSide, to, o);
|
||||
return {
|
||||
path: `M${from.x},${from.y} C${a.x},${a.y} ${s.x},${s.y} ${to.x},${to.y}`,
|
||||
from: from,
|
||||
to: to,
|
||||
side: toSide,
|
||||
};
|
||||
}
|
||||
function labelCenter(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): string {
|
||||
const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
|
||||
const len = Math.hypot(start.x - end.x, start.y - end.y), offset = clamp(len / 2, 70, 150), b = edgePos(fromSide, start, offset), s = edgePos(toSide, end, offset);
|
||||
const center = getCenter(start, end, b, s, 0.5);
|
||||
return `translate(${center.x}px, ${center.y}px)`;
|
||||
}
|
||||
function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r: { x: number, y: number }, o: { x: number, y: number }, e: number): { x: number, y: number } {
|
||||
const a = 1 - e, s = a * a * a, l = 3 * e * a * a, c = 3 * e * e * a, u = e * e * e;
|
||||
return {
|
||||
x: s * n.x + l * r.x + c * o.x + u * i.x,
|
||||
y: s * n.y + l * r.y + c * o.y + u * i.y
|
||||
};
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user