The Editor Canvas
Relevant source files
The following files were used as context for generating this wiki page:
The Editor Canvas is the central, interactive workspace within the Character Sheet Maker application. It provides a dynamic grid-based layout where users can add, position, resize, and customize various content "blocks" to design a character sheet. The system is built using React, leveraging react-grid-layout for the drag-and-drop grid functionality and Zustand for centralized state management.
The canvas itself is a fixed-width container (w-[800px]) that holds all the blocks. Its functionality is driven by the state managed in a global Zustand store, which holds the array of all blocks and their properties. The BlockRenderer component dynamically renders each block based on its type and applies styling from a selected theme.
Sources: src/pages/Editor.tsx:19-21, src/components/sheet/Canvas.tsx:14-15, src/store/useSheetStore.ts
Architecture and Component Interaction
The Editor Canvas is not a single component but a system of interacting components orchestrated by the Editor page. The Editor page assembles the main layout, including the TopBar, ToolsSidebar, and the Canvas itself.
The core components involved are:
Editor.tsx: The main page component that structures the application layout, placing theToolsSidebarand theCanvas.ToolsSidebar.tsx: Provides UI buttons for users to add new blocks to the canvas by calling theaddBlockaction from the Zustand store.useSheetStore.ts: A Zustand store that acts as the single source of truth for the sheet's state, including the list of blocks, their layouts, and global styles.Canvas.tsx: The main grid container. It consumes theblocksstate from the store and usesreact-grid-layoutto render them. It is responsible for updating the block layouts in the store when a user moves or resizes an item.BlockRenderer.tsx: A conditional renderer that takes a singleblockobject and renders the appropriate React component based on the block'stypeproperty. It also handles block-specific data updates and deletion.
Sources: src/pages/Editor.tsx:12-22, src/components/layout/ToolsSidebar.tsx:128, src/components/sheet/Canvas.tsx:14, src/components/sheet/BlockRenderer.tsx:8
This diagram illustrates the interaction between the main components of the editor.
State Management (useSheetStore)
All state related to the character sheet's content and layout is managed in a single Zustand store, defined in useSheetStore.ts. This store uses zustand/middleware/persist to automatically save the sheet's state to localStorage, providing a "Local-First" experience.
Sources: src/store/useSheetStore.ts:4, src/store/useSheetStore.ts:107-109
Block Data Structure
The primary piece of state is the blocks array. Each object in this array represents one element on the canvas and conforms to the Block interface.
This diagram shows the structure of a Block object and its optional config.
Sources: src/store/useSheetStore.ts:21-36
The fields of the Block interface are:
| Field | Type | Description |
|---|---|---|
i | string | A unique ID for the block, required by react-grid-layout. Generated via crypto.randomUUID(). |
x | number | The block's horizontal position on the grid (in grid units). |
y | number | The block's vertical position on the grid (in grid units). |
w | number | The block's width (in grid units). |
h | number | The block's height (in grid units). |
type | string | The type of the block (e.g., "text", "header"). Determines which component is rendered by BlockRenderer. |
data | any | An object containing the content of the block (e.g., { text: "..." }, { src: "..." }). |
config | object | An object for block-specific configuration, such as level for headers or shapeType for shapes. |
Sources: src/store/useSheetStore.ts:21-36, src/store/useSheetStore.ts:82
Store Actions
The store exposes several actions to manipulate the sheet state:
| Action | Description |
|---|---|
addBlock(type, config) | Adds a new block of a given type to the blocks array. New blocks are placed at y: Infinity to let react-grid-layout find the next available vertical space. |
removeBlock(id) | Filters the blocks array to remove the block with the matching id. |
updateLayout(newLayout) | Synchronizes the x, y, w, h properties of the blocks in the store with the latest layout from react-grid-layout. |
updateBlockData(id, data) | Updates the data object for a specific block, merging the new data with existing data. |
toggleCompact() | Toggles the isCompact boolean, which controls the compactType property of the grid layout. |
setTheme(theme) | Sets the global theme key (simple, medieval, cyberpunk). |
Sources: src/store/useSheetStore.ts:39-47, src/store/useSheetStore.ts:79-105
Grid Layout System (Canvas.tsx)
The Canvas component is the implementation of the grid system. It uses the ResponsiveGridLayout component from react-grid-layout, which is a width-aware, responsive grid.
Sources: src/components/sheet/Canvas.tsx:11
Key properties configured for ResponsiveGridLayout are:
cols: Defines the number of columns in the grid. It's set to 12 for thelgbreakpoint.rowHeight: The height of one grid row in pixels, set to30.layouts: The layout definition for the grid items. This is derived from theblocksstate usinguseMemoto prevent unnecessary re-renders.onLayoutChange: A callback function that is triggered whenever a block is moved or resized. It calls theupdateLayoutaction in the Zustand store to persist the changes.compactType: Controls the packing logic. It's set to"vertical"whenisCompactis true (gravity on), andnullotherwise (free mode).preventCollision: WhenisCompactis false, this is set totrueto prevent blocks from overlapping.
Sources: src/components/sheet/Canvas.tsx:43-52
Data Flow for Layout Updates
The following sequence diagram shows what happens when a user drags a block to a new position on the canvas.
This diagram shows how user actions on the grid are persisted back to the central state.
Sources: src/components/sheet/Canvas.tsx:49, src/store/useSheetStore.ts:90-102
Block Rendering and Theming
Dynamic Rendering (BlockRenderer.tsx)
The BlockRenderer component is responsible for rendering the actual content of each block. It receives a block object and uses a series of if statements on block.type to render the correct JSX. Each block type has its own UI, including input fields for editing its content.
The available block types are:
headerform-boxshapedividercontainerlistimagetext
A DeleteBtn helper component is included in each rendered block, which allows the user to remove the block by calling removeBlock(block.i).
Sources: src/components/sheet/BlockRenderer.tsx:8, src/store/useSheetStore.ts:27, src/components/sheet/BlockRenderer.tsx:11-17
Theming System (lib/themes.ts)
The visual appearance of the blocks is controlled by a theming system. The useSheetStore holds a globalStyle.theme key (e.g., "simple", "medieval"). The BlockRenderer uses this key to retrieve a theme configuration object from THEMES defined in src/lib/themes.ts.
Each theme configuration specifies:
name: The display name of the theme.fonts: Font families for headings and body text.colors: Tailwind CSS classes for text, accent, and faint colors.blocks: A nested object containing specific Tailwind CSS class strings for each block type (e.g.,header.h1,formBox.input).
The BlockRenderer combines these theme-specific classes with base classes using the cn utility to style the components.
Sources: src/lib/themes.ts:4-31, src/components/sheet/BlockRenderer.tsx:9, src/components/sheet/BlockRenderer.tsx:28-34
This flowchart shows how a theme is applied to a block.
Exporting the Canvas
The TopBar component contains an "Export" dropdown menu that allows the user to download the character sheet as a PNG or PDF. These functionalities are provided by utility functions in src/lib/exportUtils.ts.
Both export functions target the DOM element with the ID rpg-sheet-canvas. They use the html-to-image library to convert the canvas content into a PNG data URL.
downloadAsImage: Converts the element to a PNG and triggers a download.downloadAsPDF: Converts the element to a PNG first, then usesjsPDFto place the image onto an A4-sized PDF document before triggering a download. The image height is calculated proportionally to the PDF width to avoid distortion.
Sources: src/components/layout/TopBar.tsx:48-55, src/lib/exportUtils.ts:4, src/lib/exportUtils.ts:20