Presentations
Designing collaborative presentation software with slides, templates, and views.
Looking for Prezillo’s exact format?
This page covers generic CRDT design patterns for presentation apps. For Prezillo’s complete type definitions, field tables, and data model, see the Prezillo Format Specification.
Document Structure
Document
├── slides: Map<slideId, SlideData>
├── slideOrder: Array<slideId>
├── containers: Map<containerId, Container>
├── masters: Map<masterId, MasterTemplate>
├── styles: Map<styleId, Style>
└── notes: Map<slideId, string | Y.Text>Slides contain references to containers (text boxes, shapes, images). Masters define reusable layouts and styles.
Slide/Container Relationship
interface Slide {
id: string
masterId: string
containerIds: string[]
background?: Background
}
interface Container {
id: string
slideId: string
type: 'text' | 'image' | 'shape'
x: number; y: number
width: number; height: number
contentId?: string // For text: Y.Text ID; for image: blob ID
styleId?: string
zIndex: number
}When duplicating a slide, duplicate its containers too:
function duplicateSlide(slideId: string): string {
const slide = slides.get(slideId)
const newId = nanoid(8)
const newContainerIds: string[] = []
yDoc.transact(() => {
for (const cid of slide.containerIds) {
const container = containers.get(cid)
const newCid = nanoid(8)
containers.set(newCid, { ...container, id: newCid, slideId: newId })
newContainerIds.push(newCid)
}
slides.set(newId, {
...slide, id: newId,
containerIds: newContainerIds
})
const index = slideOrder.toArray().indexOf(slideId)
slideOrder.insert(index + 1, [newId])
})
return newId
}Style Inheritance
Resolve styles by cascading: master → slide → container:
function resolveStyle(container: Container): ResolvedStyle {
const slide = slides.get(container.slideId)
const master = masters.get(slide.masterId)
let style = {}
// 1. Master base style
if (master?.styles?.[container.styleId]) {
style = { ...master.styles[container.styleId] }
}
// 2. Slide overrides
if (slide?.styleOverrides?.[container.styleId]) {
style = { ...style, ...slide.styleOverrides[container.styleId] }
}
// 3. Container overrides
if (container.styleOverrides) {
style = { ...style, ...container.styleOverrides }
}
return style
}Presentation Mode
Presentation state is local (not in CRDT), but can be shared via awareness:
// Broadcast current slide for "follow presenter" mode
awareness.setLocalStateField('presenting', {
slideIndex: currentIndex,
user: getCurrentUser()
})
// Follow mode: sync to presenter's slide
awareness.on('change', () => {
if (followingClientId) {
const state = awareness.getStates().get(followingClientId)
if (state?.presenting) {
goToSlide(state.presenting.slideIndex)
}
}
})Common Mistakes
Slide content directly in slideOrder:
// WRONG: content in array
slideOrder.push([{ title: 'Intro', containers: [...] }])
// CORRECT: content separate
slides.set(id, { title: 'Intro', containerIds: [...] })
slideOrder.push([id])Not cleaning up containers on slide delete:
function deleteSlide(slideId: string) {
const slide = slides.get(slideId)
yDoc.transact(() => {
// Delete containers first
for (const cid of slide.containerIds) {
containers.delete(cid)
}
// Then slide
const index = slideOrder.toArray().indexOf(slideId)
if (index !== -1) slideOrder.delete(index, 1)
slides.delete(slideId)
})
}See Also
- Style Inheritance - Template system
- ID-Based Storage - Slide ordering