Fixed the rest
All checks were successful
Build Docker image / build (push) Successful in 2m29s
Build Docker image / deploy (push) Successful in 4s
Build Docker image / verify (push) Successful in 40s

This commit is contained in:
Roman Krček
2025-08-06 15:08:45 +02:00
parent c6cc9c6658
commit 6ed1f985e0
5 changed files with 52 additions and 66 deletions

View File

@@ -5,15 +5,17 @@
import StepColumnMap from './wizard/StepColumnMap.svelte';
import StepRowFilter from './wizard/StepRowFilter.svelte';
import StepCardDetails from './wizard/StepCardDetails.svelte';
// import StepGallery from './wizard/StepGallery.svelte';
// import StepGenerate from './wizard/StepGenerate.svelte';
import StepGallery from './wizard/StepGallery.svelte';
import StepGenerate from './wizard/StepGenerate.svelte';
const steps = [
StepAuth,
StepSheetSearch,
StepColumnMap,
StepRowFilter,
StepCardDetails
StepCardDetails,
StepGallery,
StepGenerate
];
const stepTitles = [
@@ -21,7 +23,9 @@
'Select Sheet',
'Map Columns',
'Filter Rows',
'Enter Card Details'
'Enter Card Details',
'Preview Gallery',
'Generate Cards'
];
</script>

View File

@@ -28,7 +28,6 @@
console.error('Failed to save to localStorage:', error);
}
$cardDetails = { homeSection, validityStart };
$currentStep++;
}
</script>

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { env } from '$env/dynamic/public';
import { columnMapping, filteredSheetData, currentStep, pictures, cropRects } from '$lib/stores';
import { columnMapping, sheetData, currentStep, pictures, cropRects } from '$lib/stores';
import { downloadDriveImage, isGoogleDriveUrl, createImageObjectUrl } from '$lib/google';
import Navigator from './subcomponents/Navigator.svelte';
import PhotoCard from './subcomponents/PhotoCard.svelte';
@@ -61,8 +61,6 @@
}
async function processPhotosInParallel() {
if (isProcessing) return;
console.log('Starting processPhotos with queues...');
isProcessing = true;
processedCount = 0;
@@ -73,6 +71,7 @@
console.log('Cleared IndexedDB.');
} catch (e) {
console.error('Could not clear IndexedDB:', e);
return;
}
// Initialize queues with more conservative concurrency
@@ -95,7 +94,7 @@
}
});
const validRows = $filteredSheetData.filter((row) => row._isValid);
const validRows = $sheetData.filter((row) => row._valid);
const photoUrls = new Set<string>();
const photoMap = new Map<string, any[]>();
@@ -130,16 +129,12 @@
// Initialize detector and process photos
onMount(() => {
console.log('StepGallery mounted');
initializeDetector(); // Start loading model
if ($filteredSheetData.length > 0 && $columnMapping.pictureUrl !== undefined) {
if ($sheetData.length > 0 && $columnMapping.pictureUrl !== undefined) {
console.log('Processing photos for gallery step');
processPhotosInParallel();
} else {
console.log('No data to process:', {
dataLength: $filteredSheetData.length,
pictureUrlMapping: $columnMapping.pictureUrl
});
console.log('No data to process: !');
}
});
@@ -148,7 +143,6 @@
if (!isRetry) {
photo.status = 'loading';
// No need to reassign photos array with $state reactivity
}
try {
@@ -285,14 +279,6 @@
imageTensor = tf.browser.fromPixels(img);
const predictions = await detector.estimateFaces(imageTensor, false);
// Log memory usage for debugging
const memInfo = tf.memory();
console.log(`TensorFlow.js memory after face detection for ${photo.name}:`, {
numTensors: memInfo.numTensors,
numDataBuffers: memInfo.numDataBuffers,
numBytes: memInfo.numBytes
});
if (predictions.length > 0) {
const getProbability = (p: number | tf.Tensor) =>
typeof p === 'number' ? p : p.dataSync()[0];
@@ -518,23 +504,6 @@
<div class="text-gray-600">Failed</div>
</div>
</div>
{#if photos.filter((p) => p.status === 'error').length > 0}
<div class="mt-4 rounded border border-yellow-200 bg-yellow-50 p-3">
<p class="text-sm text-yellow-800">
<strong>Note:</strong> Cards will only be generated for photos that load successfully.
</p>
</div>
{/if}
{#if !canProceed() && photos.filter((p) => p.status === 'success').length > 0}
<div class="mt-4 rounded border border-blue-200 bg-blue-50 p-3">
<p class="text-sm text-blue-800">
<strong>Tip:</strong> All photos need to be cropped before proceeding. Face detection runs
automatically.
</p>
</div>
{/if}
</div>
{/if}

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte';
import { filteredSheetData, currentStep, pictures, cropRects } from '$lib/stores';
import { sheetData, currentStep, pictures, cropRects } from '$lib/stores';
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
import * as fontkit from 'fontkit';
import { clear } from 'idb-keyval';
@@ -30,11 +30,12 @@
url: string | null;
size: number | null;
error: string | null;
downloadName?: string;
};
const initialFiles: GeneratedFile[] = [
{
name: 'people_data.pdf',
name: 'esncards_text.pdf',
displayName: 'Text PDF',
state: 'idle',
url: null,
@@ -42,7 +43,7 @@
error: null
},
{
name: 'people_photos.pdf',
name: 'esncards_photos.pdf',
displayName: 'Photos PDF',
state: 'idle',
url: null,
@@ -71,11 +72,11 @@
onMount(() => {
// Add event listener for page unload
window.addEventListener('beforeunload', handleBeforeUnload);
// Start generation automatically when the component mounts
handleGenerate('people_data.pdf');
handleGenerate('people_photos.pdf');
handleGenerate('esncards_text.pdf');
handleGenerate('esncards_photos.pdf');
// Cleanup function when component unmounts
return () => {
window.removeEventListener('beforeunload', handleBeforeUnload);
@@ -97,6 +98,17 @@
}
}
// Create a formatted timestamp string
function getTimestamp(): string {
const d = new Date();
const year = d.getFullYear();
const month = (d.getMonth() + 1).toString().padStart(2, '0');
const day = d.getDate().toString().padStart(2, '0');
const hours = d.getHours().toString().padStart(2, '0');
const minutes = d.getMinutes().toString().padStart(2, '0');
return `${year}-${month}-${day}-${hours}-${minutes}`;
}
// Crop image using canvas
async function cropImage(
imageBlob: Blob,
@@ -163,7 +175,7 @@
try {
const pdfBytes =
fileName === 'people_data.pdf' ? await generateTextPDF() : await generatePhotoPDF();
fileName === 'esncards_text.pdf' ? await generateTextPDF() : await generatePhotoPDF();
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
@@ -176,6 +188,10 @@
fileToUpdate.size = pdfBytes.length;
fileToUpdate.state = 'done';
const timestamp = getTimestamp();
const baseName = fileName.replace('.pdf', '');
fileToUpdate.downloadName = `${baseName}_${timestamp}.pdf`;
// Check if both PDFs are done, then clear sensitive data
const allDone = files.every((f) => f.state === 'done' || f.state === 'error');
if (allDone) {
@@ -214,7 +230,7 @@
let currentRow = 0;
let currentCol = 0;
const validRows = $filteredSheetData.filter((row) => row._isValid);
const validRows = $sheetData.filter((row) => row._valid);
for (let i = 0; i < validRows.length; i++) {
const row = validRows[i];
@@ -224,11 +240,10 @@
const cellY_mm = PAGE_SETTINGS.margin + currentRow * gridLayout.cellHeight;
// Get field values
const name = row.name || row.Name || '';
const surname = row.surname || row.Surname || row.lastname || row.LastName || '';
const nationality = row.nationality || row.Nationality || row.country || row.Country || '';
const birthday =
row.birthday || row.Birthday || row.birthdate || row.Birthdate || row.birth_date || '';
const name = row.name;
const surname = row.surname;
const nationality = row.nationality;
const birthday = row.birthday;
// Draw name
const namePos = getAbsolutePositionPt(
@@ -319,7 +334,7 @@
let currentRow = 0;
let currentCol = 0;
const validRows = $filteredSheetData.filter((row) => row._isValid);
const validRows = $sheetData.filter((row) => row._valid);
for (let i = 0; i < validRows.length; i++) {
const row = validRows[i];
@@ -336,7 +351,7 @@
PHOTO_FIELD_LAYOUT.photo
);
const pictureUrl = row.pictureUrl || row.picture_url || row.Picture || row.PictureUrl;
const pictureUrl = row.pictureUrl;
const pictureInfo = $pictures[pictureUrl];
const cropData = $cropRects[pictureUrl];
@@ -397,8 +412,8 @@
}
// Draw name
const name = row.name || row.Name || '';
const surname = row.surname || row.Surname || row.lastname || row.LastName || '';
const name = row.name;
const surname = row.surname;
const namePos = getAbsolutePositionPt(
cellX_mm,
cellY_mm,
@@ -430,7 +445,7 @@
if (!file.url) return;
const link = document.createElement('a');
link.href = file.url;
link.download = file.name;
link.download = file.downloadName || file.name;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
@@ -443,10 +458,10 @@
}
});
files = JSON.parse(JSON.stringify(initialFiles));
// Clear sensitive data when starting over
clearSensitiveData();
currentStep.set(0);
}
@@ -478,7 +493,7 @@
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
<div class="text-center">
<div class="text-2xl font-bold text-gray-900">
{$filteredSheetData.filter((row) => row._isValid).length}
{$sheetData.filter((row) => row._valid).length}
</div>
<div class="text-gray-600">Records to Process</div>
</div>
@@ -590,7 +605,7 @@
<!-- Navigation -->
<div class="flex justify-between">
<button
onclick={() => currentStep.set(5)}
onclick={() => currentStep.set(6)}
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300"
>
← Back to Gallery

View File

@@ -38,7 +38,6 @@ export interface RowData {
// Picture storage and metadata
export interface PictureBlobInfoType {
id: string;
blob: Blob;
url: string;
downloaded: boolean;
faceDetected: boolean;
@@ -87,7 +86,7 @@ export const sheetData = writable<RowData[]>([]);
export const pictures = writable<Record<string, PictureBlobInfoType>>({});
// Store and hold the crop rectangles from face detection
export const CropRects = writable<Record<string, CropType>>({});
export const cropRects = writable<Record<string, CropType>>({});
// Store and hold the selected sheet
export const selectedSheet = writable<SheetInfoType>({ id: '', name: '', webViewLink: '' });