@babylonjs/shared-ui-components 9.2.0 → 9.2.2
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/fluent/hoc/buttonLine.js +2 -1
- package/fluent/hoc/buttonLine.js.map +1 -1
- package/fluent/primitives/accordion.contexts.js +2 -2
- package/fluent/primitives/accordion.contexts.js.map +1 -1
- package/fluent/primitives/contextMenu.js.map +1 -1
- package/fluent/primitives/popover.js.map +1 -1
- package/fluent/primitives/tooltip.d.ts +1 -1
- package/fluent/primitives/tooltip.js.map +1 -1
- package/modularTool/components/errorBoundary.d.ts +31 -0
- package/modularTool/components/errorBoundary.js +91 -0
- package/modularTool/components/errorBoundary.js.map +1 -0
- package/modularTool/components/extensibleAccordion.d.ts +67 -0
- package/modularTool/components/extensibleAccordion.js +148 -0
- package/modularTool/components/extensibleAccordion.js.map +1 -0
- package/modularTool/components/pane.d.ts +4 -0
- package/modularTool/components/pane.js +20 -0
- package/modularTool/components/pane.js.map +1 -0
- package/modularTool/components/teachingMoment.d.ts +20 -0
- package/modularTool/components/teachingMoment.js +17 -0
- package/modularTool/components/teachingMoment.js.map +1 -0
- package/modularTool/components/theme.d.ts +10 -0
- package/modularTool/components/theme.js +24 -0
- package/modularTool/components/theme.js.map +1 -0
- package/modularTool/components/uxContextProvider.d.ts +2 -0
- package/modularTool/components/uxContextProvider.js +19 -0
- package/modularTool/components/uxContextProvider.js.map +1 -0
- package/modularTool/contexts/extensionManagerContext.d.ts +6 -0
- package/modularTool/contexts/extensionManagerContext.js +6 -0
- package/modularTool/contexts/extensionManagerContext.js.map +1 -0
- package/modularTool/contexts/settingsContext.d.ts +3 -0
- package/modularTool/contexts/settingsContext.js +6 -0
- package/modularTool/contexts/settingsContext.js.map +1 -0
- package/modularTool/extensibility/builtInsExtensionFeed.d.ts +21 -0
- package/modularTool/extensibility/builtInsExtensionFeed.js +26 -0
- package/modularTool/extensibility/builtInsExtensionFeed.js.map +1 -0
- package/modularTool/extensibility/extensionFeed.d.ts +113 -0
- package/modularTool/extensibility/extensionFeed.js +2 -0
- package/modularTool/extensibility/extensionFeed.js.map +1 -0
- package/modularTool/extensibility/extensionManager.d.ts +111 -0
- package/modularTool/extensibility/extensionManager.js +277 -0
- package/modularTool/extensibility/extensionManager.js.map +1 -0
- package/modularTool/hooks/observableHooks.d.ts +35 -0
- package/modularTool/hooks/observableHooks.js +84 -0
- package/modularTool/hooks/observableHooks.js.map +1 -0
- package/modularTool/hooks/resourceHooks.d.ts +20 -0
- package/modularTool/hooks/resourceHooks.js +101 -0
- package/modularTool/hooks/resourceHooks.js.map +1 -0
- package/modularTool/hooks/settingsHooks.d.ts +8 -0
- package/modularTool/hooks/settingsHooks.js +40 -0
- package/modularTool/hooks/settingsHooks.js.map +1 -0
- package/modularTool/hooks/teachingMomentHooks.d.ts +34 -0
- package/modularTool/hooks/teachingMomentHooks.js +89 -0
- package/modularTool/hooks/teachingMomentHooks.js.map +1 -0
- package/modularTool/hooks/themeHooks.d.ts +17 -0
- package/modularTool/hooks/themeHooks.js +38 -0
- package/modularTool/hooks/themeHooks.js.map +1 -0
- package/modularTool/hooks/useResizeHandle.d.ts +35 -0
- package/modularTool/hooks/useResizeHandle.js +75 -0
- package/modularTool/hooks/useResizeHandle.js.map +1 -0
- package/modularTool/misc/assert.d.ts +5 -0
- package/modularTool/misc/assert.js +10 -0
- package/modularTool/misc/assert.js.map +1 -0
- package/modularTool/misc/graphUtils.d.ts +44 -0
- package/modularTool/misc/graphUtils.js +90 -0
- package/modularTool/misc/graphUtils.js.map +1 -0
- package/modularTool/misc/observableCollection.d.ts +23 -0
- package/modularTool/misc/observableCollection.js +43 -0
- package/modularTool/misc/observableCollection.js.map +1 -0
- package/modularTool/modularTool.d.ts +42 -0
- package/modularTool/modularTool.js +244 -0
- package/modularTool/modularTool.js.map +1 -0
- package/modularTool/modularity/serviceContainer.d.ts +64 -0
- package/modularTool/modularity/serviceContainer.js +181 -0
- package/modularTool/modularity/serviceContainer.js.map +1 -0
- package/modularTool/modularity/serviceDefinition.d.ts +64 -0
- package/modularTool/modularity/serviceDefinition.js +11 -0
- package/modularTool/modularity/serviceDefinition.js.map +1 -0
- package/modularTool/services/extensionsListService.d.ts +3 -0
- package/modularTool/services/extensionsListService.js +202 -0
- package/modularTool/services/extensionsListService.js.map +1 -0
- package/modularTool/services/globalSettings.d.ts +3 -0
- package/modularTool/services/globalSettings.js +9 -0
- package/modularTool/services/globalSettings.js.map +1 -0
- package/modularTool/services/reactContextService.d.ts +18 -0
- package/modularTool/services/reactContextService.js +5 -0
- package/modularTool/services/reactContextService.js.map +1 -0
- package/modularTool/services/settingsService.d.ts +24 -0
- package/modularTool/services/settingsService.js +41 -0
- package/modularTool/services/settingsService.js.map +1 -0
- package/modularTool/services/settingsStore.d.ts +55 -0
- package/modularTool/services/settingsStore.js +35 -0
- package/modularTool/services/settingsStore.js.map +1 -0
- package/modularTool/services/shellService.d.ts +256 -0
- package/modularTool/services/shellService.js +729 -0
- package/modularTool/services/shellService.js.map +1 -0
- package/modularTool/services/shellSettingsService.d.ts +3 -0
- package/modularTool/services/shellSettingsService.js +35 -0
- package/modularTool/services/shellSettingsService.js.map +1 -0
- package/modularTool/services/themeSelectorService.d.ts +3 -0
- package/modularTool/services/themeSelectorService.js +42 -0
- package/modularTool/services/themeSelectorService.js.map +1 -0
- package/modularTool/services/themeService.d.ts +60 -0
- package/modularTool/services/themeService.js +69 -0
- package/modularTool/services/themeService.js.map +1 -0
- package/modularTool/services/toastService.d.ts +17 -0
- package/modularTool/services/toastService.js +5 -0
- package/modularTool/services/toastService.js.map +1 -0
- package/modularTool/themes/babylonTheme.d.ts +3 -0
- package/modularTool/themes/babylonTheme.js +36 -0
- package/modularTool/themes/babylonTheme.js.map +1 -0
- package/nodeGraphSystem/graphCanvas.js +2 -1
- package/nodeGraphSystem/graphCanvas.js.map +1 -1
- package/nodeGraphSystem/graphNode.d.ts +2 -0
- package/nodeGraphSystem/graphNode.js +5 -1
- package/nodeGraphSystem/graphNode.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
2
|
+
import { Observable } from "@babylonjs/core/Misc/observable.js";
|
|
3
|
+
import { useSettingsStore } from "../contexts/settingsContext.js";
|
|
4
|
+
import { useObservableState } from "./observableHooks.js";
|
|
5
|
+
/**
|
|
6
|
+
* Hook that reads and writes a setting from the settings store.
|
|
7
|
+
* @param descriptor The setting descriptor that identifies the setting and its default value.
|
|
8
|
+
* @returns A tuple of [currentValue, setValue, resetValue] similar to React's useState.
|
|
9
|
+
*/
|
|
10
|
+
export function useSetting(descriptor) {
|
|
11
|
+
const settingsStore = useSettingsStore();
|
|
12
|
+
// Only watch for this specific setting to change. Otherwise, any time any setting changes we would
|
|
13
|
+
// call readSetting again, which if it is an object, it will be a new instance, which can cause
|
|
14
|
+
// unnecessary re-renders in consumers of this hook.
|
|
15
|
+
const settingObservable = useMemo(() => new Observable(), []);
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (settingsStore) {
|
|
18
|
+
const observer = settingsStore.onChanged.add((key) => {
|
|
19
|
+
if (key === descriptor.key) {
|
|
20
|
+
settingObservable.notifyObservers();
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return () => {
|
|
24
|
+
observer.remove();
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}, [settingsStore, descriptor.key]);
|
|
29
|
+
const value = useObservableState(useCallback(() => settingsStore?.readSetting(descriptor) ?? descriptor.defaultValue, [settingsStore, descriptor.key, descriptor.defaultValue]), settingObservable);
|
|
30
|
+
const setValue = useCallback((newValue) => {
|
|
31
|
+
const value = typeof newValue === "function" ? newValue(settingsStore?.readSetting(descriptor) ?? descriptor.defaultValue) : newValue;
|
|
32
|
+
settingsStore?.writeSetting(descriptor, value);
|
|
33
|
+
}, [settingsStore, descriptor.key, descriptor.defaultValue]);
|
|
34
|
+
const resetValue = useCallback(() => settingsStore?.writeSetting(descriptor, descriptor.defaultValue), [settingsStore, descriptor.key, descriptor.defaultValue]);
|
|
35
|
+
if (!settingsStore) {
|
|
36
|
+
throw new Error("Settings store is not available in context.");
|
|
37
|
+
}
|
|
38
|
+
return [value, setValue, resetValue];
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=settingsHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settingsHooks.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/hooks/settingsHooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsC,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAI5F,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAI,UAAgC;IAC1D,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IAEzC,mGAAmG;IACnG,+FAA+F;IAC/F,oDAAoD;IACpD,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,IAAI,UAAU,EAAQ,EAAE,EAAE,CAAC,CAAC;IACpE,SAAS,CAAC,GAAG,EAAE;QACX,IAAI,aAAa,EAAE,CAAC;YAChB,MAAM,QAAQ,GAAG,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;gBACjD,IAAI,GAAG,KAAK,UAAU,CAAC,GAAG,EAAE,CAAC;oBACzB,iBAAiB,CAAC,eAAe,EAAE,CAAC;gBACxC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,OAAO,GAAG,EAAE;gBACR,QAAQ,CAAC,MAAM,EAAE,CAAC;YACtB,CAAC,CAAC;QACN,CAAC;QAED,OAAO,SAAS,CAAC;IACrB,CAAC,EAAE,CAAC,aAAa,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;IAEpC,MAAM,KAAK,GAAG,kBAAkB,CAC5B,WAAW,CAAC,GAAG,EAAE,CAAC,aAAa,EAAE,WAAW,CAAI,UAAU,CAAC,IAAI,UAAU,CAAC,YAAY,EAAE,CAAC,aAAa,EAAE,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC,EACjJ,iBAAiB,CACpB,CAAC;IAEF,MAAM,QAAQ,GAAG,WAAW,CACxB,CAAC,QAA2B,EAAQ,EAAE;QAClC,MAAM,KAAK,GAAG,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC,CAAE,QAA2B,CAAC,aAAa,EAAE,WAAW,CAAI,UAAU,CAAC,IAAI,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;QAC7J,aAAa,EAAE,YAAY,CAAI,UAAU,EAAE,KAAK,CAAC,CAAC;IACtD,CAAC,EACD,CAAC,aAAa,EAAE,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,YAAY,CAAC,CAC3D,CAAC;IAEF,MAAM,UAAU,GAAG,WAAW,CAAC,GAAS,EAAE,CAAC,aAAa,EAAE,YAAY,CAAI,UAAU,EAAE,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC,aAAa,EAAE,UAAU,CAAC,GAAG,EAAE,UAAU,CAAC,YAAY,CAAC,CAAC,CAAC;IAE1K,IAAI,CAAC,aAAa,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,UAAU,CAAU,CAAC;AAClD,CAAC","sourcesContent":["import { type Dispatch, type SetStateAction, useCallback, useEffect, useMemo } from \"react\";\r\n\r\nimport { type SettingDescriptor } from \"../services/settingsStore\";\r\n\r\nimport { Observable } from \"core/Misc/observable\";\r\nimport { useSettingsStore } from \"../contexts/settingsContext\";\r\nimport { useObservableState } from \"./observableHooks\";\r\n\r\n/**\r\n * Hook that reads and writes a setting from the settings store.\r\n * @param descriptor The setting descriptor that identifies the setting and its default value.\r\n * @returns A tuple of [currentValue, setValue, resetValue] similar to React's useState.\r\n */\r\nexport function useSetting<T>(descriptor: SettingDescriptor<T>): [T, Dispatch<SetStateAction<T>>, () => void] {\r\n const settingsStore = useSettingsStore();\r\n\r\n // Only watch for this specific setting to change. Otherwise, any time any setting changes we would\r\n // call readSetting again, which if it is an object, it will be a new instance, which can cause\r\n // unnecessary re-renders in consumers of this hook.\r\n const settingObservable = useMemo(() => new Observable<void>(), []);\r\n useEffect(() => {\r\n if (settingsStore) {\r\n const observer = settingsStore.onChanged.add((key) => {\r\n if (key === descriptor.key) {\r\n settingObservable.notifyObservers();\r\n }\r\n });\r\n\r\n return () => {\r\n observer.remove();\r\n };\r\n }\r\n\r\n return undefined;\r\n }, [settingsStore, descriptor.key]);\r\n\r\n const value = useObservableState(\r\n useCallback(() => settingsStore?.readSetting<T>(descriptor) ?? descriptor.defaultValue, [settingsStore, descriptor.key, descriptor.defaultValue]),\r\n settingObservable\r\n );\r\n\r\n const setValue = useCallback(\r\n (newValue: SetStateAction<T>): void => {\r\n const value = typeof newValue === \"function\" ? (newValue as (prev: T) => T)(settingsStore?.readSetting<T>(descriptor) ?? descriptor.defaultValue) : newValue;\r\n settingsStore?.writeSetting<T>(descriptor, value);\r\n },\r\n [settingsStore, descriptor.key, descriptor.defaultValue]\r\n );\r\n\r\n const resetValue = useCallback((): void => settingsStore?.writeSetting<T>(descriptor, descriptor.defaultValue), [settingsStore, descriptor.key, descriptor.defaultValue]);\r\n\r\n if (!settingsStore) {\r\n throw new Error(\"Settings store is not available in context.\");\r\n }\r\n\r\n return [value, setValue, resetValue] as const;\r\n}\r\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Nullable } from "@babylonjs/core/index.js";
|
|
2
|
+
import { type OnOpenChangeData, type PositioningImperativeRef } from "@fluentui/react-components";
|
|
3
|
+
/**
|
|
4
|
+
* Creates a hook for managing teaching moment state.
|
|
5
|
+
* @param name The unique name of the teaching moment.
|
|
6
|
+
* @returns A hook that returns the teaching moment state.
|
|
7
|
+
*/
|
|
8
|
+
export declare function MakeTeachingMoment(name: string): (suppress?: boolean) => {
|
|
9
|
+
readonly shouldDisplay: boolean;
|
|
10
|
+
readonly onDismissed: () => void;
|
|
11
|
+
readonly reset: () => void;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Creates a hook for managing teaching moment state for a dialog.
|
|
15
|
+
* @param name The unique name of the teaching moment.
|
|
16
|
+
* @returns A hook that returns the teaching moment state for a dialog.
|
|
17
|
+
*/
|
|
18
|
+
export declare function MakeDialogTeachingMoment(name: string): (suppress?: boolean) => {
|
|
19
|
+
readonly shouldDisplay: boolean;
|
|
20
|
+
readonly onOpenChange: (e: unknown, data: OnOpenChangeData) => void;
|
|
21
|
+
readonly reset: () => void;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Creates a hook for managing teaching moment state for a popover.
|
|
25
|
+
* @param name The unique name of the teaching moment.
|
|
26
|
+
* @returns A hook that returns the teaching moment state for a popover.
|
|
27
|
+
*/
|
|
28
|
+
export declare function MakePopoverTeachingMoment(name: string): (suppress?: boolean) => {
|
|
29
|
+
readonly shouldDisplay: boolean;
|
|
30
|
+
readonly positioningRef: import("react").Dispatch<import("react").SetStateAction<Nullable<PositioningImperativeRef>>>;
|
|
31
|
+
readonly targetRef: import("react").Dispatch<import("react").SetStateAction<Nullable<HTMLElement>>>;
|
|
32
|
+
readonly onOpenChange: (e: unknown, data: OnOpenChangeData) => void;
|
|
33
|
+
readonly reset: () => void;
|
|
34
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useSetting } from "./settingsHooks.js";
|
|
3
|
+
import { AsyncLock } from "@babylonjs/core/Misc/asyncLock.js";
|
|
4
|
+
import { Deferred } from "@babylonjs/core/Misc/deferred.js";
|
|
5
|
+
const SequencerLock = new AsyncLock();
|
|
6
|
+
/**
|
|
7
|
+
* Creates a hook for managing teaching moment state.
|
|
8
|
+
* @param name The unique name of the teaching moment.
|
|
9
|
+
* @returns A hook that returns the teaching moment state.
|
|
10
|
+
*/
|
|
11
|
+
export function MakeTeachingMoment(name) {
|
|
12
|
+
return (suppress) => {
|
|
13
|
+
const [hasDisplayed, setHasDisplayed, resetDisplayed] = useSetting({ key: `TeachingMoments/${name}`, defaultValue: false });
|
|
14
|
+
const [shouldDisplay, setShouldDisplay] = useState(false);
|
|
15
|
+
const deferredRef = useRef();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!hasDisplayed && !suppress && !deferredRef.current) {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
19
|
+
SequencerLock.lockAsync(async () => {
|
|
20
|
+
deferredRef.current = new Deferred();
|
|
21
|
+
setShouldDisplay(true);
|
|
22
|
+
// Just hold the lock until the hook cleanup, which is effectively component unmount (e.g. the teaching moment is dismissed).
|
|
23
|
+
await deferredRef.current.promise;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return () => {
|
|
27
|
+
deferredRef.current?.resolve();
|
|
28
|
+
};
|
|
29
|
+
}, [hasDisplayed, suppress]);
|
|
30
|
+
const onDismissed = useCallback(() => {
|
|
31
|
+
setHasDisplayed(true);
|
|
32
|
+
deferredRef.current?.resolve();
|
|
33
|
+
deferredRef.current = undefined;
|
|
34
|
+
setShouldDisplay(false);
|
|
35
|
+
}, []);
|
|
36
|
+
return {
|
|
37
|
+
shouldDisplay,
|
|
38
|
+
onDismissed,
|
|
39
|
+
reset: resetDisplayed,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a hook for managing teaching moment state for a dialog.
|
|
45
|
+
* @param name The unique name of the teaching moment.
|
|
46
|
+
* @returns A hook that returns the teaching moment state for a dialog.
|
|
47
|
+
*/
|
|
48
|
+
export function MakeDialogTeachingMoment(name) {
|
|
49
|
+
const useTeachingMoment = MakeTeachingMoment(name);
|
|
50
|
+
return (suppress) => {
|
|
51
|
+
const { shouldDisplay, onDismissed, reset } = useTeachingMoment(suppress);
|
|
52
|
+
const onOpenChange = useCallback((e, data) => {
|
|
53
|
+
if (!data.open) {
|
|
54
|
+
onDismissed();
|
|
55
|
+
}
|
|
56
|
+
}, []);
|
|
57
|
+
return {
|
|
58
|
+
shouldDisplay,
|
|
59
|
+
onOpenChange,
|
|
60
|
+
reset,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Creates a hook for managing teaching moment state for a popover.
|
|
66
|
+
* @param name The unique name of the teaching moment.
|
|
67
|
+
* @returns A hook that returns the teaching moment state for a popover.
|
|
68
|
+
*/
|
|
69
|
+
export function MakePopoverTeachingMoment(name) {
|
|
70
|
+
const useDialogTeachingMoment = MakeDialogTeachingMoment(name);
|
|
71
|
+
return (suppress) => {
|
|
72
|
+
const [target, setTarget] = useState(null);
|
|
73
|
+
const [positioningRef, setPositioningRef] = useState(null);
|
|
74
|
+
const { shouldDisplay, onOpenChange, reset } = useDialogTeachingMoment(suppress || !target || !positioningRef);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (target && positioningRef) {
|
|
77
|
+
positioningRef.setTarget(target);
|
|
78
|
+
}
|
|
79
|
+
}, [target, positioningRef]);
|
|
80
|
+
return {
|
|
81
|
+
shouldDisplay,
|
|
82
|
+
positioningRef: setPositioningRef,
|
|
83
|
+
targetRef: setTarget,
|
|
84
|
+
onOpenChange,
|
|
85
|
+
reset,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=teachingMomentHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teachingMomentHooks.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/hooks/teachingMomentHooks.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Nullable } from \"core/index\";\r\n\r\nimport { type OnOpenChangeData, type PositioningImperativeRef } from \"@fluentui/react-components\";\r\n\r\nimport { useCallback, useEffect, useRef, useState } from \"react\";\r\nimport { useSetting } from \"./settingsHooks\";\r\n\r\nimport { AsyncLock } from \"core/Misc/asyncLock\";\r\nimport { Deferred } from \"core/Misc/deferred\";\r\n\r\nconst SequencerLock = new AsyncLock();\r\n\r\n/**\r\n * Creates a hook for managing teaching moment state.\r\n * @param name The unique name of the teaching moment.\r\n * @returns A hook that returns the teaching moment state.\r\n */\r\nexport function MakeTeachingMoment(name: string) {\r\n return (suppress?: boolean) => {\r\n const [hasDisplayed, setHasDisplayed, resetDisplayed] = useSetting({ key: `TeachingMoments/${name}`, defaultValue: false });\r\n const [shouldDisplay, setShouldDisplay] = useState(false);\r\n\r\n const deferredRef = useRef<Deferred<void>>();\r\n\r\n useEffect(() => {\r\n if (!hasDisplayed && !suppress && !deferredRef.current) {\r\n // eslint-disable-next-line @typescript-eslint/no-floating-promises\r\n SequencerLock.lockAsync(async () => {\r\n deferredRef.current = new Deferred<void>();\r\n setShouldDisplay(true);\r\n // Just hold the lock until the hook cleanup, which is effectively component unmount (e.g. the teaching moment is dismissed).\r\n await deferredRef.current.promise;\r\n });\r\n }\r\n\r\n return () => {\r\n deferredRef.current?.resolve();\r\n };\r\n }, [hasDisplayed, suppress]);\r\n\r\n const onDismissed = useCallback(() => {\r\n setHasDisplayed(true);\r\n deferredRef.current?.resolve();\r\n deferredRef.current = undefined;\r\n setShouldDisplay(false);\r\n }, []);\r\n\r\n return {\r\n shouldDisplay,\r\n onDismissed,\r\n reset: resetDisplayed,\r\n } as const;\r\n };\r\n}\r\n\r\n/**\r\n * Creates a hook for managing teaching moment state for a dialog.\r\n * @param name The unique name of the teaching moment.\r\n * @returns A hook that returns the teaching moment state for a dialog.\r\n */\r\nexport function MakeDialogTeachingMoment(name: string) {\r\n const useTeachingMoment = MakeTeachingMoment(name);\r\n\r\n return (suppress?: boolean) => {\r\n const { shouldDisplay, onDismissed, reset } = useTeachingMoment(suppress);\r\n\r\n const onOpenChange = useCallback((e: unknown, data: OnOpenChangeData) => {\r\n if (!data.open) {\r\n onDismissed();\r\n }\r\n }, []);\r\n\r\n return {\r\n shouldDisplay,\r\n onOpenChange,\r\n reset,\r\n } as const;\r\n };\r\n}\r\n\r\n/**\r\n * Creates a hook for managing teaching moment state for a popover.\r\n * @param name The unique name of the teaching moment.\r\n * @returns A hook that returns the teaching moment state for a popover.\r\n */\r\nexport function MakePopoverTeachingMoment(name: string) {\r\n const useDialogTeachingMoment = MakeDialogTeachingMoment(name);\r\n\r\n return (suppress?: boolean) => {\r\n const [target, setTarget] = useState<Nullable<HTMLElement>>(null);\r\n const [positioningRef, setPositioningRef] = useState<Nullable<PositioningImperativeRef>>(null);\r\n\r\n const { shouldDisplay, onOpenChange, reset } = useDialogTeachingMoment(suppress || !target || !positioningRef);\r\n\r\n useEffect(() => {\r\n if (target && positioningRef) {\r\n positioningRef.setTarget(target);\r\n }\r\n }, [target, positioningRef]);\r\n\r\n return {\r\n shouldDisplay,\r\n positioningRef: setPositioningRef,\r\n targetRef: setTarget,\r\n onOpenChange,\r\n reset,\r\n } as const;\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ThemeMode } from "../services/themeService.js";
|
|
2
|
+
/**
|
|
3
|
+
* Hook that provides the current theme mode state and controls for changing it.
|
|
4
|
+
* @returns An object with the current dark mode state, theme mode, and functions to set or toggle the theme.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useThemeMode(): {
|
|
7
|
+
readonly isDarkMode: boolean;
|
|
8
|
+
readonly themeMode: ThemeMode;
|
|
9
|
+
readonly setThemeMode: (mode: ThemeMode) => void;
|
|
10
|
+
readonly toggleThemeMode: () => void | undefined;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Hook that returns the current Fluent UI theme based on the active theme mode.
|
|
14
|
+
* @param invert If true, inverts the theme (returns light theme in dark mode and vice versa). Defaults to false.
|
|
15
|
+
* @returns The current Fluent UI theme object.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useTheme(invert?: boolean): import("@fluentui/tokens").Theme;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ThemeModeSettingDescriptor, ThemeResolver } from "../services/themeService.js";
|
|
2
|
+
import { useCallback } from "react";
|
|
3
|
+
import { useSettingsStore } from "../contexts/settingsContext.js";
|
|
4
|
+
import { DarkTheme, LightTheme } from "../themes/babylonTheme.js";
|
|
5
|
+
import { useObservableState } from "./observableHooks.js";
|
|
6
|
+
import { useResource } from "./resourceHooks.js";
|
|
7
|
+
/**
|
|
8
|
+
* Hook that provides the current theme mode state and controls for changing it.
|
|
9
|
+
* @returns An object with the current dark mode state, theme mode, and functions to set or toggle the theme.
|
|
10
|
+
*/
|
|
11
|
+
export function useThemeMode() {
|
|
12
|
+
const settingsStore = useSettingsStore();
|
|
13
|
+
const themeResolver = useResource(useCallback(() => (settingsStore ? new ThemeResolver(settingsStore) : undefined), [settingsStore]));
|
|
14
|
+
const state = useObservableState(useCallback(() => ({
|
|
15
|
+
isDarkMode: themeResolver?.isDark ?? false,
|
|
16
|
+
themeMode: themeResolver?.mode ?? ThemeModeSettingDescriptor.defaultValue,
|
|
17
|
+
setThemeMode: (mode) => {
|
|
18
|
+
if (themeResolver) {
|
|
19
|
+
themeResolver.mode = mode;
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
toggleThemeMode: () => themeResolver?.toggle(),
|
|
23
|
+
}), [themeResolver]), themeResolver?.onChanged);
|
|
24
|
+
if (!themeResolver) {
|
|
25
|
+
throw new Error("Settings store is not available in context.");
|
|
26
|
+
}
|
|
27
|
+
return state;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hook that returns the current Fluent UI theme based on the active theme mode.
|
|
31
|
+
* @param invert If true, inverts the theme (returns light theme in dark mode and vice versa). Defaults to false.
|
|
32
|
+
* @returns The current Fluent UI theme object.
|
|
33
|
+
*/
|
|
34
|
+
export function useTheme(invert = false) {
|
|
35
|
+
const { isDarkMode } = useThemeMode();
|
|
36
|
+
return isDarkMode !== invert ? DarkTheme : LightTheme;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=themeHooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"themeHooks.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/hooks/themeHooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,0BAA0B,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAErG,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C;;;GAGG;AACH,MAAM,UAAU,YAAY;IACxB,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IACzC,MAAM,aAAa,GAAG,WAAW,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAEtI,MAAM,KAAK,GAAG,kBAAkB,CAC5B,WAAW,CACP,GAAG,EAAE,CACD,CAAC;QACG,UAAU,EAAE,aAAa,EAAE,MAAM,IAAI,KAAK;QAC1C,SAAS,EAAE,aAAa,EAAE,IAAI,IAAI,0BAA0B,CAAC,YAAY;QACzE,YAAY,EAAE,CAAC,IAAe,EAAE,EAAE;YAC9B,IAAI,aAAa,EAAE,CAAC;gBAChB,aAAa,CAAC,IAAI,GAAG,IAAI,CAAC;YAC9B,CAAC;QACL,CAAC;QACD,eAAe,EAAE,GAAG,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE;KACjD,CAAU,EACf,CAAC,aAAa,CAAC,CAClB,EACD,aAAa,EAAE,SAAS,CAC3B,CAAC;IAEF,IAAI,CAAC,aAAa,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACnE,CAAC;IAED,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAM,GAAG,KAAK;IACnC,MAAM,EAAE,UAAU,EAAE,GAAG,YAAY,EAAE,CAAC;IACtC,OAAO,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;AAC1D,CAAC","sourcesContent":["import { type ThemeMode, ThemeModeSettingDescriptor, ThemeResolver } from \"../services/themeService\";\r\n\r\nimport { useCallback } from \"react\";\r\n\r\nimport { useSettingsStore } from \"../contexts/settingsContext\";\r\nimport { DarkTheme, LightTheme } from \"../themes/babylonTheme\";\r\nimport { useObservableState } from \"./observableHooks\";\r\nimport { useResource } from \"./resourceHooks\";\r\n\r\n/**\r\n * Hook that provides the current theme mode state and controls for changing it.\r\n * @returns An object with the current dark mode state, theme mode, and functions to set or toggle the theme.\r\n */\r\nexport function useThemeMode() {\r\n const settingsStore = useSettingsStore();\r\n const themeResolver = useResource(useCallback(() => (settingsStore ? new ThemeResolver(settingsStore) : undefined), [settingsStore]));\r\n\r\n const state = useObservableState(\r\n useCallback(\r\n () =>\r\n ({\r\n isDarkMode: themeResolver?.isDark ?? false,\r\n themeMode: themeResolver?.mode ?? ThemeModeSettingDescriptor.defaultValue,\r\n setThemeMode: (mode: ThemeMode) => {\r\n if (themeResolver) {\r\n themeResolver.mode = mode;\r\n }\r\n },\r\n toggleThemeMode: () => themeResolver?.toggle(),\r\n }) as const,\r\n [themeResolver]\r\n ),\r\n themeResolver?.onChanged\r\n );\r\n\r\n if (!themeResolver) {\r\n throw new Error(\"Settings store is not available in context.\");\r\n }\r\n\r\n return state;\r\n}\r\n\r\n/**\r\n * Hook that returns the current Fluent UI theme based on the active theme mode.\r\n * @param invert If true, inverts the theme (returns light theme in dark mode and vice versa). Defaults to false.\r\n * @returns The current Fluent UI theme object.\r\n */\r\nexport function useTheme(invert = false) {\r\n const { isDarkMode } = useThemeMode();\r\n return isDarkMode !== invert ? DarkTheme : LightTheme;\r\n}\r\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type GrowDirection = "end" | "start" | "up" | "down";
|
|
2
|
+
export type UseResizeHandleParams = {
|
|
3
|
+
/**
|
|
4
|
+
* The direction in which the element is considered growing in size ('end', 'start', 'up', or 'down').
|
|
5
|
+
*/
|
|
6
|
+
growDirection: GrowDirection;
|
|
7
|
+
/**
|
|
8
|
+
* The name of the CSS variable that will be set on the wrapper element to reflect the current size of the element.
|
|
9
|
+
*/
|
|
10
|
+
variableName: string;
|
|
11
|
+
/**
|
|
12
|
+
* A callback that will be called when the element is resized.
|
|
13
|
+
*
|
|
14
|
+
* @remarks The passed function should be memoized for better performance.
|
|
15
|
+
*/
|
|
16
|
+
onChange?: (value: number) => void;
|
|
17
|
+
/**
|
|
18
|
+
* The minimum change allowed (e.g. the smallest negative number allowed).
|
|
19
|
+
*/
|
|
20
|
+
minValue?: number;
|
|
21
|
+
/**
|
|
22
|
+
* The maximum change allowed (e.g. the largest positive number allowed).
|
|
23
|
+
*/
|
|
24
|
+
maxValue?: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* A custom hook that helps with element resizing.
|
|
28
|
+
* @param params The parameters for the resize handle.
|
|
29
|
+
* @returns An object containing refs and a function to set the value.
|
|
30
|
+
*/
|
|
31
|
+
export declare function useResizeHandle(params: UseResizeHandleParams): {
|
|
32
|
+
elementRef: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
|
|
33
|
+
handleRef: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
|
|
34
|
+
setValue: (value: number) => void;
|
|
35
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// NOTE: This is basically a super simplified version of https://github.com/microsoft/fluentui-contrib/blob/main/packages/react-resize-handle/src/hooks
|
|
2
|
+
// This version does not support keyboard interactions, absolute values, automatically clamping to the actual adjusted size, accessibility, and various other features.
|
|
3
|
+
// We can switch back to Fluent's implementation once some known bugs are fixed:
|
|
4
|
+
// 1. https://github.com/microsoft/fluentui-contrib/issues/523
|
|
5
|
+
// 2. React 19 compatibility
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
|
+
import { Clamp } from "@babylonjs/core/Maths/math.scalar.functions.js";
|
|
8
|
+
/**
|
|
9
|
+
* A custom hook that helps with element resizing.
|
|
10
|
+
* @param params The parameters for the resize handle.
|
|
11
|
+
* @returns An object containing refs and a function to set the value.
|
|
12
|
+
*/
|
|
13
|
+
export function useResizeHandle(params) {
|
|
14
|
+
const { growDirection, variableName, onChange } = params;
|
|
15
|
+
const valueRef = useRef(0);
|
|
16
|
+
const [elementRef, setElementRef] = useState(null);
|
|
17
|
+
const [handleRef, setHandleRef] = useState(null);
|
|
18
|
+
const updateElementStyle = useCallback((value) => {
|
|
19
|
+
if (elementRef) {
|
|
20
|
+
elementRef.style.setProperty(variableName, `${value}px`);
|
|
21
|
+
}
|
|
22
|
+
}, [elementRef, variableName]);
|
|
23
|
+
const setValue = useCallback((value) => {
|
|
24
|
+
valueRef.current = value;
|
|
25
|
+
updateElementStyle(value);
|
|
26
|
+
onChange?.(value);
|
|
27
|
+
}, [updateElementStyle, onChange]);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
updateElementStyle(valueRef.current);
|
|
30
|
+
}, [updateElementStyle]);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (handleRef) {
|
|
33
|
+
let delta = 0;
|
|
34
|
+
const coerceDelta = (delta) => Clamp(delta, params.minValue ?? -Infinity, params.maxValue ?? Infinity);
|
|
35
|
+
const onPointerMove = (event) => {
|
|
36
|
+
event.preventDefault();
|
|
37
|
+
switch (growDirection) {
|
|
38
|
+
case "up":
|
|
39
|
+
delta -= event.movementY;
|
|
40
|
+
break;
|
|
41
|
+
case "down":
|
|
42
|
+
delta += event.movementY;
|
|
43
|
+
break;
|
|
44
|
+
case "start":
|
|
45
|
+
delta -= event.movementX;
|
|
46
|
+
break;
|
|
47
|
+
case "end":
|
|
48
|
+
delta += event.movementX;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
setValue(coerceDelta(delta));
|
|
52
|
+
};
|
|
53
|
+
const onPointerDown = (event) => {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
delta = valueRef.current;
|
|
56
|
+
handleRef.setPointerCapture(event.pointerId);
|
|
57
|
+
handleRef.addEventListener("pointermove", onPointerMove);
|
|
58
|
+
};
|
|
59
|
+
const onPointerUp = (event) => {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
handleRef.releasePointerCapture(event.pointerId);
|
|
62
|
+
handleRef.removeEventListener("pointermove", onPointerMove);
|
|
63
|
+
valueRef.current = coerceDelta(valueRef.current);
|
|
64
|
+
};
|
|
65
|
+
handleRef.addEventListener("pointerdown", onPointerDown);
|
|
66
|
+
handleRef.addEventListener("pointerup", onPointerUp);
|
|
67
|
+
}
|
|
68
|
+
}, [handleRef, setValue]);
|
|
69
|
+
return {
|
|
70
|
+
elementRef: setElementRef,
|
|
71
|
+
handleRef: setHandleRef,
|
|
72
|
+
setValue,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=useResizeHandle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResizeHandle.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/hooks/useResizeHandle.ts"],"names":[],"mappings":"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","sourcesContent":["// NOTE: This is basically a super simplified version of https://github.com/microsoft/fluentui-contrib/blob/main/packages/react-resize-handle/src/hooks\r\n// This version does not support keyboard interactions, absolute values, automatically clamping to the actual adjusted size, accessibility, and various other features.\r\n// We can switch back to Fluent's implementation once some known bugs are fixed:\r\n// 1. https://github.com/microsoft/fluentui-contrib/issues/523\r\n// 2. React 19 compatibility\r\n\r\nimport { useCallback, useEffect, useRef, useState } from \"react\";\r\n\r\nimport { Clamp } from \"core/Maths/math.scalar.functions\";\r\n\r\nexport type GrowDirection = \"end\" | \"start\" | \"up\" | \"down\";\r\n\r\nexport type UseResizeHandleParams = {\r\n /**\r\n * The direction in which the element is considered growing in size ('end', 'start', 'up', or 'down').\r\n */\r\n growDirection: GrowDirection;\r\n /**\r\n * The name of the CSS variable that will be set on the wrapper element to reflect the current size of the element.\r\n */\r\n variableName: string;\r\n /**\r\n * A callback that will be called when the element is resized.\r\n *\r\n * @remarks The passed function should be memoized for better performance.\r\n */\r\n onChange?: (value: number) => void;\r\n /**\r\n * The minimum change allowed (e.g. the smallest negative number allowed).\r\n */\r\n minValue?: number;\r\n /**\r\n * The maximum change allowed (e.g. the largest positive number allowed).\r\n */\r\n maxValue?: number;\r\n};\r\n\r\n/**\r\n * A custom hook that helps with element resizing.\r\n * @param params The parameters for the resize handle.\r\n * @returns An object containing refs and a function to set the value.\r\n */\r\nexport function useResizeHandle(params: UseResizeHandleParams) {\r\n const { growDirection, variableName, onChange } = params;\r\n\r\n const valueRef = useRef(0);\r\n const [elementRef, setElementRef] = useState<HTMLElement | null>(null);\r\n const [handleRef, setHandleRef] = useState<HTMLElement | null>(null);\r\n\r\n const updateElementStyle = useCallback(\r\n (value: number) => {\r\n if (elementRef) {\r\n elementRef.style.setProperty(variableName, `${value}px`);\r\n }\r\n },\r\n [elementRef, variableName]\r\n );\r\n\r\n const setValue = useCallback(\r\n (value: number) => {\r\n valueRef.current = value;\r\n updateElementStyle(value);\r\n onChange?.(value);\r\n },\r\n [updateElementStyle, onChange]\r\n );\r\n\r\n useEffect(() => {\r\n updateElementStyle(valueRef.current);\r\n }, [updateElementStyle]);\r\n\r\n useEffect(() => {\r\n if (handleRef) {\r\n let delta = 0;\r\n\r\n const coerceDelta = (delta: number) => Clamp(delta, params.minValue ?? -Infinity, params.maxValue ?? Infinity);\r\n\r\n const onPointerMove = (event: PointerEvent) => {\r\n event.preventDefault();\r\n switch (growDirection) {\r\n case \"up\":\r\n delta -= event.movementY;\r\n break;\r\n case \"down\":\r\n delta += event.movementY;\r\n break;\r\n case \"start\":\r\n delta -= event.movementX;\r\n break;\r\n case \"end\":\r\n delta += event.movementX;\r\n break;\r\n }\r\n setValue(coerceDelta(delta));\r\n };\r\n\r\n const onPointerDown = (event: PointerEvent) => {\r\n event.preventDefault();\r\n delta = valueRef.current;\r\n handleRef.setPointerCapture(event.pointerId);\r\n handleRef.addEventListener(\"pointermove\", onPointerMove);\r\n };\r\n\r\n const onPointerUp = (event: PointerEvent) => {\r\n event.preventDefault();\r\n handleRef.releasePointerCapture(event.pointerId);\r\n handleRef.removeEventListener(\"pointermove\", onPointerMove);\r\n valueRef.current = coerceDelta(valueRef.current);\r\n };\r\n\r\n handleRef.addEventListener(\"pointerdown\", onPointerDown);\r\n handleRef.addEventListener(\"pointerup\", onPointerUp);\r\n }\r\n }, [handleRef, setValue]);\r\n\r\n return {\r\n elementRef: setElementRef,\r\n handleRef: setHandleRef,\r\n setValue,\r\n };\r\n}\r\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assert.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/misc/assert.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,KAAc;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACxC,CAAC;AACL,CAAC","sourcesContent":["/**\r\n * Asserts that the given value is truthy.\r\n * @param value The value to check.\r\n */\r\nexport function Assert(value: unknown): asserts value {\r\n if (!value) {\r\n throw new Error(\"assertion failed\");\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs a topological sort on a graph.
|
|
3
|
+
* @param graph The set of nodes that make up the graph.
|
|
4
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
5
|
+
* @param onSortedNode A function that is called for each node in the sorted order.
|
|
6
|
+
* @remarks
|
|
7
|
+
* This function allocates. Do not use it in the hot path. Instead use an instance of GraphUtils.
|
|
8
|
+
*/
|
|
9
|
+
export declare function SortGraph<NodeT>(graph: Iterable<NodeT>, getAdjacentNodes: (node: NodeT) => Iterable<NodeT>, onSortedNode: (node: NodeT) => void): void;
|
|
10
|
+
/**
|
|
11
|
+
* Traverses a graph.
|
|
12
|
+
* @param graph The set of nodes that make up the graph.
|
|
13
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
14
|
+
* @param onBeforeTraverse A function that is called before traversing each node.
|
|
15
|
+
* @param onAfterTraverse A function that is called after traversing each node.
|
|
16
|
+
* @remarks
|
|
17
|
+
* This function allocates. Do not use it in the hot path. Instead use an instance of GraphUtils.
|
|
18
|
+
*/
|
|
19
|
+
export declare function TraverseGraph<NodeT>(graph: Iterable<NodeT>, getAdjacentNodes: (node: NodeT) => Iterable<NodeT> | null | undefined, onBeforeTraverse?: (node: NodeT) => void, onAfterTraverse?: (node: NodeT) => void): void;
|
|
20
|
+
/**
|
|
21
|
+
* A utility class for performing graph operations.
|
|
22
|
+
* @remarks
|
|
23
|
+
* The class allocates new objects, but each operation (e.g. sort, traverse) is allocation free. This is useful when used in the hot path.
|
|
24
|
+
*/
|
|
25
|
+
export declare class GraphUtils<DefaultNodeT = unknown> {
|
|
26
|
+
private readonly _traversalState;
|
|
27
|
+
private _isTraversing;
|
|
28
|
+
/**
|
|
29
|
+
* Performs a topological sort on a graph.
|
|
30
|
+
* @param graph The set of nodes that make up the graph.
|
|
31
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
32
|
+
* @param onSortedNode A function that is called for each node in the sorted order.
|
|
33
|
+
*/
|
|
34
|
+
sort<NodeT extends DefaultNodeT>(graph: Iterable<NodeT>, getAdjacentNodes: (node: NodeT) => Iterable<NodeT>, onSortedNode: (node: NodeT) => void): void;
|
|
35
|
+
/**
|
|
36
|
+
* Traverses a graph.
|
|
37
|
+
* @param graph The set of nodes that make up the graph.
|
|
38
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
39
|
+
* @param onBeforeTraverse A function that is called before traversing each node.
|
|
40
|
+
* @param onAfterTraverse A function that is called after traversing each node.
|
|
41
|
+
*/
|
|
42
|
+
traverse<NodeT extends DefaultNodeT>(graph: Iterable<NodeT>, getAdjacentNodes: (node: NodeT) => Iterable<NodeT> | null | undefined, onBeforeTraverse?: (node: NodeT) => void, onAfterTraverse?: (node: NodeT) => void): void;
|
|
43
|
+
private _traverseCore;
|
|
44
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Performs a topological sort on a graph.
|
|
3
|
+
* @param graph The set of nodes that make up the graph.
|
|
4
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
5
|
+
* @param onSortedNode A function that is called for each node in the sorted order.
|
|
6
|
+
* @remarks
|
|
7
|
+
* This function allocates. Do not use it in the hot path. Instead use an instance of GraphUtils.
|
|
8
|
+
*/
|
|
9
|
+
export function SortGraph(graph, getAdjacentNodes, onSortedNode) {
|
|
10
|
+
const sorter = new GraphUtils();
|
|
11
|
+
sorter.sort(graph, getAdjacentNodes, onSortedNode);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Traverses a graph.
|
|
15
|
+
* @param graph The set of nodes that make up the graph.
|
|
16
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
17
|
+
* @param onBeforeTraverse A function that is called before traversing each node.
|
|
18
|
+
* @param onAfterTraverse A function that is called after traversing each node.
|
|
19
|
+
* @remarks
|
|
20
|
+
* This function allocates. Do not use it in the hot path. Instead use an instance of GraphUtils.
|
|
21
|
+
*/
|
|
22
|
+
export function TraverseGraph(graph, getAdjacentNodes, onBeforeTraverse, onAfterTraverse) {
|
|
23
|
+
const traverser = new GraphUtils();
|
|
24
|
+
traverser.traverse(graph, getAdjacentNodes, onBeforeTraverse, onAfterTraverse);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A utility class for performing graph operations.
|
|
28
|
+
* @remarks
|
|
29
|
+
* The class allocates new objects, but each operation (e.g. sort, traverse) is allocation free. This is useful when used in the hot path.
|
|
30
|
+
*/
|
|
31
|
+
export class GraphUtils {
|
|
32
|
+
constructor() {
|
|
33
|
+
// Tracks three states:
|
|
34
|
+
// 1. No entry for the node - this means the node has not been encountered yet during any traversal
|
|
35
|
+
// 2. Entry with value false - this means the node is currently being traversed (needed to detect cycles)
|
|
36
|
+
// 3. Entry with value true - this means the node has already been fully traversed (and cycles were not detected)
|
|
37
|
+
this._traversalState = new Map();
|
|
38
|
+
this._isTraversing = false;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Performs a topological sort on a graph.
|
|
42
|
+
* @param graph The set of nodes that make up the graph.
|
|
43
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
44
|
+
* @param onSortedNode A function that is called for each node in the sorted order.
|
|
45
|
+
*/
|
|
46
|
+
sort(graph, getAdjacentNodes, onSortedNode) {
|
|
47
|
+
this.traverse(graph, getAdjacentNodes, undefined, onSortedNode);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Traverses a graph.
|
|
51
|
+
* @param graph The set of nodes that make up the graph.
|
|
52
|
+
* @param getAdjacentNodes A function that returns the adjacent nodes for a given node.
|
|
53
|
+
* @param onBeforeTraverse A function that is called before traversing each node.
|
|
54
|
+
* @param onAfterTraverse A function that is called after traversing each node.
|
|
55
|
+
*/
|
|
56
|
+
traverse(graph, getAdjacentNodes, onBeforeTraverse, onAfterTraverse) {
|
|
57
|
+
// Since the traversal state is re-used, disallow re-entrancy through the getAdjacentNodes or onBeforeTraverse or onAfterTraverse callbacks.
|
|
58
|
+
if (this._isTraversing) {
|
|
59
|
+
throw new Error("This TopologicalSorter instance is already traversing.");
|
|
60
|
+
}
|
|
61
|
+
this._isTraversing = true;
|
|
62
|
+
try {
|
|
63
|
+
for (const node of graph) {
|
|
64
|
+
this._traverseCore(node, getAdjacentNodes, onBeforeTraverse, onAfterTraverse);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
this._isTraversing = false;
|
|
69
|
+
this._traversalState.clear();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
_traverseCore(node, getAdjacentNodes, onBeforeTraverse, onAfterTraverse) {
|
|
73
|
+
if (this._traversalState.get(node) !== true) {
|
|
74
|
+
if (this._traversalState.get(node) === false) {
|
|
75
|
+
throw new Error("Graph has cycle.");
|
|
76
|
+
}
|
|
77
|
+
this._traversalState.set(node, false);
|
|
78
|
+
onBeforeTraverse?.(node);
|
|
79
|
+
const adjacentNodes = getAdjacentNodes(node);
|
|
80
|
+
if (adjacentNodes) {
|
|
81
|
+
for (const adjacentNode of adjacentNodes) {
|
|
82
|
+
this._traverseCore(adjacentNode, getAdjacentNodes, onBeforeTraverse, onAfterTraverse);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
this._traversalState.set(node, true);
|
|
86
|
+
onAfterTraverse?.(node);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=graphUtils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graphUtils.js","sourceRoot":"","sources":["../../../../../dev/sharedUiComponents/src/modularTool/misc/graphUtils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAQ,KAAsB,EAAE,gBAAkD,EAAE,YAAmC;IAC5I,MAAM,MAAM,GAAG,IAAI,UAAU,EAAS,CAAC;IACvC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,gBAAgB,EAAE,YAAY,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CACzB,KAAsB,EACtB,gBAAqE,EACrE,gBAAwC,EACxC,eAAuC;IAEvC,MAAM,SAAS,GAAG,IAAI,UAAU,EAAS,CAAC;IAC1C,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,CAAC,CAAC;AACnF,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,UAAU;IAAvB;QACI,uBAAuB;QACvB,mGAAmG;QACnG,yGAAyG;QACzG,iHAAiH;QAChG,oBAAe,GAAG,IAAI,GAAG,EAAyB,CAAC;QAC5D,kBAAa,GAAG,KAAK,CAAC;IAmElC,CAAC;IAjEG;;;;;OAKG;IACI,IAAI,CAA6B,KAAsB,EAAE,gBAAkD,EAAE,YAAmC;QACnJ,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;IACpE,CAAC;IAED;;;;;;OAMG;IACI,QAAQ,CACX,KAAsB,EACtB,gBAAqE,EACrE,gBAAwC,EACxC,eAAuC;QAEvC,4IAA4I;QAC5I,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC9E,CAAC;QAED,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAE1B,IAAI,CAAC;YACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACvB,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,CAAC,CAAC;YAClF,CAAC;QACL,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;YAC3B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QACjC,CAAC;IACL,CAAC;IAEO,aAAa,CACjB,IAAW,EACX,gBAAqE,EACrE,gBAAwC,EACxC,eAAuC;QAEvC,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YAC1C,IAAI,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;YACxC,CAAC;YAED,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACtC,gBAAgB,EAAE,CAAC,IAAI,CAAC,CAAC;YAEzB,MAAM,aAAa,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAC7C,IAAI,aAAa,EAAE,CAAC;gBAChB,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;oBACvC,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,eAAe,CAAC,CAAC;gBAC1F,CAAC;YACL,CAAC;YAED,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACrC,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACL,CAAC;CACJ","sourcesContent":["/**\r\n * Performs a topological sort on a graph.\r\n * @param graph The set of nodes that make up the graph.\r\n * @param getAdjacentNodes A function that returns the adjacent nodes for a given node.\r\n * @param onSortedNode A function that is called for each node in the sorted order.\r\n * @remarks\r\n * This function allocates. Do not use it in the hot path. Instead use an instance of GraphUtils.\r\n */\r\nexport function SortGraph<NodeT>(graph: Iterable<NodeT>, getAdjacentNodes: (node: NodeT) => Iterable<NodeT>, onSortedNode: (node: NodeT) => void) {\r\n const sorter = new GraphUtils<NodeT>();\r\n sorter.sort(graph, getAdjacentNodes, onSortedNode);\r\n}\r\n\r\n/**\r\n * Traverses a graph.\r\n * @param graph The set of nodes that make up the graph.\r\n * @param getAdjacentNodes A function that returns the adjacent nodes for a given node.\r\n * @param onBeforeTraverse A function that is called before traversing each node.\r\n * @param onAfterTraverse A function that is called after traversing each node.\r\n * @remarks\r\n * This function allocates. Do not use it in the hot path. Instead use an instance of GraphUtils.\r\n */\r\nexport function TraverseGraph<NodeT>(\r\n graph: Iterable<NodeT>,\r\n getAdjacentNodes: (node: NodeT) => Iterable<NodeT> | null | undefined,\r\n onBeforeTraverse?: (node: NodeT) => void,\r\n onAfterTraverse?: (node: NodeT) => void\r\n) {\r\n const traverser = new GraphUtils<NodeT>();\r\n traverser.traverse(graph, getAdjacentNodes, onBeforeTraverse, onAfterTraverse);\r\n}\r\n\r\n/**\r\n * A utility class for performing graph operations.\r\n * @remarks\r\n * The class allocates new objects, but each operation (e.g. sort, traverse) is allocation free. This is useful when used in the hot path.\r\n */\r\nexport class GraphUtils<DefaultNodeT = unknown> {\r\n // Tracks three states:\r\n // 1. No entry for the node - this means the node has not been encountered yet during any traversal\r\n // 2. Entry with value false - this means the node is currently being traversed (needed to detect cycles)\r\n // 3. Entry with value true - this means the node has already been fully traversed (and cycles were not detected)\r\n private readonly _traversalState = new Map<DefaultNodeT, boolean>();\r\n private _isTraversing = false;\r\n\r\n /**\r\n * Performs a topological sort on a graph.\r\n * @param graph The set of nodes that make up the graph.\r\n * @param getAdjacentNodes A function that returns the adjacent nodes for a given node.\r\n * @param onSortedNode A function that is called for each node in the sorted order.\r\n */\r\n public sort<NodeT extends DefaultNodeT>(graph: Iterable<NodeT>, getAdjacentNodes: (node: NodeT) => Iterable<NodeT>, onSortedNode: (node: NodeT) => void) {\r\n this.traverse(graph, getAdjacentNodes, undefined, onSortedNode);\r\n }\r\n\r\n /**\r\n * Traverses a graph.\r\n * @param graph The set of nodes that make up the graph.\r\n * @param getAdjacentNodes A function that returns the adjacent nodes for a given node.\r\n * @param onBeforeTraverse A function that is called before traversing each node.\r\n * @param onAfterTraverse A function that is called after traversing each node.\r\n */\r\n public traverse<NodeT extends DefaultNodeT>(\r\n graph: Iterable<NodeT>,\r\n getAdjacentNodes: (node: NodeT) => Iterable<NodeT> | null | undefined,\r\n onBeforeTraverse?: (node: NodeT) => void,\r\n onAfterTraverse?: (node: NodeT) => void\r\n ) {\r\n // Since the traversal state is re-used, disallow re-entrancy through the getAdjacentNodes or onBeforeTraverse or onAfterTraverse callbacks.\r\n if (this._isTraversing) {\r\n throw new Error(\"This TopologicalSorter instance is already traversing.\");\r\n }\r\n\r\n this._isTraversing = true;\r\n\r\n try {\r\n for (const node of graph) {\r\n this._traverseCore(node, getAdjacentNodes, onBeforeTraverse, onAfterTraverse);\r\n }\r\n } finally {\r\n this._isTraversing = false;\r\n this._traversalState.clear();\r\n }\r\n }\r\n\r\n private _traverseCore<NodeT extends DefaultNodeT>(\r\n node: NodeT,\r\n getAdjacentNodes: (node: NodeT) => Iterable<NodeT> | null | undefined,\r\n onBeforeTraverse?: (node: NodeT) => void,\r\n onAfterTraverse?: (node: NodeT) => void\r\n ) {\r\n if (this._traversalState.get(node) !== true) {\r\n if (this._traversalState.get(node) === false) {\r\n throw new Error(\"Graph has cycle.\");\r\n }\r\n\r\n this._traversalState.set(node, false);\r\n onBeforeTraverse?.(node);\r\n\r\n const adjacentNodes = getAdjacentNodes(node);\r\n if (adjacentNodes) {\r\n for (const adjacentNode of adjacentNodes) {\r\n this._traverseCore(adjacentNode, getAdjacentNodes, onBeforeTraverse, onAfterTraverse);\r\n }\r\n }\r\n\r\n this._traversalState.set(node, true);\r\n onAfterTraverse?.(node);\r\n }\r\n }\r\n}\r\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type IDisposable, type IReadonlyObservable } from "@babylonjs/core/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* A collection of items that can be observed for changes.
|
|
4
|
+
*/
|
|
5
|
+
export declare class ObservableCollection<T> {
|
|
6
|
+
private readonly _items;
|
|
7
|
+
private readonly _keys;
|
|
8
|
+
private readonly _observable;
|
|
9
|
+
/**
|
|
10
|
+
* An observable that notifies observers when the collection changes.
|
|
11
|
+
*/
|
|
12
|
+
get observable(): IReadonlyObservable<void>;
|
|
13
|
+
/**
|
|
14
|
+
* The items in the collection.
|
|
15
|
+
*/
|
|
16
|
+
get items(): readonly T[];
|
|
17
|
+
/**
|
|
18
|
+
* Adds an item to the collection.
|
|
19
|
+
* @param item The item to add.
|
|
20
|
+
* @returns A disposable that removes the item from the collection when disposed.
|
|
21
|
+
*/
|
|
22
|
+
add(item: T): IDisposable;
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Observable } from "@babylonjs/core/Misc/observable.js";
|
|
2
|
+
/**
|
|
3
|
+
* A collection of items that can be observed for changes.
|
|
4
|
+
*/
|
|
5
|
+
export class ObservableCollection {
|
|
6
|
+
constructor() {
|
|
7
|
+
this._items = [];
|
|
8
|
+
this._keys = [];
|
|
9
|
+
this._observable = new Observable();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* An observable that notifies observers when the collection changes.
|
|
13
|
+
*/
|
|
14
|
+
get observable() {
|
|
15
|
+
return this._observable;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The items in the collection.
|
|
19
|
+
*/
|
|
20
|
+
get items() {
|
|
21
|
+
return this._items;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Adds an item to the collection.
|
|
25
|
+
* @param item The item to add.
|
|
26
|
+
* @returns A disposable that removes the item from the collection when disposed.
|
|
27
|
+
*/
|
|
28
|
+
add(item) {
|
|
29
|
+
const key = Symbol();
|
|
30
|
+
this._items.push(item);
|
|
31
|
+
this._keys.push(key);
|
|
32
|
+
this._observable.notifyObservers();
|
|
33
|
+
return {
|
|
34
|
+
dispose: () => {
|
|
35
|
+
const index = this._keys.indexOf(key);
|
|
36
|
+
this._items.splice(index, 1);
|
|
37
|
+
this._keys.splice(index, 1);
|
|
38
|
+
this._observable.notifyObservers();
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=observableCollection.js.map
|