diff --git a/background.js b/background.js index 0d1e7654..63d0924a 100644 --- a/background.js +++ b/background.js @@ -206,11 +206,13 @@ chrome.storage.local.get('version', prefs => { }); // after the extension was enabled or just installed +// reason = 'update' is needed becase our listener processes only 'update' events injectContentScripts({reason: 'update'}); + // after an actual update chrome.runtime.onInstalled.addListener(injectContentScripts); -function injectContentScripts({reason, previousVersion, id, checkFirst} = {}) { +function injectContentScripts({reason, previousVersion, id} = {}) { // reason: install, update, chrome_update, shared_module_update // the "install" case is ignored because it was already handled by explicit invocation of this function if (!/update/.test(reason)) { diff --git a/backup/fileSaveLoad.js b/backup/fileSaveLoad.js index cf337ac1..2c4736a7 100644 --- a/backup/fileSaveLoad.js +++ b/backup/fileSaveLoad.js @@ -5,37 +5,70 @@ var STYLISH_DUMP_FILE_EXT = '.txt'; var STYLISH_DUMPFILE_EXTENSION = '.json'; var STYLISH_DEFAULT_SAVE_NAME = 'stylus-mm-dd-yyyy' + STYLISH_DUMP_FILE_EXT; -/** - * !!works only when page has representation - backgound page won't work - * - * opens open file dialog, - * gets selected file, - * gets it's path, - * gets content of it by ajax - */ -function loadFromFile (formatToFilter) { - return new Promise(function (resolve) { - var fileInput = document.createElement('input'); - fileInput.style = 'display: none;'; +function importFromFile({fileTypeFilter, file} = {}) { + return new Promise(resolve => { + const fileInput = document.createElement('input'); + if (file) { + readFile(); + return; + } + fileInput.style.display = 'none'; fileInput.type = 'file'; - fileInput.accept = formatToFilter || STYLISH_DUMP_FILE_EXT; + fileInput.accept = fileTypeFilter || STYLISH_DUMP_FILE_EXT; fileInput.acceptCharset = 'utf-8'; document.body.appendChild(fileInput); fileInput.initialValue = fileInput.value; - function changeHandler() { - if (fileInput.value !== fileInput.initialValue) { - var fReader = new FileReader(); - fReader.onloadend = function (event) { - fileInput.removeEventListener('change', changeHandler); + fileInput.onchange = readFile; + fileInput.click(); + + function readFile() { + if (file || fileInput.value !== fileInput.initialValue) { + file = file || fileInput.files[0]; + if (file.size > 100*1000*1000) { + console.warn("100MB backup? I don't believe you."); + importFromString('').then(resolve); + return; + } + document.body.style.cursor = 'wait'; + const fReader = new FileReader(); + fReader.onloadend = event => { fileInput.remove(); - resolve(event.target.result); + importFromString(event.target.result).then(numStyles => { + document.body.style.cursor = ''; + resolve(numStyles); + }); }; - fReader.readAsText(fileInput.files[0], 'utf-8'); + fReader.readAsText(file, 'utf-8'); + } + } + }); +} + +function importFromString(jsonString) { + const json = runTryCatch(() => Array.from(JSON.parse(jsonString))) || []; + const numStyles = json.length; + + if (numStyles) { + invalidateCache(true); + } + + return new Promise(resolve => { + proceed(); + function proceed() { + const nextStyle = json.shift(); + if (nextStyle) { + saveStyle(nextStyle, {notify: false}).then(style => { + handleUpdate(style); + setTimeout(proceed, 0); + }); + } else { + refreshAllTabs().then(() => { + setTimeout(alert, 100, numStyles + ' styles installed/updated'); + resolve(numStyles); + }); } } - fileInput.addEventListener('change', changeHandler); - fileInput.click(); }); } @@ -53,7 +86,7 @@ function generateFileName() { return 'stylus-' + today + STYLISH_DUMPFILE_EXTENSION; } -document.getElementById('file-all-styles').addEventListener('click', function () { +document.getElementById('file-all-styles').onclick = () => { getStyles({}, function (styles) { let text = JSON.stringify(styles, null, '\t'); let fileName = generateFileName() || STYLISH_DEFAULT_SAVE_NAME; @@ -69,28 +102,46 @@ document.getElementById('file-all-styles').addEventListener('click', function () a.dispatchEvent(new MouseEvent('click')); }); }); -}); +}; -document.getElementById('unfile-all-styles').addEventListener('click', () => { - loadFromFile(STYLISH_DUMPFILE_EXTENSION).then(rawText => { - const json = JSON.parse(rawText); - const numStyles = json.length; - invalidateCache(true); - proceed(); +document.getElementById('unfile-all-styles').onclick = () => { + importFromFile({fileTypeFilter: STYLISH_DUMPFILE_EXTENSION}); +}; - function proceed() { - const nextStyle = json.shift(); - if (nextStyle) { - saveStyle(nextStyle, {notify: false}).then(style => { - handleUpdate(style); - setTimeout(proceed, 0); - }); - } else { - refreshAllTabs().then(() => { - setTimeout(alert, 100, numStyles + ' styles installed/updated'); - }); - } +const dropTarget = Object.assign(document.body, { + ondragover: event => { + const hasFiles = event.dataTransfer.types.includes('Files'); + event.dataTransfer.dropEffect = hasFiles || event.target.type == 'search' ? 'copy' : 'none'; + dropTarget.classList.toggle('dropzone', hasFiles); + if (hasFiles) { + event.preventDefault(); + clearTimeout(dropTarget.fadeoutTimer); + dropTarget.classList.remove('fadeout'); } - }); + }, + ondragend: event => { + dropTarget.classList.add('fadeout'); + // transitionend event may not fire if the user switched to another tab so we'll use a timer + clearTimeout(dropTarget.fadeoutTimer); + dropTarget.fadeoutTimer = setTimeout(() => { + dropTarget.classList.remove('dropzone', 'fadeout'); + }, 250); + }, + ondragleave: event => { + // Chrome sets screen coords to 0 on Escape key pressed or mouse out of document bounds + if (!event.screenX && !event.screenX) { + dropTarget.ondragend(); + } + }, + ondrop: event => { + if (event.dataTransfer.files.length) { + event.preventDefault(); + importFromFile({file: event.dataTransfer.files[0]}).then(() => { + dropTarget.classList.remove('dropzone'); + }); + } else { + dropTarget.ondragend(); + } + }, }); diff --git a/manage.html b/manage.html index 1fce79ad..2a5a425f 100644 --- a/manage.html +++ b/manage.html @@ -159,6 +159,36 @@ overflow: auto; margin: 0 0 .5em 0; } + /* drag-n-drop on import button */ + .dropzone:after { + background-color: rgba(0, 0, 0, 0.7); + color: white; + left: 0; + top: 0; + right: 0; + bottom: 0; + z-index: 1000; + position: fixed; + padding: calc(50vh - 3em) calc(50vw - 5em); + content: attr(dragndrop-hint); + text-shadow: 1px 1px 10px black; + font-size: xx-large; + text-align: center; + animation: fadein 1s cubic-bezier(.03,.67,.08,.94); + animation-fill-mode: both; + } + .fadeout.dropzone:after { + animation: fadeout .25s ease-in-out; + animation-fill-mode: both; + } + @keyframes fadein { + from { opacity: 0; } + to { opacity: 1; } + } + @keyframes fadeout { + from { opacity: 1; } + to { opacity: 0; } + }