updates UI
This commit is contained in:
@@ -2334,8 +2334,13 @@
|
||||
|
||||
// --- HYDRATION_START ---
|
||||
const PRELOADED_IMAGES = [
|
||||
"20260629_124948_sc_view_023_345deg.png",
|
||||
"20260629_124523_sc_view_023_345deg.png",
|
||||
"20260629_124433_sc_view_023_345deg.png",
|
||||
"20260629_122907_fs_Step Sis will do anything to make me Delete this Videos_1633s-2269s_image.png_prev50.mp4",
|
||||
"20260629_123127_test_upload_image.png",
|
||||
"20260629_123101_test_upload_image.png",
|
||||
"20260629_120459_fs_bitch insemination_dup_20260626_003125_dup_Pasted image (9).png.mp4",
|
||||
"20260624_154536_img_72.png",
|
||||
"20260629_114451_sc_sc_image.nobg.png",
|
||||
"20260629_114346_sc_sc_image.nobg.png",
|
||||
"_turntable/cg_4e2b6449/views/view_023_345deg.png",
|
||||
@@ -5710,6 +5715,7 @@
|
||||
"20260624_154712_img_72.png",
|
||||
"20260624_154639_img_72.png",
|
||||
"20260624_154548_img_72.png",
|
||||
"20260624_154536_img_72.png",
|
||||
"20260624_154525_img_72.png",
|
||||
"20260624_154514_img_72.png",
|
||||
"20260624_154503_img_72.png",
|
||||
@@ -12806,7 +12812,8 @@
|
||||
}
|
||||
if (!data) {
|
||||
try {
|
||||
const r = await fetch(`${API}/videos`);
|
||||
const url = forceFresh ? `${API}/videos?refresh=true` : `${API}/videos`;
|
||||
const r = await fetch(url);
|
||||
if (r.ok) { data = await r.json(); }
|
||||
} catch (_) {}
|
||||
}
|
||||
@@ -12871,16 +12878,89 @@
|
||||
}
|
||||
|
||||
// Template grid view
|
||||
let html = '<div class="sb-label">Template videos</div>';
|
||||
if (!availableVideos.length) {
|
||||
let html = `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
||||
<div class="sb-label" style="margin:0">Template videos</div>
|
||||
<button class="sb-btn" onclick="refreshTemplateVideos(event)" style="font-size:10px;padding:2px 6px;line-height:1">🔄 Refresh</button>
|
||||
</div>`;
|
||||
|
||||
if (!wireframeGroups || !wireframeGroups.length) {
|
||||
html += '<div style="font-size:11px;color:#555;padding:6px 0">No wireframe videos found</div>';
|
||||
} else {
|
||||
html += '<div class="sb-template-grid">';
|
||||
availableVideos.forEach(v => {
|
||||
const vSafe = v.replace(/'/g, "\\'");
|
||||
const trimBtn = `<button class="sb-template-trim-btn" title="Trim video"
|
||||
onclick="event.stopPropagation();openTrimPanel('${vSafe}')">✂</button>`;
|
||||
html += _tplCardHTML(v, _fsSelectedVideo === v, `sbSelectTemplate('${vSafe}')`, trimBtn);
|
||||
html += '<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:12px">';
|
||||
if (!window._expandedVideoGroups) {
|
||||
window._expandedVideoGroups = {};
|
||||
}
|
||||
wireframeGroups.forEach(g => {
|
||||
const hasClips = g.clips && g.clips.length > 0;
|
||||
const repVideo = g.video || (hasClips ? g.clips[0] : null);
|
||||
if (!repVideo) return;
|
||||
|
||||
const isExpanded = !!window._expandedVideoGroups[g.stem];
|
||||
const repVideoSafe = repVideo.replace(/'/g, "\\'");
|
||||
const isSelected = _fsSelectedVideo === repVideo || (g.video && _fsSelectedVideo === g.video);
|
||||
|
||||
const selectedClip = hasClips ? g.clips.find(clip => _fsSelectedVideo === clip) : null;
|
||||
const rowBorder = (isSelected || selectedClip) ? 'border-color:#6d28d9;background:#1e1b4b' : 'border-color:#27272a;background:#09090b';
|
||||
|
||||
html += `
|
||||
<div style="border:1px solid;border-radius:6px;padding:8px;display:flex;flex-direction:column;gap:8px;transition:all 0.15s;${rowBorder}">
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div style="width:70px;height:50px;flex-shrink:0;position:relative;border-radius:4px;overflow:hidden;background:#000">
|
||||
<div class="sb-template-card compact-card ${isSelected && !selectedClip ? 'selected' : ''}"
|
||||
onclick="sbSelectTemplate('${repVideoSafe}')"
|
||||
onmouseenter="_tplPlay(this,'${repVideoSafe}')" onmouseleave="_tplStop(this)"
|
||||
style="width:100%;height:100%;margin:0;border:none">
|
||||
<img src="${API}/wireframe/frame/${encodeURIComponent(repVideo)}?t=0" loading="lazy" style="width:100%;height:100%;object-fit:cover" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex-grow:1;min-width:0">
|
||||
<div style="font-size:12px;font-weight:bold;color:#f4f4f5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="${escHtml(g.stem)}">
|
||||
📹 ${escHtml(g.stem)}
|
||||
</div>
|
||||
<div style="font-size:10px;color:#71717a;margin-top:2px;display:flex;align-items:center;gap:4px">
|
||||
<span>${g.video ? 'Full video' : 'Clips only'}</span>
|
||||
${selectedClip ? `<span style="color:#a78bfa;font-weight:bold">· Selected clip: ${escHtml(selectedClip.replace(g.stem + '_', '').replace('.mp4', ''))}</span>` : ''}
|
||||
${isSelected && !selectedClip ? `<span style="color:#a78bfa;font-weight:bold">· Selected</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:4px;flex-shrink:0">
|
||||
${g.video ? `<button class="sb-template-trim-btn" title="Trim video" onclick="event.stopPropagation();openTrimPanel('${repVideoSafe}')" style="position:static;width:24px;height:24px;line-height:24px;font-size:11px;padding:0">✂</button>` : ''}
|
||||
${hasClips ? `
|
||||
<button class="sb-btn" onclick="event.stopPropagation();toggleGroupClips('${g.stem.replace(/'/g, "\\'")}')"
|
||||
style="font-size:10px;padding:3px 6px;line-height:1;background:#27272a;color:#d4d4d8;border:1px solid #3f3f46;display:flex;align-items:center;gap:2px">
|
||||
<span>Clips (${g.clips.length})</span>
|
||||
<span>${isExpanded ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (hasClips && isExpanded) {
|
||||
html += `
|
||||
<div style="border-top:1px solid #27272a;padding-top:6px;margin-top:2px">
|
||||
<div style="font-size:9px;color:#71717a;text-transform:uppercase;font-weight:bold;letter-spacing:0.5px;margin-bottom:4px">Sub-Clips</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:4px">`;
|
||||
|
||||
g.clips.forEach(clip => {
|
||||
const cSafe = clip.replace(/'/g, "\\'");
|
||||
const isClipSelected = _fsSelectedVideo === clip;
|
||||
const clipLabel = clip.replace(g.stem + '_', '').replace('.mp4', '');
|
||||
const pillBg = isClipSelected ? 'background:#6d28d9;color:#fff;border-color:#8b5cf6' : 'background:#18181b;color:#a1a1aa;border-color:#27272a';
|
||||
|
||||
html += `
|
||||
<button class="sb-btn" onclick="sbSelectTemplate('${cSafe}')"
|
||||
style="font-size:10px;padding:2px 6px;border-radius:3px;border:1px solid;cursor:pointer;transition:all 0.1s;${pillBg}">
|
||||
${escHtml(clipLabel)}
|
||||
</button>`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += `</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
@@ -12937,6 +13017,28 @@
|
||||
renderSidebarFaceswap();
|
||||
}
|
||||
|
||||
function toggleGroupClips(stem) {
|
||||
if (!window._expandedVideoGroups) window._expandedVideoGroups = {};
|
||||
window._expandedVideoGroups[stem] = !window._expandedVideoGroups[stem];
|
||||
if (_activeSidebarTab === 'faceswap') {
|
||||
renderSidebarFaceswap();
|
||||
} else if (_activeSidebarTab === 'scenery') {
|
||||
renderSidebarScenery();
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshTemplateVideos(event) {
|
||||
if (event) event.stopPropagation();
|
||||
showToast("Refreshing video library...", "info");
|
||||
await loadTemplateVideos(true);
|
||||
if (_activeSidebarTab === 'faceswap') {
|
||||
renderSidebarFaceswap();
|
||||
} else if (_activeSidebarTab === 'scenery') {
|
||||
renderSidebarScenery();
|
||||
}
|
||||
showToast("Video library refreshed", "success");
|
||||
}
|
||||
|
||||
function openTrimPanel(videoName) {
|
||||
_fsTrimVideo = videoName;
|
||||
renderSidebarFaceswap();
|
||||
@@ -13275,56 +13377,118 @@
|
||||
|
||||
// --- Video picker grid (collapsible) ---
|
||||
if (!_sceneVideo || gridOpen) {
|
||||
html += '<div class="sb-label">Background source library</div>';
|
||||
html += `
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">
|
||||
<div class="sb-label" style="margin:0">Background source library</div>
|
||||
<button class="sb-btn" onclick="refreshTemplateVideos(event)" style="font-size:10px;padding:2px 6px;line-height:1">🔄 Refresh</button>
|
||||
</div>`;
|
||||
|
||||
if (wireframeGroups && wireframeGroups.length) {
|
||||
html += '<div style="display:flex;flex-direction:column;gap:12px;margin-bottom:12px">';
|
||||
html += '<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:12px">';
|
||||
if (!window._expandedVideoGroups) {
|
||||
window._expandedVideoGroups = {};
|
||||
}
|
||||
wireframeGroups.forEach(g => {
|
||||
const hasClips = g.clips && g.clips.length;
|
||||
const hasFrames = g.frames && g.frames.length;
|
||||
|
||||
const hasClips = g.clips && g.clips.length > 0;
|
||||
const hasFrames = g.frames && g.frames.length > 0;
|
||||
const repVideo = g.video || (hasClips ? g.clips[0] : null);
|
||||
if (!repVideo) return;
|
||||
|
||||
const isExpanded = !!window._expandedVideoGroups[g.stem];
|
||||
const repVideoSafe = repVideo.replace(/'/g, "\\'");
|
||||
const isSelected = _sceneVideo === repVideo || (g.video && _sceneVideo === g.video);
|
||||
|
||||
const selectedClip = hasClips ? g.clips.find(clip => _sceneVideo === clip) : null;
|
||||
const selectedFrame = hasFrames ? g.frames.find(frame => _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === frame) : null;
|
||||
const rowBorder = (isSelected || selectedClip || selectedFrame) ? 'border-color:#6d28d9;background:#1e1b4b' : 'border-color:#27272a;background:#09090b';
|
||||
|
||||
html += `
|
||||
<div style="background:#141414;border:1px solid #222;border-radius:6px;padding:8px">
|
||||
<div style="font-size:12px;font-weight:bold;color:#ccc;margin-bottom:6px;display:flex;align-items:center;justify-content:space-between">
|
||||
<span>📹 ${escHtml(g.stem)}</span>
|
||||
<span style="font-size:10px;color:#666">${g.video ? 'source video' : 'no source'}</span>
|
||||
<div style="border:1px solid;border-radius:6px;padding:8px;display:flex;flex-direction:column;gap:8px;transition:all 0.15s;${rowBorder}">
|
||||
<div style="display:flex;align-items:center;gap:10px">
|
||||
<div style="width:70px;height:50px;flex-shrink:0;position:relative;border-radius:4px;overflow:hidden;background:#000">
|
||||
<div class="sb-template-card compact-card ${isSelected && !selectedClip ? 'selected' : ''}"
|
||||
onclick="sceneSelectVideo('${repVideoSafe}')"
|
||||
onmouseenter="_tplPlay(this,'${repVideoSafe}')" onmouseleave="_tplStop(this)"
|
||||
style="width:100%;height:100%;margin:0;border:none">
|
||||
<img src="${API}/wireframe/frame/${encodeURIComponent(repVideo)}?t=0" loading="lazy" style="width:100%;height:100%;object-fit:cover" alt="">
|
||||
</div>
|
||||
</div>
|
||||
<div style="flex-grow:1;min-width:0">
|
||||
<div style="font-size:12px;font-weight:bold;color:#f4f4f5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis" title="${escHtml(g.stem)}">
|
||||
📹 ${escHtml(g.stem)}
|
||||
</div>
|
||||
<div style="font-size:10px;color:#71717a;margin-top:2px;display:flex;align-items:center;gap:4px">
|
||||
<span>${g.video ? 'Full video' : 'Clips only'}</span>
|
||||
${selectedClip ? `<span style="color:#a78bfa;font-weight:bold">· Selected clip: ${escHtml(selectedClip.replace(g.stem + '_', '').replace('.mp4', ''))}</span>` : ''}
|
||||
${selectedFrame ? `<span style="color:#a78bfa;font-weight:bold">· Selected frame</span>` : ''}
|
||||
${isSelected && !selectedClip ? `<span style="color:#a78bfa;font-weight:bold">· Selected</span>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:4px;flex-shrink:0">
|
||||
${(hasClips || hasFrames) ? `
|
||||
<button class="sb-btn" onclick="event.stopPropagation();toggleGroupClips('${g.stem.replace(/'/g, "\\'")}')"
|
||||
style="font-size:10px;padding:3px 6px;line-height:1;background:#27272a;color:#d4d4d8;border:1px solid #3f3f46;display:flex;align-items:center;gap:2px">
|
||||
<span>Clips/Frames</span>
|
||||
<span>${isExpanded ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// Render main video if exists
|
||||
if (g.video) {
|
||||
html += '<div class="sb-template-grid" style="margin-bottom:6px">';
|
||||
const vSafe = g.video.replace(/'/g, "\\'");
|
||||
html += _tplCardHTML(g.video, _sceneVideo === g.video, `sceneSelectVideo('${vSafe}')`);
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Render trimmed clips if they exist
|
||||
if (hasClips) {
|
||||
html += '<div style="font-size:10px;color:#888;margin:4px 0 2px">✂ Trimmed Clips:</div>';
|
||||
html += '<div class="sb-template-grid" style="margin-bottom:6px">';
|
||||
g.clips.forEach(clip => {
|
||||
const cSafe = clip.replace(/'/g, "\\'");
|
||||
html += _tplCardHTML(clip, _sceneVideo === clip, `sceneSelectVideo('${cSafe}')`);
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
// Render extracted frames if they exist
|
||||
if (hasFrames) {
|
||||
html += '<div style="font-size:10px;color:#888;margin:4px 0 2px">🖼 Extracted Frames:</div>';
|
||||
html += '<div style="display:flex;flex-wrap:wrap;gap:4px">';
|
||||
g.frames.forEach(frame => {
|
||||
const fSafe = frame.replace(/'/g, "\\'");
|
||||
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === frame;
|
||||
const borderStyle = isSelected ? 'border:2px solid #2563eb' : 'border:1px solid #333';
|
||||
|
||||
if ((hasClips || hasFrames) && isExpanded) {
|
||||
html += `
|
||||
<div style="border-top:1px solid #27272a;padding-top:6px;margin-top:2px;display:flex;flex-direction:column;gap:6px">`;
|
||||
|
||||
if (hasClips) {
|
||||
html += `
|
||||
<div style="cursor:pointer;position:relative" title="${escHtml(frame)}" onclick="sceneSelectWireframeImage('${fSafe}')">
|
||||
<img src="${API}/wireframe/${encodeURIComponent(frame)}" style="width:48px;height:48px;object-fit:cover;border-radius:4px;${borderStyle}">
|
||||
<div>
|
||||
<div style="font-size:9px;color:#71717a;text-transform:uppercase;font-weight:bold;letter-spacing:0.5px;margin-bottom:4px">Sub-Clips</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:4px">`;
|
||||
|
||||
g.clips.forEach(clip => {
|
||||
const cSafe = clip.replace(/'/g, "\\'");
|
||||
const isClipSelected = _sceneVideo === clip;
|
||||
const clipLabel = clip.replace(g.stem + '_', '').replace('.mp4', '');
|
||||
const pillBg = isClipSelected ? 'background:#6d28d9;color:#fff;border-color:#8b5cf6' : 'background:#18181b;color:#a1a1aa;border-color:#27272a';
|
||||
|
||||
html += `
|
||||
<button class="sb-btn" onclick="sceneSelectVideo('${cSafe}')"
|
||||
style="font-size:10px;padding:2px 6px;border-radius:3px;border:1px solid;cursor:pointer;transition:all 0.1s;${pillBg}">
|
||||
${escHtml(clipLabel)}
|
||||
</button>`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
}
|
||||
|
||||
if (hasFrames) {
|
||||
html += `
|
||||
<div>
|
||||
<div style="font-size:9px;color:#71717a;text-transform:uppercase;font-weight:bold;letter-spacing:0.5px;margin-bottom:4px">Extracted Frames</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:4px">`;
|
||||
|
||||
g.frames.forEach(frame => {
|
||||
const fSafe = frame.replace(/'/g, "\\'");
|
||||
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === frame;
|
||||
const borderStyle = isSelected ? 'border:2px solid #8b5cf6' : 'border:1px solid #27272a';
|
||||
html += `
|
||||
<div style="cursor:pointer;position:relative" title="${escHtml(frame)}" onclick="sceneSelectWireframeImage('${fSafe}')">
|
||||
<img src="${API}/wireframe/${encodeURIComponent(frame)}" style="width:40px;height:40px;object-fit:cover;border-radius:4px;${borderStyle}">
|
||||
</div>`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += `
|
||||
</div>`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
|
||||
html += `</div>`;
|
||||
});
|
||||
html += '</div>';
|
||||
} else if (availableVideos.length) {
|
||||
|
||||
Reference in New Issue
Block a user