powerpointproto slides.waow.tech
slides
at main 151 lines 4.7 kB view raw
1import type { Deck, Slide, Element, ShapeType } from "./api"; 2import { resolveDeckBlobs } from "./api"; 3 4// editor state 5export const editorState = $state({ 6 deck: null as Deck | null, 7 currentSlideIndex: 0, 8 selectedElements: new Set<number>(), 9 isPresenting: false, 10 showNotes: false, 11 presentationStartTime: null as number | null, 12 presenterPanelHeight: 80, // default height in pixels 13}); 14 15export const getCurrentSlide = (): Slide | null => { 16 if (!editorState.deck) return null; 17 return editorState.deck.slides[editorState.currentSlideIndex] || null; 18}; 19 20export const addSlide = () => { 21 if (!editorState.deck) return; 22 const newSlide: Slide = { elements: [], notes: "", createdAt: new Date().toISOString() }; 23 editorState.deck.slides.push(newSlide); 24 editorState.currentSlideIndex = editorState.deck.slides.length - 1; 25}; 26 27export const deleteSlide = (index: number) => { 28 if (!editorState.deck || editorState.deck.slides.length <= 1) return; 29 editorState.deck.slides.splice(index, 1); 30 if (editorState.currentSlideIndex >= editorState.deck.slides.length) { 31 editorState.currentSlideIndex = editorState.deck.slides.length - 1; 32 } 33}; 34 35export const addElement = (type: Element["type"], shapeType?: ShapeType) => { 36 const slide = getCurrentSlide(); 37 if (!slide) return; 38 39 const element: Element = { 40 type, 41 x: 100, 42 y: 100, 43 width: type === "text" ? 400 : 200, 44 height: type === "text" ? 100 : 200, 45 content: type === "text" ? "new text" : "", 46 fontSize: type === "text" ? 32 : undefined, 47 color: type === "text" ? "#ffffff" : "#3b82f6", 48 shapeType: type === "shape" ? (shapeType || "rectangle") : undefined, 49 }; 50 51 slide.elements.push(element); 52 editorState.selectedElements = new Set([slide.elements.length - 1]); 53}; 54 55export const deleteSelectedElements = () => { 56 const slide = getCurrentSlide(); 57 if (!slide || editorState.selectedElements.size === 0) return; 58 // Delete in reverse order to preserve indices 59 const indices = Array.from(editorState.selectedElements).sort((a, b) => b - a); 60 for (const index of indices) { 61 slide.elements.splice(index, 1); 62 } 63 editorState.selectedElements = new Set(); 64}; 65 66export const nextSlide = () => { 67 if (!editorState.deck) return; 68 if (editorState.currentSlideIndex < editorState.deck.slides.length - 1) { 69 editorState.currentSlideIndex++; 70 editorState.selectedElements = new Set(); 71 } 72}; 73 74export const prevSlide = () => { 75 if (editorState.currentSlideIndex > 0) { 76 editorState.currentSlideIndex--; 77 editorState.selectedElements = new Set(); 78 } 79}; 80 81export const goToSlide = (index: number) => { 82 if (!editorState.deck) return; 83 if (index >= 0 && index < editorState.deck.slides.length) { 84 editorState.currentSlideIndex = index; 85 editorState.selectedElements = new Set(); 86 } 87}; 88 89export const startPresenting = () => { 90 editorState.isPresenting = true; 91 editorState.presentationStartTime = Date.now(); 92 editorState.selectedElements = new Set(); 93}; 94 95export const stopPresenting = () => { 96 editorState.isPresenting = false; 97 editorState.presentationStartTime = null; 98}; 99 100export const newDeck = (name: string = "untitled") => { 101 const now = new Date().toISOString(); 102 editorState.deck = { 103 name, 104 slides: [{ elements: [], notes: "", createdAt: now }], 105 createdAt: now, 106 }; 107 editorState.currentSlideIndex = 0; 108 editorState.selectedElements = new Set(); 109}; 110 111export const loadDeck = (deck: Deck) => { 112 // Resolve blob refs to displayable URLs if the deck has a repo (DID) 113 const resolvedDeck = deck.repo ? resolveDeckBlobs(deck, deck.repo) : deck; 114 editorState.deck = resolvedDeck; 115 editorState.currentSlideIndex = 0; 116 editorState.selectedElements = new Set(); 117}; 118 119export const closeDeck = () => { 120 editorState.deck = null; 121 editorState.currentSlideIndex = 0; 122 editorState.selectedElements = new Set(); 123 editorState.isPresenting = false; 124}; 125 126// Helper for selection 127export const selectElement = (index: number, addToSelection: boolean = false) => { 128 if (addToSelection) { 129 const newSet = new Set(editorState.selectedElements); 130 if (newSet.has(index)) { 131 newSet.delete(index); 132 } else { 133 newSet.add(index); 134 } 135 editorState.selectedElements = newSet; 136 } else { 137 editorState.selectedElements = new Set([index]); 138 } 139}; 140 141export const clearSelection = () => { 142 editorState.selectedElements = new Set(); 143}; 144 145// Get single selected element (null if none or multiple) 146export const getSelectedElement = () => { 147 const slide = getCurrentSlide(); 148 if (!slide || editorState.selectedElements.size !== 1) return null; 149 const index = Array.from(editorState.selectedElements)[0]; 150 return slide.elements[index] || null; 151};