mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-11-06 13:40:39 +00:00
book(C3): master-layer underlay on page editor + composited thumbnails
Pages inheriting a master now render its layers *under* their own, in both the per-page editor and the Pages-overview tile thumbnails. - New `effectiveMasterIdForPage(entryOrId)` resolves the page's master: its own `masterId` override → book spec's `defaultMasterId` → null. Setting `entry.masterId = null` explicitly means "no master on this page" (overrides default). - `isLayerForCurrentMode` in page-edit mode now also accepts the page's effective-master target so those layers join the render. - `renderLayers` uses an effective-z sort key that subtracts 1e6 from master-layer z values while in page-edit mode — guarantees master layers DOM-precede page layers regardless of authored z, so masters always sit beneath the page artwork. - New body class `editing-book-master` (in addition to the shared `editing-book-page`) lets CSS tell page-edit from master-edit. CSS rule fades + click-throughs master layers only in page-edit; master-edit mode keeps them fully interactive (you're editing the master itself). - Free-layer wrappers now carry `data-target` so the CSS selector `[data-target^="book-master:"]` works without needing the renderer to know about book mode. - Pages-overview tile thumbnails composite the master layers beneath the page's own (z forced to -1e6 + i for stable order) so each tile shows the same composition the editor renders. Phase C3 ships render-side composition; the per-page "Apply master…" picker (entry.masterId override UI) is queued for the next slice.
This commit is contained in:
1 parent
5796f5d5bc
commit
f28f0d92e8
1 file changed
+68
-7
+68
-7
@@ -1581,6 +1581,15 @@
|
||||
position: absolute; top: 6px; left: 8px;
|
||||
font-size: .72rem; color: var(--muted); font-weight: 600;
|
||||
}
|
||||
/* Master layers shown as an underlay while editing a page —
|
||||
click-through and slightly faded so the page layers above
|
||||
are obviously the editable ones. Master-edit mode (the
|
||||
master IS the editing target) keeps them opaque +
|
||||
interactive via the :not(.editing-book-master) guard. */
|
||||
body.editing-book-page:not(.editing-book-master) .freeLayer[data-target^="book-master:"] {
|
||||
pointer-events: none;
|
||||
opacity: 0.55;
|
||||
}
|
||||
.bookMasterRow {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 8px 10px; border: 1px solid var(--line);
|
||||
@@ -8336,6 +8345,19 @@
|
||||
render();
|
||||
}
|
||||
|
||||
// Resolve a page's effective master — its own `masterId`
|
||||
// override, falling back to the book spec's default master.
|
||||
function effectiveMasterIdForPage(entryOrId) {
|
||||
const book = state.book;
|
||||
if (!book) return null;
|
||||
const entry = (typeof entryOrId === 'string')
|
||||
? book.pages.find(p => p.id === entryOrId)
|
||||
: entryOrId;
|
||||
if (!entry) return book.spec?.defaultMasterId || null;
|
||||
if (entry.masterId === null) return null; // explicit "no master"
|
||||
return entry.masterId || book.spec?.defaultMasterId || null;
|
||||
}
|
||||
|
||||
// Stable opaque id for masters. Same shape as page entry ids
|
||||
// so id collisions across pages and masters stay impossible.
|
||||
function makeBookMasterId() {
|
||||
@@ -8627,9 +8649,20 @@
|
||||
thumb.className = 'bookPageTileThumb';
|
||||
tile.appendChild(thumb);
|
||||
const pageTarget = `book-page:${p.id}`;
|
||||
const pageLayers = (state.layers || [])
|
||||
.filter(l => l.target === pageTarget && !l.hidden)
|
||||
.map(l => ({ ...l, target: 'canvas' })); // remap for preview helper
|
||||
const masterId = effectiveMasterIdForPage(p);
|
||||
const masterTarget = masterId ? `book-master:${masterId}` : null;
|
||||
// Composite master layers first (so they render below
|
||||
// the page's own layers) then the page's own. Both get
|
||||
// their target remapped to 'canvas' so the preview
|
||||
// helper's flat-canvas filter accepts them.
|
||||
const masterLayers = masterTarget
|
||||
? (state.layers || []).filter(l => l.target === masterTarget && !l.hidden)
|
||||
: [];
|
||||
const pageLayers = (state.layers || []).filter(l => l.target === pageTarget && !l.hidden);
|
||||
const composed = [
|
||||
...masterLayers.map((l, i) => ({ ...l, target: 'canvas', z: -1e6 + i })),
|
||||
...pageLayers.map(l => ({ ...l, target: 'canvas' })),
|
||||
];
|
||||
try {
|
||||
renderCustomArtPreviewDOM(thumb, {
|
||||
customArt: {
|
||||
@@ -8637,7 +8670,7 @@
|
||||
height: trimH * 96,
|
||||
background: p.background || book.spec?.background || '#ffffff',
|
||||
},
|
||||
layers: pageLayers,
|
||||
layers: composed,
|
||||
}, { compact: true });
|
||||
} catch (err) {
|
||||
console.warn('book page thumb render failed', err);
|
||||
@@ -8975,13 +9008,18 @@
|
||||
// is bound to. Targets are id-qualified (`book-page:<entryId>`
|
||||
// for pages, `book-master:<masterId>` for masters) so multiple
|
||||
// surfaces can coexist in `state.layers` without leaking onto
|
||||
// each other.
|
||||
// each other. Page-edit mode also pulls in the page's
|
||||
// effective master so its layers render underneath (the
|
||||
// renderer marks them non-interactive via CSS).
|
||||
if (mode === 'book') {
|
||||
if (state.book?.editingMasterId) {
|
||||
return t === `book-master:${state.book.editingMasterId}`;
|
||||
}
|
||||
if (state.book?.editingEntryId) {
|
||||
return t === `book-page:${state.book.editingEntryId}`;
|
||||
if (t === `book-page:${state.book.editingEntryId}`) return true;
|
||||
const masterId = effectiveMasterIdForPage(state.book.editingEntryId);
|
||||
if (masterId && t === `book-master:${masterId}`) return true;
|
||||
return false;
|
||||
}
|
||||
// Pages overview (no surface open) → no canvas layers render.
|
||||
return false;
|
||||
@@ -10270,8 +10308,16 @@
|
||||
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');
|
||||
// 'editing-book-page' is the shared "canvas is open in book
|
||||
// mode" class — covers both page-edit and master-edit so the
|
||||
// sheet + tool palette unhide. The more specific
|
||||
// 'editing-book-master' class lets CSS target master-edit
|
||||
// alone (e.g. don't fade master layers when *they're* the
|
||||
// editing target).
|
||||
document.body.classList.toggle('editing-book-page',
|
||||
state.templateMode === 'book' && (!!state.book?.editingEntryId || !!state.book?.editingMasterId));
|
||||
document.body.classList.toggle('editing-book-master',
|
||||
state.templateMode === 'book' && !!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);
|
||||
@@ -18122,7 +18168,18 @@
|
||||
const designerOverlay = $('designerLayerOverlay');
|
||||
overlay.innerHTML = '';
|
||||
if (designerOverlay) designerOverlay.innerHTML = '';
|
||||
const ordered = [...state.layers].sort((a, b) => (a.z || 0) - (b.z || 0));
|
||||
// In book page-edit mode, master layers render *under* the
|
||||
// page's own layers regardless of their z values. We offset
|
||||
// their effective sort key by a large negative number so they
|
||||
// always come first in DOM order (which equals lowest visual
|
||||
// stack).
|
||||
const _bookPageEdit = state.templateMode === 'book' && !!state.book?.editingEntryId;
|
||||
const effectiveZ = (layer) => {
|
||||
const z = Number(layer.z) || 0;
|
||||
if (_bookPageEdit && String(layer.target || '').startsWith('book-master:')) return z - 1e6;
|
||||
return z;
|
||||
};
|
||||
const ordered = [...state.layers].sort((a, b) => effectiveZ(a) - effectiveZ(b));
|
||||
|
||||
for (const layer of ordered) {
|
||||
const isDesignerLayer = layer.target === 'designer-disc';
|
||||
@@ -18139,6 +18196,10 @@
|
||||
const el = document.createElement('div');
|
||||
el.className = `freeLayer${layerSelected ? ' selected' : ''}${multiSelected ? ' multiSelected' : ''}`;
|
||||
el.dataset.layerId = layer.id;
|
||||
// Surface the layer's target on the wrapper so CSS can
|
||||
// style master underlays (.freeLayer[data-target^="book-master:"])
|
||||
// without needing the renderer to know about book mode.
|
||||
if (layer.target) el.dataset.target = layer.target;
|
||||
if (layer.clip && layer.clip.kind !== 'none' && resolveClipSourceShape(layer, state.layers)) {
|
||||
el.style.clipPath = `url(#${clipIdForLayer(layer)})`;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user