mirror of
https://github.com/PR0M3TH3AN/Artstr-Studio.git
synced 2026-10-27 08:40:39 +00:00
Add editable design object layers
This commit is contained in:
1 parent
6fc0fc8a98
commit
5e2f98ecd7
2 files changed
+346
-8
No files matched your search
@@ -5,11 +5,6 @@ Running list of deferred work. Detailed specs for the larger items live in
|
||||
|
||||
## Adam's working list
|
||||
|
||||
- **Smart-object design imports.** Add a way to import another design from
|
||||
Nostr or from a saved file into any current design as an editable layer /
|
||||
element, similar to Photoshop Smart Objects. Existing related pattern: the
|
||||
two-disc print layout already has per-disc "Load from file" and "Search
|
||||
Nostr" import flows for Disc 1 / Disc 2 images.
|
||||
- **Layer groups and multi-select transforms.** Add layer groups, group
|
||||
naming, selecting multiple layers / objects at once, and moving / scaling
|
||||
grouped or multi-selected objects together.
|
||||
|
||||
+346
-3
@@ -2089,6 +2089,11 @@
|
||||
.editingBannerText { flex:1; min-width:0; }
|
||||
.editingBannerText strong { font-weight:700; }
|
||||
#editingBannerCancel { font-size:.78rem; padding:4px 10px; flex-shrink:0; }
|
||||
#designObjectBanner { background:rgba(96,165,250,.13); border-bottom-color:rgba(96,165,250,.45); color:#1d4ed8; }
|
||||
body.dark #designObjectBanner { background:rgba(96,165,250,.12); color:#93c5fd; }
|
||||
#designObjectBanner button { font-size:.78rem; padding:4px 10px; flex-shrink:0; }
|
||||
.designObjectFrame { position:absolute; inset:0; overflow:hidden; background:#fff; pointer-events:none; }
|
||||
.designObjectFrame::after { content:''; position:absolute; inset:0; border:1px solid rgba(15,23,42,.12); pointer-events:none; }
|
||||
|
||||
.tipAmounts { display:grid; grid-template-columns:repeat(auto-fit,minmax(72px,1fr)); gap:8px; margin:0 0 14px; }
|
||||
.tipAmounts button { padding:10px 6px; font-size:.9rem; font-weight:600; cursor:pointer; border:1px solid var(--line); background:var(--bg); color:var(--ink); border-radius:8px; }
|
||||
@@ -2405,6 +2410,7 @@
|
||||
<button type="button" id="browseCommunityBtn">Browse Templates</button>
|
||||
<button type="button" id="loadProjectBtn">Load from file</button>
|
||||
<input id="projectFile" type="file" accept="application/json,.json" hidden />
|
||||
<input id="designLayerFile" type="file" accept="application/json,.json" hidden />
|
||||
</div>
|
||||
|
||||
<div class="quickActions designer-only" aria-label="Designer actions">
|
||||
@@ -2885,6 +2891,14 @@
|
||||
<p class="hint" style="font-size:.72rem;margin:4px 0 0">Opens the pixel editor. Double-clicking the layer on the canvas works too.</p>
|
||||
</div>
|
||||
|
||||
<div id="designLayerControls" style="display:none">
|
||||
<div class="buttonRow">
|
||||
<button type="button" id="editDesignLayerBtn">Edit embedded design</button>
|
||||
<button type="button" id="replaceDesignLayerBtn">Replace from file</button>
|
||||
</div>
|
||||
<p class="hint" style="font-size:.72rem;margin:4px 0 0">Design objects keep the imported design editable inside this layer. Double-clicking the layer opens it too.</p>
|
||||
</div>
|
||||
|
||||
<div id="qrLayerControls" style="display:none">
|
||||
<label for="qrData">QR data (URL or text)</label>
|
||||
<textarea id="qrData" rows="2" placeholder="https://example.com" style="resize:vertical;width:100%"></textarea>
|
||||
@@ -3162,6 +3176,13 @@
|
||||
</span>
|
||||
<button type="button" id="editingBannerCancel">Cancel edit</button>
|
||||
</div>
|
||||
<div id="designObjectBanner" class="editingBanner" style="display:none">
|
||||
<span class="editingBannerText">
|
||||
Editing embedded design object: <strong id="designObjectTitle"></strong>
|
||||
</span>
|
||||
<button type="button" id="designObjectDoneBtn" class="primary">Done editing object</button>
|
||||
<button type="button" id="designObjectCancelBtn">Cancel</button>
|
||||
</div>
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<h2 id="projectTitle">Cover preview</h2>
|
||||
@@ -7540,6 +7561,281 @@
|
||||
});
|
||||
}
|
||||
|
||||
let _designObjectEdit = null;
|
||||
|
||||
function clonePayload(value) {
|
||||
return JSON.parse(JSON.stringify(value || {}));
|
||||
}
|
||||
|
||||
function isDesignPayload(payload) {
|
||||
if (!payload || typeof payload !== 'object') return false;
|
||||
return Boolean(
|
||||
payload.templateMode ||
|
||||
payload.coverTemplate ||
|
||||
payload.discTemplate ||
|
||||
payload.jewelTemplate ||
|
||||
payload.customArt ||
|
||||
payload.slide ||
|
||||
payload.deck ||
|
||||
payload.pixelArt ||
|
||||
payload.discDesign
|
||||
);
|
||||
}
|
||||
|
||||
function designObjectTitle(payload) {
|
||||
const labels = {
|
||||
cover: 'Case Cover',
|
||||
disc: 'Two-Disc Sheet',
|
||||
jewel: 'Jewel Case',
|
||||
customart: 'Custom Art',
|
||||
slide: 'Slide',
|
||||
deck: 'Slide Deck',
|
||||
pixelart: 'Pixel Art',
|
||||
'disc-design': 'Disc Design'
|
||||
};
|
||||
return payload?.meta?.title || labels[normalizeTemplateMode(payload)] || 'Design object';
|
||||
}
|
||||
|
||||
function designObjectAspect(payload) {
|
||||
const mode = normalizeTemplateMode(payload);
|
||||
if (mode === 'pixelart') {
|
||||
const pa = payload.pixelArt || {};
|
||||
return Math.max(1, Number(pa.width) || 32) / Math.max(1, Number(pa.height) || 32);
|
||||
}
|
||||
if (mode === 'customart' || mode === 'slide') {
|
||||
const ca = payload.slide || payload.customArt || {};
|
||||
return Math.max(1, Number(ca.width) || 1920) / Math.max(1, Number(ca.height) || 1080);
|
||||
}
|
||||
if (mode === 'deck') return 16 / 9;
|
||||
if (mode === 'disc-design') return 1;
|
||||
if (mode === 'jewel') {
|
||||
const jt = payload.jewelTemplate?.config || jewelTemplate;
|
||||
const w = Math.max(jt.front?.w || 0, jt.tray?.w || 0, 4.75);
|
||||
const h = (jt.front?.h || 4.75) + (jt.tray?.h || 4.75);
|
||||
return w / h;
|
||||
}
|
||||
if (mode === 'disc') return 8.5 / 11;
|
||||
const ct = payload.coverTemplate || {};
|
||||
const preset = presets[ct.preset] || presets.dvd;
|
||||
const panelW = Number(ct.panelW) || preset.panelW;
|
||||
const spineW = Number(ct.spineW) || preset.spineW;
|
||||
const trimH = Number(ct.trimH) || preset.trimH;
|
||||
const bleed = Number(ct.bleed) || 0;
|
||||
return Math.max(1, panelW * 2 + spineW + bleed * 2) / Math.max(1, trimH + bleed * 2);
|
||||
}
|
||||
|
||||
function currentEditableDesignPayload() {
|
||||
if (state.appMode === 'designer' || state.templateMode === 'disc-design') return discDesignPayload();
|
||||
if (state.templateMode === 'disc') return discSheetPayload();
|
||||
if (state.templateMode === 'jewel') return jewelTemplatePayload();
|
||||
if (state.templateMode === 'deck') return deckPayload();
|
||||
if (state.templateMode === 'pixelart') return pixelArtPayload();
|
||||
if (isCanvasMode(state.templateMode)) return customArtPayload();
|
||||
return coverTemplatePayload();
|
||||
}
|
||||
|
||||
function renderDesignPayloadPreview(container, payload, opts = {}) {
|
||||
const mode = normalizeTemplateMode(payload);
|
||||
if (mode === 'disc-design') renderDiscPreviewDOM(container, payload, opts);
|
||||
else if (mode === 'disc') renderDiscSheetPreviewDOM(container, payload, opts);
|
||||
else if (mode === 'jewel') renderJewelPreviewDOM(container, payload, opts);
|
||||
else if (mode === 'customart' || mode === 'slide') renderCustomArtPreviewDOM(container, payload, opts);
|
||||
else if (mode === 'deck') renderDeckPreviewDOM(container, payload, opts);
|
||||
else if (mode === 'pixelart') renderPixelArtPreviewDOM(container, payload, opts);
|
||||
else renderTemplatePreviewDOM(container, payload, opts);
|
||||
}
|
||||
|
||||
function renderDesignObjectIntoElement(el, layer) {
|
||||
el.innerHTML = '';
|
||||
const frame = document.createElement('div');
|
||||
frame.className = 'designObjectFrame';
|
||||
const fitWrap = document.createElement('div');
|
||||
fitWrap.style.cssText = 'position:absolute;inset:0;overflow:hidden;pointer-events:none';
|
||||
frame.appendChild(fitWrap);
|
||||
try {
|
||||
renderDesignPayloadPreview(fitWrap, layer.design?.payload || {}, { compact: true });
|
||||
fitMiniPreview(fitWrap);
|
||||
} catch (err) {
|
||||
fitWrap.innerHTML = `<div class="hint" style="padding:8px;font-size:.72rem">Design preview unavailable.</div>`;
|
||||
}
|
||||
el.appendChild(frame);
|
||||
}
|
||||
|
||||
function addDesignLayerFromPayload(payload, targetOverride = '') {
|
||||
if (!isDesignPayload(payload)) {
|
||||
toastError('That file is not an Artstr design.');
|
||||
return false;
|
||||
}
|
||||
const mode = normalizeTemplateMode(payload);
|
||||
const target = targetOverride || $('layerTarget')?.value || primaryTargetForMode(state.templateMode || 'cover');
|
||||
const bounds = targetBounds(target);
|
||||
const aspect = Math.max(0.1, designObjectAspect(payload));
|
||||
let w = bounds.w * 0.7;
|
||||
let h = w / aspect;
|
||||
if (h > bounds.h * 0.7) {
|
||||
h = bounds.h * 0.7;
|
||||
w = h * aspect;
|
||||
}
|
||||
applyTargetMode(target);
|
||||
pushHistory('add design object layer');
|
||||
addLayer({
|
||||
id: makeLayerId(),
|
||||
type: 'design',
|
||||
name: designObjectTitle(payload),
|
||||
target,
|
||||
x: bounds.x + (bounds.w - w) / 2,
|
||||
y: bounds.y + (bounds.h - h) / 2,
|
||||
w, h,
|
||||
rotate: 0,
|
||||
opacity: 1,
|
||||
z: nextLayerZ(),
|
||||
design: { mode, payload: clonePayload(payload) }
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
function openDesignLayerFileImport(targetOverride = '') {
|
||||
state._designLayerImportTarget = targetOverride || $('layerTarget')?.value || primaryTargetForMode(state.templateMode || 'cover');
|
||||
state._designLayerReplaceId = '';
|
||||
const input = $('designLayerFile');
|
||||
if (input) {
|
||||
input.value = '';
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
|
||||
function replaceSelectedDesignLayerFromFile() {
|
||||
const layer = selectedLayer();
|
||||
if (!layer || layer.type !== 'design') return;
|
||||
state._designLayerReplaceId = layer.id;
|
||||
state._designLayerImportTarget = layer.target;
|
||||
const input = $('designLayerFile');
|
||||
if (input) {
|
||||
input.value = '';
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
|
||||
function openDesignLayerNostrSearch(targetOverride = '') {
|
||||
state._designLayerImportTarget = targetOverride || $('layerTarget')?.value || primaryTargetForMode(state.templateMode || 'cover');
|
||||
state._designLayerReplaceId = '';
|
||||
const typeEl = $('nostrType'); if (typeEl) typeEl.value = 'any';
|
||||
const queryEl = $('nostrQuery'); if (queryEl) queryEl.value = '';
|
||||
const catEl = $('nostrCategory'); if (catEl) catEl.value = state.meta.category || '';
|
||||
$('nostrModal')?.classList.add('open');
|
||||
if (typeof switchModalTab === 'function') switchModalTab('Search');
|
||||
runNostrSearch();
|
||||
}
|
||||
|
||||
function tryDesignLayerImportRow(row) {
|
||||
if (!state._designLayerImportTarget) return false;
|
||||
const payload = row?.payload;
|
||||
if (!payload) return false;
|
||||
if (!isDesignPayload(payload)) {
|
||||
toastError('That result is not an Artstr design.');
|
||||
return true;
|
||||
}
|
||||
try { closeNostrPreview(); } catch (e) {}
|
||||
$('nostrModal')?.classList.remove('open');
|
||||
addDesignLayerFromPayload(payload, state._designLayerImportTarget);
|
||||
state._designLayerImportTarget = '';
|
||||
state._designLayerReplaceId = '';
|
||||
toastSuccess('Design imported as an editable object layer.');
|
||||
return true;
|
||||
}
|
||||
|
||||
function handleDesignLayerFileImport(event) {
|
||||
const file = event.target.files && event.target.files[0];
|
||||
event.target.value = '';
|
||||
if (!file) return;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
let payload;
|
||||
try { payload = JSON.parse(String(reader.result || '')); }
|
||||
catch { toastError('Could not read that file as a design.'); return; }
|
||||
const replaceId = state._designLayerReplaceId || '';
|
||||
if (replaceId) {
|
||||
const layer = getLayer(replaceId);
|
||||
if (!layer || layer.type !== 'design') { toastError('Select a design object layer first.'); return; }
|
||||
if (!isDesignPayload(payload)) { toastError('That file is not an Artstr design.'); return; }
|
||||
pushHistory('replace design object');
|
||||
layer.name = designObjectTitle(payload);
|
||||
layer.design = { mode: normalizeTemplateMode(payload), payload: clonePayload(payload) };
|
||||
state._designLayerReplaceId = '';
|
||||
state._designLayerImportTarget = '';
|
||||
render();
|
||||
toastSuccess('Design object replaced.');
|
||||
return;
|
||||
}
|
||||
if (addDesignLayerFromPayload(payload, state._designLayerImportTarget || '')) {
|
||||
state._designLayerImportTarget = '';
|
||||
toastSuccess('Design imported as an editable object layer.');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
}
|
||||
|
||||
function beginDesignObjectEdit(layerId = '') {
|
||||
const layer = layerId ? getLayer(layerId) : selectedLayer();
|
||||
if (!layer || layer.type !== 'design') return;
|
||||
if (_designObjectEdit) {
|
||||
toastError('Finish the current embedded-design edit first.');
|
||||
return;
|
||||
}
|
||||
_designObjectEdit = {
|
||||
hostPayload: currentEditableDesignPayload(),
|
||||
layerId: layer.id,
|
||||
layerName: layer.name || designObjectTitle(layer.design?.payload)
|
||||
};
|
||||
try {
|
||||
loadProjectFromText(JSON.stringify(layer.design?.payload || {}));
|
||||
} catch (err) {
|
||||
_designObjectEdit = null;
|
||||
toastError(`Could not open embedded design: ${err?.message || err}`);
|
||||
return;
|
||||
}
|
||||
paintDesignObjectBanner();
|
||||
toastSuccess('Editing embedded design object. Use Done editing object to write it back.');
|
||||
}
|
||||
|
||||
function finishDesignObjectEdit(saveChanges) {
|
||||
if (!_designObjectEdit) return;
|
||||
const edit = _designObjectEdit;
|
||||
const updatedPayload = saveChanges ? currentEditableDesignPayload() : null;
|
||||
try {
|
||||
loadProjectFromText(JSON.stringify(edit.hostPayload));
|
||||
} catch (err) {
|
||||
toastError(`Could not return to parent design: ${err?.message || err}`);
|
||||
return;
|
||||
}
|
||||
_designObjectEdit = null;
|
||||
const layer = getLayer(edit.layerId);
|
||||
if (saveChanges && layer && layer.type === 'design') {
|
||||
pushHistory('update design object');
|
||||
layer.design = { mode: normalizeTemplateMode(updatedPayload), payload: clonePayload(updatedPayload) };
|
||||
layer.name = updatedPayload?.meta?.title || edit.layerName || layer.name;
|
||||
state.selectedLayerId = layer.id;
|
||||
render();
|
||||
toastSuccess('Embedded design object updated.');
|
||||
} else {
|
||||
toastSuccess('Returned to parent design.');
|
||||
}
|
||||
paintDesignObjectBanner();
|
||||
}
|
||||
|
||||
function paintDesignObjectBanner() {
|
||||
const banner = $('designObjectBanner');
|
||||
const title = $('designObjectTitle');
|
||||
if (!banner) return;
|
||||
if (_designObjectEdit) {
|
||||
if (title) title.textContent = _designObjectEdit.layerName || 'Design object';
|
||||
banner.style.display = 'flex';
|
||||
} else {
|
||||
banner.style.display = 'none';
|
||||
if (title) title.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
function addTextLayer(targetOverride = '') {
|
||||
const target = targetOverride || $('layerTarget')?.value || 'page';
|
||||
const bounds = targetBounds(target);
|
||||
@@ -9234,6 +9530,8 @@
|
||||
renderQrIntoElement(el, layer);
|
||||
} else if (layer.type === 'pixelart') {
|
||||
renderPixelArtIntoElement(el, layer);
|
||||
} else if (layer.type === 'design') {
|
||||
renderDesignObjectIntoElement(el, layer);
|
||||
} else {
|
||||
const text = document.createElement('div');
|
||||
text.className = 'textLayer';
|
||||
@@ -9260,6 +9558,11 @@
|
||||
event.stopPropagation();
|
||||
openPixelEditor(layer.id);
|
||||
});
|
||||
} else if (layer.type === 'design') {
|
||||
el.addEventListener('dblclick', (event) => {
|
||||
event.stopPropagation();
|
||||
beginDesignObjectEdit(layer.id);
|
||||
});
|
||||
}
|
||||
el.addEventListener('click', (event) => {
|
||||
event.stopPropagation();
|
||||
@@ -9379,8 +9682,16 @@
|
||||
addQrBtn.type = 'button';
|
||||
addQrBtn.textContent = 'Add QR';
|
||||
addQrBtn.addEventListener('click', () => addQrLayer('designer-disc'));
|
||||
const importDesignFileBtn = document.createElement('button');
|
||||
importDesignFileBtn.type = 'button';
|
||||
importDesignFileBtn.textContent = 'Import design file';
|
||||
importDesignFileBtn.addEventListener('click', () => openDesignLayerFileImport('designer-disc'));
|
||||
const importDesignNostrBtn = document.createElement('button');
|
||||
importDesignNostrBtn.type = 'button';
|
||||
importDesignNostrBtn.textContent = 'Import from Nostr';
|
||||
importDesignNostrBtn.addEventListener('click', () => openDesignLayerNostrSearch('designer-disc'));
|
||||
// Text + shape adds live in the left tool palette now.
|
||||
buttonRow.append(addImageBtn, addColorBtn, addQrBtn);
|
||||
buttonRow.append(addImageBtn, addColorBtn, addQrBtn, importDesignFileBtn, importDesignNostrBtn);
|
||||
|
||||
actions.append(urlRow, buttonRow);
|
||||
panel.appendChild(actions);
|
||||
@@ -9456,8 +9767,16 @@
|
||||
addQrBtn.type = 'button';
|
||||
addQrBtn.textContent = 'Add QR';
|
||||
addQrBtn.addEventListener('click', () => addQrLayer(targetDef.key));
|
||||
const importDesignFileBtn = document.createElement('button');
|
||||
importDesignFileBtn.type = 'button';
|
||||
importDesignFileBtn.textContent = 'Import design file';
|
||||
importDesignFileBtn.addEventListener('click', () => openDesignLayerFileImport(targetDef.key));
|
||||
const importDesignNostrBtn = document.createElement('button');
|
||||
importDesignNostrBtn.type = 'button';
|
||||
importDesignNostrBtn.textContent = 'Import from Nostr';
|
||||
importDesignNostrBtn.addEventListener('click', () => openDesignLayerNostrSearch(targetDef.key));
|
||||
// Text + shape adds live in the left tool palette now.
|
||||
buttonRow.append(addImageBtn, addColorBtn, addQrBtn);
|
||||
buttonRow.append(addImageBtn, addColorBtn, addQrBtn, importDesignFileBtn, importDesignNostrBtn);
|
||||
|
||||
actions.append(urlRow, buttonRow);
|
||||
panel.appendChild(actions);
|
||||
@@ -11088,6 +11407,8 @@
|
||||
if (layer && layer.type === 'qr') syncQrLayerInputs(layer);
|
||||
const pixelPanel = $('pixelLayerControls');
|
||||
if (pixelPanel) pixelPanel.style.display = layer && layer.type === 'pixelart' ? 'block' : 'none';
|
||||
const designPanel = $('designLayerControls');
|
||||
if (designPanel) designPanel.style.display = layer && layer.type === 'design' ? 'block' : 'none';
|
||||
if (layer) syncLayerClipInputs(layer);
|
||||
const layerNameRow = $('layerName');
|
||||
const layerNameLabel = layerNameRow?.previousElementSibling;
|
||||
@@ -14884,6 +15205,7 @@
|
||||
}
|
||||
rows.forEach(row => {
|
||||
wall.appendChild(buildFeedCard(row, () => {
|
||||
if (tryDesignLayerImportRow(row)) return;
|
||||
if (tryDeckImportRow(row)) return;
|
||||
if (row.mode === 'disc-design') {
|
||||
const slotTarget = state._discImportTargetSlot;
|
||||
@@ -14933,6 +15255,7 @@
|
||||
// Card factory used by both the initial render and the streaming-update diff.
|
||||
function buildFeedCardForRow(row) {
|
||||
const card = buildFeedCard(row, () => {
|
||||
if (tryDesignLayerImportRow(row)) return;
|
||||
if (tryDeckImportRow(row)) return;
|
||||
if (row.mode === 'disc-design') {
|
||||
const slotTarget = state._discImportTargetSlot;
|
||||
@@ -15304,6 +15627,7 @@
|
||||
|
||||
function forkRowIntoEditor(row) {
|
||||
if (!row?.payload) return;
|
||||
if (tryDesignLayerImportRow(row)) return;
|
||||
if (tryDeckImportRow(row)) return;
|
||||
if (row.mode === 'disc-design') {
|
||||
const slotTarget = state._discImportTargetSlot;
|
||||
@@ -15751,6 +16075,8 @@
|
||||
renderQrIntoElement(el, layer);
|
||||
} else if (layer.type === 'pixelart') {
|
||||
renderPixelArtIntoElement(el, layer);
|
||||
} else if (layer.type === 'design') {
|
||||
renderDesignObjectIntoElement(el, layer);
|
||||
} else if (layer.type === 'image' && layer.src) {
|
||||
const img = document.createElement('img');
|
||||
img.src = layer.src;
|
||||
@@ -15963,6 +16289,8 @@
|
||||
renderQrIntoElement(el, layer);
|
||||
} else if (layer.type === 'pixelart') {
|
||||
renderPixelArtIntoElement(el, layer);
|
||||
} else if (layer.type === 'design') {
|
||||
renderDesignObjectIntoElement(el, layer);
|
||||
} else if (layer.type === 'image' && layer.src) {
|
||||
const img = document.createElement('img');
|
||||
img.src = layer.src;
|
||||
@@ -16046,6 +16374,8 @@
|
||||
renderQrIntoElement(el, layer);
|
||||
} else if (layer.type === 'pixelart') {
|
||||
renderPixelArtIntoElement(el, layer);
|
||||
} else if (layer.type === 'design') {
|
||||
renderDesignObjectIntoElement(el, layer);
|
||||
} else if (layer.type === 'image' && layer.src) {
|
||||
const img = document.createElement('img');
|
||||
img.src = layer.src;
|
||||
@@ -16286,6 +16616,8 @@
|
||||
renderQrIntoElement(el, layer);
|
||||
} else if (layer.type === 'pixelart') {
|
||||
renderPixelArtIntoElement(el, layer);
|
||||
} else if (layer.type === 'design') {
|
||||
renderDesignObjectIntoElement(el, layer);
|
||||
} else if (layer.type === 'image' && layer.src) {
|
||||
const img = document.createElement('img');
|
||||
img.src = layer.src;
|
||||
@@ -16450,6 +16782,8 @@
|
||||
renderQrIntoElement(el, layer);
|
||||
} else if (layer.type === 'pixelart') {
|
||||
renderPixelArtIntoElement(el, layer);
|
||||
} else if (layer.type === 'design') {
|
||||
renderDesignObjectIntoElement(el, layer);
|
||||
} else if (layer.type === 'image' && layer.src) {
|
||||
const img = document.createElement('img');
|
||||
img.src = layer.src;
|
||||
@@ -16478,6 +16812,7 @@
|
||||
out.innerHTML = rows.length ? '' : '<div class="hint">No templates found.</div>';
|
||||
rows.slice(0, 40).forEach(row => {
|
||||
out.appendChild(buildResultCard(row, () => {
|
||||
if (tryDesignLayerImportRow(row)) return;
|
||||
if (tryDeckImportRow(row)) return;
|
||||
if (row.mode === 'disc-design') {
|
||||
const slotTarget = state._discImportTargetSlot;
|
||||
@@ -17346,6 +17681,14 @@
|
||||
const l = selectedLayer();
|
||||
if (l && l.type === 'pixelart') openPixelEditor(l.id);
|
||||
});
|
||||
$('editDesignLayerBtn')?.addEventListener('click', () => {
|
||||
const l = selectedLayer();
|
||||
if (l && l.type === 'design') beginDesignObjectEdit(l.id);
|
||||
});
|
||||
$('replaceDesignLayerBtn')?.addEventListener('click', replaceSelectedDesignLayerFromFile);
|
||||
$('designLayerFile')?.addEventListener('change', handleDesignLayerFileImport);
|
||||
$('designObjectDoneBtn')?.addEventListener('click', () => finishDesignObjectEdit(true));
|
||||
$('designObjectCancelBtn')?.addEventListener('click', () => finishDesignObjectEdit(false));
|
||||
$('pixelEditorDoneBtn')?.addEventListener('click', () => closePixelEditor(true));
|
||||
$('pixelUndoBtn')?.addEventListener('click', pixelUndo);
|
||||
$('pixelZoomIn')?.addEventListener('click', () => pixelStepZoom(1));
|
||||
@@ -17588,7 +17931,7 @@
|
||||
$("designerCategoryField").addEventListener("change", (e)=>{pushHistory(`change category to ${e.target.value || 'Uncategorized'}`); state.meta.category=e.target.value; syncIdentifierVisibility(); render();});
|
||||
$("designerLanguageField").addEventListener("change", (e)=>{pushHistory('change language'); state.meta.language=e.target.value; render();});
|
||||
|
||||
$("nostrClose").addEventListener("click", () => { closeNostrPreview(); $("nostrModal").classList.remove("open"); _feedCache.softModRevealed.clear(); state._deckImportMode = false; });
|
||||
$("nostrClose").addEventListener("click", () => { closeNostrPreview(); $("nostrModal").classList.remove("open"); _feedCache.softModRevealed.clear(); state._deckImportMode = false; state._designLayerImportTarget = ''; state._designLayerReplaceId = ''; });
|
||||
$("nostrRefreshBtn").addEventListener("click", async () => {
|
||||
const btn = $("nostrRefreshBtn");
|
||||
const original = btn.textContent;
|
||||
|
||||
Reference in new issue
Block a user