Fix: refactor import-export

This commit is contained in:
eight 2018-10-12 23:14:46 +08:00
parent 86ea846a89
commit c55675912e

View File

@ -1,11 +1,10 @@
/* global messageBox handleUpdate handleDelete styleSectionsEqual getOwnTab API /* global messageBox styleSectionsEqual getOwnTab API
tryJSONparse scrollElementIntoView $ $$ API $create t animateElement */ tryJSONparse scrollElementIntoView $ $$ API $create t animateElement */
'use strict'; 'use strict';
const STYLISH_DUMP_FILE_EXT = '.txt'; const STYLISH_DUMP_FILE_EXT = '.txt';
const STYLUS_BACKUP_FILE_EXT = '.json'; const STYLUS_BACKUP_FILE_EXT = '.json';
function importFromFile({fileTypeFilter, file} = {}) { function importFromFile({fileTypeFilter, file} = {}) {
return new Promise(resolve => { return new Promise(resolve => {
const fileInput = document.createElement('input'); const fileInput = document.createElement('input');
@ -61,9 +60,9 @@ function importFromString(jsonString, oldStyles) {
if (!oldStyles) { if (!oldStyles) {
return API.getStylesInfo().then(styles => importFromString(jsonString, styles)); return API.getStylesInfo().then(styles => importFromString(jsonString, styles));
} }
const json = tryJSONparse(jsonString) || []; const json = tryJSONparse(jsonString);
if (typeof json.slice !== 'function') { if (!Array.isArray(json)) {
json.length = 0; return Promise.reject(new Error('the backup is not a valid JSON file'));
} }
const oldStylesById = new Map( const oldStylesById = new Map(
oldStyles.map(style => [style.id, style])); oldStyles.map(style => [style.id, style]));
@ -84,24 +83,19 @@ function importFromString(jsonString, oldStyles) {
const renderQueue = []; const renderQueue = [];
const RENDER_NAP_TIME_MAX = 1000; // ms const RENDER_NAP_TIME_MAX = 1000; // ms
const RENDER_QUEUE_MAX = 50; // number of styles const RENDER_QUEUE_MAX = 50; // number of styles
const SAVE_OPTIONS = {reason: 'import', notify: false}; return proceed();
return new Promise(proceed); function proceed() {
function proceed(resolve) {
while (index < json.length) { while (index < json.length) {
const item = json[index++]; const item = json[index++];
const info = analyze(item); const info = analyze(item);
if (info) { if (!info) {
// using saveStyle directly since json was parsed in background page context continue;
// FIXME: rewrite importStyle
return API.saveStyle(Object.assign(item, SAVE_OPTIONS))
.then(style => account({style, info, resolve}));
} }
return API.importStyle(item)
.then(style => account({style, info}));
} }
renderQueue.forEach(style => handleUpdate(style, {reason: 'import'})); return done();
renderQueue.length = 0;
done(resolve);
} }
function analyze(item) { function analyze(item) {
@ -148,17 +142,19 @@ function importFromString(jsonString, oldStyles) {
.some(field => oldStyle[field] && oldStyle[field] === newStyle[field]); .some(field => oldStyle[field] && oldStyle[field] === newStyle[field]);
} }
function account({style, info, resolve}) { function account({style, info}) {
renderQueue.push(style); renderQueue.push(style);
if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX if (performance.now() - lastRenderTime > RENDER_NAP_TIME_MAX
|| renderQueue.length > RENDER_QUEUE_MAX) { || renderQueue.length > RENDER_QUEUE_MAX) {
renderQueue.forEach(style => handleUpdate(style, {reason: 'import'}));
setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id)); setTimeout(scrollElementIntoView, 0, $('#style-' + renderQueue.pop().id));
renderQueue.length = 0; renderQueue.length = 0;
lastRenderTime = performance.now(); lastRenderTime = performance.now();
} }
setTimeout(proceed, 0, resolve); updateStats(style, info);
const {oldStyle, metaEqual, codeEqual} = info; return proceed();
}
function updateStats(style, {oldStyle, metaEqual, codeEqual}) {
if (!oldStyle) { if (!oldStyle) {
stats.added.names.push(style.name); stats.added.names.push(style.name);
stats.added.ids.push(style.id); stats.added.ids.push(style.id);
@ -178,42 +174,41 @@ function importFromString(jsonString, oldStyles) {
stats.metaOnly.ids.push(style.id); stats.metaOnly.ids.push(style.id);
} }
function done(resolve) { function done() {
const numChanged = stats.metaAndCode.names.length + const numChanged = stats.metaAndCode.names.length +
stats.metaOnly.names.length + stats.metaOnly.names.length +
stats.codeOnly.names.length + stats.codeOnly.names.length +
stats.added.names.length; stats.added.names.length;
Promise.resolve(numChanged && API.refreshAllTabs()).then(() => { const report = Object.keys(stats)
const report = Object.keys(stats) .filter(kind => stats[kind].names.length)
.filter(kind => stats[kind].names.length) .map(kind => {
.map(kind => { const {ids, names, legend} = stats[kind];
const {ids, names, legend} = stats[kind]; const listItemsWithId = (name, i) =>
const listItemsWithId = (name, i) => $create('div', {dataset: {id: ids[i]}}, name);
$create('div', {dataset: {id: ids[i]}}, name); const listItems = name =>
const listItems = name => $create('div', name);
$create('div', name); const block =
const block = $create('details', {dataset: {id: kind}}, [
$create('details', {dataset: {id: kind}}, [ $create('summary',
$create('summary', $create('b', names.length + ' ' + t(legend))),
$create('b', names.length + ' ' + t(legend))), $create('small',
$create('small', names.map(ids ? listItemsWithId : listItems)),
names.map(ids ? listItemsWithId : listItems)), ]);
]); return block;
return block; });
}); scrollTo(0, 0);
scrollTo(0, 0); messageBox({
messageBox({ title: t('importReportTitle'),
title: t('importReportTitle'), contents: report.length ? report : t('importReportUnchanged'),
contents: report.length ? report : t('importReportUnchanged'), buttons: [t('confirmClose'), numChanged && t('undo')],
buttons: [t('confirmClose'), numChanged && t('undo')], onshow: bindClick,
onshow: bindClick, })
}).then(({button}) => { .then(({button}) => {
if (button === 1) { if (button === 1) {
undo(); undo();
} }
}); });
resolve(numChanged); return Promise.resolve(numChanged);
});
} }
function undo() { function undo() {
@ -224,28 +219,20 @@ function importFromString(jsonString, oldStyles) {
...stats.added.ids, ...stats.added.ids,
]; ];
let tasks = Promise.resolve(); let tasks = Promise.resolve();
let tasksUI = Promise.resolve();
for (const id of newIds) { for (const id of newIds) {
tasks = tasks.then(() => API.deleteStyle(id)); tasks = tasks.then(() => API.deleteStyle(id));
tasksUI = tasksUI.then(() => handleDelete(id));
const oldStyle = oldStylesById.get(id); const oldStyle = oldStylesById.get(id);
if (oldStyle) { if (oldStyle) {
Object.assign(oldStyle, SAVE_OPTIONS); tasks = tasks.then(() => API.importStyle(oldStyle));
// FIXME: import undo
// tasks = tasks.then(() => API.saveStyle(oldStyle));
tasksUI = tasksUI.then(() => handleUpdate(oldStyle, {reason: 'import'}));
} }
} }
// taskUI is superfast and updates style list only in this page, // taskUI is superfast and updates style list only in this page,
// which should account for 99.99999999% of cases, supposedly // which should account for 99.99999999% of cases, supposedly
return tasks return tasks.then(() => messageBox({
.then(tasksUI) title: t('importReportUndoneTitle'),
.then(API.refreshAllTabs) contents: newIds.length + ' ' + t('importReportUndone'),
.then(() => messageBox({ buttons: [t('confirmClose')],
title: t('importReportUndoneTitle'), }));
contents: newIds.length + ' ' + t('importReportUndone'),
buttons: [t('confirmClose')],
}));
} }
function bindClick() { function bindClick() {