reorder
This commit is contained in:
@@ -169,10 +169,10 @@
|
||||
|
||||
.pad-preview-overlay {
|
||||
position: absolute;
|
||||
background: rgba(245,158,11,0.25);
|
||||
background: rgba(245,158,11,0.4);
|
||||
border: 1px dashed #f59e0b;
|
||||
pointer-events: none;
|
||||
z-index: 5;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@@ -1323,6 +1323,9 @@
|
||||
position: relative; height: 100%; border-radius: 6px; overflow: hidden;
|
||||
}
|
||||
.studio-viewer.show-checker {
|
||||
background-color: transparent;
|
||||
}
|
||||
#studioCheckerboard {
|
||||
background-color: #888;
|
||||
background-image:
|
||||
linear-gradient(45deg,#555 25%,transparent 25%),
|
||||
@@ -1331,11 +1334,13 @@
|
||||
linear-gradient(-45deg,transparent 75%,#555 75%);
|
||||
background-size: 16px 16px;
|
||||
background-position: 0 0, 0 8px, 8px -8px, -8px 0;
|
||||
z-index: 1;
|
||||
}
|
||||
#lbImg {
|
||||
max-width: 100%; max-height: 100%;
|
||||
object-fit: contain; border-radius: 4px;
|
||||
cursor: zoom-in; transition: transform 0.25s; display: block;
|
||||
z-index: 2; position: relative;
|
||||
}
|
||||
#lbImg.zoomed { cursor: zoom-out; transform: scale(2.2); transform-origin: center; }
|
||||
#lbVideo { max-width: 100%; max-height: 100%; border-radius: 4px; }
|
||||
@@ -1960,6 +1965,7 @@
|
||||
onblur="updateGroupName()" onkeydown="if(event.key==='Enter')this.blur()"
|
||||
title="Group name (applies to all images in this group)" />
|
||||
<span id="lbPoseTag" style="font-size:11px;color:#f59e0b;display:none"></span>
|
||||
<span id="lbImgDims" style="font-size:11px;color:#444;margin:0 8px"></span>
|
||||
<span id="lbCounter" style="font-size:12px;color:#555"></span>
|
||||
</div>
|
||||
<div class="studio-topbar-right">
|
||||
@@ -1975,6 +1981,7 @@
|
||||
<div class="studio-viewer-row">
|
||||
<button class="studio-nav-btn" id="lbPrev" onclick="lbNav(-1)">‹</button>
|
||||
<div class="studio-viewer" id="studioViewer">
|
||||
<div id="studioCheckerboard" style="display:none;position:absolute;pointer-events:none;z-index:1"></div>
|
||||
<img id="lbImg" src="" alt="" onclick="toggleImageZoom(this)" />
|
||||
<video id="lbVideo" style="display:none;max-width:100%;max-height:100%;border-radius:4px" controls autoplay muted loop></video>
|
||||
<div class="lb-hidden-badge">Hidden from preview</div>
|
||||
@@ -2051,7 +2058,7 @@
|
||||
<button class="sb-btn" id="lbCropBtn" style="display:none" onclick="lbAutoCrop()" title="Crop away transparent border">Crop</button>
|
||||
<button class="sb-btn" id="lbDuplicateBtn" onclick="lbDuplicate()" title="Duplicate image into same group">Duplicate</button>
|
||||
<button class="sb-btn" id="lbManualCropBtn" onclick="startManualCrop()" title="Drag to crop image">Crop…</button>
|
||||
<button class="sb-btn" id="lbManualPadBtn" onclick="startManualPad()" title="Expand canvas by padding each side">Pad…</button>
|
||||
<button class="sb-btn" id="lbManualPadBtn" onclick="startManualPad()" title="Expand canvas by padding each side">Expand…</button>
|
||||
<button class="sb-btn" id="lbRotateLeftBtn" onclick="lbRotate(-90)" title="Rotate 90° counter-clockwise">⟲ 90°</button>
|
||||
<button class="sb-btn" id="lbRotateRightBtn" onclick="lbRotate(90)" title="Rotate 90° clockwise">⟳ 90°</button>
|
||||
<button class="sb-btn" id="lbPoseBtn" onclick="lbTogglePose()" title="Preview body pose skeleton">Pose</button>
|
||||
@@ -2220,8 +2227,8 @@
|
||||
|
||||
// --- HYDRATION_START ---
|
||||
const PRELOADED_IMAGES = [
|
||||
"20260627_232843_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
|
||||
"20260627_232502_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
|
||||
"20260627_232843_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
|
||||
"20260627_232526_pad_20260626_035101_pad_20260626_034830_jb.nobg.png",
|
||||
"20260627_232302_pad_20260627_232133_image.png",
|
||||
"20260627_232238_pad_20260627_232133_image.png",
|
||||
@@ -6914,19 +6921,61 @@
|
||||
updatePadPreview();
|
||||
}
|
||||
|
||||
function getImageDisplayRect(img) {
|
||||
if (!img || !img.naturalWidth) return null;
|
||||
const rect = img.getBoundingClientRect();
|
||||
const container = img.parentElement;
|
||||
const crect = container.getBoundingClientRect();
|
||||
|
||||
const iw = img.naturalWidth;
|
||||
const ih = img.naturalHeight;
|
||||
const cw = rect.width;
|
||||
const ch = rect.height;
|
||||
|
||||
const aspect = iw / ih;
|
||||
const caspect = cw / ch;
|
||||
|
||||
let dw, dh, dl, dt;
|
||||
if (aspect > caspect) {
|
||||
dw = cw;
|
||||
dh = cw / aspect;
|
||||
dl = 0;
|
||||
dt = (ch - dh) / 2;
|
||||
} else {
|
||||
dh = ch;
|
||||
dw = ch * aspect;
|
||||
dt = 0;
|
||||
dl = (cw - dw) / 2;
|
||||
}
|
||||
|
||||
// Adjust relative to container if needed, but since img is a flex item
|
||||
// centered in .studio-viewer, its rect.left/top might already be offset.
|
||||
// Actually, dl and dt above are relative to the img element's box.
|
||||
// We want coordinates relative to .studio-viewer.
|
||||
const finalLeft = rect.left - crect.left + dl;
|
||||
const finalTop = rect.top - crect.top + dt;
|
||||
|
||||
return { left: finalLeft, top: finalTop, width: dw, height: dh };
|
||||
}
|
||||
|
||||
function updatePadPreview() {
|
||||
const viewer = document.getElementById('studioViewer');
|
||||
if (!viewer) return;
|
||||
const img = document.getElementById('lbImg');
|
||||
if (!viewer || !img || img.style.display === 'none') return;
|
||||
|
||||
// Clean up existing preview overlays
|
||||
viewer.querySelectorAll('.pad-preview-overlay').forEach(el => el.remove());
|
||||
|
||||
const rect = getImageDisplayRect(img);
|
||||
if (!rect) return;
|
||||
|
||||
const isManual = !!document.getElementById('padBar');
|
||||
const getV = (side) => {
|
||||
const el = document.getElementById(isManual ? 'pad' + side : 'sbPad' + side);
|
||||
return el ? el.value : '0';
|
||||
};
|
||||
|
||||
const sides = ['Left', 'Right'];
|
||||
const sides = ['Left', 'Right', 'Top', 'Bottom'];
|
||||
sides.forEach(side => {
|
||||
const vStr = getV(side);
|
||||
if (!vStr || vStr === '0' || vStr === '0.00') return;
|
||||
@@ -6934,19 +6983,24 @@
|
||||
const val = parseFloat(vStr);
|
||||
if (isNaN(val) || val <= 0) return;
|
||||
|
||||
const isPercent = vStr.includes('%') || vStr.includes('.');
|
||||
const width = isPercent ? (val + '%') : (val + 'px');
|
||||
|
||||
const isPercent = vStr.includes('%') || (vStr.indexOf('.') !== -1 && !isNaN(vStr));
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'pad-preview-overlay';
|
||||
overlay.style.top = '0';
|
||||
overlay.style.bottom = '0';
|
||||
if (side === 'Left') {
|
||||
overlay.style.left = '0';
|
||||
overlay.style.width = width;
|
||||
|
||||
if (side === 'Left' || side === 'Right') {
|
||||
const w = isPercent ? (rect.width * val / 100) : val;
|
||||
overlay.style.width = w + 'px';
|
||||
overlay.style.top = rect.top + 'px';
|
||||
overlay.style.height = rect.height + 'px';
|
||||
if (side === 'Left') overlay.style.left = (rect.left - w) + 'px';
|
||||
else overlay.style.left = (rect.left + rect.width) + 'px';
|
||||
} else {
|
||||
overlay.style.right = '0';
|
||||
overlay.style.width = width;
|
||||
const h = isPercent ? (rect.height * val / 100) : val;
|
||||
overlay.style.height = h + 'px';
|
||||
overlay.style.left = rect.left + 'px';
|
||||
overlay.style.width = rect.width + 'px';
|
||||
if (side === 'Top') overlay.style.top = (rect.top - h) + 'px';
|
||||
else overlay.style.top = (rect.top + rect.height) + 'px';
|
||||
}
|
||||
viewer.appendChild(overlay);
|
||||
});
|
||||
@@ -7075,6 +7129,29 @@
|
||||
const isVid = isVideo(fname) || fileContentType[fname] === 'video';
|
||||
const lbImgEl = document.getElementById('lbImg');
|
||||
const lbVideoEl = document.getElementById('lbVideo');
|
||||
|
||||
lbImgEl.onload = () => {
|
||||
const dimEl = document.getElementById('lbImgDims');
|
||||
if (dimEl) dimEl.textContent = `${lbImgEl.naturalWidth} x ${lbImgEl.naturalHeight}`;
|
||||
|
||||
// Use requestAnimationFrame to ensure layout has updated before measuring for checkerboard/previews
|
||||
requestAnimationFrame(() => {
|
||||
updatePadPreview();
|
||||
const cb = document.getElementById('sbCheckerboard');
|
||||
if (cb && cb.checked) toggleCheckerboard(true);
|
||||
});
|
||||
};
|
||||
|
||||
// Show image dimensions
|
||||
const dimEl = document.getElementById('lbImgDims');
|
||||
if (dimEl) {
|
||||
if (!isVid && lbImgEl.naturalWidth) {
|
||||
dimEl.textContent = `${lbImgEl.naturalWidth} x ${lbImgEl.naturalHeight}`;
|
||||
} else {
|
||||
dimEl.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (isVid) {
|
||||
lbImgEl.style.display = 'none';
|
||||
lbVideoEl.style.display = '';
|
||||
@@ -8201,7 +8278,7 @@
|
||||
return `<label style="display:flex;align-items:center;gap:3px;font-size:11px;color:#ccc">${label}<input type="text" id="${id}" value="0.00" oninput="${onInput}" onblur="${onBlur}" style="width:52px;background:#111;border:1px solid #333;color:#ccc;border-radius:4px;padding:2px 4px;font-size:11px;text-align:right" placeholder="px or %"></label>`;
|
||||
};
|
||||
bar.innerHTML =
|
||||
`<span style="flex:1;font-size:11px;color:#aaa">Pad canvas (px or %):</span>`
|
||||
`<span style="flex:1;font-size:11px;color:#aaa">Expand canvas (px or %):</span>`
|
||||
+ numInput('padTop','↑')
|
||||
+ numInput('padRight','→')
|
||||
+ numInput('padBottom','↓')
|
||||
@@ -8216,7 +8293,7 @@
|
||||
+ `<label style="display:flex;align-items:center;gap:4px;font-size:11px;color:#ccc;cursor:pointer" title="Instruct the model to fill in the padded area">`
|
||||
+ `<input type="checkbox" id="padOutpaint" style="cursor:pointer">Outpaint</label>`
|
||||
+ `<button class="sb-btn" onclick="cancelManualPad()">Cancel</button>`
|
||||
+ `<button class="sb-btn primary" id="padConfirmBtn" onclick="confirmManualPad()">Pad</button>`;
|
||||
+ `<button class="sb-btn primary" id="padConfirmBtn" onclick="confirmManualPad()">Expand</button>`;
|
||||
viewer.appendChild(bar);
|
||||
updatePadPreview();
|
||||
}
|
||||
@@ -8279,12 +8356,11 @@
|
||||
refreshNow(true);
|
||||
} else {
|
||||
showToast('Padded', 'success');
|
||||
lbUrls[lbIdx] = IMAGE_FOLDER + fname + '?t=' + Date.now();
|
||||
updateStudio();
|
||||
_bustStudioImage(fname);
|
||||
}
|
||||
} else {
|
||||
showToast('Pad failed: ' + await r.text(), 'error');
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Pad'; }
|
||||
if (btn) { btn.disabled = false; btn.textContent = 'Expand'; }
|
||||
}
|
||||
} catch (e) {
|
||||
showToast('Pad error: ' + e, 'error');
|
||||
@@ -9802,6 +9878,15 @@
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.addEventListener('resize', () => {
|
||||
if (_isStudioOpen()) {
|
||||
requestAnimationFrame(() => {
|
||||
updatePadPreview();
|
||||
const cb = document.getElementById('sbCheckerboard');
|
||||
if (cb && cb.checked) toggleCheckerboard(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
loadPromptHistory();
|
||||
loadImages();
|
||||
autoRefreshTimer = setInterval(_timedRefresh, REFRESH_INTERVAL * 1000);
|
||||
@@ -11648,7 +11733,28 @@
|
||||
|
||||
function toggleCheckerboard(on) {
|
||||
const viewer = document.getElementById('studioViewer');
|
||||
const cbLayer = document.getElementById('studioCheckerboard');
|
||||
const img = document.getElementById('lbImg');
|
||||
|
||||
if (viewer) viewer.classList.toggle('show-checker', on);
|
||||
|
||||
if (cbLayer) {
|
||||
if (on && img && img.style.display !== 'none') {
|
||||
const rect = getImageDisplayRect(img);
|
||||
if (rect) {
|
||||
cbLayer.style.display = 'block';
|
||||
cbLayer.style.left = rect.left + 'px';
|
||||
cbLayer.style.top = rect.top + 'px';
|
||||
cbLayer.style.width = rect.width + 'px';
|
||||
cbLayer.style.height = rect.height + 'px';
|
||||
} else {
|
||||
cbLayer.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
cbLayer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
const cb = document.getElementById('sbCheckerboard');
|
||||
if (cb) cb.checked = on;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user