jhh6576 commited on
Commit
fcbf3e7
·
verified ·
1 Parent(s): b9ce9f8

Update app_enhanced.py

Browse files
Files changed (1) hide show
  1. app_enhanced.py +147 -37
app_enhanced.py CHANGED
@@ -513,8 +513,8 @@ class EnhancedComicGenerator:
513
  position: absolute; display: flex; justify-content: center; align-items: center;
514
  width: 150px; height: 80px; min-width: 50px; min-height: 30px;
515
  box-sizing: border-box;
516
- box-shadow: 2px 2px 5px rgba(0,0,0,0.3); z-index: 10; cursor: move;
517
- font-size: 13px; font-weight: bold; text-align: center;
518
  }
519
  .bubble-text { padding: 2px; word-wrap: break-word; }
520
  .speech-bubble.selected { outline: 2px dashed #4CAF50; }
@@ -522,46 +522,65 @@ class EnhancedComicGenerator:
522
 
523
  .speech-bubble.speech {
524
  font-family: "Permanent Marker", cursive;
525
- color: #000;
526
  padding: 1em;
527
- --r: 1.2em; /* radius */
528
- --b: 2em; /* base */
529
- --h: 1.5em; /* height */
530
- --p: 50%;
531
- --x: 1.8em;
532
  --c: #FFFFFF;
533
- border-radius: var(--r) var(--r) min(var(--r),100% - var(--p) - var(--b)/2) min(var(--r),var(--p) - var(--b)/2)/var(--r);
534
- clip-path: polygon(0 100%,0 0,100% 0,100% 100%,
535
- clamp(var(--b),var(--p) + var(--b)/2,100%) 100%,
536
- calc(var(--p) + var(--x)) calc(100% + var(--h)),
537
- clamp(0%,var(--p) - var(--b)/2,100% - var(--b)) 100%);
538
  background: var(--c);
539
- border-image: conic-gradient(var(--c) 0 0) 0 0 1 0/0 0 var(--h) 0/0 999px var(--h) 999px;
540
- border: 3px solid black;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
541
  }
542
 
543
  .speech-bubble.thought {
544
- border-radius: 50%;
545
- border: 3px solid black;
546
- background: white; color: black;
547
  }
548
  .speech-bubble.thought:before {
549
  content: ''; position: absolute;
550
- height: 3px; width: 3px; bottom: -20px;
551
- border-radius: 100%; background: #fff;
552
- left: 20px;
553
- box-shadow: 0 0 0 7px white, 0 0 0 10px black, -20px 15px 0 5px white, -20px 15px 0 8px black, -40px 20px 0 2px white, -40px 20px 0 5px black;
554
  }
555
  .speech-bubble.thought.flipped:before {
556
- right: 20px; left: auto;
557
- box-shadow: 0 0 0 7px white, 0 0 0 10px black, 20px 15px 0 5px white, 20px 15px 0 8px black, 40px 20px 0 2px white, 40px 20px 0 5px black;
558
  }
559
 
560
  .speech-bubble.reaction {
561
- border-radius: 50%;
562
- background: #FFD700;
563
- border: 3px solid #E53935;
564
- color: #D32F2F;
565
  clip-path: polygon(100% 50%,78% 60%,88% 82%,65% 76%,59% 99%,45% 80%,25% 93%,27% 69%,3% 67%,20% 50%,3% 33%,27% 31%,25% 7%,45% 20%,59% 1%,65% 24%,88% 18%,78% 40%);
566
  }
567
 
@@ -662,6 +681,7 @@ class EnhancedComicGenerator:
662
  let currentlySelectedBubble = null;
663
  let currentlySelectedPanel = null;
664
  let isPanning = false, panStartX, panStartY, panStartTranslateX, panStartTranslateY;
 
665
 
666
  function renderComic(data) {
667
  const container = document.getElementById('comic-pages');
@@ -766,6 +786,7 @@ class EnhancedComicGenerator:
766
  let classesToKeep = ['speech-bubble'];
767
  if (bubble.classList.contains('selected')) classesToKeep.push('selected');
768
  if (bubble.classList.contains('flipped')) classesToKeep.push('flipped');
 
769
  bubble.className = classesToKeep.join(' ');
770
  bubble.classList.add(type);
771
  bubble.dataset.type = type;
@@ -783,7 +804,12 @@ class EnhancedComicGenerator:
783
 
784
  function rotateBubbleTail() {
785
  if (!currentlySelectedBubble) { alert("Please select a bubble first."); return; }
786
- currentlySelectedBubble.classList.toggle('flipped');
 
 
 
 
 
787
  }
788
 
789
  function selectPanel(panel) {
@@ -913,7 +939,18 @@ class EnhancedComicGenerator:
913
  }
914
 
915
  async function exportPagesToPNG() {
916
- // function content...
 
 
 
 
 
 
 
 
 
 
 
917
  }
918
 
919
  function replacePanelImage() {
@@ -946,15 +983,41 @@ class EnhancedComicGenerator:
946
  }
947
 
948
  function adjustFrame(direction) {
949
- // function content...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
950
  }
951
 
952
  function updateImageTransform(img) {
953
- // function content...
 
 
 
 
954
  }
955
 
956
  function handleZoom(event) {
957
- // function content...
 
 
 
958
  }
959
 
960
  function resetPanelTransform() {
@@ -968,15 +1031,30 @@ class EnhancedComicGenerator:
968
  }
969
 
970
  function startPan(event) {
971
- // function content...
 
 
 
 
 
 
 
 
 
972
  }
973
 
974
  function panImage(event) {
975
- // function content...
 
 
 
 
976
  }
977
 
978
  function stopPan() {
979
- // function content...
 
 
980
  }
981
 
982
  function addBubbleToPanel() {
@@ -994,7 +1072,39 @@ class EnhancedComicGenerator:
994
  }
995
 
996
  function gotoTimestamp() {
997
- // function content...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
998
  }
999
  </script>
1000
  </body>
 
513
  position: absolute; display: flex; justify-content: center; align-items: center;
514
  width: 150px; height: 80px; min-width: 50px; min-height: 30px;
515
  box-sizing: border-box;
516
+ z-index: 10; cursor: move;
517
+ font-size: 14px; font-weight: bold; text-align: center;
518
  }
519
  .bubble-text { padding: 2px; word-wrap: break-word; }
520
  .speech-bubble.selected { outline: 2px dashed #4CAF50; }
 
522
 
523
  .speech-bubble.speech {
524
  font-family: "Permanent Marker", cursive;
 
525
  padding: 1em;
526
+ --b: 3em; /* base */
527
+ --h: 1.8em; /* height */
528
+ --t: .6; /* thickness (from 0 to 1) */
529
+ --p: 20%; /* main position (0%:left 100%:right) */
530
+ --r: 1.2em; /* the radius */
531
  --c: #FFFFFF;
 
 
 
 
 
532
  background: var(--c);
533
+ border-radius: var(--r) var(--r) min(var(--r),100% - var(--p) - (1 - var(--t))*var(--b)/2) min(var(--r),var(--p) - (1 - var(--t))*var(--b)/2)/var(--r);
534
+ position: relative;
535
+ }
536
+ .speech-bubble.speech:before {
537
+ content: "";
538
+ position: absolute;
539
+ top: 100%;
540
+ left: clamp(-1*var(--t)*var(--b),var(--p) - (var(--t) + 1)*var(--b)/2,100% - var(--b));
541
+ width: var(--b);
542
+ height: var(--h);
543
+ background: inherit;
544
+ border-bottom-right-radius: 100%;
545
+ -webkit-mask: radial-gradient(calc(var(--t)*100%) 105% at 0 0,#0000 99%,#000 101%);
546
+ mask: radial-gradient(calc(var(--t)*100%) 105% at 0 0,#0000 99%,#000 101%);
547
+ transform-origin: center;
548
+ }
549
+ .speech-bubble.speech.flipped:before {
550
+ left: auto;
551
+ right: clamp(-1*var(--t)*var(--b),var(--p) - (var(--t) + 1)*var(--b)/2,100% - var(--b));
552
+ transform: scaleX(-1);
553
+ }
554
+ .speech-bubble.speech.flipped-vertical:before {
555
+ top: auto;
556
+ bottom: 100%;
557
+ transform: scaleY(-1);
558
+ }
559
+ .speech-bubble.speech.flipped.flipped-vertical:before {
560
+ top: auto;
561
+ bottom: 100%;
562
+ left: auto;
563
+ right: clamp(-1*var(--t)*var(--b),var(--p) - (var(--t) + 1)*var(--b)/2,100% - var(--b));
564
+ transform: scale(-1, -1);
565
  }
566
 
567
  .speech-bubble.thought {
568
+ border-radius: 50%; border: 3px solid black; background: white; color: black;
 
 
569
  }
570
  .speech-bubble.thought:before {
571
  content: ''; position: absolute;
572
+ width: 8px; height: 8px; bottom: -12px;
573
+ border-radius: 50%; background: white;
574
+ left: 20%;
575
+ box-shadow: 0 0 0 3px black, -10px 8px 0 0px white, -10px 8px 0 3px black;
576
  }
577
  .speech-bubble.thought.flipped:before {
578
+ left: auto; right: 20%;
579
+ box-shadow: 0 0 0 3px black, 10px 8px 0 0px white, 10px 8px 0 3px black;
580
  }
581
 
582
  .speech-bubble.reaction {
583
+ background: #FFD700; border: 3px solid #E53935; color: #D32F2F; font-weight: 900;
 
 
 
584
  clip-path: polygon(100% 50%,78% 60%,88% 82%,65% 76%,59% 99%,45% 80%,25% 93%,27% 69%,3% 67%,20% 50%,3% 33%,27% 31%,25% 7%,45% 20%,59% 1%,65% 24%,88% 18%,78% 40%);
585
  }
586
 
 
681
  let currentlySelectedBubble = null;
682
  let currentlySelectedPanel = null;
683
  let isPanning = false, panStartX, panStartY, panStartTranslateX, panStartTranslateY;
684
+ let isResizing = false, resizeHandle, originalWidth, originalHeight, originalX, originalY, originalMouseX, originalMouseY;
685
 
686
  function renderComic(data) {
687
  const container = document.getElementById('comic-pages');
 
786
  let classesToKeep = ['speech-bubble'];
787
  if (bubble.classList.contains('selected')) classesToKeep.push('selected');
788
  if (bubble.classList.contains('flipped')) classesToKeep.push('flipped');
789
+ if (bubble.classList.contains('flipped-vertical')) classesToKeep.push('flipped-vertical');
790
  bubble.className = classesToKeep.join(' ');
791
  bubble.classList.add(type);
792
  bubble.dataset.type = type;
 
804
 
805
  function rotateBubbleTail() {
806
  if (!currentlySelectedBubble) { alert("Please select a bubble first."); return; }
807
+ const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
808
+ const isFlippedV = currentlySelectedBubble.classList.contains('flipped-vertical');
809
+ if (!isFlippedH && !isFlippedV) { currentlySelectedBubble.classList.add('flipped'); }
810
+ else if (isFlippedH && !isFlippedV) { currentlySelectedBubble.classList.add('flipped-vertical'); }
811
+ else if (isFlippedH && isFlippedV) { currentlySelectedBubble.classList.remove('flipped'); }
812
+ else { currentlySelectedBubble.classList.remove('flipped-vertical'); }
813
  }
814
 
815
  function selectPanel(panel) {
 
939
  }
940
 
941
  async function exportPagesToPNG() {
942
+ const pages = document.querySelectorAll('.comic-page');
943
+ if (pages.length === 0) return alert("No pages found.");
944
+ alert(`Starting export of ${pages.length} page(s).`);
945
+ for (let i = 0; i < pages.length; i++) {
946
+ try {
947
+ const canvas = await html2canvas(pages[i], { scale: 2 });
948
+ const link = document.createElement('a');
949
+ link.download = `comic-page-${i + 1}.png`;
950
+ link.href = canvas.toDataURL('image/png');
951
+ link.click();
952
+ } catch (err) { alert(`Failed to export page ${i + 1}.`); }
953
+ }
954
  }
955
 
956
  function replacePanelImage() {
 
983
  }
984
 
985
  function adjustFrame(direction) {
986
+ if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
987
+ const img = currentlySelectedPanel.querySelector('img');
988
+ let filename = img.src.substring(img.src.lastIndexOf('/') + 1).split('?')[0];
989
+ img.style.opacity = '0.5';
990
+ fetch('/regenerate_frame', {
991
+ method: 'POST',
992
+ headers: { 'Content-Type': 'application/json' },
993
+ body: JSON.stringify({ filename, direction })
994
+ })
995
+ .then(res => res.json())
996
+ .then(data => {
997
+ if (data.success) {
998
+ img.src = `/frames/final/${filename}?t=${new Date().getTime()}`;
999
+ } else { alert('Error: ' + data.message); }
1000
+ img.style.opacity = '1';
1001
+ })
1002
+ .catch(() => {
1003
+ alert('An error occurred.');
1004
+ img.style.opacity = '1';
1005
+ });
1006
  }
1007
 
1008
  function updateImageTransform(img) {
1009
+ const zoom = (img.dataset.zoom || 100) / 100;
1010
+ const x = img.dataset.translateX || 0;
1011
+ const y = img.dataset.translateY || 0;
1012
+ img.style.transform = `translateX(${x}px) translateY(${y}px) scale(${zoom})`;
1013
+ img.classList.toggle('pannable', zoom > 1);
1014
  }
1015
 
1016
  function handleZoom(event) {
1017
+ if (!currentlySelectedPanel) return;
1018
+ const img = currentlySelectedPanel.querySelector('img');
1019
+ img.dataset.zoom = event.target.value;
1020
+ updateImageTransform(img);
1021
  }
1022
 
1023
  function resetPanelTransform() {
 
1031
  }
1032
 
1033
  function startPan(event) {
1034
+ if (event.button !== 0) return;
1035
+ const img = event.target;
1036
+ if (parseFloat(img.dataset.zoom || 100) <= 100) return;
1037
+ event.preventDefault();
1038
+ isPanning = true;
1039
+ img.classList.add('panning');
1040
+ panStartX = event.clientX;
1041
+ panStartY = event.clientY;
1042
+ panStartTranslateX = parseFloat(img.dataset.translateX || 0);
1043
+ panStartTranslateY = parseFloat(img.dataset.translateY || 0);
1044
  }
1045
 
1046
  function panImage(event) {
1047
+ if (!isPanning || !currentlySelectedPanel) return;
1048
+ const img = currentlySelectedPanel.querySelector('img');
1049
+ img.dataset.translateX = panStartTranslateX + (event.clientX - panStartX);
1050
+ img.dataset.translateY = panStartTranslateY + (event.clientY - panStartY);
1051
+ updateImageTransform(img);
1052
  }
1053
 
1054
  function stopPan() {
1055
+ if (!isPanning) return;
1056
+ isPanning = false;
1057
+ currentlySelectedPanel?.querySelector('img')?.classList.remove('panning');
1058
  }
1059
 
1060
  function addBubbleToPanel() {
 
1072
  }
1073
 
1074
  function gotoTimestamp() {
1075
+ if (!currentlySelectedPanel) { alert("Please select a panel first."); return; }
1076
+ const input = document.getElementById('timestamp-input');
1077
+ const timeStr = input.value.trim();
1078
+ if (!timeStr) return;
1079
+ let parsedSeconds = 0;
1080
+ if (timeStr.includes(':')) {
1081
+ const parts = timeStr.split(':');
1082
+ parsedSeconds = parseInt(parts[0], 10) * 60 + parseFloat(parts[1]);
1083
+ } else {
1084
+ parsedSeconds = parseFloat(timeStr);
1085
+ }
1086
+ if (isNaN(parsedSeconds)) { alert("Invalid time format."); return; }
1087
+ const img = currentlySelectedPanel.querySelector('img');
1088
+ let filename = img.src.substring(img.src.lastIndexOf('/') + 1).split('?')[0];
1089
+ img.style.opacity = '0.5';
1090
+ fetch('/goto_timestamp', {
1091
+ method: 'POST',
1092
+ headers: { 'Content-Type': 'application/json' },
1093
+ body: JSON.stringify({ filename, timestamp: parsedSeconds })
1094
+ })
1095
+ .then(res => res.json())
1096
+ .then(data => {
1097
+ if (data.success) {
1098
+ img.src = `/frames/final/${filename}?t=${new Date().getTime()}`;
1099
+ input.value = '';
1100
+ resetPanelTransform();
1101
+ } else { alert('Error: ' + data.message); }
1102
+ img.style.opacity = '1';
1103
+ })
1104
+ .catch(() => {
1105
+ alert('An error occurred.');
1106
+ img.style.opacity = '1';
1107
+ });
1108
  }
1109
  </script>
1110
  </body>