Update app_enhanced.py
Browse files- app_enhanced.py +41 -6
app_enhanced.py
CHANGED
|
@@ -476,11 +476,9 @@ class EnhancedComicGenerator:
|
|
| 476 |
os.makedirs(self.output_dir, exist_ok=True)
|
| 477 |
pages_data = []
|
| 478 |
for page in pages:
|
| 479 |
-
# Ensure __dict__ is called on objects, not already-dicts from fallback
|
| 480 |
panels = [p.__dict__ if hasattr(p, '__dict__') else p for p in page.panels]
|
| 481 |
-
|
| 482 |
-
pages_data.append({'panels': panels, 'bubbles':
|
| 483 |
-
|
| 484 |
with open(os.path.join(self.output_dir, 'pages.json'), 'w', encoding='utf-8') as f:
|
| 485 |
json.dump(pages_data, f, indent=2)
|
| 486 |
self._copy_template_files()
|
|
@@ -496,8 +494,11 @@ class EnhancedComicGenerator:
|
|
| 496 |
<meta charset="UTF-8">
|
| 497 |
<title>Comic Editor</title>
|
| 498 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
|
|
|
|
|
|
|
|
|
| 499 |
<style>
|
| 500 |
-
body { margin: 0; padding: 20px; background: #f0f0f0; font-family:
|
| 501 |
.comic-container { max-width: 1200px; margin: 0 auto; }
|
| 502 |
.comic-page { background: white; width: 600px; height: 400px; box-shadow: 0 0 10px rgba(0,0,0,0.1); box-sizing: content-box; position: relative; overflow: hidden; border: 1px solid #333; padding: 10px; }
|
| 503 |
.comic-grid { display: grid; grid-template-columns: 285px 285px; grid-template-rows: 185px 185px; gap: 10px; width: 100%; height: 100%; }
|
|
@@ -508,7 +509,7 @@ class EnhancedComicGenerator:
|
|
| 508 |
.panel img { width: 100%; height: 100%; object-fit: cover; object-position: center; transition: transform 0.1s ease-out; }
|
| 509 |
.panel img.pannable { cursor: grab; }
|
| 510 |
.panel img.panning { cursor: grabbing; }
|
| 511 |
-
.speech-bubble { position: absolute; display: flex; justify-content: center; align-items: center; width: auto; height: auto; 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; }
|
| 512 |
.bubble-text { padding: 2px; word-wrap: break-word; }
|
| 513 |
.speech-bubble.selected { outline: 2px dashed #4CAF50; }
|
| 514 |
.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; }
|
|
@@ -560,6 +561,12 @@ class EnhancedComicGenerator:
|
|
| 560 |
<select id="bubble-type-select" onchange="changeBubbleType(this.value)" disabled>
|
| 561 |
<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>
|
| 562 |
</select>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 563 |
<div class="color-picker-grid">
|
| 564 |
<div>
|
| 565 |
<label for="bubble-text-color">Text</label>
|
|
@@ -572,6 +579,7 @@ class EnhancedComicGenerator:
|
|
| 572 |
</div>
|
| 573 |
<button onclick="rotateBubbleTail()" class="secondary-button">🔄 Rotate Tail</button>
|
| 574 |
<button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
|
|
|
|
| 575 |
</div>
|
| 576 |
<div class="control-group">
|
| 577 |
<label>Panel Tools (Select Panel):</label>
|
|
@@ -726,6 +734,11 @@ class EnhancedComicGenerator:
|
|
| 726 |
applyBubbleType(currentlySelectedBubble, type);
|
| 727 |
}
|
| 728 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 729 |
function rotateBubbleTail() {
|
| 730 |
if (!currentlySelectedBubble) { alert("Please select a bubble first."); return; }
|
| 731 |
const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
|
|
@@ -749,6 +762,7 @@ class EnhancedComicGenerator:
|
|
| 749 |
document.getElementById('bubble-type-select').disabled = true;
|
| 750 |
document.getElementById('bubble-text-color').disabled = true;
|
| 751 |
document.getElementById('bubble-fill-color').disabled = true;
|
|
|
|
| 752 |
}
|
| 753 |
|
| 754 |
function selectBubble(bubble) {
|
|
@@ -757,6 +771,7 @@ class EnhancedComicGenerator:
|
|
| 757 |
const textColorPicker = document.getElementById('bubble-text-color');
|
| 758 |
const fillColorPicker = document.getElementById('bubble-fill-color');
|
| 759 |
const typeSelect = document.getElementById('bubble-type-select');
|
|
|
|
| 760 |
|
| 761 |
if (currentlySelectedBubble) {
|
| 762 |
currentlySelectedBubble.classList.add('selected');
|
|
@@ -768,10 +783,18 @@ class EnhancedComicGenerator:
|
|
| 768 |
fillColorPicker.value = rgbToHex(styles.backgroundColor);
|
| 769 |
|
| 770 |
typeSelect.value = currentlySelectedBubble.dataset.type || 'speech';
|
|
|
|
|
|
|
| 771 |
document.getElementById('zoom-slider').disabled = true;
|
| 772 |
textColorPicker.disabled = false;
|
| 773 |
fillColorPicker.disabled = false;
|
| 774 |
typeSelect.disabled = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 775 |
}
|
| 776 |
}
|
| 777 |
|
|
@@ -812,6 +835,18 @@ class EnhancedComicGenerator:
|
|
| 812 |
|
| 813 |
function stopDrag() { draggedBubble = null; }
|
| 814 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 815 |
function clearSavedState() {
|
| 816 |
if (confirm("Reset all edits?")) {
|
| 817 |
localStorage.removeItem('comicEditorState');
|
|
|
|
| 476 |
os.makedirs(self.output_dir, exist_ok=True)
|
| 477 |
pages_data = []
|
| 478 |
for page in pages:
|
|
|
|
| 479 |
panels = [p.__dict__ if hasattr(p, '__dict__') else p for p in page.panels]
|
| 480 |
+
bubbles_data = [b.__dict__ if hasattr(b, '__dict__') else b for b in page.bubbles]
|
| 481 |
+
pages_data.append({'panels': panels, 'bubbles': bubbles_data})
|
|
|
|
| 482 |
with open(os.path.join(self.output_dir, 'pages.json'), 'w', encoding='utf-8') as f:
|
| 483 |
json.dump(pages_data, f, indent=2)
|
| 484 |
self._copy_template_files()
|
|
|
|
| 494 |
<meta charset="UTF-8">
|
| 495 |
<title>Comic Editor</title>
|
| 496 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
|
| 497 |
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 498 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 499 |
+
<link href="https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@700&family=Gloria+Hallelujah&family=Lato&display=swap" rel="stylesheet">
|
| 500 |
<style>
|
| 501 |
+
body { margin: 0; padding: 20px; background: #f0f0f0; font-family: 'Lato', sans-serif; }
|
| 502 |
.comic-container { max-width: 1200px; margin: 0 auto; }
|
| 503 |
.comic-page { background: white; width: 600px; height: 400px; box-shadow: 0 0 10px rgba(0,0,0,0.1); box-sizing: content-box; position: relative; overflow: hidden; border: 1px solid #333; padding: 10px; }
|
| 504 |
.comic-grid { display: grid; grid-template-columns: 285px 285px; grid-template-rows: 185px 185px; gap: 10px; width: 100%; height: 100%; }
|
|
|
|
| 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: auto; height: auto; 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; }
|
|
|
|
| 561 |
<select id="bubble-type-select" onchange="changeBubbleType(this.value)" disabled>
|
| 562 |
<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>
|
| 563 |
</select>
|
| 564 |
+
<select id="font-select" onchange="changeFont(this.value)" disabled>
|
| 565 |
+
<option value="'Comic Neue', cursive">Comic Neue</option>
|
| 566 |
+
<option value="'Bangers', cursive">Bangers</option>
|
| 567 |
+
<option value="'Gloria Hallelujah', cursive">Gloria</option>
|
| 568 |
+
<option value="'Lato', sans-serif">Lato</option>
|
| 569 |
+
</select>
|
| 570 |
<div class="color-picker-grid">
|
| 571 |
<div>
|
| 572 |
<label for="bubble-text-color">Text</label>
|
|
|
|
| 579 |
</div>
|
| 580 |
<button onclick="rotateBubbleTail()" class="secondary-button">🔄 Rotate Tail</button>
|
| 581 |
<button onclick="addBubbleToPanel()" class="action-button">💬 Add Bubble</button>
|
| 582 |
+
<button onclick="deleteBubble()" class="reset-button">🗑️ Delete Bubble</button>
|
| 583 |
</div>
|
| 584 |
<div class="control-group">
|
| 585 |
<label>Panel Tools (Select Panel):</label>
|
|
|
|
| 734 |
applyBubbleType(currentlySelectedBubble, type);
|
| 735 |
}
|
| 736 |
|
| 737 |
+
function changeFont(font) {
|
| 738 |
+
if (!currentlySelectedBubble) return;
|
| 739 |
+
currentlySelectedBubble.style.fontFamily = font;
|
| 740 |
+
}
|
| 741 |
+
|
| 742 |
function rotateBubbleTail() {
|
| 743 |
if (!currentlySelectedBubble) { alert("Please select a bubble first."); return; }
|
| 744 |
const isFlippedH = currentlySelectedBubble.classList.contains('flipped');
|
|
|
|
| 762 |
document.getElementById('bubble-type-select').disabled = true;
|
| 763 |
document.getElementById('bubble-text-color').disabled = true;
|
| 764 |
document.getElementById('bubble-fill-color').disabled = true;
|
| 765 |
+
document.getElementById('font-select').disabled = true;
|
| 766 |
}
|
| 767 |
|
| 768 |
function selectBubble(bubble) {
|
|
|
|
| 771 |
const textColorPicker = document.getElementById('bubble-text-color');
|
| 772 |
const fillColorPicker = document.getElementById('bubble-fill-color');
|
| 773 |
const typeSelect = document.getElementById('bubble-type-select');
|
| 774 |
+
const fontSelect = document.getElementById('font-select');
|
| 775 |
|
| 776 |
if (currentlySelectedBubble) {
|
| 777 |
currentlySelectedBubble.classList.add('selected');
|
|
|
|
| 783 |
fillColorPicker.value = rgbToHex(styles.backgroundColor);
|
| 784 |
|
| 785 |
typeSelect.value = currentlySelectedBubble.dataset.type || 'speech';
|
| 786 |
+
fontSelect.value = styles.fontFamily.split(',')[0].replace(/"/g, "'");
|
| 787 |
+
|
| 788 |
document.getElementById('zoom-slider').disabled = true;
|
| 789 |
textColorPicker.disabled = false;
|
| 790 |
fillColorPicker.disabled = false;
|
| 791 |
typeSelect.disabled = false;
|
| 792 |
+
fontSelect.disabled = false;
|
| 793 |
+
} else {
|
| 794 |
+
textColorPicker.disabled = true;
|
| 795 |
+
fillColorPicker.disabled = true;
|
| 796 |
+
typeSelect.disabled = true;
|
| 797 |
+
fontSelect.disabled = true;
|
| 798 |
}
|
| 799 |
}
|
| 800 |
|
|
|
|
| 835 |
|
| 836 |
function stopDrag() { draggedBubble = null; }
|
| 837 |
|
| 838 |
+
function deleteBubble() {
|
| 839 |
+
if (!currentlySelectedBubble) {
|
| 840 |
+
alert("Please select a bubble to delete.");
|
| 841 |
+
return;
|
| 842 |
+
}
|
| 843 |
+
if (confirm("Are you sure you want to delete this bubble?")) {
|
| 844 |
+
currentlySelectedBubble.remove();
|
| 845 |
+
currentlySelectedBubble = null;
|
| 846 |
+
selectBubble(null); // Deselect and disable controls
|
| 847 |
+
}
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
function clearSavedState() {
|
| 851 |
if (confirm("Reset all edits?")) {
|
| 852 |
localStorage.removeItem('comicEditorState');
|