1- import { useEffect , useMemo , useState } from "react" ;
1+ import { useMemo , useState } from "react" ;
22import { createRoot } from "react-dom/client" ;
33import type { FileDiffOptions } from "@pierre/diffs" ;
4- import { FileDiff } from "@pierre/diffs/react" ;
5- import type { GitStatusEntry } from "@pierre/trees" ;
6- import { FileTree , useFileTree } from "@pierre/trees/react" ;
74import type { HostContext , ToolResultCard } from "./card-types.js" ;
8- import {
9- buildReviewFileEntries ,
10- initialReviewPath ,
11- type ReviewFileEntry ,
12- } from "./review-model.js" ;
5+ import { ReviewFileBody } from "./review-file-body.js" ;
6+ import { FullscreenReview } from "./review-fullscreen.js" ;
7+ import { buildReviewFileEntries } from "./review-model.js" ;
8+ import { StatusLine } from "./review-status-line.js" ;
139
1410type ThemeType = "light" | "dark" ;
1511
@@ -105,118 +101,6 @@ function ReviewPayload({
105101 ) ;
106102}
107103
108- function FullscreenReview ( {
109- entries,
110- options,
111- } : {
112- entries : ReviewFileEntry [ ] ;
113- options : FileDiffOptions < undefined > ;
114- } ) {
115- const [ selectedPath , setSelectedPath ] = useState ( ( ) => initialReviewPath ( entries ) ) ;
116-
117- useEffect ( ( ) => {
118- setSelectedPath ( ( currentPath ) => initialReviewPath ( entries , currentPath ) ) ;
119- } , [ entries ] ) ;
120-
121- const selectedEntry = entries . find ( ( entry ) => entry . path === selectedPath ) ?? entries [ 0 ] ;
122-
123- return (
124- < div className = "review-workspace" >
125- < section className = "review-selected-file" >
126- < header className = "review-selected-file-header" >
127- < div className = "review-selected-file-title" >
128- < strong title = { selectedEntry ?. path } > { selectedEntry ?. path } </ strong >
129- { selectedEntry ?. previousPath && selectedEntry . previousPath !== selectedEntry . path ? (
130- < span title = { selectedEntry . previousPath } > from { selectedEntry . previousPath } </ span >
131- ) : null }
132- </ div >
133- { selectedEntry ? (
134- < span className = "review-diff-file-stats" aria-label = "Selected file diff statistics" >
135- < span className = "add" > +{ selectedEntry . additions } </ span >
136- < span className = "remove" > -{ selectedEntry . removals } </ span >
137- </ span >
138- ) : null }
139- </ header >
140- < div className = "review-selected-file-body" >
141- { selectedEntry ? (
142- < ReviewFileBody entry = { selectedEntry } options = { options } />
143- ) : (
144- < StatusLine message = "Select a changed file to review it." />
145- ) }
146- </ div >
147- </ section >
148-
149- < aside className = "review-file-tree-panel" aria-label = "Changed files" >
150- < div className = "review-file-tree-header" >
151- < strong > Changed files</ strong >
152- < span > { entries . length } </ span >
153- </ div >
154- < div className = "review-file-tree-body" >
155- < ReviewFileTree
156- entries = { entries }
157- selectedPath = { selectedEntry ?. path }
158- onSelect = { setSelectedPath }
159- />
160- </ div >
161- </ aside >
162- </ div >
163- ) ;
164- }
165-
166- function ReviewFileTree ( {
167- entries,
168- selectedPath,
169- onSelect,
170- } : {
171- entries : ReviewFileEntry [ ] ;
172- selectedPath ?: string ;
173- onSelect ( path : string ) : void ;
174- } ) {
175- const paths = useMemo ( ( ) => entries . map ( ( entry ) => entry . path ) , [ entries ] ) ;
176- const gitStatus = useMemo < GitStatusEntry [ ] > (
177- ( ) => entries . map ( ( entry ) => ( { path : entry . path , status : entry . status } ) ) ,
178- [ entries ] ,
179- ) ;
180- const { model } = useFileTree ( {
181- paths,
182- gitStatus,
183- flattenEmptyDirectories : true ,
184- initialExpansion : "open" ,
185- search : paths . length > 8 ,
186- onSelectionChange ( selectedPaths ) {
187- const path = selectedPaths . find ( ( candidate ) => paths . includes ( candidate ) ) ;
188- if ( path ) onSelect ( path ) ;
189- } ,
190- } ) ;
191-
192- useEffect ( ( ) => {
193- if ( ! selectedPath ) return ;
194- for ( const path of model . getSelectedPaths ( ) ) {
195- if ( path !== selectedPath ) model . getItem ( path ) ?. deselect ( ) ;
196- }
197- model . getItem ( selectedPath ) ?. select ( ) ;
198- model . scrollToPath ( selectedPath , { focus : false } ) ;
199- } , [ model , selectedPath ] ) ;
200-
201- return < FileTree model = { model } className = "review-file-tree" /> ;
202- }
203-
204- function ReviewFileBody ( {
205- entry,
206- options,
207- } : {
208- entry : ReviewFileEntry ;
209- options : FileDiffOptions < undefined > ;
210- } ) {
211- if ( ! entry . fileDiff ) {
212- return (
213- < StatusLine message = "This file changed without a textual diff that can be rendered." />
214- ) ;
215- }
216-
217- return < FileDiff fileDiff = { entry . fileDiff } options = { options } className = "pierre-diff" /> ;
218- }
219-
220104function diffOptions ( themeType : ThemeType ) : FileDiffOptions < undefined > {
221105 return {
222106 theme : {
@@ -235,13 +119,3 @@ function diffOptions(themeType: ThemeType): FileDiffOptions<undefined> {
235119 disableFileHeader : true ,
236120 } ;
237121}
238-
239- function StatusLine ( {
240- message,
241- tone = "muted" ,
242- } : {
243- message : string ;
244- tone ?: "muted" | "error" ;
245- } ) {
246- return < div className = { `status ${ tone } ` } > { message } </ div > ;
247- }
0 commit comments