Add grid snapping, grid preview, fix zoom slowdowns and canvas markdown editing being at the wrong size.
This commit is contained in:
parent
0b97e9a295
commit
f2d00097d6
|
|
@ -1,10 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { bezier, getBbox, getPath, opposite, posFromDir, rotation, type Box, type Direction, type Path, type Position } from '#shared/canvas.util';
|
||||
import { bezier, getBbox, opposite, posFromDir, rotation, type Box, type Direction, type Path, type Position } from '#shared/canvas.util';
|
||||
import type CanvasNodeEditor from './canvas/CanvasNodeEditor.vue';
|
||||
import type CanvasEdgeEditor from './canvas/CanvasEdgeEditor.vue';
|
||||
import { SnapFinder, type SnapHint } from '#shared/physics.util';
|
||||
import type { CanvasPreferences } from '~/types/general';
|
||||
import Id from '~/pages/user/[id].vue';
|
||||
export type Element = { type: 'node' | 'edge', id: string };
|
||||
|
||||
interface ActionMap {
|
||||
|
|
@ -67,17 +66,28 @@ const props = defineProps<{
|
|||
path: string,
|
||||
}>();
|
||||
|
||||
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), spacing = ref<number | undefined>(32);
|
||||
const focusing = ref<Element>(), editing = ref<Element>();
|
||||
const canvasRef = useTemplateRef('canvasRef'), transformRef = useTemplateRef('transformRef'), toolbarRef = useTemplateRef('toolbarRef'), viewportSize = useElementBounding(canvasRef);
|
||||
const canvasRef = useTemplateRef('canvasRef'), transformRef = useTemplateRef('transformRef'), patternRef = useTemplateRef('patternRef'), toolbarRef = useTemplateRef('toolbarRef'), viewportSize = useElementBounding(canvasRef);
|
||||
const nodes = useTemplateRef<NodeEditor[]>('nodes'), edges = useTemplateRef<EdgeEditor[]>('edges');
|
||||
const canvasSettings = useCookie<CanvasPreferences>('canvasPreference', { default: () => ({ gridSnap: true, neighborSnap: true }) });
|
||||
const canvasSettings = useCookie<CanvasPreferences>('canvasPreference', { default: () => ({ gridSnap: true, neighborSnap: true, spacing: 32 }) });
|
||||
const hints = ref<SnapHint[]>([]);
|
||||
const viewport = computed<Box>(() => {
|
||||
const width = viewportSize.width.value / zoom.value, height = viewportSize.height.value / zoom.value;
|
||||
const movementX = viewportSize.width.value - width, movementY = viewportSize.height.value - height;
|
||||
return { x: -dispX.value + movementX / 2, y: -dispY.value + movementY / 2, w: width, h: height };
|
||||
});
|
||||
const updateScaleVar = useDebounceFn(() => {
|
||||
if(transformRef.value)
|
||||
{
|
||||
console.log(zoom.value);
|
||||
transformRef.value.style.setProperty('--tw-scale', zoom.value.toString());
|
||||
}
|
||||
if(canvasRef.value)
|
||||
{
|
||||
canvasRef.value.style.setProperty('--zoom-multiplier', (1 / Math.pow(zoom.value, 0.7)).toFixed(3));
|
||||
}
|
||||
}, 100);
|
||||
|
||||
type DragOrigin = { type: 'edge', id: string, destination: 'from' | 'to', node: string } | { type: 'node', id: string };
|
||||
const fakeEdge = ref<{ from?: Position, fromSide?: Direction, to?: Position, toSide?: Direction, path?: Path, style?: { stroke: string, fill: string }, hex?: string, drag?: DragOrigin, snapped?: { node: string, side: Direction } }>({});
|
||||
|
|
@ -165,7 +175,8 @@ onMounted(() => {
|
|||
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)
|
||||
zoom.value = clamp(zoom.value * diff, minZoom.value, 3);
|
||||
spacing.value = canvasSettings.value.gridSnap ? canvasSettings.value.spacing ?? 32 : undefined;
|
||||
|
||||
updateTransform();
|
||||
}, { passive: true });
|
||||
|
|
@ -227,7 +238,23 @@ 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());
|
||||
updateScaleVar();
|
||||
}
|
||||
if(patternRef.value && canvasSettings.value.gridSnap)
|
||||
{
|
||||
patternRef.value.parentElement?.classList.remove('hidden');
|
||||
patternRef.value.setAttribute("x", (viewportSize.width.value / 2 + dispX.value % spacing.value! * zoom.value).toFixed(3));
|
||||
patternRef.value.setAttribute("y", (viewportSize.height.value / 2 + dispY.value % spacing.value! * zoom.value).toFixed(3));
|
||||
patternRef.value.setAttribute("width", (zoom.value * spacing.value!).toFixed(3));
|
||||
patternRef.value.setAttribute("height", (zoom.value * spacing.value!).toFixed(3));
|
||||
|
||||
patternRef.value.children[0].setAttribute('cx', (zoom.value).toFixed(3));
|
||||
patternRef.value.children[0].setAttribute('cy', (zoom.value).toFixed(3));
|
||||
patternRef.value.children[0].setAttribute('r', (zoom.value).toFixed(3));
|
||||
}
|
||||
else if(patternRef.value && !canvasSettings.value.gridSnap)
|
||||
{
|
||||
patternRef.value.parentElement?.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
function updateToolbarTransform()
|
||||
|
|
@ -680,7 +707,7 @@ useShortcuts({
|
|||
</script>
|
||||
|
||||
<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="createNode">
|
||||
<div ref="canvasRef" class="absolute top-0 left-0 overflow-hidden w-full h-full touch-none" @dblclick.left="createNode">
|
||||
<div class="flex flex-col absolute sm:top-2 top-10 left-2 z-[35] overflow-hidden gap-4" @click="stopPropagation" @dblclick="stopPropagation">
|
||||
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10">
|
||||
<Tooltip message="Zoom avant" side="right">
|
||||
|
|
@ -717,6 +744,20 @@ useShortcuts({
|
|||
</Tooltip>
|
||||
</div>
|
||||
<div class="border border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10">
|
||||
<Tooltip message="Préférences" side="right">
|
||||
<Dialog title="Préférences" iconClose>
|
||||
<template #trigger>
|
||||
<div 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:gear" />
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<Switch v-model="canvasSettings.neighborSnap" label="S'accrocher aux voisins" @update:model-value="snapFinder.config.preferences = canvasSettings" />
|
||||
<Switch v-model="canvasSettings.gridSnap" label="S'accrocher à la grille" @update:model-value="(v) => { canvasSettings.spacing = v ? 32 : undefined; snapFinder.config.preferences = canvasSettings }" />
|
||||
<NumberPicker v-model="canvasSettings.spacing" label="Taille de la grille" :disabled="!canvasSettings.gridSnap" @update:model-value="(v) => { spacing = v; updateTransform(); snapFinder.config.preferences = canvasSettings}" />
|
||||
</template>
|
||||
</Dialog>
|
||||
</Tooltip>
|
||||
<Tooltip message="Aide" side="right">
|
||||
<Dialog title="Aide" iconClose>
|
||||
<template #trigger>
|
||||
|
|
@ -746,6 +787,12 @@ useShortcuts({
|
|||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<svg class="absolute top-0 left-0 w-full h-full pointer-events-none">
|
||||
<pattern ref="patternRef" id="canvasPattern" patternUnits="userSpaceOnUse">
|
||||
<circle cx="0.75" cy="0.75" r="0.75" class="fill-light-35 dark:fill-dark-35"></circle>
|
||||
</pattern>
|
||||
<rect x="0" y="0" width="100%" height="100%" fill="url(#canvasPattern)"></rect>
|
||||
</svg>
|
||||
<div ref="transformRef" :style="{
|
||||
'transform-origin': 'center center',
|
||||
}" class="h-full">
|
||||
|
|
@ -871,7 +918,7 @@ useShortcuts({
|
|||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<svg class="absolute overflow-visible top-0 overflow-visible h-px w-px fill-accent-purple stroke-accent-purple stroke-1 z-50">
|
||||
<svg class="absolute overflow-visible top-0 h-px w-px fill-accent-purple stroke-accent-purple stroke-1 z-50">
|
||||
<g v-for="hint of hints">
|
||||
<circle :cx="hint.start.x" :cy="hint.start.y" r="3" />
|
||||
<circle v-if="hint.end" :cx="hint.end.x" :cy="hint.end.y" r="3" />
|
||||
|
|
|
|||
|
|
@ -1,37 +1,11 @@
|
|||
<script lang="ts">
|
||||
import type { Editor, EditorConfiguration, EditorChange } from 'codemirror';
|
||||
import { changeEnd } from 'codemirror';
|
||||
import { fromTextArea } from 'hypermd';
|
||||
|
||||
import '#shared/hypermd.extend';
|
||||
|
||||
function onChange(cm: Editor, change: EditorChange)
|
||||
{
|
||||
if (changeEnd(change).line == cm.lastLine())
|
||||
updateBottomMargin(cm);
|
||||
}
|
||||
|
||||
|
||||
function updateBottomMargin(cm: Editor)
|
||||
{
|
||||
let padding = "";
|
||||
if (cm.lineCount() > 1)
|
||||
{
|
||||
//@ts-ignore
|
||||
let totalH = cm.display.scroller.clientHeight - 30, lastLineH = cm.getLineHandle(cm.lastLine()).height;
|
||||
padding = (totalH / 2 - lastLineH) + "px";
|
||||
}
|
||||
|
||||
if (cm.state.scrollPastEndPadding != padding)
|
||||
{
|
||||
cm.state.scrollPastEndPadding = padding;
|
||||
cm.display.lineSpace.parentNode.style.paddingBottom = padding;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
const { placeholder, autofocus = false, gutters = true, format = 'd-any' } = defineProps<{
|
||||
placeholder?: string
|
||||
autofocus?: boolean
|
||||
|
|
@ -73,10 +47,7 @@ onMounted(() => {
|
|||
} as EditorConfiguration);
|
||||
|
||||
e.setValue(model.value ?? '');
|
||||
updateBottomMargin(e);
|
||||
e.on('change', onChange);
|
||||
e.on('change', (cm: Editor, change: EditorChange) => model.value = cm.getValue());
|
||||
e.on('refresh', updateBottomMargin);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -87,6 +58,8 @@ watchEffect(() => {
|
|||
editor.value.clearHistory();
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({ focus: () => editor.value?.focus() });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -108,6 +81,10 @@ watchEffect(() => {
|
|||
{
|
||||
@apply hidden;
|
||||
}
|
||||
.CodeMirror-sizer
|
||||
{
|
||||
@apply !px-3;
|
||||
}
|
||||
.cancel-gutters .CodeMirror-sizer
|
||||
{
|
||||
@apply ms-2;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<Editor ref="editor" v-model="model" autofocus :gutters="false" />
|
||||
<iframe ref="iframe" class="w-full h-full border-0" sandbox="allow-same-origin allow-scripts"></iframe>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const model = defineModel<string>();
|
||||
const editor = useTemplateRef('editor'), iframe = useTemplateRef('iframe');
|
||||
|
||||
onMounted(() => {
|
||||
if(iframe.value && iframe.value.contentDocument && editor.value)
|
||||
{
|
||||
editor.value.$el.remove();
|
||||
|
||||
iframe.value.contentDocument.documentElement.setAttribute('class', document.documentElement.getAttribute('class') ?? '');
|
||||
iframe.value.contentDocument.documentElement.setAttribute('style', document.documentElement.getAttribute('style') ?? '');
|
||||
|
||||
const base = iframe.value.contentDocument.head.appendChild(iframe.value.contentDocument.createElement('base'));
|
||||
base.setAttribute('href', window.location.href);
|
||||
|
||||
for(let element of document.getElementsByTagName('link'))
|
||||
{
|
||||
if(element.getAttribute('rel') === 'stylesheet')
|
||||
iframe.value.contentDocument.head.appendChild(element.cloneNode(true));
|
||||
}
|
||||
|
||||
for(let element of document.getElementsByTagName('style'))
|
||||
{
|
||||
iframe.value.contentDocument.head.appendChild(element.cloneNode(true));
|
||||
}
|
||||
|
||||
iframe.value.contentDocument.body.setAttribute('class', document.body.getAttribute('class') ?? '');
|
||||
iframe.value.contentDocument.body.setAttribute('style', document.body.getAttribute('style') ?? '');
|
||||
|
||||
iframe.value.contentDocument.body.appendChild(editor.value.$el);
|
||||
|
||||
editor.value.focus();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="absolute overflow-visible group">
|
||||
<div class="absolute overflow-visible group" :class="{ 'z-[1]': focusing }">
|
||||
<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">
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div v-else style="outline-style: solid;" :class="[style.border, style.outline, { '!outline-4': focusing }]" class="outline-0 transition-[outline-width] border-2 bg-light-20 dark:bg-dark-20 overflow-hidden contain-strict w-full h-full flex py-2" >
|
||||
<Editor v-model="node.text" autofocus :gutters="false"/>
|
||||
<FramedEditor v-model="node.text" autofocus :gutters="false"/>
|
||||
</div>
|
||||
<div v-if="!editing && node.type === 'group' && node.label !== undefined" @click.left="(e) => selectNode(e)" @dblclick.left="(e) => editNode(e)" :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>
|
||||
<input v-else-if="editing && node.type === 'group'" v-model="node.label" @click="e => e.stopImmediatePropagation()" v-autofocus :class="[style.border, style.outline]" 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%] appearance-none bg-transparent outline-4 mb-2 px-2 py-1 font-thin min-w-4" />
|
||||
|
|
|
|||
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -149,7 +149,8 @@ export default defineNuxtConfig({
|
|||
rateLimiter: false,
|
||||
headers: {
|
||||
contentSecurityPolicy: {
|
||||
"img-src": "'self' data: blob:"
|
||||
"img-src": "'self' data: blob:",
|
||||
"base-uri": "localhost:*"
|
||||
}
|
||||
},
|
||||
xssValidator: false,
|
||||
|
|
|
|||
|
|
@ -176,7 +176,7 @@
|
|||
</div>
|
||||
<span v-else-if="contentError">{{ contentError }}</span>
|
||||
<template v-else-if="preferences.markdown.editing === 'editing'">
|
||||
<Editor v-model="selected.content" placeholder="Commencer votre aventure ..." class="flex-1 bg-transparent appearance-none outline-none max-h-full resize-none !overflow-y-auto lg:mx-16 xl:mx-32 2xl:mx-64" />
|
||||
<Editor v-model="selected.content" autofocus class="flex-1 bg-transparent appearance-none outline-none max-h-full resize-none !overflow-y-auto lg:mx-16 xl:mx-32 2xl:mx-64" />
|
||||
</template>
|
||||
<template v-else-if="preferences.markdown.editing === 'reading'">
|
||||
<div class="flex-1 max-h-full !overflow-y-auto px-4 xl:px-32 2xl:px-64"><MarkdownRenderer :content="(debounced as string)" :proses="{ 'a': FakeA }" /></div>
|
||||
|
|
@ -184,7 +184,7 @@
|
|||
<template v-else-if="preferences.markdown.editing === 'split'">
|
||||
<SplitterGroup direction="horizontal" class="flex-1 w-full flex">
|
||||
<SplitterPanel asChild collapsible :collapsedSize="0" :minSize="20" v-slot="{ isCollapsed }" :defaultSize="50">
|
||||
<Editor v-model="selected.content" placeholder="Commencer votre aventure ..." class="flex-1 bg-transparent appearance-none outline-none max-h-full resize-none !overflow-y-auto" :class="{ 'hidden': isCollapsed }" />
|
||||
<Editor v-model="selected.content" autofocus class="flex-1 bg-transparent appearance-none outline-none max-h-full resize-none !overflow-y-auto" :class="{ 'hidden': isCollapsed }" />
|
||||
</SplitterPanel>
|
||||
<SplitterResizeHandle class="bg-light-35 dark:bg-dark-35 w-px xl!mx-4 mx-2" />
|
||||
<SplitterPanel asChild collapsible :collapsedSize="0" :minSize="20" v-slot="{ isCollapsed }">
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ class SnapPointCache {
|
|||
export class SnapFinder {
|
||||
private spatialGrid: SpatialGrid;
|
||||
private snapPointCache: SnapPointCache;
|
||||
private config: SnapConfig;
|
||||
config: SnapConfig;
|
||||
|
||||
hints: Ref<SnapHint[]>;
|
||||
viewport: Ref<Box>;
|
||||
|
|
@ -216,24 +216,28 @@ export class SnapFinder {
|
|||
this.viewport = viewport;
|
||||
}
|
||||
|
||||
add(node: CanvasNode): void {
|
||||
add(node: CanvasNode): void
|
||||
{
|
||||
this.spatialGrid.insert(node);
|
||||
this.snapPointCache.insert(node);
|
||||
this.hints.value.length = 0;
|
||||
}
|
||||
|
||||
remove(node: CanvasNode): void {
|
||||
remove(node: CanvasNode): void
|
||||
{
|
||||
this.spatialGrid.remove(node);
|
||||
this.snapPointCache.invalidate(node);
|
||||
this.hints.value.length = 0;
|
||||
}
|
||||
|
||||
update(node: CanvasNode): void {
|
||||
update(node: CanvasNode): void
|
||||
{
|
||||
this.remove(node);
|
||||
this.add(node);
|
||||
}
|
||||
|
||||
findEdgeSnapPosition(node: string, x: number, y: number): { x: number, y: number, node: string, direction: Direction } | undefined {
|
||||
findEdgeSnapPosition(node: string, x: number, y: number): { x: number, y: number, node: string, direction: Direction } | undefined
|
||||
{
|
||||
const near = [...this.spatialGrid.fetch(x, y)?.values().filter(e => e !== node).flatMap(e => this.snapPointCache.getSnapPoints(e)?.map(_e => ({ ..._e, node: e })) ?? []) ?? []].filter(e => e.type === TYPE.EDGE);
|
||||
let nearestDistance = this.config.threshold, nearest = undefined;
|
||||
|
||||
|
|
@ -248,7 +252,8 @@ export class SnapFinder {
|
|||
return nearest;
|
||||
}
|
||||
|
||||
findNodeSnapPosition(node: CanvasNode, resizeHandle?: Box): Partial<Box> {
|
||||
findNodeSnapPosition(node: CanvasNode, resizeHandle?: Box): Partial<Box>
|
||||
{
|
||||
const result: Partial<Box> = {
|
||||
x: undefined,
|
||||
y: undefined,
|
||||
|
|
@ -256,6 +261,16 @@ export class SnapFinder {
|
|||
h: undefined,
|
||||
};
|
||||
|
||||
if(!this.config.preferences.neighborSnap)
|
||||
{
|
||||
result.x = this.snapToGrid(node.x);
|
||||
result.w = this.snapToGrid(node.width);
|
||||
result.y = this.snapToGrid(node.y);
|
||||
result.h = this.snapToGrid(node.height);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
this.hints.value.length = 0;
|
||||
|
||||
this.snapPointCache.invalidate(node);
|
||||
|
|
@ -267,7 +282,8 @@ export class SnapFinder {
|
|||
return this.applySnap(node, bestSnap.x, bestSnap.y, resizeHandle);
|
||||
}
|
||||
|
||||
private findBestSnap(activePoints: SnapPoint[], otherPoints: SnapPoint[], threshold: number, resizeHandle?: Box): Partial<Position> {
|
||||
private findBestSnap(activePoints: SnapPoint[], otherPoints: SnapPoint[], threshold: number, resizeHandle?: Box): Partial<Position>
|
||||
{
|
||||
let bestSnap: Partial<Position> = {};
|
||||
let bestDiffX = threshold, bestDiffY = threshold;
|
||||
let xHints: SnapHint[] = [], yHints: SnapHint[] = [];
|
||||
|
|
@ -312,20 +328,27 @@ export class SnapFinder {
|
|||
return bestSnap;
|
||||
}
|
||||
|
||||
private applySnap(node: CanvasNode, offsetx?: number, offsety?: number, resizeHandle?: Box): Partial<Box> {
|
||||
const result: Partial<Box> = { x: undefined, y: undefined, w: undefined, h: undefined };
|
||||
|
||||
if (resizeHandle) {
|
||||
if (offsetx) result.x = node.x + offsetx * resizeHandle.x;
|
||||
if (offsetx) result.w = node.width + offsetx * resizeHandle.w;
|
||||
if (offsety) result.y = node.y + offsety * resizeHandle.y;
|
||||
if (offsety) result.h = node.height - offsety * resizeHandle.h;
|
||||
} else {
|
||||
if (offsetx) result.x = node.x + offsetx;
|
||||
if (offsety) result.y = node.y + offsety;
|
||||
private snapToGrid(pos?: number): number | undefined
|
||||
{
|
||||
return pos && this.config.preferences.gridSnap && this.config.preferences.spacing ? Math.round(pos / this.config.preferences.spacing) * this.config.preferences.spacing : undefined;
|
||||
}
|
||||
|
||||
//console.log(result, offsetx, offsety);
|
||||
private applySnap(node: CanvasNode, offsetx?: number, offsety?: number, resizeHandle?: Box): Partial<Box>
|
||||
{
|
||||
const result: Partial<Box> = { x: undefined, y: undefined, w: undefined, h: undefined };
|
||||
|
||||
if (resizeHandle)
|
||||
{
|
||||
result.x = offsetx ? node.x + offsetx * resizeHandle.x : this.snapToGrid(node.x);
|
||||
result.w = offsetx ? node.width + offsetx * resizeHandle.w : this.snapToGrid(node.width);
|
||||
result.y = offsety ? node.y + offsety * resizeHandle.y : this.snapToGrid(node.y);
|
||||
result.h = offsety ? node.height - offsety * resizeHandle.h : this.snapToGrid(node.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.x = offsetx ? node.x + offsetx : this.snapToGrid(node.x);
|
||||
result.y = offsety ? node.y + offsety : this.snapToGrid(node.y);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,6 @@ type MarkdownPreferences = {
|
|||
};
|
||||
type CanvasPreferences = {
|
||||
gridSnap: boolean;
|
||||
spacing?: number;
|
||||
neighborSnap: boolean;
|
||||
};
|
||||
Loading…
Reference in New Issue