151 lines
4.2 KiB
JavaScript
151 lines
4.2 KiB
JavaScript
import { assertNonEmptyString } from './utils/assertNonEmptyString'
|
|
import { assertNumber } from './utils/assertNumber'
|
|
import {
|
|
DEFAULT_DATA_SOURCE,
|
|
DEFAULT_LOCALE,
|
|
KEY_PREFERRED_SKINTONE,
|
|
STORE_KEYVALUE
|
|
} from './constants'
|
|
import { uniqEmoji } from './utils/uniqEmoji'
|
|
import {
|
|
closeDatabase,
|
|
deleteDatabase
|
|
} from './databaseLifecycle'
|
|
import {
|
|
getEmojiByGroup,
|
|
getEmojiBySearchQuery, getEmojiByShortcode, getEmojiByUnicode,
|
|
get, set, getTopFavoriteEmoji, incrementFavoriteEmojiCount
|
|
} from './idbInterface'
|
|
import { customEmojiIndex } from './customEmojiIndex'
|
|
import { cleanEmoji } from './utils/cleanEmoji'
|
|
import { initDatabase } from './initDatabase.js'
|
|
|
|
export default class Database {
|
|
constructor ({ dataSource = DEFAULT_DATA_SOURCE, locale = DEFAULT_LOCALE, customEmoji = [] } = {}) {
|
|
this.dataSource = dataSource
|
|
this.locale = locale
|
|
this._dbName = `emoji-picker-element-${this.locale}`
|
|
this._custom = customEmojiIndex(customEmoji)
|
|
|
|
this._clear = this._clear.bind(this)
|
|
/* no await */ this._init()
|
|
}
|
|
|
|
async _init () {
|
|
if (this._controller) {
|
|
this._controller.abort()
|
|
}
|
|
this._controller = new AbortController()
|
|
this._dbPromise = initDatabase(this._dbName, this.dataSource, this._clear, this._controller.signal)
|
|
}
|
|
|
|
async ready () {
|
|
await this._recreateIfNecessary()
|
|
await this._dbPromise
|
|
}
|
|
|
|
async _recreateIfNecessary () {
|
|
if (!this._controller) {
|
|
await this._init()
|
|
}
|
|
const db = await this._dbPromise
|
|
return db
|
|
}
|
|
|
|
async getEmojiByGroup (group) {
|
|
assertNumber(group)
|
|
const db = await this._recreateIfNecessary()
|
|
return uniqEmoji(await getEmojiByGroup(db, group)).map(cleanEmoji)
|
|
}
|
|
|
|
async getEmojiBySearchQuery (query) {
|
|
assertNonEmptyString(query)
|
|
const db = await this._recreateIfNecessary()
|
|
const customs = this._custom.search(query)
|
|
const natives = uniqEmoji(await getEmojiBySearchQuery(db, query)).map(cleanEmoji)
|
|
return [
|
|
...customs,
|
|
...natives
|
|
]
|
|
}
|
|
|
|
async getEmojiByShortcode (shortcode) {
|
|
assertNonEmptyString(shortcode)
|
|
const db = await this._recreateIfNecessary()
|
|
const custom = this._custom.byShortcode(shortcode)
|
|
if (custom) {
|
|
return custom
|
|
}
|
|
return cleanEmoji(await getEmojiByShortcode(db, shortcode))
|
|
}
|
|
|
|
async getEmojiByUnicodeOrName (unicodeOrName) {
|
|
assertNonEmptyString(unicodeOrName)
|
|
const db = await this._recreateIfNecessary()
|
|
const custom = this._custom.byName(unicodeOrName)
|
|
if (custom) {
|
|
return custom
|
|
}
|
|
return cleanEmoji(await getEmojiByUnicode(db, unicodeOrName))
|
|
}
|
|
|
|
async getPreferredSkinTone () {
|
|
const db = await this._recreateIfNecessary()
|
|
return (await get(db, STORE_KEYVALUE, KEY_PREFERRED_SKINTONE)) || 0
|
|
}
|
|
|
|
async setPreferredSkinTone (skinTone) {
|
|
assertNumber(skinTone)
|
|
const db = await this._recreateIfNecessary()
|
|
return set(db, STORE_KEYVALUE, KEY_PREFERRED_SKINTONE, skinTone)
|
|
}
|
|
|
|
async incrementFavoriteEmojiCount (unicodeOrName) {
|
|
assertNonEmptyString(unicodeOrName)
|
|
const db = await this._recreateIfNecessary()
|
|
return incrementFavoriteEmojiCount(db, unicodeOrName)
|
|
}
|
|
|
|
async getTopFavoriteEmoji (limit) {
|
|
assertNumber(limit)
|
|
const db = await this._recreateIfNecessary()
|
|
return (await getTopFavoriteEmoji(db, this._custom, limit)).map(cleanEmoji)
|
|
}
|
|
|
|
set customEmoji (customEmojis) {
|
|
this._custom = customEmojiIndex(customEmojis)
|
|
}
|
|
|
|
get customEmoji () {
|
|
return this._custom.all
|
|
}
|
|
|
|
_shutdown () {
|
|
if (this._controller) {
|
|
this._controller.abort()
|
|
this._controller = undefined
|
|
this._dbPromise = undefined
|
|
}
|
|
}
|
|
|
|
// clear references to IDB, e.g. during a close event
|
|
_clear () {
|
|
console.log('_clear database', this._dbName)
|
|
// We don't need to call removeEventListener or remove the manual "close" listeners.
|
|
// The memory leak tests prove this is unnecessary. It's because:
|
|
// 1) IDBDatabases that can no longer fire "close" automatically have listeners GCed
|
|
// 2) we clear the manual close listeners in databaseLifecycle.js.
|
|
this._shutdown()
|
|
}
|
|
|
|
async close () {
|
|
this._shutdown()
|
|
await closeDatabase(this._dbName)
|
|
}
|
|
|
|
async delete () {
|
|
this._shutdown()
|
|
await deleteDatabase(this._dbName)
|
|
}
|
|
}
|