mirror of https://github.com/artf/grapesjs.git
nocodeframeworkdrag-and-dropsite-buildersite-generatortemplate-builderui-builderweb-builderweb-builder-frameworkwebsite-builderno-codepage-builder
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
762 lines
20 KiB
762 lines
20 KiB
/**
|
|
* You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/GrapesJS/grapesjs/blob/master/src/canvas/config/config.ts)
|
|
* ```js
|
|
* const editor = grapesjs.init({
|
|
* canvas: {
|
|
* // options
|
|
* }
|
|
* })
|
|
* ```
|
|
*
|
|
* Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance.
|
|
*
|
|
* ```js
|
|
* // Listen to events
|
|
* editor.on('canvas:drop', () => { ... });
|
|
*
|
|
* // Use the API
|
|
* const canvas = editor.Canvas;
|
|
* canvas.setCoords(...);
|
|
* ```
|
|
* ## Available Events
|
|
* * `canvas:dragenter` - When something is dragged inside the canvas, `DataTransfer` instance passed as an argument
|
|
* * `canvas:dragover` - When something is dragging on canvas, `DataTransfer` instance passed as an argument
|
|
* * `canvas:drop` - Something is dropped in canvas, `DataTransfer` instance and the dropped model are passed as arguments
|
|
* * `canvas:dragend` - When a drag operation is ended, `DataTransfer` instance passed as an argument
|
|
* * `canvas:dragdata` - On any dataTransfer parse, `DataTransfer` instance and the `result` are passed as arguments.
|
|
* By changing `result.content` you're able to customize what is dropped
|
|
*
|
|
* ## Methods
|
|
* * [getConfig](#getconfig)
|
|
* * [getElement](#getelement)
|
|
* * [getFrameEl](#getframeel)
|
|
* * [getWindow](#getwindow)
|
|
* * [getDocument](#getdocument)
|
|
* * [getBody](#getbody)
|
|
* * [setCustomBadgeLabel](#setcustombadgelabel)
|
|
* * [hasFocus](#hasfocus)
|
|
* * [scrollTo](#scrollto)
|
|
* * [setZoom](#setzoom)
|
|
* * [getZoom](#getzoom)
|
|
* * [getCoords](#getcoords)
|
|
* * [setCoords](#setcoords)
|
|
*
|
|
* [Component]: component.html
|
|
* [Frame]: frame.html
|
|
*
|
|
* @module Canvas
|
|
*/
|
|
|
|
import { isUndefined } from 'underscore';
|
|
import { Module } from '../abstract';
|
|
import { AddOptions, Coordinates } from '../common';
|
|
import Component from '../dom_components/model/Component';
|
|
import ComponentView from '../dom_components/view/ComponentView';
|
|
import EditorModel from '../editor/model/Editor';
|
|
import { getElement, getViewEl } from '../utils/mixins';
|
|
import defaults, { CanvasConfig } from './config/config';
|
|
import Canvas from './model/Canvas';
|
|
import CanvasSpot, { CanvasSpotProps } from './model/CanvasSpot';
|
|
import CanvasSpots from './model/CanvasSpots';
|
|
import Frame from './model/Frame';
|
|
import { CanvasEvents } from './types';
|
|
import CanvasView, { FitViewportOptions } from './view/CanvasView';
|
|
import FrameView from './view/FrameView';
|
|
|
|
export type CanvasEvent = `${CanvasEvents}`;
|
|
|
|
export interface ToWorldOption {
|
|
toWorld?: boolean;
|
|
}
|
|
|
|
export default class CanvasModule extends Module<CanvasConfig> {
|
|
/**
|
|
* Get configuration object
|
|
* @name getConfig
|
|
* @function
|
|
* @return {Object}
|
|
*/
|
|
|
|
/**
|
|
* Used inside RTE
|
|
* @private
|
|
*/
|
|
getCanvasView(): CanvasView {
|
|
return this.canvasView as any;
|
|
}
|
|
|
|
//name = 'Canvas';
|
|
canvas: Canvas;
|
|
model: Canvas;
|
|
spots: CanvasSpots;
|
|
events = CanvasEvents;
|
|
private canvasView?: CanvasView;
|
|
|
|
/**
|
|
* Initialize module. Automatically called with a new instance of the editor
|
|
* @param {Object} config Configurations
|
|
* @private
|
|
*/
|
|
constructor(em: EditorModel) {
|
|
super(em, 'Canvas', defaults);
|
|
|
|
this.canvas = new Canvas(this);
|
|
this.spots = new CanvasSpots(this);
|
|
this.model = this.canvas;
|
|
this.startAutoscroll = this.startAutoscroll.bind(this);
|
|
this.stopAutoscroll = this.stopAutoscroll.bind(this);
|
|
return this;
|
|
}
|
|
|
|
postLoad() {
|
|
this.model.init();
|
|
}
|
|
|
|
getModel() {
|
|
return this.canvas;
|
|
}
|
|
|
|
/**
|
|
* Get the canvas element
|
|
* @returns {HTMLElement}
|
|
*/
|
|
getElement() {
|
|
return this.getCanvasView().el;
|
|
}
|
|
|
|
getFrame(index?: number) {
|
|
return this.getFrames()[index || 0];
|
|
}
|
|
|
|
/**
|
|
* Get the main frame element of the canvas
|
|
* @returns {HTMLIFrameElement}
|
|
*/
|
|
getFrameEl() {
|
|
const { frame } = this.canvasView || {};
|
|
return frame?.el as HTMLIFrameElement;
|
|
}
|
|
|
|
getFramesEl() {
|
|
return this.canvasView?.framesArea as HTMLElement;
|
|
}
|
|
|
|
/**
|
|
* Get the main frame window instance
|
|
* @returns {Window}
|
|
*/
|
|
getWindow() {
|
|
const { frame } = this.canvasView || {};
|
|
return frame?.getWindow() as Window;
|
|
}
|
|
|
|
/**
|
|
* Get the main frame document element
|
|
* @returns {HTMLDocument}
|
|
*/
|
|
getDocument() {
|
|
const frame = this.getFrameEl();
|
|
return frame?.contentDocument as Document;
|
|
}
|
|
|
|
/**
|
|
* Get the main frame body element
|
|
* @return {HTMLBodyElement}
|
|
*/
|
|
getBody() {
|
|
const doc = this.getDocument();
|
|
return doc?.body as HTMLBodyElement;
|
|
}
|
|
|
|
_getLocalEl(globalEl: any, compView: ComponentView, method: keyof FrameView) {
|
|
let result = globalEl;
|
|
const frameView = compView?.frameView;
|
|
result = frameView ? (frameView as any)[method]() : result;
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns element containing all global canvas tools
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getGlobalToolsEl() {
|
|
return this.canvasView?.toolsGlobEl;
|
|
}
|
|
|
|
/**
|
|
* Returns element containing all canvas tools
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getToolsEl(compView?: any) {
|
|
return this._getLocalEl(this.getCanvasView().toolsEl, compView, 'getToolsEl');
|
|
}
|
|
|
|
/**
|
|
* Returns highlighter element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getHighlighter(compView?: any) {
|
|
return this._getLocalEl(this.getCanvasView().hlEl, compView, 'getHighlighter');
|
|
}
|
|
|
|
/**
|
|
* Returns badge element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getBadgeEl(compView: any) {
|
|
return this._getLocalEl(this.getCanvasView().badgeEl, compView, 'getBadgeEl');
|
|
}
|
|
|
|
/**
|
|
* Returns placer element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getPlacerEl() {
|
|
return this.getCanvasView().placerEl;
|
|
}
|
|
|
|
/**
|
|
* Returns ghost element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getGhostEl() {
|
|
return this.getCanvasView().ghostEl;
|
|
}
|
|
|
|
/**
|
|
* Returns toolbar element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getToolbarEl() {
|
|
return this.getCanvasView().toolbarEl;
|
|
}
|
|
|
|
/**
|
|
* Returns resizer element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getResizerEl() {
|
|
return this.getCanvasView().resizerEl;
|
|
}
|
|
|
|
/**
|
|
* Returns offset viewer element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getOffsetViewerEl(compView: any) {
|
|
return this._getLocalEl(this.getCanvasView().offsetEl, compView, 'getOffsetViewerEl');
|
|
}
|
|
|
|
/**
|
|
* Returns fixed offset viewer element
|
|
* @returns {HTMLElement}
|
|
* @private
|
|
*/
|
|
getFixedOffsetViewerEl() {
|
|
return this.getCanvasView().fixedOffsetEl;
|
|
}
|
|
|
|
getSpotsEl() {
|
|
return this.canvasView?.spotsEl;
|
|
}
|
|
|
|
render(): HTMLElement {
|
|
this.canvasView?.remove();
|
|
this.canvasView = new CanvasView(this.canvas);
|
|
return this.canvasView.render().el;
|
|
}
|
|
|
|
/**
|
|
* Get frame position
|
|
* @returns {Object}
|
|
* @private
|
|
*/
|
|
getOffset() {
|
|
var frameOff = this.offset(this.getFrameEl());
|
|
var canvasOff = this.offset(this.getElement());
|
|
return {
|
|
top: frameOff.top - canvasOff.top,
|
|
left: frameOff.left - canvasOff.left,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Get the offset of the passed component element
|
|
* @param {HTMLElement} el
|
|
* @returns {Object}
|
|
* @private
|
|
*/
|
|
offset(el: HTMLElement) {
|
|
return this.getCanvasView().offset(el);
|
|
}
|
|
|
|
/**
|
|
* Set custom badge naming strategy
|
|
* @param {Function} f
|
|
* @example
|
|
* canvas.setCustomBadgeLabel(function(component){
|
|
* return component.getName();
|
|
* });
|
|
*/
|
|
setCustomBadgeLabel(f: Function) {
|
|
//@ts-ignore
|
|
this.config.customBadgeLabel = f;
|
|
}
|
|
|
|
/**
|
|
* Get element position relative to the canvas
|
|
* @param {HTMLElement} el
|
|
* @returns {Object}
|
|
* @private
|
|
*/
|
|
getElementPos(el: HTMLElement, opts?: any) {
|
|
return this.getCanvasView().getElementPos(el, opts);
|
|
}
|
|
|
|
/**
|
|
* Returns element's offsets like margins and paddings
|
|
* @param {HTMLElement} el
|
|
* @returns {Object}
|
|
* @private
|
|
*/
|
|
getElementOffsets(el: HTMLElement) {
|
|
return this.getCanvasView().getElementOffsets(el);
|
|
}
|
|
|
|
/**
|
|
* Get canvas rectangular data
|
|
* @returns {Object}
|
|
*/
|
|
getRect() {
|
|
const { top = 0, left = 0 } = this.getCanvasView().getPosition() ?? {};
|
|
return {
|
|
...this.getCanvasView().getCanvasOffset(),
|
|
topScroll: top,
|
|
leftScroll: left,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This method comes handy when you need to attach something like toolbars
|
|
* to elements inside the canvas, dealing with all relative position,
|
|
* offsets, etc. and returning as result the object with positions which are
|
|
* viewable by the user (when the canvas is scrolled the top edge of the element
|
|
* is not viewable by the user anymore so the new top edge is the one of the canvas)
|
|
*
|
|
* The target should be visible before being passed here as invisible elements
|
|
* return empty string as width
|
|
* @param {HTMLElement} target The target in this case could be the toolbar
|
|
* @param {HTMLElement} element The element on which I'd attach the toolbar
|
|
* @param {Object} options Custom options
|
|
* @param {Boolean} options.toRight Set to true if you want the toolbar attached to the right
|
|
* @return {Object}
|
|
* @private
|
|
*/
|
|
getTargetToElementDim(target: HTMLElement, element: HTMLElement, options: any = {}) {
|
|
var opts = options || {};
|
|
var canvasPos = this.getCanvasView().getPosition();
|
|
if (!canvasPos) return;
|
|
var pos = opts.elPos || this.getCanvasView().getElementPos(element);
|
|
var toRight = options.toRight || 0;
|
|
var targetHeight = opts.targetHeight || target.offsetHeight;
|
|
var targetWidth = opts.targetWidth || target.offsetWidth;
|
|
var eventToTrigger = opts.event || null;
|
|
|
|
var elTop = pos.top - targetHeight;
|
|
var elLeft = pos.left;
|
|
elLeft += toRight ? pos.width : 0;
|
|
elLeft = toRight ? elLeft - targetWidth : elLeft;
|
|
|
|
var leftPos = elLeft < canvasPos.left ? canvasPos.left : elLeft;
|
|
var topPos = elTop < canvasPos.top ? canvasPos.top : elTop;
|
|
topPos = topPos > pos.top + pos.height ? pos.top + pos.height : topPos;
|
|
|
|
var result = {
|
|
top: topPos,
|
|
left: leftPos,
|
|
elementTop: pos.top,
|
|
elementLeft: pos.left,
|
|
elementWidth: pos.width,
|
|
elementHeight: pos.height,
|
|
targetWidth: target.offsetWidth,
|
|
targetHeight: target.offsetHeight,
|
|
canvasTop: canvasPos.top,
|
|
canvasLeft: canvasPos.left,
|
|
canvasWidth: canvasPos.width,
|
|
canvasHeight: canvasPos.height,
|
|
};
|
|
|
|
// In this way I can catch data and also change the position strategy
|
|
if (eventToTrigger && this.em) {
|
|
this.em.trigger(eventToTrigger, result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
canvasRectOffset(el: HTMLElement, pos: { top: number; left: number }, opts: any = {}) {
|
|
const getFrameElFromDoc = (doc: Document) => {
|
|
const { defaultView } = doc;
|
|
return defaultView?.frameElement as HTMLElement;
|
|
};
|
|
|
|
const rectOff = (el: HTMLElement, top = 1, pos: { top: number; left: number }) => {
|
|
const zoom = this.em.getZoomDecimal();
|
|
const side = top ? 'top' : 'left';
|
|
const doc = el.ownerDocument;
|
|
const { offsetTop = 0, offsetLeft = 0 } = opts.offset ? getFrameElFromDoc(doc) : {};
|
|
const { scrollTop = 0, scrollLeft = 0 } = doc.body || {};
|
|
const scroll = top ? scrollTop : scrollLeft;
|
|
const offset = top ? offsetTop : offsetLeft;
|
|
|
|
return pos[side] - (scroll - offset) * zoom;
|
|
};
|
|
|
|
return {
|
|
top: rectOff(el, 1, pos),
|
|
left: rectOff(el, 0, pos),
|
|
};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {HTMLElement} el The component element in the canvas
|
|
* @param {HTMLElement} targetEl The target element to position (eg. toolbar)
|
|
* @param {Object} opts
|
|
* @private
|
|
*/
|
|
getTargetToElementFixed(el: HTMLElement, targetEl: HTMLElement, opts: any = {}) {
|
|
const elRect = opts.pos || this.getElementPos(el, { noScroll: true });
|
|
const canvasOffset = opts.canvasOff || this.canvasRectOffset(el, elRect);
|
|
const targetHeight = targetEl.offsetHeight || 0;
|
|
const targetWidth = targetEl.offsetWidth || 0;
|
|
const elRight = elRect.left + elRect.width;
|
|
const canvasView = this.getCanvasView();
|
|
const canvasRect = canvasView.getPosition();
|
|
const frameOffset = canvasView.getFrameOffset(el);
|
|
const { event } = opts;
|
|
|
|
let top = -targetHeight;
|
|
let left = !isUndefined(opts.left) ? opts.left : elRect.width - targetWidth;
|
|
left = elRect.left < -left ? -elRect.left : left;
|
|
left = elRight > canvasRect.width ? left - (elRight - canvasRect.width) : left;
|
|
|
|
// Check when the target top edge reaches the top of the viewable canvas
|
|
if (canvasOffset.top < targetHeight) {
|
|
const fullHeight = elRect.height + targetHeight;
|
|
const elIsShort = fullHeight < frameOffset.height;
|
|
|
|
// Scroll with the window if the top edge is reached and the
|
|
// element is bigger than the canvas
|
|
if (elIsShort) {
|
|
top = top + fullHeight;
|
|
} else {
|
|
top = -canvasOffset.top < elRect.height ? -canvasOffset.top : elRect.height;
|
|
}
|
|
}
|
|
|
|
const result = {
|
|
top,
|
|
left,
|
|
canvasOffsetTop: canvasOffset.top,
|
|
canvasOffsetLeft: canvasOffset.left,
|
|
elRect,
|
|
canvasOffset,
|
|
canvasRect,
|
|
targetWidth,
|
|
targetHeight,
|
|
};
|
|
|
|
// In this way I can catch data and also change the position strategy
|
|
event && this.em.trigger(event, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Instead of simply returning e.clientX and e.clientY this function
|
|
* calculates also the offset based on the canvas. This is helpful when you
|
|
* need to get X and Y position while moving between the editor area and
|
|
* canvas area, which is in the iframe
|
|
* @param {Event} e
|
|
* @return {Object}
|
|
* @private
|
|
*/
|
|
getMouseRelativePos(e: any, opts: any = {}) {
|
|
var addTop = 0;
|
|
var addLeft = 0;
|
|
var subWinOffset = opts.subWinOffset;
|
|
var doc = e.target.ownerDocument;
|
|
var win = doc.defaultView || doc.parentWindow;
|
|
var frame = win.frameElement;
|
|
var yOffset = subWinOffset ? win.pageYOffset : 0;
|
|
var xOffset = subWinOffset ? win.pageXOffset : 0;
|
|
|
|
if (frame) {
|
|
var frameRect = frame.getBoundingClientRect();
|
|
addTop = frameRect.top || 0;
|
|
addLeft = frameRect.left || 0;
|
|
}
|
|
|
|
return {
|
|
y: e.clientY + addTop - yOffset,
|
|
x: e.clientX + addLeft - xOffset,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* X and Y mouse position relative to the canvas
|
|
* @param {Event} ev
|
|
* @return {Object}
|
|
* @private
|
|
*/
|
|
getMouseRelativeCanvas(ev: MouseEvent, opts: any) {
|
|
const zoom = this.getZoomDecimal();
|
|
const { top = 0, left = 0 } = this.getCanvasView().getPosition(opts) ?? {};
|
|
|
|
return {
|
|
y: ev.clientY * zoom + top,
|
|
x: ev.clientX * zoom + left,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if the canvas is focused
|
|
* @returns {Boolean}
|
|
*/
|
|
hasFocus() {
|
|
return this.getDocument().hasFocus();
|
|
}
|
|
|
|
/**
|
|
* Detects if some input is focused (input elements, text components, etc.)
|
|
* @return {Boolean}
|
|
* @private
|
|
*/
|
|
isInputFocused() {
|
|
const doc = this.getDocument();
|
|
const frame = this.getFrameEl();
|
|
const toIgnore = ['body', ...this.config.notTextable!];
|
|
const docActive = frame && document.activeElement === frame;
|
|
const focused = docActive ? doc && doc.activeElement : document.activeElement;
|
|
|
|
return focused && !toIgnore.some(item => focused.matches(item));
|
|
}
|
|
|
|
/**
|
|
* Scroll canvas to the element if it's not visible. The scrolling is
|
|
* executed via `scrollIntoView` API and options of this method are
|
|
* passed to it. For instance, you can scroll smoothly by using
|
|
* `{ behavior: 'smooth' }`.
|
|
* @param {HTMLElement|[Component]} el
|
|
* @param {Object} [opts={}] Options, same as options for `scrollIntoView`
|
|
* @param {Boolean} [opts.force=false] Force the scroll, even if the element is already visible
|
|
* @example
|
|
* const selected = editor.getSelected();
|
|
* // Scroll smoothly (this behavior can be polyfilled)
|
|
* canvas.scrollTo(selected, { behavior: 'smooth' });
|
|
* // Force the scroll, even if the element is alredy visible
|
|
* canvas.scrollTo(selected, { force: true });
|
|
*/
|
|
scrollTo(el: any, opts = {}) {
|
|
const elem = getElement(el);
|
|
const view = elem && getViewEl(elem);
|
|
view && view.scrollIntoView(opts);
|
|
}
|
|
|
|
/**
|
|
* Start autoscroll
|
|
* @private
|
|
*/
|
|
startAutoscroll(frame: Frame) {
|
|
const fr = (frame && frame.view) || this.em.getCurrentFrame();
|
|
fr && fr.startAutoscroll();
|
|
}
|
|
|
|
/**
|
|
* Stop autoscroll
|
|
* @private
|
|
*/
|
|
stopAutoscroll(frame: Frame) {
|
|
const fr = (frame && frame.view) || this.em.getCurrentFrame();
|
|
fr && fr.stopAutoscroll();
|
|
}
|
|
|
|
/**
|
|
* Set canvas zoom value
|
|
* @param {Number} value The zoom value, from 0 to 100
|
|
* @returns {this}
|
|
* @example
|
|
* canvas.setZoom(50); // set zoom to 50%
|
|
*/
|
|
setZoom(value: number | string) {
|
|
this.canvas.set('zoom', typeof value === 'string' ? parseFloat(value) : value);
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get canvas zoom value
|
|
* @returns {Number}
|
|
* @example
|
|
* canvas.setZoom(50); // set zoom to 50%
|
|
* const zoom = canvas.getZoom(); // 50
|
|
*/
|
|
getZoom() {
|
|
return parseFloat(this.canvas.get('zoom'));
|
|
}
|
|
|
|
/**
|
|
* Set canvas position coordinates
|
|
* @param {Number} x Horizontal position
|
|
* @param {Number} y Vertical position
|
|
* @returns {this}
|
|
* @example
|
|
* canvas.setCoords(100, 100);
|
|
*/
|
|
setCoords(x?: string | number, y?: string | number, opts: ToWorldOption = {}) {
|
|
const hasX = x || x === 0;
|
|
const hasY = y || y === 0;
|
|
const coords = {
|
|
x: this.canvas.get('x'),
|
|
y: this.canvas.get('y'),
|
|
};
|
|
|
|
if (hasX) coords.x = parseFloat(`${x}`);
|
|
if (hasY) coords.y = parseFloat(`${y}`);
|
|
|
|
if (opts.toWorld) {
|
|
const delta = this.canvasView?.getViewportDelta();
|
|
if (delta) {
|
|
if (hasX) coords.x = coords.x - delta.x;
|
|
if (hasY) coords.y = coords.y - delta.y;
|
|
}
|
|
}
|
|
|
|
this.canvas.set(coords);
|
|
|
|
return this;
|
|
}
|
|
|
|
/**
|
|
* Get canvas position coordinates
|
|
* @returns {Object} Object containing coordinates
|
|
* @example
|
|
* canvas.setCoords(100, 100);
|
|
* const coords = canvas.getCoords();
|
|
* // { x: 100, y: 100 }
|
|
*/
|
|
getCoords(): Coordinates {
|
|
const { x, y } = this.canvas.attributes;
|
|
return { x, y };
|
|
}
|
|
|
|
getZoomDecimal() {
|
|
return this.getZoom() / 100;
|
|
}
|
|
|
|
getZoomMultiplier() {
|
|
const zoom = this.getZoomDecimal();
|
|
return zoom ? 1 / zoom : 1;
|
|
}
|
|
|
|
fitViewport(opts?: FitViewportOptions) {
|
|
this.canvasView?.fitViewport(opts);
|
|
}
|
|
|
|
toggleFramesEvents(on: boolean) {
|
|
const { style } = this.getFramesEl();
|
|
style.pointerEvents = on ? '' : 'none';
|
|
}
|
|
|
|
getFrames() {
|
|
return this.canvas.frames.map(item => item);
|
|
}
|
|
|
|
/**
|
|
* Add new frame to the canvas
|
|
* @param {Object} props Frame properties
|
|
* @returns {[Frame]}
|
|
* @example
|
|
* canvas.addFrame({
|
|
* name: 'Mobile home page',
|
|
* x: 100, // Position in canvas
|
|
* y: 100,
|
|
* width: 500, // Frame dimensions
|
|
* height: 600,
|
|
* // device: 'DEVICE-ID',
|
|
* components: [
|
|
* '<h1 class="testh">Title frame</h1>',
|
|
* '<p class="testp">Paragraph frame</p>',
|
|
* ],
|
|
* styles: `
|
|
* .testh { color: red; }
|
|
* .testp { color: blue; }
|
|
* `,
|
|
* });
|
|
*/
|
|
addFrame(props = {}, opts = {}) {
|
|
return this.canvas.frames.add(new Frame(this, { ...props }), opts);
|
|
}
|
|
|
|
/**
|
|
* Get the last created Component from a drag & drop to the canvas.
|
|
* @returns {[Component]|undefined}
|
|
*/
|
|
getLastDragResult(): Component | undefined {
|
|
return this.em.get('dragResult');
|
|
}
|
|
|
|
destroy() {
|
|
this.canvas.stopListening();
|
|
this.canvasView?.remove();
|
|
//[this.canvas, this.canvasView].forEach(i => (i = {}));
|
|
//@ts-ignore
|
|
['model', 'droppable'].forEach(i => (this[i] = {}));
|
|
}
|
|
|
|
setSpot<T extends CanvasSpotProps>(props: Omit<T, 'id'>, opts: AddOptions = {}) {
|
|
const spotProps = props as T;
|
|
const spots = this.getSpots<T>(spotProps);
|
|
|
|
if (spots.length) {
|
|
const spot = spots[0];
|
|
spot.set(spotProps);
|
|
return spot;
|
|
}
|
|
|
|
const cmpView = spotProps.componentView || spotProps.component?.view;
|
|
const spot = new CanvasSpot<T>(this, {
|
|
...spotProps,
|
|
id: spotProps.id || `cs_${cmpView?.cid}`,
|
|
type: spotProps.type || '',
|
|
} as T);
|
|
|
|
this.spots.add(spot, opts);
|
|
|
|
return spot;
|
|
}
|
|
|
|
getSpots<T extends CanvasSpotProps>(spotProps: Partial<T> = {}) {
|
|
return this.spots.where(spotProps.id ? { id: spotProps.id } : spotProps) as CanvasSpot<T>[];
|
|
}
|
|
|
|
removeSpot<T extends CanvasSpotProps>(spotProps: Partial<T> = {}) {
|
|
const spots = this.getSpots(spotProps);
|
|
const removed = this.spots.remove(spots);
|
|
return removed as unknown as CanvasSpot<T>[];
|
|
}
|
|
|
|
refreshSpots() {
|
|
this.spots.refresh();
|
|
}
|
|
}
|
|
|