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:
1 parent
c30a0e9002
commit
6cc1e1820d
14 files changed
+1143
-849
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user