V1.1.0 //

-> Refonte du design de sélection d'afficheurs
-> Ajout du Mode PiP
-> Patch du bug du logo de l'horaire pas affiché. Corrigé pour les Horaires TER à 6 numéros.
This commit is contained in:
AdminGit committed 2026-10-07 15:32:21 +02:00
1 parent c30a0e9002
commit 6cc1e1820d
14 files changed
+1143 -849

No files matched your search

+141 -21
View File
@@ -1,23 +1,24 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import './AfficheurViewportWrapper.css';
import { createPortal } from 'react-dom';
/**
* AfficheurViewportWrapper
* Assure un affichage 16:9 strict (1920x1080) sur tous les appareils
* (Mobile portrait, Mobile paysage, Tablettes, Ordinateurs, TV).
* Ajoute automatiquement des bordures noires (letterboxing / pillarboxing)
* pour conserver fidèlement les proportions du design original sans distorsion.
*/
export default function AfficheurViewportWrapper({ children }) {
const [scale, setScale] = useState(1);
const [pipWindow, setPipWindow] = useState(null);
const containerRef = useRef(null);
const [isPipSupported, setIsPipSupported] = useState(false);
const [autoPipTriggered, setAutoPipTriggered] = useState(false);
const isMobile = typeof window !== 'undefined' && (window.innerWidth <= 768 || /Mobi|Android/i.test(navigator.userAgent));
useEffect(() => {
if ('documentPictureInPicture' in window) {
setIsPipSupported(true);
}
const calculateScale = () => {
// Dimensions de la fenêtre d'affichage (support viewport iOS / Android safe area)
const w = window.innerWidth || (document.documentElement && document.documentElement.clientWidth) || 1920;
const h = window.innerHeight || (document.documentElement && document.documentElement.clientHeight) || 1080;
// Échelle basée sur la résolution standard 1920x1080 (16:9)
const scaleX = w / 1920;
const scaleY = h / 1080;
const s = Math.min(scaleX, scaleY);
@@ -27,7 +28,6 @@ export default function AfficheurViewportWrapper({ children }) {
calculateScale();
window.addEventListener('resize', calculateScale);
window.addEventListener('orientationchange', calculateScale);
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', calculateScale);
}
@@ -41,16 +41,136 @@ export default function AfficheurViewportWrapper({ children }) {
};
}, []);
const togglePiP = async () => {
if (pipWindow) {
pipWindow.close();
return;
}
if ('documentPictureInPicture' in window) {
try {
const pipW = isMobile ? window.innerWidth * 0.8 : 1920 / 3;
const pipH = isMobile ? (window.innerWidth * 0.8) * (1080 / 1920) : 1080 / 3;
const pip = await window.documentPictureInPicture.requestWindow({
width: Math.max(pipW, 300),
height: Math.max(pipH, 170),
});
[...document.styleSheets].forEach((styleSheet) => {
try {
const cssRules = [...styleSheet.cssRules].map(rule => rule.cssText).join('');
const style = document.createElement('style');
style.textContent = cssRules;
pip.document.head.appendChild(style);
} catch(e) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = styleSheet.type;
link.media = styleSheet.media;
link.href = styleSheet.href;
pip.document.head.appendChild(link);
}
});
pip.document.body.classList.add('pip-mode');
pip.document.body.style.margin = '0';
pip.document.body.style.padding = '0';
pip.document.body.style.overflow = 'hidden';
pip.document.body.style.width = '100vw';
pip.document.body.style.height = '100vh';
pip.document.body.style.backgroundColor = '#000';
pip.document.body.style.display = 'flex';
pip.document.body.style.justifyContent = 'center';
pip.document.body.style.alignItems = 'center';
const updatePipScale = () => {
const w = pip.innerWidth;
const h = pip.innerHeight;
const pipScale = Math.min(w / 1920, h / 1080);
pip.document.body.style.setProperty('--pip-scale', pipScale);
};
updatePipScale();
pip.addEventListener('resize', updatePipScale);
pip.addEventListener("pagehide", () => {
setPipWindow(null);
});
setPipWindow(pip);
setAutoPipTriggered(true);
} catch (err) {
console.error('Failed to enter PiP mode', err);
}
}
};
const content = (
<div
className="afficheur-pwa-canvas"
style={{
transform: pipWindow ? 'scale(1)' : `scale(${scale})`,
width: '1920px',
height: '1080px',
position: 'relative'
}}
>
{children}
</div>
);
return (
<div className="afficheur-pwa-screen-wrapper">
<div
className="afficheur-pwa-canvas"
style={{
transform: `scale(${scale})`,
}}
>
{children}
</div>
<div className="afficheur-pwa-screen-wrapper" ref={containerRef} style={{ width: '100vw', height: '100vh', overflow: 'hidden' }}>
{!pipWindow && content}
{isMobile && isPipSupported && !pipWindow && !autoPipTriggered && (
<div
onClick={togglePiP}
style={{
position: 'absolute',
top: 0, left: 0, right: 0, bottom: 0,
backgroundColor: 'rgba(0,0,0,0.85)',
zIndex: 10000,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
color: 'white',
cursor: 'pointer',
padding: '20px',
textAlign: 'center'
}}
>
<span style={{ fontSize: '48px', marginBottom: '16px' }}>📱</span>
<h2 style={{ margin: '0 0 16px 0' }}>Appuyez pour lancer l'afficheur</h2>
<p style={{ opacity: 0.8 }}>Le mode Picture-in-Picture s'activera pour une meilleure expérience sur mobile.</p>
</div>
)}
{pipWindow && (
<div style={{ color: 'white', textAlign: 'center', padding: '24px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
<h2>L'afficheur est en cours de lecture dans le lecteur réduit (PiP)</h2>
<p style={{ opacity: 0.7 }}>Vous pouvez naviguer sur votre téléphone, l'afficheur restera au premier plan.</p>
<button onClick={() => pipWindow.close()} style={{ padding: '12px 24px', marginTop: '24px', background: '#00439e', color: 'white', border: 'none', borderRadius: '4px', fontSize: '16px', fontWeight: 'bold' }}>
Fermer le mode PiP
</button>
</div>
)}
{pipWindow && createPortal(
<div style={{
width: '100%',
height: '100%',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
transform: `scale(var(--pip-scale, 1))`
}}>
{content}
</div>,
pipWindow.document.body
)}
</div>
);
}