From 160007eb0d4ca7e4b83fbc18d4f6731e2fac498b Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Fri, 6 Sep 2024 18:28:14 +0200 Subject: [PATCH] feat: preact-router and GraphInput node balance modifier --- bun.lockb | Bin 58711 -> 59110 bytes package.json | 1 + src/algorithms/flussi-su-grafi/View.jsx | 3 + src/components/GraphInput.jsx | 152 +++++++++++++++--------- src/main.jsx | 12 +- src/style.css | 83 +++++++++++-- 6 files changed, 185 insertions(+), 66 deletions(-) diff --git a/bun.lockb b/bun.lockb index cc27caf3da492d469cc30e71577bbdfe9667a0f8..e5eb4ca6dbcda7d384f54b72af2a8bc9161a5f92 100755 GIT binary patch delta 9178 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 delta 9073 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 diff --git a/package.json b/package.json index 8936432..2a7260d 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@ruby/wasm-wasi": "^2.6.2", "katex": "^0.16.11", "lodash": "^4.17.21", + "preact-router": "^4.1.2", "vite-plugin-wasm": "^3.3.0" } } diff --git a/src/algorithms/flussi-su-grafi/View.jsx b/src/algorithms/flussi-su-grafi/View.jsx index a232f72..dd5f538 100644 --- a/src/algorithms/flussi-su-grafi/View.jsx +++ b/src/algorithms/flussi-su-grafi/View.jsx @@ -20,18 +20,21 @@ export const View = ({}) => { { id: id1, label: '1', + balance: 0, x: 100 + Math.random() * 500, y: 100 + Math.random() * 300, }, { id: id2, label: '2', + balance: 0, x: 100 + Math.random() * 500, y: 100 + Math.random() * 300, }, { id: id3, label: '3', + balance: 0, x: 100 + Math.random() * 500, y: 100 + Math.random() * 300, }, diff --git a/src/components/GraphInput.jsx b/src/components/GraphInput.jsx index 16e011f..d401268 100644 --- a/src/components/GraphInput.jsx +++ b/src/components/GraphInput.jsx @@ -56,6 +56,7 @@ export const GraphInput = ({ graph, setGraph }) => { { id: crypto.randomUUID(9).split('-')[0], label: '?', + balance: 0, x, y, }, @@ -193,7 +194,7 @@ export const GraphInput = ({ graph, setGraph }) => { ))}
- {graph.nodes.map(({ id, label, x, y }, index) => ( + {graph.nodes.map(({ id, label, balance, x, y }, index) => (
{ .filter(Boolean) .join(' ')} style={{ '--x': x, '--y': y }} - onMouseDown={e => { - if (interacting) return - - if (e.ctrlKey) { - setInteracting({ - type: 'arrow', - index, - initialPos: { x, y }, - initialDragPos: { x: e.x, y: e.y }, - x: x, - y: y, - target: null, - }) - } else { - setInteracting({ - type: 'drag', - index, - initialPos: { x, y }, - initialDragPos: { x: e.x, y: e.y }, - }) - } - }} - onMouseMove={e => { - if (interacting?.type === 'arrow' && interacting.index !== index) { - setInteracting(i => ({ ...i, target: index })) - } - }} - onMouseLeave={e => { - if (interacting && interacting.type === 'arrow') { - setInteracting(i => ({ ...i, target: null })) - } - }} - onDblclick={e => { - setInteracting({ - type: 'edit-node', - index, - }) - }} onKeyDown={e => { if ( (e.key === 'Enter' || e.key === 'Escape') && @@ -251,27 +214,110 @@ export const GraphInput = ({ graph, setGraph }) => { } }} > - {interacting?.type === 'edit-node' && interacting.index === index ? ( - + +
{ + if (interacting) return + + if (e.ctrlKey) { + setInteracting({ + type: 'arrow', + index, + initialPos: { x, y }, + initialDragPos: { x: e.x, y: e.y }, + x: x, + y: y, + target: null, + }) + } else { + setInteracting({ + type: 'drag', + index, + initialPos: { x, y }, + initialDragPos: { x: e.x, y: e.y }, }) } - /> - ) : ( - label + }} + onMouseMove={e => { + if (interacting?.type === 'arrow' && interacting.index !== index) { + setInteracting(i => ({ ...i, target: index })) + } + }} + onMouseLeave={e => { + if (interacting && interacting.type === 'arrow') { + setInteracting(i => ({ ...i, target: null })) + } + }} + onDblclick={e => { + setInteracting({ + type: 'edit-node', + index, + }) + }} + > + {interacting?.type === 'edit-node' && interacting.index === index ? ( + + setGraph(g => { + const newNodes = [...g.nodes] + newNodes[interacting.index] = { + ...g.nodes[interacting.index], + label: e.target.value, + } + + return { + ...g, + nodes: newNodes, + } + }) + } + /> + ) : ( + label + )} +
+ {balance !== 0 && ( +
+ {balance < 0 ? ( + <>−{Math.abs(balance)} + ) : ( + <>+{Math.abs(balance)} + )} +
)}
))} diff --git a/src/main.jsx b/src/main.jsx index 699d66a..cc7ca38 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -4,6 +4,7 @@ import '@fontsource/inter/latin.css' import _ from 'lodash' import { render } from 'preact' +import Router, { route } from 'preact-router' import { useState } from 'preact/hooks' const ViewRegistry = Object.fromEntries( @@ -25,7 +26,7 @@ const NewAlgorithmBox = ({ title, description, onClick }) => (
) -const AlgorithmChooserView = ({ setCurrentView }) => { +const AlgorithmChooserView = ({}) => { const sections = _.groupBy(ViewRegistry, 'metadata.group') return ( @@ -41,7 +42,7 @@ const AlgorithmChooserView = ({ setCurrentView }) => { setCurrentView(id)} + onClick={() => route(id)} /> ))} @@ -98,7 +99,12 @@ const App = ({}) => {

History

-
+ + + {Object.entries(ViewRegistry).map(([id, { View }]) => ( + + ))} +
) diff --git a/src/style.css b/src/style.css index 2ee53a7..9bfe839 100644 --- a/src/style.css +++ b/src/style.css @@ -51,6 +51,22 @@ h6 { /* Components */ +button { + outline: none; + border: none; + + background: #555; + color: #fff; + + border-radius: 0.25rem; + + cursor: pointer; + + &:hover { + background: #777; + } +} + .boxes { display: flex; flex-direction: row; @@ -174,24 +190,71 @@ h6 { transform: translate(-50%, -50%); - display: grid; - place-content: center; + pointer-events: all; - width: 2.5rem; - height: 2.5rem; + > .balance { + position: absolute; + top: 100%; + left: 50%; - background: #f0f0f0; - border: 2px solid #333; - border-radius: 1.25rem; + font-size: 18px; - pointer-events: all; + transform: translateX(-50%); + } - cursor: move; + > .node-ball { + display: grid; + place-content: center; - &.targeted { + width: 2.5rem; + height: 2.5rem; + + background: #f0f0f0; + border: 2px solid #333; + border-radius: 1.25rem; + + cursor: move; + } + + > .popup { + position: absolute; + z-index: -1; + + inset: -0.25rem; + + height: 4rem; + + /* background: #e0e0e0dd; + border: 2px solid #333; */ + /* border-radius: 0.5rem; */ + + display: flex; + flex-direction: column; + gap: 0.25rem; + + right: -2.25rem; + top: 50%; + + transform: translateY(-50%); + + padding: 0.25rem; + padding-left: 3rem; + + opacity: 0; + + > * { + flex-grow: 1; + } + } + + &.targeted > .node-ball { color: #fff; background: green; } + + &:hover > .popup { + opacity: 1; + } } } }