diff --git a/CHANGELOG.md b/CHANGELOG.md index fdf6709117..7f02d462b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ ### Client - Enhance: 絵文字のオートコンプリート機能強化 #12364 - Enhance: ユーザーのRawデータを表示するページが復活 +- Enhance: リアクション選択時に音を鳴らせるように - fix: 「設定のバックアップ」で一部の項目がバックアップに含まれていなかった問題を修正 - Fix: ウィジェットのジョブキューにて音声の発音方法変更に追従できていなかったのを修正 #12367 - Fix: コードエディタが正しく表示されない問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 042c7750e1..6e9fe311f1 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1943,6 +1943,7 @@ export interface Locale { "notification": string; "antenna": string; "channel": string; + "reaction": string; }; "_ago": { "future": string; diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 58e0dd0b19..0b051b6190 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1848,6 +1848,7 @@ _sfx: notification: "通知" antenna: "アンテナ受信" channel: "チャンネル通知" + reaction: "リアクション選択時" _ago: future: "未来" diff --git a/packages/frontend/assets/sounds/syuilo/bubble1.mp3 b/packages/frontend/assets/sounds/syuilo/bubble1.mp3 new file mode 100644 index 0000000000..05b8ef8b10 Binary files /dev/null and b/packages/frontend/assets/sounds/syuilo/bubble1.mp3 differ diff --git a/packages/frontend/assets/sounds/syuilo/bubble2.mp3 b/packages/frontend/assets/sounds/syuilo/bubble2.mp3 new file mode 100644 index 0000000000..8b4f8df6e9 Binary files /dev/null and b/packages/frontend/assets/sounds/syuilo/bubble2.mp3 differ diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 6349df2e30..d047495dc9 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -163,6 +163,7 @@ import { focusPrev, focusNext } from '@/scripts/focus.js'; import { checkWordMute } from '@/scripts/check-word-mute.js'; import { userPage } from '@/filters/user.js'; import * as os from '@/os.js'; +import * as sound from '@/scripts/sound.js'; import { defaultStore, noteViewInterruptors } from '@/store.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; @@ -336,6 +337,8 @@ function react(viaKeyboard = false): void { pleaseLogin(); showMovedDialog(); if (appearNote.reactionAcceptance === 'likeOnly') { + sound.play('reaction'); + if (props.mock) { return; } @@ -354,6 +357,8 @@ function react(viaKeyboard = false): void { } else { blur(); reactionPicker.show(reactButton.value, reaction => { + sound.play('reaction'); + if (props.mock) { emit('reaction', reaction); return; diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index d1bc3f676f..d8089ac36f 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -210,6 +210,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js'; import { userPage } from '@/filters/user.js'; import { notePage } from '@/filters/note.js'; import * as os from '@/os.js'; +import * as sound from '@/scripts/sound.js'; import { defaultStore, noteViewInterruptors } from '@/store.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; @@ -369,6 +370,8 @@ function react(viaKeyboard = false): void { pleaseLogin(); showMovedDialog(); if (appearNote.reactionAcceptance === 'likeOnly') { + sound.play('reaction'); + os.api('notes/reactions/create', { noteId: appearNote.id, reaction: '❤️', @@ -383,6 +386,8 @@ function react(viaKeyboard = false): void { } else { blur(); reactionPicker.show(reactButton.value, reaction => { + sound.play('reaction'); + os.api('notes/reactions/create', { noteId: appearNote.id, reaction: reaction, diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 9a107c3674..65a5c2374e 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -28,6 +28,7 @@ import MkReactionEffect from '@/components/MkReactionEffect.vue'; import { claimAchievement } from '@/scripts/achievements.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; +import * as sound from '@/scripts/sound.js'; const props = defineProps<{ reaction: string; @@ -59,6 +60,10 @@ async function toggleReaction() { }); if (confirm.canceled) return; + if (oldReaction !== props.reaction) { + sound.play('reaction'); + } + if (mock) { emit('reactionToggled', props.reaction, (props.count - 1)); return; @@ -75,6 +80,8 @@ async function toggleReaction() { } }); } else { + sound.play('reaction'); + if (mock) { emit('reactionToggled', props.reaction, (props.count + 1)); return; diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue index cd1707a594..244bb1e0e2 100644 --- a/packages/frontend/src/pages/settings/sounds.vue +++ b/packages/frontend/src/pages/settings/sounds.vue @@ -38,7 +38,7 @@ import { defaultStore } from '@/store.js'; const masterVolume = computed(defaultStore.makeGetterSetter('sound_masterVolume')); -const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel'] as const; +const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel', 'reaction'] as const; const sounds = ref>>({ note: defaultStore.reactiveState.sound_note, @@ -46,6 +46,7 @@ const sounds = ref>>({ notification: defaultStore.reactiveState.sound_notification, antenna: defaultStore.reactiveState.sound_antenna, channel: defaultStore.reactiveState.sound_channel, + reaction: defaultStore.reactiveState.sound_reaction, }); async function updated(type: keyof typeof sounds.value, sound) { diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts index 2b604bd98a..4d7ef9bdee 100644 --- a/packages/frontend/src/scripts/sound.ts +++ b/packages/frontend/src/scripts/sound.ts @@ -38,6 +38,8 @@ export const soundsTypes = [ 'syuilo/waon', 'syuilo/popo', 'syuilo/triple', + 'syuilo/bubble1', + 'syuilo/bubble2', 'syuilo/poi1', 'syuilo/poi2', 'syuilo/pirori', @@ -77,7 +79,7 @@ export async function loadAudio(file: string, useCache = true) { return audioBuffer; } -export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification') { +export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification' | 'reaction') { const sound = defaultStore.state[`sound_${type}`]; if (_DEV_) console.log('play', type, sound); if (sound.type == null) return; diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 12660e9e8d..f2ed4e7c0b 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -411,6 +411,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: { type: 'syuilo/square-pico', volume: 1 }, }, + sound_reaction: { + where: 'device', + default: { type: 'syuilo/bubble2', volume: 1 }, + }, })); // TODO: 他のタブと永続化されたstateを同期