manage: toggle style without recreating its element

This commit is contained in:
tophf 2017-04-26 13:56:32 +03:00
parent 50ec32a7b2
commit 1e29504b9f
3 changed files with 118 additions and 34 deletions

View File

@ -335,6 +335,9 @@ Object.assign(document.body, {
this.ondragend(); this.ondragend();
if (event.dataTransfer.files.length) { if (event.dataTransfer.files.length) {
event.preventDefault(); event.preventDefault();
if ($('#onlyUpdates input').checked) {
$('#onlyUpdates input').click();
}
importFromFile({file: event.dataTransfer.files[0]}); importFromFile({file: event.dataTransfer.files[0]});
} }
}, },

View File

@ -495,11 +495,11 @@ input[id^="manage.newUI"] {
content: " (" attr(data-value) ")"; content: " (" attr(data-value) ")";
} }
#check-all-updates[disabled] { .update-in-progress #check-all-updates {
position: relative; position: relative;
} }
#check-all-updates[disabled] #update-progress { .update-in-progress #update-progress {
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;

145
manage.js
View File

@ -1,4 +1,4 @@
/* global messageBox */ /* global messageBox, getStyleWithNoCode */
'use strict'; 'use strict';
let installed; let installed;
@ -85,6 +85,9 @@ function initGlobalEvents() {
$$('[data-filter]').forEach(el => { $$('[data-filter]').forEach(el => {
el.onchange = handleEvent.filterOnChange; el.onchange = handleEvent.filterOnChange;
if (el.closest('.hidden')) {
el.checked = false;
}
}); });
handleEvent.filterOnChange({forceRefilter: true}); handleEvent.filterOnChange({forceRefilter: true});
@ -164,6 +167,7 @@ function createStyleElement({style, name}) {
entry.id = 'style-' + style.id; entry.id = 'style-' + style.id;
entry.styleId = style.id; entry.styleId = style.id;
entry.styleNameLowerCase = name || style.name.toLocaleLowerCase(); entry.styleNameLowerCase = name || style.name.toLocaleLowerCase();
entry.styleMeta = getStyleWithNoCode(style);
entry.className = parts.entryClassBase + ' ' + entry.className = parts.entryClassBase + ' ' +
(style.enabled ? 'enabled' : 'disabled') + (style.enabled ? 'enabled' : 'disabled') +
(style.updateUrl ? ' updatable' : ''); (style.updateUrl ? ' updatable' : '');
@ -381,27 +385,53 @@ Object.assign(handleEvent, {
}); });
function handleUpdate(style, {reason} = {}) { function handleUpdate(style, {reason, method} = {}) {
const entry = createStyleElement({style}); let entry;
const oldEntry = $('#style-' + style.id); let oldEntry = $('#style-' + style.id);
if (oldEntry && method == 'styleUpdated') {
handleToggledOrCodeEdited();
}
entry = entry || createStyleElement({style});
if (oldEntry) { if (oldEntry) {
if (oldEntry.styleNameLowerCase == entry.styleNameLowerCase) { if (oldEntry.styleNameLowerCase == entry.styleNameLowerCase) {
installed.replaceChild(entry, oldEntry); installed.replaceChild(entry, oldEntry);
} else { } else {
oldEntry.remove(); oldEntry.remove();
} }
if (reason == 'update') { }
entry.classList.add('update-done'); if (reason == 'update' && entry.matches('.updatable')) {
entry.classList.remove('can-update', 'updatable'); handleUpdateInstalled();
$('.update-note', entry).textContent = t('updateCompleted');
renderUpdatesOnlyFilter();
}
} }
filterAndAppend({entry}); filterAndAppend({entry});
if (!entry.classList.contains('hidden') && reason != 'import') { if (!entry.matches('.hidden') && reason != 'import') {
animateElement(entry, {className: 'highlight'}); animateElement(entry, {className: 'highlight'});
scrollElementIntoView(entry); scrollElementIntoView(entry);
} }
function handleToggledOrCodeEdited() {
const newStyleMeta = getStyleWithNoCode(style);
const diff = objectDiff(oldEntry.styleMeta, newStyleMeta);
if (diff.length == 0) {
// only code was modified
entry = oldEntry;
oldEntry = null;
}
if (diff.length == 1 && diff[0].key == 'enabled') {
oldEntry.classList.toggle('enabled', style.enabled);
oldEntry.classList.toggle('disabled', !style.enabled);
$$('.checker', oldEntry).forEach(el => (el.checked = style.enabled));
oldEntry.styleMeta = newStyleMeta;
entry = oldEntry;
oldEntry = null;
}
}
function handleUpdateInstalled() {
entry.classList.add('update-done');
entry.classList.remove('can-update', 'updatable');
$('.update-note', entry).textContent = t('updateCompleted');
renderUpdatesOnlyFilter();
}
} }
@ -505,20 +535,22 @@ function applyUpdateAll() {
function checkUpdateAll() { function checkUpdateAll() {
const ignoreDigest = this && this.id == 'check-all-updates-force'; document.body.classList.add('update-in-progress');
$('#check-all-updates').disabled = true; $('#check-all-updates').disabled = true;
$('#check-all-updates-force').classList.add('hidden'); $('#check-all-updates-force').classList.add('hidden');
$('#apply-all-updates').classList.add('hidden'); $('#apply-all-updates').classList.add('hidden');
$('#update-all-no-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 total = 0;
let checked = 0; let checked = 0;
let skippedEdited = 0; let skippedEdited = 0;
let updated = 0; let updated = 0;
$$('.updatable:not(.can-update)' + (ignoreDigest ? '' : ':not(.update-problem)')) BG.updater.checkAllStyles({observer, save: false, ignoreDigest}).then(done);
.map(el => checkUpdate(el, {single: false}));
BG.updater.checkAllStyles({observer, save: false, ignoreDigest});
function observer(state, value, details) { function observer(state, value, details) {
switch (state) { switch (state) {
@ -539,21 +571,23 @@ function checkUpdateAll() {
} }
reportUpdateState(state, value, details); reportUpdateState(state, value, details);
break; break;
case BG.updater.DONE:
$('#check-all-updates').disabled = false;
$('#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);
}
return;
} }
const progress = $('#update-progress'); const progress = $('#update-progress');
const maxWidth = progress.parentElement.clientWidth; const maxWidth = progress.parentElement.clientWidth;
progress.style.width = Math.round(checked / total * maxWidth) + 'px'; 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);
}
}
} }
@ -605,7 +639,7 @@ function reportUpdateState(state, style, details) {
$('.update-note', entry).textContent = message; $('.update-note', entry).textContent = message;
$('.check-update', entry).title = newUI.enabled ? message : ''; $('.check-update', entry).title = newUI.enabled ? message : '';
$('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate'); $('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate');
if (!$('#check-all-updates').disabled) { if (!document.body.classList.contains('update-in-progress')) {
// this is a single update job so we can decide whether to hide the filter // this is a single update job so we can decide whether to hide the filter
renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')}); renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')});
} }
@ -706,8 +740,7 @@ function searchStyles({immediately, container}) {
function filterAndAppend({entry, container}) { function filterAndAppend({entry, container}) {
if (!container) { if (!container) {
container = document.createElement('div'); container = [entry];
container.appendChild(entry);
// reverse the visibility, otherwise reapplyFilter will see no need to work // reverse the visibility, otherwise reapplyFilter will see no need to work
if (!filtersSelector.hide || !entry.matches(filtersSelector.hide)) { if (!filtersSelector.hide || !entry.matches(filtersSelector.hide)) {
entry.classList.add('hidden'); entry.classList.add('hidden');
@ -722,7 +755,7 @@ function filterAndAppend({entry, container}) {
function reapplyFilter(container = installed) { function reapplyFilter(container = installed) {
// A: show // A: show
const toUnhide = filtersSelector.hide ? $$(filtersSelector.unhide, container) : container; const toUnhide = filtersSelector.hide ? filterContainer({hide: false}) : container;
// showStyles() is building the page and no filters are active // showStyles() is building the page and no filters are active
if (toUnhide instanceof DocumentFragment) { if (toUnhide instanceof DocumentFragment) {
installed.appendChild(toUnhide); installed.appendChild(toUnhide);
@ -743,7 +776,7 @@ function reapplyFilter(container = installed) {
} }
} }
// B: hide // B: hide
const toHide = filtersSelector.hide ? $$(filtersSelector.hide, container) : []; const toHide = filtersSelector.hide ? filterContainer({hide: true}) : [];
if (!toHide.length) { if (!toHide.length) {
return; return;
} }
@ -757,7 +790,12 @@ function reapplyFilter(container = installed) {
for (const entry of toHide) { for (const entry of toHide) {
installed.appendChild(entry); installed.appendChild(entry);
} }
installed.insertBefore(container, $('.entry.hidden')); const firstHidden = $('.entry.hidden');
if (container.forEach) {
container.forEach(el => installed.insertBefore(el, firstHidden));
} else {
installed.insertBefore(container, firstHidden);
}
return; return;
} }
// normal filtering of the page or a single-element job from handleUpdate() // normal filtering of the page or a single-element job from handleUpdate()
@ -771,8 +809,17 @@ function reapplyFilter(container = installed) {
} }
return; return;
function filterContainer({hide}) {
const selector = filtersSelector[hide ? 'hide' : 'unhide'];
if (container.filter) {
return container.filter(el => el.matches(selector));
} else {
return $$(selector, container);
}
}
function shuffle(fullPass) { function shuffle(fullPass) {
if (fullPass) { if (fullPass && !document.body.classList.contains('update-in-progress')) {
$('#check-all-updates').disabled = !$('.updatable:not(.can-update)'); $('#check-all-updates').disabled = !$('.updatable:not(.can-update)');
} }
// 1. skip the visible group on top // 1. skip the visible group on top
@ -862,3 +909,37 @@ function reapplyFilter(container = installed) {
function rememberScrollPosition() { function rememberScrollPosition() {
history.replaceState({scrollY: window.scrollY}, document.title); history.replaceState({scrollY: window.scrollY}, document.title);
} }
function objectDiff(first, second, path = '') {
const diff = [];
for (const key in first) {
const a = first[key];
const b = second[key];
if (a === b) {
continue;
}
if (b === undefined) {
diff.push({path, key, values: [a], type: 'removed'});
continue;
}
if (a && typeof a.filter == 'function' && b && typeof b.filter == 'function') {
if (a.length != b.length
|| a.some((el, i) => !el || typeof el != 'object' ? el != b[i]
: objectDiff(el, b[i], path + key + '[' + i + '].').length)
) {
diff.push({path, key, values: [a, b], type: 'changed'});
}
} else if (typeof a == 'object' && typeof b == 'object') {
diff.push(...objectDiff(a, b, path + key + '.'));
} else {
diff.push({path, key, values: [a, b], type: 'changed'});
}
}
for (const key in second) {
if (!(key in first)) {
diff.push({path, key, values: [second[key]], type: 'added'});
}
}
return diff;
}