@babylonjs/inspector 8.44.1-preview → 8.45.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.
@@ -4,7 +4,7 @@ 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
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, createLightTheme, createDarkTheme, FluentProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Portal, RendererProvider, createDOMRenderer, Tooltip, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, ToolbarRadioButton, 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, Label, ToolbarDivider, Field } from '@fluentui/react-components';
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, EyeRegular, EyeOffRegular, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ChevronDownRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, MyLocationRegular, CameraRegular, LightbulbRegular, BorderOutsideRegular, BorderNoneRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
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, LinkDismissRegular, LinkEditRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, EyeRegular, EyeOffRegular, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ChevronDownRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, MyLocationRegular, CameraRegular, LightbulbRegular, BorderOutsideRegular, BorderNoneRegular, 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';
10
10
  import { useLocalStorage, useTernaryDarkMode } from 'usehooks-ts';
@@ -82,6 +82,14 @@ import { RenderingManager } from '@babylonjs/core/Rendering/renderingManager.js'
82
82
  import '@babylonjs/core/Rendering/edgesRenderer.js';
83
83
  import '@babylonjs/core/Rendering/outlineRenderer.js';
84
84
  import { GaussianSplattingMesh } from '@babylonjs/core/Meshes/GaussianSplatting/gaussianSplattingMesh.js';
85
+ import { BoxParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/boxParticleEmitter.js';
86
+ import { ConeParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/coneParticleEmitter.js';
87
+ import { CylinderParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/cylinderParticleEmitter.js';
88
+ import { HemisphericParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/hemisphericParticleEmitter.js';
89
+ import { MeshParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/meshParticleEmitter.js';
90
+ import { PointParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/pointParticleEmitter.js';
91
+ import { SphereParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/sphereParticleEmitter.js';
92
+ import { ParticleHelper } from '@babylonjs/core/Particles/particleHelper.js';
85
93
  import { FactorGradient, Color3Gradient, ColorGradient } from '@babylonjs/core/Misc/gradients.js';
86
94
  import { GradientBlockColorStep } from '@babylonjs/core/Materials/Node/Blocks/gradientBlock.js';
87
95
  import { Attractor } from '@babylonjs/core/Particles/attractor.js';
@@ -566,20 +574,27 @@ function BoundPropertyCoreImpl(props, ref) {
566
574
  // Hook the property, using the specific hook that also catches changes to nested properties as well (like x/y/z on a Vector3 for example).
567
575
  const value = useSpecificProperty(target, propertyKey);
568
576
  const convertedValue = convertTo ? convertTo(value) : value;
577
+ const onChange = useMemo(() => {
578
+ const propertyDescriptor = GetPropertyDescriptor(target, propertyKey)?.[1];
579
+ if (propertyDescriptor && (propertyDescriptor.set || propertyDescriptor.writable)) {
580
+ return (val) => {
581
+ const oldValue = target[propertyKey];
582
+ const newValue = convertFrom ? convertFrom(val) : val;
583
+ target[propertyKey] = newValue;
584
+ notifyPropertyChanged(target, propertyKey, oldValue, newValue);
585
+ };
586
+ }
587
+ return undefined;
588
+ }, [target, propertyKey, convertFrom, notifyPropertyChanged]);
569
589
  const propsToSend = {
570
590
  ...rest,
571
591
  ref,
572
592
  value: convertedValue,
573
- onChange: (val) => {
574
- const oldValue = target[propertyKey];
575
- const newValue = convertFrom ? convertFrom(val) : val;
576
- target[propertyKey] = newValue;
577
- notifyPropertyChanged(target, propertyKey, oldValue, newValue);
578
- },
593
+ onChange,
579
594
  };
580
595
  return jsx(Component, { ...propsToSend });
581
596
  };
582
- }, [useSpecificProperty]);
597
+ }, [useSpecificProperty, notifyPropertyChanged]);
583
598
  return jsx(SpecificComponent, { ...props });
584
599
  }
585
600
  const BoundPropertyCore = CreateGenericForwardRef(BoundPropertyCoreImpl);
@@ -659,10 +674,11 @@ function copyCommandToClipboard(strCommand) {
659
674
 
660
675
  const ToolContext = createContext({ useFluent: false, disableCopy: false, toolName: "", size: undefined });
661
676
 
662
- const Link = (props) => {
677
+ const Link = forwardRef((props, ref) => {
663
678
  const { target, url, onLink, ...rest } = props;
664
- return (jsxs(Link$1, { inline: true, target: target === "current" ? "_self" : "_blank", rel: "noopener noreferrer", href: url, onClick: onLink ?? undefined, ...rest, children: [props.children, jsx(Body1, { children: props.value })] }));
665
- };
679
+ return (jsxs(Link$1, { ref: ref, inline: true, target: target === "current" ? "_self" : "_blank", rel: "noopener noreferrer", href: url, onClick: onLink ?? undefined, ...rest, children: [props.children, jsx(Body1, { wrap: false, truncate: true, children: props.value })] }));
680
+ });
681
+ Link.displayName = "Link";
666
682
 
667
683
  /**
668
684
  * Toggles between two states using a button with icons.
@@ -790,10 +806,18 @@ const usePropertyLineStyles = makeStyles({
790
806
  },
791
807
  rightContent: {
792
808
  flex: "0 1 auto",
809
+ minWidth: 0,
810
+ overflow: "hidden",
793
811
  display: "flex",
794
812
  alignItems: "center",
795
813
  justifyContent: "flex-end",
796
814
  },
815
+ childWrapper: {
816
+ minWidth: 0,
817
+ overflow: "hidden",
818
+ textOverflow: "ellipsis",
819
+ whiteSpace: "nowrap",
820
+ },
797
821
  infoPopup: {
798
822
  whiteSpace: "normal",
799
823
  wordBreak: "break-word",
@@ -841,7 +865,7 @@ const PropertyLine = forwardRef((props, ref) => {
841
865
  cachedVal.current = props.value;
842
866
  props.onChange(null);
843
867
  }
844
- }, title: "Toggle null state" })), processedChildren, onCopy && !disableCopy && (jsx(Button, { className: classes.copy, title: "Copy to clipboard", appearance: "transparent", icon: size === "small" ? Copy16Regular : Copy20Regular, onClick: () => copyCommandToClipboard(onCopy()) }))] })] }), expandedContent && (jsx(Collapse, { visible: !!expanded, children: jsx("div", { className: classes.expandedContentDiv, children: expandedContent }) }))] }));
868
+ }, title: "Toggle null state" })), jsx("div", { className: classes.childWrapper, children: processedChildren }), onCopy && !disableCopy && (jsx(Button, { className: classes.copy, title: "Copy to clipboard", appearance: "transparent", icon: size === "small" ? Copy16Regular : Copy20Regular, onClick: () => copyCommandToClipboard(onCopy()) }))] })] }), expandedContent && (jsx(Collapse, { visible: !!expanded, children: jsx("div", { className: classes.expandedContentDiv, children: expandedContent }) }))] }));
845
869
  });
846
870
  const useLineStyles = makeStyles({
847
871
  container: {
@@ -892,7 +916,7 @@ const LinkToEntityPropertyLine = (props) => {
892
916
  !linkedEntity.reservedDataStore?.hidden && jsx(LinkPropertyLine, { ...rest, value: linkedEntity.name, onLink: () => (selectionService.selectedEntity = linkedEntity) }));
893
917
  };
894
918
 
895
- const useStyles$r = makeStyles({
919
+ const useStyles$s = makeStyles({
896
920
  accordion: {
897
921
  overflowX: "hidden",
898
922
  overflowY: "auto",
@@ -936,13 +960,13 @@ const useStyles$r = makeStyles({
936
960
  });
937
961
  const AccordionSection = (props) => {
938
962
  AccordionSection.displayName = "AccordionSection";
939
- const classes = useStyles$r();
963
+ const classes = useStyles$s();
940
964
  return jsx("div", { className: classes.panelDiv, children: props.children });
941
965
  };
942
966
  const StringAccordion = Accordion$1;
943
967
  const Accordion = forwardRef((props, ref) => {
944
968
  Accordion.displayName = "Accordion";
945
- const classes = useStyles$r();
969
+ const classes = useStyles$s();
946
970
  const { size } = useContext(ToolContext);
947
971
  const { children, highlightSections, ...rest } = props;
948
972
  const validChildren = useMemo(() => {
@@ -1073,7 +1097,7 @@ const CompactModeContextProvider = (props) => {
1073
1097
  function AsReadonlyArray(array) {
1074
1098
  return array;
1075
1099
  }
1076
- const useStyles$q = makeStyles({
1100
+ const useStyles$r = makeStyles({
1077
1101
  rootDiv: {
1078
1102
  flex: 1,
1079
1103
  overflow: "hidden",
@@ -1082,7 +1106,7 @@ const useStyles$q = makeStyles({
1082
1106
  },
1083
1107
  });
1084
1108
  function ExtensibleAccordion(props) {
1085
- const classes = useStyles$q();
1109
+ const classes = useStyles$r();
1086
1110
  const { children, sections, sectionContent, context, sectionsRef } = props;
1087
1111
  const defaultSections = useMemo(() => {
1088
1112
  const defaultSections = [];
@@ -1187,7 +1211,7 @@ function ExtensibleAccordion(props) {
1187
1211
  })] }) })) }));
1188
1212
  }
1189
1213
 
1190
- const useStyles$p = makeStyles({
1214
+ const useStyles$q = makeStyles({
1191
1215
  paneRootDiv: {
1192
1216
  display: "flex",
1193
1217
  flex: 1,
@@ -1200,7 +1224,7 @@ const useStyles$p = makeStyles({
1200
1224
  */
1201
1225
  const SidePaneContainer = forwardRef((props, ref) => {
1202
1226
  const { className, ...rest } = props;
1203
- const classes = useStyles$p();
1227
+ const classes = useStyles$q();
1204
1228
  return (jsx("div", { className: mergeClasses(classes.paneRootDiv, className), ref: ref, ...rest, children: props.children }));
1205
1229
  });
1206
1230
 
@@ -1266,7 +1290,7 @@ const Theme = (props) => {
1266
1290
  return (jsx(FluentProvider, { theme: isDarkMode !== invert ? DarkTheme : LightTheme, applyStylesToPortals: applyStylesToPortals, ...rest, children: props.children }));
1267
1291
  };
1268
1292
 
1269
- const useStyles$o = makeStyles({
1293
+ const useStyles$p = makeStyles({
1270
1294
  extensionTeachingPopover: {
1271
1295
  maxWidth: "320px",
1272
1296
  },
@@ -1277,7 +1301,7 @@ const useStyles$o = makeStyles({
1277
1301
  * @returns The teaching moment popover.
1278
1302
  */
1279
1303
  const TeachingMoment = ({ shouldDisplay, positioningRef, onOpenChange, title, description }) => {
1280
- const classes = useStyles$o();
1304
+ const classes = useStyles$p();
1281
1305
  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 })] }) }));
1282
1306
  };
1283
1307
 
@@ -1532,13 +1556,13 @@ function ConstructorFactory(constructor) {
1532
1556
  return (...args) => new constructor(...args);
1533
1557
  }
1534
1558
 
1535
- const useStyles$n = makeStyles({
1559
+ const useStyles$o = makeStyles({
1536
1560
  placeholderDiv: {
1537
1561
  padding: `${tokens.spacingVerticalM} ${tokens.spacingHorizontalM}`,
1538
1562
  },
1539
1563
  });
1540
1564
  const PropertiesPane = (props) => {
1541
- const classes = useStyles$n();
1565
+ const classes = useStyles$o();
1542
1566
  const entity = props.context;
1543
1567
  return entity != null ? (jsx(ExtensibleAccordion, { ...props })) : (jsx("div", { className: classes.placeholderDiv, children: jsx(Body1Strong, { italic: true, children: "No entity selected." }) }));
1544
1568
  };
@@ -1563,7 +1587,7 @@ function ToFeaturesString(options) {
1563
1587
  features.push({ key: "location", value: "no" });
1564
1588
  return features.map((feature) => `${feature.key}=${feature.value}`).join(",");
1565
1589
  }
1566
- const useStyles$m = makeStyles({
1590
+ const useStyles$n = makeStyles({
1567
1591
  container: {
1568
1592
  display: "flex",
1569
1593
  flexGrow: 1,
@@ -1578,7 +1602,7 @@ const useStyles$m = makeStyles({
1578
1602
  */
1579
1603
  const ChildWindow = (props) => {
1580
1604
  const { id, children, onOpenChange, imperativeRef: imperativeRef } = props;
1581
- const classes = useStyles$m();
1605
+ const classes = useStyles$n();
1582
1606
  const [windowState, setWindowState] = useState();
1583
1607
  const [childWindow, setChildWindow] = useState();
1584
1608
  const storageKey = id ? `Babylon/Settings/ChildWindow/${id}/Bounds` : null;
@@ -1790,7 +1814,7 @@ function useResizeHandle(params) {
1790
1814
 
1791
1815
  const RootComponentServiceIdentity = Symbol("RootComponent");
1792
1816
  const ShellServiceIdentity = Symbol("ShellService");
1793
- const useStyles$l = makeStyles({
1817
+ const useStyles$m = makeStyles({
1794
1818
  mainView: {
1795
1819
  flex: 1,
1796
1820
  display: "flex",
@@ -1976,12 +2000,12 @@ const DockMenu = (props) => {
1976
2000
  };
1977
2001
  const PaneHeader = (props) => {
1978
2002
  const { id, title, dockOptions } = props;
1979
- const classes = useStyles$l();
2003
+ const classes = useStyles$m();
1980
2004
  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, {}) }) })] }) }));
1981
2005
  };
1982
2006
  // 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.
1983
2007
  const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Component, displayName: displayName, suppressTeachingMoment }) => {
1984
- const classes = useStyles$l();
2008
+ const classes = useStyles$m();
1985
2009
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Bar/${verticalLocation}/${horizontalLocation}/${displayName ?? id}`), [displayName, id]);
1986
2010
  const teachingMoment = useTeachingMoment(suppressTeachingMoment);
1987
2011
  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, {}) })] }));
@@ -1989,7 +2013,7 @@ const ToolbarItem = ({ verticalLocation, horizontalLocation, id, component: Comp
1989
2013
  // TODO: Handle overflow, possibly via https://react.fluentui.dev/?path=/docs/components-overflow--docs with priority.
1990
2014
  // This component just renders a toolbar with left aligned toolbar items on the left and right aligned toolbar items on the right.
1991
2015
  const Toolbar = ({ location, components }) => {
1992
- const classes = useStyles$l();
2016
+ const classes = useStyles$m();
1993
2017
  const leftComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "left"), [components]);
1994
2018
  const rightComponents = useMemo(() => components.filter((entry) => entry.horizontalLocation === "right"), [components]);
1995
2019
  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))) })] })) }));
@@ -1999,7 +2023,7 @@ const SidePaneTab = (props) => {
1999
2023
  const { location, id, isSelected, dockOptions,
2000
2024
  // eslint-disable-next-line @typescript-eslint/naming-convention
2001
2025
  icon: Icon, title, suppressTeachingMoment, } = props;
2002
- const classes = useStyles$l();
2026
+ const classes = useStyles$m();
2003
2027
  const useTeachingMoment = useMemo(() => MakePopoverTeachingMoment(`Pane/${location}/${title ?? id}`), [title, id]);
2004
2028
  const teachingMoment = useTeachingMoment(suppressTeachingMoment);
2005
2029
  const tabClass = mergeClasses(classes.tab, isSelected ? undefined : classes.unselectedTab);
@@ -2012,7 +2036,7 @@ const SidePaneTab = (props) => {
2012
2036
  // In "compact" mode, the tab list is integrated into the pane itself.
2013
2037
  // In "full" mode, the returned tab list is later injected into the toolbar.
2014
2038
  function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems) {
2015
- const classes = useStyles$l();
2039
+ const classes = useStyles$m();
2016
2040
  const [topSelectedTab, setTopSelectedTab] = useState();
2017
2041
  const [bottomSelectedTab, setBottomSelectedTab] = useState();
2018
2042
  const [collapsed, setCollapsed] = useState(false);
@@ -2203,7 +2227,7 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
2203
2227
  undock: () => onDockChanged.notifyObservers({ location: "right", dock: false }),
2204
2228
  };
2205
2229
  const rootComponent = () => {
2206
- const classes = useStyles$l();
2230
+ const classes = useStyles$m();
2207
2231
  const [sidePaneDockOverrides, setSidePaneDockOverrides] = useSidePaneDockOverrides();
2208
2232
  // This function returns a promise that resolves after the dock change takes effect so that
2209
2233
  // we can then select the re-docked pane.
@@ -2638,7 +2662,7 @@ function useCommandContextMenuState(commands) {
2638
2662
  checkmark: command.icon ? null : undefined, icon: command.icon ? jsx(command.icon, {}) : undefined, name: "toggleCommands", value: command.displayName, children: command.displayName }, command.displayName)));
2639
2663
  return [checkedContextMenuItems, onContextMenuCheckedValueChange, contextMenuItems];
2640
2664
  }
2641
- const useStyles$k = makeStyles({
2665
+ const useStyles$l = makeStyles({
2642
2666
  rootDiv: {
2643
2667
  flex: 1,
2644
2668
  overflow: "hidden",
@@ -2706,14 +2730,14 @@ function MakeInlineCommandElement(command, isPlaceholder) {
2706
2730
  }
2707
2731
  const SceneTreeItem = (props) => {
2708
2732
  const { isSelected, select } = props;
2709
- const classes = useStyles$k();
2733
+ const classes = useStyles$l();
2710
2734
  const [compactMode] = useCompactMode();
2711
2735
  const treeItemLayoutClass = mergeClasses(classes.sceneTreeItemLayout, compactMode ? classes.treeItemLayoutCompact : undefined);
2712
2736
  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"));
2713
2737
  };
2714
2738
  const SectionTreeItem = (props) => {
2715
2739
  const { section, isFiltering, commandProviders, expandAll, collapseAll } = props;
2716
- const classes = useStyles$k();
2740
+ const classes = useStyles$l();
2717
2741
  const [compactMode] = useCompactMode();
2718
2742
  // Get the commands that apply to this section.
2719
2743
  const commands = useResource(useCallback(() => {
@@ -2730,7 +2754,7 @@ const SectionTreeItem = (props) => {
2730
2754
  };
2731
2755
  const EntityTreeItem = (props) => {
2732
2756
  const { entityItem, isSelected, select, isFiltering, commandProviders, expandAll, collapseAll } = props;
2733
- const classes = useStyles$k();
2757
+ const classes = useStyles$l();
2734
2758
  const [compactMode] = useCompactMode();
2735
2759
  const hasChildren = !!entityItem.children?.length;
2736
2760
  const displayInfo = useResource(useCallback(() => {
@@ -2823,7 +2847,7 @@ const EntityTreeItem = (props) => {
2823
2847
  }, 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] }) })] }));
2824
2848
  };
2825
2849
  const SceneExplorer = (props) => {
2826
- const classes = useStyles$k();
2850
+ const classes = useStyles$l();
2827
2851
  const { sections, entityCommandProviders, sectionCommandProviders, scene, selectedEntity } = props;
2828
2852
  const [openItems, setOpenItems] = useState(new Set());
2829
2853
  const [sceneVersion, setSceneVersion] = useState(0);
@@ -3618,14 +3642,14 @@ const TextPropertyLine = (props) => {
3618
3642
  return (jsx(PropertyLine, { ...props, children: jsx(Body1, { title: title, children: value }) }));
3619
3643
  };
3620
3644
 
3621
- const useStyles$j = makeStyles({
3645
+ const useStyles$k = makeStyles({
3622
3646
  pinnedStatsPane: {
3623
3647
  flex: "0 1 auto",
3624
3648
  paddingBottom: tokens.spacingHorizontalM,
3625
3649
  },
3626
3650
  });
3627
3651
  const StatsPane = (props) => {
3628
- const classes = useStyles$j();
3652
+ const classes = useStyles$k();
3629
3653
  const scene = props.context;
3630
3654
  const engine = scene.getEngine();
3631
3655
  const fps = useObservableState(() => Math.round(engine.getFps()), engine.onBeginFrameObservable);
@@ -3803,7 +3827,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3803
3827
  keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
3804
3828
  ...BabylonWebResources,
3805
3829
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3806
- getExtensionModuleAsync: async () => await import('./exportService-DCYVwNJE.js'),
3830
+ getExtensionModuleAsync: async () => await import('./exportService-bnHVeuTp.js'),
3807
3831
  },
3808
3832
  {
3809
3833
  name: "Capture Tools",
@@ -3811,7 +3835,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3811
3835
  keywords: ["capture", "screenshot", "gif", "video", "tools"],
3812
3836
  ...BabylonWebResources,
3813
3837
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3814
- getExtensionModuleAsync: async () => await import('./captureService-Bo4xI_X9.js'),
3838
+ getExtensionModuleAsync: async () => await import('./captureService-BRFdjxFs.js'),
3815
3839
  },
3816
3840
  {
3817
3841
  name: "Import Tools",
@@ -3819,7 +3843,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3819
3843
  keywords: ["import", "tools"],
3820
3844
  ...BabylonWebResources,
3821
3845
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3822
- getExtensionModuleAsync: async () => await import('./importService--DIvq_s3.js'),
3846
+ getExtensionModuleAsync: async () => await import('./importService-CGjLAv9b.js'),
3823
3847
  },
3824
3848
  {
3825
3849
  name: "Quick Creation Tools (Preview)",
@@ -3827,7 +3851,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3827
3851
  keywords: ["creation", "tools"],
3828
3852
  ...BabylonWebResources,
3829
3853
  author: { name: "Babylon.js", forumUserName: "" },
3830
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-BFr5_q0T.js'),
3854
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-B_KvGvkG.js'),
3831
3855
  },
3832
3856
  ]);
3833
3857
 
@@ -4045,7 +4069,7 @@ const Dropdown = (props) => {
4045
4069
  const NumberDropdown = Dropdown;
4046
4070
  const StringDropdown = Dropdown;
4047
4071
 
4048
- const useStyles$i = makeStyles({
4072
+ const useStyles$j = makeStyles({
4049
4073
  surface: {
4050
4074
  maxWidth: "400px",
4051
4075
  },
@@ -4060,7 +4084,7 @@ const useStyles$i = makeStyles({
4060
4084
  const Popover = forwardRef((props, ref) => {
4061
4085
  const { children } = props;
4062
4086
  const [popoverOpen, setPopoverOpen] = useState(false);
4063
- const classes = useStyles$i();
4087
+ const classes = useStyles$j();
4064
4088
  return (jsxs(Popover$1, { open: popoverOpen, onOpenChange: (_, data) => setPopoverOpen(data.open), positioning: {
4065
4089
  align: "start",
4066
4090
  overflowBoundary: document.body,
@@ -4269,7 +4293,7 @@ const ColorPropertyLine = forwardRef((props, ref) => {
4269
4293
  const Color3PropertyLine = ColorPropertyLine;
4270
4294
  const Color4PropertyLine = ColorPropertyLine;
4271
4295
 
4272
- const useStyles$h = makeStyles({
4296
+ const useStyles$i = makeStyles({
4273
4297
  dropdown: {
4274
4298
  ...UniformWidthStyling,
4275
4299
  },
@@ -4281,7 +4305,7 @@ const useStyles$h = makeStyles({
4281
4305
  */
4282
4306
  const DropdownPropertyLine = forwardRef((props, ref) => {
4283
4307
  DropdownPropertyLine.displayName = "DropdownPropertyLine";
4284
- const classes = useStyles$h();
4308
+ const classes = useStyles$i();
4285
4309
  return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
4286
4310
  });
4287
4311
  /**
@@ -4855,7 +4879,7 @@ class ServiceContainer {
4855
4879
  }
4856
4880
  }
4857
4881
 
4858
- const useStyles$g = makeStyles({
4882
+ const useStyles$h = makeStyles({
4859
4883
  themeButton: {
4860
4884
  margin: 0,
4861
4885
  },
@@ -4874,7 +4898,7 @@ const ThemeSelectorServiceDefinition = {
4874
4898
  suppressTeachingMoment: true,
4875
4899
  order: -300,
4876
4900
  component: () => {
4877
- const classes = useStyles$g();
4901
+ const classes = useStyles$h();
4878
4902
  const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
4879
4903
  const onSelectedThemeChange = useCallback((e, data) => {
4880
4904
  setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
@@ -4891,7 +4915,7 @@ const ThemeSelectorServiceDefinition = {
4891
4915
  },
4892
4916
  };
4893
4917
 
4894
- const useStyles$f = makeStyles({
4918
+ const useStyles$g = makeStyles({
4895
4919
  app: {
4896
4920
  colorScheme: "light dark",
4897
4921
  flexGrow: 1,
@@ -4928,7 +4952,7 @@ function MakeModularTool(options) {
4928
4952
  SetThemeMode(themeMode);
4929
4953
  }
4930
4954
  const modularToolRootComponent = () => {
4931
- const classes = useStyles$f();
4955
+ const classes = useStyles$g();
4932
4956
  const [extensionManagerContext, setExtensionManagerContext] = useState();
4933
4957
  const [requiredExtensions, setRequiredExtensions] = useState();
4934
4958
  const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
@@ -4954,7 +4978,7 @@ function MakeModularTool(options) {
4954
4978
  });
4955
4979
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
4956
4980
  if (extensionFeeds.length > 0) {
4957
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-BnPM7JOI.js');
4981
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-CbClUfJa.js');
4958
4982
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
4959
4983
  }
4960
4984
  // Register the theme selector service (for selecting the theme) if theming is configured.
@@ -5136,7 +5160,7 @@ const MeshIcon = createFluentIcon("Mesh", "16", '<path d="M14.03,3.54l-5.11-2.07
5136
5160
  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" />');
5137
5161
  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"/>');
5138
5162
 
5139
- const useStyles$e = makeStyles({
5163
+ const useStyles$f = makeStyles({
5140
5164
  coordinatesModeButton: {
5141
5165
  margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
5142
5166
  },
@@ -5146,7 +5170,7 @@ const useStyles$e = makeStyles({
5146
5170
  });
5147
5171
  const GizmoToolbar = (props) => {
5148
5172
  const { scene, entity, gizmoService } = props;
5149
- const classes = useStyles$e();
5173
+ const classes = useStyles$f();
5150
5174
  const gizmoManager = useResource(useCallback(() => {
5151
5175
  const utilityLayerRef = gizmoService.getUtilityLayer(scene);
5152
5176
  const keepDepthUtilityLayerRef = gizmoService.getUtilityLayer(scene, "keepDepth");
@@ -5261,7 +5285,7 @@ const GizmoToolbarServiceDefinition = {
5261
5285
  },
5262
5286
  };
5263
5287
 
5264
- const useStyles$d = makeStyles({
5288
+ const useStyles$e = makeStyles({
5265
5289
  badge: {
5266
5290
  margin: tokens.spacingHorizontalXXS,
5267
5291
  fontFamily: "monospace",
@@ -5277,7 +5301,7 @@ const MiniStatsServiceDefinition = {
5277
5301
  horizontalLocation: "right",
5278
5302
  suppressTeachingMoment: true,
5279
5303
  component: () => {
5280
- const classes = useStyles$d();
5304
+ const classes = useStyles$e();
5281
5305
  const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
5282
5306
  const engine = scene?.getEngine();
5283
5307
  const fps = useObservableState(useCallback(() => (engine ? Math.round(engine.getFps()) : null), [engine]), engine?.onBeginFrameObservable);
@@ -6085,7 +6109,7 @@ const CommonBlendModes = [
6085
6109
  * The below ParticleSystem consts were defined before new Engine alpha blend modes were added, so we have to reference
6086
6110
  * the ParticleSystem.FOO consts explicitly (as the underlying const values are different - they get mapped to engine consts within baseParticleSystem.ts)
6087
6111
  */
6088
- [
6112
+ const BlendModeOptions = [
6089
6113
  { label: "Add", value: ParticleSystem.BLENDMODE_ADD },
6090
6114
  { label: "Multiply", value: ParticleSystem.BLENDMODE_MULTIPLY },
6091
6115
  { label: "Multiply Add", value: ParticleSystem.BLENDMODE_MULTIPLYADD },
@@ -6103,6 +6127,15 @@ const AlphaModeOptions = [
6103
6127
  { label: "Subtract", value: Constants.ALPHA_SUBTRACT },
6104
6128
  { label: "Multiply", value: Constants.ALPHA_MULTIPLY },
6105
6129
  ].concat(CommonBlendModes);
6130
+ /**
6131
+ * Used to populate the billboardMode dropdown for particle systems.
6132
+ */
6133
+ const ParticleBillboardModeOptions = [
6134
+ { label: "All", value: ParticleSystem.BILLBOARDMODE_ALL },
6135
+ { label: "Y", value: ParticleSystem.BILLBOARDMODE_Y },
6136
+ { label: "Stretched", value: ParticleSystem.BILLBOARDMODE_STRETCHED },
6137
+ { label: "Stretched Local", value: ParticleSystem.BILLBOARDMODE_STRETCHED_LOCAL },
6138
+ ];
6106
6139
 
6107
6140
  const OrientationOptions = [
6108
6141
  { label: "Clockwise", value: Material.ClockWiseSideOrientation },
@@ -6247,7 +6280,7 @@ const PBRBaseMaterialSheenProperties = (props) => {
6247
6280
  } }), 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" })] })] }));
6248
6281
  };
6249
6282
 
6250
- const useStyles$c = makeStyles({
6283
+ const useStyles$d = makeStyles({
6251
6284
  root: {
6252
6285
  display: "grid",
6253
6286
  gridTemplateRows: "repeat(1fr)",
@@ -6263,6 +6296,11 @@ const useStyles$c = makeStyles({
6263
6296
  input: {
6264
6297
  minWidth: 0,
6265
6298
  },
6299
+ listbox: {
6300
+ width: "fit-content",
6301
+ minWidth: "fit-content",
6302
+ maxWidth: "350px",
6303
+ },
6266
6304
  });
6267
6305
  /**
6268
6306
  * Wrapper around a Fluent ComboBox that allows for filtering options.
@@ -6272,7 +6310,7 @@ const useStyles$c = makeStyles({
6272
6310
  const ComboBox = (props) => {
6273
6311
  ComboBox.displayName = "ComboBox";
6274
6312
  const comboId = useId();
6275
- const styles = useStyles$c();
6313
+ const styles = useStyles$d();
6276
6314
  const { size } = useContext(ToolContext);
6277
6315
  // Find the label for the current value
6278
6316
  const getLabel = (value) => props.options.find((opt) => opt.value === value)?.label ?? "";
@@ -6292,9 +6330,25 @@ const ComboBox = (props) => {
6292
6330
  setQuery(data.optionText ?? "");
6293
6331
  data.optionValue && props.onChange(data.optionValue);
6294
6332
  };
6295
- 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, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
6333
+ 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 }, listbox: { className: styles.listbox }, onOptionSelect: onOptionSelect, "aria-labelledby": comboId, placeholder: "Search..", onChange: (ev) => setQuery(ev.target.value), value: query, children: children })] }));
6296
6334
  };
6297
6335
 
6336
+ const useStyles$c = makeStyles({
6337
+ linkDiv: {
6338
+ display: "flex",
6339
+ flexDirection: "row",
6340
+ alignItems: "center",
6341
+ gap: tokens.spacingHorizontalS,
6342
+ minWidth: 0,
6343
+ overflow: "hidden",
6344
+ },
6345
+ link: {
6346
+ minWidth: 0,
6347
+ overflow: "hidden",
6348
+ textOverflow: "ellipsis",
6349
+ whiteSpace: "nowrap",
6350
+ },
6351
+ });
6298
6352
  /**
6299
6353
  * A generic primitive component with a ComboBox for selecting from a list of entities.
6300
6354
  * Supports entities with duplicate names by using uniqueId for identity.
@@ -6302,7 +6356,9 @@ const ComboBox = (props) => {
6302
6356
  * @returns EntitySelector component
6303
6357
  */
6304
6358
  function EntitySelector(props) {
6305
- const { value, onChange, getEntities, getName, filter } = props;
6359
+ const { value, onLink, getEntities, getName, filter, defaultValue } = props;
6360
+ const onChange = props.onChange;
6361
+ const classes = useStyles$c();
6306
6362
  // Build options with uniqueId as key
6307
6363
  const options = useMemo(() => {
6308
6364
  return getEntities()
@@ -6313,13 +6369,27 @@ function EntitySelector(props) {
6313
6369
  }))
6314
6370
  .sort((a, b) => a.label.localeCompare(b.label));
6315
6371
  }, [getEntities, getName, filter]);
6372
+ const [isEditing, setIsEditing] = useState(false);
6316
6373
  const handleEntitySelect = (key) => {
6317
6374
  const entity = getEntities().find((e) => e.uniqueId.toString() === key);
6318
- onChange(entity ?? null);
6375
+ onChange?.(entity ?? null);
6376
+ setIsEditing(false);
6319
6377
  };
6320
6378
  // Get current entity key for display
6321
6379
  const currentKey = value ? value.uniqueId.toString() : "";
6322
- return jsx(ComboBox, { label: "", options: options, value: currentKey, onChange: handleEntitySelect });
6380
+ if (value && !isEditing) {
6381
+ // If there is a value and we are not editing, show the link view
6382
+ return (jsxs("div", { className: classes.linkDiv, children: [jsx(Tooltip, { content: getName(value), relationship: "label", children: jsx(Link, { className: classes.link, value: getName(value), onLink: () => onLink(value) }) }), onChange &&
6383
+ (defaultValue !== undefined ? (
6384
+ // If the defaultValue is specified, then allow resetting to the default
6385
+ jsx(Button, { icon: LinkDismissRegular, onClick: () => onChange(defaultValue) })) : (
6386
+ // Otherwise, just allow editing to a new value
6387
+ jsx(Button, { icon: LinkEditRegular, onClick: () => setIsEditing(true) })))] }));
6388
+ }
6389
+ else {
6390
+ // Otherwise, show the ComboBox for selection
6391
+ return jsx(ComboBox, { label: "", options: options, value: currentKey, onChange: handleEntitySelect });
6392
+ }
6323
6393
  }
6324
6394
  EntitySelector.displayName = "EntitySelector";
6325
6395
 
@@ -6336,6 +6406,19 @@ const MaterialSelector = (props) => {
6336
6406
  return jsx(EntitySelector, { ...rest, getEntities: getMaterials, getName: getName });
6337
6407
  };
6338
6408
 
6409
+ /**
6410
+ * A primitive component with a ComboBox for selecting from existing scene nodes.
6411
+ * @param props NodeSelectorProps
6412
+ * @returns NodeSelector component
6413
+ */
6414
+ const NodeSelector = (props) => {
6415
+ NodeSelector.displayName = "NodeSelector";
6416
+ const { scene, ...rest } = props;
6417
+ const getNodes = useCallback(() => scene.getNodes(), [scene]);
6418
+ const getName = useCallback((node) => node.name, []);
6419
+ return jsx(EntitySelector, { ...rest, getEntities: getNodes, getName: getName });
6420
+ };
6421
+
6339
6422
  /**
6340
6423
  * A button that uploads a file and either:
6341
6424
  * - Updates an existing Texture or CubeTexture via updateURL (if texture prop is provided)
@@ -6410,16 +6493,31 @@ const useStyles$b = makeStyles({
6410
6493
  */
6411
6494
  const TextureSelector = (props) => {
6412
6495
  TextureSelector.displayName = "TextureSelector";
6413
- const { scene, cubeOnly, value, onChange } = props;
6496
+ const { scene, cubeOnly, value, onChange, onLink, defaultValue } = props;
6414
6497
  const classes = useStyles$b();
6415
6498
  const getTextures = useCallback(() => scene.textures, [scene.textures]);
6416
- const getName = useCallback((texture) => texture.displayName || texture.name, []);
6499
+ const getName = useCallback((texture) => texture.displayName || texture.name || `${texture.getClassName() || "Unnamed Texture"} (${texture.uniqueId})`, []);
6417
6500
  const filter = useCallback((texture) => !cubeOnly || texture.isCube, [cubeOnly]);
6418
- return (jsxs("div", { className: classes.container, children: [jsx(EntitySelector, { value: value, onChange: onChange, getEntities: getTextures, getName: getName, filter: filter }), jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
6501
+ return (jsxs("div", { className: classes.container, children: [jsx(EntitySelector, { value: value, onChange: onChange, onLink: onLink, defaultValue: defaultValue, getEntities: getTextures, getName: getName, filter: filter }), !value && jsx(TextureUpload, { scene: scene, onChange: onChange, cubeOnly: cubeOnly })] }));
6502
+ };
6503
+
6504
+ /**
6505
+ * A primitive component with a ComboBox for selecting from existing scene skeletons.
6506
+ * @param props SkeletonSelectorProps
6507
+ * @returns SkeletonSelector component
6508
+ */
6509
+ const SkeletonSelector = (props) => {
6510
+ SkeletonSelector.displayName = "SkeletonSelector";
6511
+ const { scene, ...rest } = props;
6512
+ const getSkeletons = useCallback(() => scene.skeletons, [scene.skeletons]);
6513
+ const getName = useCallback((skeleton) => skeleton.name, []);
6514
+ return jsx(EntitySelector, { ...rest, getEntities: getSkeletons, getName: getName });
6419
6515
  };
6420
6516
 
6517
+ const NodeSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(NodeSelector, { ...props }) });
6421
6518
  const MaterialSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(MaterialSelector, { ...props }) });
6422
6519
  const TextureSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(TextureSelector, { ...props }) });
6520
+ const SkeletonSelectorPropertyLine = (props) => jsx(PropertyLine, { ...props, children: jsx(SkeletonSelector, { ...props }) });
6423
6521
 
6424
6522
  /**
6425
6523
  * Displays the lighting and color properties of a PBR material.
@@ -6436,9 +6534,10 @@ const PBRMaterialLightingAndColorProperties = (props) => {
6436
6534
  * @returns A JSX element representing the texture channels.
6437
6535
  */
6438
6536
  const PBRMaterialTextureProperties = (props) => {
6439
- const { material } = props;
6537
+ const { material, selectionService } = props;
6440
6538
  const scene = material.getScene();
6441
- return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Albedo", target: material, propertyKey: "albedoTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeightTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughnessTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Metallic Roughness", target: material, propertyKey: "metallicTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflection", target: material, propertyKey: "reflectionTexture", scene: scene, cubeOnly: true, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Refraction", target: material, propertyKey: "refractionTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflectivity", target: material, propertyKey: "reflectivityTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Micro-surface", target: material, propertyKey: "microSurfaceTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Bump", target: material, propertyKey: "bumpTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Emissive", target: material, propertyKey: "emissiveTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Opacity", target: material, propertyKey: "opacityTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Ambient", target: material, propertyKey: "ambientTexture", scene: scene, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Lightmap", target: material, propertyKey: "lightmapTexture", scene: scene, defaultValue: null })] }));
6539
+ const selectEntity = (entity) => (selectionService.selectedEntity = entity);
6540
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Albedo", target: material, propertyKey: "albedoTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Weight", target: material, propertyKey: "baseWeightTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Base Diffuse Roughness", target: material, propertyKey: "baseDiffuseRoughnessTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Metallic Roughness", target: material, propertyKey: "metallicTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflection", target: material, propertyKey: "reflectionTexture", scene: scene, cubeOnly: true, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Refraction", target: material, propertyKey: "refractionTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Reflectivity", target: material, propertyKey: "reflectivityTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Micro-surface", target: material, propertyKey: "microSurfaceTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Bump", target: material, propertyKey: "bumpTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Emissive", target: material, propertyKey: "emissiveTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Opacity", target: material, propertyKey: "opacityTexture", scene: scene, onLink: selectEntity, defaultValue: null }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Ambient", target: material, propertyKey: "ambientTexture", scene: scene, onLink: selectEntity, defaultValue: null }), "component=", TextureSelectorPropertyLine, jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Lightmap", target: material, propertyKey: "lightmapTexture", scene: scene, onLink: selectEntity, defaultValue: null })] }));
6442
6541
  };
6443
6542
 
6444
6543
  // TODO: ryamtrem / gehalper This function is temporal until there is a line control to handle texture links (similar to the old TextureLinkLineComponent)
@@ -6717,7 +6816,7 @@ const MaterialPropertiesServiceDefinition = {
6717
6816
  content: [
6718
6817
  {
6719
6818
  section: "Textures",
6720
- component: ({ context }) => jsx(PBRMaterialTextureProperties, { material: context }),
6819
+ component: ({ context }) => jsx(PBRMaterialTextureProperties, { material: context, selectionService: selectionService }),
6721
6820
  },
6722
6821
  {
6723
6822
  section: "Lighting & Colors",
@@ -7008,12 +7107,10 @@ const MetadataPropertiesServiceDefinition = {
7008
7107
  const AbstractMeshGeneralProperties = (props) => {
7009
7108
  const { mesh, selectionService } = props;
7010
7109
  // Use the observable to keep keep state up-to-date and re-render the component when it changes.
7011
- const material = useObservableState(() => mesh.material, mesh.onMaterialChangedObservable);
7012
- const skeleton = useProperty(mesh, "skeleton");
7013
7110
  const isAnInstance = useProperty(mesh, "isAnInstance");
7014
7111
  // TODO: Handle case where array is mutated
7015
7112
  const subMeshes = useProperty(mesh, "subMeshes");
7016
- return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Vertices", value: mesh.getTotalVertices() }), jsx(StringifiedPropertyLine, { label: "Faces", value: mesh.getTotalIndices() / 3 }), jsx(StringifiedPropertyLine, { label: "Sub-Meshes", value: subMeshes.length }), jsx(LinkToEntityPropertyLine, { label: "Skeleton", description: "The skeleton associated with the mesh.", entity: skeleton, selectionService: selectionService }), jsx(LinkToEntityPropertyLine, { label: "Material", description: "The material used by the mesh.", entity: material, selectionService: selectionService }), !mesh.isAnInstance && (jsx(BoundProperty, { defaultValue: null, component: MaterialSelectorPropertyLine, label: "Active Material", target: mesh, propertyKey: "material", scene: mesh.getScene() })), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Pickable", target: mesh, propertyKey: "isPickable" }), isAnInstance && mesh instanceof InstancedMesh && (jsx(LinkToEntityPropertyLine, { label: "Source", description: "The source mesh from which this instance was created.", entity: mesh.sourceMesh, selectionService: selectionService }))] }));
7113
+ return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Vertices", value: mesh.getTotalVertices() }), jsx(StringifiedPropertyLine, { label: "Faces", value: mesh.getTotalIndices() / 3 }), jsx(StringifiedPropertyLine, { label: "Sub-Meshes", value: subMeshes.length }), jsx(BoundProperty, { defaultValue: null, component: SkeletonSelectorPropertyLine, label: "Skeleton", description: "The skeleton associated with the mesh.", target: mesh, propertyKey: "skeleton", scene: mesh.getScene(), onLink: (skeleton) => (selectionService.selectedEntity = skeleton) }), !mesh.isAnInstance && (jsx(BoundProperty, { defaultValue: null, component: MaterialSelectorPropertyLine, label: "Material", description: "The material used by the mesh.", target: mesh, propertyKey: "material", scene: mesh.getScene(), onLink: (material) => (selectionService.selectedEntity = material) })), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Pickable", target: mesh, propertyKey: "isPickable" }), isAnInstance && mesh instanceof InstancedMesh && (jsx(BoundProperty, { component: NodeSelectorPropertyLine, label: "Source", description: "The source mesh from which this instance was created.", target: mesh, propertyKey: "sourceMesh", scene: mesh.getScene(), onLink: (node) => (selectionService.selectedEntity = node) }))] }));
7017
7114
  };
7018
7115
  const AbstractMeshDisplayProperties = (props) => {
7019
7116
  const { mesh } = props;
@@ -7294,9 +7391,8 @@ const GaussianSplattingDisplayProperties = (props) => {
7294
7391
 
7295
7392
  const NodeGeneralProperties = (props) => {
7296
7393
  const { node, selectionService } = props;
7297
- const parent = useProperty(node, "parent");
7298
7394
  const isEnabled = useObservableState(() => node.isEnabled(false), node.onEnabledStateChangedObservable);
7299
- return (jsxs(Fragment, { children: [jsx(LinkToEntityPropertyLine, { label: "Parent", description: "The parent of this node", entity: parent, selectionService: selectionService }), jsx(SwitchPropertyLine, { label: "Is Enabled", description: "Whether the node is enabled or not.", value: isEnabled, onChange: (checked) => node.setEnabled(checked) }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Inherit Visibility", description: "Whether the node inherits visibility from its parent.", target: node, propertyKey: "inheritVisibility" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Visible", description: "Whether the node is visible or not.", target: node, propertyKey: "isVisible" })] }));
7395
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NodeSelectorPropertyLine, label: "Parent", target: node, propertyKey: "parent", scene: node.getScene(), defaultValue: null, onLink: (parentNode) => (selectionService.selectedEntity = parentNode) }), jsx(SwitchPropertyLine, { label: "Is Enabled", description: "Whether the node is enabled or not.", value: isEnabled, onChange: (checked) => node.setEnabled(checked) }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Inherit Visibility", description: "Whether the node inherits visibility from its parent.", target: node, propertyKey: "inheritVisibility" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Visible", description: "Whether the node is visible or not.", target: node, propertyKey: "isVisible" })] }));
7300
7396
  };
7301
7397
 
7302
7398
  const NodePropertiesServiceDefinition = {
@@ -7679,80 +7775,776 @@ const AttractorList = (props) => {
7679
7775
  } })] }));
7680
7776
  };
7681
7777
 
7682
- const ParticleSystemEmissionProperties = (props) => {
7683
- const { particleSystem: system } = props;
7684
- const emitRateGradients = useParticleSystemProperty(system, "getEmitRateGradients", "function", "addEmitRateGradient", "removeEmitRateGradient", "forceRefreshGradients");
7685
- return (jsx(Fragment, { children: !system.isNodeGenerated && (jsx(FactorGradientList, { gradients: emitRateGradients, label: "Emit Rate Gradient", removeGradient: (gradient) => {
7686
- system.removeEmitRateGradient(gradient.gradient);
7687
- system.forceRefreshGradients();
7688
- }, addGradient: (gradient) => {
7689
- gradient ? system.addEmitRateGradient(gradient.gradient, gradient.factor1, gradient.factor2) : system.addEmitRateGradient(Math.random(), 50, 50);
7690
- system.forceRefreshGradients();
7691
- }, onChange: (_gradient) => {
7692
- system.forceRefreshGradients();
7693
- } })) }));
7694
- };
7695
- const ParticleSystemColorProperties = (props) => {
7778
+ const SnippetDashboardStorageKey = "Babylon/InspectorV2/SnippetDashboard/ParticleSystems";
7779
+ function TryParseJsonString(value) {
7780
+ if (!value) {
7781
+ return undefined;
7782
+ }
7783
+ try {
7784
+ return JSON.parse(value);
7785
+ }
7786
+ catch {
7787
+ return undefined;
7788
+ }
7789
+ }
7790
+ function ParseJsonLoadContents(contents) {
7791
+ if (contents instanceof ArrayBuffer) {
7792
+ const decoder = new TextDecoder("utf-8");
7793
+ return TryParseJsonString(decoder.decode(contents)) ?? undefined;
7794
+ }
7795
+ if (typeof contents === "string") {
7796
+ return TryParseJsonString(contents) ?? undefined;
7797
+ }
7798
+ return undefined;
7799
+ }
7800
+ function NormalizeParticleSystemSerialization(rawData) {
7801
+ const jsonPayload = TryParseJsonString(rawData?.jsonPayload);
7802
+ const particleSystem = TryParseJsonString(jsonPayload?.particleSystem);
7803
+ return particleSystem ?? rawData;
7804
+ }
7805
+ function PersistSnippetId(snippetId) {
7806
+ // Persist snippet IDs locally for quick reuse.
7807
+ try {
7808
+ const existing = JSON.parse(localStorage.getItem(SnippetDashboardStorageKey) || "[]");
7809
+ const list = Array.isArray(existing) ? existing : [];
7810
+ if (!list.includes(snippetId)) {
7811
+ list.unshift(snippetId);
7812
+ }
7813
+ localStorage.setItem(SnippetDashboardStorageKey, JSON.stringify(list.slice(0, 50)));
7814
+ }
7815
+ catch {
7816
+ // Ignore storage failures.
7817
+ }
7818
+ }
7819
+ /**
7820
+ * Display general (high-level) information about a particle system.
7821
+ * @param props Component props.
7822
+ * @returns Render property lines.
7823
+ */
7824
+ const ParticleSystemGeneralProperties = (props) => {
7696
7825
  const { particleSystem: system } = props;
7697
- const colorGradients = useParticleSystemProperty(system, "getColorGradients", "function", "addColorGradient", "removeColorGradient", "forceRefreshGradients");
7698
- return (jsx(Fragment, { children: !system.isNodeGenerated && (jsx(Color4GradientList, { gradients: colorGradients, label: "Color Gradient", removeGradient: (gradient) => {
7699
- system.removeColorGradient(gradient.gradient);
7700
- system.forceRefreshGradients();
7701
- }, addGradient: (gradient) => {
7702
- if (gradient) {
7703
- system.addColorGradient(gradient.gradient, gradient.color1, gradient.color2);
7826
+ const scene = system.getScene();
7827
+ const isBillboardBased = useProperty(system, "isBillboardBased");
7828
+ const capacity = useObservableState(() => system.getCapacity());
7829
+ const activeCount = useObservableState(() => system.getActiveCount(), scene?.onBeforeRenderObservable);
7830
+ const isAlive = useObservableState(() => system.isAlive(), scene?.onBeforeRenderObservable);
7831
+ const isStopping = useObservableState(() => system.isStopping(), scene?.onBeforeRenderObservable);
7832
+ const snippetId = useProperty(system, "snippetId");
7833
+ const [stopRequested, setStopRequested] = useState(false);
7834
+ useEffect(() => {
7835
+ if (!stopRequested) {
7836
+ return;
7837
+ }
7838
+ // Clear stop flag once the system fully stops.
7839
+ if (!isAlive && !isStopping) {
7840
+ setStopRequested(false);
7841
+ }
7842
+ }, [stopRequested, isAlive, isStopping]);
7843
+ const applyParticleSystemJsonToSystem = useCallback((jsonObject) => {
7844
+ if (!scene) {
7845
+ alert("No scene available.");
7846
+ return;
7847
+ }
7848
+ const candidate = NormalizeParticleSystemSerialization(jsonObject);
7849
+ try {
7850
+ // Apply in-place to keep selection stable.
7851
+ ParticleSystem._Parse(candidate, system, scene, "");
7852
+ }
7853
+ catch (e) {
7854
+ alert("Failed to load particle system: " + e);
7855
+ }
7856
+ }, [scene, system]);
7857
+ const loadFromSnippetServer = useCallback(() => {
7858
+ if (!scene) {
7859
+ alert("No scene available.");
7860
+ return;
7861
+ }
7862
+ // Prompt for a snippet id (minimal UX).
7863
+ const requestedSnippetId = window.prompt("Please enter the snippet ID to use");
7864
+ const trimmed = requestedSnippetId?.trim();
7865
+ if (!trimmed) {
7866
+ return;
7867
+ }
7868
+ const request = new XMLHttpRequest();
7869
+ request.onreadystatechange = () => {
7870
+ if (request.readyState !== 4) {
7871
+ return;
7872
+ }
7873
+ if (request.status !== 200) {
7874
+ alert("Unable to load your particle system");
7875
+ return;
7876
+ }
7877
+ try {
7878
+ const responseObject = ParseJsonLoadContents(request.responseText);
7879
+ if (!responseObject) {
7880
+ alert("Unable to load your particle system");
7881
+ return;
7704
7882
  }
7705
- else {
7706
- system.addColorGradient(0, Color4.FromColor3(Color3.Black()), Color4.FromColor3(Color3.Black()));
7707
- system.addColorGradient(1, Color4.FromColor3(Color3.White()), Color4.FromColor3(Color3.White()));
7883
+ applyParticleSystemJsonToSystem(responseObject);
7884
+ system.snippetId = trimmed;
7885
+ }
7886
+ catch (e) {
7887
+ alert("Unable to load your particle system: " + e);
7888
+ }
7889
+ };
7890
+ request.open("GET", ParticleHelper.SnippetUrl + "/" + trimmed.replace(/#/g, "/"), true);
7891
+ request.send();
7892
+ }, [applyParticleSystemJsonToSystem, scene, system]);
7893
+ const saveToSnippetServer = useCallback(() => {
7894
+ // Serialize once and post as snippet payload.
7895
+ const content = JSON.stringify(system.serialize(true));
7896
+ const xmlHttp = new XMLHttpRequest();
7897
+ xmlHttp.onreadystatechange = () => {
7898
+ if (xmlHttp.readyState !== 4) {
7899
+ return;
7900
+ }
7901
+ if (xmlHttp.status !== 200) {
7902
+ alert("Unable to save your particle system");
7903
+ return;
7904
+ }
7905
+ try {
7906
+ const snippet = JSON.parse(xmlHttp.responseText);
7907
+ system.snippetId = snippet.id;
7908
+ if (snippet.version && snippet.version !== "0") {
7909
+ system.snippetId += "#" + snippet.version;
7910
+ }
7911
+ // Copy to clipboard when available.
7912
+ if (navigator.clipboard) {
7913
+ void navigator.clipboard.writeText(system.snippetId);
7708
7914
  }
7709
- system.forceRefreshGradients();
7710
- }, onChange: (_gradient) => {
7711
- system.forceRefreshGradients();
7712
- } })) }));
7915
+ PersistSnippetId(system.snippetId);
7916
+ alert("Particle system saved with ID: " + system.snippetId + " (the id was also saved to your clipboard)");
7917
+ }
7918
+ catch (e) {
7919
+ alert("Unable to save your particle system: " + e);
7920
+ }
7921
+ };
7922
+ xmlHttp.open("POST", ParticleHelper.SnippetUrl + (system.snippetId ? "/" + system.snippetId : ""), true);
7923
+ xmlHttp.setRequestHeader("Content-Type", "application/json");
7924
+ const dataToSend = {
7925
+ payload: JSON.stringify({
7926
+ particleSystem: content,
7927
+ }),
7928
+ name: "",
7929
+ description: "",
7930
+ tags: "",
7931
+ };
7932
+ xmlHttp.send(JSON.stringify(dataToSend));
7933
+ }, [system]);
7934
+ return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Capacity", description: "Maximum number of particles in the system.", value: capacity }), jsx(StringifiedPropertyLine, { label: "Active Particles", description: "Current number of active particles.", value: activeCount }), jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Blend Mode", target: system, propertyKey: "blendMode", options: BlendModeOptions }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "World Offset", target: system, propertyKey: "worldOffset" }), !system.isNodeGenerated && jsx(BoundProperty, { component: Vector3PropertyLine, label: "Gravity", target: system, propertyKey: "gravity" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Billboard", target: system, propertyKey: "isBillboardBased" }), isBillboardBased && (jsx(BoundProperty, { component: NumberDropdownPropertyLine, label: "Billboard Mode", target: system, propertyKey: "billboardMode", options: ParticleBillboardModeOptions })), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Is Local", target: system, propertyKey: "isLocal" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Force Depth Write", target: system, propertyKey: "forceDepthWrite" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Update Speed", target: system, propertyKey: "updateSpeed", min: 0, step: 0.01 }), jsx(ButtonLine, { label: system.isNodeGenerated ? "Edit in Node Particle Editor (coming soon)" : "View in Node Particle Editor (coming soon)", disabled: true, onClick: () => {
7935
+ // Hook up once Node Particle Editor UX is wired.
7936
+ } }), isStopping ? (jsx(TextPropertyLine, { label: "System is stopping...", value: "" })) : isAlive ? (jsx(ButtonLine, { label: "Stop", onClick: () => {
7937
+ setStopRequested(true);
7938
+ system.stop();
7939
+ } })) : (jsx(ButtonLine, { label: "Start", onClick: () => {
7940
+ setStopRequested(false);
7941
+ system.start();
7942
+ } })), !system.isNodeGenerated && (jsxs(Fragment, { children: [jsx(FileUploadLine, { label: "Load from file", accept: ".json", onClick: (files) => {
7943
+ if (files.length === 0) {
7944
+ return;
7945
+ }
7946
+ const file = files[0];
7947
+ Tools.ReadFile(file, (data) => {
7948
+ const jsonObject = ParseJsonLoadContents(data);
7949
+ if (!jsonObject) {
7950
+ alert("Unable to load particle system from file.");
7951
+ return;
7952
+ }
7953
+ applyParticleSystemJsonToSystem(jsonObject);
7954
+ }, undefined, true);
7955
+ } }), jsx(ButtonLine, { label: "Save to file", onClick: () => {
7956
+ // Download serialization as a JSON file.
7957
+ const data = JSON.stringify(system.serialize(true), null, 2);
7958
+ const blob = new Blob([data], { type: "application/json" });
7959
+ const name = (system.name && system.name.trim().length > 0 ? system.name.trim() : "particleSystem") + ".json";
7960
+ Tools.Download(blob, name);
7961
+ } }), snippetId && jsx(TextPropertyLine, { label: "Snippet ID", value: snippetId }), jsx(ButtonLine, { label: "Load from snippet server", onClick: loadFromSnippetServer }), jsx(ButtonLine, { label: "Save to snippet server", onClick: saveToSnippetServer })] }))] }));
7713
7962
  };
7963
+ /**
7964
+ * Display attractor-related properties for a particle system.
7965
+ * @param props Component props.
7966
+ * @returns Render property lines.
7967
+ */
7714
7968
  const ParticleSystemAttractorProperties = (props) => {
7715
7969
  const { particleSystem: system } = props;
7716
- const attractors = useParticleSystemProperty(system, "attractors", "property", "addAttractor", "removeAttractor");
7970
+ const attractorsGetter = useCallback(() => system.attractors ?? [], [system]);
7971
+ const attractors = useObservableArray(system, attractorsGetter, "addAttractor", "removeAttractor");
7717
7972
  const scene = system.getScene();
7718
7973
  return (jsx(Fragment, { children: scene ? (jsx(AttractorList, { attractors: attractors, scene: scene, system: system })) : (
7719
- // Should never get here since sceneExplorer only displays if there is a scene, but adding UX in case that assumption changes in future
7974
+ // Handle missing scene defensively.
7720
7975
  jsx(MessageBar, { intent: "info", title: "No Scene Available", message: "Cannot display attractors without a scene" })) }));
7721
7976
  };
7722
- // TODO-iv2: This can be more generic to work for not just particleSystems
7723
- const useParticleSystemProperty = (system, propertyKey, observableType, addFn, removeFn, changeFn) => {
7977
+ /**
7978
+ * Display emitter-related properties for a particle system.
7979
+ * @param props Component props.
7980
+ * @returns Render property lines.
7981
+ */
7982
+ const ParticleSystemEmitterProperties = (props) => {
7983
+ const { particleSystem: system, selectionService } = props;
7984
+ const scene = system.getScene();
7985
+ const emitter = useProperty(system, "emitter");
7986
+ const emitterObject = emitter && !(emitter instanceof Vector3) ? emitter : undefined;
7987
+ const [sceneNodesVersion, setSceneNodesVersion] = useState(0);
7988
+ useEffect(() => {
7989
+ if (!scene) {
7990
+ return;
7991
+ }
7992
+ // Bump a local version counter whenever nodes change to keep emitter options up-to-date.
7993
+ const bump = () => setSceneNodesVersion((value) => value + 1);
7994
+ const newMeshToken = scene.onNewMeshAddedObservable.add(bump);
7995
+ const meshRemovedToken = scene.onMeshRemovedObservable.add(bump);
7996
+ const newTransformNodeToken = scene.onNewTransformNodeAddedObservable.add(bump);
7997
+ const transformNodeRemovedToken = scene.onTransformNodeRemovedObservable.add(bump);
7998
+ return () => {
7999
+ scene.onNewMeshAddedObservable.remove(newMeshToken);
8000
+ scene.onMeshRemovedObservable.remove(meshRemovedToken);
8001
+ scene.onNewTransformNodeAddedObservable.remove(newTransformNodeToken);
8002
+ scene.onTransformNodeRemovedObservable.remove(transformNodeRemovedToken);
8003
+ };
8004
+ }, [scene]);
8005
+ const sceneNodes = useMemo(() => {
8006
+ if (!scene) {
8007
+ return [];
8008
+ }
8009
+ const seenUniqueIds = new Set();
8010
+ const unique = [];
8011
+ for (const mesh of scene.meshes) {
8012
+ const uniqueId = mesh.uniqueId;
8013
+ if (typeof uniqueId === "number") {
8014
+ if (seenUniqueIds.has(uniqueId)) {
8015
+ continue;
8016
+ }
8017
+ seenUniqueIds.add(uniqueId);
8018
+ }
8019
+ unique.push(mesh);
8020
+ }
8021
+ const emitterUniqueId = emitterObject?.uniqueId;
8022
+ if (emitterObject && emitterUniqueId !== undefined && !seenUniqueIds.has(emitterUniqueId)) {
8023
+ // Keep the current emitter visible even if it isn't present in the scene arrays for any reason.
8024
+ unique.unshift(emitterObject);
8025
+ }
8026
+ return unique;
8027
+ }, [scene, sceneNodesVersion, emitterObject]);
8028
+ const emitterSelectionValue = !emitter ? "none" : emitter instanceof Vector3 ? "position" : `node:${emitter.uniqueId}`;
8029
+ const emitterVector = emitter instanceof Vector3 ? emitter : undefined;
8030
+ // Subscribe to Vector3 internal components to re-render on in-place mutations.
8031
+ useProperty(emitterVector, "_x");
8032
+ useProperty(emitterVector, "_y");
8033
+ useProperty(emitterVector, "_z");
8034
+ const particleEmitterType = useProperty(system, "particleEmitterType");
8035
+ // Derive the current dropdown value from the current instance to stay in sync with external changes.
8036
+ const derivedEmitterTypeKey = (() => {
8037
+ if (particleEmitterType instanceof SphereParticleEmitter) {
8038
+ return "sphere";
8039
+ }
8040
+ if (particleEmitterType instanceof ConeParticleEmitter) {
8041
+ return "cone";
8042
+ }
8043
+ if (particleEmitterType instanceof CylinderParticleEmitter) {
8044
+ return "cylinder";
8045
+ }
8046
+ if (particleEmitterType instanceof HemisphericParticleEmitter) {
8047
+ return "hemispheric";
8048
+ }
8049
+ if (particleEmitterType instanceof PointParticleEmitter) {
8050
+ return "point";
8051
+ }
8052
+ if (particleEmitterType instanceof MeshParticleEmitter) {
8053
+ return "mesh";
8054
+ }
8055
+ // Fall back to "box" for unknown emitter types to keep the dropdown valid.
8056
+ return "box";
8057
+ })();
8058
+ const [emitterTypeKey, setEmitterTypeKey] = useState(derivedEmitterTypeKey);
8059
+ useEffect(() => {
8060
+ // Keep local dropdown state aligned with the derived key when the engine changes underneath.
8061
+ setEmitterTypeKey(derivedEmitterTypeKey);
8062
+ }, [derivedEmitterTypeKey]);
8063
+ return (jsxs(Fragment, { children: [jsx(StringDropdownPropertyLine, { label: "Emitter", value: emitterSelectionValue, options: [
8064
+ { label: "None", value: "none" },
8065
+ { label: "Position", value: "position" },
8066
+ ...sceneNodes.map((node) => {
8067
+ const uniqueId = node.uniqueId;
8068
+ const name = node.name ?? "(unnamed)";
8069
+ const label = `${name} (#${uniqueId})`;
8070
+ return {
8071
+ label,
8072
+ value: `node:${uniqueId}`,
8073
+ };
8074
+ }),
8075
+ ], onChange: (value) => {
8076
+ const next = value;
8077
+ if (next === "none") {
8078
+ system.emitter = null;
8079
+ return;
8080
+ }
8081
+ if (next === "position") {
8082
+ if (!(system.emitter instanceof Vector3)) {
8083
+ system.emitter = Vector3.Zero();
8084
+ }
8085
+ return;
8086
+ }
8087
+ const uniqueIdText = next.replace("node:", "");
8088
+ const uniqueId = Number(uniqueIdText);
8089
+ const node = sceneNodes.find((candidate) => candidate.uniqueId === uniqueId);
8090
+ if (node) {
8091
+ system.emitter = node;
8092
+ }
8093
+ } }), emitterSelectionValue === "position" && emitterVector && (jsx(Vector3PropertyLine, { label: "Position", value: emitterVector, onChange: (value) => {
8094
+ if (system.emitter instanceof Vector3) {
8095
+ system.emitter.copyFrom(value);
8096
+ }
8097
+ else {
8098
+ system.emitter = value;
8099
+ }
8100
+ } })), emitterSelectionValue !== "none" && emitter && !(emitter instanceof Vector3) && (jsx(LinkToEntityPropertyLine, { label: "Entity", entity: emitter, selectionService: selectionService })), !system.isNodeGenerated && (jsx(StringDropdownPropertyLine, { label: "Type", value: emitterTypeKey, options: [
8101
+ { label: "Box", value: "box" },
8102
+ { label: "Cone", value: "cone" },
8103
+ { label: "Cylinder", value: "cylinder" },
8104
+ { label: "Hemispheric", value: "hemispheric" },
8105
+ { label: "Point", value: "point" },
8106
+ { label: "Mesh", value: "mesh" },
8107
+ { label: "Sphere", value: "sphere" },
8108
+ ], onChange: (value) => {
8109
+ const next = value;
8110
+ setEmitterTypeKey(next);
8111
+ // Update the engine by swapping the particleEmitterType instance to match the selected key.
8112
+ switch (next) {
8113
+ case "box":
8114
+ system.createBoxEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(-0.5, -0.5, -0.5), new Vector3(0.5, 0.5, 0.5));
8115
+ break;
8116
+ case "sphere":
8117
+ system.createSphereEmitter(1, 1);
8118
+ break;
8119
+ case "cone":
8120
+ system.createConeEmitter(1, Math.PI / 4);
8121
+ break;
8122
+ case "cylinder":
8123
+ system.createCylinderEmitter(1, 1, 1, 0);
8124
+ break;
8125
+ case "hemispheric":
8126
+ system.createHemisphericEmitter(1, 1);
8127
+ break;
8128
+ case "point":
8129
+ system.createPointEmitter(new Vector3(0, 1, 0), new Vector3(0, 1, 0));
8130
+ break;
8131
+ case "mesh": {
8132
+ // Default to the first mesh in the scene when available, then allow changes via "Source".
8133
+ const defaultMesh = scene?.meshes?.[0] ?? null;
8134
+ system.particleEmitterType = new MeshParticleEmitter(defaultMesh);
8135
+ break;
8136
+ }
8137
+ }
8138
+ } })), !system.isNodeGenerated && particleEmitterType instanceof MeshParticleEmitter && (jsx(Fragment, { children: scene && scene.meshes.length > 0 ? (jsx(StringDropdownPropertyLine, { label: "Source", value: particleEmitterType.mesh ? `mesh:${particleEmitterType.mesh.uniqueId}` : `mesh:${scene.meshes[0].uniqueId}`, options: scene.meshes.map((mesh) => {
8139
+ const uniqueId = mesh.uniqueId;
8140
+ const name = mesh.name ?? "(unnamed)";
8141
+ const label = `${name} (#${uniqueId})`;
8142
+ return {
8143
+ label,
8144
+ value: `mesh:${uniqueId}`,
8145
+ };
8146
+ }), onChange: (value) => {
8147
+ const next = String(value);
8148
+ const uniqueIdText = next.replace("mesh:", "");
8149
+ const uniqueId = Number(uniqueIdText);
8150
+ const mesh = scene.meshes.find((candidate) => candidate.uniqueId === uniqueId) ?? null;
8151
+ particleEmitterType.mesh = mesh;
8152
+ } })) : (jsx(TextPropertyLine, { label: "Source", value: "No meshes in scene." })) })), !system.isNodeGenerated && particleEmitterType instanceof BoxParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Min emit box", target: particleEmitterType, propertyKey: "minEmitBox" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Max emit box", target: particleEmitterType, propertyKey: "maxEmitBox" })] })), !system.isNodeGenerated && particleEmitterType instanceof ConeParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height range", target: particleEmitterType, propertyKey: "heightRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Emit from spawn point only", target: particleEmitterType, propertyKey: "emitFromSpawnPointOnly" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), !system.isNodeGenerated && particleEmitterType instanceof SphereParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), !system.isNodeGenerated && particleEmitterType instanceof CylinderParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Height", target: particleEmitterType, propertyKey: "height", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), !system.isNodeGenerated && particleEmitterType instanceof HemisphericParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius", target: particleEmitterType, propertyKey: "radius", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Radius range", target: particleEmitterType, propertyKey: "radiusRange", min: 0, max: 1, step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Direction randomizer", target: particleEmitterType, propertyKey: "directionRandomizer", min: 0, max: 1, step: 0.01 })] })), !system.isNodeGenerated && particleEmitterType instanceof PointParticleEmitter && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction1", target: particleEmitterType, propertyKey: "direction1" }), jsx(BoundProperty, { component: Vector3PropertyLine, label: "Direction2", target: particleEmitterType, propertyKey: "direction2" })] })), !system.isNodeGenerated && !scene && jsx(TextPropertyLine, { label: "Emitter", value: "No scene available." })] }));
8153
+ };
8154
+ /**
8155
+ * Display emission-related properties for a particle system.
8156
+ * @param props Component props.
8157
+ * @returns Render property lines.
8158
+ */
8159
+ const ParticleSystemEmissionProperties = (props) => {
8160
+ const { particleSystem: system } = props;
8161
+ const emitRateGradientsGetter = useCallback(() => system.getEmitRateGradients(), [system]);
8162
+ const emitRateGradients = useObservableArray(system, emitRateGradientsGetter, "addEmitRateGradient", "removeEmitRateGradient", "forceRefreshGradients");
8163
+ const velocityGradientsGetter = useCallback(() => system.getVelocityGradients(), [system]);
8164
+ const velocityGradients = useObservableArray(system, velocityGradientsGetter, "addVelocityGradient", "removeVelocityGradient", "forceRefreshGradients");
8165
+ const limitVelocityGradientsGetter = useCallback(() => system.getLimitVelocityGradients(), [system]);
8166
+ const limitVelocityGradients = useObservableArray(system, limitVelocityGradientsGetter, "addLimitVelocityGradient", "removeLimitVelocityGradient", "forceRefreshGradients");
8167
+ const dragGradientsGetter = useCallback(() => system.getDragGradients(), [system]);
8168
+ const dragGradients = useObservableArray(system, dragGradientsGetter, "addDragGradient", "removeDragGradient", "forceRefreshGradients");
8169
+ const useEmitRateGradients = (emitRateGradients?.length ?? 0) > 0;
8170
+ const useVelocityGradients = (velocityGradients?.length ?? 0) > 0;
8171
+ const useLimitVelocityGradients = (limitVelocityGradients?.length ?? 0) > 0;
8172
+ const useDragGradients = (dragGradients?.length ?? 0) > 0;
8173
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Emit rate", target: system, propertyKey: "emitRate", min: 0, step: 1 }), !system.isNodeGenerated && !useEmitRateGradients && (jsx(ButtonLine, { label: "Use Emit rate gradients", onClick: () => {
8174
+ system.addEmitRateGradient(0, system.emitRate, system.emitRate);
8175
+ system.forceRefreshGradients();
8176
+ } })), !system.isNodeGenerated && useEmitRateGradients && (jsx(FactorGradientList, { gradients: emitRateGradients, label: "Emit Rate Gradient", removeGradient: (gradient) => {
8177
+ system.removeEmitRateGradient(gradient.gradient);
8178
+ system.forceRefreshGradients();
8179
+ }, addGradient: (gradient) => {
8180
+ if (gradient) {
8181
+ system.addEmitRateGradient(gradient.gradient, gradient.factor1, gradient.factor2);
8182
+ }
8183
+ else {
8184
+ system.addEmitRateGradient(0, system.emitRate, system.emitRate);
8185
+ }
8186
+ system.forceRefreshGradients();
8187
+ }, onChange: (_gradient) => {
8188
+ system.forceRefreshGradients();
8189
+ } })), !system.isNodeGenerated && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min Emit Power", target: system, propertyKey: "minEmitPower", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max Emit Power", target: system, propertyKey: "maxEmitPower", min: 0, step: 0.1 })] })), !system.isNodeGenerated && !useVelocityGradients && (jsx(ButtonLine, { label: "Use Velocity gradients", onClick: () => {
8190
+ system.addVelocityGradient(0, 1, 1);
8191
+ system.forceRefreshGradients();
8192
+ } })), !system.isNodeGenerated && useVelocityGradients && (jsx(FactorGradientList, { gradients: velocityGradients, label: "Velocity Gradient", removeGradient: (gradient) => {
8193
+ system.removeVelocityGradient(gradient.gradient);
8194
+ system.forceRefreshGradients();
8195
+ }, addGradient: (gradient) => {
8196
+ if (gradient) {
8197
+ system.addVelocityGradient(gradient.gradient, gradient.factor1, gradient.factor2);
8198
+ }
8199
+ else {
8200
+ system.addVelocityGradient(0, 1, 1);
8201
+ }
8202
+ system.forceRefreshGradients();
8203
+ }, onChange: (_gradient) => {
8204
+ system.forceRefreshGradients();
8205
+ } })), !system.isNodeGenerated && !useLimitVelocityGradients && (jsx(ButtonLine, { label: "Use Limit Velocity gradients", onClick: () => {
8206
+ system.addLimitVelocityGradient(0, 1, 1);
8207
+ system.forceRefreshGradients();
8208
+ } })), !system.isNodeGenerated && useLimitVelocityGradients && (jsx(FactorGradientList, { gradients: limitVelocityGradients, label: "Limit Velocity Gradient", removeGradient: (gradient) => {
8209
+ system.removeLimitVelocityGradient(gradient.gradient);
8210
+ system.forceRefreshGradients();
8211
+ }, addGradient: (gradient) => {
8212
+ if (gradient) {
8213
+ system.addLimitVelocityGradient(gradient.gradient, gradient.factor1, gradient.factor2);
8214
+ }
8215
+ else {
8216
+ system.addLimitVelocityGradient(0, 1, 1);
8217
+ }
8218
+ system.forceRefreshGradients();
8219
+ }, onChange: (_gradient) => {
8220
+ system.forceRefreshGradients();
8221
+ } })), !system.isNodeGenerated && !useDragGradients && (jsx(ButtonLine, { label: "Use Drag gradients", onClick: () => {
8222
+ system.addDragGradient(0, 1, 1);
8223
+ system.forceRefreshGradients();
8224
+ } })), !system.isNodeGenerated && useDragGradients && (jsx(FactorGradientList, { gradients: dragGradients, label: "Drag Gradient", removeGradient: (gradient) => {
8225
+ system.removeDragGradient(gradient.gradient);
8226
+ system.forceRefreshGradients();
8227
+ }, addGradient: (gradient) => {
8228
+ if (gradient) {
8229
+ system.addDragGradient(gradient.gradient, gradient.factor1, gradient.factor2);
8230
+ }
8231
+ else {
8232
+ system.addDragGradient(0, 1, 1);
8233
+ }
8234
+ system.forceRefreshGradients();
8235
+ }, onChange: (_gradient) => {
8236
+ system.forceRefreshGradients();
8237
+ } }))] }));
8238
+ };
8239
+ /**
8240
+ * Display size-related properties for a particle system.
8241
+ * @param props Component props.
8242
+ * @returns Render property lines.
8243
+ */
8244
+ const ParticleSystemSizeProperties = (props) => {
8245
+ const { particleSystem: system } = props;
8246
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min size", target: system, propertyKey: "minSize", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max size", target: system, propertyKey: "maxSize", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min scale x", target: system, propertyKey: "minScaleX", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max scale x", target: system, propertyKey: "maxScaleX", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min scale y", target: system, propertyKey: "minScaleY", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max scale y", target: system, propertyKey: "maxScaleY", min: 0, step: 0.1 })] }));
8247
+ };
8248
+ /**
8249
+ * Display lifetime-related properties for a particle system.
8250
+ * @param props Component props.
8251
+ * @returns Render property lines.
8252
+ */
8253
+ const ParticleSystemLifetimeProperties = (props) => {
8254
+ const { particleSystem: system } = props;
8255
+ if (system.isNodeGenerated) {
8256
+ return jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Target stop duration", target: system, propertyKey: "targetStopDuration", min: 0, step: 0.1 });
8257
+ }
8258
+ const lifeTimeGradientsGetter = useCallback(() => system.getLifeTimeGradients(), [system]);
8259
+ const lifeTimeGradients = useObservableArray(system, lifeTimeGradientsGetter, "addLifeTimeGradient", "removeLifeTimeGradient", "forceRefreshGradients");
8260
+ const useLifeTimeGradients = (lifeTimeGradients?.length ?? 0) > 0;
8261
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min lifetime", target: system, propertyKey: "minLifeTime", min: 0, step: 0.1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max lifetime", target: system, propertyKey: "maxLifeTime", min: 0, step: 0.1 }), !useLifeTimeGradients && (jsx(ButtonLine, { label: "Use Lifetime gradients", onClick: () => {
8262
+ system.addLifeTimeGradient(0, system.minLifeTime, system.maxLifeTime);
8263
+ system.forceRefreshGradients();
8264
+ } })), useLifeTimeGradients && (jsx(FactorGradientList, { gradients: lifeTimeGradients, label: "Lifetime Gradient", removeGradient: (gradient) => {
8265
+ system.removeLifeTimeGradient(gradient.gradient);
8266
+ system.forceRefreshGradients();
8267
+ }, addGradient: (gradient) => {
8268
+ if (gradient) {
8269
+ system.addLifeTimeGradient(gradient.gradient, gradient.factor1, gradient.factor2);
8270
+ }
8271
+ else {
8272
+ system.addLifeTimeGradient(0, system.minLifeTime, system.maxLifeTime);
8273
+ }
8274
+ system.forceRefreshGradients();
8275
+ }, onChange: (_gradient) => {
8276
+ system.forceRefreshGradients();
8277
+ } })), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Target stop duration", target: system, propertyKey: "targetStopDuration", min: 0, step: 0.1 })] }));
8278
+ };
8279
+ /**
8280
+ * Display color-related properties for a particle system.
8281
+ * @param props Component props.
8282
+ * @returns Render property lines.
8283
+ */
8284
+ const ParticleSystemColorProperties = (props) => {
8285
+ const { particleSystem: system } = props;
8286
+ const colorGradientsGetter = useCallback(() => system.getColorGradients(), [system]);
8287
+ const colorGradients = useObservableArray(system, colorGradientsGetter, "addColorGradient", "removeColorGradient", "forceRefreshGradients");
8288
+ const useRampGradients = useProperty(system, "useRampGradients");
8289
+ const rampGradientsGetter = useCallback(() => system.getRampGradients(), [system]);
8290
+ const rampGradients = useObservableArray(system, rampGradientsGetter, "addRampGradient", "removeRampGradient", "forceRefreshGradients");
8291
+ const colorRemapGradientsGetter = useCallback(() => system.getColorRemapGradients(), [system]);
8292
+ const colorRemapGradients = useObservableArray(system, colorRemapGradientsGetter, "addColorRemapGradient", "removeColorRemapGradient", "forceRefreshGradients");
8293
+ const alphaRemapGradientsGetter = useCallback(() => system.getAlphaRemapGradients(), [system]);
8294
+ const alphaRemapGradients = useObservableArray(system, alphaRemapGradientsGetter, "addAlphaRemapGradient", "removeAlphaRemapGradient", "forceRefreshGradients");
8295
+ const hasColorGradients = (colorGradients?.length ?? 0) > 0;
8296
+ const hasRampGradients = (rampGradients?.length ?? 0) > 0;
8297
+ const hasColorRemapGradients = (colorRemapGradients?.length ?? 0) > 0;
8298
+ const hasAlphaRemapGradients = (alphaRemapGradients?.length ?? 0) > 0;
8299
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: Color4PropertyLine, label: "Color 1", target: system, propertyKey: "color1" }), jsx(BoundProperty, { component: Color4PropertyLine, label: "Color 2", target: system, propertyKey: "color2" }), jsx(BoundProperty, { component: Color4PropertyLine, label: "Color dead", target: system, propertyKey: "colorDead" }), !hasColorGradients && (jsx(ButtonLine, { label: "Use Color gradients", onClick: () => {
8300
+ system.addColorGradient(0, system.color1, system.color1);
8301
+ system.addColorGradient(1, system.color2, system.color2);
8302
+ system.forceRefreshGradients();
8303
+ } })), hasColorGradients && (jsx(Color4GradientList, { gradients: colorGradients, label: "Color Gradient", removeGradient: (gradient) => {
8304
+ system.removeColorGradient(gradient.gradient);
8305
+ system.forceRefreshGradients();
8306
+ }, addGradient: (gradient) => {
8307
+ if (gradient) {
8308
+ system.addColorGradient(gradient.gradient, gradient.color1, gradient.color2);
8309
+ }
8310
+ else {
8311
+ system.addColorGradient(0, system.color1, system.color1);
8312
+ system.addColorGradient(1, system.color2, system.color2);
8313
+ }
8314
+ system.forceRefreshGradients();
8315
+ }, onChange: (_gradient) => {
8316
+ system.forceRefreshGradients();
8317
+ } })), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enable Ramp gradients", target: system, propertyKey: "useRampGradients" }), useRampGradients && (jsxs(Fragment, { children: [!hasRampGradients && (jsx(ButtonLine, { label: "Use Ramp gradients", onClick: () => {
8318
+ system.addRampGradient(0, Color3.Black());
8319
+ system.addRampGradient(1, Color3.White());
8320
+ system.forceRefreshGradients();
8321
+ } })), hasRampGradients && (jsx(Color3GradientList, { gradients: rampGradients, label: "Ramp Gradient", removeGradient: (gradient) => {
8322
+ system.removeRampGradient(gradient.gradient);
8323
+ system.forceRefreshGradients();
8324
+ }, addGradient: (gradient) => {
8325
+ if (gradient) {
8326
+ system.addRampGradient(gradient.gradient, gradient.color);
8327
+ }
8328
+ else {
8329
+ system.addRampGradient(0, Color3.Black());
8330
+ system.addRampGradient(1, Color3.White());
8331
+ }
8332
+ system.forceRefreshGradients();
8333
+ }, onChange: () => {
8334
+ system.forceRefreshGradients();
8335
+ } })), !hasColorRemapGradients && (jsx(ButtonLine, { label: "Use Color remap gradients", onClick: () => {
8336
+ system.addColorRemapGradient(0, 0, 1);
8337
+ system.addColorRemapGradient(1, 0, 1);
8338
+ system.forceRefreshGradients();
8339
+ } })), hasColorRemapGradients && (jsx(FactorGradientList, { gradients: colorRemapGradients, label: "Color Remap Gradient", removeGradient: (gradient) => {
8340
+ system.removeColorRemapGradient(gradient.gradient);
8341
+ system.forceRefreshGradients();
8342
+ }, addGradient: (gradient) => {
8343
+ if (gradient) {
8344
+ system.addColorRemapGradient(gradient.gradient, gradient.factor1 ?? 0, gradient.factor2 ?? 1);
8345
+ }
8346
+ else {
8347
+ system.addColorRemapGradient(0, 0, 1);
8348
+ system.addColorRemapGradient(1, 0, 1);
8349
+ }
8350
+ system.forceRefreshGradients();
8351
+ }, onChange: (_gradient) => {
8352
+ system.forceRefreshGradients();
8353
+ } })), !hasAlphaRemapGradients && (jsx(ButtonLine, { label: "Use Alpha remap gradients", onClick: () => {
8354
+ system.addAlphaRemapGradient(0, 0, 1);
8355
+ system.addAlphaRemapGradient(1, 0, 1);
8356
+ system.forceRefreshGradients();
8357
+ } })), hasAlphaRemapGradients && (jsx(FactorGradientList, { gradients: alphaRemapGradients, label: "Alpha Remap Gradient", removeGradient: (gradient) => {
8358
+ system.removeAlphaRemapGradient(gradient.gradient);
8359
+ system.forceRefreshGradients();
8360
+ }, addGradient: (gradient) => {
8361
+ if (gradient) {
8362
+ system.addAlphaRemapGradient(gradient.gradient, gradient.factor1 ?? 0, gradient.factor2 ?? 1);
8363
+ }
8364
+ else {
8365
+ system.addAlphaRemapGradient(0, 0, 1);
8366
+ system.addAlphaRemapGradient(1, 0, 1);
8367
+ }
8368
+ system.forceRefreshGradients();
8369
+ }, onChange: (_gradient) => {
8370
+ system.forceRefreshGradients();
8371
+ } }))] }))] }));
8372
+ };
8373
+ /**
8374
+ * Display rotation-related properties for a particle system.
8375
+ * @param props Component props.
8376
+ * @returns Render property lines.
8377
+ */
8378
+ const ParticleSystemRotationProperties = (props) => {
8379
+ const { particleSystem: system } = props;
8380
+ const angularSpeedGradientsGetter = useCallback(() => system.getAngularSpeedGradients(), [system]);
8381
+ const angularSpeedGradients = useObservableArray(system, angularSpeedGradientsGetter, "addAngularSpeedGradient", "removeAngularSpeedGradient", "forceRefreshGradients");
8382
+ const useAngularSpeedGradients = (angularSpeedGradients?.length ?? 0) > 0;
8383
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min Angular speed", target: system, propertyKey: "minAngularSpeed", step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max Angular speed", target: system, propertyKey: "maxAngularSpeed", step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Min initial rotation", target: system, propertyKey: "minInitialRotation", step: 0.01 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Max initial rotation", target: system, propertyKey: "maxInitialRotation", step: 0.01 }), !useAngularSpeedGradients && (jsx(ButtonLine, { label: "Use Angular speed gradients", onClick: () => {
8384
+ system.addAngularSpeedGradient(0, system.minAngularSpeed, system.maxAngularSpeed);
8385
+ system.forceRefreshGradients();
8386
+ } })), useAngularSpeedGradients && (jsx(FactorGradientList, { gradients: angularSpeedGradients, label: "Angular Speed Gradient", removeGradient: (gradient) => {
8387
+ system.removeAngularSpeedGradient(gradient.gradient);
8388
+ system.forceRefreshGradients();
8389
+ }, addGradient: (gradient) => {
8390
+ if (gradient) {
8391
+ system.addAngularSpeedGradient(gradient.gradient, gradient.factor1 ?? 0, gradient.factor2);
8392
+ }
8393
+ else {
8394
+ system.addAngularSpeedGradient(0, system.minAngularSpeed, system.maxAngularSpeed);
8395
+ }
8396
+ system.forceRefreshGradients();
8397
+ }, onChange: (_gradient) => {
8398
+ system.forceRefreshGradients();
8399
+ } }))] }));
8400
+ };
8401
+ /**
8402
+ * Display spritesheet-related properties for a particle system.
8403
+ * @param props Component props.
8404
+ * @returns Render property lines.
8405
+ */
8406
+ const ParticleSystemSpritesheetProperties = (props) => {
8407
+ const { particleSystem: system } = props;
8408
+ return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Animation sheet enabled", target: system, propertyKey: "isAnimationSheetEnabled" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "First sprite index", target: system, propertyKey: "startSpriteCellID", min: 0, step: 1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Last sprite index", target: system, propertyKey: "endSpriteCellID", min: 0, step: 1 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Animation loop", target: system, propertyKey: "spriteCellLoop" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Random cell start index", target: system, propertyKey: "spriteRandomStartCell" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Cell width", target: system, propertyKey: "spriteCellWidth", min: 0, step: 1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Cell height", target: system, propertyKey: "spriteCellHeight", min: 0, step: 1 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Cell change speed", target: system, propertyKey: "spriteCellChangeSpeed", min: 0, step: 0.01 })] }));
8409
+ };
8410
+ // Return a copied array and re-render when array mutators run.
8411
+ // Intercept add/remove/change functions because the underlying APIs update internal arrays in-place.
8412
+ const useObservableArray = (target, getItems, addFn, removeFn, changeFn) => {
7724
8413
  return useObservableState(useCallback(() => {
7725
- const value = observableType === "function" ? system[propertyKey]() : system[propertyKey];
8414
+ const value = getItems();
7726
8415
  return [...(value ?? [])];
7727
- }, [system, propertyKey]), useInterceptObservable("function", system, addFn), useInterceptObservable("function", system, removeFn), changeFn ? useInterceptObservable("function", system, changeFn) : undefined);
8416
+ }, [getItems]), useInterceptObservable("function", target, addFn), useInterceptObservable("function", target, removeFn), changeFn ? useInterceptObservable("function", target, changeFn) : undefined);
7728
8417
  };
7729
8418
 
8419
+ function IsParticleSystem(entity) {
8420
+ return entity instanceof ParticleSystem;
8421
+ }
8422
+ function IsNonNodeParticleSystem(entity) {
8423
+ return entity instanceof ParticleSystem && !entity.isNodeGenerated;
8424
+ }
8425
+ // TODO: This file and particleSystemProperties.tsx still need to handle CPU vs GPU systems differently where applicable.
7730
8426
  const ParticleSystemPropertiesServiceDefinition = {
7731
8427
  friendlyName: "Particle System Properties",
7732
- consumes: [PropertiesServiceIdentity],
7733
- factory: (propertiesService) => {
7734
- // TODO-iv2 complete the ParticleSystemPropertiesService registrations and the ParticleSystemProperties component(s) - ensuring the proper predicates (IParticleSystem vs ParticleSystem)
7735
- const particleSystemContent = propertiesService.addSectionContent({
7736
- key: "Particle System Properties",
7737
- predicate: (entity) => entity instanceof ParticleSystem,
8428
+ consumes: [PropertiesServiceIdentity, SelectionServiceIdentity],
8429
+ factory: (propertiesService, selectionService) => {
8430
+ // Register each section in its own call to keep ordering predictable across registrations.
8431
+ // Note: section `order` is not globally sorted across different registrations, so call order matters.
8432
+ const particleSystemGeneralContent = propertiesService.addSectionContent({
8433
+ key: "Particle System General Properties",
8434
+ predicate: IsParticleSystem,
8435
+ content: [
8436
+ {
8437
+ section: "General",
8438
+ order: 1,
8439
+ component: ({ context }) => jsx(ParticleSystemGeneralProperties, { particleSystem: context }),
8440
+ },
8441
+ ],
8442
+ });
8443
+ // The Attractors section must not be visible at all (including the accordion entry) for node-generated systems.
8444
+ const particleSystemAttractorsContent = propertiesService.addSectionContent({
8445
+ key: "Particle System Attractors Properties",
8446
+ predicate: IsNonNodeParticleSystem,
8447
+ content: [
8448
+ {
8449
+ section: "Attractors",
8450
+ order: 2,
8451
+ component: ({ context }) => jsx(ParticleSystemAttractorProperties, { particleSystem: context }),
8452
+ },
8453
+ ],
8454
+ });
8455
+ const particleSystemEmitterContent = propertiesService.addSectionContent({
8456
+ key: "Particle System Emitter Properties",
8457
+ predicate: IsParticleSystem,
8458
+ content: [
8459
+ {
8460
+ section: "Emitter",
8461
+ order: 3,
8462
+ component: ({ context }) => jsx(ParticleSystemEmitterProperties, { particleSystem: context, selectionService: selectionService }),
8463
+ },
8464
+ ],
8465
+ });
8466
+ const particleSystemEmissionContent = propertiesService.addSectionContent({
8467
+ key: "Particle System Emission Properties",
8468
+ predicate: IsParticleSystem,
7738
8469
  content: [
7739
8470
  {
7740
8471
  section: "Emission",
8472
+ order: 4,
7741
8473
  component: ({ context }) => jsx(ParticleSystemEmissionProperties, { particleSystem: context }),
7742
8474
  },
8475
+ ],
8476
+ });
8477
+ // The Size section must not be visible at all (including the accordion entry) for node-generated systems.
8478
+ const particleSystemSizeContent = propertiesService.addSectionContent({
8479
+ key: "Particle System Size Properties",
8480
+ predicate: IsNonNodeParticleSystem,
8481
+ content: [
7743
8482
  {
7744
- section: "Color",
8483
+ section: "Size",
8484
+ order: 5,
8485
+ component: ({ context }) => jsx(ParticleSystemSizeProperties, { particleSystem: context }),
8486
+ },
8487
+ ],
8488
+ });
8489
+ // Lifetime is registered for all systems; the component limits the visible fields for node-generated systems.
8490
+ const particleSystemLifetimeContent = propertiesService.addSectionContent({
8491
+ key: "Particle System Lifetime Properties",
8492
+ predicate: IsParticleSystem,
8493
+ content: [
8494
+ {
8495
+ section: "Lifetime",
8496
+ order: 6,
8497
+ component: ({ context }) => jsx(ParticleSystemLifetimeProperties, { particleSystem: context }),
8498
+ },
8499
+ ],
8500
+ });
8501
+ // Register Color after Lifetime.
8502
+ const particleSystemColorContent = propertiesService.addSectionContent({
8503
+ key: "Particle System Color Properties",
8504
+ predicate: IsNonNodeParticleSystem,
8505
+ content: [
8506
+ {
8507
+ section: "Colors",
8508
+ order: 7,
7745
8509
  component: ({ context }) => jsx(ParticleSystemColorProperties, { particleSystem: context }),
7746
8510
  },
8511
+ ],
8512
+ });
8513
+ // Register Rotation after Colors.
8514
+ const particleSystemRotationContent = propertiesService.addSectionContent({
8515
+ key: "Particle System Rotation Properties",
8516
+ predicate: IsNonNodeParticleSystem,
8517
+ content: [
7747
8518
  {
7748
- section: "Attractors",
7749
- component: ({ context }) => jsx(ParticleSystemAttractorProperties, { particleSystem: context }),
8519
+ section: "Rotation",
8520
+ order: 8,
8521
+ component: ({ context }) => jsx(ParticleSystemRotationProperties, { particleSystem: context }),
8522
+ },
8523
+ ],
8524
+ });
8525
+ // Register Spritesheet after Rotation.
8526
+ const particleSystemSpritesheetContent = propertiesService.addSectionContent({
8527
+ key: "Particle System Spritesheet Properties",
8528
+ predicate: IsNonNodeParticleSystem,
8529
+ content: [
8530
+ {
8531
+ section: "Spritesheet",
8532
+ order: 9,
8533
+ component: ({ context }) => jsx(ParticleSystemSpritesheetProperties, { particleSystem: context }),
7750
8534
  },
7751
8535
  ],
7752
8536
  });
7753
8537
  return {
7754
8538
  dispose: () => {
7755
- particleSystemContent.dispose();
8539
+ particleSystemGeneralContent.dispose();
8540
+ particleSystemAttractorsContent.dispose();
8541
+ particleSystemEmitterContent.dispose();
8542
+ particleSystemEmissionContent.dispose();
8543
+ particleSystemSizeContent.dispose();
8544
+ particleSystemLifetimeContent.dispose();
8545
+ particleSystemColorContent.dispose();
8546
+ particleSystemRotationContent.dispose();
8547
+ particleSystemSpritesheetContent.dispose();
7756
8548
  },
7757
8549
  };
7758
8550
  },
@@ -8178,8 +8970,7 @@ const SpinButtonPropertyLine = (props) => {
8178
8970
 
8179
8971
  const SpriteManagerGeneralProperties = (props) => {
8180
8972
  const { spriteManager, selectionService } = props;
8181
- const texture = useProperty(spriteManager, "texture");
8182
- return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Capacity", value: spriteManager.capacity.toString() }), jsx(LinkToEntityPropertyLine, { label: "Texture", entity: texture, selectionService: selectionService })] }));
8973
+ return (jsxs(Fragment, { children: [jsx(TextPropertyLine, { label: "Capacity", value: spriteManager.capacity.toString() }), jsx(BoundProperty, { component: TextureSelectorPropertyLine, label: "Texture", target: spriteManager, propertyKey: "texture", scene: spriteManager.scene, onLink: (texture) => (selectionService.selectedEntity = texture) })] }));
8183
8974
  };
8184
8975
  const SpriteManagerOtherProperties = (props) => {
8185
8976
  const { spriteManager } = props;
@@ -12412,4 +13203,4 @@ const TextAreaPropertyLine = (props) => {
12412
13203
  AttachDebugLayer();
12413
13204
 
12414
13205
  export { useSidePaneDockOverrides as $, Accordion as A, ButtonLine as B, Collapse as C, DebugServiceIdentity as D, ExtensibleAccordion as E, FileUploadLine as F, useVector3Property as G, useColor3Property as H, Inspector as I, useColor4Property as J, useQuaternionProperty as K, Link as L, MakeLazyComponent as M, NumberDropdownPropertyLine as N, MakePropertyHook as O, Popover as P, useInterceptObservable as Q, useEventfulState as R, SwitchPropertyLine as S, ToolsServiceIdentity as T, useObservableCollection as U, Vector3PropertyLine as V, useOrderedObservableCollection as W, usePollingObservable as X, useResource as Y, useAsyncResource as Z, useCompactMode as _, SyncedSliderPropertyLine as a, useAngleConverters as a0, MakeTeachingMoment as a1, MakeDialogTeachingMoment as a2, InterceptFunction as a3, GetPropertyDescriptor as a4, IsPropertyReadonly as a5, InterceptProperty as a6, ObservableCollection as a7, ConstructorFactory as a8, SelectionServiceIdentity as a9, TextInput as aA, ToggleButton as aB, ChildWindow as aC, FactorGradientList as aD, Color3GradientList as aE, Color4GradientList as aF, Pane as aG, BooleanBadgePropertyLine as aH, Color3PropertyLine as aI, Color4PropertyLine as aJ, HexPropertyLine as aK, NumberInputPropertyLine as aL, LinkPropertyLine as aM, PropertyLine as aN, LineContainer as aO, PlaceholderPropertyLine as aP, StringifiedPropertyLine as aQ, TextAreaPropertyLine as aR, TextPropertyLine as aS, RotationVectorPropertyLine as aT, QuaternionPropertyLine as aU, Vector2PropertyLine as aV, Vector4PropertyLine as aW, SelectionServiceDefinition as aa, SettingsContextIdentity as ab, ShowInspector as ac, Checkbox as ad, ColorPickerPopup as ae, InputHexField as af, InputHsvField as ag, ComboBox as ah, DraggableLine as ai, Dropdown as aj, NumberDropdown as ak, StringDropdown as al, FactorGradientComponent as am, Color3GradientComponent as an, Color4GradientComponent as ao, ColorStepGradientComponent as ap, InfoLabel as aq, List as ar, MessageBar as as, PositionedPopover as at, SearchBar as au, SearchBox as av, SpinButton as aw, Switch as ax, SyncedSliderInput as ay, Textarea 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, Theme as x, BuiltInsExtensionFeed as y, useProperty as z };
12415
- //# sourceMappingURL=index-9CJtnfZ3.js.map
13206
+ //# sourceMappingURL=index-bFqtVcnb.js.map