@textcortex/slidewise 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/dist/__vite-browser-external-DYxpcVy9.js +5 -0
- package/dist/__vite-browser-external-DYxpcVy9.js.map +1 -0
- package/dist/file.svg +1 -0
- package/dist/globe.svg +1 -0
- package/dist/index.mjs +16697 -0
- package/dist/index.mjs.map +1 -0
- package/dist/slidewise.css +1 -0
- package/dist/types/SlidewiseEditor.d.ts +47 -0
- package/dist/types/SlidewiseFileEditor.d.ts +54 -0
- package/dist/types/components/editor/BottomToolbar.d.ts +1 -0
- package/dist/types/components/editor/Canvas.d.ts +1 -0
- package/dist/types/components/editor/Editor.d.ts +8 -0
- package/dist/types/components/editor/ElementView.d.ts +6 -0
- package/dist/types/components/editor/FloatingToolbar.d.ts +6 -0
- package/dist/types/components/editor/GridView.d.ts +1 -0
- package/dist/types/components/editor/PlayMode.d.ts +1 -0
- package/dist/types/components/editor/SelectionFrame.d.ts +8 -0
- package/dist/types/components/editor/SlideRail.d.ts +1 -0
- package/dist/types/components/editor/SlideView.d.ts +5 -0
- package/dist/types/components/editor/TopBar.d.ts +7 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/lib/StoreProvider.d.ts +8 -0
- package/dist/types/lib/fonts.d.ts +9 -0
- package/dist/types/lib/pptx/deckToPptx.d.ts +9 -0
- package/dist/types/lib/pptx/index.d.ts +3 -0
- package/dist/types/lib/pptx/pptxToDeck.d.ts +18 -0
- package/dist/types/lib/pptx/types.d.ts +15 -0
- package/dist/types/lib/pptx/units.d.ts +25 -0
- package/dist/types/lib/schema/migrate.d.ts +25 -0
- package/dist/types/lib/seed.d.ts +2 -0
- package/dist/types/lib/store.d.ts +55 -0
- package/dist/types/lib/types.d.ts +141 -0
- package/dist/window.svg +1 -0
- package/package.json +86 -0
- package/src/App.tsx +261 -0
- package/src/SlidewiseEditor.css +146 -0
- package/src/SlidewiseEditor.tsx +214 -0
- package/src/SlidewiseFileEditor.tsx +242 -0
- package/src/components/editor/BottomToolbar.tsx +216 -0
- package/src/components/editor/Canvas.tsx +467 -0
- package/src/components/editor/Editor.tsx +53 -0
- package/src/components/editor/ElementView.tsx +588 -0
- package/src/components/editor/FloatingToolbar.tsx +729 -0
- package/src/components/editor/GridView.tsx +232 -0
- package/src/components/editor/PlayMode.tsx +260 -0
- package/src/components/editor/SelectionFrame.tsx +241 -0
- package/src/components/editor/SlideRail.tsx +285 -0
- package/src/components/editor/SlideView.tsx +55 -0
- package/src/components/editor/TopBar.tsx +240 -0
- package/src/fonts.css +2 -0
- package/src/index.css +13 -0
- package/src/index.ts +36 -0
- package/src/lib/StoreProvider.tsx +43 -0
- package/src/lib/__tests__/css-scope.test.ts +133 -0
- package/src/lib/fonts.ts +104 -0
- package/src/lib/pptx/__tests__/roundtrip.test.ts +240 -0
- package/src/lib/pptx/deckToPptx.ts +300 -0
- package/src/lib/pptx/index.ts +3 -0
- package/src/lib/pptx/pptxToDeck.ts +1515 -0
- package/src/lib/pptx/types.ts +17 -0
- package/src/lib/pptx/units.ts +32 -0
- package/src/lib/schema/__tests__/migrate.test.ts +70 -0
- package/src/lib/schema/migrate.ts +102 -0
- package/src/lib/seed.ts +777 -0
- package/src/lib/store.ts +384 -0
- package/src/lib/types.ts +185 -0
- package/src/main.tsx +10 -0
- package/src/vite-env.d.ts +3 -0
package/src/lib/store.ts
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { createStore, type StoreApi } from "zustand/vanilla";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import type {
|
|
4
|
+
Deck,
|
|
5
|
+
Slide,
|
|
6
|
+
SlideElement,
|
|
7
|
+
ElementDraft,
|
|
8
|
+
ShapeKind,
|
|
9
|
+
} from "./types";
|
|
10
|
+
import { SLIDE_W, SLIDE_H } from "./types";
|
|
11
|
+
import { migrate } from "./schema/migrate";
|
|
12
|
+
|
|
13
|
+
type Tool =
|
|
14
|
+
| "select"
|
|
15
|
+
| "text"
|
|
16
|
+
| "shape"
|
|
17
|
+
| "line"
|
|
18
|
+
| "image"
|
|
19
|
+
| "table"
|
|
20
|
+
| "formula"
|
|
21
|
+
| "icon"
|
|
22
|
+
| "embed";
|
|
23
|
+
|
|
24
|
+
interface HistorySnapshot {
|
|
25
|
+
deck: Deck;
|
|
26
|
+
currentSlideId: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type Theme = "light" | "dark";
|
|
30
|
+
type View = "editor" | "grid";
|
|
31
|
+
|
|
32
|
+
export interface EditorState {
|
|
33
|
+
deck: Deck;
|
|
34
|
+
currentSlideId: string;
|
|
35
|
+
selectedIds: string[];
|
|
36
|
+
tool: Tool;
|
|
37
|
+
zoom: number;
|
|
38
|
+
fitMode: "fit" | "fill" | "manual";
|
|
39
|
+
playing: boolean;
|
|
40
|
+
theme: Theme;
|
|
41
|
+
view: View;
|
|
42
|
+
history: HistorySnapshot[];
|
|
43
|
+
future: HistorySnapshot[];
|
|
44
|
+
|
|
45
|
+
// selectors
|
|
46
|
+
currentSlide: () => Slide;
|
|
47
|
+
|
|
48
|
+
// actions
|
|
49
|
+
setTool: (t: Tool) => void;
|
|
50
|
+
setTitle: (t: string) => void;
|
|
51
|
+
setZoom: (z: number) => void;
|
|
52
|
+
setFitMode: (f: "fit" | "fill" | "manual") => void;
|
|
53
|
+
selectSlide: (id: string) => void;
|
|
54
|
+
selectElement: (id: string | null, additive?: boolean) => void;
|
|
55
|
+
clearSelection: () => void;
|
|
56
|
+
addSlide: (afterId?: string) => void;
|
|
57
|
+
duplicateSlide: (id: string) => void;
|
|
58
|
+
deleteSlide: (id: string) => void;
|
|
59
|
+
reorderSlide: (id: string, toIndex: number) => void;
|
|
60
|
+
addElement: (partial: ElementDraft) => string;
|
|
61
|
+
updateElement: (id: string, patch: Partial<SlideElement>) => void;
|
|
62
|
+
deleteElement: (id: string) => void;
|
|
63
|
+
bringForward: (id: string) => void;
|
|
64
|
+
sendBackward: (id: string) => void;
|
|
65
|
+
setBackground: (color: string) => void;
|
|
66
|
+
play: () => void;
|
|
67
|
+
stop: () => void;
|
|
68
|
+
undo: () => void;
|
|
69
|
+
redo: () => void;
|
|
70
|
+
pushHistory: () => void;
|
|
71
|
+
setDeck: (deck: Deck) => void;
|
|
72
|
+
setTheme: (t: Theme) => void;
|
|
73
|
+
toggleTheme: () => void;
|
|
74
|
+
setView: (v: View) => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export type EditorStore = StoreApi<EditorState>;
|
|
78
|
+
|
|
79
|
+
const blankSlide = (): Slide => ({
|
|
80
|
+
id: nanoid(8),
|
|
81
|
+
background: "#FFFFFF",
|
|
82
|
+
elements: [],
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
function snap(state: EditorState): HistorySnapshot {
|
|
86
|
+
return {
|
|
87
|
+
deck: structuredClone(state.deck),
|
|
88
|
+
currentSlideId: state.currentSlideId,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createEditorStore(initialDeck: Deck): EditorStore {
|
|
93
|
+
// Run external decks through the migrator so the store always holds a
|
|
94
|
+
// current-shape Deck — even when the host hands us something written by
|
|
95
|
+
// an older Slidewise.
|
|
96
|
+
const deck = migrate(initialDeck);
|
|
97
|
+
const firstSlideId = deck.slides[0]?.id ?? "";
|
|
98
|
+
return createStore<EditorState>((set, get) => ({
|
|
99
|
+
deck,
|
|
100
|
+
currentSlideId: firstSlideId,
|
|
101
|
+
selectedIds: [],
|
|
102
|
+
tool: "select",
|
|
103
|
+
zoom: 0.6,
|
|
104
|
+
fitMode: "fit",
|
|
105
|
+
playing: false,
|
|
106
|
+
theme: "light",
|
|
107
|
+
view: "editor",
|
|
108
|
+
history: [],
|
|
109
|
+
future: [],
|
|
110
|
+
|
|
111
|
+
currentSlide: () => {
|
|
112
|
+
const s = get();
|
|
113
|
+
return (
|
|
114
|
+
s.deck.slides.find((sl) => sl.id === s.currentSlideId) ??
|
|
115
|
+
s.deck.slides[0]
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
pushHistory: () => {
|
|
120
|
+
set((s) => ({
|
|
121
|
+
history: [...s.history, snap(s)].slice(-50),
|
|
122
|
+
future: [],
|
|
123
|
+
}));
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
setTool: (t) => set({ tool: t }),
|
|
127
|
+
setTitle: (t) => {
|
|
128
|
+
set((s) => ({ deck: { ...s.deck, title: t } }));
|
|
129
|
+
},
|
|
130
|
+
setZoom: (z) =>
|
|
131
|
+
set({ zoom: Math.max(0.1, Math.min(4, z)), fitMode: "manual" }),
|
|
132
|
+
setFitMode: (f) => set({ fitMode: f }),
|
|
133
|
+
|
|
134
|
+
selectSlide: (id) => set({ currentSlideId: id, selectedIds: [] }),
|
|
135
|
+
selectElement: (id, additive) =>
|
|
136
|
+
set((s) => {
|
|
137
|
+
if (id == null) return { selectedIds: [] };
|
|
138
|
+
if (additive) {
|
|
139
|
+
const has = s.selectedIds.includes(id);
|
|
140
|
+
return {
|
|
141
|
+
selectedIds: has
|
|
142
|
+
? s.selectedIds.filter((x) => x !== id)
|
|
143
|
+
: [...s.selectedIds, id],
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
return { selectedIds: [id] };
|
|
147
|
+
}),
|
|
148
|
+
clearSelection: () => set({ selectedIds: [] }),
|
|
149
|
+
|
|
150
|
+
addSlide: (afterId) => {
|
|
151
|
+
get().pushHistory();
|
|
152
|
+
set((s) => {
|
|
153
|
+
const slide = blankSlide();
|
|
154
|
+
const slides = [...s.deck.slides];
|
|
155
|
+
const idx = afterId
|
|
156
|
+
? slides.findIndex((sl) => sl.id === afterId)
|
|
157
|
+
: slides.length - 1;
|
|
158
|
+
slides.splice(idx + 1, 0, slide);
|
|
159
|
+
return {
|
|
160
|
+
deck: { ...s.deck, slides },
|
|
161
|
+
currentSlideId: slide.id,
|
|
162
|
+
selectedIds: [],
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
duplicateSlide: (id) => {
|
|
168
|
+
get().pushHistory();
|
|
169
|
+
set((s) => {
|
|
170
|
+
const slides = [...s.deck.slides];
|
|
171
|
+
const idx = slides.findIndex((sl) => sl.id === id);
|
|
172
|
+
if (idx < 0) return s;
|
|
173
|
+
const orig = slides[idx];
|
|
174
|
+
const copy: Slide = {
|
|
175
|
+
...structuredClone(orig),
|
|
176
|
+
id: nanoid(8),
|
|
177
|
+
elements: orig.elements.map((e) => ({ ...e, id: nanoid(8) })),
|
|
178
|
+
};
|
|
179
|
+
slides.splice(idx + 1, 0, copy);
|
|
180
|
+
return {
|
|
181
|
+
deck: { ...s.deck, slides },
|
|
182
|
+
currentSlideId: copy.id,
|
|
183
|
+
};
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
deleteSlide: (id) => {
|
|
188
|
+
if (get().deck.slides.length <= 1) return;
|
|
189
|
+
get().pushHistory();
|
|
190
|
+
set((s) => {
|
|
191
|
+
const slides = s.deck.slides.filter((sl) => sl.id !== id);
|
|
192
|
+
const wasCurrent = s.currentSlideId === id;
|
|
193
|
+
return {
|
|
194
|
+
deck: { ...s.deck, slides },
|
|
195
|
+
currentSlideId: wasCurrent ? slides[0].id : s.currentSlideId,
|
|
196
|
+
selectedIds: [],
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
reorderSlide: (id, toIndex) => {
|
|
202
|
+
get().pushHistory();
|
|
203
|
+
set((s) => {
|
|
204
|
+
const slides = [...s.deck.slides];
|
|
205
|
+
const from = slides.findIndex((sl) => sl.id === id);
|
|
206
|
+
if (from < 0) return s;
|
|
207
|
+
const [moved] = slides.splice(from, 1);
|
|
208
|
+
slides.splice(toIndex, 0, moved);
|
|
209
|
+
return { deck: { ...s.deck, slides } };
|
|
210
|
+
});
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
addElement: (partial) => {
|
|
214
|
+
get().pushHistory();
|
|
215
|
+
const id = nanoid(8);
|
|
216
|
+
set((s) => {
|
|
217
|
+
const slides = s.deck.slides.map((sl) => {
|
|
218
|
+
if (sl.id !== s.currentSlideId) return sl;
|
|
219
|
+
const z = (sl.elements.reduce((m, e) => Math.max(m, e.z), 0) ?? 0) + 1;
|
|
220
|
+
return {
|
|
221
|
+
...sl,
|
|
222
|
+
elements: [...sl.elements, { ...partial, id, z } as SlideElement],
|
|
223
|
+
};
|
|
224
|
+
});
|
|
225
|
+
return { deck: { ...s.deck, slides }, selectedIds: [id] };
|
|
226
|
+
});
|
|
227
|
+
return id;
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
updateElement: (id, patch) => {
|
|
231
|
+
set((s) => {
|
|
232
|
+
const slides = s.deck.slides.map((sl) => {
|
|
233
|
+
if (sl.id !== s.currentSlideId) return sl;
|
|
234
|
+
return {
|
|
235
|
+
...sl,
|
|
236
|
+
elements: sl.elements.map((e) =>
|
|
237
|
+
e.id === id ? ({ ...e, ...patch } as SlideElement) : e
|
|
238
|
+
),
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
return { deck: { ...s.deck, slides } };
|
|
242
|
+
});
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
deleteElement: (id) => {
|
|
246
|
+
get().pushHistory();
|
|
247
|
+
set((s) => {
|
|
248
|
+
const slides = s.deck.slides.map((sl) => {
|
|
249
|
+
if (sl.id !== s.currentSlideId) return sl;
|
|
250
|
+
return { ...sl, elements: sl.elements.filter((e) => e.id !== id) };
|
|
251
|
+
});
|
|
252
|
+
return {
|
|
253
|
+
deck: { ...s.deck, slides },
|
|
254
|
+
selectedIds: s.selectedIds.filter((x) => x !== id),
|
|
255
|
+
};
|
|
256
|
+
});
|
|
257
|
+
},
|
|
258
|
+
|
|
259
|
+
bringForward: (id) => {
|
|
260
|
+
get().pushHistory();
|
|
261
|
+
set((s) => {
|
|
262
|
+
const slides = s.deck.slides.map((sl) => {
|
|
263
|
+
if (sl.id !== s.currentSlideId) return sl;
|
|
264
|
+
const maxZ = sl.elements.reduce((m, e) => Math.max(m, e.z), 0);
|
|
265
|
+
return {
|
|
266
|
+
...sl,
|
|
267
|
+
elements: sl.elements.map((e) =>
|
|
268
|
+
e.id === id ? { ...e, z: maxZ + 1 } : e
|
|
269
|
+
),
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
return { deck: { ...s.deck, slides } };
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
sendBackward: (id) => {
|
|
277
|
+
get().pushHistory();
|
|
278
|
+
set((s) => {
|
|
279
|
+
const slides = s.deck.slides.map((sl) => {
|
|
280
|
+
if (sl.id !== s.currentSlideId) return sl;
|
|
281
|
+
const minZ = sl.elements.reduce((m, e) => Math.min(m, e.z), 0);
|
|
282
|
+
return {
|
|
283
|
+
...sl,
|
|
284
|
+
elements: sl.elements.map((e) =>
|
|
285
|
+
e.id === id ? { ...e, z: minZ - 1 } : e
|
|
286
|
+
),
|
|
287
|
+
};
|
|
288
|
+
});
|
|
289
|
+
return { deck: { ...s.deck, slides } };
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
|
|
293
|
+
setBackground: (color) => {
|
|
294
|
+
get().pushHistory();
|
|
295
|
+
set((s) => {
|
|
296
|
+
const slides = s.deck.slides.map((sl) =>
|
|
297
|
+
sl.id === s.currentSlideId ? { ...sl, background: color } : sl
|
|
298
|
+
);
|
|
299
|
+
return { deck: { ...s.deck, slides } };
|
|
300
|
+
});
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
play: () => set({ playing: true, selectedIds: [] }),
|
|
304
|
+
stop: () => set({ playing: false }),
|
|
305
|
+
|
|
306
|
+
undo: () => {
|
|
307
|
+
set((s) => {
|
|
308
|
+
const last = s.history[s.history.length - 1];
|
|
309
|
+
if (!last) return s;
|
|
310
|
+
const snapshot = snap(s);
|
|
311
|
+
const targetSlide = last.deck.slides.find(
|
|
312
|
+
(sl) => sl.id === last.currentSlideId
|
|
313
|
+
);
|
|
314
|
+
const survivingIds = targetSlide
|
|
315
|
+
? s.selectedIds.filter((id) =>
|
|
316
|
+
targetSlide.elements.some((e) => e.id === id)
|
|
317
|
+
)
|
|
318
|
+
: [];
|
|
319
|
+
return {
|
|
320
|
+
deck: last.deck,
|
|
321
|
+
currentSlideId: last.currentSlideId,
|
|
322
|
+
history: s.history.slice(0, -1),
|
|
323
|
+
future: [...s.future, snapshot].slice(-50),
|
|
324
|
+
selectedIds: survivingIds,
|
|
325
|
+
};
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
redo: () => {
|
|
330
|
+
set((s) => {
|
|
331
|
+
const next = s.future[s.future.length - 1];
|
|
332
|
+
if (!next) return s;
|
|
333
|
+
const snapshot = snap(s);
|
|
334
|
+
const targetSlide = next.deck.slides.find(
|
|
335
|
+
(sl) => sl.id === next.currentSlideId
|
|
336
|
+
);
|
|
337
|
+
const survivingIds = targetSlide
|
|
338
|
+
? s.selectedIds.filter((id) =>
|
|
339
|
+
targetSlide.elements.some((e) => e.id === id)
|
|
340
|
+
)
|
|
341
|
+
: [];
|
|
342
|
+
return {
|
|
343
|
+
deck: next.deck,
|
|
344
|
+
currentSlideId: next.currentSlideId,
|
|
345
|
+
history: [...s.history, snapshot].slice(-50),
|
|
346
|
+
future: s.future.slice(0, -1),
|
|
347
|
+
selectedIds: survivingIds,
|
|
348
|
+
};
|
|
349
|
+
});
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
setDeck: (deck) => {
|
|
353
|
+
const migrated = migrate(deck);
|
|
354
|
+
set({
|
|
355
|
+
deck: migrated,
|
|
356
|
+
currentSlideId: migrated.slides[0]?.id ?? "",
|
|
357
|
+
selectedIds: [],
|
|
358
|
+
history: [],
|
|
359
|
+
future: [],
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
setTheme: (t) => set({ theme: t }),
|
|
364
|
+
|
|
365
|
+
toggleTheme: () => {
|
|
366
|
+
const next = get().theme === "light" ? "dark" : "light";
|
|
367
|
+
get().setTheme(next);
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
setView: (v) => set({ view: v }),
|
|
371
|
+
}));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export type { Tool };
|
|
375
|
+
export { SLIDE_W, SLIDE_H };
|
|
376
|
+
|
|
377
|
+
export const presetShapes: ShapeKind[] = [
|
|
378
|
+
"rect",
|
|
379
|
+
"rounded",
|
|
380
|
+
"circle",
|
|
381
|
+
"triangle",
|
|
382
|
+
"diamond",
|
|
383
|
+
"star",
|
|
384
|
+
];
|
package/src/lib/types.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
export const SLIDE_W = 1920;
|
|
2
|
+
export const SLIDE_H = 1080;
|
|
3
|
+
|
|
4
|
+
export type ElementType =
|
|
5
|
+
| "text"
|
|
6
|
+
| "shape"
|
|
7
|
+
| "image"
|
|
8
|
+
| "line"
|
|
9
|
+
| "table"
|
|
10
|
+
| "icon"
|
|
11
|
+
| "embed"
|
|
12
|
+
| "unknown";
|
|
13
|
+
|
|
14
|
+
export type EnterAnim =
|
|
15
|
+
| "none"
|
|
16
|
+
| "fade"
|
|
17
|
+
| "slide-up"
|
|
18
|
+
| "slide-down"
|
|
19
|
+
| "slide-left"
|
|
20
|
+
| "slide-right"
|
|
21
|
+
| "scale"
|
|
22
|
+
| "draw";
|
|
23
|
+
|
|
24
|
+
export interface BaseElement {
|
|
25
|
+
id: string;
|
|
26
|
+
type: ElementType;
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
w: number;
|
|
30
|
+
h: number;
|
|
31
|
+
rotation: number;
|
|
32
|
+
z: number;
|
|
33
|
+
locked?: boolean;
|
|
34
|
+
enter?: EnterAnim;
|
|
35
|
+
delay?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One styled fragment within a text element. Any field left undefined falls
|
|
40
|
+
* back to the parent TextElement's flat default. Run text may contain "\n" —
|
|
41
|
+
* which becomes a paragraph break in both renderer and PPTX writer.
|
|
42
|
+
*/
|
|
43
|
+
export interface TextRun {
|
|
44
|
+
text: string;
|
|
45
|
+
fontFamily?: string;
|
|
46
|
+
fontSize?: number;
|
|
47
|
+
fontWeight?: number;
|
|
48
|
+
italic?: boolean;
|
|
49
|
+
underline?: boolean;
|
|
50
|
+
strike?: boolean;
|
|
51
|
+
color?: string;
|
|
52
|
+
letterSpacing?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface TextElement extends BaseElement {
|
|
56
|
+
type: "text";
|
|
57
|
+
text: string;
|
|
58
|
+
fontFamily: string;
|
|
59
|
+
fontSize: number;
|
|
60
|
+
fontWeight: number;
|
|
61
|
+
italic: boolean;
|
|
62
|
+
underline: boolean;
|
|
63
|
+
strike: boolean;
|
|
64
|
+
color: string;
|
|
65
|
+
align: "left" | "center" | "right";
|
|
66
|
+
vAlign: "top" | "middle" | "bottom";
|
|
67
|
+
lineHeight: number;
|
|
68
|
+
letterSpacing: number;
|
|
69
|
+
/**
|
|
70
|
+
* Optional rich-text breakdown. When present, the renderer and PPTX writer
|
|
71
|
+
* use these per-run styles; the flat fields above act as defaults for any
|
|
72
|
+
* field a run leaves unset. Editing the text via the contentEditable surface
|
|
73
|
+
* collapses runs back to the flat representation.
|
|
74
|
+
*/
|
|
75
|
+
runs?: TextRun[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type ShapeKind =
|
|
79
|
+
| "rect"
|
|
80
|
+
| "rounded"
|
|
81
|
+
| "circle"
|
|
82
|
+
| "triangle"
|
|
83
|
+
| "star"
|
|
84
|
+
| "diamond";
|
|
85
|
+
|
|
86
|
+
export interface ShapeElement extends BaseElement {
|
|
87
|
+
type: "shape";
|
|
88
|
+
shape: ShapeKind;
|
|
89
|
+
fill: string;
|
|
90
|
+
stroke?: string;
|
|
91
|
+
strokeWidth?: number;
|
|
92
|
+
radius?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ImageElement extends BaseElement {
|
|
96
|
+
type: "image";
|
|
97
|
+
src: string;
|
|
98
|
+
alt?: string;
|
|
99
|
+
fit: "cover" | "contain" | "fill";
|
|
100
|
+
radius?: number;
|
|
101
|
+
/**
|
|
102
|
+
* PPTX <a:srcRect> source crop, expressed as fractions (0..1) of the source
|
|
103
|
+
* image to chop from each edge before placing into the bounding box.
|
|
104
|
+
* Slidewise applies it via background-image / background-position so the
|
|
105
|
+
* final paint matches PowerPoint's "crop + stretch" behaviour.
|
|
106
|
+
*/
|
|
107
|
+
crop?: { l: number; r: number; t: number; b: number };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface LineElement extends BaseElement {
|
|
111
|
+
type: "line";
|
|
112
|
+
stroke: string;
|
|
113
|
+
strokeWidth: number;
|
|
114
|
+
arrow?: boolean;
|
|
115
|
+
dashed?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface TableElement extends BaseElement {
|
|
119
|
+
type: "table";
|
|
120
|
+
rows: string[][];
|
|
121
|
+
headerFill: string;
|
|
122
|
+
rowFill: string;
|
|
123
|
+
textColor: string;
|
|
124
|
+
fontSize: number;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface IconElement extends BaseElement {
|
|
128
|
+
type: "icon";
|
|
129
|
+
icon: string;
|
|
130
|
+
color: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface EmbedElement extends BaseElement {
|
|
134
|
+
type: "embed";
|
|
135
|
+
url: string;
|
|
136
|
+
label: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Opaque OOXML element preserved for round-trip when reading a PPTX
|
|
141
|
+
* containing constructs Slidewise does not yet model (charts, SmartArt,
|
|
142
|
+
* embedded media, etc.). Position/size is editable; the inner XML is
|
|
143
|
+
* re-emitted on write so the user does not lose data.
|
|
144
|
+
*/
|
|
145
|
+
export interface UnknownElement extends BaseElement {
|
|
146
|
+
type: "unknown";
|
|
147
|
+
/** Tag name of the wrapped OOXML node, e.g. "p:graphicFrame". */
|
|
148
|
+
ooxmlTag: string;
|
|
149
|
+
/** Raw OOXML serialized as a string, re-emitted verbatim on save. */
|
|
150
|
+
ooxmlXml: string;
|
|
151
|
+
/** Human-readable label for the editor UI, e.g. "Chart" or "SmartArt". */
|
|
152
|
+
label?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export type SlideElement =
|
|
156
|
+
| TextElement
|
|
157
|
+
| ShapeElement
|
|
158
|
+
| ImageElement
|
|
159
|
+
| LineElement
|
|
160
|
+
| TableElement
|
|
161
|
+
| IconElement
|
|
162
|
+
| EmbedElement
|
|
163
|
+
| UnknownElement;
|
|
164
|
+
|
|
165
|
+
export interface Slide {
|
|
166
|
+
id: string;
|
|
167
|
+
background: string;
|
|
168
|
+
elements: SlideElement[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface Deck {
|
|
172
|
+
/**
|
|
173
|
+
* Schema version this deck conforms to. Stamped by `migrate()` and by
|
|
174
|
+
* internal Deck constructors (seed, PPTX import). Hosts should not set
|
|
175
|
+
* this manually — pass an external deck through `migrate()` and read the
|
|
176
|
+
* version off the result.
|
|
177
|
+
*/
|
|
178
|
+
version: number;
|
|
179
|
+
title: string;
|
|
180
|
+
slides: Slide[];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export type ElementDraft<T extends SlideElement = SlideElement> = T extends SlideElement
|
|
184
|
+
? Omit<T, "id" | "z">
|
|
185
|
+
: never;
|
package/src/main.tsx
ADDED