// Le parcours de dictée composé pour la largeur d'un bureau.
//
// Le flux n'est ni remplacé ni recopié. Il tient ses deux étapes, la capture
// audio, l'envoi des fragments, la remise à l'Assistant, ses refus et son
// patient armé. Cette couche ne compose que le cadre : une colonne de travail
// bornée et centrée, un en-tête qui dit l'étape et le patient, et une sortie au
// clavier que le parcours n'avait nulle part.
//
// Elle est chargée avant `legacy-surfaces.jsx` : l'hôte de prise de fenêtre du
// lot 3 enveloppe donc cette composition et lui donne toute la fenêtre, au lieu
// de l'enfermer dans la colonne de contenu. Sous le seuil, l'hôte rend l'arbre
// tel quel et cette couche rend le flux tel quel : rien ne change au téléphone.
const VASCO_DICTATION_DESKTOP_FLOW = DictationFlow;
// `DV6_STORE` est une liaison de premier niveau de `dictation-v6.jsx` : lisible
// par son nom depuis un script chargé après lui, invisible sur `window`. Le
// tableau de bord de bureau arme un patient avant d'ouvrir la dictée ; c'est
// cette couche-ci, et non la cible téléphone, qui lui ouvre la porte. Une seule
// instance, celle du parcours.
window.VascoDictationArmedPatients = DV6_STORE;
// L'étape courante, lue dans l'arbre que le parcours rend déjà : il monte sa
// couche de travail sous une clé qui est son étape (`
`), et
// React en fait une propriété publique de l'élément. On la lit, on ne la déduit
// pas, et le parcours n'a rien à publier pour nous.
function vascoDictationDesktopStep(tree) {
if (!React.isValidElement(tree)) return null;
const children = Array.isArray(tree.props.children)
? tree.props.children : [tree.props.children];
const layer = children.find((child) => React.isValidElement(child)
&& typeof child.key === 'string' && child.key.length > 0);
return layer ? layer.key : null;
}
// Les deux étapes du flux, dites dans les mots qu'il affiche déjà lui-même.
const VASCO_DICTATION_STEP_LABELS = Object.freeze({
recording: 'Enregistrement',
processing: 'Envoi et remise à l’Assistant',
});
function VascoDictationDesktopFlow(props) {
const tokens = window.VascoUI.tokens;
const { IconButton, icons } = window.VascoUI;
const useLayout = window.VascoUI.useLayout || (() => 'phone');
const desktop = useLayout() === 'desktop';
// Appel inconditionnel : les états du parcours appartiennent alors à cette
// couche et y restent stables. C'est le mécanisme déjà employé par
// `document-import.jsx` et `legacy-surfaces.jsx`.
const flow = VASCO_DICTATION_DESKTOP_FLOW(props);
const publishedStep = vascoDictationDesktopStep(flow) || 'recording';
// Pendant l'envoi, le téléphone n'offre aucune sortie : la fenêtre non plus.
// Fermer à cet instant jetterait un enregistrement déjà parti au serveur.
const sending = publishedStep === 'processing';
React.useEffect(() => {
if (!desktop || sending) return undefined;
const onKeyDown = (event) => {
if (event.key !== 'Escape') return;
event.preventDefault();
props.onClose?.();
};
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [desktop, sending, props.onClose]);
if (!desktop) return flow;
// Le patient armé est celui du parcours, lu dans le même magasin : on ne le
// choisit pas ici et on ne le consomme pas.
const armed = props.patient || DV6_STORE.current() || null;
const patientLabel = typeof dv6PatientLabel === 'function'
? dv6PatientLabel(armed) : (armed?.display_name || 'Sans patient');
return
{VASCO_DICTATION_STEP_LABELS[publishedStep] || VASCO_DICTATION_STEP_LABELS.recording}
{patientLabel}
{/* Le parcours n'avait aucune croix : au téléphone on sortait par
« Annuler », et pendant l'envoi on ne sortait pas du tout. La fenêtre
garde exactement cette règle, refus compris. */}
props.onClose?.()} size={tokens.control.icon}>
{flow}
;
}
DictationFlow = VascoDictationDesktopFlow;
window.DictationFlow = VascoDictationDesktopFlow;
window.VascoDictationDesktop = VascoDictationDesktopFlow;