tester343 commited on
Commit
1481afd
·
verified ·
1 Parent(s): daec359

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +105 -19
app_enhanced.py CHANGED
@@ -386,7 +386,16 @@ INDEX_HTML = '''
386
  .comic-grid { width: 100%; height: 100%; position: relative; background: #ffffff; }
387
 
388
  /* Panel Background White */
389
- .panel { position: absolute; overflow: hidden; background: #ffffff; cursor: pointer; border: 0; display:flex; justify-content:center; align-items:center; }
 
 
 
 
 
 
 
 
 
390
  .panel.selected { z-index: 20; }
391
  .panel.selected img { outline: 3px solid #2196F3; outline-offset: -3px; }
392
 
@@ -438,12 +447,17 @@ INDEX_HTML = '''
438
  /*
439
  IMAGE FIT: 'contain' ensures full image is visible (100% fit) without crop.
440
  Gaps are handled by the white background of the panel/grid.
 
441
  */
442
  .panel img {
443
- width: 100%; height: 100%;
 
444
  object-fit: contain; /* DEFAULT: Show full image, padded with white */
445
- transition: transform 0.1s ease-out;
446
- transform-origin: center center;
 
 
 
447
  }
448
  /* Toggle classes */
449
  .panel img.fit-cover { object-fit: cover !important; }
@@ -786,7 +800,7 @@ INDEX_HTML = '''
786
  img.dataset.zoom = pan.zoom || 100; img.dataset.translateX = pan.tx || 0; img.dataset.translateY = pan.ty || 0;
787
 
788
  // RESTORE FIT STATE
789
- if(pan.fit) img.style.objectFit = pan.fit;
790
 
791
  updateImageTransform(img);
792
  img.onmousedown = (e) => startPan(e, img);
@@ -885,6 +899,7 @@ INDEX_HTML = '''
885
  document.addEventListener('mousemove', (e) => {
886
  if(isDragging && selectedBubble) { selectedBubble.style.left = (initX + e.clientX - startX) + 'px'; selectedBubble.style.top = (initY + e.clientY - startY) + 'px'; }
887
  if(isResizing && selectedBubble) { resizeBubble(e); }
 
888
  if(isPanning && selectedPanel) { panImage(e); }
889
  });
890
 
@@ -974,25 +989,86 @@ INDEX_HTML = '''
974
  document.getElementById('bubble-text-color').addEventListener('change', (e) => { if(selectedBubble) { selectedBubble.style.setProperty('--bubble-text-color', e.target.value); saveDraft(true); } });
975
  document.getElementById('bubble-fill-color').addEventListener('change', (e) => { if(selectedBubble) { selectedBubble.style.setProperty('--bubble-fill-color', e.target.value); saveDraft(true); } });
976
 
977
- function handleZoom(el) { if(!selectedPanel) return; const img = selectedPanel.querySelector('img'); img.dataset.zoom = el.value; updateImageTransform(img); }
 
 
 
 
 
 
 
 
 
 
 
 
978
  document.getElementById('zoom-slider').addEventListener('change', () => saveDraft(true));
979
- function startPan(e, img) { if(parseFloat(img.dataset.zoom || 100) <= 100) return; e.preventDefault(); isPanning = true; selectedPanel = img.closest('.panel'); panStartX = e.clientX; panStartY = e.clientY; panStartTx = parseFloat(img.dataset.translateX || 0); panStartTy = parseFloat(img.dataset.translateY || 0); img.classList.add('panning'); }
980
- function panImage(e) { if(!isPanning || !selectedPanel) return; const img = selectedPanel.querySelector('img'); img.dataset.translateX = panStartTx + (e.clientX - panStartX); img.dataset.translateY = panStartTy + (e.clientY - panStartY); updateImageTransform(img); }
981
- 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', z > 1); }
982
- 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); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
 
984
  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(); }
985
  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); }
986
  async function gotoTimestamp() { if(isProcessing) return; if(!selectedPanel) return alert("Select a panel"); let v = document.getElementById('timestamp-input').value.trim(); if(!v) return; if(v.includes(':')) { let p = v.split(':'); v = parseInt(p[0]) * 60 + parseFloat(p[1]); } else { v = parseFloat(v); } if(isNaN(v)) return alert("Invalid time"); 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(`/goto_timestamp?sid=${sid}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({filename:fname, timestamp:v}) }); const d = await r.json(); if(d.success) { img.src = `/frames/${fname}?sid=${sid}&t=${Date.now()}`; document.getElementById('timestamp-input').value = ''; } else { alert('Error: ' + d.message); } } catch(e) { console.error(e); } img.style.opacity = '1'; setProcessing(false); saveDraft(true); }
987
 
988
- // NEW: Toggle between 'fill' (stretch 100%), 'cover' (crop), and 'contain' (bars)
989
  function toggleFitCover() {
990
  if(!selectedPanel) return alert("Select a panel");
991
  const img = selectedPanel.querySelector('img');
992
- const current = img.style.objectFit || 'contain';
993
- if(current === 'contain') img.style.objectFit = 'fill';
994
- else if(current === 'fill') img.style.objectFit = 'cover';
995
- else img.style.objectFit = 'contain';
 
 
 
 
 
 
 
 
 
 
996
  saveDraft(true);
997
  }
998
 
@@ -1005,6 +1081,13 @@ INDEX_HTML = '''
1005
  if(selectedPanel) selectedPanel.classList.remove('selected');
1006
  alert(`Exporting ${pgs.length} page(s)...`);
1007
 
 
 
 
 
 
 
 
1008
  // --- 0% ERROR FIX ---
1009
  // 1. Lock specific pixel dimensions + 1px buffer to prevent word wrapping
1010
  const bubbles = document.querySelectorAll('.speech-bubble');
@@ -1022,8 +1105,8 @@ INDEX_HTML = '''
1022
  for(let i = 0; i < pgs.length; i++) {
1023
  try {
1024
  const u = await htmlToImage.toPng(pgs[i], {
1025
- pixelRatio: 2, // High quality
1026
- style: { transform: 'none' }
1027
  });
1028
  const a = document.createElement('a');
1029
  a.href = u;
@@ -1035,8 +1118,11 @@ INDEX_HTML = '''
1035
  }
1036
  }
1037
 
1038
- // Optional: Reload page or reset styles if user wants to continue editing immediately
1039
- // In this app, styles are locked until reload, which is safer for the export focus.
 
 
 
1040
  }
1041
 
1042
  function goBackToUpload() { if(confirm('Go home? Unsaved changes will be lost.')) { document.getElementById('editor-container').style.display = 'none'; document.getElementById('upload-container').style.display = 'flex'; document.getElementById('loading-view').style.display = 'none'; } }
 
386
  .comic-grid { width: 100%; height: 100%; position: relative; background: #ffffff; }
387
 
388
  /* Panel Background White */
389
+ .panel {
390
+ position: absolute;
391
+ overflow: hidden;
392
+ background: #ffffff;
393
+ cursor: pointer;
394
+ border: 0;
395
+ display:flex;
396
+ justify-content:center;
397
+ align-items:center; /* Center image within the panel if object-fit: contain */
398
+ }
399
  .panel.selected { z-index: 20; }
400
  .panel.selected img { outline: 3px solid #2196F3; outline-offset: -3px; }
401
 
 
447
  /*
448
  IMAGE FIT: 'contain' ensures full image is visible (100% fit) without crop.
449
  Gaps are handled by the white background of the panel/grid.
450
+ Removed all transform:scale from here.
451
  */
452
  .panel img {
453
+ width: 100%;
454
+ height: 100%;
455
  object-fit: contain; /* DEFAULT: Show full image, padded with white */
456
+ image-rendering: auto; /* Ensures no blur from scaling */
457
+ transform: none !important; /* Force no JS transforms on image itself */
458
+ max-width: 100%; /* Force original size */
459
+ max-height: 100%; /* Force original size */
460
+ display: block; /* Important for proper centering and sizing */
461
  }
462
  /* Toggle classes */
463
  .panel img.fit-cover { object-fit: cover !important; }
 
800
  img.dataset.zoom = pan.zoom || 100; img.dataset.translateX = pan.tx || 0; img.dataset.translateY = pan.ty || 0;
801
 
802
  // RESTORE FIT STATE
803
+ img.style.objectFit = pan.fit || 'contain'; // Default to 'contain' to avoid initial crop
804
 
805
  updateImageTransform(img);
806
  img.onmousedown = (e) => startPan(e, img);
 
899
  document.addEventListener('mousemove', (e) => {
900
  if(isDragging && selectedBubble) { selectedBubble.style.left = (initX + e.clientX - startX) + 'px'; selectedBubble.style.top = (initY + e.clientY - startY) + 'px'; }
901
  if(isResizing && selectedBubble) { resizeBubble(e); }
902
+ // No panning when object-fit is contain (unless zoomed manually via slider)
903
  if(isPanning && selectedPanel) { panImage(e); }
904
  });
905
 
 
989
  document.getElementById('bubble-text-color').addEventListener('change', (e) => { if(selectedBubble) { selectedBubble.style.setProperty('--bubble-text-color', e.target.value); saveDraft(true); } });
990
  document.getElementById('bubble-fill-color').addEventListener('change', (e) => { if(selectedBubble) { selectedBubble.style.setProperty('--bubble-fill-color', e.target.value); saveDraft(true); } });
991
 
992
+ function handleZoom(el) {
993
+ if(!selectedPanel) return;
994
+ const img = selectedPanel.querySelector('img');
995
+ img.dataset.zoom = el.value;
996
+ // Only apply transform:scale if zoom > 100 and object-fit is NOT contain/fill
997
+ if(parseFloat(el.value) > 100 && img.style.objectFit !== 'contain' && img.style.objectFit !== 'fill') {
998
+ updateImageTransform(img);
999
+ } else if (parseFloat(el.value) === 100) {
1000
+ img.style.transform = 'none'; // Reset transform if zoom is 100
1001
+ img.dataset.translateX = 0; // Reset pan as well
1002
+ img.dataset.translateY = 0;
1003
+ }
1004
+ }
1005
  document.getElementById('zoom-slider').addEventListener('change', () => saveDraft(true));
1006
+ function startPan(e, img) {
1007
+ // Only allow pan if image is 'cover' or zoomed in
1008
+ if((img.style.objectFit === 'contain' && parseFloat(img.dataset.zoom || 100) <= 100) || img.style.objectFit === 'fill') return;
1009
+ e.preventDefault();
1010
+ isPanning = true;
1011
+ selectedPanel = img.closest('.panel');
1012
+ panStartX = e.clientX;
1013
+ panStartY = e.clientY;
1014
+ panStartTx = parseFloat(img.dataset.translateX || 0);
1015
+ panStartTy = parseFloat(img.dataset.translateY || 0);
1016
+ img.classList.add('panning');
1017
+ }
1018
+ function panImage(e) {
1019
+ if(!isPanning || !selectedPanel) return;
1020
+ const img = selectedPanel.querySelector('img');
1021
+ img.dataset.translateX = panStartTx + (e.clientX - panStartX);
1022
+ img.dataset.translateY = panStartTy + (e.clientY - panStartY);
1023
+ updateImageTransform(img);
1024
+ }
1025
+ function updateImageTransform(img) {
1026
+ // THIS FUNCTION IS NOW MODIFIED TO ONLY APPLY TRANSFORM IF NECESSARY
1027
+ const z = (img.dataset.zoom || 100) / 100;
1028
+ const x = img.dataset.translateX || 0;
1029
+ const y = img.dataset.translateY || 0;
1030
+
1031
+ // If zoom is 100 and object-fit is 'contain' or 'fill', don't apply transform
1032
+ if (z === 1 && (img.style.objectFit === 'contain' || img.style.objectFit === 'fill')) {
1033
+ img.style.transform = 'none';
1034
+ } else {
1035
+ img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${z})`;
1036
+ }
1037
+ img.classList.toggle('pannable', z > 1 || img.style.objectFit === 'cover');
1038
+ }
1039
+ function resetPanelTransform() {
1040
+ if(!selectedPanel) return alert("Select a panel");
1041
+ const img = selectedPanel.querySelector('img');
1042
+ img.dataset.zoom = 100;
1043
+ img.dataset.translateX = 0;
1044
+ img.dataset.translateY = 0;
1045
+ document.getElementById('zoom-slider').value = 100;
1046
+ updateImageTransform(img);
1047
+ saveDraft(true);
1048
+ }
1049
 
1050
  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(); }
1051
  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); }
1052
  async function gotoTimestamp() { if(isProcessing) return; if(!selectedPanel) return alert("Select a panel"); let v = document.getElementById('timestamp-input').value.trim(); if(!v) return; if(v.includes(':')) { let p = v.split(':'); v = parseInt(p[0]) * 60 + parseFloat(p[1]); } else { v = parseFloat(v); } if(isNaN(v)) return alert("Invalid time"); 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(`/goto_timestamp?sid=${sid}`, { method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({filename:fname, timestamp:v}) }); const d = await r.json(); if(d.success) { img.src = `/frames/${fname}?sid=${sid}&t=${Date.now()}`; document.getElementById('timestamp-input').value = ''; } else { alert('Error: ' + d.message); } } catch(e) { console.error(e); } img.style.opacity = '1'; setProcessing(false); saveDraft(true); }
1053
 
1054
+ // NEW: Toggle between 'contain' (no crop/zoom, but bars), 'fill' (stretch, no bars), and 'cover' (crop, no bars)
1055
  function toggleFitCover() {
1056
  if(!selectedPanel) return alert("Select a panel");
1057
  const img = selectedPanel.querySelector('img');
1058
+ const current = img.style.objectFit || 'contain'; // Default if not set
1059
+ if(current === 'contain') {
1060
+ img.style.objectFit = 'fill'; // Stretch to fill
1061
+ } else if(current === 'fill') {
1062
+ img.style.objectFit = 'cover'; // Fill, but crop to maintain aspect ratio
1063
+ } else { // current === 'cover'
1064
+ img.style.objectFit = 'contain'; // Show full image
1065
+ }
1066
+ // Reset transforms when toggling object-fit to avoid weird interactions
1067
+ img.dataset.zoom = 100;
1068
+ img.dataset.translateX = 0;
1069
+ img.dataset.translateY = 0;
1070
+ document.getElementById('zoom-slider').value = 100;
1071
+ updateImageTransform(img); // Re-evaluate transform based on new object-fit
1072
  saveDraft(true);
1073
  }
1074
 
 
1081
  if(selectedPanel) selectedPanel.classList.remove('selected');
1082
  alert(`Exporting ${pgs.length} page(s)...`);
1083
 
1084
+ // --- PREPARE FOR EXPORT: Remove editor scaling ---
1085
+ const comicPages = document.querySelectorAll('.comic-page');
1086
+ comicPages.forEach(page => {
1087
+ page.style.transform = 'none'; // Remove scaling from comic-page
1088
+ page.style.marginBottom = '0'; // Remove margin compensation
1089
+ });
1090
+
1091
  // --- 0% ERROR FIX ---
1092
  // 1. Lock specific pixel dimensions + 1px buffer to prevent word wrapping
1093
  const bubbles = document.querySelectorAll('.speech-bubble');
 
1105
  for(let i = 0; i < pgs.length; i++) {
1106
  try {
1107
  const u = await htmlToImage.toPng(pgs[i], {
1108
+ pixelRatio: 2, // High quality for print
1109
+ style: { transform: 'none' } // Ensure no transforms for html-to-image
1110
  });
1111
  const a = document.createElement('a');
1112
  a.href = u;
 
1118
  }
1119
  }
1120
 
1121
+ // --- RESTORE EDITOR SCALING AFTER EXPORT ---
1122
+ comicPages.forEach(page => {
1123
+ page.style.transform = 'scale(0.6)';
1124
+ page.style.marginBottom = '-280px';
1125
+ });
1126
  }
1127
 
1128
  function goBackToUpload() { if(confirm('Go home? Unsaved changes will be lost.')) { document.getElementById('editor-container').style.display = 'none'; document.getElementById('upload-container').style.display = 'flex'; document.getElementById('loading-view').style.display = 'none'; } }