import { useSignalEffect } from '@preact/signals'; import { useCallback, useEffect, useLayoutEffect, useState, } from 'preact/hooks'; import { type LocalStorageOptions, ReactScanInternals, Store, } from '~core/index'; import { Icon } from '~web/components/icon'; import { Toggle } from '~web/components/toggle'; import { signalWidgetViews } from '~web/state'; import { cn, readLocalStorage, saveLocalStorage } from '~web/utils/helpers'; import { constant } from '~web/utils/preact/constant'; import { FPSMeter } from '~web/widget/fps-meter'; import { getEventSeverity } from '../notifications/data'; import { Notification } from '../notifications/icons'; import { useAppNotifications } from '../notifications/notifications'; export const Toolbar = constant(() => { const events = useAppNotifications(); const [laggedEvents, setLaggedEvents] = useState(events); useEffect(() => { const timeout = setTimeout(() => { setLaggedEvents(events); // 500 + buffer to never see intermediary state // todo: check if we still need this large of buffer }, 500 + 100); return () => { clearTimeout(timeout); }; }, [events]); const inspectState = Store.inspectState; const isInspectActive = inspectState.value.kind === 'inspecting'; const isInspectFocused = inspectState.value.kind === 'focused'; const [seenEvents, setSeenEvents] = useState>([]); const onToggleInspect = useCallback(() => { const currentState = Store.inspectState.value; switch (currentState.kind) { case 'inspecting': { signalWidgetViews.value = { view: 'none', }; Store.inspectState.value = { kind: 'inspect-off', }; return; } case 'focused': { signalWidgetViews.value = { view: 'inspector', }; Store.inspectState.value = { kind: 'inspecting', hoveredDomElement: null, }; return; } // todo: auto select the root fibers first stateNode, and tell the user to select the element case 'inspect-off': { signalWidgetViews.value = { view: 'none', }; Store.inspectState.value = { kind: 'inspecting', hoveredDomElement: null, }; return; } case 'uninitialized': { return; } } }, []); const onToggleActive = useCallback((e: Event) => { e.preventDefault(); e.stopPropagation(); if (!ReactScanInternals.instrumentation) { return; } // todo: set a single source of truth const isPaused = !ReactScanInternals.instrumentation.isPaused.value; ReactScanInternals.instrumentation.isPaused.value = isPaused; const existingLocalStorageOptions = readLocalStorage('react-scan-options'); saveLocalStorage('react-scan-options', { ...existingLocalStorageOptions, enabled: !isPaused, }); }, []); useSignalEffect(() => { const state = Store.inspectState.value; if (state.kind === 'uninitialized') { Store.inspectState.value = { kind: 'inspect-off', }; } }); let inspectIcon = null; let inspectColor = '#999'; if (isInspectActive) { inspectIcon = ; inspectColor = '#8e61e3'; } else if (isInspectFocused) { inspectIcon = ; inspectColor = '#8e61e3'; } else { inspectIcon = ; inspectColor = '#999'; } // oxlint-disable-next-line react-hooks/exhaustive-deps useLayoutEffect(() => { if (signalWidgetViews.value.view !== 'notifications') { return; } const ids = new Set(events.map((event) => event.id)); setSeenEvents([...ids.values()]); }, [events.length, signalWidgetViews.value.view]); return (
{/* todo add back showFPS*/} {ReactScanInternals.options.value.showFPS && }
); });