mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-10-31 10:40:39 +00:00
Optional background image, disc-image placement fixes, browser polish
- Publishing no longer requires a background image: cover/jewel/disc designs publish with layers-only content. "Save design (JSON)" works for layer-only disc designs (removed a silent return + a payload throw). - Disc background image now lands correctly: renderDesignerDisc() runs after the designer view is laid out, and the publish/feed preview uses the same cover-pan math as the editor instead of a naive translate(%). - Publish-modal preview no longer squishes the disc vertically (dropped axis-independent max-width/height clamps; fitMiniPreview scales it). - Template browser: "Fork off!" button renamed to "Use/Fork". Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
159f60385a
commit
2298512dce
1 file changed
+44
-14
+44
-14
@@ -1657,8 +1657,7 @@
|
||||
.toast.toastOut { opacity:0; transition:opacity .2s; }
|
||||
@keyframes toastIn { from { transform:translateY(8px); opacity:0; } to { transform:translateY(0); opacity:1; } }
|
||||
|
||||
.publishConfirmPreview { background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:10px; margin:0 0 12px; display:flex; justify-content:center; align-items:center; max-height:280px; overflow:hidden; }
|
||||
.publishConfirmPreview > * { max-width:100%; max-height:260px; }
|
||||
.publishConfirmPreview { background:var(--bg); border:1px solid var(--line); border-radius:8px; padding:10px; margin:0 0 12px; display:flex; justify-content:center; align-items:center; height:280px; overflow:hidden; }
|
||||
.publishConfirmMeta { margin:0 0 12px; font-size:.82rem; }
|
||||
.publishConfirmMetaRow { display:flex; gap:10px; padding:5px 0; border-bottom:1px solid var(--line); }
|
||||
.publishConfirmMetaRow:last-child { border-bottom:0; }
|
||||
@@ -4014,7 +4013,6 @@
|
||||
function discDesignPayload() {
|
||||
persistCurrentDiscDesign();
|
||||
const design = state.discDesign;
|
||||
if (!design?.src) throw new Error('Load an image URL before publishing this disc design.');
|
||||
const layers = currentDesignerLayers();
|
||||
return {
|
||||
version: SCHEMA_VERSION,
|
||||
@@ -6090,10 +6088,12 @@
|
||||
renderJewelSlots();
|
||||
renderCombined();
|
||||
renderLayers();
|
||||
renderDesignerDisc();
|
||||
syncLayerInputs();
|
||||
syncDesignerInputs();
|
||||
updateBodyClasses();
|
||||
// After updateBodyClasses so the designer view is displayed/laid out —
|
||||
// the disc image's cover-pan math needs the wrapper's real size.
|
||||
renderDesignerDisc();
|
||||
refreshPenOverlayGeometry();
|
||||
refreshPenOverlayContent();
|
||||
if (_editorTool !== 'select') syncToolOptionInputs();
|
||||
@@ -8960,8 +8960,8 @@
|
||||
} else if (state.templateMode === 'jewel') {
|
||||
payload = jewelTemplatePayload();
|
||||
typeTag = 'casewrap-jewel';
|
||||
if (!previewFromPayload(payload, 'jewel')) {
|
||||
throw new Error('Load jewel insert artwork before publishing.');
|
||||
if (!previewFromPayload(payload, 'jewel') && !(payload.layers || []).length) {
|
||||
throw new Error('Add jewel insert artwork or at least one layer before publishing.');
|
||||
}
|
||||
} else if (state.templateMode === 'customart') {
|
||||
payload = customArtPayload();
|
||||
@@ -8974,8 +8974,8 @@
|
||||
} else {
|
||||
payload = coverTemplatePayload();
|
||||
typeTag = 'casewrap-cover';
|
||||
if (!previewFromPayload(payload, 'cover')) {
|
||||
throw new Error('Load cover artwork before publishing.');
|
||||
if (!previewFromPayload(payload, 'cover') && !(payload.layers || []).length) {
|
||||
throw new Error('Add cover artwork or at least one layer before publishing.');
|
||||
}
|
||||
}
|
||||
const mode = normalizeTemplateMode(payload);
|
||||
@@ -8999,11 +8999,13 @@
|
||||
|
||||
async function publishDiscDesignToNostr() {
|
||||
const design = state.discDesign;
|
||||
if (!design?.src) throw new Error('Load an image URL before publishing this disc design.');
|
||||
if (!state.meta.category?.trim()) {
|
||||
throw new Error('Select a category in Project Metadata before publishing.');
|
||||
}
|
||||
const payload = discDesignPayload();
|
||||
if (!design?.src && !(payload.layers || []).length) {
|
||||
throw new Error('Load an image or add at least one layer before publishing this disc design.');
|
||||
}
|
||||
const designName = (design.name || '').trim() || state.meta.title;
|
||||
if (!designName?.trim()) {
|
||||
throw new Error('Add a title in Project Metadata or a custom design name before publishing.');
|
||||
@@ -10709,7 +10711,6 @@
|
||||
if (!title) { toastError('Please enter a title in Project Metadata before saving.'); return; }
|
||||
if (!category) { toastError('Please select a category in Project Metadata before saving.'); return; }
|
||||
const design = state.discDesign;
|
||||
if (!design?.src) return;
|
||||
const payload = discDesignPayload();
|
||||
const name = (design.name || 'disc_design')
|
||||
.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
||||
@@ -13260,7 +13261,7 @@
|
||||
prevBtn.type = 'button'; prevBtn.textContent = 'Preview';
|
||||
prevBtn.addEventListener('click', e => { e.stopPropagation(); openDesignPreview(row); });
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button'; btn.textContent = 'Fork off!';
|
||||
btn.type = 'button'; btn.textContent = 'Use/Fork';
|
||||
btn.title = 'Fork this design — opens it in the editor so you can save your own version';
|
||||
btn.addEventListener('click', e => { e.stopPropagation(); onUse(); });
|
||||
const tipBtn = makeFeedCardTipButton(row);
|
||||
@@ -13803,7 +13804,7 @@
|
||||
prevBtn.addEventListener('click', e => { e.stopPropagation(); openDesignPreview(row); });
|
||||
const useBtn = document.createElement('button');
|
||||
useBtn.type = 'button';
|
||||
useBtn.textContent = 'Fork off!';
|
||||
useBtn.textContent = 'Use/Fork';
|
||||
useBtn.title = 'Fork this design — opens it in the editor so you can save your own version';
|
||||
useBtn.addEventListener('click', e => { e.stopPropagation(); onUse(); });
|
||||
actions.append(makeReactionBtn(row), prevBtn, useBtn);
|
||||
@@ -13957,7 +13958,7 @@
|
||||
|
||||
const forkBtn = document.createElement('button');
|
||||
forkBtn.type = 'button';
|
||||
forkBtn.textContent = 'Fork off!';
|
||||
forkBtn.textContent = 'Use/Fork';
|
||||
forkBtn.title = 'Fork this design — opens it in the editor so you can save your own version';
|
||||
forkBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
@@ -14177,8 +14178,37 @@
|
||||
if (disc.src) {
|
||||
const img = document.createElement('img');
|
||||
img.src = disc.src;
|
||||
img.style.cssText = `position:absolute;left:50%;top:50%;width:100%;height:100%;object-fit:${disc.fit || 'cover'};transform:translate(-50%,-50%) translate(${disc.x||0}%,${disc.y||0}%) rotate(${disc.rotate||0}deg) scale(${disc.zoom||1});z-index:1;`;
|
||||
img.alt = '';
|
||||
img.style.cssText = 'position:absolute;left:50%;top:50%;width:100%;height:100%;object-fit:fill;transform-origin:center center;user-select:none;pointer-events:none;z-index:1;';
|
||||
discWrap.appendChild(img);
|
||||
// Match the editor's cover-pan math (applyRotationCover) so the
|
||||
// background image sits exactly where the user placed it — the old
|
||||
// translate(x%) model panned by a different, much larger range.
|
||||
const updateDiscArt = () => {
|
||||
const fit = disc.fit || 'cover';
|
||||
const cW = discWrap.clientWidth, cH = discWrap.clientHeight;
|
||||
if (!cW || !cH) return;
|
||||
const nW = img.naturalWidth || cW, nH = img.naturalHeight || cH;
|
||||
let baseW = cW, baseH = cH;
|
||||
if (fit === 'none') { baseW = nW; baseH = nH; }
|
||||
else if (fit !== 'fill') {
|
||||
const fs = fit === 'contain' ? Math.min(cW / nW, cH / nH) : Math.max(cW / nW, cH / nH);
|
||||
baseW = nW * fs; baseH = nH * fs;
|
||||
}
|
||||
const rotate = Number(disc.rotate) || 0, zoom = Number(disc.zoom) || 1;
|
||||
const rad = rotate * Math.PI / 180, ac = Math.abs(Math.cos(rad)), as = Math.abs(Math.sin(rad));
|
||||
const rotatedW = baseW * ac + baseH * as, rotatedH = baseW * as + baseH * ac;
|
||||
const coverScale = fit === 'cover'
|
||||
? Math.max(cW / Math.max(rotatedW, 1), cH / Math.max(rotatedH, 1), 1) : 1;
|
||||
const finalScale = coverScale * zoom;
|
||||
const xPx = ((Number(disc.x) || 0) / 100) * Math.max(0, (rotatedW * finalScale - cW) / 2);
|
||||
const yPx = ((Number(disc.y) || 0) / 100) * Math.max(0, (rotatedH * finalScale - cH) / 2);
|
||||
img.style.width = `${baseW}px`;
|
||||
img.style.height = `${baseH}px`;
|
||||
img.style.transform = `translate(-50%,-50%) translate(${xPx}px,${yPx}px) rotate(${rotate}deg) scale(${finalScale})`;
|
||||
};
|
||||
if (img.complete) requestAnimationFrame(updateDiscArt);
|
||||
img.addEventListener('load', updateDiscArt, { once: true });
|
||||
}
|
||||
|
||||
layers.forEach((layer, i) => {
|
||||
|
||||
Reference in new issue
Block a user