Emoji: Add back to state (#39402)
This commit is contained in:
1 parent
f79e0109fe
commit
6cf9be9880
13 files changed
+217
-170
No files matched your search
@@ -140,7 +140,7 @@ export const EditFieldModal = forwardRef<
|
||||
|
||||
const customEmojis = useCustomEmojis();
|
||||
const customEmojiCodes = useMemo(
|
||||
() => Object.keys(customEmojis ?? {}),
|
||||
() => Object.keys(customEmojis),
|
||||
[customEmojis],
|
||||
);
|
||||
const checkField = useCallback(
|
||||
|
||||
@@ -323,10 +323,7 @@ class EmojiPickerDropdown extends PureComponent {
|
||||
EmojiPickerAsync().then(EmojiMart => {
|
||||
EmojiPicker = EmojiMart.Picker;
|
||||
Emoji = EmojiMart.Emoji;
|
||||
|
||||
void EmojiMart.loadCustomEmojiData().then(() => {
|
||||
this.setState({ loading: false });
|
||||
});
|
||||
this.setState({ loading: false });
|
||||
}).catch(() => {
|
||||
this.setState({ loading: false, active: false });
|
||||
});
|
||||
|
||||
@@ -13,8 +13,6 @@ import { usePickerEmojis } from './picker';
|
||||
|
||||
const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_16_0.png`;
|
||||
|
||||
export { fetchCustomEmojiData as loadCustomEmojiData } from './picker';
|
||||
|
||||
export const Picker: FC<PickerProps> = ({
|
||||
set = 'twitter',
|
||||
sheetSize = 32,
|
||||
@@ -24,17 +22,13 @@ export const Picker: FC<PickerProps> = ({
|
||||
...props
|
||||
}) => {
|
||||
const { mode } = useEmojiAppState();
|
||||
const { customCategories, customEmojis } = usePickerEmojis();
|
||||
|
||||
if (!customEmojis) {
|
||||
return null;
|
||||
}
|
||||
const { categories, emojis } = usePickerEmojis();
|
||||
|
||||
return (
|
||||
<PickerRaw
|
||||
data={EmojiData}
|
||||
custom={customEmojis}
|
||||
include={customCategories}
|
||||
custom={emojis}
|
||||
include={categories}
|
||||
set={set}
|
||||
sheetSize={sheetSize}
|
||||
sheetColumns={sheetColumns}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { initialState } from '@/mastodon/initial_state';
|
||||
|
||||
import { toSupportedLocale } from './locale';
|
||||
import { reloadCustomEmojis } from './picker';
|
||||
import type { EmojiWorkerMessage } from './types';
|
||||
import { emojiLogger } from './utils';
|
||||
|
||||
@@ -39,27 +38,37 @@ export async function initializeEmoji() {
|
||||
void fallbackLoad();
|
||||
}, WORKER_TIMEOUT);
|
||||
|
||||
tempWorker.addEventListener('message', (event: MessageEvent<string>) => {
|
||||
const { data: message } = event;
|
||||
tempWorker.addEventListener(
|
||||
'message',
|
||||
(event: MessageEvent<EmojiWorkerMessage>) => {
|
||||
const { data: message } = event;
|
||||
|
||||
worker ??= tempWorker;
|
||||
clearTimeout(timeoutId);
|
||||
worker ??= tempWorker;
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (message !== 'ready') {
|
||||
workerLog(message);
|
||||
return;
|
||||
}
|
||||
const { type } = message;
|
||||
if (type === 'log') {
|
||||
workerLog(message.message);
|
||||
} else if (type === 'done' && message.storeName === 'custom') {
|
||||
void loadEmojisToStore();
|
||||
}
|
||||
|
||||
const debugValue = localStorage.getItem('debug');
|
||||
if (debugValue) {
|
||||
messageWorker({ type: 'debug', debugValue });
|
||||
}
|
||||
if (type !== 'ready') {
|
||||
return; // Exit for other messages.
|
||||
}
|
||||
|
||||
workerLog('loading data');
|
||||
messageWorker(userLocale);
|
||||
messageWorker('custom');
|
||||
messageWorker('shortcodes');
|
||||
});
|
||||
const debugValue = localStorage.getItem('debug');
|
||||
if (debugValue) {
|
||||
messageWorker({ type: 'debug', debugValue });
|
||||
}
|
||||
|
||||
workerLog('loading data');
|
||||
messageWorker(userLocale);
|
||||
messageWorker('custom');
|
||||
messageWorker('shortcodes');
|
||||
void loadEmojisToStore();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async function fallbackLoad() {
|
||||
@@ -71,8 +80,8 @@ async function fallbackLoad() {
|
||||
const customEmojis = await importCustomEmojiData();
|
||||
if (customEmojis && customEmojis.length > 0) {
|
||||
log('loaded %d custom emojis', customEmojis.length);
|
||||
await reloadCustomEmojis();
|
||||
}
|
||||
|
||||
const shortcodes = await importLegacyShortcodes();
|
||||
if (shortcodes?.length) {
|
||||
log('loaded %d legacy shortcodes', shortcodes.length);
|
||||
@@ -82,6 +91,7 @@ async function fallbackLoad() {
|
||||
if (emojis) {
|
||||
log('loaded %d emojis to locale %s', emojis.length, userLocale);
|
||||
}
|
||||
await loadEmojisToStore();
|
||||
}
|
||||
|
||||
export async function loadCustomEmoji() {
|
||||
@@ -92,9 +102,9 @@ export async function loadCustomEmoji() {
|
||||
const emojis = await importCustomEmojiData();
|
||||
if (emojis && emojis.length > 0) {
|
||||
log('loaded %d custom emojis', emojis.length);
|
||||
await reloadCustomEmojis();
|
||||
}
|
||||
}
|
||||
await loadEmojisToStore();
|
||||
}
|
||||
|
||||
function messageWorker(data: EmojiWorkerMessage | string) {
|
||||
@@ -110,3 +120,14 @@ function messageWorker(data: EmojiWorkerMessage | string) {
|
||||
worker.postMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEmojisToStore() {
|
||||
const { store } = await import('@/mastodon/store');
|
||||
const { loadCustomEmojis, loadLocale } =
|
||||
await import('@/mastodon/reducers/slices/emojis');
|
||||
|
||||
loadLocale(userLocale);
|
||||
await store.dispatch(loadCustomEmojis());
|
||||
|
||||
log('loaded emoji data into store');
|
||||
}
|
||||
@@ -27,7 +27,6 @@ export function useEmojiAppState(): EmojiAppState {
|
||||
|
||||
return {
|
||||
currentLocale: locale,
|
||||
locales: [locale],
|
||||
mode,
|
||||
darkTheme: isDarkMode(),
|
||||
assetHost,
|
||||
@@ -64,7 +63,6 @@ export function getEmojiAppState(): EmojiAppState {
|
||||
|
||||
return {
|
||||
currentLocale,
|
||||
locales: [currentLocale],
|
||||
mode: determineEmojiMode(emojiStyle),
|
||||
darkTheme: isDarkMode(),
|
||||
assetHost,
|
||||
|
||||
@@ -1,83 +1,18 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import type { CategoryName, CustomEmoji } from 'emoji-mart';
|
||||
|
||||
import { autoPlayGif } from '@/mastodon/initial_state';
|
||||
import {
|
||||
createAppSelector,
|
||||
useAppSelector,
|
||||
} from '@/mastodon/store/typed_functions';
|
||||
import { createLimitedCache } from '@/mastodon/utils/cache';
|
||||
|
||||
import { emojiLogger } from './utils';
|
||||
|
||||
const log = emojiLogger('picker');
|
||||
|
||||
let customEmojis: CustomEmoji[] | null = null;
|
||||
let customCategories = [
|
||||
'recent',
|
||||
'people',
|
||||
'nature',
|
||||
'foods',
|
||||
'activity',
|
||||
'places',
|
||||
'objects',
|
||||
'symbols',
|
||||
'flags',
|
||||
] as CategoryName[];
|
||||
|
||||
const searchCache = createLimitedCache<LegacyEmoji[]>({ maxSize: 10, log });
|
||||
|
||||
export async function fetchCustomEmojiData() {
|
||||
if (customEmojis !== null) {
|
||||
return customEmojis;
|
||||
}
|
||||
|
||||
const { loadAllCustomEmoji } = await import('./database');
|
||||
const emojisRaw = await loadAllCustomEmoji();
|
||||
|
||||
// If it returns null then custom emojis aren't even loaded yet.
|
||||
if (emojisRaw === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// If it's empty, then they are loaded but there aren't any.
|
||||
if (emojisRaw.length === 0) {
|
||||
customEmojis = [];
|
||||
return customEmojis;
|
||||
}
|
||||
|
||||
const categories = new Set(['custom']);
|
||||
const emojis = [];
|
||||
for (const emoji of emojisRaw) {
|
||||
const name = emoji.shortcode.replaceAll(':', '');
|
||||
emojis.push({
|
||||
name,
|
||||
id: name,
|
||||
custom: true,
|
||||
short_names: [name],
|
||||
imageUrl: autoPlayGif ? emoji.url : emoji.static_url,
|
||||
customCategory: emoji.category,
|
||||
});
|
||||
|
||||
if (emoji.category) {
|
||||
categories.add(`custom-${emoji.category}`);
|
||||
}
|
||||
}
|
||||
|
||||
customEmojis = emojis.toSorted((a, b) => {
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
});
|
||||
customCategories = customCategories.toSpliced(
|
||||
1,
|
||||
0,
|
||||
...(Array.from(categories).toSorted() as CategoryName[]),
|
||||
);
|
||||
log(
|
||||
'loaded %d custom emojis in %d categories',
|
||||
customEmojis.length,
|
||||
categories.size,
|
||||
);
|
||||
|
||||
return customEmojis;
|
||||
}
|
||||
|
||||
type LegacyEmoji =
|
||||
| { id: string; custom?: false; native: string }
|
||||
| {
|
||||
@@ -85,16 +20,6 @@ type LegacyEmoji =
|
||||
custom: true;
|
||||
};
|
||||
|
||||
export async function reloadCustomEmojis() {
|
||||
customEmojis = null;
|
||||
|
||||
const { loadEmojisIntoCache } =
|
||||
await import('@/mastodon/hooks/useCustomEmojis');
|
||||
|
||||
await Promise.all([fetchCustomEmojiData(), loadEmojisIntoCache()]);
|
||||
searchCache.clear();
|
||||
}
|
||||
|
||||
// Replicates the old legacy search function.
|
||||
export async function emojiMartSearch(
|
||||
token: string,
|
||||
@@ -127,19 +52,64 @@ export async function emojiMartSearch(
|
||||
return legacyResults;
|
||||
}
|
||||
|
||||
export function usePickerEmojis() {
|
||||
const [, setLoaded] = useState(customEmojis !== null);
|
||||
const defaultCategories = [
|
||||
'people',
|
||||
'nature',
|
||||
'foods',
|
||||
'activity',
|
||||
'places',
|
||||
'objects',
|
||||
'symbols',
|
||||
'flags',
|
||||
] as CategoryName[];
|
||||
|
||||
useEffect(() => {
|
||||
if (customEmojis === null) {
|
||||
void fetchCustomEmojiData().then(() => {
|
||||
setLoaded(true);
|
||||
});
|
||||
const selectPickerData = createAppSelector(
|
||||
[(state) => state.emojis.custom, (state) => state.emojis.customCategories],
|
||||
(emojis, categories) => {
|
||||
// Create a map of shortcode to category name.
|
||||
const categoryMap = new Map<string, string>();
|
||||
for (const category in categories) {
|
||||
const catEmojis = categories[category];
|
||||
if (!catEmojis?.length) {
|
||||
continue;
|
||||
}
|
||||
for (const shortcode of catEmojis) {
|
||||
categoryMap.set(shortcode, category);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
customEmojis,
|
||||
customCategories,
|
||||
};
|
||||
const customEmojis: CustomEmoji[] = [];
|
||||
for (const shortcode in emojis) {
|
||||
const emoji = emojis[shortcode];
|
||||
if (!emoji) {
|
||||
continue;
|
||||
}
|
||||
|
||||
customEmojis.push({
|
||||
name: shortcode,
|
||||
id: shortcode,
|
||||
custom: true,
|
||||
short_names: [shortcode],
|
||||
imageUrl: autoPlayGif ? emoji.url : emoji.static_url,
|
||||
customCategory: categoryMap.get(shortcode),
|
||||
} as CustomEmoji);
|
||||
}
|
||||
|
||||
searchCache.clear();
|
||||
log('regenerated the picker data');
|
||||
|
||||
return {
|
||||
emojis: customEmojis,
|
||||
categories: [
|
||||
'recent',
|
||||
'custom',
|
||||
...Object.keys(categories).toSorted(),
|
||||
...defaultCategories,
|
||||
] as CategoryName[],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export function usePickerEmojis() {
|
||||
return useAppSelector(selectPickerData);
|
||||
}
|
||||
@@ -109,7 +109,7 @@ export async function updateHtmlWithEmoji({
|
||||
}: {
|
||||
element: Element;
|
||||
locale: string;
|
||||
} & Omit<EmojiAppState, 'currentLocale' | 'locales'>) {
|
||||
} & Omit<EmojiAppState, 'currentLocale'>) {
|
||||
if (mode === EMOJI_MODE_NATIVE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ export type CacheKey =
|
||||
| LocaleWithShortcodes;
|
||||
|
||||
export interface EmojiAppState {
|
||||
locales: Locale[];
|
||||
currentLocale: Locale;
|
||||
mode: EmojiMode;
|
||||
darkTheme: boolean;
|
||||
@@ -82,10 +81,20 @@ export type ExtraCustomEmojiMap = Record<
|
||||
>;
|
||||
|
||||
export type EmojiWorkerMessage =
|
||||
| { type: 'ready' }
|
||||
| {
|
||||
type: 'load';
|
||||
storeName: string;
|
||||
}
|
||||
| {
|
||||
type: 'done';
|
||||
storeName: string;
|
||||
importCount: number;
|
||||
}
|
||||
| {
|
||||
type: 'log';
|
||||
message: string;
|
||||
}
|
||||
| {
|
||||
type: 'debug';
|
||||
debugValue: string;
|
||||
|
||||
@@ -9,13 +9,13 @@ import {
|
||||
import type { EmojiWorkerMessage } from './types';
|
||||
|
||||
addEventListener('message', handleMessage);
|
||||
self.postMessage('ready'); // After the worker is ready, notify the main thread
|
||||
self.postMessage({ type: 'ready' } satisfies EmojiWorkerMessage); // After the worker is ready, notify the main thread
|
||||
|
||||
function handleMessage(event: MessageEvent<EmojiWorkerMessage>) {
|
||||
const { data } = event;
|
||||
if (data.type === 'debug') {
|
||||
debug.enable(data.debugValue);
|
||||
} else {
|
||||
} else if (data.type === 'load') {
|
||||
void loadData(data.storeName);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,10 @@ async function loadData(storeName: string) {
|
||||
}
|
||||
|
||||
if (importCount) {
|
||||
self.postMessage(`loaded ${importCount} emojis into ${storeName}`);
|
||||
self.postMessage({
|
||||
type: 'done',
|
||||
storeName,
|
||||
importCount,
|
||||
} satisfies EmojiWorkerMessage);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user