@babylonjs/inspector 9.2.1 → 9.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { createContext, forwardRef, useContext, useState, useCallback, Component, useMemo, useEffect, useRef, useReducer, Children, isValidElement, useLayoutEffect, useImperativeHandle, cloneElement, createElement, Suspense, memo, Fragment as Fragment$1, lazy } from 'react';
3
3
  import { tokens, makeStyles, Tooltip as Tooltip$1, Button as Button$1, Spinner, Link as Link$1, Caption1, Body1, useFluent, Accordion as Accordion$1, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, MessageBar as MessageBar$1, MessageBarBody, AccordionItem, SearchBox as SearchBox$1, Portal, ToggleButton as ToggleButton$1, InfoLabel as InfoLabel$1, Body1Strong, mergeClasses, useId, useToastController, Toast, ToastTitle, FluentProvider, Toaster, Checkbox as Checkbox$1, createLightTheme, createDarkTheme, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createDOMRenderer, RendererProvider, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, MenuGroup, MenuGroupHeader, Switch as Switch$1, treeItemLevelToken, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, MenuItemCheckbox, useMergedRefs, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, PresenceBadge, Slider as Slider$1, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Badge, Label, MessageBarTitle, useComboboxFilter, Combobox, Subtitle2, Textarea as Textarea$1, ToolbarButton, ToolbarDivider, Field } from '@fluentui/react-components';
4
- import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, WarningRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, PlugConnectedRegular, PlugDisconnectedRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, BubbleMultipleRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
4
+ import { ErrorCircleRegular, EyeFilled, EyeOffRegular, CheckmarkFilled, EditRegular, FilterRegular, PinFilled, PinRegular, ArrowCircleUpRegular, ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, CopyRegular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, SettingsRegular, DocumentTextRegular, createFluentIcon, TextSortAscendingRegular, GlobeRegular, WarningRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, ArrowUploadRegular, ArrowBidirectionalUpDownFilled, ArrowDownloadRegular, StopRegular, RecordRegular, DataBarHorizontalRegular, WrenchRegular, ArrowClockwiseRegular, WeatherSunnyRegular, WeatherMoonRegular, PlugDisconnectedRegular, PlugConnectedRegular, PlugConnectedCheckmarkRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, CameraRegular, AddRegular, DeleteRegular, FullScreenMaximizeRegular, ChevronDownRegular, ChevronRightRegular, CircleSmallFilled, SaveRegular, PreviousRegular, ArrowPreviousRegular, TriangleLeftRegular, RecordStopRegular, PlayRegular, ArrowNextRegular, NextRegular, PauseRegular, LinkDismissRegular, LinkEditRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, EyeRegular, CloudArrowUpRegular, CloudArrowDownRegular, EyeOffFilled, ArrowMoveFilled, StopFilled, PlayFilled, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, BubbleMultipleRegular, LightbulbRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, SoundWaveCircleRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
5
5
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
6
6
  import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
7
7
  import { Observable } from '@babylonjs/core/Misc/observable.js';
@@ -281,7 +281,7 @@ const Button = forwardRef((props, ref) => {
281
281
  });
282
282
  Button.displayName = "Button";
283
283
 
284
- const useStyles$Z = makeStyles({
284
+ const useStyles$_ = makeStyles({
285
285
  root: {
286
286
  display: "flex",
287
287
  flexDirection: "column",
@@ -362,7 +362,7 @@ class ErrorBoundary extends Component {
362
362
  }
363
363
  }
364
364
  function ErrorFallback({ error, onRetry }) {
365
- const styles = useStyles$Z();
365
+ const styles = useStyles$_();
366
366
  return (jsxs("div", { className: styles.root, children: [jsx(ErrorCircleRegular, { className: styles.icon }), jsx("div", { className: styles.title, children: "Something went wrong" }), jsx("div", { className: styles.message, children: "An error occurred in this component. You can try again or continue using other parts of the tool." }), jsx(Button, { label: "Try Again", appearance: "primary", onClick: onRetry }), error && jsx("div", { className: styles.details, children: error.message })] }));
367
367
  }
368
368
 
@@ -1277,12 +1277,12 @@ function useAccordionSectionItemState(props) {
1277
1277
  // Debug: warn if itemId changes (should be stable)
1278
1278
  const prevItemIdRef = useRef(itemId);
1279
1279
  useEffect(() => {
1280
- if (prevItemIdRef.current !== itemId) {
1280
+ if (accordionCtx && prevItemIdRef.current !== itemId) {
1281
1281
  Logger.Warn(`Accordion: The uniqueId "${itemId}" in section "${sectionCtx?.sectionId}" has changed from "${prevItemIdRef.current}". ` +
1282
1282
  `Each item must have a unique, stable ID for pin/hide persistence to work correctly.`);
1283
1283
  }
1284
1284
  prevItemIdRef.current = itemId;
1285
- }, [itemId, sectionCtx?.sectionId]);
1285
+ }, [accordionCtx, itemId, sectionCtx?.sectionId]);
1286
1286
  // Register item and detect duplicates (skip nested items, as children of other AccordionSectionItem should not participate in pin/hide/search).
1287
1287
  useEffect(() => {
1288
1288
  if (!accordionCtx || !itemUniqueId || isNested) {
@@ -1363,7 +1363,7 @@ function useIsSectionEmpty(sectionId) {
1363
1363
  return hasItems;
1364
1364
  }
1365
1365
 
1366
- const useStyles$Y = makeStyles({
1366
+ const useStyles$Z = makeStyles({
1367
1367
  accordion: {
1368
1368
  display: "flex",
1369
1369
  flexDirection: "column",
@@ -1455,7 +1455,7 @@ const useStyles$Y = makeStyles({
1455
1455
  */
1456
1456
  const AccordionMenuBar = () => {
1457
1457
  AccordionMenuBar.displayName = "AccordionMenuBar";
1458
- const classes = useStyles$Y();
1458
+ const classes = useStyles$Z();
1459
1459
  const accordionCtx = useContext(AccordionContext);
1460
1460
  if (!accordionCtx) {
1461
1461
  return null;
@@ -1479,7 +1479,7 @@ const AccordionMenuBar = () => {
1479
1479
  const AccordionSectionBlock = (props) => {
1480
1480
  AccordionSectionBlock.displayName = "AccordionSectionBlock";
1481
1481
  const { children, sectionId } = props;
1482
- const classes = useStyles$Y();
1482
+ const classes = useStyles$Z();
1483
1483
  const accordionCtx = useContext(AccordionContext);
1484
1484
  const { context: sectionContext, isEmpty } = useAccordionSectionBlockContext(props);
1485
1485
  if (accordionCtx) {
@@ -1499,7 +1499,7 @@ const AccordionSectionBlock = (props) => {
1499
1499
  const AccordionSectionItem = (props) => {
1500
1500
  AccordionSectionItem.displayName = "AccordionSectionItem";
1501
1501
  const { children, staticItem } = props;
1502
- const classes = useStyles$Y();
1502
+ const classes = useStyles$Z();
1503
1503
  const accordionCtx = useContext(AccordionContext);
1504
1504
  const itemState = useAccordionSectionItemState(props);
1505
1505
  const [ctrlMode, setCtrlMode] = useState(false);
@@ -1539,7 +1539,7 @@ const AccordionSectionItem = (props) => {
1539
1539
  */
1540
1540
  const AccordionPinnedContainer = () => {
1541
1541
  AccordionPinnedContainer.displayName = "AccordionPinnedContainer";
1542
- const classes = useStyles$Y();
1542
+ const classes = useStyles$Z();
1543
1543
  const accordionCtx = useContext(AccordionContext);
1544
1544
  return (jsx("div", { ref: accordionCtx?.pinnedContainerRef, className: classes.pinnedContainer, children: jsx(MessageBar$1, { className: classes.pinnedContainerEmpty, children: jsx(MessageBarBody, { children: "No pinned items" }) }) }));
1545
1545
  };
@@ -1550,7 +1550,7 @@ const AccordionPinnedContainer = () => {
1550
1550
  */
1551
1551
  const AccordionSearchBox = () => {
1552
1552
  AccordionSearchBox.displayName = "AccordionSearchBox";
1553
- const classes = useStyles$Y();
1553
+ const classes = useStyles$Z();
1554
1554
  const accordionCtx = useContext(AccordionContext);
1555
1555
  if (!accordionCtx?.features.search) {
1556
1556
  return null;
@@ -1566,7 +1566,7 @@ const AccordionSearchBox = () => {
1566
1566
  */
1567
1567
  const AccordionSection = (props) => {
1568
1568
  AccordionSection.displayName = "AccordionSection";
1569
- const classes = useStyles$Y();
1569
+ const classes = useStyles$Z();
1570
1570
  return jsx("div", { className: classes.panelDiv, children: props.children });
1571
1571
  };
1572
1572
  const StringAccordion = Accordion$1;
@@ -1574,7 +1574,7 @@ const Accordion = forwardRef((props, ref) => {
1574
1574
  Accordion.displayName = "Accordion";
1575
1575
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1576
1576
  const { children, highlightSections, uniqueId, enablePinnedItems, enableHiddenItems, enableSearchItems, ...rest } = props;
1577
- const classes = useStyles$Y();
1577
+ const classes = useStyles$Z();
1578
1578
  const { size } = useContext(ToolContext);
1579
1579
  const accordionCtx = useAccordionContext(props);
1580
1580
  const hasPinning = accordionCtx?.features.pinning ?? false;
@@ -1671,7 +1671,7 @@ const Collapse = (props) => {
1671
1671
  return (jsx(Collapse$1, { visible: props.visible, orientation: props.orientation, unmountOnExit: true, children: jsx("div", { className: `${classes.collapseContent} ${props.orientation === "horizontal" ? classes.horizontal : classes.vertical}`, children: props.children }) }));
1672
1672
  };
1673
1673
 
1674
- const useStyles$X = makeStyles({
1674
+ const useStyles$Y = makeStyles({
1675
1675
  button: {
1676
1676
  display: "flex",
1677
1677
  alignItems: "center",
@@ -1689,7 +1689,7 @@ const ToggleButton = (props) => {
1689
1689
  ToggleButton.displayName = "ToggleButton";
1690
1690
  const { value, onChange, title, appearance = "subtle" } = props;
1691
1691
  const { size } = useContext(ToolContext);
1692
- const classes = useStyles$X();
1692
+ const classes = useStyles$Y();
1693
1693
  const [checked, setChecked] = useState(value);
1694
1694
  const toggle = useCallback(() => {
1695
1695
  setChecked((prevChecked) => {
@@ -1998,7 +1998,7 @@ const UXContextProvider = (props) => {
1998
1998
  function AsReadonlyArray(array) {
1999
1999
  return array;
2000
2000
  }
2001
- const useStyles$W = makeStyles({
2001
+ const useStyles$X = makeStyles({
2002
2002
  rootDiv: {
2003
2003
  flex: 1,
2004
2004
  overflow: "hidden",
@@ -2013,7 +2013,7 @@ const useStyles$W = makeStyles({
2013
2013
  * @returns The extensible accordion component.
2014
2014
  */
2015
2015
  function ExtensibleAccordion(props) {
2016
- const classes = useStyles$W();
2016
+ const classes = useStyles$X();
2017
2017
  const { children, sections, sectionContent, context, sectionsRef, ...rest } = props;
2018
2018
  const defaultSections = useMemo(() => {
2019
2019
  const defaultSections = [];
@@ -2138,7 +2138,7 @@ function ExtensibleAccordion(props) {
2138
2138
  })] }) })) }));
2139
2139
  }
2140
2140
 
2141
- const useStyles$V = makeStyles({
2141
+ const useStyles$W = makeStyles({
2142
2142
  paneRootDiv: {
2143
2143
  display: "flex",
2144
2144
  flex: 1,
@@ -2151,7 +2151,7 @@ const useStyles$V = makeStyles({
2151
2151
  */
2152
2152
  const SidePaneContainer = forwardRef((props, ref) => {
2153
2153
  const { className, ...rest } = props;
2154
- const classes = useStyles$V();
2154
+ const classes = useStyles$W();
2155
2155
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
2156
2156
  });
2157
2157
 
@@ -2422,7 +2422,7 @@ function useTheme(invert = false) {
2422
2422
  }
2423
2423
 
2424
2424
  // Fluent doesn't apply styling to scrollbars by default, so provide our own reasonable default.
2425
- const useStyles$U = makeStyles({
2425
+ const useStyles$V = makeStyles({
2426
2426
  root: {
2427
2427
  scrollbarColor: `${tokens.colorNeutralForeground3} ${tokens.colorTransparentBackground}`,
2428
2428
  },
@@ -2438,11 +2438,11 @@ const Theme = (props) => {
2438
2438
  // break any UI within the portal. Therefore, default to false.
2439
2439
  const { invert = false, applyStylesToPortals = false, className, ...rest } = props;
2440
2440
  const theme = useTheme(invert);
2441
- const classes = useStyles$U();
2441
+ const classes = useStyles$V();
2442
2442
  return (jsx(FluentProvider, { theme: theme, className: mergeClasses(classes.root, className), applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
2443
2443
  };
2444
2444
 
2445
- const useStyles$T = makeStyles({
2445
+ const useStyles$U = makeStyles({
2446
2446
  extensionTeachingPopover: {
2447
2447
  maxWidth: "320px",
2448
2448
  },
@@ -2453,7 +2453,7 @@ const useStyles$T = makeStyles({
2453
2453
  * @returns The teaching moment popover.
2454
2454
  */
2455
2455
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
2456
- const classes = useStyles$T();
2456
+ const classes = useStyles$U();
2457
2457
  return (jsx(TeachingPopover, { appearance: "brand", open: shouldDisplay, positioning: { positioningRef }, onOpenChange: onOpenChange, children: jsxs(TeachingPopoverSurface, { className: classes.extensionTeachingPopover, children: [jsx(TeachingPopoverHeader, { children: title }), jsx(TeachingPopoverBody, { children: description })] }) }));
2458
2458
  };
2459
2459
 
@@ -2945,7 +2945,7 @@ const RootComponentServiceIdentity = Symbol("RootComponent");
2945
2945
  * The unique identity symbol for the shell service.
2946
2946
  */
2947
2947
  const ShellServiceIdentity = Symbol("ShellService");
2948
- const useStyles$S = makeStyles({
2948
+ const useStyles$T = makeStyles({
2949
2949
  mainView: {
2950
2950
  flex: 1,
2951
2951
  display: "flex",
@@ -3158,14 +3158,14 @@ const DockMenu = (props) => {
3158
3158
  };
3159
3159
  const PaneHeader = (props) => {
3160
3160
  const { id, title, dockOptions } = props;
3161
- const classes = useStyles$S();
3161
+ const classes = useStyles$T();
3162
3162
  return (jsxs("div", { className: classes.paneHeaderDiv, children: [props.icon && (jsx("div", { className: classes.paneHeaderIcon, children: jsx(props.icon, {}) })), jsx(Subtitle2Stronger, { className: mergeClasses(classes.paneHeaderText, !props.icon && classes.paneHeaderTextNoIcon), children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }));
3163
3163
  };
3164
3164
  // This is a wrapper for an item in a toolbar that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
3165
3165
  const ToolbarItem = (props) => {
3166
3166
  // eslint-disable-next-line @typescript-eslint/naming-convention
3167
3167
  const { verticalLocation, horizontalLocation, id, component: Component, displayName } = props;
3168
- const classes = useStyles$S();
3168
+ const classes = useStyles$T();
3169
3169
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
3170
3170
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3171
3171
  const title = typeof props.teachingMoment === "object" ? props.teachingMoment.title : (displayName ?? id);
@@ -3175,7 +3175,7 @@ const ToolbarItem = (props) => {
3175
3175
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
3176
3176
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
3177
3177
  const Toolbar = ({ location, components }) => {
3178
- const classes = useStyles$S();
3178
+ const classes = useStyles$T();
3179
3179
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
3180
3180
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
3181
3181
  return (jsx(Fragment, { children: components.length > 0 && (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : classes.barBottom}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, teachingMoment: entry.teachingMoment }, entry.key))) })] })) }));
@@ -3185,7 +3185,7 @@ const SidePaneTab = (props) => {
3185
3185
  const { location, id, isSelected, isFirst, isLast, dockOptions,
3186
3186
  // eslint-disable-next-line @typescript-eslint/naming-convention
3187
3187
  icon: Icon, title, } = props;
3188
- const classes = useStyles$S();
3188
+ const classes = useStyles$T();
3189
3189
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
3190
3190
  const teachingMoment = useTeachingMoment(props.teachingMoment === false);
3191
3191
  const tabClass = mergeClasses(classes.tab, isSelected ? classes.selectedTab : classes.unselectedTab, isFirst ? classes.firstTab : undefined, isLast ? classes.lastTab : undefined);
@@ -3197,7 +3197,7 @@ const SidePaneTab = (props) => {
3197
3197
  // In "compact" mode, the tab list is integrated into the pane itself.
3198
3198
  // In "full" mode, the returned tab list is later injected into the toolbar.
3199
3199
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems, initialCollapsed) {
3200
- const classes = useStyles$S();
3200
+ const classes = useStyles$T();
3201
3201
  const [topSelectedTab, setTopSelectedTab] = useState();
3202
3202
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
3203
3203
  const [collapsed, setCollapsed] = useState(initialCollapsed);
@@ -3394,7 +3394,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
3394
3394
  expand: () => onCollapseChanged.notifyObservers({ location: "right", collapsed: false }),
3395
3395
  };
3396
3396
  const rootComponent = () => {
3397
- const classes = useStyles$S();
3397
+ const classes = useStyles$T();
3398
3398
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSetting(SidePaneDockOverridesSettingDescriptor);
3399
3399
  // This function returns a promise that resolves after the dock change takes effect so that
3400
3400
  // we can then select the re-docked pane.
@@ -3781,13 +3781,13 @@ function useImpulse() {
3781
3781
  return [value, pulse];
3782
3782
  }
3783
3783
 
3784
- const useStyles$R = makeStyles({
3784
+ const useStyles$S = makeStyles({
3785
3785
  placeholderDiv: {
3786
3786
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
3787
3787
  },
3788
3788
  });
3789
3789
  const PropertiesPane = (props) => {
3790
- const classes = useStyles$R();
3790
+ const classes = useStyles$S();
3791
3791
  const entity = props.context;
3792
3792
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
3793
3793
  };
@@ -4227,7 +4227,7 @@ function CoerceEntityArray(entities, sort) {
4227
4227
  }
4228
4228
  return entities;
4229
4229
  }
4230
- const useStyles$Q = makeStyles({
4230
+ const useStyles$R = makeStyles({
4231
4231
  rootDiv: {
4232
4232
  flex: 1,
4233
4233
  overflow: "hidden",
@@ -4336,14 +4336,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
4336
4336
  }
4337
4337
  const SceneTreeItem = (props) => {
4338
4338
  const { isSelected, select } = props;
4339
- const classes = useStyles$Q();
4339
+ const classes = useStyles$R();
4340
4340
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4341
4341
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
4342
4342
  return (jsx(FlatTreeItem, { className: classes.treeItem, value: "scene", itemType: "leaf", parentValue: undefined, "aria-level": 1, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: jsx(GlobeRegular, {}), className: treeItemLayoutClass, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
4343
4343
  };
4344
4344
  const SectionTreeItem = (props) => {
4345
4345
  const { section, isFiltering, commandProviders, expandAll, collapseAll, isDropTarget, ...dropProps } = props;
4346
- const classes = useStyles$Q();
4346
+ const classes = useStyles$R();
4347
4347
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4348
4348
  // Get the commands that apply to this section.
4349
4349
  const commands = useResource(useCallback(() => {
@@ -4360,7 +4360,7 @@ const SectionTreeItem = (props) => {
4360
4360
  };
4361
4361
  const EntityTreeItem = (props) => {
4362
4362
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll, isDragging, isDropTarget, ...dragProps } = props;
4363
- const classes = useStyles$Q();
4363
+ const classes = useStyles$R();
4364
4364
  const [compactMode] = useSetting(CompactModeSettingDescriptor);
4365
4365
  const hasChildren = !!entityItem.children?.length;
4366
4366
  const displayInfo = useResource(useCallback(() => {
@@ -4476,7 +4476,7 @@ const EntityTreeItem = (props) => {
4476
4476
  }, children: jsx(Tooltip$1, { content: name, relationship: "description", children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }) }, GetEntityId$1(entityItem.entity)) }), jsx(MenuPopover, { hidden: !hasChildren && contextMenuCommands.length === 0, children: jsxs(MenuList, { children: [hasChildren && (jsxs(Fragment, { children: [jsx(MenuItem, { icon: jsx(ArrowExpandAllRegular, {}), onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { icon: jsx(ArrowCollapseAllRegular, {}), onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] })), hasChildren && contextMenuCommands.length > 0 && jsx(MenuDivider, {}), contextMenuItems] }) })] }));
4477
4477
  };
4478
4478
  const SceneExplorer = (props) => {
4479
- const classes = useStyles$Q();
4479
+ const classes = useStyles$R();
4480
4480
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity = null } = props;
4481
4481
  const [openItems, setOpenItems] = useState(new Set());
4482
4482
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -6102,7 +6102,7 @@ class CanvasGraphService {
6102
6102
  }
6103
6103
  }
6104
6104
 
6105
- const useStyles$P = makeStyles({
6105
+ const useStyles$Q = makeStyles({
6106
6106
  canvas: {
6107
6107
  flexGrow: 1,
6108
6108
  width: "100%",
@@ -6111,7 +6111,7 @@ const useStyles$P = makeStyles({
6111
6111
  });
6112
6112
  const CanvasGraph = (props) => {
6113
6113
  const { collector, scene, layoutObservable, returnToPlayheadObservable, onVisibleRangeChangedObservable, initialGraphSize } = props;
6114
- const classes = useStyles$P();
6114
+ const classes = useStyles$Q();
6115
6115
  const canvasRef = useRef(null);
6116
6116
  useEffect(() => {
6117
6117
  if (!canvasRef.current) {
@@ -6188,7 +6188,7 @@ function EvaluateExpression(rawValue) {
6188
6188
  return NaN;
6189
6189
  }
6190
6190
  }
6191
- const useStyles$O = makeStyles({
6191
+ const useStyles$P = makeStyles({
6192
6192
  icon: {
6193
6193
  "&:hover": {
6194
6194
  color: tokens.colorBrandForeground1,
@@ -6202,7 +6202,7 @@ const useStyles$O = makeStyles({
6202
6202
  const SpinButton = forwardRef((props, ref) => {
6203
6203
  SpinButton.displayName = "SpinButton2";
6204
6204
  const inputClasses = useInputStyles$1();
6205
- const classes = useStyles$O();
6205
+ const classes = useStyles$P();
6206
6206
  const { size } = useContext(ToolContext);
6207
6207
  const { min, max } = props;
6208
6208
  const baseStep = props.step ?? 1;
@@ -6469,7 +6469,7 @@ const Dropdown = (props) => {
6469
6469
  const NumberDropdown = Dropdown;
6470
6470
  const StringDropdown = Dropdown;
6471
6471
 
6472
- const useStyles$N = makeStyles({
6472
+ const useStyles$O = makeStyles({
6473
6473
  surface: {
6474
6474
  maxWidth: "400px",
6475
6475
  },
@@ -6484,7 +6484,7 @@ const useStyles$N = makeStyles({
6484
6484
  const Popover = forwardRef((props, ref) => {
6485
6485
  const { children, open: controlledOpen, onOpenChange, positioning, surfaceClassName } = props;
6486
6486
  const [internalOpen, setInternalOpen] = useState(false);
6487
- const classes = useStyles$N();
6487
+ const classes = useStyles$O();
6488
6488
  const isControlled = controlledOpen !== undefined;
6489
6489
  const popoverOpen = isControlled ? controlledOpen : internalOpen;
6490
6490
  const handleOpenChange = (_, data) => {
@@ -6728,7 +6728,7 @@ const InputAlphaField = (props) => {
6728
6728
  } }));
6729
6729
  };
6730
6730
 
6731
- const useStyles$M = makeStyles({
6731
+ const useStyles$N = makeStyles({
6732
6732
  sidebar: {
6733
6733
  display: "flex",
6734
6734
  flexDirection: "column",
@@ -6792,7 +6792,7 @@ const useStyles$M = makeStyles({
6792
6792
  });
6793
6793
  const PerformanceSidebar = (props) => {
6794
6794
  const { collector, onVisibleRangeChangedObservable } = props;
6795
- const classes = useStyles$M();
6795
+ const classes = useStyles$N();
6796
6796
  // Map from id to IPerfMetadata information
6797
6797
  const [metadataMap, setMetadataMap] = useState();
6798
6798
  // Map from category to all the ids belonging to that category
@@ -6865,7 +6865,7 @@ const PerformanceSidebar = (props) => {
6865
6865
  })] }, `category-${category || "version"}`))) }));
6866
6866
  };
6867
6867
 
6868
- const useStyles$L = makeStyles({
6868
+ const useStyles$M = makeStyles({
6869
6869
  container: {
6870
6870
  display: "flex",
6871
6871
  flexDirection: "row",
@@ -6894,7 +6894,7 @@ const useStyles$L = makeStyles({
6894
6894
  });
6895
6895
  const PerformanceViewer = (props) => {
6896
6896
  const { scene, layoutObservable, returnToLiveObservable, performanceCollector, initialGraphSize } = props;
6897
- const classes = useStyles$L();
6897
+ const classes = useStyles$M();
6898
6898
  const [onVisibleRangeChangedObservable] = useState(() => new Observable());
6899
6899
  const onReturnToPlayheadClick = () => {
6900
6900
  returnToLiveObservable.notifyObservers();
@@ -7061,14 +7061,14 @@ const TextPropertyLine = (props) => {
7061
7061
  return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value ?? "" }) }));
7062
7062
  };
7063
7063
 
7064
- const useStyles$K = makeStyles({
7064
+ const useStyles$L = makeStyles({
7065
7065
  pinnedStatsPane: {
7066
7066
  flex: "0 1 auto",
7067
7067
  paddingBottom: tokens.spacingHorizontalM,
7068
7068
  },
7069
7069
  });
7070
7070
  const StatsPane = (props) => {
7071
- const classes = useStyles$K();
7071
+ const classes = useStyles$L();
7072
7072
  const scene = props.context;
7073
7073
  const engine = scene.getEngine();
7074
7074
  const pollingObservable = usePollingObservable(250);
@@ -7231,7 +7231,7 @@ const ToolsServiceDefinition = {
7231
7231
  */
7232
7232
  const ReactContextServiceIdentity = Symbol("ReactContextService");
7233
7233
 
7234
- const useStyles$J = makeStyles({
7234
+ const useStyles$K = makeStyles({
7235
7235
  dropdown: {
7236
7236
  ...UniformWidthStyling,
7237
7237
  },
@@ -7243,7 +7243,7 @@ const useStyles$J = makeStyles({
7243
7243
  */
7244
7244
  const DropdownPropertyLine = forwardRef((props, ref) => {
7245
7245
  DropdownPropertyLine.displayName = "DropdownPropertyLine";
7246
- const classes = useStyles$J();
7246
+ const classes = useStyles$K();
7247
7247
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
7248
7248
  });
7249
7249
  /**
@@ -7401,7 +7401,7 @@ const SyncedSliderInput = (props) => {
7401
7401
  return (jsxs("div", { className: mergeClasses(classes.container, props.className), children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [hasSlider && (jsx(Slider, { className: getSliderClassName(), value: value, onChange: handleSliderChange, min: props.min, max: props.max, step: props.step, disabled: props.disabled, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, className: useCompactSizing ? classes.compactSpinButton : classes.spinButton, inputClassName: useCompactSizing ? classes.compactSpinButtonInput : classes.spinButtonInput, value: value, onChange: handleInputChange, step: props.step, disabled: props.disabled, disableDragButton: true })] })] }));
7402
7402
  };
7403
7403
 
7404
- const useStyles$I = makeStyles({
7404
+ const useStyles$J = makeStyles({
7405
7405
  uniformWidth: {
7406
7406
  ...UniformWidthStyling,
7407
7407
  },
@@ -7413,7 +7413,7 @@ const useStyles$I = makeStyles({
7413
7413
  */
7414
7414
  const SyncedSliderPropertyLine = forwardRef((props, ref) => {
7415
7415
  SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
7416
- const classes = useStyles$I();
7416
+ const classes = useStyles$J();
7417
7417
  const { label, description, ...sliderProps } = props;
7418
7418
  return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps, className: mergeClasses(classes.uniformWidth, props.className) }) }));
7419
7419
  });
@@ -8301,12 +8301,17 @@ class ServiceContainer {
8301
8301
  }
8302
8302
  }
8303
8303
 
8304
+ /**
8305
+ * The unique identity symbol for the toast service.
8306
+ */
8307
+ const ToastServiceIdentity = Symbol("ToastService");
8308
+
8304
8309
  const ExtensionManagerContext = createContext(undefined);
8305
8310
  function useExtensionManager() {
8306
8311
  return useContext(ExtensionManagerContext)?.extensionManager;
8307
8312
  }
8308
8313
 
8309
- const useStyles$H = makeStyles({
8314
+ const useStyles$I = makeStyles({
8310
8315
  themeButton: {
8311
8316
  margin: 0,
8312
8317
  },
@@ -8325,7 +8330,7 @@ const ThemeSelectorServiceDefinition = {
8325
8330
  teachingMoment: false,
8326
8331
  order: -300,
8327
8332
  component: () => {
8328
- const classes = useStyles$H();
8333
+ const classes = useStyles$I();
8329
8334
  const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
8330
8335
  const onSelectedThemeChange = useCallback((e, data) => {
8331
8336
  setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
@@ -8342,7 +8347,7 @@ const ThemeSelectorServiceDefinition = {
8342
8347
  },
8343
8348
  };
8344
8349
 
8345
- const useStyles$G = makeStyles({
8350
+ const useStyles$H = makeStyles({
8346
8351
  app: {
8347
8352
  colorScheme: "light dark",
8348
8353
  flexGrow: 1,
@@ -8385,11 +8390,21 @@ function MakeModularTool(options) {
8385
8390
  settingsStore.writeSetting(ThemeModeSettingDescriptor, themeMode);
8386
8391
  }
8387
8392
  const modularToolRootComponent = () => {
8388
- const classes = useStyles$G();
8393
+ const classes = useStyles$H();
8389
8394
  const [extensionManagerContext, setExtensionManagerContext] = useState();
8390
8395
  const [requiredExtensions, setRequiredExtensions] = useState();
8391
8396
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
8392
8397
  const [extensionInstallError, setExtensionInstallError] = useState();
8398
+ const [toastHandle, setToastHandle] = useState(null);
8399
+ const [toastQueue, setToastQueue] = useState([]);
8400
+ useEffect(() => {
8401
+ if (toastHandle && toastQueue.length > 0) {
8402
+ for (const { message, options } of toastQueue) {
8403
+ toastHandle.showToast(message, options);
8404
+ }
8405
+ setToastQueue([]);
8406
+ }
8407
+ }, [toastHandle, toastQueue]);
8393
8408
  const [rootComponentService, setRootComponentService] = useState();
8394
8409
  const [contexts, updateContexts] = useReducer((state, action) => {
8395
8410
  switch (action.type) {
@@ -8430,6 +8445,16 @@ function MakeModularTool(options) {
8430
8445
  },
8431
8446
  }),
8432
8447
  });
8448
+ // Expose the toast service so non-React code (e.g. Observable callbacks) can show toasts.
8449
+ await serviceContainer.addServiceAsync({
8450
+ friendlyName: "Toast Service",
8451
+ produces: [ToastServiceIdentity],
8452
+ factory: () => ({
8453
+ showToast: (message, options) => {
8454
+ setToastQueue((prev) => [...prev, { message, options }]);
8455
+ },
8456
+ }),
8457
+ });
8433
8458
  // Register the shell service (top level toolbar/side pane UI layout).
8434
8459
  await serviceContainer.addServiceAsync(MakeShellServiceDefinition(options));
8435
8460
  // Register a service that simply consumes the services we need before first render.
@@ -8452,7 +8477,7 @@ function MakeModularTool(options) {
8452
8477
  }
8453
8478
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
8454
8479
  if (extensionFeeds.length > 0) {
8455
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-CBQwBhYh.js');
8480
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-B_R2ChvJ.js');
8456
8481
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
8457
8482
  }
8458
8483
  // Register all external services (that make up a unique tool).
@@ -8525,7 +8550,7 @@ function MakeModularTool(options) {
8525
8550
  else {
8526
8551
  // eslint-disable-next-line @typescript-eslint/naming-convention
8527
8552
  const Content = rootComponentService.rootComponent;
8528
- return (jsx(ReactContextsWrapper, { contexts: contexts, children: jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(ToastProvider, { children: [jsx(Dialog, { open: !!requiredExtensions, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: "Required Extensions" }), jsxs(DialogContent, { children: ["Opening this URL requires the following extensions to be installed and enabled:", jsx("ul", { children: requiredExtensions?.map((name) => (jsx("li", { children: name }, name))) })] }), jsxs(DialogActions, { children: [jsx(Button$1, { appearance: "primary", onClick: onAcceptRequiredExtensions, children: "Install & Enable" }), jsx(Button$1, { appearance: "secondary", onClick: onRejectRequiredExtensions, children: "No Thanks" })] })] }) }) }), jsx(Dialog, { open: !!extensionInstallError, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: jsxs("div", { className: classes.extensionErrorTitleDiv, children: ["Extension Install Error", jsx(ErrorCircleRegular, { className: classes.extensionErrorIcon })] }) }), jsx(DialogContent, { children: jsxs(List$1, { children: [jsx(ListItem, { children: jsx(Body1, { children: `Extension "${extensionInstallError?.extension.name}" failed to install and was removed.` }) }), jsx(ListItem, { children: jsx(Body1, { children: `${extensionInstallError?.error}` }) })] }) }), jsx(DialogActions, { children: jsx(Button$1, { appearance: "primary", onClick: onAcknowledgedExtensionInstallError, children: "Close" }) })] }) }) }), jsx(Suspense, { fallback: jsx(Spinner, { className: classes.spinner }), children: jsx(Content, {}) })] }) }) }) }) }));
8553
+ return (jsx(ReactContextsWrapper, { contexts: contexts, children: jsx(SettingsStoreContext.Provider, { value: settingsStore, children: jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(ToastProvider, { imperativeRef: setToastHandle, children: [jsx(Dialog, { open: !!requiredExtensions, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: "Required Extensions" }), jsxs(DialogContent, { children: ["Opening this URL requires the following extensions to be installed and enabled:", jsx("ul", { children: requiredExtensions?.map((name) => (jsx("li", { children: name }, name))) })] }), jsxs(DialogActions, { children: [jsx(Button$1, { appearance: "primary", onClick: onAcceptRequiredExtensions, children: "Install & Enable" }), jsx(Button$1, { appearance: "secondary", onClick: onRejectRequiredExtensions, children: "No Thanks" })] })] }) }) }), jsx(Dialog, { open: !!extensionInstallError, modalType: "alert", children: jsx(DialogSurface, { children: jsxs(DialogBody, { children: [jsx(DialogTitle, { children: jsxs("div", { className: classes.extensionErrorTitleDiv, children: ["Extension Install Error", jsx(ErrorCircleRegular, { className: classes.extensionErrorIcon })] }) }), jsx(DialogContent, { children: jsxs(List$1, { children: [jsx(ListItem, { children: jsx(Body1, { children: `Extension "${extensionInstallError?.extension.name}" failed to install and was removed.` }) }), jsx(ListItem, { children: jsx(Body1, { children: `${extensionInstallError?.error}` }) })] }) }), jsx(DialogActions, { children: jsx(Button$1, { appearance: "primary", onClick: onAcknowledgedExtensionInstallError, children: "Close" }) })] }) }) }), jsx(Suspense, { fallback: jsx(Spinner, { className: classes.spinner }), children: jsx(Content, {}) })] }) }) }) }) }));
8529
8554
  }
8530
8555
  };
8531
8556
  // Set the container element to be a flex container so that the tool can be displayed properly.
@@ -8568,14 +8593,14 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
8568
8593
  description: "Adds a new panel for easy creation of various Babylon assets. This is a WIP extension...expect changes!",
8569
8594
  keywords: ["creation", "tools"],
8570
8595
  ...BabylonWebResources,
8571
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-C38aK2nP.js'),
8596
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-DaBqYmZw.js'),
8572
8597
  },
8573
8598
  {
8574
8599
  name: "Reflector",
8575
8600
  description: "Connects to the Reflector Bridge for real-time scene synchronization with the Babylon.js Sandbox.",
8576
8601
  keywords: ["reflector", "bridge", "sync", "sandbox", "tools"],
8577
8602
  ...BabylonWebResources,
8578
- getExtensionModuleAsync: async () => await import('./reflectorService-Bs9E2OMh.js'),
8603
+ getExtensionModuleAsync: async () => await import('./reflectorService-5IVRhqd-.js'),
8579
8604
  },
8580
8605
  ]);
8581
8606
 
@@ -8840,25 +8865,34 @@ function MakeInspectableBridgeServiceDefinition(options) {
8840
8865
  let ws = null;
8841
8866
  let reconnectTimer = null;
8842
8867
  let disposed = false;
8868
+ let enabled = options.autoStart;
8843
8869
  let connected = false;
8844
8870
  const onConnectionStatusChanged = new Observable();
8871
+ function notifyStatusChanged() {
8872
+ onConnectionStatusChanged.notifyObservers();
8873
+ }
8845
8874
  function setConnected(value) {
8846
8875
  if (connected !== value) {
8847
8876
  connected = value;
8848
- onConnectionStatusChanged.notifyObservers(value);
8877
+ notifyStatusChanged();
8849
8878
  }
8850
8879
  }
8851
8880
  function sendToBridge(message) {
8852
8881
  ws?.send(JSON.stringify(message));
8853
8882
  }
8854
8883
  function connect() {
8855
- if (disposed) {
8884
+ if (disposed || !enabled) {
8856
8885
  return;
8857
8886
  }
8858
8887
  try {
8888
+ // NOTE: The browser unconditionally logs a console error for failed WebSocket
8889
+ // connections at the network level. This cannot be suppressed from JavaScript.
8859
8890
  ws = new WebSocket(`ws://127.0.0.1:${options.port}`);
8860
8891
  }
8861
8892
  catch {
8893
+ ws = null;
8894
+ setConnected(false);
8895
+ Logger.Warn(`InspectableBridgeService: Failed to create WebSocket connection on port ${options.port}.`);
8862
8896
  scheduleReconnect();
8863
8897
  return;
8864
8898
  }
@@ -8884,8 +8918,20 @@ function MakeInspectableBridgeServiceDefinition(options) {
8884
8918
  // onclose will fire after onerror, which handles reconnection.
8885
8919
  };
8886
8920
  }
8921
+ function disconnect() {
8922
+ if (reconnectTimer !== null) {
8923
+ clearTimeout(reconnectTimer);
8924
+ reconnectTimer = null;
8925
+ }
8926
+ if (ws) {
8927
+ ws.onclose = null;
8928
+ ws.close();
8929
+ ws = null;
8930
+ }
8931
+ setConnected(false);
8932
+ }
8887
8933
  function scheduleReconnect() {
8888
- if (disposed || reconnectTimer !== null) {
8934
+ if (disposed || !enabled || reconnectTimer !== null) {
8889
8935
  return;
8890
8936
  }
8891
8937
  reconnectTimer = setTimeout(() => {
@@ -8937,8 +8983,9 @@ function MakeInspectableBridgeServiceDefinition(options) {
8937
8983
  }
8938
8984
  }
8939
8985
  }
8940
- // Initiate connection.
8941
- connect();
8986
+ if (enabled) {
8987
+ connect();
8988
+ }
8942
8989
  const registry = {
8943
8990
  addCommand(descriptor) {
8944
8991
  if (commands.has(descriptor.id)) {
@@ -8951,24 +8998,31 @@ function MakeInspectableBridgeServiceDefinition(options) {
8951
8998
  },
8952
8999
  };
8953
9000
  },
9001
+ get isEnabled() {
9002
+ return enabled;
9003
+ },
9004
+ set isEnabled(value) {
9005
+ if (enabled !== value) {
9006
+ enabled = value;
9007
+ if (enabled) {
9008
+ connect();
9009
+ }
9010
+ else {
9011
+ disconnect();
9012
+ }
9013
+ notifyStatusChanged();
9014
+ }
9015
+ },
8954
9016
  get isConnected() {
8955
9017
  return connected;
8956
9018
  },
8957
9019
  onConnectionStatusChanged,
8958
9020
  dispose: () => {
8959
9021
  disposed = true;
8960
- if (reconnectTimer !== null) {
8961
- clearTimeout(reconnectTimer);
8962
- reconnectTimer = null;
8963
- }
9022
+ enabled = false;
9023
+ disconnect();
8964
9024
  commands.clear();
8965
- setConnected(false);
8966
9025
  onConnectionStatusChanged.clear();
8967
- if (ws) {
8968
- ws.onclose = null;
8969
- ws.close();
8970
- ws = null;
8971
- }
8972
9026
  },
8973
9027
  };
8974
9028
  return registry;
@@ -9398,6 +9452,7 @@ function _StartInspectable(scene, options) {
9398
9452
  await serviceContainer.addServicesAsync(sceneContextServiceDefinition, MakeInspectableBridgeServiceDefinition({
9399
9453
  port,
9400
9454
  name,
9455
+ autoStart: options?.autoStart ?? false,
9401
9456
  }), EntityQueryServiceDefinition, ScreenshotCommandServiceDefinition, ShaderCommandServiceDefinition, StatsCommandServiceDefinition, PerfTraceCommandServiceDefinition);
9402
9457
  })();
9403
9458
  state = {
@@ -9530,7 +9585,7 @@ const ColorSliders = ({ color, onSliderChange }) => (jsxs(Fragment, { children:
9530
9585
  const Color3PropertyLine = ColorPropertyLine;
9531
9586
  const Color4PropertyLine = ColorPropertyLine;
9532
9587
 
9533
- const useStyles$F = makeStyles({
9588
+ const useStyles$G = makeStyles({
9534
9589
  uniformWidth: {
9535
9590
  ...UniformWidthStyling,
9536
9591
  },
@@ -9542,7 +9597,7 @@ const useStyles$F = makeStyles({
9542
9597
  */
9543
9598
  const TextInputPropertyLine = (props) => {
9544
9599
  TextInputPropertyLine.displayName = "TextInputPropertyLine";
9545
- const classes = useStyles$F();
9600
+ const classes = useStyles$G();
9546
9601
  return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
9547
9602
  };
9548
9603
  /**
@@ -9553,7 +9608,7 @@ const TextInputPropertyLine = (props) => {
9553
9608
  */
9554
9609
  const NumberInputPropertyLine = (props) => {
9555
9610
  NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
9556
- const classes = useStyles$F();
9611
+ const classes = useStyles$G();
9557
9612
  return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props, className: mergeClasses(classes.uniformWidth, props.className) }) }));
9558
9613
  };
9559
9614
 
@@ -9684,6 +9739,14 @@ const LegacyInspectableObjectPropertiesServiceDefinition = {
9684
9739
  },
9685
9740
  };
9686
9741
 
9742
+ const DocUrl = "https://www.npmjs.com/package/@babylonjs/inspector#inspector-cli";
9743
+ const useStyles$F = makeStyles({
9744
+ tooltipContent: {
9745
+ display: "flex",
9746
+ flexDirection: "column",
9747
+ gap: tokens.spacingVerticalXS,
9748
+ },
9749
+ });
9687
9750
  const CliConnectionStatusServiceDefinition = {
9688
9751
  friendlyName: "CLI Connection Status",
9689
9752
  consumes: [ShellServiceIdentity, CliConnectionStatusIdentity],
@@ -9695,9 +9758,22 @@ const CliConnectionStatusServiceDefinition = {
9695
9758
  teachingMoment: false,
9696
9759
  order: 0 /* DefaultToolbarItemOrder.CliStatus */,
9697
9760
  component: () => {
9761
+ const classes = useStyles$F();
9762
+ const isEnabled = useObservableState(() => cliConnectionStatus.isEnabled, cliConnectionStatus.onConnectionStatusChanged);
9698
9763
  const isConnected = useObservableState(() => cliConnectionStatus.isConnected, cliConnectionStatus.onConnectionStatusChanged);
9699
9764
  const { showToast } = useToast();
9700
9765
  const isFirstRender = useRef(true);
9766
+ const [connectingIconToggle, setConnectingIconToggle] = useState(false);
9767
+ const connecting = isEnabled && !isConnected;
9768
+ useEffect(() => {
9769
+ if (!connecting) {
9770
+ return;
9771
+ }
9772
+ const interval = setInterval(() => {
9773
+ setConnectingIconToggle((prev) => !prev);
9774
+ }, 700);
9775
+ return () => clearInterval(interval);
9776
+ }, [connecting]);
9701
9777
  useEffect(() => {
9702
9778
  if (isFirstRender.current) {
9703
9779
  isFirstRender.current = false;
@@ -9710,8 +9786,25 @@ const CliConnectionStatusServiceDefinition = {
9710
9786
  showToast("Inspector bridge disconnected.", { intent: "warning" });
9711
9787
  }
9712
9788
  }, [isConnected, showToast]);
9713
- // Using raw Fluent Button to pass color directly to the icon.
9714
- return (jsx(Tooltip, { content: isConnected ? "Connected to Inspector CLI Bridge" : "Disconnected from Inspector CLI Bridge", children: jsx(Button$1, { appearance: "subtle", icon: isConnected ? (jsx(PlugConnectedRegular, { color: tokens.colorPaletteGreenForeground2 })) : (jsx(PlugDisconnectedRegular, { color: tokens.colorPaletteRedForeground2 })), onClick: () => window.open("https://www.npmjs.com/package/@babylonjs/inspector", "_blank") }) }));
9789
+ let icon;
9790
+ let statusText;
9791
+ if (!isEnabled) {
9792
+ icon = jsx(PlugDisconnectedRegular, { color: tokens.colorNeutralForeground4 });
9793
+ statusText = "Inspector CLI bridge disabled — click to connect";
9794
+ }
9795
+ else if (!isConnected) {
9796
+ icon = connectingIconToggle ? (jsx(PlugConnectedRegular, { color: tokens.colorPaletteYellowForeground2 })) : (jsx(PlugDisconnectedRegular, { color: tokens.colorPaletteYellowForeground2 }));
9797
+ statusText = "Connecting to Inspector CLI bridge — click to disconnect";
9798
+ }
9799
+ else {
9800
+ icon = jsx(PlugConnectedCheckmarkRegular, { color: tokens.colorPaletteGreenForeground2 });
9801
+ statusText = "Connected to Inspector CLI bridge — click to disconnect";
9802
+ }
9803
+ const tooltipContent = (jsxs("div", { className: classes.tooltipContent, children: [jsx(Body1, { children: statusText }), jsx(Link, { url: DocUrl, value: "Inspector CLI documentation" })] }));
9804
+ // Using raw Fluent Button for custom icon coloring per connection state.
9805
+ return (jsx(Tooltip, { content: tooltipContent, children: jsx(Button$1, { appearance: "subtle", "aria-label": statusText, icon: icon, onClick: () => {
9806
+ cliConnectionStatus.isEnabled = !cliConnectionStatus.isEnabled;
9807
+ } }) }));
9715
9808
  },
9716
9809
  });
9717
9810
  },
@@ -18079,7 +18172,7 @@ const PhysicsPropertiesServiceDefinition = {
18079
18172
  */
18080
18173
  const PostProcessProperties = (props) => {
18081
18174
  const { postProcess } = props;
18082
- return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Width", description: "The width of the post process", value: postProcess.width, units: "px" }), jsx(StringifiedPropertyLine, { label: "Height", description: "The height of the post process", value: postProcess.height, units: "px" }), jsx(BoundProperty, { component: CheckboxPropertyLine, label: "Auto Clear", target: postProcess, propertyKey: "autoClear" }), postProcess.clearColor && jsx(BoundProperty, { component: Color4PropertyLine, label: "Clear Color", target: postProcess, propertyKey: "clearColor" }), jsx(BoundProperty, { component: CheckboxPropertyLine, label: "Pixel Perfect", target: postProcess, propertyKey: "enablePixelPerfectMode" }), jsx(BoundProperty, { component: CheckboxPropertyLine, label: "Fullscreen Viewport", target: postProcess, propertyKey: "forceFullscreenViewport" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Samples", target: postProcess, propertyKey: "samples", min: 1, max: 8, step: 1 })] }));
18175
+ return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Width", description: "The width of the post process", value: postProcess.width, units: "px" }), jsx(StringifiedPropertyLine, { label: "Height", description: "The height of the post process", value: postProcess.height, units: "px" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Auto Clear", target: postProcess, propertyKey: "autoClear" }), postProcess.clearColor && jsx(BoundProperty, { component: Color4PropertyLine, label: "Clear Color", target: postProcess, propertyKey: "clearColor" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Pixel Perfect", target: postProcess, propertyKey: "enablePixelPerfectMode" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Fullscreen Viewport", target: postProcess, propertyKey: "forceFullscreenViewport" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Samples", target: postProcess, propertyKey: "samples", min: 1, max: 8, step: 1 })] }));
18083
18176
  };
18084
18177
 
18085
18178
  const PostProcessPropertiesServiceDefinition = {
@@ -22727,23 +22820,68 @@ const CoordinateSystemModeOptions = [
22727
22820
  { label: "Right Handed", value: GLTFLoaderCoordinateSystemMode.FORCE_RIGHT_HANDED },
22728
22821
  ];
22729
22822
  const GLTFLoaderOptionsTool = ({ loaderOptions }) => {
22730
- return (jsx(PropertyLine, { label: "Loader Options", expandByDefault: false, expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Always Compute Bounding Box", target: loaderOptions, propertyKey: "alwaysComputeBoundingBox", nullable: true, defaultValue: LoaderOptionDefaults.alwaysComputeBoundingBox }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Always Compute Skeleton Root Node", target: loaderOptions, propertyKey: "alwaysComputeSkeletonRootNode", nullable: true, defaultValue: LoaderOptionDefaults.alwaysComputeSkeletonRootNode }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Animation Start Mode", options: AnimationStartModeOptions, target: loaderOptions, propertyKey: "animationStartMode", nullable: true, defaultValue: LoaderOptionDefaults.animationStartMode }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Capture Performance Counters", target: loaderOptions, propertyKey: "capturePerformanceCounters", nullable: true, defaultValue: LoaderOptionDefaults.capturePerformanceCounters }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Compile Materials", target: loaderOptions, propertyKey: "compileMaterials", nullable: true, defaultValue: LoaderOptionDefaults.compileMaterials }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Compile Shadow Generators", target: loaderOptions, propertyKey: "compileShadowGenerators", nullable: true, defaultValue: LoaderOptionDefaults.compileShadowGenerators }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Coordinate System", options: CoordinateSystemModeOptions, target: loaderOptions, propertyKey: "coordinateSystemMode", nullable: true, defaultValue: LoaderOptionDefaults.coordinateSystemMode }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Create Instances", target: loaderOptions, propertyKey: "createInstances", nullable: true, defaultValue: LoaderOptionDefaults.createInstances }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Don't Use Transmission Helper", target: loaderOptions, propertyKey: "dontUseTransmissionHelper", nullable: true, defaultValue: LoaderOptionDefaults.dontUseTransmissionHelper }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enable Logging", target: loaderOptions, propertyKey: "loggingEnabled", nullable: true, defaultValue: LoaderOptionDefaults.loggingEnabled }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load All Materials", target: loaderOptions, propertyKey: "loadAllMaterials", nullable: true, defaultValue: LoaderOptionDefaults.loadAllMaterials }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Morph Targets", target: loaderOptions, propertyKey: "loadMorphTargets", nullable: true, defaultValue: LoaderOptionDefaults.loadMorphTargets }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Node Animations", target: loaderOptions, propertyKey: "loadNodeAnimations", nullable: true, defaultValue: LoaderOptionDefaults.loadNodeAnimations }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Only Materials", target: loaderOptions, propertyKey: "loadOnlyMaterials", nullable: true, defaultValue: LoaderOptionDefaults.loadOnlyMaterials }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Skins", target: loaderOptions, propertyKey: "loadSkins", nullable: true, defaultValue: LoaderOptionDefaults.loadSkins }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Skip Materials", target: loaderOptions, propertyKey: "skipMaterials", nullable: true, defaultValue: LoaderOptionDefaults.skipMaterials }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Target FPS", target: loaderOptions, propertyKey: "targetFps", min: 1, max: 120, step: 1, nullable: true, defaultValue: LoaderOptionDefaults.targetFps }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Transparency As Coverage", target: loaderOptions, propertyKey: "transparencyAsCoverage", nullable: true, defaultValue: LoaderOptionDefaults.transparencyAsCoverage }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Clip Plane", target: loaderOptions, propertyKey: "useClipPlane", nullable: true, defaultValue: LoaderOptionDefaults.useClipPlane }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use glTF Texture Names", target: loaderOptions, propertyKey: "useGltfTextureNames", nullable: true, defaultValue: LoaderOptionDefaults.useGltfTextureNames }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use OpenPBR", target: loaderOptions, propertyKey: "useOpenPBR", nullable: true, defaultValue: LoaderOptionDefaults.useOpenPBR }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Range Requests", target: loaderOptions, propertyKey: "useRangeRequests", nullable: true, defaultValue: LoaderOptionDefaults.useRangeRequests }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use sRGB Buffers", target: loaderOptions, propertyKey: "useSRGBBuffers", nullable: true, defaultValue: LoaderOptionDefaults.useSRGBBuffers })] }) }));
22823
+ const resetLoaderOptions = useCallback(() => {
22824
+ for (const key of Object.keys(loaderOptions)) {
22825
+ loaderOptions[key] = null;
22826
+ }
22827
+ }, [loaderOptions]);
22828
+ return (jsx(PropertyLine, { label: "Loader Options", expandByDefault: false, expandedContent: jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Always Compute Bounding Box", target: loaderOptions, propertyKey: "alwaysComputeBoundingBox", nullable: true, defaultValue: LoaderOptionDefaults.alwaysComputeBoundingBox }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Always Compute Skeleton Root Node", target: loaderOptions, propertyKey: "alwaysComputeSkeletonRootNode", nullable: true, defaultValue: LoaderOptionDefaults.alwaysComputeSkeletonRootNode }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Animation Start Mode", options: AnimationStartModeOptions, target: loaderOptions, propertyKey: "animationStartMode", nullable: true, defaultValue: LoaderOptionDefaults.animationStartMode }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Capture Performance Counters", target: loaderOptions, propertyKey: "capturePerformanceCounters", nullable: true, defaultValue: LoaderOptionDefaults.capturePerformanceCounters }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Compile Materials", target: loaderOptions, propertyKey: "compileMaterials", nullable: true, defaultValue: LoaderOptionDefaults.compileMaterials }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Compile Shadow Generators", target: loaderOptions, propertyKey: "compileShadowGenerators", nullable: true, defaultValue: LoaderOptionDefaults.compileShadowGenerators }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Coordinate System", options: CoordinateSystemModeOptions, target: loaderOptions, propertyKey: "coordinateSystemMode", nullable: true, defaultValue: LoaderOptionDefaults.coordinateSystemMode }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Create Instances", target: loaderOptions, propertyKey: "createInstances", nullable: true, defaultValue: LoaderOptionDefaults.createInstances }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Don't Use Transmission Helper", target: loaderOptions, propertyKey: "dontUseTransmissionHelper", nullable: true, defaultValue: LoaderOptionDefaults.dontUseTransmissionHelper }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enable Logging", target: loaderOptions, propertyKey: "loggingEnabled", nullable: true, defaultValue: LoaderOptionDefaults.loggingEnabled }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load All Materials", target: loaderOptions, propertyKey: "loadAllMaterials", nullable: true, defaultValue: LoaderOptionDefaults.loadAllMaterials }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Morph Targets", target: loaderOptions, propertyKey: "loadMorphTargets", nullable: true, defaultValue: LoaderOptionDefaults.loadMorphTargets }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Node Animations", target: loaderOptions, propertyKey: "loadNodeAnimations", nullable: true, defaultValue: LoaderOptionDefaults.loadNodeAnimations }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Only Materials", target: loaderOptions, propertyKey: "loadOnlyMaterials", nullable: true, defaultValue: LoaderOptionDefaults.loadOnlyMaterials }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Load Skins", target: loaderOptions, propertyKey: "loadSkins", nullable: true, defaultValue: LoaderOptionDefaults.loadSkins }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Skip Materials", target: loaderOptions, propertyKey: "skipMaterials", nullable: true, defaultValue: LoaderOptionDefaults.skipMaterials }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Target FPS", target: loaderOptions, propertyKey: "targetFps", min: 1, max: 120, step: 1, nullable: true, defaultValue: LoaderOptionDefaults.targetFps }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Transparency As Coverage", target: loaderOptions, propertyKey: "transparencyAsCoverage", nullable: true, defaultValue: LoaderOptionDefaults.transparencyAsCoverage }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Clip Plane", target: loaderOptions, propertyKey: "useClipPlane", nullable: true, defaultValue: LoaderOptionDefaults.useClipPlane }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use glTF Texture Names", target: loaderOptions, propertyKey: "useGltfTextureNames", nullable: true, defaultValue: LoaderOptionDefaults.useGltfTextureNames }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use OpenPBR", target: loaderOptions, propertyKey: "useOpenPBR", nullable: true, defaultValue: LoaderOptionDefaults.useOpenPBR }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Range Requests", target: loaderOptions, propertyKey: "useRangeRequests", nullable: true, defaultValue: LoaderOptionDefaults.useRangeRequests }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use sRGB Buffers", target: loaderOptions, propertyKey: "useSRGBBuffers", nullable: true, defaultValue: LoaderOptionDefaults.useSRGBBuffers }), jsx(ButtonLine, { label: "Reset to Defaults", icon: ArrowResetRegular, onClick: resetLoaderOptions })] }) }));
22731
22829
  };
22732
22830
  const GLTFExtensionOptionsTool = ({ extensionOptions }) => {
22733
- return (jsx(PropertyLine, { label: "Extension Options", expandByDefault: false, expandedContent: jsx(Fragment, { children: Object.entries(extensionOptions)
22734
- .sort(([a], [b]) => a.localeCompare(b))
22735
- .map(([extensionName, options]) => {
22736
- return (jsx(BoundProperty, { component: SwitchPropertyLine, label: extensionName, target: options, propertyKey: "enabled", nullable: true, defaultValue: true, expandedContent: (extensionName === "MSFT_lod" && (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Maximum LODs", target: extensionOptions[extensionName], propertyKey: "maxLODsToLoad", min: 1, max: 10, step: 1, nullable: true, defaultValue: ExtensionOptionDefaults.MSFT_lod.maxLODsToLoad }, extensionName + "_maxLODsToLoad"))) ||
22737
- undefined }, extensionName));
22738
- }) }) }));
22831
+ const resetExtensionOptions = useCallback(() => {
22832
+ for (const options of Object.values(extensionOptions)) {
22833
+ for (const key of Object.keys(options)) {
22834
+ options[key] = null;
22835
+ }
22836
+ }
22837
+ }, [extensionOptions]);
22838
+ return (jsx(PropertyLine, { label: "Extension Options", expandByDefault: false, expandedContent: jsxs(Fragment, { children: [Object.entries(extensionOptions)
22839
+ .sort(([a], [b]) => a.localeCompare(b))
22840
+ .map(([extensionName, options]) => {
22841
+ return (jsx(BoundProperty, { component: SwitchPropertyLine, label: extensionName, target: options, propertyKey: "enabled", nullable: true, defaultValue: true, expandedContent: (extensionName === "MSFT_lod" && (jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Maximum LODs", target: extensionOptions[extensionName], propertyKey: "maxLODsToLoad", min: 1, max: 10, step: 1, nullable: true, defaultValue: ExtensionOptionDefaults.MSFT_lod.maxLODsToLoad }, extensionName + "_maxLODsToLoad"))) ||
22842
+ undefined }, extensionName));
22843
+ }), jsx(ButtonLine, { label: "Reset to Defaults", icon: ArrowResetRegular, onClick: resetExtensionOptions })] }) }));
22739
22844
  };
22740
22845
 
22846
+ const LoaderOptionsSetting = {
22847
+ key: "glTFLoaderOptions",
22848
+ defaultValue: {},
22849
+ };
22850
+ const ExtensionOptionsSetting = {
22851
+ key: "glTFExtensionOptions",
22852
+ defaultValue: {},
22853
+ };
22854
+ function CreatePersistingProxy(target, settingsStore, descriptor) {
22855
+ return new Proxy(target, {
22856
+ set(obj, prop, value) {
22857
+ const result = Reflect.set(obj, prop, value);
22858
+ settingsStore.writeSetting(descriptor, { ...obj });
22859
+ return result;
22860
+ },
22861
+ });
22862
+ }
22863
+ function HasNonNullValues(obj) {
22864
+ return Object.values(obj).some((v) => v !== null);
22865
+ }
22866
+ const OverridesWarning = (props) => {
22867
+ const { loaderOptions, extensionOptions } = props;
22868
+ useSetting(LoaderOptionsSetting);
22869
+ useSetting(ExtensionOptionsSetting);
22870
+ const hasLoaderOverrides = HasNonNullValues(loaderOptions);
22871
+ const hasExtensionOverrides = Object.values(extensionOptions).some((opts) => HasNonNullValues(opts));
22872
+ return (jsx(Collapse, { visible: hasLoaderOverrides || hasExtensionOverrides, children: jsx(MessageBar, { intent: "warning", message: "Loader option overrides are enabled and will persist across refreshes until disabled or reset." }) }));
22873
+ };
22741
22874
  const GLTFLoaderOptionsServiceDefinition = {
22742
22875
  friendlyName: "GLTF Loader Options",
22743
- consumes: [ToolsServiceIdentity],
22744
- factory: (toolsService) => {
22876
+ consumes: [ToolsServiceIdentity, SettingsStoreIdentity, ToastServiceIdentity],
22877
+ factory: (toolsService, settingsStore, toastService) => {
22745
22878
  // Current loader options with nullable properties (null means "don't override the options coming in with load calls")
22746
- const currentLoaderOptions = Object.fromEntries(Object.keys(LoaderOptionDefaults).map((key) => [key, null]));
22879
+ let currentLoaderOptions = Object.fromEntries(Object.keys(LoaderOptionDefaults).map((key) => [key, null]));
22880
+ // Hydrate loader options from persisted settings
22881
+ const persistedLoaderOptions = settingsStore.readSetting(LoaderOptionsSetting);
22882
+ Object.assign(currentLoaderOptions, persistedLoaderOptions);
22883
+ // Wrap in a proxy so property writes from the UI are automatically persisted
22884
+ currentLoaderOptions = CreatePersistingProxy(currentLoaderOptions, settingsStore, LoaderOptionsSetting);
22747
22885
  // Build extension options dynamically from the registered extensions.
22748
22886
  // Every extension gets an 'enabled' toggle; extensions in ExtensionOptionDefaults also get their extra properties.
22749
22887
  const currentExtensionOptions = {};
@@ -22752,22 +22890,50 @@ const GLTFLoaderOptionsServiceDefinition = {
22752
22890
  const extraNulls = defaults ? Object.fromEntries(Object.keys(defaults).map((key) => [key, null])) : {};
22753
22891
  currentExtensionOptions[extName] = { enabled: null, ...extraNulls };
22754
22892
  }
22893
+ // Hydrate extension options from persisted settings, only for extensions that are still registered
22894
+ const persistedExtensionOptions = settingsStore.readSetting(ExtensionOptionsSetting);
22895
+ for (const [extName, persistedOptions] of Object.entries(persistedExtensionOptions)) {
22896
+ if (currentExtensionOptions[extName] && persistedOptions) {
22897
+ Object.assign(currentExtensionOptions[extName], persistedOptions);
22898
+ }
22899
+ }
22900
+ // Wrap each extension's options object in a proxy that persists the full extension options map on write
22901
+ for (const extName of Object.keys(currentExtensionOptions)) {
22902
+ currentExtensionOptions[extName] = new Proxy(currentExtensionOptions[extName], {
22903
+ set(obj, prop, value) {
22904
+ const result = Reflect.set(obj, prop, value);
22905
+ settingsStore.writeSetting(ExtensionOptionsSetting, { ...currentExtensionOptions });
22906
+ return result;
22907
+ },
22908
+ });
22909
+ }
22755
22910
  // Subscribe to plugin activation
22756
22911
  const pluginObserver = SceneLoader.OnPluginActivatedObservable.add((plugin) => {
22757
22912
  if (plugin.name === "gltf") {
22758
22913
  const loader = plugin;
22759
22914
  // Apply loader settings (filter out null values to not override options coming in with load calls)
22760
22915
  const nonNullLoaderOptions = Object.fromEntries(Object.entries(currentLoaderOptions).filter(([_, v]) => v !== null));
22916
+ const hasLoaderOverrides = Object.keys(nonNullLoaderOptions).length > 0;
22761
22917
  Object.assign(loader, nonNullLoaderOptions);
22918
+ let hasExtensionOverrides = false;
22762
22919
  // Subscribe to extension loading
22763
22920
  loader.onExtensionLoadedObservable.add((extension) => {
22764
22921
  const extensionOptions = currentExtensionOptions[extension.name];
22765
22922
  if (extensionOptions) {
22766
22923
  // Apply extension settings (filter out null values to not override options coming in with load calls)
22767
22924
  const nonNullExtOptions = Object.fromEntries(Object.entries(extensionOptions).filter(([_, v]) => v !== null));
22925
+ if (Object.keys(nonNullExtOptions).length > 0) {
22926
+ hasExtensionOverrides = true;
22927
+ }
22768
22928
  Object.assign(extension, nonNullExtOptions);
22769
22929
  }
22770
22930
  });
22931
+ // Show a toast after all extensions have loaded if any overrides were applied
22932
+ loader.onCompleteObservable.addOnce(() => {
22933
+ if (hasLoaderOverrides || hasExtensionOverrides) {
22934
+ toastService.showToast("Applied glTF loader option overrides");
22935
+ }
22936
+ });
22771
22937
  }
22772
22938
  });
22773
22939
  const loaderToolsRegistration = toolsService.addSectionContent({
@@ -22775,7 +22941,7 @@ const GLTFLoaderOptionsServiceDefinition = {
22775
22941
  section: "GLTF Loader",
22776
22942
  order: 50,
22777
22943
  component: () => {
22778
- return (jsxs(Fragment, { children: [jsx(MessageBar, { intent: "info", message: "Reload the file for changes to take effect" }), jsx(GLTFLoaderOptionsTool, { loaderOptions: currentLoaderOptions }), jsx(GLTFExtensionOptionsTool, { extensionOptions: currentExtensionOptions })] }));
22944
+ return (jsxs(Fragment, { children: [jsx(MessageBar, { intent: "info", message: "Reload the file for changes to take effect" }), jsx(OverridesWarning, { loaderOptions: currentLoaderOptions, extensionOptions: currentExtensionOptions }), jsx(GLTFLoaderOptionsTool, { loaderOptions: currentLoaderOptions }), jsx(GLTFExtensionOptionsTool, { extensionOptions: currentExtensionOptions })] }));
22779
22945
  },
22780
22946
  });
22781
22947
  return {
@@ -24073,4 +24239,4 @@ const TextAreaPropertyLine = (props) => {
24073
24239
  AttachDebugLayer();
24074
24240
 
24075
24241
  export { GizmoServiceIdentity as $, Accordion as A, Button as B, CheckboxPropertyLine as C, ColorPickerPopup as D, ColorStepGradientComponent as E, ComboBox as F, ComboBoxPropertyLine as G, ConstructorFactory as H, ConvertOptions as I, DebugServiceIdentity as J, DetachDebugLayer as K, Link as L, MessageBar as M, NumberInputPropertyLine as N, DraggableLine as O, Popover as P, Dropdown as Q, EntitySelector as R, ShellServiceIdentity as S, TextInputPropertyLine as T, ErrorBoundary as U, Vector3PropertyLine as V, ExtensibleAccordion as W, FactorGradientComponent as X, FactorGradientList as Y, FileUploadLine as Z, GetPropertyDescriptor as _, useToast as a, ToastProvider as a$, HexPropertyLine as a0, InfoLabel as a1, InputHexField as a2, InputHsvField as a3, InspectableCommandRegistryIdentity as a4, Inspector as a5, InterceptFunction as a6, InterceptProperty as a7, IsPropertyReadonly as a8, LineContainer as a9, SelectionServiceDefinition as aA, SettingsServiceIdentity as aB, SettingsStore as aC, SettingsStoreIdentity as aD, ShowInspector as aE, SidePaneContainer as aF, SkeletonSelector as aG, Slider as aH, SpinButton as aI, StartInspectable as aJ, StatsServiceIdentity as aK, StringDropdown as aL, StringDropdownPropertyLine as aM, StringifiedPropertyLine as aN, Switch as aO, SwitchPropertyLine as aP, SyncedSliderInput as aQ, SyncedSliderPropertyLine as aR, TeachingMoment as aS, TextAreaPropertyLine as aT, TextInput as aU, TextPropertyLine as aV, Textarea as aW, TextureSelector as aX, TextureUpload as aY, Theme as aZ, ThemeServiceIdentity as a_, LinkPropertyLine as aa, LinkToEntityPropertyLine as ab, List as ac, MakeDialogTeachingMoment as ad, MakeLazyComponent as ae, MakeModularTool as af, MakePopoverTeachingMoment as ag, MakePropertyHook as ah, MakeTeachingMoment as ai, MaterialSelector as aj, NodeSelector as ak, NumberDropdown as al, NumberDropdownPropertyLine as am, ObservableCollection as an, Pane as ao, PlaceholderPropertyLine as ap, PositionedPopover as aq, PropertiesServiceIdentity as ar, Property as as, PropertyContext as at, PropertyLine as au, QuaternionPropertyLine as av, RotationVectorPropertyLine as aw, SceneExplorerServiceIdentity as ax, SearchBar as ay, SearchBox as az, useInterceptObservable as b, ToggleButton as b0, Tooltip as b1, UploadButton as b2, Vector2PropertyLine as b3, Vector4PropertyLine as b4, WatcherServiceIdentity as b5, useAngleConverters as b6, useAsyncResource as b7, useColor3Property as b8, useColor4Property as b9, useEventListener as ba, useEventfulState as bb, useKeyListener as bc, useKeyState as bd, useObservableCollection as be, useOrderedObservableCollection as bf, usePollingObservable as bg, usePropertyChangedNotifier as bh, useQuaternionProperty as bi, useResource as bj, useTheme as bk, useThemeMode as bl, useVector3Property as bm, LinkToEntity as c, SpinButtonPropertyLine as d, useProperty as e, SceneContextIdentity as f, SelectionServiceIdentity as g, useObservableState as h, AccordionSection as i, ButtonLine as j, ToolsServiceIdentity as k, AccordionSectionItem as l, AttachDebugLayer as m, BooleanBadgePropertyLine as n, BoundProperty as o, BuiltInsExtensionFeed as p, Checkbox as q, ChildWindow as r, Collapse as s, Color3GradientComponent as t, useExtensionManager as u, Color3GradientList as v, Color3PropertyLine as w, Color4GradientComponent as x, Color4GradientList as y, Color4PropertyLine as z };
24076
- //# sourceMappingURL=index-DmfAhsIm.js.map
24242
+ //# sourceMappingURL=index-DB_fpb1t.js.map