regexper-static/src/js/parser/javascript/node.js

183 lines
5.3 KiB
JavaScript
Raw Normal View History

// Base class for all nodes in the parse tree. An instance of this class is
// created for each parsed node, and then extended with one of the node-type
// modules.
import util from '../../util.js';
2014-12-10 00:02:31 +00:00
import _ from 'lodash';
export default class Node {
// Arguments passed in are defined by the canopy tool.
constructor(textValue, offset, elements, properties) {
2014-12-17 17:07:25 +00:00
this.textValue = textValue;
this.offset = offset;
this.elements = elements || [];
this.properties = properties;
// This is the current parser state (an instance
// [ParserState](./parser_state.html).)
this.state = Node.state;
}
// Node-type module to extend the Node instance with. Setting of this is
// done by canopy during parsing and is setup in [parser.js](./parser.html).
set module(mod) {
_.extend(this, mod);
2014-12-17 21:04:55 +00:00
if (this.setup) {
this.setup();
}
2014-12-17 21:04:55 +00:00
_.forOwn(this.definedProperties || {}, (methods, name) => {
Object.defineProperty(this, name, methods);
});
}
// The SVG element to render this node into. A node-type class is
// automatically added to the container. The class to set is defined on the
// module set during parsing.
2014-12-17 20:14:33 +00:00
set container(container) {
this._container = container;
this._container.addClass(this.type);
}
get container() {
return this._container;
}
// The anchor defined the points on the left and right of the rendered node
// that the centerline of the rendered expression connects to. For most
// nodes, this element will be defined by the normalizeBBox method in
// [Util](../../util.html).
2014-12-17 20:24:27 +00:00
get anchor() {
2014-12-17 20:12:04 +00:00
if (this.proxy) {
2014-12-17 20:24:27 +00:00
return this.proxy.anchor;
2014-12-13 16:35:01 +00:00
} else {
return this._anchor || {};
2014-12-13 16:35:01 +00:00
}
}
2014-12-13 16:35:01 +00:00
// Returns the bounding box of the container with the anchor included.
2014-12-10 00:02:31 +00:00
getBBox() {
return _.extend(util.normalizeBBox(this.container.getBBox()), this.anchor);
}
2014-12-10 00:02:31 +00:00
// Transforms the container.
//
// - __matrix__ - A matrix transform to be applied. Created using Snap.svg.
2014-12-11 01:28:02 +00:00
transform(matrix) {
return this.container.transform(matrix);
}
2014-12-11 01:28:02 +00:00
// Returns a Promise that will be resolved with the provided value. If the
// render is cancelled before the Promise is resolved, then an exception will
// be thrown to halt any rendering.
//
// - __value__ - Value to resolve the returned promise with.
deferredStep(value) {
return util.tick().then(() => {
if (this.state.cancelRender) {
throw 'Render cancelled';
}
return value;
2015-03-14 21:11:14 +00:00
});
}
// Render this node.
//
// - __container__ - Optional element to render this node into. A container
// must be specified, but if it has already been set, then it does not
// need to be provided to render.
render(container) {
if (container) {
this.container = container;
}
if (this.proxy) {
// For nodes that proxy to a child node, just render the child.
return this.proxy.render(this.container);
} else {
// Non-proxied nodes call their _render method (defined by the node-type
// module).
this.state.renderCounter++;
return this._render()
.then(() => {
this.state.renderCounter--;
return this;
});
}
}
// Renders a label centered within a rectangle which can be styled. Returns
// a Promise which will be resolved with the SVG group the rect and text are
// rendered in.
//
// - __text__ - String or array of strings to render as a label.
renderLabel(text) {
var group = this.container.group()
.addClass('label'),
rect = group.rect(),
text = group.text(0, 0, _.flatten([text]));
return this.deferredStep()
.then(() => {
var box = text.getBBox(),
margin = 5;
text.transform(Snap.matrix()
.translate(margin, box.height / 2 + 2 * margin));
rect.attr({
width: box.width + 2 * margin,
height: box.height + 2 * margin
});
return group;
});
}
// Renders a labeled box around another SVG element. Returns a Promise.
//
// - __label__ - String or array of strings to label the box with.
// - __content__ - SVG element to wrap in the box.
// - __options.padding__ - Pixels of padding to place between the content and
// the box.
renderLabeledBox(label, content, options) {
var label = this.container.text(0, 0, _.flatten([label]))
.addClass(`${this.type}-label`),
box = this.container.rect()
.addClass(`${this.type}-box`)
.attr({
rx: 3,
ry: 3
});
2014-12-19 16:48:09 +00:00
options = _.defaults(options || {}, {
padding: 0
});
this.container.prepend(label);
this.container.prepend(box);
return this.deferredStep()
.then(() => {
var labelBox = label.getBBox(),
contentBox = content.getBBox();
label.transform(Snap.matrix()
.translate(0, labelBox.height));
box
.transform(Snap.matrix()
.translate(0, labelBox.height))
.attr({
width: Math.max(contentBox.width + options.padding * 2, labelBox.width),
height: contentBox.height + options.padding * 2
});
content.transform(Snap.matrix()
.translate(box.getBBox().cx - contentBox.cx, labelBox.height + options.padding));
});
}
};