ismism/ismism.ts/ui/bind.ts
2022-12-09 13:21:21 +08:00

421 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// deno-lint-ignore-file no-window-prefix
import { utc_medium, utc_short } from "../src/date.ts"
import type { Agenda, Soc, User } from "../src/query.ts"
import type { Goal, Tag, Rec, Work, Worker, Fund } from "../src/typ.ts"
import type { NRec, RecOf } from "../src/db.ts"
let hash = ""
let agenda: Agenda
const tags_all: Tag[] = [
"", "进行中", "已结束",
"设施建设", "物资配给", "软件开发",
"苏州", "成都",
"工益公益", "星星家园"
]
const tags_count: number[] = []
let utc_etag = Date.now()
async function query(
q: string
) {
const res = await fetch(`/q/${q}`)
const etag = res.headers.get("etag")?.substring(3)
if (etag) utc_etag = parseInt(etag)
return res.json()
}
function template(
tid: string,
fclass: string[]
): [DocumentFragment, HTMLElement[]] {
const temp = document.getElementById(tid) as HTMLTemplateElement
const t = temp.content.cloneNode(true) as DocumentFragment
return [t, fclass.map(f => t.querySelector(`.${f}`)!)]
}
function etag(
el: HTMLElement,
tags: string[],
count: number[] = []
) {
el.innerHTML = ""
const [t, [a, n, c]] = template("tag", ["tag", "name", "count"])
const ct = tags.length === count.length
if (!ct) c.parentNode?.removeChild(c)
tags.forEach((tag, i) => {
(a as HTMLAnchorElement).href = `#${tag}`
n.innerText = tag.length === 0 ? "全部公示" : tag
if (hash === tag) a.classList.add("darkgray")
else a.classList.remove("darkgray")
if (ct) c.innerText = `${count[i]}`
el.appendChild(t.cloneNode(true))
})
}
function egoal(
el: HTMLElement,
goal: Goal[],
) {
el.innerHTML = ""
for (const { pct, name } of goal) {
const [t, [p, n]] = template("goal", ["pct", "name"])
if (pct === 100) {
p.classList.add("done")
p.innerText = "完成"
} else if (pct > 0) {
p.classList.add("ongoing")
p.style.setProperty("--pct", `${pct}`)
p.innerText = `${pct}%`
}
n.innerText = name
el.appendChild(t)
}
}
const roleclr = new Map([
["发起人", "red"],
["支持者", "purple"],
])
function erec(
rec: Rec,
uname: Map<number, string>,
aname: Map<number, string>,
role: string | Map<number, string>
) {
const [t, [cinit, cuname, crole, caname, cdate, cmsg]] = template("rec",
["initial", "uname", "role", "aname", "date", "msg"])
const n = uname.get(rec.uid)!
cinit.innerText = n[0];
(cinit as HTMLAnchorElement).href = `#u${rec.uid}`
cuname.innerText = n;
(cuname as HTMLAnchorElement).href = `#u${rec.uid}`
const r = typeof role === "string" ? role : role.get(rec.uid)!
crole.innerText = r;
(crole as HTMLAnchorElement).href = `#u${rec.uid}`
crole.classList.add(roleclr.get(r) ?? "amber")
caname.innerText = aname.get(rec._id.aid)!;
(caname as HTMLAnchorElement).href = `#a${rec._id.aid}`
cdate.innerText = utc_short(rec._id.utc)
return { t, cmsg }
}
function ework(
d: HTMLElement,
work: RecOf<Work>,
role: Map<number, string>,
) {
const uname = new Map(work.uname)
const aname = new Map(work.aname)
for (const w of work.rec.slice().reverse()) {
const { t, cmsg } = erec(w, uname, aname, role)
switch (w.op) {
case "goal": cmsg.innerText = `${JSON.stringify(w.goal)}`; break
case "work": cmsg.innerText = w.msg; break
case "video": {
cmsg.innerText = "发布了视频:"
const [t, [a]] = template("video", ["video"])
a.innerText = w.title;
(a as HTMLAnchorElement).href = w.src
cmsg.appendChild(t)
break
}
}
d.appendChild(t)
}
}
function eworker(
d: HTMLElement,
worker: RecOf<Worker>,
role: Map<number, string>,
) {
const uname = new Map(worker.uname)
const aname = new Map(worker.aname)
for (const w of worker.rec.slice().reverse()) {
const { t, cmsg } = erec(w, uname, aname, role)
cmsg.innerText = `作为 ${w.role} 参与工作`
d.appendChild(t)
}
}
function efund(
d: HTMLElement,
fund: RecOf<Fund>
) {
const uname = new Map(fund.uname)
const aname = new Map(fund.aname)
for (const f of fund.rec.slice().reverse()) {
const { t, cmsg } = erec(f, uname, aname, "支持者")
cmsg.innerText = `提供支持: +${f.fund}\n${f.msg}`
d.appendChild(t)
}
}
function erecof(
b: [HTMLElement, HTMLElement, HTMLElement],
d: [HTMLElement, HTMLElement, HTMLElement],
nrec: [number, number, number],
recof: () => Promise<[RecOf<Work>, RecOf<Worker>, RecOf<Fund>]>
) {
let loaded = false
const toggle = (n: number) => {
const on = b[n].classList.contains("darkgray")
b.forEach(b => b.classList.remove("darkgray"))
d.forEach(d => d.style.display = "none")
if (!loaded) recof().then(([work, worker, fund]) => {
const role = new Map(worker.rec.map(r => [r.uid, r.role]))
ework(d[0], work, role)
eworker(d[1], worker, role)
efund(d[2], fund)
d[n].scrollTop = d[n].scrollHeight
loaded = true
})
if (!on) {
b[n].classList.add("darkgray")
d[n].style.display = "block"
d[n].scrollTop = d[n].scrollHeight
}
}
b.forEach((btn, n) => {
btn.getElementsByTagName("span")[0].innerText = `${nrec[n]}`
btn.addEventListener("click", () => toggle(n))
})
}
function esum(
el: HTMLElement,
nrec: NRec
) {
const [t, [
bwork, bworker, bfund, dwork, dworker, dfund,
]] = template("sum", [
"tab.work", "tab.worker", "tab.fund", "rec.work", "rec.worker", "rec.fund",
])
erecof(
[bwork, bworker, bfund],
[dwork, dworker, dfund],
[nrec.work, nrec.worker, nrec.fund], () => {
return Promise.all([
query(`rec_of_recent?coll=work&utc=${utc_etag}`),
query(`rec_of_recent?coll=worker&utc=${utc_etag}`),
query(`rec_of_recent?coll=fund&utc=${utc_etag}`),
])
})
el.appendChild(t)
}
function eagenda(
el: HTMLElement,
agenda: Agenda["agenda"],
rec?: Agenda["rec"],
) {
el.innerHTML = ""
if (rec) esum(el, rec)
for (const {
_id, name, tag, utc, dat, fund, budget, expense, detail, goal, rec
} of agenda) {
const [t, [
cidname, cid, cname, ctag, cdate,
cphoto, cphoto_title, cphoto_prev, cphoto_next, cphoto_nbr, cphoto_total, cphoto_img,
cbar, cfund, cexpense, cdetail, cgoal,
bwork, bworker, bfund, dwork, dworker, dfund,
]] = template("agenda", [
"idname", "id", "name", "tag", "date",
"photo", "photo-title", "photo-prev", "photo-next", "photo-nbr", "photo-total", "photo-img",
"bar", "fund", "expense", "detail", "goal",
"tab.work", "tab.worker", "tab.fund", "rec.work", "rec.worker", "rec.fund",
]);
(cidname as HTMLAnchorElement).href = `#a${_id}`
cid.innerText = `a${_id}`
if (hash === cid.innerText) cid.classList.add("darkgray")
else cid.classList.remove("darkgray")
cname.innerText = name
etag(ctag, tag)
cdate.innerText = `公示时间: ${utc_medium(utc)}\n更新时间${utc_medium(utc_etag)}`
if (dat === null || dat.img.length === 0)
cphoto.parentNode?.parentNode?.removeChild(cphoto.parentNode)
else {
cphoto_total.innerText = `${dat.img.length}`
let n = 0
const nimg = (d: number) => {
n = ((n + d) % dat.img.length + dat.img.length) % dat.img.length
cphoto_title.innerText = dat.img[n].title
cphoto_nbr.innerText = `${n + 1}`;
(cphoto_img as HTMLImageElement).src = dat.img[n].src
}
nimg(0)
cphoto_prev.addEventListener("click", () => nimg(-1))
cphoto_next.addEventListener("click", () => nimg(1))
}
cbar.style.setProperty("--fund", `${fund}`)
cbar.style.setProperty("--budget", `${budget}`)
cbar.style.setProperty("--expense", `${expense}`)
{
const [sfund, spct, sbudget] = [...cfund.children] as HTMLSpanElement[]
sfund.innerText = `${fund}`
spct.innerText = `${budget == 0 ? 0 : (fund / budget * 100).toFixed(0)}%`
sbudget.innerText = `${budget}`
} {
const [sexpense, spct] = [...cexpense.children] as HTMLSpanElement[]
sexpense.innerText = `${expense}`
spct.innerText = `${budget == 0 ? 0 : (expense / budget * 100).toFixed(0)}%`
}
(cdetail as HTMLAnchorElement).href = detail
egoal(cgoal, goal)
erecof(
[bwork, bworker, bfund],
[dwork, dworker, dfund],
[rec.work, rec.worker, rec.fund], () => {
return Promise.all([
query(`rec_of_aid?coll=work&aid=${_id}`),
query(`rec_of_aid?coll=worker&aid=${_id}`),
query(`rec_of_aid?coll=fund&aid=${_id}`),
])
})
el.appendChild(t)
}
}
function euser(
el: HTMLElement,
uid: number,
u: User
) {
if (!u) {
el.innerHTML = `无效用户: u${uid}`
return
} else el.innerHTML = ""
const { name, utc, soc, rec } = u
const [t, [
cidname, cid, cname, cdate, csoc,
bwork, bworker, bfund, dwork, dworker, dfund,
]] = template("user", [
"idname", "id", "name", "date", "soc",
"tab.work", "tab.worker", "tab.fund", "rec.work", "rec.worker", "rec.fund",
]);
(cidname as HTMLAnchorElement).href = `#u${uid}`
cid.innerText = `u${uid}`
if (hash === cid.innerText) cid.classList.add("darkgray")
else cid.classList.remove("darkgray")
cname.innerText = name
cdate.innerText = `注册时间: ${utc_medium(utc)}`
if (soc.length === 0) csoc.innerText += "无"
else for (const s of soc) {
const a = csoc.appendChild(document.createElement("a"))
a.classList.add("member-soc")
a.href = `#s${s._id}`
a.innerText = s.name
}
erecof(
[bwork, bworker, bfund],
[dwork, dworker, dfund],
[rec.work, rec.worker, rec.fund], () => {
return Promise.all([
query(`rec_of_uid?coll=work&uid=${uid}`),
query(`rec_of_uid?coll=worker&uid=${uid}`),
query(`rec_of_uid?coll=fund&uid=${uid}`),
])
})
el.appendChild(t)
}
function esoc(
el: HTMLElement,
sid: number,
s: Soc
) {
if (!s) {
el.innerHTML = `无效团体: s${sid}`
return
} else el.innerHTML = ""
const { name, utc, intro, uid, uname, rec } = s
const [t, [
cidname, cid, cname, cdate, cintro, cuser,
bwork, bworker, bfund, dwork, dworker, dfund,
]] = template("soc", [
"idname", "id", "name", "date", "intro", "user",
"tab.work", "tab.worker", "tab.fund", "rec.work", "rec.worker", "rec.fund",
]);
(cidname as HTMLAnchorElement).href = `#s${sid}`
cid.innerText = `s${sid}`
if (hash === cid.innerText) cid.classList.add("darkgray")
else cid.classList.remove("darkgray")
cname.innerText = name
cdate.innerText = `注册时间: ${utc_medium(utc)}`
cintro.innerText += intro
const user = new Map(uname)
cuser.innerText = uid.length === 0 ? "社团成员:无" : `社团成员(${uid.length})`
for (const u of uid) {
const a = cuser.appendChild(document.createElement("a"))
a.classList.add("member-user")
a.href = `#u${u}`
a.innerText = user.get(u)!
cuser.appendChild(a)
}
erecof(
[bwork, bworker, bfund],
[dwork, dworker, dfund],
[rec.work, rec.worker, rec.fund], () => {
return Promise.all([
query(`rec_of_sid?coll=work&sid=${sid}`),
query(`rec_of_sid?coll=worker&sid=${sid}`),
query(`rec_of_sid?coll=fund&sid=${sid}`),
])
})
el.appendChild(t)
}
window.addEventListener("hashchange", async () => {
hash = decodeURI(window.location.hash).substring(1)
etag(document.querySelector(".title div.tag")!, tags_all, tags_count)
const main = document.getElementById("main")!
switch (hash[0]) {
case undefined: eagenda(main, agenda.agenda, agenda.rec); break
case "u": {
const uid = parseInt(hash.substring(1))
const u = uid > 0 ? await query(`user?uid=${uid}`) : null
euser(main, uid, u)
break
} case "s": {
const sid = parseInt(hash.substring(1))
const s = sid > 0 ? await query(`soc?sid=${sid}`) : null
esoc(main, sid, s)
break
} case "a": {
const aid = parseInt(hash.substring(1))
eagenda(main, agenda.agenda.filter(a => a._id === aid))
break
} default: eagenda(main, agenda.agenda.filter(a => a.tag.includes(hash as Tag))); break
}
})
async function load(
) {
agenda = await query("agenda")
console.log(`\n主义主义开发小组成员招募中\n\n发送自我介绍至网站维护邮箱或微信联系 728 万大可\n \n`)
console.log("ismism-0.0.2-20221209")
console.log(`loaded ${agenda.agenda.length} agenda`)
tags_count.push(agenda.agenda.length, ...tags_all.slice(1).map(
t => agenda.agenda.filter(a => a.tag.includes(t)).length)
)
window.dispatchEvent(new Event("hashchange"))
}
load()