Fixed the rest
This commit is contained in:
@@ -5,15 +5,17 @@
|
|||||||
import StepColumnMap from './wizard/StepColumnMap.svelte';
|
import StepColumnMap from './wizard/StepColumnMap.svelte';
|
||||||
import StepRowFilter from './wizard/StepRowFilter.svelte';
|
import StepRowFilter from './wizard/StepRowFilter.svelte';
|
||||||
import StepCardDetails from './wizard/StepCardDetails.svelte';
|
import StepCardDetails from './wizard/StepCardDetails.svelte';
|
||||||
// import StepGallery from './wizard/StepGallery.svelte';
|
import StepGallery from './wizard/StepGallery.svelte';
|
||||||
// import StepGenerate from './wizard/StepGenerate.svelte';
|
import StepGenerate from './wizard/StepGenerate.svelte';
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
StepAuth,
|
StepAuth,
|
||||||
StepSheetSearch,
|
StepSheetSearch,
|
||||||
StepColumnMap,
|
StepColumnMap,
|
||||||
StepRowFilter,
|
StepRowFilter,
|
||||||
StepCardDetails
|
StepCardDetails,
|
||||||
|
StepGallery,
|
||||||
|
StepGenerate
|
||||||
];
|
];
|
||||||
|
|
||||||
const stepTitles = [
|
const stepTitles = [
|
||||||
@@ -21,7 +23,9 @@
|
|||||||
'Select Sheet',
|
'Select Sheet',
|
||||||
'Map Columns',
|
'Map Columns',
|
||||||
'Filter Rows',
|
'Filter Rows',
|
||||||
'Enter Card Details'
|
'Enter Card Details',
|
||||||
|
'Preview Gallery',
|
||||||
|
'Generate Cards'
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@
|
|||||||
console.error('Failed to save to localStorage:', error);
|
console.error('Failed to save to localStorage:', error);
|
||||||
}
|
}
|
||||||
$cardDetails = { homeSection, validityStart };
|
$cardDetails = { homeSection, validityStart };
|
||||||
$currentStep++;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { env } from '$env/dynamic/public';
|
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 { downloadDriveImage, isGoogleDriveUrl, createImageObjectUrl } from '$lib/google';
|
||||||
import Navigator from './subcomponents/Navigator.svelte';
|
import Navigator from './subcomponents/Navigator.svelte';
|
||||||
import PhotoCard from './subcomponents/PhotoCard.svelte';
|
import PhotoCard from './subcomponents/PhotoCard.svelte';
|
||||||
@@ -61,8 +61,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function processPhotosInParallel() {
|
async function processPhotosInParallel() {
|
||||||
if (isProcessing) return;
|
|
||||||
|
|
||||||
console.log('Starting processPhotos with queues...');
|
console.log('Starting processPhotos with queues...');
|
||||||
isProcessing = true;
|
isProcessing = true;
|
||||||
processedCount = 0;
|
processedCount = 0;
|
||||||
@@ -73,6 +71,7 @@
|
|||||||
console.log('Cleared IndexedDB.');
|
console.log('Cleared IndexedDB.');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Could not clear IndexedDB:', e);
|
console.error('Could not clear IndexedDB:', e);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize queues with more conservative concurrency
|
// 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 photoUrls = new Set<string>();
|
||||||
const photoMap = new Map<string, any[]>();
|
const photoMap = new Map<string, any[]>();
|
||||||
|
|
||||||
@@ -130,16 +129,12 @@
|
|||||||
|
|
||||||
// Initialize detector and process photos
|
// Initialize detector and process photos
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
console.log('StepGallery mounted');
|
|
||||||
initializeDetector(); // Start loading model
|
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');
|
console.log('Processing photos for gallery step');
|
||||||
processPhotosInParallel();
|
processPhotosInParallel();
|
||||||
} else {
|
} else {
|
||||||
console.log('No data to process:', {
|
console.log('No data to process: !');
|
||||||
dataLength: $filteredSheetData.length,
|
|
||||||
pictureUrlMapping: $columnMapping.pictureUrl
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -148,7 +143,6 @@
|
|||||||
|
|
||||||
if (!isRetry) {
|
if (!isRetry) {
|
||||||
photo.status = 'loading';
|
photo.status = 'loading';
|
||||||
// No need to reassign photos array with $state reactivity
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -285,14 +279,6 @@
|
|||||||
imageTensor = tf.browser.fromPixels(img);
|
imageTensor = tf.browser.fromPixels(img);
|
||||||
const predictions = await detector.estimateFaces(imageTensor, false);
|
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) {
|
if (predictions.length > 0) {
|
||||||
const getProbability = (p: number | tf.Tensor) =>
|
const getProbability = (p: number | tf.Tensor) =>
|
||||||
typeof p === 'number' ? p : p.dataSync()[0];
|
typeof p === 'number' ? p : p.dataSync()[0];
|
||||||
@@ -518,23 +504,6 @@
|
|||||||
<div class="text-gray-600">Failed</div>
|
<div class="text-gray-600">Failed</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
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 { PDFDocument, StandardFonts, rgb } from 'pdf-lib';
|
||||||
import * as fontkit from 'fontkit';
|
import * as fontkit from 'fontkit';
|
||||||
import { clear } from 'idb-keyval';
|
import { clear } from 'idb-keyval';
|
||||||
@@ -30,11 +30,12 @@
|
|||||||
url: string | null;
|
url: string | null;
|
||||||
size: number | null;
|
size: number | null;
|
||||||
error: string | null;
|
error: string | null;
|
||||||
|
downloadName?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialFiles: GeneratedFile[] = [
|
const initialFiles: GeneratedFile[] = [
|
||||||
{
|
{
|
||||||
name: 'people_data.pdf',
|
name: 'esncards_text.pdf',
|
||||||
displayName: 'Text PDF',
|
displayName: 'Text PDF',
|
||||||
state: 'idle',
|
state: 'idle',
|
||||||
url: null,
|
url: null,
|
||||||
@@ -42,7 +43,7 @@
|
|||||||
error: null
|
error: null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'people_photos.pdf',
|
name: 'esncards_photos.pdf',
|
||||||
displayName: 'Photos PDF',
|
displayName: 'Photos PDF',
|
||||||
state: 'idle',
|
state: 'idle',
|
||||||
url: null,
|
url: null,
|
||||||
@@ -71,11 +72,11 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Add event listener for page unload
|
// Add event listener for page unload
|
||||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
|
||||||
// Start generation automatically when the component mounts
|
// Start generation automatically when the component mounts
|
||||||
handleGenerate('people_data.pdf');
|
handleGenerate('esncards_text.pdf');
|
||||||
handleGenerate('people_photos.pdf');
|
handleGenerate('esncards_photos.pdf');
|
||||||
|
|
||||||
// Cleanup function when component unmounts
|
// Cleanup function when component unmounts
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('beforeunload', handleBeforeUnload);
|
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
|
// Crop image using canvas
|
||||||
async function cropImage(
|
async function cropImage(
|
||||||
imageBlob: Blob,
|
imageBlob: Blob,
|
||||||
@@ -163,7 +175,7 @@
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const pdfBytes =
|
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' });
|
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
|
||||||
|
|
||||||
@@ -176,6 +188,10 @@
|
|||||||
fileToUpdate.size = pdfBytes.length;
|
fileToUpdate.size = pdfBytes.length;
|
||||||
fileToUpdate.state = 'done';
|
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
|
// Check if both PDFs are done, then clear sensitive data
|
||||||
const allDone = files.every((f) => f.state === 'done' || f.state === 'error');
|
const allDone = files.every((f) => f.state === 'done' || f.state === 'error');
|
||||||
if (allDone) {
|
if (allDone) {
|
||||||
@@ -214,7 +230,7 @@
|
|||||||
let currentRow = 0;
|
let currentRow = 0;
|
||||||
let currentCol = 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++) {
|
for (let i = 0; i < validRows.length; i++) {
|
||||||
const row = validRows[i];
|
const row = validRows[i];
|
||||||
@@ -224,11 +240,10 @@
|
|||||||
const cellY_mm = PAGE_SETTINGS.margin + currentRow * gridLayout.cellHeight;
|
const cellY_mm = PAGE_SETTINGS.margin + currentRow * gridLayout.cellHeight;
|
||||||
|
|
||||||
// Get field values
|
// Get field values
|
||||||
const name = row.name || row.Name || '';
|
const name = row.name;
|
||||||
const surname = row.surname || row.Surname || row.lastname || row.LastName || '';
|
const surname = row.surname;
|
||||||
const nationality = row.nationality || row.Nationality || row.country || row.Country || '';
|
const nationality = row.nationality;
|
||||||
const birthday =
|
const birthday = row.birthday;
|
||||||
row.birthday || row.Birthday || row.birthdate || row.Birthdate || row.birth_date || '';
|
|
||||||
|
|
||||||
// Draw name
|
// Draw name
|
||||||
const namePos = getAbsolutePositionPt(
|
const namePos = getAbsolutePositionPt(
|
||||||
@@ -319,7 +334,7 @@
|
|||||||
let currentRow = 0;
|
let currentRow = 0;
|
||||||
let currentCol = 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++) {
|
for (let i = 0; i < validRows.length; i++) {
|
||||||
const row = validRows[i];
|
const row = validRows[i];
|
||||||
@@ -336,7 +351,7 @@
|
|||||||
PHOTO_FIELD_LAYOUT.photo
|
PHOTO_FIELD_LAYOUT.photo
|
||||||
);
|
);
|
||||||
|
|
||||||
const pictureUrl = row.pictureUrl || row.picture_url || row.Picture || row.PictureUrl;
|
const pictureUrl = row.pictureUrl;
|
||||||
const pictureInfo = $pictures[pictureUrl];
|
const pictureInfo = $pictures[pictureUrl];
|
||||||
const cropData = $cropRects[pictureUrl];
|
const cropData = $cropRects[pictureUrl];
|
||||||
|
|
||||||
@@ -397,8 +412,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw name
|
// Draw name
|
||||||
const name = row.name || row.Name || '';
|
const name = row.name;
|
||||||
const surname = row.surname || row.Surname || row.lastname || row.LastName || '';
|
const surname = row.surname;
|
||||||
const namePos = getAbsolutePositionPt(
|
const namePos = getAbsolutePositionPt(
|
||||||
cellX_mm,
|
cellX_mm,
|
||||||
cellY_mm,
|
cellY_mm,
|
||||||
@@ -430,7 +445,7 @@
|
|||||||
if (!file.url) return;
|
if (!file.url) return;
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = file.url;
|
link.href = file.url;
|
||||||
link.download = file.name;
|
link.download = file.downloadName || file.name;
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
@@ -443,10 +458,10 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
files = JSON.parse(JSON.stringify(initialFiles));
|
files = JSON.parse(JSON.stringify(initialFiles));
|
||||||
|
|
||||||
// Clear sensitive data when starting over
|
// Clear sensitive data when starting over
|
||||||
clearSensitiveData();
|
clearSensitiveData();
|
||||||
|
|
||||||
currentStep.set(0);
|
currentStep.set(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -478,7 +493,7 @@
|
|||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="text-2xl font-bold text-gray-900">
|
<div class="text-2xl font-bold text-gray-900">
|
||||||
{$filteredSheetData.filter((row) => row._isValid).length}
|
{$sheetData.filter((row) => row._valid).length}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-gray-600">Records to Process</div>
|
<div class="text-gray-600">Records to Process</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -590,7 +605,7 @@
|
|||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<button
|
<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"
|
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-lg font-medium hover:bg-gray-300"
|
||||||
>
|
>
|
||||||
← Back to Gallery
|
← Back to Gallery
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export interface RowData {
|
|||||||
// Picture storage and metadata
|
// Picture storage and metadata
|
||||||
export interface PictureBlobInfoType {
|
export interface PictureBlobInfoType {
|
||||||
id: string;
|
id: string;
|
||||||
blob: Blob;
|
|
||||||
url: string;
|
url: string;
|
||||||
downloaded: boolean;
|
downloaded: boolean;
|
||||||
faceDetected: boolean;
|
faceDetected: boolean;
|
||||||
@@ -87,7 +86,7 @@ export const sheetData = writable<RowData[]>([]);
|
|||||||
export const pictures = writable<Record<string, PictureBlobInfoType>>({});
|
export const pictures = writable<Record<string, PictureBlobInfoType>>({});
|
||||||
|
|
||||||
// Store and hold the crop rectangles from face detection
|
// 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
|
// Store and hold the selected sheet
|
||||||
export const selectedSheet = writable<SheetInfoType>({ id: '', name: '', webViewLink: '' });
|
export const selectedSheet = writable<SheetInfoType>({ id: '', name: '', webViewLink: '' });
|
||||||
|
|||||||
Reference in New Issue
Block a user