UNPKG

grapesjs_codeapps

Version:

Free and Open Source Web Builder Framework/SC Modification

514 lines (457 loc) 13.2 kB
/** * You can customize the initial state of the module from the editor initialization, by passing the following [Configuration Object](https://github.com/artf/grapesjs/blob/master/src/canvas/config/config.js) * ```js * const editor = grapesjs.init({ * canvas: { * // options * } * }) * ``` * * Once the editor is instantiated you can use its API. Before using these methods you should get the module from the instance * * ```js * const canvas = editor.Canvas; * ``` * * * [getConfig](#getconfig) * * [getElement](#getelement) * * [getFrameEl](#getframeel) * * [getWindow](#getwindow) * * [getDocument](#getdocument) * * [getBody](#getbody) * * [getWrapperEl](#getwrapperel) * * [setCustomBadgeLabel](#setcustombadgelabel) * * [hasFocus](#hasfocus) * * [scrollTo](#scrollto) * * @module Canvas */ import { on, off, hasDnd, getElement } from 'utils/mixins'; import Droppable from 'utils/Droppable'; module.exports = () => { var c = {}, defaults = require('./config/config'), Canvas = require('./model/Canvas'), CanvasView = require('./view/CanvasView'); var canvas; var frameRect; return { /** * Used inside RTE * @private */ getCanvasView() { return CanvasView; }, /** * Name of the module * @type {String} * @private */ name: 'Canvas', /** * Initialize module. Automatically called with a new instance of the editor * @param {Object} config Configurations * @private */ init(config) { c = config || {}; for (var name in defaults) { if (!(name in c)) c[name] = defaults[name]; } var ppfx = c.pStylePrefix; if (ppfx) c.stylePrefix = ppfx + c.stylePrefix; canvas = new Canvas(config); CanvasView = new CanvasView({ model: canvas, config: c }); var cm = c.em.get('DomComponents'); if (cm) this.setWrapper(cm); this.startAutoscroll = this.startAutoscroll.bind(this); this.stopAutoscroll = this.stopAutoscroll.bind(this); this.autoscroll = this.autoscroll.bind(this); return this; }, /** * Get the configuration object * @return {Object} */ getConfig() { return c; }, /** * Add wrapper * @param {Object} wrp Wrapper * @private * */ setWrapper(wrp) { canvas.set('wrapper', wrp); }, /** * Get the canvas element * @return {HTMLElement} */ getElement() { return CanvasView.el; }, /** * Get the iframe element of the canvas * @return {HTMLIFrameElement} */ getFrameEl() { return CanvasView.frame.el; }, /** * Get the window instance of the iframe element * @return {Window} */ getWindow() { return this.getFrameEl().contentWindow; }, /** * Get the document of the iframe element * @return {HTMLDocument} */ getDocument() { return this.getFrameEl().contentDocument; }, /** * Get the body of the iframe element * @return {HTMLBodyElement} */ getBody() { return this.getDocument().body; }, /** * Get the wrapper element containing all the components * @return {HTMLElement} */ getWrapperEl() { return this.getBody().querySelector('#sc-wrapper'); }, /** * Returns element containing all canvas tools * @return {HTMLElement} * @private */ getToolsEl() { return CanvasView.toolsEl; }, /** * Returns highlighter element * @return {HTMLElement} * @private */ getHighlighter() { return CanvasView.hlEl; }, /** * Returns badge element * @return {HTMLElement} * @private */ getBadgeEl() { return CanvasView.badgeEl; }, /** * Returns placer element * @return {HTMLElement} * @private */ getPlacerEl() { return CanvasView.placerEl; }, /** * Returns ghost element * @return {HTMLElement} * @private */ getGhostEl() { return CanvasView.ghostEl; }, /** * Returns toolbar element * @return {HTMLElement} * @private */ getToolbarEl() { return CanvasView.toolbarEl; }, /** * Returns resizer element * @return {HTMLElement} * @private */ getResizerEl() { return CanvasView.resizerEl; }, /** * Returns offset viewer element * @return {HTMLElement} * @private */ getOffsetViewerEl() { return CanvasView.offsetEl; }, /** * Returns fixed offset viewer element * @return {HTMLElement} * @private */ getFixedOffsetViewerEl() { return CanvasView.fixedOffsetEl; }, /** * Render canvas * @private * */ render() { return CanvasView.render().el; }, /** * Get frame position * @return {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 * @return {Object} * @private */ offset(el) { return CanvasView.offset(el); }, /** * Set custom badge naming strategy * @param {Function} f * @example * canvas.setCustomBadgeLabel(function(component){ * return component.getName(); * }); */ setCustomBadgeLabel(f) { c.customBadgeLabel = f; }, /** * Get element position relative to the canvas * @param {HTMLElement} el * @return {Object} * @private */ getElementPos(el, opts) { return CanvasView.getElementPos(el, opts); }, /** * 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, element, options) { var opts = options || {}; var canvasPos = CanvasView.getPosition(); if (!canvasPos) return; var pos = opts.elPos || CanvasView.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 }; // In this way I can catch data and also change the position strategy if (eventToTrigger && c.em) { c.em.trigger(eventToTrigger, 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, options) { var opts = options || {}; 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} e * @return {Object} * @private */ getMouseRelativeCanvas(e, options) { var opts = options || {}; var frame = this.getFrameEl(); var body = this.getBody(); var addTop = frame.offsetTop || 0; var addLeft = frame.offsetLeft || 0; var yOffset = body.scrollTop || 0; var xOffset = body.scrollLeft || 0; return { y: e.clientY + addTop + yOffset, x: e.clientX + addLeft + xOffset }; }, /** * Check if the canvas is focused * @return {Boolean} */ hasFocus() { return this.getDocument().hasFocus(); }, /** * Detects if some input is focused (input elements, text components, etc.) * @return {Boolean} * @private */ isInputFocused() { let contentDocument = this.getFrameEl().contentDocument; return ( contentDocument.activeElement && contentDocument.activeElement.tagName !== 'BODY' ); }, /** * 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, opts = {}) { const elem = getElement(el); const cv = this.getCanvasView(); if (!cv.isElInViewport(elem) || opts.force) { elem.scrollIntoView(opts); } }, /** * Start autoscroll * @private */ startAutoscroll() { this.dragging = 1; let toListen = this.getScrollListeners(); frameRect = CanvasView.getFrameOffset(1); // By detaching those from the stack avoid browsers lags // Noticeable with "fast" drag of blocks setTimeout(() => { on(toListen, 'mousemove', this.autoscroll); on(toListen, 'mouseup', this.stopAutoscroll); }, 0); }, /** * @private */ autoscroll(e) { e.preventDefault(); if (this.dragging) { let frameWindow = this.getFrameEl().contentWindow; let actualTop = frameWindow.document.body.scrollTop; let nextTop = actualTop; let clientY = e.clientY; let limitTop = 50; let limitBottom = frameRect.height - limitTop; if (clientY < limitTop) { nextTop -= limitTop - clientY; } if (clientY > limitBottom) { nextTop += clientY - limitBottom; } //console.log(`actualTop: ${actualTop} clientY: ${clientY} nextTop: ${nextTop} frameHeigh: ${frameRect.height}`); frameWindow.scrollTo(0, nextTop); } }, /** * Stop autoscroll * @private */ stopAutoscroll() { this.dragging = 0; let toListen = this.getScrollListeners(); off(toListen, 'mousemove', this.autoscroll); off(toListen, 'mouseup', this.stopAutoscroll); }, getScrollListeners() { return [this.getFrameEl().contentWindow, this.getElement()]; }, postRender() { if (hasDnd(c.em)) this.droppable = new Droppable(c.em); }, /** * Returns wrapper element * @return {HTMLElement} * ???? * @private */ getFrameWrapperEl() { return CanvasView.frame.getWrapper(); } }; };