How Slide Rendering and Editing Works in OpenMAIC: Architecture and Implementation
OpenMAIC separates read-only slide rendering from interactive editing through a dual-layer architecture where SlideCanvas handles static previews and Canvas manages mutable element states via React hooks and Zustand stores.
The THU-MAIC/OpenMAIC codebase implements a sophisticated slide rendering and editing system that cleanly divides presentation logic from interaction logic. This architecture allows the same rendering primitives to power both thumbnail previews and full-featured editing canvases while maintaining high performance and clean state management.
The Dual-Layer Architecture
OpenMAIC organizes slide functionality into two distinct layers: a read-only renderer for static display and an interactive editor for manipulation.
Read-Only Rendering with SlideCanvas
The read-only rendering layer displays slides without editing UI, powering thumbnails, playback scenes, and static previews. The core entry point is SlideCanvas from @openmaic/renderer, wrapped by SlideRendererProvider located at packages/@openmaic/renderer/src/context.tsx.
The provider supplies the Slide object, optional custom renderers for images and video, and scale/background props. SlideCanvas (source: packages/@openmaic/renderer/src/SlideCanvas.tsx) receives this data, auto-fits the slide to its container via viewportStyles, and renders each element through the internal SlideElement hierarchy.
Custom renderers (renderImage, renderVideo) allow callers to inject thumbnail-specific UI such as play badges or retry buttons instead of default <img> or <video> tags.
// components/slide-renderer/SlideThumbnail.tsx
<SlideCanvas
slide={resolved.slide}
chrome={false}
renderImage={(el, src, defaultContent) => /* custom image UI */}
renderVideo={(el) => /* custom video UI */}
videoInteractive={false}
/>
Interactive Editing with SlideEditor
The interactive editing layer enables users to create, move, resize, rotate, and style slide elements. The entry point is SlideEditor in [components/slide-renderer/Editor/index.tsx](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/slide-renderer/Editor/index.tsx), which renders either the editable Canvas or PlaybackScreenCanvas based on the current StageMode.
// components/slide-renderer/Editor/index.tsx
export function SlideEditor({ mode }: { readonly mode: StageMode }) {
return (
<div className="flex flex-col h-full">
<div className="flex-1 overflow-hidden">
{mode === 'autonomous' ? <Canvas /> : <PlaybackScreenCanvas />}
</div>
</div>
);
}
Canvas (source: [components/slide-renderer/Editor/Canvas/index.tsx](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/slide-renderer/Editor/Canvas/index.tsx)) serves as the heart of the editor, coordinating data flow, selection states, and gesture handling.
Data Flow and State Management
The slide rendering and editing system relies on a multi-tiered state architecture that separates persistent slide data from transient UI states.
SceneContext and Mutable Slide State
Canvas pulls the current slide from the global SceneContext (useSceneSelector) as implemented in [lib/contexts/scene-context.tsx](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/contexts/scene-context.tsx). It mirrors this data in a local mutable elementList to allow drag, rotate, and scale operations to mutate positions without immediately triggering global store updates.
When a user completes a gesture, the modified elementList synchronizes back to the SceneContext. This pattern prevents excessive re-renders during continuous interactions like dragging.
Specialized Stores for UI and Input
Two Zustand stores manage editor-specific state:
-
Canvas Store ([
lib/store/canvas.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/canvas.ts)) tracks UI-only state includingactiveElementIdList(selection), ruler visibility, grid size, and context menu position. -
Keyboard Store ([
lib/store/keyboard.ts](https://github.com/THU-MAIC/OpenMAIC/blob/main/lib/store/keyboard.ts)) captures key presses such as space-bar for panning and modifier keys for shortcuts.
Element Interaction Implementation
OpenMAIC implements slide editing through a collection of specialized React hooks that handle specific gesture types, keeping the Canvas component clean and maintainable.
Gesture Hooks for Drag, Rotate, and Scale
The editor encapsulates complex interaction logic in custom hooks located under components/slide-renderer/Editor/Canvas/hooks/:
useDragElementwatches mouse movements and updates element positions, emitting alignment lines for snapping.useRotateElementrotates elements around their center points using the currentcanvasScalefor mathematically correct transforms.useScaleElementhandles corner-drag resizing, including support for multi-selection scaling viascaleMultiElement.
These hooks receive elementListRef, viewportRef, and canvasScale as parameters, operating on the mutable ref to ensure 60FPS performance during gestures.
Selection and Context Menus
Clicking an element triggers selectElement (implemented in useSelectElement), which updates activeElementIdList in the canvas store. The ContextMenu component (built via the contextmenus function inside Canvas) renders right-click menus offering copy, paste, select-all, ruler toggle, and reset page actions.
Rendering Elements in the Editor
Within the editing canvas, elements pass through a wrapper system that adds interactive handles while delegating actual rendering to type-specific components.
The EditableElement Wrapper
Inside Canvas, the component maps over elementList to render each slide element through EditableElement (source: [components/slide-renderer/Editor/Canvas/EditableElement.tsx](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/slide-renderer/Editor/Canvas/EditableElement.tsx)):
// components/slide-renderer/Editor/Canvas/index.tsx (excerpt)
{elementList.map((element: PPTElement, index: number) =>
!hiddenElementIdList.includes(element.id) ? (
<EditableElement
key={element.id}
elementInfo={element}
elementIndex={index + 1}
isMultiSelect={activeElementIdList.length > 1}
selectElement={selectElement}
openLinkDialog={openLinkDialog}
/>
) : null,
)}
EditableElement determines the concrete renderer based on element.type (text, image, video, shape) and attaches resize handles, rotation controls, and the Operate toolbar for actions like delete and duplicate.
Concrete Element Renderers
Each element type has a dedicated component under components/slide-renderer/components/element/. For example:
- Text: [
BaseTextElement.tsx](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/slide-renderer/components/element/TextElement/BaseTextElement.tsx) handles text rendering and inline editing activation. - Images: [
BaseImageElement.tsx](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/slide-renderer/components/element/ImageElement/BaseImageElement.tsx) manages image display and cropping overlays.
These components receive props for attaching editor handles while implementing their own display logic and event handlers.
Summary
- OpenMAIC uses a dual-layer architecture separating read-only
SlideCanvasfrom interactiveCanvascomponents. - State management is tiered with SceneContext for slide data, canvas store for UI state, and keyboard store for input handling.
- Interaction logic is encapsulated in specialized hooks (
useDragElement,useRotateElement,useScaleElement) that operate on mutable refs for performance. - Element rendering is modular with
EditableElementwrapping type-specific renderers and attaching editing UI. - File locations include
packages/@openmaic/renderer/src/SlideCanvas.tsxfor rendering andcomponents/slide-renderer/Editor/Canvas/index.tsxfor editing logic.
Frequently Asked Questions
How does OpenMAIC handle element selection in the slide editor?
OpenMAIC tracks element selection through the canvas store (lib/store/canvas.ts) which maintains the activeElementIdList array. When a user clicks an element, the selectElement function from useSelectElement updates this store, causing EditableElement to render selection borders and the Operate toolbar. Multi-selection is supported by checking activeElementIdList.length > 1 and rendering MultiSelectOperate handles.
What is the difference between SlideCanvas and Canvas in OpenMAIC?
SlideCanvas (packages/@openmaic/renderer/src/SlideCanvas.tsx) is a read-only renderer used for thumbnails and playback that receives a Slide object and renders it to fit a container. Canvas ([components/slide-renderer/Editor/Canvas/index.tsx](https://github.com/THU-MAIC/OpenMAIC/blob/main/components/slide-renderer/Editor/Canvas/index.tsx)) is the interactive editing surface that manages mutable element states, selection, and gestures through React hooks and Zustand stores.
How does OpenMAIC support custom media rendering in slides?
The SlideRendererProvider accepts optional renderImage and renderVideo props that override default media tags. This allows components like SlideThumbnail to inject custom UI such as play badges for videos or loading states for images while reusing the core SlideCanvas rendering logic. The custom renderers receive the element data, source URL, and default content as parameters.
Which hooks handle element transformations in the OpenMAIC editor?
OpenMAIC uses three primary hooks for transformations located in components/slide-renderer/Editor/Canvas/hooks/: useDragElement for position changes and alignment snapping, useRotateElement for calculating rotation around element centers, and useScaleElement for resizing including multi-selection scenarios. These hooks manipulate the elementListRef directly during gestures to maintain smooth 60FPS performance before committing changes to the global store.
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 →