Component: Canvas
Relevant source files
The following files were used as context for generating this wiki page:
The Canvas component is the central, interactive workspace within the Character Sheet Maker application. It provides a dynamic grid-based surface where users can add, arrange, resize, and customize various content elements, referred to as "blocks," to design a character sheet. The component is built upon the react-grid-layout library to handle the complexities of a draggable and resizable grid system.
The Canvas itself is primarily a container that orchestrates the rendering of blocks. It fetches the state of all blocks from a central Zustand store (useSheetStore) and uses the BlockRenderer component to render the specific content of each block. It is the core visual element of the Editor page, providing the "sheet" that users interact with and ultimately export.
Sources: [src/components/sheet/Canvas.tsx](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx), [src/pages/Editor.tsx:19-21](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/pages/Editor.tsx#L19-L21)
Architecture and Data Flow
The Canvas component sits at the heart of the editor, coordinating between user interactions, state management, and rendering. Its architecture relies on several key components and a centralized state store.
The diagram below illustrates the primary data flow and component interactions involving the Canvas.
This diagram shows that the Editor page assembles the main UI. The ToolsSidebar adds new blocks by calling addBlock in the useSheetStore. The Canvas subscribes to the blocks array from the store, renders them using ResponsiveGridLayout, and for each grid item, it uses BlockRenderer to display the actual content. Any layout changes made by the user in the grid are sent back to the store via the updateLayout function.
Sources: [src/pages/Editor.tsx:16](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/pages/Editor.tsx#L16), [src/components/sheet/Canvas.tsx:14](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L14), [src/components/layout/ToolsSidebar.tsx:141](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/layout/ToolsSidebar.tsx#L141), [src/store/useSheetStore.ts:81](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/store/useSheetStore.ts#L81)
State Management (useSheetStore)
All state related to the character sheet's content and layout is managed by a Zustand store defined in useSheetStore.ts. This provides a single source of truth for the application and allows different components to interact with the sheet's state in a decoupled manner. The state is also persisted to localStorage under the key rpg-sheet-storage.
Sources: [src/store/useSheetStore.ts:55](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/store/useSheetStore.ts#L55), [src/store/useSheetStore.ts:130](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/store/useSheetStore.ts#L130)
Block Data Structure
The fundamental data unit is the Block object. Each block represents an element on the canvas, such as a piece of text, an image, or an input field.
Sources: [src/store/useSheetStore.ts:18-34](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/store/useSheetStore.ts#L18-L34)
The properties of the Block interface are detailed below:
| Property | Type | Description |
|---|---|---|
i | string | A unique identifier for the block, required by react-grid-layout. Generated via crypto.randomUUID(). |
x | number | The horizontal position of the block on the grid (in grid units). |
y | number | The vertical position of the block on the grid (in grid units). |
w | number | The width of the block (in grid units). |
h | number | The height of the block (in grid units). |
type | "text" | "image" | "header" | "form-box" | "shape" | ... | Determines the type of content to be rendered by BlockRenderer. |
data | any | The actual content of the block, e.g., { text: "..." } for a text block or { src: "..." } for an image. |
config | object | Optional configuration for the block, such as level for a header or shapeType for a shape. |
Sources: [src/store/useSheetStore.ts:18-34](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/store/useSheetStore.ts#L18-L34), [src/store/useSheetStore.ts:83](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/store/useSheetStore.ts#L83)
Grid Layout System
The Canvas component uses ResponsiveGridLayout from the react-grid-layout library to create a responsive, draggable, and resizable grid.
Sources: [src/components/sheet/Canvas.tsx:11](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L11)
Configuration
The ResponsiveGridLayout is configured with several key properties that define its behavior:
| Property | Value in Code | Description |
|---|---|---|
layouts | { lg: layout } | The initial layout of grid items. The layout is a memoized array derived from the blocks state, containing only {i, x, y, w, h}. |
onLayoutChange | (currentLayout) => updateLayout(currentLayout) | A callback function that is triggered whenever the user moves or resizes a block. It calls updateLayout in the store to persist the new positions and dimensions. |
compactType | isCompact ? "vertical" : null | Controls the grid's packing algorithm. When "vertical", blocks are compacted vertically (gravity). When null, blocks can be placed freely, allowing overlap. This is toggled via the "Physics" button. |
preventCollision | isCompact ? false : true | If true, prevents blocks from overlapping when dragging. This is effectively the inverse of the compact mode. |
rowHeight | 30 | The height in pixels of one grid row. |
draggableCancel | ".nodrag" | A CSS selector for elements inside a grid item that should not trigger dragging, allowing for interactive elements like inputs and buttons. |
Sources: [src/components/sheet/Canvas.tsx:17](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L17), [src/components/sheet/Canvas.tsx:43-52](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L43-L52)
Physics (Compact Mode)
The "Physics" or "Compact Mode" is a key feature controlled by the isCompact boolean in useSheetStore. When enabled, the compactType of the grid is set to "vertical", causing blocks to "fall" and stack neatly without vertical gaps, similar to a masonry layout. When disabled, compactType is null, allowing for free-form placement and overlapping elements. This mode is toggled from the ToolsSidebar.
Sources: [src/components/sheet/Canvas.tsx:45](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L45), [src/store/useSheetStore.ts:74](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/store/useSheetStore.ts#L74), [src/components/layout/ToolsSidebar.tsx:200](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/layout/ToolsSidebar.tsx#L200)
Block Rendering
The Canvas component is responsible for iterating through the blocks array from the store and rendering the grid structure. For each block, it creates a div wrapper which acts as the grid item for react-grid-layout. Inside this wrapper, it delegates the actual content rendering to the BlockRenderer component.
Sources: [src/components/sheet/Canvas.tsx:53-66](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L53-L66)
Rendering Flow
Sources: [src/components/sheet/Canvas.tsx:53-60](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L53-L60), [src/components/sheet/BlockRenderer.tsx:10](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/BlockRenderer.tsx#L10)
Z-Index Management
The Canvas component implements a basic layering system by assigning z-index CSS classes to the block wrappers. This allows certain blocks, like containers, to be placed behind other content.
- If
block.typeis"container"orblock.config.zIndexis"back", the!z-0class is applied. - If
block.config.zIndexis"front", the!z-20class is applied. - Otherwise, a default
z-10is used.
Sources: [src/components/sheet/Canvas.tsx:55-57](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L55-L57)
Features and Interactivity
Visual Grid Toggle
A floating button at the bottom-right of the canvas allows the user to toggle a visual grid background. This is achieved by conditionally applying the bg-graph-paper CSS class to the main canvas div. The styles for this class are defined in src/index.css and use linear gradients to draw the grid lines.
Sources: [src/components/sheet/Canvas.tsx:21-26](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L21-L26), [src/components/sheet/Canvas.tsx:34](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/sheet/Canvas.tsx#L34), [src/index.css:26-34](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/index.css#L26-L34)
Exporting to Image and PDF
The TopBar component contains an "Export" dropdown menu with options to download the sheet as a PNG or PDF. These actions are handled by utility functions in src/lib/exportUtils.ts.
The export process works as follows:
- The user clicks an export button in the
TopBar. - The corresponding function (
downloadAsImageordownloadAsPDF) is called. - The function gets the canvas DOM element by its ID:
rpg-sheet-canvas. - It uses the
html-to-imagelibrary to convert the DOM element into a PNG data URL. - For PDF export, it then uses
jspdfto create a new A4 document and adds the generated image to it, calculating the correct aspect ratio. - Finally, it triggers a download of the resulting file.
Sources: [src/components/layout/TopBar.tsx:49-56](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/components/layout/TopBar.tsx#L49-L56), [src/lib/exportUtils.ts:4](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/lib/exportUtils.ts#L4), [src/lib/exportUtils.ts:20](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/lib/exportUtils.ts#L20)
The sequence diagram below visualizes the PDF export flow.
Sources: [src/lib/exportUtils.ts:18-34](https://github.com/war-light/Character-Sheet-Maker/blob/HEAD/src/lib/exportUtils.ts#L18-L34)
Conclusion
The Canvas component is the cornerstone of the Character Sheet Maker's user interface. It effectively integrates a powerful grid layout system with a reactive state management pattern to provide a flexible and intuitive design surface. By delegating block rendering and state logic to specialized components and stores, it remains a focused orchestrator, responsible for bringing together the data, layout, and visual representation of the user's character sheet. Its direct connection to the DOM also makes it the target for crucial features like image and PDF exporting.