Skip to content

Commit ff65873

Browse files
committed
nodes can now be placed and removed from the diagram!
closes #11
1 parent ae93246 commit ff65873

File tree

4 files changed

+95
-106
lines changed

4 files changed

+95
-106
lines changed

src/assets/shift.svg

Lines changed: 0 additions & 74 deletions
This file was deleted.

src/components/DebugUI.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group"
2626
import { makeShifter, type ShiftKind } from "@/logic/nodeTypes/shift"
2727
import { neg } from "@/logic/nodeTypes/neg"
2828
import type { NodeType } from "@/logic/simulation"
29+
import { PopoverClose } from "@radix-ui/react-popover"
30+
import { not } from "@/logic/nodeTypes/not"
2931

3032
type NodeInfo = {
3133
name: string
@@ -55,6 +57,7 @@ const placeableNodes: NodeInfo[] = [
5557
]
5658

5759
function AddNodePopup(props: { trigger: ReactNode }) {
60+
const { setPlacingNode } = useSimulationContext()
5861
const [selectedNode, setSelectedNode] = useState<NodeInfo | undefined>()
5962
const [shiftKind, setShiftKind] = useState<ShiftKind>("left")
6063
const [shiftBits, setShiftBits] = useState("1")
@@ -64,7 +67,7 @@ function AddNodePopup(props: { trigger: ReactNode }) {
6467
<PopoverTrigger asChild>{props.trigger}</PopoverTrigger>
6568
<PopoverContent
6669
onCloseAutoFocus={(e) => e.preventDefault()} // https://github.com/radix-ui/primitives/issues/2248#issuecomment-2037290498
67-
className="w-80"
70+
className="w-[24em]"
6871
>
6972
<div className="grid gap-4">
7073
<div className="space-y-2">
@@ -80,7 +83,9 @@ function AddNodePopup(props: { trigger: ReactNode }) {
8083
key={n.name}
8184
className={
8285
"h-fit w-fit p-3 rounded-full outline-solid outline-black text-lg cursor-pointer box-content " +
83-
(selectedNode == n ? "outline-4 font-bold" : "outline-2")
86+
(selectedNode == n
87+
? "bg-gray-200 outline-4 font-bold"
88+
: "outline-2")
8489
}
8590
onClick={() => setSelectedNode(n)}
8691
>
@@ -90,12 +95,10 @@ function AddNodePopup(props: { trigger: ReactNode }) {
9095
</div>
9196

9297
{selectedNode && selectedNode.params && (
93-
<form>
94-
<h3 className="font-bold">Params:</h3>
95-
<div className="flex mb-2 items-baseline gap-2">
98+
<>
99+
<div className="flex items-baseline gap-2">
96100
Shift direction:
97101
<ToggleGroup
98-
id="shift-direction"
99102
type="single"
100103
variant="outline"
101104
value={shiftKind}
@@ -112,7 +115,6 @@ function AddNodePopup(props: { trigger: ReactNode }) {
112115
<div className="flex items-baseline gap-2">
113116
Shift amount:
114117
<Input
115-
id="shift-num-bits"
116118
type="number"
117119
max={31}
118120
min={1}
@@ -122,10 +124,26 @@ function AddNodePopup(props: { trigger: ReactNode }) {
122124
onBlur={() => setShiftBits(Number(shiftBits).toString())}
123125
/>
124126
</div>
125-
</form>
127+
</>
126128
)}
127129

128-
<Button disabled={!selectedNode}>Add</Button>
130+
<PopoverClose asChild>
131+
<Button
132+
disabled={!selectedNode}
133+
onClick={() => {
134+
if (selectedNode) {
135+
setPlacingNode(
136+
selectedNode.node({
137+
kind: shiftKind,
138+
bits: Number(shiftBits),
139+
}),
140+
)
141+
}
142+
}}
143+
>
144+
Add
145+
</Button>
146+
</PopoverClose>
129147
</div>
130148
</PopoverContent>
131149
</Popover>

src/components/Diagram.tsx

Lines changed: 58 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
/// <reference types="vite-plugin-svgr/client" />
22

3-
import { makeIID, placedNodeId } from "@/logic/simulation"
4-
import { useContext, useEffect, useRef, useState } from "react"
3+
import { makeIID, placedNodeId, type NodeType } from "@/logic/simulation"
4+
import { useContext, useEffect, useRef, useState, type ReactNode } from "react"
55
import { MouseTooltip } from "./MouseTooltip"
6-
import { int2hex } from "@/lib/utils"
7-
import ShiftComponent from "@/assets/shift.svg?react"
6+
import { cn, int2hex } from "@/lib/utils"
87
import { MousePositionContext } from "@/context/MousePositionContext"
98
import { useSimulationContext } from "@/context/SimulationContext"
10-
import { makeShifter } from "@/logic/nodeTypes/shift"
9+
import { X } from "lucide-react"
1110

1211
/**
1312
* The stroke width of the duplicate wires for interaction, in pixels.
@@ -16,19 +15,38 @@ const INTERACTION_STROKE_WIDTH = 6
1615

1716
const strokeCSSVariable = (node: string, input: string) => `--${node}-${input}`
1817

19-
function MouseNode(props: {
20-
svg: React.FunctionComponent<React.SVGProps<SVGSVGElement>>
21-
placeable: boolean
18+
function PlacedNode(props: {
19+
type: NodeType
20+
className?: string
21+
style: React.CSSProperties
22+
children?: ReactNode
2223
}) {
24+
return (
25+
<div
26+
className={cn(
27+
"absolute p-2 font-bold bg-white border-[2px] border-black rounded-t-[50%_50%] rounded-b-[50%_50%] pointer-events-none select-none whitespace-pre-line",
28+
props.className,
29+
)}
30+
style={{
31+
translate: "-50% -50%",
32+
...props.style,
33+
}}
34+
>
35+
{props.type.label}
36+
{props.children}
37+
</div>
38+
)
39+
}
40+
41+
function MouseNode(props: { type: NodeType; placeable: boolean }) {
2342
const mousePos = useContext(MousePositionContext)
2443

2544
return (
26-
<props.svg
27-
className="absolute pointer-events-none"
45+
<PlacedNode
46+
type={props.type}
2847
style={{
2948
left: mousePos.x,
3049
top: mousePos.y,
31-
translate: "-50% -50%",
3250
opacity: props.placeable ? 1 : 0.4,
3351
}}
3452
/>
@@ -38,12 +56,17 @@ function MouseNode(props: {
3856
export function Diagram(props: {
3957
svg: React.FunctionComponent<React.SVGProps<SVGSVGElement>>
4058
}) {
41-
const { placedNodes, setPlacedNodes, simulation } = useSimulationContext()
59+
const {
60+
placedNodes,
61+
setPlacedNodes,
62+
simulation,
63+
placingNode,
64+
setPlacingNode,
65+
} = useSimulationContext()
4266
const svgRef = useRef<SVGSVGElement | null>(null)
4367
const [hoveredWire, setHoveredWire] = useState<
4468
{ nodeId: string; inputId: string; bits: number } | undefined
4569
>(undefined)
46-
const [isPlacingNode, setIsPlacingNode] = useState(false)
4770

4871
useEffect(() => {
4972
if (!svgRef.current) {
@@ -89,7 +112,7 @@ export function Diagram(props: {
89112
}, [])
90113

91114
const onDiagramClick: React.MouseEventHandler<SVGSVGElement> = (e) => {
92-
if (hoveredWire && isPlacingNode && svgRef.current) {
115+
if (hoveredWire && placingNode && svgRef.current) {
93116
const { left, top } = svgRef.current.getBoundingClientRect()
94117
setPlacedNodes(
95118
new Map([
@@ -99,12 +122,12 @@ export function Diagram(props: {
99122
{
100123
x: e.clientX - left,
101124
y: e.clientY - top,
102-
nodeType: makeShifter("left", 2),
125+
nodeType: placingNode,
103126
},
104127
],
105128
]),
106129
)
107-
setIsPlacingNode(false)
130+
setPlacingNode(undefined)
108131
}
109132
}
110133

@@ -143,7 +166,7 @@ export function Diagram(props: {
143166
<props.svg
144167
ref={svgRef}
145168
style={
146-
hoveredWire && ((simulation && tooltipContent) || isPlacingNode)
169+
hoveredWire && ((simulation && tooltipContent) || placingNode)
147170
? {
148171
[strokeCSSVariable(hoveredWire.nodeId, hoveredWire.inputId)]:
149172
"2px",
@@ -153,15 +176,28 @@ export function Diagram(props: {
153176
onClick={onDiagramClick}
154177
/>
155178
{[...placedNodes.entries()].map(([id, n]) => (
156-
<ShiftComponent
179+
<PlacedNode
157180
key={id}
158-
className="absolute pointer-events-none"
181+
className={"group " + (simulation ? "" : "pointer-events-auto")}
182+
type={n.nodeType}
159183
style={{ left: n.x, top: n.y, translate: "-50% -50%" }}
160-
/>
184+
>
185+
{!simulation && (
186+
<X
187+
size={16}
188+
className="invisible group-hover:visible absolute bg-red-400 p-0.5 rounded-full text-white right-0 top-0 cursor-pointer"
189+
onClick={() => {
190+
const newNodes = new Map(placedNodes)
191+
newNodes.delete(id)
192+
setPlacedNodes(newNodes)
193+
}}
194+
/>
195+
)}
196+
</PlacedNode>
161197
))}
162198
</div>
163-
{isPlacingNode && (
164-
<MouseNode svg={ShiftComponent} placeable={!!hoveredWire} />
199+
{placingNode && (
200+
<MouseNode type={placingNode} placeable={!!hoveredWire} />
165201
)}
166202
{tooltipContent && <MouseTooltip>{tooltipContent}</MouseTooltip>}
167203
</>

src/context/SimulationContext.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,13 @@ type Context = {
8787
setPlacedNodes: (nodes: Map<InputID, PlacedNode>) => void
8888

8989
/**
90-
* Right Tab value for Editor\Execution.
90+
* The node type currently being placed by the user.
91+
*/
92+
placingNode: NodeType | undefined
93+
setPlacingNode: React.Dispatch<React.SetStateAction<NodeType | undefined>>
94+
95+
/**
96+
* Right Tab value for Editor/Execution.
9197
*/
9298
rightTabValue: "IDE" | "debugger"
9399
/**
@@ -141,6 +147,7 @@ export function SimulationContextProvider({ children }: Props) {
141147
const [placedNodes, setPlacedNodes] = useState<Map<InputID, PlacedNode>>(
142148
new Map(),
143149
)
150+
const [placingNode, setPlacingNode] = useState<NodeType | undefined>()
144151
const editorRef = useRef<EditorInterface | undefined>(undefined)
145152

146153
const startSimulation = () => {
@@ -280,6 +287,8 @@ export function SimulationContextProvider({ children }: Props) {
280287
setError,
281288
placedNodes,
282289
setPlacedNodes,
290+
placingNode,
291+
setPlacingNode,
283292
}}
284293
>
285294
{children}

0 commit comments

Comments
 (0)