Refactoring

This commit is contained in:
barsdeveloper
2022-01-05 21:48:45 +01:00
parent 303cc5b71e
commit 7704850cf6
49 changed files with 1481 additions and 1469 deletions

0
dist/css/ueblueprint-node-value-type-color.css vendored Normal file → Executable file
View File

0
dist/css/ueblueprint-node-value-type-color.css.map vendored Normal file → Executable file
View File

0
dist/css/ueblueprint-style.css vendored Normal file → Executable file
View File

0
dist/css/ueblueprint-style.css.map vendored Normal file → Executable file
View File

0
dist/font/roboto-bold.woff vendored Normal file → Executable file
View File

0
dist/font/roboto-bold.woff2 vendored Normal file → Executable file
View File

0
dist/font/roboto-light.woff vendored Normal file → Executable file
View File

0
dist/font/roboto-light.woff2 vendored Normal file → Executable file
View File

0
dist/font/roboto-regular.woff vendored Normal file → Executable file
View File

0
dist/font/roboto-regular.woff2 vendored Normal file → Executable file
View File

534
dist/ueblueprint.js vendored Normal file → Executable file
View File

@@ -302,20 +302,30 @@ class FastSelectionModel {
}
/**
* @typedef {import("../Blueprint").default} Blueprint
* @typedef {import("../entity/Entity").default} Entity
* @typedef {import("../input/Context").default} Context
* @typedef {import("../template/Template").default} Template
*/
class GraphElement extends HTMLElement {
/**
*
* @param {import("../template/Template").default} template The template to render this node
* @param {Entity} entity The entity containing blueprint related data for this graph element
* @param {Template} template The template to render this node
*/
constructor(entity, template) {
super();
/** @type {import("../Blueprint").default}" */
/** @type {Blueprint}" */
this.blueprint = null;
/** @type {import("../entity/Entity").default}" */
/** @type {Entity}" */
this.entity = entity;
/** @type {import("../template/Template").default}" */
/** @type {Template}" */
this.template = template;
/** @type {Context[]} */
this.inputObjects = [];
}
getTemplate() {
@@ -325,9 +335,15 @@ class GraphElement extends HTMLElement {
connectedCallback() {
this.blueprint = this.closest("ueb-blueprint");
this.template.apply(this);
this.inputObjects = this.createInputObjects();
}
disconnectedCallback() {
this.inputObjects.forEach(v => v.unlistenDOMElement());
}
createInputObjects() {
return []
}
}
@@ -569,6 +585,14 @@ class BlueprintTemplate extends Template {
}
}
class Configuration {
static deleteNodesKeyboardKey = "Delete"
static expandGridSize = 400
static gridSize = 16
static gridSnap = 16
}
class Context {
constructor(target, blueprint, options) {
@@ -577,19 +601,19 @@ class Context {
/** @type {import("../Blueprint").default}" */
this.blueprint = blueprint;
this.options = options;
let self = this;
this.blueprintFocusHandler = _ => self.blueprintFocused();
this.blueprintUnfocusHandler = _ => self.blueprintUnfocused();
if (options?.wantsFocusCallback ?? false) {
let self = this;
this.blueprintfocusHandler = _ => self.blueprintFocused();
this.blueprintunfocusHandler = _ => self.blueprintUnfocused();
this.blueprint.addEventListener("blueprintfocus", this.blueprintfocusHandler);
this.blueprint.addEventListener("blueprintunfocus", this.blueprintunfocusHandler);
this.blueprint.addEventListener("blueprint-focus", this.blueprintFocusHandler);
this.blueprint.addEventListener("blueprint-unfocus", this.blueprintUnfocusHandler);
}
}
unlistenDOMElement() {
this.blueprintUnfocused();
this.blueprint.removeEventListener("blueprintfocus", this.blueprintfocusHandler);
this.blueprint.removeEventListener("blueprintunfocus", this.blueprintunfocusHandler);
this.blueprint.removeEventListener("blueprint-focus", this.blueprintFocusHandler);
this.blueprint.removeEventListener("blueprint-unfocus", this.blueprintUnfocusHandler);
}
@@ -883,6 +907,7 @@ class PinReferenceEntity extends Entity {
class PinEntity$1 extends Entity {
static lookbehind = "Pin"
static attributes = {
PinId: GuidEntity,
PinName: "",
@@ -1099,25 +1124,28 @@ class Grammar {
}
}
// Meta grammar
static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=").trim(P.optWhitespace)) =>
attributeGrammar.skip(valueSeparator)
static CreateAttributeGrammar = (r, entityType, valueSeparator = P.string("=").trim(P.optWhitespace)) =>
r.AttributeName.skip(valueSeparator)
.chain(attributeName => {
const attributeKey = attributeName.split(".");
const attribute = attributeSupplier(attributeKey);
const attribute = Utility.objectGet(entityType.attributes, attributeKey);
let attributeValueGrammar = Grammar.getGrammarForType(r, attribute, r.AttributeAnyValue);
// Returns attributeSetter: a function called with an object as argument that will set the correct attribute value
return attributeValueGrammar.map(attributeValue =>
entity => Utility.objectSet(entity, attributeKey, attributeValue, true)
) // returns attributeSetter: a function called with an object as argument that will set the correct attribute value
)
})
// Meta grammar
static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) =>
static CreateMultiAttributeGrammar = (r, entityType) =>
/**
* Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)'
* Then it populates an object of type EntityType with the attribute values found inside the parentheses.
*/
P.seqMap(
P.seq(keyGrammar, P.optWhitespace, P.string("(")),
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier)
entityType.lookbehind
? P.seq(P.string(entityType.lookbehind), P.optWhitespace, P.string("("))
: P.string("("),
Grammar.CreateAttributeGrammar(r, entityType)
.trim(P.optWhitespace)
.sepBy(P.string(","))
.skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma
@@ -1127,18 +1155,8 @@ class Grammar {
attributes.forEach(attributeSetter => attributeSetter(result));
return result
})
FunctionReference = r => Grammar.CreateMultiAttributeGrammar(
r,
P.succeed(),
FunctionReferenceEntity,
attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey)
)
Pin = r => Grammar.CreateMultiAttributeGrammar(
r,
P.string("Pin"),
PinEntity$1,
attributeKey => Utility.objectGet(PinEntity$1.attributes, attributeKey)
)
FunctionReference = r => Grammar.CreateMultiAttributeGrammar(r, FunctionReferenceEntity)
Pin = r => Grammar.CreateMultiAttributeGrammar(r, PinEntity$1)
CustomProperties = r =>
P.string("CustomProperties")
.then(P.whitespace)
@@ -1155,7 +1173,7 @@ class Grammar {
P
.alt(
r.CustomProperties,
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity.attributes, attributeKey))
Grammar.CreateAttributeGrammar(r, ObjectEntity)
)
.sepBy1(P.whitespace),
P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")),
@@ -1317,15 +1335,15 @@ class Copy extends Context {
super(target, blueprint, options);
this.serializer = new ObjectSerializer();
let self = this;
this.copyHandle = _ => self.copied();
this.copyHandler = _ => self.copied();
}
blueprintFocused() {
document.body.addEventListener("copy", this.copyHandle);
document.body.addEventListener("copy", this.copyHandler);
}
blueprintUnfocused() {
document.body.removeEventListener("copy", this.copyHandle);
document.body.removeEventListener("copy", this.copyHandler);
}
copied() {
@@ -1476,99 +1494,50 @@ class DragScroll extends MouseClickDrag {
}
}
class KeyboardShortcut extends Context {
/**
* @typedef {import("../graph/GraphLink").default} GraphLink
*/
class LinkTemplate extends Template {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true;
super(target, blueprint, options);
/** @type {String[]} */
this.key = this.options.key;
this.ctrlKey = options.ctrlKey ?? false;
this.shiftKey = options.shiftKey ?? false;
this.altKey = options.altKey ?? false;
this.metaKey = options.metaKey ?? false;
let self = this;
this.keyDownHandler = e => {
if (
e.code == self.key
&& e.ctrlKey === self.ctrlKey
&& e.shiftKey === self.shiftKey
&& e.altKey === self.altKey
&& e.metaKey === self.metaKey
) {
self.fire();
}
};
/**
* Computes the html content of the target element.
* @param {GraphLink} link Link connecting two graph nodes
* @returns The result html
*/
render(link) {
return html`
<svg viewBox="0 0 100 100">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
`
}
/**
*
* @param {String} keyString
* @returns {Object}
* Applies the style to the element.
* @param {GraphLink} link Element of the graph
*/
static keyOptionsParse(options, keyString) {
options.key = keyString;
return options
}
apply(link) {
super.apply(link);
blueprintFocused() {
document.addEventListener("keydown", this.keyDownHandler);
}
blueprintUnfocused() {
document.removeEventListener("keydown", this.keyDownHandler);
}
fire() {
}
}
class Configuration {
static deleteNodesKeyboardKey = "Delete"
static expandGridSize = 400
static gridSize = 16
static gridSnap = 16
}
class KeyvoardCanc extends KeyboardShortcut {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").default} blueprint
* @param {OBject} options
* Applies the style relative to the source pin location.
* @param {GraphLink} link Link element
*/
constructor(target, blueprint, options = {}) {
options = KeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey);
super(target, blueprint, options);
applySourceLocation(link, initialPosition) {
// Set initial position
link.style.setProperty("--ueb-link-from-x", sanitizeText(initialPosition[0]));
link.style.setProperty("--ueb-link-from-y", sanitizeText(initialPosition[1]));
}
fire() {
this.blueprint.removeGraphElement(...this.blueprint.getNodes(true));
}
}
class MouseTracking extends Pointing {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true;
super(target, blueprint, options);
let self = this;
this.mousemoveHandler = e => {
self.blueprint.entity.mousePosition = self.getLocation(e);
};
}
blueprintFocused() {
this.target.addEventListener("mousemove", this.mousemoveHandler);
}
blueprintUnfocused() {
this.target.removeEventListener("mousemove", this.mousemoveHandler);
/**
* Applies the style relative to the destination pin location.
* @param {GraphLink} link Link element
*/
applyDestinationLocation(link, finalPosition) {
link.style.setProperty("--ueb-link-to-x", sanitizeText(finalPosition[0]));
link.style.setProperty("--ueb-link-to-y", sanitizeText(finalPosition[1]));
}
}
@@ -1631,112 +1600,6 @@ class DragLink extends MouseClickDrag {
}
}
/**
* @typedef {import("../graph/GraphLink").default} GraphLink
*/
class LinkTemplate extends Template {
/**
* Computes the html content of the target element.
* @param {GraphLink} link Link connecting two graph nodes
* @returns The result html
*/
render(link) {
return html`
<svg viewBox="0 0 100 100">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
`
}
/**
* Applies the style to the element.
* @param {GraphLink} link Element of the graph
*/
apply(link) {
super.apply(link);
}
/**
* Applies the style relative to the source pin location.
* @param {GraphLink} link Link element
*/
applySourceLocation(link, initialPosition) {
// Set initial position
link.style.setProperty("--ueb-link-from-x", sanitizeText(initialPosition[0]));
link.style.setProperty("--ueb-link-from-y", sanitizeText(initialPosition[1]));
}
/**
* Applies the style relative to the destination pin location.
* @param {GraphLink} link Link element
*/
applyDestinationLocation(link, finalPosition) {
link.style.setProperty("--ueb-link-to-x", sanitizeText(finalPosition[0]));
link.style.setProperty("--ueb-link-to-y", sanitizeText(finalPosition[1]));
}
}
/**
* @type {import("./GraphPin").default} GraphPin
*/
class GraphLink extends GraphElement {
/** @type {GraphPin} */
#source
/** @type {GraphPin} */
#destination
#nodeDeleteHandler = _ => this.blueprint.removeGraphElement(this)
#nodeDragSourceHandler = _ => this.setSourceLocation(this.#source.getLinkLocation())
#nodeDragDestinatonHandler = _ => this.setDestinationLocation(this.#destination.getLinkLocation())
/**
* @param {?GraphPin} source
* @param {?GraphPin} destination
*/
constructor(source, destination) {
super(this, new LinkTemplate());
/** @type {import("../template/LinkTemplate").default} */
this.template;
this.setSource(source);
this.setDestination(destination);
}
setSourceLocation(location) {
this.template.applySourceLocation(this.#source.getLinkLocation());
}
setDestinationLocation(location) {
this.template.applyDestinationLocation(this.#destination.getLinkLocation());
}
/**
* @param {GraphPin} graphPin
*/
setSourcePin(graphPin) {
this.#source?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#source?.removeEventListener("ueb-node-drag", this.#nodeDragSourceHandler);
this.#source = graphPin;
this.#source?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#source?.addEventListener("ueb-node-drag", this.#nodeDragSourceHandler);
}
/**
*
* @param {GraphPin} graphPin
*/
setDestinationPin(graphPin) {
this.#destination?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#destination?.removeEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler);
this.#destination = graphPin;
this.#destination?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#destination?.addEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler);
}
}
customElements.define("ueb-link", GraphLink);
class GraphPin extends GraphElement {
constructor(entity) {
@@ -1747,11 +1610,12 @@ class GraphPin extends GraphElement {
this.clickableElement = null;
}
connectedCallback() {
super.connectedCallback();
new DragLink(this.clickableElement, this.blueprint, {
moveEverywhere: true
});
createInputObjects() {
return [
new DragLink(this.clickableElement, this.blueprint, {
moveEverywhere: true
}),
]
}
/**
@@ -1802,6 +1666,65 @@ class GraphPin extends GraphElement {
customElements.define("ueb-pin", GraphPin);
/**
* @type {import("./GraphPin").default} GraphPin
*/
class GraphLink extends GraphElement {
/** @type {GraphPin} */
#source
/** @type {GraphPin} */
#destination
#nodeDeleteHandler = _ => this.blueprint.removeGraphElement(this)
#nodeDragSourceHandler = _ => this.setSourceLocation(this.#source.getLinkLocation())
#nodeDragDestinatonHandler = _ => this.setDestinationLocation(this.#destination.getLinkLocation())
/**
* @param {?GraphPin} source
* @param {?GraphPin} destination
*/
constructor(source, destination) {
super(this, new LinkTemplate());
/** @type {import("../template/LinkTemplate").default} */
this.template;
this.setSource(source);
this.setDestination(destination);
}
setSourceLocation(location) {
this.template.applySourceLocation(this.#source.getLinkLocation());
}
setDestinationLocation(location) {
this.template.applyDestinationLocation(this.#destination.getLinkLocation());
}
/**
* @param {GraphPin} graphPin
*/
setSourcePin(graphPin) {
this.#source?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#source?.removeEventListener("ueb-node-drag", this.#nodeDragSourceHandler);
this.#source = graphPin;
this.#source?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#source?.addEventListener("ueb-node-drag", this.#nodeDragSourceHandler);
}
/**
*
* @param {GraphPin} graphPin
*/
setDestinationPin(graphPin) {
this.#destination?.removeEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#destination?.removeEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler);
this.#destination = graphPin;
this.#destination?.addEventListener("ueb-node-delete", this.#nodeDeleteHandler);
this.#destination?.addEventListener("ueb-node-drag", this.#nodeDragDestinatonHandler);
}
}
customElements.define("ueb-link", GraphLink);
/**
* @typedef {import("../graph/SelectableDraggable").default} SelectableDraggable
*/
@@ -1956,16 +1879,12 @@ class SelectableDraggable extends GraphElement {
};
}
connectedCallback() {
super.connectedCallback();
this.dragObject = new DragMove(this, this.blueprint, {
looseTarget: true
});
}
disconnectedCallback() {
super.disconnectedCallback();
this.dragObject.unlistenDOMElement();
createInputObjects() {
return [
new DragMove(this, this.blueprint, {
looseTarget: true
}),
]
}
setLocation(value = [0, 0]) {
@@ -2025,10 +1944,6 @@ class GraphNode extends SelectableDraggable {
return new GraphNode(entity)
}
connectedCallback() {
super.connectedCallback();
}
disconnectedCallback() {
super.disconnectedCallback();
this.dispatchDeleteEvent();
@@ -2065,6 +1980,94 @@ class GraphNode extends SelectableDraggable {
customElements.define("ueb-node", GraphNode);
class KeyboardShortcut extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true;
super(target, blueprint, options);
/** @type {String[]} */
this.key = this.options.key;
this.ctrlKey = options.ctrlKey ?? false;
this.shiftKey = options.shiftKey ?? false;
this.altKey = options.altKey ?? false;
this.metaKey = options.metaKey ?? false;
let self = this;
this.keyDownHandler = e => {
if (
e.code == self.key
&& e.ctrlKey === self.ctrlKey
&& e.shiftKey === self.shiftKey
&& e.altKey === self.altKey
&& e.metaKey === self.metaKey
) {
self.fire();
}
};
}
/**
*
* @param {String} keyString
* @returns {Object}
*/
static keyOptionsParse(options, keyString) {
options.key = keyString;
return options
}
blueprintFocused() {
document.addEventListener("keydown", this.keyDownHandler);
}
blueprintUnfocused() {
document.removeEventListener("keydown", this.keyDownHandler);
}
fire() {
}
}
class KeyvoardCanc extends KeyboardShortcut {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").default} blueprint
* @param {OBject} options
*/
constructor(target, blueprint, options = {}) {
options = KeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey);
super(target, blueprint, options);
}
fire() {
this.blueprint.removeGraphElement(...this.blueprint.getNodes(true));
}
}
class MouseTracking extends Pointing {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true;
super(target, blueprint, options);
let self = this;
this.mousemoveHandler = e => {
self.blueprint.entity.mousePosition = self.getLocation(e);
};
}
blueprintFocused() {
this.target.addEventListener("mousemove", this.mousemoveHandler);
}
blueprintUnfocused() {
this.target.removeEventListener("mousemove", this.mousemoveHandler);
}
}
class Paste extends Context {
constructor(target, blueprint, options = {}) {
@@ -2299,30 +2302,28 @@ class Blueprint extends GraphElement {
this.template.applyTranlate(this);
}
connectedCallback() {
super.connectedCallback();
this.copyObject = new Copy(this.getGridDOMElement(), this);
this.pasteObject = new Paste(this.getGridDOMElement(), this);
this.cancObject = new KeyvoardCanc(this.getGridDOMElement(), this);
this.zoomObject = new Zoom(this.getGridDOMElement(), this, {
looseTarget: true,
});
this.selectObject = new Select(this.getGridDOMElement(), this, {
clickButton: 0,
exitAnyButton: true,
moveEverywhere: true,
});
this.dragObject = new DragScroll(this.getGridDOMElement(), this, {
clickButton: 2,
exitAnyButton: false,
looseTarget: true,
moveEverywhere: true,
});
this.unfocusObject = new Unfocus(this.getGridDOMElement(), this);
this.mouseTrackingObject = new MouseTracking(this.getGridDOMElement(), this);
createInputObjects() {
return [
new Copy(this.getGridDOMElement(), this),
new Paste(this.getGridDOMElement(), this),
new KeyvoardCanc(this.getGridDOMElement(), this),
new Zoom(this.getGridDOMElement(), this, {
looseTarget: true,
}),
new Select(this.getGridDOMElement(), this, {
clickButton: 0,
exitAnyButton: true,
moveEverywhere: true,
}),
new DragScroll(this.getGridDOMElement(), this, {
clickButton: 2,
exitAnyButton: false,
looseTarget: true,
moveEverywhere: true,
}),
new Unfocus(this.getGridDOMElement(), this),
new MouseTracking(this.getGridDOMElement(), this),
]
}
getGridDOMElement() {
@@ -2332,9 +2333,6 @@ class Blueprint extends GraphElement {
disconnectedCallback() {
super.disconnectedCallback();
setSelected(false);
this.dragObject.unlistenDOMElement();
this.selectObject.unlistenDOMElement();
this.pasteObject.unlistenDOMElement();
}
getScroll() {
@@ -2570,7 +2568,7 @@ class Blueprint extends GraphElement {
if (this.focused == value) {
return;
}
let event = new CustomEvent(value ? "blueprintfocus" : "blueprintunfocus");
let event = new CustomEvent(value ? "blueprint-focus" : "blueprint-unfocus");
this.focused = value;
this.dataset.focused = this.focused;
if (!this.focused) {

0
font/roboto-bold.woff Normal file → Executable file
View File

0
font/roboto-bold.woff2 Normal file → Executable file
View File

0
font/roboto-light.woff Normal file → Executable file
View File

0
font/roboto-light.woff2 Normal file → Executable file
View File

0
font/roboto-regular.woff Normal file → Executable file
View File

0
font/roboto-regular.woff2 Normal file → Executable file
View File

View File

@@ -1,7 +1,10 @@
import BlueprintTemplate from "./template/BlueprintTemplate"
import Configuration from "./Configuration"
import Copy from "./input/Copy"
import DragScroll from "./input/DragScroll"
import GraphElement from "./graph/GraphElement"
import GraphLink from "./graph/GraphLink"
import GraphNode from "./graph/GraphNode"
import GraphSelector from "./graph/GraphSelector"
import KeyboardCanc from "./input/KeyboardCanc"
import MouseTracking from "./input/MouseTracking"
@@ -10,9 +13,6 @@ import Select from "./input/Select"
import Unfocus from "./input/Unfocus"
import Utility from "./Utility"
import Zoom from "./input/Zoom"
import GraphNode from "./graph/GraphNode"
import GraphLink from "./graph/GraphLink"
import Configuration from "./Configuration"
export default class Blueprint extends GraphElement {
@@ -93,30 +93,28 @@ export default class Blueprint extends GraphElement {
this.template.applyTranlate(this)
}
connectedCallback() {
super.connectedCallback()
this.copyObject = new Copy(this.getGridDOMElement(), this)
this.pasteObject = new Paste(this.getGridDOMElement(), this)
this.cancObject = new KeyboardCanc(this.getGridDOMElement(), this)
this.zoomObject = new Zoom(this.getGridDOMElement(), this, {
looseTarget: true,
})
this.selectObject = new Select(this.getGridDOMElement(), this, {
clickButton: 0,
exitAnyButton: true,
moveEverywhere: true,
})
this.dragObject = new DragScroll(this.getGridDOMElement(), this, {
clickButton: 2,
exitAnyButton: false,
looseTarget: true,
moveEverywhere: true,
})
this.unfocusObject = new Unfocus(this.getGridDOMElement(), this)
this.mouseTrackingObject = new MouseTracking(this.getGridDOMElement(), this)
createInputObjects() {
return [
new Copy(this.getGridDOMElement(), this),
new Paste(this.getGridDOMElement(), this),
new KeyboardCanc(this.getGridDOMElement(), this),
new Zoom(this.getGridDOMElement(), this, {
looseTarget: true,
}),
new Select(this.getGridDOMElement(), this, {
clickButton: 0,
exitAnyButton: true,
moveEverywhere: true,
}),
new DragScroll(this.getGridDOMElement(), this, {
clickButton: 2,
exitAnyButton: false,
looseTarget: true,
moveEverywhere: true,
}),
new Unfocus(this.getGridDOMElement(), this),
new MouseTracking(this.getGridDOMElement(), this),
]
}
getGridDOMElement() {
@@ -126,9 +124,6 @@ export default class Blueprint extends GraphElement {
disconnectedCallback() {
super.disconnectedCallback()
setSelected(false)
this.dragObject.unlistenDOMElement()
this.selectObject.unlistenDOMElement()
this.pasteObject.unlistenDOMElement()
}
getScroll() {
@@ -364,7 +359,7 @@ export default class Blueprint extends GraphElement {
if (this.focused == value) {
return;
}
let event = new CustomEvent(value ? "blueprintfocus" : "blueprintunfocus")
let event = new CustomEvent(value ? "blueprint-focus" : "blueprint-unfocus")
this.focused = value
this.dataset.focused = this.focused
if (!this.focused) {

14
js/Configuration.js Normal file → Executable file
View File

@@ -1,7 +1,7 @@
export default class Configuration {
static deleteNodesKeyboardKey = "Delete"
static expandGridSize = 400
static gridSize = 16
static gridSnap = 16
}
export default class Configuration {
static deleteNodesKeyboardKey = "Delete"
static expandGridSize = 400
static gridSize = 16
static gridSnap = 16
}

14
js/action/Actions.js Normal file → Executable file
View File

@@ -1,8 +1,8 @@
export default class Action {
apply() {
}
revert() {
}
export default class Action {
apply() {
}
revert() {
}
}

16
js/entity/KeyBinding.js Executable file
View File

@@ -0,0 +1,16 @@
import Entity from "./Entity"
export default class KeyBinding extends Entity {
static attributes = {
bCtrlDown: false,
bAltDown: false,
bShiftDown: false,
Key: String,
CommandName: String,
}
getAttributes() {
return KeyBinding.attributes
}
}

32
js/entity/PathSymbolEntity.js Normal file → Executable file
View File

@@ -1,16 +1,16 @@
import Entity from "./Entity"
export default class PathSymbolEntity extends Entity {
static attributes = {
value: String
}
getAttributes() {
return PathSymbolEntity.attributes
}
toString() {
return this.value
}
}
import Entity from "./Entity"
export default class PathSymbolEntity extends Entity {
static attributes = {
value: String
}
getAttributes() {
return PathSymbolEntity.attributes
}
toString() {
return this.value
}
}

View File

@@ -7,6 +7,7 @@ import TypeInitialization from "./TypeInitialization"
export default class PinEntity extends Entity {
static lookbehind = "Pin"
static attributes = {
PinId: GuidEntity,
PinName: "",

View File

@@ -1,17 +1,27 @@
/**
* @typedef {import("../Blueprint").default} Blueprint
* @typedef {import("../entity/Entity").default} Entity
* @typedef {import("../input/Context").default} Context
* @typedef {import("../template/Template").default} Template
*/
export default class GraphElement extends HTMLElement {
/**
*
* @param {import("../template/Template").default} template The template to render this node
* @param {Entity} entity The entity containing blueprint related data for this graph element
* @param {Template} template The template to render this node
*/
constructor(entity, template) {
super()
/** @type {import("../Blueprint").default}" */
/** @type {Blueprint}" */
this.blueprint = null
/** @type {import("../entity/Entity").default}" */
/** @type {Entity}" */
this.entity = entity
/** @type {import("../template/Template").default}" */
/** @type {Template}" */
this.template = template
/** @type {Context[]} */
this.inputObjects = []
}
getTemplate() {
@@ -21,8 +31,14 @@ export default class GraphElement extends HTMLElement {
connectedCallback() {
this.blueprint = this.closest("ueb-blueprint")
this.template.apply(this)
this.inputObjects = this.createInputObjects()
}
disconnectedCallback() {
this.inputObjects.forEach(v => v.unlistenDOMElement())
}
createInputObjects() {
return []
}
}

View File

@@ -17,8 +17,8 @@ export default class GraphLink extends GraphElement {
#nodeDragDestinatonHandler = _ => this.setDestinationLocation(this.#destination.getLinkLocation())
/**
* @param {?GraphPin} source
* @param {?GraphPin} destination
* @param {?GraphPin} source
* @param {?GraphPin} destination
*/
constructor(source, destination) {
super(this, new LinkTemplate())

View File

@@ -23,10 +23,6 @@ export default class GraphNode extends SelectableDraggable {
return new GraphNode(entity)
}
connectedCallback() {
super.connectedCallback()
}
disconnectedCallback() {
super.disconnectedCallback()
this.dispatchDeleteEvent()

139
js/graph/GraphPin.js Normal file → Executable file
View File

@@ -1,69 +1,70 @@
import GraphElement from "./GraphElement"
import PinTemplate from "../template/PinTemplate"
import DragLink from "../input/DragLink"
import GraphLink from "./GraphLink"
export default class GraphPin extends GraphElement {
constructor(entity) {
super(entity, new PinTemplate())
/** @type {import("../entity/PinEntity").default} */
this.entity
/** @type {HTMLElement} */
this.clickableElement = null
}
connectedCallback() {
super.connectedCallback()
new DragLink(this.clickableElement, this.blueprint, {
moveEverywhere: true
})
}
/**
*
* @returns {String}
*/
getPinDisplayName() {
return this.entity.PinName
}
getAttributes() {
return PinEntity.attributes
}
isInput() {
return this.entity.isInput()
}
isOutput() {
return this.entity.isOutput()
}
isConnected() {
return this.entity.isConnected()
}
getType() {
return this.entity.getType()
}
/**
*
* @returns {GraphLink} The link created
*/
dragLink() {
let link = new GraphLink(this)
return link
}
/**
* Returns The exact location where the link originates from or arrives at.
* @returns {Number[]} The location array
*/
getLinkLocation() {
return [0, 0];
}
}
customElements.define("ueb-pin", GraphPin)
import GraphElement from "./GraphElement"
import PinTemplate from "../template/PinTemplate"
import DragLink from "../input/DragLink"
import GraphLink from "./GraphLink"
export default class GraphPin extends GraphElement {
constructor(entity) {
super(entity, new PinTemplate())
/** @type {import("../entity/PinEntity").default} */
this.entity
/** @type {HTMLElement} */
this.clickableElement = null
}
createInputObjects() {
return [
new DragLink(this.clickableElement, this.blueprint, {
moveEverywhere: true
}),
]
}
/**
*
* @returns {String}
*/
getPinDisplayName() {
return this.entity.PinName
}
getAttributes() {
return PinEntity.attributes
}
isInput() {
return this.entity.isInput()
}
isOutput() {
return this.entity.isOutput()
}
isConnected() {
return this.entity.isConnected()
}
getType() {
return this.entity.getType()
}
/**
*
* @returns {GraphLink} The link created
*/
dragLink() {
let link = new GraphLink(this)
return link
}
/**
* Returns The exact location where the link originates from or arrives at.
* @returns {Number[]} The location array
*/
getLinkLocation() {
return [0, 0];
}
}
customElements.define("ueb-pin", GraphPin)

View File

@@ -17,16 +17,12 @@ export default class SelectableDraggable extends GraphElement {
}
}
connectedCallback() {
super.connectedCallback()
this.dragObject = new DragMove(this, this.blueprint, {
looseTarget: true
})
}
disconnectedCallback() {
super.disconnectedCallback()
this.dragObject.unlistenDOMElement()
createInputObjects() {
return [
new DragMove(this, this.blueprint, {
looseTarget: true
}),
]
}
setLocation(value = [0, 0]) {

62
js/input/Context.js Normal file → Executable file
View File

@@ -1,31 +1,31 @@
export default class Context {
constructor(target, blueprint, options) {
/** @type {HTMLElement} */
this.target = target
/** @type {import("../Blueprint").default}" */
this.blueprint = blueprint
this.options = options
if (options?.wantsFocusCallback ?? false) {
let self = this
this.blueprintfocusHandler = _ => self.blueprintFocused()
this.blueprintunfocusHandler = _ => self.blueprintUnfocused()
this.blueprint.addEventListener("blueprintfocus", this.blueprintfocusHandler)
this.blueprint.addEventListener("blueprintunfocus", this.blueprintunfocusHandler)
}
}
unlistenDOMElement() {
this.blueprintUnfocused()
this.blueprint.removeEventListener("blueprintfocus", this.blueprintfocusHandler)
this.blueprint.removeEventListener("blueprintunfocus", this.blueprintunfocusHandler)
}
/* Subclasses will probabily override the following methods */
blueprintFocused() {
}
blueprintUnfocused() {
}
}
export default class Context {
constructor(target, blueprint, options) {
/** @type {HTMLElement} */
this.target = target
/** @type {import("../Blueprint").default}" */
this.blueprint = blueprint
this.options = options
let self = this
this.blueprintFocusHandler = _ => self.blueprintFocused()
this.blueprintUnfocusHandler = _ => self.blueprintUnfocused()
if (options?.wantsFocusCallback ?? false) {
this.blueprint.addEventListener("blueprint-focus", this.blueprintFocusHandler)
this.blueprint.addEventListener("blueprint-unfocus", this.blueprintUnfocusHandler)
}
}
unlistenDOMElement() {
this.blueprintUnfocused()
this.blueprint.removeEventListener("blueprint-focus", this.blueprintFocusHandler)
this.blueprint.removeEventListener("blueprint-unfocus", this.blueprintUnfocusHandler)
}
/* Subclasses will probabily override the following methods */
blueprintFocused() {
}
blueprintUnfocused() {
}
}

52
js/input/Copy.js Normal file → Executable file
View File

@@ -1,26 +1,26 @@
import Context from "./Context"
import ObjectSerializer from "../serialization/ObjectSerializer"
export default class Copy extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
this.serializer = new ObjectSerializer()
let self = this
this.copyHandle = _ => self.copied()
}
blueprintFocused() {
document.body.addEventListener("copy", this.copyHandle)
}
blueprintUnfocused() {
document.body.removeEventListener("copy", this.copyHandle)
}
copied() {
const value = this.blueprint.getNodes(true).map(node => this.serializer.write(node.entity)).join("\n")
navigator.clipboard.writeText(value)
}
}
import Context from "./Context"
import ObjectSerializer from "../serialization/ObjectSerializer"
export default class Copy extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
this.serializer = new ObjectSerializer()
let self = this
this.copyHandler = _ => self.copied()
}
blueprintFocused() {
document.body.addEventListener("copy", this.copyHandler)
}
blueprintUnfocused() {
document.body.removeEventListener("copy", this.copyHandler)
}
copied() {
const value = this.blueprint.getNodes(true).map(node => this.serializer.write(node.entity)).join("\n")
navigator.clipboard.writeText(value)
}
}

40
js/input/KeyboardCanc.js Normal file → Executable file
View File

@@ -1,21 +1,21 @@
import KeyboardShortcut from "./KeyboardShortcut"
import Configuration from "../Configuration"
export default class KeyvoardCanc extends KeyboardShortcut {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").default} blueprint
* @param {OBject} options
*/
constructor(target, blueprint, options = {}) {
options = KeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey)
super(target, blueprint, options)
}
fire() {
this.blueprint.removeGraphElement(...this.blueprint.getNodes(true))
}
import KeyboardShortcut from "./KeyboardShortcut"
import Configuration from "../Configuration"
export default class KeyvoardCanc extends KeyboardShortcut {
/**
*
* @param {HTMLElement} target
* @param {import("../Blueprint").default} blueprint
* @param {OBject} options
*/
constructor(target, blueprint, options = {}) {
options = KeyboardShortcut.keyOptionsParse(options, Configuration.deleteNodesKeyboardKey)
super(target, blueprint, options)
}
fire() {
this.blueprint.removeGraphElement(...this.blueprint.getNodes(true))
}
}

100
js/input/KeyboardShortcut.js Normal file → Executable file
View File

@@ -1,50 +1,50 @@
import Context from "./Context"
export default class KeyboardShortcut extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
/** @type {String[]} */
this.key = this.options.key
this.ctrlKey = options.ctrlKey ?? false
this.shiftKey = options.shiftKey ?? false
this.altKey = options.altKey ?? false
this.metaKey = options.metaKey ?? false
let self = this
this.keyDownHandler = e => {
if (
e.code == self.key
&& e.ctrlKey === self.ctrlKey
&& e.shiftKey === self.shiftKey
&& e.altKey === self.altKey
&& e.metaKey === self.metaKey
) {
self.fire()
}
}
}
/**
*
* @param {String} keyString
* @returns {Object}
*/
static keyOptionsParse(options, keyString) {
options.key = keyString
return options
}
blueprintFocused() {
document.addEventListener("keydown", this.keyDownHandler)
}
blueprintUnfocused() {
document.removeEventListener("keydown", this.keyDownHandler)
}
fire() {
}
}
import Context from "./Context"
export default class KeyboardShortcut extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
/** @type {String[]} */
this.key = this.options.key
this.ctrlKey = options.ctrlKey ?? false
this.shiftKey = options.shiftKey ?? false
this.altKey = options.altKey ?? false
this.metaKey = options.metaKey ?? false
let self = this
this.keyDownHandler = e => {
if (
e.code == self.key
&& e.ctrlKey === self.ctrlKey
&& e.shiftKey === self.shiftKey
&& e.altKey === self.altKey
&& e.metaKey === self.metaKey
) {
self.fire()
}
}
}
/**
*
* @param {String} keyString
* @returns {Object}
*/
static keyOptionsParse(options, keyString) {
options.key = keyString
return options
}
blueprintFocused() {
document.addEventListener("keydown", this.keyDownHandler)
}
blueprintUnfocused() {
document.removeEventListener("keydown", this.keyDownHandler)
}
fire() {
}
}

44
js/input/MouseTracking.js Normal file → Executable file
View File

@@ -1,22 +1,22 @@
import Pointing from "./Pointing"
export default class MouseTracking extends Pointing {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
let self = this
this.mousemoveHandler = e => {
self.blueprint.entity.mousePosition = self.getLocation(e)
}
}
blueprintFocused() {
this.target.addEventListener("mousemove", this.mousemoveHandler)
}
blueprintUnfocused() {
this.target.removeEventListener("mousemove", this.mousemoveHandler)
}
}
import Pointing from "./Pointing"
export default class MouseTracking extends Pointing {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
let self = this
this.mousemoveHandler = e => {
self.blueprint.entity.mousePosition = self.getLocation(e)
}
}
blueprintFocused() {
this.target.addEventListener("mousemove", this.mousemoveHandler)
}
blueprintUnfocused() {
this.target.removeEventListener("mousemove", this.mousemoveHandler)
}
}

100
js/input/Paste.js Normal file → Executable file
View File

@@ -1,50 +1,50 @@
import GraphNode from "../graph/GraphNode"
import ObjectSerializer from "../serialization/ObjectSerializer"
import Context from "./Context"
export default class Paste extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
this.serializer = new ObjectSerializer()
let self = this
this.pasteHandle = e => self.pasted(e.clipboardData.getData("Text"))
}
blueprintFocused() {
document.body.addEventListener("paste", this.pasteHandle)
}
blueprintUnfocused() {
document.body.removeEventListener("paste", this.pasteHandle)
}
pasted(value) {
let top = 0
let left = 0
let count = 0
let nodes = this.serializer.readMultiple(value).map(entity => {
let node = new GraphNode(entity)
top += node.location[1]
left += node.location[0]
++count
return node
})
top /= count
left /= count
if (nodes.length > 0) {
this.blueprint.unselectAll()
}
let mousePosition = this.blueprint.entity.mousePosition
this.blueprint.addGraphElement(...nodes)
nodes.forEach(node => {
const locationOffset = [
mousePosition[0] - left,
mousePosition[1] - top,
]
node.addLocation(this.blueprint.compensateTranslation(locationOffset))
node.setSelected(true)
})
}
}
import GraphNode from "../graph/GraphNode"
import ObjectSerializer from "../serialization/ObjectSerializer"
import Context from "./Context"
export default class Paste extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
this.serializer = new ObjectSerializer()
let self = this
this.pasteHandle = e => self.pasted(e.clipboardData.getData("Text"))
}
blueprintFocused() {
document.body.addEventListener("paste", this.pasteHandle)
}
blueprintUnfocused() {
document.body.removeEventListener("paste", this.pasteHandle)
}
pasted(value) {
let top = 0
let left = 0
let count = 0
let nodes = this.serializer.readMultiple(value).map(entity => {
let node = new GraphNode(entity)
top += node.location[1]
left += node.location[0]
++count
return node
})
top /= count
left /= count
if (nodes.length > 0) {
this.blueprint.unselectAll()
}
let mousePosition = this.blueprint.entity.mousePosition
this.blueprint.addGraphElement(...nodes)
nodes.forEach(node => {
const locationOffset = [
mousePosition[0] - left,
mousePosition[1] - top,
]
node.addLocation(this.blueprint.compensateTranslation(locationOffset))
node.setSelected(true)
})
}
}

70
js/input/Unfocus.js Normal file → Executable file
View File

@@ -1,35 +1,35 @@
import Context from "./Context"
export default class Unfocus extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
let self = this
this.clickHandler = e => self.clickedSomewhere(e)
if (this.blueprint.focuse) {
document.addEventListener("click", this.clickHandler)
}
}
/**
*
* @param {MouseEvent} e
*/
clickedSomewhere(e) {
// If target is inside the blueprint grid
if (e.target.closest("ueb-blueprint")) {
return
}
this.blueprint.setFocused(false)
}
blueprintFocused() {
document.addEventListener("click", this.clickHandler)
}
blueprintUnfocused() {
document.removeEventListener("click", this.clickHandler)
}
}
import Context from "./Context"
export default class Unfocus extends Context {
constructor(target, blueprint, options = {}) {
options.wantsFocusCallback = true
super(target, blueprint, options)
let self = this
this.clickHandler = e => self.clickedSomewhere(e)
if (this.blueprint.focuse) {
document.addEventListener("click", this.clickHandler)
}
}
/**
*
* @param {MouseEvent} e
*/
clickedSomewhere(e) {
// If target is inside the blueprint grid
if (e.target.closest("ueb-blueprint")) {
return
}
this.blueprint.setFocused(false)
}
blueprintFocused() {
document.addEventListener("click", this.clickHandler)
}
blueprintUnfocused() {
document.removeEventListener("click", this.clickHandler)
}
}

28
js/serialization/CustomSerializer.js Normal file → Executable file
View File

@@ -1,14 +1,14 @@
import GeneralSerializer from "./GeneralSerializer"
export default class CustomSerializer extends GeneralSerializer {
constructor(objectWriter, entityType) {
super(undefined, entityType)
this.objectWriter = objectWriter
}
write(object) {
let result = this.objectWriter(object)
return result
}
}
import GeneralSerializer from "./GeneralSerializer"
export default class CustomSerializer extends GeneralSerializer {
constructor(objectWriter, entityType) {
super(undefined, entityType)
this.objectWriter = objectWriter
}
write(object) {
let result = this.objectWriter(object)
return result
}
}

View File

@@ -113,25 +113,28 @@ export default class Grammar {
}
}
// Meta grammar
static CreateAttributeGrammar = (r, attributeGrammar, attributeSupplier, valueSeparator = P.string("=").trim(P.optWhitespace)) =>
attributeGrammar.skip(valueSeparator)
static CreateAttributeGrammar = (r, entityType, valueSeparator = P.string("=").trim(P.optWhitespace)) =>
r.AttributeName.skip(valueSeparator)
.chain(attributeName => {
const attributeKey = attributeName.split(".")
const attribute = attributeSupplier(attributeKey)
const attribute = Utility.objectGet(entityType.attributes, attributeKey)
let attributeValueGrammar = Grammar.getGrammarForType(r, attribute, r.AttributeAnyValue)
// Returns attributeSetter: a function called with an object as argument that will set the correct attribute value
return attributeValueGrammar.map(attributeValue =>
entity => Utility.objectSet(entity, attributeKey, attributeValue, true)
) // returns attributeSetter: a function called with an object as argument that will set the correct attribute value
)
})
// Meta grammar
static CreateMultiAttributeGrammar = (r, keyGrammar, entityType, attributeSupplier) =>
static CreateMultiAttributeGrammar = (r, entityType) =>
/**
* Basically this creates a parser that looks for a string like 'Key (A=False,B="Something",)'
* Then it populates an object of type EntityType with the attribute values found inside the parentheses.
*/
P.seqMap(
P.seq(keyGrammar, P.optWhitespace, P.string("(")),
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeSupplier)
entityType.lookbehind
? P.seq(P.string(entityType.lookbehind), P.optWhitespace, P.string("("))
: P.string("("),
Grammar.CreateAttributeGrammar(r, entityType)
.trim(P.optWhitespace)
.sepBy(P.string(","))
.skip(P.regex(/,?/).then(P.optWhitespace)), // Optional trailing comma
@@ -141,18 +144,8 @@ export default class Grammar {
attributes.forEach(attributeSetter => attributeSetter(result))
return result
})
FunctionReference = r => Grammar.CreateMultiAttributeGrammar(
r,
P.succeed(),
FunctionReferenceEntity,
attributeKey => Utility.objectGet(FunctionReferenceEntity.attributes, attributeKey)
)
Pin = r => Grammar.CreateMultiAttributeGrammar(
r,
P.string("Pin"),
PinEntity,
attributeKey => Utility.objectGet(PinEntity.attributes, attributeKey)
)
FunctionReference = r => Grammar.CreateMultiAttributeGrammar(r, FunctionReferenceEntity)
Pin = r => Grammar.CreateMultiAttributeGrammar(r, PinEntity)
CustomProperties = r =>
P.string("CustomProperties")
.then(P.whitespace)
@@ -169,7 +162,7 @@ export default class Grammar {
P
.alt(
r.CustomProperties,
Grammar.CreateAttributeGrammar(r, r.AttributeName, attributeKey => Utility.objectGet(ObjectEntity.attributes, attributeKey))
Grammar.CreateAttributeGrammar(r, ObjectEntity)
)
.sepBy1(P.whitespace),
P.seq(r.WhitespaceNewline, P.string("End"), P.whitespace, P.string("Object")),

26
js/serialization/ToStringSerializer.js Normal file → Executable file
View File

@@ -1,13 +1,13 @@
import GeneralSerializer from "./GeneralSerializer"
export default class ToStringSerializer extends GeneralSerializer {
constructor(entityType) {
super(undefined, entityType)
}
write(object) {
let result = object.toString()
return result
}
}
import GeneralSerializer from "./GeneralSerializer"
export default class ToStringSerializer extends GeneralSerializer {
constructor(entityType) {
super(undefined, entityType)
}
write(object) {
let result = object.toString()
return result
}
}

100
js/serialization/initializeSerializerFactory.js Normal file → Executable file
View File

@@ -1,50 +1,50 @@
import CustomSerializer from "./CustomSerializer"
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity"
import GeneralSerializer from "./GeneralSerializer"
import GuidEntity from "../entity/GuidEntity"
import IntegerEntity from "../entity/IntegerEntity"
import LocalizedTextEntity from "../entity/LocalizedTextEntity"
import ObjectEntity from "../entity/ObjectEntity"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity"
import ObjectSerializer from "./ObjectSerializer"
import PathSymbolEntity from "../entity/PathSymbolEntity"
import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
import SerializerFactory from "./SerializerFactory"
import ToStringSerializer from "./ToStringSerializer"
export default function initializeSerializerFactory() {
SerializerFactory.registerSerializer(
ObjectEntity,
new ObjectSerializer()
)
SerializerFactory.registerSerializer(
PinEntity,
new GeneralSerializer(v => `Pin (${v})`, PinEntity, "", ",", true)
)
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new GeneralSerializer(v => `(${v})`, FunctionReferenceEntity, "", ",", false)
)
SerializerFactory.registerSerializer(
LocalizedTextEntity,
new GeneralSerializer(v => `NSLOCTEXT(${v})`, LocalizedTextEntity, "", ",", false, "", _ => "")
)
SerializerFactory.registerSerializer(
PinReferenceEntity,
new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "")
)
SerializerFactory.registerSerializer(
ObjectReferenceEntity,
new CustomSerializer(
/** @param {ObjectReferenceEntity} objectReference */
objectReference => (objectReference.type ?? "") + (
objectReference.path
? objectReference.type ? `'"${objectReference.path}"'` : objectReference.path
: ""
))
)
SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity))
SerializerFactory.registerSerializer(GuidEntity, new ToStringSerializer(GuidEntity))
SerializerFactory.registerSerializer(IntegerEntity, new ToStringSerializer(IntegerEntity))
}
import CustomSerializer from "./CustomSerializer"
import FunctionReferenceEntity from "../entity/FunctionReferenceEntity"
import GeneralSerializer from "./GeneralSerializer"
import GuidEntity from "../entity/GuidEntity"
import IntegerEntity from "../entity/IntegerEntity"
import LocalizedTextEntity from "../entity/LocalizedTextEntity"
import ObjectEntity from "../entity/ObjectEntity"
import ObjectReferenceEntity from "../entity/ObjectReferenceEntity"
import ObjectSerializer from "./ObjectSerializer"
import PathSymbolEntity from "../entity/PathSymbolEntity"
import PinEntity from "../entity/PinEntity"
import PinReferenceEntity from "../entity/PinReferenceEntity"
import SerializerFactory from "./SerializerFactory"
import ToStringSerializer from "./ToStringSerializer"
export default function initializeSerializerFactory() {
SerializerFactory.registerSerializer(
ObjectEntity,
new ObjectSerializer()
)
SerializerFactory.registerSerializer(
PinEntity,
new GeneralSerializer(v => `Pin (${v})`, PinEntity, "", ",", true)
)
SerializerFactory.registerSerializer(
FunctionReferenceEntity,
new GeneralSerializer(v => `(${v})`, FunctionReferenceEntity, "", ",", false)
)
SerializerFactory.registerSerializer(
LocalizedTextEntity,
new GeneralSerializer(v => `NSLOCTEXT(${v})`, LocalizedTextEntity, "", ",", false, "", _ => "")
)
SerializerFactory.registerSerializer(
PinReferenceEntity,
new GeneralSerializer(v => v, PinReferenceEntity, "", " ", false, "", _ => "")
)
SerializerFactory.registerSerializer(
ObjectReferenceEntity,
new CustomSerializer(
/** @param {ObjectReferenceEntity} objectReference */
objectReference => (objectReference.type ?? "") + (
objectReference.path
? objectReference.type ? `'"${objectReference.path}"'` : objectReference.path
: ""
))
)
SerializerFactory.registerSerializer(PathSymbolEntity, new ToStringSerializer(PathSymbolEntity))
SerializerFactory.registerSerializer(GuidEntity, new ToStringSerializer(GuidEntity))
SerializerFactory.registerSerializer(IntegerEntity, new ToStringSerializer(IntegerEntity))
}

98
js/template/LinkTemplate.js Normal file → Executable file
View File

@@ -1,49 +1,49 @@
import html from "./html"
import Template from "./Template"
/**
* @typedef {import("../graph/GraphLink").default} GraphLink
*/
export default class LinkTemplate extends Template {
/**
* Computes the html content of the target element.
* @param {GraphLink} link Link connecting two graph nodes
* @returns The result html
*/
render(link) {
return html`
<svg viewBox="0 0 100 100">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
`
}
/**
* Applies the style to the element.
* @param {GraphLink} link Element of the graph
*/
apply(link) {
super.apply(link)
}
/**
* Applies the style relative to the source pin location.
* @param {GraphLink} link Link element
*/
applySourceLocation(link, initialPosition) {
// Set initial position
link.style.setProperty("--ueb-link-from-x", sanitizeText(initialPosition[0]))
link.style.setProperty("--ueb-link-from-y", sanitizeText(initialPosition[1]))
}
/**
* Applies the style relative to the destination pin location.
* @param {GraphLink} link Link element
*/
applyDestinationLocation(link, finalPosition) {
link.style.setProperty("--ueb-link-to-x", sanitizeText(finalPosition[0]))
link.style.setProperty("--ueb-link-to-y", sanitizeText(finalPosition[1]))
}
}
import html from "./html"
import Template from "./Template"
/**
* @typedef {import("../graph/GraphLink").default} GraphLink
*/
export default class LinkTemplate extends Template {
/**
* Computes the html content of the target element.
* @param {GraphLink} link Link connecting two graph nodes
* @returns The result html
*/
render(link) {
return html`
<svg viewBox="0 0 100 100">
<line x1="0" y1="80" x2="100" y2="20" stroke="black" />
</svg>
`
}
/**
* Applies the style to the element.
* @param {GraphLink} link Element of the graph
*/
apply(link) {
super.apply(link)
}
/**
* Applies the style relative to the source pin location.
* @param {GraphLink} link Link element
*/
applySourceLocation(link, initialPosition) {
// Set initial position
link.style.setProperty("--ueb-link-from-x", sanitizeText(initialPosition[0]))
link.style.setProperty("--ueb-link-from-y", sanitizeText(initialPosition[1]))
}
/**
* Applies the style relative to the destination pin location.
* @param {GraphLink} link Link element
*/
applyDestinationLocation(link, finalPosition) {
link.style.setProperty("--ueb-link-to-x", sanitizeText(finalPosition[0]))
link.style.setProperty("--ueb-link-to-y", sanitizeText(finalPosition[1]))
}
}

74
js/template/PinTemplate.js Normal file → Executable file
View File

@@ -1,38 +1,38 @@
import html from "./html"
import sanitizeText from "./sanitizeText"
import Template from "./Template"
/**
* @typedef {import("../graph/GraphPin").default} GraphPin
*/
export default class PinTemplate extends Template {
/**
* Computes the html content of the pin.
* @param {GraphPin} pin Pin entity
* @returns The result html
*/
render(pin) {
if (pin.isInput()) {
return html`
<span class="ueb-node-value-icon ${pin.isConnected() ? 'ueb-node-value-fill' : ''}"></span>
${sanitizeText(pin.getPinDisplayName())}
`
} else {
return html`
${sanitizeText(pin.getPinDisplayName())}
<span class="ueb-node-value-icon ${pin.isConnected() ? 'ueb-node-value-fill' : ''}"></span>
`
}
}
/**
* Applies the style to the element.
* @param {GraphPin} pin Element of the graph
*/
apply(pin) {
super.apply(pin)
pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + sanitizeText(pin.getType()))
pin.clickableElement = pin.querySelector(".ueb-node-value-icon")
}
import html from "./html"
import sanitizeText from "./sanitizeText"
import Template from "./Template"
/**
* @typedef {import("../graph/GraphPin").default} GraphPin
*/
export default class PinTemplate extends Template {
/**
* Computes the html content of the pin.
* @param {GraphPin} pin Pin entity
* @returns The result html
*/
render(pin) {
if (pin.isInput()) {
return html`
<span class="ueb-node-value-icon ${pin.isConnected() ? 'ueb-node-value-fill' : ''}"></span>
${sanitizeText(pin.getPinDisplayName())}
`
} else {
return html`
${sanitizeText(pin.getPinDisplayName())}
<span class="ueb-node-value-icon ${pin.isConnected() ? 'ueb-node-value-fill' : ''}"></span>
`
}
}
/**
* Applies the style to the element.
* @param {GraphPin} pin Element of the graph
*/
apply(pin) {
super.apply(pin)
pin.classList.add("ueb-node-" + pin.isInput() ? "input" : "output", "ueb-node-value-" + sanitizeText(pin.getType()))
pin.clickableElement = pin.querySelector(".ueb-node-value-icon")
}
}

58
js/template/SelectableDraggableTemplate.js Normal file → Executable file
View File

@@ -1,29 +1,29 @@
import sanitizeText from "./sanitizeText"
import Template from "./Template"
/**
* @typedef {import("../graph/SelectableDraggable").default} SelectableDraggable
*/
export default class SelectableDraggableTemplate extends Template {
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applyLocation(element) {
element.style.setProperty("--ueb-position-x", sanitizeText(element.location[0]))
element.style.setProperty("--ueb-position-y", sanitizeText(element.location[1]))
}
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applySelected(element) {
if (element.selected) {
element.classList.add("ueb-selected")
} else {
element.classList.remove("ueb-selected")
}
}
}
import sanitizeText from "./sanitizeText"
import Template from "./Template"
/**
* @typedef {import("../graph/SelectableDraggable").default} SelectableDraggable
*/
export default class SelectableDraggableTemplate extends Template {
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applyLocation(element) {
element.style.setProperty("--ueb-position-x", sanitizeText(element.location[0]))
element.style.setProperty("--ueb-position-y", sanitizeText(element.location[1]))
}
/**
* Returns the html elements rendered from this template.
* @param {SelectableDraggable} element Element of the graph
*/
applySelected(element) {
if (element.selected) {
element.classList.add("ueb-selected")
} else {
element.classList.remove("ueb-selected")
}
}
}

98
js/template/SelectorTemplate.js Normal file → Executable file
View File

@@ -1,49 +1,49 @@
import sanitizeText from "./sanitizeText"
import Template from "./Template"
/**
* @typedef {import("../graph/GraphSelector").default} GraphSelector
*/
export default class SelectorTemplate extends Template {
/**
* Applies the style to the element.
* @param {GraphSelector} selector Selector element
*/
apply(selector) {
super.apply(selector)
selector.classList.add("ueb-selector")
this.applyFinishSelecting(selector)
}
/**
* Applies the style relative to selection beginning.
* @param {GraphSelector} selector Selector element
*/
applyStartSelecting(selector, initialPosition) {
// Set initial position
selector.style.setProperty("--ueb-select-from-x", sanitizeText(initialPosition[0]))
selector.style.setProperty("--ueb-select-from-y", sanitizeText(initialPosition[1]))
// Final position coincide with the initial position, at the beginning of selection
selector.style.setProperty("--ueb-select-to-x", sanitizeText(initialPosition[0]))
selector.style.setProperty("--ueb-select-to-y", sanitizeText(initialPosition[1]))
selector.dataset.selecting = "true"
}
/**
* Applies the style relative to selection.
* @param {GraphSelector} selector Selector element
*/
applyDoSelecting(selector, finalPosition) {
selector.style.setProperty("--ueb-select-to-x", sanitizeText(finalPosition[0]))
selector.style.setProperty("--ueb-select-to-y", sanitizeText(finalPosition[1]))
}
/**
* Applies the style relative to selection finishing.
* @param {GraphSelector} selector Selector element
*/
applyFinishSelecting(selector) {
selector.dataset.selecting = "false"
}
}
import sanitizeText from "./sanitizeText"
import Template from "./Template"
/**
* @typedef {import("../graph/GraphSelector").default} GraphSelector
*/
export default class SelectorTemplate extends Template {
/**
* Applies the style to the element.
* @param {GraphSelector} selector Selector element
*/
apply(selector) {
super.apply(selector)
selector.classList.add("ueb-selector")
this.applyFinishSelecting(selector)
}
/**
* Applies the style relative to selection beginning.
* @param {GraphSelector} selector Selector element
*/
applyStartSelecting(selector, initialPosition) {
// Set initial position
selector.style.setProperty("--ueb-select-from-x", sanitizeText(initialPosition[0]))
selector.style.setProperty("--ueb-select-from-y", sanitizeText(initialPosition[1]))
// Final position coincide with the initial position, at the beginning of selection
selector.style.setProperty("--ueb-select-to-x", sanitizeText(initialPosition[0]))
selector.style.setProperty("--ueb-select-to-y", sanitizeText(initialPosition[1]))
selector.dataset.selecting = "true"
}
/**
* Applies the style relative to selection.
* @param {GraphSelector} selector Selector element
*/
applyDoSelecting(selector, finalPosition) {
selector.style.setProperty("--ueb-select-to-x", sanitizeText(finalPosition[0]))
selector.style.setProperty("--ueb-select-to-y", sanitizeText(finalPosition[1]))
}
/**
* Applies the style relative to selection finishing.
* @param {GraphSelector} selector Selector element
*/
applyFinishSelecting(selector) {
selector.dataset.selecting = "false"
}
}

4
js/template/html.js Normal file → Executable file
View File

@@ -1,2 +1,2 @@
const html = String.raw
export default html
const html = String.raw
export default html

34
js/template/sanitizeText.js Normal file → Executable file
View File

@@ -1,18 +1,18 @@
const div = document.createElement("div")
const tagReplacement = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
}
function sanitizeText(value) {
if (value.constructor === String) {
return value.replace(/[&<>'"]/g, tag => tagReplacement[tag])
}
return value
}
const div = document.createElement("div")
const tagReplacement = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
"'": '&#39;',
'"': '&quot;'
}
function sanitizeText(value) {
if (value.constructor === String) {
return value.replace(/[&<>'"]/g, tag => tagReplacement[tag])
}
return value
}
export default sanitizeText

74
package.json Normal file → Executable file
View File

@@ -1,37 +1,37 @@
{
"name": "ueblueprint",
"version": "1.0.0",
"description": "Unreal Engine's Blueprint visualisation library",
"main": "ueblueprint.js",
"scripts": {
"build": "rollup --config && sass scss:dist/css --style=compressed"
},
"repository": {
"type": "git",
"url": "git+https://github.com/barsdeveloper/ueblueprint.git"
},
"keywords": [
"unreal",
"engine",
"blueprint"
],
"author": "barsdeveloper",
"license": "MIT",
"bugs": {
"url": "https://github.com/barsdeveloper/ueblueprint/issues"
},
"homepage": "https://github.com/barsdeveloper/ueblueprint#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"rollup": "^2.58.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-minify-html-template-literals": "^1.2.0",
"rollup-plugin-terser": "^7.0.2",
"sass": "^1.45.1",
"terser": "^5.9.0"
},
"dependencies": {
"parsimmon": "^1.18.0"
}
}
{
"name": "ueblueprint",
"version": "1.0.0",
"description": "Unreal Engine's Blueprint visualisation library",
"main": "ueblueprint.js",
"scripts": {
"build": "rollup --config && sass scss:dist/css --style=compressed"
},
"repository": {
"type": "git",
"url": "git+https://github.com/barsdeveloper/ueblueprint.git"
},
"keywords": [
"unreal",
"engine",
"blueprint"
],
"author": "barsdeveloper",
"license": "MIT",
"bugs": {
"url": "https://github.com/barsdeveloper/ueblueprint/issues"
},
"homepage": "https://github.com/barsdeveloper/ueblueprint#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "^21.0.0",
"@rollup/plugin-node-resolve": "^13.0.5",
"rollup": "^2.58.0",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-minify-html-template-literals": "^1.2.0",
"rollup-plugin-terser": "^7.0.2",
"sass": "^1.45.1",
"terser": "^5.9.0"
},
"dependencies": {
"parsimmon": "^1.18.0"
}
}

52
rollup.config.js Normal file → Executable file
View File

@@ -1,27 +1,27 @@
import { nodeResolve } from '@rollup/plugin-node-resolve'
import minifyHTML from 'rollup-plugin-minify-html-template-literals'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
import copy from 'rollup-plugin-copy'
export default {
input: 'js/export.js',
output: {
file: 'dist/ueblueprint.js',
format: 'es'
},
plugins: [
nodeResolve({ browser: true }),
//minifyHTML(),
commonjs(),
//terser()
copy({
targets: [
{
src: ["font/*"],
dest: "dist/font"
}
]
})
]
import { nodeResolve } from '@rollup/plugin-node-resolve'
import minifyHTML from 'rollup-plugin-minify-html-template-literals'
import commonjs from '@rollup/plugin-commonjs'
import { terser } from 'rollup-plugin-terser'
import copy from 'rollup-plugin-copy'
export default {
input: 'js/export.js',
output: {
file: 'dist/ueblueprint.js',
format: 'es'
},
plugins: [
nodeResolve({ browser: true }),
//minifyHTML(),
commonjs(),
//terser()
copy({
targets: [
{
src: ["font/*"],
dest: "dist/font"
}
]
})
]
}

88
scss/ueblueprint-node-value-type-color.scss Normal file → Executable file
View File

@@ -1,45 +1,45 @@
.ueb {
$ueb-node-value-color : white;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-boolean {
$ueb-node-value-color : #930000;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-integer {
$ueb-node-value-color : #1fe0ad;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-float {
$ueb-node-value-color : #9ffb44;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-vector {
$ueb-node-value-color : #fcc823;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-rotator {
$ueb-node-value-color : #9eb1fc;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-string {
$ueb-node-value-color : #fc00d2;
--ueb-node-value-color : #{$ueb-node-value-color};
--ueb-node-value-background: linear-gradient(90deg, #fc00d220, #fc00d280 15%, #fc00d250 85%, transparent);
}
.ueb-node-value-name {
$ueb-node-value-color : #cb81fc;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-objectreference {
$ueb-node-value-color : #00a8f2;
--ueb-node-value-color: #{$ueb-node-value-color};
.ueb {
$ueb-node-value-color : white;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-boolean {
$ueb-node-value-color : #930000;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-integer {
$ueb-node-value-color : #1fe0ad;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-float {
$ueb-node-value-color : #9ffb44;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-vector {
$ueb-node-value-color : #fcc823;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-rotator {
$ueb-node-value-color : #9eb1fc;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-string {
$ueb-node-value-color : #fc00d2;
--ueb-node-value-color : #{$ueb-node-value-color};
--ueb-node-value-background: linear-gradient(90deg, #fc00d220, #fc00d280 15%, #fc00d250 85%, transparent);
}
.ueb-node-value-name {
$ueb-node-value-color : #cb81fc;
--ueb-node-value-color: #{$ueb-node-value-color};
}
.ueb-node-value-objectreference {
$ueb-node-value-color : #00a8f2;
--ueb-node-value-color: #{$ueb-node-value-color};
}

860
scss/ueblueprint-style.css Normal file → Executable file
View File

@@ -1,431 +1,431 @@
@font-face {
font-family: "Roboto";
font-style : light;
src :
url("../font/roboto-light.woff2") format("woff2"),
url("../font/roboto-light.woff") format("woff");
}
@font-face {
font-family: "Roboto";
font-style : regular;
src :
url("../font/roboto-regular.woff2") format("woff2"),
url("../font/roboto-regular.woff") format("woff");
}
:root {
--ueb-fron-size : 13px;
--ueb-viewport-height : 30rem;
--ueb-viewport-width : 100%;
--ueb-grid-size : 16px;
--ueb-grid-line-width : 2px;
--ueb-grid-line-color : #353535;
--ueb-grid-set : 8;
--ueb-grid-set-line-color : #161616;
--ueb-grid-axis-line-color: black;
--ueb-grid-snap : 16px;
--ueb-node-radius : 8px;
}
ueb-blueprint {
display : block;
position : relative;
font-family: Roboto, Noto, Oxygen, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif;
font-size : var(--ueb-fron-size);
user-select: none;
}
.ueb-viewport-header {
display : flex;
position : absolute;
top : 0;
right : 0;
left : 0;
height : 1.5em;
background: rgba(0, 0, 0, 0.5);
z-index : 1;
}
.ueb-viewport-zoom {
color: #4d4d4db7;
}
.ueb-viewport-body {
position : relative;
height : var(--ueb-viewport-height);
width : var(--ueb-viewport-width);
overflow : hidden;
scrollbar-width: 0;
}
ueb-blueprint[data-focused="true"] .ueb-viewport-body {
overflow: scroll;
}
.ueb-grid {
--ueb-grid-line-actual-width: calc(var(--ueb-grid-line-width) / var(--ueb-scale));
position : absolute;
min-width : 100%;
min-height : 100%;
width : calc((100% + var(--ueb-additional-x) * 1px) / var(--ueb-scale));
height : calc((100% + var(--ueb-additional-y) * 1px) / var(--ueb-scale));
background-color : #262626;
background-image :
/* Axis lines */
linear-gradient(var(--ueb-grid-axis-line-color),
var(--ueb-grid-axis-line-color)),
linear-gradient(var(--ueb-grid-axis-line-color),
var(--ueb-grid-axis-line-color)),
/* Dark bigger grid */
linear-gradient(to right,
var(--ueb-grid-set-line-color),
var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent),
linear-gradient(to bottom,
var(--ueb-grid-set-line-color),
var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent),
/* Light grid */
linear-gradient(to right,
var(--ueb-grid-line-color),
var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent),
linear-gradient(to bottom,
var(--ueb-grid-line-color),
var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent);
background-size:
/* Axis lines */
100% var(--ueb-grid-line-actual-width),
var(--ueb-grid-line-actual-width) 100%,
/* Dark bigger grid */
calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)),
calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)),
/* Light grid */
var(--ueb-grid-actual-size) var(--ueb-grid-actual-size),
var(--ueb-grid-actual-size) var(--ueb-grid-actual-size);
background-position: calc(var(--ueb-translate-x) * 1px) calc(var(--ueb-translate-y) * 1px);
background-repeat : repeat-x, repeat-y, repeat, repeat, repeat, repeat;
transform : scale(var(--ueb-scale), var(--ueb-scale));
transform-origin : 0 0;
overflow : hidden;
}
.ueb-grid[data-drag-scrolling="true"] {
cursor: grabbing;
}
.ueb-zoom--.ueb,
.ueb {
/* 16/16 */
--ueb-scale : 1;
--ueb-grid-actual-size: var(--ueb-grid-size);
}
.ueb-zoom--1.ueb {
/* 14/16 */
--ueb-scale: 0.875;
}
.ueb-zoom--2.ueb {
/* 12/16 */
--ueb-scale: 0.75;
}
.ueb-zoom--3.ueb {
/* 10.8/16 */
--ueb-scale: 0.675;
}
.ueb-zoom--4.ueb {
/* 8/16 */
--ueb-scale : 0.5;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2);
}
.ueb-zoom--5.ueb {
/* 6/16 */
--ueb-scale : 0.375;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2);
}
.ueb-zoom--6.ueb {
--ueb-scale : 0.333333;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--7.ueb {
--ueb-scale : 0.3;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--8.ueb {
--ueb-scale : 0.266666;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--9.ueb {
--ueb-scale : 0.233333;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--10.ueb {
/* 12/16 */
--ueb-scale : 0.2;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--11.ueb {
/* 12/16 */
--ueb-scale : 0.166666;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6);
}
.ueb-zoom--12.ueb {
/* 12/16 */
--ueb-scale : 0.133333;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6);
}
.ueb-grid-content {
position : relative;
width : 0;
height : 0;
transform: translateX(calc(var(--ueb-translate-x) * 1px)) translateY(calc(var(--ueb-translate-y) * 1px));
}
ueb-node {
display : block;
position : absolute;
transform : translateX(calc(var(--ueb-position-x) * 1px)) translateY(calc(var(--ueb-position-y) * 1px));
border-radius: var(--ueb-node-radius);
box-shadow : 0 0 1px 0 black, 1px 4px 6px 0 rgba(0, 0, 0, 0.3);
will-change : transform;
}
.ueb-grid[data-drag-scrolling="false"] ueb-selector[data-selecting="false"]~ueb-node {
cursor: move;
}
.ueb-node-border {
margin : -3px;
padding : 3px;
border-radius: calc(var(--ueb-node-radius) * 1.4);
}
.ueb-selected {
z-index: 1;
}
.ueb-selected>.ueb-node-border {
background-image:
linear-gradient(to right, #f1b000 0%, #f1b000 100%),
linear-gradient(to bottom, #f1b000 0%, #cc6700 100%),
linear-gradient(to right, #cc6700 0%, #cc6700 100%),
linear-gradient(to bottom, #f1b000 0%, #cc6700 100%);
background-size : 100% 7px, 7px 100%, 100% 7px, 7px 100%;
background-position: top, right, bottom, left;
background-repeat : repeat-x, repeat-y, repeat-x, repeat-y;
outline : 3px solid #cc6700;
outline-offset : -6px;
}
.ueb-node-content {
position : relative;
padding : 1px;
box-shadow : inset 0 0 2px 0 black;
border-radius: var(--ueb-node-radius);
background : rgba(0, 0, 0, 0.7);
overflow : hidden;
}
.ueb-node-header {
padding : 0.2em 0.7em;
box-shadow : inset 0 1px 2px 0 #313631, inset 0 2px 0 0 #92c381;
border-radius: var(--ueb-node-radius) var(--ueb-node-radius) 0 0;
background : linear-gradient(170deg, #5f815a 0%, #5f815a 50%, transparent 100%);
color : #c0c0c0;
font-weight : 600;
white-space : nowrap;
}
.ueb-node-name {
background: radial-gradient(closest-side, rgba(0, 0, 0, 0.5) 0%, transparent 90%);
margin : -0.1em -1.6em;
padding : 0.1em 1.6em;
}
.ueb-node-body {
display : flex;
padding : 6px 0;
color : white;
font-weight: 100;
white-space: nowrap;
}
.ueb-node-inputs {
margin-right: auto;
padding-left: 8px;
}
.ueb-node-outputs {
padding-right: 8px;
}
ueb-pin {
display: block;
padding: 1px 2px;
}
.ueb-grid[data-drag-scrolling="false"] {
cursor: default;
}
ueb-selector[data-selecting="false"]~ueb-node ueb-pin:hover {
background: var(--ueb-node-value-background);
}
.ueb-grid[data-drag-scrolling="false"] ueb-selector[data-selecting="false"]~ueb-node ueb-pin .ueb-node-value-icon {
cursor: crosshair;
}
.ueb-node-value-icon {
display : inline-block;
position : relative;
width : 0.85em;
height : 0.85em;
vertical-align: baseline;
margin : 0 0.4em -1px 0.1em;
}
.ueb-node-value-icon::before {
content : "";
display : block;
position : absolute;
top : 0;
right : 0;
bottom : 0;
left : 0;
border : 2px solid var(--ueb-node-value-color);
border-radius: 50%;
}
.ueb-node-value-fill::before {
background: var(--ueb-node-value-color);
}
.ueb-node-value-icon::after {
content : "";
display : block;
position : absolute;
top : calc(50% - 0.3em);
left : calc(100% + 1px);
width : 0;
height : 0;
border-top : 0.3em solid transparent;
border-bottom: 0.3em solid transparent;
border-left : 0.3em solid var(--ueb-node-value-color);
}
.ueb-selector {
display : block;
position : absolute;
visibility: hidden;
top : 0;
left : 0;
width : 0;
height : 0;
background-image:
/* Top */
repeating-linear-gradient(90deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(2px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(90deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
/* Bottom */
repeating-linear-gradient(90deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(2px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(90deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
/* Left */
repeating-linear-gradient(180deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(1px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(180deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
/* Right */
repeating-linear-gradient(0deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(2px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(0deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale)));
background-size:
/* Top */
100% calc(1px / var(--ueb-scale)),
100% calc(3px / var(--ueb-scale)),
/* Bottom */
100% calc(1px / var(--ueb-scale)),
100% calc(3px / var(--ueb-scale)),
/* Left */
calc(1px / var(--ueb-scale)) 100%,
calc(3px / var(--ueb-scale)) 100%,
/* Right */
calc(1px / var(--ueb-scale)) 100%,
calc(3px / var(--ueb-scale)) 100%;
background-position:
/* Top */
0 calc(1px / var(--ueb-scale)), 0 0,
/* Bottom */
0 calc(100% - 1px / var(--ueb-scale)), 0 100%,
/* Left */
calc(1px / var(--ueb-scale)) 0, 0 0,
/* Right */
calc(100% - 1px / var(--ueb-scale)) 0, 100% 0;
background-repeat: no-repeat;
}
ueb-selector>* {
visibility: visible;
}
ueb-selector[data-selecting="true"] {
visibility: visible;
top : min(var(--ueb-select-from-y) * 1px, var(--ueb-select-to-y) * 1px);
left : min(var(--ueb-select-from-x) * 1px, var(--ueb-select-to-x) * 1px);
width : calc(max(var(--ueb-select-from-x) - var(--ueb-select-to-x), var(--ueb-select-to-x) - var(--ueb-select-from-x)) * 1px);
height : calc(max(var(--ueb-select-from-y) - var(--ueb-select-to-y), var(--ueb-select-to-y) - var(--ueb-select-from-y)) * 1px);
@font-face {
font-family: "Roboto";
font-style : light;
src :
url("../font/roboto-light.woff2") format("woff2"),
url("../font/roboto-light.woff") format("woff");
}
@font-face {
font-family: "Roboto";
font-style : regular;
src :
url("../font/roboto-regular.woff2") format("woff2"),
url("../font/roboto-regular.woff") format("woff");
}
:root {
--ueb-fron-size : 13px;
--ueb-viewport-height : 30rem;
--ueb-viewport-width : 100%;
--ueb-grid-size : 16px;
--ueb-grid-line-width : 2px;
--ueb-grid-line-color : #353535;
--ueb-grid-set : 8;
--ueb-grid-set-line-color : #161616;
--ueb-grid-axis-line-color: black;
--ueb-grid-snap : 16px;
--ueb-node-radius : 8px;
}
ueb-blueprint {
display : block;
position : relative;
font-family: Roboto, Noto, Oxygen, Ubuntu, "Open Sans", "Helvetica Neue", sans-serif;
font-size : var(--ueb-fron-size);
user-select: none;
}
.ueb-viewport-header {
display : flex;
position : absolute;
top : 0;
right : 0;
left : 0;
height : 1.5em;
background: rgba(0, 0, 0, 0.5);
z-index : 1;
}
.ueb-viewport-zoom {
color: #4d4d4db7;
}
.ueb-viewport-body {
position : relative;
height : var(--ueb-viewport-height);
width : var(--ueb-viewport-width);
overflow : hidden;
scrollbar-width: 0;
}
ueb-blueprint[data-focused="true"] .ueb-viewport-body {
overflow: scroll;
}
.ueb-grid {
--ueb-grid-line-actual-width: calc(var(--ueb-grid-line-width) / var(--ueb-scale));
position : absolute;
min-width : 100%;
min-height : 100%;
width : calc((100% + var(--ueb-additional-x) * 1px) / var(--ueb-scale));
height : calc((100% + var(--ueb-additional-y) * 1px) / var(--ueb-scale));
background-color : #262626;
background-image :
/* Axis lines */
linear-gradient(var(--ueb-grid-axis-line-color),
var(--ueb-grid-axis-line-color)),
linear-gradient(var(--ueb-grid-axis-line-color),
var(--ueb-grid-axis-line-color)),
/* Dark bigger grid */
linear-gradient(to right,
var(--ueb-grid-set-line-color),
var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent),
linear-gradient(to bottom,
var(--ueb-grid-set-line-color),
var(--ueb-grid-set-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent),
/* Light grid */
linear-gradient(to right,
var(--ueb-grid-line-color),
var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent),
linear-gradient(to bottom,
var(--ueb-grid-line-color),
var(--ueb-grid-line-color) var(--ueb-grid-line-actual-width),
transparent var(--ueb-grid-line-actual-width),
transparent);
background-size:
/* Axis lines */
100% var(--ueb-grid-line-actual-width),
var(--ueb-grid-line-actual-width) 100%,
/* Dark bigger grid */
calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)),
calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)) calc(var(--ueb-grid-set) * var(--ueb-grid-actual-size)),
/* Light grid */
var(--ueb-grid-actual-size) var(--ueb-grid-actual-size),
var(--ueb-grid-actual-size) var(--ueb-grid-actual-size);
background-position: calc(var(--ueb-translate-x) * 1px) calc(var(--ueb-translate-y) * 1px);
background-repeat : repeat-x, repeat-y, repeat, repeat, repeat, repeat;
transform : scale(var(--ueb-scale), var(--ueb-scale));
transform-origin : 0 0;
overflow : hidden;
}
.ueb-grid[data-drag-scrolling="true"] {
cursor: grabbing;
}
.ueb-zoom--.ueb,
.ueb {
/* 16/16 */
--ueb-scale : 1;
--ueb-grid-actual-size: var(--ueb-grid-size);
}
.ueb-zoom--1.ueb {
/* 14/16 */
--ueb-scale: 0.875;
}
.ueb-zoom--2.ueb {
/* 12/16 */
--ueb-scale: 0.75;
}
.ueb-zoom--3.ueb {
/* 10.8/16 */
--ueb-scale: 0.675;
}
.ueb-zoom--4.ueb {
/* 8/16 */
--ueb-scale : 0.5;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2);
}
.ueb-zoom--5.ueb {
/* 6/16 */
--ueb-scale : 0.375;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 2);
}
.ueb-zoom--6.ueb {
--ueb-scale : 0.333333;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--7.ueb {
--ueb-scale : 0.3;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--8.ueb {
--ueb-scale : 0.266666;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--9.ueb {
--ueb-scale : 0.233333;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--10.ueb {
/* 12/16 */
--ueb-scale : 0.2;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 3);
}
.ueb-zoom--11.ueb {
/* 12/16 */
--ueb-scale : 0.166666;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6);
}
.ueb-zoom--12.ueb {
/* 12/16 */
--ueb-scale : 0.133333;
--ueb-grid-actual-size: calc(var(--ueb-grid-size) * 6);
}
.ueb-grid-content {
position : relative;
width : 0;
height : 0;
transform: translateX(calc(var(--ueb-translate-x) * 1px)) translateY(calc(var(--ueb-translate-y) * 1px));
}
ueb-node {
display : block;
position : absolute;
transform : translateX(calc(var(--ueb-position-x) * 1px)) translateY(calc(var(--ueb-position-y) * 1px));
border-radius: var(--ueb-node-radius);
box-shadow : 0 0 1px 0 black, 1px 4px 6px 0 rgba(0, 0, 0, 0.3);
will-change : transform;
}
.ueb-grid[data-drag-scrolling="false"] ueb-selector[data-selecting="false"]~ueb-node {
cursor: move;
}
.ueb-node-border {
margin : -3px;
padding : 3px;
border-radius: calc(var(--ueb-node-radius) * 1.4);
}
.ueb-selected {
z-index: 1;
}
.ueb-selected>.ueb-node-border {
background-image:
linear-gradient(to right, #f1b000 0%, #f1b000 100%),
linear-gradient(to bottom, #f1b000 0%, #cc6700 100%),
linear-gradient(to right, #cc6700 0%, #cc6700 100%),
linear-gradient(to bottom, #f1b000 0%, #cc6700 100%);
background-size : 100% 7px, 7px 100%, 100% 7px, 7px 100%;
background-position: top, right, bottom, left;
background-repeat : repeat-x, repeat-y, repeat-x, repeat-y;
outline : 3px solid #cc6700;
outline-offset : -6px;
}
.ueb-node-content {
position : relative;
padding : 1px;
box-shadow : inset 0 0 2px 0 black;
border-radius: var(--ueb-node-radius);
background : rgba(0, 0, 0, 0.7);
overflow : hidden;
}
.ueb-node-header {
padding : 0.2em 0.7em;
box-shadow : inset 0 1px 2px 0 #313631, inset 0 2px 0 0 #92c381;
border-radius: var(--ueb-node-radius) var(--ueb-node-radius) 0 0;
background : linear-gradient(170deg, #5f815a 0%, #5f815a 50%, transparent 100%);
color : #c0c0c0;
font-weight : 600;
white-space : nowrap;
}
.ueb-node-name {
background: radial-gradient(closest-side, rgba(0, 0, 0, 0.5) 0%, transparent 90%);
margin : -0.1em -1.6em;
padding : 0.1em 1.6em;
}
.ueb-node-body {
display : flex;
padding : 6px 0;
color : white;
font-weight: 100;
white-space: nowrap;
}
.ueb-node-inputs {
margin-right: auto;
padding-left: 8px;
}
.ueb-node-outputs {
padding-right: 8px;
}
ueb-pin {
display: block;
padding: 1px 2px;
}
.ueb-grid[data-drag-scrolling="false"] {
cursor: default;
}
ueb-selector[data-selecting="false"]~ueb-node ueb-pin:hover {
background: var(--ueb-node-value-background);
}
.ueb-grid[data-drag-scrolling="false"] ueb-selector[data-selecting="false"]~ueb-node ueb-pin .ueb-node-value-icon {
cursor: crosshair;
}
.ueb-node-value-icon {
display : inline-block;
position : relative;
width : 0.85em;
height : 0.85em;
vertical-align: baseline;
margin : 0 0.4em -1px 0.1em;
}
.ueb-node-value-icon::before {
content : "";
display : block;
position : absolute;
top : 0;
right : 0;
bottom : 0;
left : 0;
border : 2px solid var(--ueb-node-value-color);
border-radius: 50%;
}
.ueb-node-value-fill::before {
background: var(--ueb-node-value-color);
}
.ueb-node-value-icon::after {
content : "";
display : block;
position : absolute;
top : calc(50% - 0.3em);
left : calc(100% + 1px);
width : 0;
height : 0;
border-top : 0.3em solid transparent;
border-bottom: 0.3em solid transparent;
border-left : 0.3em solid var(--ueb-node-value-color);
}
.ueb-selector {
display : block;
position : absolute;
visibility: hidden;
top : 0;
left : 0;
width : 0;
height : 0;
background-image:
/* Top */
repeating-linear-gradient(90deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(2px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(90deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
/* Bottom */
repeating-linear-gradient(90deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(2px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(90deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
/* Left */
repeating-linear-gradient(180deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(1px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(180deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
/* Right */
repeating-linear-gradient(0deg,
transparent,
transparent calc(1px / var(--ueb-scale)),
white calc(2px / var(--ueb-scale)),
white calc(7px / var(--ueb-scale)),
transparent calc(7px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale))),
repeating-linear-gradient(0deg,
black,
black calc(8px / var(--ueb-scale)),
transparent calc(9px / var(--ueb-scale)),
transparent calc(11px / var(--ueb-scale)));
background-size:
/* Top */
100% calc(1px / var(--ueb-scale)),
100% calc(3px / var(--ueb-scale)),
/* Bottom */
100% calc(1px / var(--ueb-scale)),
100% calc(3px / var(--ueb-scale)),
/* Left */
calc(1px / var(--ueb-scale)) 100%,
calc(3px / var(--ueb-scale)) 100%,
/* Right */
calc(1px / var(--ueb-scale)) 100%,
calc(3px / var(--ueb-scale)) 100%;
background-position:
/* Top */
0 calc(1px / var(--ueb-scale)), 0 0,
/* Bottom */
0 calc(100% - 1px / var(--ueb-scale)), 0 100%,
/* Left */
calc(1px / var(--ueb-scale)) 0, 0 0,
/* Right */
calc(100% - 1px / var(--ueb-scale)) 0, 100% 0;
background-repeat: no-repeat;
}
ueb-selector>* {
visibility: visible;
}
ueb-selector[data-selecting="true"] {
visibility: visible;
top : min(var(--ueb-select-from-y) * 1px, var(--ueb-select-to-y) * 1px);
left : min(var(--ueb-select-from-x) * 1px, var(--ueb-select-to-x) * 1px);
width : calc(max(var(--ueb-select-from-x) - var(--ueb-select-to-x), var(--ueb-select-to-x) - var(--ueb-select-from-x)) * 1px);
height : calc(max(var(--ueb-select-from-y) - var(--ueb-select-to-y), var(--ueb-select-to-y) - var(--ueb-select-from-y)) * 1px);
}