stub out import UI, add better keyboard shortcut handler
This commit is contained in:
parent
e2fdf8e953
commit
988c8de288
@ -22,7 +22,6 @@
|
||||
<div class="container">
|
||||
<div class="menu-overlay" style="display: none; z-index: 101; "> </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"> Import </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"> Import </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"> Import </button>
|
||||
<button class="cancelButton button">Cancel</button>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script type='text/mustache' class="bulkEditTemplate">
|
||||
<div class="dialog-header">
|
||||
Functional transform on column {{name}}
|
||||
|
||||
332
attachments/script/lib/traverse.js
Executable file
332
attachments/script/lib/traverse.js
Executable 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;
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user