mirror of https://github.com/VERT-sh/VERT.git
Merge branch 'main' into main
This commit is contained in:
commit
2128445eaf
|
|
@ -0,0 +1,4 @@
|
||||||
|
# For libvips/wasm-vips converter (images)
|
||||||
|
/*
|
||||||
|
Cross-Origin-Embedder-Policy: require-corp
|
||||||
|
Cross-Origin-Opener-Policy: same-origin
|
||||||
|
|
@ -45,6 +45,7 @@
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"jsmediatags": "^3.9.7",
|
"jsmediatags": "^3.9.7",
|
||||||
"lucide-svelte": "^0.475.0",
|
"lucide-svelte": "^0.475.0",
|
||||||
|
"vite-plugin-static-copy": "^2.2.0",
|
||||||
"wasm-vips": "^0.0.11"
|
"wasm-vips": "^0.0.11"
|
||||||
},
|
},
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
|
|
|
||||||
16
src/app.html
16
src/app.html
|
|
@ -2,8 +2,15 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%sveltekit.assets%/favicon.webp" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<link rel="apple-touch-icon" href="%sveltekit.assets%/favicon.png"">
|
||||||
|
|
||||||
|
<link rel="apple-touch-startup-image" href="%sveltekit.assets%/lettermark.jpg">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
||||||
|
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
|
|
@ -37,6 +44,13 @@
|
||||||
|
|
||||||
console.log(`Applying theme: ${theme}`);
|
console.log(`Applying theme: ${theme}`);
|
||||||
document.documentElement.classList.add(theme);
|
document.documentElement.classList.add(theme);
|
||||||
|
|
||||||
|
// Lock dark reader if it's set to dark mode
|
||||||
|
if (theme === "dark") {
|
||||||
|
const lock = document.createElement('meta');
|
||||||
|
lock.name = 'darkreader-lock';
|
||||||
|
document.head.appendChild(lock);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,9 @@ export class VipsConverter extends Converter {
|
||||||
".tif",
|
".tif",
|
||||||
".tiff",
|
".tiff",
|
||||||
".jfif",
|
".jfif",
|
||||||
|
//".heif", HEIF files that are encoded like HEIC files (and HEIC files in general) aren't supported due to https://github.com/kleisauke/wasm-vips/issues/3
|
||||||
|
".avif",
|
||||||
|
".jxl",
|
||||||
];
|
];
|
||||||
|
|
||||||
public readonly reportsProgress = false;
|
public readonly reportsProgress = false;
|
||||||
|
|
@ -51,8 +54,6 @@ export class VipsConverter extends Converter {
|
||||||
error(["converters", this.name], `error in worker: ${message.error}`);
|
error(["converters", this.name], `error in worker: ${message.error}`);
|
||||||
addToast("error", `Error in VIPS worker, some features may not work.`);
|
addToast("error", `Error in VIPS worker, some features may not work.`);
|
||||||
throw new Error(message.error);
|
throw new Error(message.error);
|
||||||
} else {
|
|
||||||
error(["converters", this.name], `unknown message type: ${message.type}`);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,7 +173,7 @@ class Files {
|
||||||
const file = files.files[i];
|
const file = files.files[i];
|
||||||
const result = file.result;
|
const result = file.result;
|
||||||
if (!result) {
|
if (!result) {
|
||||||
console.error("No result found");
|
error(["files"], "No result found");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
dlFiles.push({
|
dlFiles.push({
|
||||||
|
|
@ -215,6 +215,16 @@ export function setTheme(themeTo: "light" | "dark") {
|
||||||
});
|
});
|
||||||
log(["theme"], `set to ${themeTo}`);
|
log(["theme"], `set to ${themeTo}`);
|
||||||
theme.set(themeTo);
|
theme.set(themeTo);
|
||||||
|
|
||||||
|
// Lock dark reader if it's set to dark mode
|
||||||
|
if (themeTo === "dark") {
|
||||||
|
const lock = document.createElement('meta');
|
||||||
|
lock.name = 'darkreader-lock';
|
||||||
|
document.head.appendChild(lock);
|
||||||
|
} else {
|
||||||
|
const lock = document.querySelector('meta[name="darkreader-lock"]');
|
||||||
|
if (lock) lock.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setEffects(effectsEnabled: boolean) {
|
export function setEffects(effectsEnabled: boolean) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Converter } from "$lib/converters/converter.svelte";
|
import type { Converter } from "$lib/converters/converter.svelte";
|
||||||
|
import { error } from "$lib/logger";
|
||||||
import { addToast } from "$lib/store/ToastProvider";
|
import { addToast } from "$lib/store/ToastProvider";
|
||||||
|
|
||||||
export class VertFile {
|
export class VertFile {
|
||||||
|
|
@ -46,7 +47,7 @@ export class VertFile {
|
||||||
res = await this.converter.convert(this, this.to);
|
res = await this.converter.convert(this, this.to);
|
||||||
this.result = res;
|
this.result = res;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
error(["files"], err);
|
||||||
addToast("error", `Error converting file: ${this.file.name}`);
|
addToast("error", `Error converting file: ${this.file.name}`);
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
import { type WorkerMessage, type OmitBetterStrict } from "$lib/types";
|
import { type WorkerMessage, type OmitBetterStrict } from "$lib/types";
|
||||||
import Vips from "wasm-vips";
|
import Vips from "wasm-vips";
|
||||||
|
|
||||||
const vipsPromise = Vips({
|
const vipsPromise = Vips({});
|
||||||
// see https://github.com/kleisauke/wasm-vips/issues/85
|
|
||||||
dynamicLibraries: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
vipsPromise
|
vipsPromise
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
@ -23,6 +20,7 @@ const handleMessage = async (
|
||||||
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
|
if (!message.to.startsWith(".")) message.to = `.${message.to}`;
|
||||||
const image = vips.Image.newFromBuffer(
|
const image = vips.Image.newFromBuffer(
|
||||||
await message.input.file.arrayBuffer(),
|
await message.input.file.arrayBuffer(),
|
||||||
|
`${message.to === ".gif" || message.to === ".webp" ? "[n=-1]" : ""}`,
|
||||||
);
|
);
|
||||||
const output = image.writeToBuffer(message.to);
|
const output = image.writeToBuffer(message.to);
|
||||||
image.delete();
|
image.delete();
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="With VERT you can convert image and audio files to and from PNG, JPG, WEBP, MP3, WAV, FLAC, and more. No ads, no tracking, open source, and all processing is done on your device."
|
content="With VERT you can quickly convert any image, video and audio file. No ads, no tracking, open source, and all processing is done on your device."
|
||||||
/>
|
/>
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta
|
<meta
|
||||||
|
|
@ -73,7 +73,7 @@
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="og:description"
|
property="og:description"
|
||||||
content="With VERT you can convert image and audio files to and from PNG, JPG, WEBP, MP3, WAV, FLAC, and more. No ads, no tracking, open source, and all processing is done on your device."
|
content="With VERT you can quickly convert any image, video and audio file. No ads, no tracking, open source, and all processing is done on your device."
|
||||||
/>
|
/>
|
||||||
<meta property="og:image" content={featuredImage} />
|
<meta property="og:image" content={featuredImage} />
|
||||||
<meta property="twitter:card" content="summary_large_image" />
|
<meta property="twitter:card" content="summary_large_image" />
|
||||||
|
|
@ -83,15 +83,15 @@
|
||||||
/>
|
/>
|
||||||
<meta
|
<meta
|
||||||
property="twitter:description"
|
property="twitter:description"
|
||||||
content="With VERT you can convert image and audio files to and from PNG, JPG, WEBP, MP3, WAV, FLAC, and more. No ads, no tracking, open source, and all processing is done on your device."
|
content="With VERT you can quickly convert any image, video and audio file. No ads, no tracking, open source, and all processing is done on your device."
|
||||||
/>
|
/>
|
||||||
<meta property="twitter:image" content={featuredImage} />
|
<meta property="twitter:image" content={featuredImage} />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
{#if PUB_PLAUSIBLE_URL}<script
|
{#if PUB_PLAUSIBLE_URL}<script
|
||||||
defer
|
defer
|
||||||
data-domain={PUB_HOSTNAME || "vert.sh"}
|
data-domain={PUB_HOSTNAME || "vert.sh"}
|
||||||
src="{PUB_PLAUSIBLE_URL}/js/script.pageview-props.tagged-events.js"
|
src="{PUB_PLAUSIBLE_URL}/js/script.pageview-props.tagged-events.js"
|
||||||
></script>{/if}
|
></script>{/if}
|
||||||
<script src="/coi-serviceworker.min.js"></script>
|
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<!-- FIXME: if user resizes between desktop/mobile, highlight of page disappears (only shows on original size) -->
|
<!-- FIXME: if user resizes between desktop/mobile, highlight of page disappears (only shows on original size) -->
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
import Uploader from "$lib/components/functional/Uploader.svelte";
|
import Uploader from "$lib/components/functional/Uploader.svelte";
|
||||||
import Panel from "$lib/components/visual/Panel.svelte";
|
import Panel from "$lib/components/visual/Panel.svelte";
|
||||||
import ProgressBar from "$lib/components/visual/ProgressBar.svelte";
|
import ProgressBar from "$lib/components/visual/ProgressBar.svelte";
|
||||||
import { converters } from "$lib/converters";
|
|
||||||
import {
|
import {
|
||||||
effects,
|
effects,
|
||||||
files,
|
files,
|
||||||
|
|
@ -12,6 +11,7 @@
|
||||||
showGradient,
|
showGradient,
|
||||||
vertdLoaded,
|
vertdLoaded,
|
||||||
} from "$lib/store/index.svelte";
|
} from "$lib/store/index.svelte";
|
||||||
|
import { addToast } from "$lib/store/ToastProvider";
|
||||||
import { VertFile } from "$lib/types";
|
import { VertFile } from "$lib/types";
|
||||||
import {
|
import {
|
||||||
AudioLines,
|
AudioLines,
|
||||||
|
|
@ -26,6 +26,18 @@
|
||||||
XIcon,
|
XIcon,
|
||||||
} from "lucide-svelte";
|
} from "lucide-svelte";
|
||||||
|
|
||||||
|
const handleSelect = (option: string, file: VertFile) => {
|
||||||
|
file.result = null;
|
||||||
|
switch (option) {
|
||||||
|
case ".webp":
|
||||||
|
case ".gif":
|
||||||
|
addToast(
|
||||||
|
"warning",
|
||||||
|
`Converting this file to "${option}" may take some time if animated.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
// Set gradient color depending on the file types
|
// Set gradient color depending on the file types
|
||||||
// TODO: if more file types added, add a "fileType" property to the file object
|
// TODO: if more file types added, add a "fileType" property to the file object
|
||||||
|
|
@ -173,7 +185,7 @@
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={file.converter?.supportedFormats || []}
|
options={file.converter?.supportedFormats || []}
|
||||||
bind:selected={file.to}
|
bind:selected={file.to}
|
||||||
onselect={() => file.result && (file.result = null)}
|
onselect={(option) => handleSelect(option, file)}
|
||||||
/>
|
/>
|
||||||
<div class="w-full flex items-center justify-between">
|
<div class="w-full flex items-center justify-between">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
/// <reference types="@sveltejs/kit" />
|
||||||
|
// code modified from https://svelte.dev/docs/kit/service-workers
|
||||||
|
import { build, files, version } from "$service-worker";
|
||||||
|
|
||||||
|
// create a unique cache name for this deployment
|
||||||
|
const CACHE = `cache-${version}`;
|
||||||
|
|
||||||
|
const ASSETS = [
|
||||||
|
...build, // the app itself
|
||||||
|
...files, // everything in `static`
|
||||||
|
];
|
||||||
|
|
||||||
|
self.addEventListener("install", (event) => {
|
||||||
|
// create a new cache and add all files to it
|
||||||
|
async function addFilesToCache() {
|
||||||
|
try {
|
||||||
|
const cache = await caches.open(CACHE);
|
||||||
|
await cache.addAll(ASSETS);
|
||||||
|
console.log(`assets cached successfully: ${ASSETS}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`failed to cache assets: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`installing service worker for version ${version}`);
|
||||||
|
event.waitUntil(addFilesToCache());
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("activate", (event) => {
|
||||||
|
// remove previous cached data from disk
|
||||||
|
async function deleteOldCaches() {
|
||||||
|
try {
|
||||||
|
const keys = await caches.keys();
|
||||||
|
for (const key of keys) {
|
||||||
|
if (key !== CACHE) {
|
||||||
|
await caches.delete(key);
|
||||||
|
console.log(`deleted old cache: ${key}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`failed to delete old caches: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.waitUntil(deleteOldCaches());
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (event) => {
|
||||||
|
// ignore requests other than GET
|
||||||
|
if (event.request.method !== "GET") return;
|
||||||
|
|
||||||
|
async function respond() {
|
||||||
|
const url = new URL(event.request.url);
|
||||||
|
const cache = await caches.open(CACHE);
|
||||||
|
|
||||||
|
// assets can always be served from the cache
|
||||||
|
if (ASSETS.includes(url.pathname)) {
|
||||||
|
const response = await cache.match(url.pathname);
|
||||||
|
if (response) {
|
||||||
|
console.log(`serving ${url.pathname} from cache`);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for everything else, try the network first, but
|
||||||
|
// fall back to the cache if we're offline
|
||||||
|
try {
|
||||||
|
const response = await fetch(event.request);
|
||||||
|
|
||||||
|
// if we're offline, fetch can return a value that is not a Response instead
|
||||||
|
// of throwing, and we can't pass this non-Response to respondWith
|
||||||
|
if (!(response instanceof Response)) {
|
||||||
|
throw new Error("invalid response from fetch");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 200)
|
||||||
|
cache.put(event.request, response.clone());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
const response = await cache.match(event.request);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
console.log(`Returning ${event.request.url} from cache`);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's no cache, then just error out
|
||||||
|
// as there is nothing we can do to respond to this request
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.respondWith(respond());
|
||||||
|
});
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
|
|
||||||
let coepCredentialless=!1;"undefined"==typeof window?(self.addEventListener("install",(()=>self.skipWaiting())),self.addEventListener("activate",(e=>e.waitUntil(self.clients.claim()))),self.addEventListener("message",(e=>{e.data&&("deregister"===e.data.type?self.registration.unregister().then((()=>self.clients.matchAll())).then((e=>{e.forEach((e=>e.navigate(e.url)))})):"coepCredentialless"===e.data.type&&(coepCredentialless=e.data.value))})),self.addEventListener("fetch",(function(e){const o=e.request;if("only-if-cached"===o.cache&&"same-origin"!==o.mode)return;const s=coepCredentialless&&"no-cors"===o.mode?new Request(o,{credentials:"omit"}):o;e.respondWith(fetch(s).then((e=>{if(0===e.status)return e;const o=new Headers(e.headers);return o.set("Cross-Origin-Embedder-Policy",coepCredentialless?"credentialless":"require-corp"),coepCredentialless||o.set("Cross-Origin-Resource-Policy","cross-origin"),o.set("Cross-Origin-Opener-Policy","same-origin"),new Response(e.body,{status:e.status,statusText:e.statusText,headers:o})})).catch((e=>console.error(e))))}))):(()=>{const e=window.sessionStorage.getItem("coiReloadedBySelf");window.sessionStorage.removeItem("coiReloadedBySelf");const o="coepdegrade"==e,s={shouldRegister:()=>!e,shouldDeregister:()=>!1,coepCredentialless:()=>!0,coepDegrade:()=>!0,doReload:()=>window.location.reload(),quiet:!1,...window.coi},r=navigator,t=r.serviceWorker&&r.serviceWorker.controller;t&&!window.crossOriginIsolated&&window.sessionStorage.setItem("coiCoepHasFailed","true");const i=window.sessionStorage.getItem("coiCoepHasFailed");if(t){const e=s.coepDegrade()&&!(o||window.crossOriginIsolated);r.serviceWorker.controller.postMessage({type:"coepCredentialless",value:!(e||i&&s.coepDegrade())&&s.coepCredentialless()}),e&&(!s.quiet&&console.log("Reloading page to degrade COEP."),window.sessionStorage.setItem("coiReloadedBySelf","coepdegrade"),s.doReload("coepdegrade")),s.shouldDeregister()&&r.serviceWorker.controller.postMessage({type:"deregister"})}!1===window.crossOriginIsolated&&s.shouldRegister()&&(window.isSecureContext?r.serviceWorker?r.serviceWorker.register(window.document.currentScript.src).then((e=>{!s.quiet&&console.log("COOP/COEP Service Worker registered",e.scope),e.addEventListener("updatefound",(()=>{!s.quiet&&console.log("Reloading page to make use of updated COOP/COEP Service Worker."),window.sessionStorage.setItem("coiReloadedBySelf","updatefound"),s.doReload()})),e.active&&!r.serviceWorker.controller&&(!s.quiet&&console.log("Reloading page to make use of COOP/COEP Service Worker."),window.sessionStorage.setItem("coiReloadedBySelf","notcontrolling"),s.doReload())}),(e=>{!s.quiet&&console.error("COOP/COEP Service Worker failed to register:",e)})):!s.quiet&&console.error("COOP/COEP Service Worker not registered, perhaps due to private mode."):!s.quiet&&console.log("COOP/COEP Service Worker not registered, a secure context is required."))})();
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
|
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
"name": "VERT",
|
||||||
|
"short_name": "VERT",
|
||||||
|
"description": "The file converter you'll love",
|
||||||
|
"start_url": "/",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"theme_color": "#F2ABEE",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "lettermark.jpg",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "lettermark.jpg",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||||
import svg from "@poppanator/sveltekit-svg";
|
import svg from "@poppanator/sveltekit-svg";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
|
@ -8,7 +9,7 @@ export default defineConfig({
|
||||||
{
|
{
|
||||||
name: "vips-request-middleware",
|
name: "vips-request-middleware",
|
||||||
configureServer(server) {
|
configureServer(server) {
|
||||||
server.middlewares.use((req, res, next) => {
|
server.middlewares.use((_req, res, next) => {
|
||||||
res.setHeader(
|
res.setHeader(
|
||||||
"Cross-Origin-Embedder-Policy",
|
"Cross-Origin-Embedder-Policy",
|
||||||
"require-corp",
|
"require-corp",
|
||||||
|
|
@ -31,6 +32,14 @@ export default defineConfig({
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
viteStaticCopy({
|
||||||
|
targets: [
|
||||||
|
{
|
||||||
|
src: "_headers",
|
||||||
|
dest: "",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: [
|
exclude: [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue