aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/mbf/internal/pkgserver/ui/index.ts
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-09-15 00:06:21 +0900
committerOphestra <cat@gensokyo.uk>2026-09-15 00:06:21 +0900
commit834db962958652502aba88362dbe91df2ee618ee (patch)
tree9c06fbcab83ad56e2d90f24a8d2cf016c6c1d005 /cmd/mbf/internal/pkgserver/ui/index.ts
parentcce7a996b06e516f5afcd41d2d67c2392000b6ab (diff)
cmd/mbf: rename pkgsite
This name is less ambiguous than pkgserver. Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd/mbf/internal/pkgserver/ui/index.ts')
-rw-r--r--cmd/mbf/internal/pkgserver/ui/index.ts331
1 files changed, 0 insertions, 331 deletions
diff --git a/cmd/mbf/internal/pkgserver/ui/index.ts b/cmd/mbf/internal/pkgserver/ui/index.ts
deleted file mode 100644
index 0784b81f..00000000
--- a/cmd/mbf/internal/pkgserver/ui/index.ts
+++ /dev/null
@@ -1,331 +0,0 @@
-interface PackageIndexEntry {
- name: string
- size?: number
- description?: string
- website?: string
- version?: string
- report?: boolean
-}
-
-function entryToHTML(entry: PackageIndexEntry | SearchResult): HTMLTableRowElement {
- let v = entry.version != null ? `<span>${escapeHtml(entry.version)}</span>` : ""
- let s = entry.size != null && entry.size > 0 ? `<p>Size: ${toByteSizeString(entry.size)} (${entry.size})</p>` : ""
- let n: string
- let d: string
- if ('name_matches' in entry) {
- n = `<h2>${nameMatches(entry as SearchResult)} ${v}</h2>`
- } else {
- n = `<h2>${escapeHtml(entry.name)} ${v}</h2>`
- }
- if ('desc_matches' in entry && STATE.getIncludeDescriptions()) {
- d = descMatches(entry as SearchResult)
- } else {
- d = (entry as PackageIndexEntry).description != null ? `<p>${escapeHtml((entry as PackageIndexEntry).description)}</p>` : ""
- }
- let w = entry.website != null ? `<a href="${encodeURI(entry.website)}">Website</a>` : ""
- let r = entry.report ? `Log (<a href=\"${encodeURI('/api/v1/status/' + entry.name)}\">View</a> | <a href=\"${encodeURI('/status/' + entry.name)}\">Download</a>)` : ""
- let row = <HTMLTableRowElement>(document.createElement('tr'))
- row.innerHTML = `<td>
- ${n}
- ${d}
- ${s}
- ${w}
- ${r}
- </td>`
- return row
-}
-
-function nameMatches(sr: SearchResult): string {
- return markMatches(sr.name, sr.name_matches)
-}
-
-function descMatches(sr: SearchResult): string {
- return markMatches(sr.description!, sr.desc_matches)
-}
-
-function markMatches(str: string, indices: [number, number][]): string {
- if (indices == null) {
- return str
- }
- let out: string = ""
- let j = 0
- for (let i = 0; i < str.length; i++) {
- if (j < indices.length) {
- if (i === indices[j][0]) {
- out += `<mark>${escapeHtmlChar(str[i])}`
- continue
- }
- if (i === indices[j][1]) {
- out += `</mark>${escapeHtmlChar(str[i])}`
- j++
- continue
- }
- }
- out += escapeHtmlChar(str[i])
- }
- if (indices[j] !== undefined) {
- out += "</mark>"
- }
- return out
-}
-
-function toByteSizeString(bytes: number): string {
- if (bytes == null) return `unspecified`
- if (bytes < 1024) return `${bytes}B`
- if (bytes < Math.pow(1024, 2)) return `${(bytes / 1024).toFixed(2)}kiB`
- if (bytes < Math.pow(1024, 3)) return `${(bytes / Math.pow(1024, 2)).toFixed(2)}MiB`
- if (bytes < Math.pow(1024, 4)) return `${(bytes / Math.pow(1024, 3)).toFixed(2)}GiB`
- if (bytes < Math.pow(1024, 5)) return `${(bytes / Math.pow(1024, 4)).toFixed(2)}TiB`
- return "not only is it big, it's large"
-}
-
-const API_VERSION = 1
-const ENDPOINT = `/api/v${API_VERSION}`
-
-interface InfoPayload {
- count?: number
- hakurei_version?: string
-}
-
-async function infoRequest(): Promise<InfoPayload> {
- const res = await fetch(`${ENDPOINT}/info`)
- const payload = await res.json()
- return payload as InfoPayload
-}
-
-interface GetPayload {
- values?: PackageIndexEntry[]
-}
-
-enum SortOrders {
- DeclarationAscending,
- DeclarationDescending,
- NameAscending,
- NameDescending
-}
-
-async function getRequest(limit: number, index: number, sort: SortOrders): Promise<GetPayload> {
- const res = await fetch(`${ENDPOINT}/get?limit=${limit}&index=${index}&sort=${sort.valueOf()}`)
- const payload = await res.json()
- return payload as GetPayload
-}
-
-interface SearchResult extends PackageIndexEntry {
- name_matches: [number, number][]
- desc_matches: [number, number][]
- score: number
-}
-
-interface SearchPayload {
- count?: number
- values?: SearchResult[]
-}
-
-async function searchRequest(limit: number, index: number, search: string, desc: boolean): Promise<SearchPayload> {
- const res = await fetch(`${ENDPOINT}/search?limit=${limit}&index=${index}&search=${encodeURIComponent(search)}&desc=${desc}`)
- if (!res.ok) {
- exitSearch()
- alert("invalid search query!")
- return Promise.reject(res.statusText)
- }
- const payload = await res.json()
- return payload as SearchPayload
-}
-
-class State {
- entriesPerPage: number = 10
- entryIndex: number = 0
- maxTotal: number = 0
- maxEntries: number = 0
- sort: SortOrders = SortOrders.DeclarationAscending
- search: boolean = false
-
- getEntriesPerPage(): number {
- return this.entriesPerPage
- }
-
- setEntriesPerPage(entriesPerPage: number) {
- this.entriesPerPage = entriesPerPage
- this.setEntryIndex(Math.floor(this.getEntryIndex() / entriesPerPage) * entriesPerPage)
- }
-
- getEntryIndex(): number {
- return this.entryIndex
- }
-
- setEntryIndex(entryIndex: number) {
- this.entryIndex = entryIndex
- this.updatePage()
- this.updateRange()
- this.updateListings()
- }
-
- getMaxTotal(): number {
- return this.maxTotal
- }
-
- setMaxTotal(max: number) {
- this.maxTotal = max
- }
-
- getSortOrder(): SortOrders {
- return this.sort
- }
-
- setSortOrder(sortOrder: SortOrders) {
- this.sort = sortOrder
- this.setEntryIndex(0)
- }
-
- updatePage() {
- let page = Math.ceil(((this.getEntryIndex() + this.getEntriesPerPage()) - 1) / this.getEntriesPerPage())
- for (let e of document.getElementsByClassName("page-number")) {
- (e as HTMLInputElement).value = String(page)
- }
- }
-
- updateRange() {
- let max = Math.min(this.getEntryIndex() + this.getEntriesPerPage(), this.getMaxTotal())
- document.getElementById("entry-counter")!.textContent = `${this.getEntryIndex() + 1}-${max} of ${this.getMaxTotal()}`
- if (this.search) {
- document.getElementById("search-entry-counter")!.textContent = `${this.getEntryIndex() + 1}-${max} of ${this.maxTotal}/${this.maxEntries}`
- document.getElementById("search-query")!.innerHTML = `<code>${escapeHtml(this.getSearchQuery())}</code>`
- }
- }
-
- getSearchQuery(): string {
- let queryString = document.getElementById("search")!;
- return (queryString as HTMLInputElement).value
- }
-
- getIncludeDescriptions(): boolean {
- let includeDesc = document.getElementById("include-desc")!;
- return (includeDesc as HTMLInputElement).checked
- }
-
- updateListings() {
- if (this.search) {
- searchRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSearchQuery(), this.getIncludeDescriptions())
- .then(res => {
- let table = document.getElementById("pkg-list")!
- table.innerHTML = ''
- for (let row of res.values!) {
- table.appendChild(entryToHTML(row))
- }
- STATE.maxTotal = res.count!
- STATE.updateRange()
- if(res.count! < 1) {
- exitSearch()
- alert("no results found!")
- }
- })
- } else {
- getRequest(this.getEntriesPerPage(), this.getEntryIndex(), this.getSortOrder())
- .then(res => {
- let table = document.getElementById("pkg-list")!
- table.innerHTML = ''
- for (let row of res.values!) {
- table.appendChild(entryToHTML(row))
- }
- })
- }
- }
-}
-
-let STATE: State
-
-
-function lastPageIndex(): number {
- return Math.floor(STATE.getMaxTotal() / STATE.getEntriesPerPage()) * STATE.getEntriesPerPage()
-}
-
-function setPage(page: number) {
- STATE.setEntryIndex(Math.max(0, Math.min(STATE.getEntriesPerPage() * (page - 1), lastPageIndex())))
-}
-
-
-function escapeHtml(str?: string): string {
- let out: string = ''
- if (str == undefined) return ""
- for (let i = 0; i < str.length; i++) {
- out += escapeHtmlChar(str[i])
- }
- return out
-}
-
-function escapeHtmlChar(char: string): string {
- if (char.length != 1) return char
- switch (char[0]) {
- case '&':
- return "&amp;"
- case '<':
- return "&lt;"
- case '>':
- return "&gt;"
- case '"':
- return "&quot;"
- case "'":
- return "&apos;"
- default:
- return char
- }
-}
-
-function firstPage() {
- STATE.setEntryIndex(0)
-}
-
-function prevPage() {
- let index = STATE.getEntryIndex()
- STATE.setEntryIndex(Math.max(0, index - STATE.getEntriesPerPage()))
-}
-
-function lastPage() {
- STATE.setEntryIndex(lastPageIndex())
-}
-
-function nextPage() {
- let index = STATE.getEntryIndex()
- STATE.setEntryIndex(Math.min(lastPageIndex(), index + STATE.getEntriesPerPage()))
-}
-
-function doSearch() {
- document.getElementById("top-controls-regular")!.toggleAttribute("hidden");
- document.getElementById("search-top-controls")!.toggleAttribute("hidden");
- STATE.search = true;
- STATE.setEntryIndex(0);
-}
-
-function exitSearch() {
- document.getElementById("top-controls-regular")!.toggleAttribute("hidden");
- document.getElementById("search-top-controls")!.toggleAttribute("hidden");
- STATE.search = false;
- STATE.setMaxTotal(STATE.maxEntries)
- STATE.setEntryIndex(0)
-}
-
-function main() {
- STATE = new State()
- infoRequest()
- .then(res => {
- STATE.maxEntries = res.count!
- STATE.setMaxTotal(STATE.maxEntries)
- document.getElementById("hakurei-version")!.textContent = res.hakurei_version!
- STATE.updateRange()
- STATE.updateListings()
- })
- for (let e of document.getElementsByClassName("page-number")) {
- e.addEventListener("change", (_) => {
- setPage(parseInt((e as HTMLInputElement).value))
- })
- }
- document.getElementById("count")?.addEventListener("change", (event) => {
- STATE.setEntriesPerPage(parseInt((event.target as HTMLSelectElement).value))
- })
- document.getElementById("sort")?.addEventListener("change", (event) => {
- STATE.setSortOrder(parseInt((event.target as HTMLSelectElement).value))
- })
- document.getElementById("search")?.addEventListener("keyup", (event) => {
- if (event.key === 'Enter') doSearch()
- })
-} \ No newline at end of file