diff --git a/server/camlistored/newui/blob_item.css b/server/camlistored/newui/blob_item.css index 32de02ec6..101e78abe 100644 --- a/server/camlistored/newui/blob_item.css +++ b/server/camlistored/newui/blob_item.css @@ -76,6 +76,10 @@ background: #e6e6b8; } +.cam-blobitem-thumbtitle { + overflow: hidden; + text-overflow: ellipsis +} .cam-blobitem-thumbtitle:hover { text-decoration: underline; background: #999; diff --git a/server/camlistored/newui/blob_item_container.css b/server/camlistored/newui/blob_item_container.css index 7005222b2..c230b6b8c 100644 --- a/server/camlistored/newui/blob_item_container.css +++ b/server/camlistored/newui/blob_item_container.css @@ -1,3 +1,9 @@ .cam-blobitemcontainer { outline: 0; /* Do not show an outline when container has focus. */ + border-width: 2px; + border-color: rgba(0, 0, 0, 0); + border-style: dashed; +} +.cam-blobitemcontainer-dropactive { + border-color: #acf; } diff --git a/server/camlistored/newui/blob_item_container.js b/server/camlistored/newui/blob_item_container.js index ef84f2d84..eb7768992 100644 --- a/server/camlistored/newui/blob_item_container.js +++ b/server/camlistored/newui/blob_item_container.js @@ -10,6 +10,7 @@ goog.require('goog.dom.classes'); goog.require('goog.events.Event'); goog.require('goog.events.EventHandler'); goog.require('goog.events.EventType'); +goog.require('goog.events.FileDropHandler'); goog.require('goog.ui.Container'); goog.require('camlistore.BlobItem'); goog.require('camlistore.CreateItem'); @@ -45,6 +46,27 @@ goog.inherits(camlistore.BlobItemContainer, goog.ui.Container); camlistore.BlobItemContainer.THUMBNAIL_SIZES_ = [25, 50, 75, 100, 150, 200]; +/** + * @type {goog.events.FileDropHandler} + * @private + */ +camlistore.BlobItemContainer.prototype.fileDropHandler_ = null; + + +/** + * @type {Element} + * @private + */ +camlistore.BlobItemContainer.prototype.dragActiveElement_ = null; + + +/** + * @type {number} + * @private + */ +camlistore.BlobItemContainer.prototype.dragDepth_ = 0; + + /** * @type {number} * @private @@ -136,6 +158,22 @@ camlistore.BlobItemContainer.prototype.enterDocument = function() { camlistore.BlobItemContainer.superClass_.enterDocument.call(this); this.resetChildren_(); + + this.fileDropHandler_ = new goog.events.FileDropHandler( + this.getElement()); + this.registerDisposable(this.fileDropHandler_); + this.eh_.listen( + 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_); }; @@ -192,3 +230,103 @@ camlistore.BlobItemContainer.prototype.resetChildren_ = function() { }); } }; + + +/** + * @param {goog.events.Event} e The drag drop event. + * @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)); + } +}; + + +/** + * @private + */ +camlistore.BlobItemContainer.prototype.handleUploadSuccess_ = + function(file, blobRef) { + this.connection_.createPermanode( + goog.bind(this.handleCreatePermanodeSuccess_, this, file, blobRef)); +}; + + +/** + * @private + */ +camlistore.BlobItemContainer.prototype.handleCreatePermanodeSuccess_ = + function(file, blobRef, permanode) { + this.connection_.newSetAttributeClaim( + permanode, 'camliContent', blobRef, + goog.bind(this.handleSetAttributeSuccess_, this, + file, blobRef, permanode)); +}; + + +/** + * @private + */ +camlistore.BlobItemContainer.prototype.handleSetAttributeSuccess_ = + function(file, blobRef, permanode) { + this.connection_.describeWithThumbnails( + permanode, + this.thumbnailSize_, + goog.bind(this.handleDescribeSuccess_, this, permanode)); +}; + + +/** + * @private + */ +camlistore.BlobItemContainer.prototype.handleDescribeSuccess_ = + function(permanode, metaBag) { + var item = new camlistore.BlobItem(permanode, metaBag); + this.addChildAt(item, this.hasCreateItem_ ? 1 : 0, true); +}; + + +/** + * @private + */ +camlistore.BlobItemContainer.prototype.resetDragState_ = function() { + goog.dom.classes.remove(this.getElement(), + 'cam-blobitemcontainer-dropactive'); + this.dragActiveElement_ = null; + this.dragDepth_ = 0; +}; + + +/** + * @param {goog.events.Event} e The drag enter event. + * @private + */ +camlistore.BlobItemContainer.prototype.handleFileDragEnter_ = function(e) { + if (this.dragActiveElement_ == null) { + goog.dom.classes.add(this.getElement(), 'cam-blobitemcontainer-dropactive'); + } + this.dragDepth_ += 1; + this.dragActiveElement_ = e.target; +}; + + +/** + * @param {goog.events.Event} e The drag leave event. + * @private + */ +camlistore.BlobItemContainer.prototype.handleFileDragLeave_ = function(e) { + this.dragDepth_ -= 1; + if (this.dragActiveElement_ === this.getElement() && + e.target == this.getElement() || + this.dragDepth_ == 0) { + this.resetDragState_(); + } +}; diff --git a/server/camlistored/newui/index.html b/server/camlistored/newui/index.html index 9a08564ca..7620daa6e 100644 --- a/server/camlistored/newui/index.html +++ b/server/camlistored/newui/index.html @@ -11,7 +11,7 @@ diff --git a/server/camlistored/newui/server_connection.js b/server/camlistored/newui/server_connection.js index c54b7b465..f6e0f00b5 100644 --- a/server/camlistored/newui/server_connection.js +++ b/server/camlistored/newui/server_connection.js @@ -77,13 +77,60 @@ camlistore.ServerConnection.prototype.getRecentlyUpdatedPermanodesDone_ = }; /** - * @param {function(string)} success Success callback, called with permanode blobref. + * @param {function(string)} success Success callback, called with permanode + * blobref. * @param {Function=} opt_fail Optional fail callback. */ -camlistore.ServerConnection.prototype.createPermanode = function(success, opt_fail) { +camlistore.ServerConnection.prototype.createPermanode = + function(success, opt_fail) { // TODO(bradfitz): stop depending on camli.js. For now, cheating: camliCreateNewPermanode({ success: success, fail: opt_fail }); }; + + +/** + * @param {File} file File to be uploaded. + * @param {function(string)} success Success callback, called with blobref of + * uploaded file. + * @param {Function=} opt_fail Optional fail callback. + */ +camlistore.ServerConnection.prototype.uploadFile = + function(file, success, opt_fail) { + // TODO(bradfitz): stop depending on camli.js. For now, cheating: + camliUploadFile(file, { + success: success, + fail: opt_fail + }); +}; + + +/** + * @param {string} permanode Permanode blobref. + * @param {string} attribute Name of the attribute to set. + * @param {string} value Value to set the attribute to. + * @param {function(string)} success Success callback, called with blobref of + * uploaded file. + * @param {Function=} opt_fail Optional fail callback. + */ +camlistore.ServerConnection.prototype.newSetAttributeClaim = + function(permanode, attribute, value, success, opt_fail) { + // TODO(bradfitz): stop depending on camli.js. For now, cheating: + camliNewSetAttributeClaim(permanode, attribute, value, { + success: success, + fail: opt_fail + }); +}; + + +camlistore.ServerConnection.prototype.describeWithThumbnails = + function(blobref, thumbnailSize, success, opt_fail) { + // TODO(bradfitz): stop depending on camli.js. For now, cheating: + camliDescribeBlob(blobref, { + thumbnails: thumbnailSize, + success: success, + fail: opt_fail + }); +} \ No newline at end of file