tester343 commited on
Commit
ab71d8e
·
verified ·
1 Parent(s): 55eb357

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. 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
- .comic-page { background: white; width: 600px; height: 400px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); position: relative; overflow: hidden; border: 2px solid #000; padding: 10px; }
 
 
 
 
 
 
 
 
 
 
334
 
335
- /* === LAYOUTS === */
336
- .comic-grid { width: 100%; height: 100%; position: relative; background: #000; display:grid; gap: 10px; }
337
 
338
- .comic-grid.layout-rows { grid-template-columns: 1fr; grid-auto-rows: 1fr; }
339
- .comic-grid.layout-cols { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); grid-template-rows: 1fr; }
340
- .comic-grid.layout-grid { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
341
 
342
- /* === CUSTOM SLANT LAYOUT === */
343
- .comic-grid.layout-custom-slant { display: block; position: relative; }
344
- .comic-grid.layout-custom-slant .panel { position: absolute; width: 100%; border: none; background: transparent; }
345
- /* Panel 1 (Top) */
346
- .comic-grid.layout-custom-slant .panel:nth-child(1) { top: 0; height: 100%; z-index:2; clip-path: polygon(0 0, 100% 0, 100% var(--split-r, 55%), 0 var(--split-l, 45%)); }
347
- /* Panel 2 (Bottom) */
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 ' + (page.layout || 'layout-rows');
727
- if(page.layout === 'layout-custom-slant') {
728
- grid.style.setProperty('--split-l', page.splitL || '45%');
729
- grid.style.setProperty('--split-r', page.splitR || '55%');
730
-
731
- // Add handles
732
- const hL = document.createElement('div'); hL.className = 'split-handle left';
733
- hL.onmousedown = (e) => startSplitDrag(e, hL, grid, 'left');
734
- const hR = document.createElement('div'); hR.className = 'split-handle right';
735
- hR.onmousedown = (e) => startSplitDrag(e, hR, grid, 'right');
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); }