Skip to content

Commit b37fe87

Browse files
authored
Better loading feedback (#47)
1 parent 7bab3d8 commit b37fe87

3 files changed

Lines changed: 126 additions & 11 deletions

File tree

src/lib/utils/geometryWorker.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ interface Export3mfResult {
8585
error?: string;
8686
}
8787

88+
interface GenerationProgressMessage {
89+
type: 'generation-progress';
90+
id: number;
91+
current: number;
92+
total: number;
93+
currentItem: string;
94+
}
95+
96+
export interface GenerationProgress {
97+
current: number;
98+
total: number;
99+
currentItem: string;
100+
}
101+
88102
// Result interfaces with BufferGeometry
89103
export interface TrayGeometryData {
90104
trayId: string;
@@ -126,7 +140,12 @@ export interface GenerationResult {
126140
allLooseTrayGeometries: LooseTrayGeometryData[];
127141
}
128142

129-
type WorkerResult = GenerateResult | ExportStlResult | ExportAllStlsResult | Export3mfResult;
143+
type WorkerResult =
144+
| GenerateResult
145+
| ExportStlResult
146+
| ExportAllStlsResult
147+
| Export3mfResult
148+
| GenerationProgressMessage;
130149

131150
/**
132151
* Convert raw geometry data to Three.js BufferGeometry
@@ -152,6 +171,7 @@ export class GeometryWorkerManager {
152171
resolve: (value: unknown) => void;
153172
reject: (reason: unknown) => void;
154173
isGenerateRequest?: boolean;
174+
onProgress?: (progress: GenerationProgress) => void;
155175
}
156176
>();
157177

@@ -168,6 +188,20 @@ export class GeometryWorkerManager {
168188

169189
this.worker.onmessage = (event: MessageEvent<WorkerResult>) => {
170190
const result = event.data;
191+
192+
// Handle progress messages without removing the pending request
193+
if (result.type === 'generation-progress') {
194+
const pending = this.pendingRequests.get(result.id);
195+
if (pending?.onProgress) {
196+
pending.onProgress({
197+
current: result.current,
198+
total: result.total,
199+
currentItem: result.currentItem
200+
});
201+
}
202+
return;
203+
}
204+
171205
const pending = this.pendingRequests.get(result.id);
172206

173207
if (pending) {
@@ -201,7 +235,12 @@ export class GeometryWorkerManager {
201235
/**
202236
* Generate all geometries for a project
203237
*/
204-
async generate(project: Project, selectedBoxId: string, selectedTrayId: string): Promise<GenerationResult> {
238+
async generate(
239+
project: Project,
240+
selectedBoxId: string,
241+
selectedTrayId: string,
242+
onProgress?: (progress: GenerationProgress) => void
243+
): Promise<GenerationResult> {
205244
if (!this.worker) {
206245
await this.init();
207246
}
@@ -212,6 +251,7 @@ export class GeometryWorkerManager {
212251
return new Promise((resolve, reject) => {
213252
this.pendingRequests.set(id, {
214253
isGenerateRequest: true,
254+
onProgress,
215255
resolve: (result) => {
216256
const r = result as GenerateResult;
217257

src/lib/workers/geometry.worker.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@ interface Export3mfResult {
254254
error?: string;
255255
}
256256

257+
interface GenerationProgressMessage {
258+
type: 'generation-progress';
259+
id: number;
260+
current: number;
261+
total: number;
262+
currentItem: string;
263+
}
264+
257265
// Cache the last generated JSCAD geometries for STL export
258266
let cachedSelectedTray: Geom3 | null = null;
259267
let cachedBox: Geom3 | null = null;
@@ -697,9 +705,25 @@ function handleGenerate(msg: GenerateMessage): void {
697705
// Get all boxes from all layers
698706
const allBoxes = getAllBoxes(project.layers);
699707

708+
// Count all loose trays for progress tracking
709+
const allLooseTrays = project.layers.flatMap((layer) => layer.looseTrays);
710+
const totalOperations = allBoxes.length + allLooseTrays.length;
711+
let currentOperation = 0;
712+
700713
// Generate geometries for ALL boxes (for all-no-lid view) and cache JSCAD for STL export
701714
cachedAllBoxes = [];
702-
const allBoxGeometries: BoxGeometryResult[] = allBoxes.map((projectBox) => {
715+
const allBoxGeometries: BoxGeometryResult[] = [];
716+
717+
for (const projectBox of allBoxes) {
718+
// Send progress update
719+
currentOperation++;
720+
self.postMessage({
721+
type: 'generation-progress',
722+
id,
723+
current: currentOperation,
724+
total: totalOperations,
725+
currentItem: projectBox.name
726+
} as GenerationProgressMessage);
703727
const boxValidation = validateCustomDimensions(projectBox, cardSizes, counterShapes);
704728
if (!boxValidation.valid) {
705729
console.warn(`Box "${projectBox.name}" validation failed:`, boxValidation.errors);
@@ -778,16 +802,16 @@ function handleGenerate(msg: GenerateMessage): void {
778802
trays: cachedTraysForBox
779803
});
780804

781-
// Return box dimensions using the layer height (so all boxes in layer report same height)
782-
return {
805+
// Add box dimensions using the layer height (so all boxes in layer report same height)
806+
allBoxGeometries.push({
783807
boxId: projectBox.id,
784808
boxName: projectBox.name,
785809
boxGeometry: boxBufferGeom,
786810
lidGeometry: lidBufferGeom,
787811
trayGeometries: trayGeoms,
788812
boxDimensions: { width: projectBox.customWidth ?? 0, depth: projectBox.customDepth ?? 0, height: layerHeight }
789-
};
790-
});
813+
});
814+
}
791815

792816
// Generate geometries for all loose trays across all layers
793817
cachedAllLooseTrays = [];
@@ -798,6 +822,16 @@ function handleGenerate(msg: GenerateMessage): void {
798822
const layerHeight = layerHeights.get(layer.id) ?? 0;
799823

800824
for (const looseTray of layer.looseTrays) {
825+
// Send progress update
826+
currentOperation++;
827+
self.postMessage({
828+
type: 'generation-progress',
829+
id,
830+
current: currentOperation,
831+
total: totalOperations,
832+
currentItem: looseTray.name
833+
} as GenerationProgressMessage);
834+
801835
// Calculate tray dimensions for width/depth
802836
const trayDims = getTrayDimensionsForTray(looseTray, cardSizes, counterShapes);
803837
// Use layer height for the tray height so loose trays match box exterior height

src/routes/+page.svelte

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import { jscadToBufferGeometry } from '$lib/utils/jscadToThree';
2727
import {
2828
getGeometryWorker,
29+
type GenerationProgress,
2930
type TrayGeometryData,
3031
type BoxGeometryData,
3132
type LooseTrayGeometryData
@@ -125,6 +126,7 @@
125126
let boxGeometry = $state<BufferGeometry | null>(null);
126127
let lidGeometry = $state<BufferGeometry | null>(null);
127128
let generating = $state(false);
129+
let generationProgress = $state<GenerationProgress | null>(null);
128130
let geometryWorker = getGeometryWorker();
129131
let error = $state('');
130132
let isDirty = $state(false);
@@ -661,6 +663,7 @@
661663
});
662664
663665
generating = true;
666+
generationProgress = null;
664667
error = '';
665668
666669
// Clear stale geometry when forcing regeneration (structural changes)
@@ -674,7 +677,9 @@
674677
try {
675678
// Use web worker for geometry generation (handles both boxed and loose trays)
676679
// Pass empty string for boxId if it's a loose tray - worker handles this case
677-
const result = await geometryWorker.generate(project, box?.id ?? '', tray.id);
680+
const result = await geometryWorker.generate(project, box?.id ?? '', tray.id, (progress) => {
681+
generationProgress = progress;
682+
});
678683
679684
selectedTrayGeometry = result.selectedTrayGeometry;
680685
selectedTrayCounters = result.selectedTrayCounters;
@@ -696,6 +701,7 @@
696701
// Don't update state for superseded requests - a newer request will handle it
697702
if (!wasSuperseded) {
698703
generating = false;
704+
generationProgress = null;
699705
// Use the hash captured at generation start, not current
700706
// This prevents marking cache as valid if params changed during generation
701707
lastGeneratedHash = hashAtGenerationStart;
@@ -1916,7 +1922,13 @@
19161922
{#if generating && !debugParams.hideUI}
19171923
<div class="generatingOverlay">
19181924
<Loader />
1919-
<div class="generatingText">Generating geometry...</div>
1925+
<div class="generatingProgress">
1926+
<span class="generatingProgress__label">Generating</span>
1927+
<span class="generatingProgress__name">{generationProgress?.currentItem ?? 'geometry...'}</span>
1928+
{#if generationProgress}
1929+
<span class="generatingProgress__count">({generationProgress.current}/{generationProgress.total})</span>
1930+
{/if}
1931+
</div>
19201932
</div>
19211933
{/if}
19221934

@@ -2158,7 +2170,13 @@
21582170
{#if generating && !debugParams.hideUI}
21592171
<div class="generatingOverlay">
21602172
<Loader />
2161-
<div class="generatingText">Generating geometry...</div>
2173+
<div class="generatingProgress">
2174+
<span class="generatingProgress__label">Generating</span>
2175+
<span class="generatingProgress__name">{generationProgress?.currentItem ?? 'geometry...'}</span>
2176+
{#if generationProgress}
2177+
<span class="generatingProgress__count">({generationProgress.current}/{generationProgress.total})</span>
2178+
{/if}
2179+
</div>
21622180
</div>
21632181
{/if}
21642182

@@ -2447,8 +2465,31 @@
24472465
background: rgba(0, 0, 0, 0.5);
24482466
}
24492467
2450-
.generatingText {
2468+
.generatingProgress {
2469+
display: flex;
2470+
align-items: center;
2471+
gap: 0.375rem;
24512472
font-size: 1.125rem;
2473+
width: 20rem;
2474+
}
2475+
2476+
.generatingProgress__label {
2477+
flex-shrink: 0;
2478+
}
2479+
2480+
.generatingProgress__name {
2481+
flex: 1;
2482+
overflow: hidden;
2483+
text-overflow: ellipsis;
2484+
white-space: nowrap;
2485+
min-width: 0;
2486+
}
2487+
2488+
.generatingProgress__count {
2489+
flex-shrink: 0;
2490+
font-family: var(--fontMono, monospace);
2491+
text-align: right;
2492+
width: 4.5rem;
24522493
}
24532494
24542495
/* Resizer styling */

0 commit comments

Comments
 (0)