改完界面bug

This commit is contained in:
2026-08-14 21:15:45 +08:00
parent 9c68029ade
commit 9a0601073b
20 changed files with 791 additions and 423 deletions
+6 -3
View File
@@ -1,7 +1,7 @@
import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
import RefreshIcon from "@mui/icons-material/Refresh";
import {Box,Button,Card,CardActions,CardContent,CardHeader,Chip,Stack,Typography} from "@mui/material";
import {useEffect} from "react";
import {useEffect,useRef,useState} from "react";
import type {Gallery_Case,Gallery_Frame_Mode} from "../protocol/gallery_types";
import {use_plot_session} from "../hooks/use_plot_session";
import type {Gallery_Plot_Session} from "../session/gallery_plot_session";
@@ -13,9 +13,12 @@ import {Kernel_Observer_Summary} from "../plot/kernel_observer_summary";
export interface Selected_Plot {session:Gallery_Plot_Session;}
export function Plot_Card({gallery_case,frame_mode,active,streams_paused,on_register,on_open_inspector}:{gallery_case:Gallery_Case;frame_mode:Gallery_Frame_Mode;active:boolean;streams_paused:boolean;on_register:(session:Gallery_Plot_Session,mount:boolean)=>void;on_open_inspector:(plot:Selected_Plot)=>void}) {
const [session,snapshot]=use_plot_session(gallery_case,frame_mode);
const card_ref=useRef<HTMLDivElement|null>(null);
const [in_viewport,set_in_viewport]=useState(false);
useEffect(()=>{on_register(session,true);return()=>on_register(session,false);},[session,on_register]);
useEffect(()=>session.set_activity(active,streams_paused),[session,active,streams_paused]);
return <Card sx={{overflow:"hidden",display:"flex",flexDirection:"column",minWidth:0}} onContextMenu={event=>{event.preventDefault();if(snapshot.ready)on_open_inspector({session});}}>
useEffect(()=>{const element=card_ref.current;if(!element)return;const observer=new IntersectionObserver(entries=>set_in_viewport(Boolean(entries[0]?.isIntersecting)),{rootMargin:"320px 0px"});observer.observe(element);return()=>observer.disconnect();},[]);
useEffect(()=>session.set_activity(active&&in_viewport,streams_paused),[session,active,in_viewport,streams_paused]);
return <Card ref={card_ref} sx={{overflow:"hidden",display:"flex",flexDirection:"column",minWidth:0}} onContextMenu={event=>{event.preventDefault();if(snapshot.ready)on_open_inspector({session});}}>
<CardHeader title={gallery_case.title} subheader={gallery_case.component} avatar={<Chip size="small" label={gallery_case.category}/>} action={<Plot_Status snapshot={snapshot} active={active&&!streams_paused}/>}/>
<Plot_Canvas session={session}/><Performance_Strip telemetry={snapshot.telemetry}/>{frame_mode.observer_visible&&<Kernel_Observer_Summary telemetry={snapshot.telemetry}/>}<CardContent sx={{flexGrow:1}}><Typography color="text.secondary">{gallery_case.description}</Typography>{snapshot.notice&&<Typography variant="caption" color="secondary.main">{snapshot.notice}</Typography>}</CardContent>
<CardActions sx={{justifyContent:"space-between",px:2,pb:2}}><Stack direction="row" spacing={2}><Typography variant="caption">{snapshot.controls.length} </Typography><Typography variant="caption">{snapshot.actions.length} </Typography><Typography variant="caption">{snapshot.frame_count} </Typography></Stack><Box><Button size="small" startIcon={<RefreshIcon/>} onClick={()=>session.request_frame(performance.now(),true)}>{frame_mode.frame_button_label}</Button><Button size="small" startIcon={<MoreHorizIcon/>} disabled={!snapshot.ready} onClick={()=>on_open_inspector({session})}></Button></Box></CardActions>
@@ -30,8 +30,8 @@ export class Gallery_Plot_Session {
private stream_active(): boolean {return this.visible&&!this.streams_paused;}
private receive_socket_state(state: "connecting"|"ready"|"error"|"closed"): void {if(this.disposed)return;if(state==="ready"){this.update({connection_state:"ready",protocol_error:""});this.socket.send("gallery_open",{case:this.gallery_case.id,frame_mode:this.frame_mode.id});}else{this.update({connection_state:state,ready:state==="error"?this.snapshot.ready:false});if(state==="closed"&&this.visible)this.reconnect_timer=window.setTimeout(()=>this.connect(),1000);}}
private receive_json(raw: string): void {try{const response=parse_gallery_response(raw);if(!response)return;if(response.type==="error"){this.update({notice:Object.values(response.field_errors??{})[0]??response.message});return;}if(response.type==="observer_state"){this.update({telemetry:response.telemetry,performance_capture:response.telemetry.performance_capture??this.snapshot.performance_capture,frame_count:this.received_frames});return;}if(response.type==="case_state"||response.type==="refresh_state"){const controls=response.controls;this.update({ready:true,controls:build_controls(controls?.resources),actions:response.actions?.data??[],telemetry:response.telemetry??{},render_plan:controls?.render_plan??null,performance_capture:controls?.performance_capture??response.telemetry?.performance_capture??null,notice:response.notice??"",frame_count:this.received_frames,protocol_error:""});if(this.stream_active()){this.socket.send("show");this.request_frame(performance.now());}}}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"协议解析失败"});this.socket.close();}}
private receive_pixels(buffer: ArrayBuffer): void {try{const now=performance.now();this.frame_controller.receive_frame(now);const accepted=this.presenter.accept(buffer);this.received_frames++;this.client_performance.record_pixel(now,accepted.signature,accepted.overwritten);if(this.frame_mode.request_after_response&&this.stream_active())this.request_frame(now);}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"像素协议错误"});this.socket.close();}}
tick(now: number): void {if(!this.stream_active())return;if(this.presenter.present())this.client_performance.record_presentation(now);if(this.frame_mode.request_on_animation_frame)this.request_frame(now);if(this.snapshot.ready&&now-this.last_observe_at>=650){this.last_observe_at=now;this.socket.send("gallery_observe",{client_metrics:this.client_performance.snapshot(now,this.socket.buffered_bytes()) as unknown as Json_Value});}}
private receive_pixels(buffer: ArrayBuffer): void {try{const now=performance.now();this.frame_controller.receive_frame(now);const accepted=this.presenter.accept(buffer);this.received_frames++;this.client_performance.record_pixel(now,accepted.signature,accepted.overwritten);}catch(error){this.update({connection_state:"error",protocol_error:error instanceof Error?error.message:"像素协议错误"});this.socket.close();}}
tick(now: number): void {if(!this.stream_active())return;const presented=this.presenter.present();if(presented)this.client_performance.record_presentation(now);if(this.frame_mode.request_on_animation_frame||(this.frame_mode.request_after_response&&presented))this.request_frame(now);if(this.snapshot.ready&&now-this.last_observe_at>=650){this.last_observe_at=now;this.socket.send("gallery_observe",{client_metrics:this.client_performance.snapshot(now,this.socket.buffered_bytes()) as unknown as Json_Value});}}
request_frame(now=performance.now(), explicit=false): boolean {if(!this.snapshot.ready||!this.socket.is_open()||(!explicit&&!this.stream_active())||(explicit&&!this.visible)||(!explicit&&!this.frame_mode.automatic))return false;return this.frame_controller.request_frame(now);}
resize(width: number,height: number): void {width=Math.max(240,Math.round(width));height=Math.max(180,Math.round(height));if(width===this.sent_width&&height===this.sent_height)return;this.sent_width=width;this.sent_height=height;if(this.socket.is_open())this.socket.send("resize",{width,height});}
pointer(type: "pointer_move"|"pointer_press"|"pointer_release", payload: Record<string, Json_Value>): void {this.socket.send(type,payload);}