diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e46e6ab9de..aac5d5e833 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1119,6 +1119,12 @@ _achievements: _htl20npm: title: "流れるTL" description: "ホームタイムラインの流速が20npmを越す" + _outputHelloWorldOnScratchpad: + title: "Hello, world!" + description: "スクラッチパッドで hello world を出力した" + _open3windows: + title: "マルチウィンドウ" + description: "ウィンドウを3つ以上開いた状態にした" _driveFolderCircularReference: title: "循環参照" description: "ドライブのフォルダを再帰的な入れ子にしようとした" diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index 4ed75308eb..149974452c 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -65,6 +65,8 @@ const ACHIEVEMENT_TYPES = [ 'postedAt0min0sec', 'selfQuote', 'htl20npm', + 'outputHelloWorldOnScratchpad', + 'open3windows', 'driveFolderCircularReference', 'reactWithoutRead', 'clickedClickHere', diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index 25b9da2d0b..d12aafd06d 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -24,7 +24,7 @@ </template> <script lang="ts" setup> -import { ComputedRef, inject, provide } from 'vue'; +import { ComputedRef, inject, onMounted, onUnmounted, provide } from 'vue'; import RouterView from '@/components/global/RouterView.vue'; import MkWindow from '@/components/MkWindow.vue'; import { popout as _popout } from '@/scripts/popout'; @@ -35,6 +35,8 @@ import { mainRouter, routes } from '@/router'; import { Router } from '@/nirax'; import { i18n } from '@/i18n'; import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata'; +import { openingWindowsCount } from '@/os'; +import { claimAchievement } from '@/scripts/achievements'; const props = defineProps<{ initialPath: string; @@ -128,6 +130,17 @@ function popout() { windowEl.close(); } +onMounted(() => { + openingWindowsCount.value++; + if (openingWindowsCount.value >= 3) { + claimAchievement('open3windows'); + } +}); + +onUnmounted(() => { + openingWindowsCount.value--; +}); + defineExpose({ close, }); diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 111bd0cd8d..01f8244060 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -9,7 +9,7 @@ import * as Misskey from 'misskey-js'; import { i18n } from './i18n'; import MkPostFormDialog from '@/components/MkPostFormDialog.vue'; import MkWaitingDialog from '@/components/MkWaitingDialog.vue'; -import MkPageWindow from '@/components/MkPageWindow.vue' +import MkPageWindow from '@/components/MkPageWindow.vue'; import MkToast from '@/components/MkToast.vue'; import MkDialog from '@/components/MkDialog.vue'; import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue'; @@ -18,6 +18,8 @@ import MkPopupMenu from '@/components/MkPopupMenu.vue'; import MkContextMenu from '@/components/MkContextMenu.vue'; import { MenuItem } from '@/types/menu'; +export const openingWindowsCount = ref(0); + export const apiWithDialog = (( endpoint: string, data: Record<string, any> = {}, diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index bd68df724e..0d52850b5d 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -47,6 +47,7 @@ import { definePageMetadata } from '@/scripts/page-metadata'; import { AsUiComponent, AsUiRoot, patch, registerAsUiLib, render } from '@/scripts/aiscript/ui'; import MkAsUi from '@/components/MkAsUi.vue'; import { miLocalStorage } from '@/local-storage'; +import { claimAchievement } from '@/scripts/achievements'; const parser = new Parser(); let aiscript: Interpreter; @@ -90,6 +91,9 @@ async function run() { }); }, out: (value) => { + if (value.type === 'str' && value.value.toLowerCase().replace(',', '').includes('hello world')) { + claimAchievement('outputHelloWorldOnScratchpad'); + } logs.value.push({ id: Math.random(), text: value.type === 'str' ? value.value : utils.valToString(value), diff --git a/packages/frontend/src/scripts/achievements.ts b/packages/frontend/src/scripts/achievements.ts index c8245ad3db..70ea0ffbd4 100644 --- a/packages/frontend/src/scripts/achievements.ts +++ b/packages/frontend/src/scripts/achievements.ts @@ -61,6 +61,8 @@ export const ACHIEVEMENT_TYPES = [ 'postedAt0min0sec', 'selfQuote', 'htl20npm', + 'outputHelloWorldOnScratchpad', + 'open3windows', 'driveFolderCircularReference', 'reactWithoutRead', 'clickedClickHere', @@ -346,6 +348,16 @@ export const ACHIEVEMENT_BADGES = { bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))', frame: 'bronze', }, + 'outputHelloWorldOnScratchpad': { + img: '/fluent-emoji/1f530.png', + bg: 'linear-gradient(0deg, rgb(58 231 198), rgb(37 194 255))', + frame: 'bronze', + }, + 'open3windows': { + img: '/fluent-emoji/1f5a5.png', + bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))', + frame: 'bronze', + }, 'driveFolderCircularReference': { img: '/fluent-emoji/1f4c2.png', bg: 'linear-gradient(0deg, rgb(144 224 255), rgb(255 168 252))',