jhh6576 commited on
Commit
a1d9f17
·
verified ·
1 Parent(s): 279de67

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +109 -46
app_enhanced.py CHANGED
@@ -509,7 +509,7 @@ class EnhancedComicGenerator:
509
  .panel img { width: 100%; height: 100%; object-fit: cover; object-position: center; transition: transform 0.1s ease-out; }
510
  .panel img.pannable { cursor: grab; }
511
  .panel img.panning { cursor: grabbing; }
512
- .speech-bubble { font-family: 'Comic Neue', cursive; position: absolute; display: flex; justify-content: center; align-items: center; width: 150px; height: 80px; min-width: 50px; max-width: 220px; min-height: 30px; box-sizing: border-box; padding: 8px; box-shadow: 2px 2px 5px rgba(0,0,0,0.3); z-index: 10; cursor: move; overflow: visible; font-size: 13px; font-weight: bold; text-align: center; }
513
  .bubble-text { padding: 2px; word-wrap: break-word; }
514
  .speech-bubble.selected { outline: 2px dashed #4CAF50; }
515
  .speech-bubble textarea { position: absolute; top: 0; left: 0; width: 100%; height: 100%; box-sizing: border-box; border: 1px solid #4CAF50; background: rgba(255,255,255,0.95); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
@@ -623,6 +623,7 @@ class EnhancedComicGenerator:
623
  let currentlySelectedBubble = null;
624
  let currentlySelectedPanel = null;
625
  let isPanning = false, panStartX, panStartY, panStartTranslateX, panStartTranslateY;
 
626
 
627
  function renderComic(data) {
628
  const container = document.getElementById('comic-pages');
@@ -685,8 +686,6 @@ class EnhancedComicGenerator:
685
  bubble.addEventListener('dblclick', e => { e.stopPropagation(); editBubbleText(bubble); });
686
  bubble.addEventListener('mousedown', e => { e.stopPropagation(); startDrag(e); });
687
  bubble.addEventListener('click', e => { e.stopPropagation(); selectBubble(bubble); });
688
-
689
- // Add resize handles
690
  ['nw', 'ne', 'sw', 'se'].forEach(dir => {
691
  const handle = document.createElement('div');
692
  handle.className = `resize-handle ${dir}`;
@@ -762,20 +761,12 @@ class EnhancedComicGenerator:
762
  panel.classList.add('selected');
763
  currentlySelectedPanel = panel;
764
  selectBubble(null);
765
-
766
- const img = currentlySelectedPanel.querySelector('img');
767
- document.getElementById('zoom-slider').value = img.dataset.zoom || 100;
768
- document.getElementById('zoom-slider').disabled = false;
769
  }
770
 
771
  function selectBubble(bubble) {
772
  if (currentlySelectedBubble) currentlySelectedBubble.classList.remove('selected');
773
  currentlySelectedBubble = bubble;
774
- const textColorPicker = document.getElementById('bubble-text-color');
775
- const fillColorPicker = document.getElementById('bubble-fill-color');
776
- const typeSelect = document.getElementById('bubble-type-select');
777
- const fontSelect = document.getElementById('font-select');
778
- const bubbleControls = [textColorPicker, fillColorPicker, typeSelect, fontSelect];
779
 
780
  if (currentlySelectedBubble) {
781
  currentlySelectedBubble.classList.add('selected');
@@ -783,15 +774,16 @@ class EnhancedComicGenerator:
783
  currentlySelectedPanel = null;
784
 
785
  const styles = window.getComputedStyle(currentlySelectedBubble);
786
- textColorPicker.value = rgbToHex(styles.color);
787
- fillColorPicker.value = rgbToHex(styles.backgroundColor);
788
- typeSelect.value = currentlySelectedBubble.dataset.type || 'speech';
789
- fontSelect.value = styles.fontFamily.split(',')[0].replace(/"/g, "").replace(/'/g, "").trim();
790
 
791
  document.getElementById('zoom-slider').disabled = true;
792
- bubbleControls.forEach(c => c.disabled = false);
793
  } else {
794
- bubbleControls.forEach(c => c.disabled = true);
 
795
  }
796
  }
797
 
@@ -800,9 +792,8 @@ class EnhancedComicGenerator:
800
  currentlyEditing = bubble;
801
  const textSpan = bubble.querySelector('.bubble-text');
802
  const textarea = document.createElement('textarea');
803
- // FIX: Preserve height before editing
804
- const originalHeight = bubble.style.height || `${bubble.offsetHeight}px`;
805
- bubble.style.height = originalHeight;
806
 
807
  textarea.value = textSpan.textContent;
808
  bubble.appendChild(textarea);
@@ -812,9 +803,6 @@ class EnhancedComicGenerator:
812
  textSpan.textContent = textarea.value;
813
  bubble.removeChild(textarea);
814
  textSpan.style.display = '';
815
- // FIX: Re-apply height, then set to auto to allow natural flow
816
- bubble.style.height = originalHeight;
817
- setTimeout(() => { bubble.style.height = 'auto'; }, 0);
818
  currentlyEditing = null;
819
  };
820
  textarea.addEventListener('blur', finishEditing, { once: true });
@@ -851,9 +839,6 @@ class EnhancedComicGenerator:
851
  }
852
  }
853
 
854
- let isResizing = false;
855
- let resizeHandle, originalWidth, originalHeight, originalX, originalY, originalMouseX, originalMouseY;
856
-
857
  function startResize(e, dir) {
858
  e.preventDefault();
859
  e.stopPropagation();
@@ -875,25 +860,19 @@ class EnhancedComicGenerator:
875
  const dy = e.clientY - originalMouseY;
876
  const bubble = currentlySelectedBubble;
877
 
878
- if (resizeHandle.includes('e')) {
879
- bubble.style.width = `${originalWidth + dx}px`;
880
- }
881
  if (resizeHandle.includes('w')) {
882
  bubble.style.width = `${originalWidth - dx}px`;
883
  bubble.style.left = `${originalX + dx}px`;
884
  }
885
- if (resizeHandle.includes('s')) {
886
- bubble.style.height = `${originalHeight + dy}px`;
887
- }
888
  if (resizeHandle.includes('n')) {
889
  bubble.style.height = `${originalHeight - dy}px`;
890
  bubble.style.top = `${originalY + dy}px`;
891
  }
892
  }
893
 
894
- function stopResize() {
895
- isResizing = false;
896
- }
897
 
898
  function clearSavedState() {
899
  if (confirm("Reset all edits?")) {
@@ -903,7 +882,18 @@ class EnhancedComicGenerator:
903
  }
904
 
905
  async function exportPagesToPNG() {
906
- // function content...
 
 
 
 
 
 
 
 
 
 
 
907
  }
908
 
909
  function replacePanelImage() {
@@ -921,8 +911,8 @@ class EnhancedComicGenerator:
921
  .then(data => {
922
  if (data.success) {
923
  img.src = `/frames/final/${data.new_filename}?t=${new Date().getTime()}`;
924
- img.style.objectFit = 'contain'; // FIX: Show full image
925
- resetPanelTransform(); // FIX: Reset any old zoom/pan
926
  } else { alert('Error replacing image: ' + data.error); }
927
  img.style.opacity = '1';
928
  })
@@ -936,15 +926,41 @@ class EnhancedComicGenerator:
936
  }
937
 
938
  function adjustFrame(direction) {
939
- // function content...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
940
  }
941
 
942
  function updateImageTransform(img) {
943
- // function content...
 
 
 
 
944
  }
945
 
946
  function handleZoom(event) {
947
- // function content...
 
 
 
948
  }
949
 
950
  function resetPanelTransform() {
@@ -958,15 +974,30 @@ class EnhancedComicGenerator:
958
  }
959
 
960
  function startPan(event) {
961
- // function content...
 
 
 
 
 
 
 
 
 
962
  }
963
 
964
  function panImage(event) {
965
- // function content...
 
 
 
 
966
  }
967
 
968
  function stopPan() {
969
- // function content...
 
 
970
  }
971
 
972
  function addBubbleToPanel() {
@@ -984,7 +1015,39 @@ class EnhancedComicGenerator:
984
  }
985
 
986
  function gotoTimestamp() {
987
- // function content...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
988
  }
989
  </script>
990
  </body>
 
509
  .panel img { width: 100%; height: 100%; object-fit: cover; object-position: center; transition: transform 0.1s ease-out; }
510
  .panel img.pannable { cursor: grab; }
511
  .panel img.panning { cursor: grabbing; }
512
+ .speech-bubble { font-family: 'Comic Neue', cursive; position: absolute; display: flex; justify-content: center; align-items: center; width: 150px; height: 80px; min-width: 50px; min-height: 30px; box-sizing: border-box; padding: 8px; box-shadow: 2px 2px 5px rgba(0,0,0,0.3); z-index: 10; cursor: move; overflow: visible; font-size: 13px; font-weight: bold; text-align: center; }
513
  .bubble-text { padding: 2px; word-wrap: break-word; }
514
  .speech-bubble.selected { outline: 2px dashed #4CAF50; }
515
  .speech-bubble textarea { position: absolute; top: 0; left: 0; width: 100%; height: 100%; box-sizing: border-box; border: 1px solid #4CAF50; background: rgba(255,255,255,0.95); font: inherit; text-align: center; resize: none; padding: 8px; z-index: 102; }
 
623
  let currentlySelectedBubble = null;
624
  let currentlySelectedPanel = null;
625
  let isPanning = false, panStartX, panStartY, panStartTranslateX, panStartTranslateY;
626
+ let isResizing = false, resizeHandle, originalWidth, originalHeight, originalX, originalY, originalMouseX, originalMouseY;
627
 
628
  function renderComic(data) {
629
  const container = document.getElementById('comic-pages');
 
686
  bubble.addEventListener('dblclick', e => { e.stopPropagation(); editBubbleText(bubble); });
687
  bubble.addEventListener('mousedown', e => { e.stopPropagation(); startDrag(e); });
688
  bubble.addEventListener('click', e => { e.stopPropagation(); selectBubble(bubble); });
 
 
689
  ['nw', 'ne', 'sw', 'se'].forEach(dir => {
690
  const handle = document.createElement('div');
691
  handle.className = `resize-handle ${dir}`;
 
761
  panel.classList.add('selected');
762
  currentlySelectedPanel = panel;
763
  selectBubble(null);
 
 
 
 
764
  }
765
 
766
  function selectBubble(bubble) {
767
  if (currentlySelectedBubble) currentlySelectedBubble.classList.remove('selected');
768
  currentlySelectedBubble = bubble;
769
+ const bubbleControls = ['bubble-text-color', 'bubble-fill-color', 'bubble-type-select', 'font-select'];
 
 
 
 
770
 
771
  if (currentlySelectedBubble) {
772
  currentlySelectedBubble.classList.add('selected');
 
774
  currentlySelectedPanel = null;
775
 
776
  const styles = window.getComputedStyle(currentlySelectedBubble);
777
+ document.getElementById('bubble-text-color').value = rgbToHex(styles.color);
778
+ document.getElementById('bubble-fill-color').value = rgbToHex(styles.backgroundColor);
779
+ document.getElementById('bubble-type-select').value = currentlySelectedBubble.dataset.type || 'speech';
780
+ document.getElementById('font-select').value = styles.fontFamily.split(',')[0].replace(/"/g, "").replace(/'/g, "").trim();
781
 
782
  document.getElementById('zoom-slider').disabled = true;
783
+ bubbleControls.forEach(id => document.getElementById(id).disabled = false);
784
  } else {
785
+ bubbleControls.forEach(id => document.getElementById(id).disabled = true);
786
+ if(currentlySelectedPanel) document.getElementById('zoom-slider').disabled = false;
787
  }
788
  }
789
 
 
792
  currentlyEditing = bubble;
793
  const textSpan = bubble.querySelector('.bubble-text');
794
  const textarea = document.createElement('textarea');
795
+ const originalHeight = bubble.offsetHeight;
796
+ bubble.style.height = `${originalHeight}px`;
 
797
 
798
  textarea.value = textSpan.textContent;
799
  bubble.appendChild(textarea);
 
803
  textSpan.textContent = textarea.value;
804
  bubble.removeChild(textarea);
805
  textSpan.style.display = '';
 
 
 
806
  currentlyEditing = null;
807
  };
808
  textarea.addEventListener('blur', finishEditing, { once: true });
 
839
  }
840
  }
841
 
 
 
 
842
  function startResize(e, dir) {
843
  e.preventDefault();
844
  e.stopPropagation();
 
860
  const dy = e.clientY - originalMouseY;
861
  const bubble = currentlySelectedBubble;
862
 
863
+ if (resizeHandle.includes('e')) bubble.style.width = `${originalWidth + dx}px`;
 
 
864
  if (resizeHandle.includes('w')) {
865
  bubble.style.width = `${originalWidth - dx}px`;
866
  bubble.style.left = `${originalX + dx}px`;
867
  }
868
+ if (resizeHandle.includes('s')) bubble.style.height = `${originalHeight + dy}px`;
 
 
869
  if (resizeHandle.includes('n')) {
870
  bubble.style.height = `${originalHeight - dy}px`;
871
  bubble.style.top = `${originalY + dy}px`;
872
  }
873
  }
874
 
875
+ function stopResize() { isResizing = false; }
 
 
876
 
877
  function clearSavedState() {
878
  if (confirm("Reset all edits?")) {
 
882
  }
883
 
884
  async function exportPagesToPNG() {
885
+ const pages = document.querySelectorAll('.comic-page');
886
+ if (pages.length === 0) return alert("No pages found.");
887
+ alert(`Starting export of ${pages.length} page(s).`);
888
+ for (let i = 0; i < pages.length; i++) {
889
+ try {
890
+ const canvas = await html2canvas(pages[i], { scale: 2 });
891
+ const link = document.createElement('a');
892
+ link.download = `comic-page-${i + 1}.png`;
893
+ link.href = canvas.toDataURL('image/png');
894
+ link.click();
895
+ } catch (err) { alert(`Failed to export page ${i + 1}.`); }
896
+ }
897
  }
898
 
899
  function replacePanelImage() {
 
911
  .then(data => {
912
  if (data.success) {
913
  img.src = `/frames/final/${data.new_filename}?t=${new Date().getTime()}`;
914
+ img.style.objectFit = 'contain';
915
+ resetPanelTransform();
916
  } else { alert('Error replacing image: ' + data.error); }
917
  img.style.opacity = '1';
918
  })
 
926
  }
927
 
928
  function adjustFrame(direction) {
929
+ if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
930
+ const img = currentlySelectedPanel.querySelector('img');
931
+ let filename = img.src.substring(img.src.lastIndexOf('/') + 1).split('?')[0];
932
+ img.style.opacity = '0.5';
933
+ fetch('/regenerate_frame', {
934
+ method: 'POST',
935
+ headers: { 'Content-Type': 'application/json' },
936
+ body: JSON.stringify({ filename, direction })
937
+ })
938
+ .then(res => res.json())
939
+ .then(data => {
940
+ if (data.success) {
941
+ img.src = `/frames/final/${filename}?t=${new Date().getTime()}`;
942
+ } else { alert('Error: ' + data.message); }
943
+ img.style.opacity = '1';
944
+ })
945
+ .catch(() => {
946
+ alert('An error occurred.');
947
+ img.style.opacity = '1';
948
+ });
949
  }
950
 
951
  function updateImageTransform(img) {
952
+ const zoom = (img.dataset.zoom || 100) / 100;
953
+ const x = img.dataset.translateX || 0;
954
+ const y = img.dataset.translateY || 0;
955
+ img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${zoom})`;
956
+ img.classList.toggle('pannable', zoom > 1);
957
  }
958
 
959
  function handleZoom(event) {
960
+ if (!currentlySelectedPanel) return;
961
+ const img = currentlySelectedPanel.querySelector('img');
962
+ img.dataset.zoom = event.target.value;
963
+ updateImageTransform(img);
964
  }
965
 
966
  function resetPanelTransform() {
 
974
  }
975
 
976
  function startPan(event) {
977
+ if (event.button !== 0) return;
978
+ const img = event.target;
979
+ if (parseFloat(img.dataset.zoom || 100) <= 100) return;
980
+ event.preventDefault();
981
+ isPanning = true;
982
+ img.classList.add('panning');
983
+ panStartX = event.clientX;
984
+ panStartY = event.clientY;
985
+ panStartTranslateX = parseFloat(img.dataset.translateX || 0);
986
+ panStartTranslateY = parseFloat(img.dataset.translateY || 0);
987
  }
988
 
989
  function panImage(event) {
990
+ if (!isPanning || !currentlySelectedPanel) return;
991
+ const img = currentlySelectedPanel.querySelector('img');
992
+ img.dataset.translateX = panStartTranslateX + (event.clientX - panStartX);
993
+ img.dataset.translateY = panStartTranslateY + (event.clientY - panStartY);
994
+ updateImageTransform(img);
995
  }
996
 
997
  function stopPan() {
998
+ if (!isPanning) return;
999
+ isPanning = false;
1000
+ currentlySelectedPanel?.querySelector('img')?.classList.remove('panning');
1001
  }
1002
 
1003
  function addBubbleToPanel() {
 
1015
  }
1016
 
1017
  function gotoTimestamp() {
1018
+ if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
1019
+ const input = document.getElementById('timestamp-input');
1020
+ const timeStr = input.value.trim();
1021
+ if (!timeStr) return;
1022
+ let parsedSeconds = 0;
1023
+ if (timeStr.includes(':')) {
1024
+ const parts = timeStr.split(':');
1025
+ parsedSeconds = parseInt(parts[0], 10) * 60 + parseFloat(parts[1]);
1026
+ } else {
1027
+ parsedSeconds = parseFloat(timeStr);
1028
+ }
1029
+ if (isNaN(parsedSeconds)) { alert("Invalid time format."); return; }
1030
+ const img = currentlySelectedPanel.querySelector('img');
1031
+ let filename = img.src.substring(img.src.lastIndexOf('/') + 1).split('?')[0];
1032
+ img.style.opacity = '0.5';
1033
+ fetch('/goto_timestamp', {
1034
+ method: 'POST',
1035
+ headers: { 'Content-Type': 'application/json' },
1036
+ body: JSON.stringify({ filename, timestamp: parsedSeconds })
1037
+ })
1038
+ .then(res => res.json())
1039
+ .then(data => {
1040
+ if (data.success) {
1041
+ img.src = `/frames/final/${filename}?t=${new Date().getTime()}`;
1042
+ input.value = '';
1043
+ resetPanelTransform();
1044
+ } else { alert('Error: ' + data.message); }
1045
+ img.style.opacity = '1';
1046
+ })
1047
+ .catch(() => {
1048
+ alert('An error occurred.');
1049
+ img.style.opacity = '1';
1050
+ });
1051
  }
1052
  </script>
1053
  </body>