blob: c79a5fac9f31650f0ddd2168b01723a749cda433 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
var $ = require('../util/jquery');
var defineClass = require('../util/define-class');
var Message = require('./message');
var VK_ENTER = 13;
var VK_ESCAPE = 27;
var Messages = defineClass({
statics: {
SLIDE_DOWN: 150,
TTL: 9000,
FADE: 1000,
SLIDE_UP: 300,
OPEN_CLOSE: 400,
OLD: 30000
},
publics: {
close: function() {
var self = this;
var $messages = this.$messages;
if (this.isOpen()) {
if ($messages.children().length) {
var scrollOffset =
$messages.scrollTop() + $messages.height();
this.$messages
.addClass('animating')
.animate({ height: 0 }, {
duration: this.OPEN_CLOSE,
progress: function() {
$messages.scrollTop(
scrollOffset - $messages.height());
},
complete: function() {
$messages.removeClass('animating');
self.$.addClass('headlines');
$messages.attr('style', null);
}
});
} else {
this.$.addClass('headlines');
}
}
},
isClosed: function() {
return this.$.hasClass('headlines');
},
isOpen: function() {
return !this.$.hasClass('headlines') &&
!this.$messages.hasClass('animating');
},
open: function() {
var $messages = this.$messages;
if (this.isClosed()) {
this.$.find('.animating')
.stop(true)
.removeClass('animating')
.attr('style', null);
this.$.removeClass('headlines');
if ($messages.children().length) {
var goalHeight = $messages.height();
$messages
.addClass('animating')
.height(0)
.animate({ height: goalHeight }, {
duration: this.OPEN_CLOSE,
progress: function() {
$messages.scrollTop($messages.prop('scrollHeight'));
},
complete: function() {
$messages.removeClass('animating');
$messages.attr('style', null);
}
});
}
}
this.focus();
},
focus: function() {
this.$content.focus();
},
push: function(message) {
var self = this;
$.each(arguments, function() {
self.pushOne(this);
});
},
setUsername: function(username) {
this.username = username;
this.$username.text(username);
},
toggle: function() {
/* If this were pure CSS, we could just toggle, but we need to do some
* JS housekeeping. */
if (this.isOpen()) {
this.close();
} else if (this.isClosed()) {
this.open();
}
}
},
privates: {
inputKey: function(e) {
switch (e.which) {
case VK_ENTER: {
var raw = this.$content.prop('value');
if (raw) {
var message = Message.info(raw);
message.sender = this.username;
this.$content.prop('value', '');
this.onMessage(message, raw);
}
break;
}
case VK_ESCAPE: {
this.close();
}
}
},
pushOne: function(message) {
var self = this;
if ($.isPlainObject(message)) {
message = new Message(message);
}
this.$messages.append(message.$);
var timestamp = message.getTimestamp();
var isOld = timestamp !== undefined && timestamp !== null &&
Date.now() - timestamp >= Messages.OLD;
if (this.isOpen()) {
this.$messages.scrollTop(this.$messages.prop('scrollHeight'));
}
if (isOld) {
message.$.addClass('history');
} else {
if (!this.isOpen()) {
/*
* Implementation notes: slideDown won't work properly (won't be able
* to calculate goal height) unless the element is in the DOM tree
* prior to the call, so we hide first, attach, and then animate.
* slideDown implicitly shows the element. Furthermore, it won't run
* unless the element starts hidden.
*
* Similarly, we use animate rather than fadeIn because fadeIn
* implicitly hides the element upon completion, resulting in an
* abrupt void in the element flow. Instead, we want to keep the
* element taking up space while invisible until we've collapsed the
* height via slideUp.
*
* It would be best to use CSS animations, but at this time that would
* mean sacrificing either auto-height or flow-affecting sliding.
*/
message.$
.addClass('animating')
.hide()
.slideDown(this.SLIDE_DOWN);
}
message.onLowerPriority.add(function() {
message.$.addClass('history');
if (self.isClosed()) {
message.$
.addClass('animating')
.show()
.delay(Messages.TTL)
.animate({ opacity: 0 }, Messages.FADE)
.slideUp(Messages.SLIDE_UP, function() {
message.$
.removeClass('animating')
.attr('style', null);
});
}
});
}
}
},
constants: ['$'],
/**
* @param message the message object that should be sent.
* @param raw the raw text input.
*/
events: [ 'onMessage' ],
init: function() {
var $handle = $('<div>')
.addClass('handle no-select')
.click(this.toggle);
this.$messages = $('<ul>');
var $send = $('<div>')
.addClass('send')
.append(this.$username = $('<div>')
.addClass('username label'),
$('<div>').append(
this.$content = $('<input>')
.attr('type', 'text')
.keydown(this.inputKey)));
this.$ = $('<div>')
.addClass('messages headlines')
.append($handle, this.$messages, $send);
}
});
module.exports = Messages;