blob: a8e9b5e9c980e872519322a9215250f88efe678b [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {EmojiPickerApiProxy} from 'emoji_picker_api_proxy.js';
import {CategoryEnum, EmojiVariants, VisualContent} from './types.js';
const MAX_RECENTS = 10;
/**
* @param {string} keyName Keyname of the object stored in storage
* @return {{history:!Array<EmojiVariants>, preference:Object<string,string>}}
* recently used emoji, most recent first.
*/
function load(keyName: string) {
const stored = window.localStorage.getItem(keyName);
if (!stored) {
return {history: [], preference: {}};
}
const parsed = /** @type {?} */ (JSON.parse(stored));
// Throw out any old data
return {history: parsed.history || [], preference: parsed.preference || {}};
}
/**
* @param {{history:!Array<EmojiVariants>, preference:Object<string,string>}}
* data recently used emoji, most recent first.
*/
function save(
keyName: string,
data: {history: EmojiVariants[], preference: {[index: string]: string}}) {
window.localStorage.setItem(keyName, JSON.stringify(data));
}
export class RecentlyUsedStore {
storeName: string;
data: {history: EmojiVariants[], preference: {[index: string]: string}};
constructor(name: string) {
this.storeName = name;
this.data = load(name);
}
/**
* Saves preferences for a base emoji.
* returns True if any preferences are updated and false
* otherwise.
*/
savePreferredVariant(baseEmoji: string, variant: string) {
if (!baseEmoji) {
return false;
}
// Base emoji must not be set as preference. So, store it only
// if variant and baseEmoji are different and remove it from preference
// otherwise.
if (baseEmoji !== variant && variant) {
this.data.preference[baseEmoji] = variant;
} else if (baseEmoji in this.data.preference) {
delete this.data.preference[baseEmoji];
} else {
return false;
}
save(this.storeName, this.data);
return true;
}
getPreferenceMapping() {
return this.data.preference;
}
clearRecents() {
this.data.history = [];
save(this.storeName, this.data);
}
/**
* Moves the given item to the front of the MRU list, inserting it if
* it did not previously exist.
*/
bumpItem(category: CategoryEnum, newItem: EmojiVariants) {
// Find and remove newItem from array if it previously existed.
// Note, this explicitly allows for multiple recent item entries for the
// same "base" emoji just with a different variant.
let oldIndex;
if (category === CategoryEnum.GIF) {
oldIndex = this.data.history.findIndex(
x =>
(x.base.visualContent &&
x.base.visualContent.id === newItem.base.visualContent?.id));
} else {
oldIndex = this.data.history.findIndex(
x => (x.base.string && x.base.string === newItem.base.string));
}
if (oldIndex !== -1) {
this.data.history.splice(oldIndex, 1);
}
// insert newItem to the front of the array.
this.data.history.unshift(newItem);
// slice from end of array if it exceeds MAX_RECENTS.
if (this.data.history.length > MAX_RECENTS) {
// setting length is sufficient to truncate an array.
this.data.history.length = MAX_RECENTS;
}
save(this.storeName, this.data);
}
/**
* Removes invalid GIFs from history.
*/
async validate(apiProxy: EmojiPickerApiProxy): Promise<boolean> {
if (this.data.history.length === 0) {
// No GIFs to validate.
return false;
}
// This function is only called on history items with visual content (i.e.
// GIFs) so we can be confident an id will always exist.
const ids = this.data.history.map(x => x.base.visualContent!.id);
const {selectedGifs} = await apiProxy.getGifsByIds(ids);
const map = new Map<string, VisualContent>();
selectedGifs.forEach(gif => {
map.set(gif.id, gif);
});
const validGifHistory =
this.data.history.filter(item => map.has(item.base.visualContent!.id));
const updated = (validGifHistory.length !== this.data.history.length);
if (updated) {
this.data.history = validGifHistory;
save(this.storeName, this.data);
}
return updated;
}
}