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:
+29
-1
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user