Anki screenshot refactor (#791)

* Use more consistent style for injectScreenshot

* Move screenshot generation into AnkiNoteBuilder/Backend

* Get optionsContext before await
This commit is contained in:
toasted-nutbread 2020-09-09 12:54:59 -04:00 committed by GitHub
parent 2aa86cc5f8
commit e3a7678769
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 65 deletions

View File

@ -20,11 +20,12 @@
*/ */
class AnkiNoteBuilder { class AnkiNoteBuilder {
constructor({anki, audioSystem, renderTemplate, getClipboardImage=null}) { constructor({anki, audioSystem, renderTemplate, getClipboardImage=null, getScreenshot=null}) {
this._anki = anki; this._anki = anki;
this._audioSystem = audioSystem; this._audioSystem = audioSystem;
this._renderTemplate = renderTemplate; this._renderTemplate = renderTemplate;
this._getClipboardImage = getClipboardImage; this._getClipboardImage = getClipboardImage;
this._getScreenshot = getScreenshot;
} }
async createNote({ async createNote({
@ -130,18 +131,23 @@ class AnkiNoteBuilder {
async injectScreenshot(definition, fields, screenshot) { async injectScreenshot(definition, fields, screenshot) {
if (!this._containsMarker(fields, 'screenshot')) { return; } if (!this._containsMarker(fields, 'screenshot')) { return; }
const reading = definition.reading;
const now = new Date(Date.now()); const now = new Date(Date.now());
let fileName = `yomichan_browser_screenshot_${definition.reading}_${this._dateToString(now)}.${screenshot.format}`;
fileName = AnkiNoteBuilder.replaceInvalidFileNameCharacters(fileName);
const data = screenshot.dataUrl.replace(/^data:[\w\W]*?,/, '');
try { try {
const {windowId, tabId, ownerFrameId, format, quality} = screenshot;
const dataUrl = await this._getScreenshot(windowId, tabId, ownerFrameId, format, quality);
let fileName = `yomichan_browser_screenshot_${reading}_${this._dateToString(now)}.${format}`;
fileName = AnkiNoteBuilder.replaceInvalidFileNameCharacters(fileName);
const data = dataUrl.replace(/^data:[\w\W]*?,/, '');
await this._anki.storeMediaFile(fileName, data); await this._anki.storeMediaFile(fileName, data);
} catch (e) {
return;
}
definition.screenshotFileName = fileName; definition.screenshotFileName = fileName;
} catch (e) {
// NOP
}
} }
async injectClipboardImage(definition, fields) { async injectClipboardImage(definition, fields) {

View File

@ -61,7 +61,8 @@ class Backend {
anki: this._anki, anki: this._anki,
audioSystem: this._audioSystem, audioSystem: this._audioSystem,
renderTemplate: this._renderTemplate.bind(this), renderTemplate: this._renderTemplate.bind(this),
getClipboardImage: this._onApiClipboardImageGet.bind(this) getClipboardImage: this._onApiClipboardImageGet.bind(this),
getScreenshot: this._getScreenshot.bind(this)
}); });
this._templateRenderer = new TemplateRenderer(); this._templateRenderer = new TemplateRenderer();
@ -442,7 +443,7 @@ class Backend {
return results; return results;
} }
async _onApiDefinitionAdd({definition, mode, context, details, optionsContext}) { async _onApiDefinitionAdd({definition, mode, context, ownerFrameId, optionsContext}, sender) {
const options = this.getOptions(optionsContext); const options = this.getOptions(optionsContext);
const templates = this._getTemplates(options); const templates = this._getTemplates(options);
const fields = ( const fields = (
@ -463,13 +464,13 @@ class Backend {
await this._ankiNoteBuilder.injectClipboardImage(definition, fields); await this._ankiNoteBuilder.injectClipboardImage(definition, fields);
if (details && details.screenshot) { const {id: tabId, windowId} = (sender && sender.tab ? sender.tab : {});
const {format, quality} = options.anki.screenshot;
await this._ankiNoteBuilder.injectScreenshot( await this._ankiNoteBuilder.injectScreenshot(
definition, definition,
fields, fields,
details.screenshot {windowId, tabId, ownerFrameId, format, quality}
); );
}
const note = await this._createNote(definition, mode, context, options, templates); const note = await this._createNote(definition, mode, context, options, templates);
return this._anki.addNote(note); return this._anki.addNote(note);
@ -1626,4 +1627,40 @@ class Backend {
modeOptions modeOptions
}); });
} }
async _getScreenshot(windowId, tabId, ownerFrameId, format, quality) {
if (typeof windowId !== 'number') {
throw new Error('Invalid window ID');
}
let token = null;
try {
if (typeof tabId === 'number' && typeof ownerFrameId === 'number') {
const action = 'setAllVisibleOverride';
const params = {value: false, priority: 0, awaitFrame: true};
token = await this._sendMessageTab(tabId, {action, params}, {frameId: ownerFrameId});
}
return await new Promise((resolve, reject) => {
chrome.tabs.captureVisibleTab(windowId, {format, quality}, (result) => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve(result);
}
});
});
} finally {
if (token !== null) {
const action = 'clearAllVisibleOverride';
const params = {token};
try {
await this._sendMessageTab(tabId, {action, params}, {frameId: ownerFrameId});
} catch (e) {
// NOP
}
}
}
}
} }

View File

@ -67,7 +67,9 @@ class Frontend {
this._isPointerOverPopup = false; this._isPointerOverPopup = false;
this._runtimeMessageHandlers = new Map([ this._runtimeMessageHandlers = new Map([
['requestFrontendReadyBroadcast', {async: false, handler: this._onMessageRequestFrontendReadyBroadcast.bind(this)}] ['requestFrontendReadyBroadcast', {async: false, handler: this._onMessageRequestFrontendReadyBroadcast.bind(this)}],
['setAllVisibleOverride', {async: true, handler: this._onApiSetAllVisibleOverride.bind(this)}],
['clearAllVisibleOverride', {async: true, handler: this._onApiClearAllVisibleOverride.bind(this)}]
]); ]);
} }
@ -117,9 +119,7 @@ class Frontend {
['closePopup', {async: false, handler: this._onApiClosePopup.bind(this)}], ['closePopup', {async: false, handler: this._onApiClosePopup.bind(this)}],
['copySelection', {async: false, handler: this._onApiCopySelection.bind(this)}], ['copySelection', {async: false, handler: this._onApiCopySelection.bind(this)}],
['getPopupInfo', {async: false, handler: this._onApiGetPopupInfo.bind(this)}], ['getPopupInfo', {async: false, handler: this._onApiGetPopupInfo.bind(this)}],
['getDocumentInformation', {async: false, handler: this._onApiGetDocumentInformation.bind(this)}], ['getDocumentInformation', {async: false, handler: this._onApiGetDocumentInformation.bind(this)}]
['setAllVisibleOverride', {async: true, handler: this._onApiSetAllVisibleOverride.bind(this)}],
['clearAllVisibleOverride', {async: true, handler: this._onApiClearAllVisibleOverride.bind(this)}]
]); ]);
this._updateContentScale(); this._updateContentScale();

View File

@ -77,8 +77,8 @@ const api = (() => {
return this._invoke('kanjiFind', {text, optionsContext}); return this._invoke('kanjiFind', {text, optionsContext});
} }
definitionAdd(definition, mode, context, details, optionsContext) { definitionAdd(definition, mode, context, ownerFrameId, optionsContext) {
return this._invoke('definitionAdd', {definition, mode, context, details, optionsContext}); return this._invoke('definitionAdd', {definition, mode, context, ownerFrameId, optionsContext});
} }
definitionsAddable(definitions, modes, context, optionsContext) { definitionsAddable(definitions, modes, context, optionsContext) {

View File

@ -1047,18 +1047,10 @@ class Display extends EventDispatcher {
try { try {
this.setSpinnerVisible(true); this.setSpinnerVisible(true);
const details = {}; const ownerFrameId = this._ownerFrameId;
if (this._noteUsesScreenshot(mode)) { const optionsContext = this.getOptionsContext();
try {
const screenshot = await this._getScreenshot();
details.screenshot = screenshot;
} catch (e) {
// NOP
}
}
const noteContext = await this._getNoteContext(); const noteContext = await this._getNoteContext();
const noteId = await api.definitionAdd(definition, mode, noteContext, details, this.getOptionsContext()); const noteId = await api.definitionAdd(definition, mode, noteContext, ownerFrameId, optionsContext);
if (noteId) { if (noteId) {
const index = this._definitions.indexOf(definition); const index = this._definitions.indexOf(definition);
const adderButton = this._adderButtonFind(index, mode); const adderButton = this._adderButtonFind(index, mode);
@ -1136,36 +1128,6 @@ class Display extends EventDispatcher {
} }
} }
_noteUsesScreenshot(mode) {
const optionsAnki = this._options.anki;
const fields = (mode === 'kanji' ? optionsAnki.kanji : optionsAnki.terms).fields;
for (const fieldValue of Object.values(fields)) {
if (fieldValue.includes('{screenshot}')) {
return true;
}
}
return false;
}
async _getScreenshot() {
const ownerFrameId = this._ownerFrameId;
let token = null;
try {
if (ownerFrameId !== null) {
token = await api.crossFrame.invoke(ownerFrameId, 'setAllVisibleOverride', {value: false, priority: 0, awaitFrame: true});
}
const {format, quality} = this._options.anki.screenshot;
const dataUrl = await api.screenshotGet({format, quality});
return {dataUrl, format};
} finally {
if (token !== null) {
await api.crossFrame.invoke(ownerFrameId, 'clearAllVisibleOverride', {token});
}
}
}
_getFirstExpressionIndex() { _getFirstExpressionIndex() {
return this._options.general.resultOutputMode === 'merge' ? 0 : -1; return this._options.general.resultOutputMode === 'merge' ? 0 : -1;
} }