Update app_enhanced.py
Browse files- app_enhanced.py +85 -64
app_enhanced.py
CHANGED
|
@@ -344,7 +344,7 @@ INDEX_HTML = '''
|
|
| 344 |
/* HANDLES FOR DRAGGING */
|
| 345 |
.handle {
|
| 346 |
position: absolute;
|
| 347 |
-
/* UPDATED:
|
| 348 |
width: 30px; height: 30px;
|
| 349 |
background: #ff4757;
|
| 350 |
border: 3px solid white;
|
|
@@ -378,6 +378,9 @@ INDEX_HTML = '''
|
|
| 378 |
top: 0; left: 0;
|
| 379 |
transform-origin: 0 0;
|
| 380 |
cursor: grab;
|
|
|
|
|
|
|
|
|
|
| 381 |
}
|
| 382 |
.panel img.panning { cursor: grabbing; }
|
| 383 |
|
|
@@ -645,12 +648,11 @@ INDEX_HTML = '''
|
|
| 645 |
|
| 646 |
const panData = page.panels[i] || { src: '' };
|
| 647 |
const img = document.createElement('img');
|
|
|
|
| 648 |
img.onload = () => fitImageToPanel(img, pDiv, panData);
|
| 649 |
img.src = panData.src.includes('?') ? panData.src : panData.src + `?sid=${sid}`;
|
| 650 |
-
img.onmousedown = (e) => startPan(e, img);
|
| 651 |
|
| 652 |
pDiv.appendChild(img);
|
| 653 |
-
pDiv.onclick = (e) => { e.stopPropagation(); selectPanel(pDiv); };
|
| 654 |
div.appendChild(pDiv);
|
| 655 |
}
|
| 656 |
|
|
@@ -664,7 +666,6 @@ INDEX_HTML = '''
|
|
| 664 |
const hDiv = document.createElement('div');
|
| 665 |
hDiv.className = `handle ${h.cls||''}`; hDiv.id = `${pageId}-${h.id}`;
|
| 666 |
hDiv.dataset.role = h.id;
|
| 667 |
-
hDiv.onmousedown = (e) => startDragHandle(e, pageId, h.id);
|
| 668 |
div.appendChild(hDiv);
|
| 669 |
});
|
| 670 |
|
|
@@ -672,13 +673,6 @@ INDEX_HTML = '''
|
|
| 672 |
page.pageBubbles.forEach(bData => div.appendChild(createBubbleHTML(bData)));
|
| 673 |
}
|
| 674 |
|
| 675 |
-
div.onclick = (e) => {
|
| 676 |
-
if(e.target === div || e.target.classList.contains('comic-page')) {
|
| 677 |
-
if(selectedBubble) selectedBubble.classList.remove('selected'); selectedBubble = null;
|
| 678 |
-
if(selectedPanel) selectedPanel.classList.remove('selected'); selectedPanel = null;
|
| 679 |
-
}
|
| 680 |
-
};
|
| 681 |
-
|
| 682 |
pageWrapper.appendChild(div);
|
| 683 |
con.appendChild(pageWrapper);
|
| 684 |
|
|
@@ -723,55 +717,78 @@ INDEX_HTML = '''
|
|
| 723 |
setH('h-tier', w/2, ty);
|
| 724 |
}
|
| 725 |
|
| 726 |
-
// ---
|
| 727 |
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 769 |
|
| 770 |
-
// --- UNIFIED MOUSE MOVE ---
|
| 771 |
document.addEventListener('mousemove', (e) => {
|
| 772 |
if(!dragMode) return;
|
| 773 |
-
|
| 774 |
-
e.preventDefault(); // Prevent selection/scrolling while dragging
|
| 775 |
|
| 776 |
if(dragMode === 'layout') {
|
| 777 |
const pageEl = document.getElementById(dragData.pageId);
|
|
@@ -845,9 +862,7 @@ INDEX_HTML = '''
|
|
| 845 |
const iW = img.naturalWidth || img.width;
|
| 846 |
const iH = img.naturalHeight || img.height;
|
| 847 |
|
| 848 |
-
// CONTAIN LOGIC: Ensure WHOLE image fits
|
| 849 |
const scale = Math.min(pW / iW, pH / iH);
|
| 850 |
-
|
| 851 |
const tx = (pW - iW * scale) / 2;
|
| 852 |
const ty = (pH - iH * scale) / 2;
|
| 853 |
|
|
@@ -883,13 +898,8 @@ INDEX_HTML = '''
|
|
| 883 |
if(data.tailPos) b.style.setProperty('--tail-pos', data.tailPos);
|
| 884 |
const textSpan = document.createElement('span'); textSpan.className = 'bubble-text'; textSpan.textContent = data.text || ''; b.appendChild(textSpan);
|
| 885 |
if(type === 'thought') { for(let i=1; i<=2; i++){ const d = document.createElement('div'); d.className = `thought-dot thought-dot-${i}`; b.appendChild(d); } }
|
| 886 |
-
['nw', 'ne', 'sw', 'se'].forEach(dir => { const handle = document.createElement('div'); handle.className = `resize-handle ${dir}`;
|
| 887 |
-
|
| 888 |
-
// BUBBLE DRAG HANDLER
|
| 889 |
-
b.onmousedown = (e) => startBubbleDrag(e, b);
|
| 890 |
|
| 891 |
-
b.onclick = (e) => { e.stopPropagation(); };
|
| 892 |
-
b.ondblclick = (e) => { e.stopPropagation(); editBubbleText(b); };
|
| 893 |
return b;
|
| 894 |
}
|
| 895 |
|
|
@@ -915,6 +925,17 @@ INDEX_HTML = '''
|
|
| 915 |
handles.forEach(h=>h.style.display='block');
|
| 916 |
}
|
| 917 |
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'; } }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 918 |
</script>
|
| 919 |
</body> </html> '''
|
| 920 |
|
|
|
|
| 344 |
/* HANDLES FOR DRAGGING */
|
| 345 |
.handle {
|
| 346 |
position: absolute;
|
| 347 |
+
/* UPDATED: MUCH LARGER HANDLES (30px) */
|
| 348 |
width: 30px; height: 30px;
|
| 349 |
background: #ff4757;
|
| 350 |
border: 3px solid white;
|
|
|
|
| 378 |
top: 0; left: 0;
|
| 379 |
transform-origin: 0 0;
|
| 380 |
cursor: grab;
|
| 381 |
+
/* CRITICAL FOR PANNING: Prevent ghost image */
|
| 382 |
+
user-select: none;
|
| 383 |
+
-webkit-user-drag: none;
|
| 384 |
}
|
| 385 |
.panel img.panning { cursor: grabbing; }
|
| 386 |
|
|
|
|
| 648 |
|
| 649 |
const panData = page.panels[i] || { src: '' };
|
| 650 |
const img = document.createElement('img');
|
| 651 |
+
img.setAttribute('draggable', 'false'); // STOP BROWSER DRAG
|
| 652 |
img.onload = () => fitImageToPanel(img, pDiv, panData);
|
| 653 |
img.src = panData.src.includes('?') ? panData.src : panData.src + `?sid=${sid}`;
|
|
|
|
| 654 |
|
| 655 |
pDiv.appendChild(img);
|
|
|
|
| 656 |
div.appendChild(pDiv);
|
| 657 |
}
|
| 658 |
|
|
|
|
| 666 |
const hDiv = document.createElement('div');
|
| 667 |
hDiv.className = `handle ${h.cls||''}`; hDiv.id = `${pageId}-${h.id}`;
|
| 668 |
hDiv.dataset.role = h.id;
|
|
|
|
| 669 |
div.appendChild(hDiv);
|
| 670 |
});
|
| 671 |
|
|
|
|
| 673 |
page.pageBubbles.forEach(bData => div.appendChild(createBubbleHTML(bData)));
|
| 674 |
}
|
| 675 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 676 |
pageWrapper.appendChild(div);
|
| 677 |
con.appendChild(pageWrapper);
|
| 678 |
|
|
|
|
| 717 |
setH('h-tier', w/2, ty);
|
| 718 |
}
|
| 719 |
|
| 720 |
+
// --- GLOBAL EVENT DELEGATION FOR RELIABILITY ---
|
| 721 |
|
| 722 |
+
document.addEventListener('mousedown', (e) => {
|
| 723 |
+
const target = e.target;
|
| 724 |
+
|
| 725 |
+
// 1. HANDLES
|
| 726 |
+
if (target.classList.contains('handle')) {
|
| 727 |
+
e.preventDefault(); e.stopPropagation();
|
| 728 |
+
dragMode = 'layout';
|
| 729 |
+
dragData = { handle: target.dataset.role, pageId: target.closest('.comic-page').id };
|
| 730 |
+
return;
|
| 731 |
+
}
|
| 732 |
+
|
| 733 |
+
// 2. RESIZE HANDLES (Bubble corner)
|
| 734 |
+
if (target.classList.contains('resize-handle')) {
|
| 735 |
+
e.preventDefault(); e.stopPropagation();
|
| 736 |
+
dragMode = 'resize-bubble';
|
| 737 |
+
const bubble = target.closest('.speech-bubble');
|
| 738 |
+
const rect = bubble.getBoundingClientRect();
|
| 739 |
+
let dir = 'se'; // default
|
| 740 |
+
if(target.classList.contains('sw')) dir = 'sw';
|
| 741 |
+
if(target.classList.contains('ne')) dir = 'ne';
|
| 742 |
+
if(target.classList.contains('nw')) dir = 'nw';
|
| 743 |
+
|
| 744 |
+
dragData = {
|
| 745 |
+
el: bubble, dir: dir,
|
| 746 |
+
startX: e.clientX, startY: e.clientY,
|
| 747 |
+
startW: rect.width, startH: rect.height
|
| 748 |
+
};
|
| 749 |
+
return;
|
| 750 |
+
}
|
| 751 |
+
|
| 752 |
+
// 3. BUBBLES
|
| 753 |
+
const bubble = target.closest('.speech-bubble');
|
| 754 |
+
if (bubble) {
|
| 755 |
+
e.preventDefault(); e.stopPropagation();
|
| 756 |
+
selectBubble(bubble);
|
| 757 |
+
dragMode = 'bubble';
|
| 758 |
+
dragData = {
|
| 759 |
+
el: bubble,
|
| 760 |
+
startX: e.clientX, startY: e.clientY,
|
| 761 |
+
initX: bubble.offsetLeft, initY: bubble.offsetTop
|
| 762 |
+
};
|
| 763 |
+
return;
|
| 764 |
+
}
|
| 765 |
+
|
| 766 |
+
// 4. PANELS/IMAGES
|
| 767 |
+
if (target.tagName === 'IMG' && target.closest('.panel')) {
|
| 768 |
+
e.preventDefault(); e.stopPropagation();
|
| 769 |
+
const panel = target.closest('.panel');
|
| 770 |
+
selectPanel(panel);
|
| 771 |
+
dragMode = 'pan';
|
| 772 |
+
dragData = {
|
| 773 |
+
el: target,
|
| 774 |
+
startX: e.clientX, startY: e.clientY,
|
| 775 |
+
startTx: parseFloat(target.dataset.translateX || 0),
|
| 776 |
+
startTy: parseFloat(target.dataset.translateY || 0)
|
| 777 |
+
};
|
| 778 |
+
target.classList.add('panning');
|
| 779 |
+
return;
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
// 5. CLICK OFF (Deselect)
|
| 783 |
+
if (!target.closest('.edit-controls') && !target.closest('.modal-content')) {
|
| 784 |
+
if(selectedBubble) selectedBubble.classList.remove('selected'); selectedBubble = null;
|
| 785 |
+
if(selectedPanel) selectedPanel.classList.remove('selected'); selectedPanel = null;
|
| 786 |
+
}
|
| 787 |
+
});
|
| 788 |
|
|
|
|
| 789 |
document.addEventListener('mousemove', (e) => {
|
| 790 |
if(!dragMode) return;
|
| 791 |
+
e.preventDefault(); // Stop text selection
|
|
|
|
| 792 |
|
| 793 |
if(dragMode === 'layout') {
|
| 794 |
const pageEl = document.getElementById(dragData.pageId);
|
|
|
|
| 862 |
const iW = img.naturalWidth || img.width;
|
| 863 |
const iH = img.naturalHeight || img.height;
|
| 864 |
|
|
|
|
| 865 |
const scale = Math.min(pW / iW, pH / iH);
|
|
|
|
| 866 |
const tx = (pW - iW * scale) / 2;
|
| 867 |
const ty = (pH - iH * scale) / 2;
|
| 868 |
|
|
|
|
| 898 |
if(data.tailPos) b.style.setProperty('--tail-pos', data.tailPos);
|
| 899 |
const textSpan = document.createElement('span'); textSpan.className = 'bubble-text'; textSpan.textContent = data.text || ''; b.appendChild(textSpan);
|
| 900 |
if(type === 'thought') { for(let i=1; i<=2; i++){ const d = document.createElement('div'); d.className = `thought-dot thought-dot-${i}`; b.appendChild(d); } }
|
| 901 |
+
['nw', 'ne', 'sw', 'se'].forEach(dir => { const handle = document.createElement('div'); handle.className = `resize-handle ${dir}`; b.appendChild(handle); });
|
|
|
|
|
|
|
|
|
|
| 902 |
|
|
|
|
|
|
|
| 903 |
return b;
|
| 904 |
}
|
| 905 |
|
|
|
|
| 925 |
handles.forEach(h=>h.style.display='block');
|
| 926 |
}
|
| 927 |
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'; } }
|
| 928 |
+
|
| 929 |
+
// Select panel helper
|
| 930 |
+
function selectPanel(el) {
|
| 931 |
+
if(selectedPanel) selectedPanel.classList.remove('selected');
|
| 932 |
+
if(selectedBubble) { selectedBubble.classList.remove('selected'); selectedBubble = null; }
|
| 933 |
+
selectedPanel = el; el.classList.add('selected');
|
| 934 |
+
document.getElementById('zoom-slider').disabled = false;
|
| 935 |
+
const img = el.querySelector('img');
|
| 936 |
+
if(img) document.getElementById('zoom-slider').value = img.dataset.zoom || 100;
|
| 937 |
+
document.getElementById('bubble-type-select').disabled = true; document.getElementById('font-select').disabled = true;
|
| 938 |
+
}
|
| 939 |
</script>
|
| 940 |
</body> </html> '''
|
| 941 |
|