@babylonjs/inspector 8.41.2-preview → 8.42.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.
@@ -3,7 +3,7 @@ import { createContext, useContext, useMemo, useEffect, useState, useRef, useCal
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, Link as Link$1, Body1, ToggleButton as ToggleButton$1, Button as Button$1, tokens, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createLightTheme, createDarkTheme, FluentProvider, Tooltip, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, Portal, RendererProvider, ToolbarRadioButton, createDOMRenderer, MenuGroup, MenuGroupHeader, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, treeItemLevelToken, MenuItemCheckbox, Switch as Switch$1, PresenceBadge, useId, SpinButton as SpinButton$1, Slider, Input, Dropdown as Dropdown$1, Option, Popover, PopoverTrigger, ColorSwatch, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Spinner, Badge, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Textarea as Textarea$1, ToolbarButton, useComboboxFilter, Combobox, Field } from '@fluentui/react-components';
6
+ import { makeStyles, Link as Link$1, Body1, ToggleButton as ToggleButton$1, Button as Button$1, tokens, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createLightTheme, createDarkTheme, FluentProvider, Tooltip, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, Portal, RendererProvider, ToolbarRadioButton, createDOMRenderer, MenuGroup, MenuGroupHeader, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, MenuDivider, treeItemLevelToken, MenuItemCheckbox, Switch as Switch$1, PresenceBadge, useId, SpinButton as SpinButton$1, Slider, Input, Dropdown as Dropdown$1, Option, Popover as Popover$1, PopoverTrigger, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, ColorSwatch, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Spinner, Badge, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, useComboboxFilter, Combobox, Textarea as Textarea$1, ToolbarButton, Field } from '@fluentui/react-components';
7
7
  import { ChevronCircleRight16Regular, ChevronCircleRight20Regular, ChevronCircleDown16Regular, ChevronCircleDown20Regular, Copy16Regular, Copy20Regular, PanelLeftExpandRegular, PanelRightExpandRegular, PanelLeftContractRegular, PanelRightContractRegular, PictureInPictureEnterRegular, MoreHorizontalRegular, LayoutColumnTwoFocusLeftFilled, LayoutColumnTwoSplitLeftFocusTopLeftFilled, LayoutColumnTwoSplitLeftFocusBottomLeftFilled, LayoutColumnTwoFocusRightFilled, LayoutColumnTwoSplitRightFocusTopRightFilled, LayoutColumnTwoSplitRightFocusBottomRightFilled, DocumentTextRegular, createFluentIcon, FilterRegular, GlobeRegular, ArrowExpandAllRegular, ArrowCollapseAllRegular, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, 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, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
8
8
  import { Collapse as Collapse$1, Fade } from '@fluentui/react-motion-components-preview';
9
9
  import '@babylonjs/core/Misc/typeStore.js';
@@ -72,6 +72,7 @@ import { Constants } from '@babylonjs/core/Engines/constants.js';
72
72
  import { Engine } from '@babylonjs/core/Engines/engine.js';
73
73
  import { ParticleSystem } from '@babylonjs/core/Particles/particleSystem.js';
74
74
  import { ReadFile } from '@babylonjs/core/Misc/fileTools.js';
75
+ import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture.js';
75
76
  import { Mesh } from '@babylonjs/core/Meshes/mesh.js';
76
77
  import { SkeletonViewer } from '@babylonjs/core/Debug/skeletonViewer.js';
77
78
  import { VertexBuffer } from '@babylonjs/core/Meshes/buffer.js';
@@ -89,16 +90,15 @@ import { TransformNode } from '@babylonjs/core/Meshes/transformNode.js';
89
90
  import { PhysicsPrestepType } from '@babylonjs/core/Physics/v2/IPhysicsEnginePlugin.js';
90
91
  import '@babylonjs/core/Physics/v2/physicsEngineComponent.js';
91
92
  import { PostProcess } from '@babylonjs/core/PostProcesses/postProcess.js';
92
- import { CubeTexture } from '@babylonjs/core/Materials/Textures/cubeTexture.js';
93
93
  import { ImageProcessingConfiguration } from '@babylonjs/core/Materials/imageProcessingConfiguration.js';
94
94
  import { Skeleton } from '@babylonjs/core/Bones/skeleton.js';
95
95
  import { Sprite } from '@babylonjs/core/Sprites/sprite.js';
96
96
  import { SpriteManager } from '@babylonjs/core/Sprites/spriteManager.js';
97
+ import { WhenTextureReadyAsync, GetTextureDataAsync } from '@babylonjs/core/Misc/textureTools.js';
97
98
  import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture.js';
98
99
  import { MultiRenderTarget } from '@babylonjs/core/Materials/Textures/multiRenderTarget.js';
99
100
  import { RenderTargetTexture } from '@babylonjs/core/Materials/Textures/renderTargetTexture.js';
100
101
  import { ThinTexture } from '@babylonjs/core/Materials/Textures/thinTexture.js';
101
- import { WhenTextureReadyAsync, GetTextureDataAsync } from '@babylonjs/core/Misc/textureTools.js';
102
102
  import { ClusteredLightContainer } from '@babylonjs/core/Lights/Clustered/clusteredLightContainer.js';
103
103
  import '@babylonjs/core/Rendering/boundingBoxRenderer.js';
104
104
  import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineManagerSceneComponent.js';
@@ -682,13 +682,13 @@ const ToggleButton = (props) => {
682
682
  return (jsx(ToggleButton$1, { title: title, size: size, icon: checked ? jsx(props.checkedIcon, {}) : props.uncheckedIcon ? jsx(props.uncheckedIcon, {}) : jsx(props.checkedIcon, {}), appearance: appearance, checked: checked, onClick: toggle }));
683
683
  };
684
684
 
685
- const Button = (props) => {
686
- Button.displayName = "Button";
685
+ const Button = forwardRef((props, ref) => {
687
686
  const { size } = useContext(ToolContext);
688
687
  // eslint-disable-next-line @typescript-eslint/naming-convention
689
688
  const { icon: Icon, label, ...buttonProps } = props;
690
- return (jsx(Button$1, { iconPosition: "after", ...buttonProps, size: size, icon: Icon && jsx(Icon, {}), children: label && props.label }));
691
- };
689
+ return (jsx(Button$1, { ref: ref, iconPosition: "after", ...buttonProps, size: size, icon: Icon && jsx(Icon, {}), children: label && props.label }));
690
+ });
691
+ Button.displayName = "Button";
692
692
 
693
693
  const CustomTokens = {
694
694
  inputWidth: "150px",
@@ -885,7 +885,7 @@ const LinkToEntityPropertyLine = (props) => {
885
885
  !linkedEntity.reservedDataStore?.hidden && jsx(LinkPropertyLine, { ...rest, value: linkedEntity.name, onLink: () => (selectionService.selectedEntity = linkedEntity) }));
886
886
  };
887
887
 
888
- const useStyles$h = makeStyles({
888
+ const useStyles$j = makeStyles({
889
889
  accordion: {
890
890
  overflowX: "hidden",
891
891
  overflowY: "auto",
@@ -929,13 +929,13 @@ const useStyles$h = makeStyles({
929
929
  });
930
930
  const AccordionSection = (props) => {
931
931
  AccordionSection.displayName = "AccordionSection";
932
- const classes = useStyles$h();
932
+ const classes = useStyles$j();
933
933
  return jsx("div", { className: classes.panelDiv, children: props.children });
934
934
  };
935
935
  const StringAccordion = Accordion$1;
936
936
  const Accordion = forwardRef((props, ref) => {
937
937
  Accordion.displayName = "Accordion";
938
- const classes = useStyles$h();
938
+ const classes = useStyles$j();
939
939
  const { size } = useContext(ToolContext);
940
940
  const { children, highlightSections, ...rest } = props;
941
941
  const validChildren = useMemo(() => {
@@ -1067,7 +1067,7 @@ function AsReadonlyArray(array) {
1067
1067
  return array;
1068
1068
  }
1069
1069
  // eslint-disable-next-line @typescript-eslint/naming-convention
1070
- const useStyles$g = makeStyles({
1070
+ const useStyles$i = makeStyles({
1071
1071
  rootDiv: {
1072
1072
  flex: 1,
1073
1073
  overflow: "hidden",
@@ -1076,7 +1076,7 @@ const useStyles$g = makeStyles({
1076
1076
  },
1077
1077
  });
1078
1078
  function ExtensibleAccordion(props) {
1079
- const classes = useStyles$g();
1079
+ const classes = useStyles$i();
1080
1080
  const { children, sections, sectionContent, context, sectionsRef } = props;
1081
1081
  const defaultSections = useMemo(() => {
1082
1082
  const defaultSections = [];
@@ -1181,7 +1181,7 @@ function ExtensibleAccordion(props) {
1181
1181
  })] }) })) }));
1182
1182
  }
1183
1183
 
1184
- const useStyles$f = makeStyles({
1184
+ const useStyles$h = makeStyles({
1185
1185
  paneRootDiv: {
1186
1186
  display: "flex",
1187
1187
  flex: 1,
@@ -1194,12 +1194,12 @@ const useStyles$f = makeStyles({
1194
1194
  */
1195
1195
  const SidePaneContainer = forwardRef((props, ref) => {
1196
1196
  const { className, ...rest } = props;
1197
- const classes = useStyles$f();
1197
+ const classes = useStyles$h();
1198
1198
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
1199
1199
  });
1200
1200
 
1201
1201
  // eslint-disable-next-line @typescript-eslint/naming-convention
1202
- const useStyles$e = makeStyles({
1202
+ const useStyles$g = makeStyles({
1203
1203
  extensionTeachingPopover: {
1204
1204
  maxWidth: "320px",
1205
1205
  },
@@ -1210,7 +1210,7 @@ const useStyles$e = makeStyles({
1210
1210
  * @returns The teaching moment popover.
1211
1211
  */
1212
1212
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
1213
- const classes = useStyles$e();
1213
+ const classes = useStyles$g();
1214
1214
  return (jsx(TeachingPopover, { appearance: "brand", open: shouldDisplay, positioning: { positioningRef }, onOpenChange: onOpenChange, children: jsxs(TeachingPopoverSurface, { className: classes.extensionTeachingPopover, children: [jsx(TeachingPopoverHeader, { children: title }), jsx(TeachingPopoverBody, { children: description })] }) }));
1215
1215
  };
1216
1216
 
@@ -1466,13 +1466,13 @@ function ConstructorFactory(constructor) {
1466
1466
  }
1467
1467
 
1468
1468
  // eslint-disable-next-line @typescript-eslint/naming-convention
1469
- const useStyles$d = makeStyles({
1469
+ const useStyles$f = makeStyles({
1470
1470
  placeholderDiv: {
1471
1471
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
1472
1472
  },
1473
1473
  });
1474
1474
  const PropertiesPane = (props) => {
1475
- const classes = useStyles$d();
1475
+ const classes = useStyles$f();
1476
1476
  const entity = props.context;
1477
1477
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
1478
1478
  };
@@ -1613,7 +1613,7 @@ function useResizeHandle(params) {
1613
1613
  const RootComponentServiceIdentity = Symbol("RootComponent");
1614
1614
  const ShellServiceIdentity = Symbol("ShellService");
1615
1615
  // eslint-disable-next-line @typescript-eslint/naming-convention
1616
- const useStyles$c = makeStyles({
1616
+ const useStyles$e = makeStyles({
1617
1617
  mainView: {
1618
1618
  flex: 1,
1619
1619
  display: "flex",
@@ -1801,12 +1801,12 @@ const DockMenu = (props) => {
1801
1801
  };
1802
1802
  const PaneHeader = (props) => {
1803
1803
  const { id, title, dockOptions } = props;
1804
- const classes = useStyles$c();
1804
+ const classes = useStyles$e();
1805
1805
  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, {}) }) })] }) }));
1806
1806
  };
1807
1807
  // 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.
1808
1808
  const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
1809
- const classes = useStyles$c();
1809
+ const classes = useStyles$e();
1810
1810
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
1811
1811
  const teachingMoment = useTeachingMoment(suppressTeachingMoment);
1812
1812
  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, {}) })] }));
@@ -1814,7 +1814,7 @@ const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Comp
1814
1814
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
1815
1815
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
1816
1816
  const Toolbar = ({ location, components }) => {
1817
- const classes = useStyles$c();
1817
+ const classes = useStyles$e();
1818
1818
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
1819
1819
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
1820
1820
  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))) })] })) }));
@@ -1824,7 +1824,7 @@ const SidePaneTab = (props) => {
1824
1824
  const { location, id, isSelected, dockOptions,
1825
1825
  // eslint-disable-next-line @typescript-eslint/naming-convention
1826
1826
  icon: Icon, title, suppressTeachingMoment, } = props;
1827
- const classes = useStyles$c();
1827
+ const classes = useStyles$e();
1828
1828
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
1829
1829
  const teachingMoment = useTeachingMoment(suppressTeachingMoment);
1830
1830
  const tabClass = mergeClasses(classes.tab, isSelected ? undefined : classes.unselectedTab);
@@ -1837,7 +1837,7 @@ const SidePaneTab = (props) => {
1837
1837
  // In "compact" mode, the tab list is integrated into the pane itself.
1838
1838
  // In "full" mode, the returned tab list is later injected into the toolbar.
1839
1839
  function usePane(location, layoutMode, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems) {
1840
- const classes = useStyles$c();
1840
+ const classes = useStyles$e();
1841
1841
  const [topSelectedTab, setTopSelectedTab] = useState();
1842
1842
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
1843
1843
  const [collapsed, setCollapsed] = useState(false);
@@ -2077,7 +2077,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
2077
2077
  undock: () => onDockChanged.notifyObservers({ location: "right", dock: false }),
2078
2078
  };
2079
2079
  const rootComponent = () => {
2080
- const classes = useStyles$c();
2080
+ const classes = useStyles$e();
2081
2081
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSidePaneDockOverrides();
2082
2082
  // This function returns a promise that resolves after the dock change takes effect so that
2083
2083
  // we can then select the re-docked pane.
@@ -2513,7 +2513,7 @@ function useCommandContextMenuState(commands) {
2513
2513
  return [checkedContextMenuItems, onContextMenuCheckedValueChange, contextMenuItems];
2514
2514
  }
2515
2515
  // eslint-disable-next-line @typescript-eslint/naming-convention
2516
- const useStyles$b = makeStyles({
2516
+ const useStyles$d = makeStyles({
2517
2517
  rootDiv: {
2518
2518
  flex: 1,
2519
2519
  overflow: "hidden",
@@ -2581,14 +2581,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
2581
2581
  }
2582
2582
  const SceneTreeItem = (props) => {
2583
2583
  const { isSelected, select } = props;
2584
- const classes = useStyles$b();
2584
+ const classes = useStyles$d();
2585
2585
  const [compactMode] = useCompactMode();
2586
2586
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
2587
2587
  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: treeItemLayoutClass, style: isSelected ? { backgroundColor: tokens.colorNeutralBackground1Selected } : undefined, children: jsx(Body1Strong, { wrap: false, truncate: true, children: "Scene" }) }) }, "scene"));
2588
2588
  };
2589
2589
  const SectionTreeItem = (props) => {
2590
2590
  const { section, isFiltering, commandProviders, expandAll, collapseAll } = props;
2591
- const classes = useStyles$b();
2591
+ const classes = useStyles$d();
2592
2592
  const [compactMode] = useCompactMode();
2593
2593
  // Get the commands that apply to this section.
2594
2594
  const commands = useResource(useCallback(() => {
@@ -2605,7 +2605,7 @@ const SectionTreeItem = (props) => {
2605
2605
  };
2606
2606
  const EntityTreeItem = (props) => {
2607
2607
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll } = props;
2608
- const classes = useStyles$b();
2608
+ const classes = useStyles$d();
2609
2609
  const [compactMode] = useCompactMode();
2610
2610
  const hasChildren = !!entityItem.children?.length;
2611
2611
  const displayInfo = useResource(useCallback(() => {
@@ -2698,7 +2698,7 @@ const EntityTreeItem = (props) => {
2698
2698
  }, children: jsx(Body1, { wrap: false, truncate: true, children: name }) }) }, entityItem.entity.uniqueId) }), jsx(MenuPopover, { hidden: !hasChildren && contextMenuCommands.length === 0, children: jsxs(MenuList, { children: [hasChildren && (jsxs(Fragment, { children: [jsx(MenuItem, { icon: jsx(ArrowExpandAllRegular, {}), onClick: expandAll, children: jsx(Body1, { children: "Expand All" }) }), jsx(MenuItem, { icon: jsx(ArrowCollapseAllRegular, {}), onClick: collapseAll, children: jsx(Body1, { children: "Collapse All" }) })] })), hasChildren && contextMenuCommands.length > 0 && jsx(MenuDivider, {}), contextMenuItems] }) })] }));
2699
2699
  };
2700
2700
  const SceneExplorer = (props) => {
2701
- const classes = useStyles$b();
2701
+ const classes = useStyles$d();
2702
2702
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity } = props;
2703
2703
  const [openItems, setOpenItems] = useState(new Set());
2704
2704
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -3347,20 +3347,37 @@ const FrameStepsStats = ({ context: scene }) => {
3347
3347
  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")] }));
3348
3348
  };
3349
3349
 
3350
- const FileUploadLine = (props) => {
3351
- FileUploadLine.displayName = "FileUploadLine";
3350
+ /**
3351
+ * A button that triggers a file upload dialog.
3352
+ * Combines a Button with a hidden file input.
3353
+ * @param props UploadButtonProps
3354
+ * @returns UploadButton component
3355
+ */
3356
+ const UploadButton = (props) => {
3357
+ const { onUpload, accept, label, ...buttonProps } = props;
3358
+ UploadButton.displayName = "UploadButton";
3352
3359
  const inputRef = useRef(null);
3353
- const handleButtonClick = () => {
3360
+ const handleClick = () => {
3354
3361
  inputRef.current?.click();
3355
3362
  };
3356
3363
  const handleChange = (evt) => {
3357
3364
  const files = evt.target.files;
3358
3365
  if (files && files.length) {
3359
- props.onClick(files);
3366
+ onUpload(files);
3360
3367
  }
3361
3368
  evt.target.value = "";
3362
3369
  };
3363
- return (jsxs(Fragment, { children: [jsx(ButtonLine, { onClick: handleButtonClick, icon: ArrowUploadRegular, label: props.label }), jsx("input", { ref: inputRef, type: "file", accept: props.accept, style: { display: "none" }, onChange: handleChange })] }));
3370
+ return (jsxs(Fragment, { children: [jsx(Button, { icon: ArrowUploadRegular, title: label ?? "Upload", label: label, onClick: handleClick, ...buttonProps }), jsx("input", { ref: inputRef, type: "file", accept: accept, style: { display: "none" }, onChange: handleChange })] }));
3371
+ };
3372
+
3373
+ /**
3374
+ * A full-width line with an upload button.
3375
+ * For just the button without the line wrapper, use UploadButton directly.
3376
+ * @returns An UploadButton wrapped in a LineContainer
3377
+ */
3378
+ const FileUploadLine = ({ onClick, label, accept, ...buttonProps }) => {
3379
+ FileUploadLine.displayName = "FileUploadLine";
3380
+ return (jsx(LineContainer, { children: jsx(UploadButton, { onUpload: onClick, accept: accept, label: label, ...buttonProps }) }));
3364
3381
  };
3365
3382
 
3366
3383
  var PerfMetadataCategory;
@@ -3476,14 +3493,14 @@ const TextPropertyLine = (props) => {
3476
3493
  return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value }) }));
3477
3494
  };
3478
3495
 
3479
- const useStyles$a = makeStyles({
3496
+ const useStyles$c = makeStyles({
3480
3497
  pinnedStatsPane: {
3481
3498
  flex: "0 1 auto",
3482
3499
  paddingBottom: tokens.spacingHorizontalM,
3483
3500
  },
3484
3501
  });
3485
3502
  const StatsPane = (props) => {
3486
- const classes = useStyles$a();
3503
+ const classes = useStyles$c();
3487
3504
  const scene = props.context;
3488
3505
  const engine = scene.getEngine();
3489
3506
  const fps = useObservableState(() => Math.round(engine.getFps()), engine.onBeginFrameObservable);
@@ -3661,7 +3678,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3661
3678
  keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
3662
3679
  ...BabylonWebResources,
3663
3680
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3664
- getExtensionModuleAsync: async () => await import('./exportService-BnE7xiJF.js'),
3681
+ getExtensionModuleAsync: async () => await import('./exportService-LaUVRgd_.js'),
3665
3682
  },
3666
3683
  {
3667
3684
  name: "Capture Tools",
@@ -3669,7 +3686,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3669
3686
  keywords: ["capture", "screenshot", "gif", "video", "tools"],
3670
3687
  ...BabylonWebResources,
3671
3688
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3672
- getExtensionModuleAsync: async () => await import('./captureService-Bu7areEZ.js'),
3689
+ getExtensionModuleAsync: async () => await import('./captureService-CbpbBn5F.js'),
3673
3690
  },
3674
3691
  {
3675
3692
  name: "Import Tools",
@@ -3677,7 +3694,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3677
3694
  keywords: ["import", "tools"],
3678
3695
  ...BabylonWebResources,
3679
3696
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3680
- getExtensionModuleAsync: async () => await import('./importService-CF3uudG4.js'),
3697
+ getExtensionModuleAsync: async () => await import('./importService-B98QFvNM.js'),
3681
3698
  },
3682
3699
  {
3683
3700
  name: "Quick Creation Tools (Preview)",
@@ -3685,7 +3702,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3685
3702
  keywords: ["creation", "tools"],
3686
3703
  ...BabylonWebResources,
3687
3704
  author: { name: "Babylon.js", forumUserName: "" },
3688
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-C-yzV033.js'),
3705
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-Bg2plbI-.js'),
3689
3706
  },
3690
3707
  ]);
3691
3708
 
@@ -3903,6 +3920,29 @@ const Dropdown = (props) => {
3903
3920
  const NumberDropdown = Dropdown;
3904
3921
  const StringDropdown = Dropdown;
3905
3922
 
3923
+ const useStyles$b = makeStyles({
3924
+ surface: {
3925
+ maxWidth: "400px",
3926
+ },
3927
+ content: {
3928
+ display: "flex",
3929
+ flexDirection: "column",
3930
+ gap: tokens.spacingVerticalM,
3931
+ padding: tokens.spacingHorizontalL,
3932
+ minWidth: "300px",
3933
+ },
3934
+ });
3935
+ const Popover = (props) => {
3936
+ const { children } = props;
3937
+ const [popoverOpen, setPopoverOpen] = useState(false);
3938
+ const classes = useStyles$b();
3939
+ return (jsxs(Popover$1, { open: popoverOpen, onOpenChange: (_, data) => setPopoverOpen(data.open), positioning: {
3940
+ align: "start",
3941
+ overflowBoundary: document.body,
3942
+ autoSize: true,
3943
+ }, trapFocus: true, children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: props.trigger ?? jsx(Button, { icon: props.icon, onClick: () => setPopoverOpen(true) }) }), jsx(PopoverSurface, { className: classes.surface, children: jsx("div", { className: classes.content, children: children }) })] }));
3944
+ };
3945
+
3906
3946
  const useColorPickerStyles = makeStyles({
3907
3947
  container: {
3908
3948
  width: "350px",
@@ -3954,7 +3994,6 @@ const ColorPickerPopup = (props) => {
3954
3994
  ColorPickerPopup.displayName = "ColorPickerPopup";
3955
3995
  const classes = useColorPickerStyles();
3956
3996
  const [color, setColor] = useState(props.value);
3957
- const [popoverOpen, setPopoverOpen] = useState(false);
3958
3997
  const [isLinear, setIsLinear] = useState(props.isLinearMode ?? false);
3959
3998
  const [isFloat, setFloat] = useState(false);
3960
3999
  const { size } = useContext(ToolContext);
@@ -3972,23 +4011,19 @@ const ColorPickerPopup = (props) => {
3972
4011
  setColor(newColor);
3973
4012
  props.onChange(newColor); // Ensures the parent is notified when color changes from within colorPicker
3974
4013
  };
3975
- return (jsxs(Popover, { positioning: {
3976
- align: "start",
3977
- overflowBoundary: document.body,
3978
- autoSize: true,
3979
- }, open: popoverOpen, trapFocus: true, onOpenChange: (_, data) => setPopoverOpen(data.open), children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: jsx(ColorSwatch, { borderColor: tokens.colorNeutralShadowKeyDarker, size: size === "small" ? "extra-small" : "small", shape: "rounded", color: color.toHexString(), value: color.toHexString().slice(1) }) }), jsx(PopoverSurface, { children: jsxs("div", { className: classes.container, children: [jsxs(ColorPicker, { className: classes.colorPicker, color: rgbaToHsv(color), onColorChange: handleColorPickerChange, children: [jsx(ColorArea, { inputX: { "aria-label": "Saturation" }, inputY: { "aria-label": "Brightness" } }), jsx(ColorSlider, { "aria-label": "Hue" }), color instanceof Color4 && jsx(AlphaSlider, { "aria-label": "Alpha" })] }), jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
3980
- label: "Color Space",
3981
- info: jsx(Body1, { children: "Today this is not mutable as the color space is determined by the entity. Soon we will allow swapping" }),
3982
- }, options: [
3983
- { label: "Gamma", value: 0 },
3984
- { label: "Linear", value: 1 },
3985
- ], disabled: true, value: isLinear ? 1 : 0, onChange: (val) => setIsLinear(val === 1) }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
3986
- label: "Data Type",
3987
- info: jsx(Body1, { children: "We will introduce this functionality soon!" }),
3988
- }, options: [
3989
- { label: "Int", value: 0 },
3990
- { label: "Float", value: 1 },
3991
- ], disabled: true, value: isFloat ? 1 : 0, onChange: (val) => setFloat(val === 1) })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputRgbField, { title: "Red", value: color, rgbKey: "r", onChange: handleChange }), jsx(InputRgbField, { title: "Green", value: color, rgbKey: "g", onChange: handleChange }), jsx(InputRgbField, { title: "Blue", value: color, rgbKey: "b", onChange: handleChange }), jsx(InputAlphaField, { color: color, onChange: handleChange })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputHsvField, { title: "Hue", value: color, hsvKey: "h", max: 360, onChange: handleChange }), jsx(InputHsvField, { title: "Saturation", value: color, hsvKey: "s", max: 100, scale: 100, onChange: handleChange }), jsx(InputHsvField, { title: "Value", value: color, hsvKey: "v", max: 100, scale: 100, onChange: handleChange })] }), jsx("div", { className: classes.inputRow, children: jsx(InputHexField, { title: "Hexadecimal", linearHex: isLinear, isLinearMode: isLinear, value: color, onChange: handleChange }) })] }) })] }));
4014
+ return (jsx(Popover, { trigger: jsx(ColorSwatch, { borderColor: tokens.colorNeutralShadowKeyDarker, size: size === "small" ? "extra-small" : "small", shape: "rounded", color: color.toHexString(), value: color.toHexString().slice(1) }), children: jsxs("div", { className: classes.container, children: [jsxs(ColorPicker, { className: classes.colorPicker, color: rgbaToHsv(color), onColorChange: handleColorPickerChange, children: [jsx(ColorArea, { inputX: { "aria-label": "Saturation" }, inputY: { "aria-label": "Brightness" } }), jsx(ColorSlider, { "aria-label": "Hue" }), color instanceof Color4 && jsx(AlphaSlider, { "aria-label": "Alpha" })] }), jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
4015
+ label: "Color Space",
4016
+ info: jsx(Body1, { children: "Today this is not mutable as the color space is determined by the entity. Soon we will allow swapping" }),
4017
+ }, options: [
4018
+ { label: "Gamma", value: 0 },
4019
+ { label: "Linear", value: 1 },
4020
+ ], disabled: true, value: isLinear ? 1 : 0, onChange: (val) => setIsLinear(val === 1) }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
4021
+ label: "Data Type",
4022
+ info: jsx(Body1, { children: "We will introduce this functionality soon!" }),
4023
+ }, options: [
4024
+ { label: "Int", value: 0 },
4025
+ { label: "Float", value: 1 },
4026
+ ], disabled: true, value: isFloat ? 1 : 0, onChange: (val) => setFloat(val === 1) })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputRgbField, { title: "Red", value: color, rgbKey: "r", onChange: handleChange }), jsx(InputRgbField, { title: "Green", value: color, rgbKey: "g", onChange: handleChange }), jsx(InputRgbField, { title: "Blue", value: color, rgbKey: "b", onChange: handleChange }), jsx(InputAlphaField, { color: color, onChange: handleChange })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputHsvField, { title: "Hue", value: color, hsvKey: "h", max: 360, onChange: handleChange }), jsx(InputHsvField, { title: "Saturation", value: color, hsvKey: "s", max: 100, scale: 100, onChange: handleChange }), jsx(InputHsvField, { title: "Value", value: color, hsvKey: "v", max: 100, scale: 100, onChange: handleChange })] }), jsx("div", { className: classes.inputRow, children: jsx(InputHexField, { title: "Hexadecimal", linearHex: isLinear, isLinearMode: isLinear, value: color, onChange: handleChange }) })] }) }));
3992
4027
  };
3993
4028
  /**
3994
4029
  * Component which displays the passed in color's HEX value, either in linearSpace (if linearHex is true) or in gamma space
@@ -4108,7 +4143,7 @@ const ColorPropertyLine = forwardRef((props, ref) => {
4108
4143
  const Color3PropertyLine = ColorPropertyLine;
4109
4144
  const Color4PropertyLine = ColorPropertyLine;
4110
4145
 
4111
- const useStyles$9 = makeStyles({
4146
+ const useStyles$a = makeStyles({
4112
4147
  dropdown: {
4113
4148
  ...UniformWidthStyling,
4114
4149
  },
@@ -4120,7 +4155,7 @@ const useStyles$9 = makeStyles({
4120
4155
  */
4121
4156
  const DropdownPropertyLine = forwardRef((props, ref) => {
4122
4157
  DropdownPropertyLine.displayName = "DropdownPropertyLine";
4123
- const classes = useStyles$9();
4158
+ const classes = useStyles$a();
4124
4159
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
4125
4160
  });
4126
4161
  /**
@@ -4694,7 +4729,7 @@ class ServiceContainer {
4694
4729
  }
4695
4730
  }
4696
4731
 
4697
- const useStyles$8 = makeStyles({
4732
+ const useStyles$9 = makeStyles({
4698
4733
  themeButton: {
4699
4734
  margin: 0,
4700
4735
  },
@@ -4713,7 +4748,7 @@ const ThemeSelectorServiceDefinition = {
4713
4748
  suppressTeachingMoment: true,
4714
4749
  order: -300,
4715
4750
  component: () => {
4716
- const classes = useStyles$8();
4751
+ const classes = useStyles$9();
4717
4752
  const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
4718
4753
  const onSelectedThemeChange = useCallback((e, data) => {
4719
4754
  setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
@@ -4731,7 +4766,7 @@ const ThemeSelectorServiceDefinition = {
4731
4766
  };
4732
4767
 
4733
4768
  // eslint-disable-next-line @typescript-eslint/naming-convention
4734
- const useStyles$7 = makeStyles({
4769
+ const useStyles$8 = makeStyles({
4735
4770
  app: {
4736
4771
  colorScheme: "light dark",
4737
4772
  flexGrow: 1,
@@ -4767,7 +4802,7 @@ function MakeModularTool(options) {
4767
4802
  SetThemeMode(themeMode);
4768
4803
  }
4769
4804
  const modularToolRootComponent = () => {
4770
- const classes = useStyles$7();
4805
+ const classes = useStyles$8();
4771
4806
  const [extensionManagerContext, setExtensionManagerContext] = useState();
4772
4807
  const [requiredExtensions, setRequiredExtensions] = useState();
4773
4808
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
@@ -4793,7 +4828,7 @@ function MakeModularTool(options) {
4793
4828
  });
4794
4829
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
4795
4830
  if (extensionFeeds.length > 0) {
4796
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-s7SPclHA.js');
4831
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-CwnEf0dV.js');
4797
4832
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
4798
4833
  }
4799
4834
  // Register the theme selector service (for selecting the theme) if theming is configured.
@@ -4975,7 +5010,7 @@ const MeshIcon = createFluentIcon("Mesh", "16", '<path d="M14.03,3.54l-5.11-2.07
4975
5010
  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" />');
4976
5011
  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"/>');
4977
5012
 
4978
- const useStyles$6 = makeStyles({
5013
+ const useStyles$7 = makeStyles({
4979
5014
  coordinatesModeButton: {
4980
5015
  margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
4981
5016
  },
@@ -4985,7 +5020,7 @@ const useStyles$6 = makeStyles({
4985
5020
  });
4986
5021
  const GizmoToolbar = (props) => {
4987
5022
  const { scene, entity, gizmoService } = props;
4988
- const classes = useStyles$6();
5023
+ const classes = useStyles$7();
4989
5024
  const gizmoManager = useResource(useCallback(() => {
4990
5025
  const utilityLayerRef = gizmoService.getUtilityLayer(scene);
4991
5026
  const keepDepthUtilityLayerRef = gizmoService.getUtilityLayer(scene, "keepDepth");
@@ -5100,7 +5135,7 @@ const GizmoToolbarServiceDefinition = {
5100
5135
  },
5101
5136
  };
5102
5137
 
5103
- const useStyles$5 = makeStyles({
5138
+ const useStyles$6 = makeStyles({
5104
5139
  badge: {
5105
5140
  margin: tokens.spacingHorizontalXXS,
5106
5141
  fontFamily: "monospace",
@@ -5116,7 +5151,7 @@ const MiniStatsServiceDefinition = {
5116
5151
  horizontalLocation: "right",
5117
5152
  suppressTeachingMoment: true,
5118
5153
  component: () => {
5119
- const classes = useStyles$5();
5154
+ const classes = useStyles$6();
5120
5155
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
5121
5156
  const engine = scene?.getEngine();
5122
5157
  const fps = useObservableState(useCallback(() => (engine ? Math.round(engine.getFps()) : null), [engine]), engine?.onBeginFrameObservable);
@@ -6086,6 +6121,168 @@ const PBRBaseMaterialSheenProperties = (props) => {
6086
6121
  } }), 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" })] })] }));
6087
6122
  };
6088
6123
 
6124
+ /**
6125
+ * A button that uploads a file and either:
6126
+ * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
6127
+ * - Creates a new Texture or CubeTexture (if scene/onChange props are provided)
6128
+ * @param props TextureUploadProps
6129
+ * @returns UploadButton component that handles texture upload
6130
+ */
6131
+ const TextureUpload = (props) => {
6132
+ TextureUpload.displayName = "TextureUpload";
6133
+ const label = props.texture ? "Upload Texture" : undefined;
6134
+ // TODO: This should probably be dynamically fetching a list of supported texture extensions
6135
+ const accept = ".jpg, .png, .tga, .dds, .env, .exr";
6136
+ const handleUpload = useCallback((files) => {
6137
+ const file = files[0];
6138
+ if (!file) {
6139
+ return;
6140
+ }
6141
+ ReadFile(file, (data) => {
6142
+ const blob = new Blob([data], { type: "octet/stream" });
6143
+ // Update existing texture
6144
+ if (props.texture) {
6145
+ const { texture, onChange } = props;
6146
+ const reader = new FileReader();
6147
+ reader.readAsDataURL(blob);
6148
+ reader.onloadend = () => {
6149
+ const base64data = reader.result;
6150
+ if (texture instanceof CubeTexture) {
6151
+ let extension = undefined;
6152
+ if (file.name.toLowerCase().indexOf(".dds") > 0) {
6153
+ extension = ".dds";
6154
+ }
6155
+ else if (file.name.toLowerCase().indexOf(".env") > 0) {
6156
+ extension = ".env";
6157
+ }
6158
+ texture.updateURL(base64data, extension, () => onChange?.(texture));
6159
+ }
6160
+ else if (texture instanceof Texture) {
6161
+ texture.updateURL(base64data, null, () => onChange?.(texture));
6162
+ }
6163
+ };
6164
+ }
6165
+ else {
6166
+ // Create new texture
6167
+ const { scene, cubeOnly, onChange } = props;
6168
+ const url = URL.createObjectURL(blob);
6169
+ const extension = file.name.split(".").pop()?.toLowerCase();
6170
+ // Revoke the object URL after texture loads to prevent memory leak
6171
+ const revokeUrl = () => URL.revokeObjectURL(url);
6172
+ const newTexture = cubeOnly
6173
+ ? new CubeTexture(url, scene, [], false, undefined, revokeUrl, undefined, undefined, false, extension ? "." + extension : undefined)
6174
+ : new Texture(url, scene, false, false, undefined, revokeUrl);
6175
+ onChange(newTexture);
6176
+ }
6177
+ }, undefined, true);
6178
+ }, [props]);
6179
+ return jsx(UploadButton, { onUpload: handleUpload, accept: accept, title: "Upload Texture", label: label });
6180
+ };
6181
+
6182
+ const useStyles$5 = makeStyles({
6183
+ root: {
6184
+ // Stack the label above the field with a gap
6185
+ display: "grid",
6186
+ gridTemplateRows: "repeat(1fr)",
6187
+ justifyItems: "start",
6188
+ gap: "2px",
6189
+ maxWidth: "400px",
6190
+ },
6191
+ comboBox: {
6192
+ width: CustomTokens.inputWidth,
6193
+ minWidth: CustomTokens.inputWidth,
6194
+ boxSizing: "border-box",
6195
+ },
6196
+ input: {
6197
+ minWidth: 0, // Override Fluent's default minWidth on the input
6198
+ },
6199
+ });
6200
+ /**
6201
+ * Wrapper around a Fluent ComboBox that allows for filtering options
6202
+ * @param props
6203
+ * @returns
6204
+ */
6205
+ const ComboBox = (props) => {
6206
+ ComboBox.displayName = "ComboBox";
6207
+ const comboId = useId();
6208
+ const styles = useStyles$5();
6209
+ const { size } = useContext(ToolContext);
6210
+ const [query, setQuery] = useState(props.value ?? "");
6211
+ // Sync query with props.value when it changes externally
6212
+ useEffect(() => {
6213
+ setQuery(props.value ?? "");
6214
+ }, [props.value]);
6215
+ const children = useComboboxFilter(query, props.options, {
6216
+ noOptionsMessage: "No items match your search.",
6217
+ });
6218
+ const onOptionSelect = (_e, data) => {
6219
+ setQuery(data.optionText ?? "");
6220
+ data.optionText && props.onChange(data.optionText);
6221
+ };
6222
+ return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { size: size, root: { className: styles.comboBox }, input: { className: styles.input }, onOptionSelect: onOptionSelect, onBlur: () => props.onChange(query), "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
6223
+ };
6224
+
6225
+ const useStyles$4 = makeStyles({
6226
+ container: {
6227
+ display: "flex",
6228
+ flexDirection: "row",
6229
+ alignItems: "center",
6230
+ gap: tokens.spacingHorizontalS,
6231
+ },
6232
+ });
6233
+ /**
6234
+ * A primitive component with a ComboBox for selecting from existing scene textures
6235
+ * and a button for uploading new texture files.
6236
+ * @param props ChooseTextureProps
6237
+ * @returns ChooseTexture component
6238
+ */
6239
+ const ChooseTexture = (props) => {
6240
+ ChooseTexture.displayName = "ChooseTexture";
6241
+ const { scene, cubeOnly, value, onChange } = props;
6242
+ const classes = useStyles$4();
6243
+ // Get sorted texture names from scene
6244
+ const textureOptions = useMemo(() => {
6245
+ return scene.textures
6246
+ .filter((t) => t.name && (!cubeOnly || t.isCube))
6247
+ .map((t) => t.displayName || t.name)
6248
+ .sort((a, b) => a.localeCompare(b));
6249
+ }, [scene.textures, cubeOnly]);
6250
+ const handleTextureSelect = (textureName) => {
6251
+ const texture = scene.textures.find((t) => (t.displayName || t.name) === textureName);
6252
+ onChange(texture ?? null);
6253
+ };
6254
+ // Get current texture name for initial display
6255
+ const currentTextureName = value ? value.displayName || value.name : "";
6256
+ return (jsxs("div", { className: classes.container, children: [jsx(ComboBox, { label: "", options: textureOptions, value: currentTextureName, onChange: handleTextureSelect }), jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
6257
+ };
6258
+
6259
+ /**
6260
+ * A property line with a ComboBox for selecting from existing scene textures
6261
+ * and a button for uploading new texture files.
6262
+ * @param props - ChooseTextureProps & PropertyLineProps
6263
+ * @returns property-line wrapped ChooseTexture component
6264
+ */
6265
+ const ChooseTexturePropertyLine = (props) => {
6266
+ ChooseTexturePropertyLine.displayName = "ChooseTexturePropertyLine";
6267
+ return (jsx(PropertyLine, { ...props, children: jsx(ChooseTexture, { ...props }) }));
6268
+ };
6269
+
6270
+ /**
6271
+ * Helper to bind texture properties without needing defaultValue
6272
+ * @param props - The required properties
6273
+ * @returns ChooseTexturePropertyLine component
6274
+ */
6275
+ function BoundTextureProperty(props) {
6276
+ const { label, target, propertyKey, scene, cubeOnly } = props;
6277
+ const value = useProperty(target, propertyKey);
6278
+ const notifyPropertyChanged = usePropertyChangedNotifier();
6279
+ return (jsx(ChooseTexturePropertyLine, { label: label, value: value, onChange: (texture) => {
6280
+ const oldValue = target[propertyKey];
6281
+ target[propertyKey] = texture;
6282
+ notifyPropertyChanged(target, propertyKey, oldValue, texture);
6283
+ }, scene: scene, cubeOnly: cubeOnly }));
6284
+ }
6285
+
6089
6286
  /**
6090
6287
  * Displays the lighting and color properties of a PBR material.
6091
6288
  * @param props - The required properties
@@ -6095,6 +6292,16 @@ const PBRMaterialLightingAndColorProperties = (props) => {
6095
6292
  const { material } = props;
6096
6293
  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" })] }));
6097
6294
  };
6295
+ /**
6296
+ * Displays the texture channel properties of a PBR material.
6297
+ * @param props - The required properties
6298
+ * @returns A JSX element representing the texture channels.
6299
+ */
6300
+ const PBRMaterialTextureProperties = (props) => {
6301
+ const { material } = props;
6302
+ const scene = material.getScene();
6303
+ return (jsxs(Fragment, { children: [jsx(BoundTextureProperty, { label: "Albedo", target: material, propertyKey: "albedoTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Base Weight", target: material, propertyKey: "baseWeightTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughnessTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Metallic Roughness", target: material, propertyKey: "metallicTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Reflection", target: material, propertyKey: "reflectionTexture", scene: scene, cubeOnly: true }), jsx(BoundTextureProperty, { label: "Refraction", target: material, propertyKey: "refractionTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Reflectivity", target: material, propertyKey: "reflectivityTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Micro-surface", target: material, propertyKey: "microSurfaceTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Bump", target: material, propertyKey: "bumpTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Emissive", target: material, propertyKey: "emissiveTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Opacity", target: material, propertyKey: "opacityTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Ambient", target: material, propertyKey: "ambientTexture", scene: scene }), jsx(BoundTextureProperty, { label: "Lightmap", target: material, propertyKey: "lightmapTexture", scene: scene })] }));
6304
+ };
6098
6305
 
6099
6306
  // TODO: ryamtrem / gehalper This function is temporal until there is a line control to handle texture links (similar to the old TextureLinkLineComponent)
6100
6307
  const UpdateTexture = (file, material, textureSetter) => {
@@ -6370,6 +6577,10 @@ const MaterialPropertiesServiceDefinition = {
6370
6577
  key: "PBR Material Properties",
6371
6578
  predicate: (entity) => entity instanceof PBRMaterial,
6372
6579
  content: [
6580
+ {
6581
+ section: "Textures",
6582
+ component: ({ context }) => jsx(PBRMaterialTextureProperties, { material: context }),
6583
+ },
6373
6584
  {
6374
6585
  section: "Lighting & Colors",
6375
6586
  component: ({ context }) => jsx(PBRMaterialLightingAndColorProperties, { material: context }),
@@ -6589,7 +6800,7 @@ function SaveMetadata(entity, metadata) {
6589
6800
  entity.metadata = metadata;
6590
6801
  }
6591
6802
  }
6592
- const useStyles$4 = makeStyles({
6803
+ const useStyles$3 = makeStyles({
6593
6804
  mainDiv: {
6594
6805
  display: "flex",
6595
6806
  flexDirection: "column",
@@ -6609,7 +6820,7 @@ const useStyles$4 = makeStyles({
6609
6820
  */
6610
6821
  const MetadataProperties = (props) => {
6611
6822
  const { entity } = props;
6612
- const classes = useStyles$4();
6823
+ const classes = useStyles$3();
6613
6824
  const { size } = useContext(ToolContext);
6614
6825
  const metadata = useProperty(entity, "metadata");
6615
6826
  const stringifiedMetadata = useMemo(() => StringifyMetadata(metadata) ?? "", [metadata]);
@@ -7841,6 +8052,176 @@ const SpriteManagerCellProperties = (props) => {
7841
8052
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SpinButtonPropertyLine, label: "Cell Width", target: spriteManager, propertyKey: "cellWidth", min: 1, step: 1 }, "CellWidth"), jsx(BoundProperty, { component: SpinButtonPropertyLine, label: "Cell Height", target: spriteManager, propertyKey: "cellHeight", min: 1, step: 1 }, "CellHeight")] }));
7842
8053
  };
7843
8054
 
8055
+ const useStyles$2 = makeStyles({
8056
+ root: {
8057
+ display: "flex",
8058
+ flexDirection: "column",
8059
+ },
8060
+ controls: {
8061
+ display: "flex",
8062
+ gap: tokens.spacingHorizontalXS,
8063
+ },
8064
+ controlButton: {
8065
+ minWidth: "auto",
8066
+ flex: "1 1 0", // Equal flex grow/shrink with 0 basis
8067
+ paddingVertical: tokens.spacingVerticalXS,
8068
+ paddingHorizontal: tokens.spacingHorizontalS,
8069
+ overflow: "hidden",
8070
+ textOverflow: "ellipsis",
8071
+ },
8072
+ preview: {
8073
+ border: `1px solid ${tokens.colorNeutralStroke1}`,
8074
+ display: "block",
8075
+ objectFit: "contain",
8076
+ },
8077
+ previewContainer: {
8078
+ display: "flex",
8079
+ justifyContent: "center",
8080
+ marginTop: tokens.spacingVerticalXS,
8081
+ marginBottom: tokens.spacingVerticalS,
8082
+ width: "100%",
8083
+ },
8084
+ });
8085
+ // This method of holding TextureChannels was brought over from inspectorv1 and can likely be refactored/simplified
8086
+ const TextureChannelStates = {
8087
+ R: { R: true, G: false, B: false, A: false },
8088
+ G: { R: false, G: true, B: false, A: false },
8089
+ B: { R: false, G: false, B: true, A: false },
8090
+ A: { R: false, G: false, B: false, A: true },
8091
+ ALL: { R: true, G: true, B: true, A: true },
8092
+ };
8093
+ const TexturePreview = (props) => {
8094
+ const { texture, disableToolbar = false, maxWidth = "100%", maxHeight = "384px", offsetX = 0, offsetY = 0, width, height } = props;
8095
+ const classes = useStyles$2();
8096
+ const canvasRef = useRef(null);
8097
+ const [channels, setChannels] = useState(TextureChannelStates.ALL);
8098
+ const [face, setFace] = useState(0);
8099
+ const [canvasStyle, setCanvasStyle] = useState();
8100
+ const internalTexture = useProperty(texture, "_texture");
8101
+ const { size } = useContext(ToolContext);
8102
+ const updatePreviewAsync = useCallback(async () => {
8103
+ const canvas = canvasRef.current;
8104
+ if (!canvas) {
8105
+ return;
8106
+ }
8107
+ try {
8108
+ await WhenTextureReadyAsync(texture); // Ensure texture is loaded before grabbing size
8109
+ const { width: textureWidth, height: textureHeight } = texture.getSize();
8110
+ // Set canvas dimensions to the sub-region size
8111
+ canvas.width = width ?? textureWidth;
8112
+ canvas.height = height ?? textureHeight;
8113
+ // Calculate the width that corresponds to maxHeight while maintaining aspect ratio
8114
+ const aspectRatio = canvas.width / canvas.height;
8115
+ // Use CSS min() to pick the smaller of maxWidth or the width that corresponds to maxHeight
8116
+ const imageWidth = `min(${maxWidth}, calc(${maxHeight} * ${aspectRatio}))`;
8117
+ setCanvasStyle({ width: imageWidth });
8118
+ // Get full texture data, then draw only the sub-region
8119
+ const data = await ApplyChannelsToTextureDataAsync(texture, textureWidth, textureHeight, face, channels);
8120
+ const context = canvas.getContext("2d");
8121
+ if (context) {
8122
+ const fullImageData = context.createImageData(textureWidth, textureHeight);
8123
+ fullImageData.data.set(data);
8124
+ // Use putImageData with dirty rect to draw only the sub-region
8125
+ context.putImageData(fullImageData, -offsetX, -offsetY, offsetX, offsetY, canvas.width, canvas.height);
8126
+ }
8127
+ }
8128
+ catch {
8129
+ // If we fail, leave the canvas empty
8130
+ }
8131
+ }, [texture, face, channels, offsetX, offsetY, width, height, internalTexture]);
8132
+ useEffect(() => {
8133
+ void updatePreviewAsync();
8134
+ }, [updatePreviewAsync]);
8135
+ return (jsxs("div", { className: classes.root, children: [disableToolbar ? null : texture.isCube ? (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Cube Faces", children: ["+X", "-X", "+Y", "-Y", "+Z", "-Z"].map((label, idx) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: face === idx ? "primary" : "subtle", onClick: () => setFace(idx), children: label }, label))) })) : (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Channels", children: ["R", "G", "B", "A", "ALL"].map((ch) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: channels === TextureChannelStates[ch] ? "primary" : "subtle", onClick: () => setChannels(TextureChannelStates[ch]), children: ch }, ch))) })), jsx("div", { className: classes.previewContainer, children: jsx("canvas", { ref: canvasRef, className: classes.preview, style: canvasStyle }) }), texture.isRenderTarget && (jsx(Button$1, { appearance: "outline", onClick: () => {
8136
+ void updatePreviewAsync();
8137
+ }, children: "Refresh" }))] }));
8138
+ };
8139
+ /**
8140
+ * Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
8141
+ * This is convienent to get 8-bit RGBA values for a texture in a GPU compressed format.
8142
+ * @param texture the source texture
8143
+ * @param width the width of the result, which does not have to match the source texture width
8144
+ * @param height the height of the result, which does not have to match the source texture height
8145
+ * @param face if the texture has multiple faces, the face index to use for the source
8146
+ * @param channels a filter for which of the RGBA channels to return in the result
8147
+ * @param lod if the texture has multiple LODs, the lod index to use for the source
8148
+ * @returns the 8-bit texture data
8149
+ */
8150
+ async function ApplyChannelsToTextureDataAsync(texture, width, height, face, channels, lod = 0) {
8151
+ const data = await GetTextureDataAsync(texture, width, height, face, lod);
8152
+ if (!channels.R || !channels.G || !channels.B || !channels.A) {
8153
+ for (let i = 0; i < width * height * 4; i += 4) {
8154
+ // If alpha is the only channel, just display alpha across all channels
8155
+ if (channels.A && !channels.R && !channels.G && !channels.B) {
8156
+ data[i] = data[i + 3];
8157
+ data[i + 1] = data[i + 3];
8158
+ data[i + 2] = data[i + 3];
8159
+ data[i + 3] = 255;
8160
+ continue;
8161
+ }
8162
+ let r = data[i], g = data[i + 1], b = data[i + 2], a = data[i + 3];
8163
+ // If alpha is not visible, make everything 100% alpha
8164
+ if (!channels.A) {
8165
+ a = 255;
8166
+ }
8167
+ // If only one color channel is selected, map both colors to it. If two are selected, the unused one gets set to 0
8168
+ if (!channels.R) {
8169
+ if (channels.G && !channels.B) {
8170
+ r = g;
8171
+ }
8172
+ else if (channels.B && !channels.G) {
8173
+ r = b;
8174
+ }
8175
+ else {
8176
+ r = 0;
8177
+ }
8178
+ }
8179
+ if (!channels.G) {
8180
+ if (channels.R && !channels.B) {
8181
+ g = r;
8182
+ }
8183
+ else if (channels.B && !channels.R) {
8184
+ g = b;
8185
+ }
8186
+ else {
8187
+ g = 0;
8188
+ }
8189
+ }
8190
+ if (!channels.B) {
8191
+ if (channels.R && !channels.G) {
8192
+ b = r;
8193
+ }
8194
+ else if (channels.G && !channels.R) {
8195
+ b = g;
8196
+ }
8197
+ else {
8198
+ b = 0;
8199
+ }
8200
+ }
8201
+ data[i] = r;
8202
+ data[i + 1] = g;
8203
+ data[i + 2] = b;
8204
+ data[i + 3] = a;
8205
+ }
8206
+ }
8207
+ //To flip image on Y axis.
8208
+ if (texture.invertY || texture.isCube) {
8209
+ const numberOfChannelsByLine = width * 4;
8210
+ const halfHeight = height / 2;
8211
+ for (let i = 0; i < halfHeight; i++) {
8212
+ for (let j = 0; j < numberOfChannelsByLine; j++) {
8213
+ const currentCell = j + i * numberOfChannelsByLine;
8214
+ const targetLine = height - i - 1;
8215
+ const targetCell = j + targetLine * numberOfChannelsByLine;
8216
+ const temp = data[currentCell];
8217
+ data[currentCell] = data[targetCell];
8218
+ data[targetCell] = temp;
8219
+ }
8220
+ }
8221
+ }
8222
+ return data;
8223
+ }
8224
+
7844
8225
  function useMaxCellCount(sprite) {
7845
8226
  const manager = sprite.manager;
7846
8227
  const texture = useProperty(manager, "texture");
@@ -7884,7 +8265,15 @@ const SpriteOtherProperties = (props) => {
7884
8265
  const SpriteCellProperties = (props) => {
7885
8266
  const { sprite } = props;
7886
8267
  const maxCellCount = useMaxCellCount(sprite);
7887
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Cell Index", target: sprite, propertyKey: "cellIndex", min: 0, step: 1, max: maxCellCount }, "CellIndex"), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert U", target: sprite, propertyKey: "invertU" }, "InvertU"), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert V", target: sprite, propertyKey: "invertV" }, "InvertV")] }));
8268
+ const manager = sprite.manager;
8269
+ const texture = manager.texture;
8270
+ const size = texture.getSize();
8271
+ const cellWidth = useProperty(manager, "cellWidth");
8272
+ const cellHeight = useProperty(manager, "cellHeight");
8273
+ const cellIndex = useProperty(sprite, "cellIndex");
8274
+ const offsetX = (cellIndex * cellWidth) % size.width;
8275
+ const offsetY = Math.floor((cellIndex * cellWidth) / size.width) * cellHeight;
8276
+ return (jsxs(Fragment, { children: [jsx(TexturePreview, { disableToolbar: true, texture: texture, maxHeight: "160px", offsetX: offsetX, offsetY: offsetY, width: cellWidth, height: cellHeight }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Cell Index", target: sprite, propertyKey: "cellIndex", min: 0, step: 1, max: maxCellCount }, "CellIndex"), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert U", target: sprite, propertyKey: "invertU" }, "InvertU"), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invert V", target: sprite, propertyKey: "invertV" }, "InvertV")] }));
7888
8277
  };
7889
8278
 
7890
8279
  const SpritePropertiesServiceDefinition = {
@@ -8044,213 +8433,9 @@ function FindTextureType(type) {
8044
8433
  return null;
8045
8434
  }
8046
8435
 
8047
- const useStyles$3 = makeStyles({
8048
- root: {
8049
- display: "flex",
8050
- flexDirection: "column",
8051
- },
8052
- controls: {
8053
- display: "flex",
8054
- gap: tokens.spacingHorizontalXS,
8055
- },
8056
- controlButton: {
8057
- minWidth: "auto",
8058
- flex: "1 1 0", // Equal flex grow/shrink with 0 basis
8059
- paddingVertical: tokens.spacingVerticalXS,
8060
- paddingHorizontal: tokens.spacingHorizontalS,
8061
- overflow: "hidden",
8062
- textOverflow: "ellipsis",
8063
- },
8064
- preview: {
8065
- border: `1px solid ${tokens.colorNeutralStroke1}`,
8066
- marginTop: tokens.spacingVerticalXS,
8067
- maxWidth: "100%",
8068
- marginLeft: "auto",
8069
- marginRight: "auto",
8070
- marginBottom: tokens.spacingVerticalS,
8071
- display: "block",
8072
- },
8073
- });
8074
- // This method of holding TextureChannels was brought over from inspectorv1 and can likely be refactored/simplified
8075
- const TextureChannelStates = {
8076
- R: { R: true, G: false, B: false, A: false },
8077
- G: { R: false, G: true, B: false, A: false },
8078
- B: { R: false, G: false, B: true, A: false },
8079
- A: { R: false, G: false, B: false, A: true },
8080
- ALL: { R: true, G: true, B: true, A: true },
8081
- };
8082
- const TexturePreview = (props) => {
8083
- const { texture, width, height } = props;
8084
- const classes = useStyles$3();
8085
- const canvasRef = useRef(null);
8086
- const [channels, setChannels] = useState(TextureChannelStates.ALL);
8087
- const [face, setFace] = useState(0);
8088
- const internalTexture = useProperty(texture, "_texture");
8089
- const { size } = useContext(ToolContext);
8090
- const updatePreviewCanvasSize = useCallback((previewCanvas) => {
8091
- // This logic was brought over from inspectorv1 and can likely be refactored/simplified
8092
- const size = texture.getSize();
8093
- const ratio = size.width / size.height;
8094
- let w = width;
8095
- let h = (w / ratio) | 1;
8096
- const engine = texture.getScene()?.getEngine();
8097
- if (engine && h > engine.getCaps().maxTextureSize) {
8098
- w = size.width;
8099
- h = size.height;
8100
- }
8101
- previewCanvas.width = w;
8102
- previewCanvas.height = h;
8103
- previewCanvas.style.width = w + "px";
8104
- previewCanvas.style.height = h + "px";
8105
- return {
8106
- w: w,
8107
- h: h,
8108
- };
8109
- }, [canvasRef.current, texture, width, height, internalTexture]);
8110
- const updatePreviewAsync = useCallback(async () => {
8111
- const previewCanvas = canvasRef.current;
8112
- if (!previewCanvas) {
8113
- return;
8114
- }
8115
- try {
8116
- await WhenTextureReadyAsync(texture); // Ensure texture is loaded before grabbing size
8117
- const { w, h } = updatePreviewCanvasSize(previewCanvas); // Grab desired size
8118
- const data = await ApplyChannelsToTextureDataAsync(texture, w, h, face, channels); // get channel data to load onto canvas context
8119
- const context = previewCanvas.getContext("2d");
8120
- if (context) {
8121
- const imageData = context.createImageData(w, h);
8122
- imageData.data.set(data);
8123
- context.putImageData(imageData, 0, 0);
8124
- }
8125
- }
8126
- catch {
8127
- updatePreviewCanvasSize(previewCanvas); // If we fail above, best effort sizing preview canvas
8128
- }
8129
- }, [[texture, width, height, face, channels, internalTexture]]);
8130
- useEffect(() => {
8131
- void updatePreviewAsync();
8132
- }, [texture, width, height, face, channels, internalTexture]);
8133
- return (jsxs("div", { className: classes.root, children: [texture.isCube ? (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Cube Faces", children: ["+X", "-X", "+Y", "-Y", "+Z", "-Z"].map((label, idx) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: face === idx ? "primary" : "subtle", onClick: () => setFace(idx), children: label }, label))) })) : (jsx(Toolbar$1, { className: classes.controls, size: size, "aria-label": "Channels", children: ["R", "G", "B", "A", "ALL"].map((ch) => (jsx(ToolbarButton, { className: classes.controlButton, appearance: channels === TextureChannelStates[ch] ? "primary" : "subtle", onClick: () => setChannels(TextureChannelStates[ch]), children: ch }, ch))) })), jsx("canvas", { ref: canvasRef, className: classes.preview }), texture.isRenderTarget && (jsx(Button$1, { appearance: "outline", onClick: () => {
8134
- void updatePreviewAsync();
8135
- }, children: "Refresh" }))] }));
8136
- };
8137
- /**
8138
- * Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
8139
- * This is convienent to get 8-bit RGBA values for a texture in a GPU compressed format.
8140
- * @param texture the source texture
8141
- * @param width the width of the result, which does not have to match the source texture width
8142
- * @param height the height of the result, which does not have to match the source texture height
8143
- * @param face if the texture has multiple faces, the face index to use for the source
8144
- * @param channels a filter for which of the RGBA channels to return in the result
8145
- * @param lod if the texture has multiple LODs, the lod index to use for the source
8146
- * @returns the 8-bit texture data
8147
- */
8148
- async function ApplyChannelsToTextureDataAsync(texture, width, height, face, channels, lod = 0) {
8149
- const data = await GetTextureDataAsync(texture, width, height, face, lod);
8150
- if (!channels.R || !channels.G || !channels.B || !channels.A) {
8151
- for (let i = 0; i < width * height * 4; i += 4) {
8152
- // If alpha is the only channel, just display alpha across all channels
8153
- if (channels.A && !channels.R && !channels.G && !channels.B) {
8154
- data[i] = data[i + 3];
8155
- data[i + 1] = data[i + 3];
8156
- data[i + 2] = data[i + 3];
8157
- data[i + 3] = 255;
8158
- continue;
8159
- }
8160
- let r = data[i], g = data[i + 1], b = data[i + 2], a = data[i + 3];
8161
- // If alpha is not visible, make everything 100% alpha
8162
- if (!channels.A) {
8163
- a = 255;
8164
- }
8165
- // If only one color channel is selected, map both colors to it. If two are selected, the unused one gets set to 0
8166
- if (!channels.R) {
8167
- if (channels.G && !channels.B) {
8168
- r = g;
8169
- }
8170
- else if (channels.B && !channels.G) {
8171
- r = b;
8172
- }
8173
- else {
8174
- r = 0;
8175
- }
8176
- }
8177
- if (!channels.G) {
8178
- if (channels.R && !channels.B) {
8179
- g = r;
8180
- }
8181
- else if (channels.B && !channels.R) {
8182
- g = b;
8183
- }
8184
- else {
8185
- g = 0;
8186
- }
8187
- }
8188
- if (!channels.B) {
8189
- if (channels.R && !channels.G) {
8190
- b = r;
8191
- }
8192
- else if (channels.G && !channels.R) {
8193
- b = g;
8194
- }
8195
- else {
8196
- b = 0;
8197
- }
8198
- }
8199
- data[i] = r;
8200
- data[i + 1] = g;
8201
- data[i + 2] = b;
8202
- data[i + 3] = a;
8203
- }
8204
- }
8205
- //To flip image on Y axis.
8206
- if (texture.invertY || texture.isCube) {
8207
- const numberOfChannelsByLine = width * 4;
8208
- const halfHeight = height / 2;
8209
- for (let i = 0; i < halfHeight; i++) {
8210
- for (let j = 0; j < numberOfChannelsByLine; j++) {
8211
- const currentCell = j + i * numberOfChannelsByLine;
8212
- const targetLine = height - i - 1;
8213
- const targetCell = j + targetLine * numberOfChannelsByLine;
8214
- const temp = data[currentCell];
8215
- data[currentCell] = data[targetCell];
8216
- data[targetCell] = temp;
8217
- }
8218
- }
8219
- }
8220
- return data;
8221
- }
8222
-
8223
8436
  const BaseTexturePreviewProperties = (props) => {
8224
8437
  const { texture } = props;
8225
- const isUpdatable = texture instanceof Texture || texture instanceof CubeTexture;
8226
- const updateTexture = useCallback((file) => {
8227
- ReadFile(file, (data) => {
8228
- const blob = new Blob([data], { type: "octet/stream" });
8229
- const reader = new FileReader();
8230
- reader.readAsDataURL(blob);
8231
- reader.onloadend = () => {
8232
- const base64data = reader.result;
8233
- if (texture instanceof CubeTexture) {
8234
- let extension = undefined;
8235
- if (file.name.toLowerCase().indexOf(".dds") > 0) {
8236
- extension = ".dds";
8237
- }
8238
- else if (file.name.toLowerCase().indexOf(".env") > 0) {
8239
- extension = ".env";
8240
- }
8241
- texture.updateURL(base64data, extension);
8242
- }
8243
- else if (texture instanceof Texture) {
8244
- texture.updateURL(base64data);
8245
- }
8246
- };
8247
- }, undefined, true);
8248
- }, [texture]);
8249
- return (jsxs(Fragment, { children: [jsx(TexturePreview, { texture: texture, width: 256, height: 256 }), isUpdatable && (jsx(FileUploadLine, { label: "Load Texture From File", accept: ".jpg, .png, .tga, .dds, .env, .exr", onClick: (files) => {
8250
- if (files.length > 0) {
8251
- updateTexture(files[0]);
8252
- }
8253
- } })), jsx(ButtonLine, { label: "Edit Texture (coming soon!)", onClick: () => { } })] }));
8438
+ return (jsxs(Fragment, { children: [jsx(TexturePreview, { texture: texture }), jsx(TextureUpload, { texture: texture }), jsx(ButtonLine, { label: "Edit Texture (coming soon!)", onClick: () => { } })] }));
8254
8439
  };
8255
8440
  const BaseTextureGeneralProperties = (props) => {
8256
8441
  const { texture } = props;
@@ -10252,36 +10437,6 @@ function DetachDebugLayer() {
10252
10437
  }
10253
10438
  }
10254
10439
 
10255
- const useStyles$2 = makeStyles({
10256
- root: {
10257
- // Stack the label above the field with a gap
10258
- display: "grid",
10259
- gridTemplateRows: "repeat(1fr)",
10260
- justifyItems: "start",
10261
- gap: "2px",
10262
- maxWidth: "400px",
10263
- },
10264
- });
10265
- /**
10266
- * Wrapper around a Fluent ComboBox that allows for filtering options
10267
- * @param props
10268
- * @returns
10269
- */
10270
- const ComboBox = (props) => {
10271
- ComboBox.displayName = "ComboBox";
10272
- const comboId = useId();
10273
- const styles = useStyles$2();
10274
- const [query, setQuery] = useState("");
10275
- const children = useComboboxFilter(query, props.value, {
10276
- noOptionsMessage: "No items match your search.",
10277
- });
10278
- const onOptionSelect = (_e, data) => {
10279
- setQuery(data.optionText ?? "");
10280
- data.optionText && props.onChange(data.optionText);
10281
- };
10282
- return (jsxs("div", { className: styles.root, children: [jsx("label", { id: comboId, children: props.label }), jsx(Combobox, { onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
10283
- };
10284
-
10285
10440
  const useDraggableStyles = makeStyles({
10286
10441
  draggable: {
10287
10442
  display: "flex",
@@ -10353,7 +10508,7 @@ const PositionedPopover = (props) => {
10353
10508
  props.hide();
10354
10509
  }
10355
10510
  };
10356
- return (jsxs(Popover, { open: open, onOpenChange: handleOpenChange, positioning: {
10511
+ return (jsxs(Popover$1, { open: open, onOpenChange: handleOpenChange, positioning: {
10357
10512
  position: "below", // Places the popover directly below the trigger element
10358
10513
  autoSize: "height-always", //Automatically adjusts the popover height to fit within the viewport
10359
10514
  fallbackPositions: ["above", "after", "before"], //If the primary position doesn't fit, automatically tries these positions in order
@@ -10511,5 +10666,5 @@ const TextAreaPropertyLine = (props) => {
10511
10666
  // Attach Inspector v2 to Scene.debugLayer as a side effect for back compat.
10512
10667
  AttachDebugLayer();
10513
10668
 
10514
- export { MakeTeachingMoment as $, Accordion as A, ButtonLine as B, Collapse as C, DebugServiceIdentity as D, ExtensibleAccordion as E, FileUploadLine as F, useColor4Property as G, useQuaternionProperty as H, Inspector as I, MakePropertyHook as J, useInterceptObservable as K, Link as L, MakeLazyComponent as M, NumberDropdownPropertyLine as N, useEventfulState as O, PropertiesServiceIdentity as P, useObservableCollection as Q, useOrderedObservableCollection as R, SwitchPropertyLine as S, ToolsServiceIdentity as T, usePollingObservable as U, Vector3PropertyLine as V, useResource as W, useAsyncResource as X, useCompactMode as Y, useSidePaneDockOverrides as Z, useAngleConverters as _, SyncedSliderPropertyLine as a, MakeDialogTeachingMoment as a0, InterceptFunction as a1, GetPropertyDescriptor as a2, IsPropertyReadonly as a3, InterceptProperty as a4, ObservableCollection as a5, ConstructorFactory as a6, SelectionServiceIdentity as a7, SelectionServiceDefinition as a8, SettingsContextIdentity as a9, FactorGradientList as aA, Color3GradientList as aB, Color4GradientList as aC, Pane as aD, BooleanBadgePropertyLine as aE, Color3PropertyLine as aF, Color4PropertyLine as aG, HexPropertyLine as aH, NumberInputPropertyLine as aI, LinkPropertyLine as aJ, PropertyLine as aK, LineContainer as aL, PlaceholderPropertyLine as aM, StringifiedPropertyLine as aN, TextAreaPropertyLine as aO, TextPropertyLine as aP, RotationVectorPropertyLine as aQ, QuaternionPropertyLine as aR, Vector2PropertyLine as aS, Vector4PropertyLine as aT, ShowInspector as aa, Checkbox as ab, ColorPickerPopup as ac, InputHexField as ad, InputHsvField as ae, ComboBox as af, DraggableLine as ag, Dropdown as ah, NumberDropdown as ai, StringDropdown as aj, FactorGradientComponent as ak, Color3GradientComponent as al, Color4GradientComponent as am, ColorStepGradientComponent as an, InfoLabel as ao, List as ap, MessageBar as aq, PositionedPopover as ar, SearchBar as as, SearchBox as at, SpinButton as au, Switch as av, SyncedSliderInput as aw, Textarea as ax, TextInput as ay, ToggleButton as az, Button as b, TextInputPropertyLine as c, SpinButtonPropertyLine as d, CheckboxPropertyLine as e, ShellServiceIdentity as f, SceneContextIdentity as g, AccordionSection as h, useExtensionManager as i, MakePopoverTeachingMoment as j, TeachingMoment as k, SidePaneContainer as l, SceneExplorerServiceIdentity as m, SettingsServiceIdentity as n, StatsServiceIdentity as o, ConvertOptions as p, AttachDebugLayer as q, DetachDebugLayer as r, StringDropdownPropertyLine as s, BoundProperty as t, useObservableState as u, LinkToEntityPropertyLine as v, BuiltInsExtensionFeed as w, useProperty as x, useVector3Property as y, useColor3Property as z };
10515
- //# sourceMappingURL=index-BUtV9Nch.js.map
10669
+ export { useAngleConverters as $, Accordion as A, ButtonLine as B, Collapse as C, DebugServiceIdentity as D, ExtensibleAccordion as E, FileUploadLine as F, useColor3Property as G, useColor4Property as H, Inspector as I, useQuaternionProperty as J, MakePropertyHook as K, Link as L, MakeLazyComponent as M, NumberDropdownPropertyLine as N, useInterceptObservable as O, Popover as P, useEventfulState as Q, useObservableCollection as R, SwitchPropertyLine as S, ToolsServiceIdentity as T, useOrderedObservableCollection as U, Vector3PropertyLine as V, usePollingObservable as W, useResource as X, useAsyncResource as Y, useCompactMode as Z, useSidePaneDockOverrides as _, SyncedSliderPropertyLine as a, MakeTeachingMoment as a0, MakeDialogTeachingMoment as a1, InterceptFunction as a2, GetPropertyDescriptor as a3, IsPropertyReadonly as a4, InterceptProperty as a5, ObservableCollection as a6, ConstructorFactory as a7, SelectionServiceIdentity as a8, SelectionServiceDefinition as a9, ToggleButton as aA, FactorGradientList as aB, Color3GradientList as aC, Color4GradientList as aD, Pane as aE, BooleanBadgePropertyLine as aF, Color3PropertyLine as aG, Color4PropertyLine as aH, HexPropertyLine as aI, NumberInputPropertyLine as aJ, LinkPropertyLine as aK, PropertyLine as aL, LineContainer as aM, PlaceholderPropertyLine as aN, StringifiedPropertyLine as aO, TextAreaPropertyLine as aP, TextPropertyLine as aQ, RotationVectorPropertyLine as aR, QuaternionPropertyLine as aS, Vector2PropertyLine as aT, Vector4PropertyLine as aU, SettingsContextIdentity as aa, ShowInspector as ab, Checkbox as ac, ColorPickerPopup as ad, InputHexField as ae, InputHsvField as af, ComboBox as ag, DraggableLine as ah, Dropdown as ai, NumberDropdown as aj, StringDropdown as ak, FactorGradientComponent as al, Color3GradientComponent as am, Color4GradientComponent as an, ColorStepGradientComponent as ao, InfoLabel as ap, List as aq, MessageBar as ar, PositionedPopover as as, SearchBar as at, SearchBox as au, SpinButton as av, Switch as aw, SyncedSliderInput as ax, Textarea as ay, TextInput as az, Button as b, TextInputPropertyLine as c, SpinButtonPropertyLine as d, CheckboxPropertyLine as e, ShellServiceIdentity as f, SceneContextIdentity as g, AccordionSection as h, useExtensionManager as i, MakePopoverTeachingMoment as j, TeachingMoment as k, SidePaneContainer as l, PropertiesServiceIdentity as m, SceneExplorerServiceIdentity as n, SettingsServiceIdentity as o, StatsServiceIdentity as p, ConvertOptions as q, AttachDebugLayer as r, DetachDebugLayer as s, StringDropdownPropertyLine as t, useObservableState as u, BoundProperty as v, LinkToEntityPropertyLine as w, BuiltInsExtensionFeed as x, useProperty as y, useVector3Property as z };
10670
+ //# sourceMappingURL=index-BgzFAhky.js.map