feat(profiles): seed new profiles and refresh tabs on apply
Made-with: Cursor
This commit is contained in:
@@ -81,11 +81,117 @@ async def apply_profile(request, session, id):
|
||||
async def create_profile(request):
|
||||
"""Create a new profile."""
|
||||
try:
|
||||
data = request.json or {}
|
||||
data = dict(request.json or {})
|
||||
name = data.get("name", "")
|
||||
seed_raw = data.get("seed_dj_tab", False)
|
||||
if isinstance(seed_raw, str):
|
||||
seed_dj_tab = seed_raw.strip().lower() in ("1", "true", "yes", "on")
|
||||
else:
|
||||
seed_dj_tab = bool(seed_raw)
|
||||
# Request-only flag: do not persist on profile records.
|
||||
data.pop("seed_dj_tab", None)
|
||||
profile_id = profiles.create(name)
|
||||
# Avoid persisting request-only fields.
|
||||
data.pop("name", None)
|
||||
if data:
|
||||
profiles.update(profile_id, data)
|
||||
|
||||
# New profiles always start with a default tab pre-populated with starter presets.
|
||||
default_preset_ids = []
|
||||
default_preset_defs = [
|
||||
{
|
||||
"name": "on",
|
||||
"pattern": "on",
|
||||
"colors": ["#FFFFFF"],
|
||||
"brightness": 255,
|
||||
"delay": 100,
|
||||
"auto": True,
|
||||
},
|
||||
{
|
||||
"name": "off",
|
||||
"pattern": "off",
|
||||
"colors": [],
|
||||
"brightness": 0,
|
||||
"delay": 100,
|
||||
"auto": True,
|
||||
},
|
||||
{
|
||||
"name": "rainbow",
|
||||
"pattern": "rainbow",
|
||||
"colors": [],
|
||||
"brightness": 255,
|
||||
"delay": 100,
|
||||
"auto": True,
|
||||
"n1": 2,
|
||||
},
|
||||
{
|
||||
"name": "transition",
|
||||
"pattern": "transition",
|
||||
"colors": ["#FF0000", "#00FF00", "#0000FF"],
|
||||
"brightness": 255,
|
||||
"delay": 500,
|
||||
"auto": True,
|
||||
},
|
||||
]
|
||||
|
||||
for preset_data in default_preset_defs:
|
||||
pid = presets.create(profile_id)
|
||||
presets.update(pid, preset_data)
|
||||
default_preset_ids.append(str(pid))
|
||||
|
||||
default_tab_id = tabs.create(name="default", names=["1"], presets=[default_preset_ids])
|
||||
tabs.update(default_tab_id, {
|
||||
"presets_flat": default_preset_ids,
|
||||
"default_preset": default_preset_ids[0] if default_preset_ids else None,
|
||||
})
|
||||
|
||||
profile = profiles.read(profile_id) or {}
|
||||
profile_tabs = profile.get("tabs", []) if isinstance(profile.get("tabs", []), list) else []
|
||||
profile_tabs.append(str(default_tab_id))
|
||||
|
||||
if seed_dj_tab:
|
||||
# Seed a DJ-focused tab with three starter presets.
|
||||
seeded_preset_ids = []
|
||||
preset_defs = [
|
||||
{
|
||||
"name": "DJ Rainbow",
|
||||
"pattern": "rainbow",
|
||||
"colors": [],
|
||||
"brightness": 220,
|
||||
"delay": 60,
|
||||
"n1": 12,
|
||||
},
|
||||
{
|
||||
"name": "DJ Single Color",
|
||||
"pattern": "on",
|
||||
"colors": ["#ff00ff"],
|
||||
"brightness": 220,
|
||||
"delay": 100,
|
||||
},
|
||||
{
|
||||
"name": "DJ Transition",
|
||||
"pattern": "transition",
|
||||
"colors": ["#ff0000", "#00ff00", "#0000ff"],
|
||||
"brightness": 220,
|
||||
"delay": 250,
|
||||
},
|
||||
]
|
||||
|
||||
for preset_data in preset_defs:
|
||||
pid = presets.create(profile_id)
|
||||
presets.update(pid, preset_data)
|
||||
seeded_preset_ids.append(str(pid))
|
||||
|
||||
dj_tab_id = tabs.create(name="dj", names=["dj"], presets=[seeded_preset_ids])
|
||||
tabs.update(dj_tab_id, {
|
||||
"presets_flat": seeded_preset_ids,
|
||||
"default_preset": seeded_preset_ids[0] if seeded_preset_ids else None,
|
||||
})
|
||||
|
||||
profile_tabs.append(str(dj_tab_id))
|
||||
|
||||
profiles.update(profile_id, {"tabs": profile_tabs})
|
||||
|
||||
profile_data = profiles.read(profile_id)
|
||||
return json.dumps({profile_id: profile_data}), 201, {'Content-Type': 'application/json'}
|
||||
except Exception as e:
|
||||
|
||||
@@ -4,6 +4,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const profilesCloseButton = document.getElementById("profiles-close-btn");
|
||||
const profilesList = document.getElementById("profiles-list");
|
||||
const newProfileInput = document.getElementById("new-profile-name");
|
||||
const newProfileSeedDjInput = document.getElementById("new-profile-seed-dj");
|
||||
const createProfileButton = document.getElementById("create-profile-btn");
|
||||
|
||||
if (!profilesButton || !profilesModal || !profilesList) {
|
||||
@@ -19,6 +20,18 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
profilesModal.classList.remove("active");
|
||||
};
|
||||
|
||||
const refreshTabsForActiveProfile = async () => {
|
||||
// Clear stale current tab so tab controller falls back to first tab of applied profile.
|
||||
document.cookie = "current_tab=; path=/; max-age=0";
|
||||
|
||||
if (window.tabsManager && typeof window.tabsManager.loadTabs === "function") {
|
||||
await window.tabsManager.loadTabs();
|
||||
}
|
||||
if (window.tabsManager && typeof window.tabsManager.loadTabsModal === "function") {
|
||||
await window.tabsManager.loadTabsModal();
|
||||
}
|
||||
};
|
||||
|
||||
const renderProfiles = (profiles, currentProfileId) => {
|
||||
profilesList.innerHTML = "";
|
||||
let entries = [];
|
||||
@@ -66,7 +79,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
throw new Error("Failed to apply profile");
|
||||
}
|
||||
await loadProfiles();
|
||||
document.body.dispatchEvent(new Event("tabs-updated"));
|
||||
await refreshTabsForActiveProfile();
|
||||
} catch (error) {
|
||||
console.error("Apply profile failed:", error);
|
||||
alert("Failed to apply profile.");
|
||||
@@ -115,22 +128,8 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
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>
|
||||
`;
|
||||
}
|
||||
await refreshTabsForActiveProfile();
|
||||
} catch (error) {
|
||||
console.error("Clone profile failed:", error);
|
||||
alert("Failed to clone profile.");
|
||||
@@ -210,7 +209,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
const response = await fetch("/profiles", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ name }),
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
seed_dj_tab: !!(newProfileSeedDjInput && newProfileSeedDjInput.checked),
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to create profile");
|
||||
@@ -236,23 +238,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
}
|
||||
|
||||
newProfileInput.value = "";
|
||||
// Clear current tab and refresh the UI so the new profile starts empty.
|
||||
document.cookie = "current_tab=; path=/; max-age=0";
|
||||
if (newProfileSeedDjInput) {
|
||||
newProfileSeedDjInput.checked = false;
|
||||
}
|
||||
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>
|
||||
`;
|
||||
}
|
||||
await refreshTabsForActiveProfile();
|
||||
} catch (error) {
|
||||
console.error("Create profile failed:", error);
|
||||
alert("Failed to create profile.");
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
// Tab management JavaScript
|
||||
let currentTabId = null;
|
||||
|
||||
const isEditModeActive = () => {
|
||||
const toggle = document.querySelector('.ui-mode-toggle');
|
||||
return !!(toggle && toggle.getAttribute('aria-pressed') === 'true');
|
||||
};
|
||||
|
||||
// Get current tab from cookie
|
||||
function getCurrentTabFromCookie() {
|
||||
const cookies = document.cookie.split(';');
|
||||
@@ -38,10 +43,12 @@ async function loadTabs() {
|
||||
|
||||
// Load current tab content if available
|
||||
if (currentTabId) {
|
||||
loadTabContent(currentTabId);
|
||||
await loadTabContent(currentTabId);
|
||||
} else if (data.tab_order && data.tab_order.length > 0) {
|
||||
// Set first tab as current if none is set
|
||||
await setCurrentTab(data.tab_order[0]);
|
||||
const firstTabId = data.tab_order[0];
|
||||
await setCurrentTab(firstTabId);
|
||||
await loadTabContent(firstTabId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tabs:', error);
|
||||
@@ -62,6 +69,7 @@ function renderTabsList(tabs, tabOrder, currentTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editMode = isEditModeActive();
|
||||
let html = '<div class="tabs-list">';
|
||||
for (const tabId of tabOrder) {
|
||||
const tab = tabs[tabId];
|
||||
@@ -71,7 +79,7 @@ function renderTabsList(tabs, tabOrder, currentTabId) {
|
||||
html += `
|
||||
<button class="tab-button ${activeClass}"
|
||||
data-tab-id="${tabId}"
|
||||
title="Click to select, right-click to edit"
|
||||
title="${editMode ? 'Click to select, right-click to edit' : 'Click to select'}"
|
||||
onclick="selectTab('${tabId}')">
|
||||
${tabName}
|
||||
</button>
|
||||
@@ -106,6 +114,7 @@ function renderTabsListModal(tabs, tabOrder, currentTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const editMode = isEditModeActive();
|
||||
entries.forEach(([tabId, tab]) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "profiles-row";
|
||||
@@ -224,10 +233,12 @@ function renderTabsListModal(tabs, tabOrder, currentTabId) {
|
||||
|
||||
row.appendChild(label);
|
||||
row.appendChild(applyButton);
|
||||
row.appendChild(editButton);
|
||||
row.appendChild(sendPresetsButton);
|
||||
row.appendChild(cloneButton);
|
||||
row.appendChild(deleteButton);
|
||||
if (editMode) {
|
||||
row.appendChild(editButton);
|
||||
row.appendChild(cloneButton);
|
||||
row.appendChild(deleteButton);
|
||||
}
|
||||
container.appendChild(row);
|
||||
});
|
||||
}
|
||||
@@ -714,6 +725,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// Right-click on a tab button in the main header bar to edit that tab
|
||||
document.addEventListener('contextmenu', async (event) => {
|
||||
if (!isEditModeActive()) {
|
||||
return;
|
||||
}
|
||||
const btn = event.target.closest('.tab-button');
|
||||
if (!btn || !btn.dataset.tabId) {
|
||||
return;
|
||||
@@ -796,11 +810,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
await sendProfilePresets();
|
||||
});
|
||||
}
|
||||
|
||||
// When run/edit mode toggles, refresh tabs UI so edit actions show/hide immediately.
|
||||
document.querySelectorAll('.ui-mode-toggle').forEach((btn) => {
|
||||
btn.addEventListener('click', async () => {
|
||||
await loadTabs();
|
||||
if (tabsModal && tabsModal.classList.contains('active')) {
|
||||
await loadTabsModal();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Export for use in other scripts
|
||||
window.tabsManager = {
|
||||
loadTabs,
|
||||
loadTabsModal,
|
||||
selectTab,
|
||||
createTab,
|
||||
updateTab,
|
||||
|
||||
Reference in New Issue
Block a user