This commit is contained in:
Team 1280 Programming Laptop 2024-02-15 14:48:59 -08:00
commit 4084725537
34 changed files with 711 additions and 147 deletions

27
.github/workflows/style.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: npm run format
on: [push, workflow_dispatch]
jobs:
style:
runs-on: ubuntu-latest
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '20.x'
- run: npm install
- run: npm run format
- uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "style: format"

27
.github/workflows/zenodo.yml vendored Normal file
View file

@ -0,0 +1,27 @@
name: Upload to Zenodo
on:
workflow_dispatch:
inputs:
name:
description: 'Zenodo File Upload Name'
required: true
type: string
path:
description: 'Relative file path from scripts directory'
required: true
type: string
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Run Zenodo uploader
working-directory: scripts
env:
ZENODO: ${{ secrets.ZENODO }}
run: |
npm install
bash run.sh ${{ inputs.name }} ${{ inputs.path }}

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
*.yml

204
package-lock.json generated
View file

@ -15,6 +15,8 @@
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.0.1",
"react-konami-code": "^2.3.0",
"react-snowfall": "^2.1.0",
"react-toastify": "^10.0.4",
"zustand": "^4.5.0"
},
@ -521,6 +523,32 @@
"node": ">=6.9.0"
}
},
"node_modules/@cspotcode/source-map-support": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -971,6 +999,38 @@
"react": "^18.0.0"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
@ -1182,6 +1242,17 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@ -1729,6 +1800,14 @@
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"node_modules/create-require": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3568,6 +3647,14 @@
"node": "14 || >=16.14"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -3781,7 +3868,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@ -4239,7 +4325,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@ -4298,6 +4383,11 @@
"react": "^18.2.0"
}
},
"node_modules/react-fast-compare": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz",
"integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="
},
"node_modules/react-icons": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz",
@ -4309,8 +4399,31 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-konami-code": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/react-konami-code/-/react-konami-code-2.3.0.tgz",
"integrity": "sha512-9x90HnzstiMXs2kFS9cYsb5a+ojKEB/iC24uzNKCoE9znorLJwUcy98tjsiW2i5AHB05GuqIMTzV5RaDpVSThw==",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": ">= 16.8.0",
"react-dom": ">= 16.8.0"
}
},
"node_modules/react-snowfall": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-snowfall/-/react-snowfall-2.1.0.tgz",
"integrity": "sha512-5nYWmBTJi/rrRRTmE62QLhxSp3sfX9niPP1Ysrd4dkVZLr0fxHTkcqCLQx7BAkclCEq5MWZ7kgIHrqk5Ugq1PA==",
"dependencies": {
"react-fast-compare": "^3.2.2"
},
"peerDependencies": {
"react": "^16.8 || 17.x || 18.x",
"react-dom": "^16.8 || 17.x || 18.x"
}
},
"node_modules/react-toastify": {
"version": "10.0.4",
@ -5029,6 +5142,70 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true
},
"node_modules/ts-node": {
"version": "10.9.1",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
"dev": true,
"optional": true,
"peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
"@tsconfig/node16": "^1.0.2",
"acorn": "^8.4.1",
"acorn-walk": "^8.1.1",
"arg": "^4.1.0",
"create-require": "^1.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
},
"bin": {
"ts-node": "dist/bin.js",
"ts-node-cwd": "dist/bin-cwd.js",
"ts-node-esm": "dist/bin-esm.js",
"ts-node-script": "dist/bin-script.js",
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/ts-node/node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/ts-node/node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -5243,6 +5420,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true,
"optional": true,
"peer": true
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
@ -5462,6 +5647,17 @@
"node": ">= 14"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"optional": true,
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -17,6 +17,8 @@
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.0.1",
"react-konami-code": "^2.3.0",
"react-snowfall": "^2.1.0",
"react-toastify": "^10.0.4",
"zustand": "^4.5.0"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

2
scripts/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.js
secret.txt

1
scripts/run.sh Normal file
View file

@ -0,0 +1 @@
npx tsc zenodo.ts && node zenodo.js $1 $2

34
scripts/zenodo.ts Normal file
View file

@ -0,0 +1,34 @@
import { execSync } from 'child_process'
const TOKEN = process.env.ZENODO
const filename = process.argv[2]
const path = process.argv[3]
const run = (cmd: string): string | Buffer => {
try {
const output = execSync(cmd, { stdio: 'pipe' })
return output
} catch (error) {
console.error(`Error executing shell script: ${error}`)
throw error
}
}
const out =
run(`curl --request POST 'https://zenodo.org/api/deposit/depositions?access_token=${TOKEN}' \
--header 'Content-Type: application/json' \
--data-raw '{}'
`)
const json = JSON.parse(out.toString())
const doi = json.metadata.prereserve_doi.doi
const file = json.links.bucket
console.log('DOI: ' + doi)
console.log('File: ' + file)
const res = run(
`curl --upload-file ${path} --request PUT '${file}/${filename}?access_token=${TOKEN}'`
)
const resJSON = JSON.parse(res.toString())
console.log(resJSON)

View file

@ -28,10 +28,7 @@ export default function Page() {
<li>
You can contribute to our website development or add your
documents and user account to eeXiv on our{' '}
<Link
href='https://github.com/team-1280/eexiv'
target='_blank'
>
<Link href='https://github.com/team-1280/eexiv' target='_blank'>
GitHub repository
</Link>
.

View file

@ -36,20 +36,24 @@ export default function Page({
return (
<div>
<div className='grid grid-cols-1 max-w-3xl mx-auto'>
<div className='mx-auto mb-4 max-w-3xl md:w-auto md:h-[40vw] lg:h-[20vw]'>
<div className='mx-auto mb-4 max-w-3xl md:w-auto md:h-[40vw] lg:h-[20vw] rounded-lg shadow-lg shadow-slate-400'>
<img
alt='profile picture'
className='rounded-sm mx-auto object-cover w-full h-full shadow-md shadow-slate-400'
alt='profile'
className='rounded-lg mx-auto p-8 object-cover w-full h-full'
src={image}
/>
</div>
<span className={`${zillaSlab.className} font-bold text-4xl text-left`}>
<br />
<span
className={`${zillaSlab.className} font-bold text-4xl text-center`}
>
{name}
</span>
<div className='text-slate-600 text-2xl mt-4'>{short}</div>
<div className='text-slate-600 text-2xl mt-4 text-center'>{short}</div>
</div>
<div className='max-w-3xl mx-auto grid grid-cols-1'>
<hr className='mx-auto w-full h-1 border-0 bg-slate-200 my-2 rounded-md' />
<br />
<Description />
</div>
</div>

View file

@ -1,11 +1,12 @@
import Link from 'next/link'
import { Fragment } from 'react'
import { Fragment, Suspense } from 'react'
import { affiliations, nationalities, authors } from '../../db/data'
import { Zilla_Slab } from 'next/font/google'
import { notFound } from 'next/navigation'
import DocumentCard from '@/app/components/DocumentCard'
import findDocumentsByAuthor from './findDocumentsByAuthor'
import cardEffects from '@/app/styles/cardEffects.module.css'
import KonamiSnowfall from './KonamiSnowfall'
const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] })
@ -26,6 +27,7 @@ export default function AuthorDisplay({
const mainPosition = affiliation[0].split('@')[0]
const mainAffiliation = affiliations[mainAffiliationShort]
const { website } = data
return (
<>
<span>{mainPosition} at </span>
@ -40,17 +42,17 @@ export default function AuthorDisplay({
</a>
</div>
) : null}
<div className='my-4 max-h-12 flex flex-wrap gap-2'>
<div className='my-4 max-h-16 flex flex-wrap gap-2'>
{affiliation.map((a: string) => (
<Link
key={a}
href={`/affiliation/${a.split('@')[1]}`}
className={cardEffects['card-small']}
className={`${cardEffects['card-small']} rounded-md`}
>
<img
src={affiliations[a.split('@')[1]].image}
alt={affiliations[a.split('@')[1]].name}
className='h-12 rounded-md'
className='h-16 rounded-md p-2'
/>
</Link>
))}
@ -133,14 +135,17 @@ export default function AuthorDisplay({
return (
<>
<h1 className='text-3xl md:mt-6 mt-4 mb-2 font-serif'>Bio:</h1>
<p>{bio}</p>
<h1 className='text-3xl md:mt-6 mt-4 mb-2 font-serif'>Bio</h1>
<p className='mb-2'>{bio}</p>
</>
)
}
return (
<div>
<>
<Suspense>
<KonamiSnowfall nationalityList={nationality} />
</Suspense>
<div className='grid grid-cols-1 md:grid-cols-2 items-center max-w-3xl mx-auto'>
<div className='aspect-square w-[60vw] md:w-[30vw] lg:w-[20vw] 2xl:w-[15vw] overflow-hidden mx-auto mb-4'>
<img
@ -178,12 +183,11 @@ export default function AuthorDisplay({
))}
</div>
<Bio />
<hr className='mx-auto w-full h-1 border-0 bg-slate-200 my-2 rounded-md' />
<br />
{authorsDocuments.length > 0 && (
<>
<hr className='mx-auto w-full h-1 border-0 bg-slate-200 my-2 rounded-md mt-8' />
<h1 className='text-3xl md:my-6 my-4 font-serif'>
Published documents
Published documents {`(${authorsDocuments.length})`}
</h1>
{authorsDocuments.map((d) => (
<Fragment key={d.slug}>
@ -193,6 +197,6 @@ export default function AuthorDisplay({
</>
)}
</div>
</div>
</>
)
}

View file

@ -0,0 +1,51 @@
'use client'
import Konami from 'react-konami-code'
import { Snowfall } from 'react-snowfall'
import { useEffect, useState } from 'react'
import { nationalities } from '@/app/db/data'
export default function KonamiSnowfall({
nationalityList,
}: Readonly<{
nationalityList: string[]
}>) {
const [snowfallActivated, setSnowfallActivated] = useState<boolean>(false)
const [images, setImages] = useState<HTMLImageElement[]>([])
const handleKonami = () => {
setSnowfallActivated(!snowfallActivated)
}
useEffect(() => {
const imagesTemp: HTMLImageElement[] = []
nationalityList.forEach((n) => {
const { flag } = nationalities[n]
const image = new Image()
image.src = flag
imagesTemp.push(image)
})
setImages(imagesTemp)
}, [])
return (
<>
<Konami action={handleKonami} />
{snowfallActivated && (
<Snowfall
snowflakeCount={500}
color='white'
images={images}
radius={[20, 60]}
style={{
position: 'absolute',
top: 0,
left: 0,
zIndex: -1,
pointerEvents: 'none',
height: `${document.documentElement.scrollHeight}px`,
}}
/>
)}
</>
)
}

View file

@ -23,22 +23,38 @@ export default function News() {
</div>
<ul className='text-slate-50 px-6 list-disc'>
<li key={1}>
eeXiv 2.1 has been released! Documents are now statically generated
for instant loading speeds.{' '}
eeXiv 2.2 has been released! You can now select document version and
export as BibTex.{' '}
</li>
<li key={2}>
We are working on becoming a{' '}
<a
href='https://www.doi.org/the-foundation/about-us/'
target='_blank'
className='text-blue-300'
>
ISO 26324 DOI registry!
</a>
</li>
<li key={2}>Mobile support is currently in beta.</li>
<li key={3}>
eeXiv is currently under active development! There may be major
updates, breaking changes, or weird bugs. Report bugs, suggest new
features, or give us feedback at{' '}
<a href='https://github.com/team-1280/eexiv/issues' target='_blank' className='text-blue-300'>
<a
href='https://github.com/team-1280/eexiv/issues'
target='_blank'
className='text-blue-300'
>
our issue tracker.
</a>
</li>
<li key={4}>
Want to upload your documents or just make yourself a profile on
eeXiv? Check our <Link href='/about' className='text-blue-300'>about page</Link> for more
information!
eeXiv? Check our{' '}
<Link href='/about' className='text-blue-300'>
about page
</Link>{' '}
for more information!
</li>
</ul>
</div>

View file

@ -1,31 +1,3 @@
/*
documents db schema
documents {
slug: {
manifest: {
title: string
authors: string[]
date: unix epoch integer[] -> if multiple revisions, put the earlier dates first
type: presentation | report | whitepaper | other
latest: integer >= 1 -> the latest revision of the document (earliest = 1)
keywords: string[]
topics: string[]
references: string[],
code: url[]
},
abstract: string,
file: pdf | docx | pptx | targz | other, named file[rev].[ext]
(eg. revision 1 = file1.pdf, revision 2 = file2.pdf, etc)
the "latest" should be the latest revision
citation: a string that can be used to cite the document
reviewers: an array of reviewers, following the reviewer format. if you specify a local
profile username, it will link to the author's profile, and take priority over the link
status: draft | under review | reviewed | published no review
note: published no review should be used for documents where peer review
is not appropriate or unnecessary
}
}
*/
export type FileType = 'pdf' | 'docx' | 'pptx' | 'tar.gz' | 'other'
export type DocumentType =
| 'presentation'
@ -42,11 +14,13 @@ export type reviewer = {
profile?: string
url?: string
}
export type DocumentStatus =
| 'draft'
| 'under review'
| 'reviewed'
| 'published no review'
export interface Document {
manifest: DocumentManifest
abstract: string
@ -415,7 +389,7 @@ export const authors: Readonly<{ [key: string]: Author }> = {
formerAffiliations: ['Programming Lead@1280-programming'],
image: '/img/profiles/avenkatesh.png',
nationality: ['ind', 'eth', 'usa'],
bio: 'The king of jank.',
bio: 'King of jank. Hacker in JS, TS, R, and Python. Capitalist innovator. EECS creator. eeXiv co-founder.',
website: 'https://github.com/quantum9Innovation',
},
ywu: {
@ -441,7 +415,7 @@ export const authors: Readonly<{ [key: string]: Author }> = {
'Student@srvhs',
],
image: '/img/profiles/wlin.jpg',
nationality: ['twn', 'chn', 'jpn', 'usa'],
nationality: ['twn', 'usa'],
formerAffiliations: ['Intern@raid-zero'],
bio: 'Hi, I am Kaito or Warren. I am a Self-taught programmer and engineer. I go around doing dumb things such as my projects. I have a dream of building a community. I am currently part of many projects.',
website: 'https://kaitotlex.carrd.co/',
@ -534,6 +508,16 @@ export const authors: Readonly<{ [key: string]: Author }> = {
image: '/img/profiles/cbordalo.jpg',
nationality: ['phl', 'usa'],
},
bgraham: {
name: {
first: 'Benjamin',
last: 'Graham',
},
affiliation: ['Student@srvhs'],
image: '/img/profiles/bgraham.jpg',
bio: 'Unrelated to Ben Garrison. Maintains a Signal profile. Most likely to become President of the United States.',
nationality: ['usa'],
},
}
export interface Affiliation {
@ -545,7 +529,7 @@ export interface Affiliation {
export const affiliations: Readonly<{ [key: string]: Affiliation }> = {
'1280-mech': {
name: "Team 1280, the Ragin' C Biscuits, Mechanical Subteam",
name: "Team 1280, the Ragin' C-Biscuits, Mechanical Subteam",
short: '1280 Mech',
image: '/img/logos/1280-main.png',
description: `The mechanical subteam is the backbone of Team 1280, focusing on the physical design, construction, and mechanical integrity of their robots. This subteam is where concepts and designs become tangible, transforming ideas into the moving parts and structural components that give the robots their form and function. The Mechanical subteam's work encompasses a broad range of activities, from drafting initial sketches and CAD modeling to machining parts and assembling complex mechanical systems.
@ -557,7 +541,7 @@ Collaboration is key within the Mechanical subteam, as its members must coordina
The Mechanical subteam fosters a culture of creativity, innovation, and excellence, encouraging its members to push the boundaries of what is possible. Through their participation in the FIRST Robotics Competition, students develop not only technical skills in mechanical design and engineering but also soft skills such as teamwork, problem-solving, and project management. With access to advanced tools and guided by mentors from various engineering fields, the Mechanical subteam of Team 1280 is a place where future mechanical engineers are nurtured, ready to make their mark in the world of robotics and beyond.`,
},
'1280-eecs': {
name: "Team 1280, the Ragin' C Biscuits, Electrical Engineering and Computer Science Subteam",
name: "Team 1280, the Ragin' C-Biscuits, Electrical Engineering and Computer Science Subteam",
short: '1280 EECS',
image: '/img/logos/eecs-wordmark.png',
description: `The Team 1280 EECS (Electrical Engineering and Computer Science) subteam is an autonomous organization within Team 1280, specializing in the design, programming, and electrical systems that bring their robots to life. As the nerve center of Team 1280, the EECS subteam combines the disciplines of electrical engineering and computer science to develop sophisticated control systems, autonomous functionalities, and robust electrical infrastructures that enable their robots to perform complex tasks and maneuvers in the competitive arena.
@ -567,10 +551,10 @@ Team 1280 EECS is composed of highly skilled and passionate students who are kee
Team 1280 EECS benefits from mentorship by experienced professionals and alumni, access to state-of-the-art tools and technologies, and a culture that encourages creativity, experimentation, and continuous improvement. As a result, the EECS subteam plays a crucial role in driving Team 1280's success in competitions and inspiring the next generation of engineers and computer scientists.`,
},
'1280-programming': {
name: "Team 1280, the Ragin' C Biscuits, Programming Subteam (now defunct)",
name: "Team 1280, the Ragin' C-Biscuits, Programming Subteam (now defunct)",
short: '1280 Programming',
image: '/img/logos/1280-main.png',
description: `The former programming subteam of Team 1280, it combined with the Team 1280 electrical subteam in a historic merger to form Team 1280 EECS.`,
description: `The former programming subteam of Team 1280, which combined with the Team 1280 electrical subteam in a historic merger to form Team 1280 EECS.`,
},
'usc-viterbi': {
name: 'University of Southern California, Viterbi School of Engineering',
@ -601,7 +585,7 @@ Team 1280 EECS benefits from mentorship by experienced professionals and alumni,
the wider world.`,
},
'1280-business': {
name: "Team 1280, the Ragin' C Biscuits, Business Subteam",
name: "Team 1280, the Ragin' C-Biscuits, Business Subteam",
short: '1280 Business',
image: '/img/logos/1280-main.png',
description: `The Business subteam of Team 1280 plays a crucial role in ensuring the team's operational success and sustainability. Unlike the engineering-focused subteams, the Business subteam focuses on the financial, organizational, and community aspects of the team's operations. They are responsible for fundraising, sponsorship outreach, budget management, and public relations, ensuring that the team has the necessary resources and support to thrive in their endeavors.
@ -615,8 +599,8 @@ Through their work, the Business subteam members gain valuable experience in bus
By bridging the gap between engineering innovation and business acumen, the Business subteam ensures that Team 1280 is not only competitive in robotics challenges but also sustainable and impactful in its mission to inspire and educate future generations in STEM fields.`,
},
'raid-zero': {
name: 'Team 4253 - Raid Zero',
short: 'Raid 0',
name: 'Team 4253, Raid Zero',
short: 'Raid Zero',
image: '/img/logos/raid-zero.png',
description: `Team 4253, Raid Zero, hailing from Taipei American School in Taipei, Taipei Special Municipality, Chinese Taipei, has been a formidable presence in the world of robotics since its rookie year in 2012. As a participant in the international FIRST Robotics Competition, Raid Zero exemplifies innovation, teamwork, and the pursuit of excellence in science, technology, engineering, and mathematics (STEM).
[linebreak]
@ -640,7 +624,7 @@ Raid Zero's influence extends beyond the technical achievements in robotics comp
image: '/img/logos/cal-poly-slo.png',
description: `California Polytechnic State University, San Luis Obispo (Cal Poly SLO) is renowned for its learn-by-doing philosophy that stands at the core of its engineering education. Founded in 1901, Cal Poly SLO's College of Engineering is recognized as one of the premier engineering schools in the nation, offering a hands-on approach to education that prepares students for the practical challenges they will face in the workforce.
[linebreak]
With a wide array of undergraduate and graduate programs, the College of Engineering at Cal Poly SLO covers disciplines such as aerospace, biomedical, civil and environmental, computer science and software engineering, electrical, industrial and manufacturing, and mechanical engineering among others. This diversity ensures that students can find their niche and develop specialized skills in their chosen field.
With a wide array of undergraduate and graduate programs, the College of Engineering at Cal Poly SLO covers disciplines such as aerospace, biomedical, civil and environmental, computer science and software engineering, electrical, industrial and manufacturing, and mechanical engineering, among others. This diversity ensures that students can find their niche and develop specialized skills in their chosen field.
[linebreak]
Cal Poly SLO prides itself on its state-of-the-art laboratories and facilities, which allow students to engage directly with the material they are learning. This hands-on experience is supplemented by a curriculum that encourages interdisciplinary collaboration, critical thinking, and problem-solving skills, ensuring that graduates are well-equipped to contribute to their fields effectively.
[linebreak]
@ -651,10 +635,10 @@ Raid Zero's influence extends beyond the technical achievements in robotics comp
With its blend of rigorous academics, practical experience, and a supportive community, Cal Poly SLO stands out as a leader in engineering education, preparing the next generation of engineers to face global challenges with innovation and expertise.`,
},
'team-1280': {
name: `Team 1280, the Ragin' C Biscuits`,
name: `Team 1280, the Ragin' C-Biscuits`,
short: 'Team 1280',
image: '/img/logos/1280-main.png',
description: `We are the San Ramon Valley High School Robotics Team (FRC Team 1280) and we have been competing in the FIRST Robotics Challenge for 16 years. With just 6 weeks to design, build, program, and fundraise for a robot, FRC teaches us teamwork, business, engineering, machinery, and computer design. This years challenge is “Deep Space” and we are eagerly awaiting the challenge release and season kickoff in January! We are team of 79 students with 1 full time mentor, 2 part time mentors, and 1 staff liason.
description: `We are the San Ramon Valley High School Robotics Team (FRC Team 1280) and we have been competing in the FIRST Robotics Challenge for 16 years. With just 6 weeks to design, build, program, and fundraise for a robot, FRC teaches us teamwork, business, engineering, machinery, and computer design. We are a team of over 50 students with 1 full time mentor, 2 part time mentors, and 1 staff liason.
[linebreak]
Throughout our FRC career, we have won several regional events and numerous awards including: the Rookie Inspiration Award, both the Radio Shack and Rockwell Automation Innovation in Control Awards, the Imagery Award, the Engineering Excellence Award, and the Creativity Award. While we do focus on the competitive aspect of robotics, we also strive to spread the knowledge of STEM through our outreach programs to those who might not otherwise have access to these opportunities.
[linebreak]

View file

@ -1,5 +1,11 @@
import { Document, Author, Affiliation, Topic, Nationality } from './data'
/**
* Loads a document with the given ID using a web worker if available, and returns a promise that resolves with the document.
*
* @param {string} id - The ID of the document to load
* @return {Promise<Document>} A promise that resolves with the loaded document, or rejects with an error
*/
export const loadDocument = (id: string): Promise<Document> => {
return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') {
@ -8,13 +14,12 @@ export const loadDocument = (id: string): Promise<Document> => {
{ type: 'module' }
)
worker.onmessage = (e: MessageEvent<{ [key: string]: Document }>) => {
worker.onmessage = (e: MessageEvent<Document | undefined>) => {
const data = e.data
const doc: Document | undefined = data[id]
if (!doc) {
if (!data) {
return reject(new Error('404'))
} else {
resolve(doc)
resolve(data)
}
worker.terminate()
}
@ -24,7 +29,7 @@ export const loadDocument = (id: string): Promise<Document> => {
worker.terminate()
}
worker.postMessage('LOAD')
worker.postMessage(id)
} else {
reject(
new Error(
@ -34,6 +39,9 @@ export const loadDocument = (id: string): Promise<Document> => {
}
})
}
/**
* @deprecated This function doesn't improve efficiency and shouldn't be used
*/
export const loadAllDocuments = (): Promise<{ [key: string]: Document }> => {
return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') {
@ -83,16 +91,15 @@ export const loadAllAuthors = (): Promise<{ [key: string]: Author }> => {
worker.postMessage('LOAD')
} else {
reject(
new Error(
'Web Workers are not supported in this environment. Please avoid using a prehistoric browser.'
)
)
return
}
})
}
export const loadAuthor = (id: string): Promise<Author> => {
export const loadAuthors = (
authorIds: string[]
): Promise<{ [key: string]: Author }> => {
'use client'
return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') {
const worker = new Worker(
@ -101,12 +108,10 @@ export const loadAuthor = (id: string): Promise<Author> => {
)
worker.onmessage = (e: MessageEvent<{ [key: string]: Author }>) => {
const data = e.data
const author: Author | undefined = data[id]
if (!author) {
return reject(new Error('404'))
if (typeof e.data === 'object' && Object.keys(e.data).length > 0) {
resolve(e.data)
} else {
resolve(author)
reject(new Error('404'))
}
worker.terminate()
}
@ -116,7 +121,7 @@ export const loadAuthor = (id: string): Promise<Author> => {
worker.terminate()
}
worker.postMessage('LOAD')
worker.postMessage(authorIds)
} else {
reject(
new Error(

View file

@ -1,7 +1,30 @@
import { authors } from '../data'
import { authors, Author } from '../data'
export function getAuthorsById(authorIds: string[]): { [key: string]: Author } {
const result: { [key: string]: Author } = {}
// Iterate through the array of author IDs
for (const id of authorIds) {
const author = authors[id] // Retrieve the author entry by ID
if (author) {
result[id] = author // If the author exists, add it to the result object
}
}
return result
}
const checkIsStringArray = (data: unknown): data is string[] => {
if (Array.isArray(data)) {
return data.every((d) => typeof d === 'string')
}
return false
}
onmessage = (e) => {
if (e.data === 'LOAD') {
self.postMessage(authors)
}
let authorIds: string[] = []
checkIsStringArray(e.data) && (authorIds = e.data as string[])
let results = getAuthorsById(authorIds)
postMessage(results)
}

View file

@ -1,7 +1,5 @@
import { documents } from '../data'
onmessage = (e) => {
if (e.data === 'LOAD') {
self.postMessage(documents)
}
typeof e.data === 'string' && postMessage(documents[e.data])
}

View file

@ -1,8 +0,0 @@
export default function Page() {
return (
<h1>
This is a placeholder for a document browser that will be implemented in
the future. In the meantime, you can view specific documents by searching.
</h1>
)
}

View file

@ -1,4 +1,4 @@
import { DocumentType, documents } from '@/app/db/data'
'use client'
import { Zilla_Slab } from 'next/font/google'
import { epoch2datestring } from '@/app/utils/epoch2datestring'
import {
@ -9,14 +9,27 @@ import {
Reviewers,
} from '@/app/components/DataDisplay'
import { ItemBadge, Status } from '@/app/components/Badges'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import VersionChooser from './VersionChooser'
import crypto from 'crypto'
import { Suspense } from 'react'
import { loadDocument } from '@/app/db/loaders'
import { useSuspenseQuery } from '@tanstack/react-query'
const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] })
export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
const { manifest, abstract, file, citation } = documents[slug]
if (!manifest) return notFound()
const DocumentViewer = ({ slug }: Readonly<{ slug: string }>) => {
const { data, error } = useSuspenseQuery({
queryKey: [slug],
queryFn: () => {
const data = loadDocument(slug)
return data
},
})
if (error) throw error
let doc = data
const { manifest, abstract, citation } = doc
const {
title,
authors,
@ -30,6 +43,13 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
status,
} = manifest
// git style hash
const hash = crypto
.createHash('sha256')
.update(slug)
.digest('hex')
.substring(0, 7)
return (
<div className='max-w-4xl lg:max-w-6xl mx-auto'>
<h1
@ -51,7 +71,7 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</span>
</p>
<div className='flex flex-wrap gap-2'>
<ItemBadge itemName={type as DocumentType} />
<ItemBadge itemName={type} />
<Status statusName={status} />
<span className='border-gray-200 border-2 rounded px-2 py-1.5 mr-2 shadow-sm shadow-slate-300'>
Revision {latest}
@ -76,27 +96,21 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</p>
<p className='my-2'>
<span className='font-bold'>Cite as: </span>
{citation ? <>{citation}</> : <>eeXiv:{slug}</>}
{citation ? <>{citation}</> : <>eeXiv:{hash}</>}
</p>
<Link
href={`/download/${slug}/file${latest}${file === 'other' ? '' : `.${file}`}`}
download={`${slug}-rev-${latest}${file === 'other' ? '' : `.${file}`}`}
target='_blank'
>
<button className='button-default'>
Download{' '}
{(() => {
switch (file) {
case 'other':
return <></>
case 'tar.gz':
return 'Tarball'
default:
return file.toUpperCase()
<Suspense
fallback={
<div className='max-w-sm animate-pulse flex flex-wrap gap-2'>
<div className='rounded-sm h-10 bg-gray-300 w-3 flex-grow basis-1 mt-2 mb-2'></div>
<div className='rounded-sm h-10 bg-gray-300 w-3 flex-grow basis-1.5 mt-2 mb-2'></div>
<div className='rounded-sm h-10 bg-gray-300 w-1 flex-grow basis-1 mt-2 mb-2'></div>
</div>
}
})()}
</button>
</Link>
>
<VersionChooser doc={doc} slug={slug} />
</Suspense>
</div>
)
}
export default DocumentViewer

View file

@ -0,0 +1,94 @@
'use client'
import { useState } from 'react'
import { Document } from '@/app/db/data'
import Link from 'next/link'
import { loadAuthors } from '@/app/db/loaders'
import { useSuspenseQuery } from '@tanstack/react-query'
import { epoch2date } from '@/app/utils/epoch2datestring'
import { toast } from 'react-toastify'
const VersionChooser = ({
doc,
slug,
}: Readonly<{ doc: Document; slug: string }>) => {
const { data, error } = useSuspenseQuery({
queryKey: [doc.manifest.authors.join(' ')],
queryFn: () => {
const data = loadAuthors(doc.manifest.authors)
return data
},
})
if (error) throw error
const { file } = doc
const { authors, latest } = doc.manifest
const date = epoch2date(doc.manifest.dates[doc.manifest.dates.length - 1])
const fileEnding = file === 'other' ? '' : `.${file}`
const [selectedRevision, setSelectedRevision] = useState<number>(latest) // Initialize the selected revision with the latest revision
const notifyCopied = () => toast('BibTeX copied to clipboard!')
const handleClick = () => {
const bibtex = `@article{
author={${authors
.map((a: string, i) => {
const author = data[a].name.first + ' ' + data[a].name.last
if (i === 0) return author
else if (i === authors.length - 1) return ` and ${author}`
else return `, ${author}`
})
.join('')}},
title={${doc.manifest.title}},
journal={eeXiv journal},
year={${date.getFullYear()}},
month={${date.toLocaleString('default', { month: 'short' })}},
url={${window.location.href}}
}`
navigator.clipboard.writeText(bibtex)
notifyCopied()
}
return (
<div>
<Link
href={`/download/${slug}/file${selectedRevision}${fileEnding}`}
target='_blank'
>
<button className='button-default'>
Download{' '}
{(() => {
switch (file) {
case 'other':
return <></>
case 'tar.gz':
return 'Tarball'
}
})()}
</button>
</Link>
<button
className='ml-2 h-10 px-2.5 bg-slate-300 rounded-md'
onClick={handleClick}
>
Export BibTeX
</button>
<select
className='ml-2 h-10 px-2.5 bg-slate-300 rounded-md'
value={`v${selectedRevision}`}
onChange={(e) => {
setSelectedRevision(parseInt(e.target.value.replace(/\D/g, ''), 10))
}}
>
{Array.from({ length: latest }, (_, index) => index + 1).map(
(version) => (
<option key={version} value={`v${version}`} className='p-2.5'>
{version == latest ? 'Latest' : `v${version}`}
</option>
)
)}
</select>
</div>
)
}
export default VersionChooser

View file

@ -0,0 +1,31 @@
.skeleton-box {
display: inline-block;
height: 1em;
position: relative;
overflow: hidden;
background-color: #dddbdd;
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background-image: linear-gradient(
90deg,
rgba(#fff, 0) 0,
rgba(#fff, 0.2) 20%,
rgba(#fff, 0.5) 60%,
rgba(#fff, 0)
);
animation: shimmer 5s infinite;
content: '';
}
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
}

View file

@ -1,13 +1,13 @@
'use client'
import DocumentViewer from './DocumentViewer'
import { documents } from '@/app/db/data'
export function generateStaticParams() {
const docsList = Object.keys(documents)
return docsList.map((doc) => ({ doc }))
}
import ErrorBoundary from '@/app/utils/ErrorBoundary'
export default function Page({
params,
}: Readonly<{ params: { slug: string } }>) {
return <DocumentViewer slug={params.slug} />
return (
<ErrorBoundary>
<DocumentViewer slug={params.slug} />
</ErrorBoundary>
)
}

View file

@ -20,10 +20,23 @@ a:hover {
text-decoration: underline;
}
option {
@apply font-sans;
@apply p-4;
}
.button-default {
@apply bg-blue-600 text-slate-100 hover:bg-blue-400 font-semibold rounded py-2 px-4 my-2 shadow-sm shadow-slate-400;
}
.button-alternate {
@apply bg-slate-100 text-slate-700 hover:bg-blue-100 font-semibold rounded py-2 px-4 my-2 shadow-sm shadow-slate-400;
}
.select-default {
@apply bg-slate-100 text-slate-700 hover:bg-blue-100 font-semibold rounded py-2 px-4 my-2 shadow-sm shadow-slate-400;
}
.badge-base {
@apply px-3 py-1.5 rounded inline-block w-fit text-slate-50 border-2 shadow-sm shadow-slate-400;
}

View file

@ -1,7 +1,9 @@
export default function Page() {
return (
<div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Accessibility</h1>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Accessibility
</h1>
<p>
If you encounter any accessibility-related issues related to your use of
our site, it is likely because of our jank code architecture.

View file

@ -2,7 +2,9 @@ import Link from 'next/link'
export default function Page() {
return (
<div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Copyright</h1>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Copyright
</h1>
<p>
All content on this site is licensed under the{' '}
<a href='https://creativecommons.org/licenses/by-sa/4.0/'>

View file

@ -1,7 +1,9 @@
export default function Page() {
return (
<div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Privacy Policy</h1>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Privacy Policy
</h1>
<p>
{`User privacy is important to us. Just kidding. We don't collect any
personal information. We only use it to help us improve eeXiv. Your

View file

@ -77,7 +77,10 @@ export default function Home() {
FIRST Robotics Competition (FRC)
</Link>
. Materials on this site may be published independently through other
channels. Read more about us <Link href='/about'>here</Link>.
channels. Read more about us <Link href='/about'>here</Link>. eeXiv can
be accessed from its primary domain at{' '}
<a href='https://eexiv.org'>eexiv.org</a> or at our mirror at{' '}
<a href='https://eexiv.vercel.app'>eexiv.vercel.app</a>.
</p>
<News />
<div className='grid grid-cols-1 space-y-2 mt-4 basis-full'>

View file

@ -3,12 +3,15 @@ import Link from 'next/link'
export default function Page() {
return (
<div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Get status notifications</h1>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Get status notifications
</h1>
<p>
We currently do not have the technical support to implement mailing
lists in eeXiv. Check <Link href='/status'>status</Link> frequently for updates.
The best way to stay connected with the status of the eeXiv project is
to watch it on <a href='https://github.com/Team-1280/eeXiv'>GitHub</a>.
lists in eeXiv. Check <Link href='/status'>status</Link> frequently for
updates. The best way to stay connected with the status of the eeXiv
project is to watch it on{' '}
<a href='https://github.com/Team-1280/eeXiv'>GitHub</a>.
</p>
</div>
)

View file

@ -6,7 +6,9 @@ export default function Page() {
return (
<div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-4 font-serif'>Status</h1>
<p className={`${zillaSlab.className} p-6 rounded-lg bg-green-600 text-xl text-white text-center`}>
<p
className={`${zillaSlab.className} p-6 rounded-lg bg-green-600 text-xl text-white text-center`}
>
eeXiv is <strong>online</strong>. All systems{' '}
<strong>operational</strong>.
</p>

View file

@ -1,7 +1,9 @@
export default function Page() {
return (
<div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>Subscribe</h1>
<h1 className='text-3xl text-slate-800 mt-4 mb-2 font-serif'>
Subscribe
</h1>
<p>
We currently do not have the technical support to implement mailing
lists in eeXiv. Check back later for updates. The best way to stay

View file

@ -1,6 +1,7 @@
import { Zilla_Slab } from 'next/font/google'
import { topics } from '@/app/db/data'
import { notFound } from 'next/navigation'
import string2hex from '@/app/utils/string2hex'
const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] })
@ -17,12 +18,17 @@ export default function Page({
const { name, description, wiki } = topic
return (
<div className='flex p-4 lg:p-8 mx-auto bg-slate-200 shadow-slate-300 shadow-sm rounded-md max-w-2xl flex-col gap-4'>
<div
style={{ backgroundColor: string2hex(description) }}
className='flex p-4 lg:p-8 mx-auto bg-slate-200 shadow-slate-300 shadow-sm rounded-md max-w-2xl flex-col gap-4'
>
<h1 className={`${zillaSlab.className} text-5xl`}>{name}</h1>
<p className='text-slate-700 text-lg'>{description}</p>
<p>
<span className='text-slate-800'>Read more at:</span>{' '}
<a href={wiki}>{wiki}</a>
<a className='text-wrap hyphens-none' href={wiki}>
{wiki}
</a>
</p>
</div>
)

View file

@ -11,3 +11,7 @@ export function epoch2datestring(epoch: number): string {
return formattedDate
}
export function epoch2date(epoch: number): Date {
return new Date(epoch * 1000)
}

View file

@ -0,0 +1,22 @@
export default function string2hex(str: string): string {
// Hash function to convert string to a number
let hash = 0
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i)
hash = (hash << 5) - hash + char
hash = hash & hash // Convert to 32bit integer
}
// Convert the hash to a hex color code
let color = '#'
for (let i = 0; i < 3; i++) {
// Ensuring minimum brightness for each component to make text stand out
// Adjust the minimum brightness as needed (e.g., 0x80 for brighter colors)
const minBrightness = 0xa0 // Increased min brightness
const value =
(((hash >> (i * 8)) & 0xff) % (0xff - minBrightness)) + minBrightness
color += ('00' + value.toString(16)).slice(-2)
}
return color
}