add multi-column mode option

This commit is contained in:
tophf 2022-09-04 16:59:19 +03:00
parent 6979958908
commit ef998e423e
13 changed files with 403 additions and 381 deletions

View File

@ -762,6 +762,9 @@
"message": "Number of applies-to items",
"description": "Label for the numeric input box to limit max number of applies-to targets in the new UI on manage page"
},
"manageMinColumnWidth": {
"message": "Minimum column width (in pixels; 9999 disables multi-column mode)"
},
"manageNewStyleAsUsercss": {
"message": "as Usercss",
"description": "VERY SHORT label for the checkbox next to the 'Write new style' button in the style manager"

View File

@ -37,7 +37,7 @@ async function InjectionOrder(show, el, selector) {
parts.name.href = '/edit.html?id=' + style.id;
parts.name.textContent = style.name;
return Object.assign(entry.cloneNode(true), {
styleNameLowerCase: style.name.toLocaleLowerCase(),
styleNameLC: style.name.toLocaleLowerCase(),
});
}

View File

@ -314,9 +314,7 @@ function setupLivePrefs(ids) {
function getValue(el) {
const type = el.dataset.valueType || el.type;
return type === 'checkbox' ? el.checked :
// https://stackoverflow.com/questions/18062069/why-does-valueasnumber-return-nan-as-a-value
// valueAsNumber is not applicable for input[text/radio] or select
type === 'number' ? Number(el.value) :
type === 'number' ? parseFloat(el.value) :
el.value;
}
function isSame(el, oldValue, value) {
@ -465,6 +463,7 @@ prefs.ready.then(() => {
const max = (innerWidth < 850 ? screen.availWidth : innerWidth) / 3;
width = Math.round(Math.max(200, Math.min(max, Number(width) || 0)));
$.root.style.setProperty('--header-width', width + 'px');
dom.HWval = width;
return width;
},
});

View File

@ -55,6 +55,7 @@
'manage.actions.expanded': true,
'manage.backup.expanded': true,
'manage.filters.expanded': true,
'manage.minColumnWidth': 750,
// the new compact layout doesn't look good on Android yet
'manage.newUI': true,
'manage.newUI.favicons': false, // show favicons for the sites in applies-to

View File

@ -18,6 +18,8 @@
<a target="_blank" class="homepage"></a>
</h2>
<p class="applies-to">
<span class="style-info" data-type="age"></span>.
<label i18n="genericSize">:</label><span class="style-info" data-type="size"></span>.
<label i18n="appliesDisplay"></label>
<span class="targets"></span>
</p>
@ -29,10 +31,8 @@
<button class="disable" i18n="disableStyleLabel"></button>
<button class="delete" i18n="deleteStyleLabel"></button>
<button class="check-update" i18n="checkForUpdate"></button>
<button class="update" i18n="installUpdate"></button>
<button class="update" i18n="installUpdate" hidden></button>
<button class="configure-usercss" i18n="configureStyle"></button>
<span class="style-info" data-type="size"></span>
<span class="style-info" data-type="age"></span>
<span class="update-note"></span>
</p>
</div>
@ -49,9 +49,9 @@
&nbsp;
<span class="style-info" data-type="version"></span>
</a>
<a target="_blank" class="homepage" tabindex="0"></a>
</h2>
<p class="actions">
<a target="_blank" class="homepage" tabindex="0"></a>
<a class="delete" i18n="title:deleteStyleLabel" tabindex="0">
<svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5
@ -391,6 +391,7 @@
</template>
<link rel="stylesheet" href="manage/manage.css">
<link rel="stylesheet" href="manage/manage-newui.css" id="newUI">
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
</head>

View File

@ -130,7 +130,9 @@ const Events = {
},
name(event, entry) {
if (newUI.enabled) Events.edit(event, entry);
if (newUI.enabled && !event.target.closest('.homepage')) {
Events.edit(event, entry);
}
},
toggle(event, entry) {
@ -193,7 +195,7 @@ function handleUpdate(style, {reason, method} = {}) {
}
entry = entry || createStyleElement(styleToDummyEntry(style));
if (oldEntry) {
if (oldEntry.styleNameLowerCase === entry.styleNameLowerCase) {
if (oldEntry.styleNameLC === entry.styleNameLC) {
installed.replaceChild(entry, oldEntry);
} else {
oldEntry.remove();

View File

@ -57,7 +57,7 @@
let found;
for (const entry of rotated || entries) {
if (entry.classList.contains('hidden')) continue;
const name = entry.styleNameLowerCase;
const name = entry.styleNameLC;
const pos = name.indexOf(text);
if (pos === 0) {
found = entry;
@ -73,7 +73,7 @@
if (found && found !== focusedEntry) {
focusedEntry = found;
focusedLink = $('a', found);
focusedName = found.styleNameLowerCase;
focusedName = found.styleNameLC;
scrollElementIntoView(found, {invalidMarginRatio: .25});
animateElement(found, 'highlight-quick');
replaceInlineStyle({

278
manage/manage-newui.css Normal file
View File

@ -0,0 +1,278 @@
.disabled.entry .svg-icon {
color: var(--c50);
fill: var(--c80);
font-weight: normal;
transition: color .5s .1s, fill .5s .1s;
}
#installed {
margin-top: .75rem;
margin-bottom: .75rem;
}
.entry {
padding: 0 .5em;
display: flex;
border: none;
}
.entry.odd {
background-color: rgba(128, 128, 128, 0.05);
}
.entry > * {
padding: .5rem 0;
margin: 0;
display: flex;
align-items: center;
}
.entry .actions {
position: relative;
}
.style-info[data-type=size],
.style-info[data-type=age] {
color: var(--c50);
justify-content: end;
}
.style-info[data-type=age] {
flex: 0 0 4ch;
}
.style-info[data-type=size] {
flex: 0 0 var(--size-width);
}
.style-info[data-type=version] {
color: var(--c40);
padding-left: .5em;
font-weight: normal;
}
.style-info[data-type=version][data-is-date],
.style-info[data-type=version][data-value=""],
.style-info[data-type=version][data-value="1.0.0"] {
display: none;
}
.entry input[type="checkbox"]:not(.slider) {
pointer-events: all;
}
.style-name {
font-size: 14px;
padding-left: var(--name-padding-left);
position: relative;
cursor: pointer;
justify-content: space-between;
flex: 0 0 var(--name-width);
min-width: 25%;
max-width: 50%;
}
#installed[style*="--num-targets:0"] .style-name {
max-width: none;
flex-grow: 1;
}
.checkmate {
flex-shrink: 0;
}
.style-name-link {
width: 100%;
}
.entry .style-name:hover::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to right, hsla(180, 50%, 30%, 0.2), hsla(180, 20%, 10%, 0.05) 50%, transparent);
pointer-events: none;
}
.entry.enabled .style-name:hover .style-name-link {
color: var(--accent-1);
}
.homepage {
margin-top: -3px;
}
.actions {
flex: 0 0 calc(3 * (var(--action-size) + var(--action-margin)));
flex-wrap: nowrap;
z-index: 100;
}
.actions > * {
width: var(--action-size);
height: var(--action-size);
display: flex;
align-items: center;
}
.updater-icons > * {
transition: opacity 1s;
display: none;
}
.entry .svg-icon {
fill: var(--c60);
}
.entry:hover .svg-icon {
fill: var(--c40);
}
.entry .svg-icon.checked,
.entry:hover .svg-icon.checked,
.entry:hover .svg-icon:hover {
fill: var(--fg);
}
.checking-update .check-update {
opacity: 0;
display: inline-block;
pointer-events: none;
}
.can-update .update,
.no-update:not(.update-problem):not(.update-done) .up-to-date,
.no-update.update-problem .check-update,
.update-done .updated {
display: inline-block;
}
.up-to-date svg,
.updated svg {
cursor: auto;
}
.update-done .updated svg {
top: -4px;
position: relative;
filter: drop-shadow(0 5px 0 currentColor);
}
.can-update .update,
.no-update.update-problem .check-update {
cursor: pointer;
}
.can-update[data-details$="locally edited"] .update svg,
.update-problem .check-update svg {
fill: #ef6969;
}
.can-update[data-details$="locally edited"]:hover .update svg,
.entry.update-problem:hover .check-update svg {
fill: #fd4040;
}
.can-update[data-details$="locally edited"]:hover .update svg:hover,
.entry.update-problem:hover .check-update svg:hover {
fill: red;
}
.updater-icons > :not(.check-update):after {
content: attr(title);
position: absolute;
display: block;
width: max-content;
max-width: 25vw;
padding: 1ex 1.5ex;
border: 1px solid #ded597;
background-color: #fffbd6;
border-radius: 4px;
box-shadow: 2px 3px 10px rgba(0,0,0,.25);
font-size: 90%;
animation: fadeout 10s;
animation-fill-mode: both;
}
.update-problem .check-update:after {
background-color: red;
border: 1px solid #d40000;
color: white;
animation: none;
}
.can-update .update:after {
animation: none;
}
.can-update:not([data-details$="locally edited"]) .update:after {
background-color: #c0fff0;
border: 1px solid #89cac9;
}
.applies-to {
padding: .25em 0 .25em 1em;
flex-grow: 999;
}
#installed[style*="--num-targets:0"] .applies-to {
display: none;
}
.targets {
overflow: hidden;
max-height: calc(var(--num-targets) * 18px);
width: 100%;
}
.applies-to.expanded .targets {
max-height: none;
}
.target {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-right: 1em;
line-height: 18px;
width: 0;
min-width: 100%;
box-sizing: border-box;
}
.applies-to:not(.has-more) .expander {
display: none;
}
.target:hover {
background-color: inherit;
}
.target img {
width: 16px;
height: 16px;
vertical-align: middle;
margin: -1px 4px 0 -20px;
transition: opacity .5s, filter .5s;
/* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */
backface-visibility: hidden;
visibility: hidden;
}
.favicons-grayed .target img {
filter: grayscale(1);
opacity: .25;
}
.has-favicons .target {
padding-left: 20px;
}
.has-favicons .target img[src] {
visibility: visible;
}
.entry:hover .target img {
opacity: 1;
filter: none;
}
.target b::after {
content: '?';
margin: -2px 4px 0 -20px;
display: inline-block;
vertical-align: baseline;
background: var(--c85);
width: 16px;
line-height: 16px;
text-align: center;
border-radius: 50%;
color: var(--bg);
}
@media (max-width: 850px) {
.entry {
padding: 0;
}
.entry .checkmate {
position: absolute;
left: 14px;
top: 0;
bottom: 0;
margin: auto;
}
.entry .style-name {
text-indent: unset;
}
.entry .actions {
width: 104px;
padding: .5rem 0 .5rem 6px;
}
.entry .applies-to {
padding: .25rem .5rem .25rem 0;
}
.entry .target {
max-width: 100%;
padding-right: 0;
}
.style-name-link::after {
text-indent: 0;
display: inline-block;
}
.entry > .style-info {
display: none;
}
}

View File

@ -1,7 +1,9 @@
:root {
--name-padding-left: 20px;
--name-padding-right: 40px;
--actions-width: 75px;
--name-width: 30ch;
--size-width: 4ch;
--action-size: 20px;
--action-margin: 6px;
}
body {
/* Fill the entire viewport to enable json import via drag'n'drop */
@ -124,17 +126,21 @@ a:hover {
}
#installed {
position: relative;
padding-left: var(--header-width);
box-sizing: border-box;
width: 100%;
align-self: start;
display: flex;
flex-wrap: wrap;
}
.entry {
margin: 0;
padding: 1.25em 2em;
border-top: 1px solid var(--c85);
box-sizing: border-box;
position: relative;
width: calc(100% / var(--columns, 1));
}
.entry:first-child {
@ -158,15 +164,13 @@ a:hover {
margin-top: .25em;
overflow-wrap: break-word;
}
.style-name a, .style-edit-link {
text-decoration: none;
}
.style-name span,
.applies-to {
overflow-wrap: break-word;
overflow-wrap: anywhere;
}
.applies-to,
.actions {
padding-left: 15px;
@ -181,11 +185,10 @@ a:hover {
.actions > * {
margin-bottom: .25rem;
display: inline-block;
}
.actions > *:not(:last-child) {
margin-right: .25rem;
margin-right: var(--action-margin);
}
.applies-to label {
@ -240,8 +243,7 @@ a:hover {
}
.disabled h2 .style-name-link,
.disabled .applies-to,
.newUI .disabled.entry .svg-icon {
.disabled .applies-to {
color: var(--c50);
fill: var(--c80);
font-weight: normal;
@ -319,56 +321,8 @@ a:hover {
/* compact layout */
.newUI #installed {
display: table;
margin-top: .75rem;
margin-bottom: .75rem;
}
.newUI .entry {
display: table-row;
padding-top: 0;
padding-bottom: 0;
}
.newUI .entry.odd {
background-color: rgba(128, 128, 128, 0.05);
}
.newUI .entry > * {
padding: .5rem 0;
margin: 0;
display: table-cell;
vertical-align: middle;
}
.newUI .entry .actions {
position: relative;
}
.style-info[data-type=version] {
color: var(--c40);
padding-left: .5em;
font-weight: normal;
}
.newUI .style-info[data-type=version][data-is-date],
.newUI .style-info[data-type=version][data-value=""],
.newUI .style-info[data-type=version][data-value="1.0.0"] {
display: none;
}
.newUI .entry .style-info[data-type=size],
.newUI .entry .style-info[data-type=age] {
color: var(--c50);
text-align: right;
padding-right: 1em;
}
/************ checkbox & select************/
#newUIoptions > div, #newUIoptions > label {
margin: 4px 0;
}
.filter-selection {
position: relative;
left: -9px;
@ -393,10 +347,6 @@ a:hover {
margin-top: -2px;
}
.newUI #newUIoptions > label {
padding-left: 0;
}
.filter-selection select {
height: 18px;
border: none;
@ -439,7 +389,7 @@ a:hover {
.entry .checkmate {
vertical-align: middle;
margin: -2px 1ex 0 0;
margin-right: 1ch;
}
#manage-text {
@ -456,179 +406,10 @@ a:hover {
margin: 0 .5em;
}
.newUI .entry input[type="checkbox"]:not(.slider) {
pointer-events: all;
}
.newUI .style-name {
font-size: 14px;
padding-left: var(--name-padding-left);
padding-right: var(--name-padding-right);
position: relative;
cursor: pointer;
}
.newUI .entry .style-name:hover::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to right, hsla(180, 50%, 30%, 0.2), hsla(180, 20%, 10%, 0.05) 50%, transparent);
pointer-events: none;
}
.newUI .entry.enabled .style-name:hover .style-name-link {
color: var(--accent-1);
}
.newUI .style-name:after {
text-indent: 1.2rem;
}
.newUI .actions:after {
text-indent: -25px;
}
.newUI .actions .homepage[href=""] {
display: inline-block;
visibility: hidden;
height: 0;
}
.newUI .actions {
width: var(--actions-width);
height: 20px;
white-space: nowrap;
}
.newUI .actions > * {
margin: 0 6px 0 0;
width: 20px;
height: 20px;
}
.newUI .updater-icons > * {
transition: opacity 1s;
display: none;
}
.newUI .entry .svg-icon {
fill: var(--c60);
}
.newUI .entry:hover .svg-icon {
fill: var(--c40);
}
button .svg-icon,
.newUI .entry .svg-icon.checked,
.newUI .entry:hover .svg-icon.checked,
.newUI .entry:hover .svg-icon:hover {
button .svg-icon {
fill: var(--fg);
}
.newUI .checking-update .check-update {
opacity: 0;
display: inline-block;
pointer-events: none;
}
.newUI .can-update .update,
.newUI .no-update:not(.update-problem):not(.update-done) .up-to-date,
.newUI .no-update.update-problem .check-update,
.newUI .update-done .updated {
display: inline-block;
}
.newUI .up-to-date svg,
.newUI .updated svg {
cursor: auto;
}
.newUI .update-done .updated svg {
top: -4px;
position: relative;
filter: drop-shadow(0 5px 0 currentColor);
}
.newUI .can-update .update,
.newUI .no-update.update-problem .check-update {
cursor: pointer;
}
.newUI .can-update[data-details$="locally edited"] .update svg,
.newUI .update-problem .check-update svg {
fill: #ef6969;
}
.newUI .can-update[data-details$="locally edited"]:hover .update svg,
.newUI .entry.update-problem:hover .check-update svg {
fill: #fd4040;
}
.newUI .can-update[data-details$="locally edited"]:hover .update svg:hover,
.newUI .entry.update-problem:hover .check-update svg:hover {
fill: red;
}
.newUI .actions {
z-index: 100;
}
.newUI .updater-icons > :not(.check-update):after {
content: attr(title);
position: absolute;
margin-top: 18px;
margin-left: -36px;
padding: 1ex 1.5ex;
border: 1px solid #ded597;
background-color: #fffbd6;
border-radius: 4px;
box-shadow: 2px 3px 10px rgba(0,0,0,.25);
font-size: 90%;
animation: fadeout 10s;
animation-fill-mode: both;
}
.newUI .update-problem .check-update:after {
background-color: red;
border: 1px solid #d40000;
color: white;
animation: none;
}
.newUI .can-update .update:after {
animation: none;
}
.newUI .can-update:not([data-details$="locally edited"]) .update:after {
background-color: #c0fff0;
border: 1px solid #89cac9;
}
.newUI .applies-to {
padding-top: .25rem;
padding-bottom: .25rem;
}
.newUI .targets {
overflow: hidden;
max-height: calc(var(--num-targets) * 18px);
}
.newUI .applies-to.expanded .targets {
max-height: none;
}
.newUI .target {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: calc(75vw - var(--header-width) - var(--actions-width) - var(--name-padding-left) - var(--name-padding-right) - 6rem);
box-sizing: border-box;
padding-right: 1rem;
line-height: 18px;
}
.expander {
cursor: pointer;
position: absolute;
@ -643,54 +424,6 @@ button .svg-icon,
transform: rotate(180deg);
transform-origin: 8px 8px;
}
.newUI .applies-to:not(.has-more) .expander {
display: none;
}
.newUI .target:hover {
background-color: inherit;
}
.newUI .target img {
width: 16px;
height: 16px;
vertical-align: middle;
margin: -1px 4px 0 -20px;
transition: opacity .5s, filter .5s;
/* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */
backface-visibility: hidden;
visibility: hidden;
}
.newUI .favicons-grayed .target img {
filter: grayscale(1);
opacity: .25;
}
.newUI .has-favicons .target {
padding-left: 20px;
}
.newUI .has-favicons .target img[src] {
visibility: visible;
}
.newUI .entry:hover .target img {
opacity: 1;
filter: none;
}
.newUI .target b::after {
content: '?';
margin: -2px 4px 0 -20px;
display: inline-block;
vertical-align: baseline;
background: var(--c85);
width: 16px;
line-height: 16px;
text-align: center;
border-radius: 50%;
color: var(--bg);
}
/* Default, no update buttons */
.updater-icons .update,
@ -1032,12 +765,6 @@ button .svg-icon,
}
}
@media (max-width: 1000px) {
.newUI .entry > .style-info {
display: none;
}
}
@media (max-width: 850px) {
:root {
--name-padding-left: 34px;
@ -1058,7 +785,7 @@ button .svg-icon,
left: 3.75rem;
}
html:not(.newUI) .applies-to {
.oldUI .applies-to {
word-break: break-all;
}
@ -1066,10 +793,6 @@ button .svg-icon,
table-layout: fixed;
}
.newUI .entry .actions {
padding-right: 30px
}
#search-wrapper,
#sort-wrapper,
#header summary {
@ -1127,41 +850,6 @@ button .svg-icon,
margin-top: 0;
padding-bottom: .25rem;
}
.newUI .entry {
padding: 0;
}
.newUI .entry .checkmate {
position: absolute;
left: 14px;
top: 0;
bottom: 0;
margin: auto;
}
.newUI .entry .style-name {
text-indent: unset;
}
.newUI .entry .actions {
width: 104px;
padding: .5rem 0 .5rem 6px;
}
.newUI .entry .applies-to {
padding: .25rem .5rem .25rem 0;
}
.newUI .entry .target {
max-width: 100%;
padding-right: 0;
}
.newUI .style-name-link::after {
text-indent: 0;
display: inline-block;
}
}
@supports (-moz-appearance: none) {

View File

@ -28,9 +28,16 @@ const newUI = {
Object.assign(newUI, {
ids: Object.keys(newUI),
prefKeyForId: id => `manage.newUI.${id}`.replace(/\.enabled$/, ''),
readPrefs(dest = newUI, cb) {
for (const id of newUI.ids) {
const val = dest[id] = prefs.get(newUI.prefKeyForId(id));
if (cb) cb(id, val);
}
},
renderClass: () => {
$.rootCL.toggle('newUI', newUI.enabled);
$.rootCL.toggle('oldUI', !newUI.enabled);
$('#newUI').media = newUI.enabled ? '' : '?';
},
hasFavs: () => newUI.enabled && newUI.favicons,
badFavsKey: 'badFavs',
@ -41,9 +48,7 @@ Object.assign(newUI, {
},
});
// ...read the actual values
for (const id of newUI.ids) {
newUI[id] = prefs.get(newUI.prefKeyForId(id));
}
newUI.readPrefs();
newUI.renderClass();
(async function init() {

View File

@ -3,7 +3,6 @@
/* global URLS debounce getOwnTab isEmptyObj sessionStore stringAsRegExp */// toolbox.js
/* global filterAndAppend */// filters.js
/* global installed newUI */// manage.js
/* global prefs */
/* global sorter */
/* global t */// localization.js
'use strict';
@ -18,6 +17,7 @@ const AGES = [
[Infinity, 'y', t('dateAbbrYear', '\x01')],
];
const groupThousands = num => `${num}`.replace(/\d(?=(\d{3})+$)/g, '$&\xA0');
const renderSize = size => groupThousands(Math.round(size / 1024)) + 'k';
(() => {
const proto = HTMLImageElement.prototype;
@ -79,7 +79,7 @@ function calcObjSize(obj) {
Object.entries(obj).reduce((sum, [k, v]) => sum + k.length + calcObjSize(v), 0);
}
function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, styleSize: size}) {
function createStyleElement({styleMeta: style, styleNameLC: nameLC, styleSize: size}) {
// query the sub-elements just once, then reuse the references
if ((elementParts || {}).newUI !== newUI.enabled) {
const entry = t.template[newUI.enabled ? 'styleNewUI' : 'style'].cloneNode(true);
@ -105,7 +105,6 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style
},
oldConfigure: !newUI.enabled && $('.configure-usercss', entry),
oldCheckUpdate: !newUI.enabled && $('.check-update', entry),
oldUpdate: !newUI.enabled && $('.update', entry),
};
}
const parts = elementParts;
@ -126,12 +125,11 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style
}
createAgeText(parts.infoAge, style);
parts.infoSize.dataset.value = Math.log10(size || 1) >> 0; // for CSS to target big/small styles
parts.infoSize.textContent = groupThousands(Math.round(size / 1024)) + 'k';
parts.infoSize.textContent = renderSize(size);
parts.infoSize.title = `${t('genericSize')}: ${groupThousands(size)} B`;
if (!newUI.enabled) {
parts.oldConfigure.classList.toggle('hidden', !configurable);
parts.oldCheckUpdate.classList.toggle('hidden', !style.updateUrl);
parts.oldUpdate.classList.toggle('hidden', !style.updateUrl);
}
// clear the code to free up some memory
@ -142,7 +140,7 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style
const entry = parts.entry.cloneNode(true);
entry.id = ENTRY_ID_PREFIX_RAW + style.id;
entry.styleId = style.id;
entry.styleNameLowerCase = nameLC;
entry.styleNameLC = nameLC;
entry.styleMeta = style;
entry.styleSize = size;
entry.className = parts.entryClassBase + ' ' +
@ -166,6 +164,12 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style
}
function createTargetsElement({entry, expanded, style = entry.styleMeta}) {
const maxTargets = expanded ? 1000 : newUI.enabled ? newUI.targets : 10;
if (!maxTargets) {
entry._numTargets = 0;
return;
}
const displayed = new Set();
const entryTargets = $('.targets', entry);
const expanderCls = $('.applies-to', entry).classList;
const targets = elementParts.targets.cloneNode(true);
@ -173,8 +177,6 @@ function createTargetsElement({entry, expanded, style = entry.styleMeta}) {
let el = entryTargets.firstElementChild;
let numTargets = 0;
let allTargetsRendered = true;
const maxTargets = expanded ? 1000 : newUI.enabled ? newUI.targets : 10;
const displayed = new Set();
for (const type of TARGET_TYPES) {
for (const section of style.sections) {
for (const targetValue of section[type] || []) {
@ -352,26 +354,43 @@ function padLeft(val, width) {
return ' '.repeat(Math.max(0, width - val.length)) + val;
}
function fitNameColumn(styles) {
const align = 1e9; // required by sort()
const lengths = styles.map(s => align +
(s = s.displayName || s.name || '').length +
s.replace(/[^\u3000-\uFE00]+/g, '').length).sort(); // CJK glyphs are twice as wide
const len = styles.length;
const fringe = len * 5 / 100 | 0; // ignoring 5% of outliers at each end
let avgName = 0;
for (let i = fringe; i < len - fringe; i++) {
avgName = Math.max(avgName, lengths[i] - align);
}
$.root.style.setProperty('--name-width', avgName + 'ch');
}
function fitSizeColumn(entries) {
const max = entries.reduce((res, e) => Math.max(res, e.styleSize), 0);
$.root.style.setProperty('--size-width', renderSize(max).length + 'ch');
}
function showStyles(styles = [], matchUrlIds) {
const sorted = sorter.sort(styles.map(styleToDummyEntry));
const dummies = styles.map(styleToDummyEntry);
const sorted = sorter.sort(dummies);
let index = 0;
let firstRun = true;
installed.dataset.total = styles.length;
const scrollY = (history.state || {}).scrollY;
const shouldRenderAll = scrollY > window.innerHeight || sessionStore.justEditedStyleId;
const renderBin = document.createDocumentFragment();
if (scrollY) {
renderStyles();
} else {
requestAnimationFrame(renderStyles);
}
fitNameColumn(styles);
fitSizeColumn(dummies);
renderStyles();
function renderStyles() {
const t0 = performance.now();
while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 20)) {
const info = sorted[index++];
const entry = createStyleElement(info);
if (matchUrlIds && !matchUrlIds.includes(info.style.id)) {
while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 50)) {
const entry = createStyleElement(sorted[index++]);
if (matchUrlIds && !matchUrlIds.includes(entry.styleMeta.id)) {
entry.classList.add('not-matching');
}
renderBin.appendChild(entry);
@ -398,7 +417,7 @@ function styleToDummyEntry(style) {
styleMeta: style,
styleSize: calcObjSize(style),
// sort case-insensitively the whole list then sort dupes like `Foo` and `foo` case-sensitively
styleNameLowerCase: name.toLocaleLowerCase() + '\n' + name,
styleNameLC: name.toLocaleLowerCase() + '\n' + name,
};
}
@ -407,13 +426,11 @@ function switchUI({styleOnly} = {}) {
const current = {};
const changed = {};
let someChanged = false;
for (const id of newUI.ids) {
const value = prefs.get(newUI.prefKeyForId(id));
newUI.readPrefs(current, (id, value) => {
const valueChanged = value !== newUI[id] && (id === 'enabled' || current.enabled);
current[id] = value;
changed[id] = valueChanged;
someChanged |= valueChanged;
}
});
if (!styleOnly && !someChanged) {
return;

View File

@ -1,4 +1,4 @@
/* global $ $create messageBoxProxy */// dom.js
/* global $ $create dom messageBoxProxy */// dom.js
/* global installed */// manage.js
/* global prefs */
/* global t */// localization.js
@ -6,6 +6,10 @@
const sorter = (() => {
const COL_MIN = 300; // same as options.html
const COL_MAX = 9999; // same as options.html
const COL_PROP = '--columns';
const sorterType = {
alpha: (a, b) => a < b ? -1 : a === b ? 0 : 1,
number: (a, b) => (a || 0) - (b || 0),
@ -14,7 +18,7 @@ const sorter = (() => {
const tagData = {
title: {
text: t('genericTitle'),
parse: v => v.styleNameLowerCase,
parse: v => v.styleNameLC,
sorter: sorterType.alpha,
},
usercss: {
@ -71,12 +75,13 @@ const sorter = (() => {
const getPref = () => prefs.get(ID) || prefs.defaults[ID];
let columns = 1;
let minWidth;
function init() {
prefs.subscribe(ID, sorter.update);
$('#sorter-help').onclick = showHelp;
addOptions();
updateColumnCount();
prefs.subscribe('manage.minColumnWidth', updateColumnWidth, {runNow: true});
}
function addOptions() {
@ -172,21 +177,40 @@ const sorter = (() => {
};
function updateColumnCount() {
let newValue = 1;
for (let el = $.root.lastElementChild;
el.localName === 'style';
el = el.previousElementSibling) {
if (el.textContent.includes('--columns:')) {
newValue = Math.max(1, getComputedStyle($.root).getPropertyValue('--columns') | 0);
break;
}
}
if (columns !== newValue) {
columns = newValue;
const useStyle = [].some.call($.root.children,
el => el.tagName === 'STYLE' && el.textContent.includes(COL_PROP + ':'));
const v = useStyle ? Math.max(1, getComputedStyle($.root).getPropertyValue(COL_PROP) >> 0)
: minWidth ? onResize()
: columns;
if (columns !== v) {
columns = v;
return true;
}
}
function updateColumnWidth(_, val) {
minWidth = Math.max(val, COL_MIN);
if (val < COL_MAX) {
window.on('resize', onResize);
} else {
window.off('resize', onResize);
$.root.style.removeProperty(COL_PROP);
}
sorter.updateStripes({onlyWhenColumnsChanged: true});
}
function onResize(evt) {
const c = Math.max(1, (window.innerWidth - dom.HWval) / minWidth >> 0);
if (columns !== c) {
$.root.style.setProperty(COL_PROP, c);
if (evt) {
columns = c;
sorter.updateStripes();
}
}
return c;
}
async function showHelp(event) {
event.preventDefault();
messageBoxProxy.show({

View File

@ -240,6 +240,10 @@
<div class="block">
<h1 i18n="openManage"></h1>
<div class="items">
<label>
<span i18n="manageMinColumnWidth"></span>
<input id="manage.minColumnWidth" type="number" min="300" max="9999">
</label>
<label>
<span i18n="manageNewUI"></span>
<span class="onoffswitch">
@ -268,7 +272,7 @@
</label>
<label>
<span i18n="manageMaxTargets"></span>
<input id="manage.newUI.targets" type="number" min="1" max="99">
<input id="manage.newUI.targets" type="number" min="0" max="99">
</label>
</div>
</div>