diff --git a/web/src/frequently-used.js b/web/src/frequently-used.js
index a754e9a..d35f6a9 100644
--- a/web/src/frequently-used.js
+++ b/web/src/frequently-used.js
@@ -13,22 +13,80 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-const FREQUENTLY_USED = JSON.parse(window.localStorage.mauFrequentlyUsedStickerIDs || "{}")
+
+const FREQUENTLY_USED_STORAGE_KEY = 'mauFrequentlyUsedStickerIDs'
+const FREQUENTLY_USED_STORAGE_CACHE_KEY = 'mauFrequentlyUsedStickerCache'
+
+let FREQUENTLY_USED = JSON.parse(window.localStorage[FREQUENTLY_USED_STORAGE_KEY] ?? '{}')
let FREQUENTLY_USED_SORTED = null
-export const add = id => {
- const [count] = FREQUENTLY_USED[id] || [0]
- FREQUENTLY_USED[id] = [count + 1, Date.now()]
- window.localStorage.mauFrequentlyUsedStickerIDs = JSON.stringify(FREQUENTLY_USED)
+const sortFrequentlyUsedEntries = (entry1, entry2) => {
+ const [, [count1, date1]] = entry1
+ const [, [count2, date2]] = entry2
+ return count2 === count1 ? date2 - date1 : count2 - count1
+}
+
+export const setFrequentlyUsedStorage = (frequentlyUsed) => {
+ FREQUENTLY_USED = frequentlyUsed ?? {}
+ window.localStorage[FREQUENTLY_USED_STORAGE_KEY] = JSON.stringify(FREQUENTLY_USED)
FREQUENTLY_USED_SORTED = null
}
+export const setFrequentlyUsedCacheStorage = (stickers) => {
+ const toPutInCache = stickers.map(sticker => [sticker.id, sticker])
+ window.localStorage[FREQUENTLY_USED_STORAGE_CACHE_KEY] = JSON.stringify(toPutInCache)
+}
+
+export const add = (id) => {
+ let FREQUENTLY_USED_COPY = { ...FREQUENTLY_USED }
+ const [count] = FREQUENTLY_USED_COPY[id] || [0]
+ FREQUENTLY_USED_COPY[id] = [count + 1, Date.now()]
+ setFrequentlyUsedStorage(FREQUENTLY_USED_COPY)
+}
+
export const get = (limit = 16) => {
if (FREQUENTLY_USED_SORTED === null) {
- FREQUENTLY_USED_SORTED = Object.entries(FREQUENTLY_USED)
- .sort(([, [count1, date1]], [, [count2, date2]]) =>
- count2 === count1 ? date2 - date1 : count2 - count1)
+ FREQUENTLY_USED_SORTED = Object.entries(FREQUENTLY_USED || {})
+ .sort(sortFrequentlyUsedEntries)
.map(([emoji]) => emoji)
}
return FREQUENTLY_USED_SORTED.slice(0, limit)
}
+
+export const getFromCache = () => {
+ return Object.values(JSON.parse(localStorage[FREQUENTLY_USED_STORAGE_CACHE_KEY] ?? '[]'))
+}
+
+export const remove = (id) => {
+ let FREQUENTLY_USED_COPY = { ...FREQUENTLY_USED }
+ if (FREQUENTLY_USED_COPY[id]) {
+ delete FREQUENTLY_USED_COPY[id]
+ setFrequentlyUsedStorage(FREQUENTLY_USED_COPY)
+ }
+}
+
+export const removeMultiple = (ids) => {
+ let FREQUENTLY_USED_COPY = { ...FREQUENTLY_USED }
+ ids.forEach((id) => {
+ delete FREQUENTLY_USED_COPY[id]
+ })
+ setFrequentlyUsedStorage(FREQUENTLY_USED_COPY)
+}
+
+export const removeAll = setFrequentlyUsedStorage
+
+const compareStorageWith = (packs) => {
+ const stickersIDsFromPacks = packs.map((pack) => pack.stickers).flat().map((sticker) => sticker.id)
+ const stickersIDsFromFrequentlyUsedStorage = get()
+
+ const notFound = stickersIDsFromFrequentlyUsedStorage.filter((id) => !stickersIDsFromPacks.includes(id))
+ const found = stickersIDsFromFrequentlyUsedStorage.filter((id) => !notFound.includes(id))
+
+ return { found, notFound }
+}
+
+export const removeNotFoundFromStorage = (packs) => {
+ const { found, notFound } = compareStorageWith(packs)
+ removeMultiple(notFound)
+ return found
+}
diff --git a/web/src/index.js b/web/src/index.js
index 80fc9bf..f705d0e 100644
--- a/web/src/index.js
+++ b/web/src/index.js
@@ -47,6 +47,13 @@ const defaultState = {
},
}
+const makeFrequentlyUsedState = ({ stickerIDs, stickers } = {}) => ({
+ id: "frequently-used",
+ title: "Frequently used",
+ stickerIDs: stickerIDs ?? [],
+ stickers: stickers ?? [],
+})
+
class App extends Component {
constructor(props) {
super(props)
@@ -57,29 +64,26 @@ class App extends Component {
error: null,
stickersPerRow: parseInt(localStorage.mauStickersPerRow || "4"),
theme: localStorage.mauStickerThemeOverride || this.defaultTheme,
- frequentlyUsed: {
- id: "frequently-used",
- title: "Frequently used",
- stickerIDs: frequent.get(),
- stickers: [],
- },
+ frequentlyUsed: makeFrequentlyUsedState(),
filtering: defaultState.filtering,
}
+
if (!supportedThemes.includes(this.state.theme)) {
this.state.theme = "light"
}
if (!supportedThemes.includes(this.defaultTheme)) {
this.defaultTheme = "light"
}
- this.stickersByID = new Map(JSON.parse(localStorage.mauFrequentlyUsedStickerCache || "[]"))
- this.state.frequentlyUsed.stickers = this._getStickersByID(this.state.frequentlyUsed.stickerIDs)
+
this.imageObserver = null
this.packListRef = null
this.navRef = null
+
this.searchStickers = this.searchStickers.bind(this)
this.sendSticker = this.sendSticker.bind(this)
this.navScroll = this.navScroll.bind(this)
this.reloadPacks = this.reloadPacks.bind(this)
+ this.clearFrequentlyUsed = this.clearFrequentlyUsed.bind(this)
this.observeSectionIntersections = this.observeSectionIntersections.bind(this)
this.observeImageIntersections = this.observeImageIntersections.bind(this)
}
@@ -88,17 +92,26 @@ class App extends Component {
return ids.map(id => this.stickersByID.get(id)).filter(sticker => !!sticker)
}
+ _setFrequentlyUsed(stickerIDs = []) {
+ const stickers = this._getStickersByID(stickerIDs)
+ const frequentlyUsed = makeFrequentlyUsedState({ stickerIDs, stickers })
+ this.setState({ frequentlyUsed })
+ frequent.setFrequentlyUsedCacheStorage(stickers)
+ }
+
updateFrequentlyUsed() {
const stickerIDs = frequent.get()
- const stickers = this._getStickersByID(stickerIDs)
- this.setState({
- frequentlyUsed: {
- ...this.state.frequentlyUsed,
- stickerIDs,
- stickers,
- },
- })
- localStorage.mauFrequentlyUsedStickerCache = JSON.stringify(stickers.map(sticker => [sticker.id, sticker]))
+ this._setFrequentlyUsed(stickerIDs)
+ }
+
+ refreshFrequentlyUsed(packs) {
+ const stickerIDs = frequent.removeNotFoundFromStorage(packs)
+ this._setFrequentlyUsed(stickerIDs)
+ }
+
+ clearFrequentlyUsed() {
+ frequent.removeAll()
+ this._setFrequentlyUsed()
}
searchStickers(e) {
@@ -152,6 +165,10 @@ class App extends Component {
this._loadPacks(true)
}
+ _initializeStickersByID(ids) {
+ this.stickersByID = new Map(ids ?? [])
+ }
+
async populateStickersByID(allPacks) {
const allStickers = allPacks.map(({ stickers }) => stickers).flat()
allStickers.forEach((sticker) => {
@@ -187,17 +204,18 @@ class App extends Component {
loading: false,
})
this.populateStickersByID(fetchedPacks)
- this.updateFrequentlyUsed()
+ this.refreshFrequentlyUsed(fetchedPacks)
return fetchedPacks
}, error => this.setState({ loading: false, error }))
}
componentDidMount() {
document.documentElement.style.setProperty("--stickers-per-row", this.state.stickersPerRow.toString())
+
this._loadPacks()
- this.imageObserver = new IntersectionObserver(this.observeImageIntersections, {
- rootMargin: "100px",
- })
+ this._initializeStickersByID(frequent.getFromCache())
+
+ this.imageObserver = new IntersectionObserver(this.observeImageIntersections, { rootMargin: "100px" })
this.sectionObserver = new IntersectionObserver(this.observeSectionIntersections)
}
@@ -309,6 +327,7 @@ const Settings = ({ app }) => html`
Settings