@babylonjs/inspector 8.45.4-preview → 8.45.5-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, Caption1, Body1, ToggleButton as ToggleButton$1, Button as Button$1, Spinner, tokens, InfoLabel as InfoLabel$1, Body1Strong, Tooltip, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, createLightTheme, createDarkTheme, FluentProvider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, Portal, RendererProvider, createDOMRenderer, 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, 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, DeleteRegular, PlayRegular, EditRegular, LinkDismissRegular, LinkEditRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, EyeRegular, StopRegular, ArrowDownloadRegular, CloudArrowUpRegular, CloudArrowDownRegular, StopFilled, PlayFilled, EyeOffRegular, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ChevronDownRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, 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, DeleteRegular, PlayRegular, EditRegular, LinkDismissRegular, LinkEditRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, EyeRegular, StopRegular, ArrowDownloadRegular, CloudArrowUpRegular, CloudArrowDownRegular, StopFilled, PlayFilled, EyeOffRegular, LockOpenRegular, LockClosedRegular, ResizeRegular, ChevronUpRegular, ChevronDownRegular, ArrowResetRegular, CircleHalfFillRegular, EyedropperRegular, PaintBucketRegular, InkStrokeRegular, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, AppGenericRegular, RectangleLandscapeRegular, BorderOutsideRegular, BorderNoneRegular, MyLocationRegular, CameraRegular, LightbulbRegular, 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';
@@ -90,6 +90,7 @@ import { MeshParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/mesh
90
90
  import { PointParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/pointParticleEmitter.js';
91
91
  import { SphereParticleEmitter } from '@babylonjs/core/Particles/EmitterTypes/sphereParticleEmitter.js';
92
92
  import { ConvertToNodeParticleSystemSetAsync } from '@babylonjs/core/Particles/Node/nodeParticleSystemSet.helper.js';
93
+ import { GPUParticleSystem } from '@babylonjs/core/Particles/gpuParticleSystem.js';
93
94
  import { ParticleHelper } from '@babylonjs/core/Particles/particleHelper.js';
94
95
  import { FactorGradient, Color3Gradient, ColorGradient } from '@babylonjs/core/Misc/gradients.js';
95
96
  import { GradientBlockColorStep } from '@babylonjs/core/Materials/Node/Blocks/gradientBlock.js';
@@ -746,7 +747,6 @@ const CustomTokens = {
746
747
  dividerGapSmall: tokens.borderRadiusMedium, // 4px",
747
748
  labelMinWidth: "50px",
748
749
  sliderMinWidth: "30px",
749
- sliderMaxWidth: "80px",
750
750
  rightAlignOffset: `-${tokens.borderRadiusXLarge}`, // -8px
751
751
  };
752
752
  const UniformWidthStyling = { width: CustomTokens.inputWidth, boxSizing: "border-box" };
@@ -3888,7 +3888,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3888
3888
  keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
3889
3889
  ...BabylonWebResources,
3890
3890
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3891
- getExtensionModuleAsync: async () => await import('./exportService-D19rsLCQ.js'),
3891
+ getExtensionModuleAsync: async () => await import('./exportService-BvQmBhyv.js'),
3892
3892
  },
3893
3893
  {
3894
3894
  name: "Capture Tools",
@@ -3896,7 +3896,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3896
3896
  keywords: ["capture", "screenshot", "gif", "video", "tools"],
3897
3897
  ...BabylonWebResources,
3898
3898
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3899
- getExtensionModuleAsync: async () => await import('./captureService-C4KzF-3L.js'),
3899
+ getExtensionModuleAsync: async () => await import('./captureService-c_Nc-JkH.js'),
3900
3900
  },
3901
3901
  {
3902
3902
  name: "Import Tools",
@@ -3904,7 +3904,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3904
3904
  keywords: ["import", "tools"],
3905
3905
  ...BabylonWebResources,
3906
3906
  author: { name: "Alex Chuber", forumUserName: "alexchuber" },
3907
- getExtensionModuleAsync: async () => await import('./importService-xoACJHME.js'),
3907
+ getExtensionModuleAsync: async () => await import('./importService-DxFxT2li.js'),
3908
3908
  },
3909
3909
  {
3910
3910
  name: "Quick Creation Tools (Preview)",
@@ -3912,7 +3912,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
3912
3912
  keywords: ["creation", "tools"],
3913
3913
  ...BabylonWebResources,
3914
3914
  author: { name: "Babylon.js", forumUserName: "" },
3915
- getExtensionModuleAsync: async () => await import('./quickCreateToolsService-ROfMXsQm.js'),
3915
+ getExtensionModuleAsync: async () => await import('./quickCreateToolsService-Cu0BSIWb.js'),
3916
3916
  },
3917
3917
  ]);
3918
3918
 
@@ -3975,8 +3975,13 @@ const useSyncedSliderStyles = makeStyles({
3975
3975
  alignItems: "center",
3976
3976
  },
3977
3977
  slider: {
3978
- minWidth: CustomTokens.sliderMinWidth, // Minimum width for slider to remain usable
3979
- maxWidth: CustomTokens.sliderMaxWidth,
3978
+ flex: "1 1 auto", // Grow to fill available space
3979
+ minWidth: CustomTokens.sliderMinWidth,
3980
+ maxWidth: "none", // Allow slider to grow
3981
+ },
3982
+ compactSpinButton: {
3983
+ width: "70px",
3984
+ flex: "0 0 auto", // Don't grow, stay fixed
3980
3985
  },
3981
3986
  });
3982
3987
  /**
@@ -4027,7 +4032,7 @@ const SyncedSliderInput = (props) => {
4027
4032
  setValue(value);
4028
4033
  props.onChange(value); // Input always updates immediately
4029
4034
  };
4030
- return (jsxs("div", { className: classes.container, children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [props.min !== undefined && props.max !== undefined && (jsx(Slider, { ...passthroughProps, className: classes.slider, size: size, min: min / step, max: max / step, step: undefined, value: value / step, onChange: handleSliderChange, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, value: value, onChange: handleInputChange, step: props.step })] })] }));
4035
+ return (jsxs("div", { className: classes.container, children: [infoLabel && jsx(InfoLabel, { ...infoLabel, htmlFor: "syncedSlider" }), jsxs("div", { id: "syncedSlider", className: classes.syncedSlider, children: [props.min !== undefined && props.max !== undefined && (jsx(Slider, { ...passthroughProps, className: classes.slider, size: size, min: min / step, max: max / step, step: undefined, value: value / step, onChange: handleSliderChange, onPointerDown: handleSliderPointerDown, onPointerUp: handleSliderPointerUp })), jsx(SpinButton, { ...passthroughProps, className: props.compact ? classes.compactSpinButton : undefined, value: value, onChange: handleInputChange, step: props.step })] })] }));
4031
4036
  };
4032
4037
 
4033
4038
  /**
@@ -5044,7 +5049,7 @@ function MakeModularTool(options) {
5044
5049
  });
5045
5050
  // Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
5046
5051
  if (extensionFeeds.length > 0) {
5047
- const { ExtensionListServiceDefinition } = await import('./extensionsListService-BPIryMmP.js');
5052
+ const { ExtensionListServiceDefinition } = await import('./extensionsListService-DxjhVAwt.js');
5048
5053
  await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
5049
5054
  }
5050
5055
  // Register the theme selector service (for selecting the theme) if theming is configured.
@@ -6125,7 +6130,7 @@ const ShadowGeneratorSetupProperties = ({ context: shadowLight }) => {
6125
6130
  const useKernelBlur = useProperty(shadowGenerator, "useKernelBlur");
6126
6131
  const blurModeOptions = isCascaded ? CSMBlurModeOptions : BlurModeOptions;
6127
6132
  const near = camera?.minZ ?? 0;
6128
- const far = camera?.maxZ ?? 500000;
6133
+ const far = camera?.maxZ ?? 10000;
6129
6134
  const isPCFOrPCSS = filter === ShadowGenerator.FILTER_PCF || filter === ShadowGenerator.FILTER_PCSS;
6130
6135
  const isPCSS = filter === ShadowGenerator.FILTER_PCSS;
6131
6136
  const isBlurExponential = filter === ShadowGenerator.FILTER_BLUREXPONENTIALSHADOWMAP || filter === ShadowGenerator.FILTER_BLURCLOSEEXPONENTIALSHADOWMAP;
@@ -7837,22 +7842,19 @@ const useGradientStyles = makeStyles({
7837
7842
  gap: tokens.spacingHorizontalS,
7838
7843
  width: "100%",
7839
7844
  },
7840
- // Wrapper for each slider to control its size
7845
+ // Wrapper for factor spin buttons - fixed size
7841
7846
  valueWrapper: {
7842
- flex: "0 1 auto", // Don't grow, can shrink, size based on content
7843
- alignContent: "center",
7844
- minWidth: "80px", // Minimum width to keep usable
7845
- maxWidth: "100px", // Maximum width to prevent them from getting too wide
7847
+ flex: "0 0 auto", // Don't grow, natural size
7846
7848
  },
7847
- // Wrapper for color pickers - much smaller since they're just swatches
7849
+ // Wrapper for color pickers - fixed size since they're just swatches
7848
7850
  colorWrapper: {
7851
+ flex: "0 0 auto",
7849
7852
  alignContent: "center",
7850
- // No flex properties - just take natural size
7851
7853
  },
7852
- // Wrapper for the step slider to take remaining space
7854
+ // Wrapper for the step slider - grows to fill remaining space
7853
7855
  stepSliderWrapper: {
7854
- flex: "1 0 auto", // Can grow, don't shrink, size based on content
7855
- minWidth: "100px",
7856
+ flex: "1 1 0", // Grow to fill available space
7857
+ minWidth: 0,
7856
7858
  },
7857
7859
  });
7858
7860
  /**
@@ -7871,7 +7873,10 @@ const Gradient = (props) => {
7871
7873
  setGradient(newGradient);
7872
7874
  props.onChange(newGradient);
7873
7875
  };
7874
- return (jsxs("div", { id: "gradientContainer", className: classes.container, children: [jsx("div", { className: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value1, onChange: (color) => gradientChange({ ...gradient, value1: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, value: gradient.value1, onChange: (val) => gradientChange({ ...gradient, value1: val }) })) }), gradient.value2 !== undefined && (jsx("div", { className: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value2, onChange: (color) => gradientChange({ ...gradient, value2: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, value: gradient.value2, onChange: (val) => gradientChange({ ...gradient, value2: val }) })) })), jsx("div", { className: classes.stepSliderWrapper, children: jsx(SyncedSliderInput, { notifyOnlyOnRelease: true, min: 0, max: 1, step: 0.01, value: gradient.step, onChange: (val) => gradientChange({ ...gradient, step: val }) }) })] }));
7876
+ // Only use compact mode when there are numeric values (spinbuttons) taking up space
7877
+ const hasNumericValues = !(gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4) ||
7878
+ (gradient.value2 !== undefined && !(gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4));
7879
+ return (jsxs("div", { id: "gradientContainer", className: classes.container, children: [jsx("div", { className: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value1 instanceof Color3 || gradient.value1 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value1, onChange: (color) => gradientChange({ ...gradient, value1: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, value: gradient.value1, onChange: (val) => gradientChange({ ...gradient, value1: val }), compact: true })) }), gradient.value2 !== undefined && (jsx("div", { className: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? classes.colorWrapper : classes.valueWrapper, children: gradient.value2 instanceof Color3 || gradient.value2 instanceof Color4 ? (jsx(ColorPickerPopup, { value: gradient.value2, onChange: (color) => gradientChange({ ...gradient, value2: color }) })) : (jsx(SyncedSliderInput, { step: 0.01, value: gradient.value2, onChange: (val) => gradientChange({ ...gradient, value2: val }), compact: true })) })), jsx("div", { className: classes.stepSliderWrapper, children: jsx(SyncedSliderInput, { notifyOnlyOnRelease: true, min: 0, max: 1, step: 0.01, value: gradient.step, onChange: (val) => gradientChange({ ...gradient, step: val }), compact: hasNumericValues }) })] }));
7875
7880
  };
7876
7881
  const FactorGradientCast = Gradient;
7877
7882
  const Color3GradientCast = Gradient;
@@ -7977,6 +7982,112 @@ const Color4GradientList = (props) => {
7977
7982
  } }, "Color4"));
7978
7983
  };
7979
7984
 
7985
+ /**
7986
+ * Shared utilities for snippet server operations (save/load).
7987
+ */
7988
+ /**
7989
+ * Persist a snippet ID to local storage for quick reuse.
7990
+ * @param storageKey The local storage key to use.
7991
+ * @param snippetId The snippet ID to persist.
7992
+ * @param maxItems Maximum number of items to store (default 50).
7993
+ */
7994
+ function PersistSnippetId(storageKey, snippetId, maxItems = 50) {
7995
+ try {
7996
+ const existing = JSON.parse(localStorage.getItem(storageKey) || "[]");
7997
+ const list = Array.isArray(existing) ? existing : [];
7998
+ if (!list.includes(snippetId)) {
7999
+ list.unshift(snippetId);
8000
+ }
8001
+ localStorage.setItem(storageKey, JSON.stringify(list.slice(0, maxItems)));
8002
+ }
8003
+ catch {
8004
+ // Ignore storage failures.
8005
+ }
8006
+ }
8007
+ /**
8008
+ * Save content to the snippet server.
8009
+ * @param config Configuration for the save operation.
8010
+ * @returns Promise resolving to the save result.
8011
+ */
8012
+ async function SaveToSnippetServer(config) {
8013
+ const { snippetUrl, currentSnippetId, content, payloadKey, storageKey, entityName } = config;
8014
+ const dataToSend = {
8015
+ payload: JSON.stringify({
8016
+ [payloadKey]: content,
8017
+ }),
8018
+ name: "",
8019
+ description: "",
8020
+ tags: "",
8021
+ };
8022
+ const headers = new Headers();
8023
+ headers.append("Content-Type", "application/json");
8024
+ let response;
8025
+ try {
8026
+ response = await fetch(snippetUrl + (currentSnippetId ? "/" + currentSnippetId : ""), {
8027
+ method: "POST",
8028
+ headers,
8029
+ body: JSON.stringify(dataToSend),
8030
+ });
8031
+ }
8032
+ catch (e) {
8033
+ const errorMsg = `Unable to save your ${entityName ?? "content"}: ${e}`;
8034
+ alert(errorMsg);
8035
+ throw new Error(errorMsg);
8036
+ }
8037
+ if (!response.ok) {
8038
+ const errorMsg = `Unable to save your ${entityName ?? "content"}`;
8039
+ alert(errorMsg);
8040
+ throw new Error(errorMsg);
8041
+ }
8042
+ const snippet = await response.json();
8043
+ const oldSnippetId = currentSnippetId || "_BLANK";
8044
+ let newSnippetId = snippet.id;
8045
+ if (snippet.version && snippet.version !== "0") {
8046
+ newSnippetId += "#" + snippet.version;
8047
+ }
8048
+ // Copy to clipboard when available.
8049
+ if (navigator.clipboard) {
8050
+ await navigator.clipboard.writeText(newSnippetId);
8051
+ }
8052
+ // Persist to local storage if configured.
8053
+ if (storageKey) {
8054
+ PersistSnippetId(storageKey, newSnippetId);
8055
+ }
8056
+ // Show success alert
8057
+ alert(`${entityName ?? "Content"} saved with ID: ${newSnippetId} (the id was also saved to your clipboard)`);
8058
+ return {
8059
+ snippetId: newSnippetId,
8060
+ oldSnippetId,
8061
+ };
8062
+ }
8063
+ /**
8064
+ * Prompt the user for a snippet ID.
8065
+ * @param message The prompt message.
8066
+ * @returns The trimmed snippet ID, or null if cancelled/empty.
8067
+ */
8068
+ function PromptForSnippetId(message = "Please enter the snippet ID to use") {
8069
+ const requestedSnippetId = window.prompt(message);
8070
+ const trimmed = requestedSnippetId?.trim();
8071
+ return trimmed || null;
8072
+ }
8073
+ /**
8074
+ * Notify the playground about a snippet ID change (for code replacement).
8075
+ * NOTE this is an anti-pattern, instead playground should hook in and observe changes / update its own code
8076
+ * This is a legacy approach and should not be copied elsewhere
8077
+ * @param oldSnippetId The previous snippet ID.
8078
+ * @param newSnippetId The new snippet ID.
8079
+ * @param parseMethodName The name of the parse method (e.g., "SpriteManager.ParseFromSnippetAsync").
8080
+ */
8081
+ function NotifyPlaygroundOfSnippetChange(oldSnippetId, newSnippetId, parseMethodName) {
8082
+ const windowAsAny = window;
8083
+ if (windowAsAny.Playground && oldSnippetId) {
8084
+ windowAsAny.Playground.onRequestCodeChangeObservable.notifyObservers({
8085
+ regex: new RegExp(`${parseMethodName}\\("${oldSnippetId}`, "g"),
8086
+ replace: `${parseMethodName}("${newSnippetId}`,
8087
+ });
8088
+ }
8089
+ }
8090
+
7980
8091
  const useAttractorStyles = makeStyles({
7981
8092
  container: {
7982
8093
  // top-level div used for lineContainer, in UI overhaul update to just use linecontainer
@@ -8091,7 +8202,7 @@ const AttractorList = (props) => {
8091
8202
  } })] }));
8092
8203
  };
8093
8204
 
8094
- const SnippetDashboardStorageKey = "Babylon/InspectorV2/SnippetDashboard/ParticleSystems";
8205
+ const SnippetDashboardStorageKey$1 = "Babylon/InspectorV2/SnippetDashboard/ParticleSystems";
8095
8206
  function TryParseJsonString(value) {
8096
8207
  if (!value) {
8097
8208
  return undefined;
@@ -8118,27 +8229,13 @@ function NormalizeParticleSystemSerialization(rawData) {
8118
8229
  const particleSystem = TryParseJsonString(jsonPayload?.particleSystem);
8119
8230
  return particleSystem ?? rawData;
8120
8231
  }
8121
- function PersistSnippetId(snippetId) {
8122
- // Persist snippet IDs locally for quick reuse.
8123
- try {
8124
- const existing = JSON.parse(localStorage.getItem(SnippetDashboardStorageKey) || "[]");
8125
- const list = Array.isArray(existing) ? existing : [];
8126
- if (!list.includes(snippetId)) {
8127
- list.unshift(snippetId);
8128
- }
8129
- localStorage.setItem(SnippetDashboardStorageKey, JSON.stringify(list.slice(0, 50)));
8130
- }
8131
- catch {
8132
- // Ignore storage failures.
8133
- }
8134
- }
8135
8232
  /**
8136
8233
  * Display general (high-level) information about a particle system.
8137
8234
  * @param props Component props.
8138
8235
  * @returns Render property lines.
8139
8236
  */
8140
8237
  const ParticleSystemGeneralProperties = (props) => {
8141
- const { particleSystem: system } = props;
8238
+ const { particleSystem: system, selectionService } = props;
8142
8239
  const scene = system.getScene();
8143
8240
  const isBillboardBased = useProperty(system, "isBillboardBased");
8144
8241
  const capacity = useObservableState(() => system.getCapacity());
@@ -8170,87 +8267,50 @@ const ParticleSystemGeneralProperties = (props) => {
8170
8267
  alert("Failed to load particle system: " + e);
8171
8268
  }
8172
8269
  }, [scene, system]);
8173
- const loadFromSnippetServer = useCallback(() => {
8270
+ const loadFromSnippetServer = useCallback(async () => {
8174
8271
  if (!scene) {
8175
8272
  alert("No scene available.");
8176
8273
  return;
8177
8274
  }
8178
- // Prompt for a snippet id (minimal UX).
8179
- const requestedSnippetId = window.prompt("Please enter the snippet ID to use");
8180
- const trimmed = requestedSnippetId?.trim();
8181
- if (!trimmed) {
8275
+ const snippetId = PromptForSnippetId();
8276
+ if (!snippetId) {
8182
8277
  return;
8183
8278
  }
8184
- const request = new XMLHttpRequest();
8185
- request.onreadystatechange = () => {
8186
- if (request.readyState !== 4) {
8187
- return;
8188
- }
8189
- if (request.status !== 200) {
8190
- alert("Unable to load your particle system");
8191
- return;
8192
- }
8193
- try {
8194
- const responseObject = ParseJsonLoadContents(request.responseText);
8195
- if (!responseObject) {
8196
- alert("Unable to load your particle system");
8197
- return;
8198
- }
8199
- applyParticleSystemJsonToSystem(responseObject);
8200
- system.snippetId = trimmed;
8201
- }
8202
- catch (e) {
8203
- alert("Unable to load your particle system: " + e);
8204
- }
8205
- };
8206
- request.open("GET", ParticleHelper.SnippetUrl + "/" + trimmed.replace(/#/g, "/"), true);
8207
- request.send();
8208
- }, [applyParticleSystemJsonToSystem, scene, system]);
8279
+ const isGpu = system instanceof GPUParticleSystem;
8280
+ const oldSnippetId = system.snippetId;
8281
+ // Dispose the old system and clear selection (v1 behavior)
8282
+ system.dispose();
8283
+ selectionService.selectedEntity = null;
8284
+ try {
8285
+ const newSystem = await ParticleHelper.ParseFromSnippetAsync(snippetId, scene, isGpu);
8286
+ selectionService.selectedEntity = newSystem;
8287
+ // Notify the playground to update its code with the new snippet ID.
8288
+ NotifyPlaygroundOfSnippetChange(oldSnippetId, snippetId, "ParticleHelper.ParseFromSnippetAsync");
8289
+ }
8290
+ catch (err) {
8291
+ alert("Unable to load your particle system: " + err);
8292
+ }
8293
+ }, [scene, selectionService, system]);
8209
8294
  const saveToSnippetServer = useCallback(async () => {
8210
- const deferred = new Deferred();
8211
- // Serialize once and post as snippet payload.
8212
- const content = JSON.stringify(system.serialize(true));
8213
- const xmlHttp = new XMLHttpRequest();
8214
- xmlHttp.onreadystatechange = () => {
8215
- if (xmlHttp.readyState !== 4) {
8216
- return;
8217
- }
8218
- if (xmlHttp.status !== 200) {
8219
- deferred.reject();
8220
- alert("Unable to save your particle system");
8221
- return;
8222
- }
8223
- try {
8224
- const snippet = JSON.parse(xmlHttp.responseText);
8225
- system.snippetId = snippet.id;
8226
- if (snippet.version && snippet.version !== "0") {
8227
- system.snippetId += "#" + snippet.version;
8228
- }
8229
- // Copy to clipboard when available.
8230
- if (navigator.clipboard) {
8231
- void navigator.clipboard.writeText(system.snippetId);
8232
- }
8233
- PersistSnippetId(system.snippetId);
8234
- deferred.resolve();
8235
- alert("Particle system saved with ID: " + system.snippetId + " (the id was also saved to your clipboard)");
8236
- }
8237
- catch (e) {
8238
- deferred.reject(e);
8239
- alert("Unable to save your particle system: " + e);
8240
- }
8241
- };
8242
- xmlHttp.open("POST", ParticleHelper.SnippetUrl + (system.snippetId ? "/" + system.snippetId : ""), true);
8243
- xmlHttp.setRequestHeader("Content-Type", "application/json");
8244
- const dataToSend = {
8245
- payload: JSON.stringify({
8246
- particleSystem: content,
8247
- }),
8248
- name: "",
8249
- description: "",
8250
- tags: "",
8251
- };
8252
- xmlHttp.send(JSON.stringify(dataToSend));
8253
- await deferred.promise;
8295
+ try {
8296
+ const content = JSON.stringify(system.serialize(true));
8297
+ const currentSnippetId = system.snippetId;
8298
+ const result = await SaveToSnippetServer({
8299
+ snippetUrl: ParticleHelper.SnippetUrl,
8300
+ currentSnippetId,
8301
+ content,
8302
+ payloadKey: "particleSystem",
8303
+ storageKey: SnippetDashboardStorageKey$1,
8304
+ entityName: "particle system",
8305
+ });
8306
+ // eslint-disable-next-line require-atomic-updates
8307
+ system.snippetId = result.snippetId;
8308
+ PersistSnippetId(SnippetDashboardStorageKey$1, result.snippetId);
8309
+ NotifyPlaygroundOfSnippetChange(result.oldSnippetId, result.snippetId, "ParticleSystem.ParseFromSnippetAsync");
8310
+ }
8311
+ catch {
8312
+ // Alert already shown by SaveToSnippetServer
8313
+ }
8254
8314
  }, [system]);
8255
8315
  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" : "View", icon: system.isNodeGenerated ? EditRegular : EyeRegular, onClick: async () => {
8256
8316
  const scene = system.getScene();
@@ -8768,7 +8828,7 @@ const ParticleSystemPropertiesServiceDefinition = {
8768
8828
  {
8769
8829
  section: "General",
8770
8830
  order: 1,
8771
- component: ({ context }) => jsx(ParticleSystemGeneralProperties, { particleSystem: context }),
8831
+ component: ({ context }) => jsx(ParticleSystemGeneralProperties, { particleSystem: context, selectionService: selectionService }),
8772
8832
  },
8773
8833
  ],
8774
8834
  });
@@ -8892,6 +8952,33 @@ const PrestepOptions = [
8892
8952
  { label: "Teleport", value: PhysicsPrestepType.TELEPORT },
8893
8953
  { label: "Action", value: PhysicsPrestepType.ACTION },
8894
8954
  ];
8955
+ /**
8956
+ * Convert physics shape type to a human-readable string.
8957
+ * @param type The physics shape type.
8958
+ * @returns The human-readable string.
8959
+ */
8960
+ function GetShapeTypeString(type) {
8961
+ switch (type) {
8962
+ case 3 /* PhysicsShapeType.BOX */:
8963
+ return "Box";
8964
+ case 0 /* PhysicsShapeType.SPHERE */:
8965
+ return "Sphere";
8966
+ case 2 /* PhysicsShapeType.CYLINDER */:
8967
+ return "Cylinder";
8968
+ case 1 /* PhysicsShapeType.CAPSULE */:
8969
+ return "Capsule";
8970
+ case 5 /* PhysicsShapeType.CONTAINER */:
8971
+ return "Container";
8972
+ case 4 /* PhysicsShapeType.CONVEX_HULL */:
8973
+ return "Convex Hull";
8974
+ case 6 /* PhysicsShapeType.MESH */:
8975
+ return "Mesh";
8976
+ case 7 /* PhysicsShapeType.HEIGHTFIELD */:
8977
+ return "Heightfield";
8978
+ default:
8979
+ return "Unknown";
8980
+ }
8981
+ }
8895
8982
  const TransformNodePhysicsProperties = (props) => {
8896
8983
  const { node } = props;
8897
8984
  const physicsBody = useProperty(node, "physicsBody");
@@ -8919,15 +9006,19 @@ const PhysicsBodyProperties = (props) => {
8919
9006
  // Get current velocities
8920
9007
  const linearVelocity = useObservableState(useCallback(() => physicsBody.getLinearVelocity(), [physicsBody]), useInterceptObservable("function", physicsBody, "setLinearVelocity"));
8921
9008
  const angularVelocity = useObservableState(useCallback(() => physicsBody.getAngularVelocity(), [physicsBody]), useInterceptObservable("function", physicsBody, "setAngularVelocity"));
9009
+ // Get shape and material properties
9010
+ const shape = useProperty(physicsBody, "shape");
9011
+ const type = useProperty(shape, "type");
9012
+ const material = useProperty(shape, "material");
8922
9013
  return (jsxs(Fragment, { children: [jsx(NumberDropdownPropertyLine, { label: "Motion Type", options: MotionOptions, value: motionType, onChange: (value) => {
8923
9014
  return physicsBody.setMotionType(value);
8924
9015
  } }, "MotionType"), jsx(NumberDropdownPropertyLine, { label: "Prestep Type", options: PrestepOptions, value: prestepType, onChange: (value) => {
8925
9016
  return physicsBody.setPrestepType(value);
8926
- } }), jsx(NumberInputPropertyLine, { label: "Linear Damping", min: 0, max: 1, step: 0.01, value: linearDamping, onChange: (e) => {
9017
+ } }), shape && jsx(TextPropertyLine, { label: "Shape Type", value: GetShapeTypeString(type) }), jsx(NumberInputPropertyLine, { label: "Linear Damping", min: 0, max: 1, step: 0.01, value: linearDamping, onChange: (e) => {
8927
9018
  physicsBody.setLinearDamping(e);
8928
9019
  } }), jsx(NumberInputPropertyLine, { label: "Angular Damping", min: 0, max: 1, step: 0.01, value: angularDamping, onChange: (e) => {
8929
9020
  physicsBody.setAngularDamping(e);
8930
- } }), jsx(Vector3PropertyLine, { label: "Linear Velocity", value: linearVelocity, onChange: (value) => physicsBody.setLinearVelocity(value) }), jsx(Vector3PropertyLine, { label: "Angular Velocity", value: angularVelocity, onChange: (value) => physicsBody.setAngularVelocity(value) }), massProperties && (jsxs(Fragment, { children: [jsx(NumberInputPropertyLine, { label: "Mass", value: massProperties.mass ?? 0, min: 0, step: 0.01, onChange: (value) => {
9021
+ } }), shape && (jsxs(Fragment, { children: [jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Dynamic Friction", min: 0, max: 1, step: 0.01, target: material, propertyKey: "friction", nullable: true, defaultValue: 0.5 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Static Friction", min: 0, max: 1, step: 0.01, target: material, propertyKey: "staticFriction", nullable: true, defaultValue: material?.friction ?? 0.5 }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Restitution", min: 0, max: 1, step: 0.01, target: material, propertyKey: "restitution", nullable: true, defaultValue: 0 })] })), jsx(Vector3PropertyLine, { label: "Linear Velocity", value: linearVelocity, onChange: (value) => physicsBody.setLinearVelocity(value) }), jsx(Vector3PropertyLine, { label: "Angular Velocity", value: angularVelocity, onChange: (value) => physicsBody.setAngularVelocity(value) }), massProperties && (jsxs(Fragment, { children: [jsx(NumberInputPropertyLine, { label: "Mass", value: massProperties.mass ?? 0, min: 0, step: 0.01, onChange: (value) => {
8931
9022
  physicsBody.setMassProperties({ ...massProperties, mass: value });
8932
9023
  } }), jsx(Vector3PropertyLine, { label: "Center of Mass", value: centerOfMass, onChange: (value) => {
8933
9024
  physicsBody.setMassProperties({ ...massProperties, centerOfMass: value });
@@ -9300,6 +9391,7 @@ const SpinButtonPropertyLine = (props) => {
9300
9391
  return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props }) }));
9301
9392
  };
9302
9393
 
9394
+ const SnippetDashboardStorageKey = "Babylon/InspectorV2/SnippetDashboard/SpriteManagers";
9303
9395
  const SpriteManagerGeneralProperties = (props) => {
9304
9396
  const { spriteManager, selectionService } = props;
9305
9397
  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) })] }));
@@ -9312,6 +9404,84 @@ const SpriteManagerCellProperties = (props) => {
9312
9404
  const { spriteManager } = props;
9313
9405
  return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SpinButtonPropertyLine, label: "Cell Width", target: spriteManager, propertyKey: "cellWidth", min: 1, step: 1 }, "CellWidth"), jsx(BoundProperty, { component: SpinButtonPropertyLine, label: "Cell Height", target: spriteManager, propertyKey: "cellHeight", min: 1, step: 1 }, "CellHeight")] }));
9314
9406
  };
9407
+ const SpriteManagerFileProperties = (props) => {
9408
+ const { spriteManager, selectionService } = props;
9409
+ const scene = spriteManager.scene;
9410
+ const loadFromFile = useCallback((files) => {
9411
+ const file = files[0];
9412
+ if (!file) {
9413
+ return;
9414
+ }
9415
+ Tools.ReadFile(file, (data) => {
9416
+ const decoder = new TextDecoder("utf-8");
9417
+ const jsonObject = JSON.parse(decoder.decode(data));
9418
+ spriteManager.dispose();
9419
+ selectionService.selectedEntity = null;
9420
+ const newManager = SpriteManager.Parse(jsonObject, scene, "");
9421
+ selectionService.selectedEntity = newManager;
9422
+ }, undefined, true);
9423
+ }, [spriteManager, scene, selectionService]);
9424
+ const saveToFile = useCallback(() => {
9425
+ const content = JSON.stringify(spriteManager.serialize(true));
9426
+ Tools.Download(new Blob([content]), "spriteManager.json");
9427
+ }, [spriteManager]);
9428
+ return (jsxs(Fragment, { children: [jsx(FileUploadLine, { label: "Load", onClick: loadFromFile, accept: ".json" }), jsx(ButtonLine, { label: "Save", onClick: saveToFile })] }));
9429
+ };
9430
+ const SpriteManagerSnippetProperties = (props) => {
9431
+ const { spriteManager, selectionService } = props;
9432
+ const scene = spriteManager.scene;
9433
+ const snippetUrl = Constants.SnippetUrl;
9434
+ const snippetId = useProperty(spriteManager, "snippetId");
9435
+ const loadFromSnippet = useCallback(async () => {
9436
+ const requestedSnippetId = PromptForSnippetId();
9437
+ if (!requestedSnippetId) {
9438
+ return;
9439
+ }
9440
+ spriteManager.dispose();
9441
+ selectionService.selectedEntity = null;
9442
+ try {
9443
+ const newManager = await SpriteManager.ParseFromSnippetAsync(requestedSnippetId, scene);
9444
+ selectionService.selectedEntity = newManager;
9445
+ }
9446
+ catch (err) {
9447
+ alert("Unable to load your sprite manager: " + err);
9448
+ }
9449
+ }, [spriteManager, scene, selectionService]);
9450
+ const saveToSnippet = useCallback(async () => {
9451
+ try {
9452
+ const content = JSON.stringify(spriteManager.serialize(true));
9453
+ const currentSnippetId = spriteManager.snippetId;
9454
+ const result = await SaveToSnippetServer({
9455
+ snippetUrl,
9456
+ currentSnippetId,
9457
+ content,
9458
+ payloadKey: "spriteManager",
9459
+ storageKey: SnippetDashboardStorageKey,
9460
+ entityName: "sprite manager",
9461
+ });
9462
+ // eslint-disable-next-line require-atomic-updates
9463
+ spriteManager.snippetId = result.snippetId;
9464
+ PersistSnippetId(SnippetDashboardStorageKey, result.snippetId);
9465
+ NotifyPlaygroundOfSnippetChange(result.oldSnippetId, result.snippetId, "SpriteManager.ParseFromSnippetAsync");
9466
+ }
9467
+ catch {
9468
+ // Alert already shown by SaveToSnippetServer
9469
+ }
9470
+ }, [spriteManager, snippetUrl]);
9471
+ return (jsxs(Fragment, { children: [snippetId && jsx(TextPropertyLine, { label: "Snippet ID", value: snippetId }), jsx(ButtonLine, { label: "Load from Snippet Server", onClick: loadFromSnippet, icon: CloudArrowUpRegular }), jsx(ButtonLine, { label: "Save to Snippet Server", onClick: saveToSnippet, icon: CloudArrowDownRegular })] }));
9472
+ };
9473
+ const SpriteManagerActionsProperties = (props) => {
9474
+ const { spriteManager, selectionService } = props;
9475
+ const addNewSprite = useCallback(() => {
9476
+ const newSprite = new Sprite("new sprite", spriteManager);
9477
+ selectionService.selectedEntity = newSprite;
9478
+ }, [spriteManager, selectionService]);
9479
+ const disposeManager = useCallback(() => {
9480
+ spriteManager.dispose();
9481
+ selectionService.selectedEntity = null;
9482
+ }, [spriteManager, selectionService]);
9483
+ return (jsxs(Fragment, { children: [spriteManager.sprites.length < spriteManager.capacity && jsx(ButtonLine, { label: "Add New Sprite", onClick: addNewSprite }), jsx(ButtonLine, { label: "Dispose", onClick: disposeManager })] }));
9484
+ };
9315
9485
 
9316
9486
  /**
9317
9487
  * Gets the data of the specified texture by rendering it to an intermediate RGBA texture and retrieving the bytes from it.
@@ -9427,6 +9597,8 @@ const useStyles$9 = makeStyles({
9427
9597
  marginTop: tokens.spacingVerticalXS,
9428
9598
  marginBottom: tokens.spacingVerticalS,
9429
9599
  width: "100%",
9600
+ // Checkerboard background to show transparency
9601
+ background: "repeating-conic-gradient(#B2B2B2 0% 25%, white 25% 50%) 50% / 32px 32px",
9430
9602
  },
9431
9603
  });
9432
9604
  // This method of holding TextureChannels was brought over from inspectorv1 and can likely be refactored/simplified
@@ -9551,6 +9723,18 @@ const SpritePropertiesServiceDefinition = {
9551
9723
  section: "General",
9552
9724
  component: ({ context }) => jsx(SpriteManagerGeneralProperties, { spriteManager: context, selectionService: selectionService }),
9553
9725
  },
9726
+ {
9727
+ section: "Actions",
9728
+ component: ({ context }) => jsx(SpriteManagerActionsProperties, { spriteManager: context, selectionService: selectionService }),
9729
+ },
9730
+ {
9731
+ section: "File",
9732
+ component: ({ context }) => jsx(SpriteManagerFileProperties, { spriteManager: context, selectionService: selectionService }),
9733
+ },
9734
+ {
9735
+ section: "Snippet",
9736
+ component: ({ context }) => jsx(SpriteManagerSnippetProperties, { spriteManager: context, selectionService: selectionService }),
9737
+ },
9554
9738
  {
9555
9739
  section: "Cells",
9556
9740
  component: ({ context }) => jsx(SpriteManagerCellProperties, { spriteManager: context }),
@@ -9632,6 +9816,13 @@ const AdvancedDynamicTextureGeneralProperties = MakeLazyComponent(async () => {
9632
9816
  return (jsxs(Fragment, { children: [jsx(StringifiedPropertyLine, { label: "Last Layout Time", value: layoutTime, precision: 2, units: "ms" }), jsx(StringifiedPropertyLine, { label: "Last Render Time", value: renderTime, precision: 2, units: "ms" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Render Scale", target: texture, propertyKey: "renderScale", min: 0.1, max: 5, step: 0.1 }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Premultiply Alpha", target: texture, propertyKey: "premulAlpha" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Ideal Width", target: texture, propertyKey: "idealWidth" }), jsx(BoundProperty, { component: NumberInputPropertyLine, label: "Ideal Height", target: texture, propertyKey: "idealHeight" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Use Smallest Ideal", target: texture, propertyKey: "useSmallestIdeal" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Render at Ideal Size", target: texture, propertyKey: "renderAtIdealSize" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Invalidate Rect Optimization", target: texture, propertyKey: "useInvalidateRectOptimization" })] }));
9633
9817
  };
9634
9818
  }, { spinnerSize: "extra-tiny", spinnerLabel: "Loading..." });
9819
+ const AdvancedDynamicTexturePreviewProperties = (props) => {
9820
+ const { texture } = props;
9821
+ return (jsx(Fragment, { children: jsx(ButtonLine, { label: "Edit GUI", icon: EditRegular, onClick: async () => {
9822
+ const { GUIEditor } = await import('@babylonjs/gui-editor');
9823
+ await GUIEditor.Show({ liveGuiTexture: texture });
9824
+ } }) }));
9825
+ };
9635
9826
 
9636
9827
  const TextureFormat = [
9637
9828
  { label: "Alpha", normalizable: false, value: Constants.TEXTUREFORMAT_ALPHA },
@@ -9701,7 +9892,7 @@ const BaseTexturePreviewProperties = (props) => {
9701
9892
  const { texture, textureEditor: TextureEditor } = props;
9702
9893
  const texturePreviewImperativeRef = useRef(null);
9703
9894
  const childWindow = useRef(null);
9704
- return (jsxs(Fragment, { children: [jsx(TexturePreview, { imperativeRef: texturePreviewImperativeRef, texture: texture }), jsx(TextureUpload, { texture: texture }), jsx(ButtonLine, { label: "Edit Texture", onClick: () => childWindow.current?.open() }), jsx(ChildWindow, { id: "Texture Editor", imperativeRef: childWindow, children: jsx(TextureEditor, { texture: texture, onUpdate: async () => await texturePreviewImperativeRef.current?.refresh() }) })] }));
9895
+ return (jsxs(Fragment, { children: [jsx(TexturePreview, { imperativeRef: texturePreviewImperativeRef, texture: texture }), jsx(TextureUpload, { texture: texture }), jsx(ButtonLine, { label: "Edit Texture", onClick: () => childWindow.current?.open(), icon: EditRegular }), jsx(ChildWindow, { id: "Texture Editor", imperativeRef: childWindow, children: jsx(TextureEditor, { texture: texture, onUpdate: async () => await texturePreviewImperativeRef.current?.refresh() }) })] }));
9705
9896
  };
9706
9897
  const BaseTextureGeneralProperties = (props) => {
9707
9898
  const { texture } = props;
@@ -11458,6 +11649,7 @@ const TexturePropertiesServiceDefinition = {
11458
11649
  {
11459
11650
  section: "Preview",
11460
11651
  component: ({ context }) => jsx(TexturePreviewProperties, { texture: context }),
11652
+ order: 200,
11461
11653
  },
11462
11654
  {
11463
11655
  section: "General",
@@ -11516,6 +11708,11 @@ const TexturePropertiesServiceDefinition = {
11516
11708
  order: 100,
11517
11709
  component: ({ context }) => jsx(AdvancedDynamicTextureGeneralProperties, { texture: context }),
11518
11710
  },
11711
+ {
11712
+ section: "Preview",
11713
+ order: 100,
11714
+ component: ({ context }) => jsx(AdvancedDynamicTexturePreviewProperties, { texture: context }),
11715
+ },
11519
11716
  ],
11520
11717
  });
11521
11718
  return {
@@ -11804,6 +12001,14 @@ const FrameGraphExplorerServiceDefinition = {
11804
12001
  function IsAdvancedDynamicTexture(entity) {
11805
12002
  return entity?.getClassName?.() === "AdvancedDynamicTexture";
11806
12003
  }
12004
+ function IsContainer(entity) {
12005
+ // Check for Container-specific properties without using instanceof to avoid importing the concrete type
12006
+ return entity?.children !== undefined && entity?.onControlAddedObservable !== undefined;
12007
+ }
12008
+ function IsControl(entity) {
12009
+ // Check for Control-specific properties without using instanceof to avoid importing the concrete type
12010
+ return entity?._currentMeasure !== undefined && entity?.onPointerDownObservable !== undefined;
12011
+ }
11807
12012
  const GuiExplorerServiceDefinition = {
11808
12013
  friendlyName: "GUI Explorer",
11809
12014
  consumes: [SceneExplorerServiceIdentity, SceneContextIdentity],
@@ -11812,35 +12017,121 @@ const GuiExplorerServiceDefinition = {
11812
12017
  if (!scene) {
11813
12018
  return undefined;
11814
12019
  }
12020
+ const guiEntityAddedObservable = new Observable();
12021
+ const guiEntityRemovedObservable = new Observable();
12022
+ const textureAddedObserver = scene.onNewTextureAddedObservable.add((texture) => {
12023
+ if (IsAdvancedDynamicTexture(texture)) {
12024
+ guiEntityAddedObservable.notifyObservers(texture);
12025
+ }
12026
+ });
12027
+ const textureRemovedObserver = scene.onTextureRemovedObservable.add((texture) => {
12028
+ if (IsAdvancedDynamicTexture(texture)) {
12029
+ guiEntityRemovedObservable.notifyObservers(texture);
12030
+ }
12031
+ });
11815
12032
  const sectionRegistration = sceneExplorerService.addSection({
11816
12033
  displayName: "GUI",
11817
12034
  order: 1100 /* DefaultSectionsOrder.GUIs */,
11818
12035
  getRootEntities: () => scene.textures.filter(IsAdvancedDynamicTexture),
11819
- getEntityDisplayInfo: (texture) => {
12036
+ getEntityChildren: (entity) => (IsAdvancedDynamicTexture(entity) ? entity.getChildren() : IsContainer(entity) ? entity.children : []),
12037
+ getEntityDisplayInfo: (entity) => {
12038
+ const disposeActions = [];
11820
12039
  const onChangeObservable = new Observable();
11821
- const nameHookToken = InterceptProperty(texture, "name", {
12040
+ disposeActions.push(() => onChangeObservable.clear());
12041
+ const nameHookToken = InterceptProperty(entity, "name", {
11822
12042
  afterSet: () => {
11823
12043
  onChangeObservable.notifyObservers();
11824
12044
  },
11825
12045
  });
12046
+ disposeActions.push(() => nameHookToken.dispose());
12047
+ if (!IsAdvancedDynamicTexture(entity) && IsContainer(entity)) {
12048
+ const controlAddedObserver = entity.onControlAddedObservable.add((control) => {
12049
+ if (control) {
12050
+ guiEntityAddedObservable.notifyObservers(control);
12051
+ }
12052
+ });
12053
+ disposeActions.push(() => entity.onControlAddedObservable.remove(controlAddedObserver));
12054
+ const controlRemovedObserver = entity.onControlRemovedObservable.add((control) => {
12055
+ if (control) {
12056
+ guiEntityRemovedObservable.notifyObservers(control);
12057
+ }
12058
+ });
12059
+ disposeActions.push(() => entity.onControlRemovedObservable.remove(controlRemovedObserver));
12060
+ }
11826
12061
  return {
11827
12062
  get name() {
11828
- return texture.name;
12063
+ if (IsAdvancedDynamicTexture(entity)) {
12064
+ return entity.name;
12065
+ }
12066
+ else {
12067
+ return `${entity.name ?? "No name"} [${entity.getClassName()}]`;
12068
+ }
11829
12069
  },
11830
12070
  onChange: onChangeObservable,
11831
12071
  dispose: () => {
11832
- nameHookToken.dispose();
12072
+ disposeActions.reverse().forEach((disposeAction) => disposeAction());
12073
+ },
12074
+ };
12075
+ },
12076
+ entityIcon: ({ entity }) => (IsAdvancedDynamicTexture(entity) ? jsx(AppGenericRegular, {}) : jsx(RectangleLandscapeRegular, {})),
12077
+ getEntityAddedObservables: () => [guiEntityAddedObservable],
12078
+ getEntityRemovedObservables: () => [guiEntityRemovedObservable],
12079
+ });
12080
+ const highlightControlCommandRegistration = sceneExplorerService.addEntityCommand({
12081
+ predicate: (entity) => IsControl(entity),
12082
+ order: 1000 /* DefaultCommandsOrder.GuiHighlight */,
12083
+ getCommand: (control) => {
12084
+ const onChangeObservable = new Observable();
12085
+ const showBoundingBoxHook = InterceptProperty(control, "isHighlighted", {
12086
+ afterSet: () => onChangeObservable.notifyObservers(),
12087
+ });
12088
+ return {
12089
+ type: "toggle",
12090
+ get displayName() {
12091
+ return `${control.isHighlighted ? "Hide" : "Show"} Bounding Box`;
12092
+ },
12093
+ icon: () => (control.isHighlighted ? jsx(BorderOutsideRegular, {}) : jsx(BorderNoneRegular, {})),
12094
+ get isEnabled() {
12095
+ return control.isHighlighted;
12096
+ },
12097
+ set isEnabled(enabled) {
12098
+ control.isHighlighted = enabled;
12099
+ },
12100
+ onChange: onChangeObservable,
12101
+ dispose: () => {
12102
+ showBoundingBoxHook.dispose();
11833
12103
  onChangeObservable.clear();
11834
12104
  },
11835
12105
  };
11836
12106
  },
11837
- entityIcon: () => jsx(AppGenericRegular, {}),
11838
- getEntityAddedObservables: () => [scene.onNewTextureAddedObservable],
11839
- getEntityRemovedObservables: () => [scene.onTextureRemovedObservable],
12107
+ });
12108
+ const controlVisibilityCommandRegistration = sceneExplorerService.addEntityCommand({
12109
+ predicate: (entity) => IsControl(entity),
12110
+ order: 1100 /* DefaultCommandsOrder.GuiVisibility */,
12111
+ getCommand: (control) => {
12112
+ return {
12113
+ type: "toggle",
12114
+ get displayName() {
12115
+ return `${control.isVisible ? "Hide" : "Show"} Mesh`;
12116
+ },
12117
+ icon: () => (control.isVisible ? jsx(EyeRegular, {}) : jsx(EyeOffRegular, {})),
12118
+ get isEnabled() {
12119
+ return !control.isVisible;
12120
+ },
12121
+ set isEnabled(enabled) {
12122
+ control.isVisible = !enabled;
12123
+ },
12124
+ onChange: control.onIsVisibleChangedObservable,
12125
+ };
12126
+ },
11840
12127
  });
11841
12128
  return {
11842
12129
  dispose: () => {
12130
+ textureAddedObserver.remove();
12131
+ textureRemovedObserver.remove();
11843
12132
  sectionRegistration.dispose();
12133
+ highlightControlCommandRegistration.dispose();
12134
+ controlVisibilityCommandRegistration.dispose();
11844
12135
  },
11845
12136
  };
11846
12137
  },
@@ -13536,4 +13827,4 @@ const TextAreaPropertyLine = (props) => {
13536
13827
  AttachDebugLayer();
13537
13828
 
13538
13829
  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 };
13539
- //# sourceMappingURL=index-upm3WBf8.js.map
13830
+ //# sourceMappingURL=index-C7ey_J-r.js.map