mirror of
http://git.whoc.org.uk/git/password-manager.git
synced 2025-10-25 17:47:34 +02:00
Implemented Attachments in client
This commit is contained in:
550
frontend/delta/js/Clipperz/PM/UI/AttachmentController.js
Normal file
550
frontend/delta/js/Clipperz/PM/UI/AttachmentController.js
Normal file
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI');
|
||||
|
||||
Clipperz.PM.UI.AttachmentController = function(someParameters) {
|
||||
this.MAX_SIMULTANEOUS_READ = 1;
|
||||
this.MAX_SIMULTANEOUS_UPLOAD = 1;
|
||||
this.MAX_SIMULTANEOUS_DOWNLOAD = 1;
|
||||
this.MAX_SIMULTANEOUS_ENCRYPT = 1;
|
||||
this.MAX_SIMULTANEOUS_DECRYPT = 1;
|
||||
|
||||
this.LATEST_ENCRYPTION_VERSION = '1.0'; // Versions aren't handled completely yet!
|
||||
|
||||
this.fileQueue = [];
|
||||
this.notifications = [];
|
||||
this.operationsCount = null;
|
||||
|
||||
this.encryptedDocument = null;
|
||||
|
||||
this.uploadMessageCallback = someParameters['uploadMessageCallback'];
|
||||
this.downloadMessageCallback = someParameters['downloadMessageCallback'];
|
||||
this.reloadServerStatusCallback = someParameters['reloadServerStatusCallback'];
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, {
|
||||
|
||||
toString: function () {
|
||||
return "Clipperz.PM.UI.AttachmentController";
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
notifyUpdate: function() {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updateAttachmentQueueInfo', this.getQueueInfo(), this.getNotificationsInfo());
|
||||
},
|
||||
|
||||
getQueueInfo: function() {
|
||||
return this.fileQueue;
|
||||
},
|
||||
|
||||
getNotificationsInfo: function() {
|
||||
return this.notifications;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// Entry points
|
||||
//=========================================================================
|
||||
|
||||
addAttachment: function (anAttachment) {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.uploadAttachment", {trace:false});
|
||||
deferredResult.collectResults({
|
||||
'_attachment': MochiKit.Base.partial(MochiKit.Async.succeed, anAttachment),
|
||||
'_record': MochiKit.Base.method(anAttachment, 'record'),
|
||||
'reference': MochiKit.Base.method(anAttachment, 'reference'),
|
||||
'meta': MochiKit.Base.method(anAttachment, 'metadata'),
|
||||
'key': MochiKit.Base.method(anAttachment, 'key'),
|
||||
'nonce': MochiKit.Base.method(anAttachment, 'nonce'),
|
||||
'status': MochiKit.Base.partial(MochiKit.Async.succeed, 'WAITING_READ'),
|
||||
'file': MochiKit.Base.method(anAttachment, 'file'),
|
||||
'recordReference': MochiKit.Base.method(anAttachment.record(), 'reference'),
|
||||
'process': MochiKit.Base.partial(MochiKit.Async.succeed, 'UPLOAD'), // Used only to differentiate notifications
|
||||
}, {trace: false});
|
||||
deferredResult.addMethod(this, 'addFileToQueue');
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
getAttachment: function(anAttachment, aMessageCallback) {
|
||||
if (this.getQueuePosition(anAttachment.reference()) >= 0) {
|
||||
this.removeFileFromQueue(anAttachment.reference());
|
||||
}
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.downloadAttachment", {trace:false});
|
||||
deferredResult.collectResults({
|
||||
'_attachment': MochiKit.Base.partial(MochiKit.Async.succeed, anAttachment),
|
||||
'_record': MochiKit.Base.method(anAttachment, 'record'),
|
||||
'reference': MochiKit.Base.method(anAttachment, 'reference'),
|
||||
'meta': MochiKit.Base.method(anAttachment, 'metadata'),
|
||||
'key': MochiKit.Base.method(anAttachment, 'key'),
|
||||
'nonce': MochiKit.Base.method(anAttachment, 'nonce'),
|
||||
'status': MochiKit.Base.partial(MochiKit.Async.succeed, 'WAITING_DOWNLOAD'),
|
||||
'messageCallback': MochiKit.Async.succeed(aMessageCallback),
|
||||
'process': MochiKit.Base.partial(MochiKit.Async.succeed, 'DOWNLOAD'), // Used only to differentiate notifications
|
||||
}, {trace: false});
|
||||
deferredResult.addCallback(function(aResult){
|
||||
MochiKit.Base.update(aResult, {'messageCallback': aMessageCallback});
|
||||
return aResult;
|
||||
});
|
||||
deferredResult.addMethod(this, 'addFileToQueue');
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
cancelAttachment: function(anAttachment) {
|
||||
var deferredResult;
|
||||
|
||||
var reference = anAttachment.reference()
|
||||
var queueElement = this.getQueueElement(reference);
|
||||
|
||||
if (queueElement) {
|
||||
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.cancelAttachment", {trace:false});
|
||||
deferredResult.addMethod(this, 'updateFileInQueue', reference, {'status': 'CANCELED'});
|
||||
if (queueElement['deferredRequest']) {
|
||||
deferredResult.addMethod(queueElement['deferredRequest'], 'cancel');
|
||||
}
|
||||
|
||||
|
||||
deferredResult.callback();
|
||||
// TODO: We may also want do delete stuff in the queue element
|
||||
} else {
|
||||
deferredResult = MochiKit.Async.succeed();
|
||||
}
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// Queue management
|
||||
//=========================================================================
|
||||
|
||||
dispatchQueueOperations: function() {
|
||||
var currentElement;
|
||||
var processNextElements;
|
||||
|
||||
var count = this.updateOperationsCount();
|
||||
|
||||
this.notifyUpdate();
|
||||
|
||||
processNextElements = true;
|
||||
for (i in this.fileQueue) {
|
||||
if(processNextElements) {
|
||||
currentElement = this.fileQueue[i];
|
||||
switch (currentElement['status']) {
|
||||
case 'WAITING_READ':
|
||||
if ((count['READING']) < this.MAX_SIMULTANEOUS_READ) {
|
||||
this.readFile(currentElement['reference'], currentElement['file']);
|
||||
processNextElements = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'WAITING_ENCRYPT':
|
||||
if (count['ENCRYPTING'] < this.MAX_SIMULTANEOUS_ENCRYPT) {
|
||||
this.encryptFile(currentElement['reference'], currentElement['originalArray'], currentElement['key'], currentElement['nonce']);
|
||||
processNextElements = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'WAITING_UPLOAD':
|
||||
if (count['UPLOADING'] < this.MAX_SIMULTANEOUS_UPLOAD) {
|
||||
this.uploadFile(currentElement['reference'], currentElement['encryptedArray']);
|
||||
processNextElements = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'WAITING_DOWNLOAD':
|
||||
if (count['DOWNLOADING'] < this.MAX_SIMULTANEOUS_DOWNLOAD) {
|
||||
this.downloadFile(currentElement['reference'], currentElement['messageCallback']);
|
||||
processNextElements = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'WAITING_DECRYPT':
|
||||
if (count['DECRYPTING'] < this.MAX_SIMULTANEOUS_DECRYPT) {
|
||||
this.decryptFile(currentElement['reference'], currentElement['encryptedArray'], currentElement['key'], currentElement['nonce']);
|
||||
processNextElements = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'WAITING_SAVE':
|
||||
this.saveFile(currentElement['reference'], currentElement['decryptedArray'], currentElement['meta']['name'], currentElement['meta']['type']);
|
||||
processNextElements = false;
|
||||
Clipperz.Sound.beep();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
updateOperationsCount: function() {
|
||||
var count;
|
||||
|
||||
count = {
|
||||
'WAITING_READ': 0,
|
||||
'READING': 0,
|
||||
'WAITING_ENCRYPT': 0,
|
||||
'ENCRYPTING': 0,
|
||||
'WAITING_UPLOAD': 0,
|
||||
'UPLOADING': 0,
|
||||
'WAITING_DOWNLOAD': 0,
|
||||
'DOWNLOADING': 0,
|
||||
'WAITING_DECRYPT': 0,
|
||||
'DECRYPTING': 0,
|
||||
'WAITING_SAVE': 0,
|
||||
'DONE': 0,
|
||||
'CANCELED': 0,
|
||||
'FAILED': 0,
|
||||
};
|
||||
|
||||
for (var i in this.fileQueue) {
|
||||
count[this.fileQueue[i]['status']]++;
|
||||
}
|
||||
|
||||
this.operationsCount = count;
|
||||
|
||||
return this.operationsCount;
|
||||
},
|
||||
|
||||
addFileToQueue: function(someParameters) {
|
||||
this.fileQueue.push(someParameters);
|
||||
this.addNotification(someParameters);
|
||||
|
||||
this.dispatchQueueOperations();
|
||||
},
|
||||
|
||||
removeFileFromQueue: function(aFileReference) {
|
||||
this.fileQueue.splice(this.getQueuePosition(aFileReference), 1);
|
||||
|
||||
this.dispatchQueueOperations();
|
||||
},
|
||||
|
||||
getQueueElement: function(aFileReference) {
|
||||
var i = this.getQueuePosition(aFileReference);
|
||||
return this.fileQueue[i];
|
||||
},
|
||||
|
||||
updateFileInQueue: function(aFileReference, someParameters) {
|
||||
var queuePosition = this.getQueuePosition(aFileReference);
|
||||
|
||||
MochiKit.Base.update(this.fileQueue[queuePosition], someParameters);
|
||||
|
||||
this.dispatchQueueOperations();
|
||||
},
|
||||
|
||||
appendResult: function(aFileReference, anArray) {
|
||||
var queueElement = this.getQueueElement(aFileReference);
|
||||
queueElement['result'].set(anArray, queueElement['currentByte']);
|
||||
},
|
||||
|
||||
getQueuePosition: function(aFileReference) {
|
||||
var result;
|
||||
|
||||
result = -1;
|
||||
for (var i in this.fileQueue) {
|
||||
if (this.fileQueue[i].reference == aFileReference) {
|
||||
result = i;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// Queue Processing
|
||||
//=========================================================================
|
||||
|
||||
addNotification: function(aQueueElement) {
|
||||
this.notifications.push({
|
||||
'id': this.randomId(),
|
||||
'queueElement': aQueueElement
|
||||
})
|
||||
},
|
||||
|
||||
removeNotification: function(aNotificationId) {
|
||||
var i, position;
|
||||
|
||||
position = -1;
|
||||
for (i in this.notifications) {
|
||||
if (this.notifications[i]['id'] == aNotificationId) {
|
||||
position = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (position >= 0) {
|
||||
this.notifications.splice(position, 1);
|
||||
}
|
||||
|
||||
this.notifyUpdate();
|
||||
},
|
||||
|
||||
randomId: function() {
|
||||
return Clipperz.Crypto.PRNG.defaultRandomGenerator().getRandomBytes(32).toHexString().substring(2);
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Queue Processing: READ
|
||||
//=========================================================================
|
||||
|
||||
readFile: function(aFileReference, aFile) {
|
||||
var reader = new FileReader();
|
||||
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'READING',
|
||||
});
|
||||
|
||||
reader.onload = MochiKit.Base.method(this, 'readFileOnload', aFileReference);
|
||||
reader.readAsArrayBuffer(aFile);
|
||||
},
|
||||
|
||||
readFileOnload: function(aFileReference, anEvent) {
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'WAITING_ENCRYPT',
|
||||
'originalArray': new Uint8Array(anEvent.target.result),
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Queue Processing: ENCRYPT
|
||||
//=========================================================================
|
||||
|
||||
encryptFile: function(aFileReference, anArrayBuffer, aKey, aNonce) {
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'ENCRYPTING',
|
||||
});
|
||||
|
||||
window.crypto.subtle.importKey(
|
||||
"raw",
|
||||
aKey, //this is an example jwk key, "raw" would be an ArrayBuffer
|
||||
{ name: "AES-CBC" }, //this is the algorithm options
|
||||
false, //whether the key is extractable (i.e. can be used in exportKey)
|
||||
["encrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
|
||||
)
|
||||
// .then(MochiKit.Base.method(this, 'doEncrypt', aFileReference, anArrayBuffer, aNonce))
|
||||
.then(this.doEncrypt.bind(this,aFileReference, anArrayBuffer, aNonce))
|
||||
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'encryptFile(): encryption failed'));
|
||||
},
|
||||
|
||||
doEncrypt: function(aFileReference, anArrayBuffer, anIV, aWebcryptoKey) {
|
||||
window.crypto.subtle.encrypt(
|
||||
{
|
||||
name: "AES-CBC",
|
||||
iv: anIV,
|
||||
},
|
||||
aWebcryptoKey,
|
||||
anArrayBuffer
|
||||
)
|
||||
.then(MochiKit.Base.method(this, 'doneEncrypt', aFileReference))
|
||||
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'doEncrypt(): encryption failed'));
|
||||
},
|
||||
|
||||
doneEncrypt: function(aFileReference, anArrayBuffer) {
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'WAITING_UPLOAD',
|
||||
'encryptedArray': new Uint8Array(anArrayBuffer),
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Queue Processing: UPLOAD
|
||||
//=========================================================================
|
||||
|
||||
uploadFile: function(aFileReference, anEncryptedArray) {
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'UPLOADING',
|
||||
'deferredRequest': this.uploadFileRequest(aFileReference, anEncryptedArray),
|
||||
'requestProgress': 0,
|
||||
});
|
||||
},
|
||||
|
||||
uploadFileRequest: function(aFileReference, anEncryptedArray) {
|
||||
var deferredResult;
|
||||
var queueElement = this.getQueueElement(aFileReference);
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.uploadFileRequest", {trace:false});
|
||||
deferredResult.addCallback(this.uploadMessageCallback, {
|
||||
'attachmentReference': queueElement['_attachment'].reference(),
|
||||
'recordReference': queueElement['_attachment'].record().reference(),
|
||||
'arrayBufferData': anEncryptedArray,
|
||||
'version': this.LATEST_ENCRYPTION_VERSION,
|
||||
}, MochiKit.Base.method(this, 'uploadFileProgress', aFileReference));
|
||||
deferredResult.addMethod(this, 'uploadFileDone', aFileReference);
|
||||
deferredResult.addErrback(MochiKit.Base.method(this, 'handleException', aFileReference, 'uploadFileRequest(): request failed or canceled'));
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
uploadFileDone: function(aFileReference, aResult){
|
||||
var record = this.getQueueElement(aFileReference)['_record'];
|
||||
|
||||
return Clipperz.Async.callbacks("AttachmentController.uploadFileDone", [
|
||||
MochiKit.Base.partial(this.reloadServerStatusCallback, record),
|
||||
MochiKit.Base.method(this, 'updateFileInQueue', aFileReference, {
|
||||
'status': 'DONE',
|
||||
'requestProgress': 1,
|
||||
}),
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
uploadFileProgress: function(aFileReference, anEvent) {
|
||||
var newProgress = (anEvent.lengthComputable) ? (anEvent.loaded / anEvent.total) : -1;
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'requestProgress': newProgress,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Queue Processing: DOWNLOAD
|
||||
//=========================================================================
|
||||
|
||||
downloadFile: function(aFileReference) {
|
||||
var deferredRequest;
|
||||
var queueElement = this.getQueueElement(aFileReference);
|
||||
|
||||
deferredRequest = new Clipperz.Async.Deferred("Clipperz.PM.UI.AttachmentController.downloadFile", {trace:false});
|
||||
deferredRequest.addCallback(this.downloadMessageCallback, queueElement['_attachment'], MochiKit.Base.method(this, 'downloadFileProgress', aFileReference));
|
||||
deferredRequest.addMethod(this, 'downloadFileDone', aFileReference);
|
||||
deferredRequest.addErrback(MochiKit.Base.method(this, 'handleException', aFileReference, 'downloadFile(): download filed or canceled'));
|
||||
deferredRequest.callback();
|
||||
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'DOWNLOADING',
|
||||
'deferredRequest': deferredRequest,
|
||||
'requestProgress': 0,
|
||||
});
|
||||
},
|
||||
|
||||
downloadFileDone: function(aFileReference, aResult){
|
||||
var queueElement = this.getQueueElement(aFileReference);
|
||||
var encryptedArray = new Uint8Array(aResult);
|
||||
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'WAITING_DECRYPT',
|
||||
'key': queueElement['key'],
|
||||
'nonce': queueElement['nonce'],
|
||||
'encryptedArray': encryptedArray,
|
||||
'requestProgress': 1,
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
downloadFileProgress: function(aFileReference, anEvent) {
|
||||
var newProgress = (anEvent.lengthComputable) ? (anEvent.loaded / anEvent.total) : -1;
|
||||
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'requestProgress': newProgress,
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Queue Processing: DECRYPT
|
||||
//=========================================================================
|
||||
|
||||
decryptFile: function(aFileReference, anArrayBuffer, aKey, aNonce) {
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'DECRYPTING',
|
||||
});
|
||||
|
||||
window.crypto.subtle.importKey(
|
||||
"raw",
|
||||
aKey, //this is an example jwk key, "raw" would be an ArrayBuffer
|
||||
{name: "AES-CBC"}, //this is the algorithm options
|
||||
false, //whether the key is extractable (i.e. can be used in exportKey)
|
||||
["decrypt"] //can be "encrypt", "decrypt", "wrapKey", or "unwrapKey"
|
||||
)
|
||||
.then(MochiKit.Base.method(this, 'doDecrypt', aFileReference, anArrayBuffer, aNonce))
|
||||
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'decryptFile(): decryption failed'));
|
||||
},
|
||||
|
||||
doDecrypt: function(aFileReference, anArrayBuffer, anIV, aWebcryptoKey) {
|
||||
window.crypto.subtle.decrypt(
|
||||
{name: "AES-CBC", iv: anIV},
|
||||
aWebcryptoKey,
|
||||
anArrayBuffer
|
||||
)
|
||||
.then(MochiKit.Base.method(this, 'doneDecrypt', aFileReference))
|
||||
.catch(MochiKit.Base.method(this, 'handleException', aFileReference, 'doDecrypt(): decryption failed'));
|
||||
},
|
||||
|
||||
doneDecrypt: function(aFileReference, anArrayBuffer) {
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'WAITING_SAVE',
|
||||
'decryptedArray': new Uint8Array(anArrayBuffer),
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Queue Processing: SAVE
|
||||
//=========================================================================
|
||||
|
||||
saveFile: function(aFileReference, anArray, aFileName, aFileType) {
|
||||
var blob = new Blob([anArray], {type: aFileType});
|
||||
saveAs(blob, aFileName);
|
||||
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'DONE',
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
// Exceptions
|
||||
//=========================================================================
|
||||
|
||||
/** Handles exceptions for upload/download and encrypt/decrypt. Note that
|
||||
* an exception is thrown also when the user manually cancels the file
|
||||
* processing. In this case the status remains 'CANCELED'.
|
||||
*/
|
||||
handleException: function(aFileReference, aMessage) {
|
||||
var queueElement = this.getQueueElement(aFileReference);
|
||||
|
||||
if (queueElement['status'] != 'CANCELED') {
|
||||
this.updateFileInQueue(aFileReference, {
|
||||
'status': 'FAILED',
|
||||
});
|
||||
}
|
||||
|
||||
if (aMessage) {
|
||||
console.log("AttachmentController: caught exception (" + aMessage + ")");
|
||||
}
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components');
|
||||
|
||||
Clipperz.PM.UI.Components.AttachmentQueueBoxClass = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
'attachmentQueueInfo': React.PropTypes.object.isRequired,
|
||||
'attachmentQueueBoxStatus': React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'unreadNotifications': [],
|
||||
};
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// Actions
|
||||
//=========================================================================
|
||||
|
||||
handleClose: function(aNotificationElement) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'closeAttachmentNotification', aNotificationElement['id']);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// Render Methods
|
||||
//=========================================================================
|
||||
|
||||
renderNotificationElement: function(aNotificationElement) {
|
||||
var processIcon, status, closeButton, progressIndicator;
|
||||
|
||||
var queueElement = aNotificationElement['queueElement'];
|
||||
|
||||
processIcon = (queueElement['process'] == 'UPLOAD') ? "\u2b06" : "\u2b07";
|
||||
status = "waiting";
|
||||
closeButton = null;
|
||||
progressIndicator = null;
|
||||
if (queueElement['status'] == 'DOWNLOADING' || queueElement['status'] == 'UPLOADING') {
|
||||
status = Math.floor(queueElement['requestProgress']*100) + '%';
|
||||
progressIndicator = React.DOM.span({'className': 'progress'}, Clipperz.PM.UI.Components.RadialProgressIndicator({'progress': queueElement['requestProgress']}));
|
||||
}
|
||||
|
||||
if (queueElement['status'] == 'DONE' || queueElement['status'] == 'CANCELED' || queueElement['status'] == 'FAILED') {
|
||||
status = (queueElement['status'] == 'DONE') ? 'completed' : (queueElement['status'] == 'FAILED') ? "failed" : "canceled";
|
||||
|
||||
closeButton = React.DOM.span({'className': 'close'}, React.DOM.a({
|
||||
'className': 'close',
|
||||
'onClick': MochiKit.Base.method(this, 'handleClose', aNotificationElement),
|
||||
}, "remove field"));
|
||||
}
|
||||
|
||||
return React.DOM.li({},[
|
||||
React.DOM.span({'className': 'contentType'}, Clipperz.PM.DataModel.Attachment.contentTypeIcon(queueElement['meta']['type'])),
|
||||
React.DOM.span({'className': 'name'}, queueElement['meta']['name']),
|
||||
// React.DOM.span({'className': 'size'}, queueElement['meta']['size']),
|
||||
React.DOM.span({'className': 'status'}, [
|
||||
React.DOM.span({'className': 'statusString'}, status),
|
||||
React.DOM.span({'className': 'processIcon'}, processIcon),
|
||||
]),
|
||||
progressIndicator,
|
||||
closeButton,
|
||||
]);
|
||||
},
|
||||
|
||||
renderNotifications: function(someNotifications) {
|
||||
var result;
|
||||
|
||||
if (someNotifications.length == 0) {
|
||||
result = React.DOM.p({}, "No attachments in queue");
|
||||
} else {
|
||||
result = MochiKit.Base.map(MochiKit.Base.method(this, 'renderNotificationElement'), someNotifications);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// renderNotifications: function(someNotifications) {
|
||||
// console.log('AttachmentQueueBox.renderNotifications:', someNotifications);
|
||||
// },
|
||||
|
||||
render: function () {
|
||||
//test
|
||||
this.renderNotifications(this.props['attachmentQueueInfo']['notifications']);
|
||||
|
||||
return React.DOM.div({
|
||||
'className': 'attachmentQueueStatus '+this.props['attachmentQueueBoxStatus'],
|
||||
}, [
|
||||
React.DOM.div({'className': 'arrow'}),
|
||||
React.DOM.ul({}, this.renderNotifications(this.props['attachmentQueueInfo']['notifications'])),
|
||||
]);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.AttachmentQueueBox = React.createFactory(Clipperz.PM.UI.Components.AttachmentQueueBoxClass);
|
||||
@@ -30,22 +30,40 @@ Clipperz.PM.UI.Components.ButtonClass = React.createClass({
|
||||
'eventName': React.PropTypes.string.isRequired,
|
||||
'label': React.PropTypes.string.isRequired,
|
||||
'handler': React.PropTypes.func.isRequired,
|
||||
'className': React.PropTypes.string
|
||||
'className': React.PropTypes.string,
|
||||
'badgeTopContent': React.PropTypes.string,
|
||||
'badgeBottomContent': React.PropTypes.string,
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function () {
|
||||
var badgeTop;
|
||||
var badgeBottom;
|
||||
|
||||
var classes = {
|
||||
'button': true
|
||||
};
|
||||
if (typeof(this.props['className']) != 'undefined') {
|
||||
classes[this.props['className']] = true;
|
||||
};
|
||||
|
||||
badgeTop = null;
|
||||
if (this.props['badgeTopContent']) {
|
||||
badgeTop = React.DOM.span({'className': 'badge top'}, this.props['badgeTopContent']);
|
||||
};
|
||||
|
||||
badgeBottom = null;
|
||||
if (this.props['badgeBottomContent']) {
|
||||
badgeBottom = React.DOM.span({'className': 'badge bottom'}, this.props['badgeBottomContent']);
|
||||
};
|
||||
|
||||
|
||||
return React.DOM.div({className:Clipperz.PM.UI.Components.classNames(classes), onClick:this.props['handler']}, [
|
||||
React.DOM.div({className:this.props['eventName']}, [
|
||||
React.DOM.h3({className:'label'}, this.props['label'])
|
||||
React.DOM.h3({className:'label'}, this.props['label']),
|
||||
badgeTop,
|
||||
badgeBottom,
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,8 @@ Clipperz.PM.UI.Components.CardToolbarClass = React.createClass({
|
||||
'accountInfo': React.PropTypes.object.isRequired,
|
||||
'proxyInfo': React.PropTypes.object.isRequired,
|
||||
'messageBox': React.PropTypes.object.isRequired,
|
||||
'filter': React.PropTypes.object /*.isRequired */
|
||||
'filter': React.PropTypes.object /*.isRequired */,
|
||||
'attachmentQueueInfo': React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
@@ -48,15 +49,31 @@ Clipperz.PM.UI.Components.CardToolbarClass = React.createClass({
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'toggleSettingsPanel');
|
||||
},
|
||||
|
||||
attachmentQueueToggleHandler: function(anEvent) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'toggleAttachmentQueueBox');
|
||||
},
|
||||
|
||||
//============================================================================
|
||||
|
||||
renderWithSidePanels: function () {
|
||||
var attachmentDownloadNotificationNumber = MochiKit.Base.filter(function(anElement) {
|
||||
return anElement['queueElement']['process'] == 'DOWNLOAD';
|
||||
}, this.props['attachmentQueueInfo']['notifications']).length;
|
||||
|
||||
var attachmentUploadNotificationNumber = this.props['attachmentQueueInfo']['notifications'].length - attachmentDownloadNotificationNumber;
|
||||
|
||||
return [
|
||||
React.DOM.div({className:'selectionToggle'}, [
|
||||
Clipperz.PM.UI.Components.Button({eventName:'selectionToggleButton', label:"tags", handler:this.selectionToggleHandler})
|
||||
]),
|
||||
this.renderWithoutSidePanels(),
|
||||
// React.DOM.div({className:'attachmentToggle'}, [
|
||||
// Clipperz.PM.UI.Components.Button({eventName:'attachmentQueueToggleButton', label:"clipperz", handler:this.attachmentQueueToggleHandler})
|
||||
// ]),
|
||||
|
||||
// TODO: validate and adjust names
|
||||
React.DOM.div({className:'settingsToggle'}, [
|
||||
Clipperz.PM.UI.Components.Button({eventName:'attachmentQueueToggleButton', label:"\u2191\u2193", handler:this.attachmentQueueToggleHandler, badgeTopContent: attachmentDownloadNotificationNumber, badgeBottomContent: attachmentUploadNotificationNumber}),
|
||||
Clipperz.PM.UI.Components.Button({eventName:'settingsToggleButton', label:"menu", handler:this.settingsToggleHandler})
|
||||
])
|
||||
];
|
||||
@@ -70,6 +87,8 @@ Clipperz.PM.UI.Components.CardToolbarClass = React.createClass({
|
||||
|
||||
if (this.props['filter']['type'] == 'RECENT') {
|
||||
result = [React.DOM.div({className:'clipperz'}, [React.DOM.span({className:'logo recent'}, "recent")])];
|
||||
} else if (this.props['filter']['type'] == 'WITH_ATTACHMENTS') {
|
||||
result = [React.DOM.div({className:'clipperz'}, [React.DOM.span({className:'logo withAttachments'}, "attachment")])];
|
||||
} else if (this.props['filter']['type'] == 'TAG') {
|
||||
result = [React.DOM.div({className:'clipperz'}, [
|
||||
React.DOM.span({className:'logo tag'}, "tag"),
|
||||
|
||||
@@ -136,6 +136,7 @@ Clipperz.PM.UI.Components.Cards.CommandToolbarClass = React.createClass({
|
||||
var classes = {
|
||||
'cardDetailToolbar': true,
|
||||
'commands': true,
|
||||
'top': true,
|
||||
};
|
||||
classes[style] = true;
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ Clipperz.PM.UI.Components.Cards.DetailClass = React.createClass({
|
||||
result['showGlobalMask'] = this.props['showGlobalMask'];
|
||||
result['allTags'] = this.props['allTags'];
|
||||
result['preferences'] = this.props['preferences'];
|
||||
result['attachmentQueueInfo'] = this.props['attachmentQueueInfo'];
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -41,6 +41,8 @@ Clipperz.PM.UI.Components.Cards.EditClass = React.createClass({
|
||||
'fromFieldPosition': -1,
|
||||
'toFieldPosition': -1,
|
||||
'dropPosition': -1,
|
||||
|
||||
'skippedFiles': [],
|
||||
};
|
||||
},
|
||||
|
||||
@@ -581,7 +583,14 @@ console.log("DROP"); //, anEvent);
|
||||
},
|
||||
|
||||
renderAddNewField: function () {
|
||||
return React.DOM.div({'className':'newCardField', 'onClick':this.addNewField}, "add new field");
|
||||
// return React.DOM.div({'className':'newCardField', 'onClick':this.addNewField}, "add new field");
|
||||
return React.DOM.div({'className':'newCardField', 'onClick':this.addNewField}, [
|
||||
React.DOM.div({'className':'fieldGhostShadow'}, [
|
||||
React.DOM.div({'className':'label'}, ""),
|
||||
React.DOM.div({'className':'value'}, ""),
|
||||
]),
|
||||
React.DOM.div({'className':'addNewFieldButton'}, "add new field"),
|
||||
]);
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
@@ -603,6 +612,210 @@ console.log("DROP"); //, anEvent);
|
||||
|
||||
//............................................................................
|
||||
|
||||
handleRemoveAttachment: function(anAttachment) {
|
||||
// MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'cancelAttachment', anAttachment);
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'removeAttachment', this.record(), anAttachment);
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
uploadFiles: function(someFiles) {
|
||||
var i;
|
||||
|
||||
var newSkippedFiles = [];
|
||||
|
||||
for (i = 0; i < someFiles.length; i++) {
|
||||
var file = someFiles[i];
|
||||
|
||||
if (file.size <= Clipperz.PM.DataModel.Attachment.MAX_ATTACHMENT_SIZE) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'addAttachment', this.record(), file);
|
||||
} else {
|
||||
newSkippedFiles.push(file);
|
||||
this.setState({'skippedFiles': newSkippedFiles});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: check compatibility with all browsers
|
||||
this.refs['attachmentInput'].getDOMNode().value = null;
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
handleFileSelect: function(anEvent) {
|
||||
this.uploadFiles(anEvent.target.files);
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
handleOnDrop: function (anEvent) {
|
||||
anEvent.preventDefault();
|
||||
|
||||
this.uploadFiles(anEvent.dataTransfer.files);
|
||||
},
|
||||
|
||||
handleOnDragOver: function (anEvent) {
|
||||
// Somehow necessary:
|
||||
// http://enome.github.io/javascript/2014/03/24/drag-and-drop-with-react-js.html
|
||||
// https://code.google.com/p/chromium/issues/detail?id=168387
|
||||
// http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
|
||||
anEvent.preventDefault();
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
renderSkippedFiles: function() {
|
||||
var result;
|
||||
|
||||
result = null;
|
||||
if (this.state['skippedFiles'].length > 0) {
|
||||
result = React.DOM.div({'className': 'skippedFiles'},[
|
||||
React.DOM.p({}, "The following files exceed the size limit of " + filesize(Clipperz.PM.DataModel.Attachment.MAX_ATTACHMENT_SIZE)),
|
||||
React.DOM.ul({},
|
||||
MochiKit.Base.map(function(aFile) {
|
||||
return React.DOM.li({}, [
|
||||
React.DOM.span({'className': 'filename'}, aFile.name),
|
||||
React.DOM.span({}, " (" + filesize(aFile.size) + ")"),
|
||||
]);
|
||||
}, this.state['skippedFiles'])
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachmentProgress: function(aStatus, aServerStatus, aProgress) {
|
||||
var result;
|
||||
|
||||
var broken = (! aServerStatus && (! aStatus || aStatus == 'CANCELED' || aStatus == 'FAILED' || aStatus == 'DONE'));
|
||||
|
||||
result = null;
|
||||
if (aStatus == 'UPLOADING' || aStatus == 'DOWNLOADING') {
|
||||
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
|
||||
'progress': aProgress,
|
||||
'border': 1
|
||||
});
|
||||
} else if (! broken && aStatus != 'DONE' && aStatus != 'FAILED' && aServerStatus != 'AVAILABLE') {
|
||||
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
|
||||
'progress': 0,
|
||||
'border': 1,
|
||||
'additionalClasses': ['waiting'],
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachmentStatus: function(aStatus, aServerStatus, aProgress) {
|
||||
var result;
|
||||
|
||||
var status = aStatus ? aStatus : false;
|
||||
|
||||
result = null;
|
||||
|
||||
if (status == 'FAILED') {
|
||||
result = React.DOM.span({'className': 'failed'}, "failed");
|
||||
} else if (status == 'UPLOADING' || status == 'DOWNLOADING') {
|
||||
var actionSymbol = (status == 'UPLOADING') ? "\u2b06" : "\u2b07";
|
||||
result = React.DOM.span({'className': 'progressStatus'}, actionSymbol + Math.floor(aProgress*100) + '%');
|
||||
} else if (aServerStatus != 'AVAILABLE') {
|
||||
switch(status) {
|
||||
case 'CANCELED':
|
||||
result = React.DOM.span({'className': 'broken'}, "canceled");
|
||||
break;
|
||||
case 'DONE':
|
||||
result = React.DOM.span({'className': 'broken'}, "failed");
|
||||
break;
|
||||
case false:
|
||||
result = React.DOM.span({'className': 'broken'}, "failed");
|
||||
break;
|
||||
default:
|
||||
result = React.DOM.span({'className': 'waiting'}, "waiting");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachmentActions: function(aStatus, aServerStatus, anAttachment) {
|
||||
var result;
|
||||
|
||||
result = null;
|
||||
if (aStatus != 'DOWNLOADING') {
|
||||
result = React.DOM.a({
|
||||
'className': 'remove',
|
||||
'onClick': MochiKit.Base.method(this, 'handleRemoveAttachment', anAttachment),
|
||||
}, "remove field");
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachment: function(anAttachment) {
|
||||
var queueInfo = this.props['attachmentQueueInfo'].elementFetchCallback(anAttachment._reference) || [];
|
||||
var queueStatus = queueInfo['status'];
|
||||
var serverStatus = this.props['attachmentServerStatus'][anAttachment._reference];
|
||||
var broken = (! serverStatus && (! queueStatus || queueStatus == 'CANCELED' || queueStatus == 'FAILED' || queueStatus == 'DONE'));
|
||||
|
||||
// console.log(anAttachment['name'], queueStatus)
|
||||
|
||||
var status = this.renderAttachmentStatus(queueStatus, serverStatus, queueInfo['requestProgress']);
|
||||
var actions = this.renderAttachmentActions(queueStatus, serverStatus, anAttachment['_attachment']);
|
||||
var progressIndicator = this.renderAttachmentProgress(queueStatus, serverStatus, queueInfo['requestProgress']);;
|
||||
|
||||
return React.DOM.li({
|
||||
'className': (broken) ? 'broken' : '',
|
||||
'key': anAttachment._reference
|
||||
}, [
|
||||
React.DOM.span({'className': 'contentType'}, Clipperz.PM.DataModel.Attachment.contentTypeIcon(anAttachment.contentType)),
|
||||
React.DOM.span({'className': 'meta'}, [
|
||||
React.DOM.span({'className': 'name'}, anAttachment.name),
|
||||
React.DOM.span({'className': 'size'}, filesize(anAttachment.size)),
|
||||
]),
|
||||
React.DOM.span({'className': 'status'}, status),
|
||||
React.DOM.span({'className': 'progress'}, progressIndicator),
|
||||
React.DOM.span({'className': 'actions'}, actions),
|
||||
])
|
||||
},
|
||||
|
||||
renderAttachments: function(someAttachments) {
|
||||
return React.DOM.div({'className':'cardAttachmentWrapper'}, [
|
||||
React.DOM.div({'className': 'cardAttachments'}, [
|
||||
React.DOM.h3({'className': 'summaryText'}, "Attachments"),
|
||||
// React.DOM.p({'className': 'summaryText'}, someAttachments.length + ' files attached'),
|
||||
this.renderSkippedFiles(),
|
||||
React.DOM.ul({'className': 'attachmentList'},
|
||||
MochiKit.Base.map(MochiKit.Base.method(this, 'renderAttachment'), someAttachments)
|
||||
)
|
||||
]),
|
||||
React.DOM.div({
|
||||
'className': 'cardUploadAttachments',
|
||||
'onClick': MochiKit.Base.bind(function() { this.refs['attachmentInput'].getDOMNode().click() }, this),
|
||||
'onDragOver': this.handleOnDragOver,
|
||||
'onDrop': this.handleOnDrop,
|
||||
},[
|
||||
React.DOM.p({}, "Drag and drop your files here"),
|
||||
React.DOM.p({}, "or"),
|
||||
React.DOM.input({
|
||||
'type': 'file',
|
||||
'id': 'attachmentInput',
|
||||
'className': 'attachmentInput',
|
||||
'name': 'attachmentInput',
|
||||
'ref': 'attachmentInput',
|
||||
'onChange': this.handleFileSelect,
|
||||
'multiple': true
|
||||
}),
|
||||
React.DOM.a({
|
||||
'className': 'button',
|
||||
'onDragOver': this.handleOnDragOver,
|
||||
'onDrop': this.handleOnDrop,
|
||||
}, "select files"),
|
||||
])
|
||||
])
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
render: function () {
|
||||
var classes = {
|
||||
'edit': true
|
||||
@@ -618,8 +831,9 @@ console.log("DROP"); //, anEvent);
|
||||
this.renderTags(this.props['tags']),
|
||||
this.renderFields(this.fields()),
|
||||
this.renderAddNewField(),
|
||||
this.renderAttachments(MochiKit.Base.values(this.props['attachments'])),
|
||||
this.renderNotes(this.props['notes']),
|
||||
this.renderDirectLogins(this.props['directLogins'])
|
||||
this.renderDirectLogins(this.props['directLogins']),
|
||||
])
|
||||
]),
|
||||
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null
|
||||
|
||||
@@ -63,6 +63,7 @@ Clipperz.PM.UI.Components.Cards.ListClass = React.createClass({
|
||||
result = React.DOM.li({'className':Clipperz.PM.UI.Components.classNames(classes), 'onClick': this.handleClick, 'key':anItem['_reference'], 'data-reference':anItem['_reference'], 'data-label':anItem['label']}, [
|
||||
// React.DOM.span({'className':'favicon'}, Clipperz.PM.UI.Components.Cards.FavIcon({'src':anItem['favicon']})),
|
||||
React.DOM.span({'className':'label'}, anItem['label']),
|
||||
React.DOM.span({'className':'attachmentsCount'}, anItem['attachmentsCount'] ? 'attachment' : ''),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ Clipperz.PM.UI.Components.Cards.TagEditorClass = React.createClass({
|
||||
|
||||
renderEditField: function () {
|
||||
return [
|
||||
React.DOM.input({'type':'text', 'list':'tagListData', 'onKeyDown':this.handleKeyDown, 'onBlur':this.handleBlur, 'placeholder': "tag"}),
|
||||
// React.DOM.input({'type':'text', 'list':'tagListData', 'onKeyDown':this.handleKeyDown, 'onBlur':this.handleBlur, 'placeholder': "tag"}),
|
||||
React.DOM.datalist({'id':'tagListData'}, MochiKit.Base.map(function (aTag) { return React.DOM.option({}, aTag); }, this.listOfTagsNotUsedYet()))
|
||||
];
|
||||
},
|
||||
@@ -145,6 +145,7 @@ Clipperz.PM.UI.Components.Cards.TagEditorClass = React.createClass({
|
||||
return React.DOM.div({'className':Clipperz.PM.UI.Components.classNames(classes)}, [
|
||||
React.DOM.ul({},[
|
||||
MochiKit.Base.map(this.renderTag, this.props['selectedTags']),
|
||||
this.isReadOnly() ? null : React.DOM.li({}, React.DOM.input({'type':'text', 'list':'tagListData', 'onKeyDown':this.handleKeyDown, 'onBlur':this.handleBlur, 'placeholder': "tag"})),
|
||||
]),
|
||||
this.isReadOnly() ? null : this.renderEditField()
|
||||
]);
|
||||
|
||||
@@ -63,6 +63,7 @@ Clipperz.PM.UI.Components.Cards.TextAreaClass = React.createClass({
|
||||
handleKeyDown: function (anEvent) {
|
||||
switch (anEvent.keyCode) {
|
||||
case 27: // escape
|
||||
// console.log("ESCAPE");
|
||||
Mousetrap.trigger('esc');
|
||||
break;
|
||||
}
|
||||
@@ -78,7 +79,7 @@ Clipperz.PM.UI.Components.Cards.TextAreaClass = React.createClass({
|
||||
recalculateSize_1: function () {
|
||||
var node = this.getDOMNode();
|
||||
|
||||
node.style.height = 'auto';
|
||||
node.style.height = '33px';
|
||||
node.style.height = node.scrollHeight + 'px';
|
||||
window.scrollTo(window.scrollLeft, (node.scrollTop + node.scrollHeight));
|
||||
},
|
||||
|
||||
@@ -116,6 +116,17 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({
|
||||
return result;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
handleGetAttachment: function (anAttachment) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'getAttachment', anAttachment);
|
||||
},
|
||||
|
||||
handleCancelDownload: function (anAttachment) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'cancelAttachment', anAttachment);
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'closeAttachment', anAttachment);
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
renderEmpty: function () {
|
||||
@@ -251,6 +262,132 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({
|
||||
|
||||
//............................................................................
|
||||
|
||||
renderAttachmentProgress: function(aStatus, aServerStatus, aProgress) {
|
||||
var result;
|
||||
|
||||
var broken = (! aServerStatus && (! aStatus || aStatus == 'CANCELED' || aStatus == 'FAILED' || aStatus == 'DONE'));
|
||||
|
||||
result = null;
|
||||
if (aStatus == 'UPLOADING' || aStatus == 'DOWNLOADING') {
|
||||
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
|
||||
'progress': aProgress,
|
||||
'border': 1
|
||||
});
|
||||
} else if (! broken && aStatus != 'DONE' && aServerStatus != 'AVAILABLE') {
|
||||
result = Clipperz.PM.UI.Components.RadialProgressIndicator({
|
||||
'progress': 0,
|
||||
'border': 1,
|
||||
'additionalClasses': ['waiting'],
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachmentStatus: function(aStatus, aServerStatus, aProgress) {
|
||||
var result;
|
||||
|
||||
var status = aStatus ? aStatus : false;
|
||||
|
||||
result = null;
|
||||
|
||||
if (status == 'FAILED') {
|
||||
result = React.DOM.span({'className': 'failed'}, "failed");
|
||||
} else if (status == 'UPLOADING' || status == 'DOWNLOADING') {
|
||||
var actionSymbol = (status == 'UPLOADING') ? "\u2b06" : "\u2b07";
|
||||
result = React.DOM.span({'className': 'progressStatus'}, actionSymbol + Math.floor(aProgress*100) + '%');
|
||||
} else if (aServerStatus != 'AVAILABLE') {
|
||||
switch(status) {
|
||||
case 'CANCELED':
|
||||
result = React.DOM.span({'className': 'broken'}, "canceled");
|
||||
break;
|
||||
case 'DONE':
|
||||
result = React.DOM.span({'className': 'broken'}, "failed");
|
||||
break;
|
||||
case false:
|
||||
result = React.DOM.span({'className': 'broken'}, "failed");
|
||||
break;
|
||||
default:
|
||||
result = React.DOM.span({'className': 'waiting'}, "waiting");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachmentActions: function(aStatus, aServerStatus, anAttachment) {
|
||||
var result;
|
||||
|
||||
result = null;
|
||||
if (aStatus == 'DOWNLOADING') {
|
||||
result = React.DOM.a({
|
||||
'className': 'cancel',
|
||||
'onClick': MochiKit.Base.method(this, 'handleCancelDownload', anAttachment)
|
||||
}, "remove field");
|
||||
} else if (aServerStatus == 'AVAILABLE') {
|
||||
result = React.DOM.a({
|
||||
'className': 'download',
|
||||
'onClick': MochiKit.Base.method(this, 'handleGetAttachment', anAttachment),
|
||||
}, "\u2b07");
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachment: function (anAttachment) {
|
||||
var result;
|
||||
|
||||
if (this.props['attachmentQueueInfo'].elementFetchCallback != null) {
|
||||
var queueInfo = this.props['attachmentQueueInfo'].elementFetchCallback(anAttachment._reference) || [];
|
||||
var queueStatus = queueInfo['status'];
|
||||
var serverStatus = this.props['attachmentServerStatus'][anAttachment._reference];
|
||||
var broken = (! serverStatus && (! queueStatus || queueStatus == 'CANCELED'));
|
||||
|
||||
var status = this.renderAttachmentStatus(queueStatus, serverStatus, queueInfo['requestProgress']);
|
||||
var actions = this.renderAttachmentActions(queueStatus, serverStatus, anAttachment['_attachment']);
|
||||
var progressIndicator = this.renderAttachmentProgress(queueStatus, serverStatus, queueInfo['requestProgress']);
|
||||
|
||||
result = React.DOM.li({
|
||||
'className': (broken) ? 'broken' : '',
|
||||
'key': anAttachment._reference
|
||||
}, [
|
||||
React.DOM.span({'className': 'contentType'}, Clipperz.PM.DataModel.Attachment.contentTypeIcon(anAttachment.contentType)),
|
||||
React.DOM.span({'className': 'meta'}, [
|
||||
React.DOM.span({'className': 'name'}, anAttachment.name),
|
||||
React.DOM.span({'className': 'size'}, filesize(anAttachment.size)),
|
||||
]),
|
||||
React.DOM.span({'className': 'status'}, status),
|
||||
React.DOM.span({'className': 'progress'}, progressIndicator),
|
||||
React.DOM.span({'className': 'actions'}, actions),
|
||||
])
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderAttachments: function(someAttachments) {
|
||||
var result;
|
||||
|
||||
//console.log("View props:", this.props, someAttachments);
|
||||
if (someAttachments.length > 0) {
|
||||
result = React.DOM.div({'className': 'cardAttachments'}, [
|
||||
React.DOM.h3({'className': 'summaryText'}, "Attachments"),
|
||||
// React.DOM.p({'className': 'summaryText'}, someAttachments.length + ' files attached'),
|
||||
React.DOM.ul({'className': 'attachmentList'},
|
||||
MochiKit.Base.map(MochiKit.Base.method(this, 'renderAttachment'), someAttachments)
|
||||
)
|
||||
]);
|
||||
} else {
|
||||
result = [];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
renderCard: function () {
|
||||
var classes = {
|
||||
'view': true,
|
||||
@@ -263,8 +400,9 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({
|
||||
this.renderLabel(this.props['label']),
|
||||
this.renderTags(this.props['tags']),
|
||||
this.renderFields(this.props['fields']),
|
||||
this.renderAttachments(MochiKit.Base.values(this.props['attachments'])),
|
||||
this.renderNotes(this.props['notes']),
|
||||
this.renderDirectLogins(this.props['directLogins'])
|
||||
this.renderDirectLogins(this.props['directLogins']),
|
||||
]),
|
||||
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null
|
||||
]);
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components');
|
||||
|
||||
Clipperz.PM.UI.Components.DocumentEncryptionSandboxClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'documentMeta': null,
|
||||
'encryptedDocument': null,
|
||||
};
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// Event Handlers
|
||||
//=========================================================================
|
||||
|
||||
handleFileSelect: function(anEvent) {
|
||||
// console.log("handleFileSelect: files:", anEvent.target.files);
|
||||
|
||||
var fileToRead = anEvent.target.files[0];
|
||||
|
||||
// console.log(fileToRead);
|
||||
// return;
|
||||
|
||||
if (fileToRead) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'uploadNewFile', null, fileToRead);
|
||||
}
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// File Handling Methods
|
||||
//=========================================================================
|
||||
// handleEncryptionSynchronously: function(aKey, aByteArray) {
|
||||
// var encryptedDocument;
|
||||
// var startTime, endTime;
|
||||
|
||||
// console.log("Encrypting...");
|
||||
// startTime = Date.now()
|
||||
// encryptedDocument = Clipperz.Crypto.AES.encrypt(aKey, aByteArray);
|
||||
// endTime = Date.now();
|
||||
// console.log("Finished! Encryption took", (endTime-startTime)/1000, "seconds.");
|
||||
|
||||
// this.setState({
|
||||
// 'encryptedDocument': encryptedDocument,
|
||||
// });
|
||||
// },
|
||||
|
||||
downloadFile: function(aFileReference) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadFile', aFileReference);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
// Render Methods
|
||||
//=========================================================================
|
||||
|
||||
renderUploadButton: function() {
|
||||
var result;
|
||||
|
||||
if (window.File && window.FileReader && window.FileList && window.Blob) {
|
||||
result = React.DOM.input({
|
||||
'type': 'file',
|
||||
'id': 'files',
|
||||
'name': 'files',
|
||||
'onChange': this.handleFileSelect,
|
||||
'multiple': false
|
||||
})
|
||||
} else {
|
||||
result = React.DOM.p({}, "Browser doesn't support the FileReader API");
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderQueue: function() {
|
||||
return MochiKit.Base.map(MochiKit.Base.bind(function(anElement) {
|
||||
return React.DOM.li({},[
|
||||
React.DOM.ul({}, [
|
||||
React.DOM.li({}, anElement['meta']['name']),
|
||||
React.DOM.li({}, anElement['meta']['size']),
|
||||
React.DOM.li({}, anElement['status']),
|
||||
React.DOM.li({}, Math.floor((anElement['currentByte']/anElement['meta']['size'])*100)+'%'),
|
||||
React.DOM.li({}, React.DOM.button({
|
||||
'disabled': ! anElement.isDataReady,
|
||||
'onClick': MochiKit.Base.method(this, 'downloadFile', anElement['reference']),
|
||||
}, "Download File")),
|
||||
])
|
||||
|
||||
]);
|
||||
}, this), this.props['attachmentQueueInfo']);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({
|
||||
'style': {
|
||||
'position': 'fixed',
|
||||
'width': '80%',
|
||||
'height': '80%',
|
||||
'background': 'white',
|
||||
'color': 'black',
|
||||
'top': '10%',
|
||||
'left': '10%',
|
||||
'border': '2px solid black',
|
||||
'zIndex': '10'
|
||||
}
|
||||
}, [
|
||||
React.DOM.p({}, "This is the doc encryption proof of concept."),
|
||||
React.DOM.p({}, "*Status*: working streaming encryption and decryption! Current size limit is 250MB"),
|
||||
this.renderUploadButton(),
|
||||
React.DOM.ul({},this.renderQueue()),
|
||||
]);
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.DocumentEncryptionSandbox = React.createFactory(Clipperz.PM.UI.Components.DocumentEncryptionSandboxClass);
|
||||
@@ -135,10 +135,10 @@ Clipperz.PM.UI.Components.ExtraFeatures.OTPClass = React.createClass({
|
||||
'</html>'
|
||||
);
|
||||
|
||||
newWindow.document.close();
|
||||
newWindow.focus();
|
||||
newWindow.print();
|
||||
newWindow.close();
|
||||
// newWindow.document.close();
|
||||
// newWindow.focus();
|
||||
// newWindow.print();
|
||||
// newWindow.close();
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
@@ -34,17 +34,18 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
'tags': React.PropTypes.object,
|
||||
'allTags': React.PropTypes.array,
|
||||
'messageBox': React.PropTypes.object.isRequired,
|
||||
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
'features': React.PropTypes.array.isRequired,
|
||||
'userInfo': React.PropTypes.object.isRequired,
|
||||
'accountInfo': React.PropTypes.object.isRequired,
|
||||
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
|
||||
'locked': React.PropTypes.bool,
|
||||
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
|
||||
// 'cards': React.PropTypes.deferred.isRequired
|
||||
'tags': React.PropTypes.object,
|
||||
'allTags': React.PropTypes.array,
|
||||
'messageBox': React.PropTypes.object.isRequired,
|
||||
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
'features': React.PropTypes.array.isRequired,
|
||||
'userInfo': React.PropTypes.object.isRequired,
|
||||
'accountInfo': React.PropTypes.object.isRequired,
|
||||
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
|
||||
'locked': React.PropTypes.bool,
|
||||
'attachmentQueueInfo': React.PropTypes.object.isRequired,
|
||||
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
|
||||
// 'cards': React.PropTypes.deferred.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
@@ -67,10 +68,11 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
|
||||
classes[this.props['style']] = true;
|
||||
|
||||
result = React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
|
||||
Clipperz.PM.UI.Components.AttachmentQueueBox(this.props),
|
||||
this.props['style'] != 'extra-wide' ? Clipperz.PM.UI.Components.Panels.SelectionPanel(this.props) : null,
|
||||
Clipperz.PM.UI.Components.Panels.MainPanel(this.props),
|
||||
Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanel(this.props),
|
||||
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null
|
||||
this.props['ask'] ? Clipperz.PM.UI.Components.DialogBox(this.props['ask']) : null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,12 @@ Clipperz.PM.UI.Components.Panels.MainPanelClass = React.createClass({
|
||||
//=========================================================================
|
||||
|
||||
propTypes: {
|
||||
'allTags': React.PropTypes.array,
|
||||
'messageBox': React.PropTypes.object.isRequired,
|
||||
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
'features': React.PropTypes.array.isRequired,
|
||||
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
|
||||
'allTags': React.PropTypes.array,
|
||||
'messageBox': React.PropTypes.object.isRequired,
|
||||
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
'features': React.PropTypes.array.isRequired,
|
||||
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
|
||||
'attachmentQueueInfo': React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
||||
style: function () {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components');
|
||||
|
||||
Clipperz.PM.UI.Components.RadialProgressIndicatorClass = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
'progress': React.PropTypes.number.isRequired,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {};
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
getPathDefinition: function(aRadius, aProgress) {
|
||||
aProgress = (aProgress <= 1) ? aProgress : 1;
|
||||
aProgress = (aProgress >= 0) ? aProgress : 0;
|
||||
|
||||
var pi = Math.PI;
|
||||
var radiantAngle = 2 * pi * aProgress;
|
||||
|
||||
var x = Math.sin( radiantAngle ) * aRadius;
|
||||
var y = Math.cos( radiantAngle ) * - aRadius;
|
||||
var mid = (aProgress > 0.5) ? 1 : 0;
|
||||
|
||||
return 'M 0 0 ' + // Start from origin
|
||||
'v ' + (-aRadius) +' ' + // Straight vertical up
|
||||
'A ' + aRadius + ' ' + aRadius + ' 1 ' + // Arc, vertical radius, horizontal radius, xAxisRotate (?)...
|
||||
mid + ' 1 ' + // ...lrge arc flag,
|
||||
x + ' ' + // ...destination x
|
||||
y + ' z'; // ...destination y, close path
|
||||
|
||||
//[x,y].forEach(function( d ){
|
||||
// d = Math.round( d * 1e3 ) / 1e3;
|
||||
//});
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
getAdditionalClassesString(aList) {
|
||||
var result;
|
||||
|
||||
aList = aList || [];
|
||||
result = aList.join(' ');
|
||||
|
||||
return ' '+result;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var border;
|
||||
|
||||
var additionalClasses = this.getAdditionalClassesString(this.props['additionalClasses']);
|
||||
|
||||
var radius = Clipperz.PM.UI.Components.DEFAULT_RADIUS;
|
||||
var borderSize = (this.props['border']) ? this.props['border'] : 0;
|
||||
var boxSize = 2 * (radius + borderSize) + 2;
|
||||
var center = boxSize/2;
|
||||
|
||||
border = null;
|
||||
if (this.props['border']) {
|
||||
border = React.DOM.circle({
|
||||
'className': 'border',
|
||||
'cx': center,
|
||||
'cy': center,
|
||||
'r': radius+this.props['border'],
|
||||
});
|
||||
}
|
||||
|
||||
return React.DOM.svg({
|
||||
'className': 'radialProgressIndicator' + additionalClasses,
|
||||
'viewBox': '0 0 ' + boxSize + ' ' + boxSize,
|
||||
}, [
|
||||
border,
|
||||
React.DOM.circle({
|
||||
'className': 'background',
|
||||
'cx': center,
|
||||
'cy': center,
|
||||
'r': radius,
|
||||
}),
|
||||
React.DOM.path({
|
||||
'className': 'progress',
|
||||
'transform': 'translate(' + (center) + ', ' + (center) + ')',
|
||||
'd': this.getPathDefinition(radius, this.props['progress'])
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.RadialProgressIndicator = React.createFactory(Clipperz.PM.UI.Components.RadialProgressIndicatorClass);
|
||||
|
||||
Clipperz.PM.UI.Components.DEFAULT_RADIUS = 10; // You can resize the SVG object with CSS anyway
|
||||
@@ -36,6 +36,10 @@ Clipperz.PM.UI.Components.SelectionsClass = React.createClass({
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectRecentCards');
|
||||
},
|
||||
|
||||
selectWithAttachments: function (anEvent) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectCardsWithAttachments');
|
||||
},
|
||||
|
||||
selectUntaggedCards: function (anEvent) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectUntaggedCards');
|
||||
},
|
||||
@@ -76,6 +80,7 @@ Clipperz.PM.UI.Components.SelectionsClass = React.createClass({
|
||||
var filterValue;
|
||||
|
||||
//console.log("SELECTIONS PROPS", this.props);
|
||||
//console.log("withAttachmentCardsCount", this.props['withAttachmentCardsCount']);
|
||||
tagInfo = this.props['tags'] ? this.props['tags'] : {};
|
||||
tags = MochiKit.Base.filter(Clipperz.PM.DataModel.Record.isRegularTag, MochiKit.Base.keys(tagInfo)).sort(Clipperz.Base.caseInsensitiveCompare);
|
||||
archivedCardsCount = this.props['archivedCardsCount'];
|
||||
@@ -94,7 +99,10 @@ Clipperz.PM.UI.Components.SelectionsClass = React.createClass({
|
||||
React.DOM.span({'className':'label'}, "Recent"),
|
||||
React.DOM.span({'className':'count'}, this.props['allCardsCount'] ? '10' : '-')
|
||||
]),
|
||||
// React.DOM.li({'className':'untaggedCards', 'onClick': this.selectUntaggedCards}, "Untagged - " + this.props['untaggedCardsCount'])
|
||||
React.DOM.li({'className':'withAttachmentCards', 'onClick': this.selectWithAttachments}, [
|
||||
React.DOM.span({'className':'label'}, "With attachments"),
|
||||
React.DOM.span({'className':'count'}, this.props['withAttachmentCardsCount'] ? this.props['withAttachmentCardsCount'] : '-')
|
||||
]),
|
||||
React.DOM.li({'className':'untaggedCards', 'onClick': this.selectUntaggedCards}, [
|
||||
React.DOM.span({'className':'label'}, "Untagged"),
|
||||
React.DOM.span({'className':'count'}, this.props['untaggedCardsCount'] ? this.props['untaggedCardsCount'] : '-')
|
||||
|
||||
@@ -35,6 +35,7 @@ Clipperz.PM.UI.MainController = function() {
|
||||
|
||||
this._isSelectionPanelOpen = false;
|
||||
this._isSettingsPanelOpen = false;
|
||||
this._isAttachmentQueueBoxOpen = false;
|
||||
|
||||
this._pageStack = ['loadingPage'];
|
||||
this._overlay = new Clipperz.PM.UI.Components.Overlay();
|
||||
@@ -75,9 +76,9 @@ Clipperz.PM.UI.MainController = function() {
|
||||
'importCards',
|
||||
'downloadExport',
|
||||
'updateProgress',
|
||||
'toggleSelectionPanel', 'hideSelectionPanel', 'toggleSettingsPanel',
|
||||
'toggleSelectionPanel', 'hideSelectionPanel', 'toggleSettingsPanel', 'toggleAttachmentQueueBox',
|
||||
'matchMediaQuery', 'unmatchMediaQuery',
|
||||
'selectAllCards', 'selectRecentCards', 'search', 'tagSelected', 'selectUntaggedCards',
|
||||
'selectAllCards', 'selectRecentCards', 'selectCardsWithAttachments', 'selectUntaggedCards', 'tagSelected', 'search',
|
||||
'refreshCardEditDetail',
|
||||
'saveCardEdits', 'cancelCardEdits',
|
||||
'selectCard',
|
||||
@@ -90,9 +91,16 @@ Clipperz.PM.UI.MainController = function() {
|
||||
'closeHelp',
|
||||
'downloadOfflineCopy',
|
||||
'runDirectLogin', 'removeDirectLogin',
|
||||
'exitSearch'
|
||||
'exitSearch',
|
||||
'updateAttachmentQueueInfo', 'addAttachment', 'removeAttachment', 'getAttachment', 'cancelAttachment', 'closeAttachmentNotification',
|
||||
]);
|
||||
|
||||
this._attachmentController = new Clipperz.PM.UI.AttachmentController({
|
||||
'uploadMessageCallback': MochiKit.Base.method(this, 'uploadMessageCallback'),
|
||||
'downloadMessageCallback': MochiKit.Base.method(this, 'downloadMessageCallback'),
|
||||
'reloadServerStatusCallback': MochiKit.Base.method(this, 'reloadAttachmentServerStatusCallback')
|
||||
});
|
||||
|
||||
Mousetrap.bind(['/'], MochiKit.Base.method(this, 'focusOnSearch'));
|
||||
|
||||
Mousetrap.bind(['left', 'h', 'esc'], MochiKit.Base.method(this, 'exitCurrentSelection'));
|
||||
@@ -205,7 +213,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
renderPages: function (pages) {
|
||||
var self = this;
|
||||
MochiKit.Iter.forEach(pages, function (aPageName) {
|
||||
//console.log("RENDERING", aPageName);
|
||||
self._pages[aPageName] = React.render(
|
||||
Clipperz.PM.UI.Components.Pages[self.capitaliseFirstLetter(aPageName)](self.pageProperties(aPageName)),
|
||||
MochiKit.DOM.getElement(aPageName)
|
||||
@@ -437,7 +444,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
getPassphraseDelegate = MochiKit.Base.partial(MochiKit.Async.succeed, passphrase);
|
||||
user = new Clipperz.PM.DataModel.User({'username':oldUser.username(), 'getPassphraseFunction':getPassphraseDelegate});
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler', {trace:true});
|
||||
deferredResult = new Clipperz.Async.Deferred('MainController.unlock_handler', {trace:false});
|
||||
|
||||
deferredResult.addMethod(unlockPage, 'setProps', {'disabled': true});
|
||||
|
||||
@@ -539,7 +546,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
},
|
||||
|
||||
setUser: function (aUser) {
|
||||
//console.log("SET USER", aUser);
|
||||
this._user = aUser;
|
||||
return this._user;
|
||||
},
|
||||
@@ -604,11 +610,30 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
collectAttachmentInfo: function(anAttachment) {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred('MainController.collectAttachmentInfo', {trace:false});
|
||||
deferredResult.setValue('_attachment'); // The object itself, maybe this should be the only value being passed
|
||||
deferredResult.addMethod(anAttachment, 'reference');
|
||||
deferredResult.setValue('_reference');
|
||||
deferredResult.addMethod(anAttachment, 'name');
|
||||
deferredResult.setValue('name');
|
||||
deferredResult.addMethod(anAttachment, 'contentType');
|
||||
deferredResult.setValue('contentType');
|
||||
deferredResult.addMethod(anAttachment, 'size');
|
||||
deferredResult.setValue('size');
|
||||
deferredResult.values();
|
||||
|
||||
deferredResult.callback(anAttachment);
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
collectRecordInfo: function (aRecord) {
|
||||
var deferredResult;
|
||||
|
||||
//console.log("collectRecordInfo");
|
||||
deferredResult = new Clipperz.Async.Deferred('MainController.collectRecordInfo', {trace:false});
|
||||
deferredResult.setValue('_record');
|
||||
deferredResult.addMethod(aRecord, 'reference');
|
||||
@@ -631,12 +656,21 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.setValue('fields');
|
||||
|
||||
deferredResult.addMethod(aRecord, 'attachments');
|
||||
deferredResult.addCallback(MochiKit.Base.values);
|
||||
deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.method(this, 'collectAttachmentInfo'));
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.setValue('attachments');
|
||||
|
||||
deferredResult.addMethod(aRecord, 'directLogins');
|
||||
deferredResult.addCallback(MochiKit.Base.values);
|
||||
deferredResult.addCallback(MochiKit.Base.map, MochiKit.Base.method(this, 'collectDirectLoginInfo'));
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.setValue('directLogins');
|
||||
|
||||
deferredResult.addMethod(aRecord, 'getAttachmentServerStatus');
|
||||
deferredResult.setValue('attachmentServerStatus');
|
||||
|
||||
deferredResult.values();
|
||||
|
||||
deferredResult.callback(aRecord);
|
||||
@@ -668,8 +702,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
deferredResult = new Clipperz.Async.Deferred('MainController.updateSelectedCard', {trace:false});
|
||||
deferredResult.addMethod(this.user(), 'getRecord', someInfo['reference']);
|
||||
|
||||
// deferredResult.addMethod(this, function(d) {console.log(d); return d;});
|
||||
|
||||
deferredResult.addMethod(this, 'collectRecordInfo');
|
||||
deferredResult.addMethod(this, 'setPageProperties', 'mainPage', 'selectedCard');
|
||||
if ((this.mediaQueryStyle() == 'narrow') && shouldShowCardDetail) {
|
||||
@@ -774,6 +806,10 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
filterCriteria = MochiKit.Base.operator.truth;
|
||||
sortCriteria = Clipperz.Base.reverseComparator(MochiKit.Base.keyComparator('_accessDate'));
|
||||
rangeFilter = function (someCards) { return someCards.slice(0, 10)};
|
||||
} else if (aFilter['type'] == 'WITH_ATTACHMENTS') {
|
||||
filterCriteria = function (aRecordInfo) { return aRecordInfo.attachmentsCount > 0; };
|
||||
sortCriteria = Clipperz.Base.caseInsensitiveKeyComparator('label');
|
||||
rangeFilter = MochiKit.Base.operator.identity;
|
||||
} else if (aFilter['type'] == 'SEARCH') {
|
||||
filterCriteria = this.regExpFilterGenerator(Clipperz.PM.DataModel.Record.regExpForSearch(aFilter['value']));
|
||||
sortCriteria = Clipperz.Base.caseInsensitiveKeyComparator('label');
|
||||
@@ -879,7 +915,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
MochiKit.Base.method(this.user(), 'getPreference', 'lock'),
|
||||
MochiKit.Base.bind(function (someLockInfo) {
|
||||
if (this._lockTimeout) {
|
||||
// console.log("clearing previous lock timer");
|
||||
clearTimeout(this._lockTimeout);
|
||||
}
|
||||
|
||||
@@ -902,7 +937,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
? MochiKit.Async.succeed
|
||||
: MochiKit.Base.partial(MochiKit.Base.filter, function (someRecordInfo) { return ! someRecordInfo['_isArchived']; });
|
||||
|
||||
return Clipperz.Async.callbacks("MainController.getUntaggedCardsCount", [
|
||||
return Clipperz.Async.callbacks("MainController.getAllCardsCount", [
|
||||
MochiKit.Base.method(this.user(), 'getRecords'),
|
||||
MochiKit.Base.partial(MochiKit.Base.map, Clipperz.Async.collectResults("collectResults", {'_fullLabel':MochiKit.Base.methodcaller('fullLabel'), '_isArchived':MochiKit.Base.methodcaller('isArchived')}, {trace:false})),
|
||||
Clipperz.Async.collectAll,
|
||||
@@ -910,7 +945,23 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
function (someCards) { return someCards.length; },
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
|
||||
getCardsWithAttachmentsCount: function () {
|
||||
var archivedCardsFilter = this.shouldIncludeArchivedCards()
|
||||
? MochiKit.Async.succeed
|
||||
: MochiKit.Base.partial(MochiKit.Base.filter, function (someRecordInfo) { return ! someRecordInfo['_isArchived']; });
|
||||
|
||||
return Clipperz.Async.callbacks("MainController.getCardsWithAttachmentsCount", [
|
||||
MochiKit.Base.method(this.user(), 'getRecords'),
|
||||
MochiKit.Base.partial(MochiKit.Base.map, Clipperz.Async.collectResults("collectResults", {'attachmentsCount':MochiKit.Base.methodcaller('attachmentsCount'), '_isArchived':MochiKit.Base.methodcaller('isArchived')}, {trace:false})),
|
||||
Clipperz.Async.collectAll,
|
||||
archivedCardsFilter,
|
||||
function (aResult) {
|
||||
return MochiKit.Base.filter(function (aRecordInfo) { return aRecordInfo['attachmentsCount'] > 0; }, aResult).length;
|
||||
}
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
getArchivedCardsCount: function () {
|
||||
return Clipperz.Async.callbacks("MainController.getArchivedCardsCount", [
|
||||
MochiKit.Base.method(this.user(), 'getRecords'),
|
||||
@@ -961,6 +1012,8 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'allTags'),
|
||||
MochiKit.Base.method(this, 'getAllCardsCount'),
|
||||
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'allCardsCount'),
|
||||
MochiKit.Base.method(this, 'getCardsWithAttachmentsCount'),
|
||||
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'withAttachmentCardsCount'),
|
||||
MochiKit.Base.method(this, 'getArchivedCardsCount'),
|
||||
MochiKit.Base.method(this, 'setPageProperties', 'mainPage', 'archivedCardsCount'),
|
||||
MochiKit.Base.method(this, 'getUntaggedCardsCount'),
|
||||
@@ -979,9 +1032,12 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
//=========================================================================
|
||||
|
||||
runApplication: function (anUser) {
|
||||
window.history.replaceState({selectedCardInfo: null}, "", window.location.toString());
|
||||
window.history.pushState({selectedCardInfo: null}, "", window.location.toString()); // Hack to support filters undo with no other actions
|
||||
this.moveInPage(this.currentPage(), 'mainPage');
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'enableLock');
|
||||
this.resetLockTimeout();
|
||||
this.registerHistoryHandler();
|
||||
|
||||
return this.renderAccountData();
|
||||
},
|
||||
@@ -1129,11 +1185,13 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'LEFT');
|
||||
this.setCurrentPage(toPage);
|
||||
|
||||
if (shouldAddItemToHistory) {
|
||||
window.history.pushState({'fromPage': fromPage, 'toPage': toPage}, "");
|
||||
} else {
|
||||
// Skipping push on page change: history needs to be more granular
|
||||
// New states are pushed on user actions (view card, back to main page, ...)
|
||||
// if (shouldAddItemToHistory) {
|
||||
// window.history.pushState({'fromPage': fromPage, 'toPage': toPage}, "");
|
||||
// } else {
|
||||
//console.log("Skip HISTORY");
|
||||
}
|
||||
// }
|
||||
} else {
|
||||
//console.log("No need to move in the same page");
|
||||
}
|
||||
@@ -1202,6 +1260,32 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
}
|
||||
},
|
||||
|
||||
attachmentController: function () {
|
||||
return this._attachmentController;
|
||||
},
|
||||
|
||||
attachmentQueueInfo: function() {
|
||||
var queue;
|
||||
var notifications;
|
||||
var elementFetchCallback;
|
||||
|
||||
if (this._attachmentController) {
|
||||
queue = this._attachmentController.getQueueInfo();
|
||||
notifications = this._attachmentController.getNotificationsInfo();
|
||||
elementFetchCallback = MochiKit.Base.method(this._attachmentController, 'getQueueElement');
|
||||
} else {
|
||||
queue = [];
|
||||
notifications = [];
|
||||
elementFetchCallback = null;
|
||||
}
|
||||
|
||||
return {
|
||||
'queue': queue,
|
||||
'notifications': notifications,
|
||||
'elementFetchCallback': elementFetchCallback,
|
||||
}
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
messageBoxContent: function () {
|
||||
@@ -1211,7 +1295,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
message = "";
|
||||
level = 'HIDE';
|
||||
|
||||
//console.log("messageBox - this.user()", this.user());
|
||||
if (this.featureSet() == 'EXPIRED') {
|
||||
message = "Expired subscription";
|
||||
level = 'ERROR';
|
||||
@@ -1261,12 +1344,14 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
'messageBox': this.messageBoxContent(),
|
||||
'userInfo': this.userInfo(),
|
||||
'accountInfo': this.userAccountInfo(),
|
||||
'selectionPanelStatus': this.isSelectionPanelOpen() ? 'OPEN' : 'CLOSED',
|
||||
'settingsPanelStatus': this.isSettingsPanelOpen() ? 'OPEN' : 'CLOSED',
|
||||
'selectionPanelStatus': this.isSelectionPanelOpen() ? 'OPEN' : 'CLOSED',
|
||||
'settingsPanelStatus': this.isSettingsPanelOpen() ? 'OPEN' : 'CLOSED',
|
||||
'attachmentQueueBoxStatus': this.isAttachmentQueueBoxOpen() ? 'OPEN' : 'CLOSED',
|
||||
'featureSet': this.featureSet(),
|
||||
'features': this.features(),
|
||||
'proxyInfo': this.proxyInfo(),
|
||||
'locked': false
|
||||
'locked': false,
|
||||
'attachmentQueueInfo': this.attachmentQueueInfo(),
|
||||
// 'shouldIncludeArchivedCards': this.shouldIncludeArchivedCards(),
|
||||
// 'cards': …,
|
||||
// 'tags': …,
|
||||
@@ -1274,6 +1359,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
};
|
||||
} else if (aPageName == 'cardDetailPage') {
|
||||
extraProperties = {
|
||||
'attachmentQueueInfo': this.attachmentQueueInfo(),
|
||||
};
|
||||
} else if (aPageName == 'errorPage') {
|
||||
extraProperties = {
|
||||
@@ -1367,6 +1453,15 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
this.refreshCurrentPage();
|
||||
},
|
||||
|
||||
isAttachmentQueueBoxOpen: function() {
|
||||
return this._isAttachmentQueueBoxOpen;
|
||||
},
|
||||
|
||||
toggleAttachmentQueueBox_handler: function (anEvent) {
|
||||
this._isAttachmentQueueBoxOpen = !this._isAttachmentQueueBoxOpen;
|
||||
this.refreshCurrentPage();
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
selectedCardInfo: function () {
|
||||
@@ -1487,6 +1582,8 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
selectCard_handler: function (someInfo, shouldUpdateCardDetail) {
|
||||
this.selectCard(someInfo, shouldUpdateCardDetail);
|
||||
window.history.pushState({selectedCardInfo: someInfo}, "", window.location.toString());
|
||||
// console.log("pushing state");
|
||||
},
|
||||
|
||||
refreshCardEditDetail_handler: function (aRecordReference) {
|
||||
@@ -1689,6 +1786,10 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
},
|
||||
|
||||
cancelCardEdits_handler: function (aRecordReference) {
|
||||
return this.cancelCardEdits(aRecordReference);
|
||||
},
|
||||
|
||||
cancelCardEdits: function (aRecordReference) {
|
||||
var currentPage = this.pages()[this.currentPage()];
|
||||
var self = this;
|
||||
var wasBrandNew;
|
||||
@@ -1699,7 +1800,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
function (aValue) { wasBrandNew = aValue },
|
||||
|
||||
MochiKit.Base.method(this.user(), 'hasPendingChanges'),
|
||||
//function (aValue) { console.log("2- USER.hasPendingChanges()", aValue); return aValue; },
|
||||
Clipperz.Async.deferredIf('HasPendingChanges',[
|
||||
MochiKit.Base.method(self, 'ask', {
|
||||
'question': "There are pending changes to your card. Ignore changes?",
|
||||
@@ -1808,7 +1908,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
cloneCard_handler: function (anEvent) {
|
||||
var cardInfo;
|
||||
|
||||
//console.log("CLONE CARD", anEvent['reference']);
|
||||
return Clipperz.Async.callbacks("MainController.cloneCard_handler", [
|
||||
MochiKit.Base.method(this.user(), 'getRecord', anEvent['reference']),
|
||||
MochiKit.Base.method(this.user(), 'cloneRecord'),
|
||||
@@ -1827,6 +1926,11 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
MochiKit.Base.method(this, 'refreshUI'),
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
isPageInEditMode: function() {
|
||||
var currentPage = this.pages()[this.currentPage()];
|
||||
return currentPage.props['mode'] == 'edit';
|
||||
},
|
||||
|
||||
enterEditMode: function () {
|
||||
var currentPage = this.pages()[this.currentPage()];
|
||||
@@ -1844,8 +1948,6 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
},
|
||||
|
||||
editCard_handler: function (anEvent) {
|
||||
//console.log("EDIT CARD", anEvent['reference']);
|
||||
// this.pages()[this.currentPage()].setProps({'mode': 'edit'});
|
||||
this.enterEditMode();
|
||||
},
|
||||
|
||||
@@ -1881,11 +1983,16 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
goBackToMainPage_handler: function (anEvent) {
|
||||
this.goBackToMainPage(anEvent);
|
||||
window.history.pushState({selectedCardInfo: null}, "", window.location.toString());
|
||||
},
|
||||
|
||||
//============================================================================
|
||||
|
||||
selectAllCards_handler: function () {
|
||||
this.selectAllCards();
|
||||
},
|
||||
|
||||
selectAllCards: function () {
|
||||
this.setPageProperties('mainPage', 'searchTerm', '');
|
||||
this.resetSelectedCard();
|
||||
this.setFilter('ALL');
|
||||
@@ -1900,6 +2007,13 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
return this.refreshSelectedCards();
|
||||
},
|
||||
|
||||
selectCardsWithAttachments_handler: function () {
|
||||
this.resetSelectedCard();
|
||||
this.setFilter('WITH_ATTACHMENTS');
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'hideSelectionPanel');
|
||||
return this.refreshSelectedCards();
|
||||
},
|
||||
|
||||
search_handler: function (aValue) {
|
||||
this.resetSelectedCard();
|
||||
|
||||
@@ -2044,7 +2158,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
prevCardInfo = this.previousCardInfo();
|
||||
shouldUpdateCardDetail = this.shouldShowCardDetailWhenMovingBetweenCardsUsingKeys();
|
||||
//console.log("PREV CARD INFO", prevCardInfo);
|
||||
|
||||
if (prevCardInfo != null) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectCard', prevCardInfo, shouldUpdateCardDetail);
|
||||
}
|
||||
@@ -2056,7 +2170,7 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
nextCardInfo = this.nextCardInfo();
|
||||
shouldUpdateCardDetail = this.shouldShowCardDetailWhenMovingBetweenCardsUsingKeys();
|
||||
//console.log("NEXT CARD INFO", shouldUpdateCardDetail);
|
||||
|
||||
if (nextCardInfo != null) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'selectCard', nextCardInfo, shouldUpdateCardDetail);
|
||||
}
|
||||
@@ -2086,6 +2200,128 @@ Clipperz.log("THE BROWSER IS OFFLINE");
|
||||
},
|
||||
|
||||
//============================================================================
|
||||
|
||||
updateAttachmentQueueInfo_handler: function(someProperties) {
|
||||
this.setPageProperties(this.currentPage(), 'attachmentQueueInfo', this.attachmentQueueInfo());
|
||||
},
|
||||
|
||||
addAttachment_handler: function(aRecord, aFile) { // aReference, someMetadata, aKey, aNonce
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.addCardAttachment_handler", {'trace':false});
|
||||
|
||||
deferredResult.addMethod(aRecord, 'createNewAttachment');
|
||||
deferredResult.addCallback(MochiKit.Base.methodcaller('setFile', aFile));
|
||||
deferredResult.addMethod(this.attachmentController(), 'addAttachment');
|
||||
deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'refreshCardEditDetail', aRecord.reference());
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
uploadMessageCallback: function(someArguments, aProgressCallback) {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.uploadMessageCallback", {'trace':false});
|
||||
deferredResult.addMethod(this.user().connection(), 'uploadAttachment', someArguments, aProgressCallback);
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
removeAttachment_handler: function(aRecord, anAttachment) {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.removeAttachment_handler", {trace: false});
|
||||
deferredResult.addCallback(MochiKit.Base.method(this, 'ask', {
|
||||
'question': "Do you really want to delete this attachment?",
|
||||
'possibleAnswers':{
|
||||
'cancel': {'label':"No", 'isDefault':true, 'answer':MochiKit.Base.methodcaller('cancel', new MochiKit.Async.CancelledError())},
|
||||
'revert': {'label':"Yes", 'isDefault':false, 'answer':MochiKit.Base.methodcaller('callback')}
|
||||
}
|
||||
})),
|
||||
deferredResult.addMethod(this, 'cancelAttachment_handler', anAttachment);
|
||||
deferredResult.addMethod(aRecord, 'removeAttachment', anAttachment);
|
||||
deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'refreshCardEditDetail', aRecord.reference());
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
getAttachment_handler: function(anAttachment) {
|
||||
this.attachmentController().getAttachment(anAttachment);
|
||||
},
|
||||
|
||||
downloadMessageCallback: function(anAttachment, aProgressCallback) {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.downloadAttachment_handler", {'trace':false});
|
||||
deferredResult.addMethod(this.user().connection(), 'downloadAttachment', {
|
||||
'reference': anAttachment.reference()
|
||||
}, aProgressCallback);
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
cancelAttachment_handler: function(anAttachment) {
|
||||
return this.attachmentController().cancelAttachment(anAttachment);
|
||||
},
|
||||
|
||||
closeAttachmentNotification_handler: function(aNotificationId) {
|
||||
return this.attachmentController().removeNotification(aNotificationId);
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
reloadAttachmentServerStatusCallback: function(aRecord) {
|
||||
return Clipperz.Async.callbacks("MainController.reloadAttachmentServerStatus_handler", [
|
||||
MochiKit.Base.method(this.user(), 'getRecordDetail', aRecord),
|
||||
MochiKit.Base.bind(function () {
|
||||
if (this._selectedCardInfo && this._selectedCardInfo['reference']) {
|
||||
return this.refreshUI(this._selectedCardInfo['reference']);
|
||||
}
|
||||
}, this),
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
//============================================================================
|
||||
|
||||
registerHistoryHandler: function() {
|
||||
window.onpopstate = MochiKit.Base.method(this, 'handleOnpopstate');
|
||||
},
|
||||
|
||||
handleOnpopstate: function (anEvent) {
|
||||
if (this.filter().type != 'ALL') {
|
||||
this.selectAllCards();
|
||||
window.history.pushState(window.history.state, "", window.location.toString());
|
||||
} else if(anEvent.state) {
|
||||
if (this.isPageInEditMode()) {
|
||||
window.history.pushState({selectedCardInfo: this.selectedCardInfo()}, "", window.location.toString());
|
||||
this.cancelCardEdits(this.selectedCardReference());
|
||||
} else {
|
||||
if (anEvent.state['selectedCardInfo']) {
|
||||
this.selectCard(anEvent.state['selectedCardInfo'], true);
|
||||
} else {
|
||||
this.selectCard(null, true);
|
||||
this.goBackToMainPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('History changed', anEvent.state);
|
||||
},
|
||||
|
||||
//============================================================================
|
||||
|
||||
|
||||
/*
|
||||
wrongAppVersion: function (anError) {
|
||||
// this.pages()['errorPage'].setProps({message:anError.message});
|
||||
|
||||
Reference in New Issue
Block a user