supaslidev 0.2.1 → 0.3.1

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/app/app.config.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export default defineAppConfig({
2
2
  ui: {
3
3
  colors: {
4
- primary: 'indigo',
4
+ primary: 'blue',
5
5
  secondary: 'violet',
6
6
  neutral: 'slate',
7
7
  },
@@ -5,6 +5,9 @@ const props = defineProps<{
5
5
  presentation: Presentation;
6
6
  }>();
7
7
 
8
+ const { deployMode, showDeployDemoToast } = useDeployMode();
9
+ const deployBasePath = computed(() => (import.meta.env.BASE_URL || '/').replace(/\/$/, ''));
10
+
8
11
  const {
9
12
  isRunning,
10
13
  getPort,
@@ -33,6 +36,11 @@ async function handleDev(event: Event) {
33
36
  event.preventDefault();
34
37
  event.stopPropagation();
35
38
 
39
+ if (deployMode.value) {
40
+ window.open(`${deployBasePath.value}/presentations/${props.presentation.id}/`, '_blank');
41
+ return;
42
+ }
43
+
36
44
  if (loading.value.dev) return;
37
45
 
38
46
  loading.value.dev = true;
@@ -57,6 +65,11 @@ async function handleExport(event: Event) {
57
65
  event.preventDefault();
58
66
  event.stopPropagation();
59
67
 
68
+ if (deployMode.value) {
69
+ showDeployDemoToast();
70
+ return;
71
+ }
72
+
60
73
  if (loading.value.export) return;
61
74
 
62
75
  loading.value.export = true;
@@ -78,6 +91,11 @@ async function handleEdit(event: Event) {
78
91
  event.preventDefault();
79
92
  event.stopPropagation();
80
93
 
94
+ if (deployMode.value) {
95
+ showDeployDemoToast();
96
+ return;
97
+ }
98
+
81
99
  if (loading.value.edit) return;
82
100
 
83
101
  loading.value.edit = true;
@@ -93,9 +111,10 @@ async function handleEdit(event: Event) {
93
111
  }
94
112
  }
95
113
 
96
- function handleCardClick(event: Event) {
97
- if (running.value && port.value) {
98
- event.preventDefault();
114
+ function handleCardClick() {
115
+ if (deployMode.value) {
116
+ window.open(`${deployBasePath.value}/presentations/${props.presentation.id}/`, '_blank');
117
+ } else if (running.value && port.value) {
99
118
  window.open(`http://localhost:${port.value}`, '_blank');
100
119
  }
101
120
  }
@@ -103,13 +122,14 @@ function handleCardClick(event: Event) {
103
122
 
104
123
  <template>
105
124
  <UCard
106
- :as="running && port ? 'a' : 'div'"
107
- :href="running && port ? `http://localhost:${port}` : undefined"
108
- :target="running && port ? '_blank' : undefined"
109
- :rel="running && port ? 'noopener noreferrer' : undefined"
110
- :title="running && port ? `Open ${presentation.title}` : undefined"
125
+ as="div"
126
+ :title="deployMode || (running && port) ? `Open ${presentation.title}` : undefined"
111
127
  class="card terminal-card group transition-all duration-300"
112
- :class="{ 'terminal-card--running': running, 'cursor-default': !running }"
128
+ :class="{
129
+ 'terminal-card--running': !deployMode && running,
130
+ 'cursor-pointer': deployMode || (running && port),
131
+ 'cursor-default': !deployMode && !running,
132
+ }"
113
133
  :ui="{
114
134
  root: 'overflow-hidden',
115
135
  header: 'p-0 sm:px-0 bg-[var(--supaslidev-header-bg)]',
@@ -228,30 +248,10 @@ function handleCardClick(event: Event) {
228
248
  </template>
229
249
  edit
230
250
  </UButton>
231
-
232
- <!-- TODO: Re-enable when deploy functionality is implemented
233
- <UButton
234
- color="neutral"
235
- variant="soft"
236
- size="sm"
237
- class="flex-1 terminal-btn font-mono"
238
- :loading="loading.deploy"
239
- :disabled="loading.deploy"
240
- @click="handleDeploy"
241
- >
242
- <template #leading>
243
- <span class="terminal-prompt-symbol">$</span>
244
- </template>
245
- deploy
246
- <template #trailing>
247
- <UKbd size="xs" class="terminal-kbd">P</UKbd>
248
- </template>
249
- </UButton>
250
- -->
251
251
  </div>
252
252
 
253
253
  <div
254
- v-if="running && port"
254
+ v-if="!deployMode && running && port"
255
255
  class="terminal-status font-mono text-xs text-[var(--ui-text-muted)] flex items-center gap-2 pt-3 border-t border-[var(--ui-border-muted)]"
256
256
  >
257
257
  <span class="text-[var(--ui-success)] animate-pulse">●</span>
@@ -5,6 +5,9 @@ const props = defineProps<{
5
5
  presentation: Presentation;
6
6
  }>();
7
7
 
8
+ const { deployMode, showDeployDemoToast } = useDeployMode();
9
+ const deployBasePath = computed(() => (import.meta.env.BASE_URL || '/').replace(/\/$/, ''));
10
+
8
11
  const {
9
12
  isRunning,
10
13
  getPort,
@@ -33,6 +36,11 @@ async function handleDev(event: Event) {
33
36
  event.preventDefault();
34
37
  event.stopPropagation();
35
38
 
39
+ if (deployMode.value) {
40
+ window.open(`${deployBasePath.value}/presentations/${props.presentation.id}/`, '_blank');
41
+ return;
42
+ }
43
+
36
44
  if (loading.value.dev) return;
37
45
 
38
46
  loading.value.dev = true;
@@ -57,6 +65,11 @@ async function handleExport(event: Event) {
57
65
  event.preventDefault();
58
66
  event.stopPropagation();
59
67
 
68
+ if (deployMode.value) {
69
+ showDeployDemoToast();
70
+ return;
71
+ }
72
+
60
73
  if (loading.value.export) return;
61
74
 
62
75
  loading.value.export = true;
@@ -78,6 +91,11 @@ async function handleEdit(event: Event) {
78
91
  event.preventDefault();
79
92
  event.stopPropagation();
80
93
 
94
+ if (deployMode.value) {
95
+ showDeployDemoToast();
96
+ return;
97
+ }
98
+
81
99
  if (loading.value.edit) return;
82
100
 
83
101
  loading.value.edit = true;
@@ -93,9 +111,10 @@ async function handleEdit(event: Event) {
93
111
  }
94
112
  }
95
113
 
96
- function handleRowClick(event: Event) {
97
- if (running.value && port.value) {
98
- event.preventDefault();
114
+ function handleRowClick() {
115
+ if (deployMode.value) {
116
+ window.open(`${deployBasePath.value}/presentations/${props.presentation.id}/`, '_blank');
117
+ } else if (running.value && port.value) {
99
118
  window.open(`http://localhost:${port.value}`, '_blank');
100
119
  }
101
120
  }
@@ -113,7 +132,10 @@ function handleOpen(event: Event) {
113
132
  <div
114
133
  :class="[
115
134
  'list-item-v font-mono flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200',
116
- { 'list-item--running': running, 'cursor-pointer': running && port },
135
+ {
136
+ 'list-item--running': !deployMode && running,
137
+ 'cursor-pointer': deployMode || (running && port),
138
+ },
117
139
  ]"
118
140
  @click="handleRowClick"
119
141
  >
@@ -0,0 +1,18 @@
1
+ export function useDeployMode() {
2
+ const config = useRuntimeConfig();
3
+ const deployMode = computed(() => !!config.public.deployMode);
4
+
5
+ function showDeployDemoToast() {
6
+ const toast = useToast();
7
+ toast.add({
8
+ title: 'Dev Mode Only',
9
+ description:
10
+ 'This functionality is only available in dev mode. It is shown here for demo purposes.',
11
+ color: 'warning',
12
+ icon: 'i-lucide-info',
13
+ duration: 0,
14
+ });
15
+ }
16
+
17
+ return { deployMode, showDeployDemoToast };
18
+ }
@@ -11,8 +11,10 @@ const {
11
11
  openInEditor,
12
12
  waitForServerReady,
13
13
  } = useServers();
14
+ const { deployMode, showDeployDemoToast } = useDeployMode();
14
15
  const toast = useToast();
15
16
  const colorMode = useColorMode();
17
+ const deployBasePath = computed(() => (import.meta.env.BASE_URL || '/').replace(/\/$/, ''));
16
18
 
17
19
  function handleExportError(message: string) {
18
20
  toast.add({
@@ -85,13 +87,29 @@ function handleBeforeUnload() {
85
87
  const presentations = ref<Presentation[]>([]);
86
88
 
87
89
  onMounted(async () => {
88
- startPolling();
89
- window.addEventListener('beforeunload', handleBeforeUnload);
90
+ if (!deployMode.value) {
91
+ startPolling();
92
+ window.addEventListener('beforeunload', handleBeforeUnload);
93
+ }
90
94
  window.addEventListener('keydown', handleKeydown);
91
95
 
92
96
  try {
93
- const response = await fetch('/api/presentations');
94
- if (response.ok) {
97
+ let response: Response | undefined;
98
+
99
+ if (deployMode.value) {
100
+ try {
101
+ response = await fetch(`${deployBasePath.value}/presentations.json`);
102
+ } catch {
103
+ // static file not available
104
+ }
105
+ if (!response?.ok) {
106
+ response = await fetch('/api/presentations');
107
+ }
108
+ } else {
109
+ response = await fetch('/api/presentations');
110
+ }
111
+
112
+ if (response?.ok) {
95
113
  presentations.value = await response.json();
96
114
  }
97
115
  } catch {
@@ -100,9 +118,11 @@ onMounted(async () => {
100
118
  });
101
119
 
102
120
  onUnmounted(() => {
103
- stopPolling();
104
- stopAllServers();
105
- window.removeEventListener('beforeunload', handleBeforeUnload);
121
+ if (!deployMode.value) {
122
+ stopPolling();
123
+ stopAllServers();
124
+ window.removeEventListener('beforeunload', handleBeforeUnload);
125
+ }
106
126
  window.removeEventListener('keydown', handleKeydown);
107
127
  });
108
128
  const searchQuery = ref('');
@@ -123,6 +143,10 @@ watch(viewMode, (newMode) => {
123
143
 
124
144
  async function handlePresentCommand(presentation: Presentation) {
125
145
  isCommandPaletteOpen.value = false;
146
+ if (deployMode.value) {
147
+ window.open(`${deployBasePath.value}/presentations/${presentation.id}/`, '_blank');
148
+ return;
149
+ }
126
150
  const result = await startServer(presentation.id);
127
151
  if (result.success && result.port) {
128
152
  const isReady = await waitForServerReady(result.port);
@@ -134,6 +158,10 @@ async function handlePresentCommand(presentation: Presentation) {
134
158
 
135
159
  async function handleExportCommand(presentation: Presentation) {
136
160
  isCommandPaletteOpen.value = false;
161
+ if (deployMode.value) {
162
+ showDeployDemoToast();
163
+ return;
164
+ }
137
165
  const result = await exportPresentation(presentation.id);
138
166
  if (result.success && result.pdfPath) {
139
167
  window.open(result.pdfPath, '_blank');
@@ -144,6 +172,10 @@ async function handleExportCommand(presentation: Presentation) {
144
172
 
145
173
  async function handleEditCommand(presentation: Presentation) {
146
174
  isCommandPaletteOpen.value = false;
175
+ if (deployMode.value) {
176
+ showDeployDemoToast();
177
+ return;
178
+ }
147
179
  const result = await openInEditor(presentation.id);
148
180
  if (!result.success && result.error) {
149
181
  toast.add({
@@ -157,11 +189,17 @@ async function handleEditCommand(presentation: Presentation) {
157
189
 
158
190
  function handleCreateCommand() {
159
191
  isCommandPaletteOpen.value = false;
192
+ if (deployMode.value) {
193
+ showDeployDemoToast();
194
+ }
160
195
  isDialogOpen.value = true;
161
196
  }
162
197
 
163
198
  function handleImportCommand() {
164
199
  isCommandPaletteOpen.value = false;
200
+ if (deployMode.value) {
201
+ showDeployDemoToast();
202
+ }
165
203
  isImportDialogOpen.value = true;
166
204
  }
167
205
 
@@ -277,74 +315,74 @@ function handleExecuteCommand(command: string) {
277
315
  });
278
316
  }
279
317
 
280
- const commandPaletteGroups = computed<CommandPaletteGroup[]>(() => [
281
- {
282
- id: 'actions',
283
- label: 'Actions',
284
- items: [
285
- {
286
- label: 'New',
287
- suffix: 'Create a new presentation',
288
- icon: 'i-lucide-plus',
289
- onSelect: handleCreateCommand,
290
- },
291
- {
292
- label: 'Import',
293
- suffix: 'Import existing Sli.dev presentation(s)',
294
- icon: 'i-lucide-import',
295
- onSelect: handleImportCommand,
296
- },
297
- {
298
- label: 'Toggle theme',
299
- suffix: colorMode.value === 'dark' ? 'Switch to light mode' : 'Switch to dark mode',
300
- icon: colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon',
301
- onSelect: handleToggleThemeCommand,
302
- },
303
- {
304
- label: 'Toggle view',
305
- suffix: viewMode.value === 'grid' ? 'Switch to list layout' : 'Switch to grid layout',
306
- icon: viewMode.value === 'grid' ? 'i-lucide-list' : 'i-lucide-layout-grid',
307
- onSelect: handleToggleViewModeCommand,
308
- },
309
- ],
310
- },
311
- {
312
- id: 'presentations',
313
- label: 'Present',
314
- items: presentations.value.map(
315
- (p): CommandPaletteItem => ({
316
- label: `Present > ${p.title}`,
317
- suffix: 'Start dev server and open in browser',
318
- icon: 'i-lucide-play',
319
- onSelect: () => handlePresentCommand(p),
320
- }),
321
- ),
322
- },
323
- {
324
- id: 'export',
325
- label: 'Export',
326
- items: presentations.value.map(
327
- (p): CommandPaletteItem => ({
328
- label: `Export > ${p.title}`,
329
- suffix: 'Export to PDF',
330
- icon: 'i-lucide-download',
331
- onSelect: () => handleExportCommand(p),
332
- }),
333
- ),
334
- },
335
- {
336
- id: 'edit',
337
- label: 'Edit',
338
- items: presentations.value.map(
339
- (p): CommandPaletteItem => ({
340
- label: `Edit > ${p.title}`,
341
- suffix: 'Open in VS Code',
342
- icon: 'i-lucide-pencil',
343
- onSelect: () => handleEditCommand(p),
344
- }),
345
- ),
346
- },
347
- ]);
318
+ const commandPaletteGroups = computed<CommandPaletteGroup[]>(() => {
319
+ const actionItems: CommandPaletteItem[] = [
320
+ {
321
+ label: 'New',
322
+ suffix: 'Create a new presentation',
323
+ icon: 'i-lucide-plus',
324
+ onSelect: handleCreateCommand,
325
+ },
326
+ {
327
+ label: 'Import',
328
+ suffix: 'Import existing Sli.dev presentation(s)',
329
+ icon: 'i-lucide-import',
330
+ onSelect: handleImportCommand,
331
+ },
332
+ {
333
+ label: 'Toggle theme',
334
+ suffix: colorMode.value === 'dark' ? 'Switch to light mode' : 'Switch to dark mode',
335
+ icon: colorMode.value === 'dark' ? 'i-lucide-sun' : 'i-lucide-moon',
336
+ onSelect: handleToggleThemeCommand,
337
+ },
338
+ {
339
+ label: 'Toggle view',
340
+ suffix: viewMode.value === 'grid' ? 'Switch to list layout' : 'Switch to grid layout',
341
+ icon: viewMode.value === 'grid' ? 'i-lucide-list' : 'i-lucide-layout-grid',
342
+ onSelect: handleToggleViewModeCommand,
343
+ },
344
+ ];
345
+
346
+ return [
347
+ { id: 'actions', label: 'Actions', items: actionItems },
348
+ {
349
+ id: 'presentations',
350
+ label: 'Present',
351
+ items: presentations.value.map(
352
+ (p: Presentation): CommandPaletteItem => ({
353
+ label: `Present > ${p.title}`,
354
+ suffix: deployMode.value ? 'Open presentation' : 'Start dev server and open in browser',
355
+ icon: 'i-lucide-play',
356
+ onSelect: () => handlePresentCommand(p),
357
+ }),
358
+ ),
359
+ },
360
+ {
361
+ id: 'export',
362
+ label: 'Export',
363
+ items: presentations.value.map(
364
+ (p: Presentation): CommandPaletteItem => ({
365
+ label: `Export > ${p.title}`,
366
+ suffix: 'Export to PDF',
367
+ icon: 'i-lucide-download',
368
+ onSelect: () => handleExportCommand(p),
369
+ }),
370
+ ),
371
+ },
372
+ {
373
+ id: 'edit',
374
+ label: 'Edit',
375
+ items: presentations.value.map(
376
+ (p: Presentation): CommandPaletteItem => ({
377
+ label: `Edit > ${p.title}`,
378
+ suffix: 'Open in VS Code',
379
+ icon: 'i-lucide-pencil',
380
+ onSelect: () => handleEditCommand(p),
381
+ }),
382
+ ),
383
+ },
384
+ ];
385
+ });
348
386
 
349
387
  const filteredPresentations = computed(() => {
350
388
  if (!searchQuery.value.trim()) {
@@ -377,7 +415,7 @@ const commandOptions = computed(() => {
377
415
  presentations.value.forEach((p) => {
378
416
  options.push({
379
417
  label: `Present > ${p.title}`,
380
- description: 'Start dev server and open in browser',
418
+ description: deployMode.value ? 'Open presentation' : 'Start dev server and open in browser',
381
419
  onSelect: () => handlePresentCommand(p),
382
420
  });
383
421
  options.push({
@@ -412,13 +450,13 @@ const commandOptions = computed(() => {
412
450
  }}
413
451
  </p>
414
452
  <div class="flex items-center gap-3">
415
- <UButton variant="outline" class="font-mono" @click="isImportDialogOpen = true">
453
+ <UButton variant="outline" class="font-mono" @click="handleImportCommand">
416
454
  <template #leading>
417
455
  <span class="opacity-70">$</span>
418
456
  </template>
419
457
  import
420
458
  </UButton>
421
- <UButton class="btn-new font-mono" @click="isDialogOpen = true">
459
+ <UButton class="btn-new font-mono" @click="handleCreateCommand">
422
460
  <template #leading>
423
461
  <span class="opacity-70">$</span>
424
462
  </template>
@@ -443,7 +481,7 @@ const commandOptions = computed(() => {
443
481
  title="No presentations yet"
444
482
  description="Create your first presentation to get started with supaslidev."
445
483
  >
446
- <UButton class="font-mono" @click="isDialogOpen = true">
484
+ <UButton class="font-mono" @click="handleCreateCommand">
447
485
  <template #leading>
448
486
  <span class="opacity-70">$</span>
449
487
  </template>