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,542 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CommandPaletteGroup, CommandPaletteItem } from '@nuxt/ui';
|
|
3
|
+
import type { Presentation } from '../composables/useServers';
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
startPolling,
|
|
7
|
+
stopPolling,
|
|
8
|
+
stopAllServers,
|
|
9
|
+
startServer,
|
|
10
|
+
exportPresentation,
|
|
11
|
+
openInEditor,
|
|
12
|
+
waitForServerReady,
|
|
13
|
+
} = useServers();
|
|
14
|
+
const toast = useToast();
|
|
15
|
+
const colorMode = useColorMode();
|
|
16
|
+
|
|
17
|
+
function handleExportError(message: string) {
|
|
18
|
+
toast.add({
|
|
19
|
+
title: 'Export Failed',
|
|
20
|
+
description: message,
|
|
21
|
+
color: 'error',
|
|
22
|
+
icon: 'i-lucide-alert-circle',
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const isDialogOpen = ref(false);
|
|
27
|
+
const isImportDialogOpen = ref(false);
|
|
28
|
+
const isCommandPaletteOpen = ref(false);
|
|
29
|
+
const initialSearchQuery = ref('');
|
|
30
|
+
const appHeaderRef = ref<InstanceType<typeof AppHeader> | null>(null);
|
|
31
|
+
|
|
32
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
33
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
isCommandPaletteOpen.value = !isCommandPaletteOpen.value;
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isCommandPaletteOpen.value || isDialogOpen.value || isImportDialogOpen.value) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const activeElement = document.activeElement;
|
|
44
|
+
const isInputFocused =
|
|
45
|
+
activeElement instanceof HTMLInputElement ||
|
|
46
|
+
activeElement instanceof HTMLTextAreaElement ||
|
|
47
|
+
activeElement?.getAttribute('contenteditable') === 'true';
|
|
48
|
+
|
|
49
|
+
if (isInputFocused) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (event.metaKey || event.ctrlKey || event.altKey) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const isTypingKey = event.key.length === 1 && !event.repeat;
|
|
58
|
+
|
|
59
|
+
if (isTypingKey) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
if (appHeaderRef.value?.inputRef) {
|
|
62
|
+
appHeaderRef.value.inputRef.value = event.key;
|
|
63
|
+
appHeaderRef.value.inputRef.dispatchEvent(new Event('input', { bubbles: true }));
|
|
64
|
+
appHeaderRef.value.focusInput();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handlePresentationCreated(presentation: Presentation) {
|
|
70
|
+
presentations.value = [...presentations.value, presentation].sort((a, b) =>
|
|
71
|
+
a.title.localeCompare(b.title),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handlePresentationImported(imported: Presentation[]) {
|
|
76
|
+
presentations.value = [...presentations.value, ...imported].sort((a, b) =>
|
|
77
|
+
a.title.localeCompare(b.title),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleBeforeUnload() {
|
|
82
|
+
navigator.sendBeacon('/api/servers/stop-all');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const presentations = ref<Presentation[]>([]);
|
|
86
|
+
|
|
87
|
+
onMounted(async () => {
|
|
88
|
+
startPolling();
|
|
89
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
90
|
+
window.addEventListener('keydown', handleKeydown);
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const response = await fetch('/api/presentations');
|
|
94
|
+
if (response.ok) {
|
|
95
|
+
presentations.value = await response.json();
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
// API not available yet
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
onUnmounted(() => {
|
|
103
|
+
stopPolling();
|
|
104
|
+
stopAllServers();
|
|
105
|
+
window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
106
|
+
window.removeEventListener('keydown', handleKeydown);
|
|
107
|
+
});
|
|
108
|
+
const searchQuery = ref('');
|
|
109
|
+
|
|
110
|
+
const VIEW_MODE_STORAGE_KEY = 'supaslidev-view-mode';
|
|
111
|
+
|
|
112
|
+
function loadViewMode(): 'grid' | 'list' {
|
|
113
|
+
if (import.meta.server) return 'grid';
|
|
114
|
+
const saved = localStorage.getItem(VIEW_MODE_STORAGE_KEY);
|
|
115
|
+
return saved === 'list' ? 'list' : 'grid';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const viewMode = ref<'grid' | 'list'>(loadViewMode());
|
|
119
|
+
|
|
120
|
+
watch(viewMode, (newMode) => {
|
|
121
|
+
localStorage.setItem(VIEW_MODE_STORAGE_KEY, newMode);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
async function handlePresentCommand(presentation: Presentation) {
|
|
125
|
+
isCommandPaletteOpen.value = false;
|
|
126
|
+
const result = await startServer(presentation.id);
|
|
127
|
+
if (result.success && result.port) {
|
|
128
|
+
const isReady = await waitForServerReady(result.port);
|
|
129
|
+
if (isReady) {
|
|
130
|
+
window.open(`http://localhost:${result.port}`, '_blank');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function handleExportCommand(presentation: Presentation) {
|
|
136
|
+
isCommandPaletteOpen.value = false;
|
|
137
|
+
const result = await exportPresentation(presentation.id);
|
|
138
|
+
if (result.success && result.pdfPath) {
|
|
139
|
+
window.open(result.pdfPath, '_blank');
|
|
140
|
+
} else if (result.error) {
|
|
141
|
+
handleExportError(result.error);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function handleEditCommand(presentation: Presentation) {
|
|
146
|
+
isCommandPaletteOpen.value = false;
|
|
147
|
+
const result = await openInEditor(presentation.id);
|
|
148
|
+
if (!result.success && result.error) {
|
|
149
|
+
toast.add({
|
|
150
|
+
title: 'Editor Error',
|
|
151
|
+
description: result.error,
|
|
152
|
+
color: 'error',
|
|
153
|
+
icon: 'i-lucide-alert-circle',
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function handleCreateCommand() {
|
|
159
|
+
isCommandPaletteOpen.value = false;
|
|
160
|
+
isDialogOpen.value = true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function handleImportCommand() {
|
|
164
|
+
isCommandPaletteOpen.value = false;
|
|
165
|
+
isImportDialogOpen.value = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function handleToggleThemeCommand() {
|
|
169
|
+
isCommandPaletteOpen.value = false;
|
|
170
|
+
colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function handleToggleViewModeCommand() {
|
|
174
|
+
isCommandPaletteOpen.value = false;
|
|
175
|
+
viewMode.value = viewMode.value === 'grid' ? 'list' : 'grid';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function findPresentationByName(name: string): Presentation | undefined {
|
|
179
|
+
const normalizedName = name.toLowerCase().trim();
|
|
180
|
+
return presentations.value.find(
|
|
181
|
+
(p) => p.id.toLowerCase() === normalizedName || p.title.toLowerCase() === normalizedName,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleExecuteCommand(command: string) {
|
|
186
|
+
const parts = command.trim().split(/\s+/);
|
|
187
|
+
const action = parts[0]?.toLowerCase();
|
|
188
|
+
const arg = parts.slice(1).join(' ');
|
|
189
|
+
|
|
190
|
+
if (action === 'new') {
|
|
191
|
+
handleCreateCommand();
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (action === 'import') {
|
|
196
|
+
handleImportCommand();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (action === 'present') {
|
|
201
|
+
if (!arg) {
|
|
202
|
+
toast.add({
|
|
203
|
+
title: 'Missing argument',
|
|
204
|
+
description: 'Usage: present <presentation-name>',
|
|
205
|
+
color: 'warning',
|
|
206
|
+
icon: 'i-lucide-alert-triangle',
|
|
207
|
+
});
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const presentation = findPresentationByName(arg);
|
|
211
|
+
if (!presentation) {
|
|
212
|
+
toast.add({
|
|
213
|
+
title: 'Presentation not found',
|
|
214
|
+
description: `No presentation found with name "${arg}"`,
|
|
215
|
+
color: 'warning',
|
|
216
|
+
icon: 'i-lucide-alert-triangle',
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
handlePresentCommand(presentation);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (action === 'export') {
|
|
225
|
+
if (!arg) {
|
|
226
|
+
toast.add({
|
|
227
|
+
title: 'Missing argument',
|
|
228
|
+
description: 'Usage: export <presentation-name>',
|
|
229
|
+
color: 'warning',
|
|
230
|
+
icon: 'i-lucide-alert-triangle',
|
|
231
|
+
});
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const presentation = findPresentationByName(arg);
|
|
235
|
+
if (!presentation) {
|
|
236
|
+
toast.add({
|
|
237
|
+
title: 'Presentation not found',
|
|
238
|
+
description: `No presentation found with name "${arg}"`,
|
|
239
|
+
color: 'warning',
|
|
240
|
+
icon: 'i-lucide-alert-triangle',
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
handleExportCommand(presentation);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (action === 'edit') {
|
|
249
|
+
if (!arg) {
|
|
250
|
+
toast.add({
|
|
251
|
+
title: 'Missing argument',
|
|
252
|
+
description: 'Usage: edit <presentation-name>',
|
|
253
|
+
color: 'warning',
|
|
254
|
+
icon: 'i-lucide-alert-triangle',
|
|
255
|
+
});
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const presentation = findPresentationByName(arg);
|
|
259
|
+
if (!presentation) {
|
|
260
|
+
toast.add({
|
|
261
|
+
title: 'Presentation not found',
|
|
262
|
+
description: `No presentation found with name "${arg}"`,
|
|
263
|
+
color: 'warning',
|
|
264
|
+
icon: 'i-lucide-alert-triangle',
|
|
265
|
+
});
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
handleEditCommand(presentation);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
toast.add({
|
|
273
|
+
title: 'Unknown command',
|
|
274
|
+
description: `"${action}" is not a recognized command. Try: new, import, present, export, edit`,
|
|
275
|
+
color: 'warning',
|
|
276
|
+
icon: 'i-lucide-alert-triangle',
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
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
|
+
]);
|
|
348
|
+
|
|
349
|
+
const filteredPresentations = computed(() => {
|
|
350
|
+
if (!searchQuery.value.trim()) {
|
|
351
|
+
return presentations.value;
|
|
352
|
+
}
|
|
353
|
+
const query = searchQuery.value.toLowerCase();
|
|
354
|
+
return presentations.value.filter((p) => p.title.toLowerCase().includes(query));
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
const commandOptions = computed(() => {
|
|
358
|
+
const options: { label: string; description?: string; onSelect: () => void }[] = [
|
|
359
|
+
{ label: 'New', description: 'Create a new presentation', onSelect: handleCreateCommand },
|
|
360
|
+
{
|
|
361
|
+
label: 'Import',
|
|
362
|
+
description: 'Import existing Sli.dev presentation(s)',
|
|
363
|
+
onSelect: handleImportCommand,
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
label: 'Toggle theme',
|
|
367
|
+
description: colorMode.value === 'dark' ? 'Switch to light mode' : 'Switch to dark mode',
|
|
368
|
+
onSelect: handleToggleThemeCommand,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
label: 'Toggle view',
|
|
372
|
+
description: viewMode.value === 'grid' ? 'Switch to list layout' : 'Switch to grid layout',
|
|
373
|
+
onSelect: handleToggleViewModeCommand,
|
|
374
|
+
},
|
|
375
|
+
];
|
|
376
|
+
|
|
377
|
+
presentations.value.forEach((p) => {
|
|
378
|
+
options.push({
|
|
379
|
+
label: `Present > ${p.title}`,
|
|
380
|
+
description: 'Start dev server and open in browser',
|
|
381
|
+
onSelect: () => handlePresentCommand(p),
|
|
382
|
+
});
|
|
383
|
+
options.push({
|
|
384
|
+
label: `Export > ${p.title}`,
|
|
385
|
+
description: 'Export to PDF',
|
|
386
|
+
onSelect: () => handleExportCommand(p),
|
|
387
|
+
});
|
|
388
|
+
options.push({
|
|
389
|
+
label: `Edit > ${p.title}`,
|
|
390
|
+
description: 'Open in VS Code',
|
|
391
|
+
onSelect: () => handleEditCommand(p),
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return options;
|
|
396
|
+
});
|
|
397
|
+
</script>
|
|
398
|
+
|
|
399
|
+
<template>
|
|
400
|
+
<div>
|
|
401
|
+
<AppHeader
|
|
402
|
+
ref="appHeaderRef"
|
|
403
|
+
:commands="commandOptions"
|
|
404
|
+
@open-command-palette="isCommandPaletteOpen = true"
|
|
405
|
+
@execute-command="handleExecuteCommand"
|
|
406
|
+
/>
|
|
407
|
+
|
|
408
|
+
<div class="flex items-center justify-between mb-6">
|
|
409
|
+
<p class="text-muted font-mono text-sm">
|
|
410
|
+
{{ filteredPresentations.length }} presentation{{
|
|
411
|
+
filteredPresentations.length !== 1 ? 's' : ''
|
|
412
|
+
}}
|
|
413
|
+
</p>
|
|
414
|
+
<div class="flex items-center gap-3">
|
|
415
|
+
<UButton variant="outline" class="font-mono" @click="isImportDialogOpen = true">
|
|
416
|
+
<template #leading>
|
|
417
|
+
<span class="opacity-70">$</span>
|
|
418
|
+
</template>
|
|
419
|
+
import
|
|
420
|
+
</UButton>
|
|
421
|
+
<UButton class="btn-new font-mono" @click="isDialogOpen = true">
|
|
422
|
+
<template #leading>
|
|
423
|
+
<span class="opacity-70">$</span>
|
|
424
|
+
</template>
|
|
425
|
+
new
|
|
426
|
+
</UButton>
|
|
427
|
+
</div>
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<div class="mb-6 flex justify-center">
|
|
431
|
+
<UInput
|
|
432
|
+
v-model="searchQuery"
|
|
433
|
+
icon="i-lucide-search"
|
|
434
|
+
placeholder="Search presentations by title..."
|
|
435
|
+
class="filter-input max-w-md w-full"
|
|
436
|
+
size="lg"
|
|
437
|
+
/>
|
|
438
|
+
</div>
|
|
439
|
+
|
|
440
|
+
<template v-if="presentations.length === 0">
|
|
441
|
+
<EmptyState
|
|
442
|
+
icon="i-lucide-presentation"
|
|
443
|
+
title="No presentations yet"
|
|
444
|
+
description="Create your first presentation to get started with supaslidev."
|
|
445
|
+
>
|
|
446
|
+
<UButton class="font-mono" @click="isDialogOpen = true">
|
|
447
|
+
<template #leading>
|
|
448
|
+
<span class="opacity-70">$</span>
|
|
449
|
+
</template>
|
|
450
|
+
create presentation
|
|
451
|
+
</UButton>
|
|
452
|
+
</EmptyState>
|
|
453
|
+
</template>
|
|
454
|
+
|
|
455
|
+
<template v-else>
|
|
456
|
+
<div v-if="filteredPresentations.length > 0" class="view-container">
|
|
457
|
+
<Transition name="view-fade" mode="out-in">
|
|
458
|
+
<div
|
|
459
|
+
v-if="viewMode === 'grid'"
|
|
460
|
+
key="grid"
|
|
461
|
+
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"
|
|
462
|
+
>
|
|
463
|
+
<TransitionGroup name="card" appear>
|
|
464
|
+
<PresentationCard
|
|
465
|
+
v-for="presentation in filteredPresentations"
|
|
466
|
+
:key="presentation.id"
|
|
467
|
+
:presentation="presentation"
|
|
468
|
+
@export-error="handleExportError"
|
|
469
|
+
/>
|
|
470
|
+
</TransitionGroup>
|
|
471
|
+
</div>
|
|
472
|
+
<div v-else key="list" class="flex flex-col gap-2">
|
|
473
|
+
<TransitionGroup name="list" appear>
|
|
474
|
+
<PresentationListItem
|
|
475
|
+
v-for="presentation in filteredPresentations"
|
|
476
|
+
:key="presentation.id"
|
|
477
|
+
:presentation="presentation"
|
|
478
|
+
@export-error="handleExportError"
|
|
479
|
+
/>
|
|
480
|
+
</TransitionGroup>
|
|
481
|
+
</div>
|
|
482
|
+
</Transition>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<EmptyState
|
|
486
|
+
v-else
|
|
487
|
+
icon="i-lucide-search-x"
|
|
488
|
+
title="No presentations found"
|
|
489
|
+
description="Try adjusting your search query."
|
|
490
|
+
>
|
|
491
|
+
<UButton variant="soft" class="font-mono" @click="searchQuery = ''">
|
|
492
|
+
<template #leading>
|
|
493
|
+
<span class="opacity-70">$</span>
|
|
494
|
+
</template>
|
|
495
|
+
clear search
|
|
496
|
+
</UButton>
|
|
497
|
+
</EmptyState>
|
|
498
|
+
</template>
|
|
499
|
+
|
|
500
|
+
<CreatePresentationDialog
|
|
501
|
+
:open="isDialogOpen"
|
|
502
|
+
@close="isDialogOpen = false"
|
|
503
|
+
@created="handlePresentationCreated"
|
|
504
|
+
/>
|
|
505
|
+
|
|
506
|
+
<ImportPresentationDialog
|
|
507
|
+
:open="isImportDialogOpen"
|
|
508
|
+
@close="isImportDialogOpen = false"
|
|
509
|
+
@imported="handlePresentationImported"
|
|
510
|
+
/>
|
|
511
|
+
|
|
512
|
+
<UModal v-model:open="isCommandPaletteOpen" @after-leave="initialSearchQuery = ''">
|
|
513
|
+
<template #body>
|
|
514
|
+
<UCommandPalette
|
|
515
|
+
v-model:search-term="initialSearchQuery"
|
|
516
|
+
:groups="commandPaletteGroups"
|
|
517
|
+
:fuse="{
|
|
518
|
+
fuseOptions: {
|
|
519
|
+
threshold: 0.4,
|
|
520
|
+
keys: ['label', 'suffix'],
|
|
521
|
+
ignoreLocation: true,
|
|
522
|
+
},
|
|
523
|
+
matchAllWhenSearchEmpty: true,
|
|
524
|
+
}"
|
|
525
|
+
placeholder="Search commands..."
|
|
526
|
+
class="h-80"
|
|
527
|
+
/>
|
|
528
|
+
</template>
|
|
529
|
+
</UModal>
|
|
530
|
+
</div>
|
|
531
|
+
</template>
|
|
532
|
+
|
|
533
|
+
<style scoped>
|
|
534
|
+
.filter-input :deep(input) {
|
|
535
|
+
border: 1px solid var(--supaslidev-border);
|
|
536
|
+
border-radius: 0.5rem;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.filter-input :deep(input:focus) {
|
|
540
|
+
border-color: var(--ui-border-accented);
|
|
541
|
+
}
|
|
542
|
+
</style>
|