feat(frontend): バブルゲームを追加 (MisskeyIO#336)
1
locales/index.d.ts
vendored
@ -1190,6 +1190,7 @@ export interface Locale {
|
||||
"decorate": string;
|
||||
"addMfmFunction": string;
|
||||
"enableQuickAddMfmFunction": string;
|
||||
"bubbleGame": string;
|
||||
"abuseReportCategory": string;
|
||||
"selectCategory": string;
|
||||
"reportComplete": string;
|
||||
|
@ -1187,6 +1187,7 @@ seasonalScreenEffect: "季節に応じた画面の演出"
|
||||
decorate: "デコる"
|
||||
addMfmFunction: "装飾を追加"
|
||||
enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
|
||||
bubbleGame: "バブルゲーム"
|
||||
abuseReportCategory: "通報の種類"
|
||||
selectCategory: "カテゴリを選択"
|
||||
reportComplete: "通報完了"
|
||||
|
BIN
packages/frontend/assets/drop-and-fusion/cold_face.png
Normal file
After Width: | Height: | Size: 40 KiB |
6
packages/frontend/assets/drop-and-fusion/drop-arrow.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<path d="M0,0L128,0L64,64L0,0Z" style="fill:rgb(255,61,0);"/>
|
||||
<path d="M0,0L128,0L64,64L0,0ZM28.971,12L64,47.029C64,47.029 99.029,12 99.029,12L28.971,12Z" style="fill:rgb(255,122,0);"/>
|
||||
</svg>
|
After Width: | Height: | Size: 646 B |
BIN
packages/frontend/assets/drop-and-fusion/dropper.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
packages/frontend/assets/drop-and-fusion/exploding_head.png
Normal file
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 39 KiB |
28
packages/frontend/assets/drop-and-fusion/frame-dark.svg
Normal file
After Width: | Height: | Size: 67 KiB |
28
packages/frontend/assets/drop-and-fusion/frame-light.svg
Normal file
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 40 KiB |
BIN
packages/frontend/assets/drop-and-fusion/heart_suit.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_1.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_10.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_2.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_3.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_4.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_5.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_6.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_7.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_8.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
packages/frontend/assets/drop-and-fusion/keycap_9.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
packages/frontend/assets/drop-and-fusion/pleading_face.png
Normal file
After Width: | Height: | Size: 43 KiB |
After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 47 KiB |
BIN
packages/frontend/assets/drop-and-fusion/zany_face.png
Normal file
After Width: | Height: | Size: 44 KiB |
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
|
||||
<span class="text" :class="{ up }">+1</span>
|
||||
<span class="text" :class="{ up }">+{{ value }}</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -16,7 +16,9 @@ import * as os from '@/os.js';
|
||||
const props = withDefaults(defineProps<{
|
||||
x: number;
|
||||
y: number;
|
||||
value?: number;
|
||||
}>(), {
|
||||
value: 1,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
1039
packages/frontend/src/pages/drop-and-fusion.vue
Normal file
@ -531,6 +531,10 @@ export const routes = [{
|
||||
path: '/clicker',
|
||||
component: page(() => import('./pages/clicker.vue')),
|
||||
loginRequired: true,
|
||||
}, {
|
||||
path: '/bubble-game',
|
||||
component: page(() => import('./pages/drop-and-fusion.vue')),
|
||||
loginRequired: true,
|
||||
}, {
|
||||
path: '/timeline',
|
||||
component: page(() => import('./pages/timeline.vue')),
|
||||
|
@ -154,7 +154,13 @@ export type OperationType = typeof operationTypes[number];
|
||||
* @param soundStore サウンド設定
|
||||
* @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする
|
||||
*/
|
||||
export async function loadAudio(soundStore: SoundStore, options?: { useCache?: boolean; }) {
|
||||
export async function loadAudio(soundStore: {
|
||||
type: Exclude<SoundType, '_driveFile_'>;
|
||||
} | {
|
||||
type: '_driveFile_';
|
||||
fileId: string;
|
||||
fileUrl: string;
|
||||
}, options?: { useCache?: boolean; }) {
|
||||
if (_DEV_) console.log('loading audio. opts:', options);
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
if (soundStore.type === null || (soundStore.type === '_driveFile_' && (!$i?.policies.canUseDriveFileInSoundSettings || !soundStore.fileUrl))) {
|
||||
@ -241,18 +247,31 @@ export async function playFile(soundStore: SoundStore) {
|
||||
createSourceNode(buffer, soundStore.volume)?.start();
|
||||
}
|
||||
|
||||
export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
|
||||
export async function playRaw(type: Exclude<SoundType, '_driveFile_'>, volume = 1, pan = 0, playbackRate = 1) {
|
||||
const buffer = await loadAudio({ type });
|
||||
if (!buffer) return;
|
||||
createSourceNode(buffer, volume, pan, playbackRate)?.start();
|
||||
}
|
||||
|
||||
export function createSourceNode(buffer: AudioBuffer, volume: number, pan = 0, playbackRate = 1) : AudioBufferSourceNode | null {
|
||||
const masterVolume = defaultStore.state.sound_masterVolume;
|
||||
if (isMute() || masterVolume === 0 || volume === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const panNode = ctx.createStereoPanner();
|
||||
panNode.pan.value = pan;
|
||||
|
||||
const gainNode = ctx.createGain();
|
||||
gainNode.gain.value = masterVolume * volume;
|
||||
|
||||
const soundSource = ctx.createBufferSource();
|
||||
soundSource.buffer = buffer;
|
||||
soundSource.connect(gainNode).connect(ctx.destination);
|
||||
soundSource.playbackRate.value = playbackRate;
|
||||
soundSource
|
||||
.connect(panNode)
|
||||
.connect(gainNode)
|
||||
.connect(ctx.destination);
|
||||
|
||||
return soundSource;
|
||||
}
|
||||
|
@ -27,6 +27,11 @@ function toolsMenuItems(): MenuItem[] {
|
||||
to: '/clicker',
|
||||
text: '🍪👈',
|
||||
icon: 'ti ti-cookie',
|
||||
}, {
|
||||
type: 'link',
|
||||
to: '/bubble-game',
|
||||
text: i18n.ts.bubbleGame,
|
||||
icon: 'ti ti-apple',
|
||||
}, ($i && ($i.isAdmin || $i.policies.canManageCustomEmojis)) ? {
|
||||
type: 'link',
|
||||
to: '/custom-emojis-manager',
|
||||
|