diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b7c45fd6..85f0a5ab 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -498,6 +498,9 @@ "message": "Saved", "description": "Used in various parts of the UI to indicate that something was saved" }, + "genericSize": { + "message": "Size" + }, "genericTest": { "message": "Test", "description": "Label for the action that runs some test e.g. opens the regexp tester panel in the editor" diff --git a/manage.html b/manage.html index 0d2ae318..a05b528c 100644 --- a/manage.html +++ b/manage.html @@ -31,6 +31,8 @@ + +

@@ -57,6 +59,7 @@

+

diff --git a/manage/events.js b/manage/events.js index 2dfcbf0b..4f89af82 100644 --- a/manage/events.js +++ b/manage/events.js @@ -1,7 +1,7 @@ /* global API */// msg.js /* global changeQueue installed newUI */// manage.js /* global checkUpdate handleUpdateInstalled */// updater-ui.js -/* global createStyleElement createTargetsElement getFaviconSrc */// render.js +/* global createStyleElement createTargetsElement getFaviconSrc styleToDummyEntry */// render.js /* global debounce getOwnTab openURL sessionStore */// toolbox.js /* global filterAndAppend showFiltersStats */// filters.js /* global sorter */ @@ -191,7 +191,7 @@ function handleUpdate(style, {reason, method} = {}) { if (oldEntry && method === 'styleUpdated') { handleToggledOrCodeOnly(); } - entry = entry || createStyleElement({style}); + entry = entry || createStyleElement(styleToDummyEntry(style)); if (oldEntry) { if (oldEntry.styleNameLowerCase === entry.styleNameLowerCase) { installed.replaceChild(entry, oldEntry); diff --git a/manage/manage.css b/manage/manage.css index 9d49b73a..6f954cb9 100644 --- a/manage/manage.css +++ b/manage/manage.css @@ -356,8 +356,9 @@ a:hover { .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(--c60); + color: var(--c50); text-align: right; padding-right: 1em; } diff --git a/manage/render.js b/manage/render.js index 06a118b4..7ccf09c8 100644 --- a/manage/render.js +++ b/manage/render.js @@ -17,6 +17,7 @@ const AGES = [ [12, 'm', t('dateAbbrMonth', '\x01')], [Infinity, 'y', t('dateAbbrYear', '\x01')], ]; +const groupThousands = num => `${num}`.replace(/\d(?=(\d{3})+$)/g, '$&\xA0'); (() => { const proto = HTMLImageElement.prototype; @@ -70,7 +71,15 @@ function createAgeText(el, style) { } } -function createStyleElement({style, name: nameLC}) { +function calcObjSize(obj) { + // Inaccurate but simple + return typeof obj !== 'object' ? `${obj}`.length : + !obj ? 0 : + Array.isArray(obj) ? obj.reduce((sum, v) => sum + calcObjSize(v), 0) : + Object.entries(obj).reduce((sum, [k, v]) => sum + k.length + calcObjSize(v), 0); +} + +function createStyleElement({styleMeta: style, styleNameLowerCase: 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); @@ -85,6 +94,7 @@ function createStyleElement({style, name: nameLC}) { homepage: $('.homepage', entry), homepageIcon: t.template[`homepageIcon${newUI.enabled ? 'Small' : 'Big'}`], infoAge: $('[data-type=age]', entry), + infoSize: $('[data-type=size]', entry), infoVer: $('[data-type=version]', entry), appliesTo: $('.applies-to', entry), targets: $('.targets', entry), @@ -114,9 +124,11 @@ function createStyleElement({style, name: nameLC}) { } else { delete parts.infoVer.dataset.isDate; } - if (newUI.enabled) { - createAgeText(parts.infoAge, style); - } else { + 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.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); @@ -130,8 +142,9 @@ function createStyleElement({style, name: nameLC}) { const entry = parts.entry.cloneNode(true); entry.id = ENTRY_ID_PREFIX_RAW + style.id; entry.styleId = style.id; - entry.styleNameLowerCase = nameLC || name.toLocaleLowerCase() + '\n' + name; + entry.styleNameLowerCase = nameLC; entry.styleMeta = style; + entry.styleSize = size; entry.className = parts.entryClassBase + ' ' + (style.enabled ? 'enabled' : 'disabled') + (style.updateUrl ? ' updatable' : '') + @@ -340,16 +353,7 @@ function padLeft(val, width) { } function showStyles(styles = [], matchUrlIds) { - const sorted = sorter.sort({ - styles: styles.map(style => { - const name = style.customName || style.name || ''; - return { - style, - // sort case-insensitively the whole list then sort dupes like `Foo` and `foo` case-sensitively - name: name.toLocaleLowerCase() + '\n' + name, - }; - }), - }); + const sorted = sorter.sort(styles.map(styleToDummyEntry)); let index = 0; let firstRun = true; installed.dataset.total = styles.length; @@ -388,6 +392,16 @@ function showStyles(styles = [], matchUrlIds) { } } +function styleToDummyEntry(style) { + const name = style.customName || style.name || ''; + return { + 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, + }; +} + /* exported switchUI */ function switchUI({styleOnly} = {}) { const current = {}; diff --git a/manage/sorter.js b/manage/sorter.js index 2cd944ec..c4d72802 100644 --- a/manage/sorter.js +++ b/manage/sorter.js @@ -14,27 +14,32 @@ const sorter = (() => { const tagData = { title: { text: t('genericTitle'), - parse: ({name}) => name, + parse: v => v.styleNameLowerCase, sorter: sorterType.alpha, }, usercss: { text: 'Usercss', - parse: ({style}) => style.usercssData ? 0 : 1, + parse: v => v.styleMeta.usercssData ? 0 : 1, sorter: sorterType.number, }, disabled: { text: '', // added as either "enabled" or "disabled" by the addOptions function - parse: ({style}) => style.enabled ? 1 : 0, + parse: v => v.styleMeta.enabled ? 1 : 0, sorter: sorterType.number, }, dateInstalled: { text: t('dateInstalled'), - parse: ({style}) => style.installDate, + parse: v => v.styleMeta.installDate, sorter: sorterType.number, }, dateUpdated: { text: t('dateUpdated'), - parse: ({style}) => style.updateDate || style.installDate, + parse: ({styleMeta: s}) => s.updateDate || s.installDate, + sorter: sorterType.number, + }, + size: { + text: t('genericSize'), + parse: v => v.styleSize, sorter: sorterType.number, }, }; @@ -53,6 +58,7 @@ const sorter = (() => { 'disabled,asc, title,asc', 'disabled,desc, title,asc', 'disabled,desc, usercss,asc, title,asc', + 'size,desc, title,asc', '{groupDesc}', 'title,desc', 'usercss,asc, title,desc', @@ -120,7 +126,7 @@ const sorter = (() => { init, - sort({styles}) { + sort(styles) { const sortBy = getPref().split(splitRegex); const len = sortBy.length; return styles.sort((a, b) => { @@ -140,17 +146,9 @@ const sorter = (() => { update() { if (!installed) return; const current = [...installed.children]; - const sorted = sorter.sort({ - styles: current.map(entry => ({ - entry, - name: entry.styleNameLowerCase, - style: entry.styleMeta, - })), - }); - if (current.some((entry, index) => entry !== sorted[index].entry)) { - const renderBin = document.createDocumentFragment(); - sorted.forEach(({entry}) => renderBin.appendChild(entry)); - installed.appendChild(renderBin); + const sorted = sorter.sort([...current]); + if (current.some((el, i) => el !== sorted[i])) { + installed.append(...sorted); } sorter.updateStripes(); },