mirror of https://github.com/VERT-sh/VERT.git
fix: code review suggestions
yes used ai for this, all seems valid and noted though
This commit is contained in:
parent
8729584614
commit
d3aeb9b696
36
bun.lock
36
bun.lock
|
|
@ -11,49 +11,49 @@
|
||||||
"@fontsource/lexend": "^5.2.11",
|
"@fontsource/lexend": "^5.2.11",
|
||||||
"@fontsource/radio-canada-big": "^5.2.7",
|
"@fontsource/radio-canada-big": "^5.2.7",
|
||||||
"@imagemagick/magick-wasm": "^0.0.37",
|
"@imagemagick/magick-wasm": "^0.0.37",
|
||||||
"@mediabunny/ac3": "^1.35.1",
|
"@mediabunny/ac3": "^1.40.0",
|
||||||
"@mediabunny/flac-encoder": "^1.37.0",
|
"@mediabunny/flac-encoder": "^1.40.0",
|
||||||
"@mediabunny/mp3-encoder": "^1.35.1",
|
"@mediabunny/mp3-encoder": "^1.40.0",
|
||||||
"@stripe/stripe-js": "^8.7.0",
|
"@stripe/stripe-js": "^8.11.0",
|
||||||
"byte-data": "^19.0.1",
|
"byte-data": "^19.0.1",
|
||||||
"client-zip": "^2.5.0",
|
"client-zip": "^2.5.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"lucide-svelte": "^0.554.0",
|
"lucide-svelte": "^0.554.0",
|
||||||
"mediabunny": "^1.37.0",
|
"mediabunny": "^1.40.0",
|
||||||
"music-metadata": "^11.12.0",
|
"music-metadata": "^11.12.3",
|
||||||
"overlayscrollbars": "^2.14.0",
|
"overlayscrollbars": "^2.14.0",
|
||||||
"overlayscrollbars-svelte": "^0.5.5",
|
"overlayscrollbars-svelte": "^0.5.5",
|
||||||
"p-queue": "^9.1.0",
|
"p-queue": "^9.1.0",
|
||||||
"riff-file": "^1.0.3",
|
"riff-file": "^1.0.3",
|
||||||
"sanitize-html": "^2.17.0",
|
"sanitize-html": "^2.17.2",
|
||||||
"svelte-stripe": "^1.4.0",
|
"svelte-stripe": "^1.4.0",
|
||||||
"vert-wasm": "^0.0.2",
|
"vert-wasm": "^0.0.2",
|
||||||
"vite-plugin-wasm": "^3.5.0",
|
"vite-plugin-wasm": "^3.6.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inlang/paraglide-js": "^2.11.0",
|
"@inlang/paraglide-js": "^2.15.0",
|
||||||
"@poppanator/sveltekit-svg": "^5.0.1",
|
"@poppanator/sveltekit-svg": "^5.0.1",
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
"@sveltejs/kit": "^2.52.0",
|
"@sveltejs/kit": "^2.55.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^4.0.4",
|
"@sveltejs/vite-plugin-svelte": "^4.0.4",
|
||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/sanitize-html": "^2.16.0",
|
"@types/sanitize-html": "^2.16.1",
|
||||||
"autoprefixer": "^10.4.24",
|
"autoprefixer": "^10.4.27",
|
||||||
"css-select": "5.1.0",
|
"css-select": "5.1.0",
|
||||||
"eslint": "^9.39.2",
|
"eslint": "^9.39.4",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-svelte": "^2.46.1",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"prettier": "^3.8.1",
|
"prettier": "^3.8.1",
|
||||||
"prettier-plugin-svelte": "^3.4.1",
|
"prettier-plugin-svelte": "^3.5.1",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
"sass": "^1.97.3",
|
"sass": "^1.98.0",
|
||||||
"svelte": "^5.51.2",
|
"svelte": "^5.54.0",
|
||||||
"svelte-check": "^4.4.0",
|
"svelte-check": "^4.4.5",
|
||||||
"tailwindcss": "^3.4.19",
|
"tailwindcss": "^3.4.19",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"typescript-eslint": "^8.55.0",
|
"typescript-eslint": "^8.57.1",
|
||||||
"vite": "^5.4.21",
|
"vite": "^5.4.21",
|
||||||
"vite-plugin-top-level-await": "^1.6.0",
|
"vite-plugin-top-level-await": "^1.6.0",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
"lint": "prettier --check . && eslint ."
|
"lint": "prettier --check .; p=$?; eslint .; e=$?; [ $p -eq 0 ] && [ $e -eq 0 ]"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@inlang/paraglide-js": "^2.15.0",
|
"@inlang/paraglide-js": "^2.15.0",
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,25 @@
|
||||||
type Props = DialogProps<VertdErrorDetailsProps>;
|
type Props = DialogProps<VertdErrorDetailsProps>;
|
||||||
|
|
||||||
let { additional }: Props = $props();
|
let { additional }: Props = $props();
|
||||||
|
let errorBlobUrl = $state("");
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (!additional.errorMessage) {
|
||||||
|
errorBlobUrl = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextUrl = URL.createObjectURL(
|
||||||
|
new Blob([additional.errorMessage], {
|
||||||
|
type: "text/plain",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
errorBlobUrl = nextUrl;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
URL.revokeObjectURL(nextUrl);
|
||||||
|
};
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
|
|
@ -41,13 +60,7 @@
|
||||||
{@html sanitize(link(
|
{@html sanitize(link(
|
||||||
["view_link"],
|
["view_link"],
|
||||||
m["convert.errors.vertd_details_error_message"](),
|
m["convert.errors.vertd_details_error_message"](),
|
||||||
[
|
[errorBlobUrl || "#"],
|
||||||
URL.createObjectURL(
|
|
||||||
new Blob([additional.errorMessage], {
|
|
||||||
type: "text/plain",
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
[true],
|
[true],
|
||||||
["text-blue-500 font-normal"],
|
["text-blue-500 font-normal"],
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -90,10 +90,11 @@ export class FFmpegConverter extends Converter {
|
||||||
this.error = (msg) => error(["converters", this.name], msg);
|
this.error = (msg) => error(["converters", this.name], msg);
|
||||||
this.log(`created converter`);
|
this.log(`created converter`);
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
try {
|
|
||||||
// this is just to cache the wasm and js for when we actually use it. we're not using this ffmpeg instance
|
// this is just to cache the wasm and js for when we actually use it. we're not using this ffmpeg instance
|
||||||
this.ffmpeg = new FFmpeg();
|
this.ffmpeg = new FFmpeg();
|
||||||
(async () => {
|
void (async () => {
|
||||||
|
try {
|
||||||
const baseURL =
|
const baseURL =
|
||||||
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/esm";
|
"https://cdn.jsdelivr.net/npm/@ffmpeg/core@0.12.10/dist/esm";
|
||||||
|
|
||||||
|
|
@ -105,15 +106,15 @@ export class FFmpegConverter extends Converter {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.status = "ready";
|
this.status = "ready";
|
||||||
})();
|
} catch (err) {
|
||||||
} catch (err) {
|
this.error(`Error loading ffmpeg: ${err}`);
|
||||||
this.error(`Error loading ffmpeg: ${err}`);
|
this.status = "error";
|
||||||
this.status = "error";
|
ToastManager.add({
|
||||||
ToastManager.add({
|
type: "error",
|
||||||
type: "error",
|
message: m["workers.errors.ffmpeg"](),
|
||||||
message: m["workers.errors.ffmpeg"](),
|
});
|
||||||
});
|
}
|
||||||
}
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAvailableSettings(): Promise<SettingDefinition[]> {
|
public async getAvailableSettings(): Promise<SettingDefinition[]> {
|
||||||
|
|
@ -251,46 +252,42 @@ export class FFmpegConverter extends Converter {
|
||||||
|
|
||||||
ffmpeg.on("log", errorListener);
|
ffmpeg.on("log", errorListener);
|
||||||
|
|
||||||
const buf = new Uint8Array(await input.file.arrayBuffer());
|
try {
|
||||||
await ffmpeg.writeFile("input", buf);
|
const buf = new Uint8Array(await input.file.arrayBuffer());
|
||||||
this.log(`wrote ${input.name} to ffmpeg virtual fs`);
|
await ffmpeg.writeFile("input", buf);
|
||||||
|
this.log(`wrote ${input.name} to ffmpeg virtual fs`);
|
||||||
|
|
||||||
const command = await this.buildConversionCommand(
|
const command = await this.buildConversionCommand(
|
||||||
ffmpeg,
|
ffmpeg,
|
||||||
input,
|
input,
|
||||||
to,
|
to,
|
||||||
conversionSettings,
|
conversionSettings,
|
||||||
isAlac,
|
isAlac,
|
||||||
);
|
);
|
||||||
this.log(`FFmpeg command: ${command.join(" ")}`);
|
this.log(`FFmpeg command: ${command.join(" ")}`);
|
||||||
await ffmpeg.exec(command);
|
await ffmpeg.exec(command);
|
||||||
this.log("executed ffmpeg command");
|
this.log("executed ffmpeg command");
|
||||||
|
|
||||||
if (conversionError) {
|
if (conversionError) throw new Error(conversionError);
|
||||||
|
|
||||||
|
const output = (await ffmpeg.readFile(
|
||||||
|
"output" + to,
|
||||||
|
)) as unknown as Uint8Array;
|
||||||
|
|
||||||
|
if (!output || output.length === 0)
|
||||||
|
throw new Error("empty file returned");
|
||||||
|
|
||||||
|
const outputFileName =
|
||||||
|
input.name.split(".").slice(0, -1).join(".") + to;
|
||||||
|
this.log(`read ${outputFileName} from ffmpeg virtual fs`);
|
||||||
|
|
||||||
|
const outBuf = new Uint8Array(output).buffer.slice(0);
|
||||||
|
return new VertFile(new File([outBuf], outputFileName), to);
|
||||||
|
} finally {
|
||||||
ffmpeg.off("log", errorListener);
|
ffmpeg.off("log", errorListener);
|
||||||
|
this.activeConversions.delete(input.id);
|
||||||
ffmpeg.terminate();
|
ffmpeg.terminate();
|
||||||
throw new Error(conversionError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const output = (await ffmpeg.readFile(
|
|
||||||
"output" + to,
|
|
||||||
)) as unknown as Uint8Array;
|
|
||||||
|
|
||||||
if (!output || output.length === 0) {
|
|
||||||
ffmpeg.off("log", errorListener);
|
|
||||||
ffmpeg.terminate();
|
|
||||||
throw new Error("empty file returned");
|
|
||||||
}
|
|
||||||
|
|
||||||
const outputFileName =
|
|
||||||
input.name.split(".").slice(0, -1).join(".") + to;
|
|
||||||
this.log(`read ${outputFileName} from ffmpeg virtual fs`);
|
|
||||||
|
|
||||||
ffmpeg.off("log", errorListener);
|
|
||||||
ffmpeg.terminate();
|
|
||||||
|
|
||||||
const outBuf = new Uint8Array(output).buffer.slice(0);
|
|
||||||
return new VertFile(new File([outBuf], outputFileName), to);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cancel(input: VertFile): Promise<void> {
|
public async cancel(input: VertFile): Promise<void> {
|
||||||
|
|
@ -529,7 +526,7 @@ export class FFmpegConverter extends Converter {
|
||||||
// -map for each audio track
|
// -map for each audio track
|
||||||
if (settings.tracks > 1) {
|
if (settings.tracks > 1) {
|
||||||
for (let i = 0; i < settings.tracks; i++) {
|
for (let i = 0; i < settings.tracks; i++) {
|
||||||
tracksArgs.push("-map", `0:a:${i - 1}`);
|
tracksArgs.push("-map", `0:a:${i}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tracksArgs = ["-map", "0:a:0"]; // default to first audio track if not specified
|
tracksArgs = ["-map", "0:a:0"]; // default to first audio track if not specified
|
||||||
|
|
|
||||||
|
|
@ -49,69 +49,70 @@ export class PandocConverter extends Converter {
|
||||||
|
|
||||||
this.activeConversions.set(file.id, worker);
|
this.activeConversions.set(file.id, worker);
|
||||||
|
|
||||||
const loadMsg: WorkerMessage = {
|
try {
|
||||||
type: "load",
|
const loadMsg: WorkerMessage = {
|
||||||
wasm: this.wasm,
|
type: "load",
|
||||||
id: file.id,
|
wasm: this.wasm,
|
||||||
};
|
id: file.id,
|
||||||
worker.postMessage(loadMsg);
|
};
|
||||||
await waitForMessage(worker, "loaded");
|
worker.postMessage(loadMsg);
|
||||||
const convertMsg: WorkerMessage = {
|
await waitForMessage(worker, "loaded");
|
||||||
type: "convert",
|
const convertMsg: WorkerMessage = {
|
||||||
to,
|
type: "convert",
|
||||||
input: {
|
|
||||||
file: file.file,
|
|
||||||
name: file.name,
|
|
||||||
from: file.from,
|
|
||||||
to,
|
to,
|
||||||
},
|
input: {
|
||||||
id: file.id,
|
file: file.file,
|
||||||
conversionSettings: "", // no settings for pandoc yet
|
name: file.name,
|
||||||
};
|
from: file.from,
|
||||||
worker.postMessage(convertMsg);
|
to,
|
||||||
const result = await waitForMessage(worker);
|
},
|
||||||
if (result.type === "error") {
|
id: file.id,
|
||||||
worker.terminate();
|
conversionSettings: "", // no settings for pandoc yet
|
||||||
// throw new Error(result.error);
|
};
|
||||||
const error = result.error.toString();
|
worker.postMessage(convertMsg);
|
||||||
switch (result.errorKind) {
|
const result = await waitForMessage(worker);
|
||||||
case "PandocUnknownReaderError": {
|
if (result.type === "error") {
|
||||||
throw new Error(
|
const error = result.error.toString();
|
||||||
`${file.from} is not a supported input format for documents.`,
|
switch (result.errorKind) {
|
||||||
);
|
case "PandocUnknownReaderError": {
|
||||||
}
|
|
||||||
|
|
||||||
case "PandocUnknownWriterError": {
|
|
||||||
throw new Error(
|
|
||||||
`${to} is not a supported output format for documents.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "PandocParseError": {
|
|
||||||
if (error.includes("JSON missing pandoc-api-version")) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`This JSON file is not a pandoc-converted JSON file. It must be converted with pandoc / VERT to be converted again.`,
|
`${file.from} is not a supported input format for documents.`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-fallthrough
|
case "PandocUnknownWriterError": {
|
||||||
default:
|
|
||||||
if (result.errorKind)
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[${result.errorKind}] ${result.error}`,
|
`${to} is not a supported output format for documents.`,
|
||||||
);
|
);
|
||||||
else throw new Error(result.error);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!to.startsWith(".")) to = `.${to}`;
|
case "PandocParseError": {
|
||||||
this.activeConversions.delete(file.id);
|
if (error.includes("JSON missing pandoc-api-version")) {
|
||||||
worker.terminate();
|
throw new Error(
|
||||||
return new VertFile(
|
`This JSON file is not a pandoc-converted JSON file. It must be converted with pandoc / VERT to be converted again.`,
|
||||||
new File([result.output], file.name),
|
);
|
||||||
result.isZip ? ".zip" : to,
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-fallthrough
|
||||||
|
default:
|
||||||
|
if (result.errorKind)
|
||||||
|
throw new Error(
|
||||||
|
`[${result.errorKind}] ${result.error}`,
|
||||||
|
);
|
||||||
|
else throw new Error(result.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!to.startsWith(".")) to = `.${to}`;
|
||||||
|
return new VertFile(
|
||||||
|
new File([result.output], file.name),
|
||||||
|
result.isZip ? ".zip" : to,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
this.activeConversions.delete(file.id);
|
||||||
|
worker.terminate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async cancel(input: VertFile): Promise<void> {
|
public async cancel(input: VertFile): Promise<void> {
|
||||||
|
|
|
||||||
|
|
@ -723,11 +723,42 @@ export class VertdConverter extends Converter {
|
||||||
const apiUrl = await VertdInstance.instance.url();
|
const apiUrl = await VertdInstance.instance.url();
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
let settled = false;
|
||||||
const protocol = apiUrl.startsWith("https") ? "wss:" : "ws:";
|
const protocol = apiUrl.startsWith("https") ? "wss:" : "ws:";
|
||||||
const ws = new WebSocket(
|
const ws = new WebSocket(
|
||||||
`${protocol}//${apiUrl.replace("http://", "").replace("https://", "")}/api/ws`,
|
`${protocol}//${apiUrl.replace("http://", "").replace("https://", "")}/api/ws`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const connectTimeout = setTimeout(() => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
this.activeConversions.delete(input.id);
|
||||||
|
ws.close();
|
||||||
|
reject(new Error("vertd websocket connection timeout"));
|
||||||
|
}, 30000);
|
||||||
|
|
||||||
|
const rejectConversion = (reason: unknown) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
|
this.activeConversions.delete(input.id);
|
||||||
|
if (
|
||||||
|
ws.readyState === WebSocket.CONNECTING ||
|
||||||
|
ws.readyState === WebSocket.OPEN
|
||||||
|
) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
reject(reason);
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveConversion = (value: VertFile) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
|
this.activeConversions.delete(input.id);
|
||||||
|
resolve(value);
|
||||||
|
};
|
||||||
|
|
||||||
this.activeConversions.set(input.id, {
|
this.activeConversions.set(input.id, {
|
||||||
ws,
|
ws,
|
||||||
jobId: uploadRes.id,
|
jobId: uploadRes.id,
|
||||||
|
|
@ -735,6 +766,7 @@ export class VertdConverter extends Converter {
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
|
clearTimeout(connectTimeout);
|
||||||
this.log(
|
this.log(
|
||||||
`opened ws connection to vertd for file ${input.name}`,
|
`opened ws connection to vertd for file ${input.name}`,
|
||||||
);
|
);
|
||||||
|
|
@ -752,8 +784,32 @@ export class VertdConverter extends Converter {
|
||||||
this.log(`sent startJob message for file ${input.name}`);
|
this.log(`sent startJob message for file ${input.name}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ws.onerror = () => {
|
||||||
|
this.error(`ws error for file ${input.name}`);
|
||||||
|
rejectConversion(new Error("vertd websocket error"));
|
||||||
|
};
|
||||||
|
|
||||||
|
ws.onclose = (event) => {
|
||||||
|
if (settled) return;
|
||||||
|
this.error(
|
||||||
|
`ws closed unexpectedly for file ${input.name} (code: ${event.code})`,
|
||||||
|
);
|
||||||
|
rejectConversion(new Error("vertd websocket closed unexpectedly"));
|
||||||
|
};
|
||||||
|
|
||||||
ws.onmessage = async (e) => {
|
ws.onmessage = async (e) => {
|
||||||
const msg: VertdMessage = JSON.parse(e.data);
|
let msg: VertdMessage;
|
||||||
|
try {
|
||||||
|
if (typeof e.data !== "string") {
|
||||||
|
rejectConversion(new Error("invalid websocket payload type"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msg = JSON.parse(e.data);
|
||||||
|
} catch {
|
||||||
|
rejectConversion(new Error("invalid websocket payload"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.log(`received message ${msg.type} for file ${input.name}`);
|
this.log(`received message ${msg.type} for file ${input.name}`);
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case "progressUpdate": {
|
case "progressUpdate": {
|
||||||
|
|
@ -781,45 +837,48 @@ export class VertdConverter extends Converter {
|
||||||
case "jobFinished": {
|
case "jobFinished": {
|
||||||
this.log(`job finished for file ${input.name}`);
|
this.log(`job finished for file ${input.name}`);
|
||||||
ws.close();
|
ws.close();
|
||||||
this.activeConversions.delete(input.id);
|
|
||||||
const url = `${apiUrl}/api/download/${msg.data.jobId}/${uploadRes.auth}`;
|
|
||||||
this.log(`downloading from ${url}`);
|
|
||||||
// const res = await fetch(url).then((res) => res.blob());
|
|
||||||
const res = await downloadFile(url, input);
|
|
||||||
|
|
||||||
// confirm download to clean up on server
|
|
||||||
try {
|
try {
|
||||||
await vertdFetch(
|
const url = `${apiUrl}/api/download/${msg.data.jobId}/${uploadRes.auth}`;
|
||||||
`/api/confirm/${msg.data.jobId}/${uploadRes.auth}`,
|
this.log(`downloading from ${url}`);
|
||||||
{
|
const res = await downloadFile(url, input);
|
||||||
method: "GET",
|
|
||||||
},
|
// confirm download to clean up on server
|
||||||
);
|
try {
|
||||||
this.log(
|
await vertdFetch(
|
||||||
`confirmed download for file ${input.name}`,
|
`/api/confirm/${msg.data.jobId}/${uploadRes.auth}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.log(
|
||||||
|
`confirmed download for file ${input.name}`,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
this.error(`failed to confirm download: ${e}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveConversion(
|
||||||
|
new VertFile(new File([res], input.name), to),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.error(`failed to confirm download: ${e}`);
|
if (hash) this.failure(hash);
|
||||||
|
rejectConversion(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(new VertFile(new File([res], input.name), to));
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "jobCancelled": {
|
case "jobCancelled": {
|
||||||
this.log("job cancelled");
|
this.log("job cancelled");
|
||||||
ws.close();
|
ws.close();
|
||||||
this.activeConversions.delete(input.id);
|
rejectConversion("Conversion cancelled");
|
||||||
reject("Conversion cancelled");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case "error": {
|
case "error": {
|
||||||
this.error(`error: ${msg.data.message}`);
|
this.error(`error: ${msg.data.message}`);
|
||||||
this.activeConversions.delete(input.id);
|
|
||||||
if (hash) this.failure(hash);
|
if (hash) this.failure(hash);
|
||||||
|
|
||||||
reject({
|
rejectConversion({
|
||||||
component: VertdErrorComponent,
|
component: VertdErrorComponent,
|
||||||
additional: {
|
additional: {
|
||||||
jobId: uploadRes.id,
|
jobId: uploadRes.id,
|
||||||
|
|
@ -829,6 +888,11 @@ export class VertdConverter extends Converter {
|
||||||
errorMessage: msg.data.message,
|
errorMessage: msg.data.message,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { PUB_VERTD_URL } from "$env/static/public";
|
import { PUB_VERTD_URL } from "$env/static/public";
|
||||||
import type { ConversionBitrate } from "$lib/converters/ffmpeg.svelte";
|
import type { ConversionBitrate } from "$lib/converters/ffmpeg.svelte";
|
||||||
import type { ConversionSpeed } from "$lib/converters/vertd.svelte";
|
import type { ConversionSpeed } from "$lib/converters/vertd.svelte";
|
||||||
|
import { readSettings } from "$lib/util/settings";
|
||||||
import { VertdInstance } from "./vertdSettings.svelte";
|
import { VertdInstance } from "./vertdSettings.svelte";
|
||||||
|
|
||||||
export { default as Appearance } from "./Appearance.svelte";
|
export { default as Appearance } from "./Appearance.svelte";
|
||||||
|
|
@ -62,9 +63,9 @@ export class Settings {
|
||||||
public load() {
|
public load() {
|
||||||
try {
|
try {
|
||||||
VertdInstance.instance.load();
|
VertdInstance.instance.load();
|
||||||
const ls = localStorage.getItem("settings");
|
const persisted = readSettings<ISettings>();
|
||||||
if (!ls) return;
|
if (!Object.keys(persisted).length) return;
|
||||||
const settings: ISettings = JSON.parse(ls);
|
const settings = persisted as ISettings;
|
||||||
const vertdBlockedHashes = new Map<string, Date[]>(
|
const vertdBlockedHashes = new Map<string, Date[]>(
|
||||||
Object.entries(
|
Object.entries(
|
||||||
settings.vertdBlockedHashes ||
|
settings.vertdBlockedHashes ||
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { m } from "$lib/paraglide/messages";
|
||||||
import sanitizeHtml from "sanitize-html";
|
import sanitizeHtml from "sanitize-html";
|
||||||
import { ToastManager } from "$lib/util/toast.svelte";
|
import { ToastManager } from "$lib/util/toast.svelte";
|
||||||
import { GB } from "$lib/util/consts";
|
import { GB } from "$lib/util/consts";
|
||||||
|
import { readSettings } from "$lib/util/settings";
|
||||||
|
|
||||||
class Files {
|
class Files {
|
||||||
public files = $state<VertFile[]>([]);
|
public files = $state<VertFile[]>([]);
|
||||||
|
|
@ -52,9 +53,7 @@ class Files {
|
||||||
public requiredConverters = $derived(
|
public requiredConverters = $derived(
|
||||||
Array.from(
|
Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
this.files.flatMap((file) =>
|
this.files.flatMap((file) => this.getRequiredConverters(file)),
|
||||||
this.getRequiredConverters(file),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -93,6 +92,9 @@ class Files {
|
||||||
?.includes(file.from.toLowerCase());
|
?.includes(file.from.toLowerCase());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (file.blobUrl?.startsWith("blob:"))
|
||||||
|
URL.revokeObjectURL(file.blobUrl);
|
||||||
|
|
||||||
if (isAudio) {
|
if (isAudio) {
|
||||||
// try to get the thumbnail from the audio via music-metadata
|
// try to get the thumbnail from the audio via music-metadata
|
||||||
const { common } = await parseBlob(file.file, {
|
const { common } = await parseBlob(file.file, {
|
||||||
|
|
@ -136,55 +138,65 @@ class Files {
|
||||||
const mediaElement = isVideo
|
const mediaElement = isVideo
|
||||||
? document.createElement("video")
|
? document.createElement("video")
|
||||||
: new Image();
|
: new Image();
|
||||||
mediaElement.src = URL.createObjectURL(file);
|
const mediaUrl = URL.createObjectURL(file);
|
||||||
|
mediaElement.src = mediaUrl;
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
try {
|
||||||
if (isVideo) {
|
await new Promise((resolve, reject) => {
|
||||||
const video = mediaElement as HTMLVideoElement;
|
if (isVideo) {
|
||||||
// seek to 10% of video time or 2 seconds in
|
const video = mediaElement as HTMLVideoElement;
|
||||||
video.onloadeddata = () => {
|
// seek to 10% of video time or 2 seconds in
|
||||||
const seekTime = Math.min(video.duration * 0.1, 2);
|
video.onloadeddata = () => {
|
||||||
video.currentTime = seekTime;
|
const seekTime = Math.min(video.duration * 0.1, 2);
|
||||||
};
|
video.currentTime = seekTime;
|
||||||
video.onseeked = resolve;
|
};
|
||||||
video.onerror = reject;
|
video.onseeked = resolve;
|
||||||
} else {
|
video.onerror = reject;
|
||||||
(mediaElement as HTMLImageElement).onload = resolve;
|
} else {
|
||||||
(mediaElement as HTMLImageElement).onerror = reject;
|
(mediaElement as HTMLImageElement).onload = resolve;
|
||||||
|
(mediaElement as HTMLImageElement).onerror = reject;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) return undefined;
|
||||||
|
|
||||||
|
const width = isVideo
|
||||||
|
? (mediaElement as HTMLVideoElement).videoWidth
|
||||||
|
: (mediaElement as HTMLImageElement).width;
|
||||||
|
const height = isVideo
|
||||||
|
? (mediaElement as HTMLVideoElement).videoHeight
|
||||||
|
: (mediaElement as HTMLImageElement).height;
|
||||||
|
|
||||||
|
const scale = Math.max(maxSize / width, maxSize / height);
|
||||||
|
canvas.width = width * scale;
|
||||||
|
canvas.height = height * scale;
|
||||||
|
ctx.drawImage(mediaElement, 0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// check if completely transparent
|
||||||
|
const imageData = ctx.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height,
|
||||||
|
);
|
||||||
|
const isTransparent = Array.from(imageData.data).every(
|
||||||
|
(value, index) => {
|
||||||
|
return (index + 1) % 4 !== 0 || value === 0;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (isTransparent) {
|
||||||
|
canvas.remove();
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
const url = canvas.toDataURL();
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
if (!ctx) return undefined;
|
|
||||||
|
|
||||||
const width = isVideo
|
|
||||||
? (mediaElement as HTMLVideoElement).videoWidth
|
|
||||||
: (mediaElement as HTMLImageElement).width;
|
|
||||||
const height = isVideo
|
|
||||||
? (mediaElement as HTMLVideoElement).videoHeight
|
|
||||||
: (mediaElement as HTMLImageElement).height;
|
|
||||||
|
|
||||||
const scale = Math.max(maxSize / width, maxSize / height);
|
|
||||||
canvas.width = width * scale;
|
|
||||||
canvas.height = height * scale;
|
|
||||||
ctx.drawImage(mediaElement, 0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
// check if completely transparent
|
|
||||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
const isTransparent = Array.from(imageData.data).every(
|
|
||||||
(value, index) => {
|
|
||||||
return (index + 1) % 4 !== 0 || value === 0;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (isTransparent) {
|
|
||||||
canvas.remove();
|
canvas.remove();
|
||||||
return undefined;
|
return url;
|
||||||
|
} finally {
|
||||||
|
URL.revokeObjectURL(mediaUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = canvas.toDataURL();
|
|
||||||
canvas.remove();
|
|
||||||
return url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleZipFile(file: File): Promise<void> {
|
private async _handleZipFile(file: File): Promise<void> {
|
||||||
|
|
@ -435,7 +447,7 @@ class Files {
|
||||||
const blob = await downloadZip(dlFiles, "converted.zip").blob();
|
const blob = await downloadZip(dlFiles, "converted.zip").blob();
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
const settings = JSON.parse(localStorage.getItem("settings") ?? "{}");
|
const settings = readSettings<{ filenameFormat?: string }>();
|
||||||
const filenameFormat = settings.filenameFormat || "VERT_%name%";
|
const filenameFormat = settings.filenameFormat || "VERT_%name%";
|
||||||
|
|
||||||
const format = (name: string) => {
|
const format = (name: string) => {
|
||||||
|
|
@ -603,7 +615,10 @@ export const getMaxArrayBufferSize = (): number => {
|
||||||
// lmao uh mobile devices definitely have a much lower limit and using binary search here
|
// lmao uh mobile devices definitely have a much lower limit and using binary search here
|
||||||
// was causing crashes especially on iOS, so just return 2GB to be safe :p
|
// was causing crashes especially on iOS, so just return 2GB to be safe :p
|
||||||
if (get(isMobile)) {
|
if (get(isMobile)) {
|
||||||
log(["converters"], `mobile device likely detected, using 2GB fallback for max ArrayBuffer size`);
|
log(
|
||||||
|
["converters"],
|
||||||
|
`mobile device likely detected, using 2GB fallback for max ArrayBuffer size`,
|
||||||
|
);
|
||||||
// don't save to localStorage, since it can always be a false positive or the user's browser window is simply just small
|
// don't save to localStorage, since it can always be a false positive or the user's browser window is simply just small
|
||||||
return 2 * GB;
|
return 2 * GB;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import type {
|
||||||
SettingDefinition,
|
SettingDefinition,
|
||||||
} from "./conversion-settings";
|
} from "./conversion-settings";
|
||||||
import { log } from "$lib/util/logger";
|
import { log } from "$lib/util/logger";
|
||||||
|
import { readSettings } from "$lib/util/settings";
|
||||||
|
|
||||||
const MAX_BLOB_SIZE_LIMIT = 2 * 1024 * 1024 * 1024; // 2GB
|
const MAX_BLOB_SIZE_LIMIT = 2 * 1024 * 1024 * 1024; // 2GB
|
||||||
|
|
||||||
|
|
@ -121,7 +122,7 @@ export class VertFile {
|
||||||
constructor(file: File, to: string, blobUrl?: string) {
|
constructor(file: File, to: string, blobUrl?: string) {
|
||||||
const ext = file.name.split(".").pop();
|
const ext = file.name.split(".").pop();
|
||||||
const newFile = new File(
|
const newFile = new File(
|
||||||
[file.slice(0, file.size, file.type)],
|
[file],
|
||||||
`${file.name.split(".").slice(0, -1).join(".")}.${ext?.toLowerCase()}`,
|
`${file.name.split(".").slice(0, -1).join(".")}.${ext?.toLowerCase()}`,
|
||||||
);
|
);
|
||||||
this.file = newFile;
|
this.file = newFile;
|
||||||
|
|
@ -479,7 +480,7 @@ export class VertFile {
|
||||||
let to = this.result.to;
|
let to = this.result.to;
|
||||||
if (!to.startsWith(".")) to = `.${to}`;
|
if (!to.startsWith(".")) to = `.${to}`;
|
||||||
|
|
||||||
const settings = JSON.parse(localStorage.getItem("settings") ?? "{}");
|
const settings = readSettings<{ filenameFormat?: string }>();
|
||||||
const filenameFormat = settings.filenameFormat || "VERT_%name%";
|
const filenameFormat = settings.filenameFormat || "VERT_%name%";
|
||||||
|
|
||||||
const format = (name: string) => {
|
const format = (name: string) => {
|
||||||
|
|
@ -528,7 +529,7 @@ export class VertFile {
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
cache.delete(cacheKey);
|
cache.delete(cacheKey);
|
||||||
}, 3000);
|
}, 30000);
|
||||||
} else {
|
} else {
|
||||||
blob = URL.createObjectURL(
|
blob = URL.createObjectURL(
|
||||||
new Blob([await this.result.file.arrayBuffer()], {
|
new Blob([await this.result.file.arrayBuffer()], {
|
||||||
|
|
@ -545,7 +546,9 @@ export class VertFile {
|
||||||
a.target = "_blank";
|
a.target = "_blank";
|
||||||
a.style.display = "none";
|
a.style.display = "none";
|
||||||
a.click();
|
a.click();
|
||||||
URL.revokeObjectURL(blob);
|
setTimeout(() => {
|
||||||
|
URL.revokeObjectURL(blob);
|
||||||
|
}, 30000);
|
||||||
a.remove();
|
a.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
import { error } from "$lib/util/logger";
|
||||||
|
|
||||||
|
export function readSettings<T extends object = Record<string, unknown>>(): Partial<T> {
|
||||||
|
if (!browser) return {};
|
||||||
|
|
||||||
|
const raw = localStorage.getItem("settings");
|
||||||
|
if (!raw) return {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(raw);
|
||||||
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||||
|
localStorage.removeItem("settings");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed as Partial<T>;
|
||||||
|
} catch (e) {
|
||||||
|
error(["settings", "error"], `failed to parse saved settings: ${e}`);
|
||||||
|
localStorage.removeItem("settings");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,15 +46,20 @@ class ServiceWorkerManager {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const messageChannel = new MessageChannel();
|
const messageChannel = new MessageChannel();
|
||||||
|
let settled = false;
|
||||||
messageChannel.port1.onmessage = (event) => {
|
const timeoutId = setTimeout(() => {
|
||||||
resolve(event.data);
|
if (settled) return;
|
||||||
};
|
settled = true;
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(new Error("Timeout waiting for cache info"));
|
reject(new Error("Timeout waiting for cache info"));
|
||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
|
messageChannel.port1.onmessage = (event) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
resolve(event.data);
|
||||||
|
};
|
||||||
|
|
||||||
navigator.serviceWorker?.controller?.postMessage(
|
navigator.serviceWorker?.controller?.postMessage(
|
||||||
{ type: "GET_CACHE_INFO" },
|
{ type: "GET_CACHE_INFO" },
|
||||||
[messageChannel.port2],
|
[messageChannel.port2],
|
||||||
|
|
@ -69,8 +74,17 @@ class ServiceWorkerManager {
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const messageChannel = new MessageChannel();
|
const messageChannel = new MessageChannel();
|
||||||
|
let settled = false;
|
||||||
|
const timeoutId = setTimeout(() => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
reject(new Error("Timeout waiting for cache clear"));
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
messageChannel.port1.onmessage = (event) => {
|
messageChannel.port1.onmessage = (event) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
if (event.data.success) {
|
if (event.data.success) {
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -80,10 +94,6 @@ class ServiceWorkerManager {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(new Error("Timeout waiting for cache clear"));
|
|
||||||
}, 10000);
|
|
||||||
|
|
||||||
navigator.serviceWorker?.controller?.postMessage(
|
navigator.serviceWorker?.controller?.postMessage(
|
||||||
{ type: "CLEAR_CACHE" },
|
{ type: "CLEAR_CACHE" },
|
||||||
[messageChannel.port2],
|
[messageChannel.port2],
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,15 @@
|
||||||
// Check if the data is already in sessionStorage
|
// Check if the data is already in sessionStorage
|
||||||
const cachedContribs = sessionStorage.getItem("ghContribs");
|
const cachedContribs = sessionStorage.getItem("ghContribs");
|
||||||
if (cachedContribs) {
|
if (cachedContribs) {
|
||||||
ghContribs = JSON.parse(cachedContribs);
|
try {
|
||||||
return;
|
const parsedContribs = JSON.parse(cachedContribs);
|
||||||
|
if (Array.isArray(parsedContribs)) {
|
||||||
|
ghContribs = parsedContribs;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
sessionStorage.removeItem("ghContribs");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch GitHub contributors
|
// Fetch GitHub contributors
|
||||||
|
|
@ -104,30 +111,16 @@
|
||||||
!excludedNames.has(contrib.login),
|
!excludedNames.has(contrib.login),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Fetch and cache avatar images as Base64
|
ghContribs = filteredContribs.map(
|
||||||
const fetchAvatar = async (url: string) => {
|
(contrib: {
|
||||||
const res = await fetch(url);
|
login: string;
|
||||||
const blob = await res.blob();
|
avatar_url: string;
|
||||||
return new Promise<string>((resolve, reject) => {
|
html_url: string;
|
||||||
const reader = new FileReader();
|
}) => ({
|
||||||
reader.onloadend = () => resolve(reader.result as string);
|
name: contrib.login,
|
||||||
reader.onerror = reject;
|
avatar: contrib.avatar_url,
|
||||||
reader.readAsDataURL(blob);
|
github: contrib.html_url,
|
||||||
});
|
}),
|
||||||
};
|
|
||||||
|
|
||||||
ghContribs = await Promise.all(
|
|
||||||
filteredContribs.map(
|
|
||||||
async (contrib: {
|
|
||||||
login: string;
|
|
||||||
avatar_url: string;
|
|
||||||
html_url: string;
|
|
||||||
}) => ({
|
|
||||||
name: contrib.login,
|
|
||||||
avatar: await fetchAvatar(contrib.avatar_url),
|
|
||||||
github: contrib.html_url,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Cache the data in sessionStorage
|
// Cache the data in sessionStorage
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,22 @@
|
||||||
import { browser } from "$app/environment";
|
import { browser } from "$app/environment";
|
||||||
import { error, log } from "$lib/util/logger";
|
import { error, log } from "$lib/util/logger";
|
||||||
import * as Settings from "$lib/sections/settings/index.svelte";
|
import * as Settings from "$lib/sections/settings/index.svelte";
|
||||||
import { PUB_PLAUSIBLE_URL } from "$env/static/public";
|
|
||||||
import { SettingsIcon } from "lucide-svelte";
|
import { SettingsIcon } from "lucide-svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { m } from "$lib/paraglide/messages";
|
import { m } from "$lib/paraglide/messages";
|
||||||
import { ToastManager } from "$lib/util/toast.svelte";
|
import { ToastManager } from "$lib/util/toast.svelte";
|
||||||
import { DISABLE_ALL_EXTERNAL_REQUESTS } from "$lib/util/consts";
|
import { DISABLE_ALL_EXTERNAL_REQUESTS } from "$lib/util/consts";
|
||||||
|
import { readSettings } from "$lib/util/settings";
|
||||||
|
|
||||||
let settings = $state(Settings.Settings.instance.settings);
|
let settings = $state(Settings.Settings.instance.settings);
|
||||||
|
|
||||||
let isInitial = $state(true);
|
let isInitial = $state(true);
|
||||||
|
|
||||||
|
const readSavedSettings = () => {
|
||||||
|
const parsed = readSettings<typeof settings>();
|
||||||
|
return Object.keys(parsed).length ? parsed : null;
|
||||||
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!browser) return;
|
if (!browser) return;
|
||||||
if (isInitial) {
|
if (isInitial) {
|
||||||
|
|
@ -20,12 +25,9 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const savedSettings = localStorage.getItem("settings");
|
const parsedSettings = readSavedSettings();
|
||||||
if (savedSettings) {
|
if (parsedSettings && JSON.stringify(parsedSettings) === JSON.stringify(settings))
|
||||||
const parsedSettings = JSON.parse(savedSettings);
|
return;
|
||||||
if (JSON.stringify(parsedSettings) === JSON.stringify(settings))
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Settings.Settings.instance.settings = settings;
|
Settings.Settings.instance.settings = settings;
|
||||||
|
|
@ -41,9 +43,8 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const savedSettings = localStorage.getItem("settings");
|
const parsedSettings = readSavedSettings();
|
||||||
if (savedSettings) {
|
if (parsedSettings) {
|
||||||
const parsedSettings = JSON.parse(savedSettings);
|
|
||||||
Settings.Settings.instance.settings = {
|
Settings.Settings.instance.settings = {
|
||||||
...Settings.Settings.instance.settings,
|
...Settings.Settings.instance.settings,
|
||||||
...parsedSettings,
|
...parsedSettings,
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,10 @@ self.addEventListener("fetch", (event) => {
|
||||||
self.addEventListener("message", (event) => {
|
self.addEventListener("message", (event) => {
|
||||||
if (!event.data) return;
|
if (!event.data) return;
|
||||||
const type = event.data.type;
|
const type = event.data.type;
|
||||||
|
const port = event.ports?.[0];
|
||||||
|
|
||||||
if (type === "GET_CACHE_INFO") {
|
if (type === "GET_CACHE_INFO") {
|
||||||
|
if (!port) return;
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.open(CACHE_NAME).then(async (cache) => {
|
caches.open(CACHE_NAME).then(async (cache) => {
|
||||||
const keys = await cache.keys();
|
const keys = await cache.keys();
|
||||||
|
|
@ -159,7 +161,7 @@ self.addEventListener("message", (event) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.ports[0].postMessage({
|
port.postMessage({
|
||||||
totalSize,
|
totalSize,
|
||||||
fileCount: files.length,
|
fileCount: files.length,
|
||||||
files,
|
files,
|
||||||
|
|
@ -169,6 +171,7 @@ self.addEventListener("message", (event) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === "CLEAR_CACHE") {
|
if (type === "CLEAR_CACHE") {
|
||||||
|
if (!port) return;
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches
|
caches
|
||||||
.delete(CACHE_NAME)
|
.delete(CACHE_NAME)
|
||||||
|
|
@ -177,11 +180,11 @@ self.addEventListener("message", (event) => {
|
||||||
return caches.open(CACHE_NAME);
|
return caches.open(CACHE_NAME);
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
event.ports[0].postMessage({ success: true });
|
port.postMessage({ success: true });
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("[SW] failed to clear cache:", err);
|
console.error("[SW] failed to clear cache:", err);
|
||||||
event.ports[0].postMessage({
|
port.postMessage({
|
||||||
success: false,
|
success: false,
|
||||||
error: err.message,
|
error: err.message,
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue