feat: add history menu
This commit is contained in:
parent
38a6b5c270
commit
b5c23a1246
6 changed files with 164 additions and 23 deletions
44
src/App.tsx
44
src/App.tsx
|
|
@ -9,28 +9,30 @@ import Settings from "./pages/settings"
|
|||
import About from "./pages/about"
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 15 * 60 * 1000, // 15 minutes
|
||||
retry: 3,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: Infinity,
|
||||
gcTime: Infinity,
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
refetchOnMount: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Routes>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/entries/:entry" element={<EntryTemplate />} />
|
||||
<Route path="/tags/:tag" element={<TagTemplate />} />
|
||||
</Routes>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Routes>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/entries/:entry" element={<EntryTemplate />} />
|
||||
<Route path="/tags/:tag" element={<TagTemplate />} />
|
||||
</Routes>
|
||||
<ReactQueryDevtools initialIsOpen={false} />
|
||||
</QueryClientProvider>
|
||||
</BrowserRouter>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,34 @@
|
|||
import { SidebarTrigger } from "./ui/sidebar"
|
||||
import { Separator } from "./ui/separator"
|
||||
import Search from "@/containers/Search"
|
||||
import { Button } from "./ui/button"
|
||||
import { useState } from "react"
|
||||
import History from "./History"
|
||||
import { HistoryIcon } from "lucide-react"
|
||||
|
||||
export default function AppHeader({ pageTitle }: { pageTitle: string }) {
|
||||
export default function AppHeader({
|
||||
pageTitle,
|
||||
historyOpen,
|
||||
setHistoryOpen,
|
||||
}: {
|
||||
pageTitle: string
|
||||
}) {
|
||||
return (
|
||||
<header className="sticky top-0 z-10 flex h-12 shrink-0 items-center gap-2 border-b bg-background transition-[width,height] ease-linear">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
<SidebarTrigger className="-ml-1" />
|
||||
<Separator orientation="vertical" className="mx-2 data-[orientation=vertical]:h-4" />
|
||||
<Search />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="rounded-none"
|
||||
onClick={() => setHistoryOpen(true)}
|
||||
>
|
||||
<HistoryIcon />
|
||||
</Button>
|
||||
|
||||
<History sheetOpen={historyOpen} setSheetOpen={setHistoryOpen} error={false} />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
|
|
|||
100
src/components/History.tsx
Normal file
100
src/components/History.tsx
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { Link } from "react-router"
|
||||
import { Badge } from "./ui/badge"
|
||||
import { SheetContent, Sheet, SheetHeader, SheetTitle } from "./ui/sheet"
|
||||
import { useLocation } from "react-router"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { formatRelativeTime } from "@/lib/utils"
|
||||
import { useEffect } from "react"
|
||||
|
||||
export default function History({ sheetOpen, setSheetOpen, error }) {
|
||||
const location = useLocation()
|
||||
const queryClient = useQueryClient()
|
||||
const searchHistory = queryClient.getQueriesData({ queryKey: ["search_results"] })
|
||||
const entriesHistory = queryClient.getQueryCache().findAll({
|
||||
predicate: (query) => {
|
||||
const firstKey = query.queryKey[0]
|
||||
return (
|
||||
typeof firstKey === "string" &&
|
||||
firstKey.startsWith("entry_") &&
|
||||
query.meta?.visited === true
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const entries = entriesHistory
|
||||
.map((query) => ({
|
||||
title: query.state.data?.title,
|
||||
visitedAt: query.options?.meta?.visitedAt,
|
||||
queryKey: query.queryKey[0],
|
||||
}))
|
||||
.filter((x) => x.title !== undefined)
|
||||
.sort((a, b) => b.visitedAt - a.visitedAt)
|
||||
|
||||
// Force Sheet close on renavigation (i.e search result selection)
|
||||
useEffect(() => {
|
||||
if (sheetOpen) {
|
||||
setSheetOpen(false)
|
||||
}
|
||||
}, [location.pathname])
|
||||
|
||||
return (
|
||||
<Sheet
|
||||
open={sheetOpen}
|
||||
onOpenChange={(open) => {
|
||||
setSheetOpen(open)
|
||||
}}
|
||||
>
|
||||
<SheetContent>
|
||||
<SheetHeader className="border-b bg-sidebar">
|
||||
<SheetTitle className="flex gap-3">Session history</SheetTitle>
|
||||
</SheetHeader>
|
||||
|
||||
{error ? (
|
||||
<div className="p-4 text-sm dark:text-red-300 text-red-700">
|
||||
<div className="p-2 border-2 dark:border-red-800 border-red-500 dark:bg-red-900 bg-red-300">
|
||||
Error fetching history.
|
||||
</div>{" "}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex row justify-between bg-sidebar mx-2 pt-0">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Entries</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
{entries.length || 0}
|
||||
</Badge>
|
||||
</div>
|
||||
|
||||
<div className="overflow-y-auto overflow-x-hidden p-4 pt-0">
|
||||
{entries?.map((entry, i) => (
|
||||
<div className="flex flex-row justify-between">
|
||||
<Link
|
||||
key={i}
|
||||
to={`/entries/${entry.title}`}
|
||||
className="text-foreground underline-offset-3 text-sm underline hover:text-gray-700 dark:hover:text-green-300 block mb-2"
|
||||
>
|
||||
{i === 0 ? (
|
||||
<span className="bg-yellow-200 dark:bg-fuchsia-500">
|
||||
{entry?.title.replace(/_/g, " ")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="">{entry?.title.replace(/_/g, " ")}</span>
|
||||
)}
|
||||
</Link>
|
||||
<span className="text-[12px]">
|
||||
{formatRelativeTime(entry.visitedAt)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex row justify-between bg-sidebar mx-2 pt-0">
|
||||
<h3 className="font-medium text-sm p-1 ml-1">Searches</h3>
|
||||
<Badge variant="secondary" className="rounded-none">
|
||||
0
|
||||
</Badge>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
}
|
||||
|
|
@ -27,3 +27,14 @@ export const convertDateFriendly = (isoStamp) => {
|
|||
const year = unixSeconds.getFullYear()
|
||||
return `${day} ${month} ${year}`
|
||||
}
|
||||
|
||||
export const formatRelativeTime = (timestamp) => {
|
||||
const seconds = Math.floor((Date.now() - timestamp) / 1000)
|
||||
if (seconds < 60) return `${seconds} secs`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
if (minutes < 60) return `${minutes} mins`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
if (hours < 24) return `${hours} hours`
|
||||
const days = Math.floor(hours / 24)
|
||||
return `${days} days`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export default function EntryTemplate() {
|
|||
const { data, isLoading } = useQuery({
|
||||
queryKey: [`entry_${entry}`],
|
||||
queryFn: () => api.get(`/entries/${entry}`).then((res) => res.data),
|
||||
meta: { visited: true, visitedAt: Date.now() },
|
||||
})
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -2,14 +2,21 @@ import AppHeader from "@/components/AppHeader"
|
|||
import { AppSidebar } from "@/containers/AppSidebar"
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar"
|
||||
import { ThemeProvider } from "@/context/ThemeProvider"
|
||||
import { useState } from "react"
|
||||
|
||||
export default function MainTemplate({ children, pageTitle }) {
|
||||
const [historyOpen, setHistoryOpen] = useState(false)
|
||||
return (
|
||||
<ThemeProvider storageKey="app-theme">
|
||||
<SidebarProvider variant="inset">
|
||||
<AppSidebar />
|
||||
<SidebarInset className="flex flex-col h-screen">
|
||||
<AppHeader pageTitle={pageTitle} />
|
||||
<AppHeader
|
||||
pageTitle={pageTitle}
|
||||
historyOpen={historyOpen}
|
||||
setHistoryOpen={setHistoryOpen}
|
||||
/>
|
||||
|
||||
<main className="flex-1 overflow-x-auto">{children}</main>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue