Skip to content

Commit

Permalink
Uses Suspense when loading projects (#322)
Browse files Browse the repository at this point in the history
* Moves MenuItemHover to client

* Removes unused SidebarContext

* Uses Suspense to load projects

* Moves ProjectsContextProvider to fix unit tests

* Moves contexts.ts to client-side

* Moves ProjectsContextProvider.tsx to client-side

* Fixes build errors
  • Loading branch information
simonbs authored Aug 20, 2024
1 parent 34b9f5e commit e61cca2
Show file tree
Hide file tree
Showing 20 changed files with 227 additions and 248 deletions.
9 changes: 1 addition & 8 deletions src/app/(authed)/(home)/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
"use client"

import { useContext, useEffect } from "react"
import { ProjectsContainerContext } from "@/common"
import DelayedLoadingIndicator from "@/common/ui/DelayedLoadingIndicator"
import { useEffect } from "react"
import ErrorMessage from "@/common/ui/ErrorMessage"
import { updateWindowTitle } from "@/features/projects/domain"
import { useProjectSelection } from "@/features/projects/data"
import Documentation from "@/features/projects/view/Documentation"

export default function Page() {
const { error, isLoading } = useContext(ProjectsContainerContext)
const { project, version, specification, navigateToSelectionIfNeeded } = useProjectSelection()
// Ensure the URL reflects the current selection of project, version, and specification.
useEffect(() => {
Expand All @@ -32,10 +29,6 @@ export default function Page() {
return <ErrorMessage text="The selected branch or tag was not found."/>
} else if (project && !specification) {
return <ErrorMessage text="The selected specification was not found."/>
} else if (isLoading) {
return <DelayedLoadingIndicator/>
} else if (error) {
return <ErrorMessage text={error.message}/>
} else {
// No project is selected so we will not show anything.
return <></>
Expand Down
36 changes: 0 additions & 36 deletions src/app/(authed)/(home)/layout.tsx

This file was deleted.

13 changes: 8 additions & 5 deletions src/app/(authed)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { SessionProvider } from "next-auth/react"
import { session, projectRepository } from "@/composition"
import ErrorHandler from "@/common/ui/ErrorHandler"
import SessionBarrier from "@/features/auth/view/SessionBarrier"
import ServerSideCachedProjectsProvider from "@/features/projects/view/ServerSideCachedProjectsProvider"
import ProjectsContextProvider from "@/features/projects/view/ProjectsContextProvider"
import { SplitView } from "@/features/sidebar/view"

export default async function Layout({ children }: { children: React.ReactNode }) {
const isAuthenticated = await session.getIsAuthenticated()
Expand All @@ -15,11 +16,13 @@ export default async function Layout({ children }: { children: React.ReactNode }
<ErrorHandler>
<SessionProvider>
<SessionBarrier>
<ServerSideCachedProjectsProvider projects={projects}>
{children}
</ServerSideCachedProjectsProvider>
<ProjectsContextProvider initialProjects={projects}>
<SplitView>
{children}
</SplitView>
</ProjectsContextProvider>
</SessionBarrier>
</SessionProvider>
</ErrorHandler>
)
}
}
26 changes: 0 additions & 26 deletions src/app/api/user/projects/route.ts

This file was deleted.

19 changes: 7 additions & 12 deletions src/common/contexts.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
"use client"

import { createContext } from "react"
import { Project, } from "@/features/projects/domain"
import { Project } from "@/features/projects/domain"

export const SidebarContext = createContext<{ isToggleable: boolean }>({ isToggleable: true })

type ProjectsContainer = {
readonly projects: Project[]
readonly isLoading: boolean
readonly error?: Error
type ProjectsContextValue = {
projects: Project[],
setProjects: (projects: Project[]) => void
}

export const ProjectsContainerContext = createContext<ProjectsContainer>({
isLoading: true,
projects: []
export const ProjectsContext = createContext<ProjectsContextValue>({
projects: [],
setProjects: () => {}
})

export const ServerSideCachedProjectsContext = createContext<Project[] | undefined>(undefined)
2 changes: 2 additions & 0 deletions src/common/ui/MenuItemHover.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client"

import { SxProps } from "@mui/system"
import { Box } from "@mui/material"
import useMediaQuery from "@mui/material/useMediaQuery"
Expand Down
1 change: 0 additions & 1 deletion src/features/projects/data/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export { default as GitHubProjectDataSource } from "./GitHubProjectDataSource"
export * from "./GitHubProjectDataSource"
export { default as useProjects } from "./useProjects"
export { default as useProjectSelection } from "./useProjectSelection"
export { default as GitHubLoginDataSource } from "./GitHubLoginDataSource"
export { default as GitHubRepositoryDataSource } from "./GitHubRepositoryDataSource"
4 changes: 2 additions & 2 deletions src/features/projects/data/useProjectSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { useRouter, usePathname } from "next/navigation"
import { useContext } from "react"
import useMediaQuery from "@mui/material/useMediaQuery"
import { useTheme } from "@mui/material/styles"
import { ProjectsContainerContext } from "@/common"
import { ProjectsContext } from "@/common"
import { Project, ProjectNavigator, getProjectSelectionFromPath } from "../domain"
import { useSidebarOpen } from "@/features/sidebar/data"

export default function useProjectSelection() {
const router = useRouter()
const pathname = usePathname()
const { projects } = useContext(ProjectsContainerContext)
const { projects } = useContext(ProjectsContext)
const selection = getProjectSelectionFromPath({ projects, path: pathname })
const pathnameReader = {
get pathname() {
Expand Down
19 changes: 0 additions & 19 deletions src/features/projects/data/useProjects.ts

This file was deleted.

22 changes: 22 additions & 0 deletions src/features/projects/view/ProjectsContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use client"

import { useState } from "react"
import { ProjectsContext } from "@/common"
import { Project } from "@/features/projects/domain"

const ProjectsContextProvider = ({
initialProjects,
children
}: {
initialProjects?: Project[],
children?: React.ReactNode
}) => {
const [projects, setProjects] = useState<Project[]>(initialProjects || [])
return (
<ProjectsContext.Provider value={{ projects, setProjects }}>
{children}
</ProjectsContext.Provider>
)
}

export default ProjectsContextProvider
20 changes: 0 additions & 20 deletions src/features/projects/view/ServerSideCachedProjectsProvider.tsx

This file was deleted.

11 changes: 4 additions & 7 deletions src/features/sidebar/view/SecondarySplitHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client"

import { useState, useEffect, useContext } from "react"
import { useState, useEffect } from "react"
import { useSessionStorage } from "usehooks-ts"
import { Box, IconButton, Stack, Tooltip, Divider, Collapse } from "@mui/material"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faBars, faChevronLeft } from "@fortawesome/free-solid-svg-icons"
import { useTheme } from "@mui/material/styles"
import { SidebarContext, isMac as checkIsMac } from "@/common"
import { isMac as checkIsMac } from "@/common"
import { useSidebarOpen } from "@/features/sidebar/data"
import ToggleMobileToolbarButton from "./internal/secondary/ToggleMobileToolbarButton"

Expand All @@ -23,15 +23,13 @@ const Header = ({
}) => {
const [isSidebarOpen, setSidebarOpen] = useSidebarOpen()
const [isMac, setIsMac] = useState(false)
const { isToggleable: isSidebarToggleable } = useContext(SidebarContext)
const [isMobileToolbarVisible, setMobileToolbarVisible] = useSessionStorage("isMobileToolbarVisible", true)
useEffect(() => {
// checkIsMac uses window so we delay the check.
setIsMac(checkIsMac())
}, [isMac, setIsMac])
const openCloseKeyboardShortcut = `(${isMac ? "⌘" : "^"} + .)`
const theme = useTheme()

return (
<Box
sx={{
Expand All @@ -48,7 +46,7 @@ const Header = ({
margin: "auto",
height: HEIGHT_HEADER
}}>
{isSidebarToggleable && !isSidebarOpen &&
{!isSidebarOpen &&
<Tooltip title={`Show Projects ${openCloseKeyboardShortcut}`}>
<IconButton
size="medium"
Expand All @@ -64,7 +62,7 @@ const Header = ({
</IconButton>
</Tooltip>
}
{isSidebarToggleable && isSidebarOpen &&
{isSidebarOpen &&
<Tooltip title={`Hide Projects ${openCloseKeyboardShortcut}`}>
<IconButton
size="small"
Expand Down Expand Up @@ -104,7 +102,6 @@ const Header = ({
</Collapse>
}
{showDivider && <Divider />}

</Box>
)
}
Expand Down
62 changes: 16 additions & 46 deletions src/features/sidebar/view/SplitView.tsx
Original file line number Diff line number Diff line change
@@ -1,52 +1,22 @@
"use client"
import ClientSplitView from "./internal/ClientSplitView"
import { projectDataSource } from "@/composition"
import BaseSidebar from "./internal/sidebar/Sidebar"
import ProjectList from "./internal/sidebar/projects/ProjectList"

import { useEffect } from "react"
import { Stack } from "@mui/material"
import { isMac, useKeyboardShortcut } from "@/common"
import { useSidebarOpen } from "../data"
import PrimaryContainer from "./internal/primary/Container"
import SecondaryContainer from "./internal/secondary/Container"
import Sidebar from "./internal/sidebar/Sidebar"

const SplitView = ({
canToggleSidebar: _canToggleSidebar,
children
}: {
canToggleSidebar?: boolean
children?: React.ReactNode
}) => {
const [isSidebarOpen, setSidebarOpen] = useSidebarOpen()
const canToggleSidebar = _canToggleSidebar !== undefined ? _canToggleSidebar : true
useEffect(() => {
// Show the sidebar if no project is selected.
if (!canToggleSidebar) {
setSidebarOpen(true)
}
}, [canToggleSidebar, setSidebarOpen])
useKeyboardShortcut(event => {
const isActionKey = isMac() ? event.metaKey : event.ctrlKey
if (isActionKey && event.key === ".") {
event.preventDefault()
if (canToggleSidebar) {
setSidebarOpen(!isSidebarOpen)
}
}
}, [canToggleSidebar, setSidebarOpen])
const sidebarWidth = 320
const SplitView = ({ children }: { children?: React.ReactNode }) => {
return (
<Stack direction="row" spacing={0} sx={{ width: "100%", height: "100%" }}>
<PrimaryContainer
width={sidebarWidth}
isOpen={isSidebarOpen}
onClose={() => setSidebarOpen(false)}
>
<Sidebar/>
</PrimaryContainer>
<SecondaryContainer sidebarWidth={sidebarWidth} offsetContent={isSidebarOpen}>
{children}
</SecondaryContainer>
</Stack>
<ClientSplitView sidebar={<Sidebar/>}>
{children}
</ClientSplitView>
)
}

export default SplitView

const Sidebar = () => {
return (
<BaseSidebar>
<ProjectList projectDataSource={projectDataSource} />
</BaseSidebar>
)
}
Loading

0 comments on commit e61cca2

Please sign in to comment.