Stackedit/public/res/layout.js
2014-06-09 13:49:37 +01:00

687 lines
21 KiB
JavaScript

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 previewButtonsClosedOffset = 18;
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)).on('click.backdrop', _.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');
if($backdropElt) {
$backdropElt.off('click.backdrop');
$backdropElt[0].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
};
while(true) {
// 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();
}
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).focus(function() {
if(!menuPanel.isOpen && !documentPanel.isOpen && !isModalShown) {
editor.elt.focus();
}
});
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();
setTimeout(function() {
previewButtons.$elt.addClass('animate');
}, 0);
function openPreviewButtons() {
clearTimeout(closeTimeoutId);
if(!previewButtons.isDragged) {
previewButtons.isOpen = true;
previewButtons.x = previewButtonsOffset.x;
previewButtons.applyCss();
}
}
var closeTimeoutId;
var dropdownOpen = false;
function closePreviewButtons() {
clearTimeout(closeTimeoutId);
closeTimeoutId = setTimeout(function() {
if(!previewButtons.isDragged && !dropdownOpen) {
previewButtons.isOpen = false;
previewButtons.x = previewButtonsOffset.x + previewButtons.elt.offsetWidth + previewButtonsClosedOffset;
previewButtons.applyCss();
}
}, 3000);
}
openPreviewButtons();
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.addListener('onExtensionButtonResize', function() {
if(!previewButtons.isDragged) {
if(!previewButtons.isOpen) {
previewButtons.$elt.removeClass('animate');
previewButtons.x = previewButtonsOffset.x + previewButtons.elt.offsetWidth + previewButtonsClosedOffset;
previewButtons.applyCss();
setTimeout(function() {
previewButtons.$elt.addClass('animate');
}, 0);
}
}
else {
previewButtons.adjustPosition();
}
});
eventMgr.onLayoutCreated(layout);
return layout;
});