2014-12-20 14:26:46 +08:00
|
|
|
|
import Ember from 'ember';
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
import { PositionEnum } from '../controllers/composer';
|
2015-02-02 14:27:59 +08:00
|
|
|
|
import ActionButton from '../components/ui/controls/action-button';
|
|
|
|
|
import TaggedArray from '../utils/tagged-array';
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
var $ = Ember.$;
|
2014-12-20 14:26:46 +08:00
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
export default Ember.View.extend(Ember.Evented, {
|
2014-12-20 14:26:46 +08:00
|
|
|
|
classNames: ['composer'],
|
2015-02-02 14:27:59 +08:00
|
|
|
|
classNameBindings: [
|
2015-02-03 14:29:53 +08:00
|
|
|
|
'minimized',
|
|
|
|
|
'fullscreen',
|
2015-02-02 14:27:59 +08:00
|
|
|
|
'active'
|
|
|
|
|
],
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
position: Ember.computed.alias('controller.position'),
|
|
|
|
|
visible: Ember.computed.alias('controller.visible'),
|
|
|
|
|
normal: Ember.computed.alias('controller.normal'),
|
|
|
|
|
minimized: Ember.computed.alias('controller.minimized'),
|
|
|
|
|
fullscreen: Ember.computed.alias('controller.fullscreen'),
|
|
|
|
|
|
|
|
|
|
// 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.
|
2015-02-02 14:27:59 +08:00
|
|
|
|
computedHeight: function() {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
if (this.get('minimized')) {
|
2015-02-02 14:27:59 +08:00
|
|
|
|
return '';
|
2015-02-03 14:29:53 +08:00
|
|
|
|
} else if (this.get('fullscreen')) {
|
|
|
|
|
return $(window).height();
|
2015-02-02 14:27:59 +08:00
|
|
|
|
} else {
|
|
|
|
|
return Math.max(200, Math.min(this.get('height'), $(window).height() - $('#header').outerHeight()));
|
|
|
|
|
}
|
2015-02-03 14:29:53 +08:00
|
|
|
|
}.property('height', 'minimized', 'fullscreen'),
|
2015-02-02 14:27:59 +08:00
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
didInsertElement: function() {
|
2015-02-02 14:27:59 +08:00
|
|
|
|
var view = this;
|
2015-02-03 14:29:53 +08:00
|
|
|
|
var controller = this.get('controller');
|
2015-02-02 14:27:59 +08:00
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// Hide the composer to begin with.
|
2015-02-06 09:51:34 +08:00
|
|
|
|
this.set('height', localStorage.getItem('composerHeight') || this.$().height());
|
2015-02-02 14:27:59 +08:00
|
|
|
|
this.$().hide();
|
2015-01-30 09:52:19 +08:00
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// If the composer is minimized, allow the user to click anywhere on
|
|
|
|
|
// it to show it.
|
2015-01-30 09:52:19 +08:00
|
|
|
|
this.$('.composer-content').click(function() {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
if (view.get('minimized')) {
|
2015-01-30 09:52:19 +08:00
|
|
|
|
controller.send('show');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// Modulate the view's active property/class according to the focus
|
|
|
|
|
// state of any inputs.
|
2015-02-02 14:27:59 +08:00
|
|
|
|
this.$().on('focus', ':input', function() {
|
2015-01-30 09:52:19 +08:00
|
|
|
|
view.set('active', true);
|
2015-02-02 14:27:59 +08:00
|
|
|
|
}).on('blur', ':input', function() {
|
2015-01-30 09:52:19 +08:00
|
|
|
|
view.set('active', false);
|
|
|
|
|
});
|
2015-02-02 14:27:59 +08:00
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// Focus on the first input when the controller wants to focus.
|
2015-02-02 14:27:59 +08:00
|
|
|
|
controller.on('focus', this, this.focus);
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// Set up the handle so that the composer can be resized.
|
2015-02-02 14:27:59 +08:00
|
|
|
|
$(window).on('resize', {view: this}, this.windowWasResized).resize();
|
|
|
|
|
|
|
|
|
|
var dragData = {view: this};
|
|
|
|
|
this.$('.composer-handle').css('cursor', 'row-resize')
|
|
|
|
|
.mousedown(function(e) {
|
|
|
|
|
dragData.mouseStart = e.clientY;
|
|
|
|
|
dragData.heightStart = view.$().height();
|
|
|
|
|
dragData.handle = $(this);
|
|
|
|
|
$('body').css('cursor', 'row-resize');
|
|
|
|
|
}).bind('dragstart mousedown', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
$(document)
|
|
|
|
|
.on('mousemove', dragData, this.mouseWasMoved)
|
|
|
|
|
.on('mouseup', dragData, this.mouseWasReleased);
|
2015-02-03 14:29:53 +08:00
|
|
|
|
|
|
|
|
|
// When the escape key is pressed on any inputs, close the composer.
|
|
|
|
|
this.$().on('keydown', ':input', 'esc', function() {
|
|
|
|
|
controller.send('close');
|
|
|
|
|
});
|
2015-02-02 14:27:59 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
willDestroyElement: function() {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
$(window).off('resize', this.windowWasResized);
|
2015-02-02 14:27:59 +08:00
|
|
|
|
|
|
|
|
|
$(document)
|
|
|
|
|
.off('mousemove', this.mouseWasMoved)
|
|
|
|
|
.off('mouseup', this.mouseWasReleased);
|
|
|
|
|
},
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// 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: function(animate) {
|
|
|
|
|
// Before we change anything, work out if we're currently scrolled
|
|
|
|
|
// right to the bottom of the page. If we are, we'll want to anchor
|
|
|
|
|
// the body's scroll position to the bottom after we update the
|
|
|
|
|
// padding.
|
|
|
|
|
var anchorScroll = $(window).scrollTop() + $(window).height() >= $(document).height();
|
|
|
|
|
|
|
|
|
|
var func = animate ? 'animate' : 'css';
|
|
|
|
|
var paddingBottom = this.get('visible') ? this.get('computedHeight') - Ember.$('#footer').outerHeight(true) : 0;
|
|
|
|
|
$('#main')[func]({paddingBottom: paddingBottom}, 'fast');
|
|
|
|
|
|
|
|
|
|
if (anchorScroll) {
|
|
|
|
|
if (animate) {
|
|
|
|
|
$('html, body').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.
|
2015-02-06 11:36:22 +08:00
|
|
|
|
setContentHeight: function(height) {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
var content = this.$('.composer-content');
|
2015-02-06 11:36:22 +08:00
|
|
|
|
this.$('.flexible-height').height(height
|
2015-02-03 14:29:53 +08:00
|
|
|
|
- parseInt(content.css('padding-top'))
|
|
|
|
|
- parseInt(content.css('padding-bottom'))
|
|
|
|
|
- this.$('.composer-header').outerHeight(true)
|
|
|
|
|
- this.$('.text-editor-controls').outerHeight(true));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
// OBSERVERS
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
// Whenever the composer is minimized or goes to/from fullscreen, we need
|
|
|
|
|
// to re-populate the control buttons, because their configuration depends
|
|
|
|
|
// on the composer's current state.
|
|
|
|
|
refreshControls: function() {
|
|
|
|
|
var controlItems = TaggedArray.create();
|
|
|
|
|
this.trigger('populateControls', controlItems);
|
|
|
|
|
this.set('controlItems', controlItems);
|
|
|
|
|
}.observes('minimized', 'fullscreen'),
|
|
|
|
|
|
|
|
|
|
// Whenever the composer's computed height changes, update the DOM to
|
|
|
|
|
// reflect it.
|
|
|
|
|
updateHeight: function() {
|
2015-02-06 11:36:22 +08:00
|
|
|
|
Ember.run.scheduleOnce('afterRender', this, function() {
|
|
|
|
|
this.$().height(this.get('computedHeight'));
|
2015-02-03 14:29:53 +08:00
|
|
|
|
});
|
|
|
|
|
}.observes('computedHeight'),
|
|
|
|
|
|
2015-02-06 11:36:22 +08:00
|
|
|
|
updateContentHeight: function() {
|
|
|
|
|
Ember.run.scheduleOnce('afterRender', this, function() {
|
|
|
|
|
this.setContentHeight(this.get('computedHeight'));
|
|
|
|
|
});
|
|
|
|
|
}.observes('computedHeight', 'controller.content'),
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
positionWillChange: function() {
|
|
|
|
|
this.set('oldPosition', this.get('position'));
|
|
|
|
|
}.observesBefore('position'),
|
|
|
|
|
|
|
|
|
|
// Whenever the composer's display state changes, update the DOM to slide
|
|
|
|
|
// it in or out.
|
|
|
|
|
positionDidChange: function() {
|
|
|
|
|
// At this stage, the position property has just changed, and the
|
|
|
|
|
// class name hasn't been altered in the DOM. So, we can grab the
|
|
|
|
|
// composer's current height which we might want to animate from.
|
|
|
|
|
// After the DOM has updated, we animate to its new height.
|
2015-02-06 11:36:22 +08:00
|
|
|
|
var $composer = this.$();
|
|
|
|
|
var oldHeight = $composer ? $composer.height() : 0;
|
2015-02-03 14:29:53 +08:00
|
|
|
|
|
2015-02-06 11:36:22 +08:00
|
|
|
|
Ember.run.scheduleOnce('afterRender', this, function() {
|
|
|
|
|
var $composer = this.$();
|
2015-02-03 14:29:53 +08:00
|
|
|
|
var newHeight = $composer.height();
|
2015-02-06 11:36:22 +08:00
|
|
|
|
var view = this;
|
2015-02-03 14:29:53 +08:00
|
|
|
|
|
2015-02-06 11:36:22 +08:00
|
|
|
|
switch (this.get('position')) {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
case PositionEnum.HIDDEN:
|
2015-02-06 11:36:22 +08:00
|
|
|
|
$composer.animate({bottom: -newHeight}, 'fast', function() {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
$composer.hide();
|
2015-02-06 11:36:22 +08:00
|
|
|
|
view.get('controller').send('clearContent');
|
2015-02-03 14:29:53 +08:00
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PositionEnum.NORMAL:
|
2015-02-06 11:36:22 +08:00
|
|
|
|
if (this.get('oldPosition') !== PositionEnum.FULLSCREEN) {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
$composer.show();
|
|
|
|
|
$composer.css({height: oldHeight}).animate({bottom: 0, height: newHeight}, 'fast', function() {
|
|
|
|
|
view.focus();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case PositionEnum.MINIMIZED:
|
|
|
|
|
$composer.css({height: oldHeight}).animate({height: newHeight}, 'fast', function() {
|
|
|
|
|
view.focus();
|
|
|
|
|
});
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-06 11:36:22 +08:00
|
|
|
|
if (this.get('position') !== PositionEnum.FULLSCREEN) {
|
|
|
|
|
this.updateBodyPadding(true);
|
2015-02-03 14:29:53 +08:00
|
|
|
|
}
|
2015-02-06 11:36:22 +08:00
|
|
|
|
this.setContentHeight(this.get('computedHeight'));
|
2015-02-03 14:29:53 +08:00
|
|
|
|
});
|
|
|
|
|
}.observes('position'),
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
// LISTENERS
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
2015-02-02 14:27:59 +08:00
|
|
|
|
windowWasResized: function(event) {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// Force a recalculation of the computed height, because its value
|
|
|
|
|
// depends on the window's height.
|
2015-02-02 14:27:59 +08:00
|
|
|
|
var view = event.data.view;
|
|
|
|
|
view.notifyPropertyChange('computedHeight');
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
mouseWasMoved: function(event) {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
if (! event.data.handle) { return; }
|
2015-02-02 14:27:59 +08:00
|
|
|
|
var view = event.data.view;
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
// 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.
|
2015-02-02 14:27:59 +08:00
|
|
|
|
var deltaPixels = event.data.mouseStart - event.clientY;
|
2015-02-06 09:51:34 +08:00
|
|
|
|
var height = event.data.heightStart + deltaPixels;
|
|
|
|
|
view.set('height', height);
|
2015-02-06 11:36:22 +08:00
|
|
|
|
view.setContentHeight(height);
|
2015-02-03 14:29:53 +08:00
|
|
|
|
view.updateBodyPadding();
|
2015-02-06 09:51:34 +08:00
|
|
|
|
|
|
|
|
|
localStorage.setItem('composerHeight', height);
|
2015-02-02 14:27:59 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
mouseWasReleased: function(event) {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
if (! event.data.handle) { return; }
|
2015-02-02 14:27:59 +08:00
|
|
|
|
event.data.handle = null;
|
|
|
|
|
$('body').css('cursor', '');
|
|
|
|
|
},
|
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
focus: function() {
|
2015-02-06 11:36:22 +08:00
|
|
|
|
if (this.$().is(':hidden')) { return; }
|
|
|
|
|
|
|
|
|
|
Ember.run.scheduleOnce('afterRender', this, function() {
|
|
|
|
|
this.$().find(':input:enabled:visible:first').focus();
|
|
|
|
|
});
|
2015-02-03 14:29:53 +08:00
|
|
|
|
},
|
2015-02-02 14:27:59 +08:00
|
|
|
|
|
|
|
|
|
populateControls: function(controls) {
|
|
|
|
|
var controller = this.get('controller');
|
2015-02-03 14:29:53 +08:00
|
|
|
|
var addControl = function(action, icon, title) {
|
|
|
|
|
var control = ActionButton.create({
|
|
|
|
|
icon: icon,
|
|
|
|
|
title: title,
|
2015-02-02 14:27:59 +08:00
|
|
|
|
className: 'btn btn-icon btn-link',
|
|
|
|
|
action: function() {
|
2015-02-03 14:29:53 +08:00
|
|
|
|
controller.send(action);
|
2015-02-02 14:27:59 +08:00
|
|
|
|
}
|
|
|
|
|
});
|
2015-02-03 14:29:53 +08:00
|
|
|
|
controls.pushObjectWithTag(control, action);
|
|
|
|
|
};
|
2015-02-02 14:27:59 +08:00
|
|
|
|
|
2015-02-03 14:29:53 +08:00
|
|
|
|
if (this.get('fullscreen')) {
|
|
|
|
|
addControl('exitFullscreen', 'compress', 'Exit Full Screen');
|
|
|
|
|
} else {
|
|
|
|
|
if (! this.get('minimized')) {
|
|
|
|
|
addControl('minimize', 'minus minimize', 'Minimize');
|
|
|
|
|
addControl('fullscreen', 'expand', 'Full Screen');
|
2015-02-02 14:27:59 +08:00
|
|
|
|
}
|
2015-02-03 14:29:53 +08:00
|
|
|
|
addControl('close', 'times', 'Close');
|
2015-02-02 14:27:59 +08:00
|
|
|
|
}
|
2014-12-20 14:26:46 +08:00
|
|
|
|
}
|
|
|
|
|
});
|