The Trans Directory
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(graph): make graph non-singleton, proper cleanup, fix radial

+83 -45
+5 -5
quartz/components/Graph.tsx
··· 48 48 depth: -1, 49 49 scale: 0.9, 50 50 repelForce: 0.5, 51 - centerForce: 0.3, 51 + centerForce: 0.2, 52 52 linkDistance: 30, 53 53 fontSize: 0.6, 54 54 opacityScale: 1, ··· 67 67 <div class={classNames(displayClass, "graph")}> 68 68 <h3>{i18n(cfg.locale).components.graph.title}</h3> 69 69 <div class="graph-outer"> 70 - <div id="graph-container" data-cfg={JSON.stringify(localGraph)}></div> 71 - <button id="global-graph-icon" aria-label="Global Graph"> 70 + <div class="graph-container" data-cfg={JSON.stringify(localGraph)}></div> 71 + <button class="global-graph-icon" aria-label="Global Graph"> 72 72 <svg 73 73 version="1.1" 74 74 xmlns="http://www.w3.org/2000/svg" ··· 95 95 </svg> 96 96 </button> 97 97 </div> 98 - <div id="global-graph-outer"> 99 - <div id="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div> 98 + <div class="global-graph-outer"> 99 + <div class="global-graph-container" data-cfg={JSON.stringify(globalGraph)}></div> 100 100 </div> 101 101 </div> 102 102 )
+72 -34
quartz/components/scripts/graph.inline.ts
··· 68 68 stop: () => void 69 69 } 70 70 71 - async function renderGraph(container: string, fullSlug: FullSlug) { 71 + async function renderGraph(graph: HTMLElement, fullSlug: FullSlug) { 72 72 const slug = simplifySlug(fullSlug) 73 73 const visited = getVisited() 74 - const graph = document.getElementById(container) 75 - if (!graph) return 76 74 removeAllChildren(graph) 77 75 78 76 let { ··· 167 165 const height = Math.max(graph.offsetHeight, 250) 168 166 169 167 // we virtualize the simulation and use pixi to actually render it 170 - // Calculate the radius of the container circle 171 - const radius = Math.min(width, height) / 2 - 40 // 40px padding 172 168 const simulation: Simulation<NodeData, LinkData> = forceSimulation<NodeData>(graphData.nodes) 173 169 .force("charge", forceManyBody().strength(-100 * repelForce)) 174 170 .force("center", forceCenter().strength(centerForce)) 175 171 .force("link", forceLink(graphData.links).distance(linkDistance)) 176 172 .force("collide", forceCollide<NodeData>((n) => nodeRadius(n)).iterations(3)) 177 173 178 - if (enableRadial) 179 - simulation.force("radial", forceRadial(radius * 0.8, width / 2, height / 2).strength(0.3)) 174 + const radius = (Math.min(width, height) / 2) * 0.8 175 + if (enableRadial) simulation.force("radial", forceRadial(radius).strength(0.2)) 180 176 181 177 // precompute style prop strings as pixi doesn't support css variables 182 178 const cssVars = [ ··· 524 520 ) 525 521 } 526 522 523 + let stopAnimation = false 527 524 function animate(time: number) { 525 + if (stopAnimation) return 528 526 for (const n of nodeRenderData) { 529 527 const { x, y } = n.simulationData 530 528 if (!x || !y) continue ··· 548 546 requestAnimationFrame(animate) 549 547 } 550 548 551 - const graphAnimationFrameHandle = requestAnimationFrame(animate) 552 - window.addCleanup(() => cancelAnimationFrame(graphAnimationFrameHandle)) 549 + requestAnimationFrame(animate) 550 + return () => { 551 + stopAnimation = true 552 + app.destroy() 553 + } 554 + } 555 + 556 + let localGraphCleanups: (() => void)[] = [] 557 + let globalGraphCleanups: (() => void)[] = [] 558 + 559 + function cleanupLocalGraphs() { 560 + for (const cleanup of localGraphCleanups) { 561 + cleanup() 562 + } 563 + localGraphCleanups = [] 564 + } 565 + 566 + function cleanupGlobalGraphs() { 567 + for (const cleanup of globalGraphCleanups) { 568 + cleanup() 569 + } 570 + globalGraphCleanups = [] 553 571 } 554 572 555 573 document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { 556 574 const slug = e.detail.url 557 575 addToVisited(simplifySlug(slug)) 558 - await renderGraph("graph-container", slug) 559 576 560 - // Function to re-render the graph when the theme changes 577 + async function renderLocalGraph() { 578 + cleanupLocalGraphs() 579 + const localGraphContainers = document.getElementsByClassName("graph-container") 580 + for (const container of localGraphContainers) { 581 + localGraphCleanups.push(await renderGraph(container as HTMLElement, slug)) 582 + } 583 + } 584 + 585 + await renderLocalGraph() 561 586 const handleThemeChange = () => { 562 - renderGraph("graph-container", slug) 587 + void renderLocalGraph() 563 588 } 564 589 565 - // event listener for theme change 566 590 document.addEventListener("themechange", handleThemeChange) 567 - 568 - // cleanup for the event listener 569 591 window.addCleanup(() => { 570 592 document.removeEventListener("themechange", handleThemeChange) 571 593 }) 572 594 573 - const container = document.getElementById("global-graph-outer") 574 - const sidebar = container?.closest(".sidebar") as HTMLElement 575 - 576 - function renderGlobalGraph() { 595 + const containers = [...document.getElementsByClassName("global-graph-outer")] as HTMLElement[] 596 + async function renderGlobalGraph() { 577 597 const slug = getFullSlug(window) 578 - container?.classList.add("active") 579 - if (sidebar) { 580 - sidebar.style.zIndex = "1" 581 - } 598 + for (const container of containers) { 599 + container.classList.add("active") 600 + const sidebar = container.closest(".sidebar") as HTMLElement 601 + if (sidebar) { 602 + sidebar.style.zIndex = "1" 603 + } 582 604 583 - renderGraph("global-graph-container", slug) 584 - registerEscapeHandler(container, hideGlobalGraph) 605 + const graphContainer = container.querySelector(".global-graph-container") as HTMLElement 606 + registerEscapeHandler(container, hideGlobalGraph) 607 + if (graphContainer) { 608 + globalGraphCleanups.push(await renderGraph(graphContainer, slug)) 609 + } 610 + } 585 611 } 586 612 587 613 function hideGlobalGraph() { 588 - container?.classList.remove("active") 589 - if (sidebar) { 590 - sidebar.style.zIndex = "" 614 + cleanupGlobalGraphs() 615 + for (const container of containers) { 616 + container.classList.remove("active") 617 + const sidebar = container.closest(".sidebar") as HTMLElement 618 + if (sidebar) { 619 + sidebar.style.zIndex = "" 620 + } 591 621 } 592 622 } 593 623 594 624 async function shortcutHandler(e: HTMLElementEventMap["keydown"]) { 595 625 if (e.key === "g" && (e.ctrlKey || e.metaKey) && !e.shiftKey) { 596 626 e.preventDefault() 597 - const globalGraphOpen = container?.classList.contains("active") 598 - globalGraphOpen ? hideGlobalGraph() : renderGlobalGraph() 627 + const anyGlobalGraphOpen = containers.some((container) => 628 + container.classList.contains("active"), 629 + ) 630 + anyGlobalGraphOpen ? hideGlobalGraph() : renderGlobalGraph() 599 631 } 600 632 } 601 633 602 - const containerIcon = document.getElementById("global-graph-icon") 603 - containerIcon?.addEventListener("click", renderGlobalGraph) 604 - window.addCleanup(() => containerIcon?.removeEventListener("click", renderGlobalGraph)) 634 + const containerIcons = document.getElementsByClassName("global-graph-icon") 635 + Array.from(containerIcons).forEach((icon) => { 636 + icon.addEventListener("click", renderGlobalGraph) 637 + window.addCleanup(() => icon.removeEventListener("click", renderGlobalGraph)) 638 + }) 605 639 606 640 document.addEventListener("keydown", shortcutHandler) 607 - window.addCleanup(() => document.removeEventListener("keydown", shortcutHandler)) 641 + window.addCleanup(() => { 642 + document.removeEventListener("keydown", shortcutHandler) 643 + cleanupLocalGraphs() 644 + cleanupGlobalGraphs() 645 + }) 608 646 })
+2 -2
quartz/components/scripts/search.inline.ts
··· 384 384 preview.replaceChildren(previewInner) 385 385 386 386 // scroll to longest 387 - const highlights = [...preview.querySelectorAll(".highlight")].sort( 387 + const highlights = [...preview.getElementsByClassName("highlight")].sort( 388 388 (a, b) => b.innerHTML.length - a.innerHTML.length, 389 389 ) 390 390 highlights[0]?.scrollIntoView({ block: "start" }) ··· 488 488 document.addEventListener("nav", async (e: CustomEventMap["nav"]) => { 489 489 const currentSlug = e.detail.url 490 490 const data = await fetchData 491 - const searchElement = document.querySelectorAll(".search") 491 + const searchElement = document.getElementsByClassName("search") 492 492 for (const element of searchElement) { 493 493 await setupSearch(element, currentSlug, data) 494 494 }
+1 -1
quartz/components/scripts/toc.inline.ts
··· 25 25 } 26 26 27 27 function setupToc() { 28 - for (const toc of document.querySelectorAll(".toc")) { 28 + for (const toc of document.getElementsByClassName("toc")) { 29 29 const button = toc.querySelector(".toc-header") 30 30 const content = toc.querySelector(".toc-content") 31 31 if (!button || !content) return
+3 -3
quartz/components/styles/graph.scss
··· 15 15 position: relative; 16 16 overflow: hidden; 17 17 18 - & > #global-graph-icon { 18 + & > .global-graph-icon { 19 19 cursor: pointer; 20 20 background: none; 21 21 border: none; ··· 38 38 } 39 39 } 40 40 41 - & > #global-graph-outer { 41 + & > .global-graph-outer { 42 42 position: fixed; 43 43 z-index: 9999; 44 44 left: 0; ··· 53 53 display: inline-block; 54 54 } 55 55 56 - & > #global-graph-container { 56 + & > .global-graph-container { 57 57 border: 1px solid var(--lightgray); 58 58 background-color: var(--light); 59 59 border-radius: 5px;