|
1 | 1 | <script lang="ts"> |
2 | | - import { onDestroy } from "svelte"; |
| 2 | + import { onDestroy, untrack } from "svelte"; |
3 | 3 | import DOMPurify from "dompurify"; |
4 | 4 | import type { |
5 | 5 | AttachmentDropPointSessionSummary, |
|
65 | 65 | let pollTimer: number | null = null; |
66 | 66 | let countdownTimer: number | null = null; |
67 | 67 | let remoteRunId = 0; |
68 | | - let pollInFlight = false; |
69 | 68 |
|
70 | 69 | let isRecorded = $derived(attachments.length > 0); |
71 | 70 | let hasSelectedFiles = $derived(selectedFiles.length > 0); |
|
96 | 95 |
|
97 | 96 | const imageAttachments = attachments.filter(isImageAttachment); |
98 | 97 | const imagePaths = new Set(imageAttachments.map((file) => file.path)); |
99 | | - previewUrls = Object.fromEntries( |
100 | | - Object.entries(previewUrls).filter(([path]) => imagePaths.has(path)), |
| 98 | + const currentPreviewUrls = untrack(() => previewUrls); |
| 99 | + const retainedPreviewUrls = Object.fromEntries( |
| 100 | + Object.entries(currentPreviewUrls).filter(([path]) => imagePaths.has(path)), |
101 | 101 | ); |
| 102 | + if (!samePreviewUrls(currentPreviewUrls, retainedPreviewUrls)) { |
| 103 | + previewUrls = retainedPreviewUrls; |
| 104 | + } |
102 | 105 | for (const file of imageAttachments) { |
103 | | - if (!previewUrls[file.path]) void loadPreview(previewLoader, file, runId); |
| 106 | + if (!currentPreviewUrls[file.path]) void loadPreview(previewLoader, file, runId); |
104 | 107 | } |
105 | 108 | }); |
106 | 109 |
|
| 110 | + function samePreviewUrls( |
| 111 | + left: Record<string, string>, |
| 112 | + right: Record<string, string>, |
| 113 | + ): boolean { |
| 114 | + const leftEntries = Object.entries(left); |
| 115 | + return ( |
| 116 | + leftEntries.length === Object.keys(right).length && |
| 117 | + leftEntries.every(([path, url]) => right[path] === url) |
| 118 | + ); |
| 119 | + } |
| 120 | +
|
107 | 121 | function normalizedContentType(contentType: string): string { |
108 | 122 | return contentType.split(";")[0]?.trim().toLowerCase() ?? ""; |
109 | 123 | } |
|
150 | 164 |
|
151 | 165 | async function confirmRemoveFile(file: AttachmentState) { |
152 | 166 | if (!onremovefile) return; |
153 | | - try { |
154 | | - const ok = await confirmDialog(`Remove ${file.filename}?`, { |
155 | | - title: "Remove attachment", |
156 | | - kind: "warning", |
157 | | - okLabel: "Remove", |
158 | | - cancelLabel: "Cancel", |
159 | | - }); |
160 | | - if (ok) onremovefile(file.path); |
161 | | - } catch (e) { |
162 | | - remoteError = String(e); |
163 | | - } |
| 167 | + const ok = await confirmDialog(`Remove ${file.filename}?`, { |
| 168 | + title: "Remove attachment", |
| 169 | + kind: "warning", |
| 170 | + okLabel: "Remove", |
| 171 | + cancelLabel: "Cancel", |
| 172 | + }); |
| 173 | + if (ok) onremovefile(file.path); |
164 | 174 | } |
165 | 175 |
|
166 | 176 | async function confirmClearAll() { |
167 | 177 | if (!onclear) return; |
168 | | - try { |
169 | | - const ok = await confirmDialog( |
170 | | - `Remove all ${attachments.length} attachments from ${definition.label}?`, |
171 | | - { |
172 | | - title: "Remove all attachments", |
173 | | - kind: "warning", |
174 | | - okLabel: "Remove all", |
175 | | - cancelLabel: "Cancel", |
176 | | - }, |
177 | | - ); |
178 | | - if (ok) onclear(); |
179 | | - } catch (e) { |
180 | | - remoteError = String(e); |
181 | | - } |
| 178 | + const ok = await confirmDialog( |
| 179 | + `Remove all ${attachments.length} attachments from ${definition.label}?`, |
| 180 | + { |
| 181 | + title: "Remove all attachments", |
| 182 | + kind: "warning", |
| 183 | + okLabel: "Remove all", |
| 184 | + cancelLabel: "Cancel", |
| 185 | + }, |
| 186 | + ); |
| 187 | + if (ok) onclear(); |
182 | 188 | } |
183 | 189 |
|
184 | 190 | async function startRemoteUpload() { |
|
239 | 245 | } |
240 | 246 |
|
241 | 247 | async function pollRemoteOnce() { |
242 | | - if (!remoteSession || !onpolldrop || pollInFlight) return; |
243 | | - pollInFlight = true; |
| 248 | + if (!remoteSession || !onpolldrop) return; |
244 | 249 | const session = remoteSession; |
245 | 250 | const runId = remoteRunId; |
246 | 251 | try { |
|
276 | 281 | stopPolling(); |
277 | 282 | remotePhase = "failed"; |
278 | 283 | remoteError = String(e); |
279 | | - } finally { |
280 | | - pollInFlight = false; |
281 | 284 | } |
282 | 285 | } |
283 | 286 |
|
|
0 commit comments