diff --git a/Editor.js b/Editor.js index 48e7a8f..7968c5e 100644 --- a/Editor.js +++ b/Editor.js @@ -8,6 +8,7 @@ class Editor extends List { [EditorNode, [0, Number.POSITIVE_INFINITY]], [EditorGroup, [0, Number.POSITIVE_INFINITY]], [EditorLink, [0, Number.POSITIVE_INFINITY]], + [EditorTag, [0, Number.POSITIVE_INFINITY]], [EditorLabel, [0, 1]], [EditorHelp, [0, Number.POSITIVE_INFINITY]], ]); @@ -143,6 +144,24 @@ class Editor extends List { } return null; } + + addTagAfter(...rest) { + if (this.mayAdd(EditorTag)) { + return EditorTag.addAfter(this.container_, this.getSelected(), + this.queryEntries('.highlight', EditorNode), + ...rest); + } + return null; + } + + addTagBefore(...rest) { + if (this.mayAdd(EditorTag)) { + return EditorTag.addBefore(this.container_, this.getSelected(), + this.queryEntries('.highlight', EditorNode), + ...rest); + } + return null; + } onKeyDown(e) { switch (e.key) { @@ -202,6 +221,21 @@ class Editor extends List { } return; + case 't': + case '#': + if (this.addTagAfter()) { + e.stopPropagation(); + e.preventDefault(); + } + return; + + case 'T': + if (this.addTagBefore()) { + e.stopPropagation(); + e.preventDefault(); + } + return; + case '?': if (this.addHelpAfter()) { e.stopPropagation(); @@ -233,3 +267,4 @@ class Editor extends List { + diff --git a/EditorEntryBase.js b/EditorEntryBase.js index b8198e8..52ef7ec 100644 --- a/EditorEntryBase.js +++ b/EditorEntryBase.js @@ -104,6 +104,8 @@ class EditorEntryBase extends ListenUtils { return EditorLink.unserialize(ser); case 'node': return EditorNode.unserialize(ser); + case 'tag': + return EditorTag.unserialize(ser); } } } diff --git a/EditorHelp.js b/EditorHelp.js index a357da5..9679d3b 100644 --- a/EditorHelp.js +++ b/EditorHelp.js @@ -31,8 +31,10 @@ class EditorHelp extends EditorEntryBase { this.addText('Label'); this.addLine(); + this.addKey('t'); + this.addText('Tag '); this.addKey('?'); - this.addText('Help '); + this.addText('Help '); this.addLine(); this.addKey('⇧'); diff --git a/EditorTag.js b/EditorTag.js new file mode 100644 index 0000000..370d3eb --- /dev/null +++ b/EditorTag.js @@ -0,0 +1,39 @@ +class EditorTag extends EditorSublistBase { + constructor(id, entries) { + super(id, '#', 'tag', [ + [EditorNode, [1, Number.POSITIVE_INFINITY]], + [EditorLabel, [0, 1]], + ]); + + if (entries && entries.length) { + for (let entry of entries) { + this.nodes_.addNodeAfter(entry.getLabel()); + } + } else { + this.nodes_.addNodeAfter(); + } + } + + serialize() { + return super.serialize({ + type: 'tag', + members: this.nodes_.serialize(EditorNode), + }); + } + + getNodes() { + return this.nodes_.getEntries(EditorNode); + } + + static unserialize(ser) { + let tag = new EditorTag(ser.id); + tag.nodes_.clear(); + if (ser.label != null) { + tag.setLabel(ser.label, ser.labelObj.id); + tag.getLabelObj().setHighlight(ser.labelObj.highlight); + } + tag.setHighlight(ser.highlight); + tag.nodes_.unserialize(ser.members); + return tag.getElement(); + } +} diff --git a/Graph.js b/Graph.js index b861a5d..0d4e5c9 100644 --- a/Graph.js +++ b/Graph.js @@ -6,12 +6,14 @@ class Graph { this.groups = []; this.links = []; this.nodes = []; + this.tags = []; this.label = null; this.processList(def.editor); this.removeSoftNodes(); this.resolveLinks(); this.resolveGroups(); + this.resolveTags(); this.flattenNodes(); this.setPageRank(); this.setSubgraph(); @@ -41,6 +43,10 @@ class Graph { case 'node': this.processNode(item, soft); break; + + case 'tag': + this.processTag(item); + break; } } @@ -79,6 +85,15 @@ class Graph { getOrSet(this.nodesByLabel, node.label, []).push(node); } + processTag(item) { + let tag = GraphTag.process(item); + if (!tag) { + return; + } + this.tags.push(tag); + this.processList(item.members, true); + } + removeSoftNodes() { for (let nodes of this.nodesByLabel.values()) { for (let i = nodes.length - 1; i >= 0 && nodes.length > 1; --i) { @@ -101,6 +116,12 @@ class Graph { } } + resolveTags() { + for (let tag of this.tags) { + tag.resolve(this.nodesByLabel); + } + } + flattenNodes() { for (let nodes of this.nodesByLabel.values()) { this.nodes.push(...nodes); @@ -139,3 +160,4 @@ class Graph { + diff --git a/GraphTag.js b/GraphTag.js new file mode 100644 index 0000000..6139d6c --- /dev/null +++ b/GraphTag.js @@ -0,0 +1,31 @@ +class GraphTag { + constructor() { + this.nodes = []; + } + + resolve(nodesByLabel) { + for (let label of this.nodeLabels.values()) { + for (let node of nodesByLabel.get(label)) { + this.nodes.push(node); + } + } + } + + static process(item) { + let tag = new GraphTag(); + tag.id = item.id; + tag.label = item.label; + tag.labelId = item.labelObj ? item.labelObj.id : null; + tag.nodeLabels = new Set(); + for (let member of item.members) { + if (member.label == '') { + continue; + } + tag.nodeLabels.add(member.label); + } + if (tag.nodeLabels.size == 0) { + return null; + } + return tag; + } +} diff --git a/Grid.js b/Grid.js index 7387747..0b05420 100644 --- a/Grid.js +++ b/Grid.js @@ -46,7 +46,7 @@ class Grid { break; case 'node': - this.drawNode(step.id, step.label, step.pos); + this.drawNode(step.id, step.label, step.pos, step.tags); break; } } @@ -134,7 +134,7 @@ class Grid { this.linkToEditor(elem, id, true); } - drawNode(id, label, pos) { + drawNode(id, label, pos, tags) { let node = document.createElement('div'); this.container_.appendChild(node); node.classList.add('gridNode'); @@ -142,6 +142,9 @@ class Grid { node.innerText = label; node.style.gridColumn = pos[0] + 1; node.style.gridRow = pos[1] + 1; + for (let tag of tags) { + node.classList.add('tag' + tag); + } this.linkToEditor(node, id, true); } diff --git a/Layout.js b/Layout.js index 1e42008..a199247 100644 --- a/Layout.js +++ b/Layout.js @@ -12,6 +12,7 @@ class Layout { this.setInitialPositions(); this.resolveGroups(); this.resolveLinks(); + this.resolveTags(); this.setAffinity(); while (this.iterate()); this.addGroupPos(); @@ -74,6 +75,15 @@ class Layout { } } + resolveTags() { + this.tags_ = []; + for (let i = 0; i < this.graph_.tags.length; ++i) { + let tag = this.graph_.tags[i]; + let nodes = this.nodesFromGraphNodes(tag.nodes); + this.tags_.push(new LayoutTag(tag, nodes, i)); + } + } + setAffinity() { for (let node of this.nodes_) { node.setAffinity(this.nodesByGraphNode_); @@ -298,3 +308,4 @@ class Layout { + diff --git a/LayoutNode.js b/LayoutNode.js index 62c9760..667a644 100644 --- a/LayoutNode.js +++ b/LayoutNode.js @@ -5,6 +5,7 @@ class LayoutNode { this.pos = pos; this.groups = new Set(); + this.tags = new Set(); this.affinity_ = []; this.label = this.graphNode_.label; @@ -161,6 +162,7 @@ class LayoutNode { pos: this.pos, id: this.graphNode_.id, label: this.graphNode_.label, + tags: Array.from(this.tags), }; } } diff --git a/LayoutTag.js b/LayoutTag.js new file mode 100644 index 0000000..5fcbcb3 --- /dev/null +++ b/LayoutTag.js @@ -0,0 +1,20 @@ +class LayoutTag { + constructor(graphTag, nodes, idx) { + this.graphTag_ = graphTag; + this.nodes_ = new Set(nodes); + this.idx_ = idx; + + this.label = this.graphTag_ ? this.graphTag_.label : null; + + const NUM_TAGS = 6; + + for (let node of this.nodes_) { + node.tags.add(this.idx_ % NUM_TAGS); + } + } + + getSteps() { + let steps = []; + return steps; + } +} diff --git a/architype.css b/architype.css index 52fb589..bc94002 100644 --- a/architype.css +++ b/architype.css @@ -18,6 +18,13 @@ --input: rgba(0,0,0,0.1); --input-focus: rgba(255,0,0,0.2); --line: #000000; + + --tag0: hsl(180, 100%, 75%); + --tag1: hsl(240, 100%, 75%); + --tag2: hsl(300, 100%, 75%); + --tag3: hsl( 0, 100%, 75%); + --tag4: hsl( 60, 100%, 75%); + --tag5: hsl(120, 100%, 75%); } .theme-dark { @@ -40,6 +47,13 @@ --input: rgba(255,255,255,0.2); --input-focus: rgba(255,0,0,0.2); --line: #ffffff; + + --tag0: hsl(180, 100%, 25%); + --tag1: hsl(240, 100%, 25%); + --tag2: hsl(300, 100%, 25%); + --tag3: hsl( 0, 100%, 25%); + --tag4: hsl( 60, 100%, 25%); + --tag5: hsl(120, 100%, 25%); } :root { @@ -256,6 +270,70 @@ body { z-index: 3; } +.gridNode.tag0 { + background: var(--tag0); +} + +.gridNode.tag1 { + background: var(--tag1); +} + +.gridNode.tag2 { + background: var(--tag2); +} + +.gridNode.tag3 { + background: var(--tag3); +} + +.gridNode.tag4 { + background: var(--tag4); +} + +.gridNode.tag5 { + background: var(--tag5); +} + +.gridNode.highlight { + border-color: var(--focus); + border-width: 3px; +} + +.gridGraphLabel { + height: 100%; + width: 100%; + font-size: 30px; + text-align: center; +} + +.gridGraphLabel.highlight { + color: var(--focus); +} + +.gridGroup { + width: 100%; + height: 100%; + background: var(--group-background); + justify-content: flex-start; + border: 1px dashed var(--line); + z-index: 1; +} + +.gridGroup.highlight { + border-color: var(--focus); + border-width: 3px; +} + +.gridGroupLabel { + max-width: 90%; + max-height: 90%; + justify-content: center; + font-size: 20px; + overflow: hidden; + overflow-wrap: anywhere; + z-index: 1; +} + .gridNode.highlight { border-color: var(--focus); border-width: 3px;