diff --git a/src/options/body.css b/src/options/body.css index 0b5c8db..b4bab7f 100644 --- a/src/options/body.css +++ b/src/options/body.css @@ -509,7 +509,7 @@ html[foo=bar] { #sidebar { height: calc(6px + var(--body-height)); width: 160px; - padding: 8px 6px; + padding: 0 6px 8px; overflow: hidden; box-sizing: border-box; @@ -534,6 +534,47 @@ html[foo=bar] { background-color: var(--box-shadow-color); } #sidebar .sidebar_section[selected] { + background-color: rgba(0, 0, 0, 0.12); +} +html[dark_mode='true'] #sidebar .sidebar_section[selected] { + background-color: rgba(255, 255, 255, 0.12); +} +#sidebar_top_row { + display: flex; + justify-content: center; + margin: 0 auto 2px; + width: fit-content; + border-bottom: 1px solid var(--box-shadow-color); + border-radius: 0 0 6px 6px; +} +#sidebar_top_row .sidebar_section { + font-size: 10px; + font-weight: 400; + text-transform: lowercase; + opacity: 0.5; + padding: 2px 6px; + border-radius: 0; + display: flex; + align-items: center; + gap: 4px; +} +#sidebar_top_row .sidebar_section[selected] { + opacity: 1; +} +#active_sidebar .active_count { + font-size: 9px; + min-width: 14px; + height: 14px; + line-height: 14px; + text-align: center; + border-radius: 7px; + background-color: rgba(0, 0, 0, 0.15); + color: var(--label-color); +} +html[dark_mode='true'] #active_sidebar .active_count { + background-color: rgba(255, 255, 255, 0.2); +} +#sidebar #active_sidebar[selected] .active_count { background-color: var(--label-color); color: var(--body-background-color); } diff --git a/src/options/main.js b/src/options/main.js index e93d7b9..901fd9d 100644 --- a/src/options/main.js +++ b/src/options/main.js @@ -66,6 +66,77 @@ function logStorageChange(changes, area) { browser.storage.onChanged.addListener(logStorageChange); +function refreshActiveSection() { + const section = document.getElementById('active_section'); + const sidebarItem = document.getElementById('active_sidebar'); + if (!section || !sidebarItem) return; + + const isSelected = sidebarItem.hasAttribute('selected'); + + // Remove old option nodes + section.querySelectorAll('.option').forEach(opt => opt.remove()); + + let count = 0; + SECTIONS.forEach(s => { + s.options.forEach(option => { + const { id, name, effects, display, premium } = option; + if (display === false) return; + if (!cache[id]) return; + + count++; + const optionNode = TEMPLATE_OPTION.cloneNode(true); + optionNode.classList.remove('removed'); + optionNode.removeAttribute('id'); + optionNode.setAttribute('name', name); + optionNode.querySelector('.option_label').innerText = name; + + const svg = optionNode.querySelector('svg'); + svg.toggleAttribute('active', true); + + optionNode.addEventListener('click', async e => { + if (premium && HTML.getAttribute('is_premium') !== 'true') { + const signedIn = await Auth.isSignedIn(); + if (!signedIn) { + if (typeof openPremiumRequiredModal === 'function') { + openPremiumRequiredModal(); + } else { + displayStatus('Premium feature - sign in to upgrade'); + } + } else if (typeof openUpgradeModal === 'function') { + openUpgradeModal(); + } else { + displayStatus('Premium feature - upgrade to access'); + } + return; + } + + updateSetting(id, false, { manual: true }); + // Sync the original toggle + const originalSvg = document.querySelector(`div#${id} svg`); + originalSvg?.toggleAttribute('active', false); + + if (effects && false in effects) { + Object.entries(effects[false]).forEach(([eid, val]) => { + const s = document.querySelector(`div#${eid} svg`); + s?.toggleAttribute('active', val); + updateSetting(eid, val); + }); + } + }); + + section.append(optionNode); + }); + }); + + // Update sidebar badge + const badge = sidebarItem.querySelector('.active_count'); + if (badge) badge.innerText = count; + + // Only show the section when its sidebar item is selected and there are options + section.classList.toggle('removed', !isSelected || count === 0); +} + + function populateOptions(SECTIONS, headerSettings, SETTING_VALUES) { // Clear the options list, and the sidebar @@ -73,6 +144,13 @@ function populateOptions(SECTIONS, headerSettings, SETTING_VALUES) { SIDEBAR.innerHTML = ''; let allTags = []; + // Create the Active Options section (hidden until sidebar selected) + const activeSection = TEMPLATE_SECTION.cloneNode(true); + activeSection.id = 'active_section'; + activeSection.querySelector('.section_label > a').innerText = 'Active Options'; + activeSection.querySelector('.section_label > a').removeAttribute('href'); + OPTIONS_LIST.append(activeSection); + // Add option nodes to the HTML. SECTIONS.forEach(section => { const { name, tags, options } = section; @@ -151,6 +229,25 @@ function populateOptions(SECTIONS, headerSettings, SETTING_VALUES) { OPTIONS_LIST.append(sectionNode); }); + // Add "All" and "Active" sidebar items at the top + const sidebarTopRow = document.createElement('div'); + sidebarTopRow.id = 'sidebar_top_row'; + + const allSidebar = document.createElement('div'); + allSidebar.id = 'all_sidebar'; + allSidebar.className = 'sidebar_section'; + allSidebar.innerText = 'all'; + allSidebar.addEventListener('click', showAll); + + const activeSidebar = document.createElement('div'); + activeSidebar.id = 'active_sidebar'; + activeSidebar.className = 'sidebar_section'; + activeSidebar.innerHTML = 'active'; + activeSidebar.addEventListener('click', activeSidebarListener); + + sidebarTopRow.append(allSidebar, activeSidebar); + SIDEBAR.append(sidebarTopRow); + // Add sections to the sidebar const uniqueTags = Array.from(new Set(allTags)); uniqueTags.forEach(tag => { @@ -237,6 +334,7 @@ function populateOptions(SECTIONS, headerSettings, SETTING_VALUES) { // Begin time loop -- checks for timedChanges, scheduling timeLoop(); + refreshActiveSection(); HTML.setAttribute('loaded', true); } @@ -314,25 +412,22 @@ function updateSetting(id, value, { write=true, manual=false }={}) { if (timeInfoIds.includes(id)) { updateTimeInfo(); } + + refreshActiveSection(); } function onSearchInput(e) { - const { target } = e; - const { value } = target; - const sidebarSections = qsa('.sidebar_section'); - const sections = qsa('.section_container:not(#template_section)'); - - // Reset - sidebarSections.forEach(s => s.removeAttribute('selected')); - sections.forEach(section => { - section.classList.remove('removed'); - const options = Array.from(section.querySelectorAll('div.option')); - options.forEach(option => option.classList.remove('removed')); - }); + const { value } = e.target; + showAll(); if (value === '') return; + // Deselect "all" during active search + const allSidebar = document.getElementById('all_sidebar'); + if (allSidebar) allSidebar.removeAttribute('selected'); + + const sections = qsa('.section_container:not(#template_section):not(#active_section)'); const searchTerms = value.toLowerCase().split(' '); sections.forEach(section => { @@ -362,27 +457,53 @@ function onSearchInput(e) { } -function sidebarSectionListener(e) { - const sidebarSection = e.target; +function showAll() { const sidebarSections = qsa('.sidebar_section'); - const selected = sidebarSection.toggleAttribute('selected'); - const tag = sidebarSection.getAttribute('tag'); - const sections = qsa('.section_container:not(#template_section)'); + const sections = qsa('.section_container:not(#template_section):not(#active_section)'); + const activeSection = document.getElementById('active_section'); - recordEvent('Section selected', { tag, selected }); - - // Reset + sidebarSections.forEach(s => s.removeAttribute('selected')); + const allSidebar = document.getElementById('all_sidebar'); + if (allSidebar) allSidebar.setAttribute('selected', ''); sections.forEach(section => { section.classList.remove('removed'); - const options = Array.from(section.querySelectorAll('div.option')); - options.forEach(option => option.classList.remove('removed')); + section.querySelectorAll('div.option').forEach(opt => opt.classList.remove('removed')); }); - sidebarSections.forEach(sidebarSection => { - sidebarSection.removeAttribute('selected'); - }) + if (activeSection) activeSection.classList.add('removed'); +} - sidebarSection.toggleAttribute('selected', selected); - if (!selected) return; + +function activeSidebarListener(e) { + const target = e.currentTarget; + const selected = target.toggleAttribute('selected'); + + if (selected) { + // Reset other selections, hide regular sections, show active + qsa('.sidebar_section').forEach(s => { + if (s !== target) s.removeAttribute('selected'); + }); + qsa('.section_container:not(#template_section):not(#active_section)').forEach(s => s.classList.add('removed')); + refreshActiveSection(); + } else { + showAll(); + } +} + + +function sidebarSectionListener(e) { + const sidebarSection = e.target; + const wasSelected = sidebarSection.hasAttribute('selected'); + const tag = sidebarSection.getAttribute('tag'); + + recordEvent('Section selected', { tag, selected: !wasSelected }); + + showAll(); + if (wasSelected) return; + + const allSidebar = document.getElementById('all_sidebar'); + if (allSidebar) allSidebar.removeAttribute('selected'); + sidebarSection.setAttribute('selected', ''); + const sections = qsa('.section_container:not(#template_section):not(#active_section)'); sections.forEach(section => { const sectionTags = section.getAttribute('tags').split(',').map(t => t.trim()); diff --git a/src/options/options.css b/src/options/options.css index 3012f5c..52d51dc 100644 --- a/src/options/options.css +++ b/src/options/options.css @@ -29,6 +29,10 @@ text-decoration: none; } +#active_section .section_label { + display: none; +} + div.option { cursor: default;