feat: Ajout du redimensionnement manuel du panneau de notes

- Poignée de redimensionnement avec indicateur visuel permanent (3 points)
- Largeur ajustable entre 280px et 600px
- Sauvegarde automatique de la largeur préférée
- Optimisations avec requestAnimationFrame pour la fluidité
- Overlay pendant le drag pour capturer tous les mouvements
- Indicateur change de couleur au survol (gris → violet)
This commit is contained in:
Pierre Marx
2025-09-04 16:22:09 -04:00
parent 29a4cfb410
commit d66af7d99d
4 changed files with 218 additions and 2 deletions

View File

@@ -1,5 +1,29 @@
# Changelog - SimpleConnect Electron # Changelog - SimpleConnect Electron
## [1.2.12] - 2025-09-04
### Ajouté
- **Redimensionnement du panneau de notes** : Possibilité d'ajuster la largeur
- Poignée de redimensionnement sur le bord gauche du panneau
- Indicateur visuel permanent (3 points verticaux)
- Largeur minimale : 280px, maximale : 600px
- Sauvegarde automatique de la largeur préférée dans localStorage
- Restauration de la largeur à la réouverture
### Amélioré
- **Fluidité du redimensionnement** : Optimisations pour une meilleure performance
- Utilisation de requestAnimationFrame pour 60 FPS
- Overlay invisible pendant le drag pour capturer tous les mouvements
- Désactivation des transitions CSS pendant le redimensionnement
- Indicateur visuel toujours visible (pas seulement au survol)
- Changement de couleur de l'indicateur au survol (gris → violet)
### Technique
- Variable CSS `--notes-width` pour synchroniser panneau et webview
- Classes `.resizing` pour désactiver les transitions pendant le drag
- Gestion des événements mouse avec requestAnimationFrame
- Limites de taille avec Math.min/max pour contraindre la largeur
## [1.2.11] - 2025-09-04 ## [1.2.11] - 2025-09-04
### Ajouté ### Ajouté

View File

@@ -105,6 +105,7 @@
<!-- Zone de notes rapides - Panneau latéral --> <!-- Zone de notes rapides - Panneau latéral -->
<div id="notesSection" class="notes-section"> <div id="notesSection" class="notes-section">
<div class="notes-resize-handle" id="notesResizeHandle"></div>
<div class="notes-header"> <div class="notes-header">
<h4>Notes rapides</h4> <h4>Notes rapides</h4>
<button id="closeNotesBtn" class="btn-close-notes" title="Fermer">×</button> <button id="closeNotesBtn" class="btn-close-notes" title="Fermer">×</button>

View File

@@ -11,6 +11,11 @@ let callStats = {
appointments: 0 appointments: 0
}; };
// Variables pour le redimensionnement des notes
let isResizingNotes = false;
let notesStartWidth = 380;
let notesStartX = 0;
// === GESTION DE LA CONNEXION === // === GESTION DE LA CONNEXION ===
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
// Initialiser l'indicateur SignalR // Initialiser l'indicateur SignalR
@@ -90,6 +95,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// Charger les préférences utilisateur // Charger les préférences utilisateur
loadUserPreferences(); loadUserPreferences();
// Initialiser le redimensionnement des notes
initNotesResize();
// Gérer les boutons de la modal de déconnexion (si on est sur la page principale) // Gérer les boutons de la modal de déconnexion (si on est sur la page principale)
const cancelLogoutBtn = document.getElementById('cancelLogoutBtn'); const cancelLogoutBtn = document.getElementById('cancelLogoutBtn');
@@ -860,6 +868,16 @@ function showNotes() {
const toggleBtn = document.getElementById('toggleNotesBtn'); const toggleBtn = document.getElementById('toggleNotesBtn');
const webviewContainer = document.getElementById('webviewContainer'); const webviewContainer = document.getElementById('webviewContainer');
// Restaurer la largeur sauvegardée
const savedWidth = localStorage.getItem('notesWidth');
if (savedWidth && notesSection) {
const width = parseInt(savedWidth);
if (width >= 280 && width <= 600) {
notesSection.style.width = `${width}px`;
document.documentElement.style.setProperty('--notes-width', `${width}px`);
}
}
notesSection.classList.add('visible'); notesSection.classList.add('visible');
toggleBtn.classList.add('active'); toggleBtn.classList.add('active');
@@ -915,6 +933,19 @@ function loadUserPreferences() {
// L'utilisateur devra cliquer pour les afficher // L'utilisateur devra cliquer pour les afficher
} }
// Charger la largeur sauvegardée des notes
const savedNotesWidth = localStorage.getItem('notesWidth');
if (savedNotesWidth) {
const width = parseInt(savedNotesWidth);
if (width >= 280 && width <= 600) {
document.documentElement.style.setProperty('--notes-width', `${width}px`);
const notesSection = document.getElementById('notesSection');
if (notesSection) {
notesSection.style.width = `${width}px`;
}
}
}
// Charger d'autres préférences si nécessaire // Charger d'autres préférences si nécessaire
const lastTheme = localStorage.getItem('theme'); const lastTheme = localStorage.getItem('theme');
if (lastTheme) { if (lastTheme) {
@@ -924,8 +955,10 @@ function loadUserPreferences() {
function saveUserPreferences() { function saveUserPreferences() {
// Cette fonction peut être étendue pour sauvegarder d'autres préférences // Cette fonction peut être étendue pour sauvegarder d'autres préférences
const notesSection = document.getElementById('notesSection');
const preferences = { const preferences = {
notesVisible: document.getElementById('notesSection').classList.contains('visible'), notesVisible: notesSection.classList.contains('visible'),
notesWidth: notesSection ? notesSection.offsetWidth : 380,
theme: document.body.getAttribute('data-theme') || 'light' theme: document.body.getAttribute('data-theme') || 'light'
}; };
@@ -934,6 +967,99 @@ function saveUserPreferences() {
}); });
} }
// === GESTION DU REDIMENSIONNEMENT DES NOTES ===
function initNotesResize() {
const resizeHandle = document.getElementById('notesResizeHandle');
const notesSection = document.getElementById('notesSection');
const webviewContainer = document.getElementById('webviewContainer');
if (!resizeHandle || !notesSection) return;
let rafId = null;
resizeHandle.addEventListener('mousedown', (e) => {
isResizingNotes = true;
notesStartX = e.clientX;
notesStartWidth = notesSection.offsetWidth;
// Ajouter les classes pour désactiver les transitions
notesSection.classList.add('resizing');
if (webviewContainer) {
webviewContainer.classList.add('resizing');
}
// Empêcher la sélection de texte pendant le redimensionnement
document.body.style.userSelect = 'none';
document.body.style.cursor = 'col-resize';
// Ajouter un overlay pour capturer tous les mouvements de souris
const overlay = document.createElement('div');
overlay.id = 'resize-overlay';
overlay.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 9999; cursor: col-resize;';
document.body.appendChild(overlay);
e.preventDefault();
});
const handleMouseMove = (e) => {
if (!isResizingNotes) return;
// Utiliser requestAnimationFrame pour la fluidité
if (rafId) cancelAnimationFrame(rafId);
rafId = requestAnimationFrame(() => {
// Calculer la nouvelle largeur (inversé car le panneau est à droite)
const deltaX = notesStartX - e.clientX;
const newWidth = notesStartWidth + deltaX;
// Appliquer les limites min/max
const clampedWidth = Math.min(Math.max(newWidth, 280), 600);
// Mettre à jour la largeur avec transform pour plus de fluidité
notesSection.style.width = `${clampedWidth}px`;
// Mettre à jour la variable CSS pour la webview
document.documentElement.style.setProperty('--notes-width', `${clampedWidth}px`);
});
};
const handleMouseUp = () => {
if (!isResizingNotes) return;
isResizingNotes = false;
// Annuler l'animation frame en cours
if (rafId) cancelAnimationFrame(rafId);
// Retirer l'overlay
const overlay = document.getElementById('resize-overlay');
if (overlay) overlay.remove();
// Retirer les classes de redimensionnement
const notesSection = document.getElementById('notesSection');
const webviewContainer = document.getElementById('webviewContainer');
if (notesSection) {
notesSection.classList.remove('resizing');
}
if (webviewContainer) {
webviewContainer.classList.remove('resizing');
}
// Réactiver la sélection de texte
document.body.style.userSelect = '';
document.body.style.cursor = '';
// Sauvegarder la nouvelle largeur
if (notesSection) {
localStorage.setItem('notesWidth', notesSection.offsetWidth);
}
};
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
// === FONCTION DE RAFRAÎCHISSEMENT === // === FONCTION DE RAFRAÎCHISSEMENT ===
function refreshCurrentWebview() { function refreshCurrentWebview() {
if (activeCenter && webviews[activeCenter]) { if (activeCenter && webviews[activeCenter]) {

View File

@@ -618,6 +618,8 @@ body {
top: 60px; /* Juste sous le header */ top: 60px; /* Juste sous le header */
right: -400px; /* Caché par défaut */ right: -400px; /* Caché par défaut */
width: 380px; width: 380px;
min-width: 280px;
max-width: 600px;
height: calc(100vh - 60px); height: calc(100vh - 60px);
background: white; background: white;
box-shadow: -4px 0 20px rgba(0,0,0,0.1); box-shadow: -4px 0 20px rgba(0,0,0,0.1);
@@ -628,6 +630,69 @@ body {
border-left: 1px solid #e1e4e8; border-left: 1px solid #e1e4e8;
} }
/* Poignée de redimensionnement */
.notes-resize-handle {
position: absolute;
left: -5px;
top: 0;
bottom: 0;
width: 10px;
cursor: col-resize;
background: linear-gradient(90deg,
transparent 0%,
transparent 30%,
rgba(209, 213, 219, 0.5) 50%,
transparent 70%,
transparent 100%);
z-index: 10;
}
.notes-resize-handle:hover {
background: linear-gradient(90deg,
transparent 0%,
rgba(102, 126, 234, 0.1) 30%,
rgba(102, 126, 234, 0.3) 50%,
rgba(102, 126, 234, 0.1) 70%,
transparent 100%);
}
.notes-resize-handle:active {
background: linear-gradient(90deg,
transparent 0%,
rgba(102, 126, 234, 0.2) 30%,
rgba(102, 126, 234, 0.5) 50%,
rgba(102, 126, 234, 0.2) 70%,
transparent 100%);
}
/* Indicateur visuel permanent - 3 points verticaux */
.notes-resize-handle::before {
content: '⋮⋮⋮';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%) rotate(90deg);
font-size: 16px;
color: #9ca3af;
letter-spacing: -4px;
font-weight: bold;
pointer-events: none;
user-select: none;
}
.notes-resize-handle:hover::before {
color: #667eea;
}
/* Désactiver les transitions pendant le redimensionnement */
.notes-section.resizing {
transition: none !important;
}
.webview-container.resizing {
transition: none !important;
}
.notes-section.visible { .notes-section.visible {
right: 0; right: 0;
animation: slideInRight 0.3s ease-out; animation: slideInRight 0.3s ease-out;
@@ -750,7 +815,7 @@ body {
/* Ajustement de la webview quand les notes sont visibles */ /* Ajustement de la webview quand les notes sont visibles */
.webview-container.notes-open { .webview-container.notes-open {
margin-right: 380px; margin-right: var(--notes-width, 380px);
transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1); transition: margin-right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
} }