supaslidev 0.1.4 → 0.2.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.
Files changed (67) hide show
  1. package/app/app.config.ts +9 -0
  2. package/app/assets/css/main.css +90 -0
  3. package/app/components/AppHeader.vue +429 -0
  4. package/app/components/CreatePresentationDialog.vue +236 -0
  5. package/app/components/EmptyState.vue +37 -0
  6. package/app/components/ImportPresentationDialog.vue +865 -0
  7. package/app/components/PresentationCard.vue +343 -0
  8. package/app/components/PresentationListItem.vue +242 -0
  9. package/app/composables/useServers.ts +148 -0
  10. package/app/layouts/default.vue +49 -0
  11. package/app/pages/index.vue +542 -0
  12. package/dist/cli/index.js +183751 -137
  13. package/dist/config.d.ts +8 -0
  14. package/dist/config.js +16 -0
  15. package/dist/index.d.ts +21 -0
  16. package/dist/index.js +3 -0
  17. package/dist/module.d.ts +6 -0
  18. package/dist/module.js +9168 -0
  19. package/dist/prompt.js +847 -0
  20. package/nuxt.config.ts +53 -0
  21. package/package.json +26 -19
  22. package/server/api/export/[id].post.ts +67 -0
  23. package/server/api/open-editor/[id].post.ts +28 -0
  24. package/server/api/presentations/import.post.ts +139 -0
  25. package/server/api/presentations/index.get.ts +18 -0
  26. package/server/api/presentations/index.post.ts +175 -0
  27. package/server/api/presentations/upload.post.ts +174 -0
  28. package/server/api/presentations/validate.post.ts +14 -0
  29. package/server/api/servers/[id].delete.ts +15 -0
  30. package/server/api/servers/[id].post.ts +17 -0
  31. package/server/api/servers/index.delete.ts +5 -0
  32. package/server/api/servers/index.get.ts +5 -0
  33. package/server/api/servers/stop-all.post.ts +5 -0
  34. package/server/plugins/generate.ts +12 -0
  35. package/server/plugins/shutdown.ts +16 -0
  36. package/server/routes/exports/[...path].get.ts +25 -0
  37. package/server/utils/config.ts +13 -0
  38. package/server/utils/process-manager.ts +119 -0
  39. package/src/cli/commands/create.ts +125 -0
  40. package/src/cli/commands/deploy.ts +90 -0
  41. package/src/cli/commands/dev.ts +116 -0
  42. package/src/cli/commands/export.ts +63 -0
  43. package/src/cli/commands/import.ts +178 -0
  44. package/src/cli/commands/present.ts +111 -0
  45. package/src/cli/index.ts +87 -0
  46. package/src/cli/utils.ts +94 -0
  47. package/src/config.ts +21 -0
  48. package/src/index.ts +2 -0
  49. package/src/module.ts +12 -0
  50. package/src/shared/catalog.ts +94 -0
  51. package/src/shared/copy.ts +28 -0
  52. package/src/shared/index.ts +29 -0
  53. package/{scripts/generate-presentations.mjs → src/shared/presentations.ts} +23 -46
  54. package/src/shared/types.ts +29 -0
  55. package/src/shared/validation.ts +111 -0
  56. package/dist/assets/index-BerY9FcI.js +0 -49
  57. package/dist/assets/index-CVzsY-on.css +0 -1
  58. package/dist/index.html +0 -24
  59. package/server/api.js +0 -1225
  60. /package/{dist → public}/apple-touch-icon.png +0 -0
  61. /package/{dist → public}/favicon-96x96.png +0 -0
  62. /package/{dist → public}/favicon.ico +0 -0
  63. /package/{dist → public}/favicon.svg +0 -0
  64. /package/{dist → public}/site.webmanifest +0 -0
  65. /package/{dist → public}/ssl-logo.png +0 -0
  66. /package/{dist → public}/web-app-manifest-192x192.png +0 -0
  67. /package/{dist → public}/web-app-manifest-512x512.png +0 -0
@@ -0,0 +1,343 @@
1
+ <script setup lang="ts">
2
+ import type { Presentation } from '../composables/useServers';
3
+
4
+ const props = defineProps<{
5
+ presentation: Presentation;
6
+ }>();
7
+
8
+ const {
9
+ isRunning,
10
+ getPort,
11
+ startServer,
12
+ stopServer,
13
+ exportPresentation,
14
+ openInEditor,
15
+ waitForServerReady,
16
+ } = useServers();
17
+
18
+ const emit = defineEmits<{
19
+ exportError: [message: string];
20
+ editorError: [message: string];
21
+ }>();
22
+
23
+ const loading = ref({
24
+ dev: false,
25
+ export: false,
26
+ edit: false,
27
+ });
28
+
29
+ const running = computed(() => isRunning(props.presentation.id));
30
+ const port = computed(() => getPort(props.presentation.id));
31
+
32
+ async function handleDev(event: Event) {
33
+ event.preventDefault();
34
+ event.stopPropagation();
35
+
36
+ if (loading.value.dev) return;
37
+
38
+ loading.value.dev = true;
39
+ try {
40
+ if (running.value) {
41
+ await stopServer(props.presentation.id);
42
+ } else {
43
+ const result = await startServer(props.presentation.id);
44
+ if (result.success && result.port) {
45
+ const isReady = await waitForServerReady(result.port);
46
+ if (isReady) {
47
+ window.open(`http://localhost:${result.port}`, '_blank');
48
+ }
49
+ }
50
+ }
51
+ } finally {
52
+ loading.value.dev = false;
53
+ }
54
+ }
55
+
56
+ async function handleExport(event: Event) {
57
+ event.preventDefault();
58
+ event.stopPropagation();
59
+
60
+ if (loading.value.export) return;
61
+
62
+ loading.value.export = true;
63
+ try {
64
+ const result = await exportPresentation(props.presentation.id);
65
+ if (result.success && result.pdfPath) {
66
+ window.open(result.pdfPath, '_blank');
67
+ } else {
68
+ emit('exportError', result.error || 'Export failed');
69
+ }
70
+ } catch {
71
+ emit('exportError', 'Failed to export presentation');
72
+ } finally {
73
+ loading.value.export = false;
74
+ }
75
+ }
76
+
77
+ async function handleEdit(event: Event) {
78
+ event.preventDefault();
79
+ event.stopPropagation();
80
+
81
+ if (loading.value.edit) return;
82
+
83
+ loading.value.edit = true;
84
+ try {
85
+ const result = await openInEditor(props.presentation.id);
86
+ if (!result.success) {
87
+ emit('editorError', result.error || 'Failed to open editor');
88
+ }
89
+ } catch {
90
+ emit('editorError', 'Failed to open editor');
91
+ } finally {
92
+ loading.value.edit = false;
93
+ }
94
+ }
95
+
96
+ function handleCardClick(event: Event) {
97
+ if (running.value && port.value) {
98
+ event.preventDefault();
99
+ window.open(`http://localhost:${port.value}`, '_blank');
100
+ }
101
+ }
102
+ </script>
103
+
104
+ <template>
105
+ <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"
111
+ class="card terminal-card group transition-all duration-300"
112
+ :class="{ 'terminal-card--running': running, 'cursor-default': !running }"
113
+ :ui="{
114
+ root: 'overflow-hidden',
115
+ header: 'p-0 sm:px-0 bg-[var(--supaslidev-header-bg)]',
116
+ body: 'p-0 sm:p-0',
117
+ }"
118
+ @click="handleCardClick"
119
+ >
120
+ <template #header>
121
+ <div
122
+ class="terminal-header flex items-center px-4 py-3 gap-1.5 border-b border-[var(--supaslidev-border)]"
123
+ >
124
+ <UIcon name="i-lucide-folder" class="chevron-icon" />
125
+ <span class="font-mono text-xs opacity-80">~/{{ presentation.id }}</span>
126
+ <div class="flex-1" />
127
+ <UBadge
128
+ v-if="running"
129
+ color="success"
130
+ variant="subtle"
131
+ size="xs"
132
+ class="terminal-badge terminal-badge--live font-mono uppercase tracking-wider"
133
+ >
134
+ <span class="inline-block w-1.5 h-1.5 rounded-full bg-current mr-1 animate-pulse" />
135
+ live
136
+ </UBadge>
137
+ <UBadge
138
+ v-else
139
+ color="neutral"
140
+ variant="subtle"
141
+ size="xs"
142
+ class="terminal-badge font-mono uppercase tracking-wider"
143
+ >
144
+ idle
145
+ </UBadge>
146
+ </div>
147
+ </template>
148
+
149
+ <div class="terminal-body p-5 space-y-5">
150
+ <div class="terminal-prompt">
151
+ <div class="flex items-start gap-2">
152
+ <span class="text-[var(--ui-success)] font-mono text-sm shrink-0">❯</span>
153
+ <div class="min-w-0">
154
+ <h3
155
+ class="card-title font-mono text-base font-semibold text-[var(--ui-text)] leading-tight truncate"
156
+ >
157
+ {{ presentation.title }}
158
+ </h3>
159
+ <p
160
+ v-if="presentation.description"
161
+ class="font-mono text-xs text-[var(--ui-text-muted)] mt-1 line-clamp-2 leading-relaxed"
162
+ >
163
+ {{ presentation.description }}
164
+ </p>
165
+ </div>
166
+ </div>
167
+ </div>
168
+
169
+ <div class="terminal-meta flex gap-2 flex-wrap items-center">
170
+ <UBadge color="primary" variant="outline" size="xs" class="font-mono text-[10px]">
171
+ --theme={{ presentation.theme }}
172
+ </UBadge>
173
+ <UBadge
174
+ v-if="presentation.duration"
175
+ color="neutral"
176
+ variant="outline"
177
+ size="xs"
178
+ class="font-mono text-[10px]"
179
+ >
180
+ --duration={{ presentation.duration }}
181
+ </UBadge>
182
+ </div>
183
+
184
+ <div class="terminal-actions flex gap-2">
185
+ <UButton
186
+ :color="running ? 'error' : 'success'"
187
+ variant="soft"
188
+ size="sm"
189
+ class="present-button flex-1 terminal-btn font-mono"
190
+ :loading="loading.dev"
191
+ :disabled="loading.dev"
192
+ loading-icon="i-lucide-loader-circle"
193
+ @click="handleDev"
194
+ >
195
+ <template v-if="!loading.dev" #leading>
196
+ <span class="terminal-prompt-symbol">$</span>
197
+ </template>
198
+ {{ running ? 'stop' : 'dev' }}
199
+ </UButton>
200
+
201
+ <UButton
202
+ color="primary"
203
+ variant="soft"
204
+ size="sm"
205
+ class="flex-1 terminal-btn font-mono"
206
+ :loading="loading.export"
207
+ :disabled="loading.export"
208
+ loading-icon="i-lucide-loader-circle"
209
+ @click="handleExport"
210
+ >
211
+ <template v-if="!loading.export" #leading>
212
+ <span class="terminal-prompt-symbol">$</span>
213
+ </template>
214
+ export
215
+ </UButton>
216
+
217
+ <UButton
218
+ color="neutral"
219
+ variant="soft"
220
+ size="sm"
221
+ class="flex-1 terminal-btn font-mono"
222
+ :loading="loading.edit"
223
+ :disabled="loading.edit"
224
+ @click="handleEdit"
225
+ >
226
+ <template #leading>
227
+ <span class="terminal-prompt-symbol">$</span>
228
+ </template>
229
+ edit
230
+ </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
+ </div>
252
+
253
+ <div
254
+ v-if="running && port"
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
+ >
257
+ <span class="text-[var(--ui-success)] animate-pulse">●</span>
258
+ <code class="text-[var(--ui-success)]">localhost:{{ port }}</code>
259
+ <span class="text-[var(--ui-text-dimmed)]">|</span>
260
+ <span class="text-[var(--ui-text-dimmed)]">click to open</span>
261
+ </div>
262
+ </div>
263
+ </UCard>
264
+ </template>
265
+
266
+ <style scoped>
267
+ .terminal-card {
268
+ --terminal-glow-color: rgba(39, 201, 63, 0.2);
269
+ --terminal-glow-strong: rgba(39, 201, 63, 0.4);
270
+ border: 1px solid var(--supaslidev-border);
271
+ background: var(--ui-bg);
272
+ }
273
+
274
+ .terminal-card:hover {
275
+ border-color: var(--ui-border-accented);
276
+ box-shadow:
277
+ 0 0 0 1px var(--ui-border-accented),
278
+ 0 0 30px var(--terminal-glow-color),
279
+ 0 8px 32px rgba(0, 0, 0, 0.12);
280
+ transform: translateY(-2px);
281
+ }
282
+
283
+ .terminal-card--running {
284
+ --terminal-glow-color: rgba(39, 201, 63, 0.25);
285
+ border-color: rgba(39, 201, 63, 0.3);
286
+ }
287
+
288
+ .terminal-card--running:hover {
289
+ box-shadow:
290
+ 0 0 0 1px rgba(39, 201, 63, 0.4),
291
+ 0 0 40px var(--terminal-glow-strong),
292
+ 0 8px 32px rgba(0, 0, 0, 0.15);
293
+ }
294
+
295
+ .chevron-icon {
296
+ width: 16px;
297
+ height: 16px;
298
+ color: var(--ui-text-muted);
299
+ transition: all 0.3s ease;
300
+ }
301
+
302
+ .terminal-card:hover .chevron-icon {
303
+ color: var(--ui-text);
304
+ }
305
+
306
+ .terminal-prompt-symbol {
307
+ color: var(--ui-text-muted);
308
+ margin-right: 2px;
309
+ }
310
+
311
+ .terminal-btn {
312
+ transition: all 0.2s ease;
313
+ }
314
+
315
+ .terminal-btn:hover {
316
+ text-shadow: 0 0 10px currentColor;
317
+ }
318
+
319
+ .terminal-btn:hover .terminal-kbd {
320
+ opacity: 1;
321
+ background: var(--ui-bg-elevated);
322
+ }
323
+
324
+ .terminal-kbd {
325
+ opacity: 0.7;
326
+ transition: opacity 0.2s ease;
327
+ font-size: 9px;
328
+ }
329
+
330
+ .terminal-badge--live {
331
+ animation: pulse-glow 2s ease-in-out infinite;
332
+ }
333
+
334
+ @keyframes pulse-glow {
335
+ 0%,
336
+ 100% {
337
+ box-shadow: 0 0 4px rgba(39, 201, 63, 0.4);
338
+ }
339
+ 50% {
340
+ box-shadow: 0 0 12px rgba(39, 201, 63, 0.6);
341
+ }
342
+ }
343
+ </style>
@@ -0,0 +1,242 @@
1
+ <script setup lang="ts">
2
+ import type { Presentation } from '../composables/useServers';
3
+
4
+ const props = defineProps<{
5
+ presentation: Presentation;
6
+ }>();
7
+
8
+ const {
9
+ isRunning,
10
+ getPort,
11
+ startServer,
12
+ stopServer,
13
+ exportPresentation,
14
+ openInEditor,
15
+ waitForServerReady,
16
+ } = useServers();
17
+
18
+ const emit = defineEmits<{
19
+ exportError: [message: string];
20
+ editorError: [message: string];
21
+ }>();
22
+
23
+ const loading = ref({
24
+ dev: false,
25
+ export: false,
26
+ edit: false,
27
+ });
28
+
29
+ const running = computed(() => isRunning(props.presentation.id));
30
+ const port = computed(() => getPort(props.presentation.id));
31
+
32
+ async function handleDev(event: Event) {
33
+ event.preventDefault();
34
+ event.stopPropagation();
35
+
36
+ if (loading.value.dev) return;
37
+
38
+ loading.value.dev = true;
39
+ try {
40
+ if (running.value) {
41
+ await stopServer(props.presentation.id);
42
+ } else {
43
+ const result = await startServer(props.presentation.id);
44
+ if (result.success && result.port) {
45
+ const isReady = await waitForServerReady(result.port);
46
+ if (isReady) {
47
+ window.open(`http://localhost:${result.port}`, '_blank');
48
+ }
49
+ }
50
+ }
51
+ } finally {
52
+ loading.value.dev = false;
53
+ }
54
+ }
55
+
56
+ async function handleExport(event: Event) {
57
+ event.preventDefault();
58
+ event.stopPropagation();
59
+
60
+ if (loading.value.export) return;
61
+
62
+ loading.value.export = true;
63
+ try {
64
+ const result = await exportPresentation(props.presentation.id);
65
+ if (result.success && result.pdfPath) {
66
+ window.open(result.pdfPath, '_blank');
67
+ } else {
68
+ emit('exportError', result.error || 'Export failed');
69
+ }
70
+ } catch {
71
+ emit('exportError', 'Failed to export presentation');
72
+ } finally {
73
+ loading.value.export = false;
74
+ }
75
+ }
76
+
77
+ async function handleEdit(event: Event) {
78
+ event.preventDefault();
79
+ event.stopPropagation();
80
+
81
+ if (loading.value.edit) return;
82
+
83
+ loading.value.edit = true;
84
+ try {
85
+ const result = await openInEditor(props.presentation.id);
86
+ if (!result.success) {
87
+ emit('editorError', result.error || 'Failed to open editor');
88
+ }
89
+ } catch {
90
+ emit('editorError', 'Failed to open editor');
91
+ } finally {
92
+ loading.value.edit = false;
93
+ }
94
+ }
95
+
96
+ function handleRowClick(event: Event) {
97
+ if (running.value && port.value) {
98
+ event.preventDefault();
99
+ window.open(`http://localhost:${port.value}`, '_blank');
100
+ }
101
+ }
102
+
103
+ function handleOpen(event: Event) {
104
+ event.preventDefault();
105
+ event.stopPropagation();
106
+ if (port.value) {
107
+ window.open(`http://localhost:${port.value}`, '_blank');
108
+ }
109
+ }
110
+ </script>
111
+
112
+ <template>
113
+ <div
114
+ :class="[
115
+ '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 },
117
+ ]"
118
+ @click="handleRowClick"
119
+ >
120
+ <span
121
+ class="status-dot w-2 h-2 rounded-full shrink-0"
122
+ :class="running ? 'bg-[var(--ui-success)] animate-pulse' : 'bg-[var(--ui-text-muted)]'"
123
+ />
124
+
125
+ <span class="text-xs text-[var(--ui-text-muted)] shrink-0">~/{{ presentation.id }}</span>
126
+
127
+ <span class="text-sm text-[var(--ui-text)] truncate min-w-0 flex-1">
128
+ {{ presentation.title }}
129
+ </span>
130
+
131
+ <UBadge
132
+ color="primary"
133
+ variant="outline"
134
+ size="xs"
135
+ class="font-mono text-[10px] hidden sm:inline-flex shrink-0"
136
+ >
137
+ {{ presentation.theme }}
138
+ </UBadge>
139
+
140
+ <UBadge
141
+ v-if="presentation.duration"
142
+ color="neutral"
143
+ variant="outline"
144
+ size="xs"
145
+ class="font-mono text-[10px] hidden sm:inline-flex shrink-0"
146
+ >
147
+ {{ presentation.duration }}
148
+ </UBadge>
149
+
150
+ <div v-if="running && port" class="flex items-center gap-1 shrink-0">
151
+ <a
152
+ :href="`http://localhost:${port}`"
153
+ target="_blank"
154
+ rel="noopener noreferrer"
155
+ class="text-xs text-[var(--ui-success)] hover:underline"
156
+ @click.stop
157
+ >
158
+ :{{ port }}
159
+ </a>
160
+ <UButton
161
+ color="success"
162
+ variant="ghost"
163
+ size="xs"
164
+ icon="i-lucide-external-link"
165
+ class="action-btn"
166
+ title="Open in browser"
167
+ @click="handleOpen"
168
+ />
169
+ </div>
170
+
171
+ <div class="flex items-center gap-1 shrink-0">
172
+ <UButton
173
+ :color="running ? 'error' : 'success'"
174
+ variant="ghost"
175
+ size="xs"
176
+ :icon="loading.dev ? '' : running ? 'i-lucide-square' : 'i-lucide-play'"
177
+ :loading="loading.dev"
178
+ :disabled="loading.dev"
179
+ loading-icon="i-lucide-loader-circle"
180
+ class="action-btn"
181
+ :title="running ? 'Stop server' : 'Start dev server'"
182
+ @click="handleDev"
183
+ />
184
+
185
+ <UButton
186
+ color="primary"
187
+ variant="ghost"
188
+ size="xs"
189
+ :icon="loading.export ? '' : 'i-lucide-download'"
190
+ :loading="loading.export"
191
+ :disabled="loading.export"
192
+ loading-icon="i-lucide-loader-circle"
193
+ class="action-btn"
194
+ title="Export to PDF"
195
+ @click="handleExport"
196
+ />
197
+
198
+ <UButton
199
+ color="neutral"
200
+ variant="ghost"
201
+ size="xs"
202
+ icon="i-lucide-pencil"
203
+ :loading="loading.edit"
204
+ :disabled="loading.edit"
205
+ class="action-btn"
206
+ title="Edit in VS Code"
207
+ @click="handleEdit"
208
+ />
209
+ </div>
210
+ </div>
211
+ </template>
212
+
213
+ <style scoped>
214
+ .list-item-v {
215
+ --terminal-glow-color: rgba(39, 201, 63, 0.2);
216
+ background: var(--ui-bg);
217
+ border: 1px solid var(--supaslidev-border);
218
+ }
219
+
220
+ .list-item-v:hover {
221
+ background: var(--ui-bg-elevated);
222
+ border-color: var(--ui-border-accented);
223
+ }
224
+
225
+ .list-item-v--running {
226
+ border-color: rgba(39, 201, 63, 0.3);
227
+ box-shadow: 0 0 20px var(--terminal-glow-color);
228
+ }
229
+
230
+ .list-item-v--running:hover {
231
+ box-shadow: 0 0 30px rgba(39, 201, 63, 0.3);
232
+ }
233
+
234
+ .action-btn {
235
+ opacity: 0.75;
236
+ transition: opacity 0.2s ease;
237
+ }
238
+
239
+ .list-item-v:hover .action-btn {
240
+ opacity: 1;
241
+ }
242
+ </style>