stub out import UI, add better keyboard shortcut handler

This commit is contained in:
Max Ogden 2011-07-20 18:55:38 -07:00
parent e2fdf8e953
commit 988c8de288
6 changed files with 443 additions and 30 deletions

View File

@ -22,7 +22,6 @@
<div class="container">
<div class="menu-overlay" style="display: none; z-index: 101; ">&nbsp;</div>
<ul class="menu">
<li><a class="transform" href="JavaScript:void(0);">Transform...</a></li>
</ul>
<div id="header">
<a href="http://github.com/maxogden/recline"><img id="couchLogo" src="images/couch.png"/></a>
@ -55,18 +54,18 @@
</script>
<script type='text/mustache' class="importActionsTemplate">
<li><a class="url" href="JavaScript:void(0);">From an API</a></li>
<li><a class="paste" href="JavaScript:void(0);">Copy & Paste</a></li>
<li><a class="upload" href="JavaScript:void(0);">Upload File</a></li>
<li><a data-action="url" class="menuAction" href="JavaScript:void(0);">From an API</a></li>
<li><a data-action="paste" class="menuAction" href="JavaScript:void(0);">Copy & Paste</a></li>
<li><a data-action="upload" class="menuAction" href="JavaScript:void(0);">Upload File</a></li>
</script>
<script type='text/mustache' class="exportActionsTemplate">
<li><a class="csv" href="JavaScript:void(0);">CSV</a></li>
<li><a class="json" href="JavaScript:void(0);">JSON</a></li>
<li><a data-action="csv" class="menuAction" href="JavaScript:void(0);">CSV</a></li>
<li><a data-action="json" class="menuAction" href="JavaScript:void(0);">JSON</a></li>
</script>
<script type='text/mustache' class="columnActionsTemplate">
<li><a class="transform" href="JavaScript:void(0);">Transform...</a></li>
<li><a data-action="bulkEdit" class="menuAction" href="JavaScript:void(0);">Transform...</a></li>
</script>
<script type='text/mustache' class="titleTemplate"><span id="project-name-button" class="app-path-section">{{db_name}}</span></script>
@ -184,6 +183,67 @@
</div>
</script>
<script type='text/mustache' class="urlImportTemplate">
<div class="dialog-header">
Download and import from a URL or API
</div>
<div class="dialog-body">
<div class="grid-layout layout-full">
<form name="api-import-form" id="sign-in-form">
<table class="form-table">
<tbody>
<tr>
<th>
<label for="url">URL</label>
</th>
<td>
<input type="text" size="65" id="url-input" name="url">
</td>
</tr>
<input type="submit" style="height: 0px; width: 0px; border: none; padding: 0px; display: none;" hidefocus="true" />
</tbody>
</table>
</form>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="pasteImportTemplate">
<div class="dialog-header">
Import raw copy & pasted data
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
<div class="menu-container data-table-cell-editor">
<textarea class="data-table-cell-copypaste-editor" bind="textarea">{{value}}</textarea>
</div>
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="uploadImportTemplate">
<div class="dialog-header">
Upload and import a file
</div>
<div class="dialog-body">
<div class="grid-layout layout-tight layout-full">
IN THE FUTURE I predict the presence of a file upload form here...
</div>
</div>
<div class="dialog-footer">
<button class="okButton button">&nbsp;&nbsp;Import&nbsp;&nbsp;</button>
<button class="cancelButton button">Cancel</button>
</div>
</script>
<script type='text/mustache' class="bulkEditTemplate">
<div class="dialog-header">
Functional transform on column {{name}}

View File

@ -0,0 +1,332 @@
if (typeof module !== 'undefined' && module.exports) {
module.exports = Traverse;
}
function Traverse (obj) {
if (!(this instanceof Traverse)) return new Traverse(obj);
this.value = obj;
}
Traverse.prototype.get = function (ps) {
var node = this.value;
for (var i = 0; i < ps.length; i ++) {
var key = ps[i];
if (!Object.hasOwnProperty.call(node, key)) {
node = undefined;
break;
}
node = node[key];
}
return node;
};
Traverse.prototype.set = function (ps, value) {
var node = this.value;
for (var i = 0; i < ps.length - 1; i ++) {
var key = ps[i];
if (!Object.hasOwnProperty.call(node, key)) node[key] = {};
node = node[key];
}
node[ps[i]] = value;
return value;
};
Traverse.prototype.map = function (cb) {
return walk(this.value, cb, true);
};
Traverse.prototype.forEach = function (cb) {
this.value = walk(this.value, cb, false);
return this.value;
};
Traverse.prototype.reduce = function (cb, init) {
var skip = arguments.length === 1;
var acc = skip ? this.value : init;
this.forEach(function (x) {
if (!this.isRoot || !skip) {
acc = cb.call(this, acc, x);
}
});
return acc;
};
Traverse.prototype.deepEqual = function (obj) {
if (arguments.length !== 1) {
throw new Error(
'deepEqual requires exactly one object to compare against'
);
}
var equal = true;
var node = obj;
this.forEach(function (y) {
var notEqual = (function () {
equal = false;
//this.stop();
return undefined;
}).bind(this);
//if (node === undefined || node === null) return notEqual();
if (!this.isRoot) {
/*
if (!Object.hasOwnProperty.call(node, this.key)) {
return notEqual();
}
*/
if (typeof node !== 'object') return notEqual();
node = node[this.key];
}
var x = node;
this.post(function () {
node = x;
});
var toS = function (o) {
return Object.prototype.toString.call(o);
};
if (this.circular) {
if (Traverse(obj).get(this.circular.path) !== x) notEqual();
}
else if (typeof x !== typeof y) {
notEqual();
}
else if (x === null || y === null || x === undefined || y === undefined) {
if (x !== y) notEqual();
}
else if (x.__proto__ !== y.__proto__) {
notEqual();
}
else if (x === y) {
// nop
}
else if (typeof x === 'function') {
if (x instanceof RegExp) {
// both regexps on account of the __proto__ check
if (x.toString() != y.toString()) notEqual();
}
else if (x !== y) notEqual();
}
else if (typeof x === 'object') {
if (toS(y) === '[object Arguments]'
|| toS(x) === '[object Arguments]') {
if (toS(x) !== toS(y)) {
notEqual();
}
}
else if (x instanceof Date || y instanceof Date) {
if (!(x instanceof Date) || !(y instanceof Date)
|| x.getTime() !== y.getTime()) {
notEqual();
}
}
else {
var kx = Object.keys(x);
var ky = Object.keys(y);
if (kx.length !== ky.length) return notEqual();
for (var i = 0; i < kx.length; i++) {
var k = kx[i];
if (!Object.hasOwnProperty.call(y, k)) {
notEqual();
}
}
}
}
});
return equal;
};
Traverse.prototype.paths = function () {
var acc = [];
this.forEach(function (x) {
acc.push(this.path);
});
return acc;
};
Traverse.prototype.nodes = function () {
var acc = [];
this.forEach(function (x) {
acc.push(this.node);
});
return acc;
};
Traverse.prototype.clone = function () {
var parents = [], nodes = [];
return (function clone (src) {
for (var i = 0; i < parents.length; i++) {
if (parents[i] === src) {
return nodes[i];
}
}
if (typeof src === 'object' && src !== null) {
var dst = copy(src);
parents.push(src);
nodes.push(dst);
Object.keys(src).forEach(function (key) {
dst[key] = clone(src[key]);
});
parents.pop();
nodes.pop();
return dst;
}
else {
return src;
}
})(this.value);
};
function walk (root, cb, immutable) {
var path = [];
var parents = [];
var alive = true;
return (function walker (node_) {
var node = immutable ? copy(node_) : node_;
var modifiers = {};
var keepGoing = true;
var state = {
node : node,
node_ : node_,
path : [].concat(path),
parent : parents[parents.length - 1],
key : path.slice(-1)[0],
isRoot : path.length === 0,
level : path.length,
circular : null,
update : function (x, stopHere) {
if (!state.isRoot) {
state.parent.node[state.key] = x;
}
state.node = x;
if (stopHere) keepGoing = false;
},
'delete' : function () {
delete state.parent.node[state.key];
},
remove : function () {
if (Array.isArray(state.parent.node)) {
state.parent.node.splice(state.key, 1);
}
else {
delete state.parent.node[state.key];
}
},
before : function (f) { modifiers.before = f },
after : function (f) { modifiers.after = f },
pre : function (f) { modifiers.pre = f },
post : function (f) { modifiers.post = f },
stop : function () { alive = false },
block : function () { keepGoing = false }
};
if (!alive) return state;
if (typeof node === 'object' && node !== null) {
state.isLeaf = Object.keys(node).length == 0;
for (var i = 0; i < parents.length; i++) {
if (parents[i].node_ === node_) {
state.circular = parents[i];
break;
}
}
}
else {
state.isLeaf = true;
}
state.notLeaf = !state.isLeaf;
state.notRoot = !state.isRoot;
// use return values to update if defined
var ret = cb.call(state, state.node);
if (ret !== undefined && state.update) state.update(ret);
state.keys = null;
if (modifiers.before) modifiers.before.call(state, state.node);
if (!keepGoing) return state;
if (typeof state.node == 'object'
&& state.node !== null && !state.circular) {
parents.push(state);
var keys = state.keys || Object.keys(state.node);
keys.forEach(function (key, i) {
path.push(key);
if (modifiers.pre) modifiers.pre.call(state, state.node[key], key);
var child = walker(state.node[key]);
if (immutable && Object.hasOwnProperty.call(state.node, key)) {
state.node[key] = child.node;
}
child.isLast = i == keys.length - 1;
child.isFirst = i == 0;
if (modifiers.post) modifiers.post.call(state, child);
path.pop();
});
parents.pop();
}
if (modifiers.after) modifiers.after.call(state, state.node);
return state;
})(root).node;
}
Object.keys(Traverse.prototype).forEach(function (key) {
Traverse[key] = function (obj) {
var args = [].slice.call(arguments, 1);
var t = Traverse(obj);
return t[key].apply(t, args);
};
});
function copy (src) {
if (typeof src === 'object' && src !== null) {
var dst;
if (Array.isArray(src)) {
dst = [];
}
else if (src instanceof Date) {
dst = new Date(src);
}
else if (src instanceof Boolean) {
dst = new Boolean(src);
}
else if (src instanceof Number) {
dst = new Number(src);
}
else if (src instanceof String) {
dst = new String(src);
}
else {
dst = Object.create(Object.getPrototypeOf(src));
}
Object.keys(src).forEach(function (key) {
dst[key] = src[key];
});
return dst;
}
else return src;
}

View File

@ -4,21 +4,30 @@ var recline = function() {
return (parseFloat(bytes)/1024/1024).toString().substr(0,4) + "MB"
}
function showDialog(template, data) {
if (!data) data = {};
util.show('dialog');
util.render(template, 'dialog-content', data);
util.hide('menu');
util.observeExit($('.dialog-content'), function() {
util.hide('dialog');
})
}
function handleMenuClick() {
$( '.menu li' ).click(function(e) {
if ($(e.target).hasClass('transform')) {
util.show('dialog');
util.render('bulkEdit', 'dialog-content', {name: app.currentColumn});
util.hide('menu');
var actions = {
bulkEdit: function() { showDialog('bulkEdit', {name: app.currentColumn}) },
csv: function() { window.location.href = app.csvUrl },
json: function() { window.location.href = "_rewrite/api/json" },
url: function() { showDialog('urlImport') },
paste: function() { showDialog('pasteImport') },
upload: function() { showDialog('uploadImport') }
}
if ($(e.target).hasClass('csv')) window.location.href = app.csvUrl;
if ($(e.target).hasClass('json')) window.location.href = "_rewrite/api/json";
actions[$(e.target).attr('data-action')]();
e.preventDefault();
util.hide('menu');
})
}
@ -119,7 +128,7 @@ var recline = function() {
function bootstrap() {
util.registerEmitter();
util.listenFor(["esc"]);
util.listenFor(['esc', 'return']);
couch.request({url: app.baseURL + "api"}).then(function( dbInfo ) {
@ -158,6 +167,7 @@ var recline = function() {
return {
formatDiskSize: formatDiskSize,
handleMenuClick: handleMenuClick,
showDialog: showDialog,
bootstrap: bootstrap,
fetchRows: fetchRows,
activateControls: activateControls,

View File

@ -70,8 +70,7 @@ app.after = {
controls: function() {
$('#logged-in-status').click(function(e) {
if ($(e.target).text() === "Sign in") {
util.show('dialog');
util.render('signIn', 'dialog-content');
recline.showDialog("signIn");
} else if ($(e.target).text() === "Sign out") {
util.notify("Signing you out...", {persist: true, loader: true});
couch.logout().then(function(response) {
@ -82,13 +81,10 @@ app.after = {
});
},
exportActions: recline.handleMenuClick,
importActions: recline.handleMenuClick,
columnActions: recline.handleMenuClick,
signIn: function() {
util.observeExit($('.dialog-content'), function() {
util.hide('dialog');
})
$('.dialog-content #username-input').focus();
$('.dialog-content').find('#sign-in-form').submit(function(e) {
@ -115,10 +111,6 @@ app.after = {
},
bulkEdit: function() {
util.observeExit($('.dialog-content'), function() {
util.hide('dialog');
})
$('.dialog-content .okButton').click(function(e) {
var funcText = $('.expression-preview-code').val();
util.hide('dialog');

View File

@ -36,9 +36,19 @@ var util = function() {
}
function listenFor(keys) {
_.each(keys, function(key) {
$(document).bind('keydown', key, function() { app.emitter.emit(key, key) });
})
var shortcuts = { // from jquery.hotkeys.js
8: "backspace", 9: "tab", 13: "return", 16: "shift", 17: "ctrl", 18: "alt", 19: "pause",
20: "capslock", 27: "esc", 32: "space", 33: "pageup", 34: "pagedown", 35: "end", 36: "home",
37: "left", 38: "up", 39: "right", 40: "down", 45: "insert", 46: "del",
96: "0", 97: "1", 98: "2", 99: "3", 100: "4", 101: "5", 102: "6", 103: "7",
104: "8", 105: "9", 106: "*", 107: "+", 109: "-", 110: ".", 111 : "/",
112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", 119: "f8",
120: "f9", 121: "f10", 122: "f11", 123: "f12", 144: "numlock", 145: "scroll", 191: "/", 224: "meta"
}
window.addEventListener("keyup", function(e) {
var pressed = shortcuts[e.keyCode];
if(_.include(keys, pressed)) app.emitter.emit("keyup", pressed);
}, false);
}
function observeExit(elem, callback) {

View File

@ -808,6 +808,15 @@ a.data-table-flag-off {
margin: 3px 0;
}
.data-table-cell-copypaste-editor {
overflow: hidden;
display: block;
width: 98%;
height: 10em;
font-family: monospace;
margin: 3px 0;
}
.data-table-cell-editor-action {
float: left;
vertical-align: bottom;