@@ -4,7 +4,7 @@ import type { IDisposable, Nullable, Scene } from "core/index";
4
4
import type { TreeItemValue , TreeOpenChangeData , TreeOpenChangeEvent } from "@fluentui/react-components" ;
5
5
import type { ComponentType , FunctionComponent } from "react" ;
6
6
7
- import { Body1 , Body1Strong , Button , FlatTree , FlatTreeItem , makeStyles , tokens , Tooltip , TreeItemLayout } from "@fluentui/react-components" ;
7
+ import { Body1 , Body1Strong , Button , FlatTree , FlatTreeItem , makeStyles , tokens , ToggleButton , Tooltip , TreeItemLayout } from "@fluentui/react-components" ;
8
8
import { VirtualizerScrollView } from "@fluentui/react-components/unstable" ;
9
9
10
10
import { useCallback , useEffect , useMemo , useState } from "react" ;
@@ -52,7 +52,7 @@ export type SceneExplorerSection<T extends EntityBase> = Readonly<{
52
52
watch : ( scene : Scene , onAdded : ( entity : T ) => void , onRemoved : ( entity : T ) => void ) => IDisposable ;
53
53
} > ;
54
54
55
- export type SceneExplorerEntityCommand < T extends EntityBase > = Readonly < {
55
+ type EntityCommandBase < T extends EntityBase > = Readonly < {
56
56
/**
57
57
* An optional order for the section, relative to other commands.
58
58
* Defaults to 0.
@@ -64,11 +64,6 @@ export type SceneExplorerEntityCommand<T extends EntityBase> = Readonly<{
64
64
*/
65
65
predicate : ( entity : unknown ) => entity is T ;
66
66
67
- /**
68
- * The function that executes the command on the given entity.
69
- */
70
- execute : ( scene : Scene , entity : T ) => void ;
71
-
72
67
/**
73
68
* The display name of the command (e.g. "Delete", "Rename", etc.).
74
69
*/
@@ -80,6 +75,36 @@ export type SceneExplorerEntityCommand<T extends EntityBase> = Readonly<{
80
75
icon : ComponentType < { entity : T } > ;
81
76
} > ;
82
77
78
+ type ActionCommand < T extends EntityBase > = EntityCommandBase < T > &
79
+ Readonly < {
80
+ type : "action" ;
81
+ /**
82
+ * The function that executes the command on the given entity.
83
+ */
84
+ execute : ( scene : Scene , entity : T ) => void ;
85
+ } > ;
86
+
87
+ type ToggleCommand < T extends EntityBase > = EntityCommandBase < T > &
88
+ Readonly < {
89
+ type : "toggle" ;
90
+ /**
91
+ * A boolean indicating if the command is enabled.
92
+ */
93
+ isEnabled : ( scene : Scene , entity : T ) => boolean ;
94
+
95
+ /**
96
+ * The function that sets the enabled state of the command on the given entity.
97
+ */
98
+ setEnabled : ( scene : Scene , entity : T , enabled : boolean ) => void ;
99
+
100
+ /**
101
+ * An optional icon component to render when the command is disabled.
102
+ */
103
+ disabledIcon ?: ComponentType < { entity : T } > ;
104
+ } > ;
105
+
106
+ export type SceneExplorerEntityCommand < T extends EntityBase > = ActionCommand < T > | ToggleCommand < T > ;
107
+
83
108
type TreeItemData =
84
109
| {
85
110
type : "section" ;
@@ -112,6 +137,39 @@ const useStyles = makeStyles({
112
137
} ,
113
138
} ) ;
114
139
140
+ const ActionCommand : FunctionComponent < { command : ActionCommand < EntityBase > ; entity : EntityBase ; scene : Scene } > = ( props ) => {
141
+ const { command, entity, scene } = props ;
142
+
143
+ return (
144
+ < Tooltip key = { command . displayName } content = { command . displayName } relationship = "label" >
145
+ < Button icon = { < command . icon entity = { entity } /> } appearance = "subtle" onClick = { ( ) => command . execute ( scene , entity ) } />
146
+ </ Tooltip >
147
+ ) ;
148
+ } ;
149
+
150
+ const ToggleCommand : FunctionComponent < { command : ToggleCommand < EntityBase > ; entity : EntityBase ; scene : Scene } > = ( props ) => {
151
+ const { command, entity, scene } = props ;
152
+ const [ checked , setChecked ] = useState ( command . isEnabled ( scene , entity ) ) ;
153
+ const toggle = useCallback ( ( ) => {
154
+ setChecked ( ( prev ) => {
155
+ const enabled = ! prev ;
156
+ command . setEnabled ( scene , entity , enabled ) ;
157
+ return enabled ;
158
+ } ) ;
159
+ } , [ setChecked ] ) ;
160
+
161
+ return (
162
+ < Tooltip content = { command . displayName } relationship = "label" >
163
+ < ToggleButton
164
+ icon = { ! checked && command . disabledIcon ? < command . disabledIcon entity = { entity } /> : < command . icon entity = { entity } /> }
165
+ appearance = "transparent"
166
+ checked = { checked }
167
+ onClick = { toggle }
168
+ />
169
+ </ Tooltip >
170
+ ) ;
171
+ } ;
172
+
115
173
export const SceneExplorer : FunctionComponent < {
116
174
sections : readonly SceneExplorerSection < EntityBase > [ ] ;
117
175
commands : readonly SceneExplorerEntityCommand < EntityBase > [ ] ;
@@ -260,11 +318,13 @@ export const SceneExplorer: FunctionComponent<{
260
318
style = { item . entity === selectedEntity ? { backgroundColor : tokens . colorNeutralBackground1Selected } : undefined }
261
319
actions = { commands
262
320
. filter ( ( command ) => command . predicate ( item . entity ) )
263
- . map ( ( command ) => (
264
- < Tooltip key = { command . displayName } content = { command . displayName } relationship = "label" >
265
- < Button icon = { < command . icon entity = { item . entity } /> } appearance = "subtle" onClick = { ( ) => command . execute ( scene , item . entity ) } />
266
- </ Tooltip >
267
- ) ) }
321
+ . map ( ( command ) =>
322
+ command . type === "action" ? (
323
+ < ActionCommand key = { command . displayName } command = { command } entity = { item . entity } scene = { scene } />
324
+ ) : (
325
+ < ToggleCommand key = { command . displayName } command = { command } entity = { item . entity } scene = { scene } />
326
+ )
327
+ ) }
268
328
>
269
329
< Body1 wrap = { false } truncate >
270
330
{ item . title . substring ( 0 , 100 ) }
0 commit comments