From a5fe0ac8b5b19294298f5daf73859ff39a271e02 Mon Sep 17 00:00:00 2001 From: ThatOneCalculator Date: Thu, 22 Jun 2023 20:58:44 -0700 Subject: [PATCH] feat: :sparkles: emoji skin tone Closes #9959 --- locales/en-US.yml | 1 + .../client/src/components/MkEmojiPicker.vue | 11 +- .../client/src/pages/settings/reaction.vue | 24 ++++ packages/client/src/scripts/emojilist.ts | 107 +++++------------- packages/client/src/store.ts | 4 + 5 files changed, 60 insertions(+), 87 deletions(-) diff --git a/locales/en-US.yml b/locales/en-US.yml index cd29cebda6..c619c26086 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1109,6 +1109,7 @@ isLocked: "This account has follow approvals" isModerator: "Moderator" isAdmin: "Administrator" isPatron: "Calckey Patron" +reactionPickerSkinTone: "Preferred emoji skin tone" _sensitiveMediaDetection: description: "Reduces the effort of server moderation through automatically recognizing diff --git a/packages/client/src/components/MkEmojiPicker.vue b/packages/client/src/components/MkEmojiPicker.vue index 9cff6b910b..2f6ba21686 100644 --- a/packages/client/src/components/MkEmojiPicker.vue +++ b/packages/client/src/components/MkEmojiPicker.vue @@ -111,7 +111,7 @@
{{ i18n.ts.emoji }}
const customEmojiCategories = emojiCategories; const customEmojis = instance.emojis; const q = ref(null); -const emojilist = await getEmojiData(); const searchResultCustom = ref([]); const searchResultUnicode = ref([]); const tab = ref<"index" | "custom" | "unicode" | "tags">("index"); @@ -321,7 +320,7 @@ watch(q, () => { // 名前にキーワードが含まれている for (const emoji of emojis) { - if (keywords.every((keyword) => emoji.name.includes(keyword))) { + if (keywords.every((keyword) => emoji.slug.includes(keyword))) { matches.add(emoji); if (matches.size >= max) break; } @@ -401,7 +400,7 @@ function reset() { function getKey( emoji: string | Misskey.entities.CustomEmoji | UnicodeEmojiDef ): string { - return typeof emoji === "string" ? emoji : emoji.char || `:${emoji.name}:`; + return typeof emoji === "string" ? emoji : emoji.emoji || `:${emoji.name}:`; } function chosen(emoji: any, ev?: MouseEvent) { diff --git a/packages/client/src/pages/settings/reaction.vue b/packages/client/src/pages/settings/reaction.vue index d8578a6f7f..929c71e957 100644 --- a/packages/client/src/pages/settings/reaction.vue +++ b/packages/client/src/pages/settings/reaction.vue @@ -41,6 +41,27 @@ > + + + + + + + + + @@ -125,6 +146,9 @@ async function reloadAsk() { let reactions = $ref(deepClone(defaultStore.state.reactions)); +const reactionPickerSkinTone = $computed( + defaultStore.makeGetterSetter("reactionPickerSkinTone") +); const reactionPickerSize = $computed( defaultStore.makeGetterSetter("reactionPickerSize") ); diff --git a/packages/client/src/scripts/emojilist.ts b/packages/client/src/scripts/emojilist.ts index ad5f0abeb1..6f0e222a90 100644 --- a/packages/client/src/scripts/emojilist.ts +++ b/packages/client/src/scripts/emojilist.ts @@ -1,6 +1,7 @@ import data from "unicode-emoji-json/data-by-group.json"; -import components from "unicode-emoji-json/data-emoji-components.json"; +import emojiComponents from "unicode-emoji-json/data-emoji-components.json"; import keywordSet from "emojilib"; +import { defaultStore } from "@/store"; export const unicodeEmojiCategories = [ "emotion", @@ -26,13 +27,16 @@ export const categoryMapping = { "Flags": "flags", } as const; -const skinToneModifiers = [ - "light_skin_tone", - "medium_light_skin_tone", - "medium_skin_tone", - "medium_dark_skin_tone", - "dark_skin_tone", -]; +function addSkinTone(emoji: string) { + const skinTone = defaultStore.state.reactionPickerSkinTone; + if (skinTone === 1) return emoji; + if (skinTone === 2) return emoji + emojiComponents.light_skin_tone; + if (skinTone === 3) return emoji + emojiComponents.medium_light_skin_tone; + if (skinTone === 4) return emoji + emojiComponents.medium_skin_tone; + if (skinTone === 5) return emoji + emojiComponents.medium_dark_skin_tone; + if (skinTone === 6) return emoji + emojiComponents.dark_skin_tone; + return emoji; +} const newData = {}; @@ -42,17 +46,12 @@ Object.keys(data).forEach((originalCategory) => { newData[newCategory] = newData[newCategory] || []; Object.keys(data[originalCategory]).forEach((emojiIndex) => { const emojiObj = { ...data[originalCategory][emojiIndex] }; + if (emojiObj.skin_tone_support) { + emojiObj.emoji = addSkinTone(emojiObj.emoji); + } + emojiObj.category = newCategory; emojiObj.keywords = keywordSet[emojiObj.emoji]; newData[newCategory].push(emojiObj); - - if (emojiObj.skin_tone_support) { - skinToneModifiers.forEach((modifier) => { - const modifiedEmojiObj = { ...emojiObj }; - modifiedEmojiObj.emoji += components[modifier]; - modifiedEmojiObj.skin_tone = modifier; - newData[newCategory].push(modifiedEmojiObj); - }); - } }); } }); @@ -60,76 +59,22 @@ Object.keys(data).forEach((originalCategory) => { export type UnicodeEmojiDef = { emoji: string; category: typeof unicodeEmojiCategories[number]; - skin_tone_support: boolean; - name: string; slug: string; - emoji_version: string; - skin_tone?: string; keywords?: string[]; }; -export const emojilist = newData as UnicodeEmojiDef[]; - -const storeName = "emojiList"; - -function openDatabase() { - return new Promise((resolve, reject) => { - const openRequest = indexedDB.open("emojiDatabase", 1); - - openRequest.onupgradeneeded = () => { - const db = openRequest.result; - if (!db.objectStoreNames.contains(storeName)) { - db.createObjectStore(storeName); - } - }; - openRequest.onsuccess = () => { - resolve(openRequest.result); - }; - openRequest.onerror = () => { - reject(openRequest.error); - }; +export const emojilist: UnicodeEmojiDef[] = Object.keys(newData).reduce((acc, category) => { + const categoryItems = newData[category].map((item) => { + return { + emoji: item.emoji, + slug: item.slug, + category: item.category, + keywords: item.keywords || [], + }; }); -} + return acc.concat(categoryItems); +}, []); -function storeData(db: IDBDatabase, data) { - return new Promise((resolve, reject) => { - const transaction = db.transaction(storeName, "readwrite"); - const store = transaction.objectStore(storeName); - store.put(data, "emojiListKey"); - - transaction.oncomplete = resolve; - transaction.onerror = reject; - }); -} - -function getData(db: IDBDatabase): Promise { - return new Promise((resolve, reject) => { - const transaction = db.transaction(storeName, "readonly"); - const store = transaction.objectStore(storeName); - const getRequest = store.get("emojiListKey"); - - getRequest.onsuccess = () => resolve(getRequest.result); - getRequest.onerror = reject; - }); -} - -export async function getEmojiData(): Promise { - try { - const db = await openDatabase(); - const cachedData = await getData(db); - - if (cachedData) { - return cachedData; - } else { - await storeData(db, emojilist); - console.log("Emoji data stored in IndexedDB"); - return emojilist; - } - } catch (err) { - console.error("Error accessing IndexedDB:", err); - return emojilist; - } -} export function getNicelyLabeledCategory(internalName) { return Object.keys(categoryMapping).find( diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index c8ce96b0ee..74ee6e7555 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -242,6 +242,10 @@ export const defaultStore = markRaw( where: "device", default: "remote" as "none" | "remote" | "always", }, + reactionPickerSkinTone: { + where: "account", + default: 1, + }, reactionPickerSize: { where: "device", default: 3,