94 lines
2.6 KiB
JavaScript
94 lines
2.6 KiB
JavaScript
import emojiGroups from 'shared/components/emoji/emojisGroup.json';
|
|
|
|
// Recently used emojis persisted in localStorage.
|
|
const RECENT_EMOJI_KEY = 'emoji-icon-picker.recent-emojis';
|
|
const MAX_RECENT_EMOJIS = 16;
|
|
|
|
const matchesSearch = (emoji, term) =>
|
|
emoji.slug.replaceAll('_', ' ').includes(term) ||
|
|
emoji.name.toLowerCase().includes(term);
|
|
|
|
// Emoji sections for the search term; prepends "Frequently used" when idle.
|
|
export const buildEmojiSections = (search, recentEmojis, frequentLabel) => {
|
|
const term = search.trim().toLowerCase();
|
|
if (term) {
|
|
return emojiGroups
|
|
.map(({ name, emojis }) => ({
|
|
name,
|
|
emojis: emojis.filter(e => matchesSearch(e, term)),
|
|
}))
|
|
.filter(group => group.emojis.length);
|
|
}
|
|
return [
|
|
...(recentEmojis.length
|
|
? [{ name: frequentLabel, emojis: recentEmojis }]
|
|
: []),
|
|
...emojiGroups,
|
|
];
|
|
};
|
|
|
|
// Samples an emoji's average color (via canvas) as a translucent hover tint. Cached per emoji.
|
|
const emojiTintCache = new Map();
|
|
const NEUTRAL_TINT = 'rgb(var(--slate-9) / 0.12)';
|
|
|
|
const sampleEmojiTint = emoji => {
|
|
const canvas = Object.assign(document.createElement('canvas'), {
|
|
width: 16,
|
|
height: 16,
|
|
});
|
|
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
|
ctx.font = '14px serif';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.fillText(emoji, 8, 9);
|
|
|
|
const { data } = ctx.getImageData(0, 0, 16, 16);
|
|
const pixels = Array.from(
|
|
{ length: data.length / 4 },
|
|
(_, i) => i * 4
|
|
).filter(i => data[i + 3] > 16);
|
|
if (!pixels.length) return NEUTRAL_TINT;
|
|
|
|
const avg = offset =>
|
|
Math.round(
|
|
pixels.reduce((sum, i) => sum + data[i + offset], 0) / pixels.length
|
|
);
|
|
|
|
return `rgba(${avg(0)}, ${avg(1)}, ${avg(2)}, 0.16)`;
|
|
};
|
|
|
|
export const getEmojiTint = emoji => {
|
|
if (!emojiTintCache.has(emoji)) {
|
|
let tint = NEUTRAL_TINT;
|
|
try {
|
|
tint = sampleEmojiTint(emoji);
|
|
} catch {
|
|
/* canvas unavailable */
|
|
}
|
|
emojiTintCache.set(emoji, tint);
|
|
}
|
|
return emojiTintCache.get(emoji);
|
|
};
|
|
|
|
export const getRecentEmojis = () => {
|
|
try {
|
|
const stored = JSON.parse(localStorage.getItem(RECENT_EMOJI_KEY) ?? '[]');
|
|
return Array.isArray(stored) ? stored : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
};
|
|
|
|
export const addRecentEmoji = emoji => {
|
|
const updated = [
|
|
emoji,
|
|
...getRecentEmojis().filter(item => item.slug !== emoji.slug),
|
|
].slice(0, MAX_RECENT_EMOJIS);
|
|
try {
|
|
localStorage.setItem(RECENT_EMOJI_KEY, JSON.stringify(updated));
|
|
} catch {
|
|
/* private mode; recents are best-effort */
|
|
}
|
|
return updated;
|
|
};
|