import { reactive, shallowReactive } from 'vue' import { validAccessToken } from '../tokens' import type { BoardState, ClientMessage, ControlNode, Diagnostic, ServerMessage, SourceFile, UserAction, } from '@iot/shared' export interface OpenForm { root: ControlNode modal: boolean } /** 学生程序保存的图片(如摄像头照片),path 相对程序所在目录 */ export interface SavedImage { path: string image: string } export interface PendingMessageBox { requestId: number text: string caption: string buttons: string icon: string } /** 浏览器端的运行状态:WebSocket 连接、打开的窗体、控件树、实训台设备状态 */ export function createRunSession() { const state = reactive({ status: 'idle' as 'idle' | 'compiling' | 'running' | 'stopped', diagnostics: [] as Diagnostic[], console: '', errors: [] as string[], forms: [] as OpenForm[], messageBoxes: [] as PendingMessageBox[], /** 虚拟实训台,由服务器推送;连接建立前为 null */ board: null as BoardState | null, /** 本次运行保存的图片,显示在运行区的照片列表里;下次运行时清空 */ savedImages: [] as SavedImage[], /** 正在播放的声音(媒体播放器控件,实训台上的广播喇叭随之亮起) */ playing: null as { name: string } | null, metrics: { compileMs: 0, startMs: 0, lastRoundTripMs: 0 }, }) /** id → 控件节点,patch 按 id 修改节点属性 */ const nodes = shallowReactive(new Map()) /** 图片编号 → 网址(blob:)。运行器每张图只发一次,控件属性里只带编号 */ const images = shallowReactive(new Map()) let socket: WebSocket | undefined /** 正在建立或已建立的连接;同时多处调用 connect() 时共用同一个 */ let connecting: Promise | undefined let actionSentAt = 0 /** 每个媒体播放器控件一个