Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,920 changes: 2,972 additions & 2,948 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@mui/icons-material": "^6.3.0",
"@mui/material": "^6.3.0",
"eventemitter3": "^5.0.1",
"lodash-es": "^4.17.21",
"memoize-one": "^6.0.0",
"mnemonist": "^0.39.8",
"nullthrows": "^1.1.1",
Expand All @@ -31,6 +32,7 @@
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@tsconfig/strictest": "^2.0.5",
"@types/lodash-es": "^4.17.12",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@vitejs/plugin-react": "^4.3.4",
Expand Down
21 changes: 13 additions & 8 deletions src/common/theme.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { createTheme } from "@mui/material";

// See https://www.figma.com/file/M3dC0Gk98IGSGlxY901rBh/
export const blackColor = "#000000";
export const whiteColor = "#ffffff";
export const primaryColor = "#009966";
export const errorColor = "#ff0000";
export const editorBackgroundColor = "#fafafa";
export const editorGridColor = "#dddddd";
export const theme = {
palette: {
black: "#000000",
white: "#ffffff",
textPrimary: "#444444",
primary: "#009966",
error: "#ff0000",
editorBackground: "#fafafa",
editorGrid: "#dddddd",
},
};

export const theme = createTheme({
export const muiTheme = createTheme({
palette: {
primary: { main: primaryColor },
primary: { main: theme.palette.primary },
},
});
2 changes: 2 additions & 0 deletions src/common/vector2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const vector2 = {
x: a.x / b,
y: a.y / b,
}),
size: (a: Vector2): number => Math.hypot(a.x, a.y),
distance: (a: Vector2, b: Vector2): number => vector2.size(vector2.sub(a, b)),
fromDomEvent: (e: { offsetX: number; offsetY: number }): Vector2 => ({
x: e.offsetX,
y: e.offsetY,
Expand Down
4 changes: 2 additions & 2 deletions src/components/GlobalHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AppBar } from "@mui/material";
import type { CSSProperties } from "react";
import { whiteColor } from "../common/theme";
import { theme } from "../common/theme";

export type GlobalHeaderProps = {
style: CSSProperties;
Expand All @@ -15,7 +15,7 @@ export default function GlobalHeader({ style }: GlobalHeaderProps) {
style={style}
sx={{
p: 2,
background: whiteColor,
background: theme.palette.white,
borderBottom: "1px solid",
borderColor: "divider",
}}
Expand Down
64 changes: 64 additions & 0 deletions src/hooks/drag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { type PointerEvent, useRef } from "react";
import { type Vector2, vector2 } from "../common/vector2";

const DRAG_THRESHOLD = 5;

export type UseDraggableProps = {
onClick?(e: PointerEvent): void;
onDragStart?(e: PointerEvent): void;
onDrag?(e: PointerEvent): void;
onDragEnd?(e: PointerEvent): void;
};

export function useDraggable(props: UseDraggableProps) {
const { onClick, onDragStart, onDrag, onDragEnd } = props;

const dragStateRef = useRef<{
pointerId: number;
initialPosition: Vector2;
isDragging: boolean;
} | null>(null);

return {
onPointerDown: (e: PointerEvent) => {
if (!(e.buttons & 1)) return;
dragStateRef.current = {
pointerId: e.pointerId,
initialPosition: vector2.fromDomEvent(e.nativeEvent),
isDragging: false,
};
onDragStart?.(e);
e.currentTarget.setPointerCapture(e.pointerId);
},
onPointerMove: (e: PointerEvent) => {
if (!dragStateRef.current) return;
if (dragStateRef.current.pointerId !== e.pointerId) return;
if (dragStateRef.current.isDragging) {
onDrag?.(e);
return;
}

const currentPosition = vector2.fromDomEvent(e.nativeEvent);
const distance = vector2.distance(
dragStateRef.current.initialPosition,
currentPosition,
);
if (distance > DRAG_THRESHOLD) {
dragStateRef.current.isDragging = true;
onDragStart?.(e);
onDrag?.(e);
}
},
onPointerUp: (e: PointerEvent) => {
if (!dragStateRef.current) return;
if (dragStateRef.current.pointerId !== e.pointerId) return;
e.currentTarget.releasePointerCapture(e.pointerId);
},
onLostPointerCapture: (e: PointerEvent) => {
if (!dragStateRef.current) return;
if (dragStateRef.current.isDragging) onDragEnd?.(e);
else onClick?.(e);
dragStateRef.current = null;
},
};
}
4 changes: 2 additions & 2 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import "./index.css";
import { theme } from "./common/theme";
import { muiTheme } from "./common/theme";
import { StoreProvider } from "./store/react";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<StoreProvider>
<ThemeProvider theme={theme}>
<ThemeProvider theme={muiTheme}>
<App />
</ThemeProvider>
</StoreProvider>
Expand Down
7 changes: 4 additions & 3 deletions src/pages/edit/Editor/components/Grid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ReactElement } from "react";
import { theme } from "../../../../common/theme";
import { vector2 } from "../../../../common/vector2";
import { useComponentEditorStore } from "../store";

Expand All @@ -8,7 +9,7 @@ export default function CCComponentEditorGrid() {
const canvasOriginPosition = componentEditorState.fromStageToCanvas(
vector2.zero,
);
const canvasGridSize = 100 * 2 ** (Math.ceil(logScale) - logScale);
const canvasGridSize = 80 * 2 ** (Math.ceil(logScale) - logScale);

const elements: ReactElement[] = [];
let i = 0;
Expand All @@ -26,7 +27,7 @@ export default function CCComponentEditorGrid() {
left: 0,
width: "1px",
height: "100%",
backgroundColor: "rgba(0, 0, 0, 0.1)",
backgroundColor: theme.palette.editorGrid,
transform: `translateX(${x}px)`,
}}
/>,
Expand All @@ -47,7 +48,7 @@ export default function CCComponentEditorGrid() {
left: 0,
width: "100%",
height: "1px",
backgroundColor: "rgba(0, 0, 0, 0.1)",
backgroundColor: theme.palette.editorGrid,
transform: `translateY(${y}px)`,
}}
/>,
Expand Down
1 change: 1 addition & 0 deletions src/pages/edit/Editor/components/NodePinPropertyEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function CCComponentEditorNodePinPropertyEditor() {}
4 changes: 2 additions & 2 deletions src/pages/edit/Editor/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Box } from "@mui/material";
import nullthrows from "nullthrows";
import { useState } from "react";
import { editorBackgroundColor } from "../../../common/theme";
import { theme } from "../../../common/theme";
import { ComponentPropertyDialog } from "../../../components/ComponentPropertyDialog";
import type { CCComponentId } from "../../../store/component";
import { useStore } from "../../../store/react";
Expand Down Expand Up @@ -33,7 +33,7 @@ function CCComponentEditorContent({
sx={{
position: "relative",
overflow: "hidden",
backgroundColor: editorBackgroundColor,
backgroundColor: theme.palette.editorBackground,
}}
>
<CCComponentEditorGrid />
Expand Down
9 changes: 8 additions & 1 deletion src/pages/edit/Editor/renderer/Background.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export default function CCComponentEditorRendererBackground() {
const { currentTarget } = pointerDownEvent;
const startPerspective = componentEditorState.perspective;
const startPoint = vector2.fromDomEvent(pointerDownEvent.nativeEvent);

pointerDownEvent.currentTarget.setPointerCapture(
pointerDownEvent.pointerId,
);
const onPointerMove = (pointerMoveEvent: PointerEvent) => {
const endPoint = vector2.fromDomEvent(pointerMoveEvent);
componentEditorState.setPerspective({
Expand All @@ -28,11 +32,14 @@ export default function CCComponentEditorRendererBackground() {
),
});
};
currentTarget.addEventListener("pointermove", onPointerMove);
const onPointerUp = () => {
currentTarget.removeEventListener("pointermove", onPointerMove);
currentTarget.removeEventListener("pointerup", onPointerUp);
pointerDownEvent.currentTarget.releasePointerCapture(
pointerDownEvent.pointerId,
);
};
currentTarget.addEventListener("pointermove", onPointerMove);
currentTarget.addEventListener("pointerup", onPointerUp);
}}
onWheel={(wheelEvent) => {
Expand Down
3 changes: 2 additions & 1 deletion src/pages/edit/Editor/renderer/Connection.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import nullthrows from "nullthrows";
import { theme } from "../../../../common/theme";
import type { CCConnectionId } from "../../../../store/connection";
import { useStore } from "../../../../store/react";
import ensureStoreItem from "../../../../store/react/error";
Expand Down Expand Up @@ -31,7 +32,7 @@ export function CCComponentEditorRendererConnectionCore({
].join(" ")}`,
`h ${straightGap * direction}`,
].join(" ")}
stroke="black"
stroke={theme.palette.textPrimary}
strokeWidth="2"
fill="none"
/>
Expand Down
2 changes: 1 addition & 1 deletion src/pages/edit/Editor/renderer/Node.geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function getCCComponentEditorRendererNodeGeometry(
) {
const width = 100;
const gapY = 20;
const paddingY = 20;
const paddingY = 15;

const node = nullthrows(store.nodes.get(nodeId));
const nodePins = store.nodePins.getManyByNodeId(nodeId);
Expand Down
43 changes: 26 additions & 17 deletions src/pages/edit/Editor/renderer/Node.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import nullthrows from "nullthrows";
import { useState } from "react";
import { blackColor, primaryColor, whiteColor } from "../../../../common/theme";
import { theme } from "../../../../common/theme";
import { vector2 } from "../../../../common/vector2";
import type { CCNodeId } from "../../../../store/node";
import { useStore } from "../../../../store/react";
Expand Down Expand Up @@ -59,21 +59,7 @@ const CCComponentEditorRendererNode = ensureStoreItem(

return (
<>
<text x={geometry.x} y={geometry.y - 5} textAnchor="bottom">
{component.name}
</text>
<rect
x={geometry.x}
y={geometry.y}
width={geometry.width}
height={geometry.height}
fill={whiteColor}
stroke={
componentEditorState.selectedNodeIds.has(nodeId)
? primaryColor
: blackColor
}
strokeWidth={2}
<g
onPointerDown={handlePointerDown}
onPointerMove={handlePointerMove}
onPointerUp={handlePointerUp}
Expand All @@ -82,7 +68,30 @@ const CCComponentEditorRendererNode = ensureStoreItem(
componentEditorState.selectNode([nodeId], true);
componentEditorState.openContextMenu(e);
}}
/>
>
<text
fill={theme.palette.textPrimary}
x={geometry.x}
y={geometry.y - 5}
textAnchor="bottom"
fontSize={12}
>
{component.name}
</text>
<rect
x={geometry.x}
y={geometry.y}
width={geometry.width}
height={geometry.height}
fill={theme.palette.white}
stroke={
componentEditorState.selectedNodeIds.has(nodeId)
? theme.palette.primary
: theme.palette.textPrimary
}
strokeWidth={2}
/>
</g>
{store.nodePins.getManyByNodeId(nodeId).map((nodePin) => {
const position = nullthrows(
geometry.nodePinPositionById.get(nodePin.id),
Expand Down
Loading