/ ethereum-transaction-visualizer.html
ethereum-transaction-visualizer.html
  1  <!DOCTYPE html>
  2  <html lang="en">
  3  <head>
  4      <meta charset="UTF-8">
  5      <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6      <title>Ethereum Transaction Lifecycle Visualizer</title>
  7      <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
  8      <style>
  9          body, html { 
 10              margin: 0; 
 11              padding: 0; 
 12              width: 100%; 
 13              height: 100%; 
 14              overflow: hidden;
 15              font-family: Arial, sans-serif;
 16          }
 17          #visualization { 
 18              width: 100%; 
 19              height: 100%; 
 20          }
 21          #controls { 
 22              position: fixed;
 23              bottom: 20px;
 24              right: 20px;
 25              width: 150px; 
 26              height: 150px; 
 27          }
 28          .control-button { 
 29              position: absolute; 
 30              width: 60px; 
 31              height: 30px; 
 32              background-color: rgba(255, 255, 255, 0.7);
 33              border: 1px solid #999;
 34              border-radius: 5px;
 35          }
 36          #upBtn { top: 0; left: 45px; }
 37          #leftBtn { top: 60px; left: 0; }
 38          #rightBtn { top: 60px; right: 0; }
 39          #downBtn { bottom: 0; left: 45px; }
 40          #metadata, #addNodeForm {
 41              position: fixed;
 42              background-color: rgba(255, 255, 255, 0.9);
 43              padding: 10px;
 44              border-radius: 5px;
 45              max-width: 300px;
 46              max-height: 80%;
 47              overflow-y: auto;
 48          }
 49          #metadata {
 50              bottom: 20px;
 51              left: 20px;
 52          }
 53          #addNodeForm {
 54              top: 20px;
 55              left: 20px;
 56              display: none;  /* Initially hidden */
 57          }
 58          #metadata input, #metadata textarea, #addNodeForm input, #addNodeForm select {
 59              width: 100%;
 60              margin-bottom: 10px;
 61          }
 62          #metadata button, #addNodeForm button {
 63              margin-right: 10px;
 64          }
 65          #editToggle {
 66              position: fixed;
 67              top: 20px;
 68              right: 20px;
 69          }
 70      </style>
 71  </head>
 72  <body>
 73      <div id="Title">
 74          <h1>Preconfirmations</h1>
 75      </div>
 76      <div id="SubTitle">
 77          <h2>The setting</h2>
 78      <div id="Description">
 79          <p>Click on the "Up", "Down", "Previous", and "Next" buttons to navigate through the Ethereum transaction lifecycle.</p>
 80      </div>
 81      <div id="visualization"></div>
 82      <div id="controls">
 83          <button id="upBtn" class="control-button">Up</button>
 84          <button id="leftBtn" class="control-button">Previous</button>
 85          <button id="rightBtn" class="control-button">Next</button>
 86          <button id="downBtn" class="control-button">Down</button>
 87      </div>
 88      <div id="metadata"></div>
 89      <div id="addNodeForm">
 90          <h3>Add New Node</h3>
 91          <input type="text" id="newNodeName" placeholder="Node Name">
 92          <select id="newNodeDirection">
 93              <option value="next">Next</option>
 94              <option value="up">Up</option>
 95              <option value="down">Down</option>
 96          </select>
 97          <button onclick="addNewNode()">Add Node</button>
 98      </div>
 99      <button id="editToggle" onclick="toggleEditMode()">Edit Mode</button>
100  
101      <script>
102          let nodeIds = ["User"];
103          let connectionMatrix = {
104              "User": {}
105          };
106          let nodeMetadata = {
107              "User": {
108                  "content": "Initiates and signs the transaction",
109                  "url": "https://ethereum.org/en/developers/docs/transactions/"
110              }
111          };
112  
113          const directionToGridDelta = {
114              "next": [1, 0],
115              "previous": [-1, 0],
116              "up": [0, -1],
117              "down": [0, 1]
118          };
119  
120          let isEditMode = false;
121  
122          function assignGridPositions(startNode) {
123              const gridPositions = {};
124              const queue = [[startNode, 0, 0]];
125              const visited = new Set();
126  
127              while (queue.length > 0) {
128                  const [node, x, y] = queue.shift();
129                  if (visited.has(node)) continue;
130                  visited.add(node);
131                  gridPositions[node] = [x, y];
132  
133                  for (let neighbor in connectionMatrix[node]) {
134                      const connection = connectionMatrix[node][neighbor];
135                      if (connection) {
136                          const [dx, dy] = directionToGridDelta[connection[0]];
137                          queue.push([neighbor, x + dx, y + dy]);
138                      }
139                  }
140              }
141  
142              return gridPositions;
143          }
144  
145          function createVisualization() {
146              const width = window.innerWidth;
147              const height = window.innerHeight;
148              const nodeRadius = Math.min(width, height) * 0.05;
149              const transactionRadius = nodeRadius * 0.3;
150              const gridSize = Math.min(width, height) * 0.2;
151  
152              d3.select("#visualization").selectAll("*").remove();
153  
154              const gridPositions = assignGridPositions(nodeIds[0]);
155  
156              let nodes = nodeIds.map(id => ({
157                  id: id,
158                  x: (gridPositions[id][0] + 2) * gridSize,
159                  y: (gridPositions[id][1] + 2) * gridSize
160              }));
161  
162              const links = [];
163              for (let source in connectionMatrix) {
164                  for (let target in connectionMatrix[source]) {
165                      if (connectionMatrix[source][target]) {
166                          links.push({
167                              source: nodes.find(n => n.id === source),
168                              target: nodes.find(n => n.id === target),
169                              type: connectionMatrix[source][target][0]
170                          });
171                      }
172                  }
173              }
174  
175              const svg = d3.select("#visualization")
176                  .append("svg")
177                  .attr("width", width)
178                  .attr("height", height);
179  
180              const link = svg.append("g")
181                  .selectAll("line")
182                  .data(links)
183                  .join("line")
184                  .attr("stroke", "#999")
185                  .attr("stroke-opacity", 0.6)
186                  .attr("x1", d => d.source.x)
187                  .attr("y1", d => d.source.y)
188                  .attr("x2", d => d.target.x)
189                  .attr("y2", d => d.target.y);
190  
191              const node = svg.append("g")
192                  .selectAll("circle")
193                  .data(nodes)
194                  .join("circle")
195                  .attr("r", nodeRadius)
196                  .attr("fill", "lightblue")
197                  .attr("cx", d => d.x)
198                  .attr("cy", d => d.y);
199  
200              const label = svg.append("g")
201                  .selectAll("text")
202                  .data(nodes)
203                  .join("text")
204                  .text(d => d.id)
205                  .attr("font-size", nodeRadius * 0.3)
206                  .attr("text-anchor", "middle")
207                  .attr("dominant-baseline", "central")
208                  .attr("x", d => d.x)
209                  .attr("y", d => d.y);
210  
211              const transaction = svg.append("circle")
212                  .attr("r", transactionRadius)
213                  .attr("fill", "red");
214  
215              return { nodes, transaction };
216          }
217  
218          let { nodes, transaction } = createVisualization();
219          let currentNode = nodes[0];
220          transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
221  
222          const directionMapping = {
223              "up": "up",
224              "down": "down",
225              "next": "right",
226              "previous": "left"
227          };
228  
229          function updateMetadataDisplay(nodeId) {
230              const metadata = nodeMetadata[nodeId];
231              let html = `<h3><a href="${metadata.url}" target="_blank">${nodeId}</a></h3>`;
232              if (isEditMode) {
233                  html += `
234                      <form id="metadataForm">
235                          <input type="text" id="nodeNameInput" value="${nodeId}">
236                          <textarea id="content" name="content" placeholder="Content">${metadata.content}</textarea>
237                          <input type="url" id="url" name="url" value="${metadata.url}" placeholder="URL">
238                          <button type="button" onclick="saveMetadata('${nodeId}')">Save</button>
239                          <button type="button" onclick="resetMetadata('${nodeId}')">Reset</button>
240                          <button type="button" onclick="deleteNode('${nodeId}')">Delete</button>
241                      </form>`;
242              } else {
243                  html += `<p>${metadata.content}</p>`;
244              }
245              document.getElementById("metadata").innerHTML = html;
246          }
247  
248          function saveMetadata(oldNodeId) {
249              const newNodeId = document.getElementById("nodeNameInput").value;
250              const content = document.getElementById("content").value;
251              const url = document.getElementById("url").value;
252              
253              const newMetadata = {
254                  content: content,
255                  url: url
256              };
257  
258              if (oldNodeId !== newNodeId) {
259                  nodeMetadata[newNodeId] = newMetadata;
260                  delete nodeMetadata[oldNodeId];
261                  nodeIds = nodeIds.map(id => id === oldNodeId ? newNodeId : id);
262                  
263                  // Update connectionMatrix
264                  connectionMatrix[newNodeId] = connectionMatrix[oldNodeId];
265                  delete connectionMatrix[oldNodeId];
266                  for (let node in connectionMatrix) {
267                      if (connectionMatrix[node][oldNodeId]) {
268                          connectionMatrix[node][newNodeId] = connectionMatrix[node][oldNodeId];
269                          delete connectionMatrix[node][oldNodeId];
270                      }
271                  }
272  
273                  currentNode = { id: newNodeId, x: currentNode.x, y: currentNode.y };
274                  //Update the nodes object
275                  nodes = nodes.map(n => n.id === oldNodeId ? currentNode : n);
276                  console.log(nodes);
277              } else {
278                  nodeMetadata[newNodeId] = newMetadata;
279              }
280  
281              ({ nodes, transaction } = createVisualization());
282              transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
283              updateButtons();
284              updateMetadataDisplay(newNodeId);
285          }
286  
287          function resetMetadata(nodeId) {
288              nodeMetadata[nodeId] = {
289                  "content": "",
290                  "url": ""
291              };
292              updateMetadataDisplay(nodeId);
293          }
294  
295          function moveTransaction(direction) {
296              for (let targetId in connectionMatrix[currentNode.id]) {
297                  const connection = connectionMatrix[currentNode.id][targetId];
298                  if (connection && connection[0] === direction) {
299                      const targetNode = nodes.find(n => n.id === targetId);
300                      if (targetNode) {
301                          currentNode = targetNode;
302                          transaction.transition()
303                              .duration(500)
304                              .attr("cx", currentNode.x)
305                              .attr("cy", currentNode.y);
306                          updateButtons();
307                          updateMetadataDisplay(currentNode.id);
308                          break;
309                      }
310                  }
311              }
312          }
313  
314          function updateButtons() {
315              for (let direction in directionMapping) {
316                  const buttonId = `#${directionMapping[direction]}Btn`;
317                  let isEnabled = false;
318                  for (let targetId in connectionMatrix[currentNode.id]) {
319                      const connection = connectionMatrix[currentNode.id][targetId];
320                      if (connection && connection[0] === direction) {
321                          isEnabled = true;
322                          break;
323                      }
324                  }
325                  d3.select(buttonId).property("disabled", !isEnabled);
326              }
327          }
328  
329          function toggleEditMode() {
330              isEditMode = !isEditMode;
331              updateMetadataDisplay(currentNode.id);
332              document.getElementById("editToggle").textContent = isEditMode ? "View Mode" : "Edit Mode";
333              document.getElementById("addNodeForm").style.display = isEditMode ? "block" : "none";
334          }
335  
336          function addNewNode() {
337              const newNodeName = document.getElementById("newNodeName").value;
338              const direction = document.getElementById("newNodeDirection").value;
339              
340              if (!newNodeName || nodeIds.includes(newNodeName)) {
341                  alert("Please enter a unique node name.");
342                  return;
343              }
344  
345              nodeIds.push(newNodeName);
346              connectionMatrix[newNodeName] = {};
347              nodeMetadata[newNodeName] = {
348                  "content": "",
349                  "url": ""
350              };
351  
352              connectionMatrix[currentNode.id][newNodeName] = [direction, directionToGridDelta[direction]];
353              const oppositeDirection = direction === "next" ? "previous" : (direction === "up" ? "down" : "up");
354              connectionMatrix[newNodeName][currentNode.id] = [oppositeDirection, directionToGridDelta[oppositeDirection]];
355  
356              ({ nodes, transaction } = createVisualization());
357              
358              // Move the transaction to the new node
359              currentNode = nodes.find(n => n.id === newNodeName);
360              transaction.transition()
361                  .duration(500)
362                  .attr("cx", currentNode.x)
363                  .attr("cy", currentNode.y);
364  
365              updateButtons();
366              updateMetadataDisplay(currentNode.id);
367  
368              // Clear the input field
369              document.getElementById("newNodeName").value = "";
370          }
371  
372          d3.select("#upBtn").on("click", () => moveTransaction("up"));
373          d3.select("#downBtn").on("click", () => moveTransaction("down"));
374          d3.select("#leftBtn").on("click", () => moveTransaction("previous"));
375          d3.select("#rightBtn").on("click", () => moveTransaction("next"));
376  
377          updateButtons();
378          updateMetadataDisplay(currentNode.id);
379  
380          window.addEventListener('resize', () => {
381              ({ nodes, transaction } = createVisualization());
382              currentNode = nodes.find(n => n.id === currentNode.id);
383              transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
384              updateButtons();
385              updateMetadataDisplay(currentNode.id);
386          });
387  
388          function deleteNode(nodeId) {
389              if (nodeId === "User") {
390                  alert("Cannot delete the initial node.");
391                  return;
392              }
393  
394              delete nodeMetadata[nodeId];
395              delete connectionMatrix[nodeId];
396              nodeIds = nodeIds.filter(id => id !== nodeId);
397              for (let node in connectionMatrix) {
398                  delete connectionMatrix[node][nodeId];
399              }
400  
401              ({ nodes, transaction } = createVisualization());
402              currentNode = nodes[0];
403              transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
404              updateButtons();
405              updateMetadataDisplay(currentNode.id);
406          }
407      </script>
408  </body>
409  </html>