Update UI for palettes, presets, and patterns

This commit is contained in:
2026-01-16 22:31:36 +13:00
parent 9c43a0a22b
commit df37f15f73
8 changed files with 3649 additions and 11 deletions

186
src/static/profiles.js Normal file
View File

@@ -0,0 +1,186 @@
document.addEventListener("DOMContentLoaded", () => {
const profilesButton = document.getElementById("profiles-btn");
const profilesModal = document.getElementById("profiles-modal");
const profilesCloseButton = document.getElementById("profiles-close-btn");
const profilesList = document.getElementById("profiles-list");
const newProfileInput = document.getElementById("new-profile-name");
const createProfileButton = document.getElementById("create-profile-btn");
if (!profilesButton || !profilesModal || !profilesList) {
return;
}
const openModal = () => {
profilesModal.classList.add("active");
loadProfiles();
};
const closeModal = () => {
profilesModal.classList.remove("active");
};
const renderProfiles = (profiles, currentProfileId) => {
profilesList.innerHTML = "";
let entries = [];
if (Array.isArray(profiles)) {
entries = profiles.map((profileId) => [profileId, {}]);
} else if (profiles && typeof profiles === "object") {
entries = Object.entries(profiles);
}
if (entries.length === 0) {
const empty = document.createElement("p");
empty.className = "muted-text";
empty.textContent = "No profiles found.";
profilesList.appendChild(empty);
return;
}
entries.forEach(([profileId, profile]) => {
const row = document.createElement("div");
row.className = "profiles-row";
const label = document.createElement("span");
label.textContent = (profile && profile.name) || profileId;
if (String(profileId) === String(currentProfileId)) {
label.textContent = `${label.textContent}`;
label.style.fontWeight = "bold";
label.style.color = "#FFD700";
}
const applyButton = document.createElement("button");
applyButton.className = "btn btn-secondary btn-small profiles-apply-btn";
applyButton.textContent = "Apply";
applyButton.addEventListener("click", async () => {
try {
const response = await fetch(`/profiles/${profileId}/apply`, {
method: "POST",
headers: { Accept: "application/json" },
});
if (!response.ok) {
throw new Error("Failed to apply profile");
}
await loadProfiles();
document.body.dispatchEvent(new Event("tabs-updated"));
} catch (error) {
console.error("Apply profile failed:", error);
alert("Failed to apply profile.");
}
});
const deleteButton = document.createElement("button");
deleteButton.className = "btn btn-danger btn-small";
deleteButton.textContent = "Delete";
deleteButton.addEventListener("click", async () => {
const confirmed = confirm(`Delete profile "${label.textContent}"?`);
if (!confirmed) {
return;
}
try {
const response = await fetch(`/profiles/${profileId}`, {
method: "DELETE",
headers: { Accept: "application/json" },
});
if (!response.ok) {
throw new Error("Failed to delete profile");
}
await loadProfiles();
} catch (error) {
console.error("Delete profile failed:", error);
alert("Failed to delete profile.");
}
});
row.appendChild(label);
row.appendChild(applyButton);
row.appendChild(deleteButton);
profilesList.appendChild(row);
});
};
const loadProfiles = async () => {
profilesList.innerHTML = "";
const loading = document.createElement("p");
loading.className = "muted-text";
loading.textContent = "Loading profiles...";
profilesList.appendChild(loading);
try {
const response = await fetch("/profiles", {
headers: { Accept: "application/json" },
});
if (!response.ok) {
throw new Error("Failed to load profiles");
}
const profiles = await response.json();
let currentProfileId = null;
try {
const currentResponse = await fetch("/profiles/current", {
headers: { Accept: "application/json" },
});
if (currentResponse.ok) {
const currentData = await currentResponse.json();
currentProfileId = currentData.id || null;
}
} catch (error) {
console.warn("Failed to load current profile:", error);
}
renderProfiles(profiles, currentProfileId);
} catch (error) {
console.error("Load profiles failed:", error);
profilesList.innerHTML = "";
const errorMessage = document.createElement("p");
errorMessage.className = "muted-text";
errorMessage.textContent = "Failed to load profiles.";
profilesList.appendChild(errorMessage);
}
};
const createProfile = async () => {
if (!newProfileInput) {
return;
}
const name = newProfileInput.value.trim();
if (!name) {
alert("Profile name cannot be empty.");
return;
}
try {
const response = await fetch("/profiles", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
});
if (!response.ok) {
throw new Error("Failed to create profile");
}
newProfileInput.value = "";
await loadProfiles();
} catch (error) {
console.error("Create profile failed:", error);
alert("Failed to create profile.");
}
};
profilesButton.addEventListener("click", openModal);
if (profilesCloseButton) {
profilesCloseButton.addEventListener("click", closeModal);
}
if (createProfileButton) {
createProfileButton.addEventListener("click", createProfile);
}
if (newProfileInput) {
newProfileInput.addEventListener("keypress", (event) => {
if (event.key === "Enter") {
createProfile();
}
});
}
profilesModal.addEventListener("click", (event) => {
if (event.target === profilesModal) {
closeModal();
}
});
});