@babylonjs/inspector 8.32.3-preview → 8.33.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, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Body1, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, 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, treeItemLevelToken, Switch as Switch$1, PresenceBadge, Spinner, Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle, TabList, Tab, DialogContent, SplitButton, MenuItemRadio, DialogActions, List as List$1, ListItem, Badge, useId, Input, SpinButton as SpinButton$1, Slider, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, 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';
6
+ import { makeStyles, ToggleButton as ToggleButton$1, Button as Button$1, tokens, Link, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Body1, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createLightTheme, createDarkTheme, FluentProvider, Portal, Toolbar as Toolbar$1, Tooltip, ToolbarRadioButton, Menu, MenuTrigger, MenuPopover, MenuList, MenuGroup, MenuGroupHeader, MenuItem, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, treeItemLevelToken, Switch as Switch$1, PresenceBadge, Spinner, Dialog, DialogTrigger, DialogSurface, DialogBody, DialogTitle, TabList, Tab, DialogContent, SplitButton, MenuItemRadio, DialogActions, List as List$1, ListItem, Badge, useId, Input, SpinButton as SpinButton$1, Slider, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, 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 { ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, Copy20Regular, 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, CopyRegular, 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, PersonFeedbackRegular, DeleteFilled } from '@fluentui/react-icons';
8
+ import { ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, Copy20Regular, PanelLeftExpandRegular, PanelLeftContractRegular, PanelRightExpandRegular, PanelRightContractRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, DocumentTextRegular, createFluentIcon, FilterRegular, GlobeRegular, ArrowExpandAllRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, AppsAddInRegular, DismissRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, 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, PersonFeedbackRegular, 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';
@@ -68,6 +68,7 @@ import { MultiMaterial } from '@babylonjs/core/Materials/multiMaterial.js';
68
68
  import { PBRBaseMaterial } from '@babylonjs/core/Materials/PBR/pbrBaseMaterial.js';
69
69
  import { PBRBaseSimpleMaterial } from '@babylonjs/core/Materials/PBR/pbrBaseSimpleMaterial.js';
70
70
  import { PBRMaterial } from '@babylonjs/core/Materials/PBR/pbrMaterial.js';
71
+ import { OpenPBRMaterial } from '@babylonjs/core/Materials/PBR/openpbrMaterial.js';
71
72
  import { SkyMaterial } from '@babylonjs/materials/sky/skyMaterial.js';
72
73
  import { Constants } from '@babylonjs/core/Engines/constants.js';
73
74
  import { Engine } from '@babylonjs/core/Engines/engine.js';
@@ -943,6 +944,7 @@ const Accordion = (props) => {
943
944
  };
944
945
 
945
946
  const CompactModeStorageKey = "Babylon/Settings/IsCompactMode";
947
+ const SidePaneDockOverridesStorageKey = "Babylon/Settings/SidePaneDockOverrides";
946
948
  function useSetting(storageKey, defaultValue) {
947
949
  const [value, setValue, resetValue] = useLocalStorage(storageKey, defaultValue);
948
950
  if (!localStorage.getItem(storageKey)) {
@@ -957,6 +959,13 @@ function useSetting(storageKey, defaultValue) {
957
959
  function useCompactMode() {
958
960
  return useSetting(CompactModeStorageKey, !matchMedia("(pointer: coarse)").matches);
959
961
  }
962
+ /**
963
+ * Gets the side pane dock overrides configuration.
964
+ * @returns A record mapping side pane IDs to their dock locations.
965
+ */
966
+ function useSidePaneDockOverrides() {
967
+ return useSetting(SidePaneDockOverridesStorageKey, {});
968
+ }
960
969
  const RadiansToDegrees = 180 / Math.PI;
961
970
  function WrapAngle(angle) {
962
971
  angle %= Math.PI * 2;
@@ -1470,15 +1479,22 @@ const Theme = (props) => {
1470
1479
  */
1471
1480
  function useResizeHandle(params) {
1472
1481
  const { growDirection, variableName, onChange } = params;
1473
- // const [value, setValue] = useState(0);
1482
+ const valueRef = useRef(0);
1474
1483
  const [elementRef, setElementRef] = useState(null);
1475
1484
  const [handleRef, setHandleRef] = useState(null);
1476
- const setValue = useCallback((value) => {
1485
+ const updateElementStyle = useCallback((value) => {
1477
1486
  if (elementRef) {
1478
1487
  elementRef.style.setProperty(variableName, `${value}px`);
1479
1488
  }
1489
+ }, [elementRef, variableName]);
1490
+ const setValue = useCallback((value) => {
1491
+ valueRef.current = value;
1492
+ updateElementStyle(value);
1480
1493
  onChange?.(value);
1481
- }, [elementRef, variableName, onChange]);
1494
+ }, [updateElementStyle, onChange]);
1495
+ useEffect(() => {
1496
+ updateElementStyle(valueRef.current);
1497
+ }, [updateElementStyle]);
1482
1498
  useEffect(() => {
1483
1499
  if (handleRef) {
1484
1500
  let delta = 0;
@@ -1503,6 +1519,7 @@ function useResizeHandle(params) {
1503
1519
  };
1504
1520
  const onPointerDown = (event) => {
1505
1521
  event.preventDefault();
1522
+ delta = valueRef.current;
1506
1523
  handleRef.setPointerCapture(event.pointerId);
1507
1524
  handleRef.addEventListener("pointermove", onPointerMove);
1508
1525
  };
@@ -1510,12 +1527,12 @@ function useResizeHandle(params) {
1510
1527
  event.preventDefault();
1511
1528
  handleRef.releasePointerCapture(event.pointerId);
1512
1529
  handleRef.removeEventListener("pointermove", onPointerMove);
1513
- delta = coerceDelta(delta);
1530
+ valueRef.current = coerceDelta(valueRef.current);
1514
1531
  };
1515
1532
  handleRef.addEventListener("pointerdown", onPointerDown);
1516
1533
  handleRef.addEventListener("pointerup", onPointerUp);
1517
1534
  }
1518
- }, [handleRef]);
1535
+ }, [handleRef, setValue]);
1519
1536
  return {
1520
1537
  elementRef: setElementRef,
1521
1538
  handleRef: setHandleRef,
@@ -1615,14 +1632,16 @@ const useStyles$d = makeStyles({
1615
1632
  },
1616
1633
  paneHeaderDiv: {
1617
1634
  display: "flex",
1618
- flexDirection: "column",
1619
- justifyContent: "center",
1620
- backgroundColor: tokens.colorNeutralBackgroundInverted,
1635
+ flexDirection: "row",
1636
+ alignItems: "center",
1621
1637
  height: "36px",
1622
1638
  },
1623
1639
  paneHeaderText: {
1640
+ flex: 1,
1624
1641
  marginLeft: tokens.spacingHorizontalM,
1625
- color: tokens.colorNeutralForegroundInverted,
1642
+ },
1643
+ paneHeaderButton: {
1644
+ color: "inherit",
1626
1645
  },
1627
1646
  paneDivider: {
1628
1647
  flex: "0 0 auto",
@@ -1672,17 +1691,25 @@ const useStyles$d = makeStyles({
1672
1691
  overflow: "hidden",
1673
1692
  },
1674
1693
  });
1675
- const PaneHeader = ({ title }) => {
1694
+ const DockMenu = (props) => {
1695
+ const { openOnContext, sidePaneId, dockOptions, children } = props;
1696
+ const dockLeft = dockOptions.get("full-left");
1697
+ const dockTopLeft = dockOptions.get("top-left");
1698
+ const dockBottomLeft = dockOptions.get("bottom-left");
1699
+ const dockRight = dockOptions.get("full-right");
1700
+ const dockTopRight = dockOptions.get("top-right");
1701
+ const dockBottomRight = dockOptions.get("bottom-right");
1702
+ return (jsxs(Menu, { openOnContext: openOnContext, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: children }), jsx(Theme, { children: jsx(MenuPopover, { children: jsx(MenuList, { children: jsxs(MenuGroup, { children: [jsx(MenuGroupHeader, { children: "Dock" }), dockLeft && (jsx(MenuItem, { icon: jsx(LayoutColumnTwoFocusLeftFilled, {}), onClick: () => dockLeft(sidePaneId), children: "Left" })), dockTopLeft && (jsx(MenuItem, { icon: jsx(LayoutColumnTwoSplitLeftFocusTopLeftFilled, {}), onClick: () => dockTopLeft(sidePaneId), children: "Top Left" })), dockBottomLeft && (jsx(MenuItem, { icon: jsx(LayoutColumnTwoSplitLeftFocusBottomLeftFilled, {}), onClick: () => dockBottomLeft(sidePaneId), children: "Bottom Left" })), dockRight && (jsx(MenuItem, { icon: jsx(LayoutColumnTwoFocusRightFilled, {}), onClick: () => dockRight(sidePaneId), children: "Right" })), dockTopRight && (jsx(MenuItem, { icon: jsx(LayoutColumnTwoSplitRightFocusTopRightFilled, {}), onClick: () => dockTopRight(sidePaneId), children: "Top Right" })), dockBottomRight && (jsx(MenuItem, { icon: jsx(LayoutColumnTwoSplitRightFocusBottomRightFilled, {}), onClick: () => dockBottomRight(sidePaneId), children: "Bottom Right" }))] }) }) }) })] }));
1703
+ };
1704
+ const PaneHeader = (props) => {
1705
+ const { id, title, dockOptions } = props;
1676
1706
  const classes = useStyles$d();
1677
- if (!title) {
1678
- return null;
1679
- }
1680
- return (jsx("div", { className: classes.paneHeaderDiv, children: jsx(Subtitle2Stronger, { className: classes.paneHeaderText, children: title }) }));
1707
+ return (jsx(Theme, { invert: true, children: jsxs("div", { className: classes.paneHeaderDiv, children: [jsx(Subtitle2Stronger, { className: classes.paneHeaderText, children: title }), jsx(DockMenu, { sidePaneId: id, dockOptions: dockOptions, children: jsx(Button$1, { className: classes.paneHeaderButton, appearance: "transparent", icon: jsx(MoreHorizontalRegular, {}) }) })] }) }));
1681
1708
  };
1682
1709
  // 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.
1683
- const ToolbarItem = ({ location, alignment, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
1710
+ const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
1684
1711
  const classes = useStyles$d();
1685
- const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${location}/${alignment}/${displayName ?? id}`), [displayName, id]);
1712
+ const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
1686
1713
  const teachingMoment = useTeachingMoment(suppressTeachingMoment);
1687
1714
  return (jsxs(Fragment, { children: [jsx(TeachingMoment, { ...teachingMoment, shouldDisplay: teachingMoment.shouldDisplay && !suppressTeachingMoment, title: displayName ?? "Extension", description: `The "${displayName ?? id}" extension can be accessed here.` }), jsx("div", { className: classes.barItem, ref: teachingMoment.targetRef, children: jsx(Component, {}) })] }));
1688
1715
  };
@@ -1692,26 +1719,26 @@ const Toolbar = ({ location, components }) => {
1692
1719
  const classes = useStyles$d();
1693
1720
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
1694
1721
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
1695
- 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))) })] })) }));
1722
+ 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, { verticalLocation: location, horizontalLocation: 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, { verticalLocation: location, horizontalLocation: entry.horizontalLocation, id: entry.key, component: entry.component, displayName: entry.displayName, suppressTeachingMoment: entry.suppressTeachingMoment }, entry.key))) })] })) }));
1696
1723
  };
1697
1724
  // 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.
1698
1725
  const SidePaneTab = (props) => {
1699
- const { alignment, id, isSelected,
1726
+ const { location, id, isSelected, dockOptions,
1700
1727
  // eslint-disable-next-line @typescript-eslint/naming-convention
1701
1728
  icon: Icon, title, suppressTeachingMoment, } = props;
1702
1729
  const classes = useStyles$d();
1703
- const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${alignment}/${title ?? id}`), [title, id]);
1730
+ const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
1704
1731
  const teachingMoment = useTeachingMoment(suppressTeachingMoment);
1705
1732
  const tabClass = mergeClasses(classes.tab, isSelected ? undefined : classes.unselectedTab);
1706
- 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: {
1707
- className: isSelected ? classes.selectedTabIcon : undefined,
1708
- children: jsx(Icon, {}),
1709
- } }) })] }));
1733
+ 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(DockMenu, { openOnContext: true, sidePaneId: id, dockOptions: dockOptions, children: jsx(ToolbarRadioButton, { ref: teachingMoment.targetRef, title: title ?? id, appearance: "transparent", className: classes.tabRadioButton, name: "selectedTab", value: id, icon: {
1734
+ className: isSelected ? classes.selectedTabIcon : undefined,
1735
+ children: jsx(Icon, {}),
1736
+ } }) }) })] }));
1710
1737
  };
1711
1738
  // This hook provides a side pane container and the tab list.
1712
1739
  // In "compact" mode, the tab list is integrated into the pane itself.
1713
1740
  // In "full" mode, the returned tab list is later injected into the toolbar.
1714
- function usePane(alignment, defaultWidth, minWidth, topPanes, bottomPanes, onSelectSidePane, toolbarMode, topBarItems, bottomBarItems) {
1741
+ function usePane(location, defaultWidth, minWidth, sidePanes, topPaneContainerRef, bottomPaneContainerRef, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems) {
1715
1742
  const classes = useStyles$d();
1716
1743
  const [topSelectedTab, setTopSelectedTab] = useState();
1717
1744
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
@@ -1719,8 +1746,25 @@ function usePane(alignment, defaultWidth, minWidth, topPanes, bottomPanes, onSel
1719
1746
  const onExpandCollapseClick = useCallback(() => {
1720
1747
  setCollapsed((collapsed) => !collapsed);
1721
1748
  }, [collapsed]);
1722
- const widthStorageKey = `Babylon/Settings/${alignment}Pane/WidthAdjust`;
1723
- const heightStorageKey = `Babylon/Settings/${alignment}Pane/HeightAdjust`;
1749
+ const widthStorageKey = `Babylon/Settings/${location}Pane/WidthAdjust`;
1750
+ const heightStorageKey = `Babylon/Settings/${location}Pane/HeightAdjust`;
1751
+ const currentSidePanes = useMemo(() => sidePanes.filter((entry) => entry.horizontalLocation === location), [sidePanes, location]);
1752
+ const topPanes = useMemo(() => currentSidePanes.filter((entry) => entry.verticalLocation === "top"), [currentSidePanes]);
1753
+ const bottomPanes = useMemo(() => currentSidePanes.filter((entry) => entry.verticalLocation === "bottom"), [currentSidePanes]);
1754
+ const getValidDockOperations = useCallback((verticalLocation) => {
1755
+ const validDockOperations = new Map(dockOperations);
1756
+ // Can't re-dock to the current location.
1757
+ validDockOperations.delete(`${verticalLocation}-${location}`);
1758
+ // Full would mean there are no bottom panes, so this is also re-docking to the current location.
1759
+ validDockOperations.delete(`full-${location}`);
1760
+ // If there is only one pane left, it can't be docked to the bottom (as this would leave no top panes).
1761
+ if (currentSidePanes.length === 1) {
1762
+ validDockOperations.delete(`bottom-${location}`);
1763
+ }
1764
+ return validDockOperations;
1765
+ }, [location, dockOperations, currentSidePanes]);
1766
+ const validTopDockOptions = useMemo(() => getValidDockOperations("top"), [getValidDockOperations]);
1767
+ const validBottomDockOptions = useMemo(() => getValidDockOperations("bottom"), [getValidDockOperations]);
1724
1768
  // Selects a default top tab (during initialization or if the selected tab is removed).
1725
1769
  useEffect(() => {
1726
1770
  if ((topSelectedTab && !topPanes.includes(topSelectedTab)) || (!topSelectedTab && topPanes.length > 0)) {
@@ -1756,30 +1800,30 @@ function usePane(alignment, defaultWidth, minWidth, topPanes, bottomPanes, onSel
1756
1800
  return () => observer.remove();
1757
1801
  }, [topPanes, bottomPanes, onSelectSidePane]);
1758
1802
  const expandCollapseIcon = useMemo(() => {
1759
- if (alignment === "left") {
1803
+ if (location === "left") {
1760
1804
  return collapsed ? jsx(PanelLeftExpandRegular, {}) : jsx(PanelLeftContractRegular, {});
1761
1805
  }
1762
1806
  else {
1763
1807
  return collapsed ? jsx(PanelRightExpandRegular, {}) : jsx(PanelRightContractRegular, {});
1764
1808
  }
1765
- }, [collapsed, alignment]);
1766
- const createPaneTabList = useCallback((paneComponents, toolbarMode, selectedTab, setSelectedTab) => {
1767
- 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) => {
1809
+ }, [collapsed, location]);
1810
+ const createPaneTabList = useCallback((paneComponents, toolbarMode, selectedTab, setSelectedTab, dockOptions) => {
1811
+ return (jsx(Fragment, { children: paneComponents.length > 0 && (jsxs("div", { className: `${classes.paneTabListDiv} ${location === "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) => {
1768
1812
  const tab = paneComponents.find((entry) => entry.key === data.checkedItems[0]);
1769
1813
  setSelectedTab(tab);
1770
1814
  setCollapsed(false);
1771
1815
  }, children: paneComponents.map((entry) => {
1772
1816
  const isSelected = selectedTab?.key === entry.key;
1773
- return (jsx(SidePaneTab, { alignment: alignment, id: entry.key, title: entry.title, icon: entry.icon, suppressTeachingMoment: entry.suppressTeachingMoment, isSelected: isSelected && !collapsed }, entry.key));
1817
+ return (jsx(SidePaneTab, { location: location, id: entry.key, title: entry.title, icon: entry.icon, suppressTeachingMoment: entry.suppressTeachingMoment, isSelected: isSelected && !collapsed, dockOptions: dockOptions }, entry.key));
1774
1818
  }) }) })), 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 }) })] }))] })) }));
1775
- }, [alignment, collapsed]);
1819
+ }, [location, collapsed]);
1776
1820
  // 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).
1777
- const topPaneTabList = useMemo(() => createPaneTabList(topPanes, toolbarMode, topSelectedTab, setTopSelectedTab), [createPaneTabList, topPanes, toolbarMode, topSelectedTab]);
1778
- const bottomPaneTabList = useMemo(() => createPaneTabList(bottomPanes, "compact", bottomSelectedTab, setBottomSelectedTab), [createPaneTabList, bottomPanes, bottomSelectedTab]);
1821
+ const topPaneTabList = useMemo(() => createPaneTabList(topPanes, toolbarMode, topSelectedTab, setTopSelectedTab, validTopDockOptions), [createPaneTabList, topPanes, toolbarMode, topSelectedTab]);
1822
+ const bottomPaneTabList = useMemo(() => createPaneTabList(bottomPanes, "compact", bottomSelectedTab, setBottomSelectedTab, validBottomDockOptions), [createPaneTabList, bottomPanes, bottomSelectedTab]);
1779
1823
  // This manages the CSS variable that controls the width of the side pane.
1780
1824
  const paneWidthAdjustCSSVar = "--pane-width-adjust";
1781
1825
  const { elementRef: paneHorizontalResizeElementRef, handleRef: paneHorizontalResizeHandleRef, setValue: setPaneWidthAdjust, } = useResizeHandle({
1782
- growDirection: alignment === "left" ? "end" : "start",
1826
+ growDirection: location === "left" ? "end" : "start",
1783
1827
  variableName: paneWidthAdjustCSSVar,
1784
1828
  minValue: minWidth - defaultWidth,
1785
1829
  onChange: (value) => {
@@ -1810,56 +1854,182 @@ function usePane(alignment, defaultWidth, minWidth, topPanes, bottomPanes, onSel
1810
1854
  }, []);
1811
1855
  // This memoizes the pane itself, which may or may not include the tab list, depending on the toolbar mode.
1812
1856
  const pane = useMemo(() => {
1813
- 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", { ref: paneHorizontalResizeElementRef, className: classes.paneContainer, style: { width: `clamp(${minWidth}px, calc(${defaultWidth}px + var(${paneWidthAdjustCSSVar}, 0px)), 1000px)` }, 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", { ref: paneHorizontalResizeHandleRef, className: `${classes.resizer} ${alignment === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` } })] })) }));
1814
- }, [topPanes, topSelectedTab, bottomPanes, bottomSelectedTab, topBarItems, bottomBarItems, topPaneTabList, bottomPaneTabList, collapsed]);
1815
- return [topPaneTabList, pane];
1857
+ return (jsx(Fragment, { children: (topPanes.length > 0 || bottomPanes.length > 0) && (jsxs("div", { className: `${classes.pane} ${location === "left" ? classes.paneLeft : classes.paneRight}`, children: [jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: jsxs("div", { ref: paneHorizontalResizeElementRef, className: classes.paneContainer, style: { width: `clamp(${minWidth}px, calc(${defaultWidth}px + var(${paneWidthAdjustCSSVar}, 0px)), 1000px)` }, 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 })] }) })), topPanes.length > 0 && (jsx("div", { className: classes.paneContent, children: topSelectedTab && (jsxs(Fragment, { children: [jsx(PaneHeader, { id: topSelectedTab.key, title: topSelectedTab.title, dockOptions: validTopDockOptions }), jsx("div", { ref: topPaneContainerRef, className: classes.paneContent })] })) })), 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 && (jsx("div", { ref: paneVerticalResizeElementRef, className: classes.paneContent, style: { height: `clamp(200px, calc(45% + var(${paneHeightAdjustCSSVar}, 0px)), 100% - 300px)`, flex: "0 0 auto" }, children: bottomSelectedTab && (jsxs(Fragment, { children: [jsx(PaneHeader, { id: bottomSelectedTab.key, title: bottomSelectedTab.title, dockOptions: validBottomDockOptions }), jsx("div", { ref: bottomPaneContainerRef, className: classes.paneContent })] })) })), toolbarMode === "compact" && bottomBarItems.length > 0 && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomBarItems }) }) }))] }) }), jsx("div", { ref: paneHorizontalResizeHandleRef, className: `${classes.resizer} ${location === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` } })] })) }));
1858
+ }, [
1859
+ topPanes,
1860
+ topSelectedTab,
1861
+ validTopDockOptions,
1862
+ bottomPanes,
1863
+ bottomSelectedTab,
1864
+ validBottomDockOptions,
1865
+ topBarItems,
1866
+ bottomBarItems,
1867
+ topPaneTabList,
1868
+ bottomPaneTabList,
1869
+ collapsed,
1870
+ ]);
1871
+ return [topPaneTabList, pane, topSelectedTab, bottomSelectedTab];
1816
1872
  }
1817
- function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWidth = 350, rightPaneDefaultWidth = 350, rightPaneMinWidth = 350, toolbarMode = "full", sidePaneMode = "both", } = {}) {
1873
+ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWidth = 350, rightPaneDefaultWidth = 350, rightPaneMinWidth = 350, toolbarMode = "full", sidePaneRemapper = undefined, } = {}) {
1818
1874
  return {
1819
1875
  friendlyName: "MainView",
1820
1876
  produces: [ShellServiceIdentity, RootComponentServiceIdentity],
1821
1877
  factory: () => {
1822
- const topBarItemCollection = new ObservableCollection();
1823
- const bottomBarItemCollection = new ObservableCollection();
1824
- const topLeftPaneCollection = new ObservableCollection();
1825
- const topRightPaneCollection = new ObservableCollection();
1826
- const bottomLeftPaneCollection = new ObservableCollection();
1827
- const bottomRightPaneCollection = new ObservableCollection();
1878
+ const toolbarItemCollection = new ObservableCollection();
1879
+ const sidePaneCollection = new ObservableCollection();
1828
1880
  const centralContentCollection = new ObservableCollection();
1829
- const onSelectSidePane = new Observable();
1881
+ const onSelectSidePane = new Observable(undefined, true);
1830
1882
  const rootComponent = () => {
1831
1883
  const classes = useStyles$d();
1832
- const topBarItems = useOrderedObservableCollection(topBarItemCollection);
1833
- const bottomBarItems = useOrderedObservableCollection(bottomBarItemCollection);
1834
- const topLeftPanes = useOrderedObservableCollection(topLeftPaneCollection);
1835
- const topRightPanes = useOrderedObservableCollection(topRightPaneCollection);
1836
- const bottomLeftPanes = useOrderedObservableCollection(bottomLeftPaneCollection);
1837
- const bottomRightPanes = useOrderedObservableCollection(bottomRightPaneCollection);
1838
- const hasLeftPanes = topLeftPanes.length > 0 || bottomLeftPanes.length > 0;
1839
- const hasRightPanes = topRightPanes.length > 0 || bottomRightPanes.length > 0;
1884
+ const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSidePaneDockOverrides();
1885
+ // This function returns a promise that resolves after the dock change takes effect so that
1886
+ // we can then select the re-docked pane.
1887
+ const pendingPaneReselects = useRef([]);
1888
+ const updateSidePaneDockOverride = useCallback((key, horizontalLocation, verticalLocation) => {
1889
+ setSidePaneDockOverrides((current) => ({
1890
+ ...current,
1891
+ [key]: { horizontalLocation, verticalLocation },
1892
+ }));
1893
+ pendingPaneReselects.current.push(key);
1894
+ }, [setSidePaneDockOverrides]);
1895
+ const toolbarItems = useOrderedObservableCollection(toolbarItemCollection);
1896
+ const sidePanes = useOrderedObservableCollection(sidePaneCollection);
1897
+ const coercedSidePaneCache = useRef(new Map());
1898
+ const coercedSidePanes = useMemo(() => {
1899
+ // First pass - apply overrides and respect the side pane mode.
1900
+ const coercedSidePanes = sidePanes.map((sidePaneDefinition) => {
1901
+ let sidePaneEntry = coercedSidePaneCache.current.get(sidePaneDefinition.key);
1902
+ if (!sidePaneEntry) {
1903
+ // Manually create html element containers outside the React tree to prevent unmounting/mounting
1904
+ // when panes are re-docked or the selected tabs change. This preserves state within the side panes.
1905
+ // This is combined with the usage of React portals to make it all work.
1906
+ const sidePaneContainer = document.createElement("div");
1907
+ sidePaneContainer.style.display = "flex";
1908
+ sidePaneContainer.style.flex = "1";
1909
+ sidePaneContainer.style.flexDirection = "column";
1910
+ sidePaneContainer.style.overflow = "hidden";
1911
+ sidePaneEntry = { ...sidePaneDefinition, container: sidePaneContainer };
1912
+ coercedSidePaneCache.current.set(sidePaneDefinition.key, sidePaneEntry);
1913
+ }
1914
+ const override = sidePaneDockOverrides[sidePaneDefinition.key];
1915
+ if (override) {
1916
+ // Override (user manually re-docked) has the highest priority.
1917
+ sidePaneEntry.horizontalLocation = override.horizontalLocation;
1918
+ sidePaneEntry.verticalLocation = override.verticalLocation;
1919
+ }
1920
+ else if (sidePaneRemapper) {
1921
+ // A side pane remapper has the next highest priority.
1922
+ const { horizontalLocation, verticalLocation } = sidePaneRemapper(sidePaneDefinition);
1923
+ sidePaneEntry.horizontalLocation = horizontalLocation;
1924
+ sidePaneEntry.verticalLocation = verticalLocation;
1925
+ }
1926
+ else {
1927
+ // Otherwise use the default defined location.
1928
+ sidePaneEntry.horizontalLocation = sidePaneDefinition.horizontalLocation;
1929
+ sidePaneEntry.verticalLocation = sidePaneDefinition.verticalLocation;
1930
+ }
1931
+ return sidePaneEntry;
1932
+ });
1933
+ // Second pass - correct any invalid state, specifically if there are only bottom panes, force them to be top panes.
1934
+ for (const side of ["left", "right"]) {
1935
+ const topPanes = coercedSidePanes.filter((entry) => entry.horizontalLocation === side && entry.verticalLocation === "top");
1936
+ const bottomPanes = coercedSidePanes.filter((entry) => entry.horizontalLocation === side && entry.verticalLocation === "bottom");
1937
+ if (bottomPanes.length > 0 && topPanes.length === 0) {
1938
+ for (const pane of bottomPanes) {
1939
+ pane.verticalLocation = "top";
1940
+ updateSidePaneDockOverride(pane.key, side, "top");
1941
+ }
1942
+ }
1943
+ }
1944
+ // Cleanup any cached panes that are no longer present.
1945
+ for (const key of coercedSidePaneCache.current.keys()) {
1946
+ if (!coercedSidePanes.some((entry) => entry.key === key)) {
1947
+ coercedSidePaneCache.current.delete(key);
1948
+ }
1949
+ }
1950
+ return coercedSidePanes;
1951
+ }, [sidePanes, sidePaneDockOverrides, updateSidePaneDockOverride, sidePaneRemapper]);
1952
+ useEffect(() => {
1953
+ for (const paneKey of pendingPaneReselects.current.splice(0)) {
1954
+ onSelectSidePane.notifyObservers(paneKey);
1955
+ }
1956
+ }, [coercedSidePanes]);
1957
+ const sidePaneDockOperations = useMemo(() => {
1958
+ const sidePaneDockOperations = new Map();
1959
+ for (const side of ["left", "right"]) {
1960
+ const currentSidePanes = coercedSidePanes.filter((entry) => entry.horizontalLocation === side);
1961
+ const dockTop = (sidePaneKey) => {
1962
+ updateSidePaneDockOverride(sidePaneKey, side, "top");
1963
+ };
1964
+ const dockBottom = (sidePaneKey) => {
1965
+ updateSidePaneDockOverride(sidePaneKey, side, "bottom");
1966
+ };
1967
+ if (currentSidePanes.some((entry) => entry.verticalLocation === "bottom")) {
1968
+ // If there are bottom panes, there must also be top panes, and so top and bottom are valid locations.
1969
+ sidePaneDockOperations.set(`top-${side}`, dockTop);
1970
+ sidePaneDockOperations.set(`bottom-${side}`, dockBottom);
1971
+ }
1972
+ else if (currentSidePanes.length > 0) {
1973
+ // If there are only top panes, then full and bottom are valid locations.
1974
+ sidePaneDockOperations.set(`full-${side}`, dockTop);
1975
+ sidePaneDockOperations.set(`bottom-${side}`, dockBottom);
1976
+ }
1977
+ else {
1978
+ // If there are no panes, then only full is a valid location.
1979
+ sidePaneDockOperations.set(`full-${side}`, dockTop);
1980
+ }
1981
+ }
1982
+ return sidePaneDockOperations;
1983
+ }, [coercedSidePanes]);
1984
+ const hasLeftPanes = coercedSidePanes.some((entry) => entry.horizontalLocation === "left");
1985
+ const hasRightPanes = coercedSidePanes.some((entry) => entry.horizontalLocation === "right");
1840
1986
  // If we are in compact toolbar mode, we may need to move toolbar items from the left to the right or vice versa,
1841
1987
  // depending on whether there are any side panes on that side.
1842
- const coerceToolBarItemHorizontalLocation = (item) => {
1843
- let location = item.horizontalLocation;
1988
+ const coerceToolBarItemHorizontalLocation = useMemo(() => (item) => {
1989
+ let horizontalLocation = item.horizontalLocation;
1844
1990
  // Coercion is only needed in compact toolbar mode since there might not be a left or right pane.
1845
1991
  if (toolbarMode === "compact") {
1846
- if (location === "left" && !hasLeftPanes) {
1847
- location = "right";
1992
+ if (horizontalLocation === "left" && !hasLeftPanes) {
1993
+ horizontalLocation = "right";
1848
1994
  }
1849
- if (location === "right" && !hasRightPanes) {
1850
- location = "left";
1995
+ if (horizontalLocation === "right" && !hasRightPanes) {
1996
+ horizontalLocation = "left";
1851
1997
  }
1852
1998
  }
1853
- return location;
1854
- };
1855
- const topBarLeftItems = useMemo(() => topBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "left"), [topBarItems]);
1856
- const topBarRightItems = useMemo(() => topBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "right"), [topBarItems]);
1857
- const bottomBarLeftItems = useMemo(() => bottomBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "left"), [bottomBarItems]);
1858
- const bottomBarRightItems = useMemo(() => bottomBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "right"), [bottomBarItems]);
1999
+ return horizontalLocation;
2000
+ }, [toolbarMode, hasLeftPanes, hasRightPanes]);
2001
+ const topToolBarItems = useMemo(() => toolbarItems.filter((entry) => entry.verticalLocation === "top"), [toolbarItems]);
2002
+ const bottomToolBarItems = useMemo(() => toolbarItems.filter((entry) => entry.verticalLocation === "bottom"), [toolbarItems]);
2003
+ const topBarLeftItems = useMemo(() => topToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "left"), [topToolBarItems, coerceToolBarItemHorizontalLocation]);
2004
+ const topBarRightItems = useMemo(() => topToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "right"), [topToolBarItems, coerceToolBarItemHorizontalLocation]);
2005
+ const bottomBarLeftItems = useMemo(() => bottomToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "left"), [bottomToolBarItems, coerceToolBarItemHorizontalLocation]);
2006
+ const bottomBarRightItems = useMemo(() => bottomToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "right"), [bottomToolBarItems, coerceToolBarItemHorizontalLocation]);
1859
2007
  const centralContents = useOrderedObservableCollection(centralContentCollection);
1860
- const [leftPaneTabList, leftPane] = usePane("left", leftPaneDefaultWidth, leftPaneMinWidth, topLeftPanes, bottomLeftPanes, onSelectSidePane, toolbarMode, topBarLeftItems, bottomBarLeftItems);
1861
- const [rightPaneTabList, rightPane] = usePane("right", rightPaneDefaultWidth, rightPaneMinWidth, topRightPanes, bottomRightPanes, onSelectSidePane, toolbarMode, topBarRightItems, bottomBarRightItems);
1862
- return (jsxs("div", { className: classes.mainView, children: [toolbarMode === "full" && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [leftPaneTabList, jsx(Toolbar, { location: "top", components: topBarItems }), rightPaneTabList] }) })), jsxs("div", { className: classes.verticallyCentralContent, children: [leftPane, jsx("div", { className: classes.centralContent, children: centralContents.map((entry) => (jsx(entry.component, {}, entry.key))) }), rightPane] }), toolbarMode === "full" && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomBarItems }) }) }))] }));
2008
+ const [topLeftPaneContainer, setTopLeftPaneContainer] = useState(null);
2009
+ const [bottomLeftPaneContainer, setBottomLeftPaneContainer] = useState(null);
2010
+ const [topRightPaneContainer, setTopRightPaneContainer] = useState(null);
2011
+ const [bottomRightPaneContainer, setBottomRightPaneContainer] = useState(null);
2012
+ const [leftPaneTabList, leftPane, topLeftSelectedPane, bottomLeftSelectedPane] = usePane("left", leftPaneDefaultWidth, leftPaneMinWidth, coercedSidePanes, setTopLeftPaneContainer, setBottomLeftPaneContainer, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarLeftItems, bottomBarLeftItems);
2013
+ const [rightPaneTabList, rightPane, topRightSelectedPane, bottomRightSelectedPane] = usePane("right", rightPaneDefaultWidth, rightPaneMinWidth, coercedSidePanes, setTopRightPaneContainer, setBottomRightPaneContainer, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarRightItems, bottomBarRightItems);
2014
+ // Update the content of the top left pane container.
2015
+ useEffect(() => {
2016
+ topLeftPaneContainer?.replaceChildren(...(topLeftSelectedPane ? [topLeftSelectedPane.container] : []));
2017
+ }, [topLeftPaneContainer, topLeftSelectedPane]);
2018
+ // Update the content of the bottom left pane container.
2019
+ useEffect(() => {
2020
+ bottomLeftPaneContainer?.replaceChildren(...(bottomLeftSelectedPane ? [bottomLeftSelectedPane.container] : []));
2021
+ }, [bottomLeftPaneContainer, bottomLeftSelectedPane]);
2022
+ // Update the content of the top right pane container.
2023
+ useEffect(() => {
2024
+ topRightPaneContainer?.replaceChildren(...(topRightSelectedPane ? [topRightSelectedPane.container] : []));
2025
+ }, [topRightPaneContainer, topRightSelectedPane]);
2026
+ // Update the content of the bottom right pane container.
2027
+ useEffect(() => {
2028
+ bottomRightPaneContainer?.replaceChildren(...(bottomRightSelectedPane ? [bottomRightSelectedPane.container] : []));
2029
+ }, [bottomRightPaneContainer, bottomRightSelectedPane]);
2030
+ return (jsxs("div", { className: classes.mainView, children: [toolbarMode === "full" && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [leftPaneTabList, jsx(Toolbar, { location: "top", components: topToolBarItems }), rightPaneTabList] }) })), jsxs("div", { className: classes.verticallyCentralContent, children: [leftPane, jsx("div", { className: classes.centralContent, children: centralContents.map((entry) => (jsx(entry.component, {}, entry.key))) }), rightPane] }), toolbarMode === "full" && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomToolBarItems }) }) })), coercedSidePanes.map((sidePaneDefinition) => {
2031
+ return (jsx(Portal, { mountNode: sidePaneDefinition.container, children: jsx(sidePaneDefinition.content, {}) }, sidePaneDefinition.key));
2032
+ })] }));
1863
2033
  };
1864
2034
  rootComponent.displayName = "Shell Service Root";
1865
2035
  return {
@@ -1867,54 +2037,18 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
1867
2037
  if (!entry.component.displayName) {
1868
2038
  entry.component.displayName = `${entry.key} | ${entry.verticalLocation} ${entry.horizontalLocation} bar item`;
1869
2039
  }
1870
- if (entry.verticalLocation === "top") {
1871
- return topBarItemCollection.add(entry);
1872
- }
1873
- else {
1874
- return bottomBarItemCollection.add(entry);
1875
- }
2040
+ return toolbarItemCollection.add(entry);
1876
2041
  },
1877
2042
  addSidePane: (entry) => {
1878
2043
  if (!entry.content.displayName) {
1879
2044
  entry.content.displayName = `${entry.key} | ${entry.horizontalLocation} pane`;
1880
2045
  }
1881
- // When we are in "right" side pane mode, we need to coerce all left panes to be right panes.
1882
- const coerceSidePaneLocation = (sidePane) => {
1883
- let { horizontalLocation, verticalLocation } = sidePane;
1884
- if (sidePaneMode === "right") {
1885
- // All right panes go to right bottom.
1886
- if (horizontalLocation === "right") {
1887
- verticalLocation = "bottom";
1888
- }
1889
- // All left panes go to right top.
1890
- if (horizontalLocation === "left") {
1891
- horizontalLocation = "right";
1892
- verticalLocation = "top";
1893
- }
1894
- }
1895
- return { horizontalLocation, verticalLocation };
1896
- };
1897
- const { horizontalLocation, verticalLocation } = coerceSidePaneLocation(entry);
1898
- if (horizontalLocation === "left") {
1899
- if (verticalLocation === "top") {
1900
- return topLeftPaneCollection.add(entry);
1901
- }
1902
- else {
1903
- return bottomLeftPaneCollection.add(entry);
1904
- }
1905
- }
1906
- else {
1907
- if (verticalLocation === "top") {
1908
- return topRightPaneCollection.add(entry);
1909
- }
1910
- else {
1911
- return bottomRightPaneCollection.add(entry);
1912
- }
1913
- }
2046
+ return sidePaneCollection.add(entry);
1914
2047
  },
1915
2048
  addCentralContent: (entry) => centralContentCollection.add(entry),
2049
+ resetSidePaneLayout: () => localStorage.removeItem("Babylon/Settings/SidePaneDockOverrides"),
1916
2050
  get sidePanes() {
1917
- return [...topLeftPaneCollection.items, ...bottomLeftPaneCollection.items, ...topRightPaneCollection.items, ...bottomRightPaneCollection.items].map((sidePaneDefinition) => {
2051
+ return [...sidePaneCollection.items].map((sidePaneDefinition) => {
1918
2052
  return {
1919
2053
  key: sidePaneDefinition.key,
1920
2054
  select: () => onSelectSidePane.notifyObservers(sidePaneDefinition.key),
@@ -2765,6 +2899,16 @@ const DebugServiceDefinition = {
2765
2899
  },
2766
2900
  };
2767
2901
 
2902
+ /**
2903
+ * Wraps a button with a label in a line container
2904
+ * @param props Button props plus a label
2905
+ * @returns A button inside a line
2906
+ */
2907
+ const ButtonLine = (props) => {
2908
+ ButtonLine.displayName = "ButtonLine";
2909
+ return (jsx(LineContainer, { children: jsx(Button, { ...props }) }));
2910
+ };
2911
+
2768
2912
  const SettingsServiceIdentity = Symbol("SettingsService");
2769
2913
  const SettingsServiceDefinition = {
2770
2914
  friendlyName: "Settings",
@@ -2828,6 +2972,7 @@ const SettingsServiceDefinition = {
2828
2972
  const sectionContent = useObservableCollection(sectionContentCollection);
2829
2973
  const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
2830
2974
  const [compactMode, setCompactMode] = useCompactMode();
2975
+ const [, , resetSidePaneLayout] = useSidePaneDockOverrides();
2831
2976
  return (jsx(Fragment, { children: scene && (jsx(ExtensibleAccordion, { sections: sections, sectionContent: sectionContent, context: scene, children: jsxs(AccordionSection, { title: "UI", children: [jsx(SwitchPropertyLine, { label: "Compact Mode", description: "Use a more compact UI with less spacing.", value: compactMode, onChange: (checked) => {
2832
2977
  setCompactMode(checked);
2833
2978
  } }), jsx(SwitchPropertyLine, { label: "Use Degrees", description: "Using degrees instead of radians.", value: settings.useDegrees, onChange: (checked) => {
@@ -2836,7 +2981,7 @@ const SettingsServiceDefinition = {
2836
2981
  settings.ignoreBackfacesForPicking = checked;
2837
2982
  } }), jsx(SwitchPropertyLine, { label: "Show Properties on Selection", description: "Shows the Properties pane when an entity is selected.", value: settings.showPropertiesOnEntitySelection, onChange: (checked) => {
2838
2983
  settings.showPropertiesOnEntitySelection = checked;
2839
- } })] }) })) }));
2984
+ } }), jsx(ButtonLine, { label: "Reset Layout", onClick: resetSidePaneLayout })] }) })) }));
2840
2985
  },
2841
2986
  });
2842
2987
  settings.dispose = () => registration.dispose();
@@ -2911,16 +3056,6 @@ const FrameStepsStats = ({ context: scene }) => {
2911
3056
  return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Absolute FPS", value: absoluteFPS }, "AbsoluteFPS"), jsx(StringifiedPropertyLine, { label: "Meshes Selection", value: meshesSelection, precision: 2, units: "ms" }, "MeshesSelection"), jsx(StringifiedPropertyLine, { label: "Render Targets", value: renderTargets, precision: 2, units: "ms" }, "RenderTargets"), jsx(StringifiedPropertyLine, { label: "Particles", value: particles, precision: 2, units: "ms" }, "Particles"), jsx(StringifiedPropertyLine, { label: "Sprites", value: sprites, precision: 2, units: "ms" }, "Sprites"), jsx(StringifiedPropertyLine, { label: "Animations", value: animations, precision: 2, units: "ms" }, "Animations"), jsx(StringifiedPropertyLine, { label: "Physics", value: physics, precision: 2, units: "ms" }, "Physics"), jsx(StringifiedPropertyLine, { label: "Inter-Frame Time", value: interFrameTime, precision: 2, units: "ms" }, "InterFrameTime"), jsx(StringifiedPropertyLine, { label: "GPU Frame Time", value: gpuFrameTime, precision: 2, units: "ms" }, "GPUFrameTime"), jsx(StringifiedPropertyLine, { label: "GPU Frame Time (Average)", value: gpuFrameTimeAverage, precision: 2, units: "ms" }, "GPUFrameTimeAverage")] }));
2912
3057
  };
2913
3058
 
2914
- /**
2915
- * Wraps a button with a label in a line container
2916
- * @param props Button props plus a label
2917
- * @returns A button inside a line
2918
- */
2919
- const ButtonLine = (props) => {
2920
- ButtonLine.displayName = "ButtonLine";
2921
- return (jsx(LineContainer, { children: jsx(Button, { ...props }) }));
2922
- };
2923
-
2924
3059
  const FileUploadLine = (props) => {
2925
3060
  FileUploadLine.displayName = "FileUploadLine";
2926
3061
  const inputRef = useRef(null);
@@ -3228,19 +3363,19 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3228
3363
  name: "Export Tools",
3229
3364
  description: "Adds new features to enable exporting Babylon assets such as .gltf, .glb, .babylon, and more.",
3230
3365
  keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
3231
- getExtensionModuleAsync: async () => await import('./exportService-DMqaQAx2.js'),
3366
+ getExtensionModuleAsync: async () => await import('./exportService-smbKfT4U.js'),
3232
3367
  },
3233
3368
  {
3234
3369
  name: "Capture Tools",
3235
3370
  description: "Adds new features to enable capturing screenshots, GIFs, videos, and more.",
3236
3371
  keywords: ["capture", "screenshot", "gif", "video", "tools"],
3237
- getExtensionModuleAsync: async () => await import('./captureService-OpT4QhL3.js'),
3372
+ getExtensionModuleAsync: async () => await import('./captureService-ldJT1r7b.js'),
3238
3373
  },
3239
3374
  {
3240
3375
  name: "Import Tools",
3241
3376
  description: "Adds new features related to importing Babylon assets.",
3242
3377
  keywords: ["import", "tools"],
3243
- getExtensionModuleAsync: async () => await import('./importService-1dkQq3dn.js'),
3378
+ getExtensionModuleAsync: async () => await import('./importService-Si8UwFwR.js'),
3244
3379
  },
3245
3380
  ]);
3246
3381
 
@@ -5610,7 +5745,7 @@ const NormalMapProperties = (props) => {
5610
5745
  };
5611
5746
 
5612
5747
  // TODO: ryamtrem / gehalper This function is temporal until there is a line control to handle texture links (similar to the old TextureLinkLineComponent)
5613
- const UpdateTexture = (file, material, textureSetter) => {
5748
+ const UpdateTexture$1 = (file, material, textureSetter) => {
5614
5749
  ReadFile(file, (data) => {
5615
5750
  const blob = new Blob([data], { type: "octet/stream" });
5616
5751
  const url = URL.createObjectURL(blob);
@@ -5624,19 +5759,19 @@ const PBRBaseMaterialClearCoatProperties = (props) => {
5624
5759
  const bumpTexture = useProperty(material.clearCoat, "bumpTexture");
5625
5760
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enabled", target: material.clearCoat, propertyKey: "isEnabled" }), jsxs(Collapse, { visible: isEnabled, children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Intensity", target: material.clearCoat, propertyKey: "intensity", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Roughness", target: material.clearCoat, propertyKey: "roughness", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "IOR", description: "Index of Refraction", target: material.clearCoat, propertyKey: "indexOfRefraction", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Remap F0", target: material.clearCoat, propertyKey: "remapF0OnInterfaceChange" }), jsx(FileUploadLine, { label: "Clear coat", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5626
5761
  if (files.length > 0) {
5627
- UpdateTexture(files[0], material, (texture) => (material.clearCoat.texture = texture));
5762
+ UpdateTexture$1(files[0], material, (texture) => (material.clearCoat.texture = texture));
5628
5763
  }
5629
5764
  } }), jsx(FileUploadLine, { label: "Roughness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5630
5765
  if (files.length > 0) {
5631
- UpdateTexture(files[0], material, (texture) => (material.clearCoat.textureRoughness = texture));
5766
+ UpdateTexture$1(files[0], material, (texture) => (material.clearCoat.textureRoughness = texture));
5632
5767
  }
5633
5768
  } }), jsx(FileUploadLine, { label: "Bump", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5634
5769
  if (files.length > 0) {
5635
- UpdateTexture(files[0], material, (texture) => (material.clearCoat.bumpTexture = texture));
5770
+ UpdateTexture$1(files[0], material, (texture) => (material.clearCoat.bumpTexture = texture));
5636
5771
  }
5637
5772
  } }), jsx(Collapse, { visible: bumpTexture !== null, children: jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Bump Strength", target: bumpTexture, propertyKey: "level", min: 0, max: 2, step: 0.01 }) }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Roughness from Main Texture", target: material.clearCoat, propertyKey: "useRoughnessFromMainTexture" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Tint", target: material.clearCoat, propertyKey: "isTintEnabled" }), jsxs(Collapse, { visible: isTintEnabled, children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Tint Color", target: material.clearCoat, propertyKey: "tintColor", isLinearMode: true }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "At Distance", target: material.clearCoat, propertyKey: "tintColorAtDistance", min: 0, max: 20, step: 0.1 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tint Thickness", target: material.clearCoat, propertyKey: "tintThickness", min: 0, max: 20, step: 0.1 }), jsx(FileUploadLine, { label: "Tint", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5638
5773
  if (files.length > 0) {
5639
- UpdateTexture(files[0], material, (texture) => (material.clearCoat.tintTexture = texture));
5774
+ UpdateTexture$1(files[0], material, (texture) => (material.clearCoat.tintTexture = texture));
5640
5775
  }
5641
5776
  } })] })] })] }));
5642
5777
  };
@@ -5645,11 +5780,11 @@ const PBRBaseMaterialIridescenceProperties = (props) => {
5645
5780
  const isEnabled = useProperty(material.iridescence, "isEnabled");
5646
5781
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enabled", target: material.iridescence, propertyKey: "isEnabled" }), jsxs(Collapse, { visible: isEnabled, children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Intensity", target: material.iridescence, propertyKey: "intensity", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "IOR", description: "Index of Refraction", target: material.iridescence, propertyKey: "indexOfRefraction", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Minimum Thickness", target: material.iridescence, propertyKey: "minimumThickness", min: 0, max: 1000, step: 10 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Maxium Thickness", target: material.iridescence, propertyKey: "maximumThickness", min: 0, max: 1000, step: 10 }), jsx(FileUploadLine, { label: "Iridescence", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5647
5782
  if (files.length > 0) {
5648
- UpdateTexture(files[0], material, (texture) => (material.iridescence.texture = texture));
5783
+ UpdateTexture$1(files[0], material, (texture) => (material.iridescence.texture = texture));
5649
5784
  }
5650
5785
  } }), jsx(FileUploadLine, { label: "Thickness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5651
5786
  if (files.length > 0) {
5652
- UpdateTexture(files[0], material, (texture) => (material.iridescence.thicknessTexture = texture));
5787
+ UpdateTexture$1(files[0], material, (texture) => (material.iridescence.thicknessTexture = texture));
5653
5788
  }
5654
5789
  } })] })] }));
5655
5790
  };
@@ -5658,7 +5793,7 @@ const PBRBaseMaterialAnisotropicProperties = (props) => {
5658
5793
  const isEnabled = useProperty(material.anisotropy, "isEnabled");
5659
5794
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enabled", target: material.anisotropy, propertyKey: "isEnabled" }), jsxs(Collapse, { visible: isEnabled, children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Legacy Mode", target: material.anisotropy, propertyKey: "legacy" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Intensity", target: material.anisotropy, propertyKey: "intensity", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: Vector2PropertyLine, label: "Direction", target: material.anisotropy, propertyKey: "direction" }), jsx(FileUploadLine, { label: "Anisotropic", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5660
5795
  if (files.length > 0) {
5661
- UpdateTexture(files[0], material, (texture) => (material.anisotropy.texture = texture));
5796
+ UpdateTexture$1(files[0], material, (texture) => (material.anisotropy.texture = texture));
5662
5797
  }
5663
5798
  } })] })] }));
5664
5799
  };
@@ -5668,11 +5803,11 @@ const PBRBaseMaterialSheenProperties = (props) => {
5668
5803
  const useRoughness = useProperty(material.sheen, "_useRoughness");
5669
5804
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enabled", target: material.sheen, propertyKey: "isEnabled" }), jsxs(Collapse, { visible: isEnabled, children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Link to Albedo", target: material.sheen, propertyKey: "linkSheenWithAlbedo" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Intensity", target: material.sheen, propertyKey: "intensity", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Color", target: material.sheen, propertyKey: "color", isLinearMode: true }), jsx(FileUploadLine, { label: "Sheen", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5670
5805
  if (files.length > 0) {
5671
- UpdateTexture(files[0], material, (texture) => (material.sheen.texture = texture));
5806
+ UpdateTexture$1(files[0], material, (texture) => (material.sheen.texture = texture));
5672
5807
  }
5673
5808
  } }), jsx(FileUploadLine, { label: "Roughness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5674
5809
  if (files.length > 0) {
5675
- UpdateTexture(files[0], material, (texture) => (material.sheen.textureRoughness = texture));
5810
+ UpdateTexture$1(files[0], material, (texture) => (material.sheen.textureRoughness = texture));
5676
5811
  }
5677
5812
  } }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Roughness", target: material.sheen, propertyKey: "_useRoughness" }), jsx(Collapse, { visible: useRoughness, children: jsx(BoundProperty, { nullable: true, component: SyncedSliderPropertyLine, label: "Roughness", target: material.sheen, propertyKey: "roughness", defaultValue: 0, min: 0, max: 1, step: 0.01 }) }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Roughness from Main Texture", target: material.sheen, propertyKey: "useRoughnessFromMainTexture" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Albedo Scaling", target: material.sheen, propertyKey: "albedoScaling" })] })] }));
5678
5813
  };
@@ -5687,6 +5822,174 @@ const PBRMaterialLightingAndColorProperties = (props) => {
5687
5822
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Albedo", target: material, propertyKey: "albedoColor", isLinearMode: true }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeight", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Reflectivity", target: material, propertyKey: "reflectivityColor", isLinearMode: true }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Micro-Surface", target: material, propertyKey: "microSurface", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Emissive", target: material, propertyKey: "emissiveColor", isLinearMode: true }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Ambient", target: material, propertyKey: "ambientColor", isLinearMode: true }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Physical Light Falloff", target: material, propertyKey: "usePhysicalLightFalloff" })] }));
5688
5823
  };
5689
5824
 
5825
+ // TODO: ryamtrem / gehalper This function is temporal until there is a line control to handle texture links (similar to the old TextureLinkLineComponent)
5826
+ const UpdateTexture = (file, material, textureSetter) => {
5827
+ ReadFile(file, (data) => {
5828
+ const blob = new Blob([data], { type: "octet/stream" });
5829
+ const url = URL.createObjectURL(blob);
5830
+ textureSetter(new Texture(url, material.getScene(), false, false));
5831
+ }, undefined, true);
5832
+ };
5833
+ /**
5834
+ * Displays the base layer properties of an OpenPBR material.
5835
+ * @param props - The required properties
5836
+ * @returns A JSX element representing the base layer properties.
5837
+ */
5838
+ const OpenPBRMaterialBaseProperties = (props) => {
5839
+ const { material } = props;
5840
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Base Weight", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5841
+ if (files.length > 0) {
5842
+ UpdateTexture(files[0], material, (texture) => (material.baseWeightTexture = texture));
5843
+ }
5844
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Base Color", target: material, propertyKey: "baseColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Base Color", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5845
+ if (files.length > 0) {
5846
+ UpdateTexture(files[0], material, (texture) => (material.baseColorTexture = texture));
5847
+ }
5848
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Metalness", target: material, propertyKey: "baseMetalness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Base Metalness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5849
+ if (files.length > 0) {
5850
+ UpdateTexture(files[0], material, (texture) => (material.baseMetalnessTexture = texture));
5851
+ }
5852
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Base Diffuse Roughness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5853
+ if (files.length > 0) {
5854
+ UpdateTexture(files[0], material, (texture) => (material.baseDiffuseRoughnessTexture = texture));
5855
+ }
5856
+ } })] }));
5857
+ };
5858
+ /**
5859
+ * Displays the specular layer properties of an OpenPBR material.
5860
+ * @param props - The required properties
5861
+ * @returns A JSX element representing the specular layer properties.
5862
+ */
5863
+ const OpenPBRMaterialSpecularProperties = (props) => {
5864
+ const { material } = props;
5865
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Weight", target: material, propertyKey: "specularWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Weight", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5866
+ if (files.length > 0) {
5867
+ UpdateTexture(files[0], material, (texture) => (material.specularWeightTexture = texture));
5868
+ }
5869
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Specular Color", target: material, propertyKey: "specularColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Specular Color", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5870
+ if (files.length > 0) {
5871
+ UpdateTexture(files[0], material, (texture) => (material.specularColorTexture = texture));
5872
+ }
5873
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness", target: material, propertyKey: "specularRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Roughness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5874
+ if (files.length > 0) {
5875
+ UpdateTexture(files[0], material, (texture) => (material.specularRoughnessTexture = texture));
5876
+ }
5877
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular Roughness Anisotropy", target: material, propertyKey: "specularRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Specular Roughness Anisotropy", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5878
+ if (files.length > 0) {
5879
+ UpdateTexture(files[0], material, (texture) => (material.specularRoughnessAnisotropyTexture = texture));
5880
+ }
5881
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Specular IOR", target: material, propertyKey: "specularIor", min: 1, max: 3, step: 0.01 })] }));
5882
+ };
5883
+ /**
5884
+ * Displays the coat layer properties of an OpenPBR material.
5885
+ * @param props - The required properties
5886
+ * @returns A JSX element representing the coat layer properties.
5887
+ */
5888
+ const OpenPBRMaterialCoatProperties = (props) => {
5889
+ const { material } = props;
5890
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Weight", target: material, propertyKey: "coatWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Weight", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5891
+ if (files.length > 0) {
5892
+ UpdateTexture(files[0], material, (texture) => (material.coatWeightTexture = texture));
5893
+ }
5894
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Coat Color", target: material, propertyKey: "coatColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Coat Color", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5895
+ if (files.length > 0) {
5896
+ UpdateTexture(files[0], material, (texture) => (material.coatColorTexture = texture));
5897
+ }
5898
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness", target: material, propertyKey: "coatRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5899
+ if (files.length > 0) {
5900
+ UpdateTexture(files[0], material, (texture) => (material.coatRoughnessTexture = texture));
5901
+ }
5902
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Roughness Anisotropy", target: material, propertyKey: "coatRoughnessAnisotropy", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Roughness Anisotropy", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5903
+ if (files.length > 0) {
5904
+ UpdateTexture(files[0], material, (texture) => (material.coatRoughnessAnisotropyTexture = texture));
5905
+ }
5906
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat IOR", target: material, propertyKey: "coatIor", min: 1, max: 3, step: 0.01 }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Darkening", target: material, propertyKey: "coatDarkening", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Coat Darkening", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5907
+ if (files.length > 0) {
5908
+ UpdateTexture(files[0], material, (texture) => (material.coatDarkeningTexture = texture));
5909
+ }
5910
+ } })] }));
5911
+ };
5912
+ /**
5913
+ * Displays the fuzz layer properties of an OpenPBR material.
5914
+ * @param props - The required properties
5915
+ * @returns A JSX element representing the fuzz layer properties.
5916
+ */
5917
+ const OpenPBRMaterialFuzzProperties = (props) => {
5918
+ const { material } = props;
5919
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Weight", target: material, propertyKey: "fuzzWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Weight", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5920
+ if (files.length > 0) {
5921
+ UpdateTexture(files[0], material, (texture) => (material.fuzzWeightTexture = texture));
5922
+ }
5923
+ } }), jsx(BoundProperty, { component: Color3PropertyLine, label: "Fuzz Color", target: material, propertyKey: "fuzzColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Fuzz Color", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5924
+ if (files.length > 0) {
5925
+ UpdateTexture(files[0], material, (texture) => (material.fuzzColorTexture = texture));
5926
+ }
5927
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Fuzz Roughness", target: material, propertyKey: "fuzzRoughness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Fuzz Roughness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5928
+ if (files.length > 0) {
5929
+ UpdateTexture(files[0], material, (texture) => (material.fuzzRoughnessTexture = texture));
5930
+ }
5931
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Number of Samples", target: material, propertyKey: "fuzzSampleNumber", min: 4, max: 64, step: 1 })] }));
5932
+ };
5933
+ /**
5934
+ * Displays the emission properties of an OpenPBR material.
5935
+ * @param props - The required properties
5936
+ * @returns A JSX element representing the emission properties.
5937
+ */
5938
+ const OpenPBRMaterialEmissionProperties = (props) => {
5939
+ const { material } = props;
5940
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color3PropertyLine, label: "Emission Color", target: material, propertyKey: "emissionColor", isLinearMode: true }), jsx(FileUploadLine, { label: "Emission Color", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5941
+ if (files.length > 0) {
5942
+ UpdateTexture(files[0], material, (texture) => (material.emissionColorTexture = texture));
5943
+ }
5944
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Emission Luminance", target: material, propertyKey: "emissionLuminance", min: 0, max: 10, step: 0.01 })] }));
5945
+ };
5946
+ /**
5947
+ * Displays the thin film properties of an OpenPBR material.
5948
+ * @param props - The required properties
5949
+ * @returns A JSX element representing the thin film properties.
5950
+ */
5951
+ const OpenPBRMaterialThinFilmProperties = (props) => {
5952
+ const { material } = props;
5953
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Weight", target: material, propertyKey: "thinFilmWeight", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Weight", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5954
+ if (files.length > 0) {
5955
+ UpdateTexture(files[0], material, (texture) => (material.thinFilmWeightTexture = texture));
5956
+ }
5957
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film Thickness", target: material, propertyKey: "thinFilmThickness", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Thin Film Thickness", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5958
+ if (files.length > 0) {
5959
+ UpdateTexture(files[0], material, (texture) => (material.thinFilmThicknessTexture = texture));
5960
+ }
5961
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Thin Film IOR", target: material, propertyKey: "thinFilmIor", min: 1, max: 3, step: 0.01 })] }));
5962
+ };
5963
+ /**
5964
+ * Displays the geometry properties of an OpenPBR material.
5965
+ * @param props - The required properties
5966
+ * @returns A JSX element representing the geometry properties.
5967
+ */
5968
+ const OpenPBRMaterialGeometryProperties = (props) => {
5969
+ const { material } = props;
5970
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Opacity", target: material, propertyKey: "geometryOpacity", min: 0, max: 1, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Opacity", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5971
+ if (files.length > 0) {
5972
+ UpdateTexture(files[0], material, (texture) => (material.geometryOpacityTexture = texture));
5973
+ }
5974
+ } }), jsx(FileUploadLine, { label: "Geometry Normal", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5975
+ if (files.length > 0) {
5976
+ UpdateTexture(files[0], material, (texture) => (material.geometryNormalTexture = texture));
5977
+ }
5978
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Tangent Angle", target: material, propertyKey: "geometryTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Tangent", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5979
+ if (files.length > 0) {
5980
+ UpdateTexture(files[0], material, (texture) => (material.geometryTangentTexture = texture));
5981
+ }
5982
+ } }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Coat Tangent Angle", target: material, propertyKey: "geometryCoatTangentAngle", min: 0, max: Math.PI, step: 0.01 }), jsx(FileUploadLine, { label: "Geometry Coat Normal", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5983
+ if (files.length > 0) {
5984
+ UpdateTexture(files[0], material, (texture) => (material.geometryCoatNormalTexture = texture));
5985
+ }
5986
+ } }), jsx(FileUploadLine, { label: "Geometry Coat Tangent", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
5987
+ if (files.length > 0) {
5988
+ UpdateTexture(files[0], material, (texture) => (material.geometryCoatTangentTexture = texture));
5989
+ }
5990
+ } })] }));
5991
+ };
5992
+
5690
5993
  const SkyMaterialProperties = (props) => {
5691
5994
  const { material, settings } = props;
5692
5995
  const [toDisplayAngle, fromDisplayAngle, useDegrees] = useAngleConverters(settings);
@@ -5809,6 +6112,40 @@ const MaterialPropertiesServiceDefinition = {
5809
6112
  },
5810
6113
  ],
5811
6114
  });
6115
+ const openPBRMaterialPropertiesRegistration = propertiesService.addSectionContent({
6116
+ key: "OpenPBR Material Properties",
6117
+ predicate: (entity) => entity instanceof OpenPBRMaterial,
6118
+ content: [
6119
+ {
6120
+ section: "Base",
6121
+ component: ({ context }) => jsx(OpenPBRMaterialBaseProperties, { material: context }),
6122
+ },
6123
+ {
6124
+ section: "Specular",
6125
+ component: ({ context }) => jsx(OpenPBRMaterialSpecularProperties, { material: context }),
6126
+ },
6127
+ {
6128
+ section: "Coat",
6129
+ component: ({ context }) => jsx(OpenPBRMaterialCoatProperties, { material: context }),
6130
+ },
6131
+ {
6132
+ section: "Fuzz",
6133
+ component: ({ context }) => jsx(OpenPBRMaterialFuzzProperties, { material: context }),
6134
+ },
6135
+ {
6136
+ section: "Emission",
6137
+ component: ({ context }) => jsx(OpenPBRMaterialEmissionProperties, { material: context }),
6138
+ },
6139
+ {
6140
+ section: "Thin Film",
6141
+ component: ({ context }) => jsx(OpenPBRMaterialThinFilmProperties, { material: context }),
6142
+ },
6143
+ {
6144
+ section: "Geometry",
6145
+ component: ({ context }) => jsx(OpenPBRMaterialGeometryProperties, { material: context }),
6146
+ },
6147
+ ],
6148
+ });
5812
6149
  const skyMaterialRegistration = propertiesService.addSectionContent({
5813
6150
  key: "Sky Material Properties",
5814
6151
  predicate: (entity) => entity instanceof SkyMaterial,
@@ -5836,6 +6173,7 @@ const MaterialPropertiesServiceDefinition = {
5836
6173
  pbrBaseMaterialPropertiesRegistration.dispose();
5837
6174
  pbrMaterialPropertiesRegistration.dispose();
5838
6175
  pbrMaterialNormalMapsContentRegistration.dispose();
6176
+ openPBRMaterialPropertiesRegistration.dispose();
5839
6177
  skyMaterialRegistration.dispose();
5840
6178
  multiMaterialContentRegistration.dispose();
5841
6179
  },
@@ -9010,7 +9348,24 @@ function _ShowInspector(scene, options) {
9010
9348
  showThemeSelector: options.showThemeSelector,
9011
9349
  extensionFeeds: [DefaultInspectorExtensionFeed, ...(options.extensionFeeds ?? [])],
9012
9350
  toolbarMode: "compact",
9013
- sidePaneMode: options.embedMode ? "right" : "both",
9351
+ sidePaneRemapper: options.embedMode
9352
+ ? (sidePane) => {
9353
+ if (sidePane.horizontalLocation === "right") {
9354
+ // All right panes go to right bottom.
9355
+ return {
9356
+ horizontalLocation: "right",
9357
+ verticalLocation: "bottom",
9358
+ };
9359
+ }
9360
+ else {
9361
+ // All left panes go to right top.
9362
+ return {
9363
+ horizontalLocation: "right",
9364
+ verticalLocation: "top",
9365
+ };
9366
+ }
9367
+ }
9368
+ : undefined,
9014
9369
  });
9015
9370
  disposeActions.push(() => modularTool.dispose());
9016
9371
  let disposed = false;
@@ -9302,5 +9657,5 @@ const TextAreaPropertyLine = (props) => {
9302
9657
  return (jsx(PropertyLine, { ...props, children: jsx(Textarea, { ...props }) }));
9303
9658
  };
9304
9659
 
9305
- export { Accordion, AccordionSection, BooleanBadgePropertyLine, BoundProperty, BuiltInsExtensionFeed, Button, ButtonLine, Checkbox, CheckboxPropertyLine, Collapse, Color3GradientComponent, Color3GradientList, Color3PropertyLine, Color4GradientComponent, Color4GradientList, Color4PropertyLine, ColorPickerPopup, ColorStepGradientComponent, ComboBox, ConstructorFactory, DebugServiceIdentity, DraggableLine, Dropdown, ExtensibleAccordion, FactorGradientComponent, FactorGradientList, FileUploadLine, GetPropertyDescriptor, HexPropertyLine, HideInspector, InfoLabel, InputHexField, InputHsvField, Inspector, InterceptFunction, InterceptProperty, IsInspectorVisible, IsPropertyReadonly, LineContainer, LinkPropertyLine, LinkToEntityPropertyLine, List, MakeDialogTeachingMoment, MakeLazyComponent, MakePopoverTeachingMoment, MakePropertyHook, MakeTeachingMoment, MessageBar, NumberDropdown, NumberDropdownPropertyLine, NumberInputPropertyLine, ObservableCollection, Pane, PlaceholderPropertyLine, PositionedPopover, PropertiesServiceIdentity, PropertyLine, QuaternionPropertyLine, RotationVectorPropertyLine, SceneContextIdentity, SceneExplorerServiceIdentity, SearchBar, SearchBox, SelectionServiceDefinition, SelectionServiceIdentity, SettingsContextIdentity, SettingsServiceIdentity, ShellServiceIdentity, ShowInspector, SidePaneContainer, SpinButton, SpinButtonPropertyLine, StatsServiceIdentity, StringDropdown, StringDropdownPropertyLine, StringifiedPropertyLine, Switch, SwitchPropertyLine, SyncedSliderInput, SyncedSliderPropertyLine, TeachingMoment, TextAreaPropertyLine, TextInput, TextInputPropertyLine, TextPropertyLine, Textarea, ToggleButton, ToolsServiceIdentity, Vector2PropertyLine, Vector3PropertyLine, Vector4PropertyLine, useAngleConverters, useAsyncResource, useColor3Property, useColor4Property, useCompactMode, useEventfulState, useInterceptObservable, useObservableCollection, useObservableState, useOrderedObservableCollection, usePollingObservable, useProperty, useQuaternionProperty, useResource, useVector3Property };
9660
+ export { Accordion, AccordionSection, BooleanBadgePropertyLine, BoundProperty, BuiltInsExtensionFeed, Button, ButtonLine, Checkbox, CheckboxPropertyLine, Collapse, Color3GradientComponent, Color3GradientList, Color3PropertyLine, Color4GradientComponent, Color4GradientList, Color4PropertyLine, ColorPickerPopup, ColorStepGradientComponent, ComboBox, ConstructorFactory, DebugServiceIdentity, DraggableLine, Dropdown, ExtensibleAccordion, FactorGradientComponent, FactorGradientList, FileUploadLine, GetPropertyDescriptor, HexPropertyLine, HideInspector, InfoLabel, InputHexField, InputHsvField, Inspector, InterceptFunction, InterceptProperty, IsInspectorVisible, IsPropertyReadonly, LineContainer, LinkPropertyLine, LinkToEntityPropertyLine, List, MakeDialogTeachingMoment, MakeLazyComponent, MakePopoverTeachingMoment, MakePropertyHook, MakeTeachingMoment, MessageBar, NumberDropdown, NumberDropdownPropertyLine, NumberInputPropertyLine, ObservableCollection, Pane, PlaceholderPropertyLine, PositionedPopover, PropertiesServiceIdentity, PropertyLine, QuaternionPropertyLine, RotationVectorPropertyLine, SceneContextIdentity, SceneExplorerServiceIdentity, SearchBar, SearchBox, SelectionServiceDefinition, SelectionServiceIdentity, SettingsContextIdentity, SettingsServiceIdentity, ShellServiceIdentity, ShowInspector, SidePaneContainer, SpinButton, SpinButtonPropertyLine, StatsServiceIdentity, StringDropdown, StringDropdownPropertyLine, StringifiedPropertyLine, Switch, SwitchPropertyLine, SyncedSliderInput, SyncedSliderPropertyLine, TeachingMoment, TextAreaPropertyLine, TextInput, TextInputPropertyLine, TextPropertyLine, Textarea, ToggleButton, ToolsServiceIdentity, Vector2PropertyLine, Vector3PropertyLine, Vector4PropertyLine, useAngleConverters, useAsyncResource, useColor3Property, useColor4Property, useCompactMode, useEventfulState, useInterceptObservable, useObservableCollection, useObservableState, useOrderedObservableCollection, usePollingObservable, useProperty, useQuaternionProperty, useResource, useSidePaneDockOverrides, useVector3Property };
9306
9661
  //# sourceMappingURL=index.js.map