Wire up some bulk actions

This commit is contained in:
Rob Garrison 2018-12-30 23:45:14 -06:00
parent 30a69f5bea
commit a7026bdeee
8 changed files with 204 additions and 103 deletions

View File

@ -195,6 +195,7 @@
<script src="vendor/semver-bundle/semver.js"></script>
<script src="manage/manage-ui.js"></script>
<script src="manage/manage-actions.js"></script>
<script src="manage/bulk-actions.js"></script>
<script data-src="manage/draggable.js"></script>
<script data-src="vendor-overwrites/colorpicker/colorconverter.js"></script>
@ -429,21 +430,32 @@
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</span>
<button id="bulk-actions-apply" i18n-text="bulkActionsApply"></button>
<button id="bulk-actions-apply" i18n-text="bulkActionsApply">
<span id="update-progress"></span>
</button>
<span id="bulk-info">
<!-- Bulk update -->
<span data-bulk="update">
<button id="apply-all-updates" class="hidden" i18n-text="applyAllUpdates"></button>
<span id="update-all-no-updates" class="hidden" i18n-text="updateAllCheckSucceededNoUpdate"></span>
<button id="check-all-updates-force" class="hidden" i18n-text="checkAllUpdatesForce"></button>
</span>
<div class="dropdown export hidden">
<button class="dropbtn">
<span>Export</span>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</button>
<!-- Bulk export -->
<span data-bulk="export" class="dropdown export hidden">
Export to:
<span class="select-resizer">
<select id="manage.onlyEnabled.invert">
<option id="file-all-styles" i18n-text="bckpInstStyles"></option>
<option id="sync-dropbox-export" i18n-text="syncDropboxStyles"></option>
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</span>
</span>
</span>
<div class="dropdown-content">
<a href="#" id="file-all-styles" i18n-text="bckpInstStyles"></a>
<a href="#" id="sync-dropbox-export" i18n-text="syncDropboxStyles"></a>
</div>
</div>
<button id="apply-all-updates" class="hidden" i18n-text="applyAllUpdates"></button>
<svg class="svg-icon busy hidden" viewBox="0 0 24 24">
<path d="M12 23h-1v-6.57A5.97 5.97 0 0 1 7 18c-3.25 0-6-2.75-6-6v-1h6.57A5.97 5.97 0 0 1 6
7c0-3.25 2.75-6 6-6h1v6.57A5.97 5.97 0 0 1 17 6c3.25 0 6 2.75 6 6v1h-6.57A5.97 5.97 0 0 1
@ -501,13 +513,6 @@
</header>
</div>
<div style="display: none">
<button id="apply-all-updates"></button>
<button id="check-all-updates"></button>
<button id="check-all-updates-force"></button>
<button id="unfile-all-styles"></button>
</div>
<!-- Applies to config, can't put this in a template because these inputs are bound to subscribed prefs -->
<div class="hidden">
<div id="appliesToConfig">
@ -543,20 +548,7 @@
<div class="settings-column">
<div id="style-actions">
<div id="update-check">
<button id="check-all-updates" i18n-text="checkAllUpdates"><span id="update-progress"></span></button>
<a href="#" id="update-history" i18n-title="genericHistoryLabel" tabindex="0">
<svg class="svg-icon" viewBox="0 0 20 20" i18n-alt="helpAlt">
<path d="M13,7H7V6h6Zm6,6.5A5.5,5.5,0,0,1,8.61,16H4V3H16V8.61A5.5,5.5,0,0,1,19,13.5ZM8,14c0-.16,0-.84,0-1H7V12H8.21a5.46,5.46,0,0,1,.39-1H7V10H9.26a5.55,5.55,0,0,1,1.09-1H7V8h7V5H6v9Zm10-.5A4.5,4.5,0,1,0,13.5,18,4.5,4.5,0,0,0,18,13.5ZM14,13V10H13v4h4V13Z"/>
</svg>
</a>
</div>
<div id="update-all">
<button id="apply-all-updates" class="hidden" i18n-text="applyAllUpdates"></button>
<span id="update-all-no-updates" class="hidden" i18n-text="updateAllCheckSucceededNoUpdate"></span>
<button id="check-all-updates-force" class="hidden" i18n-text="checkAllUpdatesForce"></button>
</div>
<div id="add-style-wrapper">
<a href="edit.html">

121
manage/bulk-actions.js Normal file
View File

@ -0,0 +1,121 @@
/* global $ $$ handleEvent installed exportToFile checkUpdateAll */
/* exported bulk */
'use strict';
const bulk = {
init: () => {
document.addEventListener('change', bulk.updateBulkFilters);
$('#bulk-actions-select').onchange = bulk.handleSelect;
$('#bulk-actions-apply').onclick = bulk.handleApply;
},
handleSelect: event => {
event.preventDefault();
$$('[data-bulk]').forEach(el => el.classList.add('hidden'));
console.log('select', this.value)
switch (event.target.value) {
case 'enable':
break;
case 'disable':
break;
case 'export':
console.log('got here')
$('[data-bulk="export"]').classList.remove('hidden');
break;
case 'update':
$('[data-bulk="update"]').classList.remove('hidden');
break;
case 'reset':
break;
case 'delete':
break;
}
},
handleApply: event => {
event.preventDefault();
let styles;
const action = $('#bulk-actions-select').value;
const entries = $$('.entry-filter-toggle:checked').map(el => el.closest('.entry'));
switch (action) {
case 'enable':
case 'disable': {
const isEnabled = action === 'enable';
entries.forEach(entry => {
const box = $('.entry-state-toggle', entry);
entry.classList.toggle('enable', isEnabled);
box.checked = isEnabled;
handleEvent.toggle.call(box, event, entry);
});
break;
}
case 'export':
styles = entries.map(entry => entry.styleMeta);
exportToFile(styles);
break;
case 'update':
checkUpdateAll(entries); // TO DO
break;
case 'reset':
break;
case 'delete':
styles = entries.reduce((acc, entry) => {
const style = entry.styleMeta;
acc[style.name] = style.id;
return acc;
}, {});
bulk.deleteBulk(event, styles);
$('#toggle-all-filters').checked = false;
break;
}
$('#bulk-actions-select').value = '';
},
updateBulkFilters: ({target}) => {
// total is undefined until initialized
if (installed.dataset.total) {
// ignore filter checkboxes
if (target.type === 'checkbox' && !target.dataset.filter && target.closest('#tools-wrapper, .entry')) {
handleEvent.toggleBulkActions({hidden: false});
const bulk = $('#toggle-all-filters');
const state = target.checked;
const visibleEntries = $$('.entry-filter-toggle')
.filter(entry => !entry.closest('.entry').classList.contains('hidden'));
bulk.indeterminate = false;
if (target === bulk) {
visibleEntries.forEach(entry => {
entry.checked = state;
});
} else {
if (visibleEntries.length === visibleEntries.filter(entry => entry.checked === state).length) {
bulk.checked = state;
} else {
bulk.checked = false;
bulk.indeterminate = true;
}
}
}
const count = $$('.entry-filter-toggle').filter(entry => entry.checked).length;
$('#bulk-filter-count').textContent = count || '';
}
},
deleteBulk: (event, styles) => {
messageBox({
title: t('deleteStyleConfirm'),
contents: Object.keys(styles).join(', '),
className: 'danger center',
buttons: [t('confirmDelete'), t('confirmCancel')],
})
.then(({button}) => {
if (button === 0) {
Object.values(styles).forEach(id => API.deleteStyle(id));
installed.dataset.total -= Object.keys(styles).length;
bulk.updateBulkFilters({target: $('#toggle-all-filters')});
}
});
}
}

View File

@ -294,38 +294,36 @@ function importFromString(jsonString) {
}
function exportToFile() {
API.getAllStyles().then(styles => {
// https://crbug.com/714373
document.documentElement.appendChild(
$create('iframe', {
onload() {
const text = JSON.stringify(styles, null, '\t');
const type = 'application/json';
this.onload = null;
this.contentDocument.body.appendChild(
$create('a', {
href: URL.createObjectURL(new Blob([text], {type})),
download: generateFileName(),
type,
})
).dispatchEvent(new MouseEvent('click'));
},
// we can't use display:none as some browsers are ignoring such iframes
style: `
all: unset;
width: 0;
height: 0;
position: fixed;
opacity: 0;
border: none;
`.replace(/;/g, '!important;'),
})
);
function exportToFile(styles) {
// https://crbug.com/714373
document.documentElement.appendChild(
$create('iframe', {
onload() {
const text = JSON.stringify(styles, null, '\t');
const type = 'application/json';
this.onload = null;
this.contentDocument.body.appendChild(
$create('a', {
href: URL.createObjectURL(new Blob([text], {type})),
download: generateFileName(),
type,
})
).dispatchEvent(new MouseEvent('click'));
},
// we can't use display:none as some browsers are ignoring such iframes
style: `
all: unset;
width: 0;
height: 0;
position: fixed;
opacity: 0;
border: none;
`.replace(/;/g, '!important;'),
})
);
// we don't remove the iframe or the object URL because the browser may show
// a download dialog and we don't know how long it'll take until the user confirms it
// (some browsers like Vivaldi can't download if we revoke the URL)
});
function generateFileName() {
const today = new Date();

View File

@ -89,8 +89,6 @@ function initGlobalEvents() {
}
});
document.addEventListener('change', updateBulkFilters);
$$('[data-toggle-on-click]').forEach(el => {
// dataset on SVG doesn't work in Chrome 49-??, works in 57+
const target = $(el.getAttribute('data-toggle-on-click'));
@ -110,6 +108,7 @@ function initGlobalEvents() {
// N.B. triggers existing onchange listeners
setupLivePrefs();
bulk.init();
sorter.init();
prefs.subscribe([
@ -467,34 +466,6 @@ function onVisibilityChange() {
}
}
function updateBulkFilters({target}) {
// total is undefined until initialized
if (!installed.dataset.total) return;
// ignore filter checkboxes
if (target.type === 'checkbox' && !target.dataset.filter && target.closest('#tools-wrapper, .entry')) {
handleEvent.toggleBulkActions({hidden: false});
const bulk = $('#toggle-all-filters');
const state = target.checked;
const visibleEntries = $$('.entry-filter-toggle')
.filter(entry => !entry.closest('.entry').classList.contains('hidden'));
bulk.indeterminate = false;
if (target === bulk) {
visibleEntries.forEach(entry => {
entry.checked = state;
});
} else {
if (visibleEntries.length === visibleEntries.filter(entry => entry.checked === state).length) {
bulk.checked = state;
} else {
bulk.checked = false;
bulk.indeterminate = true;
}
}
const count = $$('.entry-filter-toggle').filter(entry => entry.checked).length;
$('#bulk-filter-count').textContent = count || '';
}
}
function removeSelection() {
const sel = window.getSelection ? window.getSelection() : document.selection;
if (sel) {

View File

@ -33,15 +33,12 @@ const UI = {
// translate CSS manually
document.head.appendChild($create('style', `
.disabled h2::after {
content: "${t('genericDisabledLabel')}";
body.all-styles-hidden-by-filters #installed:after {
content: "${t('filteredStylesAllHidden')}";
}
#update-all-no-updates[data-skipped-edited="true"]::after {
content: " ${t('updateAllCheckSucceededSomeEdited')}";
}
body.all-styles-hidden-by-filters::after {
content: "${t('filteredStylesAllHidden')}";
}
`));
},

View File

@ -73,6 +73,7 @@ a:hover {
.entry-actions > a,
.bulk-actions-select-wrapper,
#bulk-actions-apply {
position: relative;
margin-left: 2px;
}
@ -157,6 +158,13 @@ a:hover {
margin-bottom: 10px;
}
body.all-styles-hidden-by-filters #installed:after {
font-size: 20px;
position: relative;
top: 20px;
left: 30px;
}
.entry-header {
margin: 0;
padding: 0 4px;
@ -451,18 +459,20 @@ a svg, .svg-icon.sort {
fill: red;
}
.updater-icons > :not(.check-update):after {
.updater-icons > .update:after {
content: attr(data-title);
border: 1px solid gold;
background-color: goldenrod;
}
.entry-actions .entry-delete:after,
.update-problem .check-update:after {
background-color: red;
border: 1px solid #d40000;
background-color: #d40000;
border: 1px solid red;
color: white;
}
.update-problem.showTip {
animation: fadeout 10s;
animation-fill-mode: both;

View File

@ -2,6 +2,8 @@
--tooltip-bkgd: #333;
--tooltip-border: #555;
--tooltip-text: #fff;
--tooltip-error: #d40000;
--tooltip-warn: goldenrod;
}
[data-title] {
@ -150,3 +152,13 @@
right: -7px;
top: 50%
}
.can-update .updater-icons .update:before {
border-right-color: var(--tooltip-warn);
}
.entry-actions .entry-delete:before,
.update-problem .check-update:before {
border-right-color: var(--tooltip-error);
}

View File

@ -4,7 +4,7 @@
'use strict';
onDOMready().then(() => {
$('#check-all-updates').onclick = checkUpdateAll;
// $('#check-all-updates').onclick = checkUpdateAll;
$('#check-all-updates-force').onclick = checkUpdateAll;
$('#apply-all-updates').onclick = applyUpdateAll;
$('#update-history').onclick = showUpdateHistory;
@ -29,11 +29,11 @@ function applyUpdateAll() {
function checkUpdateAll() {
document.body.classList.add('update-in-progress');
const btnCheck = $('#check-all-updates');
// const btnCheck = $('#check-all-updates');
const btnCheckForce = $('#check-all-updates-force');
const btnApply = $('#apply-all-updates');
const noUpdates = $('#update-all-no-updates');
btnCheck.disabled = true;
// btnCheck.disabled = true;
btnCheckForce.classList.add('hidden');
btnApply.classList.add('hidden');
noUpdates.classList.add('hidden');
@ -82,7 +82,7 @@ function checkUpdateAll() {
port.onMessage.removeListener(observer);
document.body.classList.remove('update-in-progress');
btnCheck.disabled = total === 0;
// btnCheck.disabled = total === 0;
btnApply.disabled = false;
renderUpdatesOnlyFilter({check: updated + skippedEdited > 0});
if (!updated) {