1
0
mirror of https://github.com/flarum/core.git synced 2025-06-08 15:44:57 +02:00
2015-06-03 18:06:39 +09:30

300 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import Component from 'flarum/component';
import ItemList from 'flarum/utils/item-list';
import ActionButton from 'flarum/components/action-button';
import icon from 'flarum/helpers/icon';
import listItems from 'flarum/helpers/list-items';
import classList from 'flarum/utils/class-list';
import computed from 'flarum/utils/computed';
class Composer extends Component {
constructor(props) {
super(props);
this.position = m.prop(Composer.PositionEnum.HIDDEN);
this.height = m.prop();
// Calculate the composer's current height, based on the intended height
// (which is set when the resizing handle is dragged), and the composer's
// current state.
this.computedHeight = computed('height', 'position', function(height, position) {
if (position === Composer.PositionEnum.MINIMIZED) {
return '';
} else if (position === Composer.PositionEnum.FULLSCREEN) {
return $(window).height();
} else {
return Math.max(200, Math.min(height, $(window).height() - $('#header').outerHeight()));
}
});
}
view() {
var classes = {
'minimized': this.position() === Composer.PositionEnum.MINIMIZED,
'full-screen': this.position() === Composer.PositionEnum.FULLSCREEN
};
classes.visible = this.position() === Composer.PositionEnum.NORMAL || classes.minimized || classes.fullScreen;
if (this.component) this.component.props.disabled = classes.minimized;
return m('div.composer', {config: this.onload.bind(this), className: classList(classes)}, [
m('div.composer-handle', {config: this.configHandle.bind(this)}),
m('ul.composer-controls', listItems(this.controlItems().toArray())),
m('div.composer-content', {onclick: () => {
if (this.position() === Composer.PositionEnum.MINIMIZED) this.show();
}}, this.component ? this.component.view() : '')
]);
}
onload(element, isInitialized, context) {
this.element(element);
if (isInitialized) { return; }
context.retain = true;
// Hide the composer to begin with.
this.height(localStorage.getItem('composerHeight') || this.$().height());
this.$().hide();
// Modulate the view's active property/class according to the focus
// state of any inputs.
this.$().on('focus blur', ':input', (e) => this.$().toggleClass('active', e.type === 'focusin'));
// When the escape key is pressed on any inputs, close the composer.
this.$().on('keydown', ':input', 'esc', () => this.close());
context.onunload = this.ondestroy.bind(this);
this.handlers = {};
$(window).on('resize', this.handlers.onresize = this.onresize.bind(this)).resize();
$(document)
.on('mousemove', this.handlers.onmousemove = this.onmousemove.bind(this))
.on('mouseup', this.handlers.onmouseup = this.onmouseup.bind(this));
}
configHandle(element, isInitialized) {
if (isInitialized) { return; }
var self = this;
$(element).css('cursor', 'row-resize')
.mousedown(function(e) {
self.mouseStart = e.clientY;
self.heightStart = self.$().height();
self.handle = $(this);
$('body').css('cursor', 'row-resize');
}).bind('dragstart mousedown', function(e) {
e.preventDefault();
});
}
ondestroy() {
$(window).off('resize', this.handlers.onresize);
$(document)
.off('mousemove', this.handlers.onmousemove)
.off('mouseup', this.handlers.onmouseup);
}
updateHeight() {
this.$().height(this.computedHeight());
this.setContentHeight(this.computedHeight());
}
onresize() {
this.updateHeight();
}
onmousemove(e) {
if (!this.handle) { return; }
// Work out how much the mouse has been moved, and set the height
// relative to the old one based on that. Then update the content's
// height so that it fills the height of the composer, and update the
// body's padding.
var deltaPixels = this.mouseStart - e.clientY;
var height = this.heightStart + deltaPixels;
this.height(height);
this.updateHeight();
var scrollTop = $(window).scrollTop();
this.updateBodyPadding(false, scrollTop > 0 && scrollTop + $(window).height() >= $(document).height());
localStorage.setItem('composerHeight', height);
}
onmouseup(e) {
if (!this.handle) { return; }
this.handle = null;
$('body').css('cursor', '');
}
preventExit() {
return this.component && this.component.preventExit();
}
render(anchorToBottom) {
var $composer = this.$().stop(true);
var oldHeight = $composer.is(':visible') ? $composer.outerHeight() : 0;
if (this.position() !== Composer.PositionEnum.HIDDEN) {
m.redraw(true);
}
this.$().height(this.computedHeight());
var newHeight = $composer.outerHeight();
switch (this.position()) {
case Composer.PositionEnum.HIDDEN:
$composer.css({height: oldHeight}).animate({bottom: -newHeight}, 'fast', () => {
$composer.hide();
this.clear();
m.redraw();
});
break;
case Composer.PositionEnum.NORMAL:
if (this.oldPosition !== Composer.PositionEnum.FULLSCREEN) {
$composer.show();
$composer.css({height: oldHeight}).animate({bottom: 0, height: newHeight}, 'fast', this.component.focus.bind(this.component));
} else {
this.component.focus();
}
break;
case Composer.PositionEnum.MINIMIZED:
$composer.css({height: oldHeight}).animate({height: newHeight}, 'fast', this.component.focus.bind(this.component));
break;
}
if (this.position() !== Composer.PositionEnum.FULLSCREEN) {
this.updateBodyPadding(true, anchorToBottom);
} else {
this.component.focus();
}
$('body').toggleClass('composer-open', this.position() !== Composer.PositionEnum.HIDDEN);
this.oldPosition = this.position();
if (this.position() !== Composer.PositionEnum.HIDDEN) {
this.setContentHeight(this.computedHeight());
}
}
// Update the amount of padding-bottom on the body so that the page's
// content will still be visible above the composer when the page is
// scrolled right to the bottom.
updateBodyPadding(animate, anchorToBottom) {
var func = animate ? 'animate' : 'css';
var paddingBottom = this.position() !== Composer.PositionEnum.HIDDEN ? this.computedHeight() - parseInt($('#page').css('padding-bottom')) : 0;
$('#content')[func]({paddingBottom}, 'fast');
if (anchorToBottom) {
if (animate) {
$('html, body').stop(true).animate({scrollTop: $(document).height()}, 'fast');
} else {
$('html, body').scrollTop($(document).height());
}
}
}
// Update the height of the stuff inside of the composer. There should be
// an element with the class .flexible-height — this element is intended
// to fill up the height of the composer, minus the space taken up by the
// composer's header/footer/etc.
setContentHeight(height) {
var flexible = this.$('.flexible-height');
if (flexible.length) {
flexible.height(height -
(flexible.offset().top - this.$().offset().top) -
this.$('.text-editor-controls').outerHeight(true));
}
}
load(component) {
if (!this.preventExit()) {
// If we load a similar component into the composer, then Mithril will be
// able to diff the old/new contents and some DOM-related state from the
// old composer will remain. To prevent this from happening, we clear the
// component and force a redraw, so that the new component will be working
// on a blank slate.
if (this.component) {
this.clear();
m.redraw(true);
}
this.component = component;
}
}
clear() {
this.component = null;
}
show(anchorToBottom) {
if ([Composer.PositionEnum.MINIMIZED, Composer.PositionEnum.HIDDEN].indexOf(this.position()) !== -1) {
this.position(Composer.PositionEnum.NORMAL);
}
this.render(anchorToBottom);
}
hide() {
this.position(Composer.PositionEnum.HIDDEN);
this.render();
}
close() {
if (!this.preventExit()) {
this.hide();
}
}
minimize() {
if (this.position() !== Composer.PositionEnum.HIDDEN) {
this.position(Composer.PositionEnum.MINIMIZED);
this.render();
}
}
fullScreen() {
if (this.position() !== Composer.PositionEnum.HIDDEN) {
this.position(Composer.PositionEnum.FULLSCREEN);
this.render();
}
}
exitFullScreen() {
if (this.position() === Composer.PositionEnum.FULLSCREEN) {
this.position(Composer.PositionEnum.NORMAL);
this.render();
}
}
control(props) {
props.className = 'btn btn-icon btn-link';
return ActionButton.component(props);
}
controlItems() {
var items = new ItemList();
if (this.position() === Composer.PositionEnum.FULLSCREEN) {
items.add('exitFullScreen', this.control({ icon: 'compress', title: 'Exit Full Screen', onclick: this.exitFullScreen.bind(this) }));
} else {
if (this.position() !== Composer.PositionEnum.MINIMIZED) {
items.add('minimize', this.control({ icon: 'minus minimize', title: 'Minimize', onclick: this.minimize.bind(this) }));
items.add('fullScreen', this.control({ icon: 'expand', title: 'Full Screen', onclick: this.fullScreen.bind(this) }));
}
items.add('close', this.control({ icon: 'times', title: 'Close', onclick: this.close.bind(this) }));
}
return items;
}
}
Composer.PositionEnum = {
HIDDEN: 'hidden',
NORMAL: 'normal',
MINIMIZED: 'minimized',
FULLSCREEN: 'fullScreen'
};
export default Composer;