diff --git a/src/controllers/profile.py b/src/controllers/profile.py index 7fcbf9c..9bacb87 100644 --- a/src/controllers/profile.py +++ b/src/controllers/profile.py @@ -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: diff --git a/src/static/profiles.js b/src/static/profiles.js index 6f2a61d..fba2c77 100644 --- a/src/static/profiles.js +++ b/src/static/profiles.js @@ -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 = ` -
- Select a tab to get started -
- `; - } + 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 = ` -
- Select a tab to get started -
- `; - } + await refreshTabsForActiveProfile(); } catch (error) { console.error("Create profile failed:", error); alert("Failed to create profile."); diff --git a/src/static/tabs.js b/src/static/tabs.js index 1e3b227..c0dd4f6 100644 --- a/src/static/tabs.js +++ b/src/static/tabs.js @@ -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 = '
'; for (const tabId of tabOrder) { const tab = tabs[tabId]; @@ -71,7 +79,7 @@ function renderTabsList(tabs, tabOrder, currentTabId) { html += ` @@ -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,