From 5796f5d5bc2353ad9471d58467dc0cf0f8064bec Mon Sep 17 00:00:00 2001 From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:20:10 -0400 Subject: [PATCH] book(C2): master-edit mode (canvas binds to book-master:) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Masters modal's "Edit" button (stubbed in C1) now opens the chosen master on the existing per-page canvas. The shared `editing-book-page` body class keeps the CSS that hides the overview / shows the sheet + tool palette; the page-context bar swaps copy and routing based on which surface is active. - New `state.book.editingMasterId` field. Mutually exclusive with `editingEntryId` — opening one clears the other. - `openBookMaster(id)` enters master-edit mode and closes the Masters modal so the canvas is unobstructed. - `closeBookMaster()` clears the field and re-opens the Masters modal — the user lands back where they came from. - Canvas plumbing (currentDrawTarget, isLayerForCurrentMode, templateLayerTargetsForMode, updateLayerTargetOptions) all branch on editingMasterId vs editingEntryId so layers land on the right surface and the layers sidebar shows the right section header ("Master: NAME" vs "Page N"). - The Back-to-Pages bar relabels to "← Back to masters" + hides prev/next chips when editing a master (no chapter walking on masters). The same back button routes to the right close function based on which mode is active. - Esc and the same back button both work from either mode. - updateBodyClasses() toggles `editing-book-page` on EITHER edit field being set, so the CSS rules pick up master mode for free. --- src/index.html | 137 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 99 insertions(+), 38 deletions(-) diff --git a/src/index.html b/src/index.html index 5065f6f..0de60d0 100644 --- a/src/index.html +++ b/src/index.html @@ -8450,9 +8450,9 @@ actions.className = 'bookMasterRowActions'; const editBtn = document.createElement('button'); editBtn.type = 'button'; - editBtn.title = 'Edit master layers (Phase C2)'; + editBtn.title = 'Edit master layers on the canvas'; editBtn.textContent = 'Edit'; - editBtn.disabled = true; // C2 wires this + editBtn.addEventListener('click', () => openBookMaster(m.id)); actions.appendChild(editBtn); if (m.id !== defaultId) { const defBtn = document.createElement('button'); @@ -8753,10 +8753,9 @@ return; } book.editingEntryId = entryId; + book.editingMasterId = null; // only one editor open at a time document.body.classList.add('editing-book-page'); clearLayerSelection?.(); - // Default the editor tool to Select so users land on a familiar - // pointer; the V/A split + tool palette still works as ever. try { setEditorTool('select'); } catch {} syncBookPageEditBar(); syncInputs(); @@ -8771,6 +8770,35 @@ syncInputs(); render(); } + // Master-edit mode: opens a master on the same canvas as the + // per-page editor. Same body class so the CSS that hides + // Pages overview / shows the sheet + tool palette applies; the + // edit-bar label and Back button swap to master-flavored + // copy. Closing returns to the Masters modal. + function openBookMaster(masterId) { + const book = ensureBookState(); + const m = book.masters.find(x => x.id === masterId); + if (!m) return; + book.editingMasterId = masterId; + book.editingEntryId = null; + closeBookMastersModal(); + document.body.classList.add('editing-book-page'); + clearLayerSelection?.(); + try { setEditorTool('select'); } catch {} + syncBookPageEditBar(); + syncInputs(); + render(); + } + function closeBookMaster() { + const book = state.book; + if (book) book.editingMasterId = null; + document.body.classList.remove('editing-book-page'); + clearLayerSelection?.(); + syncBookPageEditBar(); + syncInputs(); + render(); + openBookMastersModal(); + } function gotoBookPageRelative(delta) { const book = state.book; if (!book || !book.editingEntryId) return; @@ -8792,17 +8820,31 @@ const label = document.getElementById('bookPageEditLabel'); const prev = document.getElementById('bookPageEditPrev'); const next = document.getElementById('bookPageEditNext'); + const back = document.getElementById('bookPageEditBack'); if (!bar) return; const book = state.book; - const editingId = book?.editingEntryId; - const editing = !!editingId && state.templateMode === 'book'; - bar.style.display = editing ? '' : 'none'; - if (!editing) return; - const pages = book.pages; - const designed = pages.filter(p => p.type === 'design'); - const idx = designed.findIndex(p => p.id === editingId); + const inBook = state.templateMode === 'book'; + const editingPage = inBook && !!book?.editingEntryId; + const editingMaster = inBook && !!book?.editingMasterId; + bar.style.display = (editingPage || editingMaster) ? '' : 'none'; + if (!editingPage && !editingMaster) return; + if (editingMaster) { + const m = book.masters.find(x => x.id === book.editingMasterId); + if (label) label.textContent = `Master: ${m?.label || 'Untitled'}`; + if (back) back.textContent = '← Back to masters'; + if (prev) prev.style.display = 'none'; + if (next) next.style.display = 'none'; + return; + } + // editingPage path + if (prev) prev.style.display = ''; + if (next) next.style.display = ''; + if (back) back.textContent = '← Back to Pages'; + const designed = book.pages.filter(p => p.type === 'design'); + const idx = designed.findIndex(p => p.id === book.editingEntryId); const total = designed.length; - if (label) label.textContent = `Page ${idx + 1} / ${total}${(pages[idx]?.label || '') ? ' · ' + pages[idx].label : ''}`; + const entry = designed[idx]; + if (label) label.textContent = `Page ${idx + 1} / ${total}${(entry?.label || '') ? ' · ' + entry.label : ''}`; if (prev) prev.disabled = idx <= 0; if (next) next.disabled = idx >= total - 1; } @@ -8930,17 +8972,18 @@ if (mode === 'jewel') return t.startsWith('jewel-'); if (mode === 'cover') return t === 'cover-trim' || t === 'back' || t === 'spine' || t === 'front'; // Book mode targets are scoped to whichever surface the editor - // is bound to. Targets are entry-id qualified - // (`book-page:`, `book-master:`) so multiple - // pages can coexist in `state.layers` without leaking onto each - // other. The active editing entry's id is what we filter on. + // is bound to. Targets are id-qualified (`book-page:` + // for pages, `book-master:` for masters) so multiple + // surfaces can coexist in `state.layers` without leaking onto + // each other. if (mode === 'book') { - const editId = state.book?.editingEntryId; - if (editId) { - return t === `book-page:${editId}` || t === `book-master:${editId}`; + if (state.book?.editingMasterId) { + return t === `book-master:${state.book.editingMasterId}`; } - // No page being edited yet — Pages overview is showing, so no - // canvas layers should render. + if (state.book?.editingEntryId) { + return t === `book-page:${state.book.editingEntryId}`; + } + // Pages overview (no surface open) → no canvas layers render. return false; } return true; @@ -8972,9 +9015,14 @@ { key: 'canvas', label: 'Canvas' } ]; if (state.templateMode === 'book') { - // Book mode: the layers panel scopes to whichever page is - // open in the per-page editor. No page open → no panels - // (Pages overview shows tiles, not layer rows). + // Book mode: the layers panel scopes to whichever surface is + // open in the editor. Pages overview / masters modal show no + // canvas layers, so an empty list is correct there. + const masterId = state.book?.editingMasterId; + if (masterId) { + const m = (state.book?.masters || []).find(x => x.id === masterId); + return [{ key: `book-master:${masterId}`, label: `Master: ${m?.label || 'Untitled'}` }]; + } const editId = state.book?.editingEntryId; if (editId) { const idx = (state.book?.pages || []).findIndex(p => p.id === editId); @@ -10223,7 +10271,7 @@ document.body.classList.toggle('template-deck', state.templateMode === 'deck'); document.body.classList.toggle('template-book', state.templateMode === 'book'); document.body.classList.toggle('editing-book-page', - state.templateMode === 'book' && !!state.book?.editingEntryId); + state.templateMode === 'book' && (!!state.book?.editingEntryId || !!state.book?.editingMasterId)); document.body.classList.toggle('editing-deck-slide', !!state.deck && state.deck.editingIndex !== null); document.body.classList.toggle('hide-guides', !state.showGuides); document.body.classList.toggle('hide-bleed', !state.showBleed); @@ -10696,11 +10744,12 @@ } function currentDrawTarget() { if (state.appMode === 'designer') return 'designer-disc'; - // Book mode: when a specific page is open in the editor, new - // layers land on that page's entry-qualified target so they - // stay scoped to one page across the manifest. - if (state.templateMode === 'book' && state.book?.editingEntryId) { - return `book-page:${state.book.editingEntryId}`; + // Book mode: new layers land on the active editing surface + // (per-page or per-master) so they stay scoped correctly + // across the manifest. + if (state.templateMode === 'book') { + if (state.book?.editingMasterId) return `book-master:${state.book.editingMasterId}`; + if (state.book?.editingEntryId) return `book-page:${state.book.editingEntryId}`; } return primaryTargetForMode(state.templateMode || 'cover'); } @@ -29240,15 +29289,18 @@ select.innerHTML = ``; } else if (state.templateMode === 'book') { // Book mode: the only target a new layer should land on is - // the currently-open page (Phase B). Other surfaces (cover, - // masters) get their own routes in A5/C/D1. If no page is - // open, fall back to a stub so the layers panel doesn't - // appear broken. + // whichever surface is currently open in the editor — a + // specific page or a master. Pages overview / Masters modal + // show no canvas layers, so a stub keeps the select sane. + const masterId = state.book?.editingMasterId; const editId = state.book?.editingEntryId; - if (editId) { + if (masterId) { + const m = (state.book?.masters || []).find(x => x.id === masterId); + select.innerHTML = ``; + } else if (editId) { select.innerHTML = ``; } else { - select.innerHTML = ``; + select.innerHTML = ``; } } else { select.innerHTML = ` @@ -31891,7 +31943,11 @@ on('bookAddTenBtn', 'click', addBookTenPages); on('bookViewTogglePages', 'click', () => setBookSubView('pages')); on('bookViewToggleCover', 'click', () => setBookSubView('cover')); - on('bookPageEditBack', 'click', closeBookPage); + on('bookPageEditBack', 'click', () => { + // Same back-button serves both per-page and per-master modes. + if (state.book?.editingMasterId) closeBookMaster(); + else closeBookPage(); + }); on('bookPageEditPrev', 'click', () => gotoBookPageRelative(-1)); on('bookPageEditNext', 'click', () => gotoBookPageRelative(1)); @@ -32222,7 +32278,12 @@ if (bookDocM && bookDocM.getAttribute('aria-hidden') === 'false') { closeBookDocumentSettings(); return; } const bookMastersM = $('bookMastersModal'); if (bookMastersM && bookMastersM.getAttribute('aria-hidden') === 'false') { closeBookMastersModal(); return; } - // Esc while editing a book page returns to the Pages overview. + // Esc while editing a book surface returns to the right home: + // master-edit → masters modal, page-edit → Pages overview. + if (state.templateMode === 'book' && state.book?.editingMasterId) { + closeBookMaster(); + return; + } if (state.templateMode === 'book' && state.book?.editingEntryId) { closeBookPage(); return;