11import { useLayoutEffect , useRef , useState , type ComponentProps , type CSSProperties } from "react" ;
2+ import { useStore } from "zustand" ;
23import { ChatCanvasContext } from "./ChatCanvasContext" ;
3- import { resolveChatCanvasLayout , type ChatCanvasPreview } from "./chatCanvasLayout" ;
4- import type { PreviewMiniPlayerObstacles } from "../preview/previewMiniPlayerLayout" ;
4+ import type { ChatCanvasPreview } from "./chatCanvasLayout" ;
5+ import { resolveChatCanvasModel , type ChatCanvasStore } from "./canvasStore" ;
6+ import { selectThreadPanelOpen , useRightPanelStore } from "../../rightPanelStore" ;
57import { observeResize } from "../../lib/observeResize" ;
8+ import type { ScopedThreadRef } from "@t3tools/contracts" ;
69import { useClientSettings } from "~/hooks/useSettings" ;
710
811export function ChatCanvas ( {
12+ store,
13+ threadRef,
14+ preview,
915 composerOverlayElement,
1016 detailsCardTopInset = 0 ,
1117 children,
1218 ...props
1319} : Omit < ComponentProps < "div" > , "className" | "style" | "ref" > & {
20+ store : ChatCanvasStore ;
21+ /** The thread whose details card sits on the canvas; other threads' measurements are stale. */
22+ threadRef : ScopedThreadRef | null ;
23+ /** The thread's mini player while it renders on this canvas. */
24+ preview : ChatCanvasPreview | null ;
1425 composerOverlayElement : HTMLElement | null ;
1526 detailsCardTopInset ?: number ;
1627} ) {
1728 const elementRef = useRef < HTMLDivElement | null > ( null ) ;
1829 const widthProbeRef = useRef < HTMLDivElement | null > ( null ) ;
1930 const [ timelineElement , registerTimeline ] = useState < HTMLElement | null > ( null ) ;
20- const [ preview , setPreview ] = useState < ChatCanvasPreview | null > ( null ) ;
21- const [ detailsCard , setDetailsCard ] = useState < PreviewMiniPlayerObstacles [ "detailsCard" ] > ( null ) ;
22- const reportDetailsCard = ( next : PreviewMiniPlayerObstacles [ "detailsCard" ] ) => {
23- setDetailsCard ( ( current ) =>
24- current ?. left === next ?. left &&
25- current ?. right === next ?. right &&
26- current ?. bottom === next ?. bottom
27- ? current
28- : next ,
29- ) ;
30- } ;
31- const [ measurements , setMeasurements ] = useState ( {
32- width : 0 ,
33- height : 0 ,
34- padding : 48 ,
35- maxChatWidth : 768 ,
36- minChatWidth : 640 ,
37- composerHeight : 0 ,
38- timelineGutter : 0 ,
39- findBarLeft : 0 ,
40- findBarRight : 0 ,
41- findBarBottom : 0 ,
42- } ) ;
43- const reportPreview = ( next : ChatCanvasPreview ) => {
44- setPreview ( ( current ) =>
45- current ?. key === next . key &&
46- current . width === next . width &&
47- current . lastInteraction === next . lastInteraction &&
48- current . position ?. x === next . position ?. x &&
49- current . position ?. y === next . position ?. y &&
50- current . source . width === next . source . width &&
51- current . source . height === next . source . height
52- ? current
53- : next ,
54- ) ;
55- } ;
56- const clearPreview = ( key : string ) =>
57- setPreview ( ( current ) => ( current ?. key === key ? null : current ) ) ;
31+ const measurements = useStore ( store , ( state ) => state . measurements ) ;
32+ const detailsCardMeasurement = useStore ( store , ( state ) => state . detailsCardMeasurement ) ;
5833 useLayoutEffect ( ( ) => {
5934 const element = elementRef . current ;
6035 const probe = widthProbeRef . current ;
@@ -65,10 +40,15 @@ export function ChatCanvas({
6540 const canvasBounds = element . getBoundingClientRect ( ) ;
6641 const findBarBounds = findBar ?. getBoundingClientRect ( ) ;
6742 const styles = getComputedStyle ( probe ) ;
68- const next = {
69- findBarLeft : findBarBounds ? findBarBounds . left - canvasBounds . left : 0 ,
70- findBarRight : findBarBounds ? findBarBounds . right - canvasBounds . left : 0 ,
71- findBarBottom : findBarBounds ? findBarBounds . bottom - canvasBounds . top : 0 ,
43+ store . getState ( ) . setMeasurements ( {
44+ findBar :
45+ detailsCardTopInset > 0
46+ ? {
47+ left : findBarBounds ? findBarBounds . left - canvasBounds . left : 0 ,
48+ right : findBarBounds ? findBarBounds . right - canvasBounds . left : 0 ,
49+ bottom : findBarBounds ? findBarBounds . bottom - canvasBounds . top : 0 ,
50+ }
51+ : null ,
7252 width : element . clientWidth ,
7353 height : element . clientHeight ,
7454 padding : Number . parseFloat ( styles . paddingLeft ) ,
@@ -78,49 +58,39 @@ export function ChatCanvas({
7858 timelineGutter : timelineElement
7959 ? ( timelineElement . offsetWidth - timelineElement . clientWidth ) / 2
8060 : 0 ,
81- } ;
82- setMeasurements ( ( current ) =>
83- Object . keys ( next ) . every (
84- ( key ) => current [ key as keyof typeof current ] === next [ key as keyof typeof next ] ,
85- )
86- ? current
87- : next ,
88- ) ;
61+ } ) ;
8962 } ;
9063 measure ( ) ;
9164 const observed : Element [ ] = [ element , probe ] ;
9265 if ( findBar ) observed . push ( findBar ) ;
9366 if ( composerOverlayElement ) observed . push ( composerOverlayElement ) ;
9467 if ( timelineElement ) observed . push ( timelineElement ) ;
9568 return observeResize ( observed , measure ) ;
96- } , [ composerOverlayElement , timelineElement , detailsCardTopInset ] ) ;
97- const redesign = useClientSettings ( ( s ) => s . redesignEnabled ) ;
98- const container = { width : measurements . width , height : measurements . height } ;
99- const findBar =
100- detailsCardTopInset > 0
101- ? {
102- left : measurements . findBarLeft ,
103- right : measurements . findBarRight ,
104- bottom : measurements . findBarBottom ,
105- }
106- : null ;
69+ } , [ store , composerOverlayElement , timelineElement , detailsCardTopInset ] ) ;
70+ const detailsCardInlineOpen = useRightPanelStore ( ( state ) =>
71+ threadRef === null
72+ ? false
73+ : selectThreadPanelOpen ( state . threadPanelVisibilityByThreadKey , threadRef , "inline" ) ,
74+ ) ;
75+ const centerBesideDetails = useClientSettings ( ( s ) => s . redesignEnabled ) ;
76+ const model = resolveChatCanvasModel ( {
77+ measurements,
78+ detailsCardMeasurement,
79+ threadRef,
80+ preview,
81+ detailsCardInlineOpen,
82+ centerBesideDetails,
83+ } ) ;
10784 const context = {
108- container,
109- lane : { padding : measurements . padding , minChatWidth : measurements . minChatWidth } ,
110- layout : resolveChatCanvasLayout ( {
111- ...measurements ,
112- container,
113- preview,
114- detailsCard,
115- findBar,
116- centerBesideDetails : redesign ,
117- } ) ,
85+ container : model . container ,
86+ lane : model . lane ,
87+ layout : model . layout ,
11888 previewKey : preview ?. key ?? null ,
119- reportPreview,
120- clearPreview,
12189 registerTimeline,
122- reportDetailsCard,
123- detailsCardTopInset : findBar ?. bottom ?? 0 ,
90+ measureDetailsCard : store . getState ( ) . measureDetailsCard ,
91+ detailsCardTopInset : model . detailsCardTopInset ,
92+ preferredDetailsCard : model . preferredDetailsCard ,
93+ detailsCardPlacement : model . detailsCardPlacement ,
12494 } ;
12595 const { layout } = context ;
12696 return (
0 commit comments