Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc93c2ee57 | |||
| e5ed1d3c9f | |||
| 3c3ef02f20 |
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.0.1",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "freight-calculator",
|
||||||
|
"runtimeExecutable": "node",
|
||||||
|
"runtimeArgs": ["server.js"],
|
||||||
|
"port": 3010
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Two desktop shells — same frontend
|
||||||
|
|
||||||
|
This repo ships **two interchangeable desktop shells** that both load the exact same `index.html`:
|
||||||
|
|
||||||
|
1. **Electron** (original, root of repo) — heavier (~70MB installer) but mature/familiar
|
||||||
|
2. **Tauri** (`tauri/` subfolder) — much lighter (~2MB installer, ~8MB exe) using the OS's built-in WebView2 instead of bundled Chromium
|
||||||
|
|
||||||
|
`index.html` (plus `vendor/`) is the **single source of truth** for the UI — never duplicate or fork it. The Tauri shell's `tauri/dist/` is a *generated* copy (gitignored, rebuilt by `tauri/sync-frontend.js` before each `dev`/`build`run) — edit `index.html` at the repo root, never inside `tauri/dist/`.
|
||||||
|
|
||||||
|
### Electron commands (run from repo root)
|
||||||
|
- `npm start` — run the app as an Electron desktop window (loads `index.html` via `main.js`)
|
||||||
|
- `npm run serve` — run a plain Node HTTP server (`server.js`) at http://localhost:3010, serving the same `index.html` for browser-based testing
|
||||||
|
- `npm run build` — package the Windows installer via `electron-builder` (NSIS, output goes to `dist/`)
|
||||||
|
|
||||||
|
### Tauri commands (run from `tauri/`)
|
||||||
|
- `npm run dev` — sync the frontend into `tauri/dist/` and launch the Tauri dev shell
|
||||||
|
- `npm run build` — sync the frontend and produce MSI + NSIS installers (output in `tauri/src-tauri/target/release/bundle/`)
|
||||||
|
- `npm run serve` — same static server as the Electron side, for browser-based testing
|
||||||
|
- Requires the Rust toolchain (`rustup`/`cargo`) plus MSVC Build Tools and WebView2 (Tauri prerequisites) — see https://tauri.app/start/prerequisites/
|
||||||
|
|
||||||
|
There is no test suite, linter, or build step for the app code itself — `index.html` is loaded directly (as a file in Electron/Tauri, or statically served). To verify a change, just reload the app/page in whichever shell you're using.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
This is a single-page Electron desktop app (also runnable as a static page) for calculating NMFC freight class from shipment dimensions and weight. Almost all logic lives in one file:
|
||||||
|
|
||||||
|
- **`index.html`** — contains everything: markup, CSS (`<style>` block), and all application JavaScript (`<script>` block starting around line 370). There are no separate JS/CSS files or build/bundling steps; what you see in `index.html` is what runs.
|
||||||
|
- **`main.js`** — minimal Electron bootstrap (creates the `BrowserWindow`, loads `index.html`, with `nodeIntegration: true` / `contextIsolation: false`)
|
||||||
|
- **`server.js`** — minimal static file server used only for `npm run serve` (browser-based dev/testing instead of Electron)
|
||||||
|
- **`vendor/`** — local copies of third-party JS libraries (`lucide.js`, `jspdf.umd.min.js`, `jspdf.plugin.autotable.min.js`), referenced by `index.html` via relative `<script src="vendor/...">` tags so the app runs **fully offline** with no CDN dependency. Not gitignored — these files are committed and get bundled into the installer by `electron-builder` automatically. **Do not switch these back to CDN URLs** — if a library needs upgrading, download the new version into `vendor/` and update the filename/reference together.
|
||||||
|
|
||||||
|
### Structure inside `index.html`'s script
|
||||||
|
|
||||||
|
The script is organized into clearly marked sections (look for the `// ─── Section ───` banner comments) in this order:
|
||||||
|
|
||||||
|
1. **Data** — `densityRanges` (density → freight class lookup table with descriptions) and `nmfcData` (searchable list of NMFC codes/descriptions/classes)
|
||||||
|
2. **State** — `appState`, persisted to `localStorage` under the key `freightCalcState` via `saveState()`. Defaults are merged with `Object.assign` so new fields can be added without breaking existing saved state. Shape: `{ theme, branding: {name, color, logo}, history[], units: {dim, wt}, defaults: {L, W, H, Weight} }`. The working `pieces` array (current shipment line items being entered) is ephemeral and NOT persisted.
|
||||||
|
3. **Init** — `init()`, wires up the page on load, applies branding, populates settings/defaults forms, renders the density reference table
|
||||||
|
4. **Theme** — light/dark toggle
|
||||||
|
5. **Branding** — company name/color/logo customization, applied to the page header and PDF export
|
||||||
|
6. **Defaults tab** — user-configurable default piece dimensions/weight pre-filled on new entries
|
||||||
|
7. **Modal** — settings modal open/close/revert logic (`pendingBranding` holds unsaved edits so Cancel can discard them)
|
||||||
|
8. **Tabs** — settings modal tab switching (Branding / Defaults / History)
|
||||||
|
9. **History** — calculation history stored in `appState.history` (capped at 50 entries), CSV export/import, restore-from-history (re-applies the exact units the entry was saved in)
|
||||||
|
10. **Unit toggles** — switching between in/cm and lbs/kg; conversions are applied at calculation time via `dimFactor`/`wtFactor`, not by mutating stored values
|
||||||
|
11. **Pieces** — managing the dynamic list of shipment pieces (`renderPieces`, `addPiece`)
|
||||||
|
12. **Validation & Clear** — input validation/error display, form reset
|
||||||
|
13. **Density gauge** — visual gauge showing where the calculated density falls
|
||||||
|
14. **Calculate** — `calculateFreightClass()`: the core calculation. Converts all dimensions/weights to inches/lbs, computes total cubic feet and total weight, derives density, looks up the class from `densityRanges` (first range where `density >= min` wins), saves a history entry, and updates the UI/gauge
|
||||||
|
15. **Copy to clipboard** — copies the result summary as text
|
||||||
|
16. **PDF Export** — `exportPDF()` builds a branded PDF report using `jsPDF` + `jspdf-autotable` (loaded from local `vendor/` copies via `<script>` tags in the `<head>` — see `vendor/` note above), including logo, company branding, pieces table, and footer
|
||||||
|
17. **NMFC Search** — live search over `nmfcData` by code or description; selecting a result highlights the matching row in the on-page density reference table
|
||||||
|
|
||||||
|
### Key conventions
|
||||||
|
|
||||||
|
- All persisted user data lives in one `appState` object in `localStorage`; always go through `saveState()` after mutating it.
|
||||||
|
- Unit conversions (`in`↔`cm`, `lbs`↔`kg`) happen only at calculation/display time — stored values keep their original units alongside a `units` field so they can be restored exactly.
|
||||||
|
- External libraries (`lucide` icons, `jsPDF`, `jspdf-autotable`) are vendored locally in `vendor/` and loaded via relative `<script>` tags in the `<head>` — **not** from CDNs — so the app works completely offline.
|
||||||
+55
-11
@@ -3,9 +3,10 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Freight Calculator Pro</title>
|
<title>Freight Calculator Pro</title>
|
||||||
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
|
<!-- Vendored locally (vendor/) so the app works fully offline — see CLAUDE.md -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
<script src="vendor/lucide.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.8.2/jspdf.plugin.autotable.min.js"></script>
|
<script src="vendor/jspdf.umd.min.js"></script>
|
||||||
|
<script src="vendor/jspdf.plugin.autotable.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--primary: #2563eb; --primary-hover: #1d4ed8; --bg-color: #f1f5f9;
|
--primary: #2563eb; --primary-hover: #1d4ed8; --bg-color: #f1f5f9;
|
||||||
@@ -472,6 +473,13 @@ let pendingBranding = null;
|
|||||||
// Working pieces array (ephemeral, not persisted)
|
// Working pieces array (ephemeral, not persisted)
|
||||||
let pieces = [{ L:'', W:'', H:'', Weight:'' }];
|
let pieces = [{ L:'', W:'', H:'', Weight:'' }];
|
||||||
|
|
||||||
|
// Race-condition guards: track in-flight timers/readers so rapid repeat
|
||||||
|
// actions don't let a stale callback clobber a newer one.
|
||||||
|
let copyRevertTimer = null;
|
||||||
|
let saveDefaultsRevertTimer = null;
|
||||||
|
let logoReadToken = 0;
|
||||||
|
let csvImportToken = 0;
|
||||||
|
|
||||||
function saveState() { localStorage.setItem('freightCalcState', JSON.stringify(appState)); }
|
function saveState() { localStorage.setItem('freightCalcState', JSON.stringify(appState)); }
|
||||||
|
|
||||||
// ─── Init ──────────────────────────────────────────────────────────────────────
|
// ─── Init ──────────────────────────────────────────────────────────────────────
|
||||||
@@ -549,8 +557,10 @@ document.getElementById('setting-color').oninput = e => {
|
|||||||
};
|
};
|
||||||
document.getElementById('setting-logo').onchange = e => {
|
document.getElementById('setting-logo').onchange = e => {
|
||||||
if (!e.target.files[0]) return;
|
if (!e.target.files[0]) return;
|
||||||
|
const myToken = ++logoReadToken;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = evt => {
|
reader.onload = evt => {
|
||||||
|
if (myToken !== logoReadToken) return; // a newer file selection superseded this read
|
||||||
appState.branding.logo = evt.target.result;
|
appState.branding.logo = evt.target.result;
|
||||||
const preview = document.getElementById('logo-preview');
|
const preview = document.getElementById('logo-preview');
|
||||||
preview.src = evt.target.result; preview.style.display = 'block';
|
preview.src = evt.target.result; preview.style.display = 'block';
|
||||||
@@ -574,8 +584,12 @@ document.getElementById('save-defaults-btn').onclick = () => {
|
|||||||
};
|
};
|
||||||
saveState();
|
saveState();
|
||||||
const btn = document.getElementById('save-defaults-btn');
|
const btn = document.getElementById('save-defaults-btn');
|
||||||
|
if (saveDefaultsRevertTimer) clearTimeout(saveDefaultsRevertTimer);
|
||||||
btn.innerHTML = '<i data-lucide="check"></i> Saved!'; lucide.createIcons();
|
btn.innerHTML = '<i data-lucide="check"></i> Saved!'; lucide.createIcons();
|
||||||
setTimeout(() => { btn.innerHTML = '<i data-lucide="save"></i> Save Default'; lucide.createIcons(); }, 1800);
|
saveDefaultsRevertTimer = setTimeout(() => {
|
||||||
|
btn.innerHTML = '<i data-lucide="save"></i> Save Default'; lucide.createIcons();
|
||||||
|
saveDefaultsRevertTimer = null;
|
||||||
|
}, 1800);
|
||||||
};
|
};
|
||||||
document.getElementById('apply-defaults-btn').onclick = () => {
|
document.getElementById('apply-defaults-btn').onclick = () => {
|
||||||
const d = appState.defaults;
|
const d = appState.defaults;
|
||||||
@@ -697,14 +711,18 @@ function exportHistoryCSV() {
|
|||||||
a.href = URL.createObjectURL(blob);
|
a.href = URL.createObjectURL(blob);
|
||||||
a.download = `freight-history-${new Date().toISOString().slice(0,10)}.csv`;
|
a.download = `freight-history-${new Date().toISOString().slice(0,10)}.csv`;
|
||||||
a.click();
|
a.click();
|
||||||
setTimeout(() => URL.revokeObjectURL(a.href), 100);
|
// Defer revocation generously so slower systems/rapid consecutive exports
|
||||||
|
// have time to actually start the download before the blob URL dies.
|
||||||
|
setTimeout(() => URL.revokeObjectURL(a.href), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Import history from CSV
|
// Import history from CSV
|
||||||
function importHistoryCSV(e) {
|
function importHistoryCSV(e) {
|
||||||
const file = e.target.files[0]; if (!file) return;
|
const file = e.target.files[0]; if (!file) return;
|
||||||
|
const myToken = ++csvImportToken;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = evt => {
|
reader.onload = evt => {
|
||||||
|
if (myToken !== csvImportToken) return; // a newer import selection superseded this read
|
||||||
const lines = evt.target.result.trim().split('\n').slice(1); // skip header
|
const lines = evt.target.result.trim().split('\n').slice(1); // skip header
|
||||||
const imported = [];
|
const imported = [];
|
||||||
lines.forEach(line => {
|
lines.forEach(line => {
|
||||||
@@ -782,7 +800,7 @@ function renderPieces() {
|
|||||||
});
|
});
|
||||||
// Bind input events
|
// Bind input events
|
||||||
container.querySelectorAll('input[type="number"]').forEach(el => {
|
container.querySelectorAll('input[type="number"]').forEach(el => {
|
||||||
el.addEventListener('input', e => { pieces[e.target.dataset.idx][e.target.dataset.field] = e.target.value; });
|
el.addEventListener('input', e => { pieces[parseInt(e.target.dataset.idx, 10)][e.target.dataset.field] = e.target.value; });
|
||||||
el.addEventListener('keydown', e => { if (e.key === 'Enter') calculateFreightClass(); });
|
el.addEventListener('keydown', e => { if (e.key === 'Enter') calculateFreightClass(); });
|
||||||
});
|
});
|
||||||
// Bind delete buttons
|
// Bind delete buttons
|
||||||
@@ -801,7 +819,10 @@ function addPiece() {
|
|||||||
// Focus first input of new row
|
// Focus first input of new row
|
||||||
const rows = document.querySelectorAll('.piece-row');
|
const rows = document.querySelectorAll('.piece-row');
|
||||||
const lastRow = rows[rows.length-1];
|
const lastRow = rows[rows.length-1];
|
||||||
if (lastRow) lastRow.querySelector('input') && lastRow.querySelector('input').focus();
|
if (lastRow) {
|
||||||
|
const firstInput = lastRow.querySelector('input');
|
||||||
|
if (firstInput) firstInput.focus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Validation & Clear ───────────────────────────────────────────────────────
|
// ─── Validation & Clear ───────────────────────────────────────────────────────
|
||||||
@@ -884,9 +905,14 @@ function calculateFreightClass() {
|
|||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns true once a calculation has produced a result (i.e. not the placeholder '--')
|
||||||
|
function hasResult() {
|
||||||
|
return document.getElementById('class-result').textContent !== '--';
|
||||||
|
}
|
||||||
|
|
||||||
// ─── Copy to clipboard ────────────────────────────────────────────────────────
|
// ─── Copy to clipboard ────────────────────────────────────────────────────────
|
||||||
function copyResult() {
|
function copyResult() {
|
||||||
if (document.getElementById('class-result').textContent === '--') return;
|
if (!hasResult()) return;
|
||||||
const cls = document.getElementById('class-result').textContent;
|
const cls = document.getElementById('class-result').textContent;
|
||||||
const density = document.getElementById('density-result').textContent;
|
const density = document.getElementById('density-result').textContent;
|
||||||
const du = appState.units.dim, wu = appState.units.wt;
|
const du = appState.units.dim, wu = appState.units.wt;
|
||||||
@@ -894,19 +920,34 @@ function copyResult() {
|
|||||||
const text = `Freight Class: ${cls}\nDensity: ${density} lb/ft³\n${pLines}`;
|
const text = `Freight Class: ${cls}\nDensity: ${density} lb/ft³\n${pLines}`;
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
navigator.clipboard.writeText(text).then(() => {
|
||||||
const btn = document.getElementById('copy-btn');
|
const btn = document.getElementById('copy-btn');
|
||||||
|
if (copyRevertTimer) clearTimeout(copyRevertTimer);
|
||||||
btn.classList.add('copied');
|
btn.classList.add('copied');
|
||||||
btn.innerHTML = '<i data-lucide="check"></i> Copied!'; lucide.createIcons();
|
btn.innerHTML = '<i data-lucide="check"></i> Copied!'; lucide.createIcons();
|
||||||
setTimeout(() => { btn.classList.remove('copied'); btn.innerHTML = '<i data-lucide="copy"></i> Copy'; lucide.createIcons(); }, 2000);
|
copyRevertTimer = setTimeout(() => {
|
||||||
|
btn.classList.remove('copied');
|
||||||
|
btn.innerHTML = '<i data-lucide="copy"></i> Copy'; lucide.createIcons();
|
||||||
|
copyRevertTimer = null;
|
||||||
|
}, 2000);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── PDF Export ───────────────────────────────────────────────────────────────
|
// ─── PDF Export ───────────────────────────────────────────────────────────────
|
||||||
function exportPDF() {
|
function exportPDF() {
|
||||||
if (document.getElementById('class-result').textContent === '--') {
|
if (!hasResult()) {
|
||||||
alert('Please calculate a freight class before exporting a PDF.');
|
alert('Please calculate a freight class before exporting a PDF.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (typeof window.jspdf === 'undefined') { alert('PDF library not loaded. Please check your internet connection.'); return; }
|
if (typeof window.jspdf === 'undefined') { alert('PDF library not loaded. Please check your internet connection.'); return; }
|
||||||
|
// Guard against stale/edited piece data producing Infinity/NaN in the PDF
|
||||||
|
// (e.g. user calculates, then clears a dimension before exporting without recalculating).
|
||||||
|
const piecesValidForExport = pieces.every(p => {
|
||||||
|
const L = parseFloat(p.L), Wd = parseFloat(p.W), H = parseFloat(p.H), Wt = parseFloat(p.Weight);
|
||||||
|
return L > 0 && Wd > 0 && H > 0 && Wt > 0;
|
||||||
|
});
|
||||||
|
if (!piecesValidForExport) {
|
||||||
|
alert('Piece values have changed since the last calculation. Please recalculate before exporting a PDF.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const { jsPDF } = window.jspdf;
|
const { jsPDF } = window.jspdf;
|
||||||
const doc = new jsPDF({ unit:'mm', format:'letter' });
|
const doc = new jsPDF({ unit:'mm', format:'letter' });
|
||||||
const W = doc.internal.pageSize.getWidth();
|
const W = doc.internal.pageSize.getWidth();
|
||||||
@@ -1031,7 +1072,7 @@ function nmfcSearch(query) {
|
|||||||
}
|
}
|
||||||
let html = '<table><thead><tr><th>NMFC Code</th><th>Description</th><th>Std Class</th></tr></thead><tbody>';
|
let html = '<table><thead><tr><th>NMFC Code</th><th>Description</th><th>Std Class</th></tr></thead><tbody>';
|
||||||
results.forEach(n => {
|
results.forEach(n => {
|
||||||
html += `<tr onclick="nmfcSelect(${n.cls})">
|
html += `<tr data-cls="${n.cls}">
|
||||||
<td style="font-family:monospace;font-size:0.85rem">${n.code}</td>
|
<td style="font-family:monospace;font-size:0.85rem">${n.code}</td>
|
||||||
<td>${n.desc}</td>
|
<td>${n.desc}</td>
|
||||||
<td><span class="nmfc-class-badge">${n.cls}</span></td>
|
<td><span class="nmfc-class-badge">${n.cls}</span></td>
|
||||||
@@ -1039,6 +1080,9 @@ function nmfcSearch(query) {
|
|||||||
});
|
});
|
||||||
html += '</tbody></table>';
|
html += '</tbody></table>';
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
|
container.querySelectorAll('tbody tr').forEach(row => {
|
||||||
|
row.addEventListener('click', () => nmfcSelect(parseFloat(row.dataset.cls)));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function nmfcSelect(cls) {
|
function nmfcSelect(cls) {
|
||||||
// Highlight the matching row in the density reference table
|
// Highlight the matching row in the density reference table
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "freight-calculator-pro",
|
"name": "freight-calculator-pro",
|
||||||
"version": "1.0.0",
|
"version": "1.3.0",
|
||||||
"description": "Professional Freight Class Calculator",
|
"description": "Professional Freight Class Calculator",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -17,6 +17,10 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"appId": "com.freightcalc.pro",
|
"appId": "com.freightcalc.pro",
|
||||||
"productName": "Freight Calculator Pro",
|
"productName": "Freight Calculator Pro",
|
||||||
|
"files": [
|
||||||
|
"**/*",
|
||||||
|
"!tauri/**"
|
||||||
|
],
|
||||||
"win": {
|
"win": {
|
||||||
"target": "nsis",
|
"target": "nsis",
|
||||||
"icon": "icon.ico"
|
"icon": "icon.ico"
|
||||||
|
|||||||
@@ -14,7 +14,16 @@ const mimeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
const server = http.createServer((req, res) => {
|
||||||
const filePath = path.join(__dirname, req.url === '/' ? 'index.html' : req.url);
|
// Strip query string / hash and decode URI components before resolving,
|
||||||
|
// then confirm the resolved path stays inside __dirname (no ../ escapes).
|
||||||
|
const urlPath = decodeURIComponent(req.url.split('?')[0].split('#')[0]);
|
||||||
|
const reqPath = urlPath === '/' ? 'index.html' : urlPath.replace(/^\/+/, '');
|
||||||
|
const filePath = path.join(__dirname, reqPath);
|
||||||
|
if (!filePath.startsWith(__dirname + path.sep) && filePath !== __dirname) {
|
||||||
|
res.writeHead(403, { 'Content-Type': 'text/plain' });
|
||||||
|
res.end('403 Forbidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const ext = path.extname(filePath);
|
const ext = path.extname(filePath);
|
||||||
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
const contentType = mimeTypes[ext] || 'application/octet-stream';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
node_modules/
|
||||||
|
src-tauri/target/
|
||||||
|
src-tauri/gen/schemas
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Generated by sync-frontend.js — copy of ../index.html and ../vendor/, not source of truth
|
||||||
|
dist/
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "freight-calculator-pro-tauri",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"description": "Professional Freight Class Calculator (Tauri shell)",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"sync": "node sync-frontend.js",
|
||||||
|
"dev": "npm run sync && tauri dev",
|
||||||
|
"build": "npm run sync && tauri build",
|
||||||
|
"serve": "node ../server.js"
|
||||||
|
},
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.11.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
/target/
|
||||||
|
/gen/schemas
|
||||||
Generated
+4941
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "freight-calculator-pro"
|
||||||
|
version = "1.3.0"
|
||||||
|
description = "Professional Freight Class Calculator"
|
||||||
|
authors = ["Devon Bontrager"]
|
||||||
|
license = "ISC"
|
||||||
|
repository = "https://gitea.netbird.zimspace.uk:5938/debont80/FreightCalculatorPro.git"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.77.2"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "app_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2.6.2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
log = "0.4"
|
||||||
|
tauri = { version = "2.11.2", features = [] }
|
||||||
|
tauri-plugin-log = "2"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
|
"identifier": "default",
|
||||||
|
"description": "enables the default permissions",
|
||||||
|
"windows": [
|
||||||
|
"main"
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"core:default"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.7 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.setup(|app| {
|
||||||
|
if cfg!(debug_assertions) {
|
||||||
|
app.handle().plugin(
|
||||||
|
tauri_plugin_log::Builder::default()
|
||||||
|
.level(log::LevelFilter::Info)
|
||||||
|
.build(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running tauri application");
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
app_lib::run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
|
"productName": "Freight Calculator Pro",
|
||||||
|
"version": "1.3.0",
|
||||||
|
"identifier": "com.freightcalc.pro",
|
||||||
|
"build": {
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "Freight Calculator Pro",
|
||||||
|
"width": 950,
|
||||||
|
"height": 950,
|
||||||
|
"minWidth": 800,
|
||||||
|
"minHeight": 700,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": "all",
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.icns",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Copies the canonical frontend (../index.html and ../vendor/) into ./dist
|
||||||
|
// before each Tauri dev/build run, so the Tauri shell always packages the
|
||||||
|
// exact same UI as the Electron version — no manual sync, no drift.
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..');
|
||||||
|
const DIST = path.join(__dirname, 'dist');
|
||||||
|
|
||||||
|
function copyRecursive(src, dest) {
|
||||||
|
const stat = fs.statSync(src);
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
fs.mkdirSync(dest, { recursive: true });
|
||||||
|
for (const entry of fs.readdirSync(src)) {
|
||||||
|
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fs.copyFileSync(src, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.rmSync(DIST, { recursive: true, force: true });
|
||||||
|
fs.mkdirSync(DIST, { recursive: true });
|
||||||
|
copyRecursive(path.join(ROOT, 'index.html'), path.join(DIST, 'index.html'));
|
||||||
|
copyRecursive(path.join(ROOT, 'vendor'), path.join(DIST, 'vendor'));
|
||||||
|
|
||||||
|
console.log(`Synced frontend (index.html + vendor/) from ${ROOT} -> ${DIST}`);
|
||||||
Vendored
+10
File diff suppressed because one or more lines are too long
Vendored
+398
File diff suppressed because one or more lines are too long
Vendored
+19522
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user