powerpointproto
slides.waow.tech
slides
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};