// 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;