Skip to content

Commit

Permalink
feat: popup devtools as Picture-in-Picture (#282)
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Jun 23, 2023
1 parent 41a881d commit a65f50e
Show file tree
Hide file tree
Showing 17 changed files with 474 additions and 281 deletions.
23 changes: 20 additions & 3 deletions packages/devtools-kit/src/_types/client-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools/client/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ watch(
addEventListener('keydown', (e) => {
if (e.code === 'KeyD' && e.altKey) {
client.value?.closeDevTools()
client.value?.close()
e.preventDefault()
}
})
Expand Down
8 changes: 3 additions & 5 deletions packages/devtools/client/components/DockingPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,13 @@ function refreshPage() {
<div carbon-sun dark:carbon-moon translate-y--1px /> {{ isDark.value ? 'Dark' : 'Light' }}

Check warning on line 20 in packages/devtools/client/components/DockingPanel.vue

View workflow job for this annotation

GitHub Actions / ci

UnoCSS attributes are not ordered
</NButton>
</NDarkToggle>
<NButton n="sm primary" to="/settings">
<div carbon-settings-adjust translate-y--1px /> Settings
</NButton>
<PictureInPictureButton />
</div>
<div px3 py2 flex="~ gap2">
<NButton n="solid primary xs" @click="refreshData">
<NButton n="primary sm" @click="refreshData">
Refetch Data
</NButton>
<NButton n="solid primary xs" @click="refreshPage">
<NButton n="primary sm" @click="refreshPage">
Refresh Page
</NButton>
</div>
Expand Down
42 changes: 42 additions & 0 deletions packages/devtools/client/components/PictureInPictureButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<script setup lang="ts">
const client = useClient()
const isInPopup = window.__NUXT_DEVTOOLS_IS_POPUP__
const showInfo = ref(false)
</script>

<template>
<template v-if="!isInPopup">
<NButton v-if="client?.popup" n="sm primary" @click="client.popup()">
<div carbon-launch /> Popup
</NButton>
<template v-else>
<NButton n="sm primary" @click="showInfo = true">
<div carbon-launch /> Popup <span mt-0.5 text-xs op50>(not supported)</span>
</NButton>
<NDialog
v-model="showInfo" class="max-w-150 p6 pt-2 prose"
@close="showInfo = false"
>
<h1 text-3xl>
Popup is not Supported
</h1>
<p>
To popup the DevTools, it requires the <a href="https://developer.chrome.com/docs/web-platform/document-picture-in-picture/" target="_blank">Document Picture-in-Picture API</a> which is currently in experimental state.
</p>
<p>
As June 2023, the API is only available in Chrome 111 and above, under a flag <code>#document-picture-in-picture-api</code>.
</p>
<p>
You currently browser seems not supporting the API or the flag is not enabled yet.
</p>
<div>
<NButton @click="showInfo = false">
Close
</NButton>
</div>
</NDialog>
</template>
</template>
</template>
Empty file.
4 changes: 1 addition & 3 deletions packages/devtools/client/plugins/global.ts
Original file line number Diff line number Diff line change
@@ -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__ = <NuxtDevtoolsGlobal>{
window.__NUXT_DEVTOOLS_VIEW__ = {
setClient(_client) {
if (client.value === _client)
return
Expand Down
145 changes: 11 additions & 134 deletions packages/devtools/src/runtime/plugins/devtools.client.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
}
}
71 changes: 0 additions & 71 deletions packages/devtools/src/runtime/plugins/view/Frame.vue

This file was deleted.

0 comments on commit a65f50e

Please sign in to comment.