Spaces:
Sleeping
Sleeping
| import type { JobsheetSection, Page } from "../types/session"; | |
| export type FlatPage = { | |
| sectionId: string; | |
| sectionIndex: number; | |
| sectionTitle?: string; | |
| pageIndex: number; | |
| page: Page; | |
| flatIndex: number; | |
| }; | |
| const MAX_PHOTOS_PRIMARY_PAGE = 2; | |
| const MAX_PHOTOS_CONTINUATION_PAGE = 6; | |
| function cloneTemplate(template?: Page["template"]): Page["template"] { | |
| if (!template) return undefined; | |
| return { ...template }; | |
| } | |
| function normalizePhotoIds(photoIds?: string[]): string[] { | |
| if (!Array.isArray(photoIds)) return []; | |
| return photoIds.filter((value) => Boolean(value)); | |
| } | |
| function buildPhotoContinuation(source: Page, photoIds: string[]): Page { | |
| return { | |
| items: [], | |
| template: cloneTemplate(source.template), | |
| photo_ids: photoIds, | |
| photo_layout: source.photo_layout, | |
| photo_order_locked: source.photo_order_locked, | |
| variant: "photos", | |
| }; | |
| } | |
| function splitPagePhotos(page: Page): Page[] { | |
| const items = Array.isArray(page.items) ? page.items : []; | |
| const normalized: Page = { ...page, items }; | |
| if (normalized.blank) { | |
| return [normalized]; | |
| } | |
| const photoIds = normalizePhotoIds(normalized.photo_ids); | |
| if (!photoIds.length) { | |
| return [normalized]; | |
| } | |
| if (normalized.variant === "photos") { | |
| const chunks: string[][] = []; | |
| for (let i = 0; i < photoIds.length; i += MAX_PHOTOS_CONTINUATION_PAGE) { | |
| chunks.push(photoIds.slice(i, i + MAX_PHOTOS_CONTINUATION_PAGE)); | |
| } | |
| if (chunks.length <= 1) { | |
| return [ | |
| { | |
| ...normalized, | |
| photo_ids: chunks[0] ?? [], | |
| variant: "photos", | |
| }, | |
| ]; | |
| } | |
| return chunks.map((chunk, idx) => { | |
| if (idx === 0) return { ...normalized, photo_ids: chunk, variant: "photos" }; | |
| return buildPhotoContinuation(normalized, chunk); | |
| }); | |
| } | |
| const baseChunk = photoIds.slice(0, MAX_PHOTOS_PRIMARY_PAGE); | |
| const extraIds = photoIds.slice(MAX_PHOTOS_PRIMARY_PAGE); | |
| const continuationChunks: string[][] = []; | |
| for (let i = 0; i < extraIds.length; i += MAX_PHOTOS_CONTINUATION_PAGE) { | |
| continuationChunks.push(extraIds.slice(i, i + MAX_PHOTOS_CONTINUATION_PAGE)); | |
| } | |
| const basePage: Page = { | |
| ...normalized, | |
| photo_ids: baseChunk, | |
| variant: normalized.variant ?? "full", | |
| }; | |
| if (!continuationChunks.length) { | |
| return [basePage]; | |
| } | |
| const extraPages = continuationChunks.map((chunk) => | |
| buildPhotoContinuation(normalized, chunk), | |
| ); | |
| return [basePage, ...extraPages]; | |
| } | |
| export function ensureSections(sections?: JobsheetSection[]): JobsheetSection[] { | |
| if (sections && sections.length) { | |
| return sections.map((section, index) => { | |
| const basePages = section.pages?.length ? section.pages : [{ items: [] }]; | |
| const normalizedPages = basePages.flatMap((page) => splitPagePhotos(page)); | |
| return { | |
| id: section.id, | |
| title: section.title ?? "Section", | |
| pages: normalizedPages.length ? normalizedPages : [{ items: [] }], | |
| }; | |
| }); | |
| } | |
| return [ | |
| { | |
| id: crypto.randomUUID(), | |
| title: "Section 1", | |
| pages: [{ items: [] }], | |
| }, | |
| ]; | |
| } | |
| export function flattenSections(sections: JobsheetSection[]): FlatPage[] { | |
| const result: FlatPage[] = []; | |
| sections.forEach((section, sectionIndex) => { | |
| const pages = section.pages ?? []; | |
| pages.forEach((page, pageIndex) => { | |
| result.push({ | |
| sectionId: section.id, | |
| sectionIndex, | |
| sectionTitle: section.title ?? `Section ${sectionIndex + 1}`, | |
| pageIndex, | |
| page, | |
| flatIndex: result.length, | |
| }); | |
| }); | |
| }); | |
| return result; | |
| } | |
| export function replacePage( | |
| sections: JobsheetSection[], | |
| sectionIndex: number, | |
| pageIndex: number, | |
| nextPage: Page, | |
| ): JobsheetSection[] { | |
| const next = sections.map((section, sIdx) => { | |
| if (sIdx !== sectionIndex) return section; | |
| const nextPages = [...(section.pages ?? [])]; | |
| nextPages[pageIndex] = nextPage; | |
| return { ...section, pages: nextPages }; | |
| }); | |
| return ensureSections(next); | |
| } | |
| export function insertPage( | |
| sections: JobsheetSection[], | |
| sectionIndex: number, | |
| pageIndex: number, | |
| page: Page, | |
| ): JobsheetSection[] { | |
| const next = sections.map((section, sIdx) => { | |
| if (sIdx !== sectionIndex) return section; | |
| const nextPages = [...(section.pages ?? [])]; | |
| const safeIndex = Math.max(0, Math.min(pageIndex, nextPages.length)); | |
| nextPages.splice(safeIndex, 0, page); | |
| return { ...section, pages: nextPages }; | |
| }); | |
| return ensureSections(next); | |
| } | |
| export function removePage( | |
| sections: JobsheetSection[], | |
| sectionIndex: number, | |
| pageIndex: number, | |
| ): JobsheetSection[] { | |
| const next = sections.map((section, sIdx) => { | |
| if (sIdx !== sectionIndex) return section; | |
| const nextPages = [...(section.pages ?? [])]; | |
| if (nextPages.length <= 1) return section; | |
| nextPages.splice(pageIndex, 1); | |
| return { ...section, pages: nextPages.length ? nextPages : [{ items: [] }] }; | |
| }); | |
| return ensureSections(next); | |
| } | |