Merge branch 'main' of https://github.com/Team-1280/eeXiv
This commit is contained in:
commit
4084725537
34 changed files with 711 additions and 147 deletions
27
.github/workflows/style.yml
vendored
Normal file
27
.github/workflows/style.yml
vendored
Normal 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
27
.github/workflows/zenodo.yml
vendored
Normal 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
1
.prettierignore
Normal file
|
@ -0,0 +1 @@
|
|||
*.yml
|
204
package-lock.json
generated
204
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
BIN
public/img/profiles/bgraham.jpg
Normal file
BIN
public/img/profiles/bgraham.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
2
scripts/.gitignore
vendored
Normal file
2
scripts/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.js
|
||||
secret.txt
|
1
scripts/run.sh
Normal file
1
scripts/run.sh
Normal file
|
@ -0,0 +1 @@
|
|||
npx tsc zenodo.ts && node zenodo.js $1 $2
|
34
scripts/zenodo.ts
Normal file
34
scripts/zenodo.ts
Normal 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)
|
|
@ -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>
|
||||
.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
51
src/app/author/[author]/KonamiSnowfall.tsx
Normal file
51
src/app/author/[author]/KonamiSnowfall.tsx
Normal 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`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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 year’s 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]
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
|
94
src/app/document/view/[slug]/VersionChooser.tsx
Normal file
94
src/app/document/view/[slug]/VersionChooser.tsx
Normal 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
|
31
src/app/document/view/[slug]/loading.module.css
Normal file
31
src/app/document/view/[slug]/loading.module.css
Normal 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%);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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/'>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -11,3 +11,7 @@ export function epoch2datestring(epoch: number): string {
|
|||
|
||||
return formattedDate
|
||||
}
|
||||
|
||||
export function epoch2date(epoch: number): Date {
|
||||
return new Date(epoch * 1000)
|
||||
}
|
||||
|
|
22
src/app/utils/string2hex.ts
Normal file
22
src/app/utils/string2hex.ts
Normal 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
|
||||
}
|
Loading…
Reference in a new issue