diff --git a/pkg/server/ui.go b/pkg/server/ui.go index 09e245bc0..3687730c3 100644 --- a/pkg/server/ui.go +++ b/pkg/server/ui.go @@ -428,6 +428,11 @@ func (ui *UIHandler) serveNewUI(rw http.ResponseWriter, req *http.Request) { serveStaticFile(rw, req, newuiFiles, file) return } + if wantsFileTreePage(req) { + file := "/filetree.html" + serveStaticFile(rw, req, newuiFiles, file) + return + } if suffix == "new" { // Add a trailing slash. diff --git a/server/camlistored/newui/filetree.css b/server/camlistored/newui/filetree.css new file mode 100644 index 000000000..5c4b05b14 --- /dev/null +++ b/server/camlistored/newui/filetree.css @@ -0,0 +1,32 @@ +/* +Copyright 2011 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.cam-filetree-page { + font: 16px/1.4 normal Arial, sans-serif; +} +.cam-filetree-nav:before { + content: "["; +} +.cam-filetree-nav:after { + content: "]"; +} +.cam-filetree-newp { + text-decoration: underline; + cursor: pointer; + color: darkgreen; + margin-left: .4em; + font-size: 80%; +} diff --git a/server/camlistored/newui/filetree.html b/server/camlistored/newui/filetree.html new file mode 100644 index 000000000..a1deaccab --- /dev/null +++ b/server/camlistored/newui/filetree.html @@ -0,0 +1,28 @@ + + + + Filetree + + + + + + + + + + + + +
Home
+

FileTree for

+ +
+ + + diff --git a/server/camlistored/newui/filetree.js b/server/camlistored/newui/filetree.js new file mode 100644 index 000000000..faae960be --- /dev/null +++ b/server/camlistored/newui/filetree.js @@ -0,0 +1,281 @@ +/* +Copyright 2011 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * @fileoverview Filetree page. + * + */ +goog.provide('camlistore.FiletreePage'); + +goog.require('goog.dom'); +goog.require('goog.events.EventType'); +goog.require('goog.ui.Component'); +goog.require('camlistore.ServerConnection'); + +/** + * @param {camlistore.ServerType.DiscoveryDocument} config Global config + * of the current server this page is being rendered for. + * @param {goog.dom.DomHelper=} opt_domHelper DOM helper to use. + * + * @extends {goog.ui.Component} + * @constructor + */ +camlistore.FiletreePage = function(config, opt_domHelper) { + goog.base(this, opt_domHelper); + + /** + * @type {Object} + * @private + */ + this.config_ = config; + + /** + * @type {camlistore.ServerConnection} + * @private + */ + this.connection_ = new camlistore.ServerConnection(config); + +}; +goog.inherits(camlistore.FiletreePage, goog.ui.Component); + + +/** + * @type {number} + * @private + */ +camlistore.FiletreePage.prototype.indentStep_ = 20; + + +function getPermanodeParam() { + var blobRef = getQueryParam('d'); + return (blobRef && isPlausibleBlobRef(blobRef)) ? blobRef : null; +} + +// Returns the first value from the query string corresponding to |key|. +// Returns null if the key isn't present. +getQueryParam = function(key) { + var params = document.location.search.substring(1).split('&'); + for (var i = 0; i < params.length; ++i) { + var parts = params[i].split('='); + if (parts.length == 2 && decodeURIComponent(parts[0]) == key) + return decodeURIComponent(parts[1]); + } + return null; +}; + +// Returns true if the passed-in string might be a blobref. +isPlausibleBlobRef = function(blobRef) { + return /^\w+-[a-f0-9]+$/.test(blobRef); +}; + + +/** + * Called when component's element is known to be in the document. + */ +camlistore.FiletreePage.prototype.enterDocument = function() { + camlistore.FiletreePage.superClass_.enterDocument.call(this); + var blobref = getPermanodeParam(); + + if (blobref) { + this.connection_.describeWithThumbnails( + blobref, + 0, + goog.bind(this.handleDescribeBlob_, this, blobref), + function(msg) { + alert("failed to get blob description: " + msg); + } + ); + } +} + +/** + * @param {string} permanode Node to describe. + * @param {camlistore.ServerType.DescribeResponse} describeResult Object of properties for the node. + * @private + */ +camlistore.FiletreePage.prototype.handleDescribeBlob_ = +function(permanode, describeResult) { + var meta = describeResult.meta; + if (!meta[permanode]) { + alert("didn't get blob " + permanode); + return; + } + var binfo = meta[permanode]; + if (!binfo) { + alert("Error describing blob " + permanode); + return; + } + if (binfo.camliType != "directory") { + alert("Does not contain a directory"); + return; + } + this.connection_.getBlobContents( + permanode, + goog.bind(function(data) { + var finfo = JSON.parse(data); + var fileName = finfo.fileName; + var curDir = document.getElementById('curDir'); + curDir.innerHTML = "" + fileName + ""; + this.buildTree_(); + }, this), + function(msg) { + alert("failed to get blobcontents: " + msg); + } + ); +} + +/** + * @private + */ +camlistore.FiletreePage.prototype.buildTree_ = function() { + var blobref = getPermanodeParam(); + var children = goog.dom.getElement("children"); + this.connection_.getFileTree(blobref, + goog.bind(function(jres) { + this.onChildrenFound_(children, 0, jres); + }, this) + ); +} + +/** + * @param {string} div node used as root for the tree + * @param {number} depth how deep we are in the tree, for indenting + * @param {camlistore.ServerType.DescribeResponse} jres describe result + * @private + */ +camlistore.FiletreePage.prototype.onChildrenFound_ = +function(div, depth, jres) { + var indent = depth * camlistore.FiletreePage.prototype.indentStep_; + div.innerHTML = ""; + for (var i = 0; i < jres.children.length; i++) { + var children = jres.children; + var pdiv = goog.dom.createElement("div"); + var alink = goog.dom.createElement("a"); + alink.style.paddingLeft=indent + "px" + alink.id = children[i].blobRef; + switch (children[i].type) { + case 'directory': + goog.dom.setTextContent(alink, "+ " + children[i].name); + goog.events.listen(alink, + goog.events.EventType.CLICK, + goog.bind(function (p, d) { + this.unFold_(p, d); + }, this, alink.id, depth), + false, this + ); + break; + case 'file': + goog.dom.setTextContent(alink, " " + children[i].name); + alink.href = "./?b=" + alink.id; + break; + default: + alert("not a file or dir"); + break; + } + var newPerm = goog.dom.createElement("span"); + newPerm.className = "cam-filetree-newp"; + goog.dom.setTextContent(newPerm, "P"); + goog.events.listen(newPerm, + goog.events.EventType.CLICK, + this.newPermWithContent_(alink.id), + false, this + ); + goog.dom.appendChild(pdiv, alink); + goog.dom.appendChild(pdiv, newPerm); + goog.dom.appendChild(div, pdiv); + } +} + + +/** + * @param {string} content blobref of the content + * @private + */ +camlistore.FiletreePage.prototype.newPermWithContent_ = +function(content) { + var fun = function(e) { + this.connection_.createPermanode( + goog.bind(function(permanode) { + this.connection_.newAddAttributeClaim( + permanode, "camliContent", content, + function() { + alert("permanode created"); + }, + function(msg) { + // TODO(mpl): "cancel" new permanode + alert("set permanode content failed: " + msg); + } + ); + }, this), + function(msg) { + alert("create permanode failed: " + msg); + } + ); + } + return goog.bind(fun, this); +} + + +/** + * @param {string} blobref dir to unfold. + * @param {number} depth so we know how much to indent. + * @private + */ +camlistore.FiletreePage.prototype.unFold_ = +function(blobref, depth) { + var node = goog.dom.getElement(blobref); + var div = goog.dom.createElement("div"); + this.connection_.getFileTree(blobref, + goog.bind(function(jres) { + this.onChildrenFound_(div, depth+1, jres); + insertAfter(node, div); + goog.events.removeAll(node); + goog.events.listen(node, + goog.events.EventType.CLICK, + goog.bind(function(p, d) { + this.fold_(p, d); + }, this, blobref, depth), + false, this + ); + }, this) + ); +} + +function insertAfter( referenceNode, newNode ) { + // nextSibling X2 because of the "P" span + referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling.nextSibling ); +} + +/** + * @param {string} nodeid id of the node to fold. + * @param {depth} depth so we know how much to indent. + * @private + */ +camlistore.FiletreePage.prototype.fold_ = +function(nodeid, depth) { + var node = goog.dom.getElement(nodeid); + // nextSibling X2 because of the "P" span + node.parentNode.removeChild(node.nextSibling.nextSibling); + goog.events.removeAll(node); + goog.events.listen(node, + goog.events.EventType.CLICK, + goog.bind(function(p, d) { + this.unFold_(p, d); + }, this, nodeid, depth), + false, this + ); +} + diff --git a/server/camlistored/newui/server_connection.js b/server/camlistored/newui/server_connection.js index c973239e8..5916cd5a1 100644 --- a/server/camlistored/newui/server_connection.js +++ b/server/camlistored/newui/server_connection.js @@ -153,6 +153,27 @@ function(success, opt_fail, e) { this.handleXhrResponseJson_(success, this.safeFail_(opt_fail), e); }; +/** + * @param {string} blobref root of the tree + * @param {function} success callback with data. + * @param {?Function} opt_fail optional failure calback + */ +camlistore.ServerConnection.prototype.getFileTree = +function(blobref, success, opt_fail) { + + // TODO(mpl): fix when we do the switch to newui. and + // redo it relatively to one of the roots anyway? + var path = "../tree/" + blobref; + + this.sendXhr_( + path, + goog.bind(this.genericHandleSearch_, this, + success, this.safeFail_(opt_fail) + ) + ); +}; + + /** * @param {function(camlistore.ServerType.SearchRecentResponse)} success callback with data. * @param {number=} opt_thumbnailSize