diff --git a/server/camlistored/ui/blob_item.css b/server/camlistored/ui/blob_item.css index d1243b4a4..2d4a809ba 100644 --- a/server/camlistored/ui/blob_item.css +++ b/server/camlistored/ui/blob_item.css @@ -84,3 +84,12 @@ text-decoration: underline; background: #999; } + +.cam-blobitem-dropactive { + outline: 0; /* Do not show an outline when container has focus. */ + border-width: 5px; + border-color: #acf; + border-style: dashed; + position: relative; + border-radius: 5px; +} diff --git a/server/camlistored/ui/blob_item.js b/server/camlistored/ui/blob_item.js index a27de2e32..6d3b1e635 100644 --- a/server/camlistored/ui/blob_item.js +++ b/server/camlistored/ui/blob_item.js @@ -133,6 +133,7 @@ camlistore.BlobItem.prototype.isCollection = function() { return true; }; + /** * @return {string} */ @@ -282,8 +283,48 @@ camlistore.BlobItem.prototype.disposeInternal = function() { * Called when component's element is known to be in the document. */ camlistore.BlobItem.prototype.enterDocument = function() { - camlistore.BlobItem.superClass_.enterDocument.call(this); - // Add event handlers here + camlistore.BlobItem.superClass_.enterDocument.call(this); + + var thumbLink = goog.dom.getFirstElementChild(this.getElement()); + this.eh_.listen( + thumbLink, + goog.events.EventType.DRAGENTER, + this.handleFileDragEnter_); + this.eh_.listen( + thumbLink, + goog.events.EventType.DRAGLEAVE, + this.handleFileDragLeave_); +}; + + +/** + * @param {goog.events.Event} e The drag drop event. + * @private + */ +camlistore.BlobItem.prototype.handleFileDragEnter_ = function(e) { + e.preventDefault(); + e.stopPropagation(); + if (this.isCollection()) { + goog.dom.classes.add(this.getElement(), 'cam-blobitem-dropactive'); + // we could dispatch another custom event to the container, but why bother + // since we can directly access it? + var container = this.getParent(); + container.notifyDragEnter_(this); + } +}; + +/** + * @param {goog.events.Event} e The drag drop event. + * @private + */ +camlistore.BlobItem.prototype.handleFileDragLeave_ = function(e) { + e.preventDefault(); + e.stopPropagation(); + if (this.isCollection()) { + goog.dom.classes.remove(this.getElement(), 'cam-blobitem-dropactive'); + var container = this.getParent(); + container.notifyDragLeave_(this); + } }; @@ -293,5 +334,5 @@ camlistore.BlobItem.prototype.enterDocument = function() { */ camlistore.BlobItem.prototype.exitDocument = function() { camlistore.BlobItem.superClass_.exitDocument.call(this); - // Clear event handlers here + this.eh_.removeAll(); }; diff --git a/server/camlistored/ui/blob_item_container.js b/server/camlistored/ui/blob_item_container.js index 69e04be02..956fabc95 100644 --- a/server/camlistored/ui/blob_item_container.js +++ b/server/camlistored/ui/blob_item_container.js @@ -203,14 +203,6 @@ camlistore.BlobItemContainer.prototype.enterDocument = function() { this.fileDropHandler_, goog.events.FileDropHandler.EventType.DROP, this.handleFileDrop_); - this.eh_.listen( - this.getElement(), - goog.events.EventType.DRAGENTER, - this.handleFileDragEnter_); - this.eh_.listen( - this.getElement(), - goog.events.EventType.DRAGLEAVE, - this.handleFileDragLeave_); }; @@ -531,16 +523,25 @@ camlistore.BlobItemContainer.prototype.resetChildren_ = function() { * @private */ camlistore.BlobItemContainer.prototype.handleFileDrop_ = function(e) { - this.resetDragState_(); - - var files = e.getBrowserEvent().dataTransfer.files; - for (var i = 0, n = files.length; i < n; i++) { - var file = files[i]; - // TODO(bslatkin): Add an uploading item placeholder while the upload - // is in progress. Somehow pipe through the POST progress. - this.connection_.uploadFile( - file, goog.bind(this.handleUploadSuccess_, this, file)); - } + var recipient = this.dragActiveElement_; + // TODO(mpl): I should adapt resetDragState_, but maybe Brett wanted + // to do something different with the whole d&d story, so leaving it + // as it is for now, and not using it. + this.dragActiveElement_ = null; + if (!recipient) { + console.log("No valid target to drag and drop on."); + return; + } + goog.dom.classes.remove(recipient.getElement(), 'cam-blobitem-dropactive'); + + var files = e.getBrowserEvent().dataTransfer.files; + for (var i = 0, n = files.length; i < n; i++) { + var file = files[i]; + // TODO(bslatkin): Add an uploading item placeholder while the upload + // is in progress. Somehow pipe through the POST progress. + this.connection_.uploadFile( + file, goog.bind(this.handleUploadSuccess_, this, file, recipient.blobRef_)); + } }; @@ -550,9 +551,9 @@ camlistore.BlobItemContainer.prototype.handleFileDrop_ = function(e) { * @private */ camlistore.BlobItemContainer.prototype.handleUploadSuccess_ = - function(file, blobRef) { + function(file, recipient, blobRef) { this.connection_.createPermanode( - goog.bind(this.handleCreatePermanodeSuccess_, this, file, blobRef)); + goog.bind(this.handleCreatePermanodeSuccess_, this, file, recipient, blobRef)); }; @@ -563,11 +564,11 @@ camlistore.BlobItemContainer.prototype.handleUploadSuccess_ = * @private */ camlistore.BlobItemContainer.prototype.handleCreatePermanodeSuccess_ = - function(file, blobRef, permanode) { + function(file, recipient, blobRef, permanode) { this.connection_.newSetAttributeClaim( permanode, 'camliContent', blobRef, goog.bind(this.handleSetAttributeSuccess_, this, - file, blobRef, permanode)); + file, recipient, blobRef, permanode)); }; @@ -578,11 +579,11 @@ camlistore.BlobItemContainer.prototype.handleCreatePermanodeSuccess_ = * @private */ camlistore.BlobItemContainer.prototype.handleSetAttributeSuccess_ = - function(file, blobRef, permanode) { - this.connection_.describeWithThumbnails( - permanode, - this.thumbnailSize_, - goog.bind(this.handleDescribeSuccess_, this, permanode)); +function(file, recipient, blobRef, permanode) { + this.connection_.describeWithThumbnails( + permanode, + this.thumbnailSize_, + goog.bind(this.handleDescribeSuccess_, this, recipient, permanode)); }; @@ -592,9 +593,20 @@ camlistore.BlobItemContainer.prototype.handleSetAttributeSuccess_ = * @private */ camlistore.BlobItemContainer.prototype.handleDescribeSuccess_ = - function(permanode, describeResult) { - var item = new camlistore.BlobItem(permanode, describeResult.meta); - this.addChildAt(item, this.hasCreateItem_ ? 1 : 0, true); + function(recipient, permanode, describeResult) { + var item = new camlistore.BlobItem(permanode, describeResult.meta); + this.addChildAt(item, this.hasCreateItem_ ? 1 : 0, true); + if (!recipient) { + return; + } + if (this.hasCreateItem_) { + var createItem = this.getChildAt(0); + if (!createItem || recipient == createItem) { + return; + } + } + this.connection_.newAddAttributeClaim( + recipient, 'camliMember', permanode); }; @@ -608,7 +620,30 @@ camlistore.BlobItemContainer.prototype.resetDragState_ = function() { this.dragDepth_ = 0; }; +/** + * @param {camlistore.BlobItem} blobitem the target collection where we want to drop + * @private + */ +camlistore.BlobItemContainer.prototype.notifyDragEnter_ = +function(blobitem) { + if (this.dragActiveElement_ == null) { + // TODO(mpl): show the drag-message; need to figure out the flickering issue + this.dragActiveElement_ = blobitem; + } +}; +/** + * @param {camlistore.BlobItem} blobitem the target collection where we want to drop + * @private + */ +camlistore.BlobItemContainer.prototype.notifyDragLeave_ = +function(blobitem) { + if (this.dragActiveElement_ === blobitem) { + this.dragActiveElement_ = null; + } +}; + +// TODO(mpl): not using it anymore. remove if Brett is ok with it. /** * @param {goog.events.Event} e The drag enter event. * @private @@ -622,6 +657,7 @@ camlistore.BlobItemContainer.prototype.handleFileDragEnter_ = function(e) { }; +// TODO(mpl): not using it anymore. remove if Brett is ok with it. /** * @param {goog.events.Event} e The drag leave event. * @private diff --git a/server/camlistored/ui/create_item.js b/server/camlistored/ui/create_item.js index 9823c5b6f..030678fd5 100644 --- a/server/camlistored/ui/create_item.js +++ b/server/camlistored/ui/create_item.js @@ -71,8 +71,40 @@ camlistore.CreateItem.prototype.disposeInternal = function() { */ camlistore.CreateItem.prototype.enterDocument = function() { camlistore.CreateItem.superClass_.enterDocument.call(this); + var plusEl = goog.dom.getFirstElementChild(this.getElement()); + this.eh_.listen( + plusEl, + goog.events.EventType.DRAGENTER, + this.handleFileDragEnter_); + this.eh_.listen( + plusEl, + goog.events.EventType.DRAGLEAVE, + this.handleFileDragLeave_); }; +/** + * @param {goog.events.Event} e The drag drop event. + * @private + */ +camlistore.CreateItem.prototype.handleFileDragEnter_ = function(e) { + e.preventDefault(); + e.stopPropagation(); + goog.dom.classes.add(this.getElement(), 'cam-blobitem-dropactive'); + var container = this.getParent(); + container.notifyDragEnter_(this); +}; + +/** + * @param {goog.events.Event} e The drag drop event. + * @private + */ +camlistore.CreateItem.prototype.handleFileDragLeave_ = function(e) { + e.preventDefault(); + e.stopPropagation(); + goog.dom.classes.remove(this.getElement(), 'cam-blobitem-dropactive'); + var container = this.getParent(); + container.notifyDragLeave_(this); +}; /** * Called when component's element is known to have been removed from the diff --git a/server/camlistored/ui/server_connection.js b/server/camlistored/ui/server_connection.js index 8f129eaaa..f1428dff4 100644 --- a/server/camlistored/ui/server_connection.js +++ b/server/camlistored/ui/server_connection.js @@ -447,7 +447,9 @@ function(blobref, success, opt_fail, e) { alert("upload permanode fail, expected blobRef not in response"); return; } - success(blobref); + if (success) { + success(blobref); + } }, this.safeFail_(opt_fail), e