Signal-Desktop/ts/components/fun/FunEmojiLocalizationProvide...

146 lines
4.1 KiB
TypeScript

// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, {
createContext,
memo,
type ReactNode,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import type { LocaleEmojiListType } from '../../types/emoji';
import { createLogger } from '../../logging/log';
import * as Errors from '../../types/errors';
import { drop } from '../../util/drop';
import {
getEmojiDefaultEnglishLocalizerIndex,
getEmojiDefaultEnglishSearchIndex,
} from './data/emojis';
import {
createFunEmojiLocalizerIndex,
type FunEmojiLocalizerIndex,
} from './useFunEmojiLocalizer';
import {
createFunEmojiSearchIndex,
type FunEmojiSearchIndex,
} from './useFunEmojiSearch';
import type { LocalizerType } from '../../types/I18N';
import { strictAssert } from '../../util/assert';
const log = createLogger('FunEmojiLocalizationProvider');
export type FunEmojiLocalizationContextType = Readonly<{
emojiSearchIndex: FunEmojiSearchIndex;
emojiLocalizerIndex: FunEmojiLocalizerIndex;
}>;
export const FunEmojiLocalizationContext =
createContext<FunEmojiLocalizationContextType | null>(null);
export function useFunEmojiLocalization(): FunEmojiLocalizationContextType {
const fun = useContext(FunEmojiLocalizationContext);
strictAssert(
fun != null,
'Must be wrapped with <FunEmojiLocalizationProvider>'
);
return fun;
}
export type FunEmojiLocalizationProviderProps = Readonly<{
i18n: LocalizerType;
children: ReactNode;
}>;
export const FunEmojiLocalizationProvider = memo(
function FunEmojiLocalizationProvider(
props: FunEmojiLocalizationProviderProps
) {
const localeEmojiList = useLocaleEmojiList(props.i18n);
const emojiSearchIndex = useFunEmojiSearchIndex(localeEmojiList);
const emojiLocalizerIndex = useFunEmojiLocalizerIndex(localeEmojiList);
const context = useMemo((): FunEmojiLocalizationContextType => {
return { emojiSearchIndex, emojiLocalizerIndex };
}, [emojiSearchIndex, emojiLocalizerIndex]);
return (
<FunEmojiLocalizationContext.Provider value={context}>
{props.children}
</FunEmojiLocalizationContext.Provider>
);
}
);
export type FunEmptyEmojiLocalizationProviderProps = Readonly<{
children: ReactNode;
}>;
export function FunDefaultEnglishEmojiLocalizationProvider(
props: FunEmptyEmojiLocalizationProviderProps
): JSX.Element {
const context = useMemo(() => {
return {
emojiSearchIndex: getEmojiDefaultEnglishSearchIndex(),
emojiLocalizerIndex: getEmojiDefaultEnglishLocalizerIndex(),
};
}, []);
return (
<FunEmojiLocalizationContext.Provider value={context}>
{props.children}
</FunEmojiLocalizationContext.Provider>
);
}
function useLocaleEmojiList(i18n: LocalizerType): LocaleEmojiListType | null {
const locale = useMemo(() => i18n.getLocale(), [i18n]);
const [localeEmojiList, setLocaleEmojiList] =
useState<LocaleEmojiListType | null>(null);
useEffect(() => {
let canceled = false;
async function run(): Promise<void> {
try {
const list = await window.SignalContext.getLocalizedEmojiList(locale);
if (!canceled) {
setLocaleEmojiList(list);
}
} catch (error) {
log.error(
`FunProvider: Failed to get localized emoji list for "${locale}"`,
Errors.toLogFormat(error)
);
}
}
drop(run());
return () => {
canceled = true;
};
}, [locale]);
return localeEmojiList;
}
function useFunEmojiSearchIndex(
localeEmojiList: LocaleEmojiListType | null
): FunEmojiSearchIndex {
const funEmojiSearchIndex = useMemo(() => {
return localeEmojiList != null
? createFunEmojiSearchIndex(localeEmojiList)
: getEmojiDefaultEnglishSearchIndex();
}, [localeEmojiList]);
return funEmojiSearchIndex;
}
function useFunEmojiLocalizerIndex(
localeEmojiList: LocaleEmojiListType | null
): FunEmojiLocalizerIndex {
const funEmojiLocalizerIndex = useMemo(() => {
return localeEmojiList != null
? createFunEmojiLocalizerIndex(localeEmojiList)
: getEmojiDefaultEnglishLocalizerIndex();
}, [localeEmojiList]);
return funEmojiLocalizerIndex;
}