From 6b1b16d55583b842e5c7b25ebf62acd782a85c90 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Tue, 31 Dec 2024 15:40:43 +0100 Subject: [PATCH] Refactor `useDebugger` --- .changeset/swift-shoes-press.md | 12 + extension/src/real_world.ts | 10 +- extension/src/shared.ts | 4 +- packages/debugger/README.md | 15 +- packages/debugger/package.json | 1 - packages/debugger/src/dependency/index.ts | 12 +- packages/debugger/src/index.ts | 2 +- packages/debugger/src/inspector/index.ts | 13 +- packages/debugger/src/locator/index.ts | 51 +- packages/debugger/src/main/index.ts | 541 ++++++++++++---------- packages/debugger/src/setup.ts | 4 + packages/debugger/src/structure/index.ts | 4 +- packages/debugger/src/types.ts | 2 +- packages/frontend/src/controller.tsx | 20 +- packages/frontend/src/inspector.tsx | 6 +- packages/main/src/setup.ts | 4 +- packages/overlay/src/index.tsx | 4 +- packages/shared/package.json | 1 - packages/shared/src/utils.ts | 5 + 19 files changed, 370 insertions(+), 341 deletions(-) create mode 100644 .changeset/swift-shoes-press.md diff --git a/.changeset/swift-shoes-press.md b/.changeset/swift-shoes-press.md new file mode 100644 index 00000000..b91ac7ff --- /dev/null +++ b/.changeset/swift-shoes-press.md @@ -0,0 +1,12 @@ +--- +"@solid-devtools/debugger": minor +"@solid-devtools/frontend": patch +"@solid-devtools/overlay": patch +"@solid-devtools/shared": patch +"solid-devtools": patch +"@solid-devtools/extension": patch +--- + +Refactor `useDebugger`. +`useLocator` is removed, instead use `useDebugger().setLocatorOptions()`. +`debugger.meta.versions` moved to `debugger.versions` diff --git a/extension/src/real_world.ts b/extension/src/real_world.ts index 25e026a5..620e3e1f 100644 --- a/extension/src/real_world.ts +++ b/extension/src/real_world.ts @@ -79,19 +79,19 @@ async function attach_debugger() { const instance = debug.useDebugger() /* Check versions */ - warn_on_version_mismatch(instance.meta.versions.get_client(), + warn_on_version_mismatch(instance.versions.get_client(), import.meta.env.EXPECTED_CLIENT, 'solid-devtools') - warn_on_version_mismatch(instance.meta.versions.get_solid(), - instance.meta.versions.get_expected_solid(), + warn_on_version_mismatch(instance.versions.get_solid(), + instance.versions.get_expected_solid(), 'solid-js') // in case of navigation/page reload, reset the locator mode state in the extension window_post_message('ResetPanel', undefined) window_post_message('Debugger_Connected', { - client: instance.meta.versions.get_client(), - solid: instance.meta.versions.get_solid(), + client: instance.versions.get_client(), + solid: instance.versions.get_solid(), }) /* From Content */ diff --git a/extension/src/shared.ts b/extension/src/shared.ts index 9b1e917c..c4a30f46 100644 --- a/extension/src/shared.ts +++ b/extension/src/shared.ts @@ -83,8 +83,8 @@ export interface GeneralChannels { ResetPanel: void } -export type Channels = debug.Debugger.InputChannels - & debug.Debugger.OutputChannels +export type Channels = debug.InputChannels + & debug.OutputChannels & GeneralChannels export type Message = { diff --git a/packages/debugger/README.md b/packages/debugger/README.md index 183bd2aa..3177ebb4 100644 --- a/packages/debugger/README.md +++ b/packages/debugger/README.md @@ -57,12 +57,13 @@ const debug = useDebugger() _Debugger feature inspired by [LocatorJS](https://www.locatorjs.com)_ -Locator let's you locate components on the page, and go to their source code in your IDE. All you need to do is configure it by calling `useLocator` with some options. +Locator let's you locate components on the page, and go to their source code in your IDE. All you need to do is configure it by calling `setLocatorOptions` with some options. ```ts -import { useLocator } from '@solid-devtools/debugger' // or 'solid-devtools/setup' +import { useDebugger } from '@solid-devtools/debugger' // or 'solid-devtools/setup' -useLocator() +const debug = useDebugger() +debug.setLocatorOptions() ``` It will not allow you to highlight hovered components on the page and reveal them in the IDE or the Chrome Extension. _(depending of if the extension panel is open or not)_ @@ -80,7 +81,7 @@ Choose in which IDE the component source code should be revealed. Out-of-the-box options: `vscode`, `atom`, `webstorm` and `vscode-insiders` ```ts -useLocator({ +setLocatorOptions({ targetIDE: 'vscode', }) ``` @@ -92,7 +93,7 @@ To be able to go the source code, the code location needs to be inlined during b To target custom URLs (e.g. Github files) the `targetIDE` option accepts an function returning a `string` or `false`. ```ts -useLocator({ +setLocatorOptions({ targetIDE: ({ filePath, line }) => // will navigate to this link when clicking `https://github.com/thetarnav/solid-devtools/blob/main/playgrounds/sandbox/${filePath}#L${line}`, @@ -102,7 +103,7 @@ useLocator({ Returning `false` will prevent calling `window.open` to navigate to URL, and let you handle the click yourself. ```ts -useLocator({ +setLocatorOptions({ targetIDE({ projectPath, filePath, line, column, element }) { console.log({ projectPath, filePath, line, column, element }) return false @@ -117,7 +118,7 @@ Holding which key should enable the locator overlay? It's `"Alt"` by default — Key options: `"Alt"`, `"Control"`, `"Mete"`, `"Shift"` or `string` to be compared with `e.key` property. ```tsx -useLocator({ +setLocatorOptions({ key: 'Control', }) ``` diff --git a/packages/debugger/package.json b/packages/debugger/package.json index ed7f06c8..932a0cc6 100644 --- a/packages/debugger/package.json +++ b/packages/debugger/package.json @@ -56,7 +56,6 @@ "@solid-devtools/shared": "workspace:^", "@solid-primitives/bounds": "^0.0.122", "@solid-primitives/cursor": "^0.0.115", - "@solid-primitives/event-bus": "^1.0.11", "@solid-primitives/event-listener": "^2.3.3", "@solid-primitives/keyboard": "^1.2.8", "@solid-primitives/platform": "^0.1.2", diff --git a/packages/debugger/src/dependency/index.ts b/packages/debugger/src/dependency/index.ts index b9602f8d..397c6e24 100644 --- a/packages/debugger/src/dependency/index.ts +++ b/packages/debugger/src/dependency/index.ts @@ -1,24 +1,24 @@ -import {type EmitterEmit} from '@solid-primitives/event-bus' import {throttle} from '@solid-primitives/scheduled' import {defer} from '@solid-primitives/utils' import {type Accessor, createEffect, createMemo} from 'solid-js' -import type {Debugger} from '../main/index.ts' import {DevtoolsMainView, NodeType} from '../main/constants.ts' import {ObjectType, getObjectById} from '../main/id.ts' import {type NodeID, type Solid} from '../main/types.ts' import {getNodeType} from '../main/utils.ts' import {type OnNodeUpdate, type SerializedDGraph, collectDependencyGraph} from './collect.ts' +import {type OutputEmit, type InspectedState} from '../main/index.ts' export {type SerializedDGraph} from './collect.ts' export type DGraphUpdate = SerializedDGraph.Graph | null export function createDependencyGraph(props: { - emit: EmitterEmit - enabled: Accessor - inspectedState: Accessor - onNodeUpdate: (nodeId: NodeID) => void + enabled: Accessor + inspectedState: Accessor + onNodeUpdate: (nodeId: NodeID) => void + emit: OutputEmit }) { + let clearListeners: VoidFunction | null = null const onNodeUpdate: OnNodeUpdate = id => { diff --git a/packages/debugger/src/index.ts b/packages/debugger/src/index.ts index 42a1766b..e7de647a 100644 --- a/packages/debugger/src/index.ts +++ b/packages/debugger/src/index.ts @@ -1,4 +1,4 @@ -export {useDebugger, useLocator} from './main/index.ts' +export {useDebugger} from './main/index.ts' export { addSolidUpdateListener, interceptComputationRerun, diff --git a/packages/debugger/src/inspector/index.ts b/packages/debugger/src/inspector/index.ts index bbd839b2..66a5b114 100644 --- a/packages/debugger/src/inspector/index.ts +++ b/packages/debugger/src/inspector/index.ts @@ -1,8 +1,7 @@ import {warn} from '@solid-devtools/shared/utils' -import {type EmitterEmit} from '@solid-primitives/event-bus' import {scheduleIdle, throttle} from '@solid-primitives/scheduled' import {type Accessor, createEffect, onCleanup} from 'solid-js' -import type {Debugger} from '../main/index.ts' +import {type OutputEmit} from '../main/index.ts' import {ObjectType, getObjectById} from '../main/id.ts' import {addSolidUpdateListener} from '../main/observe.ts' import {type Mapped, type NodeID, type Solid, type ValueItemID} from '../main/types.ts' @@ -20,10 +19,10 @@ export type ToggleInspectedValueData = {id: ValueItemID; selected: boolean} * Plugin module */ export function createInspector(props: { - inspectedOwnerId: Accessor - emit: EmitterEmit - enabled: Accessor + inspectedOwnerId: Accessor + enabled: Accessor resetInspectedNode: VoidFunction + emit: OutputEmit }) { let lastDetails: Mapped.OwnerDetails | undefined let inspectedOwner: Solid.Owner | null @@ -90,7 +89,9 @@ export function createInspector(props: { } // Emit updates - batchedUpdates.length && props.emit('InspectorUpdate', batchedUpdates) + if (batchedUpdates.length) { + props.emit('InspectorUpdate', batchedUpdates) + } }) const flushPropsCheck = throttle(flush, 200) diff --git a/packages/debugger/src/locator/index.ts b/packages/debugger/src/locator/index.ts index 422331a1..6c4d5281 100644 --- a/packages/debugger/src/locator/index.ts +++ b/packages/debugger/src/locator/index.ts @@ -1,20 +1,11 @@ -import {makeHoverElementListener} from '@solid-devtools/shared/primitives' -import {warn} from '@solid-devtools/shared/utils' -import {type EmitterEmit} from '@solid-primitives/event-bus' +import * as s from 'solid-js' +import {defer} from '@solid-primitives/utils' import {makeEventListener} from '@solid-primitives/event-listener' import {createKeyHold} from '@solid-primitives/keyboard' import {scheduleIdle} from '@solid-primitives/scheduled' -import {defer} from '@solid-primitives/utils' -import { - type Accessor, - createEffect, - createMemo, - createSignal, - getOwner, - onCleanup, - runWithOwner, -} from 'solid-js' -import type {Debugger} from '../main/index.ts' +import {makeHoverElementListener} from '@solid-devtools/shared/primitives' +import {warn} from '@solid-devtools/shared/utils' +import {type OutputEmit} from '../main/index.ts' import * as registry from '../main/component-registry.ts' import {ObjectType, getObjectById} from '../main/id.ts' import SolidAPI from '../main/setup.ts' @@ -36,18 +27,18 @@ import {type HighlightElementPayload, type LocatorOptions} from './types.ts' export {parseLocationString} from './find-components.ts' export function createLocator(props: { - emit: EmitterEmit - locatorEnabled: Accessor - setLocatorEnabledSignal(signal: Accessor): void + locatorEnabled: s.Accessor + setLocatorEnabledSignal(signal: s.Accessor): void onComponentClick(componentId: NodeID, next: VoidFunction): void + emit: OutputEmit }) { - const [enabledByPressingSignal, setEnabledByPressingSignal] = createSignal((): boolean => false) - props.setLocatorEnabledSignal(createMemo(() => enabledByPressingSignal()())) + const [enabledByPressingSignal, setEnabledByPressingSignal] = s.createSignal((): boolean => false) + props.setLocatorEnabledSignal(s.createMemo(() => enabledByPressingSignal()())) - const [hoverTarget, setHoverTarget] = createSignal(null) - const [devtoolsTarget, setDevtoolsTarget] = createSignal(null) + const [hoverTarget, setHoverTarget] = s.createSignal(null) + const [devtoolsTarget, setDevtoolsTarget] = s.createSignal(null) - const [highlightedComponents, setHighlightedComponents] = createSignal([]) + const [highlightedComponents, setHighlightedComponents] = s.createSignal([]) const calcHighlightedComponents = ( target: HTMLElement | HighlightElementPayload, @@ -85,7 +76,7 @@ export function createLocator(props: { })) } - createEffect( + s.createEffect( defer( () => hoverTarget() ?? devtoolsTarget(), scheduleIdle(target => @@ -97,10 +88,12 @@ export function createLocator(props: { createElementsOverlay(highlightedComponents) // notify of component hovered by using the debugger - createEffect((prev: NodeID | undefined) => { + s.createEffect((prev: NodeID | undefined) => { const target = hoverTarget() const comp = target && registry.findComponent(target) - if (prev) props.emit('HoveredComponent', {nodeId: prev, state: false}) + if (prev) { + props.emit('HoveredComponent', {nodeId: prev, state: false}) + } if (comp) { const {id} = comp props.emit('HoveredComponent', {nodeId: id, state: true}) @@ -110,12 +103,12 @@ export function createLocator(props: { let targetIDE: TargetIDE | TargetURLFunction | undefined - createEffect(() => { + s.createEffect(() => { if (!props.locatorEnabled()) return // set hovered element as target makeHoverElementListener(el => setHoverTarget(el)) - onCleanup(() => setHoverTarget(null)) + s.onCleanup(() => setHoverTarget(null)) // go to selected component source code on click makeEventListener( @@ -144,7 +137,7 @@ export function createLocator(props: { }) let locatorUsed = false - const owner = getOwner()! + const owner = s.getOwner()! /** * User function to enable user locator features. Such as element hover and go to source. * @@ -153,7 +146,7 @@ export function createLocator(props: { * @param options {@link LocatorOptions} for the locator. */ function useLocator(options: LocatorOptions): void { - runWithOwner(owner, () => { + s.runWithOwner(owner, () => { if (locatorUsed) return warn('useLocator can be called only once.') locatorUsed = true if (options.targetIDE) targetIDE = options.targetIDE diff --git a/packages/debugger/src/main/index.ts b/packages/debugger/src/main/index.ts index 591f50a3..bb5270d3 100644 --- a/packages/debugger/src/main/index.ts +++ b/packages/debugger/src/main/index.ts @@ -1,8 +1,7 @@ -import {createGlobalEmitter, type GlobalEmitter} from '@solid-primitives/event-bus' import {createStaticStore} from '@solid-primitives/static-store' import {defer} from '@solid-primitives/utils' import * as s from 'solid-js' -import {log_message} from '@solid-devtools/shared/utils' +import {log_message, mutate_remove, type Message} from '@solid-devtools/shared/utils' import {createDependencyGraph, type DGraphUpdate} from '../dependency/index.ts' import {createInspector, type InspectorUpdate, type ToggleInspectedValueData} from '../inspector/index.ts' import {createLocator} from '../locator/index.ts' @@ -13,285 +12,313 @@ import {getObjectById, getSdtId, ObjectType} from './id.ts' import setup from './setup.ts' import {type Mapped, type NodeID} from './types.ts' -export namespace Debugger { - export type InspectedState = { - readonly ownerId: NodeID | null - readonly signalId: NodeID | null - /** closest note to inspected signal/owner on the owner structure */ - readonly treeWalkerOwnerId: NodeID | null - } - - export type OutputChannels = { - DebuggerEnabled: boolean - ResetPanel: void - InspectedState: InspectedState - InspectedNodeDetails: Mapped.OwnerDetails - StructureUpdates: StructureUpdates - NodeUpdates: NodeID[] - InspectorUpdate: InspectorUpdate[] - LocatorModeChange: boolean - HoveredComponent: {nodeId: NodeID; state: boolean} - InspectedComponent: NodeID - DgraphUpdate: DGraphUpdate - } - - export type InputChannels = { - ResetState: void - InspectNode: {ownerId: NodeID | null; signalId: NodeID | null} | null - InspectValue: ToggleInspectedValueData - HighlightElementChange: HighlightElementPayload - OpenLocation: void - TreeViewModeChange: TreeWalkerMode - ViewChange: DevtoolsMainView - ToggleModule: {module: DebuggerModule; enabled: boolean} - } +export type InspectedState = { + readonly ownerId: NodeID | null + readonly signalId: NodeID | null + /** closest note to inspected signal/owner on the owner structure */ + readonly treeWalkerOwnerId: NodeID | null } -export type DebuggerEmitter = { - output: GlobalEmitter - input: GlobalEmitter +export type OutputChannels = { + DebuggerEnabled: boolean + ResetPanel: void + InspectedState: InspectedState + InspectedNodeDetails: Mapped.OwnerDetails + StructureUpdates: StructureUpdates + NodeUpdates: NodeID[] + InspectorUpdate: InspectorUpdate[] + LocatorModeChange: boolean + HoveredComponent: {nodeId: NodeID; state: boolean} + InspectedComponent: NodeID + DgraphUpdate: DGraphUpdate } - -const hub: DebuggerEmitter = { - output: createGlobalEmitter(), - input: createGlobalEmitter(), +export type InputChannels = { + ResetState: void + InspectNode: {ownerId: NodeID | null; signalId: NodeID | null} | null + InspectValue: ToggleInspectedValueData + HighlightElementChange: HighlightElementPayload + OpenLocation: void + TreeViewModeChange: TreeWalkerMode + ViewChange: DevtoolsMainView + ToggleModule: {module: DebuggerModule; enabled: boolean} } -// -// Debugger Enabled -// -const [modules, toggleModules] = createStaticStore({ - debugger: false, - locator: false, - dgraph: false, - locatorKeyPressSignal: (): boolean => false, -}) +export type InputMessage = { + [K in keyof InputChannels]: Message +}[keyof InputChannels] +export type InputListener = (e: InputMessage) => void -// The debugger can be enabled by devtools or by the locator -const debuggerEnabled = s.createMemo( - () => modules.debugger || modules.locatorKeyPressSignal() -) -const dgraphEnabled = s.createMemo( - () => modules.dgraph && debuggerEnabled() -) -// locator is enabled if debugger is enabled, and user pressed the key to activate it, or the plugin activated it -const locatorEnabled = s.createMemo( - () => (modules.locatorKeyPressSignal() || modules.locator) && debuggerEnabled(), -) +export type OutputMessage = { + [K in keyof OutputChannels]: Message +}[keyof OutputChannels] +export type OutputListener = (e: OutputMessage) => void -s.createEffect(defer(debuggerEnabled, enabled => { - hub.output.emit('DebuggerEnabled', enabled) -})) +export type OutputEmit = (name: K, details: OutputChannels[K]) => void -// -// Current Open VIEW (currently not used) -// -let currentView: DevtoolsMainView = DEFAULT_MAIN_VIEW +function createDebugger() { -// -// Enabled Modules -// -function toggleModule(data: Debugger.InputChannels['ToggleModule']): void { - switch (data.module) { - case DebuggerModule.Structure: - // * Structure is always enabled - break - case DebuggerModule.Dgraph: - toggleModules('dgraph', data.enabled) - break - case DebuggerModule.Locator: - toggleModules('locator', data.enabled) - break + const _output_listeners: OutputListener[] = [] + + function listen(listener: OutputListener): (() => void) { + _output_listeners.push(listener) + return () => mutate_remove(_output_listeners, listener) } -} - -// -// Inspected Node -// - -// Current inspected node is shared between modules -const INITIAL_INSPECTED_STATE = { - ownerId: null, - signalId: null, - treeWalkerOwnerId: null, -} as const satisfies Debugger.OutputChannels['InspectedState'] - -const [inspectedState, setInspectedState] = s.createSignal< - Debugger.OutputChannels['InspectedState'] ->(INITIAL_INSPECTED_STATE, {equals: false}) -const inspectedOwnerId = s.createMemo(() => inspectedState().ownerId) - -s.createEffect(() => hub.output.emit('InspectedState', inspectedState())) - -function getTreeWalkerOwnerId(ownerId: NodeID | null): NodeID | null { - const owner = ownerId && getObjectById(ownerId, ObjectType.Owner) - const treeWalkerOwner = owner && structure.getClosestIncludedOwner(owner) - return treeWalkerOwner ? getSdtId(treeWalkerOwner, ObjectType.Owner) : null -} - -/** Check if the inspected node doesn't need to change (treeview mode changed or sth) */ -function updateInspectedNode() { - setInspectedState(p => ({ - ...p, - treeWalkerOwnerId: getTreeWalkerOwnerId(p.treeWalkerOwnerId), + + function emitOutputObj(e: OutputMessage) { + + DEV: {log_message('Client', 'Debugger', e)} + + for (let fn of _output_listeners) fn(e) + } + const emitOutput: OutputEmit = (name, details) => { + emitOutputObj({name, details} as any) + } + + // + // Debugger Enabled + // + const [modules, toggleModules] = createStaticStore({ + debugger: false, + locator: false, + dgraph: false, + locatorKeyPressSignal: (): boolean => false, + }) + + // The debugger can be enabled by devtools or by the locator + const debuggerEnabled = s.createMemo( + () => modules.debugger || modules.locatorKeyPressSignal() + ) + const dgraphEnabled = s.createMemo( + () => modules.dgraph && debuggerEnabled() + ) + // locator is enabled if debugger is enabled, and user pressed the key to activate it, or the plugin activated it + const locatorEnabled = s.createMemo( + () => (modules.locatorKeyPressSignal() || modules.locator) && debuggerEnabled(), + ) + + s.createEffect(defer(debuggerEnabled, enabled => { + emitOutput('DebuggerEnabled', enabled) })) -} - -function resetInspectedNode() { - setInspectedState(INITIAL_INSPECTED_STATE) -} - -function setInspectedNode(data: Debugger.InputChannels['InspectNode']): void { - let {ownerId, signalId} = data ?? {ownerId: null, signalId: null} - if (ownerId && !getObjectById(ownerId, ObjectType.Owner)) ownerId = null - if (signalId && !getObjectById(signalId, ObjectType.Signal)) signalId = null - setInspectedState({ - ownerId, - signalId, - treeWalkerOwnerId: getTreeWalkerOwnerId(ownerId), + + // + // Current Open VIEW (currently not used) + // + let currentView: DevtoolsMainView = DEFAULT_MAIN_VIEW + + // + // Enabled Modules + // + function toggleModule(data: InputChannels['ToggleModule']): void { + switch (data.module) { + case DebuggerModule.Structure: + // * Structure is always enabled + break + case DebuggerModule.Dgraph: + toggleModules('dgraph', data.enabled) + break + case DebuggerModule.Locator: + toggleModules('locator', data.enabled) + break + } + } + + // + // Inspected Node + // + + // Current inspected node is shared between modules + const INITIAL_INSPECTED_STATE = { + ownerId: null, + signalId: null, + treeWalkerOwnerId: null, + } as const satisfies OutputChannels['InspectedState'] + + const [inspectedState, setInspectedState] = s.createSignal< + OutputChannels['InspectedState'] + >(INITIAL_INSPECTED_STATE, {equals: false}) + const inspectedOwnerId = s.createMemo(() => inspectedState().ownerId) + + s.createEffect(() => { + emitOutput('InspectedState', inspectedState()) }) -} - -s.createComputed( - defer(debuggerEnabled, enabled => { - if (!enabled) resetInspectedNode() - }), -) - -// Computation and signal updates -let node_updates_ids: NodeID[] = [] -let node_updates_timeout = 0 - -function pushNodeUpdate(id: NodeID) { - node_updates_ids.push(id) + function getTreeWalkerOwnerId(ownerId: NodeID | null): NodeID | null { + const owner = ownerId && getObjectById(ownerId, ObjectType.Owner) + const treeWalkerOwner = owner && structure.getClosestIncludedOwner(owner) + return treeWalkerOwner ? getSdtId(treeWalkerOwner, ObjectType.Owner) : null + } - if (node_updates_timeout === 0) { - node_updates_timeout = window.setTimeout(() => { - hub.output.emit('NodeUpdates', node_updates_ids) - node_updates_ids = [] - node_updates_timeout = 0 - }) + /** Check if the inspected node doesn't need to change (treeview mode changed or sth) */ + function updateInspectedNode() { + setInspectedState(p => ({ + ...p, + treeWalkerOwnerId: getTreeWalkerOwnerId(p.treeWalkerOwnerId), + })) } -} - -// -// Structure: -// -const structure = createStructure({ - onStructureUpdate(updates) { - hub.output.emit('StructureUpdates', updates) - updateInspectedNode() - }, - onNodeUpdate: pushNodeUpdate, - enabled: debuggerEnabled, -}) - -// -// Inspected Owner details: -// -const inspector = createInspector({ - emit: hub.output.emit, - enabled: debuggerEnabled, - inspectedOwnerId, - resetInspectedNode, -}) - -// -// Dependency Graph -// -const dgraph = createDependencyGraph({ - emit: hub.output.emit, - enabled: dgraphEnabled, - onNodeUpdate: pushNodeUpdate, - inspectedState, -}) - -// -// Locator -// -const locator = createLocator({ - emit: hub.output.emit, - locatorEnabled, - setLocatorEnabledSignal: signal => toggleModules('locatorKeyPressSignal', () => signal), - onComponentClick(componentId, next) { - modules.debugger ? hub.output.emit('InspectedComponent', componentId) : next() - }, -}) - -// Opens the source code of the inspected component -function openInspectedNodeLocation() { - const details = inspector.getLastDetails() - details?.location && locator.openElementSourceCode(details.location, details.name) -} - -// send the state of the client locator mode -s.createEffect( - defer(modules.locatorKeyPressSignal, state => hub.output.emit('LocatorModeChange', state)), -) - -hub.input.listen(e => { - - DEV: {log_message('Debugger', 'Client', e)} - - switch (e.name) { - case 'ResetState': { - // reset all the internal state - s.batch(() => { - resetInspectedNode() - currentView = DEFAULT_MAIN_VIEW - structure.resetTreeWalkerMode() - locator.setDevtoolsHighlightTarget(null) + + function resetInspectedNode() { + setInspectedState(INITIAL_INSPECTED_STATE) + } + + function setInspectedNode(data: InputChannels['InspectNode']): void { + let {ownerId, signalId} = data ?? {ownerId: null, signalId: null} + if (ownerId && !getObjectById(ownerId, ObjectType.Owner)) ownerId = null + if (signalId && !getObjectById(signalId, ObjectType.Signal)) signalId = null + setInspectedState({ + ownerId, + signalId, + treeWalkerOwnerId: getTreeWalkerOwnerId(ownerId), }) - break } - case 'HighlightElementChange': - locator.setDevtoolsHighlightTarget(e.details) - break - case 'InspectNode': - setInspectedNode(e.details) - break - case 'InspectValue': - inspector.toggleValueNode(e.details) - break - case 'OpenLocation': - openInspectedNodeLocation() - break - case 'TreeViewModeChange': - structure.setTreeWalkerMode(e.details) - break - case 'ViewChange': - currentView = e.details - structure.onViewChange(currentView) - dgraph.onViewChange(currentView) - break - case 'ToggleModule': - toggleModule(e.details) - break + + s.createComputed( + defer(debuggerEnabled, enabled => { + if (!enabled) resetInspectedNode() + }), + ) + + // Computation and signal updates + let node_updates_ids: NodeID[] = [] + let node_updates_timeout = 0 + + function pushNodeUpdate(id: NodeID) { + + node_updates_ids.push(id) + + if (node_updates_timeout === 0) { + node_updates_timeout = window.setTimeout(() => { + emitOutput('NodeUpdates', node_updates_ids) + node_updates_ids = [] + node_updates_timeout = 0 + }) + } } -}) - -DEV: { - hub.output.listen(e => { - log_message('Client', 'Debugger', e) + + // + // Structure: + // + const structure = createStructure({ + onStructureUpdate(updates) { + emitOutput('StructureUpdates', updates) + updateInspectedNode() + }, + onNodeUpdate: pushNodeUpdate, + enabled: debuggerEnabled, + }) + + // + // Inspected Owner details: + // + const inspector = createInspector({ + enabled: debuggerEnabled, + inspectedOwnerId: inspectedOwnerId, + resetInspectedNode: resetInspectedNode, + emit: emitOutput, + }) + + // + // Dependency Graph + // + const dgraph = createDependencyGraph({ + enabled: dgraphEnabled, + onNodeUpdate: pushNodeUpdate, + inspectedState: inspectedState, + emit: emitOutput, }) + + // + // Locator + // + const locator = createLocator({ + locatorEnabled, + setLocatorEnabledSignal(signal) { + toggleModules('locatorKeyPressSignal', () => signal) + }, + onComponentClick(componentId, next) { + if (modules.debugger) { + emitOutput('InspectedComponent', componentId) + } else { + next() + } + }, + emit: emitOutput, + }) + + // Opens the source code of the inspected component + function openInspectedNodeLocation() { + const details = inspector.getLastDetails() + details?.location && locator.openElementSourceCode(details.location, details.name) + } + + // send the state of the client locator mode + s.createEffect(defer(modules.locatorKeyPressSignal, state => { + emitOutput('LocatorModeChange', state) + })) + + function emitInputObj(e: InputMessage) { + + DEV: {log_message('Debugger', 'Client', e)} + + switch (e.name) { + case 'ResetState': { + // reset all the internal state + s.batch(() => { + resetInspectedNode() + currentView = DEFAULT_MAIN_VIEW + structure.resetTreeWalkerMode() + locator.setDevtoolsHighlightTarget(null) + }) + break + } + case 'HighlightElementChange': + locator.setDevtoolsHighlightTarget(e.details) + break + case 'InspectNode': + setInspectedNode(e.details) + break + case 'InspectValue': + inspector.toggleValueNode(e.details) + break + case 'OpenLocation': + openInspectedNodeLocation() + break + case 'TreeViewModeChange': + structure.setTreeWalkerMode(e.details) + break + case 'ViewChange': + currentView = e.details + structure.onViewChange(currentView) + dgraph.onViewChange(currentView) + break + case 'ToggleModule': + toggleModule(e.details) + break + } + } + function emitInput(name: K, details: InputChannels[K]) { + emitInputObj({name, details} as any) + } + + return { + versions: setup.versions, + enabled: debuggerEnabled, + listen: listen, + emit: emitInput, + emitObj: emitInputObj, + setLocatorOptions: locator.useLocator, + toggleEnabled(enabled: boolean) { + toggleModules('debugger', enabled) + }, + } } +let _debugger_instance: ReturnType | undefined + /** * Used for connecting debugger to devtools */ export function useDebugger() { - return { - meta: { - versions: setup.versions, - }, - enabled: debuggerEnabled, - toggleEnabled: (enabled: boolean) => void toggleModules('debugger', enabled), - on: hub.output.on, - listen: hub.output.listen, - emit: hub.input.emit, - } + _debugger_instance ??= createDebugger() + return _debugger_instance } - -export const useLocator = locator.useLocator diff --git a/packages/debugger/src/setup.ts b/packages/debugger/src/setup.ts index 44ce85ba..6ba1a0eb 100644 --- a/packages/debugger/src/setup.ts +++ b/packages/debugger/src/setup.ts @@ -28,9 +28,13 @@ export function getOwnerLocation(owner: Solid.Owner) { } let PassedLocatorOptions: LocatorOptions | null = null +/** @deprecated use `setLocatorOptions` */ export function useLocator(options: LocatorOptions) { PassedLocatorOptions = options } +export function setLocatorOptions(options: LocatorOptions) { + PassedLocatorOptions = options +} let ClientVersion: string | null = null let SolidVersion: string | null = null diff --git a/packages/debugger/src/structure/index.ts b/packages/debugger/src/structure/index.ts index 9ce0fbfb..a3b78470 100644 --- a/packages/debugger/src/structure/index.ts +++ b/packages/debugger/src/structure/index.ts @@ -46,8 +46,8 @@ function getClosestIncludedOwner(owner: Solid.Owner, mode: TreeWalkerMode): Soli export function createStructure(props: { onStructureUpdate: (updates: StructureUpdates) => void - onNodeUpdate: (nodeId: NodeID) => void - enabled: () => boolean + onNodeUpdate: (nodeId: NodeID) => void + enabled: () => boolean }) { let treeWalkerMode: TreeWalkerMode = DEFAULT_WALKER_MODE diff --git a/packages/debugger/src/types.ts b/packages/debugger/src/types.ts index 5d0b0e56..25cb71b2 100644 --- a/packages/debugger/src/types.ts +++ b/packages/debugger/src/types.ts @@ -1,7 +1,7 @@ export type {DGraphUpdate, SerializedDGraph} from './dependency/index.ts' export * from './inspector/types.ts' export * from './locator/types.ts' -export type {Debugger} from './main/index.ts' +export type * from './main/index.ts' export * from './main/constants.ts' export * from './main/types.ts' export type {StructureUpdates} from './structure/index.ts' diff --git a/packages/frontend/src/controller.tsx b/packages/frontend/src/controller.tsx index e484fe98..863c2e32 100644 --- a/packages/frontend/src/controller.tsx +++ b/packages/frontend/src/controller.tsx @@ -11,27 +11,15 @@ import {type Structure} from './structure.tsx' import * as ui from './ui/index.ts' -export type InputMessage = { - [K in keyof debug.Debugger.OutputChannels]: { - name: K, - details: debug.Debugger.OutputChannels[K], - } -}[keyof debug.Debugger.OutputChannels] -export type InputListener = (e: InputMessage) => void +export type InputMessage = debug.OutputMessage +export type InputListener = debug.OutputListener +export type OutputMessage = debug.InputMessage +export type OutputListener = debug.InputListener export type InputEventBus = { emit: (e: InputMessage) => void, listen: (fn: InputListener) => void, } - -export type OutputMessage = { - [K in keyof debug.Debugger.InputChannels]: { - name: K, - details: debug.Debugger.InputChannels[K], - } -}[keyof debug.Debugger.InputChannels] -export type OutputListener = (e: OutputMessage) => void - export type OutputEventBus = { emit: (e: OutputMessage) => void, listen: (fn: OutputListener) => void, diff --git a/packages/frontend/src/inspector.tsx b/packages/frontend/src/inspector.tsx index b86166e5..ae4494a7 100644 --- a/packages/frontend/src/inspector.tsx +++ b/packages/frontend/src/inspector.tsx @@ -156,7 +156,7 @@ const NULL_INSPECTED_NODE = { ownerId: null, signalId: null, treeWalkerOwnerId: null, -} as const satisfies debug.Debugger.InspectedState +} as const satisfies debug.InspectedState export default function createInspector( output: OutputEventBus, @@ -167,14 +167,14 @@ export default function createInspector( // const [inspected, setInspected] = - createStaticStore(NULL_INSPECTED_NODE) + createStaticStore(NULL_INSPECTED_NODE) const inspectedNode = s.createMemo(() => ({...inspected}), void 0, { equals: (a, b) => a.ownerId === b.ownerId && a.signalId === b.signalId, }) const isSomeNodeInspected = s.createMemo( () => inspected.ownerId !== null || inspected.signalId !== null, ) - const isInspected = s.createSelector( + const isInspected = s.createSelector( inspectedNode, (id, node) => node.ownerId === id || node.signalId === id, ) diff --git a/packages/main/src/setup.ts b/packages/main/src/setup.ts index 7465f9ff..7731f812 100644 --- a/packages/main/src/setup.ts +++ b/packages/main/src/setup.ts @@ -4,7 +4,7 @@ import { setClientVersion, setOwnerLocation, setSolidVersion, - useLocator, + setLocatorOptions, } from '@solid-devtools/debugger/setup' setClientVersion(process.env.CLIENT_VERSION) @@ -19,4 +19,4 @@ export function setComponentLocation(location: string): void { setOwnerLocation(location) } -export {useLocator as setLocatorOptions} +export {setLocatorOptions} diff --git a/packages/overlay/src/index.tsx b/packages/overlay/src/index.tsx index 40a6a2f9..e33e6792 100644 --- a/packages/overlay/src/index.tsx +++ b/packages/overlay/src/index.tsx @@ -105,7 +105,7 @@ const Overlay: s.Component = ({defaultOpen, alwaysOpen, noPaddin {_ => { - debug.emit('ResetState') + debug.emit('ResetState', undefined) s.onCleanup(() => debug.emit('InspectNode', null)) @@ -114,7 +114,7 @@ const Overlay: s.Component = ({defaultOpen, alwaysOpen, noPaddin }) devtools.output.listen(e => { - separate(e.details, details => debug.emit(e.name, details as never)) + separate(e, debug.emitObj) }) debug.listen(e => { diff --git a/packages/shared/package.json b/packages/shared/package.json index 0f66b500..4c27a07b 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -59,7 +59,6 @@ "solid-js": "^1.9.0" }, "dependencies": { - "@solid-primitives/event-bus": "^1.0.11", "@solid-primitives/event-listener": "^2.3.3", "@solid-primitives/media": "^2.2.9", "@solid-primitives/refs": "^1.0.8", diff --git a/packages/shared/src/utils.ts b/packages/shared/src/utils.ts index f9cff685..ed59c52c 100644 --- a/packages/shared/src/utils.ts +++ b/packages/shared/src/utils.ts @@ -1,3 +1,8 @@ +export type Message = { + name: K, + details: V, +} + export const LOG_LABEL_CYAN = `\x1b[1;30m\x1b[46msolid-devtools\x1b[0m` export function info(data: T): T {