Update app_enhanced.py
Browse files- app_enhanced.py +50 -54
app_enhanced.py
CHANGED
|
@@ -330,41 +330,55 @@ INDEX_HTML = '''
|
|
| 330 |
.page-wrapper { margin: 30px auto; display: flex; flex-direction: column; align-items: center; }
|
| 331 |
.page-title { text-align: center; color: #333; margin-bottom: 10px; font-size: 18px; font-weight: bold; }
|
| 332 |
|
| 333 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 334 |
|
| 335 |
-
/* ===
|
| 336 |
-
.comic-grid { width: 100%; height: 100%; position: relative; background: #000; display:
|
| 337 |
|
| 338 |
-
.
|
| 339 |
-
.comic-grid.layout-
|
| 340 |
-
.comic-grid.layout-grid { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
|
| 341 |
|
| 342 |
-
/*
|
| 343 |
-
|
| 344 |
-
.comic-grid.layout-custom-slant .panel {
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
.comic-grid.layout-custom-slant .panel:nth-child(2) { top: 0; height: 100%; z-index:1; clip-path: polygon(0 var(--split-l, 45%), 100% var(--split-r, 55%), 100% 100%, 0 100%); }
|
| 349 |
-
.comic-grid.layout-custom-slant .panel:nth-child(n+3) { display: none; }
|
| 350 |
-
|
| 351 |
-
/* Split Handles */
|
| 352 |
-
.split-handle {
|
| 353 |
-
position: absolute; width: 20px; height: 20px; background: #2196F3; border: 2px solid white; border-radius: 50%;
|
| 354 |
-
cursor: ns-resize; z-index: 1000; box-shadow: 0 2px 5px rgba(0,0,0,0.3); display: none;
|
| 355 |
}
|
| 356 |
-
.layout-custom-slant .split-handle { display: block; }
|
| 357 |
-
.split-handle.left { left: -10px; top: var(--split-l, 45%); }
|
| 358 |
-
.split-handle.right { right: -10px; top: var(--split-r, 55%); }
|
| 359 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
|
| 361 |
-
.panel { overflow: hidden; background: #eee; cursor: pointer; border: 2px solid #000; position: relative; }
|
| 362 |
-
.layout-custom-slant .panel { border: none; }
|
| 363 |
.panel.selected { z-index: 20; border-color: #2196F3; }
|
| 364 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 365 |
.panel img {
|
| 366 |
width: 100%; height: 100%;
|
| 367 |
-
object-fit: contain;
|
| 368 |
transition: transform 0.1s ease-out;
|
| 369 |
transform-origin: center center;
|
| 370 |
pointer-events: auto;
|
|
@@ -489,16 +503,6 @@ INDEX_HTML = '''
|
|
| 489 |
|
| 490 |
<button onclick="undoLastAction()" class="undo-btn">↩️ Undo</button>
|
| 491 |
|
| 492 |
-
<div class="control-group">
|
| 493 |
-
<label>📐 Layout Style:</label>
|
| 494 |
-
<select id="layout-select" onchange="changeLayout(this.value)">
|
| 495 |
-
<option value="layout-rows">Rows (Horizontal)</option>
|
| 496 |
-
<option value="layout-cols">Cols (Vertical)</option>
|
| 497 |
-
<option value="layout-grid">Grid (2x2)</option>
|
| 498 |
-
<option value="layout-custom-slant">Custom Slant (Drag Points)</option>
|
| 499 |
-
</select>
|
| 500 |
-
</div>
|
| 501 |
-
|
| 502 |
<div class="control-group">
|
| 503 |
<label>💾 Save & Load:</label>
|
| 504 |
<button onclick="saveComic()" class="save-btn">💾 Save Comic</button>
|
|
@@ -671,10 +675,6 @@ INDEX_HTML = '''
|
|
| 671 |
const pages = [];
|
| 672 |
document.querySelectorAll('.comic-page').forEach(p => {
|
| 673 |
const grid = p.querySelector('.comic-grid');
|
| 674 |
-
let layout = 'layout-rows';
|
| 675 |
-
if(grid.classList.contains('layout-cols')) layout = 'layout-cols';
|
| 676 |
-
if(grid.classList.contains('layout-grid')) layout = 'layout-grid';
|
| 677 |
-
if(grid.classList.contains('layout-custom-slant')) layout = 'layout-custom-slant';
|
| 678 |
|
| 679 |
// Layout Split Pos
|
| 680 |
const splitL = grid.style.getPropertyValue('--split-l') || '45%';
|
|
@@ -703,7 +703,7 @@ INDEX_HTML = '''
|
|
| 703 |
bubbles: bubbles
|
| 704 |
});
|
| 705 |
});
|
| 706 |
-
pages.push({ layout: layout, splitL: splitL, splitR: splitR, panels: panels });
|
| 707 |
});
|
| 708 |
return pages;
|
| 709 |
}
|
|
@@ -723,18 +723,16 @@ INDEX_HTML = '''
|
|
| 723 |
const div = document.createElement('div'); div.className = 'comic-page';
|
| 724 |
const grid = document.createElement('div');
|
| 725 |
|
| 726 |
-
grid.className = 'comic-grid
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
grid.appendChild(hL); grid.appendChild(hR);
|
| 737 |
-
}
|
| 738 |
|
| 739 |
page.panels.forEach((pan) => {
|
| 740 |
const pDiv = document.createElement('div'); pDiv.className = 'panel';
|
|
@@ -754,7 +752,6 @@ INDEX_HTML = '''
|
|
| 754 |
grid.appendChild(pDiv);
|
| 755 |
});
|
| 756 |
div.appendChild(grid); pageWrapper.appendChild(div); con.appendChild(pageWrapper);
|
| 757 |
-
if(pageIdx === 0) document.getElementById('layout-select').value = (page.layout || 'layout-rows');
|
| 758 |
});
|
| 759 |
selectedBubble = null; selectedPanel = null;
|
| 760 |
}
|
|
@@ -856,7 +853,6 @@ INDEX_HTML = '''
|
|
| 856 |
function panImage(e) { if(!isPanning || !selectedPanel) return; const img = selectedPanel.querySelector('img'); const dx = e.clientX - panStartX; const dy = e.clientY - panStartY; img.dataset.translateX = panStartTx + dx; img.dataset.translateY = panStartTy + dy; updateImageTransform(img); }
|
| 857 |
function updateImageTransform(img) { const z = (img.dataset.zoom || 100) / 100; const x = img.dataset.translateX || 0; const y = img.dataset.translateY || 0; img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${z})`; img.classList.toggle('pannable', true); }
|
| 858 |
function resetPanelTransform() { if(!selectedPanel) return alert("Select a panel"); const img = selectedPanel.querySelector('img'); img.dataset.zoom = 100; img.dataset.translateX = 0; img.dataset.translateY = 0; document.getElementById('zoom-slider').value = 100; updateImageTransform(img); saveDraft(true); }
|
| 859 |
-
function changeLayout(newLayout) { document.querySelectorAll('.comic-grid').forEach(g => { g.className = 'comic-grid ' + newLayout; if(newLayout === 'layout-custom-slant' && !g.querySelector('.split-handle')) { const hL = document.createElement('div'); hL.className = 'split-handle left'; hL.onmousedown = (e) => startSplitDrag(e, hL, g, 'left'); const hR = document.createElement('div'); hR.className = 'split-handle right'; hR.onmousedown = (e) => startSplitDrag(e, hR, g, 'right'); g.appendChild(hL); g.appendChild(hR); } }); saveDraft(true); }
|
| 860 |
function toggleFitMode() { if(!selectedPanel) return alert("Select a panel"); const img = selectedPanel.querySelector('img'); if(img.classList.contains('fit-cover')) { img.classList.remove('fit-cover'); document.getElementById('fit-btn').innerText = "Fit: Contain"; } else { img.classList.add('fit-cover'); document.getElementById('fit-btn').innerText = "Fit: Cover"; } saveDraft(true); }
|
| 861 |
function replacePanelImage() { if(!selectedPanel) return alert("Select a panel"); const inp = document.getElementById('image-uploader'); inp.onchange = async (e) => { const fd = new FormData(); fd.append('image', e.target.files[0]); const img = selectedPanel.querySelector('img'); const r = await fetch(`/replace_panel?sid=${sid}`, {method:'POST', body:fd}); const d = await r.json(); if(d.success) { img.src = `/frames/${d.new_filename}?sid=${sid}`; saveDraft(true); } inp.value = ''; }; inp.click(); }
|
| 862 |
async function adjustFrame(dir) { if(isProcessing) return; if(!selectedPanel) return alert("Select a panel"); const img = selectedPanel.querySelector('img'); let fname = img.src.split('/').pop().split('?')[0]; setProcessing(true); img.style.opacity = '0.5'; try { const r = await fetch(`/regenerate_frame?sid=${sid}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({filename:fname, direction:dir}) }); const d = await r.json(); if(d.success) { img.src = `/frames/${fname}?sid=${sid}&t=${Date.now()}`; } else { alert('Error: ' + d.message); } } catch(e) { console.error(e); } img.style.opacity = '1'; setProcessing(false); saveDraft(true); }
|
|
|
|
| 330 |
.page-wrapper { margin: 30px auto; display: flex; flex-direction: column; align-items: center; }
|
| 331 |
.page-title { text-align: center; color: #333; margin-bottom: 10px; font-size: 18px; font-weight: bold; }
|
| 332 |
|
| 333 |
+
/* === SIZE 864x1080 === */
|
| 334 |
+
.comic-page {
|
| 335 |
+
background: white;
|
| 336 |
+
width: 864px;
|
| 337 |
+
height: 1080px;
|
| 338 |
+
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
|
| 339 |
+
position: relative;
|
| 340 |
+
overflow: hidden;
|
| 341 |
+
border: 2px solid #000;
|
| 342 |
+
padding: 10px;
|
| 343 |
+
}
|
| 344 |
|
| 345 |
+
/* === CUSTOM SLANT LAYOUT === */
|
| 346 |
+
.comic-grid { width: 100%; height: 100%; position: relative; background: #000; display: block; }
|
| 347 |
|
| 348 |
+
.panel { position: absolute; overflow: hidden; background: #000; cursor: pointer; border: 2px solid #000; }
|
| 349 |
+
.comic-grid.layout-custom-slant .panel { border: none; background: transparent; width: 100%; height: 100%; }
|
|
|
|
| 350 |
|
| 351 |
+
/* Top Panel (1) */
|
| 352 |
+
/* Clip polygon uses custom props split-l and split-r (0-100%) */
|
| 353 |
+
.comic-grid.layout-custom-slant .panel:nth-child(1) {
|
| 354 |
+
top: 0; left: 0;
|
| 355 |
+
z-index:2;
|
| 356 |
+
clip-path: polygon(0 0, 100% 0, 100% var(--split-r, 55%), 0 var(--split-l, 45%));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
}
|
|
|
|
|
|
|
|
|
|
| 358 |
|
| 359 |
+
/* Bottom Panel (2) */
|
| 360 |
+
.comic-grid.layout-custom-slant .panel:nth-child(2) {
|
| 361 |
+
top: 0; left: 0;
|
| 362 |
+
z-index:1;
|
| 363 |
+
clip-path: polygon(0 var(--split-l, 45%), 100% var(--split-r, 55%), 100% 100%, 0 100%);
|
| 364 |
+
}
|
| 365 |
|
|
|
|
|
|
|
| 366 |
.panel.selected { z-index: 20; border-color: #2196F3; }
|
| 367 |
|
| 368 |
+
/* DRAG POINTS */
|
| 369 |
+
.split-handle {
|
| 370 |
+
position: absolute; width: 24px; height: 24px;
|
| 371 |
+
background: #2196F3; border: 3px solid white; border-radius: 50%;
|
| 372 |
+
cursor: ns-resize; z-index: 1000; box-shadow: 0 2px 5px rgba(0,0,0,0.4);
|
| 373 |
+
}
|
| 374 |
+
.split-handle:hover { transform: scale(1.2); }
|
| 375 |
+
.split-handle.left { left: -12px; top: var(--split-l, 45%); }
|
| 376 |
+
.split-handle.right { right: -12px; top: var(--split-r, 55%); }
|
| 377 |
+
|
| 378 |
+
|
| 379 |
.panel img {
|
| 380 |
width: 100%; height: 100%;
|
| 381 |
+
object-fit: contain; /* DEFAULT: Contain to show full image */
|
| 382 |
transition: transform 0.1s ease-out;
|
| 383 |
transform-origin: center center;
|
| 384 |
pointer-events: auto;
|
|
|
|
| 503 |
|
| 504 |
<button onclick="undoLastAction()" class="undo-btn">↩️ Undo</button>
|
| 505 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
<div class="control-group">
|
| 507 |
<label>💾 Save & Load:</label>
|
| 508 |
<button onclick="saveComic()" class="save-btn">💾 Save Comic</button>
|
|
|
|
| 675 |
const pages = [];
|
| 676 |
document.querySelectorAll('.comic-page').forEach(p => {
|
| 677 |
const grid = p.querySelector('.comic-grid');
|
|
|
|
|
|
|
|
|
|
|
|
|
| 678 |
|
| 679 |
// Layout Split Pos
|
| 680 |
const splitL = grid.style.getPropertyValue('--split-l') || '45%';
|
|
|
|
| 703 |
bubbles: bubbles
|
| 704 |
});
|
| 705 |
});
|
| 706 |
+
pages.push({ layout: 'layout-custom-slant', splitL: splitL, splitR: splitR, panels: panels });
|
| 707 |
});
|
| 708 |
return pages;
|
| 709 |
}
|
|
|
|
| 723 |
const div = document.createElement('div'); div.className = 'comic-page';
|
| 724 |
const grid = document.createElement('div');
|
| 725 |
|
| 726 |
+
grid.className = 'comic-grid layout-custom-slant';
|
| 727 |
+
grid.style.setProperty('--split-l', page.splitL || '45%');
|
| 728 |
+
grid.style.setProperty('--split-r', page.splitR || '55%');
|
| 729 |
+
|
| 730 |
+
// Add handles
|
| 731 |
+
const hL = document.createElement('div'); hL.className = 'split-handle left';
|
| 732 |
+
hL.onmousedown = (e) => startSplitDrag(e, hL, grid, 'left');
|
| 733 |
+
const hR = document.createElement('div'); hR.className = 'split-handle right';
|
| 734 |
+
hR.onmousedown = (e) => startSplitDrag(e, hR, grid, 'right');
|
| 735 |
+
grid.appendChild(hL); grid.appendChild(hR);
|
|
|
|
|
|
|
| 736 |
|
| 737 |
page.panels.forEach((pan) => {
|
| 738 |
const pDiv = document.createElement('div'); pDiv.className = 'panel';
|
|
|
|
| 752 |
grid.appendChild(pDiv);
|
| 753 |
});
|
| 754 |
div.appendChild(grid); pageWrapper.appendChild(div); con.appendChild(pageWrapper);
|
|
|
|
| 755 |
});
|
| 756 |
selectedBubble = null; selectedPanel = null;
|
| 757 |
}
|
|
|
|
| 853 |
function panImage(e) { if(!isPanning || !selectedPanel) return; const img = selectedPanel.querySelector('img'); const dx = e.clientX - panStartX; const dy = e.clientY - panStartY; img.dataset.translateX = panStartTx + dx; img.dataset.translateY = panStartTy + dy; updateImageTransform(img); }
|
| 854 |
function updateImageTransform(img) { const z = (img.dataset.zoom || 100) / 100; const x = img.dataset.translateX || 0; const y = img.dataset.translateY || 0; img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${z})`; img.classList.toggle('pannable', true); }
|
| 855 |
function resetPanelTransform() { if(!selectedPanel) return alert("Select a panel"); const img = selectedPanel.querySelector('img'); img.dataset.zoom = 100; img.dataset.translateX = 0; img.dataset.translateY = 0; document.getElementById('zoom-slider').value = 100; updateImageTransform(img); saveDraft(true); }
|
|
|
|
| 856 |
function toggleFitMode() { if(!selectedPanel) return alert("Select a panel"); const img = selectedPanel.querySelector('img'); if(img.classList.contains('fit-cover')) { img.classList.remove('fit-cover'); document.getElementById('fit-btn').innerText = "Fit: Contain"; } else { img.classList.add('fit-cover'); document.getElementById('fit-btn').innerText = "Fit: Cover"; } saveDraft(true); }
|
| 857 |
function replacePanelImage() { if(!selectedPanel) return alert("Select a panel"); const inp = document.getElementById('image-uploader'); inp.onchange = async (e) => { const fd = new FormData(); fd.append('image', e.target.files[0]); const img = selectedPanel.querySelector('img'); const r = await fetch(`/replace_panel?sid=${sid}`, {method:'POST', body:fd}); const d = await r.json(); if(d.success) { img.src = `/frames/${d.new_filename}?sid=${sid}`; saveDraft(true); } inp.value = ''; }; inp.click(); }
|
| 858 |
async function adjustFrame(dir) { if(isProcessing) return; if(!selectedPanel) return alert("Select a panel"); const img = selectedPanel.querySelector('img'); let fname = img.src.split('/').pop().split('?')[0]; setProcessing(true); img.style.opacity = '0.5'; try { const r = await fetch(`/regenerate_frame?sid=${sid}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({filename:fname, direction:dir}) }); const d = await r.json(); if(d.success) { img.src = `/frames/${fname}?sid=${sid}&t=${Date.now()}`; } else { alert('Error: ' + d.message); } } catch(e) { console.error(e); } img.style.opacity = '1'; setProcessing(false); saveDraft(true); }
|