/* global messageBox, handleUpdate */ 'use strict'; const STYLISH_DUMP_FILE_EXT = '.txt'; const STYLUS_BACKUP_FILE_EXT = '.json'; 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 = fileTypeFilter || STYLISH_DUMP_FILE_EXT; fileInput.acceptCharset = 'utf-8'; document.body.appendChild(fileInput); fileInput.initialValue = fileInput.value; fileInput.onchange = readFile; fileInput.click(); function readFile() { if (file || fileInput.value !== fileInput.initialValue) { file = file || fileInput.files[0]; if (file.size > 100e6) { 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(); importFromString(event.target.result).then(numStyles => { document.body.style.cursor = ''; resolve(numStyles); }); }; fReader.readAsText(file, 'utf-8'); } } }); } function importFromString(jsonString) { if (!BG) { onBackgroundReady().then(() => importFromString(jsonString)); return; } const json = BG.tryJSONparse(jsonString) || []; // create object in background context if (typeof json.slice != 'function') { json.length = 0; } const oldStyles = json.length && BG.deepCopy(BG.cachedStyles.list || []); const oldStylesByName = json.length && new Map( oldStyles.map(style => [style.name.trim(), style])); const stats = { added: {names: [], ids: [], legend: 'added'}, unchanged: {names: [], ids: [], legend: 'identical skipped'}, metaAndCode: {names: [], ids: [], legend: 'updated both meta info and code'}, metaOnly: {names: [], ids: [], legend: 'updated meta info'}, codeOnly: {names: [], ids: [], legend: 'updated code'}, invalid: {names: [], legend: 'invalid skipped'}, }; let index = 0; let lastRenderTime = performance.now(); const renderQueue = []; const RENDER_NAP_TIME_MAX = 1000; // ms const RENDER_QUEUE_MAX = 50; // number of styles return new Promise(proceed); function proceed(resolve) { while (index < json.length) { const item = json[index++]; if (!item || !item.name || !item.name.trim() || typeof item != 'object' || (item.sections && typeof item.sections.slice != 'function')) { stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`); continue; } item.name = item.name.trim(); const byId = BG.cachedStyles.byId.get(item.id); const byName = oldStylesByName.get(item.name); const oldStyle = byId && byId.name.trim() == item.name || !byName ? byId : byName; if (oldStyle == byName && byName) { item.id = byName.id; } const oldStyleKeys = oldStyle && Object.keys(oldStyle); const metaEqual = oldStyleKeys && oldStyleKeys.length == Object.keys(item).length && oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]); const codeEqual = oldStyle && BG.styleSectionsEqual(oldStyle, item); if (metaEqual && codeEqual) { stats.unchanged.names.push(oldStyle.name); stats.unchanged.ids.push(oldStyle.id); continue; } // using saveStyle directly since json was parsed in background page context BG.saveStyle(Object.assign(item, { reason: 'import', notify: false, })).then(style => { renderQueue.push(style); if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX || renderQueue.length > RENDER_QUEUE_MAX) { renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id)); renderQueue.length = 0; lastRenderTime = performance.now(); } setTimeout(proceed, 0, resolve); if (!oldStyle) { stats.added.names.push(style.name); stats.added.ids.push(style.id); return; } if (!metaEqual && !codeEqual) { stats.metaAndCode.names.push(reportNameChange(oldStyle, style)); stats.metaAndCode.ids.push(style.id); return; } if (!codeEqual) { stats.codeOnly.names.push(style.name); stats.codeOnly.ids.push(style.id); return; } stats.metaOnly.names.push(reportNameChange(oldStyle, style)); stats.metaOnly.ids.push(style.id); }); return; } renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); renderQueue.length = 0; done(resolve); } function done(resolve) { const numChanged = stats.metaAndCode.names.length + stats.metaOnly.names.length + stats.codeOnly.names.length + stats.added.names.length; Promise.resolve(numChanged && BG.refreshAllTabs()).then(() => { const listNames = kind => { const {ids, names} = stats[kind]; return ids ? names.map((name, i) => `