feat: add draggable zoom control with close button and menu toggle

- Add close button (X) to zoom control widget
- Make zoom control draggable anywhere on screen
- Persist dragged position in localStorage (cv-zoom-position)
- Add "Zoom" button to action bar to show control when hidden
- Persist visibility state in localStorage (cv-zoom-visible)
- Cursor changes to "move" to indicate draggability
- Interactive elements (slider, buttons) don't trigger drag
- Position stays within viewport bounds
- All features work on desktop only (zoom hidden on mobile)
This commit is contained in:
juanatsap
2025-11-12 15:09:27 +00:00
parent c89bb43fc8
commit 1c00421bd2
3 changed files with 192 additions and 3 deletions
+29 -1
View File
@@ -3474,7 +3474,7 @@ html {
.zoom-control {
position: fixed;
bottom: 100px; /* Increased from 70px to clear footer */
bottom: 100px; /* Default position, can be dragged */
left: 50%;
transform: translateX(-50%);
z-index: 900;
@@ -3490,6 +3490,34 @@ html {
transition: all 0.3s ease;
opacity: 0.7;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
cursor: move; /* Indicate draggability */
user-select: none; /* Prevent text selection while dragging */
}
/* Close button for zoom control */
.zoom-close-btn {
position: absolute;
top: -8px;
right: -8px;
width: 24px;
height: 24px;
background: rgba(220, 53, 69, 0.9);
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
transition: all 0.2s ease;
z-index: 1;
}
.zoom-close-btn:hover {
background: rgba(220, 53, 69, 1);
transform: scale(1.1);
box-shadow: 0 2px 8px rgba(220, 53, 69, 0.4);
}
.zoom-control:hover {
+145 -1
View File
@@ -405,6 +405,144 @@
applyZoom(newZoom, true);
}
/**
* Make zoom control draggable and persist position
*/
function initZoomDragging() {
const zoomControl = document.getElementById('zoom-control');
if (!zoomControl || isMobileView()) return;
let isDragging = false;
let currentX, currentY, initialX, initialY;
// Restore saved position from localStorage
const savedPosition = localStorage.getItem('cv-zoom-position');
if (savedPosition) {
const { bottom, left } = JSON.parse(savedPosition);
zoomControl.style.bottom = bottom;
zoomControl.style.left = left;
zoomControl.style.transform = 'none'; // Remove centering transform when positioned
}
// Start drag on mousedown (but not on slider, close button, or reset button)
zoomControl.addEventListener('mousedown', function(e) {
// Ignore if clicking on interactive elements
if (e.target.closest('.zoom-slider, .zoom-close-btn, .zoom-reset-btn')) {
return;
}
isDragging = true;
zoomControl.style.transition = 'none'; // Disable transitions during drag
// Get current position
const rect = zoomControl.getBoundingClientRect();
initialX = e.clientX - rect.left;
initialY = e.clientY - rect.top;
e.preventDefault();
});
// Drag on mousemove
document.addEventListener('mousemove', function(e) {
if (!isDragging) return;
e.preventDefault();
currentX = e.clientX - initialX;
currentY = e.clientY - initialY;
// Keep within viewport bounds
const maxX = window.innerWidth - zoomControl.offsetWidth;
const maxY = window.innerHeight - zoomControl.offsetHeight;
currentX = Math.max(0, Math.min(currentX, maxX));
currentY = Math.max(0, Math.min(currentY, maxY));
// Update position
zoomControl.style.left = currentX + 'px';
zoomControl.style.bottom = (window.innerHeight - currentY - zoomControl.offsetHeight) + 'px';
zoomControl.style.transform = 'none'; // Remove centering transform
});
// End drag on mouseup
document.addEventListener('mouseup', function() {
if (isDragging) {
isDragging = false;
zoomControl.style.transition = 'all 0.3s ease'; // Re-enable transitions
// Save position to localStorage
const position = {
bottom: zoomControl.style.bottom,
left: zoomControl.style.left
};
localStorage.setItem('cv-zoom-position', JSON.stringify(position));
}
});
}
/**
* Hide zoom control and show menu button
*/
function hideZoomControl() {
const zoomControl = document.getElementById('zoom-control');
const showButton = document.getElementById('show-zoom-btn');
if (zoomControl) {
zoomControl.style.display = 'none';
localStorage.setItem('cv-zoom-visible', 'false');
}
if (showButton) {
showButton.style.display = 'inline-flex';
}
}
/**
* Show zoom control and hide menu button (global function for onclick)
*/
window.showZoomControl = function() {
const zoomControl = document.getElementById('zoom-control');
const showButton = document.getElementById('show-zoom-btn');
if (zoomControl) {
zoomControl.style.display = 'flex';
localStorage.setItem('cv-zoom-visible', 'true');
}
if (showButton) {
showButton.style.display = 'none';
}
};
/**
* Initialize zoom visibility state from localStorage
*/
function initZoomVisibility() {
if (isMobileView()) return; // Always hidden on mobile
const zoomControl = document.getElementById('zoom-control');
const showButton = document.getElementById('show-zoom-btn');
const isVisible = localStorage.getItem('cv-zoom-visible');
// Default to visible if not set
if (isVisible === 'false') {
if (zoomControl) zoomControl.style.display = 'none';
if (showButton) showButton.style.display = 'inline-flex';
} else {
if (zoomControl) zoomControl.style.display = 'flex';
if (showButton) showButton.style.display = 'none';
}
// Setup close button
const closeBtn = document.getElementById('zoom-close');
if (closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent drag from starting
hideZoomControl();
});
}
}
// =============================================================================
// PRINT & PDF
// =============================================================================
@@ -518,8 +656,14 @@
window.toggleTheme();
}
// Initialize zoom control
// Initialize zoom control (zoom level, event listeners)
initZoomControl();
// Initialize zoom visibility state (show/hide based on localStorage)
initZoomVisibility();
// Initialize zoom dragging (make draggable, restore position)
initZoomDragging();
}
// =============================================================================
+18 -1
View File
@@ -174,6 +174,15 @@
<!-- Right: Action buttons -->
<div class="action-buttons-right">
<button
id="show-zoom-btn"
class="action-btn zoom-toggle-btn"
onclick="showZoomControl()"
style="display: none;"
aria-label="{{if eq .Lang "es"}}Mostrar control de zoom{{else}}Show zoom control{{end}}">
<iconify-icon icon="mdi:magnify" width="18" height="18"></iconify-icon>
{{if eq .Lang "es"}}Zoom{{else}}Zoom{{end}}
</button>
<button
class="action-btn pdf-btn"
onclick="openPdfModal()"
@@ -454,8 +463,16 @@
</div>
</div>
<!-- Zoom Control (Fixed Bottom Center) -->
<!-- Zoom Control (Fixed Bottom Center, Draggable) -->
<div id="zoom-control" class="zoom-control no-print" role="group" aria-label="{{if eq .Lang "es"}}Control de zoom{{else}}Zoom control{{end}}">
<button
id="zoom-close"
class="zoom-close-btn"
aria-label="{{if eq .Lang "es"}}Cerrar control de zoom{{else}}Close zoom control{{end}}"
title="{{if eq .Lang "es"}}Cerrar{{else}}Close{{end}}">
<iconify-icon icon="mdi:close" width="16" height="16"></iconify-icon>
</button>
<span class="zoom-value zoom-value-min" aria-hidden="true">50</span>
<input