/ preconfirmations-old.html
preconfirmations-old.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>
 79      <div id="Content">
 80  <p>When a user submits a transaction to Ethereum, they have an expectation that there will be an interval of time before they can be sure that the transaction has been included in the blockchain.</p>
 81  
 82  <p>In Ethereum, a block is produced every 12 seconds (<a href="https://ethos.dev/beacon-chain">one slot</a>), and so the shortest time interval for transaction inclusion is 12 seconds.</p> 
 83  
 84  <p>Beyond inclusion, there is the notion of finalization, which is the point at which the transaction is considered irreversible. In Ethereum, this is considered to occur after 2 epochs, where each 
 85  <a href="https://ethos.dev/beacon-chain">epoch</a> contains 32 slots of 12 seconds each. </p>
 86  
 87  <p>So from a user's perspective, a transaction goes through the following stages:</p> 
 88  
 89  <p><strong>Submission > Inclusion > Finalization </strong></p>
 90      </div>
 91      <div id="visualization"></div>
 92      <div id="controls">
 93          <button id="upBtn" class="control-button">Up</button>
 94          <button id="leftBtn" class="control-button">Previous</button>
 95          <button id="rightBtn" class="control-button">Next</button>
 96          <button id="downBtn" class="control-button">Down</button>
 97      </div>
 98      <div id="metadata"></div>
 99      <div id="addNodeForm">
100          <h3>Add New Node</h3>
101          <input type="text" id="newNodeName" placeholder="Node Name">
102          <select id="newNodeDirection">
103              <option value="next">Next</option>
104              <option value="up">Up</option>
105              <option value="down">Down</option>
106          </select>
107          <button onclick="addNewNode()">Add Node</button>
108      </div>
109      <button id="editToggle" onclick="toggleEditMode()">Edit Mode</button>
110  
111      <script>
112          let nodeIds = ["User"];
113          let connectionMatrix = {
114              "User": {}
115          };
116          let nodeMetadata = {
117              "User": {
118                  "content": "Initiates and signs the transaction",
119                  "url": "https://ethereum.org/en/developers/docs/transactions/"
120              }
121          };
122  
123          const directionToGridDelta = {
124              "next": [1, 0],
125              "previous": [-1, 0],
126              "up": [0, -1],
127              "down": [0, 1]
128          };
129  
130          let isEditMode = false;
131  
132          function assignGridPositions(startNode) {
133              const gridPositions = {};
134              const queue = [[startNode, 0, 0]];
135              const visited = new Set();
136  
137              while (queue.length > 0) {
138                  const [node, x, y] = queue.shift();
139                  if (visited.has(node)) continue;
140                  visited.add(node);
141                  gridPositions[node] = [x, y];
142  
143                  for (let neighbor in connectionMatrix[node]) {
144                      const connection = connectionMatrix[node][neighbor];
145                      if (connection) {
146                          const [dx, dy] = directionToGridDelta[connection[0]];
147                          queue.push([neighbor, x + dx, y + dy]);
148                      }
149                  }
150              }
151  
152              return gridPositions;
153          }
154  
155          function createVisualization() {
156              const width = window.innerWidth;
157              const height = window.innerHeight;
158              const nodeRadius = Math.min(width, height) * 0.05;
159              const transactionRadius = nodeRadius * 0.3;
160              const gridSize = Math.min(width, height) * 0.2;
161  
162              d3.select("#visualization").selectAll("*").remove();
163  
164              const gridPositions = assignGridPositions(nodeIds[0]);
165  
166              let nodes = nodeIds.map(id => ({
167                  id: id,
168                  x: (gridPositions[id][0] + 2) * gridSize,
169                  y: (gridPositions[id][1] + 2) * gridSize
170              }));
171  
172              const links = [];
173              for (let source in connectionMatrix) {
174                  for (let target in connectionMatrix[source]) {
175                      if (connectionMatrix[source][target]) {
176                          links.push({
177                              source: nodes.find(n => n.id === source),
178                              target: nodes.find(n => n.id === target),
179                              type: connectionMatrix[source][target][0]
180                          });
181                      }
182                  }
183              }
184  
185              const svg = d3.select("#visualization")
186                  .append("svg")
187                  .attr("width", width)
188                  .attr("height", height);
189  
190              const link = svg.append("g")
191                  .selectAll("line")
192                  .data(links)
193                  .join("line")
194                  .attr("stroke", "#999")
195                  .attr("stroke-opacity", 0.6)
196                  .attr("x1", d => d.source.x)
197                  .attr("y1", d => d.source.y)
198                  .attr("x2", d => d.target.x)
199                  .attr("y2", d => d.target.y);
200  
201              const node = svg.append("g")
202                  .selectAll("circle")
203                  .data(nodes)
204                  .join("circle")
205                  .attr("r", nodeRadius)
206                  .attr("fill", "lightblue")
207                  .attr("cx", d => d.x)
208                  .attr("cy", d => d.y);
209  
210              const label = svg.append("g")
211                  .selectAll("text")
212                  .data(nodes)
213                  .join("text")
214                  .text(d => d.id)
215                  .attr("font-size", nodeRadius * 0.3)
216                  .attr("text-anchor", "middle")
217                  .attr("dominant-baseline", "central")
218                  .attr("x", d => d.x)
219                  .attr("y", d => d.y);
220  
221              const transaction = svg.append("circle")
222                  .attr("r", transactionRadius)
223                  .attr("fill", "red");
224  
225              return { nodes, transaction };
226          }
227  
228          let { nodes, transaction } = createVisualization();
229          let currentNode = nodes[0];
230          transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
231  
232          const directionMapping = {
233              "up": "up",
234              "down": "down",
235              "next": "right",
236              "previous": "left"
237          };
238  
239          function updateMetadataDisplay(nodeId) {
240              const metadata = nodeMetadata[nodeId];
241              let html = `<h3><a href="${metadata.url}" target="_blank">${nodeId}</a></h3>`;
242              if (isEditMode) {
243                  html += `
244                      <form id="metadataForm">
245                          <input type="text" id="nodeNameInput" value="${nodeId}">
246                          <textarea id="content" name="content" placeholder="Content">${metadata.content}</textarea>
247                          <input type="url" id="url" name="url" value="${metadata.url}" placeholder="URL">
248                          <button type="button" onclick="saveMetadata('${nodeId}')">Save</button>
249                          <button type="button" onclick="deleteNode('${nodeId}')">Delete</button>
250                          <button type="button" onclick="uploadNodeData()">Upload</button>
251                          <button type="button" onclick="downloadNodeData()">Download</button>
252                      </form>`;
253              } else {
254                  html += `<p>${metadata.content}</p>`;
255              }
256              document.getElementById("metadata").innerHTML = html;
257          }
258  
259          function saveMetadata(oldNodeId) {
260              const newNodeId = document.getElementById("nodeNameInput").value;
261              const content = document.getElementById("content").value;
262              const url = document.getElementById("url").value;
263              
264              const newMetadata = {
265                  content: content,
266                  url: url
267              };
268  
269              if (oldNodeId !== newNodeId) {
270                  nodeMetadata[newNodeId] = newMetadata;
271                  delete nodeMetadata[oldNodeId];
272                  nodeIds = nodeIds.map(id => id === oldNodeId ? newNodeId : id);
273                  
274                  // Update connectionMatrix
275                  connectionMatrix[newNodeId] = connectionMatrix[oldNodeId];
276                  delete connectionMatrix[oldNodeId];
277                  for (let node in connectionMatrix) {
278                      if (connectionMatrix[node][oldNodeId]) {
279                          connectionMatrix[node][newNodeId] = connectionMatrix[node][oldNodeId];
280                          delete connectionMatrix[node][oldNodeId];
281                      }
282                  }
283  
284                  currentNode = { id: newNodeId, x: currentNode.x, y: currentNode.y };
285                  //Update the nodes object
286                  nodes = nodes.map(n => n.id === oldNodeId ? currentNode : n);
287                  console.log(nodes);
288              } else {
289                  nodeMetadata[newNodeId] = newMetadata;
290              }
291  
292              ({ nodes, transaction } = createVisualization());
293              transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
294              updateButtons();
295              updateMetadataDisplay(newNodeId);
296          }
297  
298          function resetMetadata(nodeId) {
299              nodeMetadata[nodeId] = {
300                  "content": "",
301                  "url": ""
302              };
303              updateMetadataDisplay(nodeId);
304          }
305  
306          function moveTransaction(direction) {
307              for (let targetId in connectionMatrix[currentNode.id]) {
308                  const connection = connectionMatrix[currentNode.id][targetId];
309                  if (connection && connection[0] === direction) {
310                      const targetNode = nodes.find(n => n.id === targetId);
311                      if (targetNode) {
312                          currentNode = targetNode;
313                          transaction.transition()
314                              .duration(500)
315                              .attr("cx", currentNode.x)
316                              .attr("cy", currentNode.y);
317                          updateButtons();
318                          updateMetadataDisplay(currentNode.id);
319                          break;
320                      }
321                  }
322              }
323          }
324  
325          function updateButtons() {
326              for (let direction in directionMapping) {
327                  const buttonId = `#${directionMapping[direction]}Btn`;
328                  let isEnabled = false;
329                  for (let targetId in connectionMatrix[currentNode.id]) {
330                      const connection = connectionMatrix[currentNode.id][targetId];
331                      if (connection && connection[0] === direction) {
332                          isEnabled = true;
333                          break;
334                      }
335                  }
336                  d3.select(buttonId).property("disabled", !isEnabled);
337              }
338          }
339  
340          function toggleEditMode() {
341              isEditMode = !isEditMode;
342              updateMetadataDisplay(currentNode.id);
343              document.getElementById("editToggle").textContent = isEditMode ? "View Mode" : "Edit Mode";
344              document.getElementById("addNodeForm").style.display = isEditMode ? "block" : "none";
345          }
346  
347          function addNewNode() {
348              const newNodeName = document.getElementById("newNodeName").value;
349              const direction = document.getElementById("newNodeDirection").value;
350              
351              if (!newNodeName || nodeIds.includes(newNodeName)) {
352                  alert("Please enter a unique node name.");
353                  return;
354              }
355  
356              nodeIds.push(newNodeName);
357              connectionMatrix[newNodeName] = {};
358              nodeMetadata[newNodeName] = {
359                  "content": "",
360                  "url": ""
361              };
362  
363              connectionMatrix[currentNode.id][newNodeName] = [direction, directionToGridDelta[direction]];
364              const oppositeDirection = direction === "next" ? "previous" : (direction === "up" ? "down" : "up");
365              connectionMatrix[newNodeName][currentNode.id] = [oppositeDirection, directionToGridDelta[oppositeDirection]];
366  
367              ({ nodes, transaction } = createVisualization());
368              
369              // Move the transaction to the new node
370              currentNode = nodes.find(n => n.id === newNodeName);
371              transaction.transition()
372                  .duration(500)
373                  .attr("cx", currentNode.x)
374                  .attr("cy", currentNode.y);
375  
376              updateButtons();
377              updateMetadataDisplay(currentNode.id);
378  
379              // Clear the input field
380              document.getElementById("newNodeName").value = "";
381          }
382  
383          d3.select("#upBtn").on("click", () => moveTransaction("up"));
384          d3.select("#downBtn").on("click", () => moveTransaction("down"));
385          d3.select("#leftBtn").on("click", () => moveTransaction("previous"));
386          d3.select("#rightBtn").on("click", () => moveTransaction("next"));
387  
388          updateButtons();
389          updateMetadataDisplay(currentNode.id);
390  
391          window.addEventListener('resize', () => {
392              ({ nodes, transaction } = createVisualization());
393              currentNode = nodes.find(n => n.id === currentNode.id);
394              transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
395              updateButtons();
396              updateMetadataDisplay(currentNode.id);
397          });
398  
399          function deleteNode(nodeId) {
400              if (nodeId === "User") {
401                  alert("Cannot delete the initial node.");
402                  return;
403              }
404  
405              delete nodeMetadata[nodeId];
406              delete connectionMatrix[nodeId];
407              nodeIds = nodeIds.filter(id => id !== nodeId);
408              for (let node in connectionMatrix) {
409                  delete connectionMatrix[node][nodeId];
410              }
411  
412              ({ nodes, transaction } = createVisualization());
413              currentNode = nodes[0];
414              transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
415              updateButtons();
416              updateMetadataDisplay(currentNode.id);
417          }
418  
419          function downloadNodeData() {
420              const data = {
421                  nodeIds: nodeIds,
422                  connectionMatrix: connectionMatrix,
423                  nodeMetadata: nodeMetadata
424              };
425              const blob = new Blob([JSON.stringify(data)], { type: "application/json" });
426              const url = URL.createObjectURL(blob);
427              const a = document.createElement("a");
428              a.href = url;
429              a.download = "preconfirmations.json";
430              a.click();
431          }
432  
433          function uploadNodeData() {
434              const input = document.createElement("input");
435              input.type = "file";
436              input.accept = ".json";
437              input.onchange = e => {
438                  const file = e.target.files[0];
439                  const reader = new FileReader();
440                  reader.onload = e => {
441                      const data = JSON.parse(e.target.result);
442                      nodeIds = data.nodeIds;
443                      connectionMatrix = data.connectionMatrix;
444                      nodeMetadata = data.nodeMetadata;
445                      ({ nodes, transaction } = createVisualization());
446                      currentNode = nodes[0];
447                      transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
448                      updateButtons();
449                      updateMetadataDisplay(currentNode.id);
450                  };
451                  reader.readAsText(file);
452              };
453              input.click();
454          }
455  
456          function downloadNodeData() {
457              const data = {
458                  nodeIds: nodeIds,
459                  connectionMatrix: connectionMatrix,
460                  nodeMetadata: nodeMetadata
461              };
462              const blob = new Blob([JSON.stringify(data)], { type: "application/json" });
463              const url = URL.createObjectURL(blob);
464              const a = document.createElement("a");
465              a.href = url;
466              a.download = "preconfirmations.json";
467              a.click();
468          }
469  
470          function onLoadPopulateNodesFromURl() {
471              //Fetch json file from relative URL, check if it exists, if so populate the nodes
472              fetch('preconfirmations.json')
473                  .then(response => response.json())
474                  .then(data => {
475                      nodeIds = data.nodeIds;
476                      connectionMatrix = data.connectionMatrix;
477                      nodeMetadata = data.nodeMetadata;
478                      ({ nodes, transaction } = createVisualization());
479                      currentNode = nodes[0];
480                      transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
481                      updateButtons();
482                      updateMetadataDisplay(currentNode.id);
483                  })
484                  .catch((error) => {
485                      console.error('Error:', error);
486                  });
487          }
488  
489      </script>
490  </body>
491  </html>