66 */
77import {
88 Box ,
9+ Card ,
10+ CardBody ,
911 Container ,
12+ Grid ,
13+ GridItem ,
1014 Heading ,
1115 HStack ,
1216 Icon ,
17+ IconButton ,
1318 Stack ,
1419 Text ,
1520 VStack ,
1621} from "@chakra-ui/react" ;
17- import { ReactNode , useCallback , useRef } from "react" ;
22+ import { ReactNode , Suspense , useCallback , useRef , useState } from "react" ;
1823import { RiAddLine , RiFolderOpenLine , RiRestartLine } from "react-icons/ri" ;
1924import { FormattedMessage , useIntl } from "react-intl" ;
20- import { useNavigate } from "react-router" ;
25+ import { Await , useAsyncValue , useLoaderData , useNavigate } from "react-router" ;
2126import DefaultPageLayout , {
2227 HomeMenuItem ,
2328 HomeToolbarItem ,
@@ -27,16 +32,22 @@ import LoadProjectInput, {
2732} from "../components/LoadProjectInput" ;
2833import NewPageChoice from "../components/NewPageChoice" ;
2934import { useLogging } from "../logging/logging-hooks" ;
30- import { useStore } from "../store" ;
35+ import { loadProjectFromStorage , useStore } from "../store" ;
3136import { createDataSamplesPageUrl } from "../urls" ;
3237import { useProjectName } from "../hooks/project-hooks" ;
38+ import LoadingAnimation from "../components/LoadingAnimation" ;
39+ import { ProjectData , ProjectDataWithActions , StoreAction } from "../storage" ;
40+ import { DeleteIcon } from "@chakra-ui/icons" ;
3341
3442const NewPage = ( ) => {
3543 const existingSessionTimestamp = useStore ( ( s ) => s . timestamp ) ;
3644 const projectName = useProjectName ( ) ;
3745 const newSession = useStore ( ( s ) => s . newSession ) ;
3846 const navigate = useNavigate ( ) ;
3947 const logging = useLogging ( ) ;
48+ const { allProjectData } = useLoaderData ( ) as {
49+ allProjectData : ProjectData [ ] ;
50+ } ;
4051
4152 const handleOpenLastSession = useCallback ( ( ) => {
4253 logging . event ( {
@@ -165,11 +176,120 @@ const NewPage = () => {
165176 </ NewPageChoice >
166177 < Box flex = "1" />
167178 </ HStack >
179+ < Suspense fallback = { < LoadingAnimation /> } >
180+ < Await resolve = { allProjectData } >
181+ < ProjectsList />
182+ </ Await >
183+ </ Suspense >
168184 </ VStack >
169185 </ Container >
170186 </ VStack >
171187 </ DefaultPageLayout >
172188 ) ;
173189} ;
174190
191+ const ProjectsList = ( ) => {
192+ const data = useAsyncValue ( ) as ProjectDataWithActions [ ] ;
193+ const [ projects , setProjects ] = useState ( data ) ;
194+ const deleteProject = useStore ( ( s ) => s . deleteProject ) ;
195+
196+ const handleDeleteProject = useCallback (
197+ async ( id : string ) => {
198+ setProjects ( ( prev ) => prev . filter ( ( p ) => p . id !== id ) ) ;
199+ await deleteProject ( id ) ;
200+ } ,
201+ [ deleteProject ]
202+ ) ;
203+
204+ return (
205+ < >
206+ < Heading as = "h2" fontSize = "2xl" mt = { 8 } >
207+ Projects
208+ </ Heading >
209+ < Grid mt = { 3 } gap = { 3 } templateColumns = "repeat(5, 1fr)" >
210+ { projects . map ( ( projectData ) => (
211+ < GridItem key = { projectData . id } display = "flex" >
212+ < ProjectCard
213+ id = { projectData . id }
214+ name = { projectData . name }
215+ actions = { projectData . actions }
216+ updatedAt = { projectData . updatedAt }
217+ onDeleteProject = { handleDeleteProject }
218+ />
219+ </ GridItem >
220+ ) ) }
221+ </ Grid >
222+ </ >
223+ ) ;
224+ } ;
225+
226+ interface ProjectCard {
227+ id : string ;
228+ name : string ;
229+ actions : StoreAction [ ] ;
230+ updatedAt : number ;
231+ onDeleteProject : ( id : string ) => Promise < void > ;
232+ }
233+
234+ const ProjectCard = ( {
235+ id,
236+ name,
237+ actions,
238+ updatedAt,
239+ onDeleteProject,
240+ } : ProjectCard ) => {
241+ const navigate = useNavigate ( ) ;
242+
243+ const handleLoadProject = useCallback (
244+ async ( _e : React . MouseEvent ) => {
245+ await loadProjectFromStorage ( id ) ;
246+ navigate ( createDataSamplesPageUrl ( ) ) ;
247+ } ,
248+ [ id , navigate ]
249+ ) ;
250+
251+ const handleDeleteProject = useCallback (
252+ async ( e : React . MouseEvent ) => {
253+ e . stopPropagation ( ) ;
254+ await onDeleteProject ( id ) ;
255+ } ,
256+ [ id , onDeleteProject ]
257+ ) ;
258+
259+ return (
260+ < Card onClick = { handleLoadProject } cursor = "pointer" flexGrow = { 1 } >
261+ < IconButton
262+ aria-label = "Delete project"
263+ onClick = { handleDeleteProject }
264+ icon = { < DeleteIcon /> }
265+ position = "absolute"
266+ right = { 1 }
267+ top = { 1 }
268+ borderRadius = "sm"
269+ border = "none"
270+ />
271+ < CardBody display = "flex" >
272+ < Stack h = "100%" >
273+ < Heading as = "h3" fontSize = "xl" >
274+ { name }
275+ </ Heading >
276+ < Text mb = "auto" >
277+ Actions:{ " " }
278+ { actions . length > 0
279+ ? actions . map ( ( a ) => a . name ) . join ( ", " )
280+ : "none" }
281+ </ Text >
282+ < Text > { `Last edited: ${ new Intl . DateTimeFormat ( undefined , {
283+ dateStyle : "medium" ,
284+ timeStyle : "medium" ,
285+ } ) . format ( updatedAt ) } `} </ Text >
286+ < Text fontSize = "xs" color = "gray.600" >
287+ id: { id }
288+ </ Text >
289+ </ Stack >
290+ </ CardBody >
291+ </ Card >
292+ ) ;
293+ } ;
294+
175295export default NewPage ;
0 commit comments