diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 85f0a5ab..fd952999 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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" diff --git a/injection-order/injection-order.js b/injection-order/injection-order.js index f7827db5..5a9de839 100644 --- a/injection-order/injection-order.js +++ b/injection-order/injection-order.js @@ -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(), }); } diff --git a/js/dom.js b/js/dom.js index 7e1c1d22..1237f6b9 100644 --- a/js/dom.js +++ b/js/dom.js @@ -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; }, }); diff --git a/js/prefs.js b/js/prefs.js index 5ef033e9..d130122b 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -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 diff --git a/manage.html b/manage.html index a05b528c..710cc120 100644 --- a/manage.html +++ b/manage.html @@ -18,6 +18,8 @@

+ . + .

@@ -29,10 +31,8 @@ - + - -

@@ -49,9 +49,9 @@   +

- + diff --git a/manage/events.js b/manage/events.js index 4f89af82..09dbb1f9 100644 --- a/manage/events.js +++ b/manage/events.js @@ -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(); diff --git a/manage/incremental-search.js b/manage/incremental-search.js index f0b1e01f..2fa7fc91 100644 --- a/manage/incremental-search.js +++ b/manage/incremental-search.js @@ -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({ diff --git a/manage/manage-newui.css b/manage/manage-newui.css new file mode 100644 index 00000000..ebb81fa2 --- /dev/null +++ b/manage/manage-newui.css @@ -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; + } +} diff --git a/manage/manage.css b/manage/manage.css index 3009c7a1..a7b7d670 100644 --- a/manage/manage.css +++ b/manage/manage.css @@ -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) { diff --git a/manage/manage.js b/manage/manage.js index 21ad814e..8df0cd20 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -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() { diff --git a/manage/render.js b/manage/render.js index 7ccf09c8..dda36747 100644 --- a/manage/render.js +++ b/manage/render.js @@ -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; diff --git a/manage/sorter.js b/manage/sorter.js index c4d72802..66f355c8 100644 --- a/manage/sorter.js +++ b/manage/sorter.js @@ -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({ diff --git a/options.html b/options.html index b67394c4..12ec71e9 100644 --- a/options.html +++ b/options.html @@ -240,6 +240,10 @@

+