Finished Canvas CSS
This commit is contained in:
parent
2a8abb4796
commit
6981b32a3a
|
|
@ -24,10 +24,10 @@ const rotation: Record<Direction, string> = {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<g>
|
<g :style="{'--canvas-color': color?.hex}">
|
||||||
<path :style="`stroke-linecap: butt; stroke-width: calc(3px * var(--zoom-multiplier)); --canvas-color: ${color?.hex}`" :class="(color?.class ?? undefined) ?? ((color && color?.hex !== undefined) ? 'stroke-[var(--canvas-color)]' : 'stroke-light-40 dark:stroke-dark-40')" class="fill-none stroke-[4px]" :d="path.path"></path>
|
<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-[var(--canvas-color)]' : 'stroke-light-40 dark:stroke-dark-40')" class="fill-none stroke-[4px]" :d="path.path"></path>
|
||||||
|
<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-[var(--canvas-color)]' : 'fill-light-40 dark:fill-dark-40')" points="0,0 6.5,10.4 -6.5,10.4"></polygon>
|
||||||
</g>
|
</g>
|
||||||
<g :style="`transform: translate(${path.to.x}px, ${path.to.y}px) scale(var(--zoom-multiplier)) rotate(${rotation[path.side]}deg); --canvas-color: ${color?.hex}`">
|
|
||||||
<polygon :class="(color?.class ?? undefined) ?? ((color && color?.hex !== undefined) ? 'fill-[var(--canvas-color)]' : 'fill-light-40 dark:fill-dark-40')" points="0,0 6.5,10.4 -6.5,10.4"></polygon>
|
|
||||||
</g>
|
</g>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -6,32 +6,27 @@ interface Props {
|
||||||
zoom: number;
|
zoom: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getColor(color: string): string
|
|
||||||
{
|
|
||||||
if(props.node?.color?.startsWith('#'))
|
|
||||||
return hexToRgb(color);
|
|
||||||
else
|
|
||||||
return getComputedStyle(document.body, null).getPropertyValue('--canvas-color-' + props.node.color);
|
|
||||||
}
|
|
||||||
function hexToRgb(hex: string): string
|
|
||||||
{
|
|
||||||
return `${parseInt(hex.substring(1, 3), 16)},${parseInt(hex.substring(3, 5), 16)},${parseInt(hex.substring(5, 7), 16)}`;
|
|
||||||
}
|
|
||||||
function darken(rgb: string): boolean
|
|
||||||
{
|
|
||||||
const [r, g, b] = rgb.split(',');
|
|
||||||
return (299 * parseInt(r) + 587 * parseInt(g) + 114 * parseInt(b)) / 1e3 >= 150;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
const size = Math.max(props.node.width, props.node.height);
|
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-[rgba(var(--canvas-color), 0.3)]`, border: `border-[var(--canvas-color)]` };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return { border: `border-light-40 dark:border-dark-40`, bg: `bg-light-40 dark:bg-dark-40` };
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute" :style="{transform: `translate(${node.x}px, ${node.y}px)`, width: `${node.width}px`, height: `${node.height}px`}">
|
<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}">
|
||||||
<div :class="{'z-0': node.type === 'group', 'z-[2]': node.type !== 'group'}" class="border-2 border-light-40 dark:border-dark-40 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full py-2 px-4 flex">
|
<div :class="[{'z-0': node.type === 'group', 'z-[2]': node.type !== 'group'}, colors.border]" class="border-2 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full flex" style="backface-visibility: hidden;">
|
||||||
<template v-if="node.type === 'group' || zoom > Math.min(0.38, 1000 / size)">
|
<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">
|
<div v-if="node.text?.length > 0" class="flex items-center">
|
||||||
<Markdown v-model="node.text" />
|
<Markdown v-model="node.text" />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -50,6 +45,7 @@ const size = Math.max(props.node.width, props.node.height);
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="node.type === 'group' && node.label !== undefined" style="max-width: 100%; font-size: calc(16px * var(--zoom-multiplier))" class="origin-bottom-left tracking-wider truncate inline-block bg-light-40 dark:bg-dark-40 text-light-100 dark:text-dark-100 absolute bottom-[100%] mb-2 px-2 py-1 font-thin">{{ node.label }}</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -7,55 +7,12 @@ interface Props
|
||||||
}
|
}
|
||||||
const props = defineProps<Props>();
|
const props = defineProps<Props>();
|
||||||
|
|
||||||
let dragging = false, posX = 0, posY = 0, dispX = ref(0), dispY = ref(0), minZoom = ref(0.3), zoom = ref(1);
|
let dragging = false, posX = 0, posY = 0, dispX = ref(0), dispY = ref(0), minZoom = ref(0.1), zoom = ref(1);
|
||||||
let centerX = ref(0), centerY = ref(0), canvas = ref<HTMLDivElement>();
|
|
||||||
let minX = ref(+Infinity), minY = ref(+Infinity), maxX = ref(-Infinity), maxY = ref(-Infinity);
|
let minX = ref(+Infinity), minY = ref(+Infinity), maxX = ref(-Infinity), maxY = ref(-Infinity);
|
||||||
let bbox = ref<DOMRect>();
|
let bbox = ref<DOMRect>();
|
||||||
|
|
||||||
let lastDistance = 0;
|
let lastDistance = 0;
|
||||||
|
|
||||||
let _minX = +Infinity, _minY = +Infinity, _maxX = -Infinity, _maxY = -Infinity;
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
props.canvas.nodes.forEach((e) => {
|
|
||||||
_minX = Math.min(_minX, e.x);
|
|
||||||
_minY = Math.min(_minY, e.y);
|
|
||||||
_maxX = Math.max(_maxX, e.x + e.width);
|
|
||||||
_maxY = Math.max(_maxY, e.y + e.height);
|
|
||||||
});
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
window.addEventListener('resize', onResize);
|
|
||||||
onResize();
|
|
||||||
|
|
||||||
dispX.value = -(_maxX + _minX) / 2;
|
|
||||||
dispY.value = -(_maxY + _minY) / 2;
|
|
||||||
|
|
||||||
zoom.value = minZoom.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
window.removeEventListener('resize', onResize);
|
|
||||||
})
|
|
||||||
|
|
||||||
const onResize = (event?: Event) => {
|
|
||||||
minX.value = _minX = _minX - 32;
|
|
||||||
minY.value = _minY = _minY - 32;
|
|
||||||
maxX.value = _maxX = _maxX + 32;
|
|
||||||
maxY.value = _maxY = _maxY + 32;
|
|
||||||
|
|
||||||
minZoom.value = Math.min((canvas.value?.clientWidth ?? 1920) / (_maxX - _minX), (canvas.value?.clientHeight ?? 1080) / (_maxY - _minY), 0.01) * 0.9;
|
|
||||||
zoom.value = clamp(zoom.value, minZoom.value, 3);
|
|
||||||
|
|
||||||
bbox.value = (canvas.value ?? document.getElementById('canvas'))?.getBoundingClientRect();
|
|
||||||
|
|
||||||
centerX.value = (bbox.value?.width ?? 0) / 2;
|
|
||||||
centerY.value = (bbox.value?.height ?? 0) / 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
const onPointerDown = (event: PointerEvent) => {
|
const onPointerDown = (event: PointerEvent) => {
|
||||||
if (event.isPrimary === false) return;
|
if (event.isPrimary === false) return;
|
||||||
dragging = true;
|
dragging = true;
|
||||||
|
|
@ -84,6 +41,7 @@ const onPointerUp = (event: PointerEvent) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const onWheel = (event: WheelEvent) => {
|
const onWheel = (event: WheelEvent) => {
|
||||||
|
|
||||||
zoom.value = clamp(zoom.value + (event.deltaY * -0.001), minZoom.value, 3);
|
zoom.value = clamp(zoom.value + (event.deltaY * -0.001), minZoom.value, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,8 +76,9 @@ const onTouchMove = (event: TouchEvent) => {
|
||||||
|
|
||||||
const reset = (_: MouseEvent) => {
|
const reset = (_: MouseEvent) => {
|
||||||
zoom.value = minZoom.value;
|
zoom.value = minZoom.value;
|
||||||
dispX.value = -(maxX.value + minX.value) / 2;
|
|
||||||
dispY.value = -(maxY.value + minY.value) / 2;
|
dispX.value = 0;
|
||||||
|
dispY.value = 0;
|
||||||
}
|
}
|
||||||
function clamp(x: number, min: number, max: number): number {
|
function clamp(x: number, min: number, max: number): number {
|
||||||
if (x > max)
|
if (x > max)
|
||||||
|
|
@ -210,12 +169,64 @@ function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r:
|
||||||
y: s * n.y + l * r.y + c * o.y + u * i.y
|
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
|
||||||
|
|
||||||
|
*/
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<template #default>
|
<template #default>
|
||||||
<div id="canvas" ref="canvas" @pointerdown="onPointerDown" @wheel.passive="onWheel" @touchstart.passive="onTouchStart"
|
<div id="canvas" @pointerdown="onPointerDown" @wheel.passive="onWheel" @touchstart.passive="onTouchStart"
|
||||||
@dragstart.prevent="" class="absolute top-0 left-0 overflow-hidden w-full h-full"
|
@dragstart.prevent="" class="absolute top-0 left-0 overflow-hidden w-full h-full"
|
||||||
:style="{ '--zoom-multiplier': (1 / Math.pow(zoom, 0.7)) }">
|
: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 top-2 left-2 z-[100] overflow-hidden">
|
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 absolute top-2 left-2 z-[100] overflow-hidden">
|
||||||
|
|
@ -257,20 +268,22 @@ function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r:
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute top-0 left-0 w-full h-full origin-top pointer-events-none z-10"
|
<div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none z-10"
|
||||||
:style="{transform: `translate(${centerX}px, ${centerY}px) scale(${zoom}) translate(${dispX}px, ${dispY}px)`}">
|
:style="{transform: `scale(${zoom}) translate(${dispX}px, ${dispY}px)`}">
|
||||||
<svg class="absolute top-0 left-0 overflow-visible w-full h-full origin-top pointer-events-none z-[1]">
|
<div>
|
||||||
<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>
|
|
||||||
<CanvasNode v-for="node of props.canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
|
<CanvasNode v-for="node of props.canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
|
||||||
|
</div>
|
||||||
<template v-for="edge of props.canvas.edges">
|
<template v-for="edge of props.canvas.edges">
|
||||||
<div :key="edge.id" v-if="edge.label" class="absolute z-10"
|
<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) }">
|
: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 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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<svg class="absolute top-0 left-0 overflow-visible w-full h-full origin-top pointer-events-none z-[1]">
|
||||||
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -279,33 +292,3 @@ function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r:
|
||||||
</template>
|
</template>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
|
||||||
.useless
|
|
||||||
{
|
|
||||||
@apply fill-light-red;
|
|
||||||
@apply dark:fill-dark-red;
|
|
||||||
@apply stroke-light-red;
|
|
||||||
@apply dark:stroke-dark-red;
|
|
||||||
@apply fill-light-orange;
|
|
||||||
@apply dark:fill-dark-orange;
|
|
||||||
@apply stroke-light-orange;
|
|
||||||
@apply dark:stroke-dark-orange;
|
|
||||||
@apply fill-light-yellow;
|
|
||||||
@apply dark:fill-dark-yellow;
|
|
||||||
@apply stroke-light-yellow;
|
|
||||||
@apply dark:stroke-dark-yellow;
|
|
||||||
@apply fill-light-green;
|
|
||||||
@apply dark:fill-dark-green;
|
|
||||||
@apply stroke-light-green;
|
|
||||||
@apply dark:stroke-dark-green;
|
|
||||||
@apply fill-light-cyan;
|
|
||||||
@apply dark:fill-dark-cyan;
|
|
||||||
@apply stroke-light-cyan;
|
|
||||||
@apply dark:stroke-dark-cyan;
|
|
||||||
@apply fill-light-purple;
|
|
||||||
@apply dark:fill-dark-purple;
|
|
||||||
@apply stroke-light-purple;
|
|
||||||
@apply dark:stroke-dark-purple;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import useDatabase from "~/composables/useDatabase";
|
import useDatabase from "~/composables/useDatabase";
|
||||||
import { extname, basename } from 'node:path';
|
import { extname, basename } from 'node:path';
|
||||||
import type { File, FileType } from '~/types/api';
|
import type { File, FileType } from '~/types/api';
|
||||||
import { InputTypeHTMLAttribute } from "vue";
|
|
||||||
import { CanvasColor, CanvasContent } from "~/types/canvas";
|
import { CanvasColor, CanvasContent } from "~/types/canvas";
|
||||||
|
|
||||||
const typeMapping: Record<string, FileType> = {
|
const typeMapping: Record<string, FileType> = {
|
||||||
|
|
@ -125,12 +124,12 @@ function reshapeContent(content: string, type: FileType): string | null
|
||||||
function getColor(color: string): CanvasColor
|
function getColor(color: string): CanvasColor
|
||||||
{
|
{
|
||||||
const colors: Record<string, string> = {
|
const colors: Record<string, string> = {
|
||||||
'1': 'fill-light-red dark:fill-dark-red stroke-light-red dark:stroke-dark-red',
|
'1': 'red',
|
||||||
'2': 'fill-light-orange dark:fill-dark-orange stroke-light-orange dark:stroke-dark-orange',
|
'2': 'orange',
|
||||||
'3': 'fill-light-yellow dark:fill-dark-yellow stroke-light-yellow dark:stroke-dark-yellow',
|
'3': 'yellow',
|
||||||
'4': 'fill-light-green dark:fill-dark-green stroke-light-green dark:stroke-dark-green',
|
'4': 'green',
|
||||||
'5': 'fill-light-cyan dark:fill-dark-cyan stroke-light-cyan dark:stroke-dark-cyan',
|
'5': 'cyan',
|
||||||
'6': 'fill-light-purple dark:fill-dark-purple stroke-light-purple dark:stroke-dark-purple',
|
'6': 'purple',
|
||||||
};
|
};
|
||||||
if(colors.hasOwnProperty(color))
|
if(colors.hasOwnProperty(color))
|
||||||
return { class: colors[color] };
|
return { class: colors[color] };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue