define([ 'jquery', 'underscore', 'utils', 'constants', 'settings', 'eventMgr', 'crel', 'mousetrap', 'hammerjs', ], function($, _, utils, constants, settings, eventMgr, crel, mousetrap, hammer) { var layout = {}; var resizerSize = 32; var togglerSize = 60; var navbarHeight = 50; var editorMinSize = { width: 250, height: 140 }; var previewMinSize = { width: 330, height: 160 }; var menuPanelWidth = 280; var documentPanelWidth = 320; var titleMinWidth = 200; var previewButtonsDropdownMargin = 130; var previewButtonsOffset = { x: -45, y: -6 }; var windowSize; var wrapperL1, wrapperL2, wrapperL3; var navbar, menuPanel, documentPanel, editor, previewPanel, previewContainer, navbarToggler, previewToggler, previewResizer, previewButtons; var animate = false; function startAnimation() { animate = true; wrapperL1.$elt.addClass('layout-animate'); } function endAnimation() { animate = false; wrapperL1.$elt.removeClass('layout-animate'); } function DomObject(selector) { this.selector = selector; this.elt = document.querySelector(selector); this.$elt = $(this.elt); } var transitionEndTimeoutId; var transitionEndCallbacks = []; function onTransitionEnd(evt) { if(!evt || evt.target === wrapperL1.elt || evt.target === wrapperL2.elt || evt.target === previewPanel.elt ) { transitionEndCallbacks.forEach(function(callback) { callback(); }); endAnimation(); transitionEndCallbacks.length !== 0 && onResize(); transitionEndCallbacks = []; } } DomObject.prototype.applyCss = function() { // Top/left/Bottom/Right this.top !== undefined && (this.elt.style.top = this.top + 'px'); this.left !== undefined && (this.elt.style.left = this.left + 'px'); this.bottom !== undefined && (this.elt.style.bottom = this.bottom + 'px'); this.right !== undefined && (this.elt.style.right = this.right + 'px'); // Translate if(this.x !== undefined || this.y !== undefined) { this.x = this.x || 0; this.y = this.y || 0; this.elt.style['-webkit-transform'] = 'translate(' + this.x + 'px, ' + this.y + 'px)'; this.elt.style['-ms-transform'] = 'translate(' + this.x + 'px, ' + this.y + 'px)'; this.elt.style.transform = 'translate(' + this.x + 'px, ' + this.y + 'px)'; } // Width (deferred when animate if new width is smaller) if(animate && this.width < this.oldWidth) { transitionEndCallbacks.push(_.bind(function() { this.elt.style.width = this.width + 'px'; }, this)); } else { this.width !== undefined && (this.elt.style.width = this.width + 'px'); } this.oldWidth = this.width; // Height (deferred when animate if new height is smaller) if(animate && this.height < this.oldHeight) { transitionEndCallbacks.push(_.bind(function() { this.elt.style.height = this.height + 'px'; }, this)); } else { this.height !== undefined && (this.elt.style.height = this.height + 'px'); } this.oldHeight = this.height; // Just in case transitionEnd event doesn't happen clearTimeout(transitionEndTimeoutId); animate && (transitionEndTimeoutId = setTimeout(onTransitionEnd, 800)); }; DomObject.prototype.createToggler = function(backdrop) { var backdropElt; var pushedEvents = 0; this.toggle = function(show) { if(show === this.isOpen) { return; } this.isOpen = _.isBoolean(show) ? show : !this.isOpen; if(this.isOpen) { this.$elt.addClass('panel-open').trigger('show.layout.toggle'); if(backdrop) { $(backdropElt = utils.createBackdrop(wrapperL1.elt)).click(_.bind(function() { this.toggle(false); }, this)); this.$elt.addClass('bring-to-front'); } transitionEndCallbacks.push(_.bind(function() { if(--pushedEvents === 0) { this.isOpen && this.$elt.trigger('shown.layout.toggle'); } }, this)); } else { this.$elt.trigger('hide.layout.toggle'); backdropElt && backdropElt.removeBackdrop(); backdropElt = undefined; transitionEndCallbacks.push(_.bind(function() { if(--pushedEvents === 0) { !this.isOpen && this.$elt.removeClass('panel-open bring-to-front').trigger('hidden.layout.toggle'); } }, this)); } pushedEvents++; startAnimation(); resizeAll(); }; }; DomObject.prototype.initHammer = function(drag) { this.hammer = hammer(this.elt, { drag: drag ? true : false, drag_max_touches: 0, gesture: false, hold: false, release: false, swipe: drag ? false : true, tap: false, touch: false, transform: false }); }; var maxWidthMap = [ { screenWidth: 0, maxWidth: 600 * settings.maxWidthRatio }, { screenWidth: 1000, maxWidth: 700 * settings.maxWidthRatio }, { screenWidth: 1200, maxWidth: 800 * settings.maxWidthRatio }, { screenWidth: 1400, maxWidth: 900 * settings.maxWidthRatio }, ]; var maxWidthMapReversed = maxWidthMap.slice(0).reverse(); function getMaxWidth() { return _.find(maxWidthMapReversed, function(value) { return windowSize.width > value.screenWidth; }).maxWidth; } var editorContentElt; var previewContentElt; var editorMarginElt; var navbarInnerElt; var navbarDropdownElt; var $navbarDropdownBtnElt; var navbarTitleContainerElt; var $navbarTitleElt; var navbarBtnGroups = []; var navbarBtnGroupsWidth = [80, 80, 160, 160, 80, 40].map(function(width) { return width + 18; // Add margin }); var navbarMarginWidth = 18 * 2 + 25 + 25; var buttonsDropdownWidth = 40; var workingIndicatorWidth = 18 + 70; function onResize() { var paddingBottom = wrapperL3.height - 60; var editorPadding = (editor.elt.offsetWidth - getMaxWidth()) / 2; if(editorPadding < constants.EDITOR_DEFAULT_PADDING) { editorPadding = constants.EDITOR_DEFAULT_PADDING; } editorContentElt.style.paddingLeft = editorPadding + 'px'; editorContentElt.style.paddingRight = editorPadding + 'px'; editorContentElt.style.paddingBottom = paddingBottom + 'px'; editorMarginElt.style.width = editorPadding + 'px'; var previewPadding = (previewContainer.elt.offsetWidth - getMaxWidth()) / 2; if(previewPadding < constants.EDITOR_DEFAULT_PADDING) { previewPadding = constants.EDITOR_DEFAULT_PADDING; } previewContentElt.style.paddingLeft = previewPadding + 'px'; previewContentElt.style.paddingRight = previewPadding + 'px'; previewContentElt.style.paddingBottom = paddingBottom + 'px'; var maxWidth = navbarMarginWidth + workingIndicatorWidth + titleMinWidth + buttonsDropdownWidth; var titleWidth = windowSize.width - maxWidth + titleMinWidth; navbarBtnGroups.forEach(function(group, index) { maxWidth += group.width; index === navbarBtnGroups.length - 1 && (maxWidth -= buttonsDropdownWidth); if(windowSize.width < maxWidth) { navbarDropdownElt.appendChild(group.elt); } else { navbarInnerElt.insertBefore(group.elt, navbarTitleContainerElt); titleWidth = windowSize.width - maxWidth + titleMinWidth; } }); $navbarTitleElt.css({ maxWidth: titleWidth }); $navbarDropdownBtnElt.toggleClass('hide', navbarDropdownElt.children.length === 0); eventMgr.onLayoutResize(); } var isVertical = settings.layoutOrientation == "vertical"; function resizeAll() { windowSize = { width: window.innerWidth, height: window.innerHeight }; // Layout wrapper level 1 wrapperL1.y = navbar.isOpen ? 0 : -navbarHeight; wrapperL1.x = menuPanel.isOpen ? 0 : documentPanel.isOpen ? -(menuPanelWidth + documentPanelWidth) : -menuPanelWidth; wrapperL1.width = windowSize.width + menuPanelWidth + documentPanelWidth; wrapperL1.height = windowSize.height - wrapperL1.y; // Layout wrapper level 2 wrapperL2.left = menuPanelWidth; wrapperL2.width = windowSize.width; wrapperL2.height = wrapperL1.height; // Layout wrapper level 3 wrapperL3.top = navbarHeight; wrapperL3.width = windowSize.width; wrapperL3.height = wrapperL1.height - navbarHeight; wrapperL1.applyCss(); wrapperL2.applyCss(); wrapperL3.applyCss(); if(window.viewerMode) { previewPanel.width = wrapperL3.width; previewPanel.height = wrapperL3.height; previewContainer.width = wrapperL3.width; previewContainer.height = wrapperL3.height; previewPanel.applyCss(); previewContainer.applyCss(); return onResize(); } while(true) { if(navbar.isOpen && wrapperL3.height < editorMinSize.height + resizerSize) { navbar.isOpen = false; navbar.$elt.trigger('hide.layout.toggle').trigger('hidden.layout.toggle'); continue; } if(isVertical) { if(!previewPanel.isOpen) { previewPanel.y = wrapperL3.height - resizerSize; } else { if(previewPanel.halfSize) { previewPanel.height = (wrapperL3.height + resizerSize) / 2; } if(previewPanel.height < previewMinSize.height) { previewPanel.height = previewMinSize.height; } previewPanel.y = wrapperL3.height - previewPanel.height; if(previewPanel.y < editorMinSize.height) { var previewPanelHeight = wrapperL3.height - editorMinSize.height; if(previewPanelHeight < previewMinSize.height) { previewPanel.isOpen = false; previewPanel.$elt.trigger('hide.layout.toggle').trigger('hidden.layout.toggle'); continue; } previewPanel.height = previewPanelHeight; previewPanel.y = wrapperL3.height - previewPanel.height; } } previewPanel.width = wrapperL3.width; editor.height = previewPanel.y; editor.width = wrapperL3.width; previewContainer.top = resizerSize; previewContainer.height = previewPanel.height - resizerSize; previewContainer.width = previewPanel.width; navbarToggler.width = togglerSize; previewToggler.width = togglerSize; previewToggler.x = (previewPanel.width - togglerSize) / 2; previewResizer.width = previewContainer.width; } else { if(!previewPanel.isOpen) { previewPanel.x = wrapperL3.width - resizerSize; } else { if(previewPanel.halfSize) { previewPanel.width = (wrapperL3.width + resizerSize) / 2; } if(previewPanel.width < previewMinSize.width) { previewPanel.width = previewMinSize.width; } previewPanel.x = wrapperL3.width - previewPanel.width; if(previewPanel.x < editorMinSize.width) { var previewPanelWidth = wrapperL3.width - editorMinSize.width; if(previewPanelWidth < previewMinSize.width) { previewPanel.isOpen = false; previewPanel.$elt.trigger('hide.layout.toggle').trigger('hidden.layout.toggle'); continue; } previewPanel.width = previewPanelWidth; previewPanel.x = wrapperL3.width - previewPanel.width; } } previewPanel.height = wrapperL3.height; editor.width = previewPanel.x; editor.height = wrapperL3.height; previewContainer.left = resizerSize; previewContainer.width = previewPanel.width - resizerSize; previewContainer.height = previewPanel.height; navbarToggler.height = togglerSize; previewToggler.height = togglerSize; previewToggler.y = (previewPanel.height - togglerSize) / 2; previewResizer.height = previewContainer.height; } break; } navbarToggler.$elt.toggleClass('open', navbar.isOpen); previewToggler.$elt.toggleClass('open', previewPanel.isOpen); previewResizer.$elt.toggleClass('open', previewPanel.isOpen); editor.applyCss(); previewPanel.applyCss(); previewContainer.applyCss(); previewToggler.applyCss(); previewResizer.applyCss(); navbarToggler.applyCss(); previewButtons.adjustPosition(); onResize(); } layout.init = function() { // Tweak the body element (function(bodyStyle) { bodyStyle.position = 'absolute'; bodyStyle.top = 0; bodyStyle.left = 0; bodyStyle.bottom = 0; bodyStyle.right = 0; bodyStyle.overflow = 'hidden'; })(document.body.style); document.documentElement.style.overflow = 'hidden'; wrapperL1 = new DomObject('.layout-wrapper-l1'); wrapperL2 = new DomObject('.layout-wrapper-l2'); wrapperL3 = new DomObject('.layout-wrapper-l3'); navbar = new DomObject('.navbar'); menuPanel = new DomObject('.menu-panel'); documentPanel = new DomObject('.document-panel'); editor = new DomObject('#wmd-input'); previewPanel = new DomObject('.preview-panel'); previewContainer = new DomObject('.preview-container'); navbarToggler = new DomObject('.layout-toggler-navbar'); previewToggler = new DomObject('.layout-toggler-preview'); previewResizer = new DomObject('.layout-resizer-preview'); previewButtons = new DomObject('.extension-preview-buttons'); editorContentElt = editor.elt.querySelector('.editor-content'); previewContentElt = document.getElementById('preview-contents'); editorMarginElt = editor.elt.querySelector('.editor-margin'); navbarInnerElt = navbar.elt.querySelector('.navbar-inner'); navbarDropdownElt = navbar.elt.querySelector('.buttons-dropdown .dropdown-menu'); $navbarDropdownBtnElt = navbar.$elt.find('.buttons-dropdown'); navbarTitleContainerElt = navbar.elt.querySelector('.title-container'); $navbarTitleElt = navbar.$elt.find('.file-title-navbar, .input-file-title'); _.each(navbar.elt.querySelectorAll('.right-buttons'), function(btnGroupElt) { navbarBtnGroups.push({ elt: btnGroupElt, width: navbarBtnGroupsWidth.shift() }); }); _.each(navbar.elt.querySelectorAll('.left-buttons'), function(btnGroupElt) { navbarBtnGroups.push({ elt: btnGroupElt, width: navbarBtnGroupsWidth.shift() }); }); wrapperL1.$elt.toggleClass('layout-vertical', isVertical); wrapperL1.$elt.on("webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend", onTransitionEnd); navbar.isOpen = true; navbar.createToggler(); navbarToggler.$elt.click(_.bind(navbar.toggle, navbar)); previewPanel.isOpen = true; previewPanel.createToggler(); previewPanel.halfSize = true; previewToggler.$elt.click(_.bind(previewPanel.toggle, previewPanel)); // Open StackEdit Viewer if failing to open the preview previewPanel.$elt.on('show.layout.toggle', function() { _.defer(function() { if(!previewPanel.isOpen) { window.location.href = 'viewer'; } }); }); documentPanel.isOpen = false; documentPanel.createToggler(true); documentPanel.$elt.find('.toggle-button').click(_.bind(documentPanel.toggle, documentPanel)); // Hide panels when clicking on a non collapse element documentPanel.$elt.on('click', 'a[data-toggle!=collapse]', _.bind(documentPanel.toggle, documentPanel, false)); // Focus on editor when document panel is closed documentPanel.$elt.on('hidden.layout.toggle', function() { editor.elt.focus(); }); menuPanel.isOpen = false; if(!window.viewerMode) { menuPanel.createToggler(true); menuPanel.$elt.find('.toggle-button').click(_.bind(menuPanel.toggle, menuPanel)); // Hide panels when clicking on a non collapse element menuPanel.$elt.on('click', 'a[data-toggle!=collapse]', _.bind(menuPanel.toggle, menuPanel, false)); // Close all open sub-menus when one submenu opens and when panel is closed menuPanel.$elt.on('show.bs.collapse hidden.layout.toggle', function() { menuPanel.$elt.find('.in').collapse('hide'); }); // Focus on editor when menu panel is closed menuPanel.$elt.on('hidden.layout.toggle', function() { editor.elt.focus(); }); // Gesture /* navbar.initHammer(); menuPanel.initHammer(); documentPanel.initHammer(); previewButtons.initHammer(); navbar.hammer.on('swiperight', _.bind(menuPanel.toggle, menuPanel, true)); navbar.hammer.on('swipeleft', _.bind(documentPanel.toggle, documentPanel, true)); navbar.hammer.on('swipeup', _.bind(navbar.toggle, navbar, false)); menuPanel.hammer.on('swiperight', _.bind(menuPanel.toggle, menuPanel, true)); menuPanel.hammer.on('swipeleft', _.bind(menuPanel.toggle, menuPanel, false)); documentPanel.hammer.on('swipeleft', _.bind(documentPanel.toggle, documentPanel, true)); documentPanel.hammer.on('swiperight', _.bind(documentPanel.toggle, documentPanel, false)); */ previewResizer.initHammer(true); var resizerInitialSize; previewResizer.hammer.on('dragstart', function() { resizerInitialSize = { width: previewPanel.width, height: previewPanel.height }; }).on('drag', function(evt) { if(isVertical) { previewPanel.height = resizerInitialSize.height - evt.gesture.deltaY; } else { previewPanel.width = resizerInitialSize.width - evt.gesture.deltaX; } evt.gesture.preventDefault(); previewPanel.halfSize = false; resizeAll(); }); } previewButtons.initHammer(true); previewButtons.adjustPosition = function() { if(!previewButtons.isDragged) { return; } var minX = -windowSize.width + previewButtons.elt.offsetWidth; var minY = -windowSize.height + previewButtons.elt.offsetHeight; this.x < minX && (this.x = minX); this.y < minY && (this.y = minY); this.x > 0 && (this.x = 0); this.y > 0 && (this.y = 0); this.applyCss(); }; var buttonsInitialCoord; previewButtons.hammer.on('dragstart', function() { previewButtons.isDragged = true; previewButtons.$elt.removeClass('animate'); wrapperL2.$elt.addClass('dragging'); buttonsInitialCoord = { x: previewButtons.x, y: previewButtons.y }; }).on('drag', function(evt) { previewButtons.x = buttonsInitialCoord.x + evt.gesture.deltaX; previewButtons.y = buttonsInitialCoord.y + evt.gesture.deltaY; previewButtons.adjustPosition(); evt.gesture.preventDefault(); }).on('dragend', function() { wrapperL2.$elt.removeClass('dragging'); previewButtons.$elt.find('.btn-group').toggleClass('dropup', windowSize.height/2 > -previewButtons.y); }); var isModalShown = false; $('.modal').on('show.bs.modal', function() { // Close panel if open menuPanel.toggle(false); documentPanel.toggle(false); isModalShown = true; }).on('hidden.bs.modal', function() { isModalShown = false; }); // Configure Mousetrap mousetrap.stopCallback = function() { return menuPanel.isOpen || documentPanel.isOpen || isModalShown; }; $(window).resize(resizeAll); var styleContent = ''; // Apply font function applyFont(size, screenWidth) { screenWidth = screenWidth || 0; styleContent += [ '@media (min-width: ' + screenWidth + 'px) {', '#wmd-input {', ' font-size: ' + size + 'px;', '}', '#preview-contents {', ' font-size: ' + size + 'px;', '}', '}', ].join('\n'); } applyFont(16 * settings.fontSizeRatio); applyFont(17 * settings.fontSizeRatio, 600); applyFont(18 * settings.fontSizeRatio, 1200); // Apply dynamic stylesheet var style = crel('style', { type : 'text/css' }); style.innerHTML = styleContent; document.head.appendChild(style); resizeAll(); }; eventMgr.addListener('onReady', function() { previewButtons.x = previewButtonsOffset.x; previewButtons.y = previewButtonsOffset.y; previewButtons.applyCss(); function openPreviewButtons() { clearTimeout(closeTimeoutId); if(!previewButtons.isDragged) { previewButtons.x = previewButtonsOffset.x; previewButtons.applyCss(); } } var closeTimeoutId; var dropdownOpen = false; function closePreviewButtons() { clearTimeout(closeTimeoutId); closeTimeoutId = setTimeout(function() { if(!previewButtons.isDragged && !dropdownOpen) { previewButtons.$elt.addClass('animate'); previewButtons.x = previewButtonsOffset.x + previewButtons.elt.offsetWidth; previewButtons.applyCss(); } }, 3000); } closePreviewButtons(); previewButtons.$elt.hover(openPreviewButtons, closePreviewButtons).on('show.bs.dropdown', function() { dropdownOpen = true; }).on('hidden.bs.dropdown', function() { dropdownOpen = false; closePreviewButtons(); }); _.each(previewButtons.elt.querySelectorAll('.btn-group'), function(btnGroupElt) { var $btnGroupElt = $(btnGroupElt); $btnGroupElt.on('shown.bs.dropdown', function() { // Align dropdown to the left of the screen $btnGroupElt.find('.dropdown-menu').css({ right: -previewButtons.elt.offsetWidth + $btnGroupElt.width() + $btnGroupElt.position().left }); var maxHeight = -previewButtons.y - previewButtonsDropdownMargin; // If it's a dropup if(windowSize.height/2 > -previewButtons.y) { maxHeight = windowSize.height + previewButtons.y - previewButtons.elt.offsetHeight - previewButtonsDropdownMargin; } $btnGroupElt.find('.markdown-syntax, .table-of-contents').css({ 'maxHeight': maxHeight }); }).addClass('dropup'); }); }); eventMgr.onLayoutCreated(layout); return layout; });