separation of rows and cli, more refactoring for new world, copying images from skywriter

This commit is contained in:
Joe Walker 2010-12-12 17:01:45 +00:00
commit 15fddc071c
19 changed files with 334 additions and 183 deletions

View file

@ -446,7 +446,8 @@ Requisition.prototype = {
* Helper to call canon.exec
*/
exec: function() {
canon.exec(this.command, this.getArgs());
// TODO: have a toString rather than hack this.input
canon.exec(this.command, this.getArgs(), this.input.typed);
}
};
exports.Requisition = Requisition;
@ -492,8 +493,8 @@ oop.inherits(CliRequisition, Requisition);
*
*/
CliRequisition.prototype.update = function(input) {
// TODO: We only store this so getHints can work. Find a better way.
this.input = input;
// TODO: We only store this so getHints can work. Find a better way.
this.localHints = [];
if (util.none(input.typed)) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 604 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -38,6 +38,29 @@
border: none; outline: none;
}
.cptOutput {
border: 1px solid #AAA;
-moz-border-radius-topleft: 10px;
-moz-border-radius-topright: 10px;
border-top-left-radius: 4px; border-top-right-radius: 4px;
padding: 10px;
margin: 0 15px;
background: #DDD; color: #000;
font-family: consolas, courier, monospace;
font-size: 90%;
overflow: hidden;
position: absolute;
z-index: 999;
}
.cptHints article {
margin-left: 25px;
@ -96,14 +119,6 @@
/* Layout */
.cptOutput {
background: @bg; color: @text;
font-family: @fonts; font-size: 90%;
overflow: hidden;
position: absolute;
}
/* The input area */
.cptInput {
border-top: 1px solid @border_bg;
@ -140,42 +155,3 @@
}
.cptRowIn {
display: box; display: -moz-box; display: -webkit-box;
box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal;
box-align: center; -moz-box-align: center; -webkit-box-align: center;
color: @text;
background-color: @input_bg;
width: 100%;
}
.cptRowIn > * { padding-left: 2px; padding-right: 2px; }
.cptRowIn > img { cursor: pointer; }
.cptHover { display: none; }
.cptRowIn:hover > .cptHover { display: block; }
.cptRowIn:hover > .cptHover.cptHidden { display: none; }
.cptOutTyped {
box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1;
font-weight: bold; color: @hi_text; font-size: 120%;
}
.cptRowOutput { padding-left: 10px; line-height: 1.2em; }
.cptRowOutput strong,
.cptRowOutput b,
.cptRowOutput th,
.cptRowOutput h1,
.cptRowOutput h2,
.cptRowOutput h3 { color: @hi_text; }
.cptRowOutput a { font-weight: bold; color: @link_text; text-decoration: none; }
.cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }
.cptRowOutput input[type=password],
.cptRowOutput input[type=text],
.cptRowOutput textarea {
color: @hi_text; font-size: 120%;
background: transparent; padding: 3px;
border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;
}
.cptRowOutput table,
.cptRowOutput td,
.cptRowOutput th { border: 0; padding: 0 2px; }
.cptRowOutput .right { text-align: right; }

View file

@ -37,18 +37,18 @@
define(function(require, exports, module) {
var editorCss = require("text!cockpit/ui/plain.css");
var dom = require("pilot/dom").dom;
dom.importCssString(editorCss);
var CliRequisition = require('cockpit/cli').CliRequisition;
var Hint = require('cockpit/cli').Hint;
var keyutil = require('pilot/keyboard/keyutil');
var plainRow = require("text!cockpit/ui/plainRow.html");
var Templater = require("pilot/domtemplate").Templater;
var canon = require("pilot/canon");
var Status = require('pilot/types').Status;
var keyutil = require('pilot/keyboard/keyutil');
var CliRequisition = require('cockpit/cli').CliRequisition;
var Hint = require('cockpit/cli').Hint;
var RequestView = require('cockpit/ui/requestView').RequestView;
var NO_HINT = new Hint(Status.VALID, '', 0, 0);
@ -61,19 +61,19 @@ var NO_HINT = new Hint(Status.VALID, '', 0, 0);
* 2. Attach a set of events so the command line works
*/
exports.startup = function(data, reason) {
var plainUi = new PlainUi(data);
var plainUi = new CliView(data);
};
/**
* A class to handle the simplest UI implementation
*/
function PlainUi(data) {
function CliView(data) {
this.doc = document;
this.win = this.doc.defaultView;
// TODO: we should have a better way to specify command lines???
this.input = this.doc.getElementById('cockpit');
if (!this.input) {
this.element = this.doc.getElementById('cockpit');
if (!this.element) {
console.log('No element with an id of cockpit. Bailing on plain cli');
return;
}
@ -90,26 +90,22 @@ function PlainUi(data) {
this.createElements();
}
PlainUi.prototype = {
CliView.prototype = {
/**
* Create divs for templates, completion, hints and output
* Create divs for completion, hints and output
*/
createElements: function() {
this.templates = this.doc.createElement('div');
this.templates.innerHTML = plainRow;
this.row = this.templates.firstChild;
this.completer = this.doc.createElement('div');
this.completer.className = 'cptCompletion VALID';
this.input.parentNode.insertBefore(this.completer, this.input);
this.element.parentNode.insertBefore(this.completer, this.element);
this.hinter = this.doc.createElement('div');
this.hinter.className = 'cptHints';
this.input.parentNode.insertBefore(this.hinter, this.input);
this.element.parentNode.insertBefore(this.hinter, this.element);
this.output = this.doc.createElement('div');
this.output.className = 'cptOutput';
this.input.parentNode.insertBefore(this.output, this.input);
this.element.parentNode.insertBefore(this.output, this.element);
this.win.addEventListener('resize', this.resizer.bind(this), false);
this.resizer();
@ -119,9 +115,9 @@ PlainUi.prototype = {
this.showHint.addEventListener('change', this.hintShower.bind(this));
this.hintShower();
keyutil.addKeyDownListener(this.input, this.onKeyDown.bind(this));
this.input.addEventListener('mouseup', this.onMouseUp.bind(this), false);
this.input.addEventListener('keyup', this.onKeyUp.bind(this), true);
keyutil.addKeyDownListener(this.element, this.onKeyDown.bind(this));
this.element.addEventListener('mouseup', this.onMouseUp.bind(this), false);
this.element.addEventListener('keyup', this.onKeyUp.bind(this), true);
},
/**
@ -131,15 +127,15 @@ PlainUi.prototype = {
resizer: function() {
var top, height, left, width;
if (this.input.getClientRects) {
var rect = this.input.getClientRects()[0];
if (this.element.getClientRects) {
var rect = this.element.getClientRects()[0];
top = rect.top;
height = rect.height;
left = rect.left;
width = rect.width;
}
else {
var style = this.win.getComputedStyle(this.input, null);
var style = this.win.getComputedStyle(this.element, null);
top = parseInt(style.getPropertyValue('top'), 10);
height = parseInt(style.getPropertyValue('height'), 10);
left = parseInt(style.getPropertyValue('left'), 10);
@ -156,25 +152,42 @@ PlainUi.prototype = {
this.output.style.bottom = (this.win.innerHeight - top) + 'px';
this.output.style.left = left + 'px';
this.output.style.width = width + 'px';
this.output.style.width = (width - 60) + 'px';
},
/**
* Update the display of executed commands
*/
showOutput: function(ev) {
var requestOutput = new RequestView(ev.request, this);
/*
// TODO: be less brutal in how we update this
this.output.innerHTML = '';
new RequestView(ev.request, this);
ev.requests.forEach(function(request) {
request.outputs.forEach(function(out) {
if (typeof out === 'string') {
this.output.appendChild(this.doc.createTextNode(out));
// this.duration.innerHTML = this.request.duration ?
// 'completed in ' + (this.request.duration / 1000) + ' sec ' :
// '';
// this.typed.innerHTML = this.request.typed;
request.outputs.forEach(function(output) {
var node;
if (typeof output == 'string') {
node = document.createElement('p');
node.innerHTML = output;
} else {
this.output.appendChild(out);
node = output;
}
this.output.appendChild(node);
}, this);
// this.cliView.scrollToBottom();
// util.setClass(this.output, 'cmd_error', this.request.error);
// this.throb.style.display = this.request.completed ? 'none' : 'block';
}, this);
*/
},
/**
@ -219,27 +232,26 @@ PlainUi.prototype = {
if (ev.keyCode === keyutil.KeyHelper.KEY.RETURN) {
if (this.worstHint && this.worstHint.status !== Status.VALID) {
this.input.selectionStart = this.worstHint.start;
this.input.selectionEnd = this.worstHint.end;
this.element.selectionStart = this.worstHint.start;
this.element.selectionEnd = this.worstHint.end;
}
else {
this.cli.exec();
this.input.value = '';
this.element.value = '';
}
}
if (ev.keyCode === keyutil.KeyHelper.KEY.TAB) {
if (this.shownHint && this.shownHint.predictions && this.shownHint.predictions.length > 0) {
var prefix = this.input.value.substring(0, this.shownHint.start);
var suffix = this.input.value.substring(this.shownHint.end);
var insert = this.shownHint.predictions[0];
insert = typeof insert === 'string' ? insert : insert.name;
this.input.value = prefix + insert + suffix;
// Fix the cursor.
var insertEnd = (prefix + insert).length;
this.input.selectionStart = insertEnd;
this.input.selectionEnd = insertEnd;
}
if (ev.keyCode === keyutil.KeyHelper.KEY.TAB && this.shownHint &&
this.shownHint.predictions && this.shownHint.predictions.length > 0) {
var prefix = this.element.value.substring(0, this.shownHint.start);
var suffix = this.element.value.substring(this.shownHint.end);
var insert = this.shownHint.predictions[0];
insert = typeof insert === 'string' ? insert : insert.name;
this.element.value = prefix + insert + suffix;
// Fix the cursor.
var insertEnd = (prefix + insert).length;
this.element.selectionStart = insertEnd;
this.element.selectionEnd = insertEnd;
}
this.update();
@ -260,10 +272,10 @@ PlainUi.prototype = {
*/
update: function() {
this.cli.update({
typed: this.input.value,
typed: this.element.value,
cursor: {
start: this.input.selectionStart,
end: this.input.selectionEnd
start: this.element.selectionStart,
end: this.element.selectionEnd
}
});
@ -288,10 +300,10 @@ PlainUi.prototype = {
// Create a marked up version of the input
var highlightedInput = '';
if (this.input.value.length > 0) {
if (this.element.value.length > 0) {
// 'scores' is an array which tells us what chars are errors
// Initialize with everything VALID
var scores = this.input.value.split('').map(function(char) {
var scores = this.element.value.split('').map(function(char) {
return Status.VALID;
});
// For all chars in all hints, check and upgrade the score
@ -310,9 +322,9 @@ PlainUi.prototype = {
highlightedInput += '<span class=' + scores[i].toString() + '>';
lastStatus = scores[i];
}
highlightedInput += this.input.value[i];
highlightedInput += this.element.value[i];
i++;
if (i === this.input.value.length) {
if (i === this.element.value.length) {
highlightedInput += '</span>';
break;
}
@ -351,7 +363,7 @@ PlainUi.prototype = {
// dom.addCssClass(input, status.toString());
}
};
exports.PlainUi = PlainUi;
exports.CliView = CliView;
});

View file

@ -1,47 +0,0 @@
<!-- The input area, with fixed height -->
<div id=cptInput class=cptInput>
<!-- The prompt -->
<div class="cptPrompt cptGt"><span class="cptBrackets">{ }</span> &gt;</div>
<!-- Where you type commands -->
<div class="cptKbd cptStack">
<div class=cptCompletion save="${cliInputView._completer}"></div>
<input class=cptCliInput save="${cliInputView._inputer}"/>
</div>
<!-- Help as you type -->
<div id=cptHints class=cptHints>
</div>
</div>
<div class=cptRow>
<!-- The div for the input (i.e. what was typed) -->
<div class="cptRowIn" save="${actions.rowin}"
onclick="${actions.copyToInput}"
ondblclick="${actions.executeRequest}">
<!-- What the user actually typed -->
<div class="cptGt">&gt; </div>
<div class="cptOutTyped" save="${actions.typed}"></div>
<!-- The extra details that appear on hover -->
<div class=cptHover save="${actions.duration}"></div>
<img class=cptHover onclick="${actions.hideOutput}" save="${actions.hide}"
alt="Hide command output" _src="${imagePath}/minus.png"/>
<img class="cptHover cptHidden" onclick="${actions.showOutput}" save="${actions.show}"
alt="Show command output" _src="${imagePath}/plus.png"/>
<img class=cptHover onclick="${actions.remove}"
alt="Remove this command from the history" _src="${imagePath}/closer.png"/>
</div>
<!-- The div for the command output -->
<div class="cptRowOut" save="${actions.rowout}">
<div class="cptRowOutput" save="${actions.output}"></div>
<img src="${imagePath}/throbber.gif" save="${actions.throb}"/>
</div>
</div>

View file

@ -0,0 +1,38 @@
.cptRowIn {
display: box; display: -moz-box; display: -webkit-box;
box-orient: horizontal; -moz-box-orient: horizontal; -webkit-box-orient: horizontal;
box-align: center; -moz-box-align: center; -webkit-box-align: center;
color: #333;
background-color: @input_bg;
width: 100%;
}
.cptRowIn > * { padding-left: 2px; padding-right: 2px; }
.cptRowIn > img { cursor: pointer; }
.cptHover { display: none; }
.cptRowIn:hover > .cptHover { display: block; }
.cptRowIn:hover > .cptHover.cptHidden { display: none; }
.cptOutTyped {
box-flex: 1; -moz-box-flex: 1; -webkit-box-flex: 1;
font-weight: bold; color: #000; font-size: 120%;
}
.cptRowOutput { padding-left: 10px; line-height: 1.2em; }
.cptRowOutput strong,
.cptRowOutput b,
.cptRowOutput th,
.cptRowOutput h1,
.cptRowOutput h2,
.cptRowOutput h3 { color: #000; }
.cptRowOutput a { font-weight: bold; color: #666; text-decoration: none; }
.cptRowOutput a: hover { text-decoration: underline; cursor: pointer; }
.cptRowOutput input[type=password],
.cptRowOutput input[type=text],
.cptRowOutput textarea {
color: #000; font-size: 120%;
background: transparent; padding: 3px;
border-radius: 5px; -moz-border-radius: 5px; -webkit-border-radius: 5px;
}
.cptRowOutput table,
.cptRowOutput td,
.cptRowOutput th { border: 0; padding: 0 2px; }
.cptRowOutput .right { text-align: right; }

View file

@ -0,0 +1,28 @@
<div class=cptRow>
<!-- The div for the input (i.e. what was typed) -->
<div class="cptRowIn" save="${rowin}"
onclick="${copyToInput}"
ondblclick="${executeRequest}">
<!-- What the user actually typed -->
<div class="cptGt">&gt; </div>
<div class="cptOutTyped">${request.typed}</div>
<!-- The extra details that appear on hover -->
<div class=cptHover save="${duration}"></div>
<img class=cptHover onclick="${hideOutput}" save="${hide}"
alt="Hide command output" _src="${imagePath}/minus.png"/>
<img class="cptHover cptHidden" onclick="${showOutput}" save="${show}"
alt="Show command output" _src="${imagePath}/plus.png"/>
<img class=cptHover onclick="${remove}"
alt="Remove this command from the history" _src="${imagePath}/closer.png"/>
</div>
<!-- The div for the command output -->
<div class="cptRowOut" save="${rowout}">
<div class="cptRowOutput" save="${output}"></div>
<img _src="${imagePath}/throbber.gif" save="${throb}"/>
</div>
</div>

View file

@ -0,0 +1,152 @@
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Skywriter.
*
* The Initial Developer of the Original Code is
* Mozilla.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Joe Walker (jwalker@mozilla.com)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
define(function(require, exports, module) {
var util = require('pilot/util');
var editorCss = require("text!cockpit/ui/requestView.css");
var dom = require("pilot/dom").dom;
dom.importCssString(editorCss);
var plainRow = require("text!cockpit/ui/requestView.html");
var Templater = require("pilot/domtemplate").Templater;
/**
* Pull the HTML into the DOM, but don't add it to the document
*/
var templates = document.createElement('div');
templates.innerHTML = plainRow;
var row = templates.querySelector('.cptRow');
/**
* Adds a row to the CLI output display
*/
function RequestView(request, cliView) {
this.request = request;
this.cliView = cliView;
this.imagePath = '/plugins/ace/pluging/cockpit/ui';
// Elements attached to this by the templater. For info only
this.rowin = null;
this.rowout = null;
this.output = null;
this.hide = null;
this.show = null;
this.duration = null;
this.throb = null;
new Templater().processNode(row.cloneNode(true), this);
this.cliView.output.appendChild(this.rowin);
this.cliView.output.appendChild(this.rowout);
this.request.addEventListener('output', this.onRequestChange.bind(this));
};
RequestView.prototype = {
/**
* A single click on an invocation line in the console copies the command to
* the command line
*/
copyToInput: function() {
this.cliView.input.value = this.request.typed;
},
/**
* A double click on an invocation line in the console executes the command
*/
executeRequest: function(ev) {
this.cliView.cli.update({
typed: this.request.typed,
cursor: { start:0, end:0 }
});
this.cliView.cli.exec();
},
hideOutput: function(ev) {
this.output.style.display = 'none';
util.addClass(this.hide, 'cmd_hidden');
util.removeClass(this.show, 'cmd_hidden');
ev.stopPropagation();
},
showOutput: function(ev) {
this.output.style.display = 'block';
util.removeClass(this.hide, 'cmd_hidden');
util.addClass(this.show, 'cmd_hidden');
ev.stopPropagation();
},
remove: function(ev) {
this.cliView.output.removeChild(this.rowin);
this.cliView.output.removeChild(this.rowout);
ev.stopPropagation();
},
onRequestChange: function(ev) {
this.duration.innerHTML = this.request.duration ?
'completed in ' + (this.request.duration / 1000) + ' sec ' :
'';
this.output.innerHTML = '';
this.request.outputs.forEach(function(output) {
var node;
if (typeof output == 'string') {
node = document.createElement('p');
node.innerHTML = output;
} else {
node = output;
}
this.output.appendChild(node);
}, this);
// TODO: Fix this
// this.cliView.scrollToBottom();
util.setClass(this.output, 'cmd_error', this.request.error);
this.throb.style.display = this.request.completed ? 'none' : 'block';
}
};
exports.RequestView = RequestView;
});

View file

@ -160,7 +160,8 @@ function getCommandNames() {
* everything it needs to about the command params
* @param command Either a command, or the name of one
*/
function exec(command, args) {
function exec(command, args, typed) {
// TODO: Use requisition.toString rather than 'typed'
if (typeof command === 'string') {
command = commands[command];
}
@ -171,7 +172,12 @@ function exec(command, args) {
// TODO: Ugg. really?
env.selection = env.editor.getSelection();
var request = new Request();
var request = new Request({
command: command,
args: args,
typed: typed
});
command.exec(env, args || {}, request);
return true;
};
@ -216,21 +222,6 @@ var requests = [];
*/
var maxRequestLength = 100;
/**
* Called by Request instances when some output (or a cell to async() happens)
*/
function addRequestOutput(request) {
requests.push(request);
// This could probably be optimized with some maths, but 99.99% of the
// time we will only be off by one, and I'm feeling lazy.
while (requests.length > maxRequestLength) {
requests.shiftObject();
}
exports._dispatchEvent('output', { requests: requests });
};
exports.addRequestOutput = addRequestOutput;
/**
* To create an invocation, you need to do something like this (all the ctor
* args are optional):
@ -249,7 +240,6 @@ function Request(options) {
// Will be used in the keyboard case and the cli case
this.command = options.command;
this.commandExt = options.commandExt;
// Will be used only in the cli case
this.args = options.args;
@ -274,7 +264,14 @@ Request.prototype._beginOutput = function() {
this._begunOutput = true;
this.outputs = [];
exports.addRequestOutput(this);
requests.push(this);
// This could probably be optimized with some maths, but 99.99% of the
// time we will only be off by one, and I'm feeling lazy.
while (requests.length > maxRequestLength) {
requests.shiftObject();
}
exports._dispatchEvent('output', { requests: requests, request: this });
};
/**
@ -311,7 +308,7 @@ Request.prototype.output = function(content) {
}
this.outputs.push(content);
this._dispatchEvent('changed', {});
this._dispatchEvent('output', {});
return this;
};
@ -329,7 +326,7 @@ Request.prototype.done = function(content) {
this.output(content);
}
this._dispatchEvent('changed', {});
this._dispatchEvent('output', {});
};
exports.Request = Request;

View file

@ -59,19 +59,15 @@ var setCommandSpec = {
var html;
if (!args.setting) {
// 'set' by itself lists all the settings
var settingsList = env.settings._list();
var names = env.settings.getSettingNames();
html = '';
// first sort the settingsList based on the name
settingsList.sort(function(a, b) {
if (a.name < b.name) {
return -1;
} else if (a.name == b.name) {
return 0;
} else {
return 1;
}
names.sort(function(name1, name2) {
return name1.localeCompare(name2);
});
settingsList.forEach(function(setting) {
names.forEach(function(name) {
var setting = env.settings.getSetting(name);
var url = 'https://wiki.mozilla.org/Labs/Skywriter/Settings#' +
setting.name;
html += '<a class="setting" href="' + url +

View file

@ -398,9 +398,7 @@ Templater.prototype.logError = function(message) {
console.log(message);
};
if (this.exports) {
exports.Templater = Templater;
}
exports.Templater = Templater;
});