Refactor UI to use JavaScript instead of HTMX
- Replace HTMX with plain JavaScript for tab management - Consolidate tab UI into single button like profiles - Add cookie-based current tab storage (client-side) - Update profiles.js to work with new JSON response format
This commit is contained in:
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
env/
|
||||
ENV/
|
||||
.venv
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Project specific
|
||||
*.log
|
||||
*.db
|
||||
*.sqlite
|
||||
|
||||
@@ -26,7 +26,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (Array.isArray(profiles)) {
|
||||
entries = profiles.map((profileId) => [profileId, {}]);
|
||||
} else if (profiles && typeof profiles === "object") {
|
||||
entries = Object.entries(profiles);
|
||||
// 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) {
|
||||
@@ -113,19 +117,10 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
||||
471
src/static/tabs.js
Normal file
471
src/static/tabs.js
Normal file
@@ -0,0 +1,471 @@
|
||||
// Tab management JavaScript
|
||||
let currentTabId = null;
|
||||
|
||||
// Get current tab from cookie
|
||||
function getCurrentTabFromCookie() {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let cookie of cookies) {
|
||||
const [name, value] = cookie.trim().split('=');
|
||||
if (name === 'current_tab') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load tabs list
|
||||
async function loadTabs() {
|
||||
try {
|
||||
const response = await fetch('/tabs');
|
||||
const data = await response.json();
|
||||
|
||||
// Get current tab from cookie first, then from server response
|
||||
currentTabId = getCurrentTabFromCookie() || data.current_tab_id;
|
||||
renderTabsList(data.tabs, data.tab_order, currentTabId);
|
||||
|
||||
// Load current tab content if available
|
||||
if (currentTabId) {
|
||||
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]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tabs:', error);
|
||||
const container = document.getElementById('tabs-list');
|
||||
if (container) {
|
||||
container.innerHTML = '<div class="error">Failed to load tabs</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render tabs list in the main UI
|
||||
function renderTabsList(tabs, tabOrder, currentTabId) {
|
||||
const container = document.getElementById('tabs-list');
|
||||
if (!container) return;
|
||||
|
||||
if (!tabOrder || tabOrder.length === 0) {
|
||||
container.innerHTML = '<div class="muted-text">No tabs available</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
let html = '<div class="tabs-list">';
|
||||
for (const tabId of tabOrder) {
|
||||
const tab = tabs[tabId];
|
||||
if (tab) {
|
||||
const activeClass = tabId === currentTabId ? 'active' : '';
|
||||
const tabName = tab.name || `Tab ${tabId}`;
|
||||
html += `
|
||||
<button class="tab-button ${activeClass}"
|
||||
data-tab-id="${tabId}"
|
||||
onclick="selectTab('${tabId}')">
|
||||
${tabName}
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
}
|
||||
html += '</div>';
|
||||
container.innerHTML = html;
|
||||
}
|
||||
|
||||
// Render tabs list in modal (like profiles)
|
||||
function renderTabsListModal(tabs, tabOrder, currentTabId) {
|
||||
const container = document.getElementById('tabs-list-modal');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = "";
|
||||
let entries = [];
|
||||
|
||||
if (Array.isArray(tabOrder)) {
|
||||
entries = tabOrder.map((tabId) => [tabId, tabs[tabId] || {}]);
|
||||
} else if (tabs && typeof tabs === "object") {
|
||||
entries = Object.entries(tabs).filter(([key]) => {
|
||||
return key !== 'current_tab_id' && key !== 'tabs' && key !== 'tab_order';
|
||||
});
|
||||
}
|
||||
|
||||
if (entries.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "muted-text";
|
||||
empty.textContent = "No tabs found.";
|
||||
container.appendChild(empty);
|
||||
return;
|
||||
}
|
||||
|
||||
entries.forEach(([tabId, tab]) => {
|
||||
const row = document.createElement("div");
|
||||
row.className = "profiles-row";
|
||||
|
||||
const label = document.createElement("span");
|
||||
label.textContent = (tab && tab.name) || tabId;
|
||||
if (String(tabId) === String(currentTabId)) {
|
||||
label.textContent = `✓ ${label.textContent}`;
|
||||
label.style.fontWeight = "bold";
|
||||
label.style.color = "#FFD700";
|
||||
}
|
||||
|
||||
const applyButton = document.createElement("button");
|
||||
applyButton.className = "btn btn-secondary btn-small";
|
||||
applyButton.textContent = "Select";
|
||||
applyButton.addEventListener("click", async () => {
|
||||
await selectTab(tabId);
|
||||
document.getElementById('tabs-modal').classList.remove('active');
|
||||
});
|
||||
|
||||
const editButton = document.createElement("button");
|
||||
editButton.className = "btn btn-secondary btn-small";
|
||||
editButton.textContent = "Edit";
|
||||
editButton.addEventListener("click", () => {
|
||||
openEditTabModal(tabId, tab);
|
||||
});
|
||||
|
||||
const deleteButton = document.createElement("button");
|
||||
deleteButton.className = "btn btn-danger btn-small";
|
||||
deleteButton.textContent = "Delete";
|
||||
deleteButton.addEventListener("click", async () => {
|
||||
const confirmed = confirm(`Delete tab "${label.textContent}"?`);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/tabs/${tabId}`, {
|
||||
method: "DELETE",
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({ error: "Failed to delete tab" }));
|
||||
throw new Error(errorData.error || "Failed to delete tab");
|
||||
}
|
||||
// Clear cookie if deleted tab was current
|
||||
if (tabId === currentTabId) {
|
||||
document.cookie = 'current_tab=; path=/; max-age=0';
|
||||
currentTabId = null;
|
||||
}
|
||||
await loadTabsModal();
|
||||
await loadTabs(); // Reload main tabs list
|
||||
} catch (error) {
|
||||
console.error("Delete tab failed:", error);
|
||||
alert("Failed to delete tab: " + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
row.appendChild(label);
|
||||
row.appendChild(applyButton);
|
||||
row.appendChild(editButton);
|
||||
row.appendChild(deleteButton);
|
||||
container.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Load tabs in modal
|
||||
async function loadTabsModal() {
|
||||
const container = document.getElementById('tabs-list-modal');
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = "";
|
||||
const loading = document.createElement("p");
|
||||
loading.className = "muted-text";
|
||||
loading.textContent = "Loading tabs...";
|
||||
container.appendChild(loading);
|
||||
|
||||
try {
|
||||
const response = await fetch("/tabs", {
|
||||
headers: { Accept: "application/json" },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to load tabs");
|
||||
}
|
||||
const data = await response.json();
|
||||
const tabs = data.tabs || data;
|
||||
const currentTabId = getCurrentTabFromCookie() || data.current_tab_id || null;
|
||||
renderTabsListModal(tabs, data.tab_order || [], currentTabId);
|
||||
} catch (error) {
|
||||
console.error("Load tabs failed:", error);
|
||||
container.innerHTML = "";
|
||||
const errorMessage = document.createElement("p");
|
||||
errorMessage.className = "muted-text";
|
||||
errorMessage.textContent = "Failed to load tabs.";
|
||||
container.appendChild(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// Select a tab
|
||||
async function selectTab(tabId) {
|
||||
// Update active state
|
||||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
const btn = document.querySelector(`[data-tab-id="${tabId}"]`);
|
||||
if (btn) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
|
||||
// Set as current tab
|
||||
await setCurrentTab(tabId);
|
||||
// Load tab content
|
||||
loadTabContent(tabId);
|
||||
}
|
||||
|
||||
// Set current tab in cookie
|
||||
async function setCurrentTab(tabId) {
|
||||
try {
|
||||
const response = await fetch(`/tabs/${tabId}/set-current`, {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
currentTabId = tabId;
|
||||
// Also set cookie on client side
|
||||
document.cookie = `current_tab=${tabId}; path=/; max-age=31536000`;
|
||||
} else {
|
||||
console.error('Failed to set current tab:', data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error setting current tab:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Load tab content
|
||||
async function loadTabContent(tabId) {
|
||||
const container = document.getElementById('tab-content');
|
||||
if (!container) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/tabs/${tabId}`);
|
||||
const tab = await response.json();
|
||||
|
||||
if (tab.error) {
|
||||
container.innerHTML = `<div class="error">${tab.error}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Render tab content (presets section)
|
||||
const tabName = tab.name || `Tab ${tabId}`;
|
||||
container.innerHTML = `
|
||||
<div class="presets-section" data-tab-id="${tabId}">
|
||||
<h3>Presets</h3>
|
||||
<div class="profiles-actions" style="margin-bottom: 1rem;">
|
||||
<button class="btn btn-primary" id="preset-add-btn-tab">Add Preset</button>
|
||||
</div>
|
||||
<div id="presets-list-tab" class="presets-list">
|
||||
<!-- Presets will be loaded here by presets.js -->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Trigger presets loading if the function exists
|
||||
if (typeof renderTabPresets === 'function') {
|
||||
renderTabPresets(tabId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tab content:', error);
|
||||
container.innerHTML = '<div class="error">Failed to load tab content</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// Open edit tab modal
|
||||
function openEditTabModal(tabId, tab) {
|
||||
const modal = document.getElementById('edit-tab-modal');
|
||||
const idInput = document.getElementById('edit-tab-id');
|
||||
const nameInput = document.getElementById('edit-tab-name');
|
||||
const idsInput = document.getElementById('edit-tab-ids');
|
||||
|
||||
if (idInput) idInput.value = tabId;
|
||||
if (nameInput) nameInput.value = tab ? (tab.name || '') : '';
|
||||
if (idsInput) idsInput.value = tab && tab.names ? tab.names.join(', ') : '1';
|
||||
|
||||
if (modal) modal.classList.add('active');
|
||||
}
|
||||
|
||||
// Update an existing tab
|
||||
async function updateTab(tabId, name, ids) {
|
||||
try {
|
||||
const names = ids ? ids.split(',').map(id => id.trim()) : ['1'];
|
||||
const response = await fetch(`/tabs/${tabId}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
names: names
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
// Reload tabs list
|
||||
await loadTabsModal();
|
||||
await loadTabs();
|
||||
// Close modal
|
||||
document.getElementById('edit-tab-modal').classList.remove('active');
|
||||
return true;
|
||||
} else {
|
||||
alert(`Error: ${data.error || 'Failed to update tab'}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update tab:', error);
|
||||
alert('Failed to update tab');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new tab
|
||||
async function createTab(name, ids) {
|
||||
try {
|
||||
const names = ids ? ids.split(',').map(id => id.trim()) : ['1'];
|
||||
const response = await fetch('/tabs', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
names: names
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (response.ok) {
|
||||
// Reload tabs list
|
||||
await loadTabsModal();
|
||||
await loadTabs();
|
||||
// Select the new tab
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
const newTabId = Object.keys(data)[0];
|
||||
await selectTab(newTabId);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
alert(`Error: ${data.error || 'Failed to create tab'}`);
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create tab:', error);
|
||||
alert('Failed to create tab');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadTabs();
|
||||
|
||||
// Set up tabs modal
|
||||
const tabsButton = document.getElementById('tabs-btn');
|
||||
const tabsModal = document.getElementById('tabs-modal');
|
||||
const tabsCloseButton = document.getElementById('tabs-close-btn');
|
||||
const newTabNameInput = document.getElementById('new-tab-name');
|
||||
const newTabIdsInput = document.getElementById('new-tab-ids');
|
||||
const createTabButton = document.getElementById('create-tab-btn');
|
||||
|
||||
// Set up edit tab button in header
|
||||
const editTabBtn = document.getElementById('edit-tab-btn');
|
||||
if (editTabBtn) {
|
||||
editTabBtn.addEventListener('click', async () => {
|
||||
if (!currentTabId) {
|
||||
alert('No tab selected. Please select a tab first.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch(`/tabs/${currentTabId}`);
|
||||
if (response.ok) {
|
||||
const tab = await response.json();
|
||||
openEditTabModal(currentTabId, tab);
|
||||
} else {
|
||||
alert('Failed to load tab for editing');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tab:', error);
|
||||
alert('Failed to load tab for editing');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (tabsButton && tabsModal) {
|
||||
tabsButton.addEventListener('click', () => {
|
||||
tabsModal.classList.add('active');
|
||||
loadTabsModal();
|
||||
});
|
||||
}
|
||||
|
||||
if (tabsCloseButton) {
|
||||
tabsCloseButton.addEventListener('click', () => {
|
||||
tabsModal.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
if (tabsModal) {
|
||||
tabsModal.addEventListener('click', (event) => {
|
||||
if (event.target === tabsModal) {
|
||||
tabsModal.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set up create tab
|
||||
const createTabHandler = async () => {
|
||||
if (!newTabNameInput) return;
|
||||
const name = newTabNameInput.value.trim();
|
||||
const ids = (newTabIdsInput && newTabIdsInput.value.trim()) || '1';
|
||||
|
||||
if (name) {
|
||||
await createTab(name, ids);
|
||||
if (newTabNameInput) newTabNameInput.value = '';
|
||||
if (newTabIdsInput) newTabIdsInput.value = '1';
|
||||
}
|
||||
};
|
||||
|
||||
if (createTabButton) {
|
||||
createTabButton.addEventListener('click', createTabHandler);
|
||||
}
|
||||
|
||||
if (newTabNameInput) {
|
||||
newTabNameInput.addEventListener('keypress', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
createTabHandler();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set up edit tab form
|
||||
const editTabForm = document.getElementById('edit-tab-form');
|
||||
if (editTabForm) {
|
||||
editTabForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const idInput = document.getElementById('edit-tab-id');
|
||||
const nameInput = document.getElementById('edit-tab-name');
|
||||
const idsInput = document.getElementById('edit-tab-ids');
|
||||
|
||||
const tabId = idInput ? idInput.value : null;
|
||||
const name = nameInput ? nameInput.value.trim() : '';
|
||||
const ids = idsInput ? idsInput.value.trim() : '1';
|
||||
|
||||
if (tabId && name) {
|
||||
await updateTab(tabId, name, ids);
|
||||
editTabForm.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Close edit modal when clicking outside
|
||||
const editTabModal = document.getElementById('edit-tab-modal');
|
||||
if (editTabModal) {
|
||||
editTabModal.addEventListener('click', (event) => {
|
||||
if (event.target === editTabModal) {
|
||||
editTabModal.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Export for use in other scripts
|
||||
window.tabsManager = {
|
||||
loadTabs,
|
||||
selectTab,
|
||||
createTab,
|
||||
updateTab,
|
||||
openEditTabModal,
|
||||
getCurrentTabId: () => currentTabId
|
||||
};
|
||||
@@ -5,29 +5,13 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LED Controller - Tab Mode</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<script src="/static/htmx.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container">
|
||||
<header>
|
||||
<h1>LED Controller - Tab Mode</h1>
|
||||
<div class="header-actions">
|
||||
<button class="btn btn-primary"
|
||||
hx-get="/tabs/create-form-fragment"
|
||||
hx-target="#add-tab-modal .modal-content"
|
||||
hx-swap="innerHTML"
|
||||
onclick="document.getElementById('add-tab-modal').classList.add('active')">
|
||||
+ Add Tab
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="edit-tab-btn">Edit Tab</button>
|
||||
<button class="btn btn-danger"
|
||||
hx-delete="/tabs/current"
|
||||
hx-target="#tabs-list"
|
||||
hx-swap="innerHTML"
|
||||
hx-headers='{"Accept": "text/html"}'
|
||||
hx-confirm="Are you sure you want to delete this tab?">
|
||||
Delete Tab
|
||||
</button>
|
||||
<button class="btn btn-secondary" id="tabs-btn">Tabs</button>
|
||||
<button class="btn btn-secondary" id="color-palette-btn">Color Palette</button>
|
||||
<button class="btn btn-secondary" id="presets-btn">Presets</button>
|
||||
<button class="btn btn-secondary" id="patterns-btn">Patterns</button>
|
||||
@@ -37,20 +21,12 @@
|
||||
|
||||
<div class="main-content">
|
||||
<div class="tabs-container">
|
||||
<div id="tabs-list"
|
||||
hx-get="/tabs/list-fragment"
|
||||
hx-trigger="load, tabs-updated from:body"
|
||||
hx-swap="innerHTML">
|
||||
<div id="tabs-list">
|
||||
Loading tabs...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-content"
|
||||
class="tab-content"
|
||||
hx-get="/tabs/current"
|
||||
hx-trigger="load, tabs-updated from:body"
|
||||
hx-swap="innerHTML"
|
||||
hx-headers='{"Accept": "text/html"}'>
|
||||
<div id="tab-content" class="tab-content">
|
||||
<div style="padding: 2rem; text-align: center; color: #aaa;">
|
||||
Select a tab to get started
|
||||
</div>
|
||||
@@ -58,35 +34,37 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Tab Modal -->
|
||||
<div id="add-tab-modal" class="modal">
|
||||
<!-- Tabs Modal -->
|
||||
<div id="tabs-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Add New Tab</h2>
|
||||
<form hx-post="/tabs"
|
||||
hx-target="#tabs-list"
|
||||
hx-swap="innerHTML"
|
||||
hx-headers='{"Accept": "text/html"}'
|
||||
hx-on::after-request="if(event.detail.successful) { document.getElementById('add-tab-modal').classList.remove('active'); document.body.dispatchEvent(new Event('tabs-updated')); }">
|
||||
<label>Tab Name:</label>
|
||||
<input type="text" name="name" placeholder="Enter tab name" required>
|
||||
<label>Device IDs (comma-separated):</label>
|
||||
<input type="text" name="ids" placeholder="1,2,3" value="1">
|
||||
<div class="modal-actions">
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('add-tab-modal').classList.remove('active')">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
<h2>Tabs</h2>
|
||||
<div class="profiles-actions">
|
||||
<input type="text" id="new-tab-name" placeholder="Tab name">
|
||||
<input type="text" id="new-tab-ids" placeholder="Device IDs (1,2,3)" value="1">
|
||||
<button class="btn btn-primary" id="create-tab-btn">Create</button>
|
||||
</div>
|
||||
<div id="tabs-list-modal" class="profiles-list"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" id="tabs-close-btn">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Tab Modal (placeholder for now) -->
|
||||
<!-- Edit Tab Modal -->
|
||||
<div id="edit-tab-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<h2>Edit Tab</h2>
|
||||
<p>Edit functionality coming soon...</p>
|
||||
<div class="modal-actions">
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('edit-tab-modal').classList.remove('active')">Close</button>
|
||||
</div>
|
||||
<form id="edit-tab-form">
|
||||
<input type="hidden" id="edit-tab-id">
|
||||
<label>Tab Name:</label>
|
||||
<input type="text" id="edit-tab-name" placeholder="Enter tab name" required>
|
||||
<label>Device IDs (comma-separated):</label>
|
||||
<input type="text" id="edit-tab-ids" placeholder="1,2,3" required>
|
||||
<div class="modal-actions">
|
||||
<button type="submit" class="btn btn-primary">Save</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="document.getElementById('edit-tab-modal').classList.remove('active')">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -348,6 +326,7 @@
|
||||
gap: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
<script src="/static/tabs.js"></script>
|
||||
<script src="/static/color_palette.js"></script>
|
||||
<script src="/static/profiles.js"></script>
|
||||
<script src="/static/tab_palette.js"></script>
|
||||
|
||||
Reference in New Issue
Block a user