Files
VFXdemo/admin.js
2026-04-02 11:15:21 +08:00

131 lines
4.1 KiB
JavaScript
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.
const API_BASE = "/api/shaders";
const nameInput = document.getElementById("name-input");
const authorInput = document.getElementById("author-input");
const codeInput = document.getElementById("code-input");
const saveBtn = document.getElementById("save-btn");
const exampleBtn = document.getElementById("example-btn");
const listEl = document.getElementById("list");
const listCountEl = document.getElementById("list-count");
function esc(s) {
return String(s)
.replace(/&/g, "&")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
const EXAMPLE = `void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
float t = iTime;
float v = sin(uv.x * 9.0 + t * 2.0) + cos(uv.y * 8.0 - t * 1.4);
vec3 col = 0.5 + 0.5 * cos(vec3(0.2, 1.2, 2.2) + v + t);
fragColor = vec4(col, 1.0);
}`;
function validateClientCode(code) {
const hasMainImage = code.includes("mainImage");
const hasAngleMain = code.includes("_umainImage");
if (!hasMainImage && !hasAngleMain) return "代码需包含 mainImage 或 _umainImage。";
return "";
}
async function fetchList() {
const res = await fetch(API_BASE);
if (!res.ok) throw new Error("加载失败");
return res.json();
}
function renderList(items) {
listEl.innerHTML = "";
if (listCountEl) {
listCountEl.textContent = items.length ? `${items.length}` : "";
}
if (!items.length) {
listEl.innerHTML =
'<div class="empty-state">暂无着色器,请在上方创建并保存。</div>';
return;
}
items.forEach((item) => {
const row = document.createElement("div");
row.className = "item";
row.innerHTML = `
<div class="item-main">
<strong>${esc(item.name)}</strong>
<div class="item-meta">${esc(item.author || "unknown")} · ${esc(item.id)}</div>
</div>
<div class="item-actions">
<a class="btn btn-dl" href="${API_BASE}/${encodeURIComponent(item.id)}/download" download>下载 .glsl</a>
<button type="button" class="btn btn-danger">删除</button>
</div>
`;
row.querySelector("button").addEventListener("click", async () => {
await fetch(`${API_BASE}/${encodeURIComponent(item.id)}`, { method: "DELETE" });
await reload();
});
listEl.appendChild(row);
});
}
async function reload() {
const items = await fetchList();
renderList(items);
}
saveBtn.addEventListener("click", async () => {
const name = nameInput.value.trim();
const author = authorInput.value.trim();
const code = codeInput.value.trim();
if (!name) {
alert("名称必填");
return;
}
const err = validateClientCode(code);
if (err) {
alert(err);
return;
}
const res = await fetch(API_BASE, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, author, code }),
});
if (!res.ok) {
const payload = await res.json().catch(() => ({}));
alert(payload.error || "保存失败");
return;
}
const payload = await res.json().catch(() => ({}));
if (payload.sourceFormat === "angle-metal-auto-converted") {
alert("已自动解构并转换为 GLSL展示页可直接渲染。");
}
if (payload.id && typeof window.captureShaderThumbnail === "function") {
try {
const dataUrl = await window.captureShaderThumbnail(payload.code, payload.name);
const thumbRes = await fetch(`${API_BASE}/${encodeURIComponent(payload.id)}/thumbnail`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ pngBase64: dataUrl }),
});
if (!thumbRes.ok) {
const t = await thumbRes.json().catch(() => ({}));
console.warn("缩略图上传失败:", t.error || thumbRes.status);
}
} catch (e) {
console.warn("缩略图生成失败:", e);
}
}
nameInput.value = "";
authorInput.value = "";
codeInput.value = "";
await reload();
});
exampleBtn.addEventListener("click", () => {
nameInput.value = "New Shader";
authorInput.value = "you";
codeInput.value = EXAMPLE;
});
reload();