Merge "newui: filetree"

This commit is contained in:
Brad Fitzpatrick 2013-05-25 21:42:48 +00:00 committed by Gerrit Code Review
commit 45287dbd15
5 changed files with 367 additions and 0 deletions

View File

@ -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.

View File

@ -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%;
}

View File

@ -0,0 +1,28 @@
<!doctype html>
<html>
<head>
<title>Filetree</title>
<script src="closure/goog/base.js"></script>
<script src="./deps.js"></script>
<script src="?camli.mode=config&var=CAMLISTORE_CONFIG"></script>
<!-- Begin non-Closure cheating; but depended on by server_connection.js -->
<script type="text/javascript" src="../base64.js"></script>
<script type="text/javascript" src="../Crypto.js"></script>
<script type="text/javascript" src="../SHA1.js"></script>
<!-- End non-Closure cheating -->
<script>
goog.require('camlistore.FiletreePage');
</script>
<link rel="stylesheet" href="filetree.css">
</head>
<body class="cam-filetree-page">
<div class="cam-filetree-nav"><a href="./">Home</a></div>
<h1>FileTree for <span id="curDir" class="cam-filetree-nav"></span> </h1>
<div id="children"></div>
<script>
var page = new camlistore.FiletreePage(CAMLISTORE_CONFIG);
page.decorate(document.body);
</script>
</body>
</html>

View File

@ -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 = "<a href='./?b=" + permanode + "'>" + fileName + "</a>";
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
);
}

View File

@ -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