lct-17: пульт преподавателя — сценарий, наблюдение, пометки, ссылки
Компоновка от того, по чему пульт оценит действующий преподаватель: задать сценарий и не вмешиваться, видеть работу курсанта вживую, получить готовый разбор, показать группе. Кнопка запуска одна и крупная, ссылки на АРМ курсанта и монитор печатаются рядом. Разделение прав архитектурное и здесь: пульт смотрит через observe, пишет через control, карточка курсанта у него только для чтения. Две ловушки запуска: scenario.start уходил из эффекта и мог повториться при перерисовке, а открытая из истории ссылка на идущее занятие запустила бы его заново и сбросила. Запускается только занятие, созданное кнопкой.
This commit is contained in:
parent
fadfa3e479
commit
978d7dc1aa
5 changed files with 269 additions and 1 deletions
49
frontend/src/features/instructor/useControl.ts
Normal file
49
frontend/src/features/instructor/useControl.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Канал преподавателя: только передача.
|
||||
//
|
||||
// Преподаватель смотрит через `observe`, а пишет через `control`, и это разные
|
||||
// сокеты. В `control` нет ни одной команды, меняющей карточку курсанта: он
|
||||
// управляет ситуацией, а не работой обучаемого, иначе оценка перестаёт быть
|
||||
// оценкой курсанта (docs/arch/CONTRACT.md).
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
import { controlChannel, type ChannelStatus } from "@/shared/api/ws";
|
||||
import type { SessionMode } from "@/shared/types/generated";
|
||||
|
||||
export function useControl(sessionId: string | null) {
|
||||
const [status, setStatus] = useState<ChannelStatus>("closed");
|
||||
const channel = useRef<ReturnType<typeof controlChannel> | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId) return;
|
||||
const ch = controlChannel(sessionId, { onStatus: setStatus }).connect();
|
||||
channel.current = ch;
|
||||
return () => {
|
||||
ch.close();
|
||||
channel.current = null;
|
||||
};
|
||||
}, [sessionId]);
|
||||
|
||||
const start = useCallback(
|
||||
(scenarioId: string, mode: SessionMode, trainee: string, group?: string) =>
|
||||
channel.current?.send({
|
||||
type: "scenario.start",
|
||||
scenario_id: scenarioId,
|
||||
mode,
|
||||
trainee,
|
||||
group_id: group || null,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const note = useCallback(
|
||||
(transcriptRef: string, text: string) =>
|
||||
channel.current?.send({ type: "instructor_note.add", transcript_ref: transcriptRef, text }),
|
||||
[],
|
||||
);
|
||||
|
||||
const stop = useCallback(() => channel.current?.send({ type: "session.stop" }), []);
|
||||
const playReference = useCallback(() => channel.current?.send({ type: "reference.play" }), []);
|
||||
|
||||
return { status, start, note, stop, playReference };
|
||||
}
|
||||
Loading…
Reference in a new issue