Anki settings controllers (#567)

* Convert anki-templates.js to a class

* Convert anki.js to a class
This commit is contained in:
toasted-nutbread 2020-05-29 19:52:51 -04:00 committed by GitHub
parent fde0072118
commit 5f9889fd26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 384 additions and 378 deletions

View File

@ -17,141 +17,147 @@
/* global
* AnkiNoteBuilder
* ankiGetFieldMarkers
* ankiGetFieldMarkersHtml
* api
* getOptionsContext
* getOptionsMutable
* settingsSaveOptions
*/
function onAnkiFieldTemplatesReset(e) {
e.preventDefault();
$('#field-template-reset-modal').modal('show');
}
async function onAnkiFieldTemplatesResetConfirm(e) {
e.preventDefault();
$('#field-template-reset-modal').modal('hide');
const value = await api.getDefaultAnkiFieldTemplates();
const element = document.querySelector('#field-templates');
element.value = value;
element.dispatchEvent(new Event('change'));
}
function ankiTemplatesInitialize() {
const markers = new Set(ankiGetFieldMarkers('terms').concat(ankiGetFieldMarkers('kanji')));
const fragment = ankiGetFieldMarkersHtml(markers);
const list = document.querySelector('#field-templates-list');
list.appendChild(fragment);
for (const node of list.querySelectorAll('.marker-link')) {
node.addEventListener('click', onAnkiTemplateMarkerClicked, false);
class AnkiTemplatesController {
constructor(ankiController) {
this._ankiController = ankiController;
this._cachedDefinitionValue = null;
this._cachedDefinitionText = null;
}
$('#field-templates').on('change', onAnkiFieldTemplatesChanged);
$('#field-template-render').on('click', onAnkiTemplateRender);
$('#field-templates-reset').on('click', onAnkiFieldTemplatesReset);
$('#field-templates-reset-confirm').on('click', onAnkiFieldTemplatesResetConfirm);
prepare() {
const markers = new Set([
...this._ankiController.getFieldMarkers('terms'),
...this._ankiController.getFieldMarkers('kanji')
]);
const fragment = this._ankiController.getFieldMarkersHtml(markers);
ankiTemplatesUpdateValue();
}
const list = document.querySelector('#field-templates-list');
list.appendChild(fragment);
for (const node of list.querySelectorAll('.marker-link')) {
node.addEventListener('click', this._onMarkerClicked.bind(this), false);
}
async function ankiTemplatesUpdateValue() {
const optionsContext = getOptionsContext();
const options = await api.optionsGet(optionsContext);
let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = await api.getDefaultAnkiFieldTemplates(); }
$('#field-templates').val(templates);
$('#field-templates').on('change', this._onChanged.bind(this));
$('#field-template-render').on('click', this._onRender.bind(this));
$('#field-templates-reset').on('click', this._onReset.bind(this));
$('#field-templates-reset-confirm').on('click', this._onResetConfirm.bind(this));
onAnkiTemplatesValidateCompile();
}
this.updateValue();
}
const ankiTemplatesValidateGetDefinition = (() => {
let cachedValue = null;
let cachedText = null;
async updateValue() {
const optionsContext = getOptionsContext();
const options = await api.optionsGet(optionsContext);
let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = await api.getDefaultAnkiFieldTemplates(); }
$('#field-templates').val(templates);
return async (text, optionsContext) => {
if (cachedText !== text) {
this._onValidateCompile();
}
// Private
_onReset(e) {
e.preventDefault();
$('#field-template-reset-modal').modal('show');
}
async _onResetConfirm(e) {
e.preventDefault();
$('#field-template-reset-modal').modal('hide');
const value = await api.getDefaultAnkiFieldTemplates();
const element = document.querySelector('#field-templates');
element.value = value;
element.dispatchEvent(new Event('change'));
}
async _onChanged(e) {
// Get value
let templates = e.currentTarget.value;
if (templates === await api.getDefaultAnkiFieldTemplates()) {
// Default
templates = null;
}
// Overwrite
const optionsContext = getOptionsContext();
const options = await getOptionsMutable(optionsContext);
options.anki.fieldTemplates = templates;
await settingsSaveOptions();
// Compile
this._onValidateCompile();
}
_onValidateCompile() {
const infoNode = document.querySelector('#field-template-compile-result');
this._validate(infoNode, '{expression}', 'term-kanji', false, true);
}
_onMarkerClicked(e) {
e.preventDefault();
document.querySelector('#field-template-render-text').value = `{${e.target.textContent}}`;
}
_onRender(e) {
e.preventDefault();
const field = document.querySelector('#field-template-render-text').value;
const infoNode = document.querySelector('#field-template-render-result');
infoNode.hidden = true;
this._validate(infoNode, field, 'term-kanji', true, false);
}
async _getDefinition(text, optionsContext) {
if (this._cachedDefinitionText !== text) {
const {definitions} = await api.termsFind(text, {}, optionsContext);
if (definitions.length === 0) { return null; }
cachedValue = definitions[0];
cachedText = text;
this._cachedDefinitionValue = definitions[0];
this._cachedDefinitionText = text;
}
return cachedValue;
};
})();
return this._cachedDefinitionValue;
}
async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, invalidateInput) {
const text = document.querySelector('#field-templates-preview-text').value || '';
const exceptions = [];
let result = `No definition found for ${text}`;
try {
const optionsContext = getOptionsContext();
const definition = await ankiTemplatesValidateGetDefinition(text, optionsContext);
if (definition !== null) {
const options = await api.optionsGet(optionsContext);
const context = {
document: {
title: document.title
}
};
let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = await api.getDefaultAnkiFieldTemplates(); }
const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: api.templateRender.bind(api)});
result = await ankiNoteBuilder.formatField(field, definition, mode, context, options, templates, exceptions);
async _validate(infoNode, field, mode, showSuccessResult, invalidateInput) {
const text = document.querySelector('#field-templates-preview-text').value || '';
const exceptions = [];
let result = `No definition found for ${text}`;
try {
const optionsContext = getOptionsContext();
const definition = await this._getDefinition(text, optionsContext);
if (definition !== null) {
const options = await api.optionsGet(optionsContext);
const context = {
document: {
title: document.title
}
};
let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = await api.getDefaultAnkiFieldTemplates(); }
const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: api.templateRender.bind(api)});
result = await ankiNoteBuilder.formatField(field, definition, mode, context, options, templates, exceptions);
}
} catch (e) {
exceptions.push(e);
}
} catch (e) {
exceptions.push(e);
}
const hasException = exceptions.length > 0;
infoNode.hidden = !(showSuccessResult || hasException);
infoNode.textContent = hasException ? exceptions.map((e) => `${e}`).join('\n') : (showSuccessResult ? result : '');
infoNode.classList.toggle('text-danger', hasException);
if (invalidateInput) {
const input = document.querySelector('#field-templates');
input.classList.toggle('is-invalid', hasException);
const hasException = exceptions.length > 0;
infoNode.hidden = !(showSuccessResult || hasException);
infoNode.textContent = hasException ? exceptions.map((e) => `${e}`).join('\n') : (showSuccessResult ? result : '');
infoNode.classList.toggle('text-danger', hasException);
if (invalidateInput) {
const input = document.querySelector('#field-templates');
input.classList.toggle('is-invalid', hasException);
}
}
}
async function onAnkiFieldTemplatesChanged(e) {
// Get value
let templates = e.currentTarget.value;
if (templates === await api.getDefaultAnkiFieldTemplates()) {
// Default
templates = null;
}
// Overwrite
const optionsContext = getOptionsContext();
const options = await getOptionsMutable(optionsContext);
options.anki.fieldTemplates = templates;
await settingsSaveOptions();
// Compile
onAnkiTemplatesValidateCompile();
}
function onAnkiTemplatesValidateCompile() {
const infoNode = document.querySelector('#field-template-compile-result');
ankiTemplatesValidate(infoNode, '{expression}', 'term-kanji', false, true);
}
function onAnkiTemplateMarkerClicked(e) {
e.preventDefault();
document.querySelector('#field-template-render-text').value = `{${e.target.textContent}}`;
}
function onAnkiTemplateRender(e) {
e.preventDefault();
const field = document.querySelector('#field-template-render-text').value;
const infoNode = document.querySelector('#field-template-render-result');
infoNode.hidden = true;
ankiTemplatesValidate(infoNode, field, 'term-kanji', true, false);
}

View File

@ -23,287 +23,281 @@
* utilBackgroundIsolate
*/
// Private
class AnkiController {
prepare() {
$('#anki-fields-container input,#anki-fields-container select,#anki-fields-container textarea').change(this._onFieldsChanged.bind(this));
function _ankiSpinnerShow(show) {
const spinner = $('#anki-spinner');
if (show) {
spinner.show();
} else {
spinner.hide();
}
}
function _ankiSetError(error) {
const node = document.querySelector('#anki-error');
const node2 = document.querySelector('#anki-invalid-response-error');
if (error) {
const errorString = `${error}`;
if (node !== null) {
node.hidden = false;
node.textContent = errorString;
_ankiSetErrorData(node, error);
for (const node of document.querySelectorAll('#anki-terms-model,#anki-kanji-model')) {
node.addEventListener('change', this._onModelChanged.bind(this), false);
}
if (node2 !== null) {
node2.hidden = (errorString.indexOf('Invalid response') < 0);
}
} else {
if (node !== null) {
node.hidden = true;
node.textContent = '';
this.optionsChanged();
}
async optionsChanged(options=null) {
if (options === null) {
const optionsContext = getOptionsContext();
options = await getOptionsMutable(optionsContext);
}
if (node2 !== null) {
node2.hidden = true;
if (!options.anki.enable) {
return;
}
await this._deckAndModelPopulate(options);
await Promise.all([
this._fieldsPopulate('terms', options),
this._fieldsPopulate('kanji', options)
]);
}
getFieldMarkers(type) {
switch (type) {
case 'terms':
return [
'audio',
'cloze-body',
'cloze-prefix',
'cloze-suffix',
'dictionary',
'document-title',
'expression',
'furigana',
'furigana-plain',
'glossary',
'glossary-brief',
'reading',
'screenshot',
'sentence',
'tags',
'url'
];
case 'kanji':
return [
'character',
'dictionary',
'document-title',
'glossary',
'kunyomi',
'onyomi',
'screenshot',
'sentence',
'tags',
'url'
];
default:
return [];
}
}
}
function _ankiSetErrorData(node, error) {
const data = error.data;
let message = '';
if (typeof data !== 'undefined') {
message += `${JSON.stringify(data, null, 4)}\n\n`;
}
message += `${error.stack}`.trimRight();
const button = document.createElement('a');
button.className = 'error-data-show-button';
const content = document.createElement('div');
content.className = 'error-data-container';
content.textContent = message;
content.hidden = true;
button.addEventListener('click', () => content.hidden = !content.hidden, false);
node.appendChild(button);
node.appendChild(content);
}
function _ankiSetDropdownOptions(dropdown, optionValues) {
const fragment = document.createDocumentFragment();
for (const optionValue of optionValues) {
const option = document.createElement('option');
option.value = optionValue;
option.textContent = optionValue;
fragment.appendChild(option);
}
dropdown.textContent = '';
dropdown.appendChild(fragment);
}
async function _ankiDeckAndModelPopulate(options) {
const termsDeck = {value: options.anki.terms.deck, selector: '#anki-terms-deck'};
const kanjiDeck = {value: options.anki.kanji.deck, selector: '#anki-kanji-deck'};
const termsModel = {value: options.anki.terms.model, selector: '#anki-terms-model'};
const kanjiModel = {value: options.anki.kanji.model, selector: '#anki-kanji-model'};
try {
_ankiSpinnerShow(true);
const [deckNames, modelNames] = await Promise.all([api.getAnkiDeckNames(), api.getAnkiModelNames()]);
deckNames.sort();
modelNames.sort();
termsDeck.values = deckNames;
kanjiDeck.values = deckNames;
termsModel.values = modelNames;
kanjiModel.values = modelNames;
_ankiSetError(null);
} catch (error) {
_ankiSetError(error);
} finally {
_ankiSpinnerShow(false);
getFieldMarkersHtml(markers) {
const template = document.querySelector('#anki-field-marker-template').content;
const fragment = document.createDocumentFragment();
for (const marker of markers) {
const markerNode = document.importNode(template, true).firstChild;
markerNode.querySelector('.marker-link').textContent = marker;
fragment.appendChild(markerNode);
}
return fragment;
}
for (const {value, values, selector} of [termsDeck, kanjiDeck, termsModel, kanjiModel]) {
const node = document.querySelector(selector);
_ankiSetDropdownOptions(node, Array.isArray(values) ? values : [value]);
node.value = value;
}
}
// Private
function _ankiCreateFieldTemplate(name, value, markers) {
const template = document.querySelector('#anki-field-template').content;
const content = document.importNode(template, true).firstChild;
content.querySelector('.anki-field-name').textContent = name;
const field = content.querySelector('.anki-field-value');
field.dataset.field = name;
field.value = value;
content.querySelector('.anki-field-marker-list').appendChild(ankiGetFieldMarkersHtml(markers));
return content;
}
async function _ankiFieldsPopulate(tabId, options) {
const tab = document.querySelector(`.tab-pane[data-anki-card-type=${tabId}]`);
const container = tab.querySelector('tbody');
const markers = ankiGetFieldMarkers(tabId);
const fragment = document.createDocumentFragment();
const fields = options.anki[tabId].fields;
for (const name of Object.keys(fields)) {
const value = fields[name];
const html = _ankiCreateFieldTemplate(name, value, markers);
fragment.appendChild(html);
_fieldsToDict(elements) {
const result = {};
for (const element of elements) {
result[element.dataset.field] = element.value;
}
return result;
}
container.textContent = '';
container.appendChild(fragment);
for (const node of container.querySelectorAll('.anki-field-value')) {
node.addEventListener('change', _onAnkiFieldsChanged, false);
}
for (const node of container.querySelectorAll('.marker-link')) {
node.addEventListener('click', _onAnkiMarkerClicked, false);
}
}
function _onAnkiMarkerClicked(e) {
e.preventDefault();
const link = e.currentTarget;
const input = $(link).closest('.input-group').find('.anki-field-value')[0];
input.value = `{${link.textContent}}`;
input.dispatchEvent(new Event('change'));
}
async function _onAnkiModelChanged(e) {
const node = e.currentTarget;
let fieldNames;
try {
const modelName = node.value;
fieldNames = await api.getAnkiModelFieldNames(modelName);
_ankiSetError(null);
} catch (error) {
_ankiSetError(error);
return;
} finally {
_ankiSpinnerShow(false);
_spinnerShow(show) {
const spinner = $('#anki-spinner');
if (show) {
spinner.show();
} else {
spinner.hide();
}
}
const tabId = node.dataset.ankiCardType;
if (tabId !== 'terms' && tabId !== 'kanji') { return; }
_setError(error) {
const node = document.querySelector('#anki-error');
const node2 = document.querySelector('#anki-invalid-response-error');
if (error) {
const errorString = `${error}`;
if (node !== null) {
node.hidden = false;
node.textContent = errorString;
this._setErrorData(node, error);
}
const fields = {};
for (const name of fieldNames) {
fields[name] = '';
if (node2 !== null) {
node2.hidden = (errorString.indexOf('Invalid response') < 0);
}
} else {
if (node !== null) {
node.hidden = true;
node.textContent = '';
}
if (node2 !== null) {
node2.hidden = true;
}
}
}
const optionsContext = getOptionsContext();
const options = await getOptionsMutable(optionsContext);
options.anki[tabId].fields = utilBackgroundIsolate(fields);
await settingsSaveOptions();
_setErrorData(node, error) {
const data = error.data;
let message = '';
if (typeof data !== 'undefined') {
message += `${JSON.stringify(data, null, 4)}\n\n`;
}
message += `${error.stack}`.trimRight();
await _ankiFieldsPopulate(tabId, options);
}
const button = document.createElement('a');
button.className = 'error-data-show-button';
async function _onAnkiFieldsChanged() {
const optionsContext = getOptionsContext();
const options = await getOptionsMutable(optionsContext);
const content = document.createElement('div');
content.className = 'error-data-container';
content.textContent = message;
content.hidden = true;
options.anki.terms.deck = $('#anki-terms-deck').val();
options.anki.terms.model = $('#anki-terms-model').val();
options.anki.terms.fields = utilBackgroundIsolate(ankiFieldsToDict(document.querySelectorAll('#terms .anki-field-value')));
options.anki.kanji.deck = $('#anki-kanji-deck').val();
options.anki.kanji.model = $('#anki-kanji-model').val();
options.anki.kanji.fields = utilBackgroundIsolate(ankiFieldsToDict(document.querySelectorAll('#kanji .anki-field-value')));
button.addEventListener('click', () => content.hidden = !content.hidden, false);
await settingsSaveOptions();
await onAnkiOptionsChanged(options);
}
// Public
function ankiErrorShown() {
const node = document.querySelector('#anki-error');
return node && !node.hidden;
}
function ankiFieldsToDict(elements) {
const result = {};
for (const element of elements) {
result[element.dataset.field] = element.value;
}
return result;
}
function ankiGetFieldMarkersHtml(markers) {
const template = document.querySelector('#anki-field-marker-template').content;
const fragment = document.createDocumentFragment();
for (const marker of markers) {
const markerNode = document.importNode(template, true).firstChild;
markerNode.querySelector('.marker-link').textContent = marker;
fragment.appendChild(markerNode);
}
return fragment;
}
function ankiGetFieldMarkers(type) {
switch (type) {
case 'terms':
return [
'audio',
'cloze-body',
'cloze-prefix',
'cloze-suffix',
'dictionary',
'document-title',
'expression',
'furigana',
'furigana-plain',
'glossary',
'glossary-brief',
'reading',
'screenshot',
'sentence',
'tags',
'url'
];
case 'kanji':
return [
'character',
'dictionary',
'document-title',
'glossary',
'kunyomi',
'onyomi',
'screenshot',
'sentence',
'tags',
'url'
];
default:
return [];
}
}
function ankiInitialize() {
$('#anki-fields-container input,#anki-fields-container select,#anki-fields-container textarea').change(_onAnkiFieldsChanged);
for (const node of document.querySelectorAll('#anki-terms-model,#anki-kanji-model')) {
node.addEventListener('change', _onAnkiModelChanged, false);
node.appendChild(button);
node.appendChild(content);
}
onAnkiOptionsChanged();
}
_setDropdownOptions(dropdown, optionValues) {
const fragment = document.createDocumentFragment();
for (const optionValue of optionValues) {
const option = document.createElement('option');
option.value = optionValue;
option.textContent = optionValue;
fragment.appendChild(option);
}
dropdown.textContent = '';
dropdown.appendChild(fragment);
}
async _deckAndModelPopulate(options) {
const termsDeck = {value: options.anki.terms.deck, selector: '#anki-terms-deck'};
const kanjiDeck = {value: options.anki.kanji.deck, selector: '#anki-kanji-deck'};
const termsModel = {value: options.anki.terms.model, selector: '#anki-terms-model'};
const kanjiModel = {value: options.anki.kanji.model, selector: '#anki-kanji-model'};
try {
this._spinnerShow(true);
const [deckNames, modelNames] = await Promise.all([api.getAnkiDeckNames(), api.getAnkiModelNames()]);
deckNames.sort();
modelNames.sort();
termsDeck.values = deckNames;
kanjiDeck.values = deckNames;
termsModel.values = modelNames;
kanjiModel.values = modelNames;
this._setError(null);
} catch (error) {
this._setError(error);
} finally {
this._spinnerShow(false);
}
for (const {value, values, selector} of [termsDeck, kanjiDeck, termsModel, kanjiModel]) {
const node = document.querySelector(selector);
this._setDropdownOptions(node, Array.isArray(values) ? values : [value]);
node.value = value;
}
}
_createFieldTemplate(name, value, markers) {
const template = document.querySelector('#anki-field-template').content;
const content = document.importNode(template, true).firstChild;
content.querySelector('.anki-field-name').textContent = name;
const field = content.querySelector('.anki-field-value');
field.dataset.field = name;
field.value = value;
content.querySelector('.anki-field-marker-list').appendChild(this.getFieldMarkersHtml(markers));
return content;
}
async _fieldsPopulate(tabId, options) {
const tab = document.querySelector(`.tab-pane[data-anki-card-type=${tabId}]`);
const container = tab.querySelector('tbody');
const markers = this.getFieldMarkers(tabId);
const fragment = document.createDocumentFragment();
const fields = options.anki[tabId].fields;
for (const name of Object.keys(fields)) {
const value = fields[name];
const html = this._createFieldTemplate(name, value, markers);
fragment.appendChild(html);
}
container.textContent = '';
container.appendChild(fragment);
for (const node of container.querySelectorAll('.anki-field-value')) {
node.addEventListener('change', this._onFieldsChanged.bind(this), false);
}
for (const node of container.querySelectorAll('.marker-link')) {
node.addEventListener('click', this._onMarkerClicked.bind(this), false);
}
}
_onMarkerClicked(e) {
e.preventDefault();
const link = e.currentTarget;
const input = $(link).closest('.input-group').find('.anki-field-value')[0];
input.value = `{${link.textContent}}`;
input.dispatchEvent(new Event('change'));
}
async _onModelChanged(e) {
const node = e.currentTarget;
let fieldNames;
try {
const modelName = node.value;
fieldNames = await api.getAnkiModelFieldNames(modelName);
this._setError(null);
} catch (error) {
this._setError(error);
return;
} finally {
this._spinnerShow(false);
}
const tabId = node.dataset.ankiCardType;
if (tabId !== 'terms' && tabId !== 'kanji') { return; }
const fields = {};
for (const name of fieldNames) {
fields[name] = '';
}
async function onAnkiOptionsChanged(options=null) {
if (options === null) {
const optionsContext = getOptionsContext();
options = await getOptionsMutable(optionsContext);
const options = await getOptionsMutable(optionsContext);
options.anki[tabId].fields = utilBackgroundIsolate(fields);
await settingsSaveOptions();
await this._fieldsPopulate(tabId, options);
}
if (!options.anki.enable) {
return;
}
async _onFieldsChanged() {
const optionsContext = getOptionsContext();
const options = await getOptionsMutable(optionsContext);
await _ankiDeckAndModelPopulate(options);
await Promise.all([_ankiFieldsPopulate('terms', options), _ankiFieldsPopulate('kanji', options)]);
options.anki.terms.deck = $('#anki-terms-deck').val();
options.anki.terms.model = $('#anki-terms-model').val();
options.anki.terms.fields = utilBackgroundIsolate(this._fieldsToDict(document.querySelectorAll('#terms .anki-field-value')));
options.anki.kanji.deck = $('#anki-kanji-deck').val();
options.anki.kanji.model = $('#anki-kanji-model').val();
options.anki.kanji.fields = utilBackgroundIsolate(this._fieldsToDict(document.querySelectorAll('#kanji .anki-field-value')));
await settingsSaveOptions();
await this.optionsChanged(options);
}
}

View File

@ -16,17 +16,15 @@
*/
/* global
* AnkiController
* AnkiTemplatesController
* ProfileController
* SettingsBackup
* SettingsController
* ankiInitialize
* ankiTemplatesInitialize
* ankiTemplatesUpdateValue
* api
* appearanceInitialize
* audioSettingsInitialize
* dictSettingsInitialize
* onAnkiOptionsChanged
* onDictionaryOptionsChanged
* storageInfoInitialize
* utilBackend
@ -269,9 +267,13 @@ async function onOptionsUpdated({source}) {
const options = await getOptionsMutable(optionsContext);
document.querySelector('#enable-clipboard-popups').checked = options.general.enableClipboardPopups;
ankiTemplatesUpdateValue();
if (ankiTemplatesController !== null) {
ankiTemplatesController.updateValue();
}
onDictionaryOptionsChanged();
onAnkiOptionsChanged();
if (ankiController !== null) {
ankiController.optionsChanged();
}
await formWrite(options);
}
@ -302,6 +304,8 @@ async function settingsPopulateModifierKeys() {
}
}
let ankiController = null;
let ankiTemplatesController = null;
async function onReady() {
api.forwardLogsToBackend();
@ -318,8 +322,10 @@ async function onReady() {
await audioSettingsInitialize();
await (new ProfileController()).prepare();
await dictSettingsInitialize();
ankiInitialize();
ankiTemplatesInitialize();
ankiController = new AnkiController();
ankiController.prepare();
ankiTemplatesController = new AnkiTemplatesController(ankiController);
ankiTemplatesController.prepare();
new SettingsBackup().prepare();
storageInfoInitialize();