mirror of
https://github.com/imgfloat/server.git
synced 2026-02-05 03:39:26 +00:00
354 lines
12 KiB
JavaScript
354 lines
12 KiB
JavaScript
const elements = {
|
|
adminList: document.getElementById("admin-list"),
|
|
suggestionList: document.getElementById("admin-suggestions"),
|
|
adminInput: document.getElementById("new-admin"),
|
|
addAdminButton: document.getElementById("add-admin-btn"),
|
|
canvasWidth: document.getElementById("canvas-width"),
|
|
canvasHeight: document.getElementById("canvas-height"),
|
|
canvasStatus: document.getElementById("canvas-status"),
|
|
canvasSaveButton: document.getElementById("save-canvas-btn"),
|
|
allowChannelEmotes: document.getElementById("allow-channel-emotes"),
|
|
allowSevenTvEmotes: document.getElementById("allow-7tv-emotes"),
|
|
allowScriptChat: document.getElementById("allow-script-chat"),
|
|
scriptSettingsStatus: document.getElementById("script-settings-status"),
|
|
scriptSettingsSaveButton: document.getElementById("save-script-settings-btn"),
|
|
deleteAccountButton: document.getElementById("delete-account-btn"),
|
|
};
|
|
|
|
const apiBase = `/api/channels/${encodeURIComponent(broadcaster)}`;
|
|
|
|
function buildIdentity(admin) {
|
|
const identity = document.createElement("div");
|
|
identity.className = "identity-row";
|
|
|
|
const avatar = document.createElement(admin.avatarUrl ? "img" : "div");
|
|
avatar.className = "avatar";
|
|
if (admin.avatarUrl) {
|
|
avatar.src = admin.avatarUrl;
|
|
avatar.alt = `${admin.displayName || admin.login} avatar`;
|
|
} else {
|
|
avatar.classList.add("avatar-fallback");
|
|
avatar.textContent = (admin.displayName || admin.login || "?").charAt(0).toUpperCase();
|
|
}
|
|
|
|
const details = document.createElement("div");
|
|
details.className = "identity-text";
|
|
const title = document.createElement("p");
|
|
title.className = "list-title";
|
|
title.textContent = admin.displayName || admin.login;
|
|
const subtitle = document.createElement("p");
|
|
subtitle.className = "muted";
|
|
subtitle.textContent = `@${admin.login}`;
|
|
|
|
details.appendChild(title);
|
|
details.appendChild(subtitle);
|
|
identity.appendChild(avatar);
|
|
identity.appendChild(details);
|
|
return identity;
|
|
}
|
|
|
|
function renderAdmins(list) {
|
|
if (!elements.adminList) return;
|
|
elements.adminList.innerHTML = "";
|
|
if (!list || list.length === 0) {
|
|
const empty = document.createElement("li");
|
|
empty.className = "stacked-list-item empty";
|
|
empty.textContent = "No channel admins yet.";
|
|
elements.adminList.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
list.forEach((admin) => {
|
|
const li = document.createElement("li");
|
|
li.className = "stacked-list-item";
|
|
|
|
li.appendChild(buildIdentity(admin));
|
|
|
|
const actions = document.createElement("div");
|
|
actions.className = "actions";
|
|
|
|
const removeBtn = document.createElement("button");
|
|
removeBtn.type = "button";
|
|
removeBtn.className = "secondary";
|
|
removeBtn.textContent = "Remove";
|
|
removeBtn.addEventListener("click", () => removeAdmin(admin.login));
|
|
|
|
actions.appendChild(removeBtn);
|
|
li.appendChild(actions);
|
|
elements.adminList.appendChild(li);
|
|
});
|
|
}
|
|
|
|
function renderSuggestedAdmins(list) {
|
|
if (!elements.suggestionList) return;
|
|
|
|
elements.suggestionList.innerHTML = "";
|
|
if (!list || list.length === 0) {
|
|
const empty = document.createElement("li");
|
|
empty.className = "stacked-list-item empty";
|
|
empty.textContent = "No moderator suggestions right now.";
|
|
elements.suggestionList.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
list.forEach((admin) => {
|
|
const li = document.createElement("li");
|
|
li.className = "stacked-list-item";
|
|
|
|
li.appendChild(buildIdentity(admin));
|
|
|
|
const actions = document.createElement("div");
|
|
actions.className = "actions";
|
|
|
|
const addBtn = document.createElement("button");
|
|
addBtn.type = "button";
|
|
addBtn.className = "ghost";
|
|
addBtn.textContent = "Add channel admin";
|
|
addBtn.addEventListener("click", () => addAdmin(admin.login));
|
|
|
|
actions.appendChild(addBtn);
|
|
li.appendChild(actions);
|
|
elements.suggestionList.appendChild(li);
|
|
});
|
|
}
|
|
|
|
function normalizeUsername(value) {
|
|
return (value || "").trim().replace(/^@+/, "");
|
|
}
|
|
|
|
function setButtonBusy(button, isBusy, busyLabel) {
|
|
if (!button) return;
|
|
if (!button.dataset.defaultLabel) {
|
|
button.dataset.defaultLabel = button.textContent;
|
|
}
|
|
button.disabled = isBusy;
|
|
if (busyLabel) {
|
|
button.textContent = isBusy ? busyLabel : button.dataset.defaultLabel;
|
|
}
|
|
}
|
|
|
|
async function fetchJson(path, options = {}, errorMessage = "Request failed") {
|
|
const response = await fetch(`${apiBase}${path}`, options);
|
|
if (!response.ok) {
|
|
throw new Error(errorMessage);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
async function fetchSuggestedAdmins() {
|
|
try {
|
|
const data = await fetchJson("/admins/suggestions", {}, "Failed to load admin suggestions");
|
|
renderSuggestedAdmins(data);
|
|
} catch (error) {
|
|
renderSuggestedAdmins([]);
|
|
}
|
|
}
|
|
|
|
async function fetchAdmins() {
|
|
try {
|
|
const data = await fetchJson("/admins", {}, "Failed to load admins");
|
|
renderAdmins(data);
|
|
} catch (error) {
|
|
renderAdmins([]);
|
|
showToast("Unable to load admins right now. Please try again.", "error");
|
|
}
|
|
}
|
|
|
|
async function removeAdmin(username) {
|
|
if (!username) return;
|
|
try {
|
|
const response = await fetch(
|
|
`${apiBase}/admins/${encodeURIComponent(username)}`,
|
|
{ method: "DELETE" },
|
|
);
|
|
if (!response.ok) {
|
|
throw new Error("Remove admin failed");
|
|
}
|
|
await Promise.all([fetchAdmins(), fetchSuggestedAdmins()]);
|
|
} catch (error) {
|
|
showToast("Failed to remove admin. Please retry.", "error");
|
|
}
|
|
}
|
|
|
|
async function addAdmin(usernameFromAction) {
|
|
const username = normalizeUsername(usernameFromAction || elements.adminInput?.value);
|
|
if (!username) {
|
|
showToast("Enter a Twitch username to add as an admin.", "info");
|
|
return;
|
|
}
|
|
|
|
setButtonBusy(elements.addAdminButton, true, "Adding...");
|
|
try {
|
|
await fetchJson(
|
|
"/admins",
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username }),
|
|
},
|
|
"Add admin failed",
|
|
);
|
|
if (elements.adminInput) {
|
|
elements.adminInput.value = "";
|
|
}
|
|
showToast(`Added @${username} as an admin.`, "success");
|
|
await Promise.all([fetchAdmins(), fetchSuggestedAdmins()]);
|
|
} catch (error) {
|
|
showToast("Unable to add admin right now. Please try again.", "error");
|
|
} finally {
|
|
setButtonBusy(elements.addAdminButton, false, "Adding...");
|
|
}
|
|
}
|
|
|
|
function renderCanvasSettings(settings) {
|
|
if (elements.canvasWidth) elements.canvasWidth.value = Math.round(settings.width);
|
|
if (elements.canvasHeight) elements.canvasHeight.value = Math.round(settings.height);
|
|
}
|
|
|
|
async function fetchCanvasSettings() {
|
|
try {
|
|
const data = await fetchJson("/canvas", {}, "Failed to load canvas settings");
|
|
renderCanvasSettings(data);
|
|
} catch (error) {
|
|
renderCanvasSettings({ width: 1920, height: 1080 });
|
|
showToast("Using default canvas size. Unable to load saved settings.", "warning");
|
|
}
|
|
}
|
|
|
|
async function saveCanvasSettings() {
|
|
const width = parseFloat(elements.canvasWidth?.value) || 0;
|
|
const height = parseFloat(elements.canvasHeight?.value) || 0;
|
|
if (width <= 0 || height <= 0) {
|
|
showToast("Please enter a valid width and height.", "info");
|
|
return;
|
|
}
|
|
if (elements.canvasStatus) elements.canvasStatus.textContent = "Saving...";
|
|
setButtonBusy(elements.canvasSaveButton, true, "Saving...");
|
|
try {
|
|
const settings = await fetchJson(
|
|
"/canvas",
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ width, height }),
|
|
},
|
|
"Failed to save canvas",
|
|
);
|
|
renderCanvasSettings(settings);
|
|
if (elements.canvasStatus) elements.canvasStatus.textContent = "Saved.";
|
|
showToast("Canvas size saved successfully.", "success");
|
|
setTimeout(() => {
|
|
if (elements.canvasStatus) elements.canvasStatus.textContent = "";
|
|
}, 2000);
|
|
} catch (error) {
|
|
if (elements.canvasStatus) elements.canvasStatus.textContent = "Unable to save right now.";
|
|
showToast("Unable to save canvas size. Please retry.", "error");
|
|
} finally {
|
|
setButtonBusy(elements.canvasSaveButton, false, "Saving...");
|
|
}
|
|
}
|
|
|
|
function renderScriptSettings(settings) {
|
|
if (elements.allowChannelEmotes) {
|
|
elements.allowChannelEmotes.checked = settings.allowChannelEmotesForAssets !== false;
|
|
}
|
|
if (elements.allowSevenTvEmotes) {
|
|
elements.allowSevenTvEmotes.checked = settings.allowSevenTvEmotesForAssets !== false;
|
|
}
|
|
if (elements.allowScriptChat) {
|
|
elements.allowScriptChat.checked = settings.allowScriptChatAccess !== false;
|
|
}
|
|
}
|
|
|
|
async function fetchScriptSettings() {
|
|
try {
|
|
const data = await fetchJson("/settings", {}, "Failed to load script settings");
|
|
renderScriptSettings(data);
|
|
} catch (error) {
|
|
renderScriptSettings({
|
|
allowChannelEmotesForAssets: true,
|
|
allowSevenTvEmotesForAssets: true,
|
|
allowScriptChatAccess: true,
|
|
});
|
|
showToast("Using default script settings. Unable to load saved preferences.", "warning");
|
|
}
|
|
}
|
|
|
|
async function saveScriptSettings() {
|
|
saveCanvasSettings()
|
|
const allowChannelEmotesForAssets = elements.allowChannelEmotes?.checked ?? true;
|
|
const allowSevenTvEmotesForAssets = elements.allowSevenTvEmotes?.checked ?? true;
|
|
const allowScriptChatAccess = elements.allowScriptChat?.checked ?? true;
|
|
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "Saving...";
|
|
setButtonBusy(elements.scriptSettingsSaveButton, true, "Saving...");
|
|
try {
|
|
const settings = await fetchJson(
|
|
"/settings",
|
|
{
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
allowChannelEmotesForAssets,
|
|
allowSevenTvEmotesForAssets,
|
|
allowScriptChatAccess,
|
|
}),
|
|
},
|
|
"Failed to save script settings",
|
|
);
|
|
renderScriptSettings(settings);
|
|
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "Saved.";
|
|
showToast("Script settings saved successfully.", "success");
|
|
setTimeout(() => {
|
|
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "";
|
|
}, 2000);
|
|
} catch (error) {
|
|
if (elements.scriptSettingsStatus) elements.scriptSettingsStatus.textContent = "Unable to save right now.";
|
|
showToast("Unable to save script settings. Please retry.", "error");
|
|
} finally {
|
|
setButtonBusy(elements.scriptSettingsSaveButton, false, "Saving...");
|
|
}
|
|
}
|
|
|
|
async function deleteAccount() {
|
|
const confirmation = window.prompt(
|
|
"Type DELETE to permanently remove your account, assets, and session.",
|
|
);
|
|
if (confirmation !== "DELETE") {
|
|
if (confirmation !== null) {
|
|
showToast("Account deletion cancelled.", "info");
|
|
}
|
|
return;
|
|
}
|
|
|
|
setButtonBusy(elements.deleteAccountButton, true, "Deleting...");
|
|
try {
|
|
const response = await fetch("/api/account", { method: "DELETE" });
|
|
if (!response.ok) {
|
|
throw new Error("Delete account failed");
|
|
}
|
|
showToast("Account deleted. Redirecting...", "success");
|
|
window.location.href = "/";
|
|
} catch (error) {
|
|
showToast("Unable to delete account right now. Please retry.", "error");
|
|
setButtonBusy(elements.deleteAccountButton, false, "Deleting...");
|
|
}
|
|
}
|
|
|
|
if (elements.adminInput) {
|
|
elements.adminInput.addEventListener("keydown", (event) => {
|
|
if (event.key === "Enter") {
|
|
event.preventDefault();
|
|
addAdmin();
|
|
}
|
|
});
|
|
}
|
|
|
|
fetchAdmins();
|
|
fetchSuggestedAdmins();
|
|
fetchCanvasSettings();
|
|
fetchScriptSettings();
|
|
|
|
if (elements.deleteAccountButton) {
|
|
elements.deleteAccountButton.addEventListener("click", deleteAccount);
|
|
}
|