This improves navigation and profile workflows on smaller screens. Co-authored-by: Cursor <cursoragent@cursor.com>
283 lines
9.3 KiB
JavaScript
283 lines
9.3 KiB
JavaScript
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") {
|
|
// Make sure we're iterating over profile entries, not metadata
|
|
entries = Object.entries(profiles).filter(([key]) => {
|
|
// Skip metadata keys like 'current_profile_id' if they exist
|
|
return key !== 'current_profile_id' && key !== '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 cloneButton = document.createElement("button");
|
|
cloneButton.className = "btn btn-secondary btn-small";
|
|
cloneButton.textContent = "Clone";
|
|
cloneButton.addEventListener("click", async () => {
|
|
const baseName = (profile && profile.name) || profileId;
|
|
const suggested = `${baseName}`;
|
|
const name = prompt("New profile name:", suggested);
|
|
if (name === null) {
|
|
return;
|
|
}
|
|
const trimmed = String(name).trim();
|
|
if (!trimmed) {
|
|
alert("Profile name cannot be empty.");
|
|
return;
|
|
}
|
|
try {
|
|
const response = await fetch(`/profiles/${profileId}/clone`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
body: JSON.stringify({ name: trimmed }),
|
|
});
|
|
if (!response.ok) {
|
|
throw new Error("Failed to clone profile");
|
|
}
|
|
const data = await response.json().catch(() => null);
|
|
let newProfileId = null;
|
|
if (data && typeof data === "object") {
|
|
if (data.id) {
|
|
newProfileId = String(data.id);
|
|
} else {
|
|
const ids = Object.keys(data);
|
|
if (ids.length > 0) {
|
|
newProfileId = String(ids[0]);
|
|
}
|
|
}
|
|
}
|
|
if (newProfileId) {
|
|
await fetch(`/profiles/${newProfileId}/apply`, {
|
|
method: "POST",
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
}
|
|
document.cookie = "current_tab=; path=/; max-age=0";
|
|
await loadProfiles();
|
|
if (typeof window.loadTabs === "function") {
|
|
await window.loadTabs();
|
|
}
|
|
if (typeof window.loadTabsModal === "function") {
|
|
await window.loadTabsModal();
|
|
}
|
|
const tabContent = document.getElementById("tab-content");
|
|
if (tabContent) {
|
|
tabContent.innerHTML = `
|
|
<div class="tab-content-placeholder">
|
|
Select a tab to get started
|
|
</div>
|
|
`;
|
|
}
|
|
} catch (error) {
|
|
console.error("Clone profile failed:", error);
|
|
alert("Failed to clone 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(cloneButton);
|
|
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 data = await response.json();
|
|
// Handle both old format (just profiles object) and new format (with current_profile_id)
|
|
const profiles = data.profiles || data;
|
|
const currentProfileId = data.current_profile_id || null;
|
|
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");
|
|
}
|
|
const data = await response.json().catch(() => null);
|
|
let newProfileId = null;
|
|
if (data && typeof data === "object") {
|
|
if (data.id) {
|
|
newProfileId = String(data.id);
|
|
} else {
|
|
const ids = Object.keys(data);
|
|
if (ids.length > 0) {
|
|
newProfileId = String(ids[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (newProfileId) {
|
|
await fetch(`/profiles/${newProfileId}/apply`, {
|
|
method: "POST",
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
}
|
|
|
|
newProfileInput.value = "";
|
|
// Clear current tab and refresh the UI so the new profile starts empty.
|
|
document.cookie = "current_tab=; path=/; max-age=0";
|
|
await loadProfiles();
|
|
if (typeof window.loadTabs === "function") {
|
|
await window.loadTabs();
|
|
}
|
|
if (typeof window.loadTabsModal === "function") {
|
|
await window.loadTabsModal();
|
|
}
|
|
const tabContent = document.getElementById("tab-content");
|
|
if (tabContent) {
|
|
tabContent.innerHTML = `
|
|
<div class="tab-content-placeholder">
|
|
Select a tab to get started
|
|
</div>
|
|
`;
|
|
}
|
|
} 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();
|
|
}
|
|
});
|
|
});
|