jhh6576 commited on
Commit
6539266
·
verified ·
1 Parent(s): fe43f3c

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +188 -31
app_enhanced.py CHANGED
@@ -545,11 +545,12 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
545
  <div class="edit-controls">
546
  <h4>✏️ Interactive Editor</h4>
547
  <div class="control-group">
548
- <label for="bubble-type-select">Bubble Tools (Select Bubble):</label>
549
  <select id="bubble-type-select" onchange="changeBubbleType(this.value)">
550
  <option value="speech">Speech</option><option value="thought">Thought</option><option value="reaction">Reaction</option><option value="narration">Narration</option><option value="idea">Idea</option>
551
  </select>
552
  <button onclick="rotateBubbleTail()" class="secondary-button">🔄 Rotate Tail</button>
 
553
  </div>
554
  <div class="control-group">
555
  <label>Panel Tools (Select Panel):</label>
@@ -563,7 +564,7 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
563
  <label>Zoom & Pan (Select Panel):</label>
564
  <div class="zoom-controls">
565
  <button onclick="resetPanelTransform()" class="secondary-button" style="padding: 4px 6px;">Reset</button>
566
- <input type="range" id="zoom-slider" min="100" max="300" value="100" step="5">
567
  </div>
568
  </div>
569
  <div class="control-group">
@@ -635,9 +636,9 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
635
 
636
  function initializePanelImageEvents(img) {
637
  img.addEventListener('mousedown', startPan);
638
- document.addEventListener('mousemove', panImage); // Listen on document to allow mouse to leave image
639
  document.addEventListener('mouseup', stopPan);
640
- document.addEventListener('mouseleave', stopPan); // Stop if mouse leaves window
641
  }
642
 
643
  function initializeBubbleEvents(bubble) {
@@ -668,9 +669,43 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
668
  return bubbleDiv;
669
  }
670
 
671
- function applyBubbleType(bubble, type) { /* (function unchanged) */ }
672
- function changeBubbleType(type) { /* (function unchanged) */ }
673
- function rotateBubbleTail() { /* (function unchanged) */ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
 
675
  function selectPanel(panel) {
676
  document.querySelectorAll('.panel.selected').forEach(p => p.classList.remove('selected'));
@@ -678,7 +713,6 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
678
  currentlySelectedPanel = panel;
679
  selectBubble(null);
680
 
681
- // Update zoom slider to reflect selected panel's state
682
  const img = currentlySelectedPanel.querySelector('img');
683
  const zoomSlider = document.getElementById('zoom-slider');
684
  zoomSlider.value = img.dataset.zoom || 100;
@@ -690,34 +724,141 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
690
  currentlySelectedBubble = bubble;
691
  if (currentlySelectedBubble) {
692
  currentlySelectedBubble.classList.add('selected');
693
- document.querySelectorAll('.panel.selected').forEach(p => p.classList.remove('selected'));
 
694
  document.getElementById('bubble-type-select').value = currentlySelectedBubble.dataset.type || 'speech';
695
  document.getElementById('zoom-slider').disabled = true;
696
  }
697
  }
698
 
699
- function editBubbleText(bubble) { /* (function unchanged) */ }
700
- function startDrag(e) { /* (function unchanged) */ }
701
- function drag(e) { /* (function unchanged) */ }
702
- function stopDrag() { /* (function unchanged) */ }
703
- function clearSavedState() { /* (function unchanged) */ }
704
- async function exportPagesToPNG() { /* (function unchanged) */ }
705
- function replacePanelImage() { /* (function unchanged) */ }
706
- function adjustFrame(direction) { /* (function unchanged) */ }
707
-
708
- // --- NEW ZOOM AND PAN FUNCTIONS ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
 
710
  function updateImageTransform(img) {
711
  const zoom = (img.dataset.zoom || 100) / 100;
712
  const x = img.dataset.translateX || 0;
713
  const y = img.dataset.translateY || 0;
714
  img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${zoom})`;
715
-
716
- if (zoom > 1) {
717
- img.classList.add('pannable');
718
- } else {
719
- img.classList.remove('pannable');
720
- }
721
  }
722
 
723
  function handleZoom(event) {
@@ -728,7 +869,7 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
728
  }
729
 
730
  function resetPanelTransform() {
731
- if (!currentlySelectedPanel) return;
732
  const img = currentlySelectedPanel.querySelector('img');
733
  img.dataset.zoom = 100;
734
  img.dataset.translateX = 0;
@@ -738,13 +879,11 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
738
  }
739
 
740
  function startPan(event) {
741
- // Only pan if left mouse button is clicked and image is zoomed
742
  if (event.button !== 0) return;
743
  const img = event.target;
744
  const zoom = parseFloat(img.dataset.zoom || 100);
745
  if (zoom <= 100) return;
746
-
747
- event.preventDefault(); // Prevent default image drag behavior
748
  isPanning = true;
749
  img.classList.add('panning');
750
  panStartX = event.clientX;
@@ -767,9 +906,27 @@ body { margin: 0; padding: 20px; background: #f0f0f0; font-family: Arial, sans-s
767
  if (!isPanning) return;
768
  isPanning = false;
769
  if (currentlySelectedPanel) {
770
- const img = currentlySelectedPanel.querySelector('img');
771
- img.classList.remove('panning');
 
 
 
 
 
 
772
  }
 
 
 
 
 
 
 
 
 
 
 
 
773
  }
774
  </script>
775
  </body>
 
545
  <div class="edit-controls">
546
  <h4>✏️ Interactive Editor</h4>
547
  <div class="control-group">
548
+ <label for="bubble-type-select">Bubble Tools:</label>
549
  <select id="bubble-type-select" onchange="changeBubbleType(this.value)">
550
  <option value="speech">Speech</option><option value="thought">Thought</option><option value="reaction">Reaction</option><option value="narration">Narration</option><option value="idea">Idea</option>
551
  </select>
552
  <button onclick="rotateBubbleTail()" class="secondary-button">🔄 Rotate Tail</button>
553
+ <button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
554
  </div>
555
  <div class="control-group">
556
  <label>Panel Tools (Select Panel):</label>
 
564
  <label>Zoom & Pan (Select Panel):</label>
565
  <div class="zoom-controls">
566
  <button onclick="resetPanelTransform()" class="secondary-button" style="padding: 4px 6px;">Reset</button>
567
+ <input type="range" id="zoom-slider" min="100" max="300" value="100" step="5" disabled>
568
  </div>
569
  </div>
570
  <div class="control-group">
 
636
 
637
  function initializePanelImageEvents(img) {
638
  img.addEventListener('mousedown', startPan);
639
+ document.addEventListener('mousemove', panImage);
640
  document.addEventListener('mouseup', stopPan);
641
+ document.addEventListener('mouseleave', stopPan);
642
  }
643
 
644
  function initializeBubbleEvents(bubble) {
 
669
  return bubbleDiv;
670
  }
671
 
672
+ function applyBubbleType(bubble, type) {
673
+ bubble.querySelectorAll('.thought-dot').forEach(el => el.remove());
674
+ let classesToKeep = 'speech-bubble';
675
+ if (bubble.classList.contains('selected')) classesToKeep += ' selected';
676
+ if (bubble.classList.contains('flipped')) classesToKeep += ' flipped';
677
+ if (bubble.classList.contains('flipped-vertical')) classesToKeep += ' flipped-vertical';
678
+ bubble.className = classesToKeep;
679
+ bubble.classList.add(type);
680
+ bubble.dataset.type = type;
681
+ if (type === 'thought') {
682
+ for (let i = 1; i <= 2; i++) {
683
+ const dot = document.createElement('div');
684
+ dot.className = `thought-dot thought-dot-${i}`;
685
+ bubble.appendChild(dot);
686
+ }
687
+ }
688
+ }
689
+
690
+ function changeBubbleType(type) {
691
+ if (!currentlySelectedBubble) return;
692
+ applyBubbleType(currentlySelectedBubble, type);
693
+ }
694
+
695
+ function rotateBubbleTail() {
696
+ if (!currentlySelectedBubble) { alert("Please select a bubble first."); return; }
697
+ const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
698
+ const isFlippedV = currentlySelectedBubble.classList.contains('flipped-vertical');
699
+ if (!isFlippedH && !isFlippedV) {
700
+ currentlySelectedBubble.classList.add('flipped');
701
+ } else if (isFlippedH && !isFlippedV) {
702
+ currentlySelectedBubble.classList.add('flipped-vertical');
703
+ } else if (isFlippedH && isFlippedV) {
704
+ currentlySelectedBubble.classList.remove('flipped');
705
+ } else {
706
+ currentlySelectedBubble.classList.remove('flipped-vertical');
707
+ }
708
+ }
709
 
710
  function selectPanel(panel) {
711
  document.querySelectorAll('.panel.selected').forEach(p => p.classList.remove('selected'));
 
713
  currentlySelectedPanel = panel;
714
  selectBubble(null);
715
 
 
716
  const img = currentlySelectedPanel.querySelector('img');
717
  const zoomSlider = document.getElementById('zoom-slider');
718
  zoomSlider.value = img.dataset.zoom || 100;
 
724
  currentlySelectedBubble = bubble;
725
  if (currentlySelectedBubble) {
726
  currentlySelectedBubble.classList.add('selected');
727
+ if(currentlySelectedPanel) currentlySelectedPanel.classList.remove('selected');
728
+ currentlySelectedPanel = null;
729
  document.getElementById('bubble-type-select').value = currentlySelectedBubble.dataset.type || 'speech';
730
  document.getElementById('zoom-slider').disabled = true;
731
  }
732
  }
733
 
734
+ function editBubbleText(bubble) {
735
+ if (currentlyEditing) return;
736
+ currentlyEditing = bubble;
737
+ const textSpan = bubble.querySelector('.bubble-text');
738
+ const currentText = textSpan.textContent;
739
+ textSpan.style.display = 'none';
740
+ bubble.style.height = 'auto';
741
+ const textarea = document.createElement('textarea');
742
+ textarea.value = currentText;
743
+ bubble.appendChild(textarea);
744
+ textarea.focus();
745
+ const finishEditing = () => {
746
+ textSpan.textContent = textarea.value;
747
+ bubble.removeChild(textarea);
748
+ textSpan.style.display = '';
749
+ currentlyEditing = null;
750
+ bubble.style.height = 'auto';
751
+ };
752
+ textarea.addEventListener('blur', finishEditing, { once: true });
753
+ textarea.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); textarea.blur(); }});
754
+ }
755
+
756
+ function startDrag(e) {
757
+ const bubble = e.target.closest('.speech-bubble');
758
+ if (!bubble || currentlyEditing) return;
759
+ draggedBubble = bubble;
760
+ selectBubble(bubble);
761
+ const rect = bubble.getBoundingClientRect();
762
+ offset = { x: e.clientX - rect.left, y: e.clientY - rect.top };
763
+ }
764
+
765
+ function drag(e) {
766
+ const parentRect = draggedBubble.parentElement.getBoundingClientRect();
767
+ let x = e.clientX - parentRect.left - offset.x;
768
+ let y = e.clientY - parentRect.top - offset.y;
769
+ draggedBubble.style.left = `${x}px`;
770
+ draggedBubble.style.top = `${y}px`;
771
+ }
772
+
773
+ function stopDrag() {
774
+ draggedBubble = null;
775
+ }
776
+
777
+ function clearSavedState() {
778
+ if (confirm("Reset all edits to the original AI-generated comic?")) {
779
+ localStorage.removeItem('comicEditorState');
780
+ window.location.reload();
781
+ }
782
+ }
783
+
784
+ async function exportPagesToPNG() {
785
+ const pages = document.querySelectorAll('.comic-page');
786
+ if (pages.length === 0) return alert("No pages found.");
787
+ alert(`Starting export of ${pages.length} page(s).`);
788
+ for (let i = 0; i < pages.length; i++) {
789
+ try {
790
+ const canvas = await html2canvas(pages[i], { scale: 2 });
791
+ const link = document.createElement('a');
792
+ link.download = `comic-page-${i + 1}.png`;
793
+ link.href = canvas.toDataURL('image/png');
794
+ link.click();
795
+ } catch (err) {
796
+ alert(`Failed to export page ${i + 1}.`);
797
+ }
798
+ }
799
+ }
800
+
801
+ function replacePanelImage() {
802
+ if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
803
+ const img = currentlySelectedPanel.querySelector('img');
804
+ const uploader = document.getElementById('image-uploader');
805
+ const oneTimeListener = (event) => {
806
+ const file = event.target.files[0];
807
+ if (!file) return;
808
+ const formData = new FormData();
809
+ formData.append('image', file);
810
+ img.style.opacity = '0.5';
811
+ fetch('/replace_panel', { method: 'POST', body: formData })
812
+ .then(response => response.json())
813
+ .then(data => {
814
+ if (data.success) {
815
+ img.src = `/frames/final/${data.new_filename}?t=${new Date().getTime()}`;
816
+ } else { alert('Error replacing image: ' + data.error); }
817
+ img.style.opacity = '1';
818
+ })
819
+ .catch(error => {
820
+ alert('An error occurred during the upload.');
821
+ img.style.opacity = '1';
822
+ });
823
+ uploader.removeEventListener('change', oneTimeListener);
824
+ uploader.value = '';
825
+ };
826
+ uploader.addEventListener('change', oneTimeListener, { once: true });
827
+ uploader.click();
828
+ }
829
+
830
+ function adjustFrame(direction) {
831
+ if (!currentlySelectedPanel) { alert("Please select a panel first to adjust its frame."); return; }
832
+ const img = currentlySelectedPanel.querySelector('img');
833
+ const currentSrc = img.src;
834
+ let filename = currentSrc.substring(currentSrc.lastIndexOf('/') + 1);
835
+ if (filename.includes('?')) { filename = filename.split('?')[0]; }
836
+ img.style.opacity = '0.5';
837
+ fetch('/regenerate_frame', {
838
+ method: 'POST',
839
+ headers: { 'Content-Type': 'application/json' },
840
+ body: JSON.stringify({ filename: filename, direction: direction })
841
+ })
842
+ .then(response => response.json())
843
+ .then(data => {
844
+ if (data.success) {
845
+ img.src = `/frames/final/${filename}?t=${new Date().getTime()}`;
846
+ console.log(data.message);
847
+ } else { alert('Error: ' + data.message); }
848
+ img.style.opacity = '1';
849
+ })
850
+ .catch(error => {
851
+ alert('An error occurred during frame adjustment.');
852
+ img.style.opacity = '1';
853
+ });
854
+ }
855
 
856
  function updateImageTransform(img) {
857
  const zoom = (img.dataset.zoom || 100) / 100;
858
  const x = img.dataset.translateX || 0;
859
  const y = img.dataset.translateY || 0;
860
  img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${zoom})`;
861
+ if (zoom > 1) { img.classList.add('pannable'); } else { img.classList.remove('pannable'); }
 
 
 
 
 
862
  }
863
 
864
  function handleZoom(event) {
 
869
  }
870
 
871
  function resetPanelTransform() {
872
+ if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
873
  const img = currentlySelectedPanel.querySelector('img');
874
  img.dataset.zoom = 100;
875
  img.dataset.translateX = 0;
 
879
  }
880
 
881
  function startPan(event) {
 
882
  if (event.button !== 0) return;
883
  const img = event.target;
884
  const zoom = parseFloat(img.dataset.zoom || 100);
885
  if (zoom <= 100) return;
886
+ event.preventDefault();
 
887
  isPanning = true;
888
  img.classList.add('panning');
889
  panStartX = event.clientX;
 
906
  if (!isPanning) return;
907
  isPanning = false;
908
  if (currentlySelectedPanel) {
909
+ currentlySelectedPanel.querySelector('img').classList.remove('panning');
910
+ }
911
+ }
912
+
913
+ function addBubbleToPanel() {
914
+ if (!currentlySelectedPanel) {
915
+ alert("Please select a panel first to add a bubble to it.");
916
+ return;
917
  }
918
+
919
+ const newBubble = createBubbleElement({
920
+ id: `new-bubble-${Date.now()}`,
921
+ text: 'New Text...',
922
+ left: '10%',
923
+ top: '10%'
924
+ });
925
+
926
+ currentlySelectedPanel.appendChild(newBubble);
927
+ initializeBubbleEvents(newBubble);
928
+ selectBubble(newBubble);
929
+ editBubbleText(newBubble);
930
  }
931
  </script>
932
  </body>