From cb44c208ee7372398bb88023d8357bd4f8117e03 Mon Sep 17 00:00:00 2001 From: Artur Arseniev Date: Sat, 5 Aug 2023 11:51:49 +0400 Subject: [PATCH] Add fitViewport --- src/canvas/index.ts | 19 +++++++-- src/canvas/model/Frame.ts | 25 +++++++++++- src/canvas/view/CanvasView.ts | 76 ++++++++++++++++++++++++++++++----- src/common/index.ts | 12 ++++++ src/editor/model/Editor.ts | 6 +-- 5 files changed, 121 insertions(+), 17 deletions(-) diff --git a/src/canvas/index.ts b/src/canvas/index.ts index 845cc0f7d..ce720a639 100644 --- a/src/canvas/index.ts +++ b/src/canvas/index.ts @@ -54,7 +54,7 @@ import { getElement, getViewEl } from '../utils/mixins'; import defaults, { CanvasConfig } from './config/config'; import Canvas from './model/Canvas'; import Frame from './model/Frame'; -import CanvasView from './view/CanvasView'; +import CanvasView, { FitViewportOptions } from './view/CanvasView'; import FrameView from './view/FrameView'; export type CanvasEvent = 'canvas:dragenter' | 'canvas:dragover' | 'canvas:drop' | 'canvas:dragend' | 'canvas:dragdata'; @@ -606,8 +606,17 @@ export default class CanvasModule extends Module { * @example * canvas.setCoords(100, 100); */ - setCoords(x: string, y: string) { - this.canvas.set({ x: parseFloat(x), y: parseFloat(y) }); + setCoords(x?: string | number, y?: string | number) { + const coords = { + x: this.canvas.get('x'), + y: this.canvas.get('y'), + }; + + if (x || x === 0) coords.x = parseFloat(`${x}`); + if (y || y === 0) coords.y = parseFloat(`${y}`); + + this.canvas.set(coords); + return this; } @@ -633,6 +642,10 @@ export default class CanvasModule extends Module { return zoom ? 1 / zoom : 1; } + fitViewport(opts?: FitViewportOptions) { + this.canvasView?.fitViewport(opts); + } + toggleFramesEvents(on: boolean) { const { style } = this.getFramesEl(); style.pointerEvents = on ? '' : 'none'; diff --git a/src/canvas/model/Frame.ts b/src/canvas/model/Frame.ts index 2fdd7c111..272228c1e 100644 --- a/src/canvas/model/Frame.ts +++ b/src/canvas/model/Frame.ts @@ -1,4 +1,4 @@ -import { result, forEach, isEmpty, isString } from 'underscore'; +import { result, forEach, isEmpty, isString, isNumber } from 'underscore'; import { ModuleModel } from '../../abstract'; import CanvasModule from '..'; import ComponentWrapper from '../../dom_components/model/ComponentWrapper'; @@ -10,6 +10,21 @@ import Page from '../../pages/model/Page'; const keyAutoW = '__aw'; const keyAutoH = '__ah'; +const getDimension = (frame: Frame, type: 'width' | 'height') => { + const dim = frame.get(type); + const viewDim = frame.view?.el['width' ? 'offsetWidth' : 'offsetHeight']; + + if (isNumber(dim)) { + return dim; + } else if (isString(dim) && dim.endsWith('px')) { + return parseFloat(dim); + } else if (viewDim) { + return viewDim; + } else { + return 0; + } +}; + /** * @property {Object|String} component Wrapper component definition. You can also pass an HTML string as components of the default wrapper component. * @property {String} [width=''] Width of the frame. By default, the canvas width will be taken. @@ -87,6 +102,14 @@ export default class Frame extends ModuleModel { !attr.height && this.set(keyAutoH, 1); } + get width() { + return getDimension(this, 'width'); + } + + get height() { + return getDimension(this, 'height'); + } + get head(): { tag: string; attributes: any }[] { return this.get('head'); } diff --git a/src/canvas/view/CanvasView.ts b/src/canvas/view/CanvasView.ts index 504161631..d5ec604e7 100644 --- a/src/canvas/view/CanvasView.ts +++ b/src/canvas/view/CanvasView.ts @@ -1,13 +1,14 @@ -import { bindAll } from 'underscore'; +import { bindAll, isNumber } from 'underscore'; import { ModuleView } from '../../abstract'; -import { on, off, getElement, getKeyChar, isTextNode, getElRect, getUiClass } from '../../utils/mixins'; +import { BoxRect, ElementRect } from '../../common'; +import Component from '../../dom_components/model/Component'; +import ComponentView from '../../dom_components/view/ComponentView'; import { createEl, getDocumentScroll } from '../../utils/dom'; -import FramesView from './FramesView'; +import { getElRect, getElement, getKeyChar, getUiClass, isTextNode, off, on } from '../../utils/mixins'; import Canvas from '../model/Canvas'; +import Frame from '../model/Frame'; import FrameView from './FrameView'; -import ComponentView from '../../dom_components/view/ComponentView'; -import Component from '../../dom_components/model/Component'; -import { ElementRect } from '../../common'; +import FramesView from './FramesView'; export interface MarginPaddingOffsets { marginTop?: number; @@ -25,6 +26,13 @@ export type ElementPosOpts = { avoidFrameZoom?: boolean; noScroll?: boolean; }; + +export interface FitViewportOptions { + frame?: Frame; + gap?: number | { x: number; y: number }; + ignoreHeight?: boolean; +} + export default class CanvasView extends ModuleView { events() { return { @@ -173,17 +181,65 @@ export default class CanvasView extends ModuleView { } updateFramesArea() { - const { framesArea, model } = this; + const { framesArea, model, module } = this; if (framesArea) { - const zoom = this.getZoom(); const { x, y } = model.attributes; - const mpl = zoom ? 1 / zoom : 1; + const zoomDc = module.getZoomDecimal(); + const mpl = module.getZoomMultiplier(); - framesArea.style.transform = `scale(${zoom}) translate(${x * mpl}px, ${y * mpl}px)`; + framesArea.style.transform = `scale(${zoomDc}) translate(${x * mpl}px, ${y * mpl}px)`; } } + fitViewport(opts: FitViewportOptions = {}) { + const { em, module } = this; + const canvasRect = this.getCanvasOffset(); + const frame = opts.frame || em.getCurrentFrameModel() || this.model.frames.at(0); + const { x, y } = frame.attributes; + const boxRect: BoxRect = { + x: x ?? 0, + y: y ?? 0, + width: frame.width, + height: frame.height, + }; + + const noHeight = opts.ignoreHeight; + const gap = opts.gap ?? 0; + const gapIsNum = isNumber(gap); + const gapX = gapIsNum ? gap : gap.x; + const gapY = gapIsNum ? gap : gap.y; + const boxWidth = boxRect.width + gapX * 2; + const boxHeight = boxRect.height + gapY * 2; + const canvasWidth = canvasRect.width; + const canvasHeight = canvasRect.height; + const widthRatio = canvasWidth / boxWidth; + const heightRatio = canvasHeight / boxHeight; + + const zoomRatio = noHeight ? widthRatio : Math.min(widthRatio, heightRatio); + const zoom = zoomRatio * 100; + module.setZoom(zoom); + + // check for the boxWidth is necessary as we're centering the frame via CSS + const coordX = boxRect.x + (boxWidth > canvasWidth ? canvasWidth / 2 - boxWidth / 2 : -gapX); + const coordY = boxRect.y + canvasHeight / 2 - boxHeight / 2; + + const coords = { + x: (coordX + gapX) * zoomRatio, + y: (coordY + gapY) * zoomRatio, + }; + + if (noHeight) { + const zoomMltp = module.getZoomMultiplier(); + const canvasWorldHeight = canvasHeight * zoomMltp; + const canvasHeightDiff = canvasWorldHeight - canvasHeight; + const yDelta = canvasHeightDiff / 2; + coords.y = (boxRect.y + gapY) * zoomRatio - yDelta / zoomMltp; + } + + module.setCoords(coords.x, coords.y); + } + getZoom() { return this.em.getZoomDecimal(); } diff --git a/src/common/index.ts b/src/common/index.ts index 75f7fe038..580266267 100644 --- a/src/common/index.ts +++ b/src/common/index.ts @@ -27,6 +27,18 @@ export type Position = { y: number; }; +export interface Coordinates { + x: number; + y: number; +} + +export interface Dimensions { + height: number; + width: number; +} + +export interface BoxRect extends Coordinates, Dimensions {} + export type ElementRect = { top: number; left: number; diff --git a/src/editor/model/Editor.ts b/src/editor/model/Editor.ts index 3179dddf4..237b37acf 100644 --- a/src/editor/model/Editor.ts +++ b/src/editor/model/Editor.ts @@ -904,12 +904,12 @@ export default class EditorModel extends Model { return this.set('currentFrame', frameView); } - getCurrentFrame(): FrameView { + getCurrentFrame(): FrameView | undefined { return this.get('currentFrame'); } - getCurrentFrameModel(): Frame { - return (this.getCurrentFrame() || {}).model; + getCurrentFrameModel() { + return (this.getCurrentFrame() || {})?.model; } getIcon(icon: string) {