Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions site/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,13 @@ async function init() {
const r = await fetch("/api/me");
if (r.ok) me = await r.json();
} catch { /* not logged in */ }
$("boot-view").classList.add("hidden");
$(me ? "app-view" : "login-view").classList.remove("hidden");
if (me) {
$("who-box").classList.remove("hidden");
$("who").textContent = me.login + (me.is_admin ? "(管理员)" : "");
// Honor the tab encoded in the URL hash so a refresh stays put.
applyRoute();
}
}

Expand All @@ -136,17 +139,39 @@ async function loadIndex() {
}

/* --------------------------------- tabs ---------------------------------- */
// Each tab is a distinct URL hash (#/upload, #/mine) so a page refresh keeps
// the user on the same tab and the browser back/forward buttons work.

$("tab-upload").addEventListener("click", () => switchTab("upload"));
$("tab-mine").addEventListener("click", () => { switchTab("mine"); renderMine(); });
function tabFromHash() {
return /^#\/?mine\b/.test(location.hash || "") ? "mine" : "upload";
}

function switchTab(which) {
// Reflect the current URL hash into the visible tab (no history change).
function applyRoute() {
showTab(tabFromHash());
}

// Update the visible panels/buttons for a tab, without touching the URL.
function showTab(which) {
$("tab-upload").classList.toggle("active", which === "upload");
$("tab-mine").classList.toggle("active", which === "mine");
$("upload-panel").classList.toggle("hidden", which !== "upload");
$("mine-panel").classList.toggle("hidden", which !== "mine");
if (which === "mine") renderMine();
}

// Clicking a tab navigates by hash; the hashchange handler does the rendering.
// (If the hash is already correct, hashchange won't fire, so render directly.)
function switchTab(which) {
const hash = which === "mine" ? "#/mine" : "#/upload";
if (location.hash === hash) showTab(which);
else location.hash = hash;
}

$("tab-upload").addEventListener("click", () => switchTab("upload"));
$("tab-mine").addEventListener("click", () => switchTab("mine"));
window.addEventListener("hashchange", applyRoute);

/* ------------------------------ file picking ----------------------------- */

const drop = $("drop");
Expand Down Expand Up @@ -391,7 +416,11 @@ $("submit-btn").addEventListener("click", async () => {
/* ------------------------------ my packages ------------------------------ */

async function renderMine() {
if (!me) return;
const rows = $("mine-rows");
// Show a spinner while the (possibly slow) APT index fetch is in flight, so
// an empty table never looks like a broken page.
rows.innerHTML = `<tr><td colspan="4"><div class="loading"><span class="spinner"></span>正在读取你的软件包…</div></td></tr>`;
const idx = await loadIndex();
const mine = [];
for (const [name, entries] of idx) {
Expand Down
13 changes: 12 additions & 1 deletion site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
.login-box { text-align: center; padding: 48px 20px; }
.login-box button { width: auto; padding: 12px 40px; }
.muted { color: var(--dim); font-size: 13px; }
.loading { display: flex; align-items: center; justify-content: center; gap: 10px;
color: var(--dim); font-size: 13px; padding: 30px 10px; }
.spinner { width: 16px; height: 16px; border: 2px solid var(--line);
border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite;
display: inline-block; flex: none; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
Expand All @@ -110,6 +116,11 @@ <h1>CardputerZero 开发者中心</h1>
</header>

<main>
<!-- 启动加载(拉取登录状态期间显示,避免整页空白像出 bug) -->
<section id="boot-view" class="panel">
<div class="loading"><span class="spinner"></span>加载中…</div>
</section>

<!-- 未登录 -->
<section id="login-view" class="panel login-box hidden">
<p>上传 / 管理 AppStore 软件包需要 GitHub 登录。<br>
Expand Down Expand Up @@ -195,7 +206,7 @@ <h1>CardputerZero 开发者中心</h1>
<div class="panel hidden" id="mine-panel">
<table>
<thead><tr><th>包名</th><th>版本</th><th>Maintainer</th><th></th></tr></thead>
<tbody id="mine-rows"><tr><td colspan="4" class="muted">加载中…</td></tr></tbody>
<tbody id="mine-rows"><tr><td colspan="4"><div class="loading"><span class="spinner"></span>正在读取你的软件包…</div></td></tr></tbody>
</table>
<p class="muted">列表来自线上 APT 索引,只显示归属于你的包。可直接下载已发布的 .deb;下架会生成移除 PR。</p>
</div>
Expand Down
Loading