supaslidev 0.3.6 → 0.4.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.
@@ -6,6 +6,7 @@ const props = defineProps<{
6
6
  }>();
7
7
 
8
8
  const { deployMode, showDeployDemoToast } = useDeployMode();
9
+ const toast = useToast();
9
10
  const deployBasePath = computed(() => (import.meta.env.BASE_URL || '/').replace(/\/$/, ''));
10
11
 
11
12
  const {
@@ -14,6 +15,7 @@ const {
14
15
  startServer,
15
16
  stopServer,
16
17
  exportPresentation,
18
+ generateThumbnail,
17
19
  openInEditor,
18
20
  waitForServerReady,
19
21
  } = useServers();
@@ -26,6 +28,7 @@ const emit = defineEmits<{
26
28
  const loading = ref({
27
29
  dev: false,
28
30
  export: false,
31
+ thumbnail: false,
29
32
  edit: false,
30
33
  });
31
34
 
@@ -87,6 +90,43 @@ async function handleExport(event: Event) {
87
90
  }
88
91
  }
89
92
 
93
+ async function handleThumbnail(event: Event) {
94
+ event.preventDefault();
95
+ event.stopPropagation();
96
+
97
+ if (deployMode.value) {
98
+ showDeployDemoToast();
99
+ return;
100
+ }
101
+
102
+ if (loading.value.thumbnail) return;
103
+
104
+ loading.value.thumbnail = true;
105
+ try {
106
+ const result = await generateThumbnail(props.presentation.id);
107
+ if (result.success && result.thumbnailPath) {
108
+ toast.add({
109
+ title: 'Thumbnail ready',
110
+ description: `${props.presentation.title} thumbnail generated`,
111
+ color: 'success',
112
+ icon: 'i-lucide-image',
113
+ actions: [
114
+ {
115
+ label: 'Open',
116
+ onClick: () => window.open(result.thumbnailPath, '_blank'),
117
+ },
118
+ ],
119
+ });
120
+ } else {
121
+ emit('exportError', result.error || 'Thumbnail generation failed');
122
+ }
123
+ } catch {
124
+ emit('exportError', 'Failed to generate thumbnail');
125
+ } finally {
126
+ loading.value.thumbnail = false;
127
+ }
128
+ }
129
+
90
130
  async function handleEdit(event: Event) {
91
131
  event.preventDefault();
92
132
  event.stopPropagation();
@@ -166,6 +206,17 @@ function handleCardClick() {
166
206
  </div>
167
207
  </template>
168
208
 
209
+ <div
210
+ v-if="presentation.thumbnail"
211
+ class="thumbnail-preview border-b border-[var(--supaslidev-border)]"
212
+ >
213
+ <img
214
+ :src="presentation.thumbnail"
215
+ :alt="`${presentation.title} first slide`"
216
+ class="w-full h-auto block"
217
+ />
218
+ </div>
219
+
169
220
  <div class="terminal-body p-5 space-y-5">
170
221
  <div class="terminal-prompt">
171
222
  <div class="flex items-start gap-2">
@@ -234,6 +285,22 @@ function handleCardClick() {
234
285
  export
235
286
  </UButton>
236
287
 
288
+ <UButton
289
+ color="info"
290
+ variant="soft"
291
+ size="sm"
292
+ class="flex-1 terminal-btn font-mono"
293
+ :loading="loading.thumbnail"
294
+ :disabled="loading.thumbnail"
295
+ loading-icon="i-lucide-loader-circle"
296
+ @click="handleThumbnail"
297
+ >
298
+ <template v-if="!loading.thumbnail" #leading>
299
+ <span class="terminal-prompt-symbol">$</span>
300
+ </template>
301
+ thumbnail
302
+ </UButton>
303
+
237
304
  <UButton
238
305
  color="neutral"
239
306
  variant="soft"
@@ -6,6 +6,7 @@ const props = defineProps<{
6
6
  }>();
7
7
 
8
8
  const { deployMode, showDeployDemoToast } = useDeployMode();
9
+ const toast = useToast();
9
10
  const deployBasePath = computed(() => (import.meta.env.BASE_URL || '/').replace(/\/$/, ''));
10
11
 
11
12
  const {
@@ -14,6 +15,7 @@ const {
14
15
  startServer,
15
16
  stopServer,
16
17
  exportPresentation,
18
+ generateThumbnail,
17
19
  openInEditor,
18
20
  waitForServerReady,
19
21
  } = useServers();
@@ -26,6 +28,7 @@ const emit = defineEmits<{
26
28
  const loading = ref({
27
29
  dev: false,
28
30
  export: false,
31
+ thumbnail: false,
29
32
  edit: false,
30
33
  });
31
34
 
@@ -87,6 +90,43 @@ async function handleExport(event: Event) {
87
90
  }
88
91
  }
89
92
 
93
+ async function handleThumbnail(event: Event) {
94
+ event.preventDefault();
95
+ event.stopPropagation();
96
+
97
+ if (deployMode.value) {
98
+ showDeployDemoToast();
99
+ return;
100
+ }
101
+
102
+ if (loading.value.thumbnail) return;
103
+
104
+ loading.value.thumbnail = true;
105
+ try {
106
+ const result = await generateThumbnail(props.presentation.id);
107
+ if (result.success && result.thumbnailPath) {
108
+ toast.add({
109
+ title: 'Thumbnail ready',
110
+ description: `${props.presentation.title} thumbnail generated`,
111
+ color: 'success',
112
+ icon: 'i-lucide-image',
113
+ actions: [
114
+ {
115
+ label: 'Open',
116
+ onClick: () => window.open(result.thumbnailPath, '_blank'),
117
+ },
118
+ ],
119
+ });
120
+ } else {
121
+ emit('exportError', result.error || 'Thumbnail generation failed');
122
+ }
123
+ } catch {
124
+ emit('exportError', 'Failed to generate thumbnail');
125
+ } finally {
126
+ loading.value.thumbnail = false;
127
+ }
128
+ }
129
+
90
130
  async function handleEdit(event: Event) {
91
131
  event.preventDefault();
92
132
  event.stopPropagation();
@@ -144,6 +184,13 @@ function handleOpen(event: Event) {
144
184
  :class="running ? 'bg-[var(--ui-success)] animate-pulse' : 'bg-[var(--ui-text-muted)]'"
145
185
  />
146
186
 
187
+ <img
188
+ v-if="presentation.thumbnail"
189
+ :src="presentation.thumbnail"
190
+ :alt="`${presentation.title} first slide`"
191
+ class="w-10 h-6 object-cover rounded shrink-0 border border-[var(--supaslidev-border)]"
192
+ />
193
+
147
194
  <span class="text-xs text-[var(--ui-text-muted)] shrink-0">~/{{ presentation.id }}</span>
148
195
 
149
196
  <span class="text-sm text-[var(--ui-text)] truncate min-w-0 flex-1">
@@ -217,6 +264,19 @@ function handleOpen(event: Event) {
217
264
  @click="handleExport"
218
265
  />
219
266
 
267
+ <UButton
268
+ color="info"
269
+ variant="ghost"
270
+ size="xs"
271
+ :icon="loading.thumbnail ? '' : 'i-lucide-image'"
272
+ :loading="loading.thumbnail"
273
+ :disabled="loading.thumbnail"
274
+ loading-icon="i-lucide-loader-circle"
275
+ class="action-btn"
276
+ title="Generate thumbnail"
277
+ @click="handleThumbnail"
278
+ />
279
+
220
280
  <UButton
221
281
  color="neutral"
222
282
  variant="ghost"
@@ -78,6 +78,18 @@ async function exportPresentation(
78
78
  }
79
79
  }
80
80
 
81
+ async function generateThumbnail(
82
+ presentationId: string,
83
+ ): Promise<{ success: boolean; thumbnailPath?: string; error?: string }> {
84
+ try {
85
+ const response = await fetch(`/api/thumbnail/${presentationId}`, { method: 'POST' });
86
+ const result = await response.json();
87
+ return result;
88
+ } catch {
89
+ return { success: false, error: 'Failed to connect to thumbnail service' };
90
+ }
91
+ }
92
+
81
93
  async function openInEditor(presentationId: string): Promise<{ success: boolean; error?: string }> {
82
94
  try {
83
95
  const response = await fetch(`/api/open-editor/${presentationId}`, { method: 'POST' });
@@ -140,6 +152,7 @@ export function useServers() {
140
152
  startPolling,
141
153
  stopPolling,
142
154
  exportPresentation,
155
+ generateThumbnail,
143
156
  openInEditor,
144
157
  waitForServerReady,
145
158
  };
@@ -8,6 +8,7 @@ const {
8
8
  stopAllServers,
9
9
  startServer,
10
10
  exportPresentation,
11
+ generateThumbnail,
11
12
  openInEditor,
12
13
  waitForServerReady,
13
14
  } = useServers();
@@ -169,6 +170,42 @@ async function handleExportCommand(presentation: Presentation) {
169
170
  }
170
171
  }
171
172
 
173
+ async function handleThumbnailCommand(presentation: Presentation) {
174
+ isCommandPaletteOpen.value = false;
175
+ if (deployMode.value) {
176
+ showDeployDemoToast();
177
+ return;
178
+ }
179
+ toast.add({
180
+ title: 'Generating thumbnail...',
181
+ description: `Creating thumbnail for ${presentation.title}`,
182
+ color: 'info',
183
+ icon: 'i-lucide-image',
184
+ });
185
+ const result = await generateThumbnail(presentation.id);
186
+ if (result.success && result.thumbnailPath) {
187
+ toast.add({
188
+ title: 'Thumbnail ready',
189
+ description: `${presentation.title} thumbnail generated`,
190
+ color: 'success',
191
+ icon: 'i-lucide-image',
192
+ actions: [
193
+ {
194
+ label: 'Open',
195
+ onClick: () => window.open(result.thumbnailPath, '_blank'),
196
+ },
197
+ ],
198
+ });
199
+ } else if (result.error) {
200
+ toast.add({
201
+ title: 'Thumbnail Failed',
202
+ description: result.error,
203
+ color: 'error',
204
+ icon: 'i-lucide-alert-circle',
205
+ });
206
+ }
207
+ }
208
+
172
209
  async function handleEditCommand(presentation: Presentation) {
173
210
  isCommandPaletteOpen.value = false;
174
211
  if (deployMode.value) {
@@ -284,6 +321,30 @@ function handleExecuteCommand(command: string) {
284
321
  return;
285
322
  }
286
323
 
324
+ if (action === 'thumbnail') {
325
+ if (!arg) {
326
+ toast.add({
327
+ title: 'Missing argument',
328
+ description: 'Usage: thumbnail <presentation-name>',
329
+ color: 'warning',
330
+ icon: 'i-lucide-alert-triangle',
331
+ });
332
+ return;
333
+ }
334
+ const presentation = findPresentationByName(arg);
335
+ if (!presentation) {
336
+ toast.add({
337
+ title: 'Presentation not found',
338
+ description: `No presentation found with name "${arg}"`,
339
+ color: 'warning',
340
+ icon: 'i-lucide-alert-triangle',
341
+ });
342
+ return;
343
+ }
344
+ handleThumbnailCommand(presentation);
345
+ return;
346
+ }
347
+
287
348
  if (action === 'edit') {
288
349
  if (!arg) {
289
350
  toast.add({
@@ -310,7 +371,7 @@ function handleExecuteCommand(command: string) {
310
371
 
311
372
  toast.add({
312
373
  title: 'Unknown command',
313
- description: `"${action}" is not a recognized command. Try: new, import, present, export, edit`,
374
+ description: `"${action}" is not a recognized command. Try: new, import, present, export, thumbnail, edit`,
314
375
  color: 'warning',
315
376
  icon: 'i-lucide-alert-triangle',
316
377
  });
@@ -370,6 +431,18 @@ const commandPaletteGroups = computed<CommandPaletteGroup[]>(() => {
370
431
  }),
371
432
  ),
372
433
  },
434
+ {
435
+ id: 'thumbnail',
436
+ label: 'Thumbnail',
437
+ items: presentations.value.map(
438
+ (p: Presentation): CommandPaletteItem => ({
439
+ label: `Thumbnail > ${p.title}`,
440
+ suffix: 'Generate PNG of first slide',
441
+ icon: 'i-lucide-image',
442
+ onSelect: () => handleThumbnailCommand(p),
443
+ }),
444
+ ),
445
+ },
373
446
  {
374
447
  id: 'edit',
375
448
  label: 'Edit',
@@ -424,6 +497,11 @@ const commandOptions = computed(() => {
424
497
  description: 'Export to PDF',
425
498
  onSelect: () => handleExportCommand(p),
426
499
  });
500
+ options.push({
501
+ label: `Thumbnail > ${p.title}`,
502
+ description: 'Generate PNG of first slide',
503
+ onSelect: () => handleThumbnailCommand(p),
504
+ });
427
505
  options.push({
428
506
  label: `Edit > ${p.title}`,
429
507
  description: 'Open in VS Code',