Skip to content

Commit 973b4fe

Browse files
authored
Inspector v2: Playground integration (#16741)
This is an initial integration of the inspector v2 into Playground. Some notes on this: - Using the inspectorv2 query param (distinct from the newux param @georginahalpern is using for the UI overhaul work, so that we can enable/disable these separately). - In a later PR (probably after a bit more inspector v2 feature work is done), we'll add a button in the Playground toolbar that adds the query param for you. - Inspector v2 does not have debugLayer integration yet, so the code uses the inspector v2 API directly. Additionally, we'll want Playground to be able to use new inspector v2 APIs (e.g. adding additional Playground specific inspector features), in which case it will need to directly call the inspector v2 APIs anyway. - This required a change to prevent Fluent focus management from stealing tab keys when focus is in the code editor (which should insert tabs, not move focus!). More details in the comments. - Inspector v2 does not have "embed" mode yet (scene explorer stacked on top of the right pane), so in Playground it shows up as a left pane and a right pane for now. ![image](https://github.com/user-attachments/assets/b831ce14-11bd-4521-9d8b-a049a4ce860b)
1 parent f9a0776 commit 973b4fe

File tree

5 files changed

+79
-20
lines changed

5 files changed

+79
-20
lines changed

packages/dev/inspector-v2/src/inspector.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ function _ShowInspector(scene: Nullable<Scene>, options: Partial<IInspectorOptio
5959
scene = EngineStore.LastCreatedScene;
6060
}
6161

62-
if (!scene) {
62+
if (!scene || scene.isDisposed) {
6363
return;
6464
}
6565

@@ -198,14 +198,27 @@ function _ShowInspector(scene: Nullable<Scene>, options: Partial<IInspectorOptio
198198
});
199199
disposeActions.push(() => modularTool.dispose());
200200

201+
let disposed = false;
201202
CurrentInspectorToken = {
202203
dispose: () => {
204+
if (disposed) {
205+
return;
206+
}
207+
203208
disposeActions.reverse().forEach((dispose) => dispose());
204209
if (options.handleResize) {
205210
scene.getEngine().resize();
206211
}
212+
213+
disposed = true;
207214
},
208215
};
216+
217+
const sceneDisposedObserver = scene.onDisposeObservable.addOnce(() => {
218+
HideInspector();
219+
});
220+
221+
disposeActions.push(() => sceneDisposedObserver.remove());
209222
}
210223

211224
export function HideInspector() {

packages/tools/playground/src/components/monacoComponent.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface IMonacoComponentProps {
1010
globalState: GlobalState;
1111
}
1212
export class MonacoComponent extends React.Component<IMonacoComponentProps> {
13+
private readonly _mutationObserver: MutationObserver;
1314
private _monacoManager: MonacoManager;
1415

1516
public constructor(props: IMonacoComponentProps) {
@@ -26,14 +27,41 @@ export class MonacoComponent extends React.Component<IMonacoComponentProps> {
2627
editorDiv.webkitRequestFullscreen();
2728
}
2829
});
30+
31+
// NOTE: This is a workaround currently needed when using Fluent. Specifically, Fluent currently manages focus (handling tab key events),
32+
// and only excludes elements with `contentEditable` set to `"true"`. Monaco does not set this attribute on textareas by default. Probably
33+
// Fluent should be checking `isContentEditable` instead as `contentEditable` can be set to `"inherit"`, in which case `isContentEditable`
34+
// is inherited from the parent element. If it worked this way, then we could simply set `contentEditable` to `"true"` on the monacoHost
35+
// div in this file.
36+
this._mutationObserver = new MutationObserver((mutations) => {
37+
for (const mutation of mutations) {
38+
for (const node of mutation.addedNodes) {
39+
if (node.nodeType === Node.ELEMENT_NODE) {
40+
// If the added node is a textarea
41+
if ((node as HTMLElement).tagName === "TEXTAREA") {
42+
(node as HTMLTextAreaElement).contentEditable = "true";
43+
}
44+
// If the added node contains textareas as descendants
45+
(node as HTMLElement).querySelectorAll?.("textarea").forEach((textArea) => {
46+
textArea.contentEditable = "true";
47+
});
48+
}
49+
}
50+
}
51+
});
2952
}
3053

3154
override componentDidMount() {
3255
const hostElement = this.props.refObject.current!;
56+
this._mutationObserver.observe(hostElement, { childList: true, subtree: true });
3357
// eslint-disable-next-line @typescript-eslint/no-floating-promises
3458
this._monacoManager.setupMonacoAsync(hostElement, true);
3559
}
3660

61+
override componentWillUnmount(): void {
62+
this._mutationObserver.disconnect();
63+
}
64+
3765
public override render() {
3866
return <div id="monacoHost" ref={this.props.refObject} className={this.props.className}></div>;
3967
}

packages/tools/playground/src/components/rendererComponent.tsx

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import type { Nullable, Scene } from "@dev/core";
1111

1212
import "../scss/rendering.scss";
1313

14+
// If the "inspectorv2" query parameter is present, preload (asynchronously) the new inspector v2 module.
15+
const InspectorV2ModulePromise = new URLSearchParams(window.location.search).has("inspectorv2") ? import("inspector-v2/inspector") : null;
16+
1417
interface IRenderingComponentProps {
1518
globalState: GlobalState;
1619
}
@@ -54,29 +57,40 @@ export class RenderingComponent extends React.Component<IRenderingComponentProps
5457
this._downloadManager.download(this._engine);
5558
});
5659

57-
this.props.globalState.onInspectorRequiredObservable.add(() => {
60+
this.props.globalState.onInspectorRequiredObservable.add(async () => {
5861
if (!this._scene) {
5962
return;
6063
}
6164

62-
// support for older versions
63-
// openedPanes was not available until 7.44.0, so we need to fallback to the inspector's _OpenedPane property
64-
if (this._scene.debugLayer.openedPanes === undefined) {
65-
this._inspectorFallback = true;
66-
}
65+
if (InspectorV2ModulePromise) {
66+
const inspectorV2Module = await InspectorV2ModulePromise;
67+
if (inspectorV2Module.IsInspectorVisible()) {
68+
inspectorV2Module.HideInspector();
69+
} else {
70+
inspectorV2Module.ShowInspector(this._scene, {
71+
embedMode: true,
72+
});
73+
}
74+
} else {
75+
// support for older versions
76+
// openedPanes was not available until 7.44.0, so we need to fallback to the inspector's _OpenedPane property
77+
if (this._scene.debugLayer.openedPanes === undefined) {
78+
this._inspectorFallback = true;
79+
}
6780

68-
// fallback?
69-
if (this._inspectorFallback) {
70-
const debugLayer: any = this._scene.debugLayer;
71-
debugLayer.openedPanes = debugLayer.BJSINSPECTOR?.Inspector?._OpenedPane || 0;
72-
}
81+
// fallback?
82+
if (this._inspectorFallback) {
83+
const debugLayer: any = this._scene.debugLayer;
84+
debugLayer.openedPanes = debugLayer.BJSINSPECTOR?.Inspector?._OpenedPane || 0;
85+
}
7386

74-
if (this._scene.debugLayer.openedPanes === 0) {
75-
this._scene.debugLayer.show({
76-
embedMode: true,
77-
});
78-
} else {
79-
this._scene.debugLayer.hide();
87+
if (this._scene.debugLayer.openedPanes === 0) {
88+
this._scene.debugLayer.show({
89+
embedMode: true,
90+
});
91+
} else {
92+
this._scene.debugLayer.hide();
93+
}
8094
}
8195
});
8296

@@ -128,7 +142,7 @@ export class RenderingComponent extends React.Component<IRenderingComponentProps
128142

129143
this.props.globalState.onErrorObservable.notifyObservers(null);
130144

131-
const displayInspector = this._scene?.debugLayer.isVisible();
145+
const displayInspector = InspectorV2ModulePromise ? (await InspectorV2ModulePromise).IsInspectorVisible() : this._scene?.debugLayer.isVisible();
132146

133147
const webgpuPromise = WebGPUEngine ? WebGPUEngine.IsSupportedAsync : Promise.resolve(false);
134148
const webGPUSupported = await webgpuPromise;

packages/tools/playground/tsconfig.build.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"core/*": ["dev/core/dist/*"],
99
"gui/*": ["dev/gui/dist/*"],
1010
"shared-ui-components/*": ["dev/sharedUiComponents/dist/*"],
11+
"inspector-v2/*": ["dev/inspector-v2/dist/*"],
1112
"playground/*": ["tools/playground/src/*"]
1213
}
1314
},

packages/tools/playground/webpack.config.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ module.exports = (env) => {
1919
resolve: {
2020
extensions: [".js", ".ts", ".tsx", ".scss", "*.svg"],
2121
alias: {
22-
"shared-ui-components": path.resolve("../../dev/sharedUiComponents/src"),
22+
"shared-ui-components": path.resolve("../../dev/sharedUiComponents/dist"),
23+
"inspector-v2": path.resolve("../../dev/inspector-v2/dist"),
24+
core: path.resolve("../../dev/core/dist"),
25+
loaders: path.resolve("../../dev/loaders/dist"),
2326
},
2427
},
2528
externals: {

0 commit comments

Comments
 (0)