diff options
| author | 1Step621 <86859447+1STEP621@users.noreply.github.com> | 2024-02-24 10:22:23 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-02-24 10:22:23 +0900 |
| commit | e3dd3f6b63efffde6dd125e8ecef66aa7069c1a0 (patch) | |
| tree | 2f90c46c89d786d33808e1335ab11e4f812183ef | |
| parent | enhance?: DeleteAccountServiceでユーザーを削除する際にuserChangeD... (diff) | |
| download | sharkey-e3dd3f6b63efffde6dd125e8ecef66aa7069c1a0.tar.gz sharkey-e3dd3f6b63efffde6dd125e8ecef66aa7069c1a0.tar.bz2 sharkey-e3dd3f6b63efffde6dd125e8ecef66aa7069c1a0.zip | |
Enhance(frontend): リアクションピッカーを調整 (#13354)
* 打てない絵文字を表示しないのではなくグレーアウトするように など
* fix: 今度は検索とピン留めに効いてなかった
* lint fix
* use Map
* 斜めに線を引いてわかりやすく
* 斜め線は右上からのほうが良かったかも
* デザイン調整
5 files changed, 90 insertions, 20 deletions
diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index 30ad2bcbbf..c295ab6bb7 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only :key="emoji" :data-emoji="emoji" class="_button item" + :disabled="disabledEmojis?.value.includes(emoji)" @pointerenter="computeButtonTitle" @click="emit('chosen', emoji, $event)" > @@ -48,6 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only :key="emoji" :data-emoji="emoji" class="_button item" + :disabled="disabledEmojis?.value.includes(emoji)" @pointerenter="computeButtonTitle" @click="emit('chosen', emoji, $event)" > @@ -67,6 +69,7 @@ import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue'; const props = defineProps<{ emojis: string[] | Ref<string[]>; + disabledEmojis?: Ref<string[]>; initialShown?: boolean; hasChildSection?: boolean; customEmojiTree?: CustomEmojiFolderTree[]; diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 366273118b..061afa66ac 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-for="emoji in searchResultCustom" :key="emoji.name" class="_button item" + :disabled="!canReact(emoji)" :title="emoji.name" tabindex="0" @click="chosen(emoji, $event)" @@ -39,16 +40,17 @@ SPDX-License-Identifier: AGPL-3.0-only <section v-if="showPinned && (pinned && pinned.length > 0)"> <div class="body"> <button - v-for="emoji in pinned" - :key="emoji" - :data-emoji="emoji" + v-for="emoji in pinnedEmojisDef" + :key="getKey(emoji)" + :data-emoji="getKey(emoji)" class="_button item" + :disabled="!canReact(emoji)" tabindex="0" @pointerenter="computeButtonTitle" @click="chosen(emoji, $event)" > - <MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/> - <MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/> + <MkCustomEmoji v-if="!emoji.hasOwnProperty('char')" class="emoji" :name="getKey(emoji)" :normal="true"/> + <MkEmoji v-else class="emoji" :emoji="getKey(emoji)" :normal="true"/> </button> </div> </section> @@ -57,15 +59,16 @@ SPDX-License-Identifier: AGPL-3.0-only <header class="_acrylic"><i class="ti ti-clock ti-fw"></i> {{ i18n.ts.recentUsed }}</header> <div class="body"> <button - v-for="emoji in recentlyUsedEmojis" - :key="emoji" + v-for="emoji in recentlyUsedEmojisDef" + :key="getKey(emoji)" class="_button item" - :data-emoji="emoji" + :disabled="!canReact(emoji)" + :data-emoji="getKey(emoji)" @pointerenter="computeButtonTitle" @click="chosen(emoji, $event)" > - <MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/> - <MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/> + <MkCustomEmoji v-if="!emoji.hasOwnProperty('char')" class="emoji" :name="getKey(emoji)" :normal="true"/> + <MkEmoji v-else class="emoji" :emoji="getKey(emoji)" :normal="true"/> </button> </div> </section> @@ -76,7 +79,8 @@ SPDX-License-Identifier: AGPL-3.0-only v-for="child in customEmojiFolderRoot.children" :key="`custom:${child.value}`" :initialShown="false" - :emojis="computed(() => customEmojis.filter(e => child.value === '' ? (e.category === 'null' || !e.category) : e.category === child.value).filter(filterAvailable).map(e => `:${e.name}:`))" + :emojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).map(e => `:${e.name}:`))" + :disabledEmojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).filter(e => !canReact(e)).map(e => `:${e.name}:`))" :hasChildSection="child.children.length !== 0" :customEmojiTree="child.children" @chosen="chosen" @@ -104,6 +108,7 @@ import * as Misskey from 'misskey-js'; import XSection from '@/components/MkEmojiPicker.section.vue'; import { emojilist, + unicodeEmojisMap, emojiCharByCategory, UnicodeEmojiDef, unicodeEmojiCategories as categories, @@ -146,6 +151,13 @@ const { recentlyUsedEmojis, } = defaultStore.reactiveState; +const recentlyUsedEmojisDef = computed(() => { + return recentlyUsedEmojis.value.map(getDef); +}); +const pinnedEmojisDef = computed(() => { + return pinned.value?.map(getDef); +}); + const pinned = computed(() => props.pinnedEmojis); const size = computed(() => emojiPickerScale.value); const width = computed(() => emojiPickerWidth.value); @@ -337,14 +349,18 @@ watch(q, () => { return matches; }; - searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable); + searchResultCustom.value = Array.from(searchCustom()); searchResultUnicode.value = Array.from(searchUnicode()); }); -function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean { +function canReact(emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef): boolean { return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji); } +function filterCategory(emoji: Misskey.entities.EmojiSimple, category: string): boolean { + return category === '' ? (emoji.category === 'null' || !emoji.category) : emoji.category === category; +} + function focus() { if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) { searchEl.value?.focus({ @@ -362,6 +378,14 @@ function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef): return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`; } +function getDef(emoji: string) { + if (emoji.includes(':')) { + return customEmojisMap.get(emoji.replace(/:/g, ''))!; + } else { + return unicodeEmojisMap.get(emoji)!; + } +} + /** @see MkEmojiPicker.section.vue */ function computeButtonTitle(ev: MouseEvent): void { const elm = ev.target as HTMLElement; @@ -526,6 +550,18 @@ defineExpose({ width: auto; height: auto; min-width: 0; + + &:disabled { + cursor: not-allowed; + background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%); + opacity: 1; + + > .emoji { + filter: grayscale(1); + mix-blend-mode: exclusion; + opacity: 0.8; + } + } } } } @@ -548,6 +584,18 @@ defineExpose({ width: auto; height: auto; min-width: 0; + + &:disabled { + cursor: not-allowed; + background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%); + opacity: 1; + + > .emoji { + filter: grayscale(1); + mix-blend-mode: exclusion; + opacity: 0.8; + } + } } } } @@ -663,6 +711,18 @@ defineExpose({ box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); } + &:disabled { + cursor: not-allowed; + background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%); + opacity: 1; + + > .emoji { + filter: grayscale(1); + mix-blend-mode: exclusion; + opacity: 0.8; + } + } + > .emoji { height: 1.25em; vertical-align: -.25em; diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 0dcd8b0ea2..bccee5109d 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -33,7 +33,8 @@ import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as sound from '@/scripts/sound.js'; import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js'; -import { customEmojis } from '@/custom-emojis.js'; +import { customEmojisMap } from '@/custom-emojis.js'; +import { unicodeEmojisMap } from '@/scripts/emojilist.js'; const props = defineProps<{ reaction: string; @@ -50,13 +51,11 @@ const emit = defineEmits<{ const buttonEl = shallowRef<HTMLElement>(); -const isCustomEmoji = computed(() => props.reaction.includes(':')); -const emoji = computed(() => isCustomEmoji.value ? customEmojis.value.find(emoji => emoji.name === props.reaction.replace(/:/g, '').replace(/@\./, '')) : null); +const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, '')); +const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? unicodeEmojisMap.get(props.reaction)); const canToggle = computed(() => { - return !props.reaction.match(/@\w/) && $i - && (emoji.value && checkReactionPermissions($i, props.note, emoji.value)) - || !isCustomEmoji.value; + return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value); }); const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':')); diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts index c9d2a5bfc6..da704717c1 100644 --- a/packages/frontend/src/scripts/check-reaction-permissions.ts +++ b/packages/frontend/src/scripts/check-reaction-permissions.ts @@ -1,6 +1,10 @@ import * as Misskey from 'misskey-js'; +import { UnicodeEmojiDef } from './emojilist.js'; -export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple): boolean { +export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef): boolean { + if ('char' in emoji) return true; // UnicodeEmojiDefなら常にリアクション可能 + + emoji = emoji as Misskey.entities.EmojiSimple; const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? []; return !(emoji.localOnly && note.user.host !== me.host) && !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote')) diff --git a/packages/frontend/src/scripts/emojilist.ts b/packages/frontend/src/scripts/emojilist.ts index 54d45e025f..2a6120f3ba 100644 --- a/packages/frontend/src/scripts/emojilist.ts +++ b/packages/frontend/src/scripts/emojilist.ts @@ -20,6 +20,10 @@ export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({ category: unicodeEmojiCategories[x[2]], })); +export const unicodeEmojisMap = new Map<string, UnicodeEmojiDef>( + emojilist.map(x => [x.char, x]) +); + const _indexByChar = new Map<string, number>(); const _charGroupByCategory = new Map<string, string[]>(); for (let i = 0; i < emojilist.length; i++) { |