From 2646d910ab93a79d33d2f6dd3788746cdbaefafa Mon Sep 17 00:00:00 2001 From: Rob Garrison Date: Sat, 23 Dec 2017 19:14:39 -0600 Subject: [PATCH] Modularize sorter --- manage/filters.js | 4 +- manage/manage.js | 20 +-- manage/sort.js | 374 ++++++++++++++++++++++--------------------- manage/updater-ui.js | 4 +- 4 files changed, 203 insertions(+), 199 deletions(-) diff --git a/manage/filters.js b/manage/filters.js index 56e1b117..01e99b0a 100644 --- a/manage/filters.js +++ b/manage/filters.js @@ -1,5 +1,5 @@ /* global installed messageBox */ -/* global updateSort */ +/* global sorter */ 'use strict'; const filtersSelector = { @@ -156,7 +156,7 @@ function filterOnChange({target: el, forceRefilter}) { if (installed) { reapplyFilter(); } - debounce(updateSort); + debounce(sorter().updateSort); } diff --git a/manage/manage.js b/manage/manage.js index 96206fbe..e16696cd 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -3,7 +3,7 @@ /* global checkUpdate, handleUpdateInstalled */ /* global objectDiff */ /* global configDialog */ -/* global sortInit, sortStyles, updateSort */ +/* global sorter */ 'use strict'; let installed; @@ -85,7 +85,7 @@ function initGlobalEvents() { // N.B. triggers existing onchange listeners setupLivePrefs(); - sortInit(); + sorter().sortInit(); $$('[id^="manage.newUI"]') .forEach(el => (el.oninput = (el.onchange = switchUI))); @@ -108,7 +108,7 @@ function initGlobalEvents() { function showStyles(styles = []) { - const sorted = sortStyles({ + const sorted = sorter().sortStyles({ parser: 'style', styles: styles.map(style => ({ style, @@ -459,7 +459,7 @@ function handleUpdate(style, {reason, method} = {}) { handleUpdateInstalled(entry, reason); } filterAndAppend({entry}); - debounce(updateSort); + debounce(sorter().updateSort); if (!entry.matches('.hidden') && reason !== 'import') { animateElement(entry); scrollElementIntoView(entry); @@ -568,18 +568,6 @@ function switchUI({styleOnly} = {}) { } -function updateStripes() { - let index = 0; - [...installed.children].forEach(entry => { - const list = entry.classList; - if (!list.contains('hidden')) { - list.add(index % 2 ? 'odd' : 'even'); - list.remove(index++ % 2 ? 'even' : 'odd'); - } - }); -} - - function rememberScrollPosition() { history.replaceState({scrollY: window.scrollY}, document.title); } diff --git a/manage/sort.js b/manage/sort.js index 430d5419..98f61709 100644 --- a/manage/sort.js +++ b/manage/sort.js @@ -2,195 +2,211 @@ /* global messageBox */ 'use strict'; -const sorterType = { - alpha: (a, b) => (a < b ? -1 : a === b ? 0 : 1), - number: (a, b) => a - b -}; +var sorter = (() => { -const tagData = { - title: { - text: t('genericTitle'), - parse: { - style: ({name}) => name, - entry: entry => entry.styleNameLowerCase, - }, - sorter: sorterType.alpha - }, - usercss: { - text: 'Usercss', - parse: { - style: ({style}) => (style.usercssData ? 0 : 1), - entry: entry => (entry.classList.contains('usercss') ? 0 : 1) - }, - sorter: sorterType.number - }, - disabled: { - text: '', // added as either "enabled" or "disabled" by the addSortOptions function - parse: { - style: ({style}) => (style.enabled ? 1 : 0), - entry: entry => (entry.classList.contains('enabled') ? 1 : 0) - }, - sorter: sorterType.number - }, - dateInstalled: { - text: t('dateInstalled'), - parse: { - style: ({style}) => style.installDate, - entry: entry => entry.dataset.installdate - }, - sorter: sorterType.number - }, - dateUpdated: { - text: t('dateUpdated'), - parse: { - style: ({style}) => style.updateDate, - entry: entry => entry.dataset.updatedate - }, - sorter: sorterType.number - } -}; - -// Adding (assumed) most commonly used ('title,asc' should always be first) -// whitespace before & after the comma is ignored -const sortSelectOptions = [ - '{groupAsc}', - 'title,asc', - 'dateInstalled,desc, title,asc', - 'dateInstalled,asc, title,asc', - 'dateUpdated,desc, title,asc', - 'dateUpdated,asc, title,asc', - 'usercss,asc, title,asc', - 'usercss,desc, title,asc', - 'disabled,asc, title,asc', - 'disabled,desc, title,asc', - 'disabled,desc, usercss,asc, title,asc', - '{groupDesc}', - 'title,desc', - 'usercss,asc, title,desc', - 'usercss,desc, title,desc', - 'disabled,desc, title,desc', - 'disabled,desc, usercss,asc, title,desc' -]; - -const sortByRegex = /\s*,\s*/; - -function addSortOptions() { - let container; - const select = $('#sort-select'); - const renderBin = document.createDocumentFragment(); - const option = $create('option'); - const optgroup = $create('optgroup'); - const meta = { - desc: ' \uea4d', - enabled: t('genericEnabledLabel'), - disabled: t('genericDisabledLabel'), - dateNew: ` (${t('sortDateNewestFirst')})`, - dateOld: ` (${t('sortDateOldestFirst')})`, - labelFirst: ` (${t('sortLabelFirst')})`, - labelLast: ` (${t('sortLabelLast')})`, - groupAsc: t('sortLabelTitleAsc'), - groupDesc: t('sortLabelTitleDesc') + const sorterType = { + alpha: (a, b) => (a < b ? -1 : a === b ? 0 : 1), + number: (a, b) => a - b }; - const optgroupRegex = /\{\w+\}/; - sortSelectOptions.forEach(sort => { - if (optgroupRegex.test(sort)) { - if (container) { - renderBin.appendChild(container); + + const tagData = { + title: { + text: t('genericTitle'), + parse: { + style: ({name}) => name, + entry: entry => entry.styleNameLowerCase, + }, + sorter: sorterType.alpha + }, + usercss: { + text: 'Usercss', + parse: { + style: ({style}) => (style.usercssData ? 0 : 1), + entry: entry => (entry.classList.contains('usercss') ? 0 : 1) + }, + sorter: sorterType.number + }, + disabled: { + text: '', // added as either "enabled" or "disabled" by the addSortOptions function + parse: { + style: ({style}) => (style.enabled ? 1 : 0), + entry: entry => (entry.classList.contains('enabled') ? 1 : 0) + }, + sorter: sorterType.number + }, + dateInstalled: { + text: t('dateInstalled'), + parse: { + style: ({style}) => style.installDate, + entry: entry => entry.dataset.installdate + }, + sorter: sorterType.number + }, + dateUpdated: { + text: t('dateUpdated'), + parse: { + style: ({style}) => style.updateDate, + entry: entry => entry.dataset.updatedate + }, + sorter: sorterType.number + } + }; + + // Adding (assumed) most commonly used ('title,asc' should always be first) + // whitespace before & after the comma is ignored + const sortSelectOptions = [ + '{groupAsc}', + 'title,asc', + 'dateInstalled,desc, title,asc', + 'dateInstalled,asc, title,asc', + 'dateUpdated,desc, title,asc', + 'dateUpdated,asc, title,asc', + 'usercss,asc, title,asc', + 'usercss,desc, title,asc', + 'disabled,asc, title,asc', + 'disabled,desc, title,asc', + 'disabled,desc, usercss,asc, title,asc', + '{groupDesc}', + 'title,desc', + 'usercss,asc, title,desc', + 'usercss,desc, title,desc', + 'disabled,desc, title,desc', + 'disabled,desc, usercss,asc, title,desc' + ]; + + const sortByRegex = /\s*,\s*/; + + function addSortOptions() { + let container; + const select = $('#sort-select'); + const renderBin = document.createDocumentFragment(); + const option = $create('option'); + const optgroup = $create('optgroup'); + const meta = { + desc: ' \uea4d', + enabled: t('genericEnabledLabel'), + disabled: t('genericDisabledLabel'), + dateNew: ` (${t('sortDateNewestFirst')})`, + dateOld: ` (${t('sortDateOldestFirst')})`, + labelFirst: ` (${t('sortLabelFirst')})`, + labelLast: ` (${t('sortLabelLast')})`, + groupAsc: t('sortLabelTitleAsc'), + groupDesc: t('sortLabelTitleDesc') + }; + const optgroupRegex = /\{\w+\}/; + sortSelectOptions.forEach(sort => { + if (optgroupRegex.test(sort)) { + if (container) { + renderBin.appendChild(container); + } + container = optgroup.cloneNode(); + container.label = meta[sort.substring(1, sort.length - 1)]; + return; } - container = optgroup.cloneNode(); - container.label = meta[sort.substring(1, sort.length - 1)]; - return; + let lastTag = ''; + const opt = option.cloneNode(); + opt.textContent = sort.split(sortByRegex).reduce((acc, val) => { + if (tagData[val]) { + lastTag = val; + return acc + (acc !== '' ? ' + ' : '') + tagData[val].text; + } + if (lastTag.indexOf('date') > -1) return acc + meta[val === 'desc' ? 'dateNew' : 'dateOld']; + if (lastTag === 'disabled') return acc + meta[val === 'desc' ? 'enabled' : 'disabled'] + meta['labelFirst']; + if (lastTag !== 'title') return acc + meta[val === 'desc' ? 'labelLast' : 'labelFirst']; + return acc + (meta[val] || ''); + }, ''); + opt.value = sort; + container.appendChild(opt); + }); + renderBin.appendChild(container); + select.appendChild(renderBin); + select.value = prefs.get('manage.newUI.sort'); + } + + function sortStyles({styles, parser}) { + if (!styles) { + styles = [...installed.children]; + parser = 'entry'; + } else { + parser = 'style'; } - let lastTag = ''; - const opt = option.cloneNode(); - opt.textContent = sort.split(sortByRegex).reduce((acc, val) => { - if (tagData[val]) { - lastTag = val; - return acc + (acc !== '' ? ' + ' : '') + tagData[val].text; + const sortBy = prefs.get('manage.newUI.sort').split(sortByRegex); // 'title,asc' + const len = sortBy.length; + return styles.sort((a, b) => { + let types, direction; + let result = 0; + let indx = 0; + // multi-sort + while (result === 0 && indx < len) { + types = tagData[sortBy[indx++]]; + direction = sortBy[indx++] === 'asc' ? 1 : -1; + result = types.sorter(types.parse[parser](a), types.parse[parser](b)) * direction; } - if (lastTag.indexOf('date') > -1) return acc + meta[val === 'desc' ? 'dateNew' : 'dateOld']; - if (lastTag === 'disabled') return acc + meta[val === 'desc' ? 'enabled' : 'disabled'] + meta['labelFirst']; - if (lastTag !== 'title') return acc + meta[val === 'desc' ? 'labelLast' : 'labelFirst']; - return acc + (meta[val] || ''); - }, ''); - opt.value = sort; - container.appendChild(opt); - }); - renderBin.appendChild(container); - select.appendChild(renderBin); - select.value = prefs.get('manage.newUI.sort'); -} - -function sortStyles({styles, parser}) { - if (!styles) { - styles = [...installed.children]; - parser = 'entry'; - } else { - parser = 'style'; + return result; + }); } - const sortBy = prefs.get('manage.newUI.sort').split(sortByRegex); // 'title,asc' - const len = sortBy.length; - return styles.sort((a, b) => { - let types, direction; - let result = 0; - let indx = 0; - // multi-sort - while (result === 0 && indx < len) { - types = tagData[sortBy[indx++]]; - direction = sortBy[indx++] === 'asc' ? 1 : -1; - result = types.sorter(types.parse[parser](a), types.parse[parser](b)) * direction; - } - return result; - }); -} -function manageSort(event) { - event.preventDefault(); - prefs.set('manage.newUI.sort', this.value); - debounce(updateSort); -} - -function updateSort() { - const renderBin = document.createDocumentFragment(); - const entries = sortStyles({parser: 'entry'}); - const isDiffSort = [...installed.children].find((entry, index) => entry.id !== entries[index].id); - let index = 0; - function moveEntries() { - const t0 = performance.now(); - let moved = 0; - while ( - index < entries.length && - (++moved < 10 || performance.now() - t0 < 10) - ) { - renderBin.appendChild(entries[index++]); + function updateSort() { + const renderBin = document.createDocumentFragment(); + const entries = sortStyles({parser: 'entry'}); + const isDiffSort = [...installed.children].find((entry, index) => entry.id !== entries[index].id); + let index = 0; + function moveEntries() { + const t0 = performance.now(); + let moved = 0; + while ( + index < entries.length && + (++moved < 10 || performance.now() - t0 < 10) + ) { + renderBin.appendChild(entries[index++]); + } + if (index < entries.length) { + requestAnimationFrame(moveEntries); + return; + } } - if (index < entries.length) { - requestAnimationFrame(moveEntries); - return; + if (isDiffSort !== undefined) { + moveEntries(); + installed.appendChild(renderBin); + updateStripes(); } } - if (isDiffSort !== undefined) { - moveEntries(); - installed.appendChild(renderBin); - updateStripes(); + + function manageSort(event) { + event.preventDefault(); + prefs.set('manage.newUI.sort', this.value); + debounce(updateSort); } -} -function showSortHelp(event) { - event.preventDefault(); - messageBox({ - className: 'help-text', - title: t('sortStylesHelpTitle'), - contents: - $create('div', - t('sortStylesHelp').split('\n').map(line => - $create('p', line))), - buttons: [t('confirmOK')], - }); -} + function showSortHelp(event) { + event.preventDefault(); + messageBox({ + className: 'help-text', + title: t('sortStylesHelpTitle'), + contents: + $create('div', + t('sortStylesHelp').split('\n').map(line => + $create('p', line))), + buttons: [t('confirmOK')], + }); + } -function sortInit() { - $('#sort-select').addEventListener('change', manageSort); - $('#sorter-help').onclick = showSortHelp; - addSortOptions(); -} + function sortInit() { + $('#sort-select').addEventListener('change', manageSort); + $('#sorter-help').onclick = showSortHelp; + addSortOptions(); + } + + function updateStripes() { + let index = 0; + [...installed.children].forEach(entry => { + const list = entry.classList; + if (!list.contains('hidden')) { + list.add(index % 2 ? 'odd' : 'even'); + list.remove(index++ % 2 ? 'even' : 'odd'); + } + }); + } + + return {sortInit, updateSort, sortStyles, updateStripes}; +}); diff --git a/manage/updater-ui.js b/manage/updater-ui.js index e276c097..3cf85c8c 100644 --- a/manage/updater-ui.js +++ b/manage/updater-ui.js @@ -1,6 +1,6 @@ /* global messageBox */ /* global ENTRY_ID_PREFIX, newUI */ -/* global filtersSelector, filterAndAppend, updateSort */ +/* global filtersSelector, filterAndAppend, sorter */ 'use strict'; onDOMready().then(() => { @@ -144,7 +144,7 @@ function reportUpdateState(state, style, details) { } if (filtersSelector.hide) { filterAndAppend({entry}); - debounce(updateSort); + debounce(sorter().updateSort); } }