feat(profiles): seed new profiles and refresh tabs on apply

Made-with: Cursor
This commit is contained in:
2026-03-21 23:15:19 +13:00
parent 3ee7b74152
commit 91de705647
3 changed files with 161 additions and 40 deletions

View File

@@ -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:

View File

@@ -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.");

View File

@@ -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);
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,