mirror of
https://github.com/moparisthebest/kaiwa
synced 2024-11-12 04:25:05 -05:00
Add avatar changer.
This commit is contained in:
parent
6be75b7f6c
commit
f790aaebc2
@ -186,15 +186,21 @@ module.exports = function (client, app) {
|
||||
|
||||
client.on('avatar', function (info) {
|
||||
var contact = me.getContact(info.jid);
|
||||
if (contact) {
|
||||
var id = '';
|
||||
var type = 'image/png';
|
||||
if (info.avatars.length > 0) {
|
||||
id = info.avatars[0].id;
|
||||
type = info.avatars[0].type || 'image/png';
|
||||
if (!contact) {
|
||||
if (me.isMe(info.jid)) {
|
||||
contact = me;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
contact.setAvatar(id, type);
|
||||
}
|
||||
|
||||
var id = '';
|
||||
var type = 'image/png';
|
||||
if (info.avatars.length > 0) {
|
||||
id = info.avatars[0].id;
|
||||
type = info.avatars[0].type || 'image/png';
|
||||
}
|
||||
contact.setAvatar(id, type);
|
||||
});
|
||||
|
||||
client.on('chatState', function (info) {
|
||||
|
114
clientapp/libraries/resampler.js
Normal file
114
clientapp/libraries/resampler.js
Normal file
@ -0,0 +1,114 @@
|
||||
var Resample = (function (canvas) {
|
||||
|
||||
// (C) WebReflection Mit Style License
|
||||
|
||||
// Resample function, accepts an image
|
||||
// as url, base64 string, or Image/HTMLImgElement
|
||||
// optional width or height, and a callback
|
||||
// to invoke on operation complete
|
||||
function Resample(img, width, height, onresample) {
|
||||
var
|
||||
// check the image type
|
||||
load = typeof img == "string",
|
||||
// Image pointer
|
||||
i = load || img
|
||||
;
|
||||
// if string, a new Image is needed
|
||||
if (load) {
|
||||
i = new Image;
|
||||
// with propers callbacks
|
||||
i.onload = onload;
|
||||
i.onerror = onerror;
|
||||
}
|
||||
// easy/cheap way to store info
|
||||
i._onresample = onresample;
|
||||
i._width = width;
|
||||
i._height = height;
|
||||
// if string, we trust the onload event
|
||||
// otherwise we call onload directly
|
||||
// with the image as callback context
|
||||
load ? (i.src = img) : onload.call(img);
|
||||
}
|
||||
|
||||
// just in case something goes wrong
|
||||
function onerror() {
|
||||
throw ("not found: " + this.src);
|
||||
}
|
||||
|
||||
// called when the Image is ready
|
||||
function onload() {
|
||||
var
|
||||
// minifier friendly
|
||||
img = this,
|
||||
// the desired width, if any
|
||||
width = img._width,
|
||||
// the desired height, if any
|
||||
height = img._height,
|
||||
// the callback
|
||||
onresample = img._onresample
|
||||
;
|
||||
// if width and height are both specified
|
||||
// the resample uses these pixels
|
||||
// if width is specified but not the height
|
||||
// the resample respects proportions
|
||||
// accordingly with orginal size
|
||||
// same is if there is a height, but no width
|
||||
width == null && (width = round(img.width * height / img.height));
|
||||
height == null && (height = round(img.height * width / img.width));
|
||||
// remove (hopefully) stored info
|
||||
delete img._onresample;
|
||||
delete img._width;
|
||||
delete img._height;
|
||||
// when we reassign a canvas size
|
||||
// this clears automatically
|
||||
// the size should be exactly the same
|
||||
// of the final image
|
||||
// so that toDataURL ctx method
|
||||
// will return the whole canvas as png
|
||||
// without empty spaces or lines
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
// drawImage has different overloads
|
||||
// in this case we need the following one ...
|
||||
context.drawImage(
|
||||
// original image
|
||||
img,
|
||||
// starting x point
|
||||
0,
|
||||
// starting y point
|
||||
0,
|
||||
// image width
|
||||
img.width,
|
||||
// image height
|
||||
img.height,
|
||||
// destination x point
|
||||
0,
|
||||
// destination y point
|
||||
0,
|
||||
// destination width
|
||||
width,
|
||||
// destination height
|
||||
height
|
||||
);
|
||||
// retrieve the canvas content as
|
||||
// base4 encoded PNG image
|
||||
// and pass the result to the callback
|
||||
onresample(canvas.toDataURL("image/png"));
|
||||
}
|
||||
|
||||
var
|
||||
// point one, use every time ...
|
||||
context = canvas.getContext("2d"),
|
||||
// local scope shortcut
|
||||
round = Math.round
|
||||
;
|
||||
|
||||
return Resample;
|
||||
|
||||
}(
|
||||
// lucky us we don't even need to append
|
||||
// and render anything on the screen
|
||||
// let's keep this DOM node in RAM
|
||||
// for all resizes we want
|
||||
this.document.createElement("canvas"))
|
||||
);
|
@ -708,14 +708,14 @@ module.exports = function (client) {
|
||||
});
|
||||
|
||||
client.publishAvatar = function (id, data, cb) {
|
||||
client.publish(null, 'urn:xmpp:avatar:data', {
|
||||
client.publish('', 'urn:xmpp:avatar:data', {
|
||||
id: id,
|
||||
avatarData: data
|
||||
}, cb);
|
||||
};
|
||||
|
||||
client.useAvatars = function (info, cb) {
|
||||
client.publish(null, 'urn:xmpp:avatar:metadata', {
|
||||
client.publish('', 'urn:xmpp:avatar:metadata', {
|
||||
id: 'current',
|
||||
avatars: info
|
||||
}, cb);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*global app, client*/
|
||||
/*global app, client, XMPP*/
|
||||
"use strict";
|
||||
|
||||
var HumanModel = require('human-model');
|
||||
@ -20,6 +20,7 @@ module.exports = HumanModel.define({
|
||||
jid: ['object', true],
|
||||
status: ['string', true, ''],
|
||||
avatar: ['string', true, ''],
|
||||
avatarID: ['string', true, ''],
|
||||
connected: ['bool', true, false],
|
||||
shouldAskForAlertsPermission: ['bool', true, false],
|
||||
hasFocus: ['bool', true, false],
|
||||
@ -41,6 +42,44 @@ module.exports = HumanModel.define({
|
||||
}
|
||||
this._activeContact = jid;
|
||||
},
|
||||
setAvatar: function (id, type) {
|
||||
var self = this;
|
||||
|
||||
if (!id) {
|
||||
var gID = XMPP.crypto.createHash('md5').update(this.jid).digest('hex');
|
||||
self.avatar = 'https://gravatar.com/avatar/' + gID + '?s=30&d=mm';
|
||||
return;
|
||||
}
|
||||
|
||||
app.storage.avatars.get(id, function (err, avatar) {
|
||||
if (err) {
|
||||
if (!type) {
|
||||
// We can't find the ID, and we don't know the type, so fallback.
|
||||
var gID = XMPP.crypto.createHash('md5').update(self.jid.bare).digest('hex');
|
||||
self.avatar = 'https://gravatar.com/avatar/' + gID + '?s=30&d=mm';
|
||||
return;
|
||||
}
|
||||
app.whenConnected(function () {
|
||||
client.getAvatar(self.jid.bare, id, function (err, resp) {
|
||||
if (err) return;
|
||||
resp = resp.toJSON();
|
||||
var avatarData = resp.pubsub.retrieve.item.avatarData;
|
||||
var dataURI = 'data:' + type + ';base64,' + avatarData;
|
||||
app.storage.avatars.add({id: id, uri: dataURI});
|
||||
self.set({
|
||||
avatar: dataURI,
|
||||
avatarID: id
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self.set({
|
||||
avatar: avatar.uri,
|
||||
avatarID: avatar.id
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
getContact: function (jid, alt) {
|
||||
if (typeof jid === 'string') jid = new client.JID(jid);
|
||||
if (typeof alt === 'string') alt = new client.JID(alt);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/*global app, me*/
|
||||
/*global app, me, XMPP, client, Resample*/
|
||||
"use strict";
|
||||
|
||||
var BasePage = require('./base');
|
||||
@ -8,10 +8,15 @@ var templates = require('../templates');
|
||||
module.exports = BasePage.extend({
|
||||
template: templates.pages.main,
|
||||
classBindings: {
|
||||
'shouldAskForAlertsPermission': '.enableAlerts'
|
||||
shouldAskForAlertsPermission: '.enableAlerts'
|
||||
},
|
||||
srcBindings: {
|
||||
avatar: '#avatarChanger img'
|
||||
},
|
||||
events: {
|
||||
'click .enableAlerts': 'enableAlerts'
|
||||
'click .enableAlerts': 'enableAlerts',
|
||||
'dragover': 'handleAvatarChangeDragOver',
|
||||
'drop': 'handleAvatarChange'
|
||||
},
|
||||
initialize: function (spec) {
|
||||
me.shouldAskForAlertsPermission = app.notifier.shouldAskPermission();
|
||||
@ -27,5 +32,36 @@ module.exports = BasePage.extend({
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
handleAvatarChangeDragOver: function (e) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
},
|
||||
handleAvatarChange: function (e) {
|
||||
e.preventDefault();
|
||||
var file = e.dataTransfer.files[0];
|
||||
if (file.type.match('image.*')) {
|
||||
console.log('Got an image file!', file.type);
|
||||
var fileTracker = new FileReader();
|
||||
fileTracker.onload = function () {
|
||||
var resampler = new Resample(this.result, 80, 80, function (data) {
|
||||
var b64Data = data.split(',')[1];
|
||||
var id = XMPP.crypto.createHash('sha1').update(atob(b64Data)).digest('hex');
|
||||
console.log(id);
|
||||
app.storage.avatars.add({id: id, uri: data});
|
||||
client.publishAvatar(id, b64Data, function (err, res) {
|
||||
if (err) return;
|
||||
client.useAvatars([{
|
||||
id: id,
|
||||
width: 80,
|
||||
height: 80,
|
||||
type: 'image/png',
|
||||
bytes: b64Data.length
|
||||
}]);
|
||||
});
|
||||
});
|
||||
};
|
||||
fileTracker.readAsDataURL(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -73,6 +73,15 @@ exports.includes.mucListItem = function anonymous(locals) {
|
||||
return buf.join("");
|
||||
};
|
||||
|
||||
// mucMessage.jade compiled template
|
||||
exports.includes.mucMessage = function anonymous(locals) {
|
||||
var buf = [];
|
||||
with (locals || {}) {
|
||||
buf.push('<li><div class="message"><span class="sender">' + jade.escape(null == (jade.interp = message.nick) ? "" : jade.interp) + '</span><span class="timestamp">' + jade.escape(null == (jade.interp = message.created) ? "" : jade.interp) + '</span><p class="body">' + jade.escape(null == (jade.interp = message.body) ? "" : jade.interp) + "</p></div></li>");
|
||||
}
|
||||
return buf.join("");
|
||||
};
|
||||
|
||||
// growlMessage.jade compiled template
|
||||
exports.misc.growlMessage = function anonymous(locals) {
|
||||
var buf = [];
|
||||
@ -122,7 +131,7 @@ exports.pages.groupchat = function anonymous(locals) {
|
||||
exports.pages.main = function anonymous(locals) {
|
||||
var buf = [];
|
||||
with (locals || {}) {
|
||||
buf.push('<section class="page main"><button class="enableAlerts">Enable alerts</button><h1>This space intentionally blank</h1></section>');
|
||||
buf.push('<section class="page main"><button class="enableAlerts">Enable alerts</button><div id="avatarChanger"><h1>Change Avatar</h1><div class="uploadRegion"><p>Drag and drop a new avatar here</p><img width="40" height="40"/><form><input id="uploader" type="file"/></form></div></div><h1>This space intentionally blank</h1></section>');
|
||||
}
|
||||
return buf.join("");
|
||||
};
|
||||
|
@ -1,4 +1,12 @@
|
||||
section.page.main
|
||||
button.enableAlerts Enable alerts
|
||||
|
||||
div#avatarChanger
|
||||
h1 Change Avatar
|
||||
div.uploadRegion
|
||||
p Drag and drop a new avatar here
|
||||
img(width="40", height="40")
|
||||
form
|
||||
input#uploader(type="file")
|
||||
|
||||
h1 This space intentionally blank
|
||||
|
9
public/css/app/settings.css
Normal file
9
public/css/app/settings.css
Normal file
@ -0,0 +1,9 @@
|
||||
.uploadRegion {
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-khtml-border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed #777;
|
||||
}
|
12
public/css/app/settings.styl
Normal file
12
public/css/app/settings.styl
Normal file
@ -0,0 +1,12 @@
|
||||
@import '../_variables'
|
||||
@import '../_mixins'
|
||||
|
||||
.uploadRegion
|
||||
font-size: 12px
|
||||
roundall: 10px
|
||||
border: 1px dashed #777
|
||||
margin: 10px
|
||||
text-align: center
|
||||
|
||||
input
|
||||
width: 100%
|
@ -245,6 +245,67 @@ td {
|
||||
.clearfix {
|
||||
zoom: 1;
|
||||
}
|
||||
body {
|
||||
background: #ecf0f2;
|
||||
color: #222;
|
||||
font-family: 'Lucdia Grande', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
body #pages {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
left: 156px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body #menu {
|
||||
width: 155px;
|
||||
}
|
||||
#connectionOverlay {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
-webkit-transition: all 0.25s linear 0;
|
||||
-o-transition: all 0.25s linear 0;
|
||||
transition: all 0.25s linear 0;
|
||||
-moz-transition: all 0.25s linear 0;
|
||||
}
|
||||
#connectionOverlay.connected {
|
||||
height: 0px;
|
||||
}
|
||||
#connectionOverlay.connected #connectionStatus {
|
||||
top: -51px;
|
||||
}
|
||||
#connectionStatus {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
top: 0px;
|
||||
position: fixed;
|
||||
background-color: #333;
|
||||
border-bottom: 1px solid #000;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
text-align: center;
|
||||
}
|
||||
#connectionStatus span.message {
|
||||
display: inline-block;
|
||||
padding: 0px 10px;
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
}
|
||||
#connectionStatus button {
|
||||
padding: 5px 8px;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
#menu {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
@ -627,66 +688,20 @@ td {
|
||||
.chatBox textarea.editing {
|
||||
background-color: #ff0;
|
||||
}
|
||||
#connectionOverlay {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
-webkit-transition: all 0.25s linear 0;
|
||||
-o-transition: all 0.25s linear 0;
|
||||
transition: all 0.25s linear 0;
|
||||
-moz-transition: all 0.25s linear 0;
|
||||
}
|
||||
#connectionOverlay.connected {
|
||||
height: 0px;
|
||||
}
|
||||
#connectionOverlay.connected #connectionStatus {
|
||||
top: -51px;
|
||||
}
|
||||
#connectionStatus {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
top: 0px;
|
||||
position: fixed;
|
||||
background-color: #333;
|
||||
border-bottom: 1px solid #000;
|
||||
width: 100%;
|
||||
z-index: 9999;
|
||||
.uploadRegion {
|
||||
font-size: 12px;
|
||||
-moz-border-radius: 10px;
|
||||
-webkit-border-radius: 10px;
|
||||
-khtml-border-radius: 10px;
|
||||
-o-border-radius: 10px;
|
||||
-border-radius: 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px dashed #777;
|
||||
margin: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
#connectionStatus span.message {
|
||||
display: inline-block;
|
||||
padding: 0px 10px;
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
}
|
||||
#connectionStatus button {
|
||||
padding: 5px 8px;
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
body {
|
||||
background: #ecf0f2;
|
||||
color: #222;
|
||||
font-family: 'Lucdia Grande', 'Helvetica Neue Light', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
font-size: 17px;
|
||||
font-weight: 500;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
body #pages {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
left: 156px;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body #menu {
|
||||
width: 155px;
|
||||
.uploadRegion input {
|
||||
width: 100%;
|
||||
}
|
||||
.aux header {
|
||||
margin-top: 10%;
|
||||
|
@ -1,8 +1,9 @@
|
||||
@import '_reset'
|
||||
@import '_variables'
|
||||
@import '_mixins'
|
||||
@import 'app/layout'
|
||||
@import 'app/connectionStatus'
|
||||
@import 'app/roster'
|
||||
@import 'app/chat'
|
||||
@import 'app/connectionStatus'
|
||||
@import 'app/layout'
|
||||
@import 'app/settings'
|
||||
@import 'app/aux'
|
||||
|
@ -24,6 +24,7 @@ var clientApp = new Moonboots({
|
||||
libraries: [
|
||||
__dirname + '/clientapp/libraries/zepto.js',
|
||||
__dirname + '/clientapp/libraries/ui.js',
|
||||
__dirname + '/clientapp/libraries/resampler.js',
|
||||
__dirname + '/clientapp/libraries/IndexedDBShim.min.js',
|
||||
__dirname + '/clientapp/libraries/stanza.io.js'
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user