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.
- package/app/app.config.ts +9 -0
- package/app/assets/css/main.css +90 -0
- package/app/components/AppHeader.vue +429 -0
- package/app/components/CreatePresentationDialog.vue +236 -0
- package/app/components/EmptyState.vue +37 -0
- package/app/components/ImportPresentationDialog.vue +865 -0
- package/app/components/PresentationCard.vue +343 -0
- package/app/components/PresentationListItem.vue +242 -0
- package/app/composables/useServers.ts +148 -0
- package/app/layouts/default.vue +49 -0
- package/app/pages/index.vue +542 -0
- package/dist/cli/index.js +183751 -137
- package/dist/config.d.ts +8 -0
- package/dist/config.js +16 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +3 -0
- package/dist/module.d.ts +6 -0
- package/dist/module.js +9168 -0
- package/dist/prompt.js +847 -0
- package/nuxt.config.ts +53 -0
- package/package.json +26 -19
- package/server/api/export/[id].post.ts +67 -0
- package/server/api/open-editor/[id].post.ts +28 -0
- package/server/api/presentations/import.post.ts +139 -0
- package/server/api/presentations/index.get.ts +18 -0
- package/server/api/presentations/index.post.ts +175 -0
- package/server/api/presentations/upload.post.ts +174 -0
- package/server/api/presentations/validate.post.ts +14 -0
- package/server/api/servers/[id].delete.ts +15 -0
- package/server/api/servers/[id].post.ts +17 -0
- package/server/api/servers/index.delete.ts +5 -0
- package/server/api/servers/index.get.ts +5 -0
- package/server/api/servers/stop-all.post.ts +5 -0
- package/server/plugins/generate.ts +12 -0
- package/server/plugins/shutdown.ts +16 -0
- package/server/routes/exports/[...path].get.ts +25 -0
- package/server/utils/config.ts +13 -0
- package/server/utils/process-manager.ts +119 -0
- package/src/cli/commands/create.ts +125 -0
- package/src/cli/commands/deploy.ts +90 -0
- package/src/cli/commands/dev.ts +116 -0
- package/src/cli/commands/export.ts +63 -0
- package/src/cli/commands/import.ts +178 -0
- package/src/cli/commands/present.ts +111 -0
- package/src/cli/index.ts +87 -0
- package/src/cli/utils.ts +94 -0
- package/src/config.ts +21 -0
- package/src/index.ts +2 -0
- package/src/module.ts +12 -0
- package/src/shared/catalog.ts +94 -0
- package/src/shared/copy.ts +28 -0
- package/src/shared/index.ts +29 -0
- package/{scripts/generate-presentations.mjs → src/shared/presentations.ts} +23 -46
- package/src/shared/types.ts +29 -0
- package/src/shared/validation.ts +111 -0
- package/dist/assets/index-BerY9FcI.js +0 -49
- package/dist/assets/index-CVzsY-on.css +0 -1
- package/dist/index.html +0 -24
- package/server/api.js +0 -1225
- /package/{dist → public}/apple-touch-icon.png +0 -0
- /package/{dist → public}/favicon-96x96.png +0 -0
- /package/{dist → public}/favicon.ico +0 -0
- /package/{dist → public}/favicon.svg +0 -0
- /package/{dist → public}/site.webmanifest +0 -0
- /package/{dist → public}/ssl-logo.png +0 -0
- /package/{dist → public}/web-app-manifest-192x192.png +0 -0
- /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>
|