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