Skip to content

Commit 558d195

Browse files
CedrickGDclaude
andcommitted
Restructure into monorepo with shared UI and web (Cloudflare Pages) target
- Root remains the desktop app (Tauri), unchanged workflow - packages/shared: single source of truth for all Svelte components, stores, styles - packages/web: FFmpeg.wasm-powered web version for Cloudflare Pages - Platform adapter pattern abstracts Tauri vs browser APIs - Deployed web version at convert-x-media-converter.pages.dev Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 26d214d commit 558d195

35 files changed

+868
-119
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ node_modules/
55
dist/
66
release/
77
src-tauri/target/
8+
packages/desktop/src-tauri/target/
89

910
# Bundled binaries (too large for git)
1011
src-tauri/bin/*.exe
12+
packages/desktop/src-tauri/bin/*.exe
1113

1214
# Generated schemas
1315
src-tauri/gen/
16+
packages/desktop/src-tauri/gen/
1417

1518
# IDE
1619
.vscode/

index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="stylesheet" href="/src/assets/styles.css" />
65
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>ConvertX</title>
6+
<title>Convert-X</title>
87
</head>
98
<body>
109
<div id="app"></div>

package-lock.json

Lines changed: 78 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
{
2-
"name": "convertx",
2+
"name": "convert-x",
33
"private": true,
44
"version": "0.1.0",
55
"type": "module",
6+
"workspaces": ["packages/*"],
67
"scripts": {
78
"dev": "vite",
89
"build": "vite build",
910
"preview": "vite preview",
10-
"tauri": "tauri"
11+
"tauri": "tauri",
12+
"dev:web": "npm run dev -w packages/web",
13+
"build:web": "npm run build -w packages/web"
1114
},
1215
"dependencies": {
1316
"@tauri-apps/api": "^2",

packages/shared/package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "@convertx/shared",
3+
"private": true,
4+
"version": "0.1.0",
5+
"type": "module",
6+
"main": "src/index.js",
7+
"exports": {
8+
".": "./src/index.js",
9+
"./components/*": "./src/components/*",
10+
"./stores/*": "./src/stores/*",
11+
"./assets/*": "./src/assets/*",
12+
"./platform": "./src/platform.js"
13+
}
14+
}

src/lib/AdvancedSettings.svelte renamed to packages/shared/src/components/AdvancedSettings.svelte

File renamed without changes.
Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,53 @@
22
import {
33
filesStore, settingsStore, appView, appMode, fileTypes,
44
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";
97
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+
2221
let files = [];
2322
let settings = {};
2423
let view = "idle";
2524
let types = new Set();
2625
let mode = "convert";
2726
let cancelled = false;
2827
28+
const platform = getPlatform();
29+
const isWeb = platform.platformType === "web";
30+
2931
filesStore.subscribe((v) => (files = v));
3032
settingsStore.subscribe((v) => (settings = v));
3133
appView.subscribe((v) => (view = v));
3234
fileTypes.subscribe((v) => (types = v));
3335
appMode.subscribe((v) => (mode = v));
3436
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 }) => {
3839
filesStore.update((all) =>
3940
all.map((f) => f.id === file_id ? { ...f, progress, elapsed } : f)
4041
);
4142
});
4243
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);
4846
});
47+
48+
return () => {
49+
if (typeof unlistenProgress === "function") unlistenProgress();
50+
if (typeof unlistenDrop === "function") unlistenDrop();
51+
};
4952
});
5053
5154
function switchMode(newMode) {
@@ -67,23 +70,25 @@
6770
return dot > 0 ? fileName.substring(0, dot) : fileName;
6871
}
6972
70-
async function handleFilesDrop(paths) {
73+
async function handleFilesDrop(entries) {
7174
if (view !== "idle" && view !== "ready") {
7275
resetAll();
7376
}
7477
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));
7680
filesStore.update((existing) => [...existing, ...newEntries]);
7781
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) }));
8084
}
8185
8286
appView.set("ready");
8387
8488
for (const entry of newEntries) {
8589
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);
8792
filesStore.update((all) =>
8893
all.map((f) =>
8994
f.id === entry.id
@@ -116,8 +121,8 @@
116121
}
117122
}
118123
119-
function addMoreFiles(paths) {
120-
handleFilesDrop(paths);
124+
function addMoreFiles(entries) {
125+
handleFilesDrop(entries);
121126
}
122127
123128
function removeFile(id) {
@@ -153,9 +158,10 @@
153158
);
154159
155160
try {
156-
const result = await invoke("convert_file", {
161+
const result = await platform.convertFile({
157162
fileId: file.id,
158163
filePath: file.filePath,
164+
fileObj: file.fileObj,
159165
fileType: file.detectedType,
160166
outputFormat: fmt,
161167
quality: settings.quality,
@@ -174,7 +180,14 @@
174180
filesStore.update((all) =>
175181
all.map((f) =>
176182
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+
}
178191
: f
179192
)
180193
);
@@ -199,7 +212,6 @@
199212
200213
const currentFiles = [...files];
201214
202-
// Mark image files as queued, non-images as skipped
203215
filesStore.update((all) =>
204216
all.map((f) => {
205217
if (f.status === "error") return f;
@@ -218,13 +230,13 @@
218230
);
219231
220232
try {
221-
// Determine output format
222233
const ext = file.filePath.split(".").pop().toLowerCase();
223234
const fmt = settings.resizeFormat || ext;
224235
225-
const result = await invoke("resize_image", {
236+
const result = await platform.resizeImage({
226237
fileId: file.id,
227238
filePath: file.filePath,
239+
fileObj: file.fileObj,
228240
resizeMode: settings.resizeMode,
229241
width: settings.resizeMode === "pixels" ? (settings.resizeWidth || null) : null,
230242
height: settings.resizeMode === "pixels" ? (settings.resizeHeight || null) : null,
@@ -239,7 +251,14 @@
239251
filesStore.update((all) =>
240252
all.map((f) =>
241253
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+
}
243262
: f
244263
)
245264
);
@@ -259,7 +278,7 @@
259278
260279
async function handleCancel() {
261280
cancelled = true;
262-
try { await invoke("cancel_conversion"); } catch (_) {}
281+
try { await platform.cancelConversion(); } catch (_) {}
263282
appView.set("ready");
264283
filesStore.update((all) =>
265284
all.map((f) =>
@@ -309,7 +328,7 @@
309328
<header>
310329
<div class="header-inner">
311330
<div class="spacer"></div>
312-
<h1>Convert<span class="x">X</span></h1>
331+
<h1>Convert-<span class="x">X</span></h1>
313332
<div class="spacer end"><ThemeToggle /></div>
314333
</div>
315334
<Navbar activeMode={mode} onModeChange={switchMode} />
@@ -331,7 +350,7 @@
331350
onAddFiles={addMoreFiles}
332351
/>
333352
{:else if singleFile}
334-
<FilePreview metadata={singleFile.metadata} filePath={singleFile.filePath} />
353+
<FilePreview metadata={singleFile.metadata} filePath={singleFile.filePath} fileObj={singleFile.fileObj} />
335354
{/if}
336355
337356
{#if mode === "convert"}
@@ -353,6 +372,7 @@
353372
trimStart={settings.trimStart || 0}
354373
trimEnd={settings.trimEnd || clipMaxDuration}
355374
filePath={clipVideoFile?.filePath || ""}
375+
fileObj={clipVideoFile?.fileObj || null}
356376
outputFormat={settings.selectedFormat}
357377
stripAudio={settings.stripAudio || false}
358378
onUpdate={(start, end) => {

0 commit comments

Comments
 (0)