Compare commits

..

No commits in common. "main" and "migrate-to-sveltekit" have entirely different histories.

23 changed files with 215 additions and 503 deletions

View file

@ -13,15 +13,24 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.SSH_KEY }}" > ./deploy.key
chmod 600 ./deploy.key
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
- name: Use Node.js 19
uses: actions/setup-node@v4
with:
node-version: 19
env:
SSH_PRIVATE_KEY: ${{secrets.SSH_KEY}}
SSH_KNOWN_HOSTS: ${{secrets.SSH_KNOWN_HOSTS}}
- name: Install PM2
run: npm i -g pm2
- name: Add Deploy Key to SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" >> ./deploy.key
sudo chmod 600 ./deploy.key
echo "${{ secrets.SSH_KNOWN_HOSTS}}" > ~/.ssh/known_hosts
- name: Deploy
run: pm2 deploy ecosystem.config.cjs production

65
.gitignore vendored
View file

@ -8,68 +8,3 @@ node_modules
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
deploy.key
vite: {
server: {
port: 7280,
},
},
server {
if ($host = www.embroideryviewer.xyz) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = embroideryviewer.xyz) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name embroideryviewer.xyz www.embroideryviewer.xyz;
location / {
proxy_pass http://embroidery; # Replace with actual port
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
server {
listen 443 ssl;
server_name embroideryviewer.xyz www.embroideryviewer.xyz;
location / {
proxy_pass http://embroidery; # Replace with actual port
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
ssl_certificate /etc/letsencrypt/live/embroideryviewer.xyz/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/embroideryviewer.xyz/privkey.pem; # managed by Certbot
}
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444; # Or return 404;
}

View file

@ -1,12 +1,38 @@
# Embroidery Viewer
# sv
![Deploy workflow status](https://git.leomurca.xyz/leomurca/embroidery-viewer/actions/workflows/deploy.yml/badge.svg)
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
A free online tool to view embroidery files.
Available at https://embroideryviewer.xyz.
## Creating a project
![Demo](/demo.gif)
If you're seeing this, you've probably already done this step. Congrats!
Current supported formats: **.pes, .dst, .pec, .jef and .exp**.
```bash
# create a new project in the current directory
npx sv create
Inspired by https://github.com/redteam316/html5-embroidery.git..
# create a new project in my-app
npx sv create my-app
```
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.

BIN
demo.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 MiB

View file

@ -1,7 +1,7 @@
module.exports = {
apps: [
{
name: 'embroidery-viewer-prod',
name: 'embroidery-viewer',
script: './build/index.js',
time: true,
instances: 1,
@ -11,7 +11,6 @@ module.exports = {
max_memory_restart: '1G',
env: {
NODE_ENV: 'production',
PORT: 7281,
},
},
],
@ -25,9 +24,9 @@ module.exports = {
path: '/home/deployer/embroidery-viewer',
'pre-deploy': 'rm package-lock.json && npm i',
'post-deploy':
'npm run build && pm2 reload ecosystem.config.cjs --only embroidery-viewer-prod --env production && pm2 save',
'npm run build && pm2 reload ecosystem.config.cjs --only acelera-alagoas-prod --env production && pm2 save',
env: {
PORT: 7281,
PORT: 7017,
NODE_ENV: 'production',
},
},

293
package-lock.json generated
View file

@ -8,13 +8,13 @@
"name": "embroidery-viewer",
"version": "0.0.1",
"dependencies": {
"@sveltejs/adapter-node": "^5.2.12",
"accept-language-parser": "^1.5.0",
"sveltekit-i18n": "^2.4.2"
},
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"eslint": "^9.28.0",
@ -49,6 +49,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -65,6 +66,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -81,6 +83,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -97,6 +100,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -113,6 +117,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -129,6 +134,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -145,6 +151,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -161,6 +168,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -177,6 +185,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -193,6 +202,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -209,6 +219,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -225,6 +236,7 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -241,6 +253,7 @@
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -257,6 +270,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -273,6 +287,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -289,6 +304,7 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -305,6 +321,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -321,6 +338,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -337,6 +355,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -353,6 +372,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -369,6 +389,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -385,6 +406,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -401,6 +423,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -417,6 +440,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -433,6 +457,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -732,109 +757,9 @@
"version": "1.0.0-next.29",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz",
"integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==",
"dev": true,
"license": "MIT"
},
"node_modules/@rollup/plugin-commonjs": {
"version": "28.0.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.3.tgz",
"integrity": "sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"commondir": "^1.0.1",
"estree-walker": "^2.0.2",
"fdir": "^6.2.0",
"is-reference": "1.2.1",
"magic-string": "^0.30.3",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=16.0.0 || 14 >= 14.17"
},
"peerDependencies": {
"rollup": "^2.68.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-commonjs/node_modules/is-reference": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
"integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/@rollup/plugin-json": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-json/-/plugin-json-6.1.0.tgz",
"integrity": "sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.1.0"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz",
"integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==",
"license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/pluginutils": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
"license": "MIT",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
"picomatch": "^4.0.2"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
"optional": true
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz",
@ -842,6 +767,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -855,6 +781,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -868,6 +795,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -881,6 +809,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -894,6 +823,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -907,6 +837,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -920,6 +851,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -933,6 +865,7 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -946,6 +879,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -959,6 +893,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -972,6 +907,7 @@
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -985,6 +921,7 @@
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -998,6 +935,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1011,6 +949,7 @@
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1024,6 +963,7 @@
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1037,6 +977,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1050,6 +991,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1063,6 +1005,7 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1076,6 +1019,7 @@
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1089,6 +1033,7 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@ -1104,25 +1049,21 @@
"acorn": "^8.9.0"
}
},
"node_modules/@sveltejs/adapter-node": {
"version": "5.2.12",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-node/-/adapter-node-5.2.12.tgz",
"integrity": "sha512-0bp4Yb3jKIEcZWVcJC/L1xXp9zzJS4hDwfb4VITAkfT4OVdkspSHsx7YhqJDbb2hgLl6R9Vs7VQR+fqIVOxPUQ==",
"node_modules/@sveltejs/adapter-auto": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-6.0.1.tgz",
"integrity": "sha512-mcWud3pYGPWM2Pphdj8G9Qiq24nZ8L4LB7coCUckUEy5Y7wOWGJ/enaZ4AtJTcSm5dNK1rIkBRoqt+ae4zlxcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@rollup/plugin-commonjs": "^28.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.0",
"rollup": "^4.9.5"
},
"peerDependencies": {
"@sveltejs/kit": "^2.4.0"
"@sveltejs/kit": "^2.0.0"
}
},
"node_modules/@sveltejs/kit": {
"version": "2.21.1",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.21.1.tgz",
"integrity": "sha512-vLbtVwtDcK8LhJKnFkFYwM0uCdFmzioQnif0bjEYH1I24Arz22JPr/hLUiXGVYAwhu8INKx5qrdvr4tHgPwX6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sveltejs/acorn-typescript": "^1.0.5",
@ -1154,6 +1095,7 @@
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz",
"integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
@ -1175,6 +1117,7 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-4.0.1.tgz",
"integrity": "sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==",
"dev": true,
"license": "MIT",
"dependencies": {
"debug": "^4.3.7"
@ -1207,6 +1150,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/estree": {
@ -1222,12 +1166,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/resolve": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
"license": "MIT"
},
"node_modules/accept-language-parser": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz",
@ -1404,12 +1342,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
"integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1421,6 +1353,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.6"
@ -1458,6 +1391,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@ -1482,6 +1416,7 @@
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -1491,12 +1426,14 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
"dev": true,
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
@ -1756,12 +1693,6 @@
"node": ">=4.0"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@ -1797,6 +1728,7 @@
"version": "6.4.5",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.5.tgz",
"integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"picomatch": "^3 || ^4"
@ -1862,6 +1794,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@ -1872,15 +1805,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -1917,18 +1841,6 @@
"node": ">=8"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -1966,21 +1878,6 @@
"node": ">=0.8.19"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -2004,12 +1901,6 @@
"node": ">=0.10.0"
}
},
"node_modules/is-module": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
"integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
"license": "MIT"
},
"node_modules/is-reference": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
@ -2074,6 +1965,7 @@
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
"integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -2165,6 +2057,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@ -2174,6 +2067,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
"integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@ -2183,12 +2077,14 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
@ -2293,22 +2189,18 @@
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@ -2321,6 +2213,7 @@
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz",
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -2514,26 +2407,6 @@
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -2548,6 +2421,7 @@
"version": "4.41.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
"integrity": "sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.7"
@ -2587,6 +2461,7 @@
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"mri": "^1.1.0"
@ -2612,6 +2487,7 @@
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"dev": true,
"license": "MIT"
},
"node_modules/shebang-command": {
@ -2641,6 +2517,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz",
"integrity": "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@polka/url": "^1.0.0-next.24",
@ -2655,6 +2532,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@ -2686,18 +2564,6 @@
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/svelte": {
"version": "5.33.13",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.33.13.tgz",
@ -2796,6 +2662,7 @@
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz",
"integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.4.4",
@ -2812,6 +2679,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz",
"integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -2865,6 +2733,7 @@
"version": "6.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
@ -2939,6 +2808,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.0.6.tgz",
"integrity": "sha512-+Rex1GlappUyNN6UfwbVZne/9cYC4+R2XDk9xkNXBKMw6HQagdX9PgZ8V2v1WUSK1wfBLp7qbI1+XSNIlB1xmA==",
"dev": true,
"license": "MIT",
"workspaces": [
"tests/deps/*",
@ -2983,6 +2853,7 @@
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
"integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
"dev": true,
"license": "ISC",
"optional": true,
"peer": true,

View file

@ -1,7 +1,7 @@
{
"name": "embroidery-viewer",
"private": true,
"version": "2.1.1",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite dev",
@ -16,6 +16,7 @@
"devDependencies": {
"@eslint/compat": "^1.2.5",
"@eslint/js": "^9.18.0",
"@sveltejs/adapter-auto": "^6.0.0",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"eslint": "^9.28.0",
@ -30,7 +31,6 @@
"vite": "^6.2.6"
},
"dependencies": {
"@sveltejs/adapter-node": "^5.2.12",
"accept-language-parser": "^1.5.0",
"sveltekit-i18n": "^2.4.2"
}

View file

@ -1,13 +0,0 @@
<script>
import { isDevelopment } from '$lib/utils/env';
</script>
<svelte:head>
{#if !isDevelopment()}
<script
defer
src="https://umami.leomurca.xyz/script.js"
data-website-id="bd4c0533-36e6-402d-ac04-577993aaf43a"
></script>
{/if}
</svelte:head>

View file

@ -2,30 +2,17 @@
import { t } from '$lib/translations';
import renderFileToCanvas from '$lib/file-renderer';
/**
* @type {ArrayLike<any>}
*/
export let files = [];
/**
* @type {HTMLElement}
*/
let errorMessageRef;
let canvasRefs = [];
let colorRefs = [];
let stitchesRefs = [];
let sizeRefs = [];
let errorMessageRef;
let localizedStrings = {
stitches: $t('viewer.stitches'),
dimensions: $t('viewer.dimensions'),
};
/**
* Downloads a given HTMLCanvasElement as a PNG image.
*
* @param {HTMLCanvasElement} canvas - The canvas element to export as an image.
* @param {string} filename - The desired name of the downloaded file (extension will be replaced with `.png`).
*/
const downloadCanvasAsImage = (canvas, filename) => {
const image = canvas
.toDataURL('image/png')
@ -37,15 +24,9 @@
link.click();
};
/**
* Cliks the button to render the files when user press enter.
*
* @param {KeyboardEvent} evt - The event that triggered the language switch.
*/
const onKeydown = (evt) => {
if (evt.key === 'Enter') {
const button = document.getElementById('download-button');
if (button) button.click();
document.getElementById('download-button').click();
}
};
</script>
@ -63,8 +44,8 @@
id="download-button"
role="button"
tabindex="0"
onkeydown={onKeydown}
onclick={() => downloadCanvasAsImage(canvasRefs[i], file.name)}
on:keydown={onKeydown}
on:click={() => downloadCanvasAsImage(canvasRefs[i], file.name)}
>
{$t('viewer.download')}
</div>

View file

@ -11,7 +11,6 @@
</script>
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
id="dropzone"
tabindex={0}

View file

@ -1,8 +1,5 @@
<script>
export let title;
/**
* @type {ArrayLike<any>}
*/
export let files = [];
export let isError = false;
</script>

View file

@ -1,28 +1,15 @@
<script>
import { t, locale, SUPPORTED_LOCALES } from '$lib/translations';
import { t, locale, locales, SUPPORTED_LOCALES } from '$lib/translations';
import logo from '$lib/assets/logo.webp';
import MediaQuery from './MediaQuery.svelte';
/**
*
* Returns logo image configuration based on the screen size match.
*
* @param {boolean} matches - The event that triggered the language switch.
*/
const configsFor = (matches) => {
const configsFor = (/** @type {boolean} */ matches) => {
return matches
? { src: logo, width: 150, height: 70 } // mobile
: { src: logo, width: 150, height: 100 }; // desktop
};
/**
* Switches the current locale to the opposite language (EN_US <-> PT_BR).
* Prevents the default link behavior (e.g., page jump).
*
* @param {MouseEvent | KeyboardEvent} e - The event that triggered the language switch.
*/
const onSwitchToOppositeLang = (e) => {
e.preventDefault();
const onSwitchToOppositeLang = () => {
$locale =
$locale === SUPPORTED_LOCALES.EN_US
? SUPPORTED_LOCALES.PT_BR
@ -51,7 +38,7 @@
<MediaQuery query="(max-width: 768px)" let:matches>
<slot let-matches>
{#if matches}
<button class="hamburger" onclick={() => (isMenuOpen = !isMenuOpen)}>
<button class="hamburger" on:click={() => (isMenuOpen = !isMenuOpen)}>
{#if isMenuOpen}x{:else}{/if}
</button>
{/if}
@ -74,22 +61,17 @@
</ul>
</nav>
<button
type="button"
<a
class="common-switch {$locale === SUPPORTED_LOCALES.EN_US
? 'portuguese-switch'
: 'english-switch'}"
onclick={onSwitchToOppositeLang}
onkeydown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
onSwitchToOppositeLang(e);
}
}}
href="#"
on:click|preventDefault={onSwitchToOppositeLang}
>
<div style="display: flex; width: fit-content;">
<span style="font-size: 20px;">{$t('header.languageSwitch')}</span>
</div>
</button>
</a>
</div>
</header>
@ -148,10 +130,7 @@
}
.common-switch {
border: none;
background-color: unset;
width: fit-content;
cursor: pointer;
}
.portuguese-switch {

View file

@ -50,12 +50,12 @@
<meta property="og:type" content={ogType} />
<meta property="og:title" content={$t(ogTitle)} />
<meta property="og:description" content={$t(ogDescription)} />
<!-- <meta property="og:image" content={$t(ogImage)} /> -->
<meta property="og:url" content={$t(url)} />
<meta property="og:image" content={ogImage} />
<meta property="og:url" content={url} />
<!-- Twitter -->
<!-- <meta name="twitter:card" content={$t(twitterCard)} /> -->
<meta name="twitter:title" content={$t(twitterTitle)} />
<meta name="twitter:card" content={twitterCard} />
<meta name="twitter:title" content={twitterTitle} />
<meta name="twitter:description" content={$t(twitterDescription)} />
<!-- <meta name="twitter:image" content={$t(twitterImage)} /> -->
<meta name="twitter:image" content={twitterImage} />
</svelte:head>

View file

@ -12,7 +12,7 @@
"paypal.link": "Open Donation link",
"seo.title": "💖 Donate Support Embroidery Viewer",
"seo.description": "Help keep Embroidery Viewer free and improving by making a donation. Choose from Bitcoin, Monero, PayPal, or other secure options to support ongoing development and hosting.",
"seo.keywords": "donate embroidery viewer, support embroidery viewer, embroidery viewer donations, help embroidery viewer, fund embroidery viewer, bitcoin donation embroidery, monero donation embroidery, paypal donation embroidery",
"keywords": "donate embroidery viewer, support embroidery viewer, embroidery viewer donations, help embroidery viewer, fund embroidery viewer, bitcoin donation embroidery, monero donation embroidery, paypal donation embroidery",
"url": "https://embroideryviewer.xyz/donate",
"image": "https://embroideryviewer.xyz/og/donate.png"
}

View file

@ -16,7 +16,7 @@ export const SUPPORTED_LOCALES = Object.freeze({
/** @type {import('sveltekit-i18n').Config} */
const config = {
initLocale: SUPPORTED_LOCALES.EN_US,
initLocale: navigator.language,
fallbackLocale: SUPPORTED_LOCALES.EN_US,
loaders: [
{
@ -127,10 +127,7 @@ export const {
} = new i18n(config);
locale.subscribe(($locale) => {
if (typeof localStorage !== 'undefined' && $locale) {
const existing = localStorage.getItem('locale');
if (existing !== $locale) {
localStorage.setItem('locale', $locale);
}
if (typeof document !== 'undefined') {
document.cookie = `locale=${$locale}; path=/; SameSite=Strict;`;
}
});

View file

@ -9,7 +9,6 @@
"selected": "Arquivos selecionados",
"rejected": "Arquivos recusados",
"stitches": "Pontos",
"dimensions": "Dimensões (x, y)",
"download": "Baixar imagem",
"warning.copyright": "Não carregue material protegido por direitos autorais que você não possui ou sobre os quais não tenha direitos.",
"seo.title": "🧵 Visualizador Online Gratuito de Arquivos de Bordado Rápido, Privado e Sem Cadastro",

View file

@ -4,8 +4,4 @@ function appVersion() {
return APP_VERSION;
}
function isDevelopment() {
return import.meta.env.MODE === 'development';
}
export { appVersion, isDevelopment };
export { appVersion };

View file

@ -1,51 +1,17 @@
import { browser } from '$app/environment';
import { loadTranslations, setLocale, setRoute } from '$lib/translations';
import { SUPPORTED_LOCALES } from '$lib/translations';
import { setLocale, setRoute } from '$lib/translations';
/**
* Type guard that checks if a string is a supported locale.
* @param {string | null} locale
* @returns {locale is "en-US" | "pt-BR"}
* @typedef {Object} LayoutData
* @property {string} route
* @property {string} language
*/
function isSupportedLocale(locale) {
// @ts-ignore
return locale !== null && Object.values(SUPPORTED_LOCALES).includes(locale);
}
/**
* Client-side load function to initialize translations based on localStorage or server fallback.
*
* This function runs in the browser and:
* - Prioritizes locale from localStorage (if valid)
* - Falls back to the server-provided fallbackLanguage (from Accept-Language)
* - Initializes translations and routing
*
* @type {import('@sveltejs/kit').Load}
*/
export const load = async ({ data, url }) => {
/** @type {string} */
const route = url.pathname;
/** @type {import('@sveltejs/kit').Load<LayoutData>} */
export const load = async ({ data }) => {
const { route, language } = data ?? {};
/** @type {"en-US" | "pt-BR"} */
let language;
if (route) await setRoute(route);
if (language) await setLocale(language);
if (browser) {
/**
* Locale stored in the browser, if any.
* @type {string | null}
*/
const stored = localStorage.getItem('locale');
if (isSupportedLocale(stored)) {
language = stored; // Type narrowed here
} else {
language = data?.fallbackLanguage ?? SUPPORTED_LOCALES.EN_US;
}
await loadTranslations(language, route);
await setLocale(language);
await setRoute(route);
}
return {};
return data ?? {};
};

View file

@ -1,44 +1,54 @@
import { parse } from 'accept-language-parser';
import { loadTranslations, setLocale, setRoute } from '$lib/translations';
import { SUPPORTED_LOCALES } from '$lib/translations';
/**
* A Set of all supported locale codes for quick validation.
* A set of all supported locale codes, used to validate and match against
* user preferences from cookies or Accept-Language headers. We're using a
* Set for better performance in lookup.
*
* Example values: "en-US", "pt-BR"
* @type {Set<string>}
*/
const SUPPORTED_LOCALE_SET = new Set(Object.values(SUPPORTED_LOCALES));
/**
* Extracts the best matching locale from an Accept-Language HTTP header.
*
* @param {string | null} header - The Accept-Language header value.
* @returns {string | null} - A supported locale string like "en-US", or null if none matched.
* Returns a valid locale from cookies, or null if not valid/found.
* @param {{ get: (cookies: string) => any; }} cookies
*/
function localeFromHeader(header) {
if (!header) return null;
const parsed = parse(header);
for (const { code, region } of parsed) {
const locale = region ? `${code}-${region}` : code;
if (SUPPORTED_LOCALE_SET.has(locale)) return locale;
}
return null;
function localeFromCookies(cookies) {
const locale = cookies.get('locale');
return locale && SUPPORTED_LOCALE_SET.has(locale) ? locale : null;
}
/**
* Server-side load function that returns the initial route and fallback language.
* The language is inferred from the Accept-Language header.
* `localStorage` will take precedence on the client.
*
* @type {import('@sveltejs/kit').ServerLoad}
* Parses the Accept-Language header and returns the best matching locale.
* @param {string | null | undefined} header
*/
export async function load({ url, request }) {
/** @type {string} */
function localeFromHeader(header) {
if (!header) return null;
const parsedLanguages = parse(header);
for (const { code, region } of parsedLanguages) {
const locale = region ? `${code}-${region}` : code;
if (SUPPORTED_LOCALE_SET.has(locale)) {
return locale;
}
}
return null;
}
/** @type {import('@sveltejs/kit').ServerLoad}*/
export async function load({ url, request, cookies }) {
const cookieLocale = localeFromCookies(cookies);
const headerLocale = localeFromHeader(request.headers.get('accept-language'));
const language = cookieLocale || headerLocale || SUPPORTED_LOCALES.EN_US;
const route = url.pathname;
/** @type {string} */
const fallbackLanguage =
localeFromHeader(request.headers.get('accept-language')) ||
SUPPORTED_LOCALES.EN_US;
await loadTranslations(language, route);
setLocale(language);
setRoute(route);
return { fallbackLanguage, route };
return { language, route };
}

View file

@ -1,29 +1,13 @@
<script>
import { browser } from '$app/environment';
import { onMount } from 'svelte';
import Header from '$lib/components/Header.svelte';
import Footer from '$lib/components/Footer.svelte';
import Analytics from '$lib/components/Analytics.svelte';
let mounted = false;
if (browser) {
onMount(() => {
mounted = true;
});
}
</script>
<Analytics />
{#if mounted}
<Header />
<main>
<Header />
<main>
<slot />
</main>
<Footer />
{/if}
</main>
<Footer />
<style>
main {

View file

@ -24,14 +24,7 @@
maxSize: 1000000,
};
/**
* Update the flag that indicates that the files were rendered.
*
* @param {SubmitEvent} e
*/
function onSubmit(e) {
e.preventDefault();
e.stopPropagation();
function onSubmit() {
areAcceptedFilesRendered = true;
}
@ -85,7 +78,11 @@
<Seo {...metadata} />
<form id="form" enctype="multipart/form-data" onsubmit={onSubmit}>
<form
id="form"
enctype="multipart/form-data"
on:submit|preventDefault|stopPropagation={onSubmit}
>
<div class="title-container">
<h2>{$t('viewer.title')}</h2>
</div>

View file

@ -1,22 +1,5 @@
import adapter from '@sveltejs/adapter-node';
import adapter from '@sveltejs/adapter-auto';
/** @type {import('@sveltejs/kit').Config} */
const config = {
extensions: ['.svelte'],
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter(),
},
vite: {
server: {
port: 7280,
},
},
prerender: {
origin: 'https://embroideryviewer.xyz',
},
};
const config = { kit: { adapter: adapter() } };
export default config;

View file

@ -6,7 +6,4 @@ export default defineConfig({
define: {
APP_VERSION: JSON.stringify(process.env.npm_package_version),
},
build: {
minify: true,
},
});