@babylonjs/inspector 8.36.1-preview → 8.37.0-preview
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/{captureService-Blk4cfMr.js → captureService-DF30oxR-.js} +4 -3
- package/lib/{captureService-Blk4cfMr.js.map → captureService-DF30oxR-.js.map} +1 -1
- package/lib/{exportService-B6ej2VVA.js → exportService-BB4L49R4.js} +4 -3
- package/lib/{exportService-B6ej2VVA.js.map → exportService-BB4L49R4.js.map} +1 -1
- package/lib/{extensionsListService-D9hRcNXP.js → extensionsListService-DcpjIM_c.js} +4 -3
- package/lib/{extensionsListService-D9hRcNXP.js.map → extensionsListService-DcpjIM_c.js.map} +1 -1
- package/lib/{importService-ClQU2IkW.js → importService-DEe18q7F.js} +4 -3
- package/lib/{importService-ClQU2IkW.js.map → importService-DEe18q7F.js.map} +1 -1
- package/lib/{index-CmULGJMc.js → index-B-XOu4uI.js} +1135 -911
- package/lib/index-B-XOu4uI.js.map +1 -0
- package/lib/index.d.ts +202 -172
- package/lib/index.js +3 -2
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/lib/index-CmULGJMc.js.map +0 -1
|
@@ -3,8 +3,8 @@ import { useMemo, useEffect, useState, useRef, useCallback, forwardRef, createCo
|
|
|
3
3
|
import { Color3, Color4 } from '@babylonjs/core/Maths/math.color.js';
|
|
4
4
|
import { Vector3, Quaternion, Matrix, Vector2, Vector4, TmpVectors } from '@babylonjs/core/Maths/math.vector.js';
|
|
5
5
|
import { Observable } from '@babylonjs/core/Misc/observable.js';
|
|
6
|
-
import { makeStyles, Link as Link$1, Body1, ToggleButton as ToggleButton$1, Button as Button$1, tokens, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createLightTheme, createDarkTheme, FluentProvider, Tooltip, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, Portal, RendererProvider, ToolbarRadioButton, createDOMRenderer, MenuGroup, MenuGroupHeader, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, treeItemLevelToken, Switch as Switch$1, PresenceBadge,
|
|
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, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, MyLocationRegular, CameraRegular, LightbulbRegular, BorderOutsideRegular, BorderNoneRegular, EyeRegular, EyeOffRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, DeleteFilled } from '@fluentui/react-icons';
|
|
6
|
+
import { makeStyles, Link as Link$1, Body1, ToggleButton as ToggleButton$1, Button as Button$1, tokens, InfoLabel as InfoLabel$1, Body1Strong, Checkbox as Checkbox$1, mergeClasses, Accordion as Accordion$1, AccordionItem, AccordionHeader, Subtitle2Stronger, AccordionPanel, Divider, TeachingPopover, TeachingPopoverSurface, TeachingPopoverHeader, TeachingPopoverBody, createLightTheme, createDarkTheme, FluentProvider, Tooltip, Menu, MenuTrigger, SplitButton, MenuPopover, MenuList, MenuItem, Toolbar as Toolbar$1, Portal, RendererProvider, ToolbarRadioButton, createDOMRenderer, MenuGroup, MenuGroupHeader, SearchBox as SearchBox$1, FlatTree, FlatTreeItem, TreeItemLayout, treeItemLevelToken, Switch as Switch$1, PresenceBadge, useId, SpinButton as SpinButton$1, Slider, Input, Dropdown as Dropdown$1, Option, Popover, PopoverTrigger, ColorSwatch, PopoverSurface, ColorPicker, ColorArea, ColorSlider, AlphaSlider, MenuItemRadio, Dialog, DialogSurface, DialogBody, DialogTitle, DialogContent, DialogActions, List as List$1, ListItem, Spinner, Badge, MessageBar as MessageBar$1, MessageBarBody, MessageBarTitle, Textarea as Textarea$1, ToolbarButton, useComboboxFilter, Combobox, Field } from '@fluentui/react-components';
|
|
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, CubeTreeRegular, BugRegular, SettingsRegular, ArrowUploadRegular, DataBarHorizontalRegular, WrenchRegular, WeatherSunnyRegular, WeatherMoonRegular, ErrorCircleRegular, ArrowRotateClockwiseRegular, ArrowExpandRegular, SelectObjectRegular, CubeRegular, SaveRegular, ArrowUndoRegular, BracesRegular, BracesDismiss16Regular, CopyRegular, DeleteRegular, EyeOffFilled, EyeFilled, ArrowMoveFilled, StopFilled, PlayFilled, StackRegular, FilmstripRegular, PauseFilled, WeatherSunnyLowFilled, LayerRegular, FrameRegular, PlayRegular, AppGenericRegular, MyLocationRegular, CameraRegular, LightbulbRegular, BorderOutsideRegular, BorderNoneRegular, EyeRegular, EyeOffRegular, VideoFilled, VideoRegular, FlashlightRegular, FlashlightOffRegular, DropRegular, BlurRegular, PipelineRegular, PersonWalkingRegular, DataLineRegular, PersonSquareRegular, LayerDiagonalPersonRegular, ImageEditRegular, ImageRegular, TargetRegular, PersonFeedbackRegular, BranchRegular, DeleteFilled } from '@fluentui/react-icons';
|
|
8
8
|
import { Collapse as Collapse$1, Fade } from '@fluentui/react-motion-components-preview';
|
|
9
9
|
import '@babylonjs/core/Misc/typeStore.js';
|
|
10
10
|
import { useLocalStorage, useTernaryDarkMode } from 'usehooks-ts';
|
|
@@ -32,7 +32,6 @@ import { PerfCollectionStrategy } from '@babylonjs/core/Misc/PerformanceViewer/p
|
|
|
32
32
|
import '@babylonjs/core/Misc/PerformanceViewer/performanceViewerSceneExtension.js';
|
|
33
33
|
import { PressureObserverWrapper } from '@babylonjs/core/Misc/pressureObserverWrapper.js';
|
|
34
34
|
import { AbstractEngine } from '@babylonjs/core/Engines/abstractEngine.js';
|
|
35
|
-
import { EngineStore } from '@babylonjs/core/Engines/engineStore.js';
|
|
36
35
|
import { createRoot } from 'react-dom/client';
|
|
37
36
|
import { Logger } from '@babylonjs/core/Misc/logger.js';
|
|
38
37
|
import { FrameGraphUtils } from '@babylonjs/core/FrameGraph/frameGraphUtils.js';
|
|
@@ -105,6 +104,8 @@ import '@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineMa
|
|
|
105
104
|
import '@babylonjs/core/Sprites/spriteSceneComponent.js';
|
|
106
105
|
import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture.js';
|
|
107
106
|
import { PointerEventTypes } from '@babylonjs/core/Events/pointerEvents.js';
|
|
107
|
+
import { EngineStore } from '@babylonjs/core/Engines/engineStore.js';
|
|
108
|
+
import { UniqueIdGenerator } from '@babylonjs/core/Misc/uniqueIdGenerator.js';
|
|
108
109
|
|
|
109
110
|
const InterceptorHooksMaps$1 = new WeakMap();
|
|
110
111
|
/**
|
|
@@ -1631,6 +1632,17 @@ const useStyles$c = makeStyles({
|
|
|
1631
1632
|
flexDirection: "column",
|
|
1632
1633
|
overflowX: "hidden",
|
|
1633
1634
|
overflowY: "hidden",
|
|
1635
|
+
zIndex: 1,
|
|
1636
|
+
},
|
|
1637
|
+
paneContainerOverlay: {
|
|
1638
|
+
position: "absolute",
|
|
1639
|
+
height: "100%",
|
|
1640
|
+
},
|
|
1641
|
+
paneContainerOverlayLeft: {
|
|
1642
|
+
left: 0,
|
|
1643
|
+
},
|
|
1644
|
+
paneContainerOverlayRight: {
|
|
1645
|
+
right: 0,
|
|
1634
1646
|
},
|
|
1635
1647
|
paneContent: {
|
|
1636
1648
|
display: "flex",
|
|
@@ -1759,7 +1771,7 @@ const SidePaneTab = (props) => {
|
|
|
1759
1771
|
// This hook provides a side pane container and the tab list.
|
|
1760
1772
|
// In "compact" mode, the tab list is integrated into the pane itself.
|
|
1761
1773
|
// In "full" mode, the returned tab list is later injected into the toolbar.
|
|
1762
|
-
function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems) {
|
|
1774
|
+
function usePane(location, layoutMode, defaultWidth, minWidth, sidePanes, onSelectSidePane, dockOperations, toolbarMode, topBarItems, bottomBarItems) {
|
|
1763
1775
|
const classes = useStyles$c();
|
|
1764
1776
|
const [topSelectedTab, setTopSelectedTab] = useState();
|
|
1765
1777
|
const [bottomSelectedTab, setBottomSelectedTab] = useState();
|
|
@@ -1959,7 +1971,9 @@ function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane,
|
|
|
1959
1971
|
const pane = useMemo(() => {
|
|
1960
1972
|
if (!windowState) {
|
|
1961
1973
|
// If there is no window state, then we are docked, so render the resizable div and the collapse container.
|
|
1962
|
-
return (jsx("div", { ref: paneContainerRef, className:
|
|
1974
|
+
return (jsx("div", { ref: paneContainerRef, className: mergeClasses(classes.paneContainer, layoutMode === "inline"
|
|
1975
|
+
? undefined
|
|
1976
|
+
: mergeClasses(classes.paneContainerOverlay, location === "left" ? classes.paneContainerOverlayLeft : classes.paneContainerOverlayRight)), children: (topPanes.length > 0 || bottomPanes.length > 0) && (jsxs("div", { className: `${classes.pane} ${location === "left" ? classes.paneLeft : classes.paneRight}`, children: [jsx(Collapse, { orientation: "horizontal", visible: !collapsed, children: jsx("div", { ref: paneHorizontalResizeElementRef, className: classes.paneContainer, style: { width: `clamp(${minWidth}px, calc(${defaultWidth}px + var(${paneWidthAdjustCSSVar}, 0px)), 1000px)` }, children: corePane }) }), jsx("div", { ref: paneHorizontalResizeHandleRef, className: `${classes.resizer} ${location === "left" ? classes.resizerLeft : classes.resizerRight}`, style: { pointerEvents: `${collapsed ? "none" : "auto"}` } })] })) }));
|
|
1963
1977
|
}
|
|
1964
1978
|
else {
|
|
1965
1979
|
// Otherwise we are undocked, so render into the portal that targets the body of the child window.
|
|
@@ -1971,7 +1985,7 @@ function usePane(location, defaultWidth, minWidth, sidePanes, onSelectSidePane,
|
|
|
1971
1985
|
}, [collapsed, corePane, windowState]);
|
|
1972
1986
|
return [topPaneTabList, pane, collapsed, setCollapsed];
|
|
1973
1987
|
}
|
|
1974
|
-
function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWidth = 350, rightPaneDefaultWidth = 350, rightPaneMinWidth = 350, toolbarMode = "full", sidePaneRemapper = undefined, } = {}) {
|
|
1988
|
+
function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWidth = 350, rightPaneDefaultWidth = 350, rightPaneMinWidth = 350, toolbarMode = "full", sidePaneRemapper = undefined, layoutMode = "inline", } = {}) {
|
|
1975
1989
|
return {
|
|
1976
1990
|
friendlyName: "MainView",
|
|
1977
1991
|
produces: [ShellServiceIdentity, RootComponentServiceIdentity],
|
|
@@ -1998,7 +2012,8 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
|
|
|
1998
2012
|
const coercedSidePaneCache = useRef(new Map());
|
|
1999
2013
|
const coercedSidePanes = useMemo(() => {
|
|
2000
2014
|
// First pass - apply overrides and respect the side pane mode.
|
|
2001
|
-
const coercedSidePanes = sidePanes
|
|
2015
|
+
const coercedSidePanes = sidePanes
|
|
2016
|
+
.map((sidePaneDefinition) => {
|
|
2002
2017
|
let coercedSidePane = coercedSidePaneCache.current.get(sidePaneDefinition.key);
|
|
2003
2018
|
if (!coercedSidePane) {
|
|
2004
2019
|
coercedSidePane = { ...sidePaneDefinition };
|
|
@@ -2012,9 +2027,14 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
|
|
|
2012
2027
|
}
|
|
2013
2028
|
else if (sidePaneRemapper) {
|
|
2014
2029
|
// A side pane remapper has the next highest priority.
|
|
2015
|
-
const
|
|
2016
|
-
|
|
2017
|
-
|
|
2030
|
+
const remapping = sidePaneRemapper(sidePaneDefinition);
|
|
2031
|
+
if (!remapping) {
|
|
2032
|
+
coercedSidePane = undefined;
|
|
2033
|
+
}
|
|
2034
|
+
else {
|
|
2035
|
+
coercedSidePane.horizontalLocation = remapping.horizontalLocation;
|
|
2036
|
+
coercedSidePane.verticalLocation = remapping.verticalLocation;
|
|
2037
|
+
}
|
|
2018
2038
|
}
|
|
2019
2039
|
else {
|
|
2020
2040
|
// Otherwise use the default defined location.
|
|
@@ -2022,7 +2042,8 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
|
|
|
2022
2042
|
coercedSidePane.verticalLocation = sidePaneDefinition.verticalLocation;
|
|
2023
2043
|
}
|
|
2024
2044
|
return coercedSidePane;
|
|
2025
|
-
})
|
|
2045
|
+
})
|
|
2046
|
+
.filter((sidePane) => !!sidePane);
|
|
2026
2047
|
// Second pass - correct any invalid state, specifically if there are only bottom panes, force them to be top panes.
|
|
2027
2048
|
for (const side of ["left", "right"]) {
|
|
2028
2049
|
const topPanes = coercedSidePanes.filter((entry) => entry.horizontalLocation === side && entry.verticalLocation === "top");
|
|
@@ -2098,8 +2119,8 @@ function MakeShellServiceDefinition({ leftPaneDefaultWidth = 350, leftPaneMinWid
|
|
|
2098
2119
|
const bottomBarLeftItems = useMemo(() => bottomToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "left"), [bottomToolBarItems, coerceToolBarItemHorizontalLocation]);
|
|
2099
2120
|
const bottomBarRightItems = useMemo(() => bottomToolBarItems.filter((entry) => coerceToolBarItemHorizontalLocation(entry) === "right"), [bottomToolBarItems, coerceToolBarItemHorizontalLocation]);
|
|
2100
2121
|
const centralContents = useOrderedObservableCollection(centralContentCollection);
|
|
2101
|
-
const [leftPaneTabList, leftPane, leftPaneCollapsed, setLeftPaneCollapsed] = usePane("left", leftPaneDefaultWidth, leftPaneMinWidth, coercedSidePanes, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarLeftItems, bottomBarLeftItems);
|
|
2102
|
-
const [rightPaneTabList, rightPane, rightPaneCollapsed, setRightPaneCollapsed] = usePane("right", rightPaneDefaultWidth, rightPaneMinWidth, coercedSidePanes, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarRightItems, bottomBarRightItems);
|
|
2122
|
+
const [leftPaneTabList, leftPane, leftPaneCollapsed, setLeftPaneCollapsed] = usePane("left", layoutMode, leftPaneDefaultWidth, leftPaneMinWidth, coercedSidePanes, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarLeftItems, bottomBarLeftItems);
|
|
2123
|
+
const [rightPaneTabList, rightPane, rightPaneCollapsed, setRightPaneCollapsed] = usePane("right", layoutMode, rightPaneDefaultWidth, rightPaneMinWidth, coercedSidePanes, onSelectSidePane, sidePaneDockOperations, toolbarMode, topBarRightItems, bottomBarRightItems);
|
|
2103
2124
|
return (jsxs("div", { className: classes.mainView, children: [toolbarMode === "full" && (jsx(Fragment, { children: jsxs("div", { className: classes.barDiv, children: [leftPaneTabList, jsx(Toolbar, { location: "top", components: topToolBarItems }), rightPaneTabList] }) })), jsxs("div", { className: classes.verticallyCentralContent, children: [leftPane, jsxs("div", { className: classes.centralContent, children: [centralContents.map((entry) => (jsx(entry.component, {}, entry.key))), toolbarMode === "compact" && (jsxs(Fragment, { children: [jsx(Fade, { visible: leftPaneCollapsed, delay: 50, children: jsx("div", { className: mergeClasses(classes.expandButtonContainer, classes.expandButtonContainerLeft), children: jsx(Tooltip, { content: "Show Side Pane", relationship: "label", children: jsx(Button$1, { className: classes.expandButton, icon: jsx(PanelLeftExpandRegular, {}), onClick: () => setLeftPaneCollapsed(false) }) }) }) }), jsx(Fade, { visible: rightPaneCollapsed, delay: 50, children: jsx("div", { className: mergeClasses(classes.expandButtonContainer, classes.expandButtonContainerRight), children: jsx(Tooltip, { content: "Show Side Pane", relationship: "label", children: jsx(Button$1, { className: classes.expandButton, icon: jsx(PanelRightExpandRegular, {}), onClick: () => setRightPaneCollapsed(false) }) }) }) })] }))] }), rightPane] }), toolbarMode === "full" && (jsx(Fragment, { children: jsx("div", { className: classes.barDiv, children: jsx(Toolbar, { location: "bottom", components: bottomToolBarItems }) }) }))] }));
|
|
2104
2125
|
};
|
|
2105
2126
|
rootComponent.displayName = "Shell Service Root";
|
|
@@ -3452,7 +3473,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
3452
3473
|
keywords: ["export", "gltf", "glb", "babylon", "exporter", "tools"],
|
|
3453
3474
|
...BabylonWebResources,
|
|
3454
3475
|
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
|
|
3455
|
-
getExtensionModuleAsync: async () => await import('./exportService-
|
|
3476
|
+
getExtensionModuleAsync: async () => await import('./exportService-BB4L49R4.js'),
|
|
3456
3477
|
},
|
|
3457
3478
|
{
|
|
3458
3479
|
name: "Capture Tools",
|
|
@@ -3460,7 +3481,7 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
3460
3481
|
keywords: ["capture", "screenshot", "gif", "video", "tools"],
|
|
3461
3482
|
...BabylonWebResources,
|
|
3462
3483
|
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
|
|
3463
|
-
getExtensionModuleAsync: async () => await import('./captureService-
|
|
3484
|
+
getExtensionModuleAsync: async () => await import('./captureService-DF30oxR-.js'),
|
|
3464
3485
|
},
|
|
3465
3486
|
{
|
|
3466
3487
|
name: "Import Tools",
|
|
@@ -3468,164 +3489,753 @@ const DefaultInspectorExtensionFeed = new BuiltInsExtensionFeed("Inspector", [
|
|
|
3468
3489
|
keywords: ["import", "tools"],
|
|
3469
3490
|
...BabylonWebResources,
|
|
3470
3491
|
author: { name: "Alex Chuber", forumUserName: "alexchuber" },
|
|
3471
|
-
getExtensionModuleAsync: async () => await import('./importService-
|
|
3492
|
+
getExtensionModuleAsync: async () => await import('./importService-DEe18q7F.js'),
|
|
3472
3493
|
},
|
|
3473
3494
|
]);
|
|
3474
3495
|
|
|
3475
|
-
const ExtensionManagerContext = createContext(undefined);
|
|
3476
|
-
function useExtensionManager() {
|
|
3477
|
-
return useContext(ExtensionManagerContext)?.extensionManager;
|
|
3478
|
-
}
|
|
3479
|
-
|
|
3480
|
-
const InstalledExtensionsKey = "Babylon/Extensions/InstalledExtensions";
|
|
3481
|
-
const ExtensionInstalledKeyPrefix = "Babylon/Extensions/IsExtensionInstalled";
|
|
3482
|
-
function GetExtensionInstalledKey(name) {
|
|
3483
|
-
return `${ExtensionInstalledKeyPrefix}/${name}`;
|
|
3484
|
-
}
|
|
3485
|
-
function GetExtensionIdentity(feed, name) {
|
|
3486
|
-
return `${feed}|${name}`;
|
|
3487
|
-
}
|
|
3488
3496
|
/**
|
|
3489
|
-
*
|
|
3497
|
+
* Renders a label with an optional popup containing more info
|
|
3498
|
+
* @param props
|
|
3499
|
+
* @returns
|
|
3490
3500
|
*/
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
const installedExtensionNames = JSON.parse(localStorage.getItem(InstalledExtensionsKey) ?? "[]");
|
|
3511
|
-
for (const installedExtensionName of installedExtensionNames) {
|
|
3512
|
-
const installedExtensionRaw = localStorage.getItem(GetExtensionInstalledKey(installedExtensionName));
|
|
3513
|
-
if (installedExtensionRaw) {
|
|
3514
|
-
const installedExtensionData = JSON.parse(installedExtensionRaw);
|
|
3515
|
-
const feed = feeds.find((feed) => feed.name === installedExtensionData.feed);
|
|
3516
|
-
if (feed) {
|
|
3517
|
-
const installedExtension = extensionManager._createInstalledExtension(installedExtensionData.metadata, feed);
|
|
3518
|
-
extensionManager._installedExtensions.set(installedExtension.metadata.name, installedExtension);
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3501
|
+
const InfoLabel = (props) => {
|
|
3502
|
+
InfoLabel.displayName = "InfoLabel";
|
|
3503
|
+
return (jsx(InfoLabel$1, { htmlFor: props.htmlFor, info: props.info, children: jsx(Body1, { children: props.label }) }));
|
|
3504
|
+
};
|
|
3505
|
+
|
|
3506
|
+
const SpinButton = (props) => {
|
|
3507
|
+
SpinButton.displayName = "SpinButton";
|
|
3508
|
+
const classes = useInputStyles$1();
|
|
3509
|
+
const { size } = useContext(ToolContext);
|
|
3510
|
+
const { min, max } = props;
|
|
3511
|
+
const [value, setValue] = useState(props.value);
|
|
3512
|
+
const lastCommittedValue = useRef(props.value);
|
|
3513
|
+
// step and forceInt are not mutually exclusive since there could be cases where you want to forceInt but have spinButton jump >1 int per spin
|
|
3514
|
+
const step = props.step != undefined ? props.step : props.forceInt ? 1 : undefined;
|
|
3515
|
+
const precision = Math.min(4, step !== undefined ? Math.max(0, CalculatePrecision(step)) : 2); // If no step, set precision to 2. Regardless, cap precision at 4 to avoid wild numbers
|
|
3516
|
+
useEffect(() => {
|
|
3517
|
+
if (props.value !== lastCommittedValue.current) {
|
|
3518
|
+
lastCommittedValue.current = props.value;
|
|
3519
|
+
setValue(props.value); // Update local state when props.value changes
|
|
3521
3520
|
}
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3521
|
+
}, [props.value]);
|
|
3522
|
+
const validateValue = (numericValue) => {
|
|
3523
|
+
const outOfBounds = (min !== undefined && numericValue < min) || (max !== undefined && numericValue > max);
|
|
3524
|
+
const failsValidator = props.validator && !props.validator(numericValue);
|
|
3525
|
+
const failsIntCheck = props.forceInt ? !Number.isInteger(numericValue) : false;
|
|
3526
|
+
const invalid = !!outOfBounds || !!failsValidator || isNaN(numericValue) || !!failsIntCheck;
|
|
3527
|
+
return !invalid;
|
|
3528
|
+
};
|
|
3529
|
+
const tryCommitValue = (currVal) => {
|
|
3530
|
+
// Only commit if valid and different from last committed value
|
|
3531
|
+
if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
|
|
3532
|
+
lastCommittedValue.current = currVal;
|
|
3533
|
+
props.onChange(currVal);
|
|
3535
3534
|
}
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
*/
|
|
3543
|
-
get feedNames() {
|
|
3544
|
-
return this._feeds.map((feed) => feed.name);
|
|
3545
|
-
}
|
|
3546
|
-
/**
|
|
3547
|
-
* Queries the extension manager for extensions.
|
|
3548
|
-
* @param filter The filter to apply to the query.
|
|
3549
|
-
* @param feeds The feeds to include in the query.
|
|
3550
|
-
* @param installedOnly Whether to only include installed extensions.
|
|
3551
|
-
* @returns A promise that resolves to the extension query.
|
|
3552
|
-
*/
|
|
3553
|
-
async queryExtensionsAsync(filter = "", feeds = this.feedNames, installedOnly = false) {
|
|
3554
|
-
if (installedOnly) {
|
|
3555
|
-
const installedExtensions = Array.from(this._installedExtensions.values()).filter((installedExtension) => feeds.includes(installedExtension.feed.name));
|
|
3556
|
-
return {
|
|
3557
|
-
totalCount: installedExtensions.length,
|
|
3558
|
-
getExtensionsAsync: async (index, count) => {
|
|
3559
|
-
return installedExtensions.slice(index, index + count).map((installedExtension) => this._createExtension(installedExtension.metadata, installedExtension.feed));
|
|
3560
|
-
},
|
|
3561
|
-
};
|
|
3535
|
+
};
|
|
3536
|
+
const handleChange = (event, data) => {
|
|
3537
|
+
event.stopPropagation(); // Prevent event propagation
|
|
3538
|
+
if (data.value != null && !Number.isNaN(data.value)) {
|
|
3539
|
+
setValue(data.value);
|
|
3540
|
+
tryCommitValue(data.value);
|
|
3562
3541
|
}
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
for (const query of queries) {
|
|
3571
|
-
if (remaining <= 0) {
|
|
3572
|
-
break;
|
|
3573
|
-
}
|
|
3574
|
-
if (index >= query.totalCount) {
|
|
3575
|
-
index -= query.totalCount;
|
|
3576
|
-
continue;
|
|
3577
|
-
}
|
|
3578
|
-
// This is intentionally sequential as we are querying for results until the count of results is met.
|
|
3579
|
-
// eslint-disable-next-line no-await-in-loop
|
|
3580
|
-
const metadataSlice = await query.getExtensionMetadataAsync(index, remaining);
|
|
3581
|
-
extensions.push(...metadataSlice.map((metadata) => this._createExtension(metadata, query.feed)));
|
|
3582
|
-
remaining -= metadataSlice.length;
|
|
3583
|
-
index = 0;
|
|
3584
|
-
}
|
|
3585
|
-
return extensions;
|
|
3586
|
-
},
|
|
3587
|
-
};
|
|
3588
|
-
}
|
|
3589
|
-
/**
|
|
3590
|
-
* Disposes the extension manager.
|
|
3591
|
-
*/
|
|
3592
|
-
dispose() {
|
|
3593
|
-
for (const installedExtension of this._installedExtensions.values()) {
|
|
3594
|
-
// eslint-disable-next-line github/no-then
|
|
3595
|
-
this._disableAsync(installedExtension.metadata, false).catch((error) => {
|
|
3596
|
-
Logger.Warn(`Failed to disable extension ${installedExtension.metadata.name}: ${error}`);
|
|
3597
|
-
});
|
|
3542
|
+
};
|
|
3543
|
+
const handleKeyUp = (event) => {
|
|
3544
|
+
event.stopPropagation(); // Prevent event propagation
|
|
3545
|
+
if (event.key !== "Enter") {
|
|
3546
|
+
const currVal = parseFloat(event.target.value); // Cannot use currentTarget.value as it won't have the most recently typed value
|
|
3547
|
+
setValue(currVal);
|
|
3548
|
+
tryCommitValue(currVal);
|
|
3598
3549
|
}
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3550
|
+
};
|
|
3551
|
+
const id = useId("spin-button");
|
|
3552
|
+
const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
|
|
3553
|
+
return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(SpinButton$1, { ...props, input: { className: classes.inputSlot }, step: step, id: id, size: size, precision: precision, displayValue: `${value.toFixed(precision)}${props.unit ? " " + props.unit : ""}`, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
|
|
3554
|
+
};
|
|
3555
|
+
|
|
3556
|
+
const useSyncedSliderStyles = makeStyles({
|
|
3557
|
+
container: { display: "flex" },
|
|
3558
|
+
syncedSlider: {
|
|
3559
|
+
flex: "1 1 0",
|
|
3560
|
+
flexDirection: "row",
|
|
3561
|
+
display: "flex",
|
|
3562
|
+
alignItems: "center",
|
|
3563
|
+
},
|
|
3564
|
+
slider: {
|
|
3565
|
+
minWidth: CustomTokens.sliderMinWidth, // Minimum width for slider to remain usable
|
|
3566
|
+
maxWidth: CustomTokens.sliderMaxWidth,
|
|
3567
|
+
},
|
|
3568
|
+
});
|
|
3569
|
+
/**
|
|
3570
|
+
* Component which synchronizes a slider and an input field, allowing the user to change the value using either control
|
|
3571
|
+
* @param props
|
|
3572
|
+
* @returns SyncedSlider component
|
|
3573
|
+
*/
|
|
3574
|
+
const SyncedSliderInput = (props) => {
|
|
3575
|
+
SyncedSliderInput.displayName = "SyncedSliderInput";
|
|
3576
|
+
const { infoLabel, ...passthroughProps } = props;
|
|
3577
|
+
const classes = useSyncedSliderStyles();
|
|
3578
|
+
const { size } = useContext(ToolContext);
|
|
3579
|
+
const [value, setValue] = useState(props.value);
|
|
3580
|
+
const pendingValueRef = useRef(undefined);
|
|
3581
|
+
const isDraggingRef = useRef(false);
|
|
3582
|
+
// NOTE: The Fluent slider will add tick marks if the step prop is anything other than undefined.
|
|
3583
|
+
// To avoid this, we scale the min/max based on the step so we can always make step undefined.
|
|
3584
|
+
// The actual step size in the Fluent slider is 1 when it is ste to undefined.
|
|
3585
|
+
const min = props.min ?? 0;
|
|
3586
|
+
const max = props.max ?? 100;
|
|
3587
|
+
const step = props.step ?? 1;
|
|
3588
|
+
useEffect(() => {
|
|
3589
|
+
!isDraggingRef.current && setValue(props.value ?? ""); // Update local state when props.value changes as long as user is not actively dragging
|
|
3590
|
+
}, [props.value]);
|
|
3591
|
+
const handleSliderChange = (_, data) => {
|
|
3592
|
+
const newValue = data.value * step;
|
|
3593
|
+
setValue(newValue);
|
|
3594
|
+
if (props.notifyOnlyOnRelease) {
|
|
3595
|
+
// Store the value but don't notify parent yet
|
|
3596
|
+
pendingValueRef.current = newValue;
|
|
3624
3597
|
}
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3598
|
+
else {
|
|
3599
|
+
// Notify parent as slider changes
|
|
3600
|
+
props.onChange(newValue);
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
const handleSliderPointerDown = () => {
|
|
3604
|
+
isDraggingRef.current = true;
|
|
3605
|
+
};
|
|
3606
|
+
const handleSliderPointerUp = () => {
|
|
3607
|
+
if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
|
|
3608
|
+
props.onChange(pendingValueRef.current);
|
|
3609
|
+
pendingValueRef.current = undefined;
|
|
3610
|
+
}
|
|
3611
|
+
isDraggingRef.current = false;
|
|
3612
|
+
};
|
|
3613
|
+
const handleInputChange = (value) => {
|
|
3614
|
+
setValue(value);
|
|
3615
|
+
props.onChange(value); // Input always updates immediately
|
|
3616
|
+
};
|
|
3617
|
+
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 })] })] }));
|
|
3618
|
+
};
|
|
3619
|
+
|
|
3620
|
+
/**
|
|
3621
|
+
* Renders a simple wrapper around the SyncedSliderInput
|
|
3622
|
+
* @param props
|
|
3623
|
+
* @returns
|
|
3624
|
+
*/
|
|
3625
|
+
const SyncedSliderPropertyLine = forwardRef((props, ref) => {
|
|
3626
|
+
SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
|
|
3627
|
+
const { label, description, ...sliderProps } = props;
|
|
3628
|
+
return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps }) }));
|
|
3629
|
+
});
|
|
3630
|
+
|
|
3631
|
+
const TextInput = (props) => {
|
|
3632
|
+
TextInput.displayName = "TextInput";
|
|
3633
|
+
const classes = useInputStyles$1();
|
|
3634
|
+
const [value, setValue] = useState(props.value);
|
|
3635
|
+
const lastCommittedValue = useRef(props.value);
|
|
3636
|
+
const { size } = useContext(ToolContext);
|
|
3637
|
+
useEffect(() => {
|
|
3638
|
+
if (props.value !== lastCommittedValue.current) {
|
|
3639
|
+
setValue(props.value); // Update local state when props.value changes
|
|
3640
|
+
lastCommittedValue.current = props.value;
|
|
3641
|
+
}
|
|
3642
|
+
}, [props.value]);
|
|
3643
|
+
const validateValue = (val) => {
|
|
3644
|
+
const failsValidator = props.validator && !props.validator(val);
|
|
3645
|
+
return !failsValidator;
|
|
3646
|
+
};
|
|
3647
|
+
const tryCommitValue = (currVal) => {
|
|
3648
|
+
// Only commit if valid and different from last committed value
|
|
3649
|
+
if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
|
|
3650
|
+
lastCommittedValue.current = currVal;
|
|
3651
|
+
props.onChange(currVal);
|
|
3652
|
+
}
|
|
3653
|
+
};
|
|
3654
|
+
const handleChange = (event, data) => {
|
|
3655
|
+
event.stopPropagation();
|
|
3656
|
+
setValue(data.value);
|
|
3657
|
+
tryCommitValue(data.value);
|
|
3658
|
+
};
|
|
3659
|
+
const handleKeyUp = (event) => {
|
|
3660
|
+
event.stopPropagation();
|
|
3661
|
+
setValue(event.currentTarget.value);
|
|
3662
|
+
tryCommitValue(event.currentTarget.value);
|
|
3663
|
+
};
|
|
3664
|
+
const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
|
|
3665
|
+
const id = useId("input-button");
|
|
3666
|
+
return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Input, { ...props, input: { className: classes.inputSlot }, id: id, size: size, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
|
|
3667
|
+
};
|
|
3668
|
+
|
|
3669
|
+
const useDropdownStyles = makeStyles({
|
|
3670
|
+
dropdown: {
|
|
3671
|
+
minWidth: 0,
|
|
3672
|
+
width: "100%",
|
|
3673
|
+
},
|
|
3674
|
+
container: {
|
|
3675
|
+
display: "flex",
|
|
3676
|
+
flexDirection: "column",
|
|
3677
|
+
justifyContent: "center", // align items vertically
|
|
3678
|
+
},
|
|
3679
|
+
dropdownText: { textAlign: "end", textOverflow: "ellipsis", whiteSpace: "nowrap", overflowX: "hidden" },
|
|
3680
|
+
});
|
|
3681
|
+
/**
|
|
3682
|
+
* Renders a fluent UI dropdown component for the options passed in, and an additional 'Not Defined' option if null is set to true
|
|
3683
|
+
* This component can handle both null and undefined values
|
|
3684
|
+
* @param props
|
|
3685
|
+
* @returns dropdown component
|
|
3686
|
+
*/
|
|
3687
|
+
const Dropdown = (props) => {
|
|
3688
|
+
Dropdown.displayName = "Dropdown";
|
|
3689
|
+
const classes = useDropdownStyles();
|
|
3690
|
+
const { options, value } = props;
|
|
3691
|
+
const [defaultVal, setDefaultVal] = useState(props.value);
|
|
3692
|
+
const { size } = useContext(ToolContext);
|
|
3693
|
+
useEffect(() => {
|
|
3694
|
+
setDefaultVal(value);
|
|
3695
|
+
}, [props.value]);
|
|
3696
|
+
const id = useId("dropdown");
|
|
3697
|
+
const mergedClassName = mergeClasses(classes.container, props.className);
|
|
3698
|
+
const optionLabel = options.find((o) => o.value === defaultVal)?.label;
|
|
3699
|
+
return (jsxs("div", { className: mergedClassName, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Dropdown$1, { id: id, disabled: props.disabled, size: size, className: classes.dropdown, button: jsx("span", { className: classes.dropdownText, children: optionLabel }), onOptionSelect: (evt, data) => {
|
|
3700
|
+
const value = typeof props.value === "number" ? Number(data.optionValue) : data.optionValue;
|
|
3701
|
+
if (value !== undefined) {
|
|
3702
|
+
setDefaultVal(value);
|
|
3703
|
+
props.onChange(value);
|
|
3704
|
+
}
|
|
3705
|
+
}, selectedOptions: [defaultVal.toString()], value: optionLabel, children: options.map((option) => (jsx(Option, { value: option.value.toString(), disabled: false, children: option.label }, option.label))) })] }));
|
|
3706
|
+
};
|
|
3707
|
+
const NumberDropdown = Dropdown;
|
|
3708
|
+
const StringDropdown = Dropdown;
|
|
3709
|
+
|
|
3710
|
+
const useColorPickerStyles = makeStyles({
|
|
3711
|
+
container: {
|
|
3712
|
+
width: "350px",
|
|
3713
|
+
display: "flex", // becomes a flexbox
|
|
3714
|
+
flexDirection: "column", // with children in a column
|
|
3715
|
+
alignItems: "center", // centers children horizontally
|
|
3716
|
+
justifyContent: "center", // centers children vertically (if height is set)
|
|
3717
|
+
gap: tokens.spacingVerticalM,
|
|
3718
|
+
overflow: "visible",
|
|
3719
|
+
},
|
|
3720
|
+
row: {
|
|
3721
|
+
flex: 1, // is a row in the container's flex column
|
|
3722
|
+
display: "flex", // becomes its own flexbox
|
|
3723
|
+
flexDirection: "row", // with children in a row
|
|
3724
|
+
gap: tokens.spacingHorizontalXL,
|
|
3725
|
+
alignItems: "center", // align items vertically
|
|
3726
|
+
width: "100%",
|
|
3727
|
+
},
|
|
3728
|
+
colorPicker: {
|
|
3729
|
+
flex: 1,
|
|
3730
|
+
width: "350px",
|
|
3731
|
+
height: "350px",
|
|
3732
|
+
},
|
|
3733
|
+
previewColor: {
|
|
3734
|
+
width: "60px",
|
|
3735
|
+
height: "60px",
|
|
3736
|
+
borderRadius: tokens.borderRadiusMedium, // 4px?
|
|
3737
|
+
border: `${tokens.spacingVerticalXXS} solid ${tokens.colorNeutralShadowKeyLighter}`,
|
|
3738
|
+
"@media (forced-colors: active)": {
|
|
3739
|
+
forcedColorAdjust: "none", // ensures elmement maintains color in high constrast mode
|
|
3740
|
+
},
|
|
3741
|
+
},
|
|
3742
|
+
inputRow: {
|
|
3743
|
+
display: "flex",
|
|
3744
|
+
flexDirection: "row",
|
|
3745
|
+
flex: 1, // grow and fill available space
|
|
3746
|
+
justifyContent: "center",
|
|
3747
|
+
gap: "10px",
|
|
3748
|
+
width: "100%",
|
|
3749
|
+
},
|
|
3750
|
+
inputField: {
|
|
3751
|
+
flex: 1, // grow and fill available space
|
|
3752
|
+
width: "auto",
|
|
3753
|
+
minWidth: 0,
|
|
3754
|
+
gap: tokens.spacingVerticalSNudge, // 6px
|
|
3755
|
+
},
|
|
3756
|
+
});
|
|
3757
|
+
const ColorPickerPopup = (props) => {
|
|
3758
|
+
ColorPickerPopup.displayName = "ColorPickerPopup";
|
|
3759
|
+
const classes = useColorPickerStyles();
|
|
3760
|
+
const [color, setColor] = useState(props.value);
|
|
3761
|
+
const [popoverOpen, setPopoverOpen] = useState(false);
|
|
3762
|
+
const [isLinear, setIsLinear] = useState(props.isLinearMode ?? false);
|
|
3763
|
+
const [isFloat, setFloat] = useState(false);
|
|
3764
|
+
const { size } = useContext(ToolContext);
|
|
3765
|
+
useEffect(() => {
|
|
3766
|
+
setColor(props.value); // Ensures the trigger color updates when props.value changes
|
|
3767
|
+
}, [props.value]);
|
|
3768
|
+
const handleColorPickerChange = (_, data) => {
|
|
3769
|
+
let color = Color3.FromHSV(data.color.h, data.color.s, data.color.v);
|
|
3770
|
+
if (props.value instanceof Color4) {
|
|
3771
|
+
color = Color4.FromColor3(color, data.color.a ?? 1);
|
|
3772
|
+
}
|
|
3773
|
+
handleChange(color);
|
|
3774
|
+
};
|
|
3775
|
+
const handleChange = (newColor) => {
|
|
3776
|
+
setColor(newColor);
|
|
3777
|
+
props.onChange(newColor); // Ensures the parent is notified when color changes from within colorPicker
|
|
3778
|
+
};
|
|
3779
|
+
return (jsxs(Popover, { positioning: {
|
|
3780
|
+
align: "start",
|
|
3781
|
+
overflowBoundary: document.body,
|
|
3782
|
+
autoSize: true,
|
|
3783
|
+
}, open: popoverOpen, trapFocus: true, onOpenChange: (_, data) => setPopoverOpen(data.open), children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: jsx(ColorSwatch, { borderColor: tokens.colorNeutralShadowKeyDarker, size: size === "small" ? "extra-small" : "small", shape: "rounded", color: color.toHexString(), value: color.toHexString().slice(1) }) }), jsx(PopoverSurface, { children: jsxs("div", { className: classes.container, children: [jsxs(ColorPicker, { className: classes.colorPicker, color: rgbaToHsv(color), onColorChange: handleColorPickerChange, children: [jsx(ColorArea, { inputX: { "aria-label": "Saturation" }, inputY: { "aria-label": "Brightness" } }), jsx(ColorSlider, { "aria-label": "Hue" }), color instanceof Color4 && jsx(AlphaSlider, { "aria-label": "Alpha" })] }), jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
|
|
3784
|
+
label: "Color Space",
|
|
3785
|
+
info: jsx(Body1, { children: "Today this is not mutable as the color space is determined by the entity. Soon we will allow swapping" }),
|
|
3786
|
+
}, options: [
|
|
3787
|
+
{ label: "Gamma", value: 0 },
|
|
3788
|
+
{ label: "Linear", value: 1 },
|
|
3789
|
+
], disabled: true, value: isLinear ? 1 : 0, onChange: (val) => setIsLinear(val === 1) }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
|
|
3790
|
+
label: "Data Type",
|
|
3791
|
+
info: jsx(Body1, { children: "We will introduce this functionality soon!" }),
|
|
3792
|
+
}, options: [
|
|
3793
|
+
{ label: "Int", value: 0 },
|
|
3794
|
+
{ label: "Float", value: 1 },
|
|
3795
|
+
], disabled: true, value: isFloat ? 1 : 0, onChange: (val) => setFloat(val === 1) })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputRgbField, { title: "Red", value: color, rgbKey: "r", onChange: handleChange }), jsx(InputRgbField, { title: "Green", value: color, rgbKey: "g", onChange: handleChange }), jsx(InputRgbField, { title: "Blue", value: color, rgbKey: "b", onChange: handleChange }), jsx(InputAlphaField, { color: color, onChange: handleChange })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputHsvField, { title: "Hue", value: color, hsvKey: "h", max: 360, onChange: handleChange }), jsx(InputHsvField, { title: "Saturation", value: color, hsvKey: "s", max: 100, scale: 100, onChange: handleChange }), jsx(InputHsvField, { title: "Value", value: color, hsvKey: "v", max: 100, scale: 100, onChange: handleChange })] }), jsx("div", { className: classes.inputRow, children: jsx(InputHexField, { title: "Hexadecimal", linearHex: isLinear, isLinearMode: isLinear, value: color, onChange: handleChange }) })] }) })] }));
|
|
3796
|
+
};
|
|
3797
|
+
/**
|
|
3798
|
+
* Component which displays the passed in color's HEX value, either in linearSpace (if linearHex is true) or in gamma space
|
|
3799
|
+
* When the hex color is changed by user, component calculates the new Color3/4 value and calls onChange
|
|
3800
|
+
*
|
|
3801
|
+
* Component uses the isLinearMode boolean to display an informative label regarding linear / gamma space
|
|
3802
|
+
* @param props - The properties for the InputHexField component.
|
|
3803
|
+
* @returns
|
|
3804
|
+
*/
|
|
3805
|
+
const InputHexField = (props) => {
|
|
3806
|
+
const classes = useColorPickerStyles();
|
|
3807
|
+
const { title, value, onChange, linearHex, isLinearMode } = props;
|
|
3808
|
+
return (jsx(TextInput, { disabled: linearHex ? !isLinearMode : false, className: classes.inputField, value: linearHex ? value.toLinearSpace().toHexString() : value.toHexString(), validator: ValidateColorHex, onChange: (val) => (linearHex ? onChange(Color3.FromHexString(val).toGammaSpace()) : onChange(Color3.FromHexString(val))), infoLabel: title
|
|
3809
|
+
? {
|
|
3810
|
+
label: title,
|
|
3811
|
+
// If not representing a linearHex, no info is needed.
|
|
3812
|
+
info: !props.linearHex ? undefined : !isLinearMode ? ( // If representing a linear hex but we are in gammaMode, simple message explaining why linearHex is disabled
|
|
3813
|
+
jsx(Fragment, { children: " This color picker is attached to an entity whose color is stored in gamma space, so we are showing linear hex in disabled view " })) : (
|
|
3814
|
+
// If representing a linear hex and we are in linearMode, give information about how to use these hex values
|
|
3815
|
+
jsxs(Fragment, { children: ["This color picker is attached to an entity whose color is stored in linear space (ex: PBR Material), and Babylon converts the color to gamma space before rendering on screen because the human eye is best at processing colors in gamma space. We thus also want to display the color picker in gamma space so that the color chosen here will match the color seen in your entity.", jsx("br", {}), "If you want to copy/paste the HEX into your code, you can either use", jsx(Body1Strong, { children: "Color3.FromHexString(LINEAR_HEX)" }), jsx("br", {}), "or", jsx("br", {}), jsx(Body1Strong, { children: "Color3.FromHexString(GAMMA_HEX).toLinearSpace()" }), jsx("br", {}), jsx("br", {}), jsx(Link, { url: "https://doc.babylonjs.com/preparingArtForBabylon/controllingColorSpace/", value: "Read more in our docs!" })] })),
|
|
3816
|
+
}
|
|
3817
|
+
: undefined }));
|
|
3818
|
+
};
|
|
3819
|
+
const InputRgbField = (props) => {
|
|
3820
|
+
const { value, onChange, title, rgbKey } = props;
|
|
3821
|
+
const classes = useColorPickerStyles();
|
|
3822
|
+
const handleChange = useCallback((val) => {
|
|
3823
|
+
const newColor = value.clone();
|
|
3824
|
+
newColor[rgbKey] = val / 255.0; // Convert to 0-1 range
|
|
3825
|
+
onChange(newColor);
|
|
3826
|
+
}, [value, onChange, rgbKey]);
|
|
3827
|
+
return (jsx(SpinButton, { title: title, infoLabel: title ? { label: title } : undefined, className: classes.inputField, min: 0, max: 255, value: Math.round(value[rgbKey] * 255), forceInt: true, onChange: handleChange }));
|
|
3828
|
+
};
|
|
3829
|
+
function rgbaToHsv(color) {
|
|
3830
|
+
const c = new Color3(color.r, color.g, color.b);
|
|
3831
|
+
const hsv = c.toHSV();
|
|
3832
|
+
return { h: hsv.r, s: hsv.g, v: hsv.b, a: color.a };
|
|
3833
|
+
}
|
|
3834
|
+
/**
|
|
3835
|
+
* In the HSV (Hue, Saturation, Value) color model, Hue (H) ranges from 0 to 360 degrees, representing the color's position on the color wheel.
|
|
3836
|
+
* Saturation (S) ranges from 0 to 100%, indicating the intensity or purity of the color, with 0 being shades of gray and 100 being a fully saturated color.
|
|
3837
|
+
* Value (V) ranges from 0 to 100%, representing the brightness of the color, with 0 being black and 100 being the brightest.
|
|
3838
|
+
* @param props - The properties for the InputHsvField component.
|
|
3839
|
+
*/
|
|
3840
|
+
const InputHsvField = (props) => {
|
|
3841
|
+
const { value, title, hsvKey, max, onChange, scale = 1 } = props;
|
|
3842
|
+
const classes = useColorPickerStyles();
|
|
3843
|
+
const handleChange = useCallback((val) => {
|
|
3844
|
+
// Convert current color to HSV, update the new hsv value, then call onChange prop
|
|
3845
|
+
const hsv = rgbaToHsv(value);
|
|
3846
|
+
hsv[hsvKey] = val / scale;
|
|
3847
|
+
let newColor = Color3.FromHSV(hsv.h, hsv.s, hsv.v);
|
|
3848
|
+
if (value instanceof Color4) {
|
|
3849
|
+
newColor = Color4.FromColor3(newColor, value.a ?? 1);
|
|
3850
|
+
}
|
|
3851
|
+
props.onChange(newColor);
|
|
3852
|
+
}, [value, onChange, hsvKey, scale]);
|
|
3853
|
+
return (jsx(SpinButton, { infoLabel: title ? { label: title } : undefined, title: title, className: classes.inputField, min: 0, max: max, value: Math.round(rgbaToHsv(value)[hsvKey] * scale), forceInt: true, onChange: handleChange }));
|
|
3854
|
+
};
|
|
3855
|
+
/**
|
|
3856
|
+
* Displays the alpha value of a color, either in the disabled state (if color is Color3) or as a spin button (if color is Color4).
|
|
3857
|
+
* @param props
|
|
3858
|
+
* @returns
|
|
3859
|
+
*/
|
|
3860
|
+
const InputAlphaField = (props) => {
|
|
3861
|
+
const classes = useColorPickerStyles();
|
|
3862
|
+
const { color, onChange } = props;
|
|
3863
|
+
const handleChange = useCallback((value) => {
|
|
3864
|
+
if (Number.isNaN(value) || value < 0 || value > 1) {
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
if (color instanceof Color4) {
|
|
3868
|
+
const newColor = color.clone();
|
|
3869
|
+
newColor.a = value;
|
|
3870
|
+
return newColor;
|
|
3871
|
+
}
|
|
3872
|
+
else {
|
|
3873
|
+
return Color4.FromColor3(color, value);
|
|
3874
|
+
}
|
|
3875
|
+
}, [onChange]);
|
|
3876
|
+
return (jsx(SpinButton, { disabled: color instanceof Color3, min: 0, max: 1, className: classes.inputField, value: color instanceof Color3 ? 1 : color.a, step: 0.01, onChange: handleChange, infoLabel: {
|
|
3877
|
+
label: "Alpha",
|
|
3878
|
+
info: color instanceof Color3 ? (jsx(Fragment, { children: "Because this color picker is representing a Color3, we do not permit modifying alpha from the color picker. You can however modify the entity's alpha property directly, either in code via entity.alpha OR via inspector's transparency section." })) : undefined,
|
|
3879
|
+
} }));
|
|
3880
|
+
};
|
|
3881
|
+
|
|
3882
|
+
/**
|
|
3883
|
+
* Reusable component which renders a color property line containing a label, colorPicker popout, and expandable RGBA values
|
|
3884
|
+
* The expandable RGBA values are synced sliders that allow the user to modify the color's RGBA values directly
|
|
3885
|
+
* @param props - PropertyLine props, replacing children with a color object so that we can properly display the color
|
|
3886
|
+
* @returns Component wrapping a colorPicker component with a property line
|
|
3887
|
+
*/
|
|
3888
|
+
const ColorPropertyLine = forwardRef((props, ref) => {
|
|
3889
|
+
ColorPropertyLine.displayName = "ColorPropertyLine";
|
|
3890
|
+
const [color, setColor] = useState(props.value);
|
|
3891
|
+
useEffect(() => {
|
|
3892
|
+
setColor(props.value);
|
|
3893
|
+
}, [props.value]);
|
|
3894
|
+
const onSliderChange = (value, key) => {
|
|
3895
|
+
let newColor;
|
|
3896
|
+
if (key === "a") {
|
|
3897
|
+
newColor = Color4.FromColor3(color, value);
|
|
3898
|
+
}
|
|
3899
|
+
else {
|
|
3900
|
+
newColor = color.clone();
|
|
3901
|
+
newColor[key] = value / 255;
|
|
3902
|
+
}
|
|
3903
|
+
setColor(newColor); // Create a new object to trigger re-render
|
|
3904
|
+
props.onChange(newColor);
|
|
3905
|
+
};
|
|
3906
|
+
const onColorPickerChange = (newColor) => {
|
|
3907
|
+
setColor(newColor);
|
|
3908
|
+
props.onChange(newColor);
|
|
3909
|
+
};
|
|
3910
|
+
return (jsx(PropertyLine, { ref: ref, ...props, expandedContent: jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "R", value: color.r * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "r") }), jsx(SyncedSliderPropertyLine, { label: "G", value: color.g * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "g") }), jsx(SyncedSliderPropertyLine, { label: "B", value: color.b * 255, min: 0, max: 255, onChange: (value) => onSliderChange(value, "b") }), color instanceof Color4 && jsx(SyncedSliderPropertyLine, { label: "A", value: color.a, min: 0, max: 1, step: 0.01, onChange: (value) => onSliderChange(value, "a") })] }), children: jsx(ColorPickerPopup, { ...props, onChange: onColorPickerChange, value: color }) }));
|
|
3911
|
+
});
|
|
3912
|
+
const Color3PropertyLine = ColorPropertyLine;
|
|
3913
|
+
const Color4PropertyLine = ColorPropertyLine;
|
|
3914
|
+
|
|
3915
|
+
const useStyles$9 = makeStyles({
|
|
3916
|
+
dropdown: {
|
|
3917
|
+
...UniformWidthStyling,
|
|
3918
|
+
},
|
|
3919
|
+
});
|
|
3920
|
+
/**
|
|
3921
|
+
* Wraps a dropdown in a property line
|
|
3922
|
+
* @param props - PropertyLineProps and DropdownProps
|
|
3923
|
+
* @returns property-line wrapped dropdown
|
|
3924
|
+
*/
|
|
3925
|
+
const DropdownPropertyLine = forwardRef((props, ref) => {
|
|
3926
|
+
DropdownPropertyLine.displayName = "DropdownPropertyLine";
|
|
3927
|
+
const classes = useStyles$9();
|
|
3928
|
+
return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
|
|
3929
|
+
});
|
|
3930
|
+
/**
|
|
3931
|
+
* Dropdown component for number values.
|
|
3932
|
+
*/
|
|
3933
|
+
const NumberDropdownPropertyLine = DropdownPropertyLine;
|
|
3934
|
+
/**
|
|
3935
|
+
* Dropdown component for string values
|
|
3936
|
+
*/
|
|
3937
|
+
const StringDropdownPropertyLine = DropdownPropertyLine;
|
|
3938
|
+
|
|
3939
|
+
/**
|
|
3940
|
+
* Wraps a text input in a property line
|
|
3941
|
+
* @param props - PropertyLineProps and InputProps
|
|
3942
|
+
* @returns property-line wrapped input component
|
|
3943
|
+
*/
|
|
3944
|
+
const TextInputPropertyLine = (props) => {
|
|
3945
|
+
TextInputPropertyLine.displayName = "TextInputPropertyLine";
|
|
3946
|
+
return (jsx(PropertyLine, { ...props, children: jsx(TextInput, { ...props }) }));
|
|
3947
|
+
};
|
|
3948
|
+
/**
|
|
3949
|
+
* Wraps a number input in a property line
|
|
3950
|
+
* To force integer values, use forceInt param (this is distinct from the 'step' param, which will still allow submitting an integer value. forceInt will not)
|
|
3951
|
+
* @param props - PropertyLineProps and InputProps
|
|
3952
|
+
* @returns property-line wrapped input component
|
|
3953
|
+
*/
|
|
3954
|
+
const NumberInputPropertyLine = (props) => {
|
|
3955
|
+
NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
|
|
3956
|
+
return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props }) }));
|
|
3957
|
+
};
|
|
3958
|
+
|
|
3959
|
+
const HasZ = (vector) => !(vector instanceof Vector2);
|
|
3960
|
+
const HasW = (vector) => vector instanceof Vector4 || vector instanceof Quaternion;
|
|
3961
|
+
/**
|
|
3962
|
+
* Reusable component which renders a vector property line containing a label, vector value, and expandable XYZW values
|
|
3963
|
+
* The expanded section contains a slider/input box for each component of the vector (x, y, z, w)
|
|
3964
|
+
* @param props
|
|
3965
|
+
* @returns
|
|
3966
|
+
*/
|
|
3967
|
+
const TensorPropertyLine = (props) => {
|
|
3968
|
+
TensorPropertyLine.displayName = "TensorPropertyLine";
|
|
3969
|
+
const converted = (val) => (props.valueConverter ? props.valueConverter.from(val) : val);
|
|
3970
|
+
const formatted = (val) => converted(val).toFixed(props.step !== undefined ? Math.max(0, CalculatePrecision(props.step)) : 2);
|
|
3971
|
+
const [vector, setVector] = useState(props.value);
|
|
3972
|
+
const { min, max } = props;
|
|
3973
|
+
const onChange = (val, key) => {
|
|
3974
|
+
const value = props.valueConverter ? props.valueConverter.to(val) : val;
|
|
3975
|
+
const newVector = vector.clone();
|
|
3976
|
+
newVector[key] = value; // The syncedSlider for 'w' is only rendered when vector is a Vector4, so this is safe
|
|
3977
|
+
setVector(newVector);
|
|
3978
|
+
props.onChange(newVector);
|
|
3979
|
+
};
|
|
3980
|
+
useEffect(() => {
|
|
3981
|
+
setVector(props.value);
|
|
3982
|
+
}, [props.value, props.expandedContent]);
|
|
3983
|
+
return (jsx(PropertyLine, { ...props, onCopy: () => `new ${props.value.getClassName()}(${vector.x},${vector.y}${HasZ(vector) ? `,${vector.z}` : ""}${HasW(vector) ? `,${vector.w}` : ""})`, expandedContent: jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: props.unit, step: props.step }), jsx(SyncedSliderPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: props.unit, step: props.step }), HasZ(vector) && (jsx(SyncedSliderPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: props.unit, step: props.step })), HasW(vector) && (jsx(SyncedSliderPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: props.unit, step: props.step }))] }), children: jsx(Body1, { children: `[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : ""}${HasW(props.value) ? `, ${formatted(props.value.w)}` : ""}]` }) }));
|
|
3984
|
+
};
|
|
3985
|
+
const ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };
|
|
3986
|
+
const RotationVectorPropertyLine = (props) => {
|
|
3987
|
+
RotationVectorPropertyLine.displayName = "RotationVectorPropertyLine";
|
|
3988
|
+
const min = props.useDegrees ? 0 : undefined;
|
|
3989
|
+
const max = props.useDegrees ? 360 : undefined;
|
|
3990
|
+
return (jsx(Vector3PropertyLine, { ...props, unit: props.useDegrees ? "deg" : "rad", valueConverter: props.useDegrees ? ToDegreesConverter : undefined, min: min, max: max, step: 0.001 }));
|
|
3991
|
+
};
|
|
3992
|
+
const QuaternionPropertyLineInternal = TensorPropertyLine;
|
|
3993
|
+
const QuaternionPropertyLine = (props) => {
|
|
3994
|
+
QuaternionPropertyLine.displayName = "QuaternionPropertyLine";
|
|
3995
|
+
const min = props.useDegrees ? 0 : undefined;
|
|
3996
|
+
const max = props.useDegrees ? 360 : undefined;
|
|
3997
|
+
const [quat, setQuat] = useState(props.value);
|
|
3998
|
+
useEffect(() => {
|
|
3999
|
+
setQuat(props.value);
|
|
4000
|
+
}, [props.value]);
|
|
4001
|
+
// Extract only the properties that exist on QuaternionPropertyLineProps
|
|
4002
|
+
const { useDegrees, ...restProps } = props;
|
|
4003
|
+
const onQuatChange = (val) => {
|
|
4004
|
+
setQuat(val);
|
|
4005
|
+
props.onChange(val);
|
|
4006
|
+
};
|
|
4007
|
+
const onEulerChange = (val) => {
|
|
4008
|
+
const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);
|
|
4009
|
+
onQuatChange(quat);
|
|
4010
|
+
};
|
|
4011
|
+
return useDegrees ? (jsx(Vector3PropertyLine, { ...restProps, nullable: false, ignoreNullable: false, value: quat.toEulerAngles(), valueConverter: ToDegreesConverter, min: min, max: max, onChange: onEulerChange, unit: "deg" })) : (jsx(QuaternionPropertyLineInternal, { ...props, nullable: false, value: quat, min: min, max: max, onChange: onQuatChange }));
|
|
4012
|
+
};
|
|
4013
|
+
const Vector2PropertyLine = TensorPropertyLine;
|
|
4014
|
+
const Vector3PropertyLine = TensorPropertyLine;
|
|
4015
|
+
const Vector4PropertyLine = TensorPropertyLine;
|
|
4016
|
+
|
|
4017
|
+
function IsInspectableObject(entity) {
|
|
4018
|
+
return !!entity.inspectableCustomProperties;
|
|
4019
|
+
}
|
|
4020
|
+
const LegacyInspectableObjectPropertiesServiceDefinition = {
|
|
4021
|
+
friendlyName: "Additional Nodes",
|
|
4022
|
+
consumes: [PropertiesServiceIdentity],
|
|
4023
|
+
factory: (propertiesService) => {
|
|
4024
|
+
const propertiesSectionRegistration = propertiesService.addSection({
|
|
4025
|
+
identity: "Custom",
|
|
4026
|
+
order: Number.MAX_SAFE_INTEGER,
|
|
4027
|
+
});
|
|
4028
|
+
const propertiesContentRegistration = propertiesService.addSectionContent({
|
|
4029
|
+
key: "Additional Nodes Properties",
|
|
4030
|
+
predicate: (entity) => IsInspectableObject(entity),
|
|
4031
|
+
content: [
|
|
4032
|
+
{
|
|
4033
|
+
section: "Custom",
|
|
4034
|
+
component: ({ context }) => {
|
|
4035
|
+
return (jsx(Fragment, { children: (context.inspectableCustomProperties ?? []).map((prop) => {
|
|
4036
|
+
const commonProps = {
|
|
4037
|
+
target: context,
|
|
4038
|
+
propertyKey: prop.propertyName,
|
|
4039
|
+
label: prop.label,
|
|
4040
|
+
ignoreNullable: true,
|
|
4041
|
+
defaultValue: undefined,
|
|
4042
|
+
};
|
|
4043
|
+
switch (prop.type) {
|
|
4044
|
+
case 0 /* InspectableType.Checkbox */:
|
|
4045
|
+
return jsx(BoundProperty, { ...commonProps, component: SwitchPropertyLine }, prop.propertyName);
|
|
4046
|
+
case 1 /* InspectableType.Slider */:
|
|
4047
|
+
return (jsx(BoundProperty, { ...commonProps, min: prop.min, max: prop.max, step: prop.step, component: SyncedSliderPropertyLine }, prop.propertyName));
|
|
4048
|
+
case 2 /* InspectableType.Vector3 */:
|
|
4049
|
+
return jsx(BoundProperty, { ...commonProps, component: Vector3PropertyLine }, prop.propertyName);
|
|
4050
|
+
case 3 /* InspectableType.Quaternion */:
|
|
4051
|
+
return jsx(BoundProperty, { ...commonProps, component: QuaternionPropertyLine }, prop.propertyName);
|
|
4052
|
+
case 4 /* InspectableType.Color3 */:
|
|
4053
|
+
return jsx(BoundProperty, { ...commonProps, component: Color3PropertyLine }, prop.propertyName);
|
|
4054
|
+
case 5 /* InspectableType.String */:
|
|
4055
|
+
return jsx(BoundProperty, { ...commonProps, component: TextInputPropertyLine }, prop.propertyName);
|
|
4056
|
+
case 6 /* InspectableType.Button */:
|
|
4057
|
+
return jsx(ButtonLine, { label: prop.label, onClick: () => prop.callback?.() }, prop.propertyName);
|
|
4058
|
+
case 7 /* InspectableType.Options */:
|
|
4059
|
+
return jsx(BoundProperty, { ...commonProps, component: DropdownPropertyLine, options: prop.options ?? [] }, prop.propertyName);
|
|
4060
|
+
case 8 /* InspectableType.Tab */:
|
|
4061
|
+
return jsx(BoundProperty, { ...commonProps, component: TextPropertyLine }, prop.propertyName);
|
|
4062
|
+
case 9 /* InspectableType.FileButton */:
|
|
4063
|
+
return (jsx(FileUploadLine, { label: prop.label, accept: prop.accept ?? "", onClick: (files) => {
|
|
4064
|
+
if (files.length > 0 && prop.fileCallback) {
|
|
4065
|
+
prop.fileCallback(files[0]);
|
|
4066
|
+
}
|
|
4067
|
+
} }, prop.propertyName));
|
|
4068
|
+
case 10 /* InspectableType.Vector2 */:
|
|
4069
|
+
return jsx(BoundProperty, { ...commonProps, component: Vector2PropertyLine }, prop.propertyName);
|
|
4070
|
+
}
|
|
4071
|
+
}) }));
|
|
4072
|
+
},
|
|
4073
|
+
},
|
|
4074
|
+
],
|
|
4075
|
+
});
|
|
4076
|
+
return {
|
|
4077
|
+
dispose: () => {
|
|
4078
|
+
propertiesSectionRegistration.dispose();
|
|
4079
|
+
propertiesContentRegistration.dispose();
|
|
4080
|
+
},
|
|
4081
|
+
};
|
|
4082
|
+
},
|
|
4083
|
+
};
|
|
4084
|
+
|
|
4085
|
+
const ExtensionManagerContext = createContext(undefined);
|
|
4086
|
+
function useExtensionManager() {
|
|
4087
|
+
return useContext(ExtensionManagerContext)?.extensionManager;
|
|
4088
|
+
}
|
|
4089
|
+
|
|
4090
|
+
const InstalledExtensionsKey = "Babylon/Extensions/InstalledExtensions";
|
|
4091
|
+
const ExtensionInstalledKeyPrefix = "Babylon/Extensions/IsExtensionInstalled";
|
|
4092
|
+
function GetExtensionInstalledKey(name) {
|
|
4093
|
+
return `${ExtensionInstalledKeyPrefix}/${name}`;
|
|
4094
|
+
}
|
|
4095
|
+
function GetExtensionIdentity(feed, name) {
|
|
4096
|
+
return `${feed}|${name}`;
|
|
4097
|
+
}
|
|
4098
|
+
/**
|
|
4099
|
+
* Manages the installation, uninstallation, enabling, and disabling of extensions.
|
|
4100
|
+
*/
|
|
4101
|
+
class ExtensionManager {
|
|
4102
|
+
constructor(_serviceContainer, _feeds, _onInstallFailed) {
|
|
4103
|
+
this._serviceContainer = _serviceContainer;
|
|
4104
|
+
this._feeds = _feeds;
|
|
4105
|
+
this._onInstallFailed = _onInstallFailed;
|
|
4106
|
+
this._installedExtensions = new Map();
|
|
4107
|
+
this._stateChangedHandlers = new Map();
|
|
4108
|
+
}
|
|
4109
|
+
/**
|
|
4110
|
+
* Creates a new instance of the ExtensionManager.
|
|
4111
|
+
* This will automatically rehydrate previously installed and enabled extensions.
|
|
4112
|
+
* @param serviceContainer The service container to use.
|
|
4113
|
+
* @param feeds The extension feeds to include.
|
|
4114
|
+
* @param onInstallFailed A callback that is called when an extension installation fails.
|
|
4115
|
+
* @returns A promise that resolves to the new instance of the ExtensionManager.
|
|
4116
|
+
*/
|
|
4117
|
+
static async CreateAsync(serviceContainer, feeds, onInstallFailed) {
|
|
4118
|
+
const extensionManager = new ExtensionManager(serviceContainer, feeds, onInstallFailed);
|
|
4119
|
+
// Rehydrate installed extensions.
|
|
4120
|
+
const installedExtensionNames = JSON.parse(localStorage.getItem(InstalledExtensionsKey) ?? "[]");
|
|
4121
|
+
for (const installedExtensionName of installedExtensionNames) {
|
|
4122
|
+
const installedExtensionRaw = localStorage.getItem(GetExtensionInstalledKey(installedExtensionName));
|
|
4123
|
+
if (installedExtensionRaw) {
|
|
4124
|
+
const installedExtensionData = JSON.parse(installedExtensionRaw);
|
|
4125
|
+
const feed = feeds.find((feed) => feed.name === installedExtensionData.feed);
|
|
4126
|
+
if (feed) {
|
|
4127
|
+
const installedExtension = extensionManager._createInstalledExtension(installedExtensionData.metadata, feed);
|
|
4128
|
+
extensionManager._installedExtensions.set(installedExtension.metadata.name, installedExtension);
|
|
4129
|
+
}
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
// Load installed and enabled extensions.
|
|
4133
|
+
const enablePromises = [];
|
|
4134
|
+
for (const extension of extensionManager._installedExtensions.values()) {
|
|
4135
|
+
enablePromises.push((async () => {
|
|
4136
|
+
try {
|
|
4137
|
+
await extensionManager._enableAsync(extension.metadata, false, false);
|
|
4138
|
+
}
|
|
4139
|
+
catch {
|
|
4140
|
+
// If enabling the extension fails, uninstall it. The extension install fail callback will still be called,
|
|
4141
|
+
// so the owner of the ExtensionManager instance can decide what to do with the error.
|
|
4142
|
+
await extensionManager._uninstallAsync(extension.metadata, false);
|
|
4143
|
+
}
|
|
4144
|
+
})());
|
|
4145
|
+
}
|
|
4146
|
+
await Promise.all(enablePromises);
|
|
4147
|
+
return extensionManager;
|
|
4148
|
+
}
|
|
4149
|
+
/**
|
|
4150
|
+
* Gets the names of the feeds that are included in the extension manager.
|
|
4151
|
+
* @returns The names of the feeds.
|
|
4152
|
+
*/
|
|
4153
|
+
get feedNames() {
|
|
4154
|
+
return this._feeds.map((feed) => feed.name);
|
|
4155
|
+
}
|
|
4156
|
+
/**
|
|
4157
|
+
* Queries the extension manager for extensions.
|
|
4158
|
+
* @param filter The filter to apply to the query.
|
|
4159
|
+
* @param feeds The feeds to include in the query.
|
|
4160
|
+
* @param installedOnly Whether to only include installed extensions.
|
|
4161
|
+
* @returns A promise that resolves to the extension query.
|
|
4162
|
+
*/
|
|
4163
|
+
async queryExtensionsAsync(filter = "", feeds = this.feedNames, installedOnly = false) {
|
|
4164
|
+
if (installedOnly) {
|
|
4165
|
+
const installedExtensions = Array.from(this._installedExtensions.values()).filter((installedExtension) => feeds.includes(installedExtension.feed.name));
|
|
4166
|
+
return {
|
|
4167
|
+
totalCount: installedExtensions.length,
|
|
4168
|
+
getExtensionsAsync: async (index, count) => {
|
|
4169
|
+
return installedExtensions.slice(index, index + count).map((installedExtension) => this._createExtension(installedExtension.metadata, installedExtension.feed));
|
|
4170
|
+
},
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
const queries = await Promise.all(this._feeds.filter((feed) => feeds.includes(feed.name)).map(async (feed) => Object.assign(await feed.queryExtensionsAsync(filter), { feed })));
|
|
4174
|
+
const totalCount = queries.reduce((sum, query) => sum + query.totalCount, 0);
|
|
4175
|
+
return {
|
|
4176
|
+
totalCount,
|
|
4177
|
+
getExtensionsAsync: async (index, count) => {
|
|
4178
|
+
const extensions = new Array();
|
|
4179
|
+
let remaining = count;
|
|
4180
|
+
for (const query of queries) {
|
|
4181
|
+
if (remaining <= 0) {
|
|
4182
|
+
break;
|
|
4183
|
+
}
|
|
4184
|
+
if (index >= query.totalCount) {
|
|
4185
|
+
index -= query.totalCount;
|
|
4186
|
+
continue;
|
|
4187
|
+
}
|
|
4188
|
+
// This is intentionally sequential as we are querying for results until the count of results is met.
|
|
4189
|
+
// eslint-disable-next-line no-await-in-loop
|
|
4190
|
+
const metadataSlice = await query.getExtensionMetadataAsync(index, remaining);
|
|
4191
|
+
extensions.push(...metadataSlice.map((metadata) => this._createExtension(metadata, query.feed)));
|
|
4192
|
+
remaining -= metadataSlice.length;
|
|
4193
|
+
index = 0;
|
|
4194
|
+
}
|
|
4195
|
+
return extensions;
|
|
4196
|
+
},
|
|
4197
|
+
};
|
|
4198
|
+
}
|
|
4199
|
+
/**
|
|
4200
|
+
* Disposes the extension manager.
|
|
4201
|
+
*/
|
|
4202
|
+
dispose() {
|
|
4203
|
+
for (const installedExtension of this._installedExtensions.values()) {
|
|
4204
|
+
// eslint-disable-next-line github/no-then
|
|
4205
|
+
this._disableAsync(installedExtension.metadata, false).catch((error) => {
|
|
4206
|
+
Logger.Warn(`Failed to disable extension ${installedExtension.metadata.name}: ${error}`);
|
|
4207
|
+
});
|
|
4208
|
+
}
|
|
4209
|
+
this._stateChangedHandlers.clear();
|
|
4210
|
+
}
|
|
4211
|
+
async _installAsync(metadata, feed, isNestedStateChange) {
|
|
4212
|
+
let installedExtension = this._installedExtensions.get(metadata.name);
|
|
4213
|
+
if (!installedExtension) {
|
|
4214
|
+
installedExtension = this._createInstalledExtension(metadata, feed);
|
|
4215
|
+
installedExtension.isStateChanging = true;
|
|
4216
|
+
this._installedExtensions.set(metadata.name, installedExtension);
|
|
4217
|
+
try {
|
|
4218
|
+
// Enable the extension.
|
|
4219
|
+
await this._enableAsync(metadata, true, true);
|
|
4220
|
+
}
|
|
4221
|
+
catch (error) {
|
|
4222
|
+
this._installedExtensions.delete(metadata.name);
|
|
4223
|
+
throw error;
|
|
4224
|
+
}
|
|
4225
|
+
finally {
|
|
4226
|
+
!isNestedStateChange && (installedExtension.isStateChanging = false);
|
|
4227
|
+
}
|
|
4228
|
+
// Mark the extension as being installed.
|
|
4229
|
+
localStorage.setItem(GetExtensionInstalledKey(GetExtensionIdentity(feed.name, metadata.name)), JSON.stringify({
|
|
4230
|
+
feed: feed.name,
|
|
4231
|
+
metadata,
|
|
4232
|
+
}));
|
|
4233
|
+
localStorage.setItem(InstalledExtensionsKey, JSON.stringify(Array.from(this._installedExtensions.values()).map((extension) => GetExtensionIdentity(extension.feed.name, extension.metadata.name))));
|
|
4234
|
+
}
|
|
4235
|
+
return installedExtension;
|
|
4236
|
+
}
|
|
4237
|
+
async _uninstallAsync(metadata, isNestedStateChange) {
|
|
4238
|
+
const installedExtension = this._installedExtensions.get(metadata.name);
|
|
3629
4239
|
if (installedExtension && (isNestedStateChange || !installedExtension.isStateChanging)) {
|
|
3630
4240
|
try {
|
|
3631
4241
|
!isNestedStateChange && (installedExtension.isStateChanging = true);
|
|
@@ -3888,7 +4498,7 @@ class ServiceContainer {
|
|
|
3888
4498
|
}
|
|
3889
4499
|
}
|
|
3890
4500
|
|
|
3891
|
-
const useStyles$
|
|
4501
|
+
const useStyles$8 = makeStyles({
|
|
3892
4502
|
themeButton: {
|
|
3893
4503
|
margin: 0,
|
|
3894
4504
|
},
|
|
@@ -3907,7 +4517,7 @@ const ThemeSelectorServiceDefinition = {
|
|
|
3907
4517
|
suppressTeachingMoment: true,
|
|
3908
4518
|
order: -300,
|
|
3909
4519
|
component: () => {
|
|
3910
|
-
const classes = useStyles$
|
|
4520
|
+
const classes = useStyles$8();
|
|
3911
4521
|
const { isDarkMode, themeMode, setThemeMode } = useThemeMode();
|
|
3912
4522
|
const onSelectedThemeChange = useCallback((e, data) => {
|
|
3913
4523
|
setThemeMode(data.checkedItems.includes("System") ? "system" : data.checkedItems[0].toLocaleLowerCase());
|
|
@@ -3925,7 +4535,7 @@ const ThemeSelectorServiceDefinition = {
|
|
|
3925
4535
|
};
|
|
3926
4536
|
|
|
3927
4537
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
3928
|
-
const useStyles$
|
|
4538
|
+
const useStyles$7 = makeStyles({
|
|
3929
4539
|
app: {
|
|
3930
4540
|
colorScheme: "light dark",
|
|
3931
4541
|
flexGrow: 1,
|
|
@@ -3961,7 +4571,7 @@ function MakeModularTool(options) {
|
|
|
3961
4571
|
SetThemeMode(themeMode);
|
|
3962
4572
|
}
|
|
3963
4573
|
const modularToolRootComponent = () => {
|
|
3964
|
-
const classes = useStyles$
|
|
4574
|
+
const classes = useStyles$7();
|
|
3965
4575
|
const [extensionManagerContext, setExtensionManagerContext] = useState();
|
|
3966
4576
|
const [requiredExtensions, setRequiredExtensions] = useState();
|
|
3967
4577
|
const [requiredExtensionsDeferred, setRequiredExtensionsDeferred] = useState();
|
|
@@ -3987,7 +4597,7 @@ function MakeModularTool(options) {
|
|
|
3987
4597
|
});
|
|
3988
4598
|
// Register the extension list service (for browsing/installing extensions) if extension feeds are provided.
|
|
3989
4599
|
if (extensionFeeds.length > 0) {
|
|
3990
|
-
const { ExtensionListServiceDefinition } = await import('./extensionsListService-
|
|
4600
|
+
const { ExtensionListServiceDefinition } = await import('./extensionsListService-DcpjIM_c.js');
|
|
3991
4601
|
await serviceContainer.addServiceAsync(ExtensionListServiceDefinition);
|
|
3992
4602
|
}
|
|
3993
4603
|
// Register the theme selector service (for selecting the theme) if theming is configured.
|
|
@@ -4165,7 +4775,7 @@ const MeshIcon = createFluentIcon("Mesh", "16", '<path d="M14.03,3.54l-5.11-2.07
|
|
|
4165
4775
|
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" />');
|
|
4166
4776
|
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"/>');
|
|
4167
4777
|
|
|
4168
|
-
const useStyles$
|
|
4778
|
+
const useStyles$6 = makeStyles({
|
|
4169
4779
|
coordinatesModeButton: {
|
|
4170
4780
|
margin: `0 0 0 ${tokens.spacingHorizontalXS}`,
|
|
4171
4781
|
},
|
|
@@ -4175,7 +4785,7 @@ const useStyles$7 = makeStyles({
|
|
|
4175
4785
|
});
|
|
4176
4786
|
const GizmoToolbar = (props) => {
|
|
4177
4787
|
const { scene, entity, gizmoService } = props;
|
|
4178
|
-
const classes = useStyles$
|
|
4788
|
+
const classes = useStyles$6();
|
|
4179
4789
|
const gizmoManager = useResource(useCallback(() => {
|
|
4180
4790
|
const utilityLayerRef = gizmoService.getUtilityLayer(scene);
|
|
4181
4791
|
const keepDepthUtilityLayerRef = gizmoService.getUtilityLayer(scene, "keepDepth");
|
|
@@ -4259,260 +4869,67 @@ const GizmoToolbar = (props) => {
|
|
|
4259
4869
|
};
|
|
4260
4870
|
}, [gizmoManager, gizmoMode, entity]);
|
|
4261
4871
|
const updateGizmoMode = useCallback((mode) => {
|
|
4262
|
-
setGizmoMode((currentMode) => (currentMode === mode ? undefined : mode));
|
|
4263
|
-
}, []);
|
|
4264
|
-
const onCoordinatesModeChange = useCallback((e, data) => {
|
|
4265
|
-
gizmoManager.coordinatesMode = Number(data.checkedItems[0]);
|
|
4266
|
-
}, []);
|
|
4267
|
-
const toggleCoordinatesMode = useCallback(() => {
|
|
4268
|
-
gizmoManager.coordinatesMode = coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? 0 /* GizmoCoordinatesMode.World */ : 1 /* GizmoCoordinatesMode.Local */;
|
|
4269
|
-
}, [gizmoManager, coordinatesMode]);
|
|
4270
|
-
return (jsxs(Fragment, { children: [jsx(ToggleButton, { title: "Translate", checkedIcon: TranslateIcon, value: gizmoMode === "translate", onChange: () => updateGizmoMode("translate") }), jsx(ToggleButton, { title: "Rotate", checkedIcon: ArrowRotateClockwiseRegular, value: gizmoMode === "rotate", onChange: () => updateGizmoMode("rotate") }), jsx(ToggleButton, { title: "Scale", checkedIcon: ArrowExpandRegular, value: gizmoMode === "scale", onChange: () => updateGizmoMode("scale") }), jsx(ToggleButton, { title: "Bounding Box", checkedIcon: SelectObjectRegular, value: gizmoMode === "boundingBox", onChange: () => updateGizmoMode("boundingBox") }), jsx(Collapse, { visible: !!gizmoMode, orientation: "horizontal", children: jsxs(Menu, { positioning: "below-end", checkedValues: { coordinatesMode: [coordinatesMode.toString()] }, onCheckedValueChange: onCoordinatesModeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Coordinates Mode", relationship: "label", children: jsx(SplitButton, { className: classes.coordinatesModeButton, menuButton: triggerProps, primaryActionButton: {
|
|
4271
|
-
onClick: toggleCoordinatesMode,
|
|
4272
|
-
}, size: "small", appearance: "transparent", shape: "rounded", icon: coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? jsx(CubeRegular, {}) : jsx(GlobeRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.coordinatesModeMenu, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "coordinatesMode", value: 1 /* GizmoCoordinatesMode.Local */.toString(), children: "Local" }), jsx(MenuItemRadio, { name: "coordinatesMode", value: 0 /* GizmoCoordinatesMode.World */.toString(), children: "World" })] }) })] }) })] }));
|
|
4273
|
-
};
|
|
4274
|
-
|
|
4275
|
-
const GizmoToolbarServiceDefinition = {
|
|
4276
|
-
friendlyName: "Gizmo Toolbar",
|
|
4277
|
-
consumes: [SceneContextIdentity, ShellServiceIdentity, SelectionServiceIdentity, GizmoServiceIdentity],
|
|
4278
|
-
factory: (sceneContext, shellService, selectionService, gizmoService) => {
|
|
4279
|
-
shellService.addToolbarItem({
|
|
4280
|
-
key: "Gizmo Toolbar",
|
|
4281
|
-
verticalLocation: "top",
|
|
4282
|
-
horizontalLocation: "left",
|
|
4283
|
-
suppressTeachingMoment: true,
|
|
4284
|
-
component: () => {
|
|
4285
|
-
const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
|
|
4286
|
-
const selectedEntity = useObservableState(() => selectionService.selectedEntity, selectionService.onSelectedEntityChanged);
|
|
4287
|
-
return scene ? jsx(GizmoToolbar, { scene: scene, entity: selectedEntity, gizmoService: gizmoService }) : null;
|
|
4288
|
-
},
|
|
4289
|
-
});
|
|
4290
|
-
},
|
|
4291
|
-
};
|
|
4292
|
-
|
|
4293
|
-
const useStyles$6 = makeStyles({
|
|
4294
|
-
badge: {
|
|
4295
|
-
margin: tokens.spacingHorizontalXXS,
|
|
4296
|
-
fontFamily: "monospace",
|
|
4297
|
-
},
|
|
4298
|
-
});
|
|
4299
|
-
const MiniStatsServiceDefinition = {
|
|
4300
|
-
friendlyName: "Mini Stats",
|
|
4301
|
-
consumes: [SceneContextIdentity, ShellServiceIdentity],
|
|
4302
|
-
factory: (sceneContext, shellService) => {
|
|
4303
|
-
shellService.addToolbarItem({
|
|
4304
|
-
key: "Mini Stats",
|
|
4305
|
-
verticalLocation: "bottom",
|
|
4306
|
-
horizontalLocation: "right",
|
|
4307
|
-
suppressTeachingMoment: true,
|
|
4308
|
-
component: () => {
|
|
4309
|
-
const classes = useStyles$6();
|
|
4310
|
-
const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
|
|
4311
|
-
const engine = scene?.getEngine();
|
|
4312
|
-
const fps = useObservableState(useCallback(() => (engine ? Math.round(engine.getFps()) : null), [engine]), engine?.onBeginFrameObservable);
|
|
4313
|
-
return fps != null ? jsx(Badge, { appearance: "outline", className: classes.badge, children: `${fps} fps` }) : null;
|
|
4314
|
-
},
|
|
4315
|
-
});
|
|
4316
|
-
},
|
|
4317
|
-
};
|
|
4318
|
-
|
|
4319
|
-
const TargetedAnimationGeneralProperties = (props) => {
|
|
4320
|
-
const { selectionService } = props;
|
|
4321
|
-
return (jsx(Fragment, { children: jsx(LinkToEntityPropertyLine, { label: "Target", description: "The entity animated by this animation.", entity: props.targetedAnimation.target, selectionService: selectionService }) }));
|
|
4322
|
-
};
|
|
4323
|
-
|
|
4324
|
-
/**
|
|
4325
|
-
* Renders a label with an optional popup containing more info
|
|
4326
|
-
* @param props
|
|
4327
|
-
* @returns
|
|
4328
|
-
*/
|
|
4329
|
-
const InfoLabel = (props) => {
|
|
4330
|
-
InfoLabel.displayName = "InfoLabel";
|
|
4331
|
-
return (jsx(InfoLabel$1, { htmlFor: props.htmlFor, info: props.info, children: jsx(Body1, { children: props.label }) }));
|
|
4332
|
-
};
|
|
4333
|
-
|
|
4334
|
-
const TextInput = (props) => {
|
|
4335
|
-
TextInput.displayName = "TextInput";
|
|
4336
|
-
const classes = useInputStyles$1();
|
|
4337
|
-
const [value, setValue] = useState(props.value);
|
|
4338
|
-
const lastCommittedValue = useRef(props.value);
|
|
4339
|
-
const { size } = useContext(ToolContext);
|
|
4340
|
-
useEffect(() => {
|
|
4341
|
-
if (props.value !== lastCommittedValue.current) {
|
|
4342
|
-
setValue(props.value); // Update local state when props.value changes
|
|
4343
|
-
lastCommittedValue.current = props.value;
|
|
4344
|
-
}
|
|
4345
|
-
}, [props.value]);
|
|
4346
|
-
const validateValue = (val) => {
|
|
4347
|
-
const failsValidator = props.validator && !props.validator(val);
|
|
4348
|
-
return !failsValidator;
|
|
4349
|
-
};
|
|
4350
|
-
const tryCommitValue = (currVal) => {
|
|
4351
|
-
// Only commit if valid and different from last committed value
|
|
4352
|
-
if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
|
|
4353
|
-
lastCommittedValue.current = currVal;
|
|
4354
|
-
props.onChange(currVal);
|
|
4355
|
-
}
|
|
4356
|
-
};
|
|
4357
|
-
const handleChange = (event, data) => {
|
|
4358
|
-
event.stopPropagation();
|
|
4359
|
-
setValue(data.value);
|
|
4360
|
-
tryCommitValue(data.value);
|
|
4361
|
-
};
|
|
4362
|
-
const handleKeyUp = (event) => {
|
|
4363
|
-
event.stopPropagation();
|
|
4364
|
-
setValue(event.currentTarget.value);
|
|
4365
|
-
tryCommitValue(event.currentTarget.value);
|
|
4366
|
-
};
|
|
4367
|
-
const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
|
|
4368
|
-
const id = useId("input-button");
|
|
4369
|
-
return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Input, { ...props, input: { className: classes.inputSlot }, id: id, size: size, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
|
|
4370
|
-
};
|
|
4371
|
-
|
|
4372
|
-
const SpinButton = (props) => {
|
|
4373
|
-
SpinButton.displayName = "SpinButton";
|
|
4374
|
-
const classes = useInputStyles$1();
|
|
4375
|
-
const { size } = useContext(ToolContext);
|
|
4376
|
-
const { min, max } = props;
|
|
4377
|
-
const [value, setValue] = useState(props.value);
|
|
4378
|
-
const lastCommittedValue = useRef(props.value);
|
|
4379
|
-
// step and forceInt are not mutually exclusive since there could be cases where you want to forceInt but have spinButton jump >1 int per spin
|
|
4380
|
-
const step = props.step != undefined ? props.step : props.forceInt ? 1 : undefined;
|
|
4381
|
-
const precision = Math.min(4, step !== undefined ? Math.max(0, CalculatePrecision(step)) : 2); // If no step, set precision to 2. Regardless, cap precision at 4 to avoid wild numbers
|
|
4382
|
-
useEffect(() => {
|
|
4383
|
-
if (props.value !== lastCommittedValue.current) {
|
|
4384
|
-
lastCommittedValue.current = props.value;
|
|
4385
|
-
setValue(props.value); // Update local state when props.value changes
|
|
4386
|
-
}
|
|
4387
|
-
}, [props.value]);
|
|
4388
|
-
const validateValue = (numericValue) => {
|
|
4389
|
-
const outOfBounds = (min !== undefined && numericValue < min) || (max !== undefined && numericValue > max);
|
|
4390
|
-
const failsValidator = props.validator && !props.validator(numericValue);
|
|
4391
|
-
const failsIntCheck = props.forceInt ? !Number.isInteger(numericValue) : false;
|
|
4392
|
-
const invalid = !!outOfBounds || !!failsValidator || isNaN(numericValue) || !!failsIntCheck;
|
|
4393
|
-
return !invalid;
|
|
4394
|
-
};
|
|
4395
|
-
const tryCommitValue = (currVal) => {
|
|
4396
|
-
// Only commit if valid and different from last committed value
|
|
4397
|
-
if (validateValue(currVal) && currVal !== lastCommittedValue.current) {
|
|
4398
|
-
lastCommittedValue.current = currVal;
|
|
4399
|
-
props.onChange(currVal);
|
|
4400
|
-
}
|
|
4401
|
-
};
|
|
4402
|
-
const handleChange = (event, data) => {
|
|
4403
|
-
event.stopPropagation(); // Prevent event propagation
|
|
4404
|
-
if (data.value != null && !Number.isNaN(data.value)) {
|
|
4405
|
-
setValue(data.value);
|
|
4406
|
-
tryCommitValue(data.value);
|
|
4407
|
-
}
|
|
4408
|
-
};
|
|
4409
|
-
const handleKeyUp = (event) => {
|
|
4410
|
-
event.stopPropagation(); // Prevent event propagation
|
|
4411
|
-
if (event.key !== "Enter") {
|
|
4412
|
-
const currVal = parseFloat(event.target.value); // Cannot use currentTarget.value as it won't have the most recently typed value
|
|
4413
|
-
setValue(currVal);
|
|
4414
|
-
tryCommitValue(currVal);
|
|
4415
|
-
}
|
|
4416
|
-
};
|
|
4417
|
-
const id = useId("spin-button");
|
|
4418
|
-
const mergedClassName = mergeClasses(classes.input, !validateValue(value) ? classes.invalid : "", props.className);
|
|
4419
|
-
return (jsxs("div", { className: classes.container, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(SpinButton$1, { ...props, input: { className: classes.inputSlot }, step: step, id: id, size: size, precision: precision, displayValue: `${value.toFixed(precision)}${props.unit ? " " + props.unit : ""}`, value: value, onChange: handleChange, onKeyUp: handleKeyUp, onKeyDown: HandleKeyDown, onBlur: HandleOnBlur, className: mergedClassName })] }));
|
|
4872
|
+
setGizmoMode((currentMode) => (currentMode === mode ? undefined : mode));
|
|
4873
|
+
}, []);
|
|
4874
|
+
const onCoordinatesModeChange = useCallback((e, data) => {
|
|
4875
|
+
gizmoManager.coordinatesMode = Number(data.checkedItems[0]);
|
|
4876
|
+
}, []);
|
|
4877
|
+
const toggleCoordinatesMode = useCallback(() => {
|
|
4878
|
+
gizmoManager.coordinatesMode = coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? 0 /* GizmoCoordinatesMode.World */ : 1 /* GizmoCoordinatesMode.Local */;
|
|
4879
|
+
}, [gizmoManager, coordinatesMode]);
|
|
4880
|
+
return (jsxs(Fragment, { children: [jsx(ToggleButton, { title: "Translate", checkedIcon: TranslateIcon, value: gizmoMode === "translate", onChange: () => updateGizmoMode("translate") }), jsx(ToggleButton, { title: "Rotate", checkedIcon: ArrowRotateClockwiseRegular, value: gizmoMode === "rotate", onChange: () => updateGizmoMode("rotate") }), jsx(ToggleButton, { title: "Scale", checkedIcon: ArrowExpandRegular, value: gizmoMode === "scale", onChange: () => updateGizmoMode("scale") }), jsx(ToggleButton, { title: "Bounding Box", checkedIcon: SelectObjectRegular, value: gizmoMode === "boundingBox", onChange: () => updateGizmoMode("boundingBox") }), jsx(Collapse, { visible: !!gizmoMode, orientation: "horizontal", children: jsxs(Menu, { positioning: "below-end", checkedValues: { coordinatesMode: [coordinatesMode.toString()] }, onCheckedValueChange: onCoordinatesModeChange, children: [jsx(MenuTrigger, { disableButtonEnhancement: true, children: (triggerProps) => (jsx(Tooltip, { content: "Coordinates Mode", relationship: "label", children: jsx(SplitButton, { className: classes.coordinatesModeButton, menuButton: triggerProps, primaryActionButton: {
|
|
4881
|
+
onClick: toggleCoordinatesMode,
|
|
4882
|
+
}, size: "small", appearance: "transparent", shape: "rounded", icon: coordinatesMode === 1 /* GizmoCoordinatesMode.Local */ ? jsx(CubeRegular, {}) : jsx(GlobeRegular, {}) }) })) }), jsx(MenuPopover, { className: classes.coordinatesModeMenu, children: jsxs(MenuList, { children: [jsx(MenuItemRadio, { name: "coordinatesMode", value: 1 /* GizmoCoordinatesMode.Local */.toString(), children: "Local" }), jsx(MenuItemRadio, { name: "coordinatesMode", value: 0 /* GizmoCoordinatesMode.World */.toString(), children: "World" })] }) })] }) })] }));
|
|
4420
4883
|
};
|
|
4421
4884
|
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
NumberInputPropertyLine.displayName = "NumberInputPropertyLine";
|
|
4439
|
-
return (jsx(PropertyLine, { ...props, children: jsx(SpinButton, { ...props }) }));
|
|
4885
|
+
const GizmoToolbarServiceDefinition = {
|
|
4886
|
+
friendlyName: "Gizmo Toolbar",
|
|
4887
|
+
consumes: [SceneContextIdentity, ShellServiceIdentity, SelectionServiceIdentity, GizmoServiceIdentity],
|
|
4888
|
+
factory: (sceneContext, shellService, selectionService, gizmoService) => {
|
|
4889
|
+
shellService.addToolbarItem({
|
|
4890
|
+
key: "Gizmo Toolbar",
|
|
4891
|
+
verticalLocation: "top",
|
|
4892
|
+
horizontalLocation: "left",
|
|
4893
|
+
suppressTeachingMoment: true,
|
|
4894
|
+
component: () => {
|
|
4895
|
+
const scene = useObservableState(() => sceneContext.currentScene, sceneContext.currentSceneObservable);
|
|
4896
|
+
const selectedEntity = useObservableState(() => selectionService.selectedEntity, selectionService.onSelectedEntityChanged);
|
|
4897
|
+
return scene ? jsx(GizmoToolbar, { scene: scene, entity: selectedEntity, gizmoService: gizmoService }) : null;
|
|
4898
|
+
},
|
|
4899
|
+
});
|
|
4900
|
+
},
|
|
4440
4901
|
};
|
|
4441
4902
|
|
|
4442
|
-
const
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
flexDirection: "row",
|
|
4447
|
-
display: "flex",
|
|
4448
|
-
alignItems: "center",
|
|
4449
|
-
},
|
|
4450
|
-
slider: {
|
|
4451
|
-
minWidth: CustomTokens.sliderMinWidth, // Minimum width for slider to remain usable
|
|
4452
|
-
maxWidth: CustomTokens.sliderMaxWidth,
|
|
4903
|
+
const useStyles$5 = makeStyles({
|
|
4904
|
+
badge: {
|
|
4905
|
+
margin: tokens.spacingHorizontalXXS,
|
|
4906
|
+
fontFamily: "monospace",
|
|
4453
4907
|
},
|
|
4454
4908
|
});
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
const step = props.step ?? 1;
|
|
4474
|
-
useEffect(() => {
|
|
4475
|
-
!isDraggingRef.current && setValue(props.value ?? ""); // Update local state when props.value changes as long as user is not actively dragging
|
|
4476
|
-
}, [props.value]);
|
|
4477
|
-
const handleSliderChange = (_, data) => {
|
|
4478
|
-
const newValue = data.value * step;
|
|
4479
|
-
setValue(newValue);
|
|
4480
|
-
if (props.notifyOnlyOnRelease) {
|
|
4481
|
-
// Store the value but don't notify parent yet
|
|
4482
|
-
pendingValueRef.current = newValue;
|
|
4483
|
-
}
|
|
4484
|
-
else {
|
|
4485
|
-
// Notify parent as slider changes
|
|
4486
|
-
props.onChange(newValue);
|
|
4487
|
-
}
|
|
4488
|
-
};
|
|
4489
|
-
const handleSliderPointerDown = () => {
|
|
4490
|
-
isDraggingRef.current = true;
|
|
4491
|
-
};
|
|
4492
|
-
const handleSliderPointerUp = () => {
|
|
4493
|
-
if (props.notifyOnlyOnRelease && isDraggingRef.current && pendingValueRef.current !== undefined) {
|
|
4494
|
-
props.onChange(pendingValueRef.current);
|
|
4495
|
-
pendingValueRef.current = undefined;
|
|
4496
|
-
}
|
|
4497
|
-
isDraggingRef.current = false;
|
|
4498
|
-
};
|
|
4499
|
-
const handleInputChange = (value) => {
|
|
4500
|
-
setValue(value);
|
|
4501
|
-
props.onChange(value); // Input always updates immediately
|
|
4502
|
-
};
|
|
4503
|
-
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 })] })] }));
|
|
4909
|
+
const MiniStatsServiceDefinition = {
|
|
4910
|
+
friendlyName: "Mini Stats",
|
|
4911
|
+
consumes: [SceneContextIdentity, ShellServiceIdentity],
|
|
4912
|
+
factory: (sceneContext, shellService) => {
|
|
4913
|
+
shellService.addToolbarItem({
|
|
4914
|
+
key: "Mini Stats",
|
|
4915
|
+
verticalLocation: "bottom",
|
|
4916
|
+
horizontalLocation: "right",
|
|
4917
|
+
suppressTeachingMoment: true,
|
|
4918
|
+
component: () => {
|
|
4919
|
+
const classes = useStyles$5();
|
|
4920
|
+
const scene = useObservableState(useCallback(() => sceneContext.currentScene, [sceneContext.currentScene]), sceneContext.currentSceneObservable);
|
|
4921
|
+
const engine = scene?.getEngine();
|
|
4922
|
+
const fps = useObservableState(useCallback(() => (engine ? Math.round(engine.getFps()) : null), [engine]), engine?.onBeginFrameObservable);
|
|
4923
|
+
return fps != null ? jsx(Badge, { appearance: "outline", className: classes.badge, children: `${fps} fps` }) : null;
|
|
4924
|
+
},
|
|
4925
|
+
});
|
|
4926
|
+
},
|
|
4504
4927
|
};
|
|
4505
4928
|
|
|
4506
|
-
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
*/
|
|
4511
|
-
const SyncedSliderPropertyLine = forwardRef((props, ref) => {
|
|
4512
|
-
SyncedSliderPropertyLine.displayName = "SyncedSliderPropertyLine";
|
|
4513
|
-
const { label, description, ...sliderProps } = props;
|
|
4514
|
-
return (jsx(PropertyLine, { ref: ref, ...props, children: jsx(SyncedSliderInput, { ...sliderProps }) }));
|
|
4515
|
-
});
|
|
4929
|
+
const TargetedAnimationGeneralProperties = (props) => {
|
|
4930
|
+
const { selectionService } = props;
|
|
4931
|
+
return (jsx(Fragment, { children: jsx(LinkToEntityPropertyLine, { label: "Target", description: "The entity animated by this animation.", entity: props.targetedAnimation.target, selectionService: selectionService }) }));
|
|
4932
|
+
};
|
|
4516
4933
|
|
|
4517
4934
|
const AnimationGroupControlProperties = (props) => {
|
|
4518
4935
|
const { animationGroup } = props;
|
|
@@ -4557,440 +4974,136 @@ const AnimationGroupPropertiesServiceDefinition = {
|
|
|
4557
4974
|
component: ({ context }) => jsx(AnimationGroupControlProperties, { animationGroup: context }),
|
|
4558
4975
|
},
|
|
4559
4976
|
{
|
|
4560
|
-
section: "Info",
|
|
4561
|
-
component: ({ context }) => jsx(AnimationGroupInfoProperties, { animationGroup: context }),
|
|
4562
|
-
},
|
|
4563
|
-
],
|
|
4564
|
-
});
|
|
4565
|
-
const targetedAnimationContentRegistration = propertiesService.addSectionContent({
|
|
4566
|
-
key: "Targeted Animation Properties",
|
|
4567
|
-
predicate: (entity) => entity instanceof TargetedAnimation,
|
|
4568
|
-
content: [
|
|
4569
|
-
{
|
|
4570
|
-
section: "General",
|
|
4571
|
-
component: ({ context }) => jsx(TargetedAnimationGeneralProperties, { targetedAnimation: context, selectionService: selectionService }),
|
|
4572
|
-
},
|
|
4573
|
-
],
|
|
4574
|
-
});
|
|
4575
|
-
return {
|
|
4576
|
-
dispose: () => {
|
|
4577
|
-
animationGroupContentRegistration.dispose();
|
|
4578
|
-
targetedAnimationContentRegistration.dispose();
|
|
4579
|
-
},
|
|
4580
|
-
};
|
|
4581
|
-
},
|
|
4582
|
-
};
|
|
4583
|
-
|
|
4584
|
-
const useClasses = makeStyles({
|
|
4585
|
-
container: {
|
|
4586
|
-
display: "flex",
|
|
4587
|
-
flexDirection: "column",
|
|
4588
|
-
gap: tokens.spacingVerticalS, // 8px
|
|
4589
|
-
},
|
|
4590
|
-
});
|
|
4591
|
-
const MessageBar = (props) => {
|
|
4592
|
-
MessageBar.displayName = "MessageBar";
|
|
4593
|
-
const { message, title: header, intent, docLink } = props;
|
|
4594
|
-
const classes = useClasses();
|
|
4595
|
-
return (jsx("div", { className: classes.container, children: jsx(MessageBar$1, { intent: intent, layout: "multiline", children: jsxs(MessageBarBody, { children: [jsx(MessageBarTitle, { children: header }), message, docLink && (jsxs(Fragment, { children: [" - ", jsx(Link, { url: docLink, value: "Learn More" })] }))] }) }) }));
|
|
4596
|
-
};
|
|
4597
|
-
|
|
4598
|
-
const AnimationsProperties = (props) => {
|
|
4599
|
-
const { scene, entity } = props;
|
|
4600
|
-
const animations = entity.animations ?? [];
|
|
4601
|
-
const ranges = entity.getAnimationRanges?.()?.filter((range) => !!range) ?? [];
|
|
4602
|
-
const childAnimatablesAnimations = entity.getAnimatables?.().flatMap((animatable) => animatable.animations ?? []) ?? [];
|
|
4603
|
-
animations.concat(childAnimatablesAnimations);
|
|
4604
|
-
const lastFrom = useRef(0);
|
|
4605
|
-
const lastTo = useRef(0);
|
|
4606
|
-
const lastLoop = useRef(false);
|
|
4607
|
-
const animatablesForTarget = scene.getAllAnimatablesByTarget(entity);
|
|
4608
|
-
const isPlaying = animatablesForTarget.length > 0;
|
|
4609
|
-
const mainAnimatable = isPlaying ? animatablesForTarget[0] : undefined;
|
|
4610
|
-
const animationPropertiesOverride = useProperty(mainAnimatable, "animationPropertiesOverride");
|
|
4611
|
-
if (mainAnimatable) {
|
|
4612
|
-
lastFrom.current = mainAnimatable.fromFrame;
|
|
4613
|
-
lastTo.current = mainAnimatable.toFrame;
|
|
4614
|
-
lastLoop.current = mainAnimatable.loopAnimation;
|
|
4615
|
-
}
|
|
4616
|
-
const hasAnimations = animations.length > 0 || ranges.length > 0;
|
|
4617
|
-
const currentFrame = useObservableState(useCallback(() => {
|
|
4618
|
-
return mainAnimatable ? mainAnimatable.masterFrame : (scene.getAllAnimatablesByTarget(entity)[0]?.masterFrame ?? 0);
|
|
4619
|
-
}, [scene, entity, mainAnimatable]), hasAnimations ? scene.onAfterAnimationsObservable : undefined);
|
|
4620
|
-
return (jsx(Fragment, { children: !hasAnimations ? (jsx(MessageBar, { intent: "info", title: "No Animations", message: "To modify animations, attach an animation to this node.", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/animation/" })) : (jsxs(Fragment, { children: [ranges.length > 0 && (jsx(PropertyLine, { label: "Ranges", expandedContent: jsx(Fragment, { children: ranges.map((range) => {
|
|
4621
|
-
return (jsx(ButtonLine, { label: range.name, onClick: () => {
|
|
4622
|
-
scene.beginAnimation(entity, range.from, range.to, true);
|
|
4623
|
-
} }, range.name));
|
|
4624
|
-
}) }), children: jsx(Badge, { appearance: "filled", children: ranges.length }) })), animations.length > 0 && (jsxs(Fragment, { children: [jsx(PropertyLine, { label: "Animations", expandedContent: jsx(Fragment, { children: animations.map((animation, index) => {
|
|
4625
|
-
return jsx(TextPropertyLine, { label: `${index}: ${animation.name}`, value: animation.targetProperty }, animation.uniqueId);
|
|
4626
|
-
}) }), children: jsx(Badge, { appearance: "filled", children: animations.length }) }), mainAnimatable && (jsx(Fragment, { children: jsx(PropertyLine, { label: "Animation Controls", expandedContent: jsxs(Fragment, { children: [jsx(NumberInputPropertyLine, { label: "From", value: mainAnimatable.fromFrame, onChange: (value) => {
|
|
4627
|
-
scene.stopAnimation(entity);
|
|
4628
|
-
scene.beginAnimation(entity, value, mainAnimatable.toFrame, true);
|
|
4629
|
-
} }), jsx(NumberInputPropertyLine, { label: "To", value: mainAnimatable.toFrame, onChange: (value) => {
|
|
4630
|
-
scene.stopAnimation(entity);
|
|
4631
|
-
scene.beginAnimation(entity, mainAnimatable.fromFrame, value, true);
|
|
4632
|
-
} }), jsx(SwitchPropertyLine, { label: "Loop", value: mainAnimatable.loopAnimation, onChange: (value) => {
|
|
4633
|
-
for (const animatable of animatablesForTarget) {
|
|
4634
|
-
animatable.loopAnimation = value;
|
|
4635
|
-
}
|
|
4636
|
-
} }), jsx(SyncedSliderPropertyLine, { label: "Current Frame", value: currentFrame, min: mainAnimatable.fromFrame, max: mainAnimatable.toFrame, step: (mainAnimatable.toFrame - mainAnimatable.fromFrame) / 1000, onChange: (value) => {
|
|
4637
|
-
mainAnimatable.goToFrame(value);
|
|
4638
|
-
} })] }), expandByDefault: true }) })), jsx(ButtonLine, { label: isPlaying ? "Stop Animation" : "Play Animation", onClick: () => {
|
|
4639
|
-
if (isPlaying) {
|
|
4640
|
-
scene.stopAnimation(entity);
|
|
4641
|
-
}
|
|
4642
|
-
else {
|
|
4643
|
-
scene.beginAnimation(entity, lastFrom.current, lastTo.current, lastLoop.current);
|
|
4644
|
-
}
|
|
4645
|
-
} }), mainAnimatable && (ranges.length > 0 || animations.length > 0) ? (jsxs(Fragment, { children: [jsx(SwitchPropertyLine, { label: "Enable Override", value: animationPropertiesOverride != null, onChange: (value) => {
|
|
4646
|
-
if (value) {
|
|
4647
|
-
mainAnimatable.animationPropertiesOverride = new AnimationPropertiesOverride();
|
|
4648
|
-
mainAnimatable.animationPropertiesOverride.blendingSpeed = 0.05;
|
|
4649
|
-
}
|
|
4650
|
-
else {
|
|
4651
|
-
mainAnimatable.animationPropertiesOverride = undefined;
|
|
4652
|
-
}
|
|
4653
|
-
} }), jsx(Collapse, { visible: animationPropertiesOverride != null, children: jsxs("div", { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enable Blending", target: animationPropertiesOverride, propertyKey: "enableBlending" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Blending Speed", target: animationPropertiesOverride, propertyKey: "blendingSpeed", min: 0, max: 0.1, step: 0.01 })] }) })] })) : null] }))] })) }));
|
|
4654
|
-
};
|
|
4655
|
-
|
|
4656
|
-
function IsAnimatable(entity) {
|
|
4657
|
-
return entity.animations !== undefined;
|
|
4658
|
-
}
|
|
4659
|
-
function IsAnimationRangeContainer(entity) {
|
|
4660
|
-
return entity.getAnimationRanges !== undefined;
|
|
4661
|
-
}
|
|
4662
|
-
function IsAnimatableContainer(entity) {
|
|
4663
|
-
return entity.getAnimatables !== undefined;
|
|
4664
|
-
}
|
|
4665
|
-
const AnimationPropertiesServiceDefinition = {
|
|
4666
|
-
friendlyName: "Animation Properties",
|
|
4667
|
-
consumes: [PropertiesServiceIdentity, SelectionServiceIdentity, SceneContextIdentity],
|
|
4668
|
-
factory: (propertiesService, selectionService, sceneContext) => {
|
|
4669
|
-
const scene = sceneContext.currentScene;
|
|
4670
|
-
if (!scene) {
|
|
4671
|
-
return undefined;
|
|
4672
|
-
}
|
|
4673
|
-
const animationContentRegistration = propertiesService.addSectionContent({
|
|
4674
|
-
key: "Animation Properties",
|
|
4675
|
-
predicate: (entity) => IsAnimatable(entity) || IsAnimationRangeContainer(entity) || IsAnimatableContainer(entity),
|
|
4977
|
+
section: "Info",
|
|
4978
|
+
component: ({ context }) => jsx(AnimationGroupInfoProperties, { animationGroup: context }),
|
|
4979
|
+
},
|
|
4980
|
+
],
|
|
4981
|
+
});
|
|
4982
|
+
const targetedAnimationContentRegistration = propertiesService.addSectionContent({
|
|
4983
|
+
key: "Targeted Animation Properties",
|
|
4984
|
+
predicate: (entity) => entity instanceof TargetedAnimation,
|
|
4676
4985
|
content: [
|
|
4677
4986
|
{
|
|
4678
|
-
section: "
|
|
4679
|
-
component: ({ context }) => jsx(
|
|
4987
|
+
section: "General",
|
|
4988
|
+
component: ({ context }) => jsx(TargetedAnimationGeneralProperties, { targetedAnimation: context, selectionService: selectionService }),
|
|
4680
4989
|
},
|
|
4681
4990
|
],
|
|
4682
4991
|
});
|
|
4683
4992
|
return {
|
|
4684
4993
|
dispose: () => {
|
|
4685
|
-
|
|
4994
|
+
animationGroupContentRegistration.dispose();
|
|
4995
|
+
targetedAnimationContentRegistration.dispose();
|
|
4686
4996
|
},
|
|
4687
4997
|
};
|
|
4688
4998
|
},
|
|
4689
4999
|
};
|
|
4690
5000
|
|
|
4691
|
-
const
|
|
4692
|
-
const HasW = (vector) => vector instanceof Vector4 || vector instanceof Quaternion;
|
|
4693
|
-
/**
|
|
4694
|
-
* Reusable component which renders a vector property line containing a label, vector value, and expandable XYZW values
|
|
4695
|
-
* The expanded section contains a slider/input box for each component of the vector (x, y, z, w)
|
|
4696
|
-
* @param props
|
|
4697
|
-
* @returns
|
|
4698
|
-
*/
|
|
4699
|
-
const TensorPropertyLine = (props) => {
|
|
4700
|
-
TensorPropertyLine.displayName = "TensorPropertyLine";
|
|
4701
|
-
const converted = (val) => (props.valueConverter ? props.valueConverter.from(val) : val);
|
|
4702
|
-
const formatted = (val) => converted(val).toFixed(props.step !== undefined ? Math.max(0, CalculatePrecision(props.step)) : 2);
|
|
4703
|
-
const [vector, setVector] = useState(props.value);
|
|
4704
|
-
const { min, max } = props;
|
|
4705
|
-
const onChange = (val, key) => {
|
|
4706
|
-
const value = props.valueConverter ? props.valueConverter.to(val) : val;
|
|
4707
|
-
const newVector = vector.clone();
|
|
4708
|
-
newVector[key] = value; // The syncedSlider for 'w' is only rendered when vector is a Vector4, so this is safe
|
|
4709
|
-
setVector(newVector);
|
|
4710
|
-
props.onChange(newVector);
|
|
4711
|
-
};
|
|
4712
|
-
useEffect(() => {
|
|
4713
|
-
setVector(props.value);
|
|
4714
|
-
}, [props.value, props.expandedContent]);
|
|
4715
|
-
return (jsx(PropertyLine, { ...props, onCopy: () => `new ${props.value.getClassName()}(${vector.x},${vector.y}${HasZ(vector) ? `,${vector.z}` : ""}${HasW(vector) ? `,${vector.w}` : ""})`, expandedContent: jsxs(Fragment, { children: [jsx(SyncedSliderPropertyLine, { label: "X", value: converted(vector.x), min: min, max: max, onChange: (val) => onChange(val, "x"), unit: props.unit, step: props.step }), jsx(SyncedSliderPropertyLine, { label: "Y", value: converted(vector.y), min: min, max: max, onChange: (val) => onChange(val, "y"), unit: props.unit, step: props.step }), HasZ(vector) && (jsx(SyncedSliderPropertyLine, { label: "Z", value: converted(vector.z), min: min, max: max, onChange: (val) => onChange(val, "z"), unit: props.unit, step: props.step })), HasW(vector) && (jsx(SyncedSliderPropertyLine, { label: "W", value: converted(vector.w), min: min, max: max, onChange: (val) => onChange(val, "w"), unit: props.unit, step: props.step }))] }), children: jsx(Body1, { children: `[${formatted(props.value.x)}, ${formatted(props.value.y)}${HasZ(props.value) ? `, ${formatted(props.value.z)}` : ""}${HasW(props.value) ? `, ${formatted(props.value.w)}` : ""}]` }) }));
|
|
4716
|
-
};
|
|
4717
|
-
const ToDegreesConverter = { from: Tools.ToDegrees, to: Tools.ToRadians };
|
|
4718
|
-
const RotationVectorPropertyLine = (props) => {
|
|
4719
|
-
RotationVectorPropertyLine.displayName = "RotationVectorPropertyLine";
|
|
4720
|
-
const min = props.useDegrees ? 0 : undefined;
|
|
4721
|
-
const max = props.useDegrees ? 360 : undefined;
|
|
4722
|
-
return (jsx(Vector3PropertyLine, { ...props, unit: props.useDegrees ? "deg" : "rad", valueConverter: props.useDegrees ? ToDegreesConverter : undefined, min: min, max: max, step: 0.001 }));
|
|
4723
|
-
};
|
|
4724
|
-
const QuaternionPropertyLineInternal = TensorPropertyLine;
|
|
4725
|
-
const QuaternionPropertyLine = (props) => {
|
|
4726
|
-
QuaternionPropertyLine.displayName = "QuaternionPropertyLine";
|
|
4727
|
-
const min = props.useDegrees ? 0 : undefined;
|
|
4728
|
-
const max = props.useDegrees ? 360 : undefined;
|
|
4729
|
-
const [quat, setQuat] = useState(props.value);
|
|
4730
|
-
useEffect(() => {
|
|
4731
|
-
setQuat(props.value);
|
|
4732
|
-
}, [props.value]);
|
|
4733
|
-
// Extract only the properties that exist on QuaternionPropertyLineProps
|
|
4734
|
-
const { useDegrees, ...restProps } = props;
|
|
4735
|
-
const onQuatChange = (val) => {
|
|
4736
|
-
setQuat(val);
|
|
4737
|
-
props.onChange(val);
|
|
4738
|
-
};
|
|
4739
|
-
const onEulerChange = (val) => {
|
|
4740
|
-
const quat = Quaternion.FromEulerAngles(val.x, val.y, val.z);
|
|
4741
|
-
onQuatChange(quat);
|
|
4742
|
-
};
|
|
4743
|
-
return useDegrees ? (jsx(Vector3PropertyLine, { ...restProps, nullable: false, ignoreNullable: false, value: quat.toEulerAngles(), valueConverter: ToDegreesConverter, min: min, max: max, onChange: onEulerChange, unit: "deg" })) : (jsx(QuaternionPropertyLineInternal, { ...props, nullable: false, value: quat, min: min, max: max, onChange: onQuatChange }));
|
|
4744
|
-
};
|
|
4745
|
-
const Vector2PropertyLine = TensorPropertyLine;
|
|
4746
|
-
const Vector3PropertyLine = TensorPropertyLine;
|
|
4747
|
-
const Vector4PropertyLine = TensorPropertyLine;
|
|
4748
|
-
|
|
4749
|
-
const useDropdownStyles = makeStyles({
|
|
4750
|
-
dropdown: {
|
|
4751
|
-
minWidth: 0,
|
|
4752
|
-
width: "100%",
|
|
4753
|
-
},
|
|
5001
|
+
const useClasses = makeStyles({
|
|
4754
5002
|
container: {
|
|
4755
5003
|
display: "flex",
|
|
4756
5004
|
flexDirection: "column",
|
|
4757
|
-
|
|
4758
|
-
},
|
|
4759
|
-
dropdownText: { textAlign: "end", textOverflow: "ellipsis", whiteSpace: "nowrap", overflowX: "hidden" },
|
|
4760
|
-
});
|
|
4761
|
-
/**
|
|
4762
|
-
* Renders a fluent UI dropdown component for the options passed in, and an additional 'Not Defined' option if null is set to true
|
|
4763
|
-
* This component can handle both null and undefined values
|
|
4764
|
-
* @param props
|
|
4765
|
-
* @returns dropdown component
|
|
4766
|
-
*/
|
|
4767
|
-
const Dropdown = (props) => {
|
|
4768
|
-
Dropdown.displayName = "Dropdown";
|
|
4769
|
-
const classes = useDropdownStyles();
|
|
4770
|
-
const { options, value } = props;
|
|
4771
|
-
const [defaultVal, setDefaultVal] = useState(props.value);
|
|
4772
|
-
const { size } = useContext(ToolContext);
|
|
4773
|
-
useEffect(() => {
|
|
4774
|
-
setDefaultVal(value);
|
|
4775
|
-
}, [props.value]);
|
|
4776
|
-
const id = useId("dropdown");
|
|
4777
|
-
const mergedClassName = mergeClasses(classes.container, props.className);
|
|
4778
|
-
const optionLabel = options.find((o) => o.value === defaultVal)?.label;
|
|
4779
|
-
return (jsxs("div", { className: mergedClassName, children: [props.infoLabel && jsx(InfoLabel, { ...props.infoLabel, htmlFor: id }), jsx(Dropdown$1, { id: id, disabled: props.disabled, size: size, className: classes.dropdown, button: jsx("span", { className: classes.dropdownText, children: optionLabel }), onOptionSelect: (evt, data) => {
|
|
4780
|
-
const value = typeof props.value === "number" ? Number(data.optionValue) : data.optionValue;
|
|
4781
|
-
if (value !== undefined) {
|
|
4782
|
-
setDefaultVal(value);
|
|
4783
|
-
props.onChange(value);
|
|
4784
|
-
}
|
|
4785
|
-
}, selectedOptions: [defaultVal.toString()], value: optionLabel, children: options.map((option) => (jsx(Option, { value: option.value.toString(), disabled: false, children: option.label }, option.label))) })] }));
|
|
4786
|
-
};
|
|
4787
|
-
const NumberDropdown = Dropdown;
|
|
4788
|
-
const StringDropdown = Dropdown;
|
|
4789
|
-
|
|
4790
|
-
const useColorPickerStyles = makeStyles({
|
|
4791
|
-
container: {
|
|
4792
|
-
width: "350px",
|
|
4793
|
-
display: "flex", // becomes a flexbox
|
|
4794
|
-
flexDirection: "column", // with children in a column
|
|
4795
|
-
alignItems: "center", // centers children horizontally
|
|
4796
|
-
justifyContent: "center", // centers children vertically (if height is set)
|
|
4797
|
-
gap: tokens.spacingVerticalM,
|
|
4798
|
-
overflow: "visible",
|
|
4799
|
-
},
|
|
4800
|
-
row: {
|
|
4801
|
-
flex: 1, // is a row in the container's flex column
|
|
4802
|
-
display: "flex", // becomes its own flexbox
|
|
4803
|
-
flexDirection: "row", // with children in a row
|
|
4804
|
-
gap: tokens.spacingHorizontalXL,
|
|
4805
|
-
alignItems: "center", // align items vertically
|
|
4806
|
-
width: "100%",
|
|
4807
|
-
},
|
|
4808
|
-
colorPicker: {
|
|
4809
|
-
flex: 1,
|
|
4810
|
-
width: "350px",
|
|
4811
|
-
height: "350px",
|
|
4812
|
-
},
|
|
4813
|
-
previewColor: {
|
|
4814
|
-
width: "60px",
|
|
4815
|
-
height: "60px",
|
|
4816
|
-
borderRadius: tokens.borderRadiusMedium, // 4px?
|
|
4817
|
-
border: `${tokens.spacingVerticalXXS} solid ${tokens.colorNeutralShadowKeyLighter}`,
|
|
4818
|
-
"@media (forced-colors: active)": {
|
|
4819
|
-
forcedColorAdjust: "none", // ensures elmement maintains color in high constrast mode
|
|
4820
|
-
},
|
|
4821
|
-
},
|
|
4822
|
-
inputRow: {
|
|
4823
|
-
display: "flex",
|
|
4824
|
-
flexDirection: "row",
|
|
4825
|
-
flex: 1, // grow and fill available space
|
|
4826
|
-
justifyContent: "center",
|
|
4827
|
-
gap: "10px",
|
|
4828
|
-
width: "100%",
|
|
4829
|
-
},
|
|
4830
|
-
inputField: {
|
|
4831
|
-
flex: 1, // grow and fill available space
|
|
4832
|
-
width: "auto",
|
|
4833
|
-
minWidth: 0,
|
|
4834
|
-
gap: tokens.spacingVerticalSNudge, // 6px
|
|
5005
|
+
gap: tokens.spacingVerticalS, // 8px
|
|
4835
5006
|
},
|
|
4836
|
-
});
|
|
4837
|
-
const
|
|
4838
|
-
|
|
4839
|
-
const
|
|
4840
|
-
const
|
|
4841
|
-
|
|
4842
|
-
const [isLinear, setIsLinear] = useState(props.isLinearMode ?? false);
|
|
4843
|
-
const [isFloat, setFloat] = useState(false);
|
|
4844
|
-
const { size } = useContext(ToolContext);
|
|
4845
|
-
useEffect(() => {
|
|
4846
|
-
setColor(props.value); // Ensures the trigger color updates when props.value changes
|
|
4847
|
-
}, [props.value]);
|
|
4848
|
-
const handleColorPickerChange = (_, data) => {
|
|
4849
|
-
let color = Color3.FromHSV(data.color.h, data.color.s, data.color.v);
|
|
4850
|
-
if (props.value instanceof Color4) {
|
|
4851
|
-
color = Color4.FromColor3(color, data.color.a ?? 1);
|
|
4852
|
-
}
|
|
4853
|
-
handleChange(color);
|
|
4854
|
-
};
|
|
4855
|
-
const handleChange = (newColor) => {
|
|
4856
|
-
setColor(newColor);
|
|
4857
|
-
props.onChange(newColor); // Ensures the parent is notified when color changes from within colorPicker
|
|
4858
|
-
};
|
|
4859
|
-
return (jsxs(Popover, { positioning: {
|
|
4860
|
-
align: "start",
|
|
4861
|
-
overflowBoundary: document.body,
|
|
4862
|
-
autoSize: true,
|
|
4863
|
-
}, open: popoverOpen, trapFocus: true, onOpenChange: (_, data) => setPopoverOpen(data.open), children: [jsx(PopoverTrigger, { disableButtonEnhancement: true, children: jsx(ColorSwatch, { borderColor: tokens.colorNeutralShadowKeyDarker, size: size === "small" ? "extra-small" : "small", shape: "rounded", color: color.toHexString(), value: color.toHexString().slice(1) }) }), jsx(PopoverSurface, { children: jsxs("div", { className: classes.container, children: [jsxs(ColorPicker, { className: classes.colorPicker, color: rgbaToHsv(color), onColorChange: handleColorPickerChange, children: [jsx(ColorArea, { inputX: { "aria-label": "Saturation" }, inputY: { "aria-label": "Brightness" } }), jsx(ColorSlider, { "aria-label": "Hue" }), color instanceof Color4 && jsx(AlphaSlider, { "aria-label": "Alpha" })] }), jsxs("div", { className: classes.row, children: [jsx("div", { className: classes.previewColor, style: { backgroundColor: color.toHexString() } }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
|
|
4864
|
-
label: "Color Space",
|
|
4865
|
-
info: jsx(Body1, { children: "Today this is not mutable as the color space is determined by the entity. Soon we will allow swapping" }),
|
|
4866
|
-
}, options: [
|
|
4867
|
-
{ label: "Gamma", value: 0 },
|
|
4868
|
-
{ label: "Linear", value: 1 },
|
|
4869
|
-
], disabled: true, value: isLinear ? 1 : 0, onChange: (val) => setIsLinear(val === 1) }), jsx(NumberDropdown, { className: classes.inputField, infoLabel: {
|
|
4870
|
-
label: "Data Type",
|
|
4871
|
-
info: jsx(Body1, { children: "We will introduce this functionality soon!" }),
|
|
4872
|
-
}, options: [
|
|
4873
|
-
{ label: "Int", value: 0 },
|
|
4874
|
-
{ label: "Float", value: 1 },
|
|
4875
|
-
], disabled: true, value: isFloat ? 1 : 0, onChange: (val) => setFloat(val === 1) })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputRgbField, { title: "Red", value: color, rgbKey: "r", onChange: handleChange }), jsx(InputRgbField, { title: "Green", value: color, rgbKey: "g", onChange: handleChange }), jsx(InputRgbField, { title: "Blue", value: color, rgbKey: "b", onChange: handleChange }), jsx(InputAlphaField, { color: color, onChange: handleChange })] }), jsxs("div", { className: classes.inputRow, children: [jsx(InputHsvField, { title: "Hue", value: color, hsvKey: "h", max: 360, onChange: handleChange }), jsx(InputHsvField, { title: "Saturation", value: color, hsvKey: "s", max: 100, scale: 100, onChange: handleChange }), jsx(InputHsvField, { title: "Value", value: color, hsvKey: "v", max: 100, scale: 100, onChange: handleChange })] }), jsx("div", { className: classes.inputRow, children: jsx(InputHexField, { title: "Hexadecimal", linearHex: isLinear, isLinearMode: isLinear, value: color, onChange: handleChange }) })] }) })] }));
|
|
4876
|
-
};
|
|
4877
|
-
/**
|
|
4878
|
-
* Component which displays the passed in color's HEX value, either in linearSpace (if linearHex is true) or in gamma space
|
|
4879
|
-
* When the hex color is changed by user, component calculates the new Color3/4 value and calls onChange
|
|
4880
|
-
*
|
|
4881
|
-
* Component uses the isLinearMode boolean to display an informative label regarding linear / gamma space
|
|
4882
|
-
* @param props - The properties for the InputHexField component.
|
|
4883
|
-
* @returns
|
|
4884
|
-
*/
|
|
4885
|
-
const InputHexField = (props) => {
|
|
4886
|
-
const classes = useColorPickerStyles();
|
|
4887
|
-
const { title, value, onChange, linearHex, isLinearMode } = props;
|
|
4888
|
-
return (jsx(TextInput, { disabled: linearHex ? !isLinearMode : false, className: classes.inputField, value: linearHex ? value.toLinearSpace().toHexString() : value.toHexString(), validator: ValidateColorHex, onChange: (val) => (linearHex ? onChange(Color3.FromHexString(val).toGammaSpace()) : onChange(Color3.FromHexString(val))), infoLabel: title
|
|
4889
|
-
? {
|
|
4890
|
-
label: title,
|
|
4891
|
-
// If not representing a linearHex, no info is needed.
|
|
4892
|
-
info: !props.linearHex ? undefined : !isLinearMode ? ( // If representing a linear hex but we are in gammaMode, simple message explaining why linearHex is disabled
|
|
4893
|
-
jsx(Fragment, { children: " This color picker is attached to an entity whose color is stored in gamma space, so we are showing linear hex in disabled view " })) : (
|
|
4894
|
-
// If representing a linear hex and we are in linearMode, give information about how to use these hex values
|
|
4895
|
-
jsxs(Fragment, { children: ["This color picker is attached to an entity whose color is stored in linear space (ex: PBR Material), and Babylon converts the color to gamma space before rendering on screen because the human eye is best at processing colors in gamma space. We thus also want to display the color picker in gamma space so that the color chosen here will match the color seen in your entity.", jsx("br", {}), "If you want to copy/paste the HEX into your code, you can either use", jsx(Body1Strong, { children: "Color3.FromHexString(LINEAR_HEX)" }), jsx("br", {}), "or", jsx("br", {}), jsx(Body1Strong, { children: "Color3.FromHexString(GAMMA_HEX).toLinearSpace()" }), jsx("br", {}), jsx("br", {}), jsx(Link, { url: "https://doc.babylonjs.com/preparingArtForBabylon/controllingColorSpace/", value: "Read more in our docs!" })] })),
|
|
4896
|
-
}
|
|
4897
|
-
: undefined }));
|
|
4898
|
-
};
|
|
4899
|
-
const InputRgbField = (props) => {
|
|
4900
|
-
const { value, onChange, title, rgbKey } = props;
|
|
4901
|
-
const classes = useColorPickerStyles();
|
|
4902
|
-
const handleChange = useCallback((val) => {
|
|
4903
|
-
const newColor = value.clone();
|
|
4904
|
-
newColor[rgbKey] = val / 255.0; // Convert to 0-1 range
|
|
4905
|
-
onChange(newColor);
|
|
4906
|
-
}, [value, onChange, rgbKey]);
|
|
4907
|
-
return (jsx(SpinButton, { title: title, infoLabel: title ? { label: title } : undefined, className: classes.inputField, min: 0, max: 255, value: Math.round(value[rgbKey] * 255), forceInt: true, onChange: handleChange }));
|
|
4908
|
-
};
|
|
4909
|
-
function rgbaToHsv(color) {
|
|
4910
|
-
const c = new Color3(color.r, color.g, color.b);
|
|
4911
|
-
const hsv = c.toHSV();
|
|
4912
|
-
return { h: hsv.r, s: hsv.g, v: hsv.b, a: color.a };
|
|
4913
|
-
}
|
|
4914
|
-
/**
|
|
4915
|
-
* In the HSV (Hue, Saturation, Value) color model, Hue (H) ranges from 0 to 360 degrees, representing the color's position on the color wheel.
|
|
4916
|
-
* Saturation (S) ranges from 0 to 100%, indicating the intensity or purity of the color, with 0 being shades of gray and 100 being a fully saturated color.
|
|
4917
|
-
* Value (V) ranges from 0 to 100%, representing the brightness of the color, with 0 being black and 100 being the brightest.
|
|
4918
|
-
* @param props - The properties for the InputHsvField component.
|
|
4919
|
-
*/
|
|
4920
|
-
const InputHsvField = (props) => {
|
|
4921
|
-
const { value, title, hsvKey, max, onChange, scale = 1 } = props;
|
|
4922
|
-
const classes = useColorPickerStyles();
|
|
4923
|
-
const handleChange = useCallback((val) => {
|
|
4924
|
-
// Convert current color to HSV, update the new hsv value, then call onChange prop
|
|
4925
|
-
const hsv = rgbaToHsv(value);
|
|
4926
|
-
hsv[hsvKey] = val / scale;
|
|
4927
|
-
let newColor = Color3.FromHSV(hsv.h, hsv.s, hsv.v);
|
|
4928
|
-
if (value instanceof Color4) {
|
|
4929
|
-
newColor = Color4.FromColor3(newColor, value.a ?? 1);
|
|
4930
|
-
}
|
|
4931
|
-
props.onChange(newColor);
|
|
4932
|
-
}, [value, onChange, hsvKey, scale]);
|
|
4933
|
-
return (jsx(SpinButton, { infoLabel: title ? { label: title } : undefined, title: title, className: classes.inputField, min: 0, max: max, value: Math.round(rgbaToHsv(value)[hsvKey] * scale), forceInt: true, onChange: handleChange }));
|
|
5007
|
+
});
|
|
5008
|
+
const MessageBar = (props) => {
|
|
5009
|
+
MessageBar.displayName = "MessageBar";
|
|
5010
|
+
const { message, title: header, intent, docLink } = props;
|
|
5011
|
+
const classes = useClasses();
|
|
5012
|
+
return (jsx("div", { className: classes.container, children: jsx(MessageBar$1, { intent: intent, layout: "multiline", children: jsxs(MessageBarBody, { children: [jsx(MessageBarTitle, { children: header }), message, docLink && (jsxs(Fragment, { children: [" - ", jsx(Link, { url: docLink, value: "Learn More" })] }))] }) }) }));
|
|
4934
5013
|
};
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
const
|
|
4941
|
-
|
|
4942
|
-
const
|
|
4943
|
-
const
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
5014
|
+
|
|
5015
|
+
const AnimationsProperties = (props) => {
|
|
5016
|
+
const { scene, entity } = props;
|
|
5017
|
+
const animations = entity.animations ?? [];
|
|
5018
|
+
const ranges = entity.getAnimationRanges?.()?.filter((range) => !!range) ?? [];
|
|
5019
|
+
const childAnimatablesAnimations = entity.getAnimatables?.().flatMap((animatable) => animatable.animations ?? []) ?? [];
|
|
5020
|
+
animations.concat(childAnimatablesAnimations);
|
|
5021
|
+
const lastFrom = useRef(0);
|
|
5022
|
+
const lastTo = useRef(0);
|
|
5023
|
+
const lastLoop = useRef(false);
|
|
5024
|
+
const animatablesForTarget = scene.getAllAnimatablesByTarget(entity);
|
|
5025
|
+
const isPlaying = animatablesForTarget.length > 0;
|
|
5026
|
+
const mainAnimatable = isPlaying ? animatablesForTarget[0] : undefined;
|
|
5027
|
+
const animationPropertiesOverride = useProperty(mainAnimatable, "animationPropertiesOverride");
|
|
5028
|
+
if (mainAnimatable) {
|
|
5029
|
+
lastFrom.current = mainAnimatable.fromFrame;
|
|
5030
|
+
lastTo.current = mainAnimatable.toFrame;
|
|
5031
|
+
lastLoop.current = mainAnimatable.loopAnimation;
|
|
5032
|
+
}
|
|
5033
|
+
const hasAnimations = animations.length > 0 || ranges.length > 0;
|
|
5034
|
+
const currentFrame = useObservableState(useCallback(() => {
|
|
5035
|
+
return mainAnimatable ? mainAnimatable.masterFrame : (scene.getAllAnimatablesByTarget(entity)[0]?.masterFrame ?? 0);
|
|
5036
|
+
}, [scene, entity, mainAnimatable]), hasAnimations ? scene.onAfterAnimationsObservable : undefined);
|
|
5037
|
+
return (jsx(Fragment, { children: !hasAnimations ? (jsx(MessageBar, { intent: "info", title: "No Animations", message: "To modify animations, attach an animation to this node.", docLink: "https://doc.babylonjs.com/features/featuresDeepDive/animation/" })) : (jsxs(Fragment, { children: [ranges.length > 0 && (jsx(PropertyLine, { label: "Ranges", expandedContent: jsx(Fragment, { children: ranges.map((range) => {
|
|
5038
|
+
return (jsx(ButtonLine, { label: range.name, onClick: () => {
|
|
5039
|
+
scene.beginAnimation(entity, range.from, range.to, true);
|
|
5040
|
+
} }, range.name));
|
|
5041
|
+
}) }), children: jsx(Badge, { appearance: "filled", children: ranges.length }) })), animations.length > 0 && (jsxs(Fragment, { children: [jsx(PropertyLine, { label: "Animations", expandedContent: jsx(Fragment, { children: animations.map((animation, index) => {
|
|
5042
|
+
return jsx(TextPropertyLine, { label: `${index}: ${animation.name}`, value: animation.targetProperty }, animation.uniqueId);
|
|
5043
|
+
}) }), children: jsx(Badge, { appearance: "filled", children: animations.length }) }), mainAnimatable && (jsx(Fragment, { children: jsx(PropertyLine, { label: "Animation Controls", expandedContent: jsxs(Fragment, { children: [jsx(NumberInputPropertyLine, { label: "From", value: mainAnimatable.fromFrame, onChange: (value) => {
|
|
5044
|
+
scene.stopAnimation(entity);
|
|
5045
|
+
scene.beginAnimation(entity, value, mainAnimatable.toFrame, true);
|
|
5046
|
+
} }), jsx(NumberInputPropertyLine, { label: "To", value: mainAnimatable.toFrame, onChange: (value) => {
|
|
5047
|
+
scene.stopAnimation(entity);
|
|
5048
|
+
scene.beginAnimation(entity, mainAnimatable.fromFrame, value, true);
|
|
5049
|
+
} }), jsx(SwitchPropertyLine, { label: "Loop", value: mainAnimatable.loopAnimation, onChange: (value) => {
|
|
5050
|
+
for (const animatable of animatablesForTarget) {
|
|
5051
|
+
animatable.loopAnimation = value;
|
|
5052
|
+
}
|
|
5053
|
+
} }), jsx(SyncedSliderPropertyLine, { label: "Current Frame", value: currentFrame, min: mainAnimatable.fromFrame, max: mainAnimatable.toFrame, step: (mainAnimatable.toFrame - mainAnimatable.fromFrame) / 1000, onChange: (value) => {
|
|
5054
|
+
mainAnimatable.goToFrame(value);
|
|
5055
|
+
} })] }), expandByDefault: true }) })), jsx(ButtonLine, { label: isPlaying ? "Stop Animation" : "Play Animation", onClick: () => {
|
|
5056
|
+
if (isPlaying) {
|
|
5057
|
+
scene.stopAnimation(entity);
|
|
5058
|
+
}
|
|
5059
|
+
else {
|
|
5060
|
+
scene.beginAnimation(entity, lastFrom.current, lastTo.current, lastLoop.current);
|
|
5061
|
+
}
|
|
5062
|
+
} }), mainAnimatable && (ranges.length > 0 || animations.length > 0) ? (jsxs(Fragment, { children: [jsx(SwitchPropertyLine, { label: "Enable Override", value: animationPropertiesOverride != null, onChange: (value) => {
|
|
5063
|
+
if (value) {
|
|
5064
|
+
mainAnimatable.animationPropertiesOverride = new AnimationPropertiesOverride();
|
|
5065
|
+
mainAnimatable.animationPropertiesOverride.blendingSpeed = 0.05;
|
|
5066
|
+
}
|
|
5067
|
+
else {
|
|
5068
|
+
mainAnimatable.animationPropertiesOverride = undefined;
|
|
5069
|
+
}
|
|
5070
|
+
} }), jsx(Collapse, { visible: animationPropertiesOverride != null, children: jsxs("div", { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Enable Blending", target: animationPropertiesOverride, propertyKey: "enableBlending" }), jsx(BoundProperty, { component: SyncedSliderPropertyLine, label: "Blending Speed", target: animationPropertiesOverride, propertyKey: "blendingSpeed", min: 0, max: 0.1, step: 0.01 })] }) })] })) : null] }))] })) }));
|
|
4960
5071
|
};
|
|
4961
5072
|
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
if (
|
|
4977
|
-
|
|
4978
|
-
}
|
|
4979
|
-
else {
|
|
4980
|
-
newColor = color.clone();
|
|
4981
|
-
newColor[key] = value / 255;
|
|
5073
|
+
function IsAnimatable(entity) {
|
|
5074
|
+
return entity.animations !== undefined;
|
|
5075
|
+
}
|
|
5076
|
+
function IsAnimationRangeContainer(entity) {
|
|
5077
|
+
return entity.getAnimationRanges !== undefined;
|
|
5078
|
+
}
|
|
5079
|
+
function IsAnimatableContainer(entity) {
|
|
5080
|
+
return entity.getAnimatables !== undefined;
|
|
5081
|
+
}
|
|
5082
|
+
const AnimationPropertiesServiceDefinition = {
|
|
5083
|
+
friendlyName: "Animation Properties",
|
|
5084
|
+
consumes: [PropertiesServiceIdentity, SelectionServiceIdentity, SceneContextIdentity],
|
|
5085
|
+
factory: (propertiesService, selectionService, sceneContext) => {
|
|
5086
|
+
const scene = sceneContext.currentScene;
|
|
5087
|
+
if (!scene) {
|
|
5088
|
+
return undefined;
|
|
4982
5089
|
}
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
4993
|
-
|
|
5090
|
+
const animationContentRegistration = propertiesService.addSectionContent({
|
|
5091
|
+
key: "Animation Properties",
|
|
5092
|
+
predicate: (entity) => IsAnimatable(entity) || IsAnimationRangeContainer(entity) || IsAnimatableContainer(entity),
|
|
5093
|
+
content: [
|
|
5094
|
+
{
|
|
5095
|
+
section: "Animation",
|
|
5096
|
+
component: ({ context }) => jsx(AnimationsProperties, { scene: scene, entity: context }),
|
|
5097
|
+
},
|
|
5098
|
+
],
|
|
5099
|
+
});
|
|
5100
|
+
return {
|
|
5101
|
+
dispose: () => {
|
|
5102
|
+
animationContentRegistration.dispose();
|
|
5103
|
+
},
|
|
5104
|
+
};
|
|
5105
|
+
},
|
|
5106
|
+
};
|
|
4994
5107
|
|
|
4995
5108
|
const GeneralAtmosphereProperties = (props) => {
|
|
4996
5109
|
const { entity: atmosphere } = props;
|
|
@@ -5093,30 +5206,6 @@ const ArcRotateCameraBehaviorsProperties = (props) => {
|
|
|
5093
5206
|
return (jsxs(Fragment, { children: [jsx(BoundProperty, { component: SwitchPropertyLine, label: "Auto Rotation", target: camera, propertyKey: "useAutoRotationBehavior" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Bouncing", target: camera, propertyKey: "useBouncingBehavior" }), jsx(BoundProperty, { component: SwitchPropertyLine, label: "Framing", target: camera, propertyKey: "useFramingBehavior" })] }));
|
|
5094
5207
|
};
|
|
5095
5208
|
|
|
5096
|
-
const useStyles$5 = makeStyles({
|
|
5097
|
-
dropdown: {
|
|
5098
|
-
...UniformWidthStyling,
|
|
5099
|
-
},
|
|
5100
|
-
});
|
|
5101
|
-
/**
|
|
5102
|
-
* Wraps a dropdown in a property line
|
|
5103
|
-
* @param props - PropertyLineProps and DropdownProps
|
|
5104
|
-
* @returns property-line wrapped dropdown
|
|
5105
|
-
*/
|
|
5106
|
-
const DropdownPropertyLine = forwardRef((props, ref) => {
|
|
5107
|
-
DropdownPropertyLine.displayName = "DropdownPropertyLine";
|
|
5108
|
-
const classes = useStyles$5();
|
|
5109
|
-
return (jsx(PropertyLine, { ...props, ref: ref, children: jsx(Dropdown, { ...props, className: classes.dropdown }) }));
|
|
5110
|
-
});
|
|
5111
|
-
/**
|
|
5112
|
-
* Dropdown component for number values.
|
|
5113
|
-
*/
|
|
5114
|
-
const NumberDropdownPropertyLine = DropdownPropertyLine;
|
|
5115
|
-
/**
|
|
5116
|
-
* Dropdown component for string values
|
|
5117
|
-
*/
|
|
5118
|
-
const StringDropdownPropertyLine = DropdownPropertyLine;
|
|
5119
|
-
|
|
5120
5209
|
// The below conversion functions are taken from old HexLineComponent and can likely be simplified
|
|
5121
5210
|
const ConvertToHexString = (valueString) => {
|
|
5122
5211
|
while (valueString.length < 10) {
|
|
@@ -9154,32 +9243,19 @@ const UserFeedbackServiceDefinition = {
|
|
|
9154
9243
|
},
|
|
9155
9244
|
};
|
|
9156
9245
|
|
|
9157
|
-
|
|
9158
|
-
|
|
9159
|
-
|
|
9160
|
-
|
|
9161
|
-
function ShowInspector(scene, options) {
|
|
9162
|
-
_ShowInspector(scene, options ?? {});
|
|
9163
|
-
}
|
|
9164
|
-
function _ShowInspector(scene, options) {
|
|
9165
|
-
// TODO: Lots more work to do to respect all the Inspector v1 options.
|
|
9246
|
+
// TODO: The key should probably be the Canvas, because we only want to show one inspector instance per canvas.
|
|
9247
|
+
// If it is called for a different scene that is rendering to the same canvas, then we should probably
|
|
9248
|
+
// switch the inspector instance to that scene (once this is supported).
|
|
9249
|
+
const InspectorTokens = new WeakMap();
|
|
9250
|
+
function ShowInspector(scene, options = {}) {
|
|
9166
9251
|
options = {
|
|
9167
|
-
|
|
9168
|
-
showExplorer: true,
|
|
9169
|
-
showInspector: true,
|
|
9170
|
-
embedMode: false,
|
|
9171
|
-
enableClose: true,
|
|
9172
|
-
handleResize: true,
|
|
9173
|
-
enablePopup: true,
|
|
9252
|
+
autoResizeEngine: true,
|
|
9174
9253
|
...options,
|
|
9175
9254
|
};
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
}
|
|
9179
|
-
|
|
9180
|
-
return;
|
|
9181
|
-
}
|
|
9182
|
-
let parentElement = options.globalRoot ?? null;
|
|
9255
|
+
const inspectorToken = {
|
|
9256
|
+
dispose: () => { },
|
|
9257
|
+
};
|
|
9258
|
+
let parentElement = options.containerElement ?? null;
|
|
9183
9259
|
if (!parentElement) {
|
|
9184
9260
|
parentElement = scene.getEngine().getRenderingCanvas()?.parentElement ?? null;
|
|
9185
9261
|
while (parentElement) {
|
|
@@ -9192,10 +9268,12 @@ function _ShowInspector(scene, options) {
|
|
|
9192
9268
|
}
|
|
9193
9269
|
}
|
|
9194
9270
|
if (!parentElement) {
|
|
9195
|
-
return;
|
|
9271
|
+
return inspectorToken;
|
|
9196
9272
|
}
|
|
9197
|
-
|
|
9198
|
-
|
|
9273
|
+
const existingToken = InspectorTokens.get(scene);
|
|
9274
|
+
if (existingToken) {
|
|
9275
|
+
existingToken.dispose();
|
|
9276
|
+
InspectorTokens.delete(scene);
|
|
9199
9277
|
}
|
|
9200
9278
|
const disposeActions = [];
|
|
9201
9279
|
const canvasContainerDisplay = parentElement.style.display;
|
|
@@ -9249,11 +9327,10 @@ function _ShowInspector(scene, options) {
|
|
|
9249
9327
|
};
|
|
9250
9328
|
},
|
|
9251
9329
|
};
|
|
9252
|
-
if (options.
|
|
9330
|
+
if (options.autoResizeEngine) {
|
|
9253
9331
|
const observer = scene.onBeforeRenderObservable.add(() => scene.getEngine().resize());
|
|
9254
9332
|
disposeActions.push(() => observer.remove());
|
|
9255
9333
|
}
|
|
9256
|
-
if (options.showExplorer) ;
|
|
9257
9334
|
const modularTool = MakeModularTool({
|
|
9258
9335
|
containerElement: parentElement,
|
|
9259
9336
|
serviceDefinitions: [
|
|
@@ -9318,15 +9395,147 @@ function _ShowInspector(scene, options) {
|
|
|
9318
9395
|
UserFeedbackServiceDefinition,
|
|
9319
9396
|
// Adds always present "mini stats" (like fps) to the toolbar, etc.
|
|
9320
9397
|
MiniStatsServiceDefinition,
|
|
9398
|
+
// Legacy service to support custom inspectable properties on objects.
|
|
9399
|
+
LegacyInspectableObjectPropertiesServiceDefinition,
|
|
9321
9400
|
// Additional services passed in to the Inspector.
|
|
9322
9401
|
...(options.serviceDefinitions ?? []),
|
|
9323
9402
|
],
|
|
9324
9403
|
themeMode: options.themeMode,
|
|
9325
9404
|
showThemeSelector: options.showThemeSelector,
|
|
9326
9405
|
extensionFeeds: [DefaultInspectorExtensionFeed, ...(options.extensionFeeds ?? [])],
|
|
9406
|
+
layoutMode: options.layoutMode,
|
|
9327
9407
|
toolbarMode: "compact",
|
|
9328
|
-
sidePaneRemapper: options.
|
|
9329
|
-
|
|
9408
|
+
sidePaneRemapper: options.sidePaneRemapper,
|
|
9409
|
+
});
|
|
9410
|
+
disposeActions.push(() => modularTool.dispose());
|
|
9411
|
+
let disposed = false;
|
|
9412
|
+
inspectorToken.dispose = () => {
|
|
9413
|
+
if (disposed) {
|
|
9414
|
+
return;
|
|
9415
|
+
}
|
|
9416
|
+
disposeActions.reverse().forEach((dispose) => dispose());
|
|
9417
|
+
if (options.autoResizeEngine) {
|
|
9418
|
+
scene.getEngine().resize();
|
|
9419
|
+
}
|
|
9420
|
+
disposed = true;
|
|
9421
|
+
};
|
|
9422
|
+
const sceneDisposedObserver = scene.onDisposeObservable.addOnce(() => {
|
|
9423
|
+
inspectorToken.dispose();
|
|
9424
|
+
});
|
|
9425
|
+
disposeActions.push(() => sceneDisposedObserver.remove());
|
|
9426
|
+
disposeActions.push(() => {
|
|
9427
|
+
InspectorTokens.delete(scene);
|
|
9428
|
+
});
|
|
9429
|
+
return inspectorToken;
|
|
9430
|
+
}
|
|
9431
|
+
|
|
9432
|
+
function ConvertOptions(v1Options) {
|
|
9433
|
+
// Options not currently handled:
|
|
9434
|
+
// • enablePopup: Do users care about this one?
|
|
9435
|
+
// • enableClose: Currently Inspector v2 does not allow panes/tabs to be closed.
|
|
9436
|
+
// • gizmoCamera: Do users care about this one?
|
|
9437
|
+
// • skipDefaultFontLoading: Probably doesn't make sense for Inspector v2 using Fluent.
|
|
9438
|
+
// TODO:
|
|
9439
|
+
// • explorerExtensibility
|
|
9440
|
+
// • contextMenu
|
|
9441
|
+
// • contextMenuOverride
|
|
9442
|
+
v1Options = {
|
|
9443
|
+
overlay: false,
|
|
9444
|
+
showExplorer: true,
|
|
9445
|
+
showInspector: true,
|
|
9446
|
+
embedMode: false,
|
|
9447
|
+
enableClose: true,
|
|
9448
|
+
handleResize: true,
|
|
9449
|
+
enablePopup: true,
|
|
9450
|
+
...v1Options,
|
|
9451
|
+
};
|
|
9452
|
+
const serviceDefinitions = [];
|
|
9453
|
+
if (v1Options.initialTab) {
|
|
9454
|
+
const paneKey = (() => {
|
|
9455
|
+
switch (v1Options.initialTab) {
|
|
9456
|
+
case 1 /* DebugLayerTab.Debug */:
|
|
9457
|
+
return "Debug";
|
|
9458
|
+
case 2 /* DebugLayerTab.Statistics */:
|
|
9459
|
+
return "Statistics";
|
|
9460
|
+
case 4 /* DebugLayerTab.Settings */:
|
|
9461
|
+
return "Settings";
|
|
9462
|
+
case 3 /* DebugLayerTab.Tools */:
|
|
9463
|
+
return "Tools";
|
|
9464
|
+
}
|
|
9465
|
+
})();
|
|
9466
|
+
const initialTabServiceDefinition = {
|
|
9467
|
+
friendlyName: "Initial Tab Selector",
|
|
9468
|
+
consumes: [ShellServiceIdentity],
|
|
9469
|
+
factory: (shellService) => {
|
|
9470
|
+
// Just find and select the requested initial tab.
|
|
9471
|
+
shellService.sidePanes.find((pane) => pane.key === paneKey)?.select();
|
|
9472
|
+
},
|
|
9473
|
+
};
|
|
9474
|
+
serviceDefinitions.push(initialTabServiceDefinition);
|
|
9475
|
+
}
|
|
9476
|
+
if (v1Options.additionalNodes && v1Options.additionalNodes.length > 0) {
|
|
9477
|
+
const { additionalNodes } = v1Options;
|
|
9478
|
+
const additionalNodesServiceDefinition = {
|
|
9479
|
+
friendlyName: "Additional Nodes",
|
|
9480
|
+
consumes: [SceneExplorerServiceIdentity],
|
|
9481
|
+
factory: (sceneExplorerService) => {
|
|
9482
|
+
const sceneExplorerSectionRegistrations = additionalNodes.map((node) => sceneExplorerService.addSection({
|
|
9483
|
+
displayName: node.name,
|
|
9484
|
+
order: Number.MAX_SAFE_INTEGER,
|
|
9485
|
+
getRootEntities: () => {
|
|
9486
|
+
const children = node.getContent();
|
|
9487
|
+
for (const child of children) {
|
|
9488
|
+
const entity = child;
|
|
9489
|
+
if (!entity.uniqueId) {
|
|
9490
|
+
entity.uniqueId = UniqueIdGenerator.UniqueId;
|
|
9491
|
+
}
|
|
9492
|
+
}
|
|
9493
|
+
return children;
|
|
9494
|
+
},
|
|
9495
|
+
getEntityDisplayInfo: (entity) => {
|
|
9496
|
+
const onChangeObservable = new Observable();
|
|
9497
|
+
const nameHookToken = InterceptProperty(entity, "name", {
|
|
9498
|
+
afterSet: () => {
|
|
9499
|
+
onChangeObservable.notifyObservers();
|
|
9500
|
+
},
|
|
9501
|
+
});
|
|
9502
|
+
return {
|
|
9503
|
+
get name() {
|
|
9504
|
+
return entity.name;
|
|
9505
|
+
},
|
|
9506
|
+
onChange: onChangeObservable,
|
|
9507
|
+
dispose: () => {
|
|
9508
|
+
nameHookToken.dispose();
|
|
9509
|
+
onChangeObservable.clear();
|
|
9510
|
+
},
|
|
9511
|
+
};
|
|
9512
|
+
},
|
|
9513
|
+
entityIcon: () => jsx(BranchRegular, {}),
|
|
9514
|
+
getEntityAddedObservables: () => [],
|
|
9515
|
+
getEntityRemovedObservables: () => [],
|
|
9516
|
+
}));
|
|
9517
|
+
return {
|
|
9518
|
+
dispose: () => {
|
|
9519
|
+
sceneExplorerSectionRegistrations.forEach((registration) => registration.dispose());
|
|
9520
|
+
},
|
|
9521
|
+
};
|
|
9522
|
+
},
|
|
9523
|
+
};
|
|
9524
|
+
serviceDefinitions.push(additionalNodesServiceDefinition);
|
|
9525
|
+
}
|
|
9526
|
+
const v2Options = {
|
|
9527
|
+
containerElement: v1Options.globalRoot,
|
|
9528
|
+
layoutMode: v1Options.overlay ? "overlay" : "inline",
|
|
9529
|
+
autoResizeEngine: v1Options.handleResize,
|
|
9530
|
+
sidePaneRemapper: (sidePane) => {
|
|
9531
|
+
if (v1Options.showExplorer === false && sidePane.key === "Scene Explorer") {
|
|
9532
|
+
return null;
|
|
9533
|
+
}
|
|
9534
|
+
if (v1Options.showInspector === false &&
|
|
9535
|
+
(sidePane.key === "Properties" || sidePane.key === "Debug" || sidePane.key === "Statistics" || sidePane.key === "Settings" || sidePane.key === "Tools")) {
|
|
9536
|
+
return null;
|
|
9537
|
+
}
|
|
9538
|
+
if (v1Options.embedMode) {
|
|
9330
9539
|
if (sidePane.horizontalLocation === "right") {
|
|
9331
9540
|
// All right panes go to right bottom.
|
|
9332
9541
|
return {
|
|
@@ -9342,42 +9551,57 @@ function _ShowInspector(scene, options) {
|
|
|
9342
9551
|
};
|
|
9343
9552
|
}
|
|
9344
9553
|
}
|
|
9345
|
-
|
|
9346
|
-
});
|
|
9347
|
-
disposeActions.push(() => modularTool.dispose());
|
|
9348
|
-
let disposed = false;
|
|
9349
|
-
CurrentInspectorToken = {
|
|
9350
|
-
dispose: () => {
|
|
9351
|
-
if (disposed) {
|
|
9352
|
-
return;
|
|
9353
|
-
}
|
|
9354
|
-
disposeActions.reverse().forEach((dispose) => dispose());
|
|
9355
|
-
if (options.handleResize) {
|
|
9356
|
-
scene.getEngine().resize();
|
|
9357
|
-
}
|
|
9358
|
-
disposed = true;
|
|
9554
|
+
return sidePane;
|
|
9359
9555
|
},
|
|
9556
|
+
serviceDefinitions,
|
|
9360
9557
|
};
|
|
9361
|
-
|
|
9362
|
-
HideInspector();
|
|
9363
|
-
});
|
|
9364
|
-
disposeActions.push(() => sceneDisposedObserver.remove());
|
|
9365
|
-
}
|
|
9366
|
-
function HideInspector() {
|
|
9367
|
-
CurrentInspectorToken?.dispose();
|
|
9368
|
-
CurrentInspectorToken = null;
|
|
9558
|
+
return v2Options;
|
|
9369
9559
|
}
|
|
9560
|
+
/**
|
|
9561
|
+
* @deprecated This class only exists for backward compatibility. Use the module-level ShowInspector function instead.
|
|
9562
|
+
*/
|
|
9370
9563
|
class Inspector {
|
|
9564
|
+
static MarkLineContainerTitleForHighlighting(title) {
|
|
9565
|
+
throw new Error("Not Implemented");
|
|
9566
|
+
}
|
|
9567
|
+
static MarkMultipleLineContainerTitlesForHighlighting(titles) {
|
|
9568
|
+
throw new Error("Not Implemented");
|
|
9569
|
+
}
|
|
9570
|
+
static PopupEmbed() {
|
|
9571
|
+
// Show with embed mode on (stacked right panes) and undocked?
|
|
9572
|
+
throw new Error("Not Implemented");
|
|
9573
|
+
}
|
|
9574
|
+
static PopupSceneExplorer() {
|
|
9575
|
+
// Show with all right panes (not stacked), scene explorer tab selected, and undocked?
|
|
9576
|
+
throw new Error("Not Implemented");
|
|
9577
|
+
}
|
|
9578
|
+
static PopupInspector() {
|
|
9579
|
+
// Show with all right panes (not stacked), properties tab selected, and undocked?
|
|
9580
|
+
throw new Error("Not Implemented");
|
|
9581
|
+
}
|
|
9371
9582
|
static get IsVisible() {
|
|
9372
|
-
return
|
|
9583
|
+
return !!this._CurrentInspectorToken;
|
|
9373
9584
|
}
|
|
9374
9585
|
static Show(scene, userOptions) {
|
|
9375
|
-
|
|
9586
|
+
this._Show(scene, userOptions);
|
|
9587
|
+
}
|
|
9588
|
+
static _Show(scene, userOptions) {
|
|
9589
|
+
if (!scene) {
|
|
9590
|
+
scene = EngineStore.LastCreatedScene;
|
|
9591
|
+
}
|
|
9592
|
+
if (!scene || scene.isDisposed) {
|
|
9593
|
+
return;
|
|
9594
|
+
}
|
|
9595
|
+
this._CurrentInspectorToken = ShowInspector(scene, ConvertOptions(userOptions));
|
|
9376
9596
|
}
|
|
9377
9597
|
static Hide() {
|
|
9378
|
-
|
|
9598
|
+
this._CurrentInspectorToken?.dispose();
|
|
9599
|
+
this._CurrentInspectorToken = null;
|
|
9379
9600
|
}
|
|
9380
9601
|
}
|
|
9602
|
+
Inspector._CurrentInspectorToken = null;
|
|
9603
|
+
Inspector.OnSelectionChangeObservable = new Observable();
|
|
9604
|
+
Inspector.OnPropertyChangedObservable = new Observable();
|
|
9381
9605
|
|
|
9382
9606
|
const useStyles$2 = makeStyles({
|
|
9383
9607
|
root: {
|
|
@@ -9634,5 +9858,5 @@ const TextAreaPropertyLine = (props) => {
|
|
|
9634
9858
|
return (jsx(PropertyLine, { ...props, children: jsx(Textarea, { ...props }) }));
|
|
9635
9859
|
};
|
|
9636
9860
|
|
|
9637
|
-
export {
|
|
9638
|
-
//# sourceMappingURL=index-
|
|
9861
|
+
export { SettingsContextIdentity as $, useAsyncResource as A, ButtonLine as B, Collapse as C, DebugServiceIdentity as D, ExtensibleAccordion as E, FileUploadLine as F, useCompactMode as G, useSidePaneDockOverrides as H, Inspector as I, useAngleConverters as J, MakeTeachingMoment as K, Link as L, MakeLazyComponent as M, NumberDropdownPropertyLine as N, MakeDialogTeachingMoment as O, PropertiesServiceIdentity as P, InterceptFunction as Q, GetPropertyDescriptor as R, SwitchPropertyLine as S, ToolsServiceIdentity as T, IsPropertyReadonly as U, InterceptProperty as V, ObservableCollection as W, ConstructorFactory as X, SceneContextIdentity as Y, SelectionServiceIdentity as Z, SelectionServiceDefinition as _, SyncedSliderPropertyLine as a, ShowInspector as a0, AccordionSection as a1, Accordion as a2, Button as a3, Checkbox as a4, ColorPickerPopup as a5, InputHexField as a6, InputHsvField as a7, ComboBox as a8, DraggableLine as a9, Color4PropertyLine as aA, HexPropertyLine as aB, TextInputPropertyLine as aC, NumberInputPropertyLine as aD, LinkPropertyLine as aE, PropertyLine as aF, LineContainer as aG, PlaceholderPropertyLine as aH, SpinButtonPropertyLine as aI, StringifiedPropertyLine as aJ, TextAreaPropertyLine as aK, TextPropertyLine as aL, RotationVectorPropertyLine as aM, QuaternionPropertyLine as aN, Vector2PropertyLine as aO, Vector3PropertyLine as aP, Vector4PropertyLine as aQ, Dropdown as aa, NumberDropdown as ab, StringDropdown as ac, FactorGradientComponent as ad, Color3GradientComponent as ae, Color4GradientComponent as af, ColorStepGradientComponent as ag, InfoLabel as ah, List as ai, MessageBar as aj, PositionedPopover as ak, SearchBar as al, SearchBox as am, SpinButton as an, Switch as ao, SyncedSliderInput as ap, Textarea as aq, TextInput as ar, ToggleButton as as, FactorGradientList as at, Color3GradientList as au, Color4GradientList as av, Pane as aw, BooleanBadgePropertyLine as ax, CheckboxPropertyLine as ay, Color3PropertyLine as az, ShellServiceIdentity as b, MakePopoverTeachingMoment as c, TeachingMoment as d, SidePaneContainer as e, SceneExplorerServiceIdentity as f, SettingsServiceIdentity as g, StatsServiceIdentity as h, StringDropdownPropertyLine as i, BoundProperty as j, LinkToEntityPropertyLine as k, BuiltInsExtensionFeed as l, useProperty as m, useVector3Property as n, useColor3Property as o, useColor4Property as p, useQuaternionProperty as q, MakePropertyHook as r, useInterceptObservable as s, useEventfulState as t, useExtensionManager as u, useObservableState as v, useObservableCollection as w, useOrderedObservableCollection as x, usePollingObservable as y, useResource as z };
|
|
9862
|
+
//# sourceMappingURL=index-B-XOu4uI.js.map
|