diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue index 3600805ea4..c66e4fd6c0 100644 --- a/packages/frontend/src/components/MkEmojiPickerWindow.vue +++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue @@ -52,16 +52,12 @@ function chosen(emoji: string) { const windowEl = useTemplateRef('window'); -function onCloseRequested() { +function close() { windowEl.value?.close(); } -onMounted(() => { - globalEvents.on('requestCloseEmojiPickerWindow', onCloseRequested); -}); - -onBeforeUnmount(() => { - globalEvents.off('requestCloseEmojiPickerWindow', onCloseRequested); +defineExpose({ + close, }); diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index e2bf6dbff8..8d39c9fab0 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -1104,9 +1104,7 @@ onMounted(() => { }); onBeforeUnmount(() => { - // MkPostFormDialogでも発火しているが、Dialogではない場合は呼ばれないためこちらでも呼ぶ必要がある - // なのでDialogの場合は2回発火されるが、ウィンドウを閉じる指示のため悪影響はない - globalEvents.emit('requestCloseEmojiPickerWindow'); + emoijPicker.closeWindow(); }); defineExpose({ diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue index 9e85d9b548..970af83440 100644 --- a/packages/frontend/src/components/MkPostFormDialog.vue +++ b/packages/frontend/src/components/MkPostFormDialog.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { useTemplateRef } from 'vue'; import MkModal from '@/components/MkModal.vue'; import MkPostForm from '@/components/MkPostForm.vue'; -import { globalEvents } from '@/events.js'; +import { emojiPicker } from '@/utility/emoji-picker.js'; import type { PostFormProps } from '@/types/post-form.js'; const props = withDefaults(defineProps @@ -58,7 +58,7 @@ import MkLoading from '@/components/global/MkLoading.vue'; const emit = defineEmits<{ (ev: 'done', value: Misskey.entities.Role[]), (ev: 'close'), - (ev: 'dispose'), + (ev: 'closed'), }>(); const props = withDefaults(defineProps<{ diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts index 6a57ecce58..dfd3d4120c 100644 --- a/packages/frontend/src/events.ts +++ b/packages/frontend/src/events.ts @@ -10,5 +10,4 @@ export const globalEvents = new EventEmitter<{ themeChanging: () => void; themeChanged: () => void; clientNotification: (notification: Misskey.entities.Notification) => void; - requestCloseEmojiPickerWindow: () => void; }>(); diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index 8e4c97e59f..bd0a77087c 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -5,10 +5,10 @@ // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する -import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue'; +import { markRaw, ref, shallowRef, defineAsyncComponent, nextTick } from 'vue'; import { EventEmitter } from 'eventemitter3'; import * as Misskey from 'misskey-js'; -import type { Component, Ref } from 'vue'; +import type { Component, Ref, ShallowRef } from 'vue'; import type { ComponentProps as CP } from 'vue-component-type-helpers'; import type { Form, GetFormResultType } from '@/utility/form.js'; import type { MenuItem } from '@/types/menu.js'; @@ -141,6 +141,7 @@ let popupIdCount = 0; export const popups = ref<{ id: number; component: Component; + componentRef: ShallowRef; props: Record; events: Record; }[]>([]); @@ -178,13 +179,21 @@ type EmitsExtractor = { [K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize : K extends string ? never : K]: T[K]; }; -export function popup( +export function popup< + T extends Component, + TI extends T extends new (...args: unknown[]) => infer I ? I : T, +>( component: T, props: ComponentProps, events: ComponentEmit = {} as ComponentEmit, -): { dispose: () => void } { +): { + dispose: () => void; + componentRef: ShallowRef; +} { markRaw(component); + const componentRef = shallowRef(null); + const id = ++popupIdCount; const dispose = () => { // このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ? @@ -194,6 +203,7 @@ export function popup( }; const state = { component, + componentRef, props, events, id, @@ -203,6 +213,7 @@ export function popup( return { dispose, + componentRef, }; } @@ -627,14 +638,17 @@ export async function selectRole(params: { { canceled: false; result: Misskey.entities.Role[] } > { return new Promise((resolve) => { - popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, { done: roles => { resolve({ canceled: false, result: roles }); }, close: () => { resolve({ canceled: true, result: undefined }); }, - }, 'dispose'); + closed: () => { + dispose(); + }, + }); }); } diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index a39a4ee86b..a51504b274 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only :is="popup.component" v-for="popup in popups" :key="popup.id" + :ref="(el: Component | null) => popup.componentRef = el" v-bind="popup.props" v-on="popup.events" /> diff --git a/packages/frontend/src/utility/emoji-picker.ts b/packages/frontend/src/utility/emoji-picker.ts index c7d8858a8b..7bbc5bd0a5 100644 --- a/packages/frontend/src/utility/emoji-picker.ts +++ b/packages/frontend/src/utility/emoji-picker.ts @@ -3,8 +3,9 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defineAsyncComponent, ref, watch } from 'vue'; -import type { Ref } from 'vue'; +import { defineAsyncComponent, ref, shallowRef, watch } from 'vue'; +import type { Ref, ShallowRef } from 'vue'; +import type MkEmojiPickerWindow_TypeOnly from '@/components/MkEmojiPickerWindow.vue'; import { popup } from '@/os.js'; import { prefer } from '@/preferences.js'; @@ -18,6 +19,7 @@ class EmojiPicker { private src: Ref = ref(null); private isWindow: boolean = false; + private windowComponentEl: ShallowRef | null> = shallowRef(null); private windowShowing: boolean = false; private dialogShowing = ref(false); @@ -73,7 +75,7 @@ class EmojiPicker { if (this.isWindow) { if (this.windowShowing) return; this.windowShowing = true; - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerWindow.vue')), { + const { dispose, componentRef } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerWindow.vue')), { src: opts.src, pinnedEmojis: this.emojisRef, asReactionPicker: false, @@ -87,6 +89,7 @@ class EmojiPicker { dispose(); }, }); + this.windowComponentEl.value = componentRef.value; } else { this.src.value = opts.src; this.dialogShowing.value = true; @@ -94,6 +97,12 @@ class EmojiPicker { this.onClosed = opts.onClosed; } } + + public closeWindow() { + if (this.isWindow && this.windowComponentEl.value) { + this.windowComponentEl.value.close(); + } + } } export const emojiPicker = new EmojiPicker();