Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

Dictionary images #446

Merged
merged 12 commits into from
Apr 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ext/bg/background.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<script src="/bg/js/handlebars.js"></script>
<script src="/bg/js/japanese.js"></script>
<script src="/bg/js/json-schema.js"></script>
<script src="/bg/js/media-utility.js"></script>
<script src="/bg/js/options.js"></script>
<script src="/bg/js/profile-conditions.js"></script>
<script src="/bg/js/request.js"></script>
Expand Down
81 changes: 79 additions & 2 deletions ext/bg/data/dictionary-term-bank-v3-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,85 @@
"type": "array",
"description": "Array of definitions for the term/expression.",
"items": {
"type": "string",
"description": "Single definition for the term/expression."
"oneOf": [
{
"type": "string",
"description": "Single definition for the term/expression."
},
{
"type": "object",
"description": "Single detailed definition for the term/expression.",
"required": [
"type"
],
"properties": {
"type": {
"type": "string",
"description": "The type of the data for this definition.",
"enum": ["text", "image"]
}
},
"oneOf": [
{
"required": [
"type",
"text"
],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": ["text"]
},
"text": {
"type": "string",
"description": "Single definition for the term/expression."
}
}
},
{
"required": [
"type",
"path"
],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": ["image"]
},
"path": {
"type": "string",
"description": "Path to the image file in the archive."
},
"width": {
"type": "integer",
"description": "Preferred width of the image.",
"minimum": 1
},
"height": {
"type": "integer",
"description": "Preferred width of the image.",
"minimum": 1
},
"title": {
"type": "string",
"description": "Hover text for the image."
},
"description": {
"type": "string",
"description": "Description of the image."
},
"pixelated": {
"type": "boolean",
"description": "Whether or not the image should appear pixelated at sizes larger than the image's native resolution.",
"default": false
}
}
}
]
}
]
}
},
{
Expand Down
7 changes: 6 additions & 1 deletion ext/bg/js/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ class Backend {
['getAnkiModelFieldNames', {handler: this._onApiGetAnkiModelFieldNames.bind(this), async: true}],
['getDictionaryInfo', {handler: this._onApiGetDictionaryInfo.bind(this), async: true}],
['getDictionaryCounts', {handler: this._onApiGetDictionaryCounts.bind(this), async: true}],
['purgeDatabase', {handler: this._onApiPurgeDatabase.bind(this), async: true}]
['purgeDatabase', {handler: this._onApiPurgeDatabase.bind(this), async: true}],
['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}]
]);

this._commandHandlers = new Map([
Expand Down Expand Up @@ -762,6 +763,10 @@ class Backend {
return await this.translator.purgeDatabase();
}

async _onApiGetMedia({targets}) {
return await this.database.getMedia(targets);
}

// Command handlers

async _onCommandSearch(params) {
Expand Down
43 changes: 42 additions & 1 deletion ext/bg/js/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Database {
}

try {
this.db = await Database._open('dict', 5, (db, transaction, oldVersion) => {
this.db = await Database._open('dict', 6, (db, transaction, oldVersion) => {
Database._upgrade(db, transaction, oldVersion, [
{
version: 2,
Expand Down Expand Up @@ -90,6 +90,15 @@ class Database {
indices: ['dictionary', 'expression', 'reading', 'sequence', 'expressionReverse', 'readingReverse']
}
}
},
{
version: 6,
stores: {
media: {
primaryKey: {keyPath: 'id', autoIncrement: true},
indices: ['dictionary', 'path']
}
}
}
]);
});
Expand Down Expand Up @@ -268,6 +277,34 @@ class Database {
return result;
}

async getMedia(targets) {
this._validate();

const count = targets.length;
const promises = [];
const results = new Array(count).fill(null);
const createResult = Database._createMedia;
const processRow = (row, [index, dictionaryName]) => {
if (row.dictionary === dictionaryName) {
results[index] = createResult(row, index);
}
};

const transaction = this.db.transaction(['media'], 'readonly');
const objectStore = transaction.objectStore('media');
const index = objectStore.index('path');

for (let i = 0; i < count; ++i) {
const {path, dictionaryName} = targets[i];
const only = IDBKeyRange.only(path);
promises.push(Database._getAll(index, only, [i, dictionaryName], processRow));
}

await Promise.all(promises);

return results;
}

async getDictionaryInfo() {
this._validate();

Expand Down Expand Up @@ -432,6 +469,10 @@ class Database {
return {character, mode, data, dictionary, index};
}

static _createMedia(row, index) {
return Object.assign({}, row, {index});
}

static _getAll(dbIndex, query, context, processRow) {
const fn = typeof dbIndex.getAll === 'function' ? Database._getAllFast : Database._getAllUsingCursor;
return fn(dbIndex, query, context, processRow);
Expand Down
90 changes: 90 additions & 0 deletions ext/bg/js/dictionary-importer.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
/* global
* JSZip
* JsonSchema
* mediaUtility
* requestJson
*/

Expand Down Expand Up @@ -148,6 +149,22 @@ class DictionaryImporter {
}
}

// Extended data support
const extendedDataContext = {
archive,
media: new Map()
};
for (const entry of termList) {
const glossaryList = entry.glossary;
for (let i = 0, ii = glossaryList.length; i < ii; ++i) {
const glossary = glossaryList[i];
if (typeof glossary !== 'object' || glossary === null) { continue; }
glossaryList[i] = await this._formatDictionaryTermGlossaryObject(glossary, extendedDataContext, entry);
}
}

const media = [...extendedDataContext.media.values()];

// Add dictionary
const summary = this._createSummary(dictionaryTitle, version, index, {prefixWildcardsSupported});

Expand Down Expand Up @@ -188,6 +205,7 @@ class DictionaryImporter {
await bulkAdd('kanji', kanjiList);
await bulkAdd('kanjiMeta', kanjiMetaList);
await bulkAdd('tagMeta', tagList);
await bulkAdd('media', media);

return {result: summary, errors};
}
Expand Down Expand Up @@ -275,4 +293,76 @@ class DictionaryImporter {

return [termBank, termMetaBank, kanjiBank, kanjiMetaBank, tagBank];
}

async _formatDictionaryTermGlossaryObject(data, context, entry) {
switch (data.type) {
case 'text':
return data.text;
case 'image':
return await this._formatDictionaryTermGlossaryImage(data, context, entry);
default:
throw new Error(`Unhandled data type: ${data.type}`);
}
}

async _formatDictionaryTermGlossaryImage(data, context, entry) {
const dictionary = entry.dictionary;
const {path, width: preferredWidth, height: preferredHeight, title, description, pixelated} = data;
if (context.media.has(path)) {
// Already exists
return data;
}

let errorSource = entry.expression;
if (entry.reading.length > 0) {
errorSource += ` (${entry.reading});`;
}

const file = context.archive.file(path);
if (file === null) {
throw new Error(`Could not find image at path ${JSON.stringify(path)} for ${errorSource}`);
}

const content = await file.async('base64');
const mediaType = mediaUtility.getImageMediaTypeFromFileName(path);
if (mediaType === null) {
throw new Error(`Could not determine media type for image at path ${JSON.stringify(path)} for ${errorSource}`);
}

let image;
try {
image = await mediaUtility.loadImageBase64(mediaType, content);
} catch (e) {
throw new Error(`Could not load image at path ${JSON.stringify(path)} for ${errorSource}`);
}

const width = image.naturalWidth;
const height = image.naturalHeight;

// Create image data
const mediaData = {
dictionary,
path,
mediaType,
width,
height,
content
};
context.media.set(path, mediaData);

// Create new data
const newData = {
type: 'image',
path,
width,
height
};
if (typeof preferredWidth === 'number') { newData.preferredWidth = preferredWidth; }
if (typeof preferredHeight === 'number') { newData.preferredHeight = preferredHeight; }
if (typeof title === 'string') { newData.title = title; }
if (typeof description === 'string') { newData.description = description; }
if (typeof pixelated === 'boolean') { newData.pixelated = pixelated; }

return newData;
}
}
Loading