@babylonjs/inspector 8.30.5-preview → 8.31.0-preview

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.
package/lib/index.js CHANGED
@@ -3,9 +3,9 @@ import { useMemo, useEffect, useState, useRef, useCallback, forwardRef, createCo
3
3
  import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
4
4
  import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
5
5
  import { Observable } from '@babylonjs/core/Misc/observable.js';
6
- import { makeStyles, ToggleButton as ToggleButton$1, Button as Button$1, tokens, Link, InfoLabel as InfoLabel$1, Body1Stronger, Checkbox as Checkbox$1, Body1, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, mergeClasses, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Body1Strong, TabList, Tooltip, Title3, Tab, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, Menu, MenuTrigger, MenuPopover, MenuList, MenuItem, Switch as Switch$1, PresenceBadge, Spinner, Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle, DialogContent, SplitButton, MenuItemRadio, createLightTheme, createDarkTheme, FluentProvider, DialogActions, List as List$1, ListItem, useId, Input, SpinButton as SpinButton$1, Slider, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Badge, Dropdown as Dropdown$1, Option, Popover, PopoverTrigger, ColorSwatch, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, Textarea as Textarea$1, Toolbar as Toolbar$1, ToolbarButton, useComboboxFilter, Combobox, Field } from '@fluentui/react-components';
6
+ import { makeStyles, ToggleButton as ToggleButton$1, Button as Button$1, tokens, Link, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, Body1, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, mergeClasses, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createLightTheme, createDarkTheme, FluentProvider, Toolbar as Toolbar$1, Tooltip, ToolbarRadioButton, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, Menu, MenuTrigger, MenuPopover, MenuList, MenuItem, Switch as Switch$1, PresenceBadge, Spinner, Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle, TabList, Tab, DialogContent, SplitButton, MenuItemRadio, DialogActions, List as List$1, ListItem, useId, Input, SpinButton as SpinButton$1, Slider, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Badge, Dropdown as Dropdown$1, Option, Popover, PopoverTrigger, ColorSwatch, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, Textarea as Textarea$1, ToolbarButton, useComboboxFilter, Combobox, Field } from '@fluentui/react-components';
7
7
  export { Link } from '@fluentui/react-components';
8
- import { ChevronCircleRight20Regular, ChevronCircleDown20Regular, CopyRegular, PanelLeftExpandRegular, PanelLeftContractRegular, PanelRightExpandRegular, PanelRightContractRegular, DocumentTextRegular, FilterRegular, MoviesAndTvRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, AppsAddInRegular, DismissRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowMoveRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, GlobeRegular, SaveRegular, ArrowUndoRegular, ClearFormattingRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, PaintBrushRegular, BoxRegular, BranchRegular, CameraRegular, LightbulbRegular, EyeRegular, EyeOffRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, Cone16Filled, Cone16Regular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, DeleteFilled } from '@fluentui/react-icons';
8
+ import { ChevronCircleRight20Regular, ChevronCircleDown20Regular, CopyRegular, PanelLeftExpandRegular, PanelLeftContractRegular, PanelRightExpandRegular, PanelRightContractRegular, DocumentTextRegular, createFluentIcon, FilterRegular, GlobeRegular, ArrowExpandAllRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, AppsAddInRegular, DismissRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, MyLocationRegular, CameraRegular, LightbulbRegular, BorderOutsideRegular, BorderNoneRegular, EyeRegular, EyeOffRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, DeleteFilled } from '@fluentui/react-icons';
9
9
  import { Collapse as Collapse$1 } from '@fluentui/react-motion-components-preview';
10
10
  import '@babylonjs/core/Misc/typeStore.js';
11
11
  import { useLocalStorage, useTernaryDarkMode } from 'usehooks-ts';
@@ -99,6 +99,7 @@ import { MultiRenderTarget } from '@babylonjs/core/Materials/Textures/multiRende
99
99
  import { RenderTargetTexture } from '@babylonjs/core/Materials/Textures/renderTargetTexture.js';
100
100
  import { ThinTexture } from '@babylonjs/core/Materials/Textures/thinTexture.js';
101
101
  import { WhenTextureReadyAsync, GetTextureDataAsync } from '@babylonjs/core/Misc/textureTools.js';
102
+ import '@babylonjs/core/Rendering/boundingBoxRenderer.js';
102
103
  import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineManagerSceneComponent.js';
103
104
  import '@babylonjs/core/Sprites/spriteSceneComponent.js';
104
105
  import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture.js';
@@ -674,9 +675,10 @@ const CustomTokens = {
674
675
  sliderMaxWidth: "80px",
675
676
  rightAlignOffset: "-8px",
676
677
  };
677
- const UniformWidthStyling = { width: CustomTokens.inputWidth, textAlign: "right", boxSizing: "border-box" };
678
+ const UniformWidthStyling = { width: CustomTokens.inputWidth, boxSizing: "border-box" };
678
679
  const useInputStyles$1 = makeStyles({
679
680
  input: UniformWidthStyling,
681
+ inputSlot: { textAlign: "right" },
680
682
  invalid: { backgroundColor: tokens.colorPaletteRedBackground2 },
681
683
  container: {
682
684
  flex: 1,
@@ -711,22 +713,26 @@ const usePropertyLineStyles = makeStyles({
711
713
  justifyContent: "flex-start",
712
714
  width: "100%",
713
715
  },
714
- label: {
716
+ infoLabel: {
717
+ display: "flex",
715
718
  flex: "1 1 0", // grow=1, shrink =1, basis = 0 initial size before
716
719
  minWidth: CustomTokens.labelMinWidth,
717
720
  textAlign: "left",
718
- overflow: "hidden",
719
- textOverflow: "ellipsis",
721
+ },
722
+ labelSlot: {
723
+ display: "flex",
724
+ minWidth: 0,
720
725
  },
721
726
  labelText: {
722
727
  whiteSpace: "nowrap",
728
+ overflow: "hidden",
729
+ textOverflow: "ellipsis",
723
730
  },
724
731
  rightContent: {
725
732
  flex: "0 1 auto",
726
733
  display: "flex",
727
734
  alignItems: "center",
728
735
  justifyContent: "flex-end",
729
- minWidth: "50%",
730
736
  },
731
737
  infoPopup: {
732
738
  whiteSpace: "normal",
@@ -760,7 +766,7 @@ const PropertyLine = forwardRef((props, ref) => {
760
766
  defaultValue: undefined, // Don't pass defaultValue to children as there is no guarantee how this will be used and we can't mix controlled + uncontrolled state
761
767
  })
762
768
  : children;
763
- return (jsxs(LineContainer, { ref: ref, children: [jsxs("div", { className: classes.baseLine, children: [jsx(InfoLabel$1, { className: classes.label, info: description ? jsx("div", { className: classes.infoPopup, children: description }) : undefined, title: label, children: jsx(Body1Stronger, { className: classes.labelText, children: label }) }), jsxs("div", { className: classes.rightContent, children: [expandedContent && (jsx(ToggleButton, { title: "Expand/Collapse property", appearance: "transparent", checkedIcon: ChevronCircleDown20Regular, uncheckedIcon: ChevronCircleRight20Regular, value: expanded === true, onChange: setExpanded })), nullable && !ignoreNullable && (
769
+ return (jsxs(LineContainer, { ref: ref, children: [jsxs("div", { className: classes.baseLine, children: [jsx(InfoLabel$1, { className: classes.infoLabel, label: { className: classes.labelSlot }, info: description ? jsx("div", { className: classes.infoPopup, children: description }) : undefined, title: label, children: jsx(Body1Strong, { className: classes.labelText, children: label }) }), jsxs("div", { className: classes.rightContent, children: [expandedContent && (jsx(ToggleButton, { title: "Expand/Collapse property", appearance: "transparent", checkedIcon: ChevronCircleDown20Regular, uncheckedIcon: ChevronCircleRight20Regular, value: expanded === true, onChange: setExpanded })), nullable && !ignoreNullable && (
764
770
  // If this is a nullableProperty and ignoreNullable was not sent, display a checkbox used to toggle null ('checked' means 'non null')
765
771
  jsx(Checkbox$1, { checked: !(props.value == null), onChange: (_, data) => {
766
772
  if (data.checked) {
@@ -1342,6 +1348,64 @@ const SelectionServiceDefinition = {
1342
1348
  },
1343
1349
  };
1344
1350
 
1351
+ const ThemeModeStorageKey = `Babylon/Settings/ThemeMode`;
1352
+ /**
1353
+ * Custom hook to manage the theme mode (system/dark/light).
1354
+ * @returns An object containing the theme mode state and helper functions.
1355
+ */
1356
+ function useThemeMode() {
1357
+ const { isDarkMode, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode({
1358
+ localStorageKey: ThemeModeStorageKey,
1359
+ });
1360
+ // Make sure there is a stored value initially, even before changing the theme.
1361
+ // This way, other usages of this hook will get the correct initial value.
1362
+ if (!localStorage.getItem(ThemeModeStorageKey)) {
1363
+ SetThemeMode(ternaryDarkMode);
1364
+ }
1365
+ return { isDarkMode, themeMode: ternaryDarkMode, setThemeMode: setTernaryDarkMode };
1366
+ }
1367
+ /**
1368
+ * Sets the theme mode.
1369
+ * @param mode The desired theme mode (system/dark/light).
1370
+ */
1371
+ function SetThemeMode(mode) {
1372
+ localStorage.setItem(ThemeModeStorageKey, JSON.stringify(mode));
1373
+ }
1374
+
1375
+ /* eslint-disable @typescript-eslint/naming-convention */
1376
+ // Generated from https://react.fluentui.dev/?path=/docs/theme-theme-designer--docs
1377
+ // Key color: #3A94FC
1378
+ const babylonRamp = {
1379
+ 10: "#020305",
1380
+ 20: "#121721",
1381
+ 30: "#1A263A",
1382
+ 40: "#1F314F",
1383
+ 50: "#243E64",
1384
+ 60: "#294B7B",
1385
+ 70: "#2D5892",
1386
+ 80: "#3166AA",
1387
+ 90: "#3473C3",
1388
+ 100: "#3782DC",
1389
+ 110: "#3990F6",
1390
+ 120: "#5A9EFD",
1391
+ 130: "#7BACFE",
1392
+ 140: "#96BAFF",
1393
+ 150: "#AFC9FF",
1394
+ 160: "#C6D8FF",
1395
+ };
1396
+ const LightTheme = {
1397
+ ...createLightTheme(babylonRamp),
1398
+ };
1399
+ const DarkTheme = {
1400
+ ...createDarkTheme(babylonRamp),
1401
+ };
1402
+
1403
+ const Theme = (props) => {
1404
+ const { invert = false, ...rest } = props;
1405
+ const { isDarkMode } = useThemeMode();
1406
+ return (jsx(FluentProvider, { theme: isDarkMode !== invert ? DarkTheme : LightTheme, ...rest, children: props.children }));
1407
+ };
1408
+
1345
1409
  const RootComponentServiceIdentity = Symbol("RootComponent");
1346
1410
  const ShellServiceIdentity = Symbol("ShellService");
1347
1411
  // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -1361,6 +1425,7 @@ const useStyles$b = makeStyles({
1361
1425
  display: "flex",
1362
1426
  flexDirection: "row",
1363
1427
  flex: "0 0 auto",
1428
+ height: "36px",
1364
1429
  backgroundColor: tokens.colorNeutralBackground2,
1365
1430
  },
1366
1431
  bar: {
@@ -1368,16 +1433,14 @@ const useStyles$b = makeStyles({
1368
1433
  flex: "1",
1369
1434
  height: "32px",
1370
1435
  overflow: "hidden",
1371
- padding: `${tokens.spacingVerticalSNudge} ${tokens.spacingHorizontalSNudge}`,
1436
+ padding: `${tokens.spacingVerticalXXS} ${tokens.spacingHorizontalXXS}`,
1372
1437
  border: `1px solid ${tokens.colorNeutralStroke2}`,
1438
+ borderBottomWidth: 0,
1373
1439
  backgroundColor: tokens.colorNeutralBackground1,
1374
1440
  },
1375
1441
  barTop: {
1376
1442
  borderTopWidth: 0,
1377
1443
  },
1378
- barBottom: {
1379
- borderBottomWidth: 0,
1380
- },
1381
1444
  barLeft: {
1382
1445
  marginRight: "auto",
1383
1446
  display: "flex",
@@ -1429,22 +1492,46 @@ const useStyles$b = makeStyles({
1429
1492
  display: "flex",
1430
1493
  flexGrow: 1,
1431
1494
  flexDirection: "column",
1432
- paddingTop: tokens.spacingVerticalS,
1433
1495
  overflow: "hidden",
1434
1496
  },
1435
- paneHeader: {
1497
+ paneHeaderDiv: {
1498
+ display: "flex",
1499
+ flexDirection: "column",
1500
+ justifyContent: "center",
1501
+ backgroundColor: tokens.colorNeutralBackgroundInverted,
1502
+ height: "36px",
1503
+ },
1504
+ paneHeaderText: {
1436
1505
  marginLeft: tokens.spacingHorizontalM,
1506
+ color: tokens.colorNeutralForegroundInverted,
1437
1507
  },
1438
- headerDivider: {
1508
+ paneDivider: {
1439
1509
  flex: "0 0 auto",
1440
1510
  marginTop: tokens.spacingVerticalM,
1511
+ margin: "0",
1512
+ minHeight: tokens.spacingVerticalM,
1513
+ cursor: "ns-resize",
1514
+ alignItems: "end",
1515
+ },
1516
+ tabToolbar: {
1517
+ padding: 0,
1441
1518
  },
1442
1519
  tab: {
1443
- paddingTop: tokens.spacingVerticalXS,
1444
- paddingBottom: tokens.spacingVerticalXS,
1445
- paddingLeft: tokens.spacingHorizontalS,
1446
- paddingRight: tokens.spacingHorizontalS,
1447
- alignSelf: "center",
1520
+ display: "flex",
1521
+ height: "100%",
1522
+ width: "36px",
1523
+ justifyContent: "center",
1524
+ borderTopLeftRadius: tokens.borderRadiusMedium,
1525
+ borderTopRightRadius: tokens.borderRadiusMedium,
1526
+ },
1527
+ unselectedTab: {
1528
+ backgroundColor: "transparent",
1529
+ },
1530
+ tabRadioButton: {
1531
+ backgroundColor: "transparent",
1532
+ },
1533
+ selectedTabIcon: {
1534
+ color: tokens.colorNeutralForeground1,
1448
1535
  },
1449
1536
  resizer: {
1450
1537
  width: "8px",
@@ -1466,6 +1553,13 @@ const useStyles$b = makeStyles({
1466
1553
  overflow: "hidden",
1467
1554
  },
1468
1555
  });
1556
+ const PaneHeader = ({ title }) => {
1557
+ const classes = useStyles$b();
1558
+ if (!title) {
1559
+ return null;
1560
+ }
1561
+ return (jsx("div", { className: classes.paneHeaderDiv, children: jsx(Subtitle2Stronger, { className: classes.paneHeaderText, children: title }) }));
1562
+ };
1469
1563
  // 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.
1470
1564
  const ToolbarItem = ({ location, alignment, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
1471
1565
  const classes = useStyles$b();
@@ -1479,16 +1573,21 @@ const Toolbar = ({ location, components }) => {
1479
1573
  const classes = useStyles$b();
1480
1574
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
1481
1575
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
1482
- 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, { location: location, alignment: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { location: location, alignment: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) })] })) }));
1576
+ return (jsx(Fragment, { children: components.length > 0 && (jsxs("div", { className: `${classes.bar} ${location === "top" ? classes.barTop : null}`, children: [jsx("div", { className: classes.barLeft, children: leftComponents.map((entry) => (jsx(ToolbarItem, { location: location, alignment: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) }), jsx("div", { className: classes.barRight, children: rightComponents.map((entry) => (jsx(ToolbarItem, { location: location, alignment: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) })] })) }));
1483
1577
  };
1484
1578
  // This is a wrapper for a tab in a side pane that simply adds a teaching moment, which is useful for dynamically added items, possibly from extensions.
1485
- const SidePaneTab = ({ alignment, id,
1486
- // eslint-disable-next-line @typescript-eslint/naming-convention
1487
- icon: Icon, title, suppressTeachingMoment, }) => {
1579
+ const SidePaneTab = (props) => {
1580
+ const { alignment, id, isSelected,
1581
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1582
+ icon: Icon, title, suppressTeachingMoment, } = props;
1488
1583
  const classes = useStyles$b();
1489
1584
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${alignment}/${title ?? id}`), [title, id]);
1490
1585
  const teachingMoment = useTeachingMoment(suppressTeachingMoment);
1491
- return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay && !suppressTeachingMoment, title: title ?? "Extension", description: `The "${title ?? id}" extension can be accessed here.` }), jsx(Tab, { ref: teachingMoment.targetRef, className: classes.tab, value: id, icon: jsx(Tooltip, { content: title ?? id, relationship: "description", children: jsx(Icon, {}) }) }, id)] }));
1586
+ const tabClass = mergeClasses(classes.tab, isSelected ? undefined : classes.unselectedTab);
1587
+ return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay && !suppressTeachingMoment, title: title ?? "Extension", description: `The "${title ?? id}" extension can be accessed here.` }), jsx(Theme, { className: tabClass, invert: isSelected, children: jsx(ToolbarRadioButton, { ref: teachingMoment.targetRef, title: title ?? id, appearance: "transparent", className: classes.tabRadioButton, name: "selectedTab", value: id, icon: {
1588
+ className: isSelected ? classes.selectedTabIcon : undefined,
1589
+ children: jsx(Icon, {}),
1590
+ } }) })] }));
1492
1591
  };
1493
1592
  // This hook provides a side pane container and the tab list.
1494
1593
  // In "compact" mode, the tab list is integrated into the pane itself.
@@ -1559,15 +1658,18 @@ function usePane(alignment, defaultWidth, minWidth, topPanes, bottomPanes, toolb
1559
1658
  }, { once: true });
1560
1659
  }, [resizing]);
1561
1660
  const createPaneTabList = useCallback((paneComponents, toolbarMode, selectedTab, setSelectedTab) => {
1562
- return (jsx(Fragment, { children: paneComponents.length > 0 && (jsxs("div", { className: `${classes.paneTabListDiv} ${alignment === "left" || toolbarMode === "compact" ? classes.paneTabListDivLeft : classes.paneTabListDivRight}`, children: [paneComponents.length > 1 && (jsx(Fragment, { children: jsx(TabList, { selectedValue: selectedTab?.key ?? "", onTabSelect: (event, data) => {
1563
- const tab = paneComponents.find((entry) => entry.key === data.value);
1661
+ return (jsx(Fragment, { children: paneComponents.length > 0 && (jsxs("div", { className: `${classes.paneTabListDiv} ${alignment === "left" || toolbarMode === "compact" ? classes.paneTabListDivLeft : classes.paneTabListDivRight}`, children: [paneComponents.length > 1 && (jsx(Fragment, { children: jsx(Toolbar$1, { className: classes.tabToolbar, checkedValues: { selectedTab: [selectedTab?.key ?? ""] }, onCheckedValueChange: (event, data) => {
1662
+ const tab = paneComponents.find((entry) => entry.key === data.checkedItems[0]);
1564
1663
  setSelectedTab(tab);
1565
1664
  setCollapsed(false);
1566
- }, children: paneComponents.map((entry) => (jsx(SidePaneTab, { alignment: alignment, id: entry.key, title: entry.title, icon: entry.icon, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) }) })), toolbarMode === "full" && (jsxs(Fragment, { children: [jsx(Divider, { vertical: true, inset: true }), jsx(Tooltip, { content: collapsed ? "Show Side Pane" : "Hide Side Pane", relationship: "label", children: jsx(Button$1, { className: classes.paneCollapseButton, appearance: "subtle", icon: expandCollapseIcon, onClick: onExpandCollapseClick }) })] }))] })) }));
1665
+ }, children: paneComponents.map((entry) => {
1666
+ const isSelected = selectedTab?.key === entry.key;
1667
+ return (jsx(SidePaneTab, { alignment: alignment, id: entry.key, title: entry.title, icon: entry.icon, suppressTeachingMoment: entry.suppressTeachingMoment, isSelected: isSelected && !collapsed }, entry.key));
1668
+ }) }) })), toolbarMode === "full" && (jsxs(Fragment, { children: [paneComponents.length > 1 && (jsxs(Fragment, { children: [jsx(Divider, { vertical: true, inset: true, style: { minHeight: 0 } }), " "] })), jsx(Tooltip, { content: collapsed ? "Show Side Pane" : "Hide Side Pane", relationship: "label", children: jsx(Button$1, { className: classes.paneCollapseButton, appearance: "subtle", icon: expandCollapseIcon, onClick: onExpandCollapseClick }) })] }))] })) }));
1567
1669
  }, [alignment, collapsed]);
1568
1670
  // This memos the TabList to make it easy for the JSX to be inserted at the top of the pane (in "compact" mode) or returned to the caller to be used in the toolbar (in "full" mode).
1569
- const topPaneTabList = useMemo(() => createPaneTabList(topPanes, toolbarMode, topSelectedTab, setTopSelectedTab), [topPanes, toolbarMode, topSelectedTab]);
1570
- const bottomPaneTabList = useMemo(() => createPaneTabList(bottomPanes, "compact", bottomSelectedTab, setBottomSelectedTab), [bottomPanes, bottomSelectedTab]);
1671
+ const topPaneTabList = useMemo(() => createPaneTabList(topPanes, toolbarMode, topSelectedTab, setTopSelectedTab), [createPaneTabList, topPanes, toolbarMode, topSelectedTab]);
1672
+ const bottomPaneTabList = useMemo(() => createPaneTabList(bottomPanes, "compact", bottomSelectedTab, setBottomSelectedTab), [createPaneTabList, bottomPanes, bottomSelectedTab]);
1571
1673
  // This manages the CSS variable that controls the height of the bottom pane.
1572
1674
  const paneHeightAdjustCSSVar = "--pane-height-adjust";
1573
1675
  const { elementRef: paneVerticalResizeElementRef, handleRef: paneVerticalResizeHandleRef, setValue: setPaneHeightAdjust, } = useResizeHandle({
@@ -1589,8 +1691,8 @@ function usePane(alignment, defaultWidth, minWidth, topPanes, bottomPanes, toolb
1589
1691
  });
1590
1692
  // This memoizes the pane itself, which may or may not include the tab list, depending on the toolbar mode.
1591
1693
  const pane = useMemo(() => {
1592
- return (jsx(Fragment, { children: (topPanes.length > 0 || bottomPanes.length > 0) && (jsxs("div", { className: `${classes.pane} ${alignment === "left" ? classes.paneLeft : classes.paneRight}`, children: [jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: jsxs("div", { className: classes.paneContainer, style: { width: `${width}px` }, children: [toolbarMode === "compact" && (topPanes.length > 1 || topBarItems.length > 0) && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [topPaneTabList, jsx(Toolbar, { location: "top", components: topBarItems })] }) })), jsxs("div", { className: classes.paneContent, children: [topSelectedTab?.title ? (jsxs(Fragment, { children: [jsx(Title3, { className: classes.paneHeader, children: topSelectedTab.title }), jsx(Divider, { inset: true, className: classes.headerDivider, appearance: "brand" })] })) : null, topSelectedTab?.content && jsx(topSelectedTab.content, {})] }), topPanes.length > 0 && bottomPanes.length > 0 && (jsx(Divider, { ref: paneVerticalResizeHandleRef, className: classes.headerDivider, style: { margin: "0", minHeight: tokens.spacingVerticalM, cursor: "ns-resize" } })), bottomPanes.length > 1 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: bottomPaneTabList }) })), bottomPanes.length > 0 && (jsxs("div", { ref: paneVerticalResizeElementRef, className: classes.paneContent, style: { height: `clamp(200px,calc(45% + var(${paneHeightAdjustCSSVar}, 0px)), 100% - 300px)`, flex: "0 0 auto" }, children: [bottomSelectedTab?.title ? (jsxs(Fragment, { children: [jsx(Title3, { className: classes.paneHeader, children: bottomSelectedTab.title }), jsx(Divider, { inset: true, className: classes.headerDivider, appearance: "brand" })] })) : null, bottomSelectedTab?.content && jsx(bottomSelectedTab.content, {})] })), toolbarMode === "compact" && bottomBarItems.length > 0 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomBarItems }) }) }))] }) }), jsx("div", { className: `${classes.resizer} ${alignment === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` }, onPointerDown: onResizerPointerDown })] })) }));
1593
- }, [topPanes, topSelectedTab, bottomPanes, bottomSelectedTab, topBarItems, bottomBarItems, collapsed, width, resizing]);
1694
+ return (jsx(Fragment, { children: (topPanes.length > 0 || bottomPanes.length > 0) && (jsxs("div", { className: `${classes.pane} ${alignment === "left" ? classes.paneLeft : classes.paneRight}`, children: [jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: jsxs("div", { className: classes.paneContainer, style: { width: `${width}px` }, children: [toolbarMode === "compact" && (topPanes.length > 1 || topBarItems.length > 0) && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [topPaneTabList, jsx(Toolbar, { location: "top", components: topBarItems })] }) })), jsxs("div", { className: classes.paneContent, children: [jsx(PaneHeader, { title: topSelectedTab?.title }), topSelectedTab?.content && jsx(topSelectedTab.content, {})] }), topPanes.length > 0 && bottomPanes.length > 0 && jsx(Divider, { ref: paneVerticalResizeHandleRef, className: classes.paneDivider }), bottomPanes.length > 1 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: bottomPaneTabList }) })), bottomPanes.length > 0 && (jsxs("div", { ref: paneVerticalResizeElementRef, className: classes.paneContent, style: { height: `clamp(200px,calc(45% + var(${paneHeightAdjustCSSVar}, 0px)), 100% - 300px)`, flex: "0 0 auto" }, children: [jsx(PaneHeader, { title: bottomSelectedTab?.title }), bottomSelectedTab?.content && jsx(bottomSelectedTab.content, {})] })), toolbarMode === "compact" && bottomBarItems.length > 0 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomBarItems }) }) }))] }) }), jsx("div", { className: `${classes.resizer} ${alignment === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` }, onPointerDown: onResizerPointerDown })] })) }));
1695
+ }, [topPanes, topSelectedTab, bottomPanes, bottomSelectedTab, topBarItems, bottomBarItems, topPaneTabList, bottomPaneTabList, collapsed, width, resizing]);
1594
1696
  return [topPaneTabList, pane];
1595
1697
  }
1596
1698
  function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWidth = 350, rightPaneDefaultWidth = 350, rightPaneMinWidth = 350, toolbarMode = "full", sidePaneMode = "both", } = {}) {
@@ -1873,10 +1975,29 @@ const ToggleCommand = (props) => {
1873
1975
  // TODO-iv2: Consolidate icon prop passing approach for inspector and shared components
1874
1976
  return jsx(ToggleButton, { appearance: "transparent", title: displayName, checkedIcon: Icon, value: isEnabled, onChange: (val) => (command.isEnabled = val) });
1875
1977
  };
1978
+ // This "placeholder" command has a blank icon and is a no-op. It is used for aside
1979
+ // alignment when some toggle commands are enabled. See more details on the commands
1980
+ // for setting the aside state.
1981
+ const PlaceHolderCommand = {
1982
+ type: "action",
1983
+ displayName: "",
1984
+ icon: createFluentIcon("Placeholder", "1em", ""),
1985
+ execute: () => {
1986
+ /* No-op */
1987
+ },
1988
+ };
1989
+ function MakeCommandElement(command, isPlaceholder) {
1990
+ if (isPlaceholder) {
1991
+ // Placeholders are not visible and not interacted with, so they are always ActionCommand
1992
+ // components, just to ensure the exact right amount of space is taken up.
1993
+ return jsx(ActionCommand, { command: PlaceHolderCommand }, command.displayName);
1994
+ }
1995
+ return command.type === "action" ? jsx(ActionCommand, { command: command }, command.displayName) : jsx(ToggleCommand, { command: command }, command.displayName);
1996
+ }
1876
1997
  const SceneTreeItem = (props) => {
1877
1998
  const { isSelected, select } = props;
1878
1999
  const classes = useStyles$a();
1879
- return (jsx(FlatTreeItem, { value: "scene", itemType: "leaf", parentValue: undefined, "aria-level": 1, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: jsx(MoviesAndTvRegular, {}), className: classes.sceneTreeItemLayout, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
2000
+ return (jsx(FlatTreeItem, { value: "scene", itemType: "leaf", parentValue: undefined, "aria-level": 1, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: jsx(GlobeRegular, {}), className: classes.sceneTreeItemLayout, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
1880
2001
  };
1881
2002
  const SectionTreeItem = (props) => {
1882
2003
  const { section, isFiltering, expandAll, collapseAll } = props;
@@ -1886,6 +2007,7 @@ const SectionTreeItem = (props) => {
1886
2007
  };
1887
2008
  const EntityTreeItem = (props) => {
1888
2009
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll } = props;
2010
+ const hasChildren = !!entityItem.children?.length;
1889
2011
  const displayInfo = useResource(useCallback(() => {
1890
2012
  const displayInfo = entityItem.getDisplayInfo();
1891
2013
  if (!displayInfo.dispose) {
@@ -1898,25 +2020,63 @@ const EntityTreeItem = (props) => {
1898
2020
  const name = useObservableState(() => displayInfo.name, displayInfo.onChange);
1899
2021
  // Get the commands that apply to this entity.
1900
2022
  const commands = useResource(useCallback(() => {
1901
- const commands = commandProviders
2023
+ const commands = [...commandProviders]
1902
2024
  .filter((provider) => provider.predicate(entityItem.entity))
1903
- .map((provider) => provider.getCommand(entityItem.entity));
2025
+ .map((provider) => {
2026
+ return {
2027
+ order: provider.order,
2028
+ command: provider.getCommand(entityItem.entity),
2029
+ };
2030
+ })
2031
+ .sort((a, b) => {
2032
+ // Action commands always come before toggle commands, because toggle commands will remain
2033
+ // visible when they are enabled, even when the pointer is not hovering the item, and we
2034
+ // don't want a bunch of blank space.
2035
+ if (a.command.type !== b.command.type) {
2036
+ return a.command.type === "action" ? -1 : 1;
2037
+ }
2038
+ // Within each group of command types, sort by order (default 0) ascending.
2039
+ return (a.order ?? 0) - (b.order ?? 0);
2040
+ })
2041
+ .map((entry) => entry.command);
1904
2042
  return Object.assign(commands, {
1905
2043
  dispose: () => commands.forEach((command) => command.dispose?.()),
1906
2044
  });
1907
2045
  }, [entityItem.entity, commandProviders]));
1908
- const [enabledToggleCommands, setEnabledToggleCommands] = useState([]);
1909
- // For enabled/active toggle commands, we should always show them so the user knows this command is toggled on.
2046
+ // TreeItemLayout actions (totally unrelated to "Action" type commands) are only visible when the item is focused or has pointer hover.
2047
+ const actions = useMemo(() => {
2048
+ const defaultCommands = [];
2049
+ if (hasChildren) {
2050
+ defaultCommands.push({
2051
+ type: "action",
2052
+ displayName: "Expand All",
2053
+ icon: () => jsx(ArrowExpandAllRegular, {}),
2054
+ execute: () => expandAll(),
2055
+ });
2056
+ }
2057
+ return [...defaultCommands, ...commands].map((command) => MakeCommandElement(command, false));
2058
+ }, [commands, hasChildren, expandAll]);
2059
+ // TreeItemLayout asides are always visible.
2060
+ const [aside, setAside] = useState([]);
2061
+ // This useEffect keeps the aside up-to-date. What should always show is any enabled toggle command, along with
2062
+ // placeholders to the right to keep the position of the actions consistent.
1910
2063
  useEffect(() => {
1911
- const toggleCommands = commands.filter((command) => command.type === "toggle");
1912
- const updateEnabledToggleCommands = () => {
1913
- setEnabledToggleCommands(toggleCommands.filter((command) => command.isEnabled));
2064
+ const updateAside = () => {
2065
+ let isAnyCommandEnabled = false;
2066
+ const aside = [];
2067
+ for (const command of commands) {
2068
+ isAnyCommandEnabled || (isAnyCommandEnabled = command.type === "toggle" && command.isEnabled);
2069
+ if (isAnyCommandEnabled) {
2070
+ aside.push(MakeCommandElement(command, command.type !== "toggle" || !command.isEnabled));
2071
+ }
2072
+ }
2073
+ setAside(aside);
1914
2074
  };
1915
- updateEnabledToggleCommands();
1916
- const observers = toggleCommands
2075
+ updateAside();
2076
+ const observers = commands
1917
2077
  .map((command) => command.onChange)
1918
2078
  .filter((onChange) => !!onChange)
1919
- .map((onChange) => onChange.add(updateEnabledToggleCommands));
2079
+ .map((onChange) => onChange.add(updateAside));
1920
2080
  return () => {
1921
2081
  for (const observer of observers) {
1922
2082
  observer.remove();
@@ -1925,15 +2085,14 @@ const EntityTreeItem = (props) => {
1925
2085
  }, [commands]);
1926
2086
  return (jsxs(Menu, { openOnContext: true, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: jsx(FlatTreeItem, { value: entityItem.entity.uniqueId,
1927
2087
  // Disable manual expand/collapse when a filter is active.
1928
- itemType: !isFiltering && (entityItem.children?.length ?? 0) > 0 ? "branch" : "leaf", parentValue: entityItem.parent.type === "section" ? entityItem.parent.sectionName : entityItem.entity.uniqueId, "aria-level": entityItem.depth, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: entityItem.icon ? jsx(entityItem.icon, { entity: entityItem.entity }) : null, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined,
1929
- // Actions are only visible when the item is focused or has pointer hover.
1930
- actions: commands.map((command) => command.type === "action" ? (jsx(ActionCommand, { command: command }, command.displayName)) : (jsx(ToggleCommand, { command: command }, command.displayName))),
1931
- // Asides are always visible.
1932
- aside: {
2088
+ itemType: !isFiltering && hasChildren ? "branch" : "leaf", parentValue: entityItem.parent.type === "section" ? entityItem.parent.sectionName : entityItem.entity.uniqueId, "aria-level": entityItem.depth, "aria-setsize": 1, "aria-posinset": 1, onClick: select, children: jsx(TreeItemLayout, { iconBefore: entityItem.icon ? jsx(entityItem.icon, { entity: entityItem.entity }) : null, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, actions: actions, aside: {
1933
2089
  // Match the gap and padding of the actions.
1934
- style: { gap: 0, paddingRight: tokens.spacingHorizontalS },
1935
- children: enabledToggleCommands.map((command) => jsx(ToggleCommand, { command: command }, command.displayName)),
1936
- }, children: jsx(Body1, { wrap: false, truncate: true, children: name.substring(0, 100) }) }) }, entityItem.entity.uniqueId) }), jsx(MenuPopover, { hidden: !entityItem.children?.length, children: jsxs(MenuList, { children: [jsx(MenuItem, { onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] }) })] }));
2090
+ style: { gap: 0, paddingLeft: tokens.spacingHorizontalS, paddingRight: tokens.spacingHorizontalS },
2091
+ children: aside,
2092
+ }, main: {
2093
+ // Prevent the "main" content (the Body1 below) from growing too large and pushing the actions/aside out of view.
2094
+ style: { flex: "1 1 0", overflow: "hidden", textOverflow: "ellipsis" },
2095
+ }, children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }, entityItem.entity.uniqueId) }), jsx(MenuPopover, { hidden: !hasChildren, children: jsxs(MenuList, { children: [jsx(MenuItem, { onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] }) })] }));
1937
2096
  };
1938
2097
  const SceneExplorer = (props) => {
1939
2098
  const classes = useStyles$a();
@@ -2859,19 +3018,19 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
2859
3018
  name: "Export Tools",
2860
3019
  description: "Adds new features to enable exporting Babylon assets such as .gltf, .glb, .babylon, and more.",
2861
3020
  keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
2862
- getExtensionModuleAsync: async () => await import('./exportService-D-dQNOY7.js'),
3021
+ getExtensionModuleAsync: async () => await import('./exportService-Cu9CXW_A.js'),
2863
3022
  },
2864
3023
  {
2865
3024
  name: "Capture Tools",
2866
3025
  description: "Adds new features to enable capturing screenshots, GIFs, videos, and more.",
2867
3026
  keywords: ["capture", "screenshot", "gif", "video", "tools"],
2868
- getExtensionModuleAsync: async () => await import('./captureService-BApVw0DN.js'),
3027
+ getExtensionModuleAsync: async () => await import('./captureService-CuhjVz1y.js'),
2869
3028
  },
2870
3029
  {
2871
3030
  name: "Import Tools",
2872
3031
  description: "Adds new features related to importing Babylon assets.",
2873
3032
  keywords: ["import", "tools"],
2874
- getExtensionModuleAsync: async () => await import('./importService-Bnx5hsFQ.js'),
3033
+ getExtensionModuleAsync: async () => await import('./importService-BETG4FuQ.js'),
2875
3034
  },
2876
3035
  ]);
2877
3036
 
@@ -3147,27 +3306,6 @@ class ExtensionManager {
3147
3306
  }
3148
3307
  }
3149
3308
 
3150
- const ThemeModeStorageKey = `Babylon/Settings/ThemeMode`;
3151
- /**
3152
- * Custom hook to manage the theme mode (light/dark/auto).
3153
- * @param modeOverride If specified, any previously stored theme mode will be replaced with this mode.
3154
- * @returns An object containing the theme mode state and helper functions.
3155
- */
3156
- function useThemeMode(modeOverride) {
3157
- const { isDarkMode, ternaryDarkMode, setTernaryDarkMode } = useTernaryDarkMode({
3158
- defaultValue: modeOverride,
3159
- initializeWithValue: !modeOverride,
3160
- localStorageKey: ThemeModeStorageKey,
3161
- });
3162
- // If a modeOverride is provided, replace any previously stored mode.
3163
- // Also make sure there is a stored value initially, even before changing the theme.
3164
- // This way, other usages of this hook will get the correct initial value.
3165
- if (modeOverride || !localStorage.getItem(ThemeModeStorageKey)) {
3166
- localStorage.setItem(ThemeModeStorageKey, JSON.stringify(ternaryDarkMode));
3167
- }
3168
- return { isDarkMode, themeMode: ternaryDarkMode, setThemeMode: setTernaryDarkMode };
3169
- }
3170
-
3171
3309
  // This sorts a set of service definitions based on their dependencies (e.g. a topological sort).
3172
3310
  function SortServiceDefinitions(serviceDefinitions) {
3173
3311
  const sortedServiceDefinitions = [];
@@ -3458,7 +3596,7 @@ const ThemeSelectorServiceDefinition = {
3458
3596
  const toggleTheme = useCallback(() => {
3459
3597
  setThemeMode(isDarkMode ? "light" : "dark");
3460
3598
  }, [isDarkMode]);
3461
- return (jsxs(Menu, { positioning: "below-end", checkedValues: { theme: [themeMode] }, onCheckedValueChange: onSelectedThemeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Select Theme", relationship: "label", children: jsx(SplitButton, { className: classes.themeButton, menuButton: triggerProps, primaryActionButton: { onClick: toggleTheme }, size: "small", appearance: "secondary", shape: "circular", icon: isDarkMode ? jsx(WeatherSunnyRegular, {}) : jsx(WeatherMoonRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.themeMenuPopover, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "theme", value: "system", children: "System" }), jsx(MenuItemRadio, { name: "theme", value: "light", children: "Light" }), jsx(MenuItemRadio, { name: "theme", value: "dark", children: "Dark" })] }) })] }));
3599
+ return (jsxs(Menu, { positioning: "below-end", checkedValues: { theme: [themeMode] }, onCheckedValueChange: onSelectedThemeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Select Theme", relationship: "label", children: jsx(SplitButton, { className: classes.themeButton, menuButton: triggerProps, primaryActionButton: { onClick: toggleTheme }, size: "small", appearance: "transparent", shape: "circular", icon: isDarkMode ? jsx(WeatherSunnyRegular, {}) : jsx(WeatherMoonRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.themeMenuPopover, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "theme", value: "system", children: "System" }), jsx(MenuItemRadio, { name: "theme", value: "light", children: "Light" }), jsx(MenuItemRadio, { name: "theme", value: "dark", children: "Dark" })] }) })] }));
3462
3600
  },
3463
3601
  });
3464
3602
  return {
@@ -3467,34 +3605,6 @@ const ThemeSelectorServiceDefinition = {
3467
3605
  },
3468
3606
  };
3469
3607
 
3470
- /* eslint-disable @typescript-eslint/naming-convention */
3471
- // Generated from https://react.fluentui.dev/?path=/docs/theme-theme-designer--docs
3472
- // Key color: #3A94FC
3473
- const babylonRamp = {
3474
- 10: "#020305",
3475
- 20: "#121721",
3476
- 30: "#1A263A",
3477
- 40: "#1F314F",
3478
- 50: "#243E64",
3479
- 60: "#294B7B",
3480
- 70: "#2D5892",
3481
- 80: "#3166AA",
3482
- 90: "#3473C3",
3483
- 100: "#3782DC",
3484
- 110: "#3990F6",
3485
- 120: "#5A9EFD",
3486
- 130: "#7BACFE",
3487
- 140: "#96BAFF",
3488
- 150: "#AFC9FF",
3489
- 160: "#C6D8FF",
3490
- };
3491
- const LightTheme = {
3492
- ...createLightTheme(babylonRamp),
3493
- };
3494
- const DarkTheme = {
3495
- ...createDarkTheme(babylonRamp),
3496
- };
3497
-
3498
3608
  // eslint-disable-next-line @typescript-eslint/naming-convention
3499
3609
  const useStyles$6 = makeStyles({
3500
3610
  app: {
@@ -3528,10 +3638,12 @@ const useStyles$6 = makeStyles({
3528
3638
  */
3529
3639
  function MakeModularTool(options) {
3530
3640
  const { containerElement, serviceDefinitions, themeMode, showThemeSelector = true, extensionFeeds = [] } = options;
3641
+ if (themeMode) {
3642
+ SetThemeMode(themeMode);
3643
+ }
3531
3644
  const modularToolRootComponent = () => {
3532
3645
  const classes = useStyles$6();
3533
3646
  const [extensionManagerContext, setExtensionManagerContext] = useState();
3534
- const { isDarkMode } = useThemeMode(themeMode);
3535
3647
  const [requiredExtensions, setRequiredExtensions] = useState();
3536
3648
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
3537
3649
  const [extensionInstallError, setExtensionInstallError] = useState();
@@ -3628,7 +3740,7 @@ function MakeModularTool(options) {
3628
3740
  // Show a spinner until a main view has been set.
3629
3741
  // eslint-disable-next-line @typescript-eslint/naming-convention
3630
3742
  const Content = rootComponent ?? (() => jsx(Spinner, { className: classes.spinner }));
3631
- return (jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(FluentProvider, { className: classes.app, theme: isDarkMode ? DarkTheme : LightTheme, children: jsxs(Fragment, { 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, {}) })] }) }) }));
3743
+ return (jsx(ExtensionManagerContext.Provider, { value: extensionManagerContext, children: jsx(Theme, { className: classes.app, children: jsxs(Fragment, { 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, {}) })] }) }) }));
3632
3744
  };
3633
3745
  // Set the container element to be a flex container so that the tool can be displayed properly.
3634
3746
  const originalContainerElementDisplay = containerElement.style.display;
@@ -3729,6 +3841,10 @@ const GizmoServiceDefinition = {
3729
3841
  },
3730
3842
  };
3731
3843
 
3844
+ const MeshIcon = createFluentIcon("Mesh", "16", '<path d="M14.03,3.54l-5.11-2.07c-.61-.25-1.27-.25-1.88,0L1.93,3.54c-.57.23-.94.78-.94,1.39v6.15c0,.61.37,1.16.94,1.39l5.11,2.07c.3.12.62.18.94.18s.64-.06.94-.18l5.12-2.07c.57-.23.94-.78.94-1.39v-6.15c0-.61-.37-1.16-.94-1.39ZM13.97,7.71l-2.11.86v-2.71l2.11-.86v2.71ZM1.99,5l2.11.86v2.71l-2.11-.86v-2.71ZM11.35,4.98l-2.04-.83,1.78-.72,2.04.83-1.78.72ZM10.02,5.52l-2.04.83-2.04-.83,2.04-.83,2.04.83ZM4.6,4.98l-1.78-.72,2.04-.83,1.78.72-2.04.83ZM5.1,6.26l2.38.96v2.71l-2.38-.96v-2.71ZM8.48,7.22l2.38-.96v2.71l-2.38.96v-2.71ZM7.41,2.39c.18-.07.37-.11.56-.11s.38.04.56.11l1.22.49-1.78.72-1.79-.72,1.22-.49ZM1.99,11.07v-2.29l2.11.86v2.62l-1.8-.73c-.19-.08-.31-.26-.31-.46ZM5.1,12.67v-2.62l2.38.96v2.61s-.04,0-.06-.01l-2.31-.94ZM8.54,13.61s-.04,0-.06.01v-2.61l2.38-.96v2.62l-2.31.94ZM13.66,11.54l-1.8.73v-2.62l2.11-.86v2.29c0,.2-.12.39-.31.46Z"/>');
3845
+ const TranslateIcon = createFluentIcon("Translate", "24", '<path d="M20.16,12.98l-2.75-2.75c-.29-.29-.77-.29-1.06,0-.29.29-.29.77,0,1.06l1.47,1.47h-6.69v-6.69l1.47,1.47c.29.29.77.29,1.06,0,.29-.29.29-.77,0-1.06l-2.75-2.75c-.14-.14-.33-.22-.53-.22s-.39.08-.53.22l-2.75,2.75c-.29.29-.29.77,0,1.06.29.29.77.29,1.06,0l1.47-1.47v7.13l-3.52,3.52v-2.08c0-.41-.34-.75-.75-.75s-.75.34-.75.75v3.89c0,.2.08.39.22.53.14.14.33.22.53.22h3.89c.41,0,.75-.34.75-.75s-.34-.75-.75-.75h-2.08s3.52-3.52,3.52-3.52h7.13l-1.47,1.47c-.29.29-.29.77,0,1.06s.77.29,1.06,0l2.75-2.75c.14-.14.22-.33.22-.53s-.08-.39-.22-.53Z" />');
3846
+ const MaterialIcon = createFluentIcon("Material", "16", '<path d="M14.74,6.3c-.09-.36-.38-.64-.75-.72-.04-.09-.08-.18-.12-.27.1-.15.16-.32.16-.51,0-.18-.05-.34-.13-.48-1.23-1.97-3.41-3.28-5.9-3.28C4.16,1.04,1.04,4.16,1.04,7.99c0,.39.23.72.57.88.02.12.03.25.06.37-.18.18-.3.42-.3.7,0,.11.02.21.06.31.94,2.74,3.53,4.71,6.58,4.71,3.84,0,6.96-3.12,6.96-6.96,0-.59-.08-1.16-.22-1.7ZM2.07,8.58c-.02-.19-.03-.39-.03-.58,0-3.29,2.67-5.96,5.96-5.96,2.23,0,4.17,1.23,5.2,3.05.05.18-.07.45-.3.75-.57-.73-1.45-1.21-2.45-1.21-1.72,0-3.12,1.4-3.12,3.11,0,.33.07.65.16.95-3.05.82-5.17.52-5.42-.11ZM12.56,7.75c0,1.17-.95,2.11-2.11,2.11s-2.12-.95-2.12-2.11.95-2.11,2.12-2.11,2.11.95,2.11,2.11ZM8,13.96c-2.6,0-4.81-1.68-5.62-4.01.5.16,1.11.24,1.79.24,1.15,0,2.49-.22,3.79-.59.57.76,1.47,1.26,2.49,1.26,1.72,0,3.11-1.4,3.11-3.11,0-.34-.07-.65-.17-.96.13-.13.24-.26.34-.39.14.51.22,1.04.22,1.6,0,3.29-2.67,5.96-5.96,5.96Z"/>');
3847
+
3732
3848
  const useStyles$5 = makeStyles({
3733
3849
  coordinatesModeButton: {
3734
3850
  margin: `0 ${tokens.spacingVerticalXS}`,
@@ -3831,9 +3947,9 @@ const GizmoToolbar = (props) => {
3831
3947
  const toggleCoordinatesMode = useCallback(() => {
3832
3948
  gizmoManager.coordinatesMode = coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? 0 /* GizmoCoordinatesMode.World */ : 1 /* GizmoCoordinatesMode.Local */;
3833
3949
  }, [gizmoManager, coordinatesMode]);
3834
- return (jsxs(Fragment, { children: [jsx(ToggleButton, { title: "Translate", checkedIcon: ArrowMoveRegular, value: gizmoMode === "translate", onChange: () => updateGizmoMode("translate") }), jsx(ToggleButton, { title: "Rotate", checkedIcon: ArrowRotateClockwiseRegular, value: gizmoMode === "rotate", onChange: () => updateGizmoMode("rotate") }), jsx(ToggleButton, { title: "Scale", checkedIcon: ArrowExpandRegular, value: gizmoMode === "scale", onChange: () => updateGizmoMode("scale") }), jsx(ToggleButton, { title: "Bounding Box", checkedIcon: SelectObjectRegular, value: gizmoMode === "boundingBox", onChange: () => updateGizmoMode("boundingBox") }), jsx(Collapse, { visible: !!gizmoMode, orientation: "horizontal", children: jsxs(Menu, { positioning: "below-end", checkedValues: { coordinatesMode: [coordinatesMode.toString()] }, onCheckedValueChange: onCoordinatesModeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Coordinates Mode", relationship: "label", children: jsx(SplitButton, { className: classes.coordinatesModeButton, menuButton: triggerProps, primaryActionButton: {
3950
+ return (jsxs(Fragment, { children: [jsx(ToggleButton, { title: "Translate", checkedIcon: TranslateIcon, value: gizmoMode === "translate", onChange: () => updateGizmoMode("translate") }), jsx(ToggleButton, { title: "Rotate", checkedIcon: ArrowRotateClockwiseRegular, value: gizmoMode === "rotate", onChange: () => updateGizmoMode("rotate") }), jsx(ToggleButton, { title: "Scale", checkedIcon: ArrowExpandRegular, value: gizmoMode === "scale", onChange: () => updateGizmoMode("scale") }), jsx(ToggleButton, { title: "Bounding Box", checkedIcon: SelectObjectRegular, value: gizmoMode === "boundingBox", onChange: () => updateGizmoMode("boundingBox") }), jsx(Collapse, { visible: !!gizmoMode, orientation: "horizontal", children: jsxs(Menu, { positioning: "below-end", checkedValues: { coordinatesMode: [coordinatesMode.toString()] }, onCheckedValueChange: onCoordinatesModeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Coordinates Mode", relationship: "label", children: jsx(SplitButton, { className: classes.coordinatesModeButton, menuButton: triggerProps, primaryActionButton: {
3835
3951
  onClick: toggleCoordinatesMode,
3836
- }, size: "small", appearance: "secondary", shape: "rounded", icon: coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? jsx(CubeRegular, {}) : jsx(GlobeRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.coordinatesModeMenu, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "coordinatesMode", value: 1 /* GizmoCoordinatesMode.Local */.toString(), children: "Local" }), jsx(MenuItemRadio, { name: "coordinatesMode", value: 0 /* GizmoCoordinatesMode.World */.toString(), children: "World" })] }) })] }) })] }));
3952
+ }, size: "small", appearance: "transparent", shape: "rounded", icon: coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? jsx(CubeRegular, {}) : jsx(GlobeRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.coordinatesModeMenu, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "coordinatesMode", value: 1 /* GizmoCoordinatesMode.Local */.toString(), children: "Local" }), jsx(MenuItemRadio, { name: "coordinatesMode", value: 0 /* GizmoCoordinatesMode.World */.toString(), children: "World" })] }) })] }) })] }));
3837
3953
  };
3838
3954
 
3839
3955
  const GizmoToolbarServiceDefinition = {
@@ -3903,7 +4019,7 @@ const TextInput = (props) => {
3903
4019
  };
3904
4020
  const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
3905
4021
  const id = useId("input-button");
3906
- return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Input, { ...props, id: id, size: "medium", value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
4022
+ return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Input, { ...props, input: { className: classes.inputSlot }, id: id, size: "medium", value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
3907
4023
  };
3908
4024
 
3909
4025
  const SpinButton = (props) => {
@@ -3951,7 +4067,7 @@ const SpinButton = (props) => {
3951
4067
  };
3952
4068
  const id = useId("spin-button");
3953
4069
  const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
3954
- return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(SpinButton$1, { ...props, step: step, id: id, size: "medium", precision: CalculatePrecision(step ?? 1.01), displayValue: props.unit ? `${PrecisionRound(value, CalculatePrecision(step ?? 1.01))} ${props.unit}` : undefined, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
4070
+ return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(SpinButton$1, { ...props, input: { className: classes.inputSlot }, step: step, id: id, size: "medium", precision: CalculatePrecision(step ?? 1.01), displayValue: props.unit ? `${PrecisionRound(value, CalculatePrecision(step ?? 1.01))} ${props.unit}` : undefined, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
3955
4071
  };
3956
4072
  /**
3957
4073
  * Fluent's CalculatePrecision function
@@ -4326,6 +4442,7 @@ const useDropdownStyles = makeStyles({
4326
4442
  justifyContent: "center", // align items vertically
4327
4443
  gap: "4px",
4328
4444
  },
4445
+ button: { textAlign: "end" },
4329
4446
  });
4330
4447
  /**
4331
4448
  * Renders a fluent UI dropdown component for the options passed in, and an additional 'Not Defined' option if null is set to true
@@ -4343,7 +4460,7 @@ const Dropdown = (props) => {
4343
4460
  }, [props.value]);
4344
4461
  const id = useId("dropdown");
4345
4462
  const mergedClassName = mergeClasses(classes.dropdown, props.className);
4346
- return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Dropdown$1, { id: id, disabled: props.disabled, size: "medium", className: mergedClassName, onOptionSelect: (evt, data) => {
4463
+ return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Dropdown$1, { id: id, disabled: props.disabled, size: "medium", className: mergedClassName, button: { className: classes.button }, onOptionSelect: (evt, data) => {
4347
4464
  const value = typeof props.value === "number" ? Number(data.optionValue) : data.optionValue;
4348
4465
  if (value !== undefined) {
4349
4466
  setDefaultVal(value);
@@ -5577,6 +5694,11 @@ function SaveMetadata(entity, metadata) {
5577
5694
  }
5578
5695
  }
5579
5696
  const useStyles$4 = makeStyles({
5697
+ mainDiv: {
5698
+ display: "flex",
5699
+ flexDirection: "column",
5700
+ gap: tokens.spacingVerticalXS,
5701
+ },
5580
5702
  buttonDiv: {
5581
5703
  display: "grid",
5582
5704
  gridAutoFlow: "column",
@@ -5601,14 +5723,14 @@ const MetadataProperties = (props) => {
5601
5723
  const [editedMetadata, setEditedMetadata] = useState(stringifiedMetadata);
5602
5724
  const isEditedMetadataJSON = useMemo(() => IsParsable(editedMetadata), [editedMetadata]);
5603
5725
  const unformattedEditedMetadata = useMemo(() => Restringify(editedMetadata, false), [editedMetadata]);
5604
- return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Property Type", value: metadataType }), jsx(Collapse, { visible: canPreventObjectCorruption, children: jsx(SwitchPropertyLine, { label: "Prevent Object Corruption", value: isReadonly, onChange: setPreventObjectCorruption }) }), jsx(Textarea, { disabled: isReadonly, value: editedMetadata, onChange: setEditedMetadata }), jsx(ButtonLine, { label: "Populate glTF extras", disabled: !IsParsable(editedMetadata) || HasGltfExtras(editedMetadata), onClick: () => {
5605
- const isFormatted = Restringify(editedMetadata, true) === editedMetadata;
5606
- let withGLTFExtras = PopulateGLTFExtras(editedMetadata);
5607
- if (isFormatted) {
5608
- withGLTFExtras = Restringify(withGLTFExtras, true);
5609
- }
5610
- setEditedMetadata(withGLTFExtras);
5611
- } }), jsx(LineContainer, { children: jsxs("div", { className: classes.buttonDiv, children: [jsx(Button$1, { icon: jsx(SaveRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => SaveMetadata(entity, editedMetadata), children: jsx(Body1, { children: "Save" }) }), jsx(Tooltip, { content: "Undo Changes", relationship: "label", children: jsx(Button$1, { icon: jsx(ArrowUndoRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => setEditedMetadata(stringifiedMetadata) }) }), jsx(Tooltip, { content: "Format (Pretty Print)", relationship: "label", children: jsx(Button$1, { icon: jsx("svg", { ...props, viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", children: jsx("text", { x: "3", y: "14", fontSize: "14", fill: "currentColor", children: "{ }" }) }), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, true)) }) }), jsx(Tooltip, { content: "Clear Formatting", relationship: "label", children: jsx(Button$1, { icon: jsx(ClearFormattingRegular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, false)) }) })] }) })] }));
5726
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Property Type", value: metadataType }), jsx(Collapse, { visible: canPreventObjectCorruption, children: jsx(SwitchPropertyLine, { label: "Prevent Object Corruption", value: isReadonly, onChange: setPreventObjectCorruption }) }), jsxs("div", { className: classes.mainDiv, children: [jsx(Textarea, { disabled: isReadonly, value: editedMetadata, onChange: setEditedMetadata }), jsx(ButtonLine, { label: "Populate glTF extras", disabled: !!editedMetadata && (!IsParsable(editedMetadata) || HasGltfExtras(editedMetadata)), onClick: () => {
5727
+ const isFormatted = Restringify(editedMetadata, true) === editedMetadata;
5728
+ let withGLTFExtras = PopulateGLTFExtras(editedMetadata);
5729
+ if (isFormatted) {
5730
+ withGLTFExtras = Restringify(withGLTFExtras, true);
5731
+ }
5732
+ setEditedMetadata(withGLTFExtras);
5733
+ } }), jsx(LineContainer, { children: jsxs("div", { className: classes.buttonDiv, children: [jsx(Button$1, { icon: jsx(SaveRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => SaveMetadata(entity, editedMetadata), children: jsx(Body1, { children: "Save" }) }), jsx(Tooltip, { content: "Undo Changes", relationship: "label", children: jsx(Button$1, { icon: jsx(ArrowUndoRegular, {}), disabled: stringifiedMetadata === unformattedEditedMetadata, onClick: () => setEditedMetadata(stringifiedMetadata) }) }), jsx(Tooltip, { content: "Format (Pretty Print)", relationship: "label", children: jsx(Button$1, { icon: jsx(BracesRegular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, true)) }) }), jsx(Tooltip, { content: "Clear Formatting (Undo Pretty Print)", relationship: "label", children: jsx(Button$1, { icon: jsx(BracesDismiss16Regular, {}), disabled: !isEditedMetadataJSON, onClick: () => setEditedMetadata(Restringify(editedMetadata, false)) }) })] }) })] })] }));
5612
5734
  };
5613
5735
 
5614
5736
  function IsMetadataContainer(entity) {
@@ -7476,6 +7598,7 @@ const AnimationGroupExplorerServiceDefinition = {
7476
7598
  });
7477
7599
  const animationPlayPauseCommandRegistration = sceneExplorerService.addCommand({
7478
7600
  predicate: (entity) => entity instanceof AnimationGroup,
7601
+ order: 900 /* DefaultCommandsOrder.AnimationGroupPlay */,
7479
7602
  getCommand: (animationGroup) => {
7480
7603
  const onChangeObservable = new Observable();
7481
7604
  const playObserver = animationGroup.onAnimationGroupPlayObservable.add(() => onChangeObservable.notifyObservers());
@@ -7638,6 +7761,7 @@ const FrameGraphExplorerServiceDefinition = {
7638
7761
  });
7639
7762
  const activeFrameGraphCommandRegistration = sceneExplorerService.addCommand({
7640
7763
  predicate: (entity) => entity instanceof FrameGraph,
7764
+ order: 900 /* DefaultCommandsOrder.FrameGraphPlay */,
7641
7765
  getCommand: (frameGraph) => {
7642
7766
  const onChangeObservable = new Observable();
7643
7767
  const frameGraphHook = InterceptProperty(scene, "frameGraph", {
@@ -7748,7 +7872,7 @@ const MaterialExplorerServiceDefinition = {
7748
7872
  },
7749
7873
  };
7750
7874
  },
7751
- entityIcon: () => jsx(PaintBrushRegular, {}),
7875
+ entityIcon: () => jsx(MaterialIcon, {}),
7752
7876
  getEntityAddedObservables: () => [scene.onNewMaterialAddedObservable],
7753
7877
  getEntityRemovedObservables: () => [scene.onMaterialRemovedObservable],
7754
7878
  });
@@ -7796,7 +7920,7 @@ const NodeExplorerServiceDefinition = {
7796
7920
  },
7797
7921
  };
7798
7922
  },
7799
- entityIcon: ({ entity: node }) => node instanceof AbstractMesh ? (jsx(BoxRegular, {})) : node instanceof TransformNode ? (jsx(BranchRegular, {})) : node instanceof Camera ? (jsx(CameraRegular, {})) : node instanceof Light ? (jsx(LightbulbRegular, {})) : (jsx(Fragment, {})),
7923
+ entityIcon: ({ entity: node }) => node instanceof AbstractMesh ? (jsx(MeshIcon, {})) : node instanceof TransformNode ? (jsx(MyLocationRegular, {})) : node instanceof Camera ? (jsx(CameraRegular, {})) : node instanceof Light ? (jsx(LightbulbRegular, {})) : (jsx(Fragment, {})),
7800
7924
  getEntityAddedObservables: () => [
7801
7925
  scene.onNewMeshAddedObservable,
7802
7926
  scene.onNewTransformNodeAddedObservable,
@@ -7811,8 +7935,37 @@ const NodeExplorerServiceDefinition = {
7811
7935
  ],
7812
7936
  getEntityMovedObservables: () => [nodeMovedObservable],
7813
7937
  });
7938
+ const abstractMeshBoundingBoxCommandRegistration = sceneExplorerService.addCommand({
7939
+ predicate: (entity) => entity instanceof AbstractMesh && entity.getTotalVertices() > 0,
7940
+ order: 1000 /* DefaultCommandsOrder.MeshBoundingBox */,
7941
+ getCommand: (mesh) => {
7942
+ const onChangeObservable = new Observable();
7943
+ const showBoundingBoxHook = InterceptProperty(mesh, "showBoundingBox", {
7944
+ afterSet: () => onChangeObservable.notifyObservers(),
7945
+ });
7946
+ return {
7947
+ type: "toggle",
7948
+ get displayName() {
7949
+ return `${mesh.showBoundingBox ? "Hide" : "Show"} Bounding Box`;
7950
+ },
7951
+ icon: () => (mesh.showBoundingBox ? jsx(BorderOutsideRegular, {}) : jsx(BorderNoneRegular, {})),
7952
+ get isEnabled() {
7953
+ return mesh.showBoundingBox;
7954
+ },
7955
+ set isEnabled(enabled) {
7956
+ mesh.showBoundingBox = enabled;
7957
+ },
7958
+ onChange: onChangeObservable,
7959
+ dispose: () => {
7960
+ showBoundingBoxHook.dispose();
7961
+ onChangeObservable.clear();
7962
+ },
7963
+ };
7964
+ },
7965
+ });
7814
7966
  const abstractMeshVisibilityCommandRegistration = sceneExplorerService.addCommand({
7815
7967
  predicate: (entity) => entity instanceof AbstractMesh && entity.getTotalVertices() > 0,
7968
+ order: 1100 /* DefaultCommandsOrder.MeshVisibility */,
7816
7969
  getCommand: (mesh) => {
7817
7970
  const onChangeObservable = new Observable();
7818
7971
  const isVisibleHook = InterceptProperty(mesh, "isVisible", {
@@ -7840,6 +7993,7 @@ const NodeExplorerServiceDefinition = {
7840
7993
  });
7841
7994
  const activeCameraCommandRegistration = sceneExplorerService.addCommand({
7842
7995
  predicate: (entity) => entity instanceof Camera,
7996
+ order: 700 /* DefaultCommandsOrder.CameraActive */,
7843
7997
  getCommand: (camera) => {
7844
7998
  const scene = camera.getScene();
7845
7999
  const onChangeObservable = new Observable();
@@ -7871,6 +8025,7 @@ const NodeExplorerServiceDefinition = {
7871
8025
  function addGizmoCommand(nodeClass, getGizmoRef) {
7872
8026
  return sceneExplorerService.addCommand({
7873
8027
  predicate: (entity) => entity instanceof nodeClass,
8028
+ order: 800 /* DefaultCommandsOrder.GizmoActive */,
7874
8029
  getCommand: (node) => {
7875
8030
  const onChangeObservable = new Observable();
7876
8031
  let gizmoRef = null;
@@ -7879,7 +8034,7 @@ const NodeExplorerServiceDefinition = {
7879
8034
  get displayName() {
7880
8035
  return `Turn ${gizmoRef ? "Off" : "On"} Gizmo`;
7881
8036
  },
7882
- icon: () => (gizmoRef ? jsx(Cone16Filled, {}) : jsx(Cone16Regular, {})),
8037
+ icon: () => (gizmoRef ? jsx(EyeRegular, {}) : jsx(EyeOffRegular, {})),
7883
8038
  get isEnabled() {
7884
8039
  return !!gizmoRef;
7885
8040
  },
@@ -7909,6 +8064,7 @@ const NodeExplorerServiceDefinition = {
7909
8064
  const cameraGizmoCommandRegistration = addGizmoCommand(Camera, gizmoService.getCameraGizmo.bind(gizmoService));
7910
8065
  const lightEnabledCommandRegistration = sceneExplorerService.addCommand({
7911
8066
  predicate: (entity) => entity instanceof Light,
8067
+ order: 700 /* DefaultCommandsOrder.LightActive */,
7912
8068
  getCommand: (light) => {
7913
8069
  return {
7914
8070
  type: "toggle",
@@ -7930,6 +8086,7 @@ const NodeExplorerServiceDefinition = {
7930
8086
  return {
7931
8087
  dispose: () => {
7932
8088
  sectionRegistration.dispose();
8089
+ abstractMeshBoundingBoxCommandRegistration.dispose();
7933
8090
  abstractMeshVisibilityCommandRegistration.dispose();
7934
8091
  activeCameraCommandRegistration.dispose();
7935
8092
  cameraGizmoCommandRegistration.dispose();
@@ -8142,6 +8299,7 @@ const SpriteManagerExplorerServiceDefinition = {
8142
8299
  });
8143
8300
  const spritePlayStopCommandRegistration = sceneExplorerService.addCommand({
8144
8301
  predicate: (entity) => entity instanceof Sprite,
8302
+ order: 600 /* DefaultCommandsOrder.SpritePlay */,
8145
8303
  getCommand: (sprite) => {
8146
8304
  const onChangeObservable = new Observable();
8147
8305
  const playHook = InterceptFunction(sprite, "playAnimation", {