|
1 | 1 | <script lang="ts"> |
2 | 2 | /** Project dashboard orchestrator. */ |
3 | | - import { push } from "svelte-spa-router"; |
4 | 3 | import { Command as CommandIcon, Plus, Upload } from "@lucide/svelte"; |
5 | 4 | import Button from "$lib/ui/Button.svelte"; |
6 | 5 | import TitleBar from "$lib/components/TitleBar.svelte"; |
|
10 | 9 | import DashboardStates from "@/features/dashboard/DashboardStates.svelte"; |
11 | 10 | import ProjectGrid from "@/features/dashboard/ProjectGrid.svelte"; |
12 | 11 | import ConfirmDialog from "$lib/ui/ConfirmDialog.svelte"; |
13 | | - import { |
14 | | - createProjectMutation, |
15 | | - duplicateProjectMutation, |
16 | | - deleteProjectMutation, |
17 | | - exportProjectMutation, |
18 | | - importProjectMutation, |
19 | | - renameProjectMutation, |
20 | | - } from "$lib/queries"; |
21 | 12 | import { createRenameState } from "$lib/lib/rename-state.svelte"; |
22 | 13 | import { isModKey, isTypingTarget } from "$lib/lib/keyboard"; |
23 | 14 | import { modKeyLabel } from "$lib/lib/platform"; |
24 | 15 | import { setWindowTitle } from "$lib/lib/window-title"; |
25 | | - import { |
26 | | - subscribeToAppMenu, |
27 | | - type AppMenuHandlers, |
28 | | - } from "$lib/lib/app-menu.svelte"; |
29 | 16 | import { |
30 | 17 | ui, |
31 | 18 | applyUiTheme, |
32 | 19 | setIsCommandOpen, |
33 | 20 | setIsShortcutsOpen, |
34 | | - toggleTheme, |
35 | 21 | } from "$lib/stores/ui-state.svelte"; |
36 | | - import { api } from "$lib/lib/tauri-api"; |
37 | 22 | import { createDashboardState } from "./dashboard-state.svelte"; |
| 23 | + import { |
| 24 | + createDashboardActions, |
| 25 | + installDashboardMenu, |
| 26 | + } from "./dashboard-actions"; |
38 | 27 | import { provideProjectCardActions } from "./project-card-actions.svelte"; |
39 | | - import { notify } from "$lib/lib/toast"; |
40 | 28 |
|
41 | 29 | const st = createDashboardState(); |
42 | 30 | const listQuery = st.query; |
43 | 31 | const projects = $derived(st.projects); |
44 | | - const createMutation = createProjectMutation(); |
45 | | - const duplicateMutation = duplicateProjectMutation(); |
46 | | - const deleteMutation = deleteProjectMutation(); |
47 | | - const exportMutation = exportProjectMutation(); |
48 | | - const importMutation = importProjectMutation(); |
49 | | - const renameMutation = renameProjectMutation(); |
50 | | -
|
| 32 | + const actions = createDashboardActions(st); |
51 | 33 |
|
52 | 34 | // Native + document window title for the dashboard route. |
53 | 35 | $effect(() => { |
|
69 | 51 | return () => window.removeEventListener("keydown", onKey); |
70 | 52 | }); |
71 | 53 |
|
72 | | - const handleOpen = (id: string) => void push(`/editor/${id}`); |
73 | | - const handleDuplicate = (id: string) => duplicateMutation.mutate(id); |
74 | | - const handleExport = (id: string) => exportMutation.mutate(id); |
75 | | - const handleDelete = (id: string, name: string) => (st.deleteTarget = { id, name }); |
76 | | -
|
77 | | - function handleConfirmDelete() { |
78 | | - if (st.deleteTarget) { |
79 | | - deleteMutation.mutate(st.deleteTarget.id); |
80 | | - st.deleteTarget = null; |
81 | | - } |
82 | | - } |
83 | | -
|
84 | 54 | const rename = createRenameState(async (id: string, name: string) => { |
85 | | - await renameMutation.mutateAsync({ |
| 55 | + await actions.renameMutation.mutateAsync({ |
86 | 56 | projectId: id, |
87 | 57 | name: name || "Untitled Presentation", |
88 | 58 | }); |
|
98 | 68 | return rename.value; |
99 | 69 | }, |
100 | 70 | get duplicateBusy() { |
101 | | - return duplicateMutation.isPending; |
| 71 | + return actions.duplicateMutation.isPending; |
102 | 72 | }, |
103 | 73 | get commitBusy() { |
104 | | - return renameMutation.isPending; |
| 74 | + return actions.renameMutation.isPending; |
105 | 75 | }, |
106 | 76 | setRenameValue: (v: string) => (rename.value = v), |
107 | 77 | commitRename: rename.commit, |
108 | 78 | cancelRename: rename.cancel, |
109 | 79 | startRename: rename.start, |
110 | | - open: handleOpen, |
111 | | - duplicate: handleDuplicate, |
112 | | - exportProject: handleExport, |
113 | | - remove: handleDelete, |
| 80 | + open: actions.open, |
| 81 | + duplicate: actions.duplicate, |
| 82 | + exportProject: actions.exportProject, |
| 83 | + remove: actions.requestDelete, |
114 | 84 | }); |
115 | 85 |
|
116 | | - async function handleCreate() { |
117 | | - try { |
118 | | - const project = await createMutation.mutateAsync( |
119 | | - st.newName.trim() || "Untitled Presentation", |
120 | | - ); |
121 | | - if (st.selectedTheme && st.selectedTheme !== "dark-plus" && st.selectedTheme !== project.theme) { |
122 | | - try { |
123 | | - await api.updateProjectTheme(project.id, st.selectedTheme); |
124 | | - } catch { |
125 | | - // The deck itself exists and is ready to edit even if its optional |
126 | | - // theme update fails, so do not strand the user on the dashboard. |
127 | | - notify.error("Presentation created, but the selected theme could not be applied"); |
128 | | - } |
129 | | - } |
130 | | - st.resetForm(); |
131 | | - void push(`/editor/${project.id}`); |
132 | | - } catch { |
133 | | - // The mutation presents the creation error. |
134 | | - } |
135 | | - } |
136 | | -
|
137 | | - async function handleImport() { |
138 | | - try { |
139 | | - const project = await importMutation.mutateAsync(); |
140 | | - void push(`/editor/${project.id}`); |
141 | | - } catch { |
142 | | - /* the mutation owns the error toast */ |
143 | | - } |
144 | | - } |
145 | | -
|
146 | | - const menuHandlers: AppMenuHandlers = { |
147 | | - "menu://new-project": () => (st.creating = true), |
148 | | - "menu://open-dashboard": () => void push("/"), |
149 | | - "menu://command-palette": () => setIsCommandOpen(true), |
150 | | - "menu://toggle-theme": () => toggleTheme(), |
151 | | - "menu://shortcuts-app": () => setIsShortcutsOpen(true), |
152 | | - "menu://shortcuts-help": () => setIsShortcutsOpen(true), |
153 | | - "menu://export": () => { |
154 | | - const first = projects[0]; |
155 | | - if (first) exportMutation.mutate(first.id); |
156 | | - }, |
157 | | - }; |
158 | | - subscribeToAppMenu(() => menuHandlers); |
| 86 | + installDashboardMenu(st, actions); |
159 | 87 |
|
160 | 88 | const mod = modKeyLabel(); |
161 | 89 | </script> |
|
182 | 110 | variant="outline" |
183 | 111 | size="sm" |
184 | 112 | class="gap-1.5" |
185 | | - onclick={() => void handleImport()} |
186 | | - disabled={importMutation.isPending} |
| 113 | + onclick={() => void actions.importProject()} |
| 114 | + disabled={actions.importMutation.isPending} |
187 | 115 | > |
188 | 116 | <Upload class="h-4 w-4" />Import |
189 | 117 | </Button> |
|
202 | 130 | onNameChange={(v) => (st.newName = v)} |
203 | 131 | selectedTheme={st.selectedTheme} |
204 | 132 | onThemeChange={(t) => (st.selectedTheme = t)} |
205 | | - onCreate={() => void handleCreate()} |
206 | | - isPending={createMutation.isPending} |
| 133 | + onCreate={() => void actions.create()} |
| 134 | + isPending={actions.createMutation.isPending} |
207 | 135 | isStandalone={true} |
208 | 136 | /> |
209 | 137 | </div> |
|
215 | 143 | error={(listQuery.error as Error | null) ?? null} |
216 | 144 | projectCount={projects.length} |
217 | 145 | onCreate={() => (st.creating = true)} |
218 | | - onImport={() => void handleImport()} |
| 146 | + onImport={() => void actions.importProject()} |
219 | 147 | showEmptyState={!st.creating} |
220 | 148 | > |
221 | 149 | {#if !listQuery.isLoading && !listQuery.isError && projects.length > 0} |
|
231 | 159 | description="This cannot be undone." |
232 | 160 | confirmLabel="Delete" |
233 | 161 | destructive |
234 | | - onConfirm={handleConfirmDelete} |
| 162 | + onConfirm={actions.confirmDelete} |
235 | 163 | onCancel={() => (st.deleteTarget = null)} |
236 | 164 | /> |
237 | 165 | </div> |
0 commit comments