feat(gif-picker): add gif groups
feat(gif-picker): increase default size
This commit is contained in:
parent
eaeff1b8d5
commit
6f4cc62530
11 changed files with 399 additions and 82 deletions
29
packages/chat/src/components/emoji/emojiPicker.tsx
Normal file
29
packages/chat/src/components/emoji/emojiPicker.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import { Button } from "@methanium/ui";
|
||||
import { Emoji } from "@methanium/ui/markdown";
|
||||
import { getRecentEmojis, useEmojiRanks } from "./emojiRanks";
|
||||
|
||||
export default function EmojiPicker({
|
||||
onSelect,
|
||||
}: {
|
||||
onSelect: (emoji: string) => void;
|
||||
}) {
|
||||
const { ranks } = useEmojiRanks();
|
||||
const emojis = getRecentEmojis(ranks, 3);
|
||||
|
||||
return (
|
||||
<div className="flex flex-row gap-1 p-1">
|
||||
{emojis.map((emoji) => (
|
||||
<Button
|
||||
key={emoji}
|
||||
aria-label={`Select ${emoji}`}
|
||||
className="h-10 w-10 p-0"
|
||||
onClick={() => onSelect(emoji)}
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
<Emoji shortcode={emoji} />
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
95
packages/chat/src/components/emoji/emojiRanks.ts
Normal file
95
packages/chat/src/components/emoji/emojiRanks.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { useStorage } from "@tensamin/storage/context";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { normalizeShortcode } from "@methanium/ui/markdown";
|
||||
|
||||
const RANKS_CHANGED_EVENT = "tensamin-reaction-ranks-changed";
|
||||
let recordQueue = Promise.resolve();
|
||||
|
||||
function normalizeRanks(ranks: Record<string, number>) {
|
||||
return Object.entries(ranks).reduce<Record<string, number>>(
|
||||
(normalized, [value, frequency]) => {
|
||||
const shortcode = normalizeShortcode(value);
|
||||
if (!shortcode || !Number.isFinite(frequency) || frequency <= 0) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
normalized[shortcode] = (normalized[shortcode] ?? 0) + frequency;
|
||||
return normalized;
|
||||
},
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
function rankedEmojis(ranks: Record<string, number>) {
|
||||
return Object.entries(ranks)
|
||||
.filter(
|
||||
([emoji, frequency]) =>
|
||||
normalizeShortcode(emoji) !== undefined &&
|
||||
Number.isFinite(frequency) &&
|
||||
frequency > 0,
|
||||
)
|
||||
.sort(([emojiA, frequencyA], [emojiB, frequencyB]) =>
|
||||
frequencyB === frequencyA
|
||||
? emojiA.localeCompare(emojiB)
|
||||
: frequencyB - frequencyA,
|
||||
)
|
||||
.flatMap(([emoji]) => {
|
||||
const shortcode = normalizeShortcode(emoji);
|
||||
return shortcode ? [shortcode] : [];
|
||||
});
|
||||
}
|
||||
|
||||
export function getRecentEmojis(ranks: Record<string, number>, amount: number) {
|
||||
return rankedEmojis(ranks).slice(0, amount);
|
||||
}
|
||||
|
||||
export function useEmojiRanks() {
|
||||
const { load, save } = useStorage();
|
||||
const [ranks, setRanks] = useState<Record<string, number>>({});
|
||||
|
||||
useEffect(() => {
|
||||
void load("reactions").then((storedRanks) => {
|
||||
const normalized = normalizeRanks(storedRanks);
|
||||
setRanks(normalized);
|
||||
if (JSON.stringify(normalized) !== JSON.stringify(storedRanks)) {
|
||||
void save("reactions", normalized);
|
||||
}
|
||||
});
|
||||
|
||||
function handleRanksChanged(event: Event) {
|
||||
setRanks((event as CustomEvent<Record<string, number>>).detail);
|
||||
}
|
||||
|
||||
window.addEventListener(RANKS_CHANGED_EVENT, handleRanksChanged);
|
||||
return () =>
|
||||
window.removeEventListener(RANKS_CHANGED_EVENT, handleRanksChanged);
|
||||
}, [load, save]);
|
||||
|
||||
return { ranks };
|
||||
}
|
||||
|
||||
export function useRecordEmojiUse() {
|
||||
const { load, save } = useStorage();
|
||||
|
||||
return useCallback(
|
||||
(emoji: string) => {
|
||||
const shortcode = normalizeShortcode(emoji);
|
||||
if (!shortcode) return;
|
||||
|
||||
recordQueue = recordQueue
|
||||
.catch(() => undefined)
|
||||
.then(async () => {
|
||||
const normalized = normalizeRanks(await load("reactions"));
|
||||
const next = {
|
||||
...normalized,
|
||||
[shortcode]: (normalized[shortcode] ?? 0) + 1,
|
||||
};
|
||||
await save("reactions", next);
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(RANKS_CHANGED_EVENT, { detail: next }),
|
||||
);
|
||||
});
|
||||
},
|
||||
[load, save],
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue