Wire up some bulk actions
This commit is contained in:
parent
30a69f5bea
commit
a7026bdeee
56
manage.html
56
manage.html
|
@ -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
121
manage/bulk-actions.js
Normal 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')});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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')}";
|
||||
}
|
||||
`));
|
||||
},
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user