1
0
mirror of https://github.com/moparisthebest/kaiwa synced 2024-08-13 17:03:51 -04:00

Add avatar changer.

This commit is contained in:
Lance Stout 2013-09-18 16:24:40 -07:00
parent 6be75b7f6c
commit f790aaebc2
12 changed files with 324 additions and 74 deletions

View File

@ -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) {

View 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"))
);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
}
});

View 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("");
};

View File

@ -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

View 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;
}

View 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%

View File

@ -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%;

View File

@ -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'

View File

@ -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'
],