Fix bulk export

This commit is contained in:
Rob Garrison 2019-01-01 06:59:59 -06:00
parent a7026bdeee
commit 44889f6158
6 changed files with 208 additions and 15 deletions

View File

@ -93,7 +93,8 @@
"description": "Message for backup" "description": "Message for backup"
}, },
"bckpInstStyles": { "bckpInstStyles": {
"message": "Export styles" "message": "Local device",
"description": "Selected option to backup indicated styles to local device/drive"
}, },
"checkAllUpdates": { "checkAllUpdates": {
"message": "Check all styles for updates", "message": "Check all styles for updates",
@ -1238,6 +1239,10 @@
"message": "Click to open the filter, search and bulk actions panel", "message": "Click to open the filter, search and bulk actions panel",
"description": "Text for button to apply the selected action" "description": "Text for button to apply the selected action"
}, },
"bulkActionsError": {
"message": "Choose at least one style",
"description": "Error displayed in a tooltip when the user attempts to apply an action with no styles selected"
},
"sectionAdd": { "sectionAdd": {
"message": "Add another section", "message": "Add another section",
"description": "Label for the button to add a section" "description": "Label for the button to add a section"

View File

@ -27,6 +27,7 @@ self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
'manage.onlyEnabled.invert': false, // display only disabled styles 'manage.onlyEnabled.invert': false, // display only disabled styles
'manage.onlyLocal.invert': false, // display only externally installed styles 'manage.onlyLocal.invert': false, // display only externally installed styles
'manage.onlyUsercss.invert': false, // display only non-usercss (standard) styles 'manage.onlyUsercss.invert': false, // display only non-usercss (standard) styles
'manage.export.destination': 'local', // default export destination (local or dropbox)
'manage.newUI.favicons': false, // show favicons for the sites in applies-to 'manage.newUI.favicons': false, // show favicons for the sites in applies-to
'manage.newUI.faviconsGray': true, // gray out favicons 'manage.newUI.faviconsGray': true, // gray out favicons

View File

@ -175,7 +175,7 @@
<div class="dropdown-content"> <div class="dropdown-content">
<a href="#" class="unfile-all-styles" i18n-text="retrieveBckp"></a> <a href="#" class="unfile-all-styles" i18n-text="retrieveBckp"></a>
<a href="#" class="sync-dropbox-import" i18n-text="retrieveDropboxSync"></a> <a href="#" class="sync-dropbox-import" i18n-text="syncDropboxStyles"></a>
</div> </div>
</div> </div>
</template> </template>
@ -360,7 +360,7 @@
</svg> </svg>
</label> </label>
<span class="select-resizer"> <span class="select-resizer">
<select id="manage.onlyLocal.invert" i18n-data-title="manageOnlyLocalTooltip"> <select id="manage.onlyLocal.invert" class="tt-s" i18n-data-title="manageOnlyLocalTooltip">
<option i18n-text="manageOnlyLocal" value="false"></option> <option i18n-text="manageOnlyLocal" value="false"></option>
<option i18n-text="manageOnlyExternal" value="true"></option> <option i18n-text="manageOnlyExternal" value="true"></option>
</select> </select>
@ -430,7 +430,7 @@
</select> </select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg> <svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</span> </span>
<button id="bulk-actions-apply" i18n-text="bulkActionsApply"> <button id="bulk-actions-apply" i18n-text="bulkActionsApply" class="tt-e" disabled>
<span id="update-progress"></span> <span id="update-progress"></span>
</button> </button>
<span id="bulk-info"> <span id="bulk-info">
@ -445,9 +445,9 @@
<span data-bulk="export" class="dropdown export hidden"> <span data-bulk="export" class="dropdown export hidden">
Export to: Export to:
<span class="select-resizer"> <span class="select-resizer">
<select id="manage.onlyEnabled.invert"> <select id="manage.export.destination">
<option id="file-all-styles" i18n-text="bckpInstStyles"></option> <option value="local" i18n-text="bckpInstStyles"></option>
<option id="sync-dropbox-export" i18n-text="syncDropboxStyles"></option> <option value="dropbox" i18n-text="syncDropboxStyles"></option>
</select> </select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg> <svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</span> </span>

View File

@ -1,4 +1,5 @@
/* global $ $$ handleEvent installed exportToFile checkUpdateAll */ /* global $ $$ API t prefs handleEvent installed exportToFile checkUpdateAll exportDropbox
messageBox */
/* exported bulk */ /* exported bulk */
'use strict'; 'use strict';
@ -13,14 +14,13 @@ const bulk = {
handleSelect: event => { handleSelect: event => {
event.preventDefault(); event.preventDefault();
$$('[data-bulk]').forEach(el => el.classList.add('hidden')); $$('[data-bulk]').forEach(el => el.classList.add('hidden'));
console.log('select', this.value)
switch (event.target.value) { switch (event.target.value) {
case 'enable': case 'enable':
break; break;
case 'disable': case 'disable':
break; break;
case 'export': case 'export':
console.log('got here')
$('[data-bulk="export"]').classList.remove('hidden'); $('[data-bulk="export"]').classList.remove('hidden');
break; break;
case 'update': case 'update':
@ -51,10 +51,14 @@ console.log('select', this.value)
}); });
break; break;
} }
case 'export': case 'export': {
styles = entries.map(entry => entry.styleMeta); styles = entries.map(entry => entry.styleMeta);
exportToFile(styles); const destination = prefs.get('manage.export.destination');
break; if (destination === 'dropbox') {
return exportDropbox(styles);
}
return exportToFile(styles);
}
case 'update': case 'update':
checkUpdateAll(entries); // TO DO checkUpdateAll(entries); // TO DO
break; break;
@ -99,6 +103,12 @@ console.log('select', this.value)
} }
const count = $$('.entry-filter-toggle').filter(entry => entry.checked).length; const count = $$('.entry-filter-toggle').filter(entry => entry.checked).length;
$('#bulk-filter-count').textContent = count || ''; $('#bulk-filter-count').textContent = count || '';
if (count > 0 && $('#bulk-actions-select').value !== '') {
$('#bulk-actions-apply').removeAttribute('disabled');
} else {
$('#bulk-actions-apply').setAttribute('disabled', true);
}
} }
}, },
@ -118,4 +128,4 @@ console.log('select', this.value)
}); });
} }
} };

View File

@ -8,6 +8,7 @@
[data-title] { [data-title] {
position: relative; position: relative;
overflow: visible;
} }
[data-title]:after { [data-title]:after {
@ -161,4 +162,3 @@
.update-problem .check-update:before { .update-problem .check-update:before {
border-right-color: var(--tooltip-error); border-right-color: var(--tooltip-error);
} }

View File

@ -0,0 +1,177 @@
/* global messageBox Dropbox createZipFileFromText readZipFileFromBlob
launchWebAuthFlow getRedirectUrlAuthFlow importFromString resolve
$ $create t chromeLocal API getOwnTab */
/* exported exportDropbox */
'use strict';
const DROPBOX_API_KEY = 'zg52vphuapvpng9';
const FILENAME_ZIP_FILE = 'stylus.json';
const DROPBOX_FILE = 'stylus.zip';
const API_ERROR_STATUS_FILE_NOT_FOUND = 409;
const HTTP_STATUS_CANCEL = 499;
function messageProgressBar(data) {
return messageBox({
title: `${data.title}`,
className: 'config-dialog',
contents: [
$create('p', data.text)
],
buttons: [{
textContent: t('confirmClose'),
dataset: {cmd: 'close'},
}],
}).then(() => {
document.body.style.minWidth = '';
document.body.style.minHeight = '';
});
}
function hasDropboxAccessToken() {
return chromeLocal.getValue('dropbox_access_token');
}
function requestDropboxAccessToken() {
const client = new Dropbox.Dropbox({
clientId: DROPBOX_API_KEY,
fetch
});
const authUrl = client.getAuthenticationUrl(getRedirectUrlAuthFlow());
return launchWebAuthFlow({url: authUrl, interactive: true})
.then(urlReturned => {
const params = new URLSearchParams(new URL(urlReturned).hash.replace('#', ''));
chromeLocal.setValue('dropbox_access_token', params.get('access_token'));
return params.get('access_token');
});
}
function uploadFileDropbox(client, stylesText) {
return client.filesUpload({path: '/' + DROPBOX_FILE, contents: stylesText});
}
function exportDropbox(styles) {
const mode = localStorage.installType;
const title = t('syncDropboxStyles');
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
messageProgressBar({title, text});
if (mode !== 'normal') return;
hasDropboxAccessToken()
.then(token => token || requestDropboxAccessToken())
.then(token => {
const client = new Dropbox.Dropbox({
clientId: DROPBOX_API_KEY,
accessToken: token,
fetch
});
return client.filesDownload({path: '/' + DROPBOX_FILE})
.then(() => messageBox.confirm(t('overwriteFileExport')))
.then(ok => {
// deletes file if user want to
if (!ok) {
return Promise.reject({status: HTTP_STATUS_CANCEL});
}
return client.filesDelete({path: '/' + DROPBOX_FILE});
})
// file deleted with success, process styles and create a file
.then(() => {
messageProgressBar({title: title, text: t('gettingStyles')});
return JSON.stringify(styles, null, '\t');
})
// create zip file
.then(stylesText => {
messageProgressBar({title: title, text: t('zipStyles')});
return createZipFileFromText(FILENAME_ZIP_FILE, stylesText);
})
// create file dropbox
.then(zipedText => {
messageProgressBar({title: title, text: t('uploadingFile')});
return uploadFileDropbox(client, zipedText);
})
// gives feedback to user
.then(() => messageProgressBar({title: title, text: t('exportSavedSuccess')}))
// handle not found cases and cancel action
.catch(error => {
console.log(error);
// saving file first time
if (error.status === API_ERROR_STATUS_FILE_NOT_FOUND) {
API.getAllStyles()
.then(styles => {
messageProgressBar({title: title, text: t('gettingStyles')});
return JSON.stringify(styles, null, '\t');
})
.then(stylesText => {
messageProgressBar({title: title, text: t('zipStyles')});
return createZipFileFromText(FILENAME_ZIP_FILE, stylesText);
})
.then(zipedText => {
messageProgressBar({title: title, text: t('uploadingFile')});
return uploadFileDropbox(client, zipedText);
})
.then(() => messageProgressBar({title: title, text: t('exportSavedSuccess')}))
.catch(err => messageBox.alert(err));
return;
}
// user cancelled the flow
if (error.status === HTTP_STATUS_CANCEL) {
return;
}
console.error(error);
});
});
};
$('#sync-dropbox-import').onclick = () => {
const mode = localStorage.installType;
const title = t('syncDropboxStyles');
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
messageProgressBar({title, text});
if (mode !== 'normal') return;
hasDropboxAccessToken()
.then(token => token || requestDropboxAccessToken())
.then(token => {
const client = new Dropbox.Dropbox({
clientId: DROPBOX_API_KEY,
accessToken: token,
fetch
});
return client.filesDownload({path: '/' + DROPBOX_FILE})
.then(response => {
messageProgressBar({title: title, text: t('unzipStyles')});
return readZipFileFromBlob(response.fileBlob);
})
.then(zipedFileBlob => {
messageProgressBar({title: title, text: t('readingStyles')});
document.body.style.cursor = 'wait';
const fReader = new FileReader();
fReader.onloadend = event => {
const text = event.target.result;
const maybeUsercss = !/^[\s\r\n]*\[/.test(text) &&
(text.includes('==UserStyle==') || /==UserStyle==/i.test(text));
(!maybeUsercss ?
importFromString(text) :
getOwnTab().then(tab => {
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
return API.openUsercssInstallPage({direct: true, tab})
.then(() => URL.revokeObjectURL(tab.url));
})
).then(numStyles => {
document.body.style.cursor = '';
resolve(numStyles);
});
};
fReader.readAsText(zipedFileBlob, 'utf-8');
})
.catch(error => {
// no file
if (error.status === API_ERROR_STATUS_FILE_NOT_FOUND) {
messageBox.alert(t('noFileToImport'));
return;
}
messageBox.alert(error);
});
});
};