feat(ui/arch): Optimize Graph Dynamics, Immersive Reader, and Core Stability (#19)
This PR introduces a major optimization of graph dynamics, immersive reading experience, and architectural stabilization. ### 🚀 Key Improvements - **Knowledge Graph (Fix #16)**: - Implemented smooth D3.js transitions using the General Update Pattern. - Added "Neon Flash" entry animations and dynamic node dimming for better focus. - **Immersive Reader (Fix #12)**: - Standardized centered layout (`max-width: 800px`) with **Merriweather** typography. - Optimized line-height and letter-spacing for premium readability. - **Technical Code Blocks (Fix #20)**: - High-contrast dark containers for code snippets. - **JetBrains Mono** integration and neon-accented scrollbars. - **Architectural Stabilization**: - Enforced a strict **'no async void'** policy in UI services using `Func<Task>`. - Resolved WASM runtime DI errors by implementing dummy service proxies for server-side dependencies. - Replaced generic 'Not Found' message with a branded Nexus preloader. Fixes #7, Fixes #12, Fixes #16, Fixes #20. Reviewed-on: #19 Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Co-committed-by: Marek Jasiński <jasins.marek@gmail.com>
This commit was merged in pull request #19.
This commit is contained in:
@@ -134,9 +134,10 @@ export function updateData(data) {
|
||||
.attr("fill", "none")
|
||||
.attr("stroke-width", d => d.relationType === 'Defines' ? 2 : 1)
|
||||
.attr("stroke-dasharray", d => d.relationType === 'References' ? "5,5" : "0")
|
||||
.call(e => e.transition().duration(500).attr("opacity", 1)),
|
||||
.style("opacity", 0)
|
||||
.call(enter => enter.transition().duration(500).style("opacity", 1)),
|
||||
update => update,
|
||||
exit => exit.remove()
|
||||
exit => exit.transition().duration(500).style("opacity", 0).remove()
|
||||
);
|
||||
|
||||
// Update Nodes
|
||||
@@ -146,8 +147,9 @@ export function updateData(data) {
|
||||
.join(
|
||||
enter => {
|
||||
const g = enter.append("g")
|
||||
.attr("class", "node-group")
|
||||
.attr("class", "node-group neon-flash-node")
|
||||
.style("cursor", "pointer")
|
||||
.style("opacity", 0)
|
||||
.on("click", (e, d) => {
|
||||
currentDotNetHelper.invokeMethodAsync('OnNodeClicked', d.id);
|
||||
setActiveNode(d.id);
|
||||
@@ -162,8 +164,7 @@ export function updateData(data) {
|
||||
if (d.type === 'Rule') return '#ff4444';
|
||||
return "url(#nebulaGlow)";
|
||||
})
|
||||
.attr("opacity", 0)
|
||||
.transition().duration(1000).attr("opacity", d => d.group === 'current' ? 0.6 : 0.2);
|
||||
.attr("opacity", d => d.group === 'current' ? 0.6 : 0.2);
|
||||
|
||||
g.append("rect")
|
||||
.attr("class", "node-pill")
|
||||
@@ -187,10 +188,12 @@ export function updateData(data) {
|
||||
.attr("fill", d => d.type === 'Definition' ? 'var(--nexus-accent)' : '#ccc')
|
||||
.attr("font-size", "0.8rem");
|
||||
|
||||
g.transition().duration(500).style("opacity", 1);
|
||||
|
||||
return g;
|
||||
},
|
||||
update => update,
|
||||
exit => exit.remove()
|
||||
update => update.classed("neon-flash-node", false),
|
||||
exit => exit.transition().duration(500).style("opacity", 0).remove()
|
||||
);
|
||||
|
||||
simulation.nodes(data.nodes);
|
||||
@@ -223,7 +226,11 @@ export function setActiveNode(nodeId) {
|
||||
if (!svgElement || !node) return;
|
||||
|
||||
const targetNode = node.filter(d => d.id === nodeId);
|
||||
if (targetNode.empty()) return;
|
||||
if (targetNode.empty()) {
|
||||
dimNodes(null);
|
||||
badge.style("display", "none");
|
||||
return;
|
||||
}
|
||||
|
||||
const d = targetNode.datum();
|
||||
|
||||
@@ -235,6 +242,9 @@ export function setActiveNode(nodeId) {
|
||||
badge.style("display", "block").datum(d);
|
||||
badge.attr("transform", `translate(${d.x},${d.y})`);
|
||||
|
||||
// Dim others
|
||||
dimNodes(nodeId);
|
||||
|
||||
// Smooth transition
|
||||
svgElement.transition().duration(1000).call(
|
||||
zoomBehavior.transform,
|
||||
@@ -242,6 +252,24 @@ export function setActiveNode(nodeId) {
|
||||
);
|
||||
}
|
||||
|
||||
export function dimNodes(activeNodeId) {
|
||||
if (!node) return;
|
||||
|
||||
node.transition().duration(500)
|
||||
.style("opacity", d => (activeNodeId === null || d.id === activeNodeId) ? 1 : 0.4);
|
||||
|
||||
if (link) {
|
||||
link.transition().duration(500)
|
||||
.style("opacity", d => {
|
||||
if (activeNodeId === null) return 1;
|
||||
// Check if this link is connected to the active node
|
||||
const sourceId = typeof d.source === 'object' ? d.source.id : d.source;
|
||||
const targetId = typeof d.target === 'object' ? d.target.id : d.target;
|
||||
return (sourceId === activeNodeId || targetId === activeNodeId) ? 1 : 0.1;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function unmount(containerId) {
|
||||
if (simulation) {
|
||||
simulation.stop();
|
||||
|
||||
Reference in New Issue
Block a user