Skip to content

Commit 27a49fa

Browse files
committed
chore(rules): enhance rules for components, layers, react, story, ts
1 parent 7b438de commit 27a49fa

File tree

7 files changed

+321
-5
lines changed

7 files changed

+321
-5
lines changed

.cursor/rules/components-rules.mdc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Canvas components are located in src/components/canvas/ and divided into several
2929
- anchors/ - anchors for connections
3030
- layers/ - layers for rendering various elements
3131
- groups/ - element grouping
32+
- **Note:** These Canvas components manage rendering directly onto a `<canvas>` element. They are distinct from React components (even those used *within* the HTML layer at high zoom levels, like `GraphBlock`), although they share some lifecycle concepts managed by the core library. Rules specific to Canvas component rendering might not apply directly to React components used for the HTML layer.
3233

3334
# React Integration
3435
For integration of Canvas components with React, wrappers are used in the src/react-component/ directory. These components allow the use of Canvas in React applications.

.cursor/rules/graph-structure.mdc

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,25 @@ This file contains rules and information about the architecture of the graph lib
1010
- The graph is built on a layered architecture
1111
- Canvas is used for high performance rendering
1212
- React components are used for detailed interaction
13-
- The system automatically switches between rendering modes
13+
- The system automatically switches between rendering modes
14+
15+
# Project Structure
16+
- src/ - source code
17+
- api/ - API for interacting with the graph
18+
- components/ - Canvas components
19+
- canvas/ - contains blocks, connections, anchors and layers for rendering
20+
- lib/ - helper libraries
21+
- plugins/ - custom layers and related utilities (previously called plugins)
22+
- react-component/ - React wrappers for Canvas components
23+
- services/ - services, including camera, layers, etc.
24+
- store/ - graph state storage
25+
- stories/ - examples of component usage for Storybook
26+
- utils/ - utility functions
27+
- docs/ - project documentation
28+
- .storybook/ - Storybook configuration
29+
30+
# Extension Pattern
31+
- The primary mechanism for extending graph functionality (adding visuals, interactions) is by creating **Custom Layers**.
32+
- Custom layers should extend the base `Layer` class (`src/services/Layer.ts`).
33+
- Layers are added to the graph either via the `layers` array in `TGraphConfig` during initialization, or dynamically using `graph.addLayer()`.
34+
- There is **no separate base `Plugin` class**. Functionality previously considered "plugins" should be implemented as Layers and potentially related services or components, typically residing in the `src/plugins/` directory.

.cursor/rules/layer-rules.mdc

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,59 @@ Layers are fundamental to the Canvas rendering pipeline in @gravity-ui/graph. Th
1919

2020
## Working with Layers
2121
- **Layers are Components:** Treat layers as specialized components. They follow the standard component lifecycle (`mount`, `unmount`, `render`, `stateChanged`, etc.) and **all general component rules (see `components-rules`) apply to layers as well**. A layer's `render` method might be empty if it only handles behavior.
22-
- **Creating New Layers:**
23-
- If adding new types of visual elements or behaviors, create a new layer class.
24-
- New layers should ideally extend a base `Layer` class or a suitable existing layer, inheriting common functionality.
25-
- Register the new layer in the main graph's processing pipeline in the correct order.
22+
23+
- **Inheritance and Generics:**
24+
- Custom layers should extend the base `Layer` class from `src/services/Layer.ts`.
25+
- The base `Layer` class has the following generic signature: `Layer<Props extends LayerProps, Context extends LayerContext, State extends TComponentState>`.
26+
- `LayerProps`, `LayerContext`, and `TComponentState` are defined in `src/services/Layer.ts` and `src/lib/Component.ts` respectively.
27+
- When defining your custom layer, specify your custom Props, Context (if needed, extending `LayerContext`), and State (if needed, extending `TComponentState`) in this order: `class MyLayer extends Layer<TMyLayerProps, TMyLayerContext, TMyLayerState> {...}`.
28+
- Your custom `TMyLayerProps` interface **must extend the base `LayerProps`**. This is because the base constructor requires properties like `graph` and `camera`.
29+
30+
- **Constructor and Props:**
31+
- The constructor of your custom layer will typically receive the *full* `LayerProps` (or your extended `TMyLayerProps`).
32+
- You should pass these props up to the base class constructor using `super(props)`. You can override specific options like `canvas` within the object passed to `super`, e.g., `super({...props, canvas: {...props.canvas, zIndex: 100, respectPixelRatio: true}})`.
33+
34+
- **Creating and Adding Layers:**
35+
- Layers are typically added to the graph via the `layers` array in the `TGraphConfig` or dynamically using `graph.addLayer()`.
36+
- The `graph.addLayer<T extends Constructor<Layer>>(LayerClass, props)` method has a specific signature for its `props` argument: `Omit<LayerProps, 'root' | 'camera' | 'graph' | 'emitter'> & { root?: LayerProps['root'] }` (or the omitted version of your custom props if `LayerClass` uses them).
37+
- **Important:** `graph.addLayer` automatically provides the `graph`, `camera`, `root`, and `emitter` properties to the layer's constructor. You **do not** need to include them in the `props` object passed to `graph.addLayer`.
38+
- **Example Usage:**
39+
```typescript
40+
// In TGraphConfig:
41+
const graphConfig = {
42+
// ... other config
43+
layers: [
44+
[MyLayer, { myCustomProp: 'value' }] // Pass only custom/overridden props here
45+
]
46+
};
47+
48+
// Dynamic addition:
49+
const myLayerInstance = graph.addLayer(MyLayer, { myCustomProp: 'value' });
50+
```
51+
- Register the new layer in the main graph's processing pipeline in the correct order (often controlled by `zIndex` in `props.canvas` or `props.html`).
52+
2653
- **Modifying Existing Layers:**
2754
- When changing how elements are rendered or behaviors are managed, modify the relevant methods (e.g., `render`, event handlers) of the corresponding layer.
2855
- Ensure efficiency; avoid unnecessary computations or drawing invisible elements.
56+
- **Handling Device Pixel Ratio (DPR):** For crisp rendering on HiDPI displays, ensure the layer respects DPR.
57+
- **Recommended:** Set `respectPixelRatio: true` in the `canvas` options when initializing the layer via `super({ canvas: { respectPixelRatio: true }, ... })`. The base `Layer` class will then attempt to handle canvas sizing automatically.
58+
- **Manual (if needed):** If automatic handling isn't sufficient or more control is required:
59+
1. Get the DPR from context: `const dpr = this.context.constants.system.PIXEL_RATIO;`
60+
2. In your layer's `render` method, reset the transform: `ctx.setTransform(1, 0, 0, 1, 0, 0);`
61+
3. Clear the correct area: `ctx.clearRect(0, 0, viewWidth * dpr, viewHeight * dpr);` (where `viewWidth`/`viewHeight` are CSS dimensions from camera state).
62+
4. Scale the context *before* drawing: `ctx.scale(dpr, dpr);`
63+
- **Layer Lifecycle & Context:**
64+
- Use `afterInit` for setup that requires the canvas and context to be ready.
65+
- Inside `afterInit`, reliably get the canvas using `this.getCanvas()`.
66+
- Initialize or update the layer's full context using `this.setContext({ canvas, ctx: canvas.getContext('2d'), camera: ..., constants: ..., colors: ..., graph: ..., ...this.context });`. This ensures the context object (`this.context`) is correctly typed and populated for your layer.
67+
- Attach event listeners (e.g., `mousemove`, `mouseleave`) to the graph's root element (`this.props.graph.layers?.$root`) within `afterInit`.
68+
- Always clean up listeners and subscriptions in the `unmount` method.
69+
- **Camera Interaction & Coordinates:**
70+
- Subscribe to graph's `'camera-change'` event (`this.props.graph.on(...)`) to get updates. The event detail (`event.detail`) provides the `TCameraState` (containing `width`, `height`, `scale`, `x`, `y`).
71+
- `cameraState.x` and `cameraState.y` represent the *screen coordinates* of the world origin (0,0). Use these (`worldOriginScreenX`, `worldOriginScreenY`) for coordinate calculations.
72+
- To convert **screen coordinates to world coordinates** (e.g., mouse position), use `this.context.camera.applyToPoint(screenX, screenY)`.
73+
- To convert **world coordinates to screen coordinates** (e.g., placing ticks), use the formula: `screenX = worldX * scale + worldOriginScreenX` and `screenY = worldY * scale + worldOriginScreenY`.
74+
- To determine the **world coordinates visible** in the viewport, calculate the boundaries: `worldViewLeft = (0 - worldOriginScreenX) / scale`, `worldViewRight = (viewWidth - worldOriginScreenX) / scale`, etc. Use these boundaries to optimize rendering loops.
2975
- **Interaction & Behavior:** Layers are suitable for encapsulating specific interaction logic (e.g., drag-and-drop handling, tool activation). These layers might not draw anything but listen to events and modify the graph state.
3076
- **Event Propagation & Camera Interaction:** Since layers are often added directly to the root container (and not nested within the `GraphLayer` which handles core event delegation and contains the `Camera`), mouse events intended for camera interactions (like panning via click/drag) might be intercepted by the layer. To ensure the camera receives these events, you may need to override the layer's `getParent()` method to directly return the camera component: `return this.props.graph.getGraphLayer().$.camera;`. This effectively bypasses the standard hierarchy for event bubbling, delegating the event to the camera. *Note:* This is a workaround; be mindful of potential side effects on other event handling within your layer. See the `BlockGroups` layer (`src/components/canvas/groups/BlockGroups.ts`) for a practical example.
3177
- **State Management:** Layers typically access the graph's central state store (`store/`) to get the data they need and to dispatch changes. Use reactive patterns (signals) to trigger updates when relevant data changes.

.cursor/rules/project-rules.mdc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,24 @@ Project structure:
2222
- docs/ - project documentation
2323
- .storybook/ - Storybook configuration
2424

25+
## Key File Locations
26+
- **Main Graph Class:** `src/graph.ts` (Exports `Graph`, `TGraphConfig`)
27+
- **Base Layer Class:** `src/services/Layer.ts` (Exports `Layer`, `LayerProps`, `LayerContext`)
28+
- **Base Component Classes:**
29+
- `src/lib/CoreComponent.ts` (Exports `CoreComponent`, `CoreComponentProps`, `CoreComponentContext`)
30+
- `src/lib/Component.ts` (Exports `Component`, `TComponentProps`, `TComponentState` - extends `CoreComponent`)
31+
- **Camera Service:** `src/services/camera/CameraService.ts` (Exports `CameraService`, `ICamera`, `TCameraState`)
32+
- **Block Definitions:**
33+
- Base `Block` component: `src/components/canvas/blocks/Block.ts` (Exports `Block`, `TBlock`)
34+
- Block Store State: `src/store/block/Block.ts` (Exports `BlockState`, `TBlockId`, `IS_BLOCK_TYPE`)
35+
- **Connection Definitions:**
36+
- Connection Store State: `src/store/connection/ConnectionState.ts` (Exports `ConnectionState`, `TConnection`)
37+
- Base `Connection` component: `src/components/canvas/connections/Connection.ts` (Exports `Connection`)
38+
- **React Integration:**
39+
- `GraphCanvas` component: `src/react-component/GraphCanvas.tsx`
40+
- `useGraph` hook: `src/react-component/hooks/useGraph.ts`
41+
- `GraphBlock` component: `src/react-component/GraphBlock.tsx` (Or similar path)
42+
2543
## Technologies
2644
Technology stack:
2745
- TypeScript - main development language

.cursor/rules/react-rules.mdc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
---
2+
description: React rules
3+
globs: *.tsx,*.jsx
4+
alwaysApply: false
5+
---
6+
# React Development Rules
7+
8+
## Component Structure
9+
- Use functional components with hooks
10+
- Implement proper cleanup in useEffect when needed
11+
- Keep components focused and single-responsibility
12+
- Extract reusable logic into custom hooks
13+
14+
## Hooks Usage
15+
- Follow hooks naming convention: use[HookName]
16+
- Place hooks at the top level of component
17+
- Add all dependencies to useEffect/useCallback/useMemo
18+
- Use useCallback for event handlers passed to children
19+
- Use useMemo for expensive computations
20+
21+
## Performance Optimization
22+
- Avoid unnecessary re-renders:
23+
```typescript
24+
// Good
25+
const memoizedCallback = useCallback(() => {
26+
// callback logic
27+
}, [/* dependencies */]);
28+
29+
// Good
30+
const memoizedValue = useMemo(() => {
31+
// expensive computation
32+
}, [/* dependencies */]);
33+
```
34+
- Use React.memo() for pure components that render often
35+
- Keep render logic simple and performant
36+
37+
## Event Handling
38+
- Use proper event typing:
39+
```typescript
40+
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
41+
// handle click
42+
};
43+
```
44+
- Prefer controlled components over uncontrolled
45+
- Use event delegation when appropriate
46+
47+
## Props and State Management
48+
- Define explicit prop types using TypeScript interfaces
49+
- Use state only for values that trigger re-renders
50+
- Keep state as close to where it's used as possible
51+
- Use context for truly global state
52+
53+
## Component Lifecycle
54+
- Initialize resources in useEffect
55+
- Clean up subscriptions and event listeners
56+
- Handle loading and error states explicitly
57+
- Use ErrorBoundary for error handling
58+
59+
## Best Practices
60+
- Use semantic HTML elements
61+
- Implement proper accessibility attributes
62+
- Keep JSX clean and readable
63+
- Document complex component behavior
64+
- Write unit tests for components
65+
- Use React.StrictMode in development

.cursor/rules/story-rules.mdc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
description: Best practices for creation storybook graph stories
3+
globs: *.stories.tsx
4+
alwaysApply: false
5+
---
6+
# Story Creation Rules
7+
8+
## Documentation and References
9+
- Read the `docs/react/usage.md` for general React integration documentation.
10+
- Refer to `/src/stories/Playground` for complex examples.
11+
- Always use `GraphBlock` for rendering graph blocks in the HTML layer unless you have a specific custom renderer.
12+
13+
## Graph Initialization (Recommended Pattern)
14+
- **Use `useGraph` hook:** Initialize the graph instance within your story component using the `useGraph` hook from `src/react-component/hooks/useGraph.ts`.
15+
```typescript
16+
import { useGraph, GraphCanvas, GraphBlock } from "@gravity-ui/graph"; // Adjust path as needed
17+
18+
const MyStoryComponent = (props) => {
19+
const { graph } = useGraph({
20+
// Initial graph configuration (e.g., layers, renderBlock)
21+
layers: [[MyCustomLayer, { customProp: 'value' }]],
22+
renderBlock: (g, block) => <GraphBlock graph={g} block={block}>...</GraphBlock>
23+
});
24+
// ... useEffect for setting data and starting
25+
return <GraphCanvas graph={graph} ... />;
26+
};
27+
```
28+
- **Set data and start in `useEffect`:** Use a `useEffect` hook (with `graph` as a dependency) to populate the graph with data (`graph.setEntities({ blocks: ..., connections: ... })`) and then start the graph (`graph.start()`). This ensures the graph is ready before data is loaded.
29+
```typescript
30+
useEffect(() => {
31+
if (graph) {
32+
graph.setEntities({ blocks: blocksData, connections: connectionsData });
33+
graph.start();
34+
}
35+
}, [graph]);
36+
```
37+
- **Avoid direct `new Graph()` in story render:** While possible, using `useGraph` is preferred as it integrates better with React's lifecycle and state management for stories.
38+
39+
## Data Types and Required Fields
40+
- Ensure blocks have all required TBlock fields:
41+
```typescript
42+
{
43+
is: "Block" as const,
44+
selected: false,
45+
name: string,
46+
anchors: [],
47+
// other fields...
48+
}
49+
```
50+
- Connections must include `sourceBlockId` and `targetBlockId`
51+
- Use generics to extend block data types if needed
52+
53+
## Component Structure
54+
- Use `GraphCanvas` as the main container component
55+
- Wrap custom block content in `GraphBlock` component
56+
- Utilize `className` prop on `GraphBlock` for styling
57+
- Set explicit container dimensions (e.g., `height: "600px"`)
58+
59+
## UI Integration
60+
- Import and include UI library styles if using one
61+
- Wrap story in appropriate theme provider if needed
62+
- Use design system tokens/CSS variables for consistent styling
63+
- Use `useCallback` for `renderBlock` function to prevent unnecessary rerenders
64+
65+
## Best Practices
66+
- Keep blocks and connections data outside the component
67+
- Use `useEffect` for graph initialization and setup
68+
- Follow the UI library's design system guidelines
69+
- Ensure proper cleanup in useEffect if needed
70+
- Test different zoom levels to verify both canvas and HTML rendering
71+
- **Testing Layers:** When creating stories for custom layers or components that interact heavily with the canvas/camera:
72+
- Test behavior under different DPR settings (if possible, simulate different device pixel ratios).
73+
- Verify rendering and interactions at various zoom levels (`ECameraScaleLevel.LOW`, `MEDIUM`, `HIGH`) and during camera panning.
74+
- Test edge cases, like empty data or interactions near viewport boundaries.

.cursor/rules/typescript-rules.mdc

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
description:
3+
globs: *.tsx,*.ts
4+
alwaysApply: false
5+
---
6+
# TypeScript Development Rules
7+
8+
## Type Definitions
9+
- Use explicit type annotations for function parameters and returns
10+
- Prefer interfaces over type aliases for object types
11+
- Use type aliases for unions and complex types
12+
- Make types as specific as possible:
13+
```typescript
14+
// Good
15+
interface BlockData {
16+
id: string;
17+
type: 'input' | 'output' | 'process';
18+
position: { x: number; y: number };
19+
}
20+
21+
// Bad
22+
interface BlockData {
23+
[key: string]: any;
24+
}
25+
```
26+
27+
## Generics
28+
- Use generics to create reusable components and functions
29+
- Provide clear and descriptive type constraints
30+
- Use meaningful type parameter names:
31+
```typescript
32+
// Good
33+
function transformBlock<TData extends BlockData>(block: TData): TransformedBlock<TData> {
34+
// transform logic
35+
}
36+
37+
// Bad
38+
function transformBlock<T>(block: T): any {
39+
// transform logic
40+
}
41+
```
42+
43+
## Type Safety
44+
- Avoid using `any` type
45+
- Use `unknown` instead of `any` for values of unknown type
46+
- Enable strict TypeScript compiler options:
47+
```json
48+
{
49+
"strict": true,
50+
"noImplicitAny": true,
51+
"strictNullChecks": true,
52+
"strictFunctionTypes": true
53+
}
54+
```
55+
- Use type guards for runtime type checking
56+
57+
## Type Assertions
58+
- Minimize use of type assertions
59+
- Use `as const` for readonly arrays and objects
60+
- Prefer type guards over type assertions
61+
- Use `satisfies` operator for type checking:
62+
```typescript
63+
const config = {
64+
type: 'block',
65+
dimensions: { width: 100, height: 100 }
66+
} satisfies BlockConfig;
67+
```
68+
69+
## Error Handling
70+
- Create custom error types for specific errors
71+
- Use discriminated unions for error states
72+
- Handle all possible error cases:
73+
```typescript
74+
type Result<T> =
75+
| { success: true; data: T }
76+
| { success: false; error: Error };
77+
```
78+
79+
## Module Organization
80+
- Use barrel exports (index.ts) for public APIs
81+
- Keep internal types in separate files
82+
- Use namespaces sparingly
83+
- Export types explicitly when needed
84+
85+
## Best Practices
86+
- Write self-documenting code with clear types
87+
- Use TypeScript's built-in utility types when appropriate
88+
- Document complex types with JSDoc comments
89+
- Keep type definitions close to their usage
90+
- Use readonly modifiers for immutable data
91+
- Leverage TypeScript's inference when obvious

0 commit comments

Comments
 (0)