feat: implement pwa, add dockerfiles for web and cli, and introduce folder selection with wasm error handling.

This commit is contained in:
2026-02-14 19:44:19 -05:00
parent 91e7af0c04
commit 48aa59540a
17 changed files with 7637 additions and 127 deletions

View File

@@ -59,6 +59,33 @@ header h1 {
text-align: center;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
/* For folder button positioning */
}
.folder-select-btn {
position: absolute;
top: 1rem;
right: 1rem;
background: rgba(255, 255, 255, 0.05);
/* Slight visibility */
border: 1px solid var(--border);
border-radius: 8px;
padding: 0.5rem;
color: var(--text-muted);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
z-index: 10;
}
.folder-select-btn:hover {
background: var(--primary);
color: white;
border-color: var(--primary);
transform: scale(1.05);
}
.dropzone:hover,

View File

@@ -3,7 +3,7 @@ import { useDropzone } from 'react-dropzone'
import { saveAs } from 'file-saver'
import JSZip from 'jszip'
import { FaCloudUploadAlt, FaFileExport, FaCheckCircle, FaSpinner, FaTrash } from 'react-icons/fa'
import { MdError, Md3dRotation } from 'react-icons/md'
import { MdError, Md3dRotation, MdFolderOpen } from 'react-icons/md'
import './App.css'
import { useManifold, convertFile } from './logic/useNametagConverter'
@@ -24,6 +24,21 @@ function App() {
const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop })
const onFolderSelect = (e) => {
if (e.target.files && e.target.files.length > 0) {
const selectedFiles = Array.from(e.target.files);
const svgs = selectedFiles.filter(f => f.name.toLowerCase().endsWith('.svg') && !f.name.startsWith('._') && !f.name.startsWith('.'));
if (svgs.length > 0) {
setFiles(prev => [...prev, ...svgs]);
addLog(`Added ${svgs.length} SVG files from folder.`);
} else {
addLog(`No SVG files found in selected folder.`);
}
}
// Reset
e.target.value = null;
}
const processQueue = async () => {
if (!manifold) {
alert("WASM Engine still loading... please wait a moment.")
@@ -83,6 +98,27 @@ function App() {
<main>
<div {...getRootProps()} className={`dropzone ${isDragActive ? 'active' : ''}`}>
<input {...getInputProps()} />
{/* Folder Selection Button */}
<div
className="folder-select-btn"
onClick={(e) => e.stopPropagation()}
title="Select Folder to Batch Process"
>
<label htmlFor="folder-input" style={{ cursor: 'pointer', display: 'flex' }}>
<MdFolderOpen size={28} />
</label>
<input
id="folder-input"
type="file"
webkitdirectory=""
directory=""
multiple
style={{ display: 'none' }}
onChange={onFolderSelect}
/>
</div>
<div className="dropzone-content">
<FaCloudUploadAlt size={64} color={isDragActive ? "#00aeef" : "#666"} />
{isDragActive ?

View File

@@ -12,20 +12,25 @@ export const useManifold = () => {
useEffect(() => {
const init = async () => {
if (!wasmModule) {
// Fix: Explicitly tell Manifold where to find the WASM file
// distinct from the bundle path
wasmModule = await Module({
locateFile: (path) => {
if (path.endsWith('.wasm')) {
return '/manifold.wasm';
try {
if (!wasmModule) {
// Fix: Explicitly tell Manifold where to find the WASM file
// distinct from the bundle path
wasmModule = await Module({
locateFile: (path) => {
if (path.endsWith('.wasm')) {
return '/manifold.wasm';
}
return path;
}
return path;
}
});
wasmModule.setup();
});
wasmModule.setup();
}
setManifold(wasmModule);
} catch (e) {
console.error("WASM Init Failed:", e);
// We could set an error state here, but for now logging prevents crash
}
setManifold(wasmModule);
};
init();
}, []);
@@ -158,7 +163,7 @@ export const convertFile = async (file, manifold, addLog) => {
// Constants
const BG_THICK = 3.0;
const TXT_THICK = 2.0;
const TXT_Z = 4.0; // Raised by 4mm
const TXT_Z = 3.0; // Starts at 3mm, Center at 4mm
return {
black: createMeshBlob(paths.black, BG_THICK, 0),