diff --git a/web/frequently-used.js b/web/frequently-used.js new file mode 100644 index 0000000..1601e03 --- /dev/null +++ b/web/frequently-used.js @@ -0,0 +1,24 @@ +// Copyright (c) 2020 Tulir Asokan +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +const FREQUENTLY_USED = JSON.parse(window.localStorage.mauFrequentlyUsedStickerIDs || "{}") +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) + FREQUENTLY_USED_SORTED = null +} + +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) + .map(([emoji]) => emoji) + } + return FREQUENTLY_USED_SORTED.slice(0, limit) +} diff --git a/web/index.css b/web/index.css index e3c8583..d0ea06c 100644 --- a/web/index.css +++ b/web/index.css @@ -37,6 +37,10 @@ nav { nav > a > div.sticker { width: 12vw; height: 12vw; } + nav > a > div.sticker.icon > img { + width: 70%; + height: 70%; + padding: 15%; } div.pack-list, nav { scrollbar-width: none; } diff --git a/web/index.js b/web/index.js index ab39761..236e327 100644 --- a/web/index.js +++ b/web/index.js @@ -5,7 +5,8 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. import { html, render, Component } from "https://unpkg.com/htm/preact/index.mjs?module" import { Spinner } from "./spinner.js" -import { sendSticker } from "./widget-api.js" +import * as widgetAPI from "./widget-api.js" +import * as frequent from "./frequently-used.js" // The base URL for fetching packs. The app will first fetch ${PACK_BASE_URL}/index.json, // then ${PACK_BASE_URL}/${packFile} for each packFile in the packs object of the index.json file. @@ -26,9 +27,35 @@ class App extends Component { packs: [], loading: true, error: null, + frequentlyUsed: { + id: "frequently-used", + title: "Frequently used", + stickerIDs: frequent.get(), + stickers: [], + }, } + 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.sendSticker = this.sendSticker.bind(this) + } + + _getStickersByID(ids) { + return ids.map(id => this.stickersByID.get(id)).filter(sticker => !!sticker) + } + + 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])) } componentDidMount() { @@ -46,11 +73,15 @@ class App extends Component { for (const packFile of indexData.packs) { const packRes = await fetch(`${PACKS_BASE_URL}/${packFile}`) const packData = await packRes.json() + for (const sticker of packData.stickers) { + this.stickersByID.set(sticker.id, sticker) + } this.setState({ packs: [...this.state.packs, packData], loading: false, }) } + this.updateFrequentlyUsed() }, error => this.setState({ loading: false, error })) this.imageObserver = new IntersectionObserver(this.observeImageIntersections, { @@ -98,6 +129,14 @@ class App extends Component { this.sectionObserver.disconnect() } + sendSticker(evt) { + const id = evt.currentTarget.getAttribute("data-sticker-id") + const sticker = this.stickersByID.get(id) + frequent.add(id) + this.updateFrequentlyUsed() + widgetAPI.sendSticker(sticker) + } + render() { if (this.state.loading) { return html`
<${Spinner} size=${80} green />
` @@ -111,10 +150,12 @@ class App extends Component { } return html`
this.packListRef = elem}> - ${this.state.packs.map(pack => html`<${Pack} id=${pack.id} pack=${pack}/>`)} + <${Pack} pack=${this.state.frequentlyUsed} send=${this.sendSticker} /> + ${this.state.packs.map(pack => html`<${Pack} id=${pack.id} pack=${pack} send=${this.sendSticker} />`)}
` } @@ -128,29 +169,33 @@ const scrollToSection = (evt, id) => { evt.preventDefault() } -const NavBarItem = ({ pack }) => html` +const NavBarItem = ({ pack, iconOverride = null, altOverride = null }) => html` scrollToSection(evt, pack.id)) : undefined}> -
- ${pack.stickers[0].body} +
+ ${iconOverride ? html` + ${altOverride} + ` : html` + ${pack.stickers[0].body} + `}
` -const Pack = ({ pack }) => html` +const Pack = ({ pack, send }) => html`

${pack.title}

${pack.stickers.map(sticker => html` - <${Sticker} key=${sticker.id} content=${sticker}/> + <${Sticker} key=${sticker.id} content=${sticker} send=${send}/> `)}
` -const Sticker = ({ content }) => html` -
sendSticker(content)}> +const Sticker = ({ content, send }) => html` +
${content.body}
` diff --git a/web/index.sass b/web/index.sass index 6f5ad4a..a2e84fa 100644 --- a/web/index.sass +++ b/web/index.sass @@ -54,6 +54,10 @@ nav > div.sticker width: $nav-sticker-size height: $nav-sticker-size + > div.sticker.icon > img + width: 70% + height: 70% + padding: 15% div.pack-list, nav scrollbar-width: none diff --git a/web/res/favorite.svg b/web/res/favorite.svg new file mode 100644 index 0000000..4af2b49 --- /dev/null +++ b/web/res/favorite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/res/recent.svg b/web/res/recent.svg new file mode 100644 index 0000000..59be87d --- /dev/null +++ b/web/res/recent.svg @@ -0,0 +1 @@ + \ No newline at end of file