diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd192f2..9ac88ed 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: - name: Install Node.js dependencies run: npm install - working-directory: string-art-demo + working-directory: string-art-website - name: Set up Rust uses: actions-rs/toolchain@v1 @@ -49,7 +49,7 @@ jobs: - name: Build static files id: build run: npm run build - working-directory: string-art-demo + working-directory: string-art-website - name: Upload static files as artifact id: deployment diff --git a/build-wasm.sh b/build-wasm.sh index bf83756..a154938 100755 --- a/build-wasm.sh +++ b/build-wasm.sh @@ -13,16 +13,16 @@ wasm-pack build --release --target web --features wasm echo "✅ WASM package built successfully!" # Copy the package to the React app's src directory for proper importing -if [ -d "../string-art-demo/src" ]; then +if [ -d "../string-art-website/src" ]; then echo "📦 Copying WASM files to React app..." - mkdir -p ../string-art-demo/src/wasm - cp -r pkg/* ../string-art-demo/src/wasm/ + mkdir -p ../string-art-website/src/wasm + cp -r pkg/* ../string-art-website/src/wasm/ echo "✅ WASM files copied to React app!" fi echo "🎉 Build complete! You can now use the WASM module in your React app." echo "" echo "To test:" -echo "1. Make sure the React dev server is running: cd string-art-demo && npm run dev" +echo "1. Make sure the React dev server is running: cd string-art-website && npm run dev" echo "2. Open http://localhost:5173 in your browser" echo "3. Upload an image and click 'Generate String Art'" diff --git a/string-art-demo/src/features/1Upload/components/ImageUploader.tsx b/string-art-demo/src/features/1Upload/components/ImageUploader.tsx deleted file mode 100644 index 2889dfd..0000000 --- a/string-art-demo/src/features/1Upload/components/ImageUploader.tsx +++ /dev/null @@ -1,166 +0,0 @@ -import React, { useCallback, useState } from 'react'; - -interface ImageUploaderProps { - onImageSelected: (imageData: Uint8Array, imageUrl: string) => void; - disabled?: boolean; -} - -export const ImageUploader: React.FC = ({ onImageSelected, disabled = false }) => { - const [isDragOver, setIsDragOver] = useState(false); - - const handleFileSelect = useCallback(async (file: File) => { - if (!file.type.startsWith('image/')) { - alert('Please select an image file'); - return; - } - - try { - const arrayBuffer = await file.arrayBuffer(); - const imageData = new Uint8Array(arrayBuffer); - const imageUrl = URL.createObjectURL(file); - onImageSelected(imageData, imageUrl); - } catch (error) { - console.error('Error reading file:', error); - alert('Error reading file'); - } - }, [onImageSelected]); - - const handleDrop = useCallback((e: React.DragEvent) => { - e.preventDefault(); - setIsDragOver(false); - - if (disabled) return; - - const files = Array.from(e.dataTransfer.files); - if (files.length > 0) { - handleFileSelect(files[0]); - } - }, [handleFileSelect, disabled]); - - const handleDragOver = useCallback((e: React.DragEvent) => { - e.preventDefault(); - if (!disabled) { - setIsDragOver(true); - } - }, [disabled]); - - const handleDragLeave = useCallback((e: React.DragEvent) => { - e.preventDefault(); - setIsDragOver(false); - }, []); - - const handleFileInputChange = useCallback((e: React.ChangeEvent) => { - const files = e.target.files; - if (files && files.length > 0) { - handleFileSelect(files[0]); - } - }, [handleFileSelect]); - - return ( -
-
-
📸
-

- {disabled - ? 'Upload disabled during generation' - : 'Drag & drop an image here, or click to select' - } -

- - -
- - -
- ); -}; diff --git a/string-art-demo/src/features/3RenderImage/components/StringArtConfig/StringArtConfigSection.tsx b/string-art-demo/src/features/3RenderImage/components/StringArtConfig/StringArtConfigSection.tsx deleted file mode 100644 index 5889490..0000000 --- a/string-art-demo/src/features/3RenderImage/components/StringArtConfig/StringArtConfigSection.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useSelector, useDispatch } from "react-redux"; -import { setSettings } from "../../../shared/redux/stringArtSlice"; -import type { StringArtConfig } from "../../../shared/interfaces/stringArtConfig"; - -const Slider = ({ - title, - index, - value, - settings, -}: { - title: string; - index: keyof StringArtConfig; - value: string | number; - settings: StringArtConfig; -}) => { - const minMaxVals: Record< - keyof Omit< - StringArtConfig, - | "preserve_eyes" - | "preserve_negative_space" - >, - [number, number, number] - > = { - //min, max, step - image_size: [500, 2000, 100], - line_darkness: [25, 300, 5], - max_lines: [800, 5000, 100], - min_improvement_score: [0, 100, 1], - negative_space_penalty: [0, 100, 1], - negative_space_threshold: [0, 100, 1], - num_nails: [360, 1440, 360/4], - progress_frequency: [200, 500, 50], - }; - - const dispatch = useDispatch(); - const dontRender = !index || !Object.keys(minMaxVals).includes(index as string) - const [min, max, step] = dontRender? [0,0,0] : minMaxVals[index as keyof typeof minMaxVals]; - - if (dontRender) return null; - return ( -
- { - dispatch(setSettings({ ...settings, [index]: Number(target.value) })); - }} - /> - -
- ); -}; - -const StringArtConfigSection = () => { - const settings = useSelector((state: { stringArt: { settings: StringArtConfig } }) => state.stringArt.settings); - - if (!settings) { - return null; - } - return ( -
-

String Art Configuration

- {(Object.keys(settings) as (keyof StringArtConfig)[]).map((key) => - ( - - ) - )} -
- ); -}; - -export default StringArtConfigSection; diff --git a/string-art-demo/src/main.tsx b/string-art-demo/src/main.tsx deleted file mode 100644 index 735a757..0000000 --- a/string-art-demo/src/main.tsx +++ /dev/null @@ -1,12 +0,0 @@ -// main.tsx -import { createRoot } from 'react-dom/client'; -import { Provider } from 'react-redux'; -import { store } from './features/shared/redux/store'; -import './index.css'; -import App from './App.tsx'; - -createRoot(document.getElementById('root')!).render( - - - -); diff --git a/string-art-demo/vite.config.ts b/string-art-demo/vite.config.ts deleted file mode 100644 index 0736e3f..0000000 --- a/string-art-demo/vite.config.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { NodeGlobalsPolyfillPlugin } from '@esbuild-plugins/node-globals-polyfill' -import { NodeModulesPolyfillPlugin } from '@esbuild-plugins/node-modules-polyfill' - -// https://vite.dev/config/ -export default defineConfig({ - base: '/StringArtGenerator/', - plugins: [react()], - server: { - fs: { - allow: ['..'] - }, - headers: { - 'Cross-Origin-Embedder-Policy': 'require-corp', - 'Cross-Origin-Opener-Policy': 'same-origin', - } - }, - optimizeDeps: { - exclude: ['string-art-wasm'], - esbuildOptions: { - plugins: [ - NodeGlobalsPolyfillPlugin({ - process: true, - buffer: true, - }), - NodeModulesPolyfillPlugin(), - ], - }, - }, - resolve: { - alias: { - crypto: 'crypto-browserify', - }, - }, - build: { - target: 'esnext' - } -}) diff --git a/string-art-demo/.gitignore b/string-art-website/.gitignore similarity index 100% rename from string-art-demo/.gitignore rename to string-art-website/.gitignore diff --git a/string-art-demo/README.md b/string-art-website/README.md similarity index 100% rename from string-art-demo/README.md rename to string-art-website/README.md diff --git a/string-art-demo/eslint.config.js b/string-art-website/eslint.config.js similarity index 100% rename from string-art-demo/eslint.config.js rename to string-art-website/eslint.config.js diff --git a/string-art-website/i18next-scanner.config.cjs b/string-art-website/i18next-scanner.config.cjs new file mode 100644 index 0000000..9a89dbb --- /dev/null +++ b/string-art-website/i18next-scanner.config.cjs @@ -0,0 +1,40 @@ +var fs = require('fs'); +var chalk = require('chalk'); +const typescriptTransform = require('i18next-scanner-typescript'); + +module.exports = { + output: './public/locales', + options: { + func: { + // don't pass ts or tsx here! + extensions: ['.js', '.jsx'], + }, + trans: { + // don't pass ts or tsx here! + extensions: ['.js', '.jsx'], + }, + + }, + // your i18next-scanner config + // ... + transform: typescriptTransform( + // options + { + // default value for extensions + extensions: [".ts", ".tsx"], + // optional ts configuration + tsOptions: { + target: "es2017", + }, + }, + + // optional custom transform function + function customTransform(outputText, file, enc, done) { + // do something custom with the transpiled `outputText` + this.parser.parseTransFromString(outputText); + this.parser.parseFuncFromString(outputText); + + done(); + }, + ), +}; \ No newline at end of file diff --git a/string-art-demo/index.html b/string-art-website/index.html similarity index 89% rename from string-art-demo/index.html rename to string-art-website/index.html index e4b78ea..f444a49 100644 --- a/string-art-demo/index.html +++ b/string-art-website/index.html @@ -4,7 +4,7 @@ - Vite + React + TS + String Art Generator
diff --git a/string-art-demo/package-lock.json b/string-art-website/package-lock.json similarity index 81% rename from string-art-demo/package-lock.json rename to string-art-website/package-lock.json index 6a9647f..51756bb 100644 --- a/string-art-demo/package-lock.json +++ b/string-art-website/package-lock.json @@ -14,8 +14,11 @@ "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@mui/material": "^7.3.1", "@reduxjs/toolkit": "^2.8.2", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-xhr-backend": "^3.2.2", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-i18next": "^15.7.1", "react-redux": "^9.2.0" }, "devDependencies": { @@ -27,6 +30,8 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", + "i18next-scanner": "^4.6.0", + "i18next-scanner-typescript": "^1.2.1", "typescript": "~5.8.3", "typescript-eslint": "^8.39.1", "vite": "^7.1.2" @@ -1070,6 +1075,19 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2161,6 +2179,22 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-class-fields": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/acorn-class-fields/-/acorn-class-fields-0.3.7.tgz", + "integrity": "sha512-jdUWSFce0fuADUljmExz4TWpPkxmRW/ZCPRqeeUzbGf0vFUcpQYbyq52l75qGd0oSwwtAepeL6hgb/naRgvcKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-private-class-elements": "^0.2.7" + }, + "engines": { + "node": ">=4.8.2" + }, + "peerDependencies": { + "acorn": "^6 || ^7 || ^8" + } + }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -2171,6 +2205,82 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-private-class-elements": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/acorn-private-class-elements/-/acorn-private-class-elements-0.2.7.tgz", + "integrity": "sha512-+GZH2wOKNZOBI4OOPmzpo4cs6mW297sn6fgIk1dUI08jGjhAaEwvC39mN2gJAg2lmAQJ1rBkFqKWonL3Zz6PVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.8.2" + }, + "peerDependencies": { + "acorn": "^6.1.0 || ^7 || ^8" + } + }, + "node_modules/acorn-private-methods": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/acorn-private-methods/-/acorn-private-methods-0.3.3.tgz", + "integrity": "sha512-46oeEol3YFvLSah5m9hGMlNpxDBCEkdceJgf01AjqKYTK9r6HexKs2rgSbLK81pYjZZMonhftuUReGMlbbv05w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-private-class-elements": "^0.2.7" + }, + "engines": { + "node": ">=4.8.2" + }, + "peerDependencies": { + "acorn": "^6 || ^7 || ^8" + } + }, + "node_modules/acorn-stage3": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-stage3/-/acorn-stage3-4.0.0.tgz", + "integrity": "sha512-BR+LaADtA6GTB5prkNqWmlmCLYmkyW0whvSxdHhbupTaro2qBJ95fJDEiRLPUmiACGHPaYyeH9xmNJWdGfXRQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-class-fields": "^0.3.7", + "acorn-private-methods": "^0.3.3", + "acorn-static-class-features": "^0.2.4" + }, + "engines": { + "node": ">=4.8.2" + }, + "peerDependencies": { + "acorn": "^7.4 || ^8" + } + }, + "node_modules/acorn-static-class-features": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/acorn-static-class-features/-/acorn-static-class-features-0.2.4.tgz", + "integrity": "sha512-5X4mpYq5J3pdndLmIB0+WtFd/mKWnNYpuTlTzj32wUu/PMmEGOiayQ5UrqgwdBNiaZBtDDh5kddpP7Yg2QaQYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-private-class-elements": "^0.2.7" + }, + "engines": { + "node": ">=4.8.2" + }, + "peerDependencies": { + "acorn": "^6.1.0 || ^7 || ^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2204,6 +2314,20 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2211,6 +2335,13 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -2233,6 +2364,47 @@ "dev": true, "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.1.tgz", + "integrity": "sha512-AuTJkq9XmE6Vk0FJVNq5QxETrSA/vKHarWVBG5l/JbdCL1prJemiyJqUS0jrlXO0MftuPq4m3YVYhoNc5+aE/g==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2290,6 +2462,31 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2337,6 +2534,31 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2366,6 +2588,16 @@ "dev": true, "license": "MIT" }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2380,6 +2612,13 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -2450,6 +2689,16 @@ "dev": true, "license": "MIT" }, + "node_modules/deepmerge": { + "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" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2467,6 +2716,20 @@ "dev": true, "license": "ISC" }, + "node_modules/ensure-type": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ensure-type/-/ensure-type-1.5.1.tgz", + "integrity": "sha512-Dxe+mVF4MupV6eueWiFa6hUd9OL9lIM2/LqR40k1P+dwG+G2il2UigXTU9aQlaw+Y/N0BKSaTofNw73htTbC5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/eol": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", + "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==", + "dev": true, + "license": "MIT" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2671,6 +2934,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima-next": { + "version": "5.8.4", + "resolved": "https://registry.npmjs.org/esprima-next/-/esprima-next-5.8.4.tgz", + "integrity": "sha512-8nYVZ4ioIH4Msjb/XmhnBdz5WRRBaYqevKa1cv9nGJdCehMbzZCPNEEnqfLCZVetUVrUPEcb5IYyu1GG4hFqgg==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -2730,6 +3007,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -2854,6 +3138,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2901,6 +3199,26 @@ "node": ">=10.13.0" } }, + "node_modules/glob-stream": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/globals": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", @@ -2914,6 +3232,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -2921,6 +3246,60 @@ "dev": true, "license": "MIT" }, + "node_modules/gulp-sort": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-sort/-/gulp-sort-2.0.0.tgz", + "integrity": "sha512-MyTel3FXOdh1qhw1yKhpimQrAmur9q1X0ZigLmCOxouQD+BD3za9/89O+HfbgBQvvh4igEbp0/PUWO+VqGYG1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "through2": "^2.0.1" + } + }, + "node_modules/gulp-sort/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/gulp-sort/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/gulp-sort/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/gulp-sort/node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2958,44 +3337,181 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", "license": "MIT", - "engines": { - "node": ">= 4" + "dependencies": { + "void-elements": "3.1.0" } }, - "node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "node_modules/i18next": { + "version": "25.4.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.0.tgz", + "integrity": "sha512-UH5aiamXsO3cfrZFurCHiB6YSs3C+s+XY9UaJllMMSbmaoXILxFgqDEZu4NbfzJFjmUo3BNMa++Rjkr3ofjfLw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" + "dependencies": { + "@babel/runtime": "^7.27.6" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@babel/runtime": "^7.23.2" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/i18next-scanner": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/i18next-scanner/-/i18next-scanner-4.6.0.tgz", + "integrity": "sha512-I/xKcwKfii3L3is3bUvfaIU0QA/wYhpZnjppfrzyb61QQddxkcpspASEtmfnxSYvE6yIaAxDlIxg0EHV7mxssg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.0.4", + "acorn-jsx": "^5.3.1", + "acorn-stage3": "^4.0.0", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "clone-deep": "^4.0.0", + "commander": "^9.0.0", + "deepmerge": "^4.0.0", + "ensure-type": "^1.5.0", + "eol": "^0.9.1", + "esprima-next": "^5.7.0", + "gulp-sort": "^2.0.0", + "i18next": "*", + "lodash": "^4.0.0", + "parse5": "^6.0.0", + "sortobject": "^4.0.0", + "through2": "^4.0.0", + "vinyl": "^3.0.0", + "vinyl-fs": "^4.0.0" + }, + "bin": { + "i18next-scanner": "bin/cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/i18next-scanner-typescript": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/i18next-scanner-typescript/-/i18next-scanner-typescript-1.2.1.tgz", + "integrity": "sha512-CAvjGBTpudGEc8o31ayFEHkz+7qyjPpJa+iA+7Yu7hSTdJqS4LRKafA79wgIncSd0Th85knG/rdZZybLSPoWDQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x" + } + }, + "node_modules/i18next-xhr-backend": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/i18next-xhr-backend/-/i18next-xhr-backend-3.2.2.tgz", + "integrity": "sha512-OtRf2Vo3IqAxsttQbpjYnmMML12IMB5e0fc5B7qKJFLScitYaXa1OhMX0n0X/3vrfFlpHL9Ro/H+ps4Ej2j7QQ==", + "deprecated": "replaced by i18next-http-backend", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.5.5" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, @@ -3004,6 +3520,13 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3048,6 +3571,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3058,6 +3591,36 @@ "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3065,6 +3628,16 @@ "dev": true, "license": "ISC" }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3146,6 +3719,26 @@ "json-buffer": "3.0.1" } }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3182,6 +3775,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3296,6 +3896,29 @@ "dev": true, "license": "MIT" }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3305,6 +3928,16 @@ "node": ">=0.10.0" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3385,6 +4018,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3478,6 +4118,13 @@ "node": ">= 0.8.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -3547,6 +4194,32 @@ "react": "^19.1.1" } }, + "node_modules/react-i18next": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.7.1.tgz", + "integrity": "sha512-o4VsKh30fy7p0z5ACHuyWqB6xu9WpQIQy2/ZcbCqopNnrnTVOPn/nAv9uYP4xYAWg99QMpvZ9Bu/si3eGurzGw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.4.0", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", @@ -3602,6 +4275,21 @@ "react-dom": ">=16.6.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", @@ -3617,6 +4305,23 @@ "redux": "^5.0.0" } }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true, + "license": "ISC" + }, + "node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", @@ -3652,6 +4357,19 @@ "node": ">=4" } }, + "node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -3757,6 +4475,34 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -3773,6 +4519,19 @@ "semver": "bin/semver.js" } }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3796,6 +4555,19 @@ "node": ">=8" } }, + "node_modules/sortobject": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/sortobject/-/sortobject-4.17.0.tgz", + "integrity": "sha512-gzx7USv55AFRQ7UCWJHHauwD/ptUHF9MLXCGO3f5M9zauDPZ/4a9H6/VVbOXefdpEoI1unwB/bArHIVMbWBHmA==", + "dev": true, + "license": "Artistic-2.0", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -3822,6 +4594,40 @@ "deprecated": "Please use @jridgewell/sourcemap-codec instead", "license": "MIT" }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.13.2" + } + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3866,6 +4672,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -3924,6 +4760,19 @@ "node": ">=8.0" } }, + "node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -3954,7 +4803,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -4038,6 +4887,97 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/vite": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz", @@ -4141,6 +5081,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4167,6 +5116,23 @@ "node": ">=0.10.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/string-art-demo/package.json b/string-art-website/package.json similarity index 74% rename from string-art-demo/package.json rename to string-art-website/package.json index 49d20ee..38244bf 100644 --- a/string-art-demo/package.json +++ b/string-art-website/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "translate": "npx i18next-scanner \"src/**/*.tsx\" --config ./i18next-scanner.config.cjs" }, "dependencies": { "@emotion/react": "^11.14.0", @@ -16,8 +17,11 @@ "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@mui/material": "^7.3.1", "@reduxjs/toolkit": "^2.8.2", + "i18next-browser-languagedetector": "^8.2.0", + "i18next-xhr-backend": "^3.2.2", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-i18next": "^15.7.1", "react-redux": "^9.2.0" }, "devDependencies": { @@ -29,6 +33,8 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^16.3.0", + "i18next-scanner": "^4.6.0", + "i18next-scanner-typescript": "^1.2.1", "typescript": "~5.8.3", "typescript-eslint": "^8.39.1", "vite": "^7.1.2" diff --git a/string-art-website/public/locales/i18n/en/translation.json b/string-art-website/public/locales/i18n/en/translation.json new file mode 100644 index 0000000..309fa6a --- /dev/null +++ b/string-art-website/public/locales/i18n/en/translation.json @@ -0,0 +1,36 @@ +{ + "Loading WASM Module": "Loading WASM Module", + "Please wait while we initialize the string art generator": "Please wait while we initialize the string art generator", + "Error Loading WASM Module": "Error Loading WASM Module", + "Retry": "Retry", + "String Art Generator": "String Art Generator", + "Upload an image and watch it transform into beautiful string art in real-time!": "Upload an image and watch it transform into beautiful string art in real-time!", + "Please select an image file": "Please select an image file", + "Error reading file": "Error reading file", + "Upload disabled during generation": "Upload disabled during generation", + "Drag & drop an image here, or click to select": "Drag & drop an image here, or click to select", + "Choose Image": "Choose Image", + "Number of Nails": "Number of Nails", + "Image Size": "Image Size", + "Preserve Eyes": "Preserve Eyes", + "Preserve Negative Space": "Preserve Negative Space", + "Negative Space Penalty": "Negative Space Penalty", + "Negative Space Threshold": "Negative Space Threshold", + "Max Lines": "Max Lines", + "Line Darkness": "Line Darkness", + "Min Improvement Score": "Min Improvement Score", + "Progress Frequency": "Progress Frequency", + "String Art Configuration": "String Art Configuration", + "Generating": "Generating", + "Generate String Art": "Generate String Art", + "Progress ": "Progress ", + "Lines ": "Lines ", + "Score ": "Score ", + "Upload Image": "Upload Image", + "Render String Art": "Render String Art", + "All steps completed - you're finished": "All steps completed - you're finished", + "Download Image": "Download Image", + "Back": "Back", + "Finish": "Finish", + "Next": "Next" +} \ No newline at end of file diff --git a/string-art-demo/public/package.json b/string-art-website/public/package.json similarity index 100% rename from string-art-demo/public/package.json rename to string-art-website/public/package.json diff --git a/string-art-demo/public/string_art_rust_impl.d.ts b/string-art-website/public/string_art_rust_impl.d.ts similarity index 100% rename from string-art-demo/public/string_art_rust_impl.d.ts rename to string-art-website/public/string_art_rust_impl.d.ts diff --git a/string-art-demo/public/string_art_rust_impl.js b/string-art-website/public/string_art_rust_impl.js similarity index 100% rename from string-art-demo/public/string_art_rust_impl.js rename to string-art-website/public/string_art_rust_impl.js diff --git a/string-art-demo/public/string_art_rust_impl_bg.wasm b/string-art-website/public/string_art_rust_impl_bg.wasm similarity index 100% rename from string-art-demo/public/string_art_rust_impl_bg.wasm rename to string-art-website/public/string_art_rust_impl_bg.wasm diff --git a/string-art-demo/public/string_art_rust_impl_bg.wasm.d.ts b/string-art-website/public/string_art_rust_impl_bg.wasm.d.ts similarity index 100% rename from string-art-demo/public/string_art_rust_impl_bg.wasm.d.ts rename to string-art-website/public/string_art_rust_impl_bg.wasm.d.ts diff --git a/string-art-demo/public/vite.svg b/string-art-website/public/vite.svg similarity index 100% rename from string-art-demo/public/vite.svg rename to string-art-website/public/vite.svg diff --git a/string-art-demo/src/App.css b/string-art-website/src/App.css similarity index 100% rename from string-art-demo/src/App.css rename to string-art-website/src/App.css diff --git a/string-art-demo/src/App.tsx b/string-art-website/src/App.tsx similarity index 62% rename from string-art-demo/src/App.tsx rename to string-art-website/src/App.tsx index 52c3b4e..45fea58 100644 --- a/string-art-demo/src/App.tsx +++ b/string-art-website/src/App.tsx @@ -6,19 +6,19 @@ import { } from './features/shared/redux/stringArtSlice'; import { ThemeProvider } from '@mui/material/styles'; import theme from './theme'; - +import {useTranslation} from 'react-i18next'; function App() { const { isLoading, error, } = useSelector((state: { stringArt: StringArtState }) => state.stringArt); - + const i18next = useTranslation(); if (isLoading) { return (
-

Loading WASM Module...

-

Please wait while we initialize the string art generator.

+

{i18next.t('Loading WASM Module')}

+

{i18next.t('Please wait while we initialize the string art generator.')}

); } @@ -26,9 +26,9 @@ function App() { if (error) { return (
-

Error Loading WASM Module

+

{i18next.t('Error Loading WASM Module')}

{error}

- +
); } @@ -37,8 +37,8 @@ function App() {
-

{'String Art Generator'}

-

Upload an image and watch it transform into beautiful string art in real-time!

+

{i18next.t('String Art Generator')}

+

{i18next.t('Upload an image and watch it transform into beautiful string art in real-time!')}

diff --git a/string-art-demo/src/assets/react.svg b/string-art-website/src/assets/react.svg similarity index 100% rename from string-art-demo/src/assets/react.svg rename to string-art-website/src/assets/react.svg diff --git a/string-art-demo/src/features/1Upload/UploadScreen.tsx b/string-art-website/src/features/1Upload/UploadScreen.tsx similarity index 100% rename from string-art-demo/src/features/1Upload/UploadScreen.tsx rename to string-art-website/src/features/1Upload/UploadScreen.tsx diff --git a/string-art-website/src/features/1Upload/components/ImageUploader.tsx b/string-art-website/src/features/1Upload/components/ImageUploader.tsx new file mode 100644 index 0000000..7d55f5f --- /dev/null +++ b/string-art-website/src/features/1Upload/components/ImageUploader.tsx @@ -0,0 +1,112 @@ +import React, { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import "./style.css"; +interface ImageUploaderProps { + onImageSelected: (imageData: Uint8Array, imageUrl: string) => void; + disabled?: boolean; +} + +export const ImageUploader: React.FC = ({ + onImageSelected, + disabled = false, +}) => { + const [isDragOver, setIsDragOver] = useState(false); + const i18next = useTranslation(); + const handleFileSelect = useCallback( + async (file: File) => { + if (!file.type.startsWith("image/")) { + alert(i18next.t("Please select an image file")); + return; + } + + try { + const arrayBuffer = await file.arrayBuffer(); + const imageData = new Uint8Array(arrayBuffer); + const imageUrl = URL.createObjectURL(file); + onImageSelected(imageData, imageUrl); + } catch (error) { + console.error(i18next.t("Error reading file:"), error); + alert(i18next.t("Error reading file")); + } + }, + [onImageSelected, i18next] + ); + + const handleDrop = useCallback( + (e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + + if (disabled) return; + + const files = Array.from(e.dataTransfer.files); + if (files.length > 0) { + handleFileSelect(files[0]); + } + }, + [handleFileSelect, disabled] + ); + + const handleDragOver = useCallback( + (e: React.DragEvent) => { + e.preventDefault(); + if (!disabled) { + setIsDragOver(true); + } + }, + [disabled] + ); + + const handleDragLeave = useCallback((e: React.DragEvent) => { + e.preventDefault(); + setIsDragOver(false); + }, []); + + const handleFileInputChange = useCallback( + (e: React.ChangeEvent) => { + const files = e.target.files; + if (files && files.length > 0) { + handleFileSelect(files[0]); + } + }, + [handleFileSelect] + ); + + return ( +
+
+
📸
+

+ {disabled + ? i18next.t("Upload disabled during generation") + : i18next.t("Drag & drop an image here, or click to select")} +

+ + +
+
+ ); +}; diff --git a/string-art-website/src/features/1Upload/components/style.css b/string-art-website/src/features/1Upload/components/style.css new file mode 100644 index 0000000..546b257 --- /dev/null +++ b/string-art-website/src/features/1Upload/components/style.css @@ -0,0 +1,69 @@ + +.image-uploader { + border: 2px dashed #ccc; + border-radius: 8px; + padding: 2rem; + text-align: center; + background: #fafafa; + transition: all 0.3s ease; + cursor: pointer; +} + +.image-uploader:hover:not(.disabled) { + border-color: #007bff; + background: #f0f8ff; +} + +.image-uploader.drag-over { + border-color: #007bff; + background: #e3f2fd; + transform: scale(1.02); +} + +.image-uploader.disabled { + opacity: 0.6; + cursor: not-allowed; + background: #f5f5f5; +} + +.upload-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.upload-icon { + font-size: 3rem; + opacity: 0.6; +} + +.upload-text { + margin: 0; + color: #666; + font-size: 1rem; +} + +.file-input { + display: none; +} + +.upload-button { + background: #007bff; + color: white; + border: none; + padding: 0.75rem 1.5rem; + border-radius: 4px; + cursor: pointer; + font-size: 1rem; + transition: background 0.3s ease; +} + +.upload-button:hover:not(:disabled) { + background: #0056b3; +} + +.upload-button:disabled { + background: #ccc; + cursor: not-allowed; +} \ No newline at end of file diff --git a/string-art-demo/src/features/3RenderImage/RenderImageScreen.tsx b/string-art-website/src/features/3RenderImage/RenderImageScreen.tsx similarity index 83% rename from string-art-demo/src/features/3RenderImage/RenderImageScreen.tsx rename to string-art-website/src/features/3RenderImage/RenderImageScreen.tsx index 0729a86..2df9d0f 100644 --- a/string-art-demo/src/features/3RenderImage/RenderImageScreen.tsx +++ b/string-art-website/src/features/3RenderImage/RenderImageScreen.tsx @@ -7,9 +7,11 @@ import { } from "../shared/redux/stringArtSlice"; import StringArtConfigSection from "./components/StringArtConfig/StringArtConfigSection"; import { StringArtCanvas } from "./components/StringArtCanvas/StringArtCanvas"; +import { useTranslation } from 'react-i18next'; const RenderImageScreen = forwardRef((_, ref) => { const dispatch = useDispatch(); + const i18next = useTranslation(); const { imageData, isGenerating, progress, settings } = useSelector( (state: { stringArt: StringArtState }) => state.stringArt ); @@ -35,17 +37,17 @@ const RenderImageScreen = forwardRef((_, ref) => { disabled={isGenerating} className="generate-button" > - {isGenerating ? "Generating..." : "Generate String Art"} + {isGenerating ? i18next.t("Generating...") : i18next.t("Generate String Art")} {progress && (
- Progress: {progress.completion_percent.toFixed(1)}% + {i18next.t("Progress ") + progress.completion_percent.toFixed(2) + "%"} - Lines: {progress.lines_completed}/{progress.total_lines} + {i18next.t("Lines ") + progress.lines_completed + "/" + progress.total_lines}
@@ -55,7 +57,7 @@ const RenderImageScreen = forwardRef((_, ref) => { >
- Score: {progress.score.toFixed(1)} + {i18next.t("Score ") + progress.score.toFixed(1)}
)} diff --git a/string-art-demo/src/features/3RenderImage/components/StringArtCanvas/StringArtCanvas.tsx b/string-art-website/src/features/3RenderImage/components/StringArtCanvas/StringArtCanvas.tsx similarity index 100% rename from string-art-demo/src/features/3RenderImage/components/StringArtCanvas/StringArtCanvas.tsx rename to string-art-website/src/features/3RenderImage/components/StringArtCanvas/StringArtCanvas.tsx diff --git a/string-art-demo/src/features/3RenderImage/components/StringArtCanvas/style.module.css b/string-art-website/src/features/3RenderImage/components/StringArtCanvas/style.module.css similarity index 100% rename from string-art-demo/src/features/3RenderImage/components/StringArtCanvas/style.module.css rename to string-art-website/src/features/3RenderImage/components/StringArtCanvas/style.module.css diff --git a/string-art-website/src/features/3RenderImage/components/StringArtConfig/StringArtConfigSection.tsx b/string-art-website/src/features/3RenderImage/components/StringArtConfig/StringArtConfigSection.tsx new file mode 100644 index 0000000..45c1958 --- /dev/null +++ b/string-art-website/src/features/3RenderImage/components/StringArtConfig/StringArtConfigSection.tsx @@ -0,0 +1,102 @@ +import { useSelector, useDispatch } from "react-redux"; +import { setSettings } from "../../../shared/redux/stringArtSlice"; +import type { StringArtConfig } from "../../../shared/interfaces/stringArtConfig"; +import { useTranslation } from "react-i18next"; +import { useMemo } from "react"; + +const minMaxVals: Record< + keyof Omit, + [number, number, number] +> = { + //min, max, step + image_size: [500, 2000, 100], + line_darkness: [25, 300, 5], + max_lines: [800, 5000, 100], + min_improvement_score: [0, 100, 1], + negative_space_penalty: [0, 100, 1], + negative_space_threshold: [0, 100, 1], + num_nails: [360, 1440, 360 / 4], + progress_frequency: [200, 500, 50], +}; + +const Slider = ({ + index, + value, + settings, +}: { + index: keyof StringArtConfig; + value: string | number; + settings: StringArtConfig; +}) => { + const i18next = useTranslation(); + + const titles = useMemo( + () => ({ + num_nails: i18next.t("Number of Nails"), + image_size: i18next.t("Image Size"), + preserve_eyes: i18next.t("Preserve Eyes"), + preserve_negative_space: i18next.t("Preserve Negative Space"), + negative_space_penalty: i18next.t("Negative Space Penalty"), + negative_space_threshold: i18next.t("Negative Space Threshold"), + max_lines: i18next.t("Max Lines"), + line_darkness: i18next.t("Line Darkness"), + min_improvement_score: i18next.t("Min Improvement Score"), + progress_frequency: i18next.t("Progress Frequency"), + }), + [i18next] + ); + + const dispatch = useDispatch(); + const dontRender = + !index || !Object.keys(minMaxVals).includes(index as string); + const [min, max, step] = dontRender + ? [0, 0, 0] + : minMaxVals[index as keyof typeof minMaxVals]; + + if (dontRender) return null; + return ( +
+ { + dispatch(setSettings({ ...settings, [index]: Number(target.value) })); + }} + /> + +
+ ); +}; + +const StringArtConfigSection = () => { + const settings = useSelector( + (state: { stringArt: { settings: StringArtConfig } }) => + state.stringArt.settings + ); + const i18next = useTranslation(); + if (!settings) { + return null; + } + return ( +
+

{i18next.t("String Art Configuration")}

+ {(Object.keys(settings) as (keyof StringArtConfig)[]).map((key) => ( + + ))} +
+ ); +}; + +export default StringArtConfigSection; diff --git a/string-art-demo/src/features/3RenderImage/components/StringArtConfig/index.ts b/string-art-website/src/features/3RenderImage/components/StringArtConfig/index.ts similarity index 100% rename from string-art-demo/src/features/3RenderImage/components/StringArtConfig/index.ts rename to string-art-website/src/features/3RenderImage/components/StringArtConfig/index.ts diff --git a/string-art-demo/src/features/Stepper/StepperScreen.tsx b/string-art-website/src/features/Stepper/StepperScreen.tsx similarity index 89% rename from string-art-demo/src/features/Stepper/StepperScreen.tsx rename to string-art-website/src/features/Stepper/StepperScreen.tsx index ed168db..2833814 100644 --- a/string-art-demo/src/features/Stepper/StepperScreen.tsx +++ b/string-art-website/src/features/Stepper/StepperScreen.tsx @@ -9,14 +9,15 @@ import UploadScreen from '../1Upload/UploadScreen'; import RenderImageScreen from '../3RenderImage/RenderImageScreen'; import { useSelector } from 'react-redux'; import { type StringArtState } from '../shared/redux/stringArtSlice'; +import { useTranslation } from 'react-i18next'; -const steps = ['Upload Image', 'Render String Art']; export default function StepperScreen() { + const i18next = useTranslation(); + const steps = [i18next.t('Upload Image'), i18next.t('Render String Art')]; const [activeStep, setActiveStep] = React.useState(0); const { imageData } = useSelector((state: { stringArt: StringArtState }) => state.stringArt); const renderImageScreenRef = React.useRef(null); - const handleNext = () => { setActiveStep((prevActiveStep) => prevActiveStep + 1); }; @@ -77,11 +78,11 @@ export default function StepperScreen() { {activeStep === steps.length ? ( - All steps completed - you're finished + {i18next.t("All steps completed - you're finished")} - + ) : ( @@ -95,11 +96,11 @@ export default function StepperScreen() { onClick={handleBack} sx={{ mr: 1 }} > - Back + {i18next.t("Back")} diff --git a/string-art-demo/src/features/shared/interfaces/stringArtConfig.ts b/string-art-website/src/features/shared/interfaces/stringArtConfig.ts similarity index 100% rename from string-art-demo/src/features/shared/interfaces/stringArtConfig.ts rename to string-art-website/src/features/shared/interfaces/stringArtConfig.ts diff --git a/string-art-demo/src/features/shared/redux/store.ts b/string-art-website/src/features/shared/redux/store.ts similarity index 100% rename from string-art-demo/src/features/shared/redux/store.ts rename to string-art-website/src/features/shared/redux/store.ts diff --git a/string-art-demo/src/features/shared/redux/stringArtSlice.ts b/string-art-website/src/features/shared/redux/stringArtSlice.ts similarity index 87% rename from string-art-demo/src/features/shared/redux/stringArtSlice.ts rename to string-art-website/src/features/shared/redux/stringArtSlice.ts index 1e091d8..af80160 100644 --- a/string-art-demo/src/features/shared/redux/stringArtSlice.ts +++ b/string-art-website/src/features/shared/redux/stringArtSlice.ts @@ -103,18 +103,7 @@ const stringArtSlice = createSlice({ state.progress = null; state.error = null; }) - .addCase(generateStringArtThunk.fulfilled, (state, action) => { - // result.path and result.nailCoords from thunk payload - const result = action.payload; - if (result.path) { - const pathArr: number[] = Array.isArray(result.path) - ? result.path - : Array.from(result.path); - state.currentPath = [...state.currentPath, ...pathArr]; - } - if (result.nailCoords.length > 0) { - state.nailCoords = result.nailCoords; - } + .addCase(generateStringArtThunk.fulfilled, (state) => { state.isGenerating = false; }) .addCase(generateStringArtThunk.rejected, (state, action) => { diff --git a/string-art-demo/src/features/shared/services/StringArtService.ts b/string-art-website/src/features/shared/services/StringArtService.ts similarity index 100% rename from string-art-demo/src/features/shared/services/StringArtService.ts rename to string-art-website/src/features/shared/services/StringArtService.ts diff --git a/string-art-demo/src/features/shared/workers/stringArtWorker.ts b/string-art-website/src/features/shared/workers/stringArtWorker.ts similarity index 100% rename from string-art-demo/src/features/shared/workers/stringArtWorker.ts rename to string-art-website/src/features/shared/workers/stringArtWorker.ts diff --git a/string-art-website/src/i18n.tsx b/string-art-website/src/i18n.tsx new file mode 100644 index 0000000..7c77d8b --- /dev/null +++ b/string-art-website/src/i18n.tsx @@ -0,0 +1,30 @@ +import i18n from 'i18next'; +import Backend from 'i18next-xhr-backend'; +import LanguageDetector from 'i18next-browser-languagedetector'; +i18n + // load translation using xhr -> see /public/locales + // learn more: https://github.com/i18next/i18next-xhr-backend + .use(Backend) + // detect user language + // learn more: https://github.com/i18next/i18next-browser-languageDetector + .use(LanguageDetector) + // init i18next + // for all options read: https://www.i18next.com/overview/configuration-options + .init({ + fallbackLng: 'en', + debug: true, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, + backend: { + // path where resources get loaded from + loadPath: '/StringArtGenerator/locales/i18n/{{lng}}/{{ns}}.json', + }, + // special options for react-i18next + // learn more: https://react.i18next.com/components/i18next-instance + react: { + useSuspense: false, + }, + }); + +export default i18n; \ No newline at end of file diff --git a/string-art-demo/src/index.css b/string-art-website/src/index.css similarity index 100% rename from string-art-demo/src/index.css rename to string-art-website/src/index.css diff --git a/string-art-website/src/main.tsx b/string-art-website/src/main.tsx new file mode 100644 index 0000000..b6743ad --- /dev/null +++ b/string-art-website/src/main.tsx @@ -0,0 +1,19 @@ +// main.tsx +import { createRoot } from 'react-dom/client'; +import { Provider } from 'react-redux'; +import { store } from './features/shared/redux/store'; +import './index.css'; +import App from './App.tsx'; +import { I18nextProvider } from 'react-i18next'; +import i18n from './i18n.tsx'; + +(async () => { + await i18n.init (); + createRoot(document.getElementById('root')!).render( + + + + + + ); +})(); \ No newline at end of file diff --git a/string-art-demo/src/theme.ts b/string-art-website/src/theme.ts similarity index 100% rename from string-art-demo/src/theme.ts rename to string-art-website/src/theme.ts diff --git a/string-art-demo/src/vite-env.d.ts b/string-art-website/src/vite-env.d.ts similarity index 100% rename from string-art-demo/src/vite-env.d.ts rename to string-art-website/src/vite-env.d.ts diff --git a/string-art-demo/src/wasm/package.json b/string-art-website/src/wasm/package.json similarity index 100% rename from string-art-demo/src/wasm/package.json rename to string-art-website/src/wasm/package.json diff --git a/string-art-demo/src/wasm/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.js b/string-art-website/src/wasm/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.js similarity index 100% rename from string-art-demo/src/wasm/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.js rename to string-art-website/src/wasm/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.js diff --git a/string-art-demo/src/wasm/string_art_rust_impl.d.ts b/string-art-website/src/wasm/string_art_rust_impl.d.ts similarity index 100% rename from string-art-demo/src/wasm/string_art_rust_impl.d.ts rename to string-art-website/src/wasm/string_art_rust_impl.d.ts diff --git a/string-art-demo/src/wasm/string_art_rust_impl.js b/string-art-website/src/wasm/string_art_rust_impl.js similarity index 100% rename from string-art-demo/src/wasm/string_art_rust_impl.js rename to string-art-website/src/wasm/string_art_rust_impl.js diff --git a/string-art-demo/src/wasm/string_art_rust_impl_bg.wasm b/string-art-website/src/wasm/string_art_rust_impl_bg.wasm similarity index 100% rename from string-art-demo/src/wasm/string_art_rust_impl_bg.wasm rename to string-art-website/src/wasm/string_art_rust_impl_bg.wasm diff --git a/string-art-demo/src/wasm/string_art_rust_impl_bg.wasm.d.ts b/string-art-website/src/wasm/string_art_rust_impl_bg.wasm.d.ts similarity index 100% rename from string-art-demo/src/wasm/string_art_rust_impl_bg.wasm.d.ts rename to string-art-website/src/wasm/string_art_rust_impl_bg.wasm.d.ts diff --git a/string-art-demo/tsconfig.app.json b/string-art-website/tsconfig.app.json similarity index 100% rename from string-art-demo/tsconfig.app.json rename to string-art-website/tsconfig.app.json diff --git a/string-art-demo/tsconfig.json b/string-art-website/tsconfig.json similarity index 100% rename from string-art-demo/tsconfig.json rename to string-art-website/tsconfig.json diff --git a/string-art-demo/tsconfig.node.json b/string-art-website/tsconfig.node.json similarity index 100% rename from string-art-demo/tsconfig.node.json rename to string-art-website/tsconfig.node.json diff --git a/string-art-website/vite.config.ts b/string-art-website/vite.config.ts new file mode 100644 index 0000000..ea0548d --- /dev/null +++ b/string-art-website/vite.config.ts @@ -0,0 +1,39 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfill"; +import { NodeModulesPolyfillPlugin } from "@esbuild-plugins/node-modules-polyfill"; + +// https://vite.dev/config/ +export default defineConfig({ + base: "/StringArtGenerator/", + plugins: [react()], + server: { + fs: { + allow: [".."], + }, + headers: { + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Opener-Policy": "same-origin", + }, + }, + optimizeDeps: { + exclude: ["string-art-wasm"], + esbuildOptions: { + plugins: [ + NodeGlobalsPolyfillPlugin({ + process: true, + buffer: true, + }), + NodeModulesPolyfillPlugin(), + ], + }, + }, + resolve: { + alias: { + crypto: "crypto-browserify", + }, + }, + build: { + target: "esnext", + }, +});