mirror of
https://github.com/BioArchLinux/Rosa.git
synced 2025-03-10 12:02:43 +00:00
[frontend] initial commit of frontend
This commit is contained in:
parent
47f35c5a45
commit
165bf8c947
23 changed files with 6369 additions and 0 deletions
24
frontend/.gitignore
vendored
Normal file
24
frontend/.gitignore
vendored
Normal 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
7
frontend/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
## Build
|
||||
|
||||
```console
|
||||
npm run build
|
||||
```
|
||||
|
||||
output under `dist` directory
|
32
frontend/index.html
Normal file
32
frontend/index.html
Normal 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
7
frontend/jsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
5785
frontend/package-lock.json
generated
Normal file
5785
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
frontend/package.json
Normal file
24
frontend/package.json
Normal 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
6
frontend/src/App.vue
Normal file
|
@ -0,0 +1,6 @@
|
|||
<script setup>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view></router-view>
|
||||
</template>
|
11
frontend/src/api.js
Normal file
11
frontend/src/api.js
Normal 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()
|
||||
}
|
81
frontend/src/components/Index/PackageList.vue
Normal file
81
frontend/src/components/Index/PackageList.vue
Normal 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;">< 上一页</a>
|
||||
<a href="#" @click.prevent="pageDown">下一页 ></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;">< 上一页</a>
|
||||
<a href="#" @click.prevent="pageDown">下一页 ></a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#index-table {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
33
frontend/src/components/Index/PackageListItem.vue
Normal file
33
frontend/src/components/Index/PackageListItem.vue
Normal 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>
|
13
frontend/src/components/PackageInfo/ViewDetail.vue
Normal file
13
frontend/src/components/PackageInfo/ViewDetail.vue
Normal 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
8
frontend/src/main.js
Normal 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
14
frontend/src/router.js
Normal 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
3
frontend/src/state.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import { ref } from 'vue'
|
||||
|
||||
export const displayPackages = ref([])
|
44
frontend/src/style/_nav.scss
Normal file
44
frontend/src/style/_nav.scss
Normal 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;
|
||||
}
|
||||
}
|
21
frontend/src/style/_pkginfo.scss
Normal file
21
frontend/src/style/_pkginfo.scss
Normal 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;
|
||||
}
|
15
frontend/src/style/_root.scss
Normal file
15
frontend/src/style/_root.scss
Normal 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%;
|
||||
}
|
23
frontend/src/style/_variable.scss
Normal file
23
frontend/src/style/_variable.scss
Normal 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;
|
||||
}
|
||||
}
|
38
frontend/src/style/main.scss
Normal file
38
frontend/src/style/main.scss
Normal 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";
|
14
frontend/src/utils/sizing.js
Normal file
14
frontend/src/utils/sizing.js
Normal 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')
|
||||
}
|
62
frontend/src/views/Index.vue
Normal file
62
frontend/src/views/Index.vue
Normal 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>
|
85
frontend/src/views/Package.vue
Normal file
85
frontend/src/views/Package.vue
Normal 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
19
frontend/vite.config.js
Normal 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'
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Add table
Reference in a new issue