From 70969b2208dcba29b2aef8145379cef4be5a7326 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 4 Nov 2022 16:39:53 +0100 Subject: [PATCH 01/30] template setup for tree component --- object_database/web/devtools/tree/Tree.js | 35 +++++++++++++++++++ .../web/devtools/tree/examples/tree.html | 18 ++++++++++ .../web/devtools/tree/package.json | 14 ++++++++ .../web/devtools/tree/tests/component.js | 19 ++++++++++ .../web/devtools/tree/tests/test-setup.js | 18 ++++++++++ 5 files changed, 104 insertions(+) create mode 100644 object_database/web/devtools/tree/Tree.js create mode 100644 object_database/web/devtools/tree/examples/tree.html create mode 100644 object_database/web/devtools/tree/package.json create mode 100644 object_database/web/devtools/tree/tests/component.js create mode 100644 object_database/web/devtools/tree/tests/test-setup.js diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js new file mode 100644 index 000000000..3d3efae58 --- /dev/null +++ b/object_database/web/devtools/tree/Tree.js @@ -0,0 +1,35 @@ +/** + * Tree Graph Web component + **/ + + +// Simple grid-based sheet component +const templateString = ` + +
A Tree
+`; + +class Tree extends HTMLElement { + constructor() { + super(); + this.template = document.createElement("template"); + this.template.innerHTML = templateString; + this.attachShadow({ mode: "open" }); + this.shadowRoot.appendChild(this.template.content.cloneNode(true)); + } + + get ok(){ + return "ok"; + } +} + +window.customElements.define("my-tree", Tree); + +export { Tree as default, Tree } diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html new file mode 100644 index 000000000..0a098e1aa --- /dev/null +++ b/object_database/web/devtools/tree/examples/tree.html @@ -0,0 +1,18 @@ + + + + Basic Grid Tests + + + + + + + + + diff --git a/object_database/web/devtools/tree/package.json b/object_database/web/devtools/tree/package.json new file mode 100644 index 000000000..fc27b4de3 --- /dev/null +++ b/object_database/web/devtools/tree/package.json @@ -0,0 +1,14 @@ +{ + "type": "module", + "devDependencies": { + "chai": "^4.3.4", + "esm": "^3.2.25", + "jsdom": "^19.0.0", + "jsdom-global": "^3.0.2", + "mocha": "^10.0.0", + "prettier": "^2.7.1" + }, + "scripts": { + "test": "mocha --require ./tests/test-setup.js ./tests/" + } +} diff --git a/object_database/web/devtools/tree/tests/component.js b/object_database/web/devtools/tree/tests/component.js new file mode 100644 index 000000000..a942e54bf --- /dev/null +++ b/object_database/web/devtools/tree/tests/component.js @@ -0,0 +1,19 @@ +/** + * Core tests for the Tree component + **/ + +import chai from "chai"; +const assert = chai.assert; + +import { Tree } from '../tree.js'; + +describe("Tree Component Tests", () => { + const tree = document.createElement("my-tree"); + beforeEach(() => { + }); + it("Ok", () => { + assert.equal(tree.ok, "ok"); + }); +}) + + diff --git a/object_database/web/devtools/tree/tests/test-setup.js b/object_database/web/devtools/tree/tests/test-setup.js new file mode 100644 index 000000000..01ad11442 --- /dev/null +++ b/object_database/web/devtools/tree/tests/test-setup.js @@ -0,0 +1,18 @@ +// Preload file for JSDOM required tests +import { JSDOM } from "jsdom"; + +const resetDOM = () => { + const dom = new JSDOM( + "/>/>" + ); + globalThis.window = dom.window; + globalThis.document = dom.window.document; + globalThis.HTMLElement = dom.window.HTMLElement; + globalThis.customElements = dom.window.customElements; + globalThis.CustomEvent = dom.window.CustomEvent; + globalThis.KeyboardEvent = dom.window.KeyboardEvent; +}; + +globalThis.resetDOM = resetDOM; + +resetDOM(); From 8f8fcf36dd0824af1e0f3224f2cc29b7d694e91b Mon Sep 17 00:00:00 2001 From: dkrasner Date: Fri, 4 Nov 2022 21:37:14 +0100 Subject: [PATCH 02/30] adding TreeNode and updating basic example setup --- object_database/web/devtools/tree/Tree.js | 34 +++++++++++++++++-- object_database/web/devtools/tree/TreeNode.js | 33 ++++++++++++++++++ .../web/devtools/tree/examples/tree.html | 33 +++++++++++++++--- .../web/devtools/tree/package.json | 3 ++ 4 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 object_database/web/devtools/tree/TreeNode.js diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 3d3efae58..9f084401a 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -13,7 +13,7 @@ const templateString = ` } -
A Tree
+
A Tree
`; class Tree extends HTMLElement { @@ -23,13 +23,43 @@ class Tree extends HTMLElement { this.template.innerHTML = templateString; this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(this.template.content.cloneNode(true)); + + // bind methods + this.setup = this.setup.bind(this); + this.setupNode = this.setupNode.bind(this); + } + + connectedCallback(){ + if(this.isConnected){ + this.setup(); + } } + setup(data){ + this.setupNode(data); + } + + + setupNode(nodeData){ + if(nodeData){ + const wrapper = this.shadowRoot.querySelector("#wrapper"); + const node = document.createElement("tree-node"); + wrapper.append(node); + // setup the children + nodeData.children.forEach((childData) => { + this.setupNode(childData); + }) + } + } + + + + get ok(){ return "ok"; } } -window.customElements.define("my-tree", Tree); +window.customElements.define("tree-graph", Tree); export { Tree as default, Tree } diff --git a/object_database/web/devtools/tree/TreeNode.js b/object_database/web/devtools/tree/TreeNode.js new file mode 100644 index 000000000..accc10d4a --- /dev/null +++ b/object_database/web/devtools/tree/TreeNode.js @@ -0,0 +1,33 @@ +/** + * Tree Node Web component + **/ + + +// Simple grid-based sheet component +const templateString = ` + +
A Node
+`; + +class TreeNode extends HTMLElement { + constructor() { + super(); + this.template = document.createElement("template"); + this.template.innerHTML = templateString; + this.attachShadow({ mode: "open" }); + this.shadowRoot.appendChild(this.template.content.cloneNode(true)); + } + + get ok(){ + return "ok"; + } +} + +window.customElements.define("tree-node", TreeNode); + +export { TreeNode as default, TreeNode } diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index 0a098e1aa..f9c50bbe0 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -4,15 +4,38 @@ Basic Grid Tests + - - +
diff --git a/object_database/web/devtools/tree/package.json b/object_database/web/devtools/tree/package.json index fc27b4de3..b33e0cbc2 100644 --- a/object_database/web/devtools/tree/package.json +++ b/object_database/web/devtools/tree/package.json @@ -10,5 +10,8 @@ }, "scripts": { "test": "mocha --require ./tests/test-setup.js ./tests/" + }, + "dependencies": { + "leader-line": "^1.0.7" } } From 7fbf00cdd501908e5d3c85c066d456696ebbe6dd Mon Sep 17 00:00:00 2001 From: dkrasner Date: Sat, 5 Nov 2022 22:21:33 +0100 Subject: [PATCH 03/30] adding depths to trees and webpack --- object_database/web/devtools/tree/Tree.js | 36 +++++++++++++------ object_database/web/devtools/tree/TreeNode.js | 10 ++++-- .../web/devtools/tree/examples/tree.html | 3 +- object_database/web/devtools/tree/main.js | 6 ++++ .../web/devtools/tree/package.json | 8 +++-- .../web/devtools/tree/webpack.config.js | 30 ++++++++++++++++ 6 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 object_database/web/devtools/tree/main.js create mode 100644 object_database/web/devtools/tree/webpack.config.js diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 9f084401a..5ae8fd78c 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -2,6 +2,7 @@ * Tree Graph Web component **/ +import LeaderLine from "leader-line"; // Simple grid-based sheet component const templateString = ` @@ -12,8 +13,13 @@ const templateString = ` overflow: hidden; /* For auto-resize without scrolling on */ } +.depth { + display: flex; + justify-content: space-around; +} + -
A Tree
+
`; class Tree extends HTMLElement { @@ -36,24 +42,34 @@ class Tree extends HTMLElement { } setup(data){ - this.setupNode(data); + const wrapper = this.shadowRoot.querySelector("#wrapper"); + const nodeDepth = document.createElement("div"); + nodeDepth.classList.add("depth"); + wrapper.append(nodeDepth); + this.setupNode(data, nodeDepth); } - - setupNode(nodeData){ + setupNode(nodeData, nodeDepth){ if(nodeData){ - const wrapper = this.shadowRoot.querySelector("#wrapper"); const node = document.createElement("tree-node"); - wrapper.append(node); - // setup the children + node.name = nodeData.name; + node.setAttribute("id", nodeData.id); + nodeDepth.append(node); + // setup the children in a new node depth + const wrapper = this.shadowRoot.querySelector("#wrapper"); + const childrenDepth = document.createElement("div"); + childrenDepth.classList.add("depth"); + wrapper.append(childrenDepth); nodeData.children.forEach((childData) => { - this.setupNode(childData); + this.setupNode(childData, childrenDepth); + }) } } - - + updateLeaderLines(depth){ + //TODO: + } get ok(){ return "ok"; diff --git a/object_database/web/devtools/tree/TreeNode.js b/object_database/web/devtools/tree/TreeNode.js index accc10d4a..af509386f 100644 --- a/object_database/web/devtools/tree/TreeNode.js +++ b/object_database/web/devtools/tree/TreeNode.js @@ -11,7 +11,9 @@ const templateString = ` } -
A Node
+
+ +
`; class TreeNode extends HTMLElement { @@ -23,8 +25,10 @@ class TreeNode extends HTMLElement { this.shadowRoot.appendChild(this.template.content.cloneNode(true)); } - get ok(){ - return "ok"; + set name(s){ + const name = this.shadowRoot.querySelector("#name"); + name.innerText = s; + } } diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index f9c50bbe0..082674f9e 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -3,8 +3,7 @@ Basic Grid Tests - - +
diff --git a/object_database/web/devtools/tree/main.js b/object_database/web/devtools/tree/main.js new file mode 100644 index 000000000..9109ce112 --- /dev/null +++ b/object_database/web/devtools/tree/main.js @@ -0,0 +1,6 @@ +/* Main */ +import { Tree } from "./Tree.js"; +import { TreeNode } from "./TreeNode.js"; +import LeaderLine from "leader-line"; + +export { Tree, TreeNode, LeaderLine, Tree as default }; diff --git a/object_database/web/devtools/tree/package.json b/object_database/web/devtools/tree/package.json index b33e0cbc2..8361a9956 100644 --- a/object_database/web/devtools/tree/package.json +++ b/object_database/web/devtools/tree/package.json @@ -9,9 +9,13 @@ "prettier": "^2.7.1" }, "scripts": { - "test": "mocha --require ./tests/test-setup.js ./tests/" + "test": "mocha --require ./tests/test-setup.js ./tests/", + "build": "./node_modules/webpack/bin/webpack.js" }, "dependencies": { - "leader-line": "^1.0.7" + "leader-line": "^1.0.7", + "skeleton-loader": "^2.0.0", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0" } } diff --git a/object_database/web/devtools/tree/webpack.config.js b/object_database/web/devtools/tree/webpack.config.js new file mode 100644 index 000000000..4961f974c --- /dev/null +++ b/object_database/web/devtools/tree/webpack.config.js @@ -0,0 +1,30 @@ +import path from "path"; +import * as url from "url"; +const __filename = url.fileURLToPath(import.meta.url); +const __dirname = url.fileURLToPath(new URL(".", import.meta.url)); + +let config = { + entry: "./main.js", + output: { + path: path.resolve(__dirname, "dist/"), + filename: "tree.bundle.js", + }, + module: { + rules: [ + { + test: path.resolve(__dirname, "node_modules/leader-line/"), + use: [ + { + loader: "skeleton-loader", + options: { + procedure: (content) => `${content}export default LeaderLine`, + }, + }, + ], + }, + ], + }, + mode: "development", +}; + +export { config as default }; From 947d46fb1008a631e1f3ab28cec549b0b5288423 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 7 Nov 2022 21:12:42 +0100 Subject: [PATCH 04/30] [WIP] rewriting Tree as regular object to dealing with leaderlines --- object_database/web/devtools/tree/Tree.js | 61 ++++++++++++++----- .../web/devtools/tree/examples/tree.html | 56 ++++++++++------- 2 files changed, 80 insertions(+), 37 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 5ae8fd78c..80b0fbbed 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -16,6 +16,13 @@ const templateString = ` .depth { display: flex; justify-content: space-around; + border: 2px black solid; +} + +.child-wrapper { + display: flex; + justify-content: space-around; + border: 1px blue solid; } @@ -33,42 +40,68 @@ class Tree extends HTMLElement { // bind methods this.setup = this.setup.bind(this); this.setupNode = this.setupNode.bind(this); + this.setupLeaderLines = this.setupLeaderLines.bind(this); } connectedCallback(){ if(this.isConnected){ - this.setup(); + // this.setup(); } } setup(data){ - const wrapper = this.shadowRoot.querySelector("#wrapper"); + // const wrapper = this.shadowRoot.querySelector("#wrapper"); + const wrapper = document.body; const nodeDepth = document.createElement("div"); nodeDepth.classList.add("depth"); + nodeDepth.setAttribute("id", "depth-0"); wrapper.append(nodeDepth); - this.setupNode(data, nodeDepth); + const nodeWrapper = document.createElement("div"); + nodeWrapper.classList.add("child-wrapper"); + nodeDepth.append(nodeWrapper); + this.setupNode(data, nodeWrapper, 1); } - setupNode(nodeData, nodeDepth){ + setupNode(nodeData, wrapperDiv, depth){ if(nodeData){ + console.log(depth); const node = document.createElement("tree-node"); node.name = nodeData.name; node.setAttribute("id", nodeData.id); - nodeDepth.append(node); + wrapperDiv.append(node); // setup the children in a new node depth - const wrapper = this.shadowRoot.querySelector("#wrapper"); - const childrenDepth = document.createElement("div"); - childrenDepth.classList.add("depth"); - wrapper.append(childrenDepth); - nodeData.children.forEach((childData) => { - this.setupNode(childData, childrenDepth); + if(nodeData.children.length){ + // if the corresponding depth has not been added, do so now + let depthDiv = document.body.querySelector(`#depth-${depth}`); + if (!depthDiv) { + depthDiv = document.createElement("div"); + depthDiv.setAttribute("id", `depth-${depth}`); + depthDiv.classList.add("depth"); + // const wrapper = this.shadowRoot.querySelector("#wrapper"); + const wrapper = document.body; + wrapper.append(depthDiv); + } + const childWrapper = document.createElement("div"); + childWrapper.classList.add("child-wrapper"); + depthDiv.append(childWrapper); + nodeData.children.forEach((childData) => { + const child = this.setupNode(childData, childWrapper, depth + 1); + // this.updateLeaderLines(node, child); - }) + }) + } + return node; } } - updateLeaderLines(depth){ - //TODO: + setupLeaderLines(data){ + if (data && data.children) { + const parent = document.body.querySelector(`tree-node#${data.id}`); + data.children.forEach((cdata) => { + const child = document.body.querySelector(`tree-node#${cdata.id}`); + new LeaderLine(parent, child); + }) + } } get ok(){ diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index 082674f9e..5ff6e59cb 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -1,7 +1,7 @@ - Basic Grid Tests + Basic Tree Tests @@ -9,32 +9,42 @@
From 44676d4901f6000ed641789ec24f101cfda511b5 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Tue, 8 Nov 2022 13:34:48 +0100 Subject: [PATCH 05/30] [WIP] add tree.css and minor updates to leaderlines --- object_database/web/devtools/tree/Tree.js | 2 +- object_database/web/devtools/tree/examples/tree.html | 4 ++-- object_database/web/devtools/tree/tree.css | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 object_database/web/devtools/tree/tree.css diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 80b0fbbed..ae3fc1326 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -86,7 +86,7 @@ class Tree extends HTMLElement { depthDiv.append(childWrapper); nodeData.children.forEach((childData) => { const child = this.setupNode(childData, childWrapper, depth + 1); - // this.updateLeaderLines(node, child); + const l = new LeaderLine(node, child); }) } diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index 5ff6e59cb..831bd0a1c 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -2,7 +2,7 @@ Basic Tree Tests - + @@ -44,7 +44,7 @@ const main = document.getElementById("main"); main.append(tree); tree.setup(data); - tree.setupLeaderLines(data); + // tree.setupLeaderLines(data); }); diff --git a/object_database/web/devtools/tree/tree.css b/object_database/web/devtools/tree/tree.css new file mode 100644 index 000000000..065fc9a45 --- /dev/null +++ b/object_database/web/devtools/tree/tree.css @@ -0,0 +1,11 @@ +.depth { + display: flex; + justify-content: space-around; + border: 2px black solid; +} + +.child-wrapper { + display: flex; + justify-content: space-around; + border: 1px blue solid; +} From de7677bd1cbdb16f5af8bb8d6031035afb06312a Mon Sep 17 00:00:00 2001 From: dkrasner Date: Tue, 15 Nov 2022 14:09:52 +0100 Subject: [PATCH 06/30] v0 of working tree with conneiton lines --- object_database/web/devtools/tree/Tree.js | 81 ++++++++++++++----- object_database/web/devtools/tree/TreeNode.js | 5 +- .../web/devtools/tree/examples/tree.html | 6 +- 3 files changed, 71 insertions(+), 21 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index ae3fc1326..d1c3d5d88 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -2,7 +2,6 @@ * Tree Graph Web component **/ -import LeaderLine from "leader-line"; // Simple grid-based sheet component const templateString = ` @@ -16,13 +15,15 @@ const templateString = ` .depth { display: flex; justify-content: space-around; - border: 2px black solid; + margin-top: 20px; + margin-bottom: 20px; } .child-wrapper { + margin-top: 5px; + margin-bottom: 5px; display: flex; justify-content: space-around; - border: 1px blue solid; } @@ -40,18 +41,19 @@ class Tree extends HTMLElement { // bind methods this.setup = this.setup.bind(this); this.setupNode = this.setupNode.bind(this); - this.setupLeaderLines = this.setupLeaderLines.bind(this); + this.setupPaths = this.setupPaths.bind(this); + this.addSVGPath = this.addSVGPath.bind(this); } connectedCallback(){ if(this.isConnected){ // this.setup(); + } } setup(data){ - // const wrapper = this.shadowRoot.querySelector("#wrapper"); - const wrapper = document.body; + const wrapper = this.shadowRoot.querySelector("#wrapper"); const nodeDepth = document.createElement("div"); nodeDepth.classList.add("depth"); nodeDepth.setAttribute("id", "depth-0"); @@ -60,11 +62,11 @@ class Tree extends HTMLElement { nodeWrapper.classList.add("child-wrapper"); nodeDepth.append(nodeWrapper); this.setupNode(data, nodeWrapper, 1); + this.setupPaths(data); } setupNode(nodeData, wrapperDiv, depth){ if(nodeData){ - console.log(depth); const node = document.createElement("tree-node"); node.name = nodeData.name; node.setAttribute("id", nodeData.id); @@ -72,21 +74,20 @@ class Tree extends HTMLElement { // setup the children in a new node depth if(nodeData.children.length){ // if the corresponding depth has not been added, do so now - let depthDiv = document.body.querySelector(`#depth-${depth}`); + let depthDiv = this.shadowRoot.querySelector(`#depth-${depth}`); if (!depthDiv) { depthDiv = document.createElement("div"); depthDiv.setAttribute("id", `depth-${depth}`); depthDiv.classList.add("depth"); - // const wrapper = this.shadowRoot.querySelector("#wrapper"); - const wrapper = document.body; + const wrapper = this.shadowRoot.querySelector("#wrapper"); wrapper.append(depthDiv); } const childWrapper = document.createElement("div"); childWrapper.classList.add("child-wrapper"); depthDiv.append(childWrapper); nodeData.children.forEach((childData) => { - const child = this.setupNode(childData, childWrapper, depth + 1); - const l = new LeaderLine(node, child); + this.setupNode(childData, childWrapper, depth + 1); + // this.addSVGPath(node, child); }) } @@ -94,15 +95,59 @@ class Tree extends HTMLElement { } } - setupLeaderLines(data){ - if (data && data.children) { - const parent = document.body.querySelector(`tree-node#${data.id}`); - data.children.forEach((cdata) => { - const child = document.body.querySelector(`tree-node#${cdata.id}`); - new LeaderLine(parent, child); + setupPaths(nodeData){ + if (nodeData) { + const parent = this.shadowRoot.querySelector(`#${nodeData.id}`); + nodeData.children.forEach((childData) => { + const child = this.shadowRoot.querySelector(`#${childData.id}`); + this.addSVGPath(parent, child); + this.setupPaths(childData); }) } } + /** + * I add an SVG bezier curve which starts at the bottom middle + * of the startNode and ends at the top middle of the endNode + * @param {element} startNode + * @param {element} endNode + */ + addSVGPath(startNode, endNode){ + const wrapper = this.shadowRoot.querySelector("#wrapper"); + const startRect = startNode.getBoundingClientRect(); + + const startY = startRect.bottom; + const startX = startRect.left + (startRect.width / 2); + const endRect = endNode.getBoundingClientRect(); + const endY = endRect.top; + const endX = endRect.left + (endRect.width / 2); + + const temp = document.createElement("div"); + temp.innerHTML = ``; + wrapper.append(temp.childNodes[0]) + /* + const svg = document.createElement("svg"); + svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); + svg.setAttribute("width", "100"); + svg.setAttribute("height", "100"); + svg.style.left = `${startX}px`; + svg.style.top = `${startY}px`; + svg.style.position = "absolute"; + + const path = document.createElement("path"); + path.setAttribute("d", `M ${startX} ${startY} C 20 20, 40 20, ${endX} ${endY}`); + path.setAttribute("stroke", "black"); + path.setAttribute("fill", "transparent"); + //svg.append(path); + + const line = document.createElement("line"); + line.setAttribute("x1", "100"); + line.setAttribute("y1", "0"); + line.setAttribute("x2", "0"); + line.setAttribute("y2", "100"); + svg.append(line); + wrapper.append(svg); + */ + } get ok(){ return "ok"; diff --git a/object_database/web/devtools/tree/TreeNode.js b/object_database/web/devtools/tree/TreeNode.js index af509386f..76e7973d9 100644 --- a/object_database/web/devtools/tree/TreeNode.js +++ b/object_database/web/devtools/tree/TreeNode.js @@ -7,7 +7,10 @@ const templateString = ` diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index 831bd0a1c..d013207b5 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -12,7 +12,8 @@ const treeMaker = function(depth, maxChildNum){ let rootNode = { name: "root", - id: "roodID", + id: "roodID", + order: -1, children: [] }; rootNode = addChildren(rootNode, 0, depth, maxChildNum); @@ -25,7 +26,8 @@ for (let i = 1; i <= numChildren; i++){ const child = { name: `node_${depth}_${i}`, - id: `node_${depth}_${i}`, + id: "id-" + crypto.randomUUID(), + order: i, children: [] }; node.children.push( From a94d0cff19367f81a280b0d354582e8f7c3916a4 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Tue, 15 Nov 2022 20:23:33 +0100 Subject: [PATCH 07/30] leadrlines use proper js document.createElementNS API adding tests for the tree component --- object_database/web/devtools/tree/Tree.js | 44 ++++++----- .../web/devtools/tree/tests/component.js | 74 +++++++++++++++++-- 2 files changed, 93 insertions(+), 25 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index d1c3d5d88..9d6dc5c73 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -108,6 +108,8 @@ class Tree extends HTMLElement { /** * I add an SVG bezier curve which starts at the bottom middle * of the startNode and ends at the top middle of the endNode + * NODE: svg-type elemnents need to be created using the + * SVG name space, ie document.createElementNS... * @param {element} startNode * @param {element} endNode */ @@ -121,31 +123,33 @@ class Tree extends HTMLElement { const endY = endRect.top; const endX = endRect.left + (endRect.width / 2); - const temp = document.createElement("div"); - temp.innerHTML = ``; - wrapper.append(temp.childNodes[0]) + const svg = document.createElementNS('http://www.w3.org/2000/svg', "svg"); + svg.setAttribute("width", "100%"); + svg.setAttribute("height", "100%"); + svg.setAttribute("data-start-node-id", startNode.id); + svg.setAttribute("data-end-node-id", endNode.id); + svg.style.left = 0; + svg.style.top = 0; + + const line = document.createElementNS('http://www.w3.org/2000/svg', "line"); + line.setAttribute("x1", `${startX}`); + line.setAttribute("y1", `${startY}`); + line.setAttribute("x2", `${endX}`); + line.setAttribute("y2", `${endY}`); + line.setAttribute("stroke", "black"); + svg.append(line); + wrapper.append(svg); /* - const svg = document.createElement("svg"); - svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); - svg.setAttribute("width", "100"); - svg.setAttribute("height", "100"); - svg.style.left = `${startX}px`; - svg.style.top = `${startY}px`; - svg.style.position = "absolute"; - - const path = document.createElement("path"); - path.setAttribute("d", `M ${startX} ${startY} C 20 20, 40 20, ${endX} ${endY}`); + const path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttribute("d", `M ${startX} ${startY} Q 95 10, ${endX} ${endY}`); path.setAttribute("stroke", "black"); path.setAttribute("fill", "transparent"); - //svg.append(path); + svg.append(path); - const line = document.createElement("line"); - line.setAttribute("x1", "100"); - line.setAttribute("y1", "0"); - line.setAttribute("x2", "0"); - line.setAttribute("y2", "100"); - svg.append(line); wrapper.append(svg); + const temp = document.createElement("div"); + temp.innerHTML = ``; + wrapper.append(temp.childNodes[0]) */ } diff --git a/object_database/web/devtools/tree/tests/component.js b/object_database/web/devtools/tree/tests/component.js index a942e54bf..85f344318 100644 --- a/object_database/web/devtools/tree/tests/component.js +++ b/object_database/web/devtools/tree/tests/component.js @@ -3,16 +3,80 @@ **/ import chai from "chai"; +import crypto from 'crypto'; +import { Tree } from '../tree.js'; const assert = chai.assert; -import { Tree } from '../tree.js'; +const treeMaker = function(depth, maxChildNum){ + let rootNode = { + name: "root", + id: "roodID", + order: -1, + children: [] + }; + rootNode = addChildren(rootNode, 0, depth, maxChildNum); + return rootNode; +} + +const addChildren = (node, depth, totalDepth, maxChildNum) => { + const numChildren = Math.ceil(Math.random()* maxChildNum); + if (depth <= totalDepth) { + for (let i = 1; i <= numChildren; i++){ + const child = { + name: `node_${depth}_${i}`, + id: "id-" + crypto.randomUUID(), + order: i, + children: [] + }; + node.children.push( + addChildren(child, depth + 1, totalDepth, maxChildNum) + ); + + } + } + return node; +} + describe("Tree Component Tests", () => { - const tree = document.createElement("my-tree"); - beforeEach(() => { + let tree; + let data + before(() => { + tree = document.createElement("tree-graph"); + data = treeMaker(3, 2); + tree.setup(data); + document.body.append(tree); + }); + it("Assert the tree exists", () => { + assert.exists(tree); + }); + it("Tree has a node for each element in the data", () => { + const nodeCheck = (nodeData) => { + if (nodeData) { + const node = tree.shadowRoot.querySelector(`#${nodeData.id}`); + assert.exists(node); + nodeData.children.forEach((childData) => { + const childNode = tree.shadowRoot.querySelector(`#${childData.id}`); + assert.exists(childNode); + nodeCheck(childData); + }) + } + } + nodeCheck(data); }); - it("Ok", () => { - assert.equal(tree.ok, "ok"); + it("Tree has leaderlines for every parent-child node", () => { + const leaderlineCheck = (nodeData) => { + if (nodeData) { + nodeData.children.forEach((childData) => { + const svg = tree.shadowRoot.querySelector( + `svg[data-start-node-id="${nodeData.id}"][data-end-node-id="${childData.id}"]` + ); + assert.exists(svg); + leaderlineCheck(childData); + }) + } + } + leaderlineCheck(data); }); }) From 64175ad0d5e73f61478b0a125c58954f9eb9020a Mon Sep 17 00:00:00 2001 From: dkrasner Date: Wed, 16 Nov 2022 13:33:09 +0100 Subject: [PATCH 08/30] adding global css, better styling and window resize listener --- object_database/web/devtools/tree/Tree.js | 43 +++++++++++-------- object_database/web/devtools/tree/TreeNode.js | 3 ++ object_database/web/devtools/tree/tree.css | 18 ++++---- 3 files changed, 35 insertions(+), 29 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 9d6dc5c73..aa9bd1009 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -15,8 +15,8 @@ const templateString = ` .depth { display: flex; justify-content: space-around; - margin-top: 20px; - margin-bottom: 20px; + margin-top: 30px; + margin-bottom: 30px; } .child-wrapper { @@ -38,21 +38,27 @@ class Tree extends HTMLElement { this.attachShadow({ mode: "open" }); this.shadowRoot.appendChild(this.template.content.cloneNode(true)); + this.data; + // bind methods this.setup = this.setup.bind(this); + this.clear = this.clear.bind(this); this.setupNode = this.setupNode.bind(this); this.setupPaths = this.setupPaths.bind(this); this.addSVGPath = this.addSVGPath.bind(this); + this.onWindowResize = this.onWindowResize.bind(this); } connectedCallback(){ if(this.isConnected){ - // this.setup(); - + // add event listeners + window.addEventListener("resize", this.onWindowResize); } } setup(data){ + // cache the data; TODO: think through this + this.data = data; const wrapper = this.shadowRoot.querySelector("#wrapper"); const nodeDepth = document.createElement("div"); nodeDepth.classList.add("depth"); @@ -128,7 +134,8 @@ class Tree extends HTMLElement { svg.setAttribute("height", "100%"); svg.setAttribute("data-start-node-id", startNode.id); svg.setAttribute("data-end-node-id", endNode.id); - svg.style.left = 0; + svg.style.position = "absolute"; + svg.style.left = 0; svg.style.top = 0; const line = document.createElementNS('http://www.w3.org/2000/svg', "line"); @@ -136,25 +143,23 @@ class Tree extends HTMLElement { line.setAttribute("y1", `${startY}`); line.setAttribute("x2", `${endX}`); line.setAttribute("y2", `${endY}`); - line.setAttribute("stroke", "black"); + line.setAttribute("stroke", "var(--palette-blue)"); + line.setAttribute("stroke-width", "3px"); svg.append(line); wrapper.append(svg); - /* - const path = document.createElementNS('http://www.w3.org/2000/svg', "path"); - path.setAttribute("d", `M ${startX} ${startY} Q 95 10, ${endX} ${endY}`); - path.setAttribute("stroke", "black"); - path.setAttribute("fill", "transparent"); - svg.append(path); + } - wrapper.append(svg); - const temp = document.createElement("div"); - temp.innerHTML = ``; - wrapper.append(temp.childNodes[0]) - */ + /** + * On a window resize I first clear the tree and then redraw it + **/ + onWindowResize(event){ + this.clear(); + this.setup(this.data); } - get ok(){ - return "ok"; + clear(){ + const wrapper = this.shadowRoot.querySelector("#wrapper"); + wrapper.innerHTML = ""; } } diff --git a/object_database/web/devtools/tree/TreeNode.js b/object_database/web/devtools/tree/TreeNode.js index 76e7973d9..db4073d4a 100644 --- a/object_database/web/devtools/tree/TreeNode.js +++ b/object_database/web/devtools/tree/TreeNode.js @@ -11,6 +11,9 @@ const templateString = ` border: 1px blue solid; margin-right: 10px; margin-left: 10px; + background-color: var(--palette-orange); + padding: 5px; + border-radius: 10px; } diff --git a/object_database/web/devtools/tree/tree.css b/object_database/web/devtools/tree/tree.css index 065fc9a45..30489caca 100644 --- a/object_database/web/devtools/tree/tree.css +++ b/object_database/web/devtools/tree/tree.css @@ -1,11 +1,9 @@ -.depth { - display: flex; - justify-content: space-around; - border: 2px black solid; -} - -.child-wrapper { - display: flex; - justify-content: space-around; - border: 1px blue solid; +:root { + --palette-beige: #FBE8A6; + --palette-blue: #303C6C; + --palette-lightblue: #B4DFE5; + --palette-cyan: #D2FDFF; + --palette-orange: #F4976C; + --palette-black: black; + --palette-white: white; } From 6f75f80c592015cfad49489f9687d0c88e44e4e4 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Wed, 30 Nov 2022 13:10:26 +0100 Subject: [PATCH 09/30] adding subtree zoom in/out and related css updates to tree and node adding a readme --- .gitignore | 1 + object_database/web/devtools/tree/README.md | 7 +++ object_database/web/devtools/tree/Tree.js | 54 +++++++++++++++++-- object_database/web/devtools/tree/TreeNode.js | 5 ++ 4 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 object_database/web/devtools/tree/README.md diff --git a/.gitignore b/.gitignore index d8092b3c2..d8dd43d83 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ object_database/web/content/dist \#* \.#* *~ +.projectile # leading slash means only match at repo-root level /.python-version diff --git a/object_database/web/devtools/tree/README.md b/object_database/web/devtools/tree/README.md new file mode 100644 index 000000000..3dc5e0270 --- /dev/null +++ b/object_database/web/devtools/tree/README.md @@ -0,0 +1,7 @@ +## About ## + +The tree plotting library is a simple way of plotting nested list n-arry tree representations. It is build on two main web-components: the [Tree](./Tree.js) and [TreeNode](./TreeNode.js). The nodes are connected by svg lines and any node of your choosing can be used. IE `Tree` class-element simply expects that there is a `tree-node` element defined in the DOM and that is has some width and height. + +### Installation and Build ### + +In a `nodeenv` run `npm install && npm run build`. To see an example serve the [examples](./examples) directory using `python -m http.server` (or whatever web-server you prefer). diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index aa9bd1009..b8bcf27f0 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -26,6 +26,10 @@ const templateString = ` justify-content: space-around; } +svg { + z-index: -1; +} +
`; @@ -47,6 +51,8 @@ class Tree extends HTMLElement { this.setupPaths = this.setupPaths.bind(this); this.addSVGPath = this.addSVGPath.bind(this); this.onWindowResize = this.onWindowResize.bind(this); + // event handlers + this.onNodeDblclick = this.onNodeDblclick.bind(this); } connectedCallback(){ @@ -56,9 +62,13 @@ class Tree extends HTMLElement { } } - setup(data){ - // cache the data; TODO: think through this - this.data = data; + setup(data, cache=true){ + if (cache){ + // cache the data; TODO: think through this + this.data = data; + } + // mark the first node as the root + data.root = true; const wrapper = this.shadowRoot.querySelector("#wrapper"); const nodeDepth = document.createElement("div"); nodeDepth.classList.add("depth"); @@ -76,9 +86,13 @@ class Tree extends HTMLElement { const node = document.createElement("tree-node"); node.name = nodeData.name; node.setAttribute("id", nodeData.id); + if (nodeData.root) { + node.setAttribute("data-root-node", true); + } wrapperDiv.append(node); + node.addEventListener("dblclick", this.onNodeDblclick); // setup the children in a new node depth - if(nodeData.children.length){ + if (nodeData.children.length) { // if the corresponding depth has not been added, do so now let depthDiv = this.shadowRoot.querySelector(`#depth-${depth}`); if (!depthDiv) { @@ -157,9 +171,39 @@ class Tree extends HTMLElement { this.setup(this.data); } + onNodeDblclick(event){ + this.clear(); + // if clicking on the root node, reset back to cached this.data tree + if (event.target.hasAttribute("data-root-node")) { + this.setup(this.data); + } else { + const id = event.target.id; + const subTree = this.findSubTree(id, this.data); + this.setup(subTree, false); // do not cache this data + } + } + + /** + * I recursively walk the tree to find the corresponding + * node, and when I do I return its subtree + **/ + findSubTree(id, node){ + if(node.id == id) { + return node; + } + let subTree; + node.children.forEach((childNode) => { + const out = this.findSubTree(id, childNode); + if (out) { + subTree = out; + } + }) + return subTree; + } + clear(){ const wrapper = this.shadowRoot.querySelector("#wrapper"); - wrapper.innerHTML = ""; + wrapper.replaceChildren(); } } diff --git a/object_database/web/devtools/tree/TreeNode.js b/object_database/web/devtools/tree/TreeNode.js index db4073d4a..97182fcd3 100644 --- a/object_database/web/devtools/tree/TreeNode.js +++ b/object_database/web/devtools/tree/TreeNode.js @@ -16,6 +16,11 @@ const templateString = ` border-radius: 10px; } +div#wrapper { + max-width: 100px; + text-overflow: ellipsis; + overflow: hidden; +}
From 1344b956458309cef7983a3183a880fb7d6d3bf7 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Wed, 30 Nov 2022 15:42:46 +0100 Subject: [PATCH 10/30] optimizing svg line rendering; adding findParentSubTree for nav --- object_database/web/devtools/tree/Tree.js | 106 +++++++++++++++++----- 1 file changed, 84 insertions(+), 22 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index b8bcf27f0..b7b5df3fa 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -6,12 +6,46 @@ // Simple grid-based sheet component const templateString = `
`; @@ -77,9 +81,6 @@ class Tree extends HTMLElement { this.data; - this.maxDepth = 3; //TODO!!!! - this.attainedDepth; - // bind methods this.setup = this.setup.bind(this); this.clear = this.clear.bind(this); @@ -97,6 +98,7 @@ class Tree extends HTMLElement { // add event listeners window.addEventListener("resize", this.onWindowResize); document.addEventListener("keyup", this.onKeyUp); + this.setAttribute("display-depth", 3); } } @@ -110,8 +112,6 @@ class Tree extends HTMLElement { // cache the data; TODO: think through this this.data = data; } - // TODO depth should be an attribute - this.attainedDepth = 0; const wrapper = this.shadowRoot.querySelector("#wrapper"); wrapper.addEventListener("dblclick", this.onNodeDblclick); const nodeDepth = document.createElement("div"); @@ -130,9 +130,6 @@ class Tree extends HTMLElement { svg.style.left = 0; svg.style.top = 0; wrapper.append(svg); - - // TODO depth should be an attribute - this.attainedDepth = 0; this.setupPaths(svg, data); // fade the wrapper wrapper.classList.remove("animation-fade-out"); @@ -140,8 +137,7 @@ class Tree extends HTMLElement { } setupNode(nodeData, wrapperDiv, depth, root=false){ - if(nodeData && this.attainedDepth <= this.maxDepth){ - this.attainedDepth += 1; // TODO + if(nodeData){ const node = document.createElement("tree-node"); node.name = nodeData.name; node.setAttribute("id", nodeData.id); @@ -152,6 +148,12 @@ class Tree extends HTMLElement { node.addEventListener("dblclick", this.onNodeDblclick); // setup the children in a new node depth if (nodeData.children.length) { + // if we are at the display-depth don't iterate on the children + // simply mark that the nodes have children + if (depth == this.getAttribute("display-depth")){ + node.classList.add("non-final-leaf"); + return; + } // if the corresponding depth has not been added, do so now let depthDiv = this.shadowRoot.querySelector(`#depth-${depth}`); if (!depthDiv) { @@ -174,7 +176,7 @@ class Tree extends HTMLElement { } setupPaths(svg, nodeData){ - if (nodeData && this.attainedDepth < this.maxDepth) { + if (nodeData) { this.attainedDepth += 1; const parent = this.shadowRoot.querySelector(`#${nodeData.id}`); nodeData.children.forEach((childData) => { @@ -206,6 +208,7 @@ class Tree extends HTMLElement { const endX = endRect.left + (endRect.width / 2); + /** const line = document.createElementNS('http://www.w3.org/2000/svg', "line"); line.setAttribute("x1", `${startX}`); line.setAttribute("y1", `${startY}`); @@ -216,6 +219,33 @@ class Tree extends HTMLElement { line.setAttribute("data-start-node-id", startNode.id); line.setAttribute("data-end-node-id", endNode.id); svg.append(line); + **/ + // add a quadratic bezier curve path + let midX; + let controlX; + let controlSlope = 1; + if (endX < startX) { + midX = endX + 0.5 * (startX - endX); + controlX = midX + 0.5 * (startX - midX); + controlSlope *= -1; + } else { + midX = startX + 0.5 * (endX - startX); + controlX = startX + 0.5 * (startX - midX); + } + const midY = startY + 0.5 * (endY - startY); + let controlY = startY + 0.5 * (midY - startY); + controlX *= controlSlope; + controlY *= controlSlope; + const path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttribute("stroke", "var(--palette-blue)"); + path.setAttribute("stroke-width", "3px"); + path.setAttribute("fill", "transparent"); + path.setAttribute("data-start-node-id", startNode.id); + path.setAttribute("data-end-node-id", endNode.id); + const d = `M ${startX} ${startY} Q ${controlX} ${controlY}, ${midX} ${midY} T ${endX} ${endY}`; + path.setAttribute("d", d); + svg.append(path); + } /** @@ -295,6 +325,16 @@ class Tree extends HTMLElement { wrapper.classList.add("animation-fade-out"); wrapper.replaceChildren(); } + + static get observedAttributes() { + return ["display-depth"]; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name == "display-depth") { + this.setup(this.data); + } + } } window.customElements.define("tree-graph", Tree); diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index 7774ff424..8f9096307 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -39,7 +39,7 @@ return node; } - const data = treeMaker(5, 3); + const data = treeMaker(6, 3); console.log(data); document.addEventListener("DOMContentLoaded", () => { const tree = document.createElement("tree-graph"); From 0675f741bb35be45e7de0f7d7134b34981345aff Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 5 Dec 2022 22:16:02 +0100 Subject: [PATCH 14/30] fixing up connector lines in tree --- object_database/web/devtools/tree/Tree.js | 9 +++++---- object_database/web/devtools/tree/examples/tree.html | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 59bbe9e71..bfa4f1539 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -223,17 +223,18 @@ class Tree extends HTMLElement { // add a quadratic bezier curve path let midX; let controlX; + const midY = startY + 0.5 * (endY - startY); + let controlY = startY + 0.5 * (midY - startY); let controlSlope = 1; if (endX < startX) { midX = endX + 0.5 * (startX - endX); controlX = midX + 0.5 * (startX - midX); - controlSlope *= -1; + controlSlope = 1.02; } else { midX = startX + 0.5 * (endX - startX); - controlX = startX + 0.5 * (startX - midX); + controlX = startX + 0.5 * (midX - startX); + controlSlope = 0.98; } - const midY = startY + 0.5 * (endY - startY); - let controlY = startY + 0.5 * (midY - startY); controlX *= controlSlope; controlY *= controlSlope; const path = document.createElementNS('http://www.w3.org/2000/svg', "path"); diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index 8f9096307..8a573c1a3 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -39,7 +39,7 @@ return node; } - const data = treeMaker(6, 3); + const data = treeMaker(5, 4); console.log(data); document.addEventListener("DOMContentLoaded", () => { const tree = document.createElement("tree-graph"); From e784196b373b72eaab972d819f82cedf744c3674 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Sat, 31 Dec 2022 14:41:58 +0100 Subject: [PATCH 15/30] updating id key for messages from CellHandler to devtools --- object_database/web/content/src/CellHandler.js | 2 +- object_database/web/devtools/background.js | 4 ++-- object_database/web/devtools/js/cell_panel.js | 2 ++ object_database/web/devtools/tree/examples/tree.html | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index a1f8b7cd5..2e2fcf930 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -520,7 +520,7 @@ class CellHandler { const buildTree = (cell) => { return { name: cell.constructor.name, - identity: cell.identity, + id: cell.identity, children: mapChildren(cell.namedChildren) } } diff --git a/object_database/web/devtools/background.js b/object_database/web/devtools/background.js index b97fc84c0..05a81972a 100644 --- a/object_database/web/devtools/background.js +++ b/object_database/web/devtools/background.js @@ -29,7 +29,7 @@ function connected(port) { // from the target window we forward this message to the panels // if the connection is not alive we log this in the devtools's // debugger console - notifyDevtoolsPabel(msg.data); + notifyDevtoolsPanel(msg.data); }); } // notify if the port has disconnected @@ -42,7 +42,7 @@ function connected(port) { chrome.runtime.onConnect.addListener(connected); -function notifyDevtoolsPabel(msg){ +function notifyDevtoolsPanel(msg){ if (portFromPanel){ portFromPanel.postMessage(msg); } else { diff --git a/object_database/web/devtools/js/cell_panel.js b/object_database/web/devtools/js/cell_panel.js index ff6810239..29411b46f 100644 --- a/object_database/web/devtools/js/cell_panel.js +++ b/object_database/web/devtools/js/cell_panel.js @@ -54,6 +54,8 @@ const cellsTreeDisplay = (cells) => { const tree = document.createElement("tree-graph"); const main = document.getElementById("main"); main.append(tree); + // displaying tree + console.log(cells); tree.setup(cells); } diff --git a/object_database/web/devtools/tree/examples/tree.html b/object_database/web/devtools/tree/examples/tree.html index 8a573c1a3..7d7e0a7c0 100644 --- a/object_database/web/devtools/tree/examples/tree.html +++ b/object_database/web/devtools/tree/examples/tree.html @@ -39,7 +39,7 @@ return node; } - const data = treeMaker(5, 4); + const data = treeMaker(10, 5); console.log(data); document.addEventListener("DOMContentLoaded", () => { const tree = document.createElement("tree-graph"); From 06620c1af30918933605559564badc3a6c1776f5 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Sat, 31 Dec 2022 18:31:02 +0100 Subject: [PATCH 16/30] adding handling of DOM invalid cell/node ids prepending "node-" to each DOM element id keeping the original id in "data-original-id" attribute --- object_database/web/devtools/tree/Tree.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index bfa4f1539..3967471b4 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -140,7 +140,10 @@ class Tree extends HTMLElement { if(nodeData){ const node = document.createElement("tree-node"); node.name = nodeData.name; - node.setAttribute("id", nodeData.id); + // since the id might be a non-valid DOM element id such as int + // we prepend "node-" and keep the original id in a data attribute + node.setAttribute("id", `node-${nodeData.id}`); + node.setAttribute("data-original-id", nodeData.id); if (root) { node.setAttribute("data-root-node", true); } @@ -178,9 +181,9 @@ class Tree extends HTMLElement { setupPaths(svg, nodeData){ if (nodeData) { this.attainedDepth += 1; - const parent = this.shadowRoot.querySelector(`#${nodeData.id}`); + const parent = this.shadowRoot.querySelector(`#node-${nodeData.id}`); nodeData.children.forEach((childData) => { - const child = this.shadowRoot.querySelector(`#${childData.id}`); + const child = this.shadowRoot.querySelector(`#node-${childData.id}`); if (parent && child) { this.addSVGPath(svg, parent, child); this.setupPaths(svg, childData); @@ -265,7 +268,7 @@ class Tree extends HTMLElement { const subTree = this.findParentSubTree(id, this.data); this.setup(subTree, false); // do not cache this data } else { - const id = event.target.id; + const id = event.target.getAttribute("data-original-id"); const subTree = this.findSubTree(id, this.data); this.setup(subTree, false); // do not cache this data } @@ -275,7 +278,8 @@ class Tree extends HTMLElement { if (event.key == "ArrowUp") { // re-render the tree from the parent of the current root node const rootNode = this.shadowRoot.querySelector("tree-node[data-root-node]"); - const subTree = this.findParentSubTree(rootNode.id, this.data); + const rootNodeId = rootNode.getAttribute("data-original-id"); + const subTree = this.findParentSubTree(rootNodeId, this.data); this.setup(subTree, false); // do not cache this data } } From 17c8426ca6c0c999e231bfc4675807fcba3cc846 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 2 Jan 2023 12:38:43 +0100 Subject: [PATCH 17/30] addiung non-starting-root node handling fixing up navigation bugs --- object_database/web/devtools/tree/Tree.js | 33 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 3967471b4..adb1a5807 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -64,7 +64,20 @@ svg { } .non-final-leaf { - background-color: var(--palette-lightblue) + background-color: var(--palette-lightblue); + cursor: pointer; + pointer-events: auto; +} + +.non-starting-root { + background-color: var(--palette-cyan); + cursor: pointer; + pointer-events: auto; + +} + +tree-node { + pointer-events: none; } @@ -113,7 +126,7 @@ class Tree extends HTMLElement { this.data = data; } const wrapper = this.shadowRoot.querySelector("#wrapper"); - wrapper.addEventListener("dblclick", this.onNodeDblclick); + // wrapper.addEventListener("dblclick", this.onNodeDblclick); const nodeDepth = document.createElement("div"); nodeDepth.classList.add("depth"); nodeDepth.setAttribute("id", "depth-0"); @@ -146,15 +159,22 @@ class Tree extends HTMLElement { node.setAttribute("data-original-id", nodeData.id); if (root) { node.setAttribute("data-root-node", true); + // if the node is not the root of entire tree + // mark it as such and add the dblclick event listener + // to allow up the tree navigation + if (nodeData.id !== this.data.id) { + node.classList.add("non-starting-root"); + node.addEventListener("dblclick", this.onNodeDblclick); + } } wrapperDiv.append(node); - node.addEventListener("dblclick", this.onNodeDblclick); // setup the children in a new node depth if (nodeData.children.length) { // if we are at the display-depth don't iterate on the children // simply mark that the nodes have children if (depth == this.getAttribute("display-depth")){ node.classList.add("non-final-leaf"); + node.addEventListener("dblclick", this.onNodeDblclick); return; } // if the corresponding depth has not been added, do so now @@ -264,7 +284,7 @@ class Tree extends HTMLElement { if (event.target.nodeName != "TREE-NODE") { this.setup(this.data); } else if (event.target.hasAttribute("data-root-node")) { - const id = event.target.id; + const id = event.target.getAttribute("data-original-id"); const subTree = this.findParentSubTree(id, this.data); this.setup(subTree, false); // do not cache this data } else { @@ -275,12 +295,17 @@ class Tree extends HTMLElement { } onKeyUp(event){ + event.preventDefault(); + event.stopPropagation(); if (event.key == "ArrowUp") { // re-render the tree from the parent of the current root node const rootNode = this.shadowRoot.querySelector("tree-node[data-root-node]"); const rootNodeId = rootNode.getAttribute("data-original-id"); const subTree = this.findParentSubTree(rootNodeId, this.data); this.setup(subTree, false); // do not cache this data + } else if (event.key == "Esc") { + // re-render from the starting root node + this.setup(this.data, false); } } /** From 098b492595e9883d90c0f2a462e715227a4984a4 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 2 Jan 2023 14:52:25 +0100 Subject: [PATCH 18/30] fixing up vertical paths in tree and adding on hover highlight when tree nodes are (almost) vertically aligned, use a basic svg path line instead of a bezier curve adding hover (mouseover mouseleave) handling on the target window object and corresponding no-op methods to tree setup these methods --- .../web/content/src/CellHandler.js | 29 +++++++ object_database/web/devtools/js/cell_panel.js | 17 +++- object_database/web/devtools/tree/Tree.js | 81 ++++++++++--------- 3 files changed, 90 insertions(+), 37 deletions(-) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index 2e2fcf930..60df1f861 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -62,6 +62,7 @@ class CellHandler { // devtools related messages this.sendMessageToDevtools = this.sendMessageToDevtools.bind(this); this.updateDevtools = this.updateDevtools.bind(this); + this.setupDevtools = this.setupDevtools.bind(this); } tearDownAllLiveCells() { @@ -507,6 +508,34 @@ class CellHandler { } } + /** + * Setup utilities for devtools to call using chrome.devtools.inspectedWindow.eval() + * TODO: potentially this should pass via the content-scripts + */ + setupDevtools(){ + const = "cells-devtools-overlay"; + window.addDevtoolsHighlight = (id) => { + const cell = document.querySelector(`[data-cell-id="${id}"}]`); + const rect = cell.getBoundingClientRect(); + const overlay = document.createElement("div"); + overlay.style.position = "absolute"; + overlay.style.backgroundColor = "red"; + overlay.style.opacity = "0.5"; + overlay.style.left = rect.left +"px"; + overlay.style.top = rect.top + "px"; + overlay.style.height = rect.height + "px"; + overlay.style.width = rect.width + "px"; + overlay.setAttribute("id", overlayId); + document.body.append(overlay); + } + + window.removeDevtoolsHighlight = () => { + const overlay = document.getElementById(overlayId); + if (overlay) { + overlay.remove(); + } + } + } sendMessageToDevtools(msg){ // TODO perhaps this should be run by a worker msg.type = "cells_devtools"; diff --git a/object_database/web/devtools/js/cell_panel.js b/object_database/web/devtools/js/cell_panel.js index 29411b46f..5af6db10b 100644 --- a/object_database/web/devtools/js/cell_panel.js +++ b/object_database/web/devtools/js/cell_panel.js @@ -54,8 +54,23 @@ const cellsTreeDisplay = (cells) => { const tree = document.createElement("tree-graph"); const main = document.getElementById("main"); main.append(tree); + // setup node hover event listeners + // NOTE: these are defined on window by CellHandler + tree.onNodeMouseover = (event) => { + // highlight the corresponding element in the target window + const id = event.target.getAttribute("data-original-id"); + chrome.devtools.inspectedWindow.eval( + `window.addDevtoolsHighlight(${id})` + ); + } + tree.onNodeMouseleave = (event) => { + // un-highlight the corresponding element in the target window + const id = event.target.getAttribute("data-original-id"); + chrome.devtools.inspectedWindow.eval( + `window.removeDevtoolsHighlight()` + ); + } // displaying tree - console.log(cells); tree.setup(cells); } diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index adb1a5807..aac45f915 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -103,6 +103,8 @@ class Tree extends HTMLElement { this.onWindowResize = this.onWindowResize.bind(this); // event handlers this.onNodeDblclick = this.onNodeDblclick.bind(this); + this.onNodeMouseover = this.onNodeMouseover.bind(this); + this.onNodeMouseleave = this.onNodeMouseleave.bind(this); this.onKeyUp = this.onKeyUp.bind(this); } @@ -157,6 +159,9 @@ class Tree extends HTMLElement { // we prepend "node-" and keep the original id in a data attribute node.setAttribute("id", `node-${nodeData.id}`); node.setAttribute("data-original-id", nodeData.id); + // add mouseover and leave event handlers (implemented outside of tree) + node.addEventListener("mouseover", this.onNodeMouseover); + node.addEventListener("mouseleave", this.onNodeMouseleave); if (root) { node.setAttribute("data-root-node", true); // if the node is not the root of entire tree @@ -222,6 +227,15 @@ class Tree extends HTMLElement { * @param {element} endNode */ addSVGPath(svg, startNode, endNode){ + // generic svg path attributes setup here + const path = document.createElementNS('http://www.w3.org/2000/svg', "path"); + path.setAttribute("stroke", "var(--palette-blue)"); + path.setAttribute("stroke-width", "3px"); + path.setAttribute("fill", "transparent"); + path.setAttribute("data-start-node-id", startNode.id); + path.setAttribute("data-end-node-id", endNode.id); + + // calculate position here const startRect = startNode.getBoundingClientRect(); const startY = startRect.bottom; @@ -230,46 +244,33 @@ class Tree extends HTMLElement { const endY = endRect.top; const endX = endRect.left + (endRect.width / 2); - - /** - const line = document.createElementNS('http://www.w3.org/2000/svg', "line"); - line.setAttribute("x1", `${startX}`); - line.setAttribute("y1", `${startY}`); - line.setAttribute("x2", `${endX}`); - line.setAttribute("y2", `${endY}`); - line.setAttribute("stroke", "var(--palette-blue)"); - line.setAttribute("stroke-width", "3px"); - line.setAttribute("data-start-node-id", startNode.id); - line.setAttribute("data-end-node-id", endNode.id); - svg.append(line); - **/ - // add a quadratic bezier curve path - let midX; - let controlX; - const midY = startY + 0.5 * (endY - startY); - let controlY = startY + 0.5 * (midY - startY); - let controlSlope = 1; - if (endX < startX) { - midX = endX + 0.5 * (startX - endX); - controlX = midX + 0.5 * (startX - midX); - controlSlope = 1.02; + let d; // this is the path data + if ( Math.abs(endX - startX) < 5) { + // draw a straight vertical line, ie the two nodes + // are on top of each other + d = `M ${startX} ${startY} L ${endX} ${endY}`; } else { - midX = startX + 0.5 * (endX - startX); - controlX = startX + 0.5 * (midX - startX); - controlSlope = 0.98; + // add a quadratic bezier curve path + let midX; + let controlX; + const midY = startY + 0.5 * (endY - startY); + let controlY = startY + 0.5 * (midY - startY); + let controlSlope = 1; + if (endX < startX) { + midX = endX + 0.5 * (startX - endX); + controlX = midX + 0.5 * (startX - midX); + controlSlope = 1.02; + } else { + midX = startX + 0.5 * (endX - startX); + controlX = startX + 0.5 * (midX - startX); + controlSlope = 0.98; + } + controlX *= controlSlope; + controlY *= controlSlope; + d = `M ${startX} ${startY} Q ${controlX} ${controlY}, ${midX} ${midY} T ${endX} ${endY}`; } - controlX *= controlSlope; - controlY *= controlSlope; - const path = document.createElementNS('http://www.w3.org/2000/svg', "path"); - path.setAttribute("stroke", "var(--palette-blue)"); - path.setAttribute("stroke-width", "3px"); - path.setAttribute("fill", "transparent"); - path.setAttribute("data-start-node-id", startNode.id); - path.setAttribute("data-end-node-id", endNode.id); - const d = `M ${startX} ${startY} Q ${controlX} ${controlY}, ${midX} ${midY} T ${endX} ${endY}`; path.setAttribute("d", d); svg.append(path); - } /** @@ -308,6 +309,14 @@ class Tree extends HTMLElement { this.setup(this.data, false); } } + + onNodeMouseover(event) { + // no-op + } + + onNodeMouseleave(event) { + // no-op + } /** * I recursively walk the tree to find the corresponding * node, and when I do I return its subtree From 1d35ea0fd79c108f84470b10ffcf2480e99cf4a0 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 2 Jan 2023 14:57:26 +0100 Subject: [PATCH 19/30] adding forgotten setupDevtools() in CellHandler --- object_database/web/content/src/CellHandler.js | 1 + 1 file changed, 1 insertion(+) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index 60df1f861..f4edb7c89 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -132,6 +132,7 @@ class CellHandler { ]) ]) ); + this.setupDevtools(); this.sendMessageToDevtools({ status: "initial load" }); From 580767f94a2c3955dd8a4ea2f6a53963558e0f68 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 2 Jan 2023 14:59:12 +0100 Subject: [PATCH 20/30] typo fix --- object_database/web/content/src/CellHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index f4edb7c89..b9a39ac02 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -514,7 +514,7 @@ class CellHandler { * TODO: potentially this should pass via the content-scripts */ setupDevtools(){ - const = "cells-devtools-overlay"; + const overlayId = "cells-devtools-overlay"; window.addDevtoolsHighlight = (id) => { const cell = document.querySelector(`[data-cell-id="${id}"}]`); const rect = cell.getBoundingClientRect(); From 9006db80847aa3fb0219525715189ee0fa0f22d0 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 2 Jan 2023 15:07:23 +0100 Subject: [PATCH 21/30] hightlight query selector fix --- object_database/web/content/src/CellHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index b9a39ac02..08549733d 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -516,7 +516,7 @@ class CellHandler { setupDevtools(){ const overlayId = "cells-devtools-overlay"; window.addDevtoolsHighlight = (id) => { - const cell = document.querySelector(`[data-cell-id="${id}"}]`); + const cell = document.querySelector(`[data-cell-id="${id}"]`); const rect = cell.getBoundingClientRect(); const overlay = document.createElement("div"); overlay.style.position = "absolute"; From 6db1ea7b89479480aa6836c678bb67450184c842 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 2 Jan 2023 15:11:34 +0100 Subject: [PATCH 22/30] adding better overlay cleanup for devtools --- object_database/web/content/src/CellHandler.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index 08549733d..01aa1ff4c 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -531,10 +531,8 @@ class CellHandler { } window.removeDevtoolsHighlight = () => { - const overlay = document.getElementById(overlayId); - if (overlay) { - overlay.remove(); - } + const overlays = document.querySelectorAll(`#${overlayId}`); + overlays.forEach((el) => el.remove()); } } sendMessageToDevtools(msg){ From 35b3cb1b9001cb20dc1fe167ff7dd5d7d6be2114 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 2 Jan 2023 15:21:15 +0100 Subject: [PATCH 23/30] adding better hover devtools highlighting and related changes --- object_database/web/devtools/tree/Tree.js | 26 +++++++++-------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index aac45f915..5f2c29fe9 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -66,20 +66,14 @@ svg { .non-final-leaf { background-color: var(--palette-lightblue); cursor: pointer; - pointer-events: auto; } .non-starting-root { background-color: var(--palette-cyan); cursor: pointer; - pointer-events: auto; } -tree-node { - pointer-events: none; -} -
`; @@ -282,16 +276,16 @@ class Tree extends HTMLElement { onNodeDblclick(event){ // if clicking on the root node, reset back to cached this.data tree - if (event.target.nodeName != "TREE-NODE") { - this.setup(this.data); - } else if (event.target.hasAttribute("data-root-node")) { - const id = event.target.getAttribute("data-original-id"); - const subTree = this.findParentSubTree(id, this.data); - this.setup(subTree, false); // do not cache this data - } else { - const id = event.target.getAttribute("data-original-id"); - const subTree = this.findSubTree(id, this.data); - this.setup(subTree, false); // do not cache this data + if (event.target.nodeName == "TREE-NODE") { + if (event.target.hasAttribute("data-root-node")) { + const id = event.target.getAttribute("data-original-id"); + const subTree = this.findParentSubTree(id, this.data); + this.setup(subTree, false); // do not cache this data + } else { + const id = event.target.getAttribute("data-original-id"); + const subTree = this.findSubTree(id, this.data); + this.setup(subTree, false); // do not cache this data + } } } From d8871ce3004069a57f2e0ba0a0f1cafaa4a4ced2 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Tue, 24 Jan 2023 16:08:41 +0100 Subject: [PATCH 24/30] filtering out non cells from messager to devtools in CellHandler --- .../web/content/src/CellHandler.js | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index 01aa1ff4c..abb267a3b 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -546,10 +546,12 @@ class CellHandler { **/ updateDevtools(){ const buildTree = (cell) => { - return { - name: cell.constructor.name, - id: cell.identity, - children: mapChildren(cell.namedChildren) + if (cell.isCell) { + return { + name: cell.constructor.name, + id: cell.identity, + children: mapChildren(cell.namedChildren) + } } } // NOTE: sometimes a named child is a cell, sometimes it's an array of cells @@ -561,10 +563,16 @@ class CellHandler { if (child){ if (child instanceof Array){ child.forEach((subchild) => { - children.push(buildTree(subchild)); + const subTree = buildTree(subchild); + if (subTree){ + children.push(buildTree(subchild)); + } }) } - children.push(buildTree(child)); + const subTree = buildTree(child); + if (subTree) { + children.push(buildTree(child)); + } } }) } From f04608c14d4c00422e8c4086690f7bf6aeb02d2c Mon Sep 17 00:00:00 2001 From: dkrasner Date: Wed, 25 Jan 2023 12:12:19 +0100 Subject: [PATCH 25/30] removing old d3 tree js file --- object_database/web/devtools/js/tree.js | 263 ------------------------ 1 file changed, 263 deletions(-) delete mode 100644 object_database/web/devtools/js/tree.js diff --git a/object_database/web/devtools/js/tree.js b/object_database/web/devtools/js/tree.js deleted file mode 100644 index a8ac2594f..000000000 --- a/object_database/web/devtools/js/tree.js +++ /dev/null @@ -1,263 +0,0 @@ - -class CellsTree extends Object { - constructor(data) { - super(); - - this.data = data; - - // basic view settings - // TODO: maybe these should be passed as params to the constructor - this.duration = 750; - this.rectW = 60; - this.rectH=30; - - // bound methods - this.setupTree = this.setupTree.bind(this); - this.update = this.update.bind(this); - this.collapse = this.collapse.bind(this); - this.redraw = this.redraw.bind(this); - this.onDblclick = this.onDblclick.bind(this); - this.onClick = this.onClick.bind(this); - this.onMouseover = this.onMouseover.bind(this); - this.onMouseleave = this.onMouseleave.bind(this); - } - - setupTree(){ - this.tree = d3.layout.tree().nodeSize([70, 40]); - const zm = d3.behavior.zoom() - .scaleExtent([1,3]) - .on("zoom", this.redraw) - // the main svg container for the tree - this.svg = d3.select("#main").append("svg") - .attr("width", "100%") - .attr("height", 1000) - .call(zm) - .append("g") - .attr( - "transform", - `translate(${350},${20})` - ); - //necessary so that zoom knows where to zoom and unzoom from - zm.translate([350, 20]); - - this.data.x0 = 0; - this.data.y0 = 700 / 2; - - // collapse all children for now - // TODO do we want this? - this.data.children.forEach(this.collapse); - - // build the tree - this.update(this.data); - } - - update(source) { - // need to define these in method scope since a number of the - // callbacks are not bound to the class. TODO - let id = 0; - const rectW = this.rectW; - const rectH = this.rectH; - - const diagonal = d3.svg.diagonal() - .projection(function (d) { - return [d.x + rectW / 2, d.y + rectH / 2]; - }); - // Compute the new tree layout. - const nodes = this.tree.nodes(this.data).reverse(); - const links = this.tree.links(nodes); - - // Normalize for fixed-depth. - nodes.forEach(function (d) { - d.y = d.depth * 180; - }); - - // Update the nodes… - const node = this.svg.selectAll("g.node") - .data(nodes, function (d) { - return d.id = ++id; - } - ); - - // Enter any new nodes at the parent's previous position. - const nodeEnter = node.enter().append("g") - .attr("class", "node") - .attr("transform", function (d) { - return `translate(${source.x0},${source.y0})`; - }) - .on("dblclick", this.onDblclick) - .on("click", this.onClick) - .on("mouseover", this.onMouseover) - .on("mouseleave", this.onMouseleave); - - - nodeEnter.append("rect") - .attr("width", this.rectW) - .attr("height", this.rectH) - .attr("stroke", "black") - .attr("stroke-width", 1) - .style("fill", function (d) { - return d._children ? "lightsteelblue" : "#fff"; - } - ); - - nodeEnter.append("text") - .attr("x", this.rectW / 2) - .attr("y", this.rectH / 2) - .attr("dy", ".35em") - .attr("text-anchor", "middle") - .text(function (d) { - // limit the name to 7 chars since text-overflow - // doesn't seem to work here - let name = d.name; - if (name.length > 7){ - name = name.slice(0, 7) + "..."; - } - return name; - } - ); - - // Transition nodes to their new position. - const nodeUpdate = node.transition() - .duration(this.duration) - .attr("transform", function (d) { - return `translate(${d.x},${d.y})`; - } - ); - - nodeUpdate.select("rect") - .attr("width", this.rectW) - .attr("height", this.rectH) - .attr("stroke", "black") - .attr("stroke-width", 1) - .style("fill", function (d) { - return d._children ? "lightsteelblue" : "#fff"; - } - ); - - nodeUpdate.select("text") - .style("fill-opacity", 1); - - // Transition exiting nodes to the parent's new position. - const nodeExit = node.exit().transition() - .duration(this.duration) - .attr("transform", function (d) { - return `translate(${source.x},${source.y})`; - } - ).remove(); - - nodeExit.select("rect") - .attr("width", this.rectW) - .attr("height", this.rectH) - .attr("stroke", "black") - .attr("stroke-width", 1); - - nodeExit.select("text"); - - // Update the links… - const link = this.svg.selectAll("path.link") - .data(links, function (d) { - return d.target.id; - } - ); - - // Enter any new links at the parent's previous position. - link.enter().insert("path", "g") - .attr("class", "link") - .attr("x", this.rectW / 2) - .attr("y", this.rectH / 2) - .attr("d", function (d) { - const o = { - x: source.x0, - y: source.y0 - }; - return diagonal({ - source: o, - target: o - }); - }); - - // Transition links to their new position. - link.transition() - .duration(this.duration) - .attr("d", diagonal); - - // Transition exiting nodes to the parent's new position. - link.exit().transition() - .duration(this.duration) - .attr("d", function (d) { - const o = { - x: source.x, - y: source.y - }; - return diagonal({ - source: o, - target: o - }); - }) - .remove(); - - // Stash the old positions for transition. - nodes.forEach(function (d) { - d.x0 = d.x; - d.y0 = d.y; - }); - } - - collapse(d) { - if (d.children) { - d._children = d.children.slice(); - d._children.forEach(this.collapse); - d.children = null; - } - } - - - // Toggle children on click. - onDblclick(d) { - // prevent the default zooming in/out behavior - d3.event.stopPropagation(); - if (d.children) { - d._children = d.children.slice(); - d.children = null; - } else { - d.children = d._children.slice(); - d._children = null; - } - this.update(d); - } - - onClick(event){ - // update the cell data - // probably should be handled by a different class - const infoDiv = document.getElementById("cell-info") - infoDiv.textContent = `${event.name} (id: ${event.identity})`; - } - - onMouseover(event){ - // highlighte the corresponding element in the target window - chrome.devtools.inspectedWindow.eval( - `document.querySelector("[data-cell-id='${event.identity}']").style.backgroundColor = 'lightblue'` - ); - } - - onMouseleave(event){ - console.log(event); - // highlighte the corresponding element in the target window - chrome.devtools.inspectedWindow.eval( - `document.querySelector("[data-cell-id='${event.identity}']").style.backgroundColor= 'initial'` - ); - } - //Redraw for zoom - redraw() { - this.svg.attr("transform", - `translate(${d3.event.translate})` - +`scale(${d3.event.scale})` - ); - } -} - - -export { - CellsTree, - CellsTree as default -} From 9b0227b3e910bdb52b5799d9ad7d412d1eb83cbb Mon Sep 17 00:00:00 2001 From: dkrasner Date: Wed, 25 Jan 2023 15:16:06 +0100 Subject: [PATCH 26/30] adding node click and customization handlers click handler display node data in the info panel customization performs additional highlighting --- object_database/web/devtools/js/cell_panel.js | 25 +++++++++++++++++++ object_database/web/devtools/tree/Tree.js | 13 +++++++++- object_database/web/devtools/tree/TreeNode.js | 5 ++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/object_database/web/devtools/js/cell_panel.js b/object_database/web/devtools/js/cell_panel.js index 5af6db10b..2b75ae0c6 100644 --- a/object_database/web/devtools/js/cell_panel.js +++ b/object_database/web/devtools/js/cell_panel.js @@ -44,6 +44,18 @@ const reconnectingDisplay = () => { main.textContent = "Reconnecting: no cells loaded"; } +const updateInfoPanel = (node) => { + const infoPanel = document.getElementById("cell-info"); + const id = node.getAttribute("data-original-id"); + let info = `cell-id: ${id}`; + const tree = document.querySelector("tree-graph"); + const parentSubtree = tree.findParentSubTree(id, tree.data); + if (parentSubtree.name.match("Subscribed")) { + info = `${info}\nsubscribed`; + } + infoPanel.innerText = info; +} + const cellsTreeDisplay = (cells) => { clearDisplay(); // init and run @@ -54,6 +66,7 @@ const cellsTreeDisplay = (cells) => { const tree = document.createElement("tree-graph"); const main = document.getElementById("main"); main.append(tree); + tree.setAttribute("display-depth", 4); // setup node hover event listeners // NOTE: these are defined on window by CellHandler tree.onNodeMouseover = (event) => { @@ -70,6 +83,18 @@ const cellsTreeDisplay = (cells) => { `window.removeDevtoolsHighlight()` ); } + + tree.onNodeClick = (event) => { + updateInfoPanel(event.target); + } + tree.customizeNode = (node) => { + if (node.name == "Subscribed") { + node.style.backgroundColor = "var(--palette-beige)"; + } + // customize a tooltip here + const id = node.getAttribute("data-original-id"); + node.title = `cell-id: ${id}`; + } // displaying tree tree.setup(cells); } diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 5f2c29fe9..8cd6b7ec9 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -71,7 +71,6 @@ svg { .non-starting-root { background-color: var(--palette-cyan); cursor: pointer; - } @@ -95,10 +94,12 @@ class Tree extends HTMLElement { this.setupPaths = this.setupPaths.bind(this); this.addSVGPath = this.addSVGPath.bind(this); this.onWindowResize = this.onWindowResize.bind(this); + this.customizeNode = this.customizeNode; // event handlers this.onNodeDblclick = this.onNodeDblclick.bind(this); this.onNodeMouseover = this.onNodeMouseover.bind(this); this.onNodeMouseleave = this.onNodeMouseleave.bind(this); + this.onNodeClick = this.onNodeClick.bind(this); this.onKeyUp = this.onKeyUp.bind(this); } @@ -115,6 +116,10 @@ class Tree extends HTMLElement { document.removeEventListener("key", this.onKeyUp); } + customizeNode(node){ + //Noop, to be used by consumers + } + setup(data, cache=true){ this.clear(); if (cache){ @@ -156,6 +161,7 @@ class Tree extends HTMLElement { // add mouseover and leave event handlers (implemented outside of tree) node.addEventListener("mouseover", this.onNodeMouseover); node.addEventListener("mouseleave", this.onNodeMouseleave); + node.addEventListener("click", this.onNodeClick); if (root) { node.setAttribute("data-root-node", true); // if the node is not the root of entire tree @@ -166,6 +172,7 @@ class Tree extends HTMLElement { node.addEventListener("dblclick", this.onNodeDblclick); } } + this.customizeNode(node); wrapperDiv.append(node); // setup the children in a new node depth if (nodeData.children.length) { @@ -311,6 +318,10 @@ class Tree extends HTMLElement { onNodeMouseleave(event) { // no-op } + + onNodeClick(event) { + // no-op + } /** * I recursively walk the tree to find the corresponding * node, and when I do I return its subtree diff --git a/object_database/web/devtools/tree/TreeNode.js b/object_database/web/devtools/tree/TreeNode.js index 97182fcd3..941c466c1 100644 --- a/object_database/web/devtools/tree/TreeNode.js +++ b/object_database/web/devtools/tree/TreeNode.js @@ -30,6 +30,7 @@ div#wrapper { class TreeNode extends HTMLElement { constructor() { super(); + this.template = document.createElement("template"); this.template.innerHTML = templateString; this.attachShadow({ mode: "open" }); @@ -39,7 +40,11 @@ class TreeNode extends HTMLElement { set name(s){ const name = this.shadowRoot.querySelector("#name"); name.innerText = s; + } + get name() { + const name = this.shadowRoot.querySelector("#name"); + return name.innerText; } } From 4de7667c084bd3d2b6f982eace1edfc56fe5d4b7 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Mon, 1 May 2023 16:20:06 +0200 Subject: [PATCH 27/30] improving the bezier curves connecting trees --- object_database/web/devtools/tree/Tree.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/object_database/web/devtools/tree/Tree.js b/object_database/web/devtools/tree/Tree.js index 8cd6b7ec9..d30c7419f 100644 --- a/object_database/web/devtools/tree/Tree.js +++ b/object_database/web/devtools/tree/Tree.js @@ -246,14 +246,29 @@ class Tree extends HTMLElement { const endX = endRect.left + (endRect.width / 2); let d; // this is the path data - if ( Math.abs(endX - startX) < 5) { + if ( Math.abs(endX - startX) < 100) { // draw a straight vertical line, ie the two nodes // are on top of each other d = `M ${startX} ${startY} L ${endX} ${endY}`; } else { // add a quadratic bezier curve path let midX; + const midY = startY + 0.5 * (endY - startY); let controlX; + let controlY; + if (endX < startX) { + midX = endX + 0.5 * (startX - endX); + controlX = midX + 0.5 * (startX - midX); + } else { + midX = startX + 0.5 * (endX - startX); + controlX = startX + 0.5 * (midX - startX); + } + // the controlLine is perpendicular to the line which connects + // the start to the end points + const clSlope = -1 * (endY - startY) / (endX - startX); + const clYInt = midY - clSlope * midX; + controlY = clSlope * controlX + clYInt; + /* const midY = startY + 0.5 * (endY - startY); let controlY = startY + 0.5 * (midY - startY); let controlSlope = 1; @@ -268,6 +283,9 @@ class Tree extends HTMLElement { } controlX *= controlSlope; controlY *= controlSlope; + */ + console.log("new controls") + d = `M ${startX} ${startY} Q ${controlX} ${controlY}, ${midX} ${midY} T ${endX} ${endY}`; } path.setAttribute("d", d); From a6ee429e9f8c60438780506039471d37c5d9f3cc Mon Sep 17 00:00:00 2001 From: dkrasner Date: Wed, 3 May 2023 16:56:38 +0200 Subject: [PATCH 28/30] updating cell info panel --- object_database/web/content/src/CellHandler.js | 2 +- object_database/web/devtools/js/cell_panel.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/object_database/web/content/src/CellHandler.js b/object_database/web/content/src/CellHandler.js index abb267a3b..2cbed4f53 100644 --- a/object_database/web/content/src/CellHandler.js +++ b/object_database/web/content/src/CellHandler.js @@ -520,7 +520,7 @@ class CellHandler { const rect = cell.getBoundingClientRect(); const overlay = document.createElement("div"); overlay.style.position = "absolute"; - overlay.style.backgroundColor = "red"; + overlay.style.backgroundColor = "#cec848"; overlay.style.opacity = "0.5"; overlay.style.left = rect.left +"px"; overlay.style.top = rect.top + "px"; diff --git a/object_database/web/devtools/js/cell_panel.js b/object_database/web/devtools/js/cell_panel.js index 2b75ae0c6..21cb09a96 100644 --- a/object_database/web/devtools/js/cell_panel.js +++ b/object_database/web/devtools/js/cell_panel.js @@ -53,6 +53,14 @@ const updateInfoPanel = (node) => { if (parentSubtree.name.match("Subscribed")) { info = `${info}\nsubscribed`; } + const nodeTree = parentSubtree.children.filter((n) => { + return n.id = node.id; + })[0] + let childIds = ""; + nodeTree.children.forEach((c) => { + childIds = `${childIds}, ${c.id}`; + }); + info = `${info}\nchild node ids: ${childIds}`; infoPanel.innerText = info; } @@ -78,7 +86,6 @@ const cellsTreeDisplay = (cells) => { } tree.onNodeMouseleave = (event) => { // un-highlight the corresponding element in the target window - const id = event.target.getAttribute("data-original-id"); chrome.devtools.inspectedWindow.eval( `window.removeDevtoolsHighlight()` ); From 71d0049a4c7d1fb70a77a578b345a4491f3ff7a8 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Thu, 4 May 2023 22:27:53 +0200 Subject: [PATCH 29/30] temp removal of child node display in info panel looks like a weird race condition --- object_database/web/devtools/js/cell_panel.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/object_database/web/devtools/js/cell_panel.js b/object_database/web/devtools/js/cell_panel.js index 21cb09a96..7b26b5670 100644 --- a/object_database/web/devtools/js/cell_panel.js +++ b/object_database/web/devtools/js/cell_panel.js @@ -53,6 +53,7 @@ const updateInfoPanel = (node) => { if (parentSubtree.name.match("Subscribed")) { info = `${info}\nsubscribed`; } + /* const nodeTree = parentSubtree.children.filter((n) => { return n.id = node.id; })[0] @@ -61,6 +62,7 @@ const updateInfoPanel = (node) => { childIds = `${childIds}, ${c.id}`; }); info = `${info}\nchild node ids: ${childIds}`; + */ infoPanel.innerText = info; } From 6de8bd82f9aa79ce4434d737a8bdeb9949f27b29 Mon Sep 17 00:00:00 2001 From: dkrasner Date: Thu, 25 May 2023 11:38:09 +0200 Subject: [PATCH 30/30] minor clean and improvement of cells panel --- object_database/web/devtools/cells_panel.css | 3 +++ object_database/web/devtools/js/cell_panel.js | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/object_database/web/devtools/cells_panel.css b/object_database/web/devtools/cells_panel.css index eff198b16..369acfdb6 100644 --- a/object_database/web/devtools/cells_panel.css +++ b/object_database/web/devtools/cells_panel.css @@ -22,6 +22,9 @@ div#cell-info { justify-content: center; align-items: center; min-width: 30%; + text-align: center; + font-family: Roboto; + font-size: 20px; } .node { diff --git a/object_database/web/devtools/js/cell_panel.js b/object_database/web/devtools/js/cell_panel.js index 7b26b5670..2b2b34a0e 100644 --- a/object_database/web/devtools/js/cell_panel.js +++ b/object_database/web/devtools/js/cell_panel.js @@ -47,11 +47,12 @@ const reconnectingDisplay = () => { const updateInfoPanel = (node) => { const infoPanel = document.getElementById("cell-info"); const id = node.getAttribute("data-original-id"); - let info = `cell-id: ${id}`; + const name = node.name; + let info = `${name}\ncell-id: ${id}`; const tree = document.querySelector("tree-graph"); const parentSubtree = tree.findParentSubTree(id, tree.data); if (parentSubtree.name.match("Subscribed")) { - info = `${info}\nsubscribed`; + info = `${info}\nsubscribed to cell-id: ${parentSubtree.id}`; } /* const nodeTree = parentSubtree.children.filter((n) => {