How DrawDB Handles Notes and Annotations in Diagrams: A Technical Deep-Dive
DrawDB manages notes through a dedicated React context that provides CRUD operations, with UI components consuming a useNotes hook to create, position, edit, and delete draggable annotations on the canvas.
Notes and annotations in database diagrams serve a critical purpose: documenting design decisions, marking TODOs, or explaining complex relationships. DrawDB implements this feature as a lightweight, fully-integrated subsystem rather than an afterthought. This article examines the architecture, state management, and persistence mechanisms that power DrawDB's note functionality, based on the drawdb-io/drawdb source code.
State Management with NotesContext
The foundation of DrawDB's note system resides in src/context/NotesContext.jsx. This file establishes a React context that centralizes all note-related state across the application.
The context provider wraps the entire editor, ensuring any component can access notes without prop drilling. The context stores an array of note objects, each containing:
id: Unique identifier for the notex,y: Canvas coordinates for positioningcontent: The annotation texttheme: Background color styling
According to the drawdb-io/drawdb source code, the context exposes four primary actions: addNote, updateNote, removeNote, and getNotes. These pure functions modify the React state, triggering re-renders wherever notes are consumed.
The useNotes Hook Interface
To abstract the context API, DrawDB provides src/hooks/useNotes.js. This custom hook simplifies component integration by returning an object with the available actions.
Components throughout the editor—toolbar buttons, canvas elements, and export utilities—all rely on this hook rather than directly importing the context. This pattern decouples the UI from state implementation details and enables easier testing.
// Consuming the notes API in a component
import { useNotes } from '../hooks/useNotes';
function CanvasNotes() {
const { addNote, updateNote, removeNote, notes } = useNotes();
// Component logic using note operations
return notes.map(note => <NoteElement key={note.id} note={note} />);
}
Creating Notes: The Toolbar Integration
User interaction begins at the toolbar through src/icons/IconAddNote.jsx. Clicking this icon invokes addNote with the current cursor position.
The creation flow works as follows:
- User clicks the Add Note icon
- Event coordinates are captured from
e.nativeEvent addNotereceives position data plus default styling fromsrc/data/constants.js- A new note object enters the context state
- React re-renders the canvas, displaying the draggable note
// Triggering note creation from toolbar
import { useNotes } from '../hooks/useNotes';
import { defaultNoteTheme } from '../data/constants';
function AddNoteButton() {
const { addNote } = useNotes();
const handleClick = (e) => {
const { x, y } = e.nativeEvent;
addNote({
x,
y,
content: '',
theme: defaultNoteTheme
});
};
return <IconAddNote onClick={handleClick} />;
}
Rendering and Editing Note Elements
Each note renders as a draggable, resizable DOM element positioned absolutely on the canvas. The note component—integrated within src/pages/Editor.jsx—consumes the NotesContext for live updates.
The editing interface supports:
- Inline text editing via a
<textarea>element - Drag repositioning with coordinate updates on
onDragEnd - Theme customization through color selection
- Deletion with immediate context removal
// Core note component implementation pattern
import { useNotes } from '../hooks/useNotes';
function Note({ note }) {
const { updateNote, removeNote } = useNotes();
return (
<div
className="drawdb-note"
style={{
left: note.x,
top: note.y,
background: note.theme
}}
draggable
onDragEnd={(e) => updateNote(note.id, { x: e.x, y: e.y })}
>
<textarea
value={note.content}
onChange={(e) => updateNote(note.id, { content: e.target.value })}
/>
<button onClick={() => removeNote(note.id)}>
Delete
</button>
</div>
);
}
Styling and Theming Configuration
Visual consistency for notes originates in src/data/constants.js. The defaultNoteTheme constant defines the initial background color (#fcf7ac, a light yellow) that distinguishes notes from tables and relationships.
Users can override this default per-note through the editing UI. The selected theme persists in the note object's state and survives export/import cycles.
Persisting Annotations Across Formats
Notes integrate into DrawDB's export pipeline, ensuring annotations survive beyond the current editing session. Two primary export paths handle note serialization:
DBML Export (src/utils/exportAs/dbml.js)
The DBML formatter appends note declarations after table definitions:
function exportDbml(tables, notes) {
const tablePart = tables.map(formatTable).join('\n');
const notesPart = notes
.map((n) => `Note ${n.id} "${n.content}" at (${n.x}, ${n.y})`)
.join('\n');
return `${tablePart}\n${notesPart}`;
}
Documentation/Image Export
The src/utils/exportAs/documentation.js utility includes notes in generated PNG exports and shareable URLs. Position and content data serialize alongside schema information, enabling full diagram restoration.
Complete Interaction Flow
Understanding the full lifecycle clarifies how these subsystems cooperate:
- Initialization:
NotesContextProvidermounts inEditor.jsx, establishing empty note state - Creation: Toolbar click →
useNotes().addNote()→ state update → canvas re-render - Positioning: User drags note →
onDragEndfires →updateNotecommits new coordinates - Editing: Text input →
onChangedebounced →updateNotesynchronizes content - Styling: Color picker selection →
updateNoteapplies new theme value - Deletion: Remove button →
removeNotefilters state → component unmounts - Export: Formatter iterates
notesarray → serializes to target format
Summary
- Centralized state:
NotesContext.jsxmaintains the source of truth for all diagram annotations - Clean API:
useNoteshook provides ergonomic access to CRUD operations without context boilerplate - Visual integration: Notes render as first-class canvas elements with drag, resize, and edit capabilities
- Customizable appearance: Default theming from
constants.jswith per-note overrides - Persistence guarantee: Export utilities in
dbml.jsanddocumentation.jspreserve notes across formats - React-native architecture: Pure state updates ensure UI consistency without external dependencies
Frequently Asked Questions
Where are notes stored in DrawDB's codebase?
Notes live in a React context defined in src/context/NotesContext.jsx, with the hook interface at src/hooks/useNotes.js. This pattern keeps note state accessible throughout the component tree while avoiding prop drilling.
Can notes be exported with the database schema?
Yes. The DBML export utility in src/utils/exportAs/dbml.js serializes notes alongside tables, including their content and canvas coordinates. Notes also appear in PNG exports and shareable URLs through src/utils/exportAs/documentation.js.
How does DrawDB position notes on the canvas?
Each note stores x and y pixel coordinates in its state object. When rendered, the note component applies these as absolute CSS positioning values. Drag operations update these coordinates through updateNote, with the new position persisting immediately.
Is there a default style for new notes?
Yes. src/data/constants.js exports defaultNoteTheme set to #fcf7ac (light yellow). This value applies automatically when creating notes, though users can change individual note colors through the editing interface.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →