mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-06 05:30:52 +00:00
book(A2): Template-tab "Book" entry + mode switching
Adds the Book button to the Template tab's layout picker
(alongside Case cover / Disc labels / Slide deck) and wires the
applyLayout('book') path so the user can switch into book mode.
- `mode-book` button + click binding via the existing applyLayout
loop (one-line list addition keeps every layout tab routed
through the same handler).
- `applyLayout('book')` calls `ensureBookState()` to lazy-init the
manifest the first time a user enters the mode.
- New `template-book` body class + CSS gating: the new
`#bookFieldset` sidebar block shows only in book mode, and the
cover / disc / deck / custom-art fieldsets hide in book mode so
the sidebar stays focused on the spec + masters affordances.
- `WORKFLOW_ORDER` learns the book column.
- `sheetW` / `sheetH` fall back to trim + bleed when
`templateMode === 'book'` so the canvas-host placeholder is
sized correctly (it stays blank in this phase; the actual Pages
overview + Cover view come in A4/A5).
- Sidebar fieldset shows a derived spec summary (trim, bleed,
paper thickness, derived spine width, page + master count)
rendered by a new `syncBookSidebarSummary()` hook into
`syncInputs()`. Book-state helpers (`bookPageCount`,
`bookSpineWidthInches`) are pure functions so future phases can
reuse them.
- Two placeholder buttons (Document settings… / Manage masters…)
are stubbed disabled with a hint pointing at the upcoming
phases, so users can see where the controls will land.
This commit is contained in:
1 parent
314b582b0c
commit
7bed2a6a3f
1 file changed
+81
-2
+81
-2
@@ -500,6 +500,20 @@
|
||||
body.template-deck #deckThemeFieldset { display: block; }
|
||||
#deckSlideThemeWrap { display: none; margin-top: 10px; }
|
||||
body.editing-deck-slide #deckSlideThemeWrap { display: block; }
|
||||
/* Book Designer sidebar fieldset shows only in book mode. */
|
||||
#bookFieldset { display: none; }
|
||||
body.template-book #bookFieldset { display: block; }
|
||||
/* Hide cover / disc / artwork panels that aren't relevant in book
|
||||
mode (Phase A2 — book mode shows only its own fieldset + layers
|
||||
+ metadata + save). The Pages overview canvas-host comes in
|
||||
Phase A4; until then the canvas-host remains blank. */
|
||||
body.template-book #coverSizeFieldset,
|
||||
body.template-book #coverArtworkFieldset,
|
||||
body.template-book #discArtworkFieldset,
|
||||
body.template-book #jewelArtworkFieldset,
|
||||
body.template-book #customArtFieldset,
|
||||
body.template-book #deckFieldset,
|
||||
body.template-book #deckThemeFieldset { display: none !important; }
|
||||
#layersFieldset { order: 40; }
|
||||
#designerMetadataFieldset { order: 45; }
|
||||
#exportFieldset { order: 50; }
|
||||
@@ -3013,6 +3027,7 @@
|
||||
<button type="button" class="layoutTab" id="mode-cover" data-template-mode="cover" role="tab">Case cover</button>
|
||||
<button type="button" class="layoutTab" id="mode-disc" data-template-mode="disc" role="tab">Disc labels</button>
|
||||
<button type="button" class="layoutTab" id="mode-deck" data-template-mode="deck" role="tab">Slide deck</button>
|
||||
<button type="button" class="layoutTab" id="mode-book" data-template-mode="book" role="tab">Book</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
@@ -3196,6 +3211,17 @@
|
||||
<p class="hint">Click a slide thumbnail to edit it. Drag thumbnails to reorder.</p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="workflowStep template-only" id="bookFieldset">
|
||||
<legend data-step="2">Book</legend>
|
||||
<p class="hint">A book combines a cover spread (front + spine + back), an ordered list of designed pages, optional reflowable chapters written as NIP-23 markdown, and master pages for shared running heads and page numbers. See <code>docs/BOOK_DESIGNER_FEATURE.md</code> for the full spec.</p>
|
||||
<div id="bookSpecSummary" class="hint" style="margin-top:8px"></div>
|
||||
<div class="buttonRow" style="margin-top:8px">
|
||||
<button type="button" id="bookDocumentSettingsBtn" disabled>Document settings…</button>
|
||||
<button type="button" id="bookManageMastersBtn" disabled>Manage masters…</button>
|
||||
</div>
|
||||
<p class="hint" style="margin-top:8px">Document settings, Pages overview, Cover view, and PDF export ship in subsequent phases. This entry is the Phase A2 skeleton.</p>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="workflowStep template-only" id="deckThemeFieldset">
|
||||
<legend data-step="3">Deck theme</legend>
|
||||
<p class="hint">A deck theme restyles every slide in the thumbnails and Presenter Mode — the slides themselves are never changed.</p>
|
||||
@@ -7810,6 +7836,38 @@
|
||||
if (!state.book) state.book = defaultBookState();
|
||||
return state.book;
|
||||
}
|
||||
// Page count + derived spine width readouts for the sidebar.
|
||||
// Page count is the manifest entry count for Phase A — once reflow
|
||||
// segments land in C.5 this counts physical pages after pagination.
|
||||
function bookPageCount() {
|
||||
return (state.book?.pages || []).length;
|
||||
}
|
||||
function bookSpineWidthInches() {
|
||||
const spec = state.book?.spec;
|
||||
if (!spec) return 0;
|
||||
if (!spec.spineFromPages) return Number(spec.spineWManual) || 0;
|
||||
return bookPageCount() * (Number(spec.paperThickness) || 0);
|
||||
}
|
||||
function syncBookSidebarSummary() {
|
||||
const el = $('bookSpecSummary');
|
||||
if (!el) return;
|
||||
if (state.templateMode !== 'book' || !state.book) {
|
||||
el.textContent = '';
|
||||
return;
|
||||
}
|
||||
const s = state.book.spec;
|
||||
const pageCount = bookPageCount();
|
||||
const spineW = bookSpineWidthInches();
|
||||
const spineNote = s.spineFromPages
|
||||
? `derived from ${pageCount} page${pageCount === 1 ? '' : 's'}`
|
||||
: 'manual';
|
||||
const fmt = (n) => Number(n).toFixed(n % 1 === 0 ? 0 : 3);
|
||||
el.innerHTML = ''
|
||||
+ `<b>${s.bookSize || 'Custom'}</b> · ${fmt(s.trimW)} × ${fmt(s.trimH)} in`
|
||||
+ `<br>Bleed ${fmt(s.bleed)} in · Paper ${fmt(s.paperThickness)} in/page`
|
||||
+ `<br>Spine ${fmt(spineW)} in <span class="hint">(${spineNote})</span>`
|
||||
+ `<br>${pageCount} page${pageCount === 1 ? '' : 's'} · ${(state.book.masters || []).length} master${(state.book.masters || []).length === 1 ? '' : 's'}`;
|
||||
}
|
||||
|
||||
function isLayerForCurrentMode(layer) {
|
||||
const t = String(layer?.target || '');
|
||||
@@ -8857,6 +8915,10 @@
|
||||
if (state.templateMode === 'disc') return discTemplate.sheetW;
|
||||
if (state.templateMode === 'jewel') return jewelTemplate.sheetW;
|
||||
if (isCanvasMode(state.templateMode)) return customArtCanvasInches().wIn;
|
||||
if (state.templateMode === 'book') {
|
||||
const s = state.book?.spec;
|
||||
return (s ? (Number(s.trimW) || 0) + (Number(s.bleed) || 0) * 2 : 6);
|
||||
}
|
||||
return totalTrimW() + state.bleed * 2;
|
||||
}
|
||||
|
||||
@@ -8864,6 +8926,13 @@
|
||||
if (state.templateMode === 'disc') return discTemplate.sheetH;
|
||||
if (state.templateMode === 'jewel') return jewelTemplate.sheetH;
|
||||
if (isCanvasMode(state.templateMode)) return customArtCanvasInches().hIn;
|
||||
if (state.templateMode === 'book') {
|
||||
// Phase A2: the canvas-host renders blank in book mode (no
|
||||
// active page surface yet). Use trim + bleed so the host has
|
||||
// sensible dimensions for the placeholder.
|
||||
const s = state.book?.spec;
|
||||
return (s ? (Number(s.trimH) || 0) + (Number(s.bleed) || 0) * 2 : 9);
|
||||
}
|
||||
return state.trimH + state.bleed * 2;
|
||||
}
|
||||
|
||||
@@ -8925,12 +8994,13 @@
|
||||
cover: !isDesignerApp && state.templateMode === 'cover',
|
||||
disc: !isDesignerApp && state.templateMode === 'disc',
|
||||
deck: !isDesignerApp && state.templateMode === 'deck',
|
||||
book: !isDesignerApp && state.templateMode === 'book',
|
||||
customart: !isDesignerApp && state.templateMode === 'customart',
|
||||
slide: !isDesignerApp && state.templateMode === 'slide',
|
||||
pixelart: !isDesignerApp && state.templateMode === 'pixelart',
|
||||
discdesign: isDesignerApp
|
||||
};
|
||||
for (const m of ['cover', 'disc', 'deck', 'customart', 'slide', 'pixelart', 'discdesign']) {
|
||||
for (const m of ['cover', 'disc', 'deck', 'book', 'customart', 'slide', 'pixelart', 'discdesign']) {
|
||||
const button = $(`mode-${m}`);
|
||||
if (!button) continue;
|
||||
button.classList.toggle('active', !!layoutActive[m]);
|
||||
@@ -9050,6 +9120,7 @@
|
||||
customart: ['designerLayoutFieldset', 'customArtFieldset', 'layersFieldset', 'metadataFieldset', 'exportFieldset', 'printSaveFieldset'],
|
||||
slide: ['designerLayoutFieldset', 'customArtFieldset', 'layersFieldset', 'metadataFieldset', 'exportFieldset', 'printSaveFieldset'],
|
||||
deck: ['layoutFieldset', 'deckFieldset', 'deckThemeFieldset', 'metadataFieldset', 'printSaveFieldset'],
|
||||
book: ['layoutFieldset', 'bookFieldset', 'metadataFieldset', 'printSaveFieldset'],
|
||||
pixelart: ['designerLayoutFieldset', 'metadataFieldset', 'printSaveFieldset'],
|
||||
designer: ['designerLayoutFieldset', 'designerFieldset', 'layersFieldset', 'designerMetadataFieldset', 'designerPrintSaveFieldset']
|
||||
};
|
||||
@@ -9078,6 +9149,7 @@
|
||||
document.body.classList.toggle('template-pixelart', state.templateMode === 'pixelart');
|
||||
document.body.classList.toggle('template-slide', state.templateMode === 'slide');
|
||||
document.body.classList.toggle('template-deck', state.templateMode === 'deck');
|
||||
document.body.classList.toggle('template-book', state.templateMode === 'book');
|
||||
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);
|
||||
@@ -9089,6 +9161,7 @@
|
||||
document.body.classList.toggle('app-template', state.appMode !== 'designer');
|
||||
updateLayerTargetOptions();
|
||||
renumberWorkflowSteps();
|
||||
syncBookSidebarSummary();
|
||||
}
|
||||
|
||||
function renderDimensions() {
|
||||
@@ -28588,6 +28661,12 @@
|
||||
state.deck = state.deck || { theme: { fontFamily: '', background: '' }, slides: [], editingIndex: null };
|
||||
state.deck.editingIndex = null;
|
||||
}
|
||||
if (layout === 'book') {
|
||||
// Lazy-init the book block when first entering Book mode. The
|
||||
// factory lives next to the other state defaults so the shape
|
||||
// matches docs/BOOK_DESIGNER_FEATURE.md.
|
||||
ensureBookState();
|
||||
}
|
||||
if (isCanvasMode(layout) || layout === 'pixelart') state._lastDesignerLayout = layout;
|
||||
else state._lastTemplateLayout = layout;
|
||||
syncInputs();
|
||||
@@ -28839,7 +28918,7 @@
|
||||
function attachEvents() {
|
||||
// Layout tabs: cover/disc live on the Template tab; customart/discdesign
|
||||
// on the Designer tab. applyLayout() sorts out the appMode crossing.
|
||||
for (const layout of ['cover', 'disc', 'deck', 'customart', 'slide', 'pixelart', 'discdesign']) {
|
||||
for (const layout of ['cover', 'disc', 'deck', 'book', 'customart', 'slide', 'pixelart', 'discdesign']) {
|
||||
const btn = $(`mode-${layout}`);
|
||||
if (btn) btn.addEventListener('click', () => applyLayout(layout));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user