diff --git a/package-lock.json b/package-lock.json index 962718f..9f15eeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "eexiv-2", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.20.2", "minisearch": "^6.3.0", "next": "14.1.0", "react": "^18", @@ -466,6 +467,30 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.20.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.20.2.tgz", + "integrity": "sha512-sAILwNiyA1I52e6imOsmNDUA/PuOayOzqz5jcLiIB5wBXqVk+HIiriWouPcAkjS8RqARfHUehuoPwcZ7Uzh0GQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.20.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.20.2.tgz", + "integrity": "sha512-949myvMY77cPqwb71m3wRG2ypgwPijshO5kN9w0CDKWrFC0X8Wh1mwSqst88kIr58tWlWNsGy3U40AK23RgYQA==", + "dependencies": { + "@tanstack/query-core": "5.20.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, "node_modules/@types/file-saver": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz", diff --git a/package.json b/package.json index 77e4a3b..65d4611 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "format": "prettier --write ." }, "dependencies": { + "@tanstack/react-query": "^5.20.2", "minisearch": "^6.3.0", "next": "14.1.0", "react": "^18", diff --git a/src/app/db/data.ts b/src/app/db/data.ts index ab5b9bc..5bec8e0 100644 --- a/src/app/db/data.ts +++ b/src/app/db/data.ts @@ -346,22 +346,20 @@ authorName (as a slug): { website: website url } */ -export interface Authors { - [key: string]: { - name: { - first: string - last: string - nickname?: string - } - affiliation: string[] - image: string - nationality: string[] - formerAffiliations?: string[] - bio?: string - website?: string +export interface Author { + name: { + first: string + last: string + nickname?: string } + affiliation: string[] + image: string + nationality: string[] + formerAffiliations?: string[] + bio?: string + website?: string } -export const authors: Authors = { +export const authors: { [key: string]: Author } = { shasan: { name: { first: 'Saim', diff --git a/src/app/db/loaders.ts b/src/app/db/loaders.ts index ec3e37d..af4bcce 100644 --- a/src/app/db/loaders.ts +++ b/src/app/db/loaders.ts @@ -1,4 +1,4 @@ -import { Document } from './data' +import { Document, Author } from './data' export const loadDocument = (id: string): Promise => { return new Promise((resolve, reject) => { @@ -62,3 +62,32 @@ export const loadAllDocuments = (): Promise<{ [key: string]: Document }> => { } }) } + +export const loadAllAuthors = (): Promise<{ [key: string]: Author }> => { + return new Promise((resolve, reject) => { + if (typeof Worker !== 'undefined') { + const worker = new Worker( + new URL('./workers/documentLoader.worker.ts', import.meta.url), + { type: 'module' } + ) + + worker.onmessage = (e: MessageEvent<{ [key: string]: Author }>) => { + resolve(e.data) + worker.terminate() + } + + worker.onerror = (error) => { + reject(error) + worker.terminate() + } + + worker.postMessage('LOAD') + } else { + reject( + new Error( + 'Web Workers are not supported in this environment. Please avoid using a prehistoric browser.' + ) + ) + } + }) +} diff --git a/src/app/db/workers/affiliationLoader.worker.ts b/src/app/db/workers/affiliationLoader.worker.ts new file mode 100644 index 0000000..fd489c7 --- /dev/null +++ b/src/app/db/workers/affiliationLoader.worker.ts @@ -0,0 +1,7 @@ +import { affiliations } from '../data' + +onmessage = (e) => { + if (e.data === 'LOAD') { + self.postMessage(affiliations) + } +} diff --git a/src/app/db/workers/authorLoader.worker.ts b/src/app/db/workers/authorLoader.worker.ts new file mode 100644 index 0000000..4ec63fe --- /dev/null +++ b/src/app/db/workers/authorLoader.worker.ts @@ -0,0 +1,7 @@ +import { authors } from '../data' + +onmessage = (e) => { + if (e.data === 'LOAD') { + self.postMessage(authors) + } +} diff --git a/src/app/db/workers/nationalityLoader.worker.ts b/src/app/db/workers/nationalityLoader.worker.ts new file mode 100644 index 0000000..3b9aa9e --- /dev/null +++ b/src/app/db/workers/nationalityLoader.worker.ts @@ -0,0 +1,7 @@ +import { nationalities } from '../data' + +onmessage = (e) => { + if (e.data === 'LOAD') { + self.postMessage(nationalities) + } +} diff --git a/src/app/db/workers/topicLoader.worker.ts b/src/app/db/workers/topicLoader.worker.ts new file mode 100644 index 0000000..080a691 --- /dev/null +++ b/src/app/db/workers/topicLoader.worker.ts @@ -0,0 +1,7 @@ +import { topics } from '../data' + +onmessage = (e) => { + if (e.data === 'LOAD') { + self.postMessage(topics) + } +} diff --git a/src/app/document/view/[slug]/DocumentViewer.tsx b/src/app/document/view/[slug]/DocumentViewer.tsx index a523bb0..62bdb84 100644 --- a/src/app/document/view/[slug]/DocumentViewer.tsx +++ b/src/app/document/view/[slug]/DocumentViewer.tsx @@ -1,4 +1,4 @@ -import { DocumentType, Document } from '@/app/db/data' +import { DocumentType } from '@/app/db/data' import { Zilla_Slab } from 'next/font/google' import { epoch2datestring } from '@/app/utils/epoch2datestring' import { @@ -10,14 +10,21 @@ import { } from '@/app/components/DataDisplay' import { ItemBadge, Status } from '@/app/components/Badges' import Link from 'next/link' +import { useSuspenseQuery } from '@tanstack/react-query' +import { loadDocument } from '@/app/db/loaders' const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500', '700'] }) -export default function DocumentViewer({ - doc, - slug, -}: Readonly<{ doc: Document; slug: string }>) { - const { manifest, abstract, file, citation } = doc +export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) { + const { data, error } = useSuspenseQuery({ + queryKey: [slug], + queryFn: () => { + const data = loadDocument(slug) + return data + }, + }) + + const { manifest, abstract, file, citation } = data const { title, authors, @@ -31,6 +38,8 @@ export default function DocumentViewer({ status, } = manifest + if (error) throw error + return (

) { - // const docResource = createResource(loadDocument(params.slug)) - // const doc = docResource.read() - - useEffect(() => { - const check = async () => { - const doc = await loadDocument(params.slug) - console.log(doc) - } - check() - }) - return ( - <> -
hi
- - + + + ) } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d9fe98d..2ff9b1e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,6 +8,7 @@ import Container from './container/Container' import MobileMenu from './components/MobileMenu' import { ToastContainer } from 'react-toastify' import 'react-toastify/dist/ReactToastify.css' +import Providers from './providers' /* The default font is Inter. If you want to use Zilla Slab (or any other Google Font, which are pre-provided by Next.js in the 'next/font/google' module), you need to @@ -31,80 +32,82 @@ export default function RootLayout({ return ( - -
-
- - EECS - - -
-
-
-
-

- - eeXiv + + +
+
+ + EECS -

-
- -
-
- +
-
- {children} -
-
-
    -
  • - About -
  • -
  • - Help -
  • -
  • - Contact -
  • -
  • - Subscribe -
  • -
  • - Copyright -
  • -
  • - Privacy Policy -
  • -
  • - Accessibility -
  • -
  • - eeXiv status -
  • -
  • - - Get status notifications +
    +
    +

    + + eeXiv -

  • -
+

+
+ +
+
+ +
+
- + {children} + + ) diff --git a/src/app/providers.jsx b/src/app/providers.jsx new file mode 100644 index 0000000..8b311d6 --- /dev/null +++ b/src/app/providers.jsx @@ -0,0 +1,20 @@ +'use client' + +import { useState } from 'react' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +export default function Providers({ children }) { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + }, + }, + }) + ) + return ( + {children} + ) +} diff --git a/src/app/utils/ErrorBoundary.jsx b/src/app/utils/ErrorBoundary.jsx new file mode 100644 index 0000000..48e99a0 --- /dev/null +++ b/src/app/utils/ErrorBoundary.jsx @@ -0,0 +1,29 @@ +import React from 'react' +import { notFound } from 'next/navigation' + +export default class ErrorBoundary extends React.Component { + constructor(props) { + super(props) + this.state = { hasError: false, error: null } + } + + static getDerivedStateFromError(error) { + // Update state so the next render will show the fallback UI. + return { hasError: true, error } + } + + componentDidCatch(error, errorInfo) { + // You can also log the error to an error reporting service + console.error('Error caught by Error Boundary:', error, errorInfo) + } + + render() { + if (this.state.hasError) { + // You can render any custom fallback UI + if (this.state.error.message === '404') notFound() + return

Something went wrong.

+ } + + return this.props.children + } +} diff --git a/src/app/utils/createResource.ts b/src/app/utils/createResource.ts deleted file mode 100644 index d8dcf7f..0000000 --- a/src/app/utils/createResource.ts +++ /dev/null @@ -1,26 +0,0 @@ -export default function wrapPromise(promise: Promise) { - let status = 'pending' - let result: any - let suspender = promise.then( - (r) => { - status = 'success' - result = r - }, - (e) => { - status = 'error' - result = e - } - ) - - return { - read() { - if (status === 'pending') { - throw suspender - } else if (status === 'error') { - throw result - } else if (status === 'success') { - return result - } - }, - } -}