mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-06 16:16:42 +08:00
feat(plugin): support extensible sources and dialog teardown
This commit is contained in:
@@ -9,6 +9,8 @@ import ProgressDialog from '../dialog/ProgressDialog.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { loadRemoteComponent } from '@/utils/federationLoader'
|
||||
import { usePluginNativeSubscribe } from '@/composables/usePluginNativeSubscribe'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { openSharedDialog } from '@/composables/useSharedDialog'
|
||||
import { usePluginSidebarNavStore } from '@/stores/pluginSidebarNav'
|
||||
import RemoteComponentError from '@/components/misc/RemoteComponentError.vue'
|
||||
import RemoteComponentLoading from '@/components/misc/RemoteComponentLoading.vue'
|
||||
@@ -47,6 +49,13 @@ const $toast = useToast()
|
||||
// 向联邦插件提供主应用 Toast,避免远程组件自行创建通知容器。
|
||||
provide('moviepilot:toast', $toast)
|
||||
|
||||
// 向联邦插件提供主应用公共弹窗,内容由 App 的 SharedDialogHost 统一承载。
|
||||
provide('moviepilot:dialog', openSharedDialog)
|
||||
|
||||
// 确认弹窗单独提供,保持简单确认调用的兼容性。
|
||||
const createConfirm = useConfirm()
|
||||
provide('moviepilot:confirm', createConfirm)
|
||||
|
||||
// 配置联邦组件沿用与其它插件宿主一致的原生订阅能力。
|
||||
const nativeSubscribe = usePluginNativeSubscribe()
|
||||
provide('moviepilot:nativeSubscribe', nativeSubscribe)
|
||||
|
||||
@@ -7,6 +7,8 @@ import { loadRemoteComponent } from '@/utils/federationLoader'
|
||||
import { usePWA } from '@/composables/usePWA'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { usePluginNativeSubscribe } from '@/composables/usePluginNativeSubscribe'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { openSharedDialog } from '@/composables/useSharedDialog'
|
||||
import RemoteComponentError from '@/components/misc/RemoteComponentError.vue'
|
||||
import RemoteComponentLoading from '@/components/misc/RemoteComponentLoading.vue'
|
||||
|
||||
@@ -34,6 +36,13 @@ const { appMode } = usePWA()
|
||||
const $toast = useToast()
|
||||
provide('moviepilot:toast', $toast)
|
||||
|
||||
// 向联邦插件提供主应用公共弹窗,内容由 App 的 SharedDialogHost 统一承载。
|
||||
provide('moviepilot:dialog', openSharedDialog)
|
||||
|
||||
// 确认弹窗单独提供,避免把简单确认与自定义组件弹窗混为一谈。
|
||||
const createConfirm = useConfirm()
|
||||
provide('moviepilot:confirm', createConfirm)
|
||||
|
||||
// 向联邦插件同时提供 prop 与 inject 形式的主程序原生订阅入口。
|
||||
const nativeSubscribe = usePluginNativeSubscribe()
|
||||
provide('moviepilot:nativeSubscribe', nativeSubscribe)
|
||||
|
||||
@@ -15,7 +15,9 @@ const mocks = vi.hoisted(() => {
|
||||
apiPut,
|
||||
ensureSidebarNav: vi.fn(),
|
||||
loadRemoteComponent: vi.fn(),
|
||||
openSharedDialog: vi.fn(),
|
||||
nativeSubscribe: vi.fn(),
|
||||
createConfirm: vi.fn(),
|
||||
toast: { error: vi.fn(), success: vi.fn() },
|
||||
}
|
||||
})
|
||||
@@ -33,6 +35,14 @@ vi.mock('@/composables/usePluginNativeSubscribe', () => ({
|
||||
usePluginNativeSubscribe: () => mocks.nativeSubscribe,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useConfirm', () => ({
|
||||
useConfirm: () => mocks.createConfirm,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useSharedDialog', () => ({
|
||||
openSharedDialog: mocks.openSharedDialog,
|
||||
}))
|
||||
|
||||
vi.mock('@/stores/pluginSidebarNav', () => ({
|
||||
usePluginSidebarNavStore: () => ({ ensureSidebarNav: mocks.ensureSidebarNav }),
|
||||
}))
|
||||
@@ -89,6 +99,8 @@ type RemoteCapture = {
|
||||
initialConfig: Record<string, unknown>
|
||||
injectedNativeSubscribe: unknown
|
||||
injectedToast: unknown
|
||||
injectedDialog: unknown
|
||||
injectedConfirm: unknown
|
||||
nativeSubscribe: unknown
|
||||
}
|
||||
|
||||
@@ -118,6 +130,8 @@ function createRemoteConfig(captures: RemoteCapture[]): Component {
|
||||
initialConfig: props.initialConfig,
|
||||
injectedNativeSubscribe: inject('moviepilot:nativeSubscribe'),
|
||||
injectedToast: inject('moviepilot:toast'),
|
||||
injectedDialog: inject('moviepilot:dialog'),
|
||||
injectedConfirm: inject('moviepilot:confirm'),
|
||||
nativeSubscribe: props.nativeSubscribe,
|
||||
})
|
||||
return () =>
|
||||
@@ -152,7 +166,9 @@ describe('PluginConfigDialog', () => {
|
||||
mocks.apiPut.mockReset()
|
||||
mocks.ensureSidebarNav.mockReset().mockResolvedValue(undefined)
|
||||
mocks.loadRemoteComponent.mockReset()
|
||||
mocks.openSharedDialog.mockReset()
|
||||
mocks.nativeSubscribe.mockReset()
|
||||
mocks.createConfirm.mockReset()
|
||||
mocks.toast.error.mockReset()
|
||||
mocks.toast.success.mockReset()
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
@@ -229,6 +245,8 @@ describe('PluginConfigDialog', () => {
|
||||
expect(captures[0].nativeSubscribe).toBe(mocks.nativeSubscribe)
|
||||
expect(captures[0].injectedNativeSubscribe).toBe(mocks.nativeSubscribe)
|
||||
expect(captures[0].injectedToast).toBe(mocks.toast)
|
||||
expect(captures[0].injectedDialog).toBe(mocks.openSharedDialog)
|
||||
expect(captures[0].injectedConfirm).toBe(mocks.createConfirm)
|
||||
|
||||
await fireEvent.click(screen.getByRole('button', { name: '调整布局' }))
|
||||
expect(screen.getByRole('dialog')).toHaveAttribute('data-max-width', '72rem')
|
||||
|
||||
@@ -12,7 +12,9 @@ const mocks = vi.hoisted(() => {
|
||||
api: { get: apiGet },
|
||||
apiGet,
|
||||
loadRemoteComponent: vi.fn(),
|
||||
openSharedDialog: vi.fn(),
|
||||
nativeSubscribe: vi.fn(),
|
||||
createConfirm: vi.fn(),
|
||||
toast: { error: vi.fn(), success: vi.fn() },
|
||||
}
|
||||
})
|
||||
@@ -30,6 +32,14 @@ vi.mock('@/composables/usePluginNativeSubscribe', () => ({
|
||||
usePluginNativeSubscribe: () => mocks.nativeSubscribe,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useConfirm', () => ({
|
||||
useConfirm: () => mocks.createConfirm,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useSharedDialog', () => ({
|
||||
openSharedDialog: mocks.openSharedDialog,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/usePWA', () => ({
|
||||
usePWA: () => ({ appMode: false }),
|
||||
}))
|
||||
@@ -88,6 +98,8 @@ type RemoteCapture = {
|
||||
api: unknown
|
||||
injectedNativeSubscribe: unknown
|
||||
injectedToast: unknown
|
||||
injectedDialog: unknown
|
||||
injectedConfirm: unknown
|
||||
nativeSubscribe: unknown
|
||||
showSwitch: boolean
|
||||
}
|
||||
@@ -115,6 +127,8 @@ function createRemotePage(captures: RemoteCapture[]): Component {
|
||||
api: props.api,
|
||||
injectedNativeSubscribe: inject('moviepilot:nativeSubscribe'),
|
||||
injectedToast: inject('moviepilot:toast'),
|
||||
injectedDialog: inject('moviepilot:dialog'),
|
||||
injectedConfirm: inject('moviepilot:confirm'),
|
||||
nativeSubscribe: props.nativeSubscribe,
|
||||
showSwitch: props.show_switch,
|
||||
})
|
||||
@@ -147,7 +161,9 @@ describe('PluginDataDialog', () => {
|
||||
beforeEach(() => {
|
||||
mocks.apiGet.mockReset()
|
||||
mocks.loadRemoteComponent.mockReset()
|
||||
mocks.openSharedDialog.mockReset()
|
||||
mocks.nativeSubscribe.mockReset()
|
||||
mocks.createConfirm.mockReset()
|
||||
mocks.toast.error.mockReset()
|
||||
mocks.toast.success.mockReset()
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
@@ -202,6 +218,8 @@ describe('PluginDataDialog', () => {
|
||||
expect(captures[0].nativeSubscribe).toBe(mocks.nativeSubscribe)
|
||||
expect(captures[0].injectedNativeSubscribe).toBe(mocks.nativeSubscribe)
|
||||
expect(captures[0].injectedToast).toBe(mocks.toast)
|
||||
expect(captures[0].injectedDialog).toBe(mocks.openSharedDialog)
|
||||
expect(captures[0].injectedConfirm).toBe(mocks.createConfirm)
|
||||
|
||||
await fireEvent.click(screen.getByRole('button', { name: '刷新远程页面' }))
|
||||
await fireEvent.click(screen.getByRole('button', { name: '切换配置' }))
|
||||
|
||||
@@ -7,6 +7,8 @@ import { isNullOrEmptyObject } from '@/@core/utils'
|
||||
import { loadRemoteComponent } from '@/utils/federationLoader'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { usePluginNativeSubscribe } from '@/composables/usePluginNativeSubscribe'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { openSharedDialog } from '@/composables/useSharedDialog'
|
||||
import RemoteComponentError from './RemoteComponentError.vue'
|
||||
|
||||
type DashboardComponentLoader = () => Promise<any>
|
||||
@@ -15,6 +17,13 @@ type DashboardComponentLoader = () => Promise<any>
|
||||
const $toast = useToast()
|
||||
provide('moviepilot:toast', $toast)
|
||||
|
||||
// 向仪表板联邦组件导出主应用公共弹窗入口。
|
||||
provide('moviepilot:dialog', openSharedDialog)
|
||||
|
||||
// 确认弹窗单独提供,避免插件自行挂载确认组件。
|
||||
const createConfirm = useConfirm()
|
||||
provide('moviepilot:confirm', createConfirm)
|
||||
|
||||
// 向仪表板联邦组件导出主程序原生订阅入口。
|
||||
const nativeSubscribe = usePluginNativeSubscribe()
|
||||
provide('moviepilot:nativeSubscribe', nativeSubscribe)
|
||||
|
||||
@@ -9,7 +9,9 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
const mocks = vi.hoisted(() => ({
|
||||
apiGet: vi.fn(),
|
||||
loadRemoteComponent: vi.fn(),
|
||||
openSharedDialog: vi.fn(),
|
||||
nativeSubscribe: vi.fn(),
|
||||
createConfirm: vi.fn(),
|
||||
toast: { error: vi.fn(), success: vi.fn() },
|
||||
}))
|
||||
|
||||
@@ -26,6 +28,14 @@ vi.mock('@/composables/usePluginNativeSubscribe', () => ({
|
||||
usePluginNativeSubscribe: () => mocks.nativeSubscribe,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useConfirm', () => ({
|
||||
useConfirm: () => mocks.createConfirm,
|
||||
}))
|
||||
|
||||
vi.mock('@/composables/useSharedDialog', () => ({
|
||||
openSharedDialog: mocks.openSharedDialog,
|
||||
}))
|
||||
|
||||
vi.mock('vue-toastification', () => ({
|
||||
useToast: () => mocks.toast,
|
||||
}))
|
||||
@@ -35,6 +45,8 @@ type RemoteCapture = {
|
||||
api: unknown
|
||||
config: DashboardItem
|
||||
injectedNativeSubscribe: unknown
|
||||
injectedDialog: unknown
|
||||
injectedConfirm: unknown
|
||||
injectedToast: unknown
|
||||
nativeSubscribe: unknown
|
||||
}
|
||||
@@ -66,6 +78,8 @@ function createRemoteDashboard(captures: RemoteCapture[], label = 'remote'): Com
|
||||
config: props.config,
|
||||
injectedNativeSubscribe: inject('moviepilot:nativeSubscribe'),
|
||||
injectedToast: inject('moviepilot:toast'),
|
||||
injectedDialog: inject('moviepilot:dialog'),
|
||||
injectedConfirm: inject('moviepilot:confirm'),
|
||||
nativeSubscribe: props.nativeSubscribe,
|
||||
})
|
||||
return () => h('div', { 'data-testid': 'remote-dashboard' }, `${label}:${props.config.name}`)
|
||||
@@ -89,8 +103,10 @@ function createPluginDashboard(overrides: Partial<DashboardItem> = {}): Dashboar
|
||||
describe('DashboardElement plugin host', () => {
|
||||
beforeEach(() => {
|
||||
mocks.loadRemoteComponent.mockReset()
|
||||
mocks.openSharedDialog.mockReset()
|
||||
mocks.apiGet.mockReset()
|
||||
mocks.nativeSubscribe.mockReset()
|
||||
mocks.createConfirm.mockReset()
|
||||
mocks.toast.error.mockReset()
|
||||
mocks.toast.success.mockReset()
|
||||
vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
@@ -116,6 +132,8 @@ describe('DashboardElement plugin host', () => {
|
||||
expect(captures[0].nativeSubscribe).toBe(mocks.nativeSubscribe)
|
||||
expect(captures[0].injectedNativeSubscribe).toBe(mocks.nativeSubscribe)
|
||||
expect(captures[0].injectedToast).toBe(mocks.toast)
|
||||
expect(captures[0].injectedDialog).toBe(mocks.openSharedDialog)
|
||||
expect(captures[0].injectedConfirm).toBe(mocks.createConfirm)
|
||||
await waitFor(() => expect(result.emitted().loaded).toHaveLength(1))
|
||||
|
||||
result.unmount()
|
||||
|
||||
Reference in New Issue
Block a user