/** * Window.js * * Released under LGPL License. * Copyright (c) 1999-2017 Ephox Corp. All rights reserved * * License: http://www.tinymce.com/license * Contributing: http://www.tinymce.com/contributing */ import DomQuery from 'tinymce/core/api/dom/DomQuery'; import Env from 'tinymce/core/api/Env'; import Delay from 'tinymce/core/api/util/Delay'; import BoxUtils from './BoxUtils'; import DomUtils from './DomUtils'; import DragHelper from './DragHelper'; import FloatPanel from './FloatPanel'; import Panel from './Panel'; /** * Creates a new window. * * @-x-less Window.less * @class tinymce.ui.Window * @extends tinymce.ui.FloatPanel */ interface Window { _fullscreen: boolean; layoutRect: any; moveTo: Function; settings: any; } const windows: Window[] = []; let oldMetaValue = ''; function toggleFullScreenState(state) { const noScaleMetaValue = 'width=device-width,initial-scale=1.0,user-scalable=0,minimum-scale=1.0,maximum-scale=1.0'; let viewport = DomQuery('meta[name=viewport]')[0], contentValue; if (Env.overrideViewPort === false) { return; } if (!viewport) { viewport = document.createElement('meta'); viewport.setAttribute('name', 'viewport'); document.getElementsByTagName('head')[0].appendChild(viewport); } contentValue = viewport.getAttribute('content'); if (contentValue && typeof oldMetaValue !== 'undefined') { oldMetaValue = contentValue; } viewport.setAttribute('content', state ? noScaleMetaValue : oldMetaValue); } function toggleBodyFullScreenClasses(classPrefix, state) { if (checkFullscreenWindows() && state === false) { DomQuery([document.documentElement, document.body]).removeClass(classPrefix + 'fullscreen'); } } function checkFullscreenWindows() { for (let i = 0; i < windows.length; i++) { if (windows[i]._fullscreen) { return true; } } return false; } function handleWindowResize() { if (!Env.desktop) { let lastSize = { w: window.innerWidth, h: window.innerHeight }; Delay.setInterval(function () { const w = window.innerWidth, h = window.innerHeight; if (lastSize.w !== w || lastSize.h !== h) { lastSize = { w, h }; DomQuery(window).trigger('resize'); } }, 100); } function reposition() { let i; const rect = DomUtils.getWindowSize(); let layoutRect; for (i = 0; i < windows.length; i++) { layoutRect = windows[i].layoutRect(); windows[i].moveTo( windows[i].settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2), windows[i].settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2) ); } } DomQuery(window).on('resize', reposition); } const Window = FloatPanel.extend({ modal: true, Defaults: { border: 1, layout: 'flex', containerCls: 'panel', role: 'dialog', callbacks: { submit () { this.fire('submit', { data: this.toJSON() }); }, close () { this.close(); } } }, /** * Constructs a instance with the specified settings. * * @constructor * @param {Object} settings Name/value object with settings. */ init (settings) { const self = this; self._super(settings); if (self.isRtl()) { self.classes.add('rtl'); } self.classes.add('window'); self.bodyClasses.add('window-body'); self.state.set('fixed', true); // Create statusbar if (settings.buttons) { self.statusbar = new Panel({ layout: 'flex', border: '1 0 0 0', spacing: 3, padding: 10, align: 'center', pack: self.isRtl() ? 'start' : 'end', defaults: { type: 'button' }, items: settings.buttons }); self.statusbar.classes.add('foot'); self.statusbar.parent(self); } self.on('click', function (e) { const closeClass = self.classPrefix + 'close'; if (DomUtils.hasClass(e.target, closeClass) || DomUtils.hasClass(e.target.parentNode, closeClass)) { self.close(); } }); self.on('cancel', function () { self.close(); }); self.on('move', (e) => { if (e.control === self) { FloatPanel.hideAll(); } }); self.aria('describedby', self.describedBy || self._id + '-none'); self.aria('label', settings.title); self._fullscreen = false; }, /** * Recalculates the positions of the controls in the current container. * This is invoked by the reflow method and shouldn't be called directly. * * @method recalc */ recalc () { const self = this; const statusbar = self.statusbar; let layoutRect, width, x, needsRecalc; if (self._fullscreen) { self.layoutRect(DomUtils.getWindowSize()); self.layoutRect().contentH = self.layoutRect().innerH; } self._super(); layoutRect = self.layoutRect(); // Resize window based on title width if (self.settings.title && !self._fullscreen) { width = layoutRect.headerW; if (width > layoutRect.w) { x = layoutRect.x - Math.max(0, width / 2); self.layoutRect({ w: width, x }); needsRecalc = true; } } // Resize window based on statusbar width if (statusbar) { statusbar.layoutRect({ w: self.layoutRect().innerW }).recalc(); width = statusbar.layoutRect().minW + layoutRect.deltaW; if (width > layoutRect.w) { x = layoutRect.x - Math.max(0, width - layoutRect.w); self.layoutRect({ w: width, x }); needsRecalc = true; } } // Recalc body and disable auto resize if (needsRecalc) { self.recalc(); } }, /** * Initializes the current controls layout rect. * This will be executed by the layout managers to determine the * default minWidth/minHeight etc. * * @method initLayoutRect * @return {Object} Layout rect instance. */ initLayoutRect () { const self = this; const layoutRect = self._super(); let deltaH = 0, headEl; // Reserve vertical space for title if (self.settings.title && !self._fullscreen) { headEl = self.getEl('head'); const size = DomUtils.getSize(headEl); layoutRect.headerW = size.width; layoutRect.headerH = size.height; deltaH += layoutRect.headerH; } // Reserve vertical space for statusbar if (self.statusbar) { deltaH += self.statusbar.layoutRect().h; } layoutRect.deltaH += deltaH; layoutRect.minH += deltaH; // layoutRect.innerH -= deltaH; layoutRect.h += deltaH; const rect = DomUtils.getWindowSize(); layoutRect.x = self.settings.x || Math.max(0, rect.w / 2 - layoutRect.w / 2); layoutRect.y = self.settings.y || Math.max(0, rect.h / 2 - layoutRect.h / 2); return layoutRect; }, /** * Renders the control as a HTML string. * * @method renderHtml * @return {String} HTML representing the control. */ renderHtml () { const self = this, layout = self._layout, id = self._id, prefix = self.classPrefix; const settings = self.settings; let headerHtml = '', footerHtml = '', html = settings.html; self.preRender(); layout.preRender(self); if (settings.title) { headerHtml = ( '
' + '
' + self.encode(settings.title) + '
' + '
' + '' + '
' ); } if (settings.url) { html = ''; } if (typeof html === 'undefined') { html = layout.renderHtml(self); } if (self.statusbar) { footerHtml = self.statusbar.renderHtml(); } return ( '
' + '
' + headerHtml + '
' + html + '
' + footerHtml + '
' + '
' ); }, /** * Switches the window fullscreen mode. * * @method fullscreen * @param {Boolean} state True/false state. * @return {tinymce.ui.Window} Current window instance. */ fullscreen (state) { const self = this; const documentElement = document.documentElement; let slowRendering; const prefix = self.classPrefix; let layoutRect; if (state !== self._fullscreen) { DomQuery(window).on('resize', function () { let time; if (self._fullscreen) { // Time the layout time if it's to slow use a timeout to not hog the CPU if (!slowRendering) { time = new Date().getTime(); const rect = DomUtils.getWindowSize(); self.moveTo(0, 0).resizeTo(rect.w, rect.h); if ((new Date().getTime()) - time > 50) { slowRendering = true; } } else { if (!self._timer) { self._timer = Delay.setTimeout(function () { const rect = DomUtils.getWindowSize(); self.moveTo(0, 0).resizeTo(rect.w, rect.h); self._timer = 0; }, 50); } } } }); layoutRect = self.layoutRect(); self._fullscreen = state; if (!state) { self.borderBox = BoxUtils.parseBox(self.settings.border); self.getEl('head').style.display = ''; layoutRect.deltaH += layoutRect.headerH; DomQuery([documentElement, document.body]).removeClass(prefix + 'fullscreen'); self.classes.remove('fullscreen'); self.moveTo(self._initial.x, self._initial.y).resizeTo(self._initial.w, self._initial.h); } else { self._initial = { x: layoutRect.x, y: layoutRect.y, w: layoutRect.w, h: layoutRect.h }; self.borderBox = BoxUtils.parseBox('0'); self.getEl('head').style.display = 'none'; layoutRect.deltaH -= layoutRect.headerH + 2; DomQuery([documentElement, document.body]).addClass(prefix + 'fullscreen'); self.classes.add('fullscreen'); const rect = DomUtils.getWindowSize(); self.moveTo(0, 0).resizeTo(rect.w, rect.h); } } return self.reflow(); }, /** * Called after the control has been rendered. * * @method postRender */ postRender () { const self = this; let startPos; setTimeout(function () { self.classes.add('in'); self.fire('open'); }, 0); self._super(); if (self.statusbar) { self.statusbar.postRender(); } self.focus(); this.dragHelper = new DragHelper(self._id + '-dragh', { start () { startPos = { x: self.layoutRect().x, y: self.layoutRect().y }; }, drag (e) { self.moveTo(startPos.x + e.deltaX, startPos.y + e.deltaY); } }); self.on('submit', function (e) { if (!e.isDefaultPrevented()) { self.close(); } }); windows.push(self); toggleFullScreenState(true); }, /** * Fires a submit event with the serialized form. * * @method submit * @return {Object} Event arguments object. */ submit () { return this.fire('submit', { data: this.toJSON() }); }, /** * Removes the current control from DOM and from UI collections. * * @method remove * @return {tinymce.ui.Control} Current control instance. */ remove () { const self = this; let i; self.dragHelper.destroy(); self._super(); if (self.statusbar) { this.statusbar.remove(); } toggleBodyFullScreenClasses(self.classPrefix, false); i = windows.length; while (i--) { if (windows[i] === self) { windows.splice(i, 1); } } toggleFullScreenState(windows.length > 0); }, /** * Returns the contentWindow object of the iframe if it exists. * * @method getContentWindow * @return {Window} window object or null. */ getContentWindow () { const ifr = this.getEl().getElementsByTagName('iframe')[0]; return ifr ? ifr.contentWindow : null; } }); handleWindowResize(); export default Window;