[frontend] initial commit of frontend

This commit is contained in:
sinsong 2023-06-27 22:18:38 +08:00
parent 47f35c5a45
commit 165bf8c947
23 changed files with 6369 additions and 0 deletions

24
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

7
frontend/README.md Normal file
View file

@ -0,0 +1,7 @@
## Build
```console
npm run build
```
output under `dist` directory

32
frontend/index.html Normal file
View file

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BioArchLinux packages</title>
</head>
<body>
<!-- ArchLinux Like NavBar -->
<nav>
<div id="logo">
<h1><a href="/" title="Take me home">Bio Arch Linux</a></h1>
</div>
<ul id="navbarlist">
<li><a href="https://bioarchlinux.org/">Home</a></li>
<li><a href="/" class="active">Packages</a></li>
<li><a href="https://matrix.to/#/#bioarchlinux:matrix.org">Chat</a></li>
<li><a href="https://wiki.bioarchlinux.org/">Wiki</a></li>
<li><a href="https://repo.bioarchlinux.org/">Repository</a></li>
<li><a href="https://build.bioarchlinux.org/">Status</a></li>
<li><a href="https://hub.docker.com/r/bioarchlinux/bioarchlinux">Docker</a></li>
<li><a href="https://github.com/BioArchLinux/wsl">WSL</a></li>
</ul>
</nav>
<!-- Vue Render ROOT -->
<div id="app"></div>
<script type="module" src="./src/main.js"></script>
</body>
</html>

7
frontend/jsconfig.json Normal file
View file

@ -0,0 +1,7 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}

5785
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
frontend/package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"lint": "standard"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"sass": "^1.62.1",
"standard": "^17.1.0",
"vite": "^4.3.9"
},
"dependencies": {
"flexsearch": "^0.7.31",
"moment": "^2.29.4",
"vue": "^3.3.4",
"vue-router": "^4.2.2"
}
}

6
frontend/src/App.vue Normal file
View file

@ -0,0 +1,6 @@
<script setup>
</script>
<template>
<router-view></router-view>
</template>

11
frontend/src/api.js Normal file
View file

@ -0,0 +1,11 @@
const API_PKGLIST = 'https://bioarchlinux.org/api/pkglist.json'
export
async function requestPackageList () {
return await (await fetch('/api/pkglist.json')).json()
}
export
async function requestPackageInfo (pkg) {
return await (await fetch(`/api/${pkg}.json`)).json()
}

View file

@ -0,0 +1,81 @@
<script setup>
import { ref, watch } from 'vue';
import PackageListItem from './PackageListItem.vue'
import { displayPackages } from '@/state'
import { useRouter } from 'vue-router'
const router = useRouter()
const currentDisplay = ref([])
const count = ref(0)
// Paging
let PAGE_ITEM_SIZE = 100
let pages = 0
let currentPage = 1
function performPaging() {
pages = Math.ceil(count.value / PAGE_ITEM_SIZE)
currentPage = 1
displayPage()
}
function displayPage() {
let start = (currentPage - 1) * PAGE_ITEM_SIZE
currentDisplay.value = displayPackages.value.slice(start, start + PAGE_ITEM_SIZE)
}
function pageUp() { if (currentPage > 1) { --currentPage; displayPage(); } }
function pageDown() { if (currentPage < pages) { ++currentPage; displayPage(); } }
function navigatorToItem(pkg) {
router.push(`/${pkg}`)
}
watch(
displayPackages,
async newVal => {
count.value = newVal.length
performPaging()
}
)
</script>
<template>
<div style="display: flex;">
<p>{{ count }} packages. page {{ currentPage }} of {{ pages }}.</p>
<div style="margin-left: auto;">
<a href="#" @click.prevent="pageUp" style="margin-right: 2em;">&lt; 上一页</a>
<a href="#" @click.prevent="pageDown">下一页 &gt;</a>
</div>
</div>
<table id="index-table">
<thead>
<tr>
<td>Name</td>
<td>Version</td>
<td>Description</td>
<td>Last Build</td>
</tr>
</thead>
<tbody v-for="pkg in currentDisplay" :key="pkg">
<PackageListItem :pkgname="pkg" @click="navigatorToItem(pkg)"></PackageListItem>
</tbody>
</table>
<div style="display: flex;">
<p>{{ count }} packages. page {{ currentPage }} of {{ pages }}.</p>
<div style="margin-left: auto;">
<a href="#" @click.prevent="pageUp" style="margin-right: 2em;">&lt; 上一页</a>
<a href="#" @click.prevent="pageDown">下一页 &gt;</a>
</div>
</div>
</template>
<style>
#index-table {
width: 100%;
}
</style>

View file

@ -0,0 +1,33 @@
<script setup>
import { requestPackageInfo } from '@/api'
import { ref } from 'vue'
import moment from 'moment'
const props = defineProps({
pkgname: String
})
let info_name = ref("...")
let info_version = ref("...")
let info_desc = ref("...")
let info_date = ref("...")
// https://bioarchlinux.org/api/abacas.json
requestPackageInfo(props.pkgname)
.then(obj => {
info_name.value = obj.name
info_version.value = obj.version
info_desc.value = obj.desc
info_date.value = moment.unix(obj.builddate).format('YYYY-MM-DD')
})
</script>
<template>
<tr class="index-item">
<td>{{ info_name }}</td>
<td>{{ info_version }}</td>
<td>{{ info_desc }}</td>
<td>{{ info_date }}</td>
</tr>
</template>

View file

@ -0,0 +1,13 @@
<script setup>
import {ref} from 'vue'
const props = defineProps({
message: String
})
const expand = ref(false)
</script>
<template>
<slot v-if="expand"></slot>
<a href="#" v-else @click.prevent="expand = true">{{ message }}</a>
</template>

8
frontend/src/main.js Normal file
View file

@ -0,0 +1,8 @@
import { createApp } from 'vue'
import './style/main.scss'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')

14
frontend/src/router.js Normal file
View file

@ -0,0 +1,14 @@
import { createRouter, createWebHistory } from "vue-router"
import Index from './views/Index.vue'
import Package from './views/Package.vue'
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: Index },
{ path: '/:packageName', component: Package }
]
})
export default router

3
frontend/src/state.js Normal file
View file

@ -0,0 +1,3 @@
import { ref } from 'vue'
export const displayPackages = ref([])

View file

@ -0,0 +1,44 @@
nav {
padding: 10px 15px;
background: #333;
border-bottom: 5px #08c solid;
display: flex;
}
#logo {
margin-right: auto;
}
#navbarlist {
list-style: none;
}
#navbarlist li {
font-size: 14px;
font-family: sans-serif;
line-height: 14px;
display: inline-block;
&:not(:last-child) {
margin-right: 10px;
}
}
#navbarlist a{
color: #999;
font-weight: bold;
text-decoration: none;
display: inline-block;
padding: 14px 10px 0px;
&.active {
color: white;
}
&:hover {
color: white;
text-decoration: underline;
}
}

View file

@ -0,0 +1,21 @@
.pkgname-header {
border-bottom: 1px solid #888;
margin-bottom: .5em;
}
table.pkginfo {
th {
text-align: start;
}
td {
padding-left: 2em;
}
}
.pkginfo-section {
background: #555;
color: #fff;
margin: .5em 0;
padding: .2em .35em;
}

View file

@ -0,0 +1,15 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: var(--fg);
background: var(--bg);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}

View file

@ -0,0 +1,23 @@
@media screen and (prefers-color-scheme: light){
:root {
--fg: #000;
--bg: #fff;
--box-bg: #ecf2f5;
--index-thead-border: #000;
--index-item-hover: #ffd;
}
}
@media screen and (prefers-color-scheme: dark){
:root {
--fg: rgba(255, 255, 255, 0.87);
--bg: #000;
--box-bg: #212427;
--index-thead-border: #fff;
--index-item-hover: #424242;
}
}

View file

@ -0,0 +1,38 @@
// Configuration
@import "variable";
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
#app {
padding: 15px;
}
table {
border-collapse: collapse;
margin: .5em 0;
}
.box {
border: 1px solid #bcd;
padding: .5em;
background: var(--box-bg);
}
table#index-table thead {
border-bottom: 1px solid var(--index-thead-border);
}
tr.index-item:hover {
background: var(--index-item-hover);
}
// Components
@import "root";
@import "nav";
@import "pkginfo";

View file

@ -0,0 +1,14 @@
export function calculateSize(size) {
function wellSized(num) {
return num > 1 && num < 1024
}
if (size === undefined) { return }
if (wellSized(size)) { return `${size.toFixed(1)} B` }
size /= 1024
if (wellSized(size)) { return `${size.toFixed(1)} KB` }
size /= 1024
if (wellSized(size)) { return `${size.toFixed(1)} MB` }
size /= 1024
if (wellSized(size)) { return `${size.toFixed(1)} GB` }
console.error('size too big')
}

View file

@ -0,0 +1,62 @@
<script setup>
import { ref, watch } from 'vue'
import PackageList from '@/components/Index/PackageList.vue'
import Index from 'flexsearch/dist/module/index'
import { requestPackageInfo, requestPackageList } from '../api'
import { displayPackages } from '@/state'
let packageList = null
const searchIndex = new Index({ tokenize: 'forward' })
let searchInput = ref("")
function updateSearchIndex(pkglist) {
pkglist.forEach((content, index) => {
searchIndex.add(index, content)
})
}
requestPackageList()
.then((obj) => {
packageList = obj
displayPackages.value = obj
updateSearchIndex(obj)
})
watch(searchInput, async(newVal, oldVal) => {
if (searchInput.value.length === 0) {
displayPackages.value = packageList
}
else {
const searchResult = searchIndex.search(searchInput.value)
const finalResult = []
searchResult.forEach((elem) => {
finalResult.push(packageList[elem])
})
displayPackages.value = finalResult
}
})
</script>
<template>
<div class="box" style="margin-bottom: 15px;">
<input class="search" type="text" v-model="searchInput">
</div>
<div class="box">
<PackageList></PackageList>
</div>
</template>
<style>
.search {
width: 100%;
margin: 15px 0;
font-size: 15px;
padding: .5em .5em;
}
</style>

View file

@ -0,0 +1,85 @@
<script setup>
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import ViewDetail from '@/components/PackageInfo/ViewDetail.vue'
import moment from 'moment'
import { requestPackageInfo } from '@/api'
import { calculateSize } from '@/utils/sizing'
const route = useRoute()
const pkginfo = ref({})
const depends = ref([])
const optdepends = ref([])
const makedepends = ref([])
const files = ref([])
const builddate = ref("")
requestPackageInfo(route.params.packageName)
.then(obj => {
pkginfo.value = obj
depends.value = obj.depends.split('\n').filter((elem) => elem.length > 0)
optdepends.value = obj.optdepends.split('\n').filter((elem) => elem.length > 0)
makedepends.value = obj.makedepends.split('\n').filter((elem) => elem.length > 0)
files.value = obj.files.split('\n').filter((elem) => elem.length > 0)
builddate.value = moment.unix(obj.builddate).format('YYYY-MM-DD HH:MM:SS')
})
</script>
<template>
<div class="box">
<h2 class="pkgname-header">{{ pkginfo.name }} {{ pkginfo.version }}</h2>
<table class="pkginfo">
<tbody>
<tr>
<th>Architecture:</th>
<td>{{ pkginfo.arch }}</td>
</tr>
<tr>
<th>Description:</th>
<td>{{ pkginfo.desc }}</td>
</tr>
<tr>
<th>Upstream URL:</th>
<td><a :href="pkginfo.url">{{ pkginfo.url }}</a></td>
</tr>
<tr>
<th>License(s):</th>
<td>{{ pkginfo.license }}</td>
</tr>
<tr>
<th>Packager:</th>
<td>{{ pkginfo.packager }}</td>
</tr>
<tr>
<th>Package Size:</th>
<td>{{ calculateSize(pkginfo.csize) }}</td>
</tr>
<tr>
<th>Installed Size:</th>
<td>{{ calculateSize(pkginfo.isize) }}</td>
</tr>
<tr>
<th>Build Date:</th>
<td>{{ builddate }}</td>
</tr>
</tbody>
</table>
<h3 class="pkginfo-section">Dependencies</h3>
<p v-for="dep in depends"> {{ dep }}</p>
<p v-for="optdep in optdepends"> {{ optdep }} (optional)</p>
<p v-for="makedep in makedepends"> {{ makedep }} (make)</p>
<h3 class="pkginfo-section">Package Content</h3>
<ViewDetail :message="`View the file list for ${pkginfo.name}`">
<p v-for="file in files"> {{ file }}</p>
</ViewDetail>
</div>
</template>

19
frontend/vite.config.js Normal file
View file

@ -0,0 +1,19 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
// used for development, bypass CORS policy
proxy: {
'/api': 'https://bioarchlinux.org'
}
}
})