/* global messageBox */ /* global ENTRY_ID_PREFIX, newUI */ /* global filtersSelector, filterAndAppend, sorter */ 'use strict'; onDOMready().then(() => { $('#check-all-updates').onclick = checkUpdateAll; $('#check-all-updates-force').onclick = checkUpdateAll; $('#apply-all-updates').onclick = applyUpdateAll; $('#update-history').onclick = showUpdateHistory; }); function applyUpdateAll() { const btnApply = $('#apply-all-updates'); btnApply.disabled = true; setTimeout(() => { btnApply.classList.add('hidden'); btnApply.disabled = false; renderUpdatesOnlyFilter({show: false}); }, 1000); $$('.can-update .update').forEach(button => { scrollElementIntoView(button); button.click(); }); } function checkUpdateAll() { document.body.classList.add('update-in-progress'); $('#check-all-updates').disabled = true; $('#check-all-updates-force').classList.add('hidden'); $('#apply-all-updates').classList.add('hidden'); $('#update-all-no-updates').classList.add('hidden'); const ignoreDigest = this && this.id === 'check-all-updates-force'; $$('.updatable:not(.can-update)' + (ignoreDigest ? '' : ':not(.update-problem)')) .map(el => checkUpdate(el, {single: false})); let total = 0; let checked = 0; let skippedEdited = 0; let updated = 0; BG.updater.checkAllStyles({observer, save: false, ignoreDigest}).then(done); function observer(state, value, details) { switch (state) { case BG.updater.COUNT: total = value; break; case BG.updater.UPDATED: if (++updated === 1) { $('#apply-all-updates').disabled = true; $('#apply-all-updates').classList.remove('hidden'); } $('#apply-all-updates').dataset.value = updated; // fallthrough case BG.updater.SKIPPED: checked++; if (details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED) { skippedEdited++; } reportUpdateState(state, value, details); break; } const progress = $('#update-progress'); const maxWidth = progress.parentElement.clientWidth; progress.style.width = Math.round(checked / total * maxWidth) + 'px'; } function done() { document.body.classList.remove('update-in-progress'); $('#check-all-updates').disabled = total === 0; $('#apply-all-updates').disabled = false; renderUpdatesOnlyFilter({check: updated + skippedEdited > 0}); if (!updated) { $('#update-all-no-updates').dataset.skippedEdited = skippedEdited > 0; $('#update-all-no-updates').classList.remove('hidden'); $('#check-all-updates-force').classList.toggle('hidden', skippedEdited === 0); } } } function checkUpdate(entry, {single = true} = {}) { $('.update-note', entry).textContent = t('checkingForUpdate'); $('.check-update', entry).title = ''; if (single) { BG.updater.checkStyle({ save: false, ignoreDigest: entry.classList.contains('update-problem'), style: BG.cachedStyles.byId.get(entry.styleId), observer: reportUpdateState, }); } entry.classList.remove('checking-update', 'no-update', 'update-problem'); entry.classList.add('checking-update'); } function reportUpdateState(state, style, details) { const entry = $(ENTRY_ID_PREFIX + style.id); const newClasses = new Map([ /* When a style is updated/installed, handleUpdateInstalled() clears "updatable" and sets "update-done" class (optionally "install-done"). If you don't close the manager and the style is changed remotely, checking for updates would find an update so we need to ensure the entry is "updatable" */ ['updatable', true], // falsy = remove ['checking-update', 0], ['update-done', 0], ['install-done', 0], ['no-update', 0], ['update-problem', 0], ]); switch (state) { case BG.updater.UPDATED: newClasses.set('can-update', true); entry.updatedCode = style; $('.update-note', entry).textContent = ''; $('#only-updates').classList.remove('hidden'); break; case BG.updater.SKIPPED: { if (entry.classList.contains('can-update')) { break; } const same = ( details === BG.updater.SAME_MD5 || details === BG.updater.SAME_CODE || details === BG.updater.SAME_VERSION ); const edited = details === BG.updater.EDITED || details === BG.updater.MAYBE_EDITED; entry.dataset.details = details; if (!details) { details = t('updateCheckFailServerUnreachable') + '\n' + style.updateUrl; } else if (typeof details === 'number') { details = t('updateCheckFailBadResponseCode', [details]) + '\n' + style.updateUrl; } else if (details === BG.updater.EDITED) { details = t('updateCheckSkippedLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); } else if (details === BG.updater.MAYBE_EDITED) { details = t('updateCheckSkippedMaybeLocallyEdited') + '\n' + t('updateCheckManualUpdateHint'); } const message = same ? t('updateCheckSucceededNoUpdate') : details; newClasses.set('no-update', true); newClasses.set('update-problem', !same); $('.update-note', entry).textContent = message; $('.check-update', entry).title = newUI.enabled ? message : ''; $('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate'); if (!document.body.classList.contains('update-in-progress')) { // this is a single update job so we can decide whether to hide the filter renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')}); } } } // construct a new className: // 1. add all truthy newClasses // 2. remove falsy newClasses // 3. keep existing classes otherwise const classes = new Map([...entry.classList.values()].map(cls => [cls, true])); [...newClasses.entries()].forEach(([cls, newState]) => classes.set(cls, newState)); const className = [...classes.entries()].filter(([, state]) => state).map(([cls]) => cls).join(' '); if (className !== entry.className) entry.className = className; if (filtersSelector.hide) { filterAndAppend({entry}); sorter.update(); } } function renderUpdatesOnlyFilter({show, check} = {}) { const numUpdatable = $$('.can-update').length; const mightUpdate = numUpdatable > 0 || $('.update-problem'); const checkbox = $('#only-updates input'); show = show !== undefined ? show : mightUpdate; check = check !== undefined ? show && check : checkbox.checked && mightUpdate; $('#only-updates').classList.toggle('hidden', !show); checkbox.checked = check && show; checkbox.dispatchEvent(new Event('change')); const btnApply = $('#apply-all-updates'); if (!btnApply.matches('.hidden')) { if (numUpdatable > 0) { btnApply.dataset.value = numUpdatable; } else { btnApply.classList.add('hidden'); } } } function showUpdateHistory(event) { event.preventDefault(); const log = $create('.update-history-log'); let logText, scroller, toggler; let deleted = false; BG.chromeLocal.getValue('updateLog').then((lines = []) => { logText = lines.join('\n'); messageBox({ title: t('updateCheckHistory'), contents: log, blockScroll: true, buttons: [ t('confirmOK'), logText && {textContent: t('confirmDelete'), onclick: deleteHistory}, ], onshow: logText && (() => { scroller = $('#message-box-contents'); scroller.tabIndex = 0; setTimeout(() => scroller.focus()); scrollToBottom(); $('#message-box-buttons button').insertAdjacentElement('afterend', // TODO: add a global class for our labels // TODO: add a