Skip to content

Commit

Permalink
* Implemented UI for manipulating cells.
Browse files Browse the repository at this point in the history
* Redesigned Rekall Runplugin UI.
* Added --open_browser flag to webconsole plugin.

BUG=
R=scudette@gmail.com

Review URL: https://codereview.appspot.com/97170043
  • Loading branch information
mbushkov committed May 8, 2014
1 parent d78a43e commit 2d9cf22
Show file tree
Hide file tree
Showing 15 changed files with 491 additions and 83 deletions.
24 changes: 22 additions & 2 deletions manuskript/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

from manuskript import plugins as manuskript_plugins

from werkzeug import serving


STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")

Expand All @@ -15,8 +17,21 @@
manuskript_plugins.PythonCall]


class WebconsoleWSGIServer(serving.BaseWSGIServer):
"""Custom WSGI server that supports post-activate hook."""

def __init__(self, host, port, app, post_activate_callback=None):
self.post_activate_callback = post_activate_callback
super(WebconsoleWSGIServer, self).__init__(host, port, app)

def server_activate(self):
super(WebconsoleWSGIServer, self).server_activate()
if self.post_activate_callback:
self.post_activate_callback()


def RunServer(host="localhost", port=5000, debug=False, plugins=None,
config=None):
config=None, post_activate_callback=None):
if not plugins:
plugins = DEFAULT_PLUGINS

Expand Down Expand Up @@ -54,4 +69,9 @@ def add_header(response): # pylint: disable=unused-variable
for k, v in config.items():
app.config[k] = v

app.run(host=host, port=port, debug=debug)
if debug:
app.run(host=host, port=port, debug=debug)
else:
WebconsoleWSGIServer(
host, port, app,
post_activate_callback=post_activate_callback).serve_forever()
206 changes: 182 additions & 24 deletions manuskript/static/app-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,59 +15,161 @@ var manuskriptPluginsList = manuskriptPluginsList || [];
'manuskript.configuration'].concat(manuskriptPluginsList));

module.controller("ManuskriptAppController", function(
$scope,
$scope, $modal, $timeout,
manuskriptCoreNodePluginRegistryService,
manuskriptConfiguration) {

$scope.pageTitle = manuskriptConfiguration.pageTitle || "Manuskript";

/**
* List of nodes shown to the user.
*/
$scope.nodes = manuskriptConfiguration.nodes || [];;

$scope.selection = {
node: null,
nodeIndex: -1
};

/**
* Returns CSS class of a cell corresponding to the given node.
* @param node - New node will be added before this node..
* @returns {string} CSS class name.
*/
$scope.cellClass = function(node) {
if ($scope.selection.node === node) {
return ["cell", "selected"];
} else {
return ["cell"];
}
}

/**
* When selected node or index of the selected node changes (index changes
* when node is moved), scroll the screen to show the node, unless it's
* already visible.
*/
var selectionChangeHandler = function(newValue, oldValue) {
if (newValue != null && newValue !== oldValue) {
var scrollIntoCell = function() {
var selectedCell = angular.element("#cells .cell.selected");
if (selectedCell.length > 0) {
var cellTop = selectedCell.offset().top;
var cellHeight = selectedCell.height();
var windowScrollTop = angular.element(window).scrollTop();
var windowHeight = window.innerHeight;

var elementVisible = (cellTop > windowScrollTop &&
cellTop < (windowScrollTop + windowHeight) ||
(cellTop + cellHeight) > windowScrollTop &&
(cellTop + cellHeight) < (windowScrollTop + windowHeight));

if (!elementVisible) {
angular.element(window).scrollTop(Math.max(0, cellTop - 40));
}
} else {
$timeout(scrollIntoCell);
}
}
$timeout(scrollIntoCell);
}
};
$scope.$watch('selection.node', selectionChangeHandler);
$scope.$watch('selection.nodeIndex', selectionChangeHandler);

/**
* If selection changes, forced saving of the previous node.
*/
$scope.$watch('selection.node', function(newValue, oldValue) {
$scope.saveNode(oldValue);
});

/**
* Currently edited node, or null if no node is being edited.
* Only one node can be edited at any given moment.
* When selected node changes, update selected node index.
*/
$scope.currentlyEditedNode = null;
$scope.$watchCollection('nodes', function() {
$scope.selection.nodeIndex = $scope.nodes.indexOf($scope.selection.node);
});

/**
* Adds new node to the list.
* @param {string} nodeType - Type of node to add.
* @param {int=} beforeNodeIndex - Optional, if defined, the node will be
* inserted into the list of nodes before
* the element with this index.
*/
$scope.addNode = function(nodeType, beforeNodeIndex) {
if (beforeNodeIndex === undefined) {
beforeNodeIndex = $scope.nodes.length;
}

var modalInstance = $modal.open({
templateUrl: 'static/components/core/addnode-dialog.html',
controller: 'AddNodeDialogController',
resolve: {
items: function() {
return $scope.listPlugins();
}
}
});

modalInstance.result.then(function(typeKey) {
var node = manuskriptCoreNodePluginRegistryService.createDefaultNodeForPlugin(
typeKey);
$scope.nodes.splice(beforeNodeIndex, 0, node);
$scope.editNode(node);
});
};

/**
* Add new node before given node.
* @param node - New node will be added before this node..
*/
$scope.addNodeBefore = function(node) {
$scope.addNode(node.type, $scope.nodes.indexOf(node));
};

/**
* Add new node after given node.
* @param node - New node will be added after this node..
*/
$scope.addNode = function(nodeType) {
var node = manuskriptCoreNodePluginRegistryService.createDefaultNodeForPlugin(
nodeType);
$scope.nodes.push(node);
$scope.editNode(node);
$scope.addNodeAfter = function(node) {
$scope.addNode(node.type, $scope.nodes.indexOf(node) + 1);
};

/**
* Duplicate given node in a list.
* @param node - Node to be duplicated. Duplicate will be inserted right
* after this node.
*/
$scope.duplicateNode = function(node) {
var newNode = angular.copy(node);
var nodeIndex = $scope.nodes.indexOf(node);
$scope.nodes.splice(nodeIndex + 1, 0, newNode);
};

/**
* Starts editing of a given node.
* @param node - Node to edit.
*/
$scope.editNode = function(node) {
if ($scope.currentlyEditedNode) {
$scope.saveNode($scope.currentlyEditedNode);
}
$scope.selection.node = node;
node.state = 'edit';
$scope.currentlyEditedNode = node;
};

/**
* Finishes editing of the node that is currently being edited.
* @throws {IllegalStateError} If there's no node that's being edited.
*/
$scope.saveNode = function() {
if (!$scope.currentlyEditedNode) {
throw {
name: 'IllegalStateError',
message: 'No node is currently being edited'
};
* @param node - Node to be saved. If not specified, currently selected
* node will be used.
*/
$scope.saveNode = function(node) {
if (node === undefined) {
node = $scope.selection.node;
}

$scope.renderNode($scope.currentlyEditedNode);
if (node != null && node.state == 'edit') {
$scope.renderNode(node);
}
};

/**
Expand All @@ -86,6 +188,62 @@ var manuskriptPluginsList = manuskriptPluginsList || [];
return node.state == 'edit';
};

/**
* Checks if given node can be moved up in the list.
* @returns {boolean} True if node can be moved.
*/
$scope.canMoveNodeUp = function(node) {
return $scope.nodes.indexOf(node) > 0;
}

/**
* Checks if given node can be moved down in the list.
* @returns {boolean} True if node can be moved.
*/
$scope.canMoveNodeDown = function(node) {
return $scope.nodes.indexOf(node) < $scope.nodes.length - 1;
}

/**
* Moves node up in the list.
*/
$scope.moveNodeUp = function(node) {
var nodeIndex = $scope.nodes.indexOf(node);
$scope.nodes.splice(nodeIndex, 1);
$scope.nodes.splice(nodeIndex - 1, 0, node);
}

/**
* Moves node down in the list.
*/
$scope.moveNodeDown = function(node) {
var nodeIndex = $scope.nodes.indexOf(node);
$scope.nodes.splice(nodeIndex, 1);
$scope.nodes.splice(nodeIndex + 1, 0, node);
}

/**
* Removes node from the list.
*/
$scope.removeNode = function(node) {
var nodeIndex = $scope.nodes.indexOf(node);
$scope.nodes.splice(nodeIndex, 1);

if ($scope.selection.node === node) {
$scope.selection.node = null;
}
}

/**
* Clears the list of nodes.
*/
$scope.removeAllNodes = function() {
// It's better to modify the nodes array than to assign new array to the
// scope variable. Some details:
// https://github.com/angular/angular.js/wiki/Understanding-Scopes#ngRepeat:
$scope.nodes.splice(0, $scope.nodes.length);
}

/**
* @returns {string[]} Array of names of available Manuskript plugins.
*/
Expand All @@ -112,7 +270,7 @@ var manuskriptPluginsList = manuskriptPluginsList || [];
/**
* Sets state of all the nodes to 'render'.
*/
$scope.renderAll = function() {
$scope.renderAllNodes = function() {
for (var i = 0; i < $scope.nodes.length; ++i) {
var node = $scope.nodes[i];
node.state = "render";
Expand Down
14 changes: 14 additions & 0 deletions manuskript/static/components/core/addnode-dialog-controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(function() {
var module = angular.module('manuskript.core.addNodeDialog.controller', ['ui.bootstrap']);

module.controller("AddNodeDialogController", function($scope, $modalInstance, items) {

$scope.items = items;

$scope.ok = function(selectedItem) {
$modalInstance.close(selectedItem);
};

});

})();
9 changes: 9 additions & 0 deletions manuskript/static/components/core/addnode-dialog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!-- "Add Cell" dialog -->
<div class="modal-header">
<h4 class="modal-title">Select New Cell Type</h4>
</div>
<div class="modal-body">
<div class="list-group">
<a ng-repeat="(typeKey, type) in items" href="#" data-dismiss="modal" class="list-group-item" ng-click="ok(typeKey)">{{type.description}}</a>
</div>
</div>
7 changes: 5 additions & 2 deletions manuskript/static/components/core/core.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
(function() {
angular.module('manuskript.core', [
'manuskript.core.nodePluginRegistry.service',
'manuskript.core.addNodeDialog.controller',
'manuskript.core.codeEditor.directive',
'manuskript.core.fileInput.directive',
'manuskript.core.onAltEnter.directive',
'manuskript.core.scrollTo.directive',
'manuskript.core.splitList.directive',
'manuskript.core.codeEditor.directive',
'manuskript.core.fileInput.directive']);
]);
})();
23 changes: 23 additions & 0 deletions manuskript/static/components/core/scrollto-directive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
(function() {

var module = angular.module('manuskript.core.scrollTo.directive', []);

/**
* 'scrollTo' directive scrolls container to a child with a corresponding
* selector.
*/
module.directive('scrollTo', function($timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$timeout(function() {
var scrollAnchors = element.find(attrs["scrollTo"]);
if (scrollAnchors.length > 0) {
element.scrollTop(Math.max(0, scrollAnchors.position().top - 20));
}
});
}
};
});

})();
Loading

0 comments on commit 2d9cf22

Please sign in to comment.