fase 5
This commit is contained in:
+203
@@ -0,0 +1,203 @@
|
||||
import {
|
||||
createContext,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
onCleanup,
|
||||
useContext,
|
||||
} from 'solid-js'
|
||||
import { clearDelegatedEvents, delegateEvents } from 'solid-js/web'
|
||||
import { PIP_DEFAULT_HEIGHT } from '../constants'
|
||||
import { useQueryDevtoolsContext } from './QueryDevtoolsContext'
|
||||
import type { Accessor, JSX } from 'solid-js'
|
||||
import type { StorageObject, StorageSetter } from '@solid-primitives/storage'
|
||||
|
||||
interface PiPProviderProps {
|
||||
children: JSX.Element
|
||||
localStore: StorageObject<string>
|
||||
setLocalStore: StorageSetter<string, unknown>
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
type PiPContextType = {
|
||||
pipWindow: Window | null
|
||||
requestPipWindow: (width: number, height: number) => void
|
||||
closePipWindow: () => void
|
||||
disabled: boolean
|
||||
}
|
||||
|
||||
class PipOpenError extends Error {}
|
||||
|
||||
const PiPContext = createContext<Accessor<PiPContextType> | undefined>(
|
||||
undefined,
|
||||
)
|
||||
|
||||
export const PiPProvider = (props: PiPProviderProps) => {
|
||||
// Expose pipWindow that is currently active
|
||||
const [pipWindow, setPipWindow] = createSignal<Window | null>(null)
|
||||
|
||||
// Close pipWindow programmatically
|
||||
const closePipWindow = () => {
|
||||
const w = pipWindow()
|
||||
if (w != null) {
|
||||
w.close()
|
||||
setPipWindow(null)
|
||||
}
|
||||
}
|
||||
|
||||
// Open new pipWindow
|
||||
const requestPipWindow = (width: number, height: number) => {
|
||||
// We don't want to allow multiple requests.
|
||||
if (pipWindow() != null) {
|
||||
return
|
||||
}
|
||||
|
||||
const pip = window.open(
|
||||
'',
|
||||
'TSQD-Devtools-Panel',
|
||||
`width=${width},height=${height},popup`,
|
||||
)
|
||||
|
||||
if (!pip) {
|
||||
throw new PipOpenError(
|
||||
'Failed to open popup. Please allow popups for this site to view the devtools in picture-in-picture mode.',
|
||||
)
|
||||
}
|
||||
|
||||
// Remove existing styles
|
||||
pip.document.head.innerHTML = ''
|
||||
// Remove existing body
|
||||
pip.document.body.innerHTML = ''
|
||||
// Clear Delegated Events
|
||||
clearDelegatedEvents(pip.document)
|
||||
|
||||
pip.document.title = 'TanStack Query Devtools'
|
||||
pip.document.body.style.margin = '0'
|
||||
|
||||
// Detect when window is closed by user
|
||||
pip.addEventListener('pagehide', () => {
|
||||
props.setLocalStore('pip_open', 'false')
|
||||
setPipWindow(null)
|
||||
})
|
||||
|
||||
// It is important to copy all parent window styles. Otherwise, there would be no CSS available at all
|
||||
// https://developer.chrome.com/docs/web-platform/document-picture-in-picture/#copy-style-sheets-to-the-picture-in-picture-window
|
||||
;[
|
||||
...(useQueryDevtoolsContext().shadowDOMTarget || document).styleSheets,
|
||||
].forEach((styleSheet) => {
|
||||
try {
|
||||
const cssRules = [...styleSheet.cssRules]
|
||||
.map((rule) => rule.cssText)
|
||||
.join('')
|
||||
const style = document.createElement('style')
|
||||
const style_node = styleSheet.ownerNode
|
||||
let style_id = ''
|
||||
|
||||
if (style_node && 'id' in style_node) {
|
||||
style_id = style_node.id
|
||||
}
|
||||
|
||||
if (style_id) {
|
||||
style.setAttribute('id', style_id)
|
||||
}
|
||||
style.textContent = cssRules
|
||||
pip.document.head.appendChild(style)
|
||||
} catch (e) {
|
||||
const link = document.createElement('link')
|
||||
if (styleSheet.href == null) {
|
||||
return
|
||||
}
|
||||
|
||||
link.rel = 'stylesheet'
|
||||
link.type = styleSheet.type
|
||||
link.media = styleSheet.media.toString()
|
||||
link.href = styleSheet.href
|
||||
pip.document.head.appendChild(link)
|
||||
}
|
||||
})
|
||||
delegateEvents(
|
||||
[
|
||||
'focusin',
|
||||
'focusout',
|
||||
'pointermove',
|
||||
'keydown',
|
||||
'pointerdown',
|
||||
'pointerup',
|
||||
'click',
|
||||
'mousedown',
|
||||
'input',
|
||||
],
|
||||
pip.document,
|
||||
)
|
||||
props.setLocalStore('pip_open', 'true')
|
||||
setPipWindow(pip)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
const pip_open = (props.localStore.pip_open ?? 'false') as 'true' | 'false'
|
||||
if (pip_open === 'true' && !props.disabled) {
|
||||
try {
|
||||
requestPipWindow(
|
||||
Number(window.innerWidth),
|
||||
Number(props.localStore.height || PIP_DEFAULT_HEIGHT),
|
||||
)
|
||||
} catch (error) {
|
||||
if (error instanceof PipOpenError) {
|
||||
console.error(error.message)
|
||||
props.setLocalStore('pip_open', 'false')
|
||||
props.setLocalStore('open', 'false')
|
||||
return
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
// Setup mutation observer for goober styles with id `_goober
|
||||
const gooberStyles = (
|
||||
useQueryDevtoolsContext().shadowDOMTarget || document
|
||||
).querySelector('#_goober')
|
||||
const w = pipWindow()
|
||||
if (gooberStyles && w) {
|
||||
const observer = new MutationObserver(() => {
|
||||
const pip_style = (
|
||||
useQueryDevtoolsContext().shadowDOMTarget || w.document
|
||||
).querySelector('#_goober')
|
||||
if (pip_style) {
|
||||
pip_style.textContent = gooberStyles.textContent
|
||||
}
|
||||
})
|
||||
observer.observe(gooberStyles, {
|
||||
childList: true, // observe direct children
|
||||
subtree: true, // and lower descendants too
|
||||
characterDataOldValue: true, // pass old data to callback
|
||||
})
|
||||
onCleanup(() => {
|
||||
observer.disconnect()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const value = createMemo(() => ({
|
||||
pipWindow: pipWindow(),
|
||||
requestPipWindow,
|
||||
closePipWindow,
|
||||
disabled: props.disabled ?? false,
|
||||
}))
|
||||
|
||||
return (
|
||||
<PiPContext.Provider value={value}>{props.children}</PiPContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const usePiPWindow = () => {
|
||||
const context = createMemo(() => {
|
||||
const ctx = useContext(PiPContext)
|
||||
if (!ctx) {
|
||||
throw new Error('usePiPWindow must be used within a PiPProvider')
|
||||
}
|
||||
return ctx()
|
||||
})
|
||||
return context
|
||||
}
|
||||
Generated
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
import { createContext, useContext } from 'solid-js'
|
||||
import type { Query, QueryClient, onlineManager } from '@tanstack/query-core'
|
||||
|
||||
type XPosition = 'left' | 'right'
|
||||
type YPosition = 'top' | 'bottom'
|
||||
export type DevtoolsPosition = XPosition | YPosition
|
||||
export type DevtoolsButtonPosition = `${YPosition}-${XPosition}` | 'relative'
|
||||
export type Theme = 'dark' | 'light' | 'system'
|
||||
|
||||
export interface DevtoolsErrorType {
|
||||
/**
|
||||
* The name of the error.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* How the error is initialized.
|
||||
*/
|
||||
initializer: (query: Query) => Error
|
||||
}
|
||||
|
||||
export interface QueryDevtoolsProps {
|
||||
readonly client: QueryClient
|
||||
queryFlavor: string
|
||||
version: string
|
||||
onlineManager: typeof onlineManager
|
||||
|
||||
buttonPosition?: DevtoolsButtonPosition
|
||||
position?: DevtoolsPosition
|
||||
initialIsOpen?: boolean
|
||||
errorTypes?: Array<DevtoolsErrorType>
|
||||
shadowDOMTarget?: ShadowRoot
|
||||
onClose?: () => unknown
|
||||
hideDisabledQueries?: boolean
|
||||
theme?: Theme
|
||||
}
|
||||
|
||||
export const QueryDevtoolsContext = createContext<QueryDevtoolsProps>({
|
||||
client: undefined as unknown as QueryClient,
|
||||
onlineManager: undefined as unknown as typeof onlineManager,
|
||||
queryFlavor: '',
|
||||
version: '',
|
||||
shadowDOMTarget: undefined,
|
||||
})
|
||||
|
||||
export function useQueryDevtoolsContext() {
|
||||
return useContext(QueryDevtoolsContext)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { createContext, useContext } from 'solid-js'
|
||||
import type { Accessor } from 'solid-js'
|
||||
|
||||
export const ThemeContext = createContext<Accessor<'light' | 'dark'>>(
|
||||
() => 'dark' as const,
|
||||
)
|
||||
|
||||
export function useTheme() {
|
||||
return useContext(ThemeContext)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export * from './PiPContext'
|
||||
export * from './QueryDevtoolsContext'
|
||||
export * from './ThemeContext'
|
||||
Reference in New Issue
Block a user