From a65f50ee4a36182e05c32494d11c41716b23da96 Mon Sep 17 00:00:00 2001 From: Anthony Fu Date: Fri, 23 Jun 2023 16:58:23 +0200 Subject: [PATCH] feat: popup devtools as Picture-in-Picture (#282) --- .../devtools-kit/src/_types/client-api.ts | 23 +- packages/devtools/client/app.vue | 2 +- .../client/components/DockingPanel.vue | 8 +- .../components/PictureInPictureButton.vue | 42 +++ .../components/PictureInPictureInfoDialog.vue | 0 packages/devtools/client/plugins/global.ts | 4 +- .../src/runtime/plugins/devtools.client.ts | 145 +--------- .../src/runtime/plugins/view/Frame.vue | 71 ----- .../src/runtime/plugins/view/FrameBox.vue | 101 ++++--- .../src/runtime/plugins/view/Main.vue | 9 +- .../src/runtime/plugins/view/client.ts | 254 +++++++++++++++++- .../src/runtime/plugins/view/state.ts | 27 +- packages/devtools/src/types/global.ts | 62 +++++ packages/devtools/src/types/index.ts | 2 + playgrounds/empty/nuxt.config.ts | 1 + .../module-starter/client/pages/index.vue | 2 +- playgrounds/tab-seo/nuxt.config.ts | 2 +- 17 files changed, 474 insertions(+), 281 deletions(-) create mode 100644 packages/devtools/client/components/PictureInPictureButton.vue create mode 100644 packages/devtools/client/components/PictureInPictureInfoDialog.vue delete mode 100644 packages/devtools/src/runtime/plugins/view/Frame.vue create mode 100644 packages/devtools/src/types/global.ts diff --git a/packages/devtools-kit/src/_types/client-api.ts b/packages/devtools-kit/src/_types/client-api.ts index d96db94b8..aa38f1754 100644 --- a/packages/devtools-kit/src/_types/client-api.ts +++ b/packages/devtools-kit/src/_types/client-api.ts @@ -53,12 +53,29 @@ export interface NuxtDevtoolsHostClient { getClientPluginMetrics(): PluginMetric[] reloadPage(): void - closeDevTools(): void + + close(): void + open(): void + toggle(): void /** - * Update client, send to iframe if provided + * Popup the DevTools frame into Picture-in-Picture mode + * + * Requires Chrome 111 with experimental flag enabled. + * + * Function is undefined when not supported. + * + * @see https://developer.chrome.com/docs/web-platform/document-picture-in-picture/ */ - updateClient(iframe?: HTMLIFrameElement): NuxtDevtoolsHostClient + popup?(): any + + /** + * Update client + * @internal + */ + updateClient(): NuxtDevtoolsHostClient + + getIframe(): HTMLIFrameElement } export interface NuxtDevtoolsClient { diff --git a/packages/devtools/client/app.vue b/packages/devtools/client/app.vue index e2941218c..8785a179b 100644 --- a/packages/devtools/client/app.vue +++ b/packages/devtools/client/app.vue @@ -45,7 +45,7 @@ watch( addEventListener('keydown', (e) => { if (e.code === 'KeyD' && e.altKey) { - client.value?.closeDevTools() + client.value?.close() e.preventDefault() } }) diff --git a/packages/devtools/client/components/DockingPanel.vue b/packages/devtools/client/components/DockingPanel.vue index d665c83b4..97e2f41b3 100644 --- a/packages/devtools/client/components/DockingPanel.vue +++ b/packages/devtools/client/components/DockingPanel.vue @@ -20,15 +20,13 @@ function refreshPage() {
{{ isDark.value ? 'Dark' : 'Light' }} - -
Settings - +
- + Refetch Data - + Refresh Page
diff --git a/packages/devtools/client/components/PictureInPictureButton.vue b/packages/devtools/client/components/PictureInPictureButton.vue new file mode 100644 index 000000000..4c3fbcfc0 --- /dev/null +++ b/packages/devtools/client/components/PictureInPictureButton.vue @@ -0,0 +1,42 @@ + + + diff --git a/packages/devtools/client/components/PictureInPictureInfoDialog.vue b/packages/devtools/client/components/PictureInPictureInfoDialog.vue new file mode 100644 index 000000000..e69de29bb diff --git a/packages/devtools/client/plugins/global.ts b/packages/devtools/client/plugins/global.ts index 7cb4078ee..53553fcf6 100644 --- a/packages/devtools/client/plugins/global.ts +++ b/packages/devtools/client/plugins/global.ts @@ -1,11 +1,9 @@ -import type { NuxtDevtoolsGlobal } from '../../src/types' - export default defineNuxtPlugin(() => { const client = useClient() const inspectorData = useComponentInspectorData() const router = useRouter() - window.__NUXT_DEVTOOLS_VIEW__ = { + window.__NUXT_DEVTOOLS_VIEW__ = { setClient(_client) { if (client.value === _client) return diff --git a/packages/devtools/src/runtime/plugins/devtools.client.ts b/packages/devtools/src/runtime/plugins/devtools.client.ts index 744d6498d..07eeca139 100644 --- a/packages/devtools/src/runtime/plugins/devtools.client.ts +++ b/packages/devtools/src/runtime/plugins/devtools.client.ts @@ -1,15 +1,15 @@ -import { createApp, h, markRaw, ref, shallowReactive, watch, watchEffect } from 'vue' +import { shallowReactive, watchEffect } from 'vue' import { setupHooksDebug } from '../shared/hooks' -import type { LoadingTimeMetric, NuxtDevtoolsHostClient, PluginMetric, VueInspectorClient } from '../../types' - -import { useClientColorMode } from './view/client' // eslint-disable-next-line @typescript-eslint/prefer-ts-expect-error // @ts-ignore tsconfig -import { defineNuxtPlugin, useAppConfig, useRouter, useState } from '#imports' +import { defineNuxtPlugin, useRouter, useState } from '#imports' export default defineNuxtPlugin((nuxt: any) => { + if (window.__NUXT_DEVTOOLS_DISABLE__ || window.parent?.__NUXT_DEVTOOLS_DISABLE__) + return + // TODO: Stackblitz support? if (typeof document === 'undefined' || typeof window === 'undefined') return @@ -46,135 +46,12 @@ export default defineNuxtPlugin((nuxt: any) => { timeMetric.ssrStart = ssrState.value.timeSsrStart }) - async function init() { - const { closePanel, togglePanel } = await import('./view/state') - const { createHooks } = await import('hookable') - const { default: Main } = await import('./view/Main.vue') - - const isInspecting = ref(false) - const colorMode = useClientColorMode() - const client: NuxtDevtoolsHostClient = shallowReactive({ - nuxt: markRaw(nuxt as any), - appConfig: useAppConfig() as any, - hooks: createHooks(), - getClientHooksMetrics: () => Object.values(clientHooks), - getClientPluginMetrics: () => { - return window.__NUXT_DEVTOOLS_PLUGINS_METRIC__ || [] - }, - loadingTimeMetrics: timeMetric, - reloadPage() { - location.reload() - }, - closeDevTools: closePanel, - inspector: getInspectorInstance(), - colorMode, - updateClient(iframe?: HTMLIFrameElement): NuxtDevtoolsHostClient { - if (!client.inspector) - client.inspector = getInspectorInstance() - - try { - iframe?.contentWindow?.__NUXT_DEVTOOLS_VIEW__?.setClient(client) - } - catch (e) { - // cross-origin - } - return client - }, - }) - - function enableComponentInspector() { - window.__VUE_INSPECTOR__?.enable() - isInspecting.value = true - } - - function disableComponentInspector() { - if (!window.__VUE_INSPECTOR__?.enabled) - return - - window.__VUE_INSPECTOR__?.disable() - client?.hooks.callHook('host:inspector:close') - isInspecting.value = false - } - - function getInspectorInstance(): NuxtDevtoolsHostClient['inspector'] { - const componentInspector = window.__VUE_INSPECTOR__ as VueInspectorClient - - if (componentInspector) { - componentInspector.openInEditor = async (baseUrl, file, line, column) => { - disableComponentInspector() - await client.hooks.callHook('host:inspector:click', baseUrl, file, line, column) - } - componentInspector.onUpdated = () => { - client.hooks.callHook('host:inspector:update', { - ...componentInspector.linkParams, - ...componentInspector.position, - }) - } - } - return markRaw({ - isEnabled: isInspecting, - enable: enableComponentInspector, - disable: disableComponentInspector, - toggle: () => { - if (window.__VUE_INSPECTOR__?.enabled) - disableComponentInspector() - else - enableComponentInspector() - }, - instance: componentInspector, + import('./view/client') + .then(({ setupDevToolsClient }) => { + setupDevToolsClient({ + nuxt, + clientHooks, + timeMetric, }) - } - - function refreshReactivity() { - client.hooks.callHook('host:update:reactivity') - } - - // trigger update for reactivity - watch(() => [ - client.nuxt.payload, - client.colorMode.value, - client.loadingTimeMetrics, - ], () => { - refreshReactivity() - }, { deep: true }) - // trigger update for route change - client.nuxt.vueApp.config.globalProperties?.$router?.afterEach(() => { - refreshReactivity() - }) - // trigger update for app mounted - client.nuxt.hook('app:mounted', () => { - refreshReactivity() - }) - - client.updateClient() - - const holder = document.createElement('div') - holder.id = 'nuxt-devtools-container' - holder.setAttribute('data-v-inspector-ignore', 'true') - document.body.appendChild(holder) - - // Shortcut to toggle devtools - addEventListener('keydown', (e) => { - if (e.code === 'KeyD' && e.altKey && e.shiftKey) - togglePanel() - }) - - const app = createApp({ - render: () => h(Main, { client }), - devtools: { - hide: true, - }, }) - app.mount(holder) - } - - setTimeout(init, 1) }) - -declare global { - interface Window { - __NUXT_DEVTOOLS_PLUGINS_METRIC__?: PluginMetric[] - __NUXT_DEVTOOLS_TIME_METRIC__?: LoadingTimeMetric - __VUE_INSPECTOR__?: VueInspectorClient - } -} diff --git a/packages/devtools/src/runtime/plugins/view/Frame.vue b/packages/devtools/src/runtime/plugins/view/Frame.vue deleted file mode 100644 index 73ec20195..000000000 --- a/packages/devtools/src/runtime/plugins/view/Frame.vue +++ /dev/null @@ -1,71 +0,0 @@ - - -