Implemented Attachments in client
This commit is contained in:
@@ -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
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user