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": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-konami-code": "^2.3.0",
"react-snowfall": "^2.1.0",
"react-toastify": "^10.0.4", "react-toastify": "^10.0.4",
"zustand": "^4.5.0" "zustand": "^4.5.0"
}, },
@ -521,6 +523,32 @@
"node": ">=6.9.0" "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": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0", "version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -971,6 +999,38 @@
"react": "^18.0.0" "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": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "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" "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": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "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", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" "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": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -3568,6 +3647,14 @@
"node": "14 || >=16.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": { "node_modules/merge2": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -3781,7 +3868,6 @@
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -4239,7 +4325,6 @@
"version": "15.8.1", "version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.4.0", "loose-envify": "^1.4.0",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -4298,6 +4383,11 @@
"react": "^18.2.0" "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": { "node_modules/react-icons": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz",
@ -4309,8 +4399,31 @@
"node_modules/react-is": { "node_modules/react-is": {
"version": "16.13.1", "version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
"dev": true },
"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": { "node_modules/react-toastify": {
"version": "10.0.4", "version": "10.0.4",
@ -5029,6 +5142,70 @@
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true "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": { "node_modules/tsconfig-paths": {
"version": "3.15.0", "version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -5243,6 +5420,14 @@
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true "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": { "node_modules/webpack-sources": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
@ -5462,6 +5647,17 @@
"node": ">= 14" "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": { "node_modules/yocto-queue": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View file

@ -17,6 +17,8 @@
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-konami-code": "^2.3.0",
"react-snowfall": "^2.1.0",
"react-toastify": "^10.0.4", "react-toastify": "^10.0.4",
"zustand": "^4.5.0" "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> <li>
You can contribute to our website development or add your You can contribute to our website development or add your
documents and user account to eeXiv on our{' '} documents and user account to eeXiv on our{' '}
<Link <Link href='https://github.com/team-1280/eexiv' target='_blank'>
href='https://github.com/team-1280/eexiv'
target='_blank'
>
GitHub repository GitHub repository
</Link> </Link>
. .

View file

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

View file

@ -1,11 +1,12 @@
import Link from 'next/link' import Link from 'next/link'
import { Fragment } from 'react' import { Fragment, Suspense } from 'react'
import { affiliations, nationalities, authors } from '../../db/data' import { affiliations, nationalities, authors } from '../../db/data'
import { Zilla_Slab } from 'next/font/google' import { Zilla_Slab } from 'next/font/google'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import DocumentCard from '@/app/components/DocumentCard' import DocumentCard from '@/app/components/DocumentCard'
import findDocumentsByAuthor from './findDocumentsByAuthor' import findDocumentsByAuthor from './findDocumentsByAuthor'
import cardEffects from '@/app/styles/cardEffects.module.css' import cardEffects from '@/app/styles/cardEffects.module.css'
import KonamiSnowfall from './KonamiSnowfall'
const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] }) const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] })
@ -26,6 +27,7 @@ export default function AuthorDisplay({
const mainPosition = affiliation[0].split('@')[0] const mainPosition = affiliation[0].split('@')[0]
const mainAffiliation = affiliations[mainAffiliationShort] const mainAffiliation = affiliations[mainAffiliationShort]
const { website } = data const { website } = data
return ( return (
<> <>
<span>{mainPosition} at </span> <span>{mainPosition} at </span>
@ -40,17 +42,17 @@ export default function AuthorDisplay({
</a> </a>
</div> </div>
) : null} ) : 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) => ( {affiliation.map((a: string) => (
<Link <Link
key={a} key={a}
href={`/affiliation/${a.split('@')[1]}`} href={`/affiliation/${a.split('@')[1]}`}
className={cardEffects['card-small']} className={`${cardEffects['card-small']} rounded-md`}
> >
<img <img
src={affiliations[a.split('@')[1]].image} src={affiliations[a.split('@')[1]].image}
alt={affiliations[a.split('@')[1]].name} alt={affiliations[a.split('@')[1]].name}
className='h-12 rounded-md' className='h-16 rounded-md p-2'
/> />
</Link> </Link>
))} ))}
@ -133,14 +135,17 @@ export default function AuthorDisplay({
return ( return (
<> <>
<h1 className='text-3xl md:mt-6 mt-4 mb-2 font-serif'>Bio:</h1> <h1 className='text-3xl md:mt-6 mt-4 mb-2 font-serif'>Bio</h1>
<p>{bio}</p> <p className='mb-2'>{bio}</p>
</> </>
) )
} }
return ( 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='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'> <div className='aspect-square w-[60vw] md:w-[30vw] lg:w-[20vw] 2xl:w-[15vw] overflow-hidden mx-auto mb-4'>
<img <img
@ -178,12 +183,11 @@ export default function AuthorDisplay({
))} ))}
</div> </div>
<Bio /> <Bio />
<hr className='mx-auto w-full h-1 border-0 bg-slate-200 my-2 rounded-md' />
<br />
{authorsDocuments.length > 0 && ( {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'> <h1 className='text-3xl md:my-6 my-4 font-serif'>
Published documents Published documents {`(${authorsDocuments.length})`}
</h1> </h1>
{authorsDocuments.map((d) => ( {authorsDocuments.map((d) => (
<Fragment key={d.slug}> <Fragment key={d.slug}>
@ -193,6 +197,6 @@ export default function AuthorDisplay({
</> </>
)} )}
</div> </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> </div>
<ul className='text-slate-50 px-6 list-disc'> <ul className='text-slate-50 px-6 list-disc'>
<li key={1}> <li key={1}>
eeXiv 2.1 has been released! Documents are now statically generated eeXiv 2.2 has been released! You can now select document version and
for instant loading speeds.{' '} 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>
<li key={2}>Mobile support is currently in beta.</li>
<li key={3}> <li key={3}>
eeXiv is currently under active development! There may be major eeXiv is currently under active development! There may be major
updates, breaking changes, or weird bugs. Report bugs, suggest new updates, breaking changes, or weird bugs. Report bugs, suggest new
features, or give us feedback at{' '} 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. our issue tracker.
</a> </a>
</li> </li>
<li key={4}> <li key={4}>
Want to upload your documents or just make yourself a profile on 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 eeXiv? Check our{' '}
information! <Link href='/about' className='text-blue-300'>
about page
</Link>{' '}
for more information!
</li> </li>
</ul> </ul>
</div> </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 FileType = 'pdf' | 'docx' | 'pptx' | 'tar.gz' | 'other'
export type DocumentType = export type DocumentType =
| 'presentation' | 'presentation'
@ -42,11 +14,13 @@ export type reviewer = {
profile?: string profile?: string
url?: string url?: string
} }
export type DocumentStatus = export type DocumentStatus =
| 'draft' | 'draft'
| 'under review' | 'under review'
| 'reviewed' | 'reviewed'
| 'published no review' | 'published no review'
export interface Document { export interface Document {
manifest: DocumentManifest manifest: DocumentManifest
abstract: string abstract: string
@ -415,7 +389,7 @@ export const authors: Readonly<{ [key: string]: Author }> = {
formerAffiliations: ['Programming Lead@1280-programming'], formerAffiliations: ['Programming Lead@1280-programming'],
image: '/img/profiles/avenkatesh.png', image: '/img/profiles/avenkatesh.png',
nationality: ['ind', 'eth', 'usa'], 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', website: 'https://github.com/quantum9Innovation',
}, },
ywu: { ywu: {
@ -441,7 +415,7 @@ export const authors: Readonly<{ [key: string]: Author }> = {
'Student@srvhs', 'Student@srvhs',
], ],
image: '/img/profiles/wlin.jpg', image: '/img/profiles/wlin.jpg',
nationality: ['twn', 'chn', 'jpn', 'usa'], nationality: ['twn', 'usa'],
formerAffiliations: ['Intern@raid-zero'], 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.', 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/', website: 'https://kaitotlex.carrd.co/',
@ -534,6 +508,16 @@ export const authors: Readonly<{ [key: string]: Author }> = {
image: '/img/profiles/cbordalo.jpg', image: '/img/profiles/cbordalo.jpg',
nationality: ['phl', 'usa'], 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 { export interface Affiliation {
@ -545,7 +529,7 @@ export interface Affiliation {
export const affiliations: Readonly<{ [key: string]: Affiliation }> = { export const affiliations: Readonly<{ [key: string]: Affiliation }> = {
'1280-mech': { '1280-mech': {
name: "Team 1280, the Ragin' C Biscuits, Mechanical Subteam", name: "Team 1280, the Ragin' C-Biscuits, Mechanical Subteam",
short: '1280 Mech', short: '1280 Mech',
image: '/img/logos/1280-main.png', 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. 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.`, 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': { '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', short: '1280 EECS',
image: '/img/logos/eecs-wordmark.png', 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. 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.`, 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': { '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', short: '1280 Programming',
image: '/img/logos/1280-main.png', 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': { 'usc-viterbi': {
name: 'University of Southern California, Viterbi School of Engineering', 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.`, the wider world.`,
}, },
'1280-business': { '1280-business': {
name: "Team 1280, the Ragin' C Biscuits, Business Subteam", name: "Team 1280, the Ragin' C-Biscuits, Business Subteam",
short: '1280 Business', short: '1280 Business',
image: '/img/logos/1280-main.png', 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. 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.`, 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': { 'raid-zero': {
name: 'Team 4253 - Raid Zero', name: 'Team 4253, Raid Zero',
short: 'Raid 0', short: 'Raid Zero',
image: '/img/logos/raid-zero.png', 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). 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] [linebreak]
@ -640,7 +624,7 @@ Raid Zero's influence extends beyond the technical achievements in robotics comp
image: '/img/logos/cal-poly-slo.png', 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. 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] [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] [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. 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] [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.`, 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': { 'team-1280': {
name: `Team 1280, the Ragin' C Biscuits`, name: `Team 1280, the Ragin' C-Biscuits`,
short: 'Team 1280', short: 'Team 1280',
image: '/img/logos/1280-main.png', 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] [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. 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] [linebreak]

View file

@ -1,5 +1,11 @@
import { Document, Author, Affiliation, Topic, Nationality } from './data' 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> => { export const loadDocument = (id: string): Promise<Document> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') { if (typeof Worker !== 'undefined') {
@ -8,13 +14,12 @@ export const loadDocument = (id: string): Promise<Document> => {
{ type: 'module' } { type: 'module' }
) )
worker.onmessage = (e: MessageEvent<{ [key: string]: Document }>) => { worker.onmessage = (e: MessageEvent<Document | undefined>) => {
const data = e.data const data = e.data
const doc: Document | undefined = data[id] if (!data) {
if (!doc) {
return reject(new Error('404')) return reject(new Error('404'))
} else { } else {
resolve(doc) resolve(data)
} }
worker.terminate() worker.terminate()
} }
@ -24,7 +29,7 @@ export const loadDocument = (id: string): Promise<Document> => {
worker.terminate() worker.terminate()
} }
worker.postMessage('LOAD') worker.postMessage(id)
} else { } else {
reject( reject(
new Error( 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 }> => { export const loadAllDocuments = (): Promise<{ [key: string]: Document }> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') { if (typeof Worker !== 'undefined') {
@ -83,16 +91,15 @@ export const loadAllAuthors = (): Promise<{ [key: string]: Author }> => {
worker.postMessage('LOAD') worker.postMessage('LOAD')
} else { } else {
reject( return
new Error(
'Web Workers are not supported in this environment. Please avoid using a prehistoric browser.'
)
)
} }
}) })
} }
export const loadAuthor = (id: string): Promise<Author> => { export const loadAuthors = (
authorIds: string[]
): Promise<{ [key: string]: Author }> => {
'use client'
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (typeof Worker !== 'undefined') { if (typeof Worker !== 'undefined') {
const worker = new Worker( const worker = new Worker(
@ -101,12 +108,10 @@ export const loadAuthor = (id: string): Promise<Author> => {
) )
worker.onmessage = (e: MessageEvent<{ [key: string]: Author }>) => { worker.onmessage = (e: MessageEvent<{ [key: string]: Author }>) => {
const data = e.data if (typeof e.data === 'object' && Object.keys(e.data).length > 0) {
const author: Author | undefined = data[id] resolve(e.data)
if (!author) {
return reject(new Error('404'))
} else { } else {
resolve(author) reject(new Error('404'))
} }
worker.terminate() worker.terminate()
} }
@ -116,7 +121,7 @@ export const loadAuthor = (id: string): Promise<Author> => {
worker.terminate() worker.terminate()
} }
worker.postMessage('LOAD') worker.postMessage(authorIds)
} else { } else {
reject( reject(
new Error( 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) => { onmessage = (e) => {
if (e.data === 'LOAD') { let authorIds: string[] = []
self.postMessage(authors) checkIsStringArray(e.data) && (authorIds = e.data as string[])
} let results = getAuthorsById(authorIds)
postMessage(results)
} }

View file

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

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 { Zilla_Slab } from 'next/font/google'
import { epoch2datestring } from '@/app/utils/epoch2datestring' import { epoch2datestring } from '@/app/utils/epoch2datestring'
import { import {
@ -9,14 +9,27 @@ import {
Reviewers, Reviewers,
} from '@/app/components/DataDisplay' } from '@/app/components/DataDisplay'
import { ItemBadge, Status } from '@/app/components/Badges' import { ItemBadge, Status } from '@/app/components/Badges'
import Link from 'next/link' import VersionChooser from './VersionChooser'
import { notFound } from 'next/navigation' 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'] }) const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] })
export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) { const DocumentViewer = ({ slug }: Readonly<{ slug: string }>) => {
const { manifest, abstract, file, citation } = documents[slug] const { data, error } = useSuspenseQuery({
if (!manifest) return notFound() queryKey: [slug],
queryFn: () => {
const data = loadDocument(slug)
return data
},
})
if (error) throw error
let doc = data
const { manifest, abstract, citation } = doc
const { const {
title, title,
authors, authors,
@ -30,6 +43,13 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
status, status,
} = manifest } = manifest
// git style hash
const hash = crypto
.createHash('sha256')
.update(slug)
.digest('hex')
.substring(0, 7)
return ( return (
<div className='max-w-4xl lg:max-w-6xl mx-auto'> <div className='max-w-4xl lg:max-w-6xl mx-auto'>
<h1 <h1
@ -51,7 +71,7 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</span> </span>
</p> </p>
<div className='flex flex-wrap gap-2'> <div className='flex flex-wrap gap-2'>
<ItemBadge itemName={type as DocumentType} /> <ItemBadge itemName={type} />
<Status statusName={status} /> <Status statusName={status} />
<span className='border-gray-200 border-2 rounded px-2 py-1.5 mr-2 shadow-sm shadow-slate-300'> <span className='border-gray-200 border-2 rounded px-2 py-1.5 mr-2 shadow-sm shadow-slate-300'>
Revision {latest} Revision {latest}
@ -76,27 +96,21 @@ export default function DocumentViewer({ slug }: Readonly<{ slug: string }>) {
</p> </p>
<p className='my-2'> <p className='my-2'>
<span className='font-bold'>Cite as: </span> <span className='font-bold'>Cite as: </span>
{citation ? <>{citation}</> : <>eeXiv:{slug}</>} {citation ? <>{citation}</> : <>eeXiv:{hash}</>}
</p> </p>
<Link <Suspense
href={`/download/${slug}/file${latest}${file === 'other' ? '' : `.${file}`}`} fallback={
download={`${slug}-rev-${latest}${file === 'other' ? '' : `.${file}`}`} <div className='max-w-sm animate-pulse flex flex-wrap gap-2'>
target='_blank' <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>
<button className='button-default'> <div className='rounded-sm h-10 bg-gray-300 w-1 flex-grow basis-1 mt-2 mb-2'></div>
Download{' '} </div>
{(() => {
switch (file) {
case 'other':
return <></>
case 'tar.gz':
return 'Tarball'
default:
return file.toUpperCase()
} }
})()} >
</button> <VersionChooser doc={doc} slug={slug} />
</Link> </Suspense>
</div> </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 DocumentViewer from './DocumentViewer'
import { documents } from '@/app/db/data' import ErrorBoundary from '@/app/utils/ErrorBoundary'
export function generateStaticParams() {
const docsList = Object.keys(documents)
return docsList.map((doc) => ({ doc }))
}
export default function Page({ export default function Page({
params, params,
}: Readonly<{ params: { slug: string } }>) { }: 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; text-decoration: underline;
} }
option {
@apply font-sans;
@apply p-4;
}
.button-default { .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; @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 { .badge-base {
@apply px-3 py-1.5 rounded inline-block w-fit text-slate-50 border-2 shadow-sm shadow-slate-400; @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() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <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> <p>
If you encounter any accessibility-related issues related to your use of If you encounter any accessibility-related issues related to your use of
our site, it is likely because of our jank code architecture. 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() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <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> <p>
All content on this site is licensed under the{' '} All content on this site is licensed under the{' '}
<a href='https://creativecommons.org/licenses/by-sa/4.0/'> <a href='https://creativecommons.org/licenses/by-sa/4.0/'>

View file

@ -1,7 +1,9 @@
export default function Page() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <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> <p>
{`User privacy is important to us. Just kidding. We don't collect any {`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 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) FIRST Robotics Competition (FRC)
</Link> </Link>
. Materials on this site may be published independently through other . 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> </p>
<News /> <News />
<div className='grid grid-cols-1 space-y-2 mt-4 basis-full'> <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() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <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> <p>
We currently do not have the technical support to implement mailing We currently do not have the technical support to implement mailing
lists in eeXiv. Check <Link href='/status'>status</Link> frequently for updates. lists in eeXiv. Check <Link href='/status'>status</Link> frequently for
The best way to stay connected with the status of the eeXiv project is updates. The best way to stay connected with the status of the eeXiv
to watch it on <a href='https://github.com/Team-1280/eeXiv'>GitHub</a>. project is to watch it on{' '}
<a href='https://github.com/Team-1280/eeXiv'>GitHub</a>.
</p> </p>
</div> </div>
) )

View file

@ -6,7 +6,9 @@ export default function Page() {
return ( return (
<div className='content text-slate-800'> <div className='content text-slate-800'>
<h1 className='text-3xl text-slate-800 mt-4 mb-4 font-serif'>Status</h1> <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{' '} eeXiv is <strong>online</strong>. All systems{' '}
<strong>operational</strong>. <strong>operational</strong>.
</p> </p>

View file

@ -1,7 +1,9 @@
export default function Page() { export default function Page() {
return ( return (
<div className='content text-slate-800'> <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> <p>
We currently do not have the technical support to implement mailing We currently do not have the technical support to implement mailing
lists in eeXiv. Check back later for updates. The best way to stay 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 { Zilla_Slab } from 'next/font/google'
import { topics } from '@/app/db/data' import { topics } from '@/app/db/data'
import { notFound } from 'next/navigation' import { notFound } from 'next/navigation'
import string2hex from '@/app/utils/string2hex'
const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] }) const zillaSlab = Zilla_Slab({ subsets: ['latin'], weight: ['500'] })
@ -17,12 +18,17 @@ export default function Page({
const { name, description, wiki } = topic const { name, description, wiki } = topic
return ( 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> <h1 className={`${zillaSlab.className} text-5xl`}>{name}</h1>
<p className='text-slate-700 text-lg'>{description}</p> <p className='text-slate-700 text-lg'>{description}</p>
<p> <p>
<span className='text-slate-800'>Read more at:</span>{' '} <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> </p>
</div> </div>
) )

View file

@ -11,3 +11,7 @@ export function epoch2datestring(epoch: number): string {
return formattedDate 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
}