Compare commits
2 Commits
49df6bd44c
...
7e42164c5c
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e42164c5c | |||
| ed6d2e763d |
+2
-5
@@ -2,7 +2,6 @@ import { useState, useEffect } from 'react';
|
|||||||
import useLiveKit from './hooks/useLiveKit';
|
import useLiveKit from './hooks/useLiveKit';
|
||||||
import PTTButton from './components/PTTButton';
|
import PTTButton from './components/PTTButton';
|
||||||
import UserList from './components/UserList';
|
import UserList from './components/UserList';
|
||||||
import AudioIndicator from './components/AudioIndicator';
|
|
||||||
import GroupSelector from './components/GroupSelector';
|
import GroupSelector from './components/GroupSelector';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
|
||||||
@@ -241,14 +240,12 @@ function App() {
|
|||||||
{/* Liste des participants */}
|
{/* Liste des participants */}
|
||||||
<UserList participants={participants} />
|
<UserList participants={participants} />
|
||||||
|
|
||||||
{/* Indicateur audio */}
|
{/* Bouton PTT principal avec VU-mètre intégré */}
|
||||||
<AudioIndicator level={audioLevel} isTalking={isTalking} />
|
|
||||||
|
|
||||||
{/* Bouton PTT principal */}
|
|
||||||
<PTTButton
|
<PTTButton
|
||||||
isTalking={isTalking}
|
isTalking={isTalking}
|
||||||
onPressStart={startTalking}
|
onPressStart={startTalking}
|
||||||
onPressEnd={stopTalking}
|
onPressEnd={stopTalking}
|
||||||
|
audioLevel={audioLevel}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,6 +11,24 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Wrapper bouton + anneau VU-mètre */
|
||||||
|
.ptt-button-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anneau VU-mètre (SVG) */
|
||||||
|
.audio-ring {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ptt-button {
|
.ptt-button {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
height: 240px;
|
height: 240px;
|
||||||
@@ -27,6 +45,7 @@
|
|||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
z-index: 1; /* Au-dessus de l'anneau */
|
||||||
|
|
||||||
/* Mobile touch optimizations */
|
/* Mobile touch optimizations */
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
@@ -189,6 +208,11 @@
|
|||||||
height: 200px;
|
height: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audio-ring {
|
||||||
|
width: 240px;
|
||||||
|
height: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
.ptt-icon {
|
.ptt-icon {
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
@@ -210,6 +234,11 @@
|
|||||||
height: 160px;
|
height: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.audio-ring {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
.ptt-icon {
|
.ptt-icon {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ import './PTTButton.css';
|
|||||||
* Modes :
|
* Modes :
|
||||||
* - PTT classique : maintenir pour parler
|
* - PTT classique : maintenir pour parler
|
||||||
* - Mode continu (lock) : glisser vers le haut pendant qu'on parle
|
* - Mode continu (lock) : glisser vers le haut pendant qu'on parle
|
||||||
|
* Inclut VU-mètre intégré (anneau autour du bouton)
|
||||||
*/
|
*/
|
||||||
export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
export default function PTTButton({ isTalking, onPressStart, onPressEnd, audioLevel = 0 }) {
|
||||||
const buttonRef = useRef(null);
|
const buttonRef = useRef(null);
|
||||||
const isPressingRef = useRef(false);
|
const isPressingRef = useRef(false);
|
||||||
const [isLockMode, setIsLockMode] = useState(false);
|
const [isLockMode, setIsLockMode] = useState(false);
|
||||||
@@ -241,6 +242,25 @@ export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Calculer le niveau audio normalisé (0-100)
|
||||||
|
const normalizedLevel = Math.min(100, Math.max(0, audioLevel));
|
||||||
|
|
||||||
|
// Convertir le niveau en angle pour le cercle SVG (0-360°)
|
||||||
|
const levelAngle = (normalizedLevel / 100) * 360;
|
||||||
|
|
||||||
|
// Calculer le dasharray pour l'arc SVG
|
||||||
|
const radius = 130; // Rayon du cercle VU-mètre
|
||||||
|
const circumference = 2 * Math.PI * radius;
|
||||||
|
const dashOffset = circumference - (levelAngle / 360) * circumference;
|
||||||
|
|
||||||
|
// Déterminer la couleur selon le niveau
|
||||||
|
const getAudioColor = () => {
|
||||||
|
if (normalizedLevel > 90) return '#ef4444'; // Danger (rouge)
|
||||||
|
if (normalizedLevel > 75) return '#f59e0b'; // Warning (orange)
|
||||||
|
if (isTalking) return '#3b82f6'; // Talking (bleu)
|
||||||
|
return '#10b981'; // Normal (vert)
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="ptt-container">
|
<div className="ptt-container">
|
||||||
{/* Zone de drag vers le haut (indicateur visuel) */}
|
{/* Zone de drag vers le haut (indicateur visuel) */}
|
||||||
@@ -253,15 +273,53 @@ export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Bouton PTT principal */}
|
{/* Conteneur bouton + VU-mètre */}
|
||||||
<button
|
<div className="ptt-button-wrapper">
|
||||||
ref={buttonRef}
|
{/* VU-mètre circulaire (SVG) */}
|
||||||
className={`ptt-button ${isTalking ? 'talking' : ''} ${isLockMode ? 'locked' : ''}`}
|
<svg
|
||||||
type="button"
|
className="audio-ring"
|
||||||
style={{
|
width="280"
|
||||||
transform: dragOffset > 0 && !isLockMode ? `translateY(-${dragOffset * 0.3}px)` : 'none'
|
height="280"
|
||||||
}}
|
viewBox="0 0 280 280"
|
||||||
>
|
>
|
||||||
|
{/* Cercle de fond (gris) */}
|
||||||
|
<circle
|
||||||
|
cx="140"
|
||||||
|
cy="140"
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
stroke="rgba(255, 255, 255, 0.1)"
|
||||||
|
strokeWidth="8"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Cercle de niveau audio (coloré) */}
|
||||||
|
<circle
|
||||||
|
cx="140"
|
||||||
|
cy="140"
|
||||||
|
r={radius}
|
||||||
|
fill="none"
|
||||||
|
stroke={getAudioColor()}
|
||||||
|
strokeWidth="8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeDasharray={circumference}
|
||||||
|
strokeDashoffset={dashOffset}
|
||||||
|
transform="rotate(-90 140 140)"
|
||||||
|
style={{
|
||||||
|
transition: 'stroke-dashoffset 0.1s ease, stroke 0.2s ease',
|
||||||
|
filter: normalizedLevel > 0 ? `drop-shadow(0 0 8px ${getAudioColor()})` : 'none'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
{/* Bouton PTT principal */}
|
||||||
|
<button
|
||||||
|
ref={buttonRef}
|
||||||
|
className={`ptt-button ${isTalking ? 'talking' : ''} ${isLockMode ? 'locked' : ''}`}
|
||||||
|
type="button"
|
||||||
|
style={{
|
||||||
|
transform: dragOffset > 0 && !isLockMode ? `translateY(-${dragOffset * 0.3}px)` : 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
|
||||||
<div className="ptt-icon">
|
<div className="ptt-icon">
|
||||||
{isTalking ? (
|
{isTalking ? (
|
||||||
@@ -295,6 +353,7 @@ export default function PTTButton({ isTalking, onPressStart, onPressEnd }) {
|
|||||||
: 'Maintenir pour parler'}
|
: 'Maintenir pour parler'}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="ptt-hint">
|
<p className="ptt-hint">
|
||||||
{isLockMode
|
{isLockMode
|
||||||
|
|||||||
@@ -3,8 +3,32 @@
|
|||||||
.user-list {
|
.user-list {
|
||||||
background: var(--color-surface);
|
background: var(--color-surface);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
max-height: 180px;
|
max-height: 300px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* Améliorer le scroll sur mobile */
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
/* Style de scrollbar personnalisé */
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: rgba(255, 255, 255, 0.3) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scrollbar personnalisée (webkit) */
|
||||||
|
.user-list::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-list::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-list::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-list::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-list.empty {
|
.user-list.empty {
|
||||||
@@ -36,7 +60,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.user-list-items {
|
.user-list-items {
|
||||||
padding: var(--spacing-sm);
|
padding: var(--spacing-xs) var(--spacing-sm);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Item utilisateur */
|
/* Item utilisateur */
|
||||||
@@ -131,7 +158,7 @@
|
|||||||
/* Responsive */
|
/* Responsive */
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.user-list {
|
.user-list {
|
||||||
max-height: 150px;
|
max-height: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-avatar {
|
.user-avatar {
|
||||||
@@ -148,3 +175,18 @@
|
|||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Portrait mobile - encore plus d'espace */
|
||||||
|
@media (max-width: 640px) and (orientation: portrait) {
|
||||||
|
.user-list {
|
||||||
|
max-height: 200px;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Paysage - réduire un peu pour laisser place au PTT */
|
||||||
|
@media (max-height: 500px) and (orientation: landscape) {
|
||||||
|
.user-list {
|
||||||
|
max-height: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user