|
2 | 2 | import { |
3 | 3 | filesStore, settingsStore, appView, appMode, fileTypes, |
4 | 4 | createFileEntry, resetAll, isFormatCompatible, |
5 | | - } from "./stores/fileStore.js"; |
6 | | - import { invoke } from "@tauri-apps/api/core"; |
7 | | - import { listen } from "@tauri-apps/api/event"; |
8 | | - import { getCurrentWindow } from "@tauri-apps/api/window"; |
| 5 | + } from "../stores/fileStore.js"; |
| 6 | + import { getPlatform } from "../platform.js"; |
9 | 7 | import { onMount } from "svelte"; |
10 | | - import Navbar from "./lib/Navbar.svelte"; |
11 | | - import Dropzone from "./lib/Dropzone.svelte"; |
12 | | - import FileList from "./lib/FileList.svelte"; |
13 | | - import FilePreview from "./lib/FilePreview.svelte"; |
14 | | - import FormatPicker from "./lib/FormatPicker.svelte"; |
15 | | - import OutputSettings from "./lib/OutputSettings.svelte"; |
16 | | - import AdvancedSettings from "./lib/AdvancedSettings.svelte"; |
17 | | - import GifEditor from "./lib/GifEditor.svelte"; |
18 | | - import ResizeSettings from "./lib/ResizeSettings.svelte"; |
19 | | - import ProgressBar from "./lib/ProgressBar.svelte"; |
20 | | - import OutputPanel from "./lib/OutputPanel.svelte"; |
21 | | - import ThemeToggle from "./lib/ThemeToggle.svelte"; |
| 8 | + import Navbar from "./Navbar.svelte"; |
| 9 | + import Dropzone from "./Dropzone.svelte"; |
| 10 | + import FileList from "./FileList.svelte"; |
| 11 | + import FilePreview from "./FilePreview.svelte"; |
| 12 | + import FormatPicker from "./FormatPicker.svelte"; |
| 13 | + import OutputSettings from "./OutputSettings.svelte"; |
| 14 | + import AdvancedSettings from "./AdvancedSettings.svelte"; |
| 15 | + import GifEditor from "./GifEditor.svelte"; |
| 16 | + import ResizeSettings from "./ResizeSettings.svelte"; |
| 17 | + import ProgressBar from "./ProgressBar.svelte"; |
| 18 | + import OutputPanel from "./OutputPanel.svelte"; |
| 19 | + import ThemeToggle from "./ThemeToggle.svelte"; |
| 20 | +
|
22 | 21 | let files = []; |
23 | 22 | let settings = {}; |
24 | 23 | let view = "idle"; |
25 | 24 | let types = new Set(); |
26 | 25 | let mode = "convert"; |
27 | 26 | let cancelled = false; |
28 | 27 |
|
| 28 | + const platform = getPlatform(); |
| 29 | + const isWeb = platform.platformType === "web"; |
| 30 | +
|
29 | 31 | filesStore.subscribe((v) => (files = v)); |
30 | 32 | settingsStore.subscribe((v) => (settings = v)); |
31 | 33 | appView.subscribe((v) => (view = v)); |
32 | 34 | fileTypes.subscribe((v) => (types = v)); |
33 | 35 | appMode.subscribe((v) => (mode = v)); |
34 | 36 |
|
35 | | - onMount(async () => { |
36 | | - await listen("conversion-progress", (event) => { |
37 | | - const { file_id, progress, elapsed } = event.payload; |
| 37 | + onMount(() => { |
| 38 | + const unlistenProgress = platform.onProgress(({ file_id, progress, elapsed }) => { |
38 | 39 | filesStore.update((all) => |
39 | 40 | all.map((f) => f.id === file_id ? { ...f, progress, elapsed } : f) |
40 | 41 | ); |
41 | 42 | }); |
42 | 43 |
|
43 | | - const appWindow = getCurrentWindow(); |
44 | | - await appWindow.onDragDropEvent((event) => { |
45 | | - if (event.payload.type === "drop" && event.payload.paths?.length > 0) { |
46 | | - handleFilesDrop(event.payload.paths); |
47 | | - } |
| 44 | + const unlistenDrop = platform.onFileDrop((entries) => { |
| 45 | + handleFilesDrop(entries); |
48 | 46 | }); |
| 47 | +
|
| 48 | + return () => { |
| 49 | + if (typeof unlistenProgress === "function") unlistenProgress(); |
| 50 | + if (typeof unlistenDrop === "function") unlistenDrop(); |
| 51 | + }; |
49 | 52 | }); |
50 | 53 |
|
51 | 54 | function switchMode(newMode) { |
|
67 | 70 | return dot > 0 ? fileName.substring(0, dot) : fileName; |
68 | 71 | } |
69 | 72 |
|
70 | | - async function handleFilesDrop(paths) { |
| 73 | + async function handleFilesDrop(entries) { |
71 | 74 | if (view !== "idle" && view !== "ready") { |
72 | 75 | resetAll(); |
73 | 76 | } |
74 | 77 |
|
75 | | - const newEntries = paths.map((p) => createFileEntry(p)); |
| 78 | + // entries: [{name, path, fileObj}] from platform adapter |
| 79 | + const newEntries = entries.map((e) => createFileEntry(e.path || e.name, e.fileObj || null)); |
76 | 80 | filesStore.update((existing) => [...existing, ...newEntries]); |
77 | 81 |
|
78 | | - if (!settings.outputDir && paths[0]) { |
79 | | - settingsStore.update((s) => ({ ...s, outputDir: getDefaultDir(paths[0]) })); |
| 82 | + if (!isWeb && !settings.outputDir && entries[0]?.path) { |
| 83 | + settingsStore.update((s) => ({ ...s, outputDir: getDefaultDir(entries[0].path) })); |
80 | 84 | } |
81 | 85 |
|
82 | 86 | appView.set("ready"); |
83 | 87 |
|
84 | 88 | for (const entry of newEntries) { |
85 | 89 | try { |
86 | | - const meta = await invoke("detect_file", { filePath: entry.filePath }); |
| 90 | + const fileRef = entry.fileObj || entry.filePath; |
| 91 | + const meta = await platform.detectFile(fileRef); |
87 | 92 | filesStore.update((all) => |
88 | 93 | all.map((f) => |
89 | 94 | f.id === entry.id |
|
116 | 121 | } |
117 | 122 | } |
118 | 123 |
|
119 | | - function addMoreFiles(paths) { |
120 | | - handleFilesDrop(paths); |
| 124 | + function addMoreFiles(entries) { |
| 125 | + handleFilesDrop(entries); |
121 | 126 | } |
122 | 127 |
|
123 | 128 | function removeFile(id) { |
|
153 | 158 | ); |
154 | 159 |
|
155 | 160 | try { |
156 | | - const result = await invoke("convert_file", { |
| 161 | + const result = await platform.convertFile({ |
157 | 162 | fileId: file.id, |
158 | 163 | filePath: file.filePath, |
| 164 | + fileObj: file.fileObj, |
159 | 165 | fileType: file.detectedType, |
160 | 166 | outputFormat: fmt, |
161 | 167 | quality: settings.quality, |
|
174 | 180 | filesStore.update((all) => |
175 | 181 | all.map((f) => |
176 | 182 | f.id === file.id |
177 | | - ? { ...f, status: "done", outputPath: result.output_path, outputSize: result.output_size, progress: 100 } |
| 183 | + ? { |
| 184 | + ...f, |
| 185 | + status: "done", |
| 186 | + outputPath: result.output_path || result.outputName || "", |
| 187 | + outputSize: result.output_size || result.outputSize || 0, |
| 188 | + outputBlob: result.outputBlob || null, |
| 189 | + progress: 100, |
| 190 | + } |
178 | 191 | : f |
179 | 192 | ) |
180 | 193 | ); |
|
199 | 212 |
|
200 | 213 | const currentFiles = [...files]; |
201 | 214 |
|
202 | | - // Mark image files as queued, non-images as skipped |
203 | 215 | filesStore.update((all) => |
204 | 216 | all.map((f) => { |
205 | 217 | if (f.status === "error") return f; |
|
218 | 230 | ); |
219 | 231 |
|
220 | 232 | try { |
221 | | - // Determine output format |
222 | 233 | const ext = file.filePath.split(".").pop().toLowerCase(); |
223 | 234 | const fmt = settings.resizeFormat || ext; |
224 | 235 |
|
225 | | - const result = await invoke("resize_image", { |
| 236 | + const result = await platform.resizeImage({ |
226 | 237 | fileId: file.id, |
227 | 238 | filePath: file.filePath, |
| 239 | + fileObj: file.fileObj, |
228 | 240 | resizeMode: settings.resizeMode, |
229 | 241 | width: settings.resizeMode === "pixels" ? (settings.resizeWidth || null) : null, |
230 | 242 | height: settings.resizeMode === "pixels" ? (settings.resizeHeight || null) : null, |
|
239 | 251 | filesStore.update((all) => |
240 | 252 | all.map((f) => |
241 | 253 | f.id === file.id |
242 | | - ? { ...f, status: "done", outputPath: result.output_path, outputSize: result.output_size, progress: 100 } |
| 254 | + ? { |
| 255 | + ...f, |
| 256 | + status: "done", |
| 257 | + outputPath: result.output_path || result.outputName || "", |
| 258 | + outputSize: result.output_size || result.outputSize || 0, |
| 259 | + outputBlob: result.outputBlob || null, |
| 260 | + progress: 100, |
| 261 | + } |
243 | 262 | : f |
244 | 263 | ) |
245 | 264 | ); |
|
259 | 278 |
|
260 | 279 | async function handleCancel() { |
261 | 280 | cancelled = true; |
262 | | - try { await invoke("cancel_conversion"); } catch (_) {} |
| 281 | + try { await platform.cancelConversion(); } catch (_) {} |
263 | 282 | appView.set("ready"); |
264 | 283 | filesStore.update((all) => |
265 | 284 | all.map((f) => |
|
309 | 328 | <header> |
310 | 329 | <div class="header-inner"> |
311 | 330 | <div class="spacer"></div> |
312 | | - <h1>Convert<span class="x">X</span></h1> |
| 331 | + <h1>Convert-<span class="x">X</span></h1> |
313 | 332 | <div class="spacer end"><ThemeToggle /></div> |
314 | 333 | </div> |
315 | 334 | <Navbar activeMode={mode} onModeChange={switchMode} /> |
|
331 | 350 | onAddFiles={addMoreFiles} |
332 | 351 | /> |
333 | 352 | {:else if singleFile} |
334 | | - <FilePreview metadata={singleFile.metadata} filePath={singleFile.filePath} /> |
| 353 | + <FilePreview metadata={singleFile.metadata} filePath={singleFile.filePath} fileObj={singleFile.fileObj} /> |
335 | 354 | {/if} |
336 | 355 |
|
337 | 356 | {#if mode === "convert"} |
|
353 | 372 | trimStart={settings.trimStart || 0} |
354 | 373 | trimEnd={settings.trimEnd || clipMaxDuration} |
355 | 374 | filePath={clipVideoFile?.filePath || ""} |
| 375 | + fileObj={clipVideoFile?.fileObj || null} |
356 | 376 | outputFormat={settings.selectedFormat} |
357 | 377 | stripAudio={settings.stripAudio || false} |
358 | 378 | onUpdate={(start, end) => { |
|
0 commit comments