forked from EduCraft/curriculum-project-hub
124 lines
4.8 KiB
Svelte
124 lines
4.8 KiB
Svelte
<script lang="ts">
|
|
import { api } from "./api.js";
|
|
import { toastOk, toastErr } from "./stores.js";
|
|
import { currentNode } from "./browser.js";
|
|
import type { ExportJob, NodeDetail } from "./types.js";
|
|
import Modal from "./Modal.svelte";
|
|
|
|
let { node }: { node: NodeDetail } = $props();
|
|
|
|
let showEditDesc = $state(false);
|
|
let descDraft = $state("");
|
|
let exportJob = $state<ExportJob | null>(null);
|
|
|
|
const canEdit = $derived(node.role === "MANAGE" || node.role === "EDIT");
|
|
const roleLabel = $derived(node.role === "MANAGE" ? "可管理" : node.role === "EDIT" ? "可编辑" : "只读");
|
|
|
|
$effect(() => {
|
|
void node.id;
|
|
exportJob = null;
|
|
});
|
|
|
|
function openEditDesc(): void {
|
|
descDraft = node.description ?? "";
|
|
showEditDesc = true;
|
|
}
|
|
|
|
async function saveDesc(): Promise<void> {
|
|
const description = descDraft.trim();
|
|
try {
|
|
await api(`/database/api/nodes/${node.id}`, {
|
|
method: "PATCH",
|
|
body: { description: description === "" ? null : description },
|
|
});
|
|
toastOk("简介已保存");
|
|
showEditDesc = false;
|
|
const next = description === "" ? null : description;
|
|
currentNode.update((n) => (n && n.id === node.id ? { ...n, description: next } : n));
|
|
} catch (e) {
|
|
toastErr(e instanceof Error ? e.message : String(e));
|
|
}
|
|
}
|
|
|
|
async function submitExport(): Promise<void> {
|
|
try {
|
|
const r = await api<{ jobId: string; status: string }>(`/database/api/projects/${node.id}/exports`, {
|
|
method: "POST",
|
|
body: { target: "manifest" },
|
|
});
|
|
toastOk("导出已提交");
|
|
void pollExport(r.jobId);
|
|
} catch (e) {
|
|
toastErr(e instanceof Error ? e.message : String(e));
|
|
}
|
|
}
|
|
|
|
async function pollExport(jobId: string): Promise<void> {
|
|
for (;;) {
|
|
await new Promise((r) => setTimeout(r, 800));
|
|
try {
|
|
const job = await api<ExportJob>(`/database/api/exports/${jobId}`);
|
|
exportJob = job;
|
|
if (job.status === "DONE" || job.status === "FAILED") break;
|
|
} catch {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="rounded-xl border border-line-soft bg-panel p-5">
|
|
<div class="mb-4">
|
|
<div class="mb-1.5 text-[11.5px] text-ink-3">简介</div>
|
|
<div class="text-[13.5px] leading-7 text-ink">
|
|
{#if node.description}
|
|
{node.description}
|
|
{:else}
|
|
<span class="italic text-ink-3">暂无简介</span>
|
|
{/if}
|
|
{#if canEdit}
|
|
<button class="ml-2.5 rounded-lg border border-line bg-panel px-2.5 py-0.5 align-middle text-[11.5px] text-ink transition hover:bg-hover" onclick={openEditDesc}>编辑</button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="my-4 border-t border-line-soft"></div>
|
|
|
|
<div class="flex flex-col gap-1.5 text-[13px] text-ink-2">
|
|
<div>我的角色 <b class="font-semibold text-ink">{roleLabel}</b></div>
|
|
<div>创建时间 <b class="font-semibold text-ink">{new Date(node.createdAt).toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" })}</b></div>
|
|
<div>最近修改 <b class="font-semibold text-ink">{new Date(node.updatedAt).toLocaleDateString("zh-CN", { year: "numeric", month: "long", day: "numeric" })}</b></div>
|
|
</div>
|
|
|
|
<div class="my-4 border-t border-line-soft"></div>
|
|
|
|
<div class="mb-2 text-[12.5px] font-semibold">导出</div>
|
|
<div class="flex items-center gap-2">
|
|
<button class="rounded-lg border border-line bg-panel px-3 py-1.5 text-[12.5px] font-medium text-ink transition hover:bg-hover" onclick={submitExport}>开始导出</button>
|
|
{#if exportJob}
|
|
<span class="font-mono text-[11px] text-ink-3">
|
|
{#if exportJob.status === "DONE"}
|
|
完成 · <a class="text-accent underline" href="/database/api/exports/{exportJob.id}/download">下载</a>
|
|
{:else if exportJob.status === "FAILED"}
|
|
失败:{exportJob.error ?? ""}
|
|
{:else}
|
|
{exportJob.status}…
|
|
{/if}
|
|
</span>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
{#if showEditDesc}
|
|
<Modal title="编辑简介" onclose={() => (showEditDesc = false)}>
|
|
<div class="mb-3">
|
|
<label class="mb-1 block text-[11.5px] text-ink-3" for="desc-draft">简要说明这个项目的内容</label>
|
|
<textarea id="desc-draft" rows="5" class="w-full rounded-lg border border-line bg-panel px-3 py-2 text-[13px] leading-7 outline-none focus:border-accent" bind:value={descDraft} placeholder="例如:高中物理必修一第三章,表面张力相关内容……"></textarea>
|
|
</div>
|
|
<div class="mt-4 flex justify-end gap-2">
|
|
<button class="rounded-lg border border-line bg-panel px-3.5 py-1.5 text-[12.5px] font-medium text-ink transition hover:bg-hover" onclick={() => (showEditDesc = false)}>取消</button>
|
|
<button class="rounded-lg bg-accent px-3.5 py-1.5 text-[12.5px] font-medium text-white transition hover:bg-accent-hover" onclick={saveDesc}>保存</button>
|
|
</div>
|
|
</Modal>
|
|
{/if}
|