From d7c6b4199223e8e9015739d1a5400880271e1798 Mon Sep 17 00:00:00 2001 From: Raymond Hill Date: Mon, 18 Nov 2024 10:16:01 -0500 Subject: [PATCH] [mv3] Code review re. re-worked dashboard Related commit: https://github.com/gorhill/uBlock/commit/ae4754415c9a01f18d3219972ef35b959536891f Fine-tuned visuals; fixed sublist quirks related to admin-selected lists. --- .../mv3/extension/_locales/en/messages.json | 4 ++ .../mv3/extension/css/dashboard-common.css | 1 + platform/mv3/extension/css/dashboard.css | 5 +- platform/mv3/extension/css/settings.css | 26 +++---- platform/mv3/extension/dashboard.html | 2 +- platform/mv3/extension/js/filter-lists.js | 72 +++++++++++-------- 6 files changed, 64 insertions(+), 46 deletions(-) diff --git a/platform/mv3/extension/_locales/en/messages.json b/platform/mv3/extension/_locales/en/messages.json index f8c4b376c..60cc95401 100644 --- a/platform/mv3/extension/_locales/en/messages.json +++ b/platform/mv3/extension/_locales/en/messages.json @@ -230,5 +230,9 @@ "showBlockedCountLabel": { "message": "Show the number of blocked requests on the toolbar icon", "description": "Label for a checkbox in the options page" + }, + "findListsPlaceholder": { + "message": "Find lists", + "description": "Placeholder for the input field used to find lists" } } diff --git a/platform/mv3/extension/css/dashboard-common.css b/platform/mv3/extension/css/dashboard-common.css index 8621d0870..7abaeb566 100644 --- a/platform/mv3/extension/css/dashboard-common.css +++ b/platform/mv3/extension/css/dashboard-common.css @@ -3,6 +3,7 @@ body { box-sizing: border-box; display: flex; flex-direction: column; + max-height: 100vh; padding: 0 var(--default-gap-xxsmall); } body > * { diff --git a/platform/mv3/extension/css/dashboard.css b/platform/mv3/extension/css/dashboard.css index 454a79987..b7aa5adee 100644 --- a/platform/mv3/extension/css/dashboard.css +++ b/platform/mv3/extension/css/dashboard.css @@ -7,9 +7,6 @@ flex-wrap: wrap; overflow-x: hidden; padding: 0; - position: sticky; - top: 0; - z-index: 100; } .tabButton { background-color: transparent; @@ -43,6 +40,8 @@ body[data-pane="about"] #dashboard-nav .tabButton[data-pane="about"] { body > section { display: none; + overflow: auto; + padding-bottom: 8rem; } body[data-pane="settings"] > section[data-pane="settings"], body[data-pane="rulesets"] > section[data-pane="rulesets"], diff --git a/platform/mv3/extension/css/settings.css b/platform/mv3/extension/css/settings.css index da6e13a1a..fb5ba9151 100644 --- a/platform/mv3/extension/css/settings.css +++ b/platform/mv3/extension/css/settings.css @@ -1,12 +1,3 @@ -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } -legend { - color: var(--ink-3); - font-size: var(--font-size-smaller); - padding: var(--default-gap-xxsmall); - } body .firstRun { display: none; } @@ -78,8 +69,19 @@ h3[data-i18n="filteringMode0Name"]::first-letter { width: 100%; } -#lists { - padding-block-end: 8rem; +section[data-pane="rulesets"] > div:first-of-type { + background-color: var(--surface-1); + flex-shrink: 0; + padding: 1em 0; + position: sticky; + top: 0; + z-index: 10; + } +section[data-pane="rulesets"] > div:first-of-type > p:first-of-type { + margin-top: 0; + } +section[data-pane="rulesets"] > div:first-of-type > p:last-of-type { + margin-bottom: 0; } .listEntry { display: flex; @@ -112,7 +114,7 @@ h3[data-i18n="filteringMode0Name"]::first-letter { .listEntry.hideUnused > .listEntries > .listEntry:not(.isDefault):has(> .detailbar input:not(:checked)) { display: none; } -.listEntry.fromAdmin:has(input[disabled]:not(:checked)) { +.listEntry.fromAdmin:has(> .detailbar input[disabled]:not(:checked)) { display: none; } .listEntry > * { diff --git a/platform/mv3/extension/dashboard.html b/platform/mv3/extension/dashboard.html index 73dd72ac0..2169031eb 100644 --- a/platform/mv3/extension/dashboard.html +++ b/platform/mv3/extension/dashboard.html @@ -101,8 +101,8 @@

+

-
search
diff --git a/platform/mv3/extension/js/filter-lists.js b/platform/mv3/extension/js/filter-lists.js index a52dcb433..c48f3e357 100644 --- a/platform/mv3/extension/js/filter-lists.js +++ b/platform/mv3/extension/js/filter-lists.js @@ -36,7 +36,9 @@ function renderNumber(value) { return value.toLocaleString(); } -function renderRuleCounts() { +/******************************************************************************/ + +function renderTotalRuleCounts() { let rulesetCount = 0; let filterCount = 0; let ruleCount = 0; @@ -49,8 +51,8 @@ function renderRuleCounts() { filterCount += stats.filterCount; } dom.text('#listsOfBlockedHostsPrompt', i18n$('perRulesetStats') - .replace('{{ruleCount}}', ruleCount.toLocaleString()) - .replace('{{filterCount}}', filterCount.toLocaleString()) + .replace('{{ruleCount}}', renderNumber(ruleCount)) + .replace('{{filterCount}}', renderNumber(filterCount)) ); dom.cl.toggle(dom.body, 'noMoreRuleset', @@ -62,16 +64,26 @@ function renderRuleCounts() { function updateNodes(listEntries) { listEntries = listEntries || qs$('#lists'); + const sublistSelector = '.listEntry[data-rulesetid] > .detailbar input'; + const checkedSublistSelector = `${sublistSelector}:checked`; + const adminSublistSelector = '.listEntry.fromAdmin[data-rulesetid] > .detailbar input'; for ( const listEntry of qsa$(listEntries, '.listEntry[data-nodeid]') ) { - const totalCount = qsa$(listEntry, '.listEntry[data-rulesetid] input').length; - const checkedCount = qsa$(listEntry, '.listEntry[data-rulesetid] input:checked').length; - dom.text(qs$(listEntry, '.detailbar .count'), `${checkedCount}/${totalCount}`); - const checkbox = qs$(listEntry, ':scope > .detailbar .checkbox'); - if ( checkbox === null ) { continue; } - dom.prop(qs$(checkbox, 'input'), 'checked', checkedCount !== 0); - dom.cl.toggle(checkbox, 'partial', + const countElem = qs$(listEntry, ':scope > .detailbar .count'); + if ( countElem === null ) { continue; } + const totalCount = qsa$(listEntry, sublistSelector).length; + const checkedCount = qsa$(listEntry, checkedSublistSelector).length; + dom.text(countElem, `${checkedCount}/${totalCount}`); + const checkboxElem = qs$(listEntry, ':scope > .detailbar .checkbox'); + if ( checkboxElem === null ) { continue; } + const checkboxInput = qs$(checkboxElem, 'input'); + dom.prop(checkboxInput, 'checked', checkedCount !== 0); + dom.cl.toggle(checkboxElem, 'partial', checkedCount !== 0 && checkedCount !== totalCount ); + const adminCount = qsa$(listEntry, adminSublistSelector).length; + const fromAdmin = adminCount === totalCount; + dom.cl.toggle(listEntry, 'fromAdmin', fromAdmin); + dom.attr(checkboxInput, 'disabled', fromAdmin ? '' : null); } } @@ -124,6 +136,7 @@ export function renderFilterLists(rulesetData) { } dom.cl.toggle(listEntry, 'isDefault', ruleset.id === 'default'); const stats = rulesetStats(ruleset.id); + if ( stats === undefined ) { return; } listEntry.title = listStatsTemplate .replace('{{ruleCount}}', renderNumber(stats.ruleCount)) .replace('{{filterCount}}', renderNumber(stats.filterCount)); @@ -135,7 +148,6 @@ export function renderFilterLists(rulesetData) { 'disabled', disabled ? '' : null ); - return listEntry; }; // Update already rendered DOM lists @@ -146,7 +158,7 @@ export function renderFilterLists(rulesetData) { initializeListEntry(ruleset, listEntry); } updateNodes(); - renderRuleCounts(); + renderTotalRuleCounts(); return; } @@ -229,20 +241,17 @@ export function renderFilterLists(rulesetData) { // Build list tree const listTree = {}; const groupNames = new Map(); - for ( const [ groupKey, groupRulesets ] of groups ) { - let groupName = groupNames.get(groupKey); - if ( groupName === undefined ) { - groupName = i18n$('3pGroup' + groupKey.charAt(0).toUpperCase() + groupKey.slice(1)); - groupNames.set(groupKey, groupName); + for ( const [ nodeid, rulesets ] of groups ) { + let name = groupNames.get(nodeid); + if ( name === undefined ) { + name = i18n$(`3pGroup${nodeid.charAt(0).toUpperCase()}${nodeid.slice(1)}`); + groupNames.set(nodeid, name); } - const groupDetails = { - name: groupName, - lists: {}, - }; - listTree[groupKey] = groupDetails; - for ( const ruleset of groupRulesets ) { + const details = { name, lists: {} }; + listTree[nodeid] = details; + for ( const ruleset of rulesets ) { if ( ruleset.parent !== undefined ) { - let lists = groupDetails.lists; + let lists = details.lists; for ( const parent of ruleset.parent.split('|') ) { if ( lists[parent] === undefined ) { lists[parent] = { name: parent, lists: {} }; @@ -251,11 +260,11 @@ export function renderFilterLists(rulesetData) { } lists[ruleset.id] = ruleset; } else { - groupDetails.lists[ruleset.id] = ruleset; + details.lists[ruleset.id] = ruleset; } } } - // Move lonely sublist to list level + // Replace composite list with only one sublist with sublist itself const promoteLonelySublist = (parent, depth = 0) => { if ( Boolean(parent.lists) === false ) { return parent; } const childKeys = Object.keys(parent.lists); @@ -279,7 +288,7 @@ export function renderFilterLists(rulesetData) { dom.clear('#lists'); qs$('#lists').append(listEntries); - renderRuleCounts(); + renderTotalRuleCounts(); } /******************************************************************************/ @@ -337,11 +346,12 @@ localRead('hideUnusedFilterLists').then(value => { /******************************************************************************/ const searchFilterLists = ( ) => { - const pattern = dom.prop('.searchfield input', 'value') || ''; + const pattern = dom.prop('#findInLists', 'value') || ''; dom.cl.toggle('#lists', 'searchMode', pattern !== ''); if ( pattern === '' ) { return; } const re = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i'); for ( const listEntry of qsa$('#lists [data-role="leaf"]') ) { + if ( dom.cl.has(listEntry, 'fromAdmin') ) { continue; } const rulesetid = listEntry.dataset.rulesetid; const rulesetDetails = rulesetMap.get(rulesetid); if ( rulesetDetails === undefined ) { continue; } @@ -365,7 +375,7 @@ const searchFilterLists = ( ) => { const perListHaystack = new WeakMap(); -dom.on('.searchfield input', 'input', searchFilterLists); +dom.on('#findInLists', 'input', searchFilterLists); /******************************************************************************/ @@ -379,6 +389,8 @@ async function applyEnabledRulesets() { enabledRulesets.push(rulesetid); } + if ( enabledRulesets.length === 0 ) { return; } + await sendMessage({ what: 'applyRulesets', enabledRulesets, @@ -396,7 +408,7 @@ dom.on('#lists', 'change', '.listEntry input[type="checkbox"]', ev => { input.checked = checkAll; } } - renderRuleCounts(); updateNodes(); + renderTotalRuleCounts(); applyEnabledRulesets(); });