Skip to content

Commit 3a642d4

Browse files
chelproctaka231Rn86222
authored
improve overall quality (#57)
Co-authored-by: taka231 <[email protected]> Co-authored-by: Rn86222 <[email protected]>
1 parent eebcb7b commit 3a642d4

35 files changed

+2055
-1154
lines changed

.github/workflows/lint.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ jobs:
77
- run: npm ci
88
- run: npm run type-check
99
- run: npm run check
10+
- run: npm test

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
33
"vcs": {
44
"enabled": true,
55
"clientKind": "git",

package-lock.json

Lines changed: 1044 additions & 659 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,38 @@
1010
"type-check": "tsc",
1111
"type-check:watch": "tsc --watch",
1212
"check": "biome check",
13-
"check:fix": "biome check --fix"
13+
"check:fix": "biome check --fix",
14+
"test": "vitest"
1415
},
1516
"dependencies": {
1617
"@emotion/react": "^11.14.0",
17-
"@emotion/styled": "^11.14.0",
18-
"@mui/icons-material": "^6.3.0",
19-
"@mui/material": "^6.3.0",
18+
"@emotion/styled": "^11.14.1",
19+
"@mui/icons-material": "^7.3.1",
20+
"@mui/material": "^7.3.1",
21+
"debounce": "^2.2.0",
22+
"es-toolkit": "^1.39.10",
2023
"eventemitter3": "^5.0.1",
21-
"lodash-es": "^4.17.21",
2224
"memoize-one": "^6.0.0",
23-
"mnemonist": "^0.39.8",
25+
"mnemonist": "^0.40.3",
2426
"nullthrows": "^1.1.1",
25-
"react": "^19.0.0",
26-
"react-dom": "^19.0.0",
27+
"react": "^19.1.1",
28+
"react-dom": "^19.1.1",
2729
"react-use": "^17.6.0",
2830
"tiny-invariant": "^1.3.3",
29-
"transformation-matrix": "^2.16.1",
30-
"zustand": "^5.0.2"
31+
"transformation-matrix": "^3.1.0",
32+
"zod": "^4.1.12",
33+
"zustand": "^5.0.8"
3134
},
3235
"devDependencies": {
33-
"@biomejs/biome": "^1.9.4",
36+
"@biomejs/biome": "^2.2.2",
3437
"@tsconfig/strictest": "^2.0.5",
3538
"@types/lodash-es": "^4.17.12",
36-
"@types/react": "^19.0.2",
37-
"@types/react-dom": "^19.0.2",
38-
"@vitejs/plugin-react": "^4.3.4",
39-
"type-fest": "^4.31.0",
40-
"typescript": "^5.7.2",
41-
"vite": "^6.0.6"
39+
"@types/react": "^19.1.12",
40+
"@types/react-dom": "^19.1.9",
41+
"@vitejs/plugin-react": "^5.0.2",
42+
"type-fest": "^4.41.0",
43+
"typescript": "^5.9.2",
44+
"vite": "^7.1.3",
45+
"vitest": "^3.2.4"
4246
}
4347
}

src/components/ComponentPropertyDialog.tsx

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,157 @@
11
import {
2+
Box,
23
Button,
34
Dialog,
45
DialogActions,
56
DialogContent,
67
DialogTitle,
8+
FormLabel,
9+
Stack,
710
TextField,
811
} from "@mui/material";
12+
import nullthrows from "nullthrows";
913
import { useState } from "react";
14+
import { z } from "zod";
15+
import type CCStore from "../store";
16+
import type { CCComponentId } from "../store/component";
17+
import type { CCComponentPinId } from "../store/componentPin";
18+
import { useStore } from "../store/react";
1019

1120
export type ComponentPropertyDialogProps = {
21+
componentId: CCComponentId;
1222
defaultName: string;
13-
onAccept(newName: string): void;
23+
onClose(): void;
1424
onCancel(): void;
1525
};
1626

27+
const stateSchema = z.object({
28+
name: z.string().nonempty(),
29+
pinNameById: z.map(z.custom<CCComponentPinId>(), z.string().nonempty()),
30+
});
31+
function extractStateFromStore(
32+
store: CCStore,
33+
componentId: CCComponentId,
34+
): z.input<typeof stateSchema> {
35+
const component = nullthrows(store.components.get(componentId));
36+
return {
37+
name: component.name,
38+
pinNameById: new Map(
39+
store.componentPins
40+
.getManyByComponentId(componentId)
41+
.map((pin) => [pin.id, pin.name]),
42+
),
43+
};
44+
}
45+
function applyStateToStore(
46+
store: CCStore,
47+
componentId: CCComponentId,
48+
state: z.output<typeof stateSchema>,
49+
) {
50+
store.components.update(componentId, { name: state.name });
51+
for (const [pinId, pinName] of state.pinNameById) {
52+
store.componentPins.update(pinId, { name: pinName });
53+
}
54+
}
55+
1756
export function ComponentPropertyDialog({
18-
defaultName,
19-
onAccept,
20-
onCancel,
57+
componentId,
58+
onClose,
2159
}: ComponentPropertyDialogProps) {
22-
const [newName, setNewName] = useState(defaultName);
60+
const { store } = useStore();
61+
const component = nullthrows(store.components.get(componentId));
62+
const [state, setState] = useState(() =>
63+
extractStateFromStore(store, componentId),
64+
);
65+
const result = stateSchema.safeParse(state);
2366

2467
return (
25-
<Dialog maxWidth="xs" fullWidth open onClose={onCancel}>
68+
<Dialog maxWidth="sm" fullWidth open onClose={onClose}>
2669
<form
2770
onSubmit={(e) => {
2871
e.preventDefault();
29-
if (!newName) return;
30-
onAccept(newName);
72+
if (!result.success) return;
73+
applyStateToStore(store, componentId, result.data);
74+
onClose();
3175
}}
3276
>
33-
<DialogTitle>Component property</DialogTitle>
77+
<DialogTitle>{component.name} Properties</DialogTitle>
3478
<DialogContent>
79+
<FormLabel component="div">Name</FormLabel>
3580
<TextField
36-
label="Name"
37-
value={newName}
81+
size="small"
82+
value={state.name}
3883
fullWidth
39-
onChange={(e) => setNewName(e.target.value)}
84+
onChange={(e) => setState({ ...state, name: e.target.value })}
4085
placeholder="Name"
86+
sx={{ mt: 0.5 }}
4187
/>
88+
<Box
89+
sx={{
90+
display: "grid",
91+
gridTemplateColumns: "1fr 1fr",
92+
gap: 2,
93+
mt: 2,
94+
}}
95+
>
96+
<Stack sx={{ gap: 0.5 }}>
97+
<FormLabel component="div">Input Pins</FormLabel>
98+
{store.componentPins
99+
.getManyByComponentId(componentId)
100+
.filter((pin) => pin.type === "input")
101+
.map((pin) => (
102+
<TextField
103+
key={pin.id}
104+
size="small"
105+
value={state.pinNameById.get(pin.id)}
106+
fullWidth
107+
onChange={(e) =>
108+
setState({
109+
...state,
110+
pinNameById: new Map(state.pinNameById).set(
111+
pin.id,
112+
e.target.value,
113+
),
114+
})
115+
}
116+
/>
117+
))}
118+
</Stack>
119+
<Stack sx={{ gap: 0.5 }}>
120+
<FormLabel component="div">Output Pins</FormLabel>
121+
{store.componentPins
122+
.getManyByComponentId(componentId)
123+
.filter((pin) => pin.type === "output")
124+
.map((pin) => (
125+
<TextField
126+
key={pin.id}
127+
size="small"
128+
value={state.pinNameById.get(pin.id)}
129+
fullWidth
130+
onChange={(e) =>
131+
setState({
132+
...state,
133+
pinNameById: new Map(state.pinNameById).set(
134+
pin.id,
135+
e.target.value,
136+
),
137+
})
138+
}
139+
/>
140+
))}
141+
</Stack>
142+
</Box>
42143
</DialogContent>
43144
<DialogActions>
44-
<Button onClick={onCancel} color="inherit">
145+
<Button onClick={onClose} color="inherit">
45146
Cancel
46147
</Button>
47148
<Button
48149
variant="outlined"
49-
color="inherit"
150+
color="primary"
50151
type="submit"
51-
disabled={!newName}
152+
disabled={!result.success}
52153
>
53-
Create
154+
Apply
54155
</Button>
55156
</DialogActions>
56157
</form>

src/pages/edit/Editor/components/ContextMenu.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ export default function CCComponentEditorContextMenu({
4747
width: "200px",
4848
}}
4949
>
50-
<MenuItem onClick={componentEditorState.closeContextMenu}>
50+
{/* <MenuItem onClick={componentEditorState.closeContextMenu}>
5151
Create a node
52-
</MenuItem>
52+
</MenuItem> */}
5353
{componentEditorState.selectedNodeIds.size > 0 && (
5454
<MenuItem
5555
onClick={() => {
@@ -135,7 +135,7 @@ export default function CCComponentEditorContextMenu({
135135
store.connections.unregister([
136136
...componentEditorState.selectedConnectionIds,
137137
]);
138-
componentEditorState.selectNode([], true);
138+
// componentEditorState.selectNode([], true);
139139
componentEditorState.selectConnection([], false);
140140
componentEditorState.closeContextMenu();
141141
}}

src/pages/edit/Editor/components/NodePinPropertyEditor.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Button, Popover, Stack, TextField, Typography } from "@mui/material";
2-
import { zip } from "lodash-es";
2+
import { zip } from "es-toolkit";
33
import nullthrows from "nullthrows";
44
import { useState } from "react";
55
import invariant from "tiny-invariant";
@@ -28,7 +28,7 @@ export function CCComponentEditorNodePinPropertyEditor() {
2828
.getManyByNodeIdAndComponentPinId(target.nodeId, target.componentPinId)
2929
.toSorted((a, b) => a.order - b.order);
3030
invariant(
31-
nodePins.every((p) => p.userSpecifiedBitWidth !== null),
31+
nodePins.every((p) => p.manualBitWidth !== null),
3232
"NodePinPropertyEditor can only be used for node pins with user specified bit width",
3333
);
3434
const componentPinAttributes = nullthrows(
@@ -64,7 +64,7 @@ export function CCComponentEditorNodePinPropertyEditor() {
6464

6565
const bitWidthList =
6666
newBitWidthList ??
67-
nodePins.map((nodePin) => nullthrows(nodePin.userSpecifiedBitWidth));
67+
nodePins.map((nodePin) => nullthrows(nodePin.manualBitWidth));
6868

6969
const isTouched = Boolean(newBitWidthList);
7070
const isValid = bitWidthList.every((bitWidth) => bitWidth > 0);
@@ -103,7 +103,7 @@ export function CCComponentEditorNodePinPropertyEditor() {
103103
componentPinId: target.componentPinId,
104104
nodeId: target.nodeId,
105105
order: ++maxOrder,
106-
userSpecifiedBitWidth: bitWidth,
106+
manualBitWidth: bitWidth,
107107
}),
108108
);
109109
continue;
@@ -116,10 +116,25 @@ export function CCComponentEditorNodePinPropertyEditor() {
116116
// Update NodePin
117117
if (nodePin && bitWidth) {
118118
maxOrder = nodePin.order; // nodePins are sorted by order
119-
if (nodePin.userSpecifiedBitWidth !== bitWidth)
119+
if (nodePin.manualBitWidth !== bitWidth) {
120120
store.nodePins.update(nodePin.id, {
121-
userSpecifiedBitWidth: bitWidth,
121+
manualBitWidth: bitWidth,
122122
});
123+
const connections = store.connections.getConnectionsByNodePinId(
124+
nodePin.id,
125+
);
126+
for (const connection of connections) {
127+
const anotherNodePinId =
128+
connection.from === nodePin.id
129+
? connection.to
130+
: connection.from;
131+
if (
132+
!store.nodePins.isConnectable(nodePin.id, anotherNodePinId)
133+
) {
134+
store.connections.unregister([connection.id]);
135+
}
136+
}
137+
}
123138
continue;
124139
}
125140
throw new Error("Unreachable");

src/pages/edit/Editor/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,9 @@ function CCComponentEditorContent({
5252
<CCComponentEditorNodePinPropertyEditor />
5353
{isComponentPropertyDialogOpen && (
5454
<ComponentPropertyDialog
55+
componentId={componentId}
5556
defaultName={component.name}
56-
onAccept={(newName) => {
57-
store.components.update(componentId, { name: newName });
57+
onClose={() => {
5858
setIsComponentPropertyDialogOpen(false);
5959
}}
6060
onCancel={() => {

src/pages/edit/Editor/renderer/Background.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export default function CCComponentEditorRendererBackground() {
66
const viewBox = componentEditorState.getViewBox();
77

88
return (
9+
// biome-ignore lint/a11y/noStaticElementInteractions: SVG
910
<rect
1011
{...viewBox}
1112
onClick={() => {

0 commit comments

Comments
 (0)