fix: UX interface admin et client
- Settings : suppression paramètres inutiles (mode PTT continu, feedback audio non implémenté) - Settings : conservation uniquement du paramètre vibrations (fonctionnel) - PTTButton : suppression init mode continu par défaut (redondant avec geste verrouillage) - PWAInstallPrompt : ajout fond semi-transparent et couleurs hardcodées pour lisibilité - Admin : fix dropdowns audio qui se réinitialisaient (useRef au lieu de useState pour édition)
This commit is contained in:
+24
-18
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import './Admin.css';
|
||||
import AudioRoutingMatrix from './components/AudioRoutingMatrix';
|
||||
|
||||
@@ -19,11 +19,12 @@ function Admin() {
|
||||
const [selectedInputDevice, setSelectedInputDevice] = useState(null);
|
||||
const [selectedOutputDevice, setSelectedOutputDevice] = useState(null);
|
||||
const [selectedSampleRate, setSelectedSampleRate] = useState(48000);
|
||||
const [isEditingAudio, setIsEditingAudio] = useState(false);
|
||||
const isEditingAudioRef = useRef(false);
|
||||
|
||||
// Channel names (Phase 2.5)
|
||||
const [channelNames, setChannelNames] = useState({ inputs: {}, outputs: {} });
|
||||
const [editingChannelNames, setEditingChannelNames] = useState(false);
|
||||
const editingChannelNamesRef = useRef(false);
|
||||
const [, forceUpdate] = useState({});
|
||||
|
||||
// Gestion formulaire nouveau groupe
|
||||
const [showGroupForm, setShowGroupForm] = useState(false);
|
||||
@@ -104,10 +105,14 @@ function Admin() {
|
||||
|
||||
const device = currentData.device || { inputChannels: 8, outputChannels: 8 };
|
||||
setCurrentDevice(device);
|
||||
setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} });
|
||||
|
||||
// Ne réinitialiser les sélections que si l'utilisateur n'est pas en train d'éditer
|
||||
if (!isEditingAudio) {
|
||||
// Ne pas écraser les noms de canaux pendant l'édition
|
||||
if (!editingChannelNamesRef.current) {
|
||||
setChannelNames(channelNamesData.channelNames || { inputs: {}, outputs: {} });
|
||||
}
|
||||
|
||||
// Ne réinitialiser les sélections que lors du chargement initial (pas en train d'éditer)
|
||||
if (!isEditingAudioRef.current) {
|
||||
setSelectedInputDevice(device.inputDeviceId ?? null);
|
||||
setSelectedOutputDevice(device.outputDeviceId ?? null);
|
||||
setSelectedSampleRate(device.sampleRate || 48000);
|
||||
@@ -216,7 +221,8 @@ function Admin() {
|
||||
|
||||
if (res.ok) {
|
||||
alert('Noms de canaux sauvegardés avec succès!');
|
||||
setEditingChannelNames(false);
|
||||
editingChannelNamesRef.current = false;
|
||||
forceUpdate({});
|
||||
await loadAudioDevices();
|
||||
} else {
|
||||
const error = await res.json();
|
||||
@@ -251,7 +257,7 @@ function Admin() {
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
setIsEditingAudio(false); // Désactiver le mode édition
|
||||
isEditingAudioRef.current = false; // Désactiver le mode édition
|
||||
alert('Configuration audio sauvegardée avec succès!');
|
||||
await loadAudioDevices();
|
||||
} else {
|
||||
@@ -442,7 +448,7 @@ function Admin() {
|
||||
<select
|
||||
value={selectedInputDevice ?? ''}
|
||||
onChange={(e) => {
|
||||
setIsEditingAudio(true);
|
||||
isEditingAudioRef.current = true;
|
||||
setSelectedInputDevice(e.target.value === '' ? null : e.target.value);
|
||||
}}
|
||||
className="device-select"
|
||||
@@ -468,7 +474,7 @@ function Admin() {
|
||||
<select
|
||||
value={selectedOutputDevice ?? ''}
|
||||
onChange={(e) => {
|
||||
setIsEditingAudio(true);
|
||||
isEditingAudioRef.current = true;
|
||||
setSelectedOutputDevice(e.target.value === '' ? null : e.target.value);
|
||||
}}
|
||||
className="device-select"
|
||||
@@ -494,7 +500,7 @@ function Admin() {
|
||||
<select
|
||||
value={selectedSampleRate}
|
||||
onChange={(e) => {
|
||||
setIsEditingAudio(true);
|
||||
isEditingAudioRef.current = true;
|
||||
setSelectedSampleRate(parseInt(e.target.value));
|
||||
}}
|
||||
className="device-select"
|
||||
@@ -514,8 +520,8 @@ function Admin() {
|
||||
<div className="audio-section">
|
||||
<div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 'var(--spacing-md)'}}>
|
||||
<h3>Nommage des canaux physiques</h3>
|
||||
{!editingChannelNames ? (
|
||||
<button onClick={() => setEditingChannelNames(true)} className="btn-secondary">
|
||||
{!editingChannelNamesRef.current ? (
|
||||
<button onClick={() => { editingChannelNamesRef.current = true; forceUpdate({}); }} className="btn-secondary">
|
||||
Modifier les noms
|
||||
</button>
|
||||
) : (
|
||||
@@ -523,7 +529,7 @@ function Admin() {
|
||||
<button onClick={handleSaveChannelNames} className="btn-primary">
|
||||
Sauvegarder
|
||||
</button>
|
||||
<button onClick={() => { setEditingChannelNames(false); loadAudioDevices(); }} className="btn-secondary">
|
||||
<button onClick={() => { editingChannelNamesRef.current = false; forceUpdate({}); loadAudioDevices(); }} className="btn-secondary">
|
||||
Annuler
|
||||
</button>
|
||||
</div>
|
||||
@@ -544,10 +550,10 @@ function Admin() {
|
||||
value={channelNames.inputs?.[i] || ''}
|
||||
onChange={(e) => updateChannelName('inputs', i, e.target.value)}
|
||||
placeholder={`Input ${i}`}
|
||||
disabled={!editingChannelNames}
|
||||
disabled={!editingChannelNamesRef.current}
|
||||
style={{
|
||||
padding: 'var(--spacing-sm)',
|
||||
background: editingChannelNames ? 'var(--color-bg)' : 'var(--color-surface-hover)',
|
||||
background: editingChannelNamesRef.current ? 'var(--color-bg)' : 'var(--color-surface-hover)',
|
||||
border: '1px solid var(--color-border)',
|
||||
borderRadius: '6px',
|
||||
color: 'var(--color-text)',
|
||||
@@ -572,10 +578,10 @@ function Admin() {
|
||||
value={channelNames.outputs?.[i] || ''}
|
||||
onChange={(e) => updateChannelName('outputs', i, e.target.value)}
|
||||
placeholder={`Output ${i}`}
|
||||
disabled={!editingChannelNames}
|
||||
disabled={!editingChannelNamesRef.current}
|
||||
style={{
|
||||
padding: 'var(--spacing-sm)',
|
||||
background: editingChannelNames ? 'var(--color-bg)' : 'var(--color-surface-hover)',
|
||||
background: editingChannelNamesRef.current ? 'var(--color-bg)' : 'var(--color-surface-hover)',
|
||||
border: '1px solid var(--color-border)',
|
||||
borderRadius: '6px',
|
||||
color: 'var(--color-text)',
|
||||
|
||||
@@ -22,16 +22,6 @@ export default function PTTButton({ isTalking, onPressStart, onPressEnd, audioLe
|
||||
const currentYRef = useRef(null);
|
||||
const [dragOffset, setDragOffset] = useState(0); // Offset visuel du drag (en pixels)
|
||||
|
||||
// Initialiser le mode selon les préférences au démarrage
|
||||
useEffect(() => {
|
||||
const currentSettings = loadSettings();
|
||||
if (currentSettings.defaultPTTMode === 'continuous') {
|
||||
setIsLockMode(true);
|
||||
isLockModeRef.current = true;
|
||||
console.log('Mode continu activé par défaut (préférences)');
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const button = buttonRef.current;
|
||||
if (!button) return;
|
||||
|
||||
@@ -3,8 +3,22 @@
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 1001;
|
||||
animation: slideUp 0.3s ease;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
@@ -17,12 +31,14 @@
|
||||
}
|
||||
|
||||
.pwa-prompt {
|
||||
background: var(--bg-secondary);
|
||||
width: 100%;
|
||||
background: #1a1a1a;
|
||||
border-top-left-radius: 16px;
|
||||
border-top-right-radius: 16px;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
animation: slideUp 0.3s ease;
|
||||
}
|
||||
|
||||
.pwa-prompt-header {
|
||||
@@ -36,13 +52,13 @@
|
||||
.pwa-prompt-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
color: var(--text-primary);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pwa-prompt-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-radius: 8px;
|
||||
@@ -50,8 +66,8 @@
|
||||
}
|
||||
|
||||
.pwa-prompt-close:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.pwa-prompt-content {
|
||||
@@ -59,8 +75,8 @@
|
||||
}
|
||||
|
||||
.pwa-prompt-content > p {
|
||||
margin: 0 0 var(--spacing-lg) 0;
|
||||
color: var(--text-secondary);
|
||||
margin: 0 0 1.5rem 0;
|
||||
color: #d1d5db;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@@ -73,9 +89,9 @@
|
||||
.pwa-prompt-step {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-md);
|
||||
padding: var(--spacing-md);
|
||||
background: var(--bg-hover);
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
@@ -85,7 +101,7 @@
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--primary-color);
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-weight: 600;
|
||||
@@ -95,13 +111,13 @@
|
||||
.pwa-prompt-step p {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
color: var(--text-primary);
|
||||
color: #ffffff;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.pwa-prompt-step svg {
|
||||
flex-shrink: 0;
|
||||
color: var(--primary-color);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.pwa-prompt-footer {
|
||||
|
||||
@@ -4,9 +4,7 @@ import './Settings.css';
|
||||
const STORAGE_KEY = 'ptt-live-settings';
|
||||
|
||||
const defaultSettings = {
|
||||
defaultPTTMode: 'normal', // 'normal' ou 'continuous'
|
||||
vibrationEnabled: true,
|
||||
audioFeedbackEnabled: true
|
||||
vibrationEnabled: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -68,39 +66,6 @@ export default function Settings({ isOpen, onClose }) {
|
||||
</div>
|
||||
|
||||
<div className="settings-content">
|
||||
<div className="setting-section">
|
||||
<h3>Mode PTT</h3>
|
||||
<p className="setting-description">
|
||||
Choisissez le mode de fonctionnement par défaut du bouton PTT
|
||||
</p>
|
||||
|
||||
<label className="radio-option">
|
||||
<input
|
||||
type="radio"
|
||||
name="pttMode"
|
||||
checked={settings.defaultPTTMode === 'normal'}
|
||||
onChange={() => handleChange('defaultPTTMode', 'normal')}
|
||||
/>
|
||||
<div>
|
||||
<strong>Mode normal (Push-To-Talk)</strong>
|
||||
<p>Maintenir le bouton pour parler, relâcher pour arrêter</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="radio-option">
|
||||
<input
|
||||
type="radio"
|
||||
name="pttMode"
|
||||
checked={settings.defaultPTTMode === 'continuous'}
|
||||
onChange={() => handleChange('defaultPTTMode', 'continuous')}
|
||||
/>
|
||||
<div>
|
||||
<strong>Mode continu (verrouillé)</strong>
|
||||
<p>Un appui active le micro en continu, un second appui le désactive</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="setting-section">
|
||||
<h3>Feedback</h3>
|
||||
|
||||
@@ -112,19 +77,7 @@ export default function Settings({ isOpen, onClose }) {
|
||||
/>
|
||||
<div>
|
||||
<strong>Vibrations</strong>
|
||||
<p>Activer le retour haptique (si disponible)</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label className="checkbox-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={settings.audioFeedbackEnabled}
|
||||
onChange={(e) => handleChange('audioFeedbackEnabled', e.target.checked)}
|
||||
/>
|
||||
<div>
|
||||
<strong>Feedback audio</strong>
|
||||
<p>Sons de confirmation pour les actions</p>
|
||||
<p>Activer le retour haptique lors du verrouillage PTT</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user