diff --git a/bun.lockb b/bun.lockb index cc27caf..e5eb4ca 100755 Binary files a/bun.lockb and b/bun.lockb differ 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; + } } } }