mirror of
https://github.com/gorhill/uBlock.git
synced 2026-03-11 09:04:36 +00:00
[mv3] Code review re. re-worked dashboard
Related commit:
ae4754415c
Fine-tuned visuals; fixed sublist quirks related to admin-selected
lists.
This commit is contained in:
parent
ae4754415c
commit
d7c6b41992
6 changed files with 64 additions and 46 deletions
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ body {
|
|||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 100vh;
|
||||
padding: 0 var(--default-gap-xxsmall);
|
||||
}
|
||||
body > * {
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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 > * {
|
||||
|
|
|
|||
|
|
@ -101,8 +101,8 @@
|
|||
<section data-pane="rulesets">
|
||||
<div>
|
||||
<p id="listsOfBlockedHostsPrompt"></p>
|
||||
<p><input id="findInLists" type="search" spellcheck="false" placeholder="findListsPlaceholder" /></p>
|
||||
</div>
|
||||
<div class="searchfield"><input type="search" spellcheck="false" placeholder="" /><span class="fa-icon">search</span></div>
|
||||
<div id="lists"></div>
|
||||
</section>
|
||||
<!-- -------- -->
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue