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

Commit

Permalink
Add support for custom recents providers (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
joeattardi committed Jun 11, 2022
1 parent a94914e commit 848ee8b
Show file tree
Hide file tree
Showing 22 changed files with 294 additions and 180 deletions.
19 changes: 19 additions & 0 deletions docs/docs/02 - usage/15 - recent-emojis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Recent emojis

PicMo will keep track of your most recently used emojis in the "Recently Used" category. As emojis are selected, they will be added to the "Recently Used" category.

## Recents providers

### Built-in web storage providers

By default, recent emoji data is kept in [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). This is managed by the [`LocalStorageProvider`](../api/picmo/classes/local-storage-provider) class.

PicMo also includes a [`SessionStorageProvider`](../api/picmo/classes/session-storage-provider) class that uses [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) instead.

A provider is selected by passing a `recentsProvider` option to the [`createPicker`](../api/picmo/functions/create-picker) function. The value should be an *instance* of the provider class.

### Creating a custom recents provider

You can create a custom provider to store recent emojis in some other way. To do this, you need to create a class that extends from the [`RecentsProvider`](../api/picmo/classes/recents-provider) class and implement the three methods required (`clear`, `getRecents`, and `addOrUpdateRecent`).

An instance of this class should be passed to the `recentsProvider` option of the [`createPicker`](../api/picmo/functions/create-picker) function.
7 changes: 7 additions & 0 deletions docs/docs/api/picmo/classes/emoji-picker.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ Adds an event listener to the picker.
- `listener`: The function to call when this event is emitted.
- **Type**: `function`

### `clearRecents`

```
clearRecents(): void
```

Removes all recent emojis from the picker, as managed by the recents provider.

### `destroy`

Expand Down
3 changes: 3 additions & 0 deletions docs/docs/api/picmo/classes/local-storage-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `LocalStorageProvider`

A [`RecentsProvider`](./recents-provider) that uses [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to store the recent emojis.
2 changes: 1 addition & 1 deletion docs/docs/api/picmo/classes/native-renderer.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Renders emojis using the native operating system font glyphs.

```
import { NativeRenderer } from 'picmo/renderers/native';
import { NativeRenderer } from 'picmo';
```

<pre>
Expand Down
35 changes: 35 additions & 0 deletions docs/docs/api/picmo/classes/recents-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# `RecentsProvider`

Abstract base class that provides a storage mechanism for remembering recently selected emojis.

To create a custom recents provider, create a subclass of `RecentsProvider`.

```
import { RecentsProvider } from 'picmo';
```

## Methods

### `clear`

```
clear(): void
```

Removes all stored recent emojis.

### `getRecents`

<pre>
getRecents(maxCount: number): <a href="../types/emoji-record">EmojiRecord</a>[]
</pre>

Gets the current set of recent emojis. The returned list will be truncated to satisfy the `maxCount` parameter.

### `addOrUpdateRecent`

<pre>
addOrUpdateRecent(emoji: <a href="../types/emoji-record">EmojiRecord</a>, maxCount: number): void
</pre>

Adds a new recent emoji to the list, or updates an existing one if it already exists.
3 changes: 3 additions & 0 deletions docs/docs/api/picmo/classes/session-storage-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# `SessionStorageProvider`

A [`RecentsProvider`](./recents-provider) that uses [session storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) to store the recent emojis.
75 changes: 75 additions & 0 deletions docs/docs/api/picmo/types/emoji-record.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# `EmojiRecord`

Describes a single emoji inside the picker.

This is _not_ what is emitted when an emoji is selected; for that, see [`EmojiSelection`](./emoji-selection). Rather, it is used internally by the picker as well as the recents provider.

## Properties

### `custom`

- **Type**: `boolean` | `undefined`

Whether or not this is a custom emoji.

### `data`

- **Type**: `any` | `undefined`

Arbitrary data associated with an emoji. Only applies to custom emojis.

### `emoji`

- **Type**: `string`

The native emoji string containing the Unicode sequenece for the emoji. For custom emojis, this is a unique identifier for the custom emoji.

### `hexcode`

- **Type**: `string` | `undefined`

The hex codes for the emoji, separated by hyphens, e.g. `'1F3CB-1F3FB-200D-2642-FE0F'`.

Only applies to non-custom emojis.

### `label`

- **Type**: `string`

The display name for the emoji.

### `order`

- **Type**: `number` | `undefined`

The order of the emoji within its category.

Only applies to non-custom emojis; custom emojis are ordered depending on their order in the `custom` array.

### `skins`

- **Type**: `EmojiRecord[]` | `undefined`

Any variants of the emoji, typically for different skin tones, though sometimes they can be for other attributes such as hair color.

### `tags`

- **Type**: `string[]` | `undefined`

An array of tags for this emoji, if any.

### `url`

- **Type**: `string` | `undefined`

For custom emojis, the URL of the image to use for the emoji.

Only applies to custom emojis.

### `version`

- **Type**: `number` | `undefined`

For non-custom emojis, specifies the version of the Emoji specification that the emoji was added in.

Only applies to non-custom emojis.
7 changes: 7 additions & 0 deletions docs/docs/api/picmo/types/picker-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ The maximum number of recent emojis to cache locally.

The messages data to use for the picker, containing category names. If not specified, the data will be downloaded from the CDN when the database is created.

### `recentsProvider`

- **Type**: [`RecentsProvider`](./recents-provider) subclass
- **Default**: Instance of [`LocalStorageProvider`](./local-storage-provider)

The provider to use for storing recent emojis.

### `renderer`

- **Type**: [`Renderer`](../classes/renderer) subclass instance
Expand Down
4 changes: 2 additions & 2 deletions docs/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const config = {
type: 'doc',
docId: 'getting-started/overview',
position: 'right',
label: 'Docs'
label: 'Guide'
},
{
type: 'doc',
Expand All @@ -83,7 +83,7 @@ const config = {
style: 'dark',
links: [
{
title: 'Docs',
title: 'Guide',
items: [
{
label: 'Getting Started',
Expand Down
19 changes: 12 additions & 7 deletions packages/picmo/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import { Locale, MessagesDataset, Emoji } from 'emojibase';
import { initDatabase as initDatabaseInternal } from './data/emojiData';

export { createPicker } from './createPicker';
export { Renderer } from './renderers/renderer';
export { deleteDatabase } from './data/emojiData';
export { clear as deleteRecents } from './recents';
export { EmojiPicker } from './views/EmojiPicker';
export * from './util';
export * from './focusTrap';
export * from './options';
export * from './events';
export { ExternalEvent, ExternalEventKey } from './ExternalEvents';
export * from './types';
export * from './themes';
import { DataStoreFactory } from './data/DataStore';

export { createPicker } from './createPicker';
export { EmojiPicker } from './views/EmojiPicker';
export { ExternalEvent, ExternalEventKey } from './ExternalEvents';

export { Renderer } from './renderers/renderer';
export { NativeRenderer } from './renderers/native';

export { default as en } from './i18n/lang-en';

export { deleteDatabase } from './data/emojiData';
import { DataStoreFactory } from './data/DataStore';
export { IndexedDbStoreFactory } from './data/IndexedDbStore';
export { InMemoryStoreFactory } from './data/InMemoryStore';

export * from './recents/index';

export async function createDatabase(locale: Locale, factory: DataStoreFactory, staticMessages?: MessagesDataset, staticEmojis?: Emoji[]): Promise<void> {
const database = await initDatabaseInternal(locale, factory, staticMessages, staticEmojis);
database.close();
Expand Down
11 changes: 7 additions & 4 deletions packages/picmo/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { NativeRenderer } from './renderers/native';
import { lightTheme } from './themes';
import en from './i18n/lang-en';
import { IndexedDbStoreFactory } from './data/IndexedDbStore';
import { LocalStorageProvider } from './recents/LocalStorageProvider';

const defaultOptions: Partial<PickerOptions> = {
renderer: new NativeRenderer(),
Expand All @@ -12,20 +13,22 @@ const defaultOptions: Partial<PickerOptions> = {

animate: true,

showSearch: true,
showCategoryTabs: true,
showVariants: true,
showRecents: true,
showPreview: true,
showRecents: true,
showSearch: true,
showVariants: true,

emojisPerRow: 8,
visibleRows: 6,

emojiVersion: 'auto',
maxRecents: 50,
i18n: en,
locale: 'en',

maxRecents: 50,
recentsProvider: new LocalStorageProvider(),

custom: []
};

Expand Down
38 changes: 14 additions & 24 deletions packages/picmo/src/recents.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import { EmojiRecord } from './types';
import { RecentsProvider } from './recents/RecentsProvider';

const LOCAL_STORAGE_KEY = 'PicMo:recents';
// Deprecated legacy interface to clear recents for backwards compatibility.
// This should not be used by new code; instead, call clear() on the
// RecentsProvider itself.

export function clear(): void {
localStorage.removeItem(LOCAL_STORAGE_KEY);
}
// It's a little odd and won't work well with a custom provider, but leaving it in
// so as not to create breaking changes.

// @deprecated Remove in 6.0.0.

export function getRecents(maxCount: number): Array<EmojiRecord> {
try {
const recents = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY) ?? '[]');
return recents.slice(0, maxCount);
} catch (error) { // localStorage is not available, no recents
return [];
}
let provider: RecentsProvider;

export function setProvider(_provider: RecentsProvider) {
provider = _provider;
}

export function addOrUpdateRecent(emoji: EmojiRecord, maxCount: number) {
// Add the new recent to the beginning of the list, removing it if it exists already
const recents = [
emoji,
...getRecents(maxCount).filter(recent => recent.hexcode !== emoji.hexcode)
].slice(0, maxCount);

try {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(recents));
} catch (error) {
// localStorage is not available, no recents
}
export function clear(): void {
provider.clear();
}
7 changes: 7 additions & 0 deletions packages/picmo/src/recents/LocalStorageProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { WebStorageProvider } from './WebStorageProvider';

export class LocalStorageProvider extends WebStorageProvider {
constructor() {
super(localStorage);
}
}
7 changes: 7 additions & 0 deletions packages/picmo/src/recents/RecentsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { EmojiRecord } from '../types';

export abstract class RecentsProvider {
abstract clear(): void;
abstract getRecents(maxCount: number): Array<EmojiRecord>;
abstract addOrUpdateRecent(emoji: EmojiRecord, maxCount: number): void;
}
7 changes: 7 additions & 0 deletions packages/picmo/src/recents/SessionStorageProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { WebStorageProvider } from './WebStorageProvider';

export class SessionStorageProvider extends WebStorageProvider {
constructor() {
super(sessionStorage);
}
}
40 changes: 40 additions & 0 deletions packages/picmo/src/recents/WebStorageProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { EmojiRecord } from '../types';
import { RecentsProvider } from './RecentsProvider';

const STORAGE_KEY = 'PicMo:recents';

export abstract class WebStorageProvider extends RecentsProvider {
storage: Storage;

constructor(storage: Storage) {
super();
this.storage = storage;
}

clear(): void {
this.storage.removeItem(STORAGE_KEY);
}

getRecents(maxCount: number): Array<EmojiRecord> {
try {
const recents = JSON.parse(this.storage.getItem(STORAGE_KEY) ?? '[]');
return recents.slice(0, maxCount);
} catch (error) { // storage is not available, no recents
return [];
}
}

addOrUpdateRecent(emoji: EmojiRecord, maxCount: number) {
// Add the new recent to the beginning of the list, removing it if it exists already
const recents = [
emoji,
...this.getRecents(maxCount).filter(recent => recent.hexcode !== emoji.hexcode)
].slice(0, maxCount);

try {
this.storage.setItem(STORAGE_KEY, JSON.stringify(recents));
} catch (error) {
console.warn('storage is not available, recent emojis will not be saved');
}
}
}
3 changes: 3 additions & 0 deletions packages/picmo/src/recents/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { LocalStorageProvider } from './LocalStorageProvider';
export { SessionStorageProvider } from './SessionStorageProvider';
export { RecentsProvider } from './RecentsProvider';
6 changes: 3 additions & 3 deletions packages/picmo/src/renderers/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ export abstract class Renderer {
const { content, resolver } = this.render(emoji, classNames);
const contentElement = content instanceof HTMLElement ? content : content.el;

if (lazyLoader && resolver) {
return lazyLoader.lazyLoad(contentElement, resolver)
}
// if (lazyLoader && resolver) {
// return lazyLoader.lazyLoad(contentElement, resolver)
// }

if (resolver) {
resolver();
Expand Down
Loading

0 comments on commit 848ee8b

Please sign in to comment.