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 +1 -1
- package/app/components/PresentationCard.vue +30 -30
- package/app/components/PresentationListItem.vue +26 -4
- package/app/composables/useDeployMode.ts +18 -0
- package/app/pages/index.vue +117 -79
- package/dist/cli/index.js +16939 -13045
- package/dist/module.d.ts +3 -3
- package/dist/module.js +2 -2
- package/nuxt.config.ts +1 -0
- package/package.json +6 -6
- package/server/api/presentations/import.post.ts +2 -7
- package/server/api/presentations/upload.post.ts +2 -7
- package/src/cli/commands/deploy.ts +236 -63
- package/src/cli/commands/import.ts +3 -0
- package/src/cli/index.ts +15 -10
- package/src/cli/utils.ts +0 -47
- package/src/module.ts +4 -1
- package/src/shared/catalog.ts +5 -19
- package/src/shared/index.ts +1 -2
package/app/app.config.ts
CHANGED
|
@@ -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(
|
|
97
|
-
if (
|
|
98
|
-
|
|
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
|
-
|
|
107
|
-
:
|
|
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="{
|
|
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(
|
|
97
|
-
if (
|
|
98
|
-
|
|
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
|
-
{
|
|
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
|
+
}
|
package/app/pages/index.vue
CHANGED
|
@@ -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
|
-
|
|
89
|
-
|
|
90
|
+
if (!deployMode.value) {
|
|
91
|
+
startPolling();
|
|
92
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
93
|
+
}
|
|
90
94
|
window.addEventListener('keydown', handleKeydown);
|
|
91
95
|
|
|
92
96
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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="
|
|
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="
|
|
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="
|
|
484
|
+
<UButton class="font-mono" @click="handleCreateCommand">
|
|
447
485
|
<template #leading>
|
|
448
486
|
<span class="opacity-70">$</span>
|
|
449
487
|
</template>
|