Google auth done

This commit is contained in:
Roman Krček
2025-07-17 15:40:54 +02:00
parent 9f4b3a3804
commit f83595f3c3
20 changed files with 1956 additions and 4 deletions

208
.github/core-instructions.md vendored Normal file
View File

@@ -0,0 +1,208 @@
# Copilot Instruction Set **GoogleSheet  PhotoPDF** Wizard (SvelteKit)
> **Goal**
> A privacyfirst SvelteKit SPA that guides a user through importing a Google Sheet, mapping its columns, validating/culling rows, reviewing & cropping photos, and finally exporting two A4PDFs (text sheet + photo sheet). No personal data ever leaves the browser after the tab closes.
---
## 0. Tech Stack & Design Constraints
| Area | Decision |
| ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Frontend | **SvelteKit** (latest) SPA mode (`prerender = false`) |
| State | **Svelte Stores** (writable & derived). No persistence beyond sessionStorage/IndexedDB. |
| Styling | **Tailwind CSS** + `@tailwind/typography`, base theme. Page background `bg-gray-50`. Use DaisyUI or shadcnui components if needed. |
| Auth & API | Google Identity Services (popup flow). Scopes: `spreadsheets`, `drive` (read/write). |
| Data storage | • Structured data → **IndexedDB** via `idb` helper. |
| • Image blobs → **Cache Storage** (`caches.open'photos-v1') a | |
| Face detection | **Mediapipe BlazeFace via TF.js (lite)** in a Web Worker. Confidence ≥ 0.8. |
| Virtual list | `svelte-virtual` (reactwindow equivalent). |
| PDF | **pdf-lib** (via ESM import). Embed custom font **Lato** (ttf provided in `/static/fonts`). |
---
## 1. User Stories & Acceptance Criteria
1. **US01 Splash** As a visitor I see a logo + “Start” button (no data loaded). Clicking starts the wizard.
2. **US02 SignIn** I authenticate with Google in a popup and grant the requested scopes. On success, token is cached in memory only.
3. **US03 Sheet Search** I type part of a Sheet name; a fuzzy list of my sheets appears (max 20). I select one.
4. **US04 Column Mapping** Columns AF preview (first 3 data rows). Under each header is a dropdown: Name / Surname / Nationality / Birthday / Picture URL / Ignore. All five fields must be mapped exactly once. Previous mapping is remembered for future sessions in `localStorage` (key: `columnHints`).
5. **US05 Row Validate & Select** I see rows in a virtualized table with validation states. Invalid rows (missing data, age < 18 or > 40) are preflagged. I can tick checkboxes to include/exclude. No inapp editing.
6. **US06 Photo Gallery** Lazy virtual grid of thumbnails. Auto facecrop overlay (dashed rectangle). Pictures with 0 or >1 faces are floated to top & flagged “Need manual crop”. I can drag/resize a fixedratio rectangle to override. Cropped area is stored as `{x,y,w,h}` in store.
7. **US07 Progress** While downloading or processing images / faces, I see a determinate progress bar (0100%). Each stage (download, face detect, PDF gen).
8. **US08 Generate PDFs** I click “Generate”. Two PDFs download:
* `people_data.pdf` textonly; grid fills as many boxes per A4 as fit (auto calculate cols/rows). Each box uses provided coordinates (see §4).
* `people_photos.pdf` same grid but with cropped headshot @ modest DPI, name beneath.
9. **US09 Cleanup** On window/tab unload, all IndexedDB records and CacheStorage entries are cleared.
---
## 2. Component Tree (Svelte)
```
+Layout.svelte (Tailwind container)
├─ Splash.svelte
├─ Wizard.svelte (stateful)
├─ StepAuth.svelte
├─ StepSheetSearch.svelte
├─ StepColumnMap.svelte
├─ StepRowFilter.svelte
├─ StepGallery.svelte
└─ StepGenerate.svelte
```
### Shared Stores (`src/lib/stores.ts`)
```ts
export const session = writable<{
token?: string;
user?: { name: string; email: string };
}>({});
export const sheetData = writable<RowData[]>(); // after mapping
export const pictures = writable<Record<RowID, PictureBlobInfo>>();
export const cropRects = writable<Record<RowID, Crop>>();
```
---
## 3. Data Flow
1. **Auth** ⇒ `session.token` set.
2. **Sheet fetch** (Google Sheets API) ⇒ raw 2D array.
3. **Mapping** ⇒ transform to `RowData` objects.
4. **Validation** ⇒ add `valid` flag; excluded rows removed from downstream.
5. **Download IMG** (Drive/HTTP) ⇒ cache → `pictures` store.
6. **Face Detect** (Worker) ⇒ proposed crop rectangle.
7. **Manual Crop** ⇒ update `cropRects`.
8. **Generate PDFs** ⇒ read `RowData`, `pictures`, `cropRects`.
---
## 4. PDF Layout Spec (mm → points helper)
* **Font**: Lato Regular, 10 pt. Embed once per doc via `pdfDoc.embedFont()`.
* **Grid**: auto compute `colWidth = 70 mm`, `rowHeight = 35 mm` for text; `imageBox = 30 mm × 30 mm` centred.
* **Coordinates (text sheet)** within each cell (0,0 is cell topleft):
* Full Name x 5 mm, y 5 mm
* Nationality x 5 mm, y 12 mm
* Birthday x 5 mm, y 19 mm
* **Coordinates (photo sheet)**
* Image topleft x 5 mm, y 5 mm (square)
* Name text centre below at y (5 + imageBox + 3 mm)
* Fit rows top→bottom, left→right. When page full, add new page.
---
## 5. Key Helpers
### `googleClient.ts`
* `initAuth(): Promise<Token>`
* `listSheets(query): Promise<SheetMeta[]>` (fuzzy)
* `getSheetValues(id, range): Promise<string[][]>`
### `faceWorker.ts` (Web Worker)
* Loads BlazeFace lite model once.
* `detectFaces(arrayBuffer) → {faces: Face[], blobURL}`
### `pdfGenerator.ts`
* `generateTextPDF(rows: RowData[]): Uint8Array`
* `generatePhotoPDF(rows: RowData[], crops: CropMap): Uint8Array`
---
## 6. Progress Bus (simple derived store)
```ts
export const progress = writable<{ stage: string; done: number; total: number }>();
```
Set from download loop and face detect worker.
---
## 7. Error Handling
* Auth popup closed ⇒ toast “Signin cancelled”.
* Sheet fetch 404 ⇒ return to StepSheetSearch with error banner.
* Image download error ⇒ mark row `pictureError = true`, bubble to top.
---
## 8. Folder Structure Skeleton
```
src/
lib/
stores.ts
googleClient.ts
faceWorker.ts
pdfGenerator.ts
routes/
+layout.svelte
index.svelte (Splash)
wizard/
+page.svelte (mount Wizard.svelte)
components/ (each Step*.svelte)
workers/
faceWorker.js
static/
fonts/Lato-Regular.ttf
```
---
## 9. Copilot Prompt Comments (templates)
### Example: `googleClient.ts`
```ts
/**
* Google API wrapper Browseronly, no refresh tokens.
* Prereqs: `<script src="https://accounts.google.com/gsi/client" async defer></script>` in root.
*
* Helper responsibilities:
* 1. Launch popup OAuth (scopes: spreadsheets, drive).
* 2. List up to 20 spreadsheets matching fuzzy query.
* 3. Download a specified range (A:Z) as 2D array.
* 4. Use inmemory token, autorefresh not required.
*/
```
### Example: `StepGallery.svelte`
```svelte
<script lang="ts">
/**
* Renders a virtual grid of downloaded photos.
* Highlights items needing manual crop (no face/multiple faces).
* When user drags the crop rectangle:
* Persist to cropRects store.
* Overlay rectangle turns green (valid).
*/
</script>
```
Copypaste these comment headers into the top of each file to prime GitHub Copilot with context.
---
## 10. Outstanding TODOs (owner = you)
* [ ] Provide exact mm → point conversion util if different spec required.
* [ ] Supply precise image/text box sizes & page margins.
* [ ] Drop in `Lato-Regular.ttf` into `/static/fonts`.
* [ ] Decide on logo / brand in Splash.
* [ ] Accessibility pass once UI stabilises.
---
### End of base instruction set
Feel free to ask for tweaks or deeper code snippets!