@tomorrowevening/hermes 0.0.0
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/LICENSE +674 -0
- package/README.md +10 -0
- package/dist/hermes.js +1202 -0
- package/dist/hermes.umd.cjs +29 -0
- package/dist/images/debug/icon_camera.png +0 -0
- package/dist/images/debug/icon_folder.png +0 -0
- package/dist/images/debug/icon_interactive.png +0 -0
- package/dist/images/debug/icon_lights.png +0 -0
- package/dist/images/debug/icon_refresh.png +0 -0
- package/dist/images/debug/icon_ui.png +0 -0
- package/dist/images/debug/icon_utils.png +0 -0
- package/dist/images/debug/icon_world.png +0 -0
- package/dist/style.css +1 -0
- package/package.json +63 -0
- package/src/core/Application.ts +69 -0
- package/src/core/RemoteController.ts +135 -0
- package/src/core/remote/BaseRemote.ts +16 -0
- package/src/core/remote/RemoteComponents.ts +32 -0
- package/src/core/remote/RemoteTheatre.ts +117 -0
- package/src/core/remote/RemoteTweakpane.ts +176 -0
- package/src/core/types.ts +43 -0
- package/src/editor/Editor.tsx +15 -0
- package/src/editor/components/Draggable.tsx +40 -0
- package/src/editor/components/DraggableItem.tsx +22 -0
- package/src/editor/components/Dropdown.tsx +30 -0
- package/src/editor/components/DropdownItem.tsx +64 -0
- package/src/editor/components/NavButton.tsx +11 -0
- package/src/editor/components/icons/CloseIcon.tsx +7 -0
- package/src/editor/components/icons/DragIcon.tsx +9 -0
- package/src/editor/components/types.ts +41 -0
- package/src/editor/global.ts +13 -0
- package/src/editor/sceneHierarchy/ChildObject.tsx +56 -0
- package/src/editor/sceneHierarchy/ContainerObject.tsx +12 -0
- package/src/editor/sceneHierarchy/SceneHierarchy.tsx +77 -0
- package/src/editor/sceneHierarchy/types.ts +10 -0
- package/src/editor/sceneHierarchy/utils.ts +25 -0
- package/src/editor/scss/_debug.scss +68 -0
- package/src/editor/scss/_draggable.scss +43 -0
- package/src/editor/scss/_dropdown.scss +83 -0
- package/src/editor/scss/_sceneHierarchy.scss +170 -0
- package/src/editor/scss/_theme.scss +9 -0
- package/src/editor/scss/index.scss +58 -0
- package/src/editor/utils.ts +20 -0
- package/src/example/App.css +6 -0
- package/src/example/App.tsx +88 -0
- package/src/example/constants.ts +13 -0
- package/src/example/index.scss +37 -0
- package/src/example/main.tsx +75 -0
- package/src/library.ts +16 -0
- package/src/vite-env.d.ts +1 -0
- package/types/core/Application.d.ts +21 -0
- package/types/core/RemoteController.d.ts +2 -0
- package/types/core/remote/BaseRemote.d.ts +6 -0
- package/types/core/remote/RemoteComponents.d.ts +7 -0
- package/types/core/remote/RemoteDebug.d.ts +23 -0
- package/types/core/remote/RemoteTheatre.d.ts +16 -0
- package/types/core/remote/RemoteTweakpane.d.ts +23 -0
- package/types/core/types.d.ts +13 -0
- package/types/debug/Editor.d.ts +8 -0
- package/types/debug/components/Draggable.d.ts +2 -0
- package/types/debug/components/DraggableItem.d.ts +2 -0
- package/types/debug/components/Dropdown.d.ts +2 -0
- package/types/debug/components/DropdownItem.d.ts +2 -0
- package/types/debug/components/NavButton.d.ts +5 -0
- package/types/debug/components/icons/CloseIcon.d.ts +2 -0
- package/types/debug/components/icons/DragIcon.d.ts +2 -0
- package/types/debug/components/types.d.ts +31 -0
- package/types/debug/global.d.ts +9 -0
- package/types/debug/sceneHierarchy/ChildObject.d.ts +2 -0
- package/types/debug/sceneHierarchy/ContainerObject.d.ts +2 -0
- package/types/debug/sceneHierarchy/SceneHierarchy.d.ts +13 -0
- package/types/debug/sceneHierarchy/types.d.ts +8 -0
- package/types/debug/sceneHierarchy/utils.d.ts +2 -0
- package/types/debug/utils.d.ts +4 -0
- package/types/editor/Editor.d.ts +8 -0
- package/types/editor/components/Draggable.d.ts +2 -0
- package/types/editor/components/DraggableItem.d.ts +2 -0
- package/types/editor/components/Dropdown.d.ts +2 -0
- package/types/editor/components/DropdownItem.d.ts +2 -0
- package/types/editor/components/NavButton.d.ts +5 -0
- package/types/editor/components/icons/CloseIcon.d.ts +2 -0
- package/types/editor/components/icons/DragIcon.d.ts +2 -0
- package/types/editor/components/types.d.ts +31 -0
- package/types/editor/global.d.ts +9 -0
- package/types/editor/sceneHierarchy/ChildObject.d.ts +2 -0
- package/types/editor/sceneHierarchy/ContainerObject.d.ts +2 -0
- package/types/editor/sceneHierarchy/SceneHierarchy.d.ts +13 -0
- package/types/editor/sceneHierarchy/types.d.ts +8 -0
- package/types/editor/sceneHierarchy/utils.d.ts +2 -0
- package/types/editor/utils.d.ts +4 -0
- package/types/example/App.d.ts +3 -0
- package/types/example/constants.d.ts +3 -0
- package/types/example/main.d.ts +1 -0
- package/types/library.d.ts +14 -0
@@ -0,0 +1,176 @@
|
|
1
|
+
// Libs
|
2
|
+
import { Pane } from 'tweakpane'
|
3
|
+
import * as EssentialsPlugin from '@tweakpane/plugin-essentials'
|
4
|
+
// Core
|
5
|
+
import Application from '../Application'
|
6
|
+
import BaseRemote from './BaseRemote'
|
7
|
+
import { noop } from '../types'
|
8
|
+
import type { DataUpdateCallback, VoidCallback } from '../types'
|
9
|
+
|
10
|
+
export default class RemoteTweakpane extends BaseRemote {
|
11
|
+
appTab: any = undefined
|
12
|
+
systemTab: any = undefined
|
13
|
+
utilsTab: any = undefined
|
14
|
+
bindCBs: Map<string, DataUpdateCallback>
|
15
|
+
buttonCBs: Map<string, VoidCallback>
|
16
|
+
|
17
|
+
protected pane?: Pane | undefined = undefined
|
18
|
+
protected appCallbacks = 0
|
19
|
+
protected editorCallbacks = 0
|
20
|
+
|
21
|
+
constructor(app: Application) {
|
22
|
+
super(app)
|
23
|
+
this.bindCBs = new Map()
|
24
|
+
this.buttonCBs = new Map()
|
25
|
+
|
26
|
+
if (app.editor) this.createGUI()
|
27
|
+
}
|
28
|
+
|
29
|
+
protected createGUI() {
|
30
|
+
this.pane = new Pane({ title: 'GUI' })
|
31
|
+
this.pane.registerPlugin(EssentialsPlugin)
|
32
|
+
const guiElement = this.pane.element.parentElement as HTMLElement
|
33
|
+
guiElement.style.left = '50%'
|
34
|
+
guiElement.style.top = '0'
|
35
|
+
guiElement.style.maxHeight = '100%'
|
36
|
+
guiElement.style.overflowX = 'hidden'
|
37
|
+
guiElement.style.overflowY = 'auto'
|
38
|
+
guiElement.style.transform = 'translateX(-50%)'
|
39
|
+
guiElement.style.width = '300px'
|
40
|
+
guiElement.style.zIndex = '100'
|
41
|
+
|
42
|
+
// @ts-ignore
|
43
|
+
const tabs = this.pane.addTab({
|
44
|
+
pages: [{ title: 'App' }, { title: 'System' }, { title: 'Tools' }],
|
45
|
+
})
|
46
|
+
this.appTab = tabs.pages[0]
|
47
|
+
this.systemTab = tabs.pages[1]
|
48
|
+
this.utilsTab = tabs.pages[2]
|
49
|
+
}
|
50
|
+
|
51
|
+
override dispose(): void {
|
52
|
+
this.bindCBs.clear()
|
53
|
+
this.buttonCBs.clear()
|
54
|
+
this.appCallbacks = 0
|
55
|
+
this.editorCallbacks = 0
|
56
|
+
|
57
|
+
if (this.app.editor) {
|
58
|
+
this.appTab?.dispose()
|
59
|
+
this.systemTab?.dispose()
|
60
|
+
this.utilsTab?.dispose()
|
61
|
+
this.pane?.dispose()
|
62
|
+
this.appTab = undefined
|
63
|
+
this.systemTab = undefined
|
64
|
+
this.utilsTab = undefined
|
65
|
+
this.pane = undefined
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
addFolder(name: string, params: any = undefined, parent: any = undefined) {
|
70
|
+
if (this.app.editor) {
|
71
|
+
if (this.pane === undefined) this.createGUI()
|
72
|
+
|
73
|
+
const container = parent !== undefined ? parent : this.appTab
|
74
|
+
return container.addFolder({
|
75
|
+
title: name,
|
76
|
+
...params,
|
77
|
+
})
|
78
|
+
} else {
|
79
|
+
this.app.send({
|
80
|
+
event: 'addFolder',
|
81
|
+
data: {
|
82
|
+
name,
|
83
|
+
params,
|
84
|
+
parent
|
85
|
+
}
|
86
|
+
})
|
87
|
+
}
|
88
|
+
}
|
89
|
+
|
90
|
+
get bindID(): string {
|
91
|
+
return `debug_${Math.max(this.appCallbacks, this.editorCallbacks)}`
|
92
|
+
}
|
93
|
+
|
94
|
+
// Binding
|
95
|
+
|
96
|
+
bind(obj: any, name: string, params: any, parent: any = undefined) {
|
97
|
+
const bindID = this.bindID
|
98
|
+
const callback = params.onChange !== undefined ? params.onChange : noop
|
99
|
+
this.bindCBs.set(bindID, callback)
|
100
|
+
|
101
|
+
if (this.app.editor) {
|
102
|
+
if (this.pane === undefined) this.createGUI()
|
103
|
+
|
104
|
+
const container = parent !== undefined ? parent : this.appTab
|
105
|
+
container
|
106
|
+
.addBinding(obj, name, params)
|
107
|
+
.on('change', (evt: any) => {
|
108
|
+
this.app.send({
|
109
|
+
event: 'updateBind',
|
110
|
+
data: {
|
111
|
+
id: bindID,
|
112
|
+
value: evt.value,
|
113
|
+
}
|
114
|
+
})
|
115
|
+
})
|
116
|
+
this.editorCallbacks++
|
117
|
+
} else {
|
118
|
+
this.app.send({
|
119
|
+
event: 'bindObject',
|
120
|
+
data: {
|
121
|
+
id: bindID,
|
122
|
+
name,
|
123
|
+
params,
|
124
|
+
parent
|
125
|
+
}
|
126
|
+
})
|
127
|
+
this.appCallbacks++
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
triggerBind(id: string, data: any) {
|
132
|
+
const cb = this.bindCBs.get(id)
|
133
|
+
if (cb !== undefined) cb(data)
|
134
|
+
else console.warn(`No callback for: ${id}`, data)
|
135
|
+
}
|
136
|
+
|
137
|
+
// Buttons
|
138
|
+
|
139
|
+
button(name: string, callback: VoidCallback, parent: any = undefined) {
|
140
|
+
const bindID = this.bindID
|
141
|
+
this.buttonCBs.set(bindID, callback)
|
142
|
+
|
143
|
+
if (this.app.editor) {
|
144
|
+
if (this.pane === undefined) this.createGUI()
|
145
|
+
|
146
|
+
const container = parent !== undefined ? parent : this.appTab
|
147
|
+
container
|
148
|
+
.addButton({ title: name })
|
149
|
+
.on('click', () => {
|
150
|
+
this.app.send({
|
151
|
+
event: 'clickButton',
|
152
|
+
data: {
|
153
|
+
id: bindID,
|
154
|
+
}
|
155
|
+
})
|
156
|
+
})
|
157
|
+
this.editorCallbacks++
|
158
|
+
} else {
|
159
|
+
this.app.send({
|
160
|
+
event: 'addButton',
|
161
|
+
data: {
|
162
|
+
id: bindID,
|
163
|
+
name,
|
164
|
+
callback,
|
165
|
+
parent
|
166
|
+
}
|
167
|
+
})
|
168
|
+
this.appCallbacks++
|
169
|
+
}
|
170
|
+
}
|
171
|
+
|
172
|
+
triggerButton(id: string) {
|
173
|
+
const cb = this.buttonCBs.get(id)
|
174
|
+
if (cb !== undefined) cb()
|
175
|
+
}
|
176
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
// Interfaces
|
2
|
+
|
3
|
+
export interface BroadcastData {
|
4
|
+
event: EditorEvent
|
5
|
+
data: any
|
6
|
+
}
|
7
|
+
|
8
|
+
// Types
|
9
|
+
|
10
|
+
export type ApplicationMode = 'listener' | 'editor'
|
11
|
+
|
12
|
+
export type VoidCallback = () => void
|
13
|
+
|
14
|
+
export type DataUpdateCallback = (data: any) => void
|
15
|
+
|
16
|
+
export type EditorEvent =
|
17
|
+
// Theatre
|
18
|
+
| 'setSheet'
|
19
|
+
| 'setSheetObject'
|
20
|
+
| 'updateSheetObject'
|
21
|
+
| 'updateTimeline'
|
22
|
+
// GUI
|
23
|
+
| 'addFolder'
|
24
|
+
| 'bindObject'
|
25
|
+
| 'updateBind'
|
26
|
+
| 'addButton'
|
27
|
+
| 'clickButton'
|
28
|
+
// Components
|
29
|
+
| 'selectComponent'
|
30
|
+
| 'draggableListUpdate'
|
31
|
+
|
32
|
+
export type VoidFunc = () => void
|
33
|
+
|
34
|
+
export type BroadcastCallback = (data: BroadcastData) => void
|
35
|
+
|
36
|
+
export type TheatreUpdateCallback = (data: any) => void
|
37
|
+
|
38
|
+
// Consts
|
39
|
+
|
40
|
+
export const noop = () => {}
|
41
|
+
|
42
|
+
export const defaultTheatreCallback: TheatreUpdateCallback = () => {}
|
43
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import './scss/index.scss'
|
2
|
+
|
3
|
+
type EditorProps = {
|
4
|
+
components?: JSX.Element | JSX.Element[]
|
5
|
+
children?: JSX.Element | JSX.Element[]
|
6
|
+
}
|
7
|
+
|
8
|
+
export default function Editor(props: EditorProps) {
|
9
|
+
return (
|
10
|
+
<div className="editor">
|
11
|
+
<div className="navBar">{props.children}</div>
|
12
|
+
{props.components}
|
13
|
+
</div>
|
14
|
+
)
|
15
|
+
}
|
@@ -0,0 +1,40 @@
|
|
1
|
+
// Libs
|
2
|
+
import { useState } from 'react'
|
3
|
+
import { Reorder } from 'framer-motion'
|
4
|
+
// Components
|
5
|
+
import NavButton from './NavButton'
|
6
|
+
import DraggableItem from './DraggableItem'
|
7
|
+
import { DraggableProps } from './types'
|
8
|
+
|
9
|
+
export default function Draggable(props: DraggableProps) {
|
10
|
+
const [expanded, setExpanded] = useState(false)
|
11
|
+
const [list, setList] = useState<string[]>(props.options)
|
12
|
+
|
13
|
+
const updateList = (updated: string[]) => {
|
14
|
+
props.onDragComplete(updated)
|
15
|
+
setList(updated)
|
16
|
+
}
|
17
|
+
|
18
|
+
const onDelete = (index: number) => {
|
19
|
+
const newArray = [...list]
|
20
|
+
newArray.splice(index, 1)
|
21
|
+
updateList(newArray)
|
22
|
+
}
|
23
|
+
|
24
|
+
const elements: any[] = []
|
25
|
+
list.forEach((value: string, index: number) => {
|
26
|
+
elements.push(<DraggableItem key={value} index={index} title={value} onDelete={onDelete} />)
|
27
|
+
})
|
28
|
+
|
29
|
+
let ddClassName = 'dropdown draggable'
|
30
|
+
if (props.subdropdown) ddClassName += ' subdropdown'
|
31
|
+
|
32
|
+
return (
|
33
|
+
<div className={ddClassName} onMouseEnter={() => setExpanded(true)} onMouseLeave={() => setExpanded(false)}>
|
34
|
+
<NavButton title={props.title} />
|
35
|
+
<Reorder.Group axis="y" values={list} onReorder={updateList} style={{ visibility: expanded ? 'visible' : 'hidden' }}>
|
36
|
+
{elements}
|
37
|
+
</Reorder.Group>
|
38
|
+
</div>
|
39
|
+
)
|
40
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
// Libs
|
2
|
+
import { Reorder } from 'framer-motion'
|
3
|
+
// Components
|
4
|
+
import CloseIcon from './icons/CloseIcon'
|
5
|
+
import DragIcon from './icons/DragIcon'
|
6
|
+
import { DraggableItemProps } from './types'
|
7
|
+
|
8
|
+
export default function DraggableItem(props: DraggableItemProps) {
|
9
|
+
return (
|
10
|
+
<Reorder.Item key={props.title} value={props.title}>
|
11
|
+
<div>
|
12
|
+
{DragIcon}
|
13
|
+
<span>{props.title}</span>
|
14
|
+
<button className="closeIcon" onClick={() => {
|
15
|
+
props.onDelete(props.index)
|
16
|
+
}}>
|
17
|
+
{CloseIcon}
|
18
|
+
</button>
|
19
|
+
</div>
|
20
|
+
</Reorder.Item>
|
21
|
+
)
|
22
|
+
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
// Libs
|
2
|
+
import { useState } from 'react'
|
3
|
+
// Views
|
4
|
+
import NavButton from './NavButton'
|
5
|
+
import DropdownItem from './DropdownItem'
|
6
|
+
import { DropdownOption, DropdownProps } from './types'
|
7
|
+
|
8
|
+
export default function Dropdown(props: DropdownProps) {
|
9
|
+
const [expanded, setExpanded] = useState(false)
|
10
|
+
|
11
|
+
const list: Array<any> = []
|
12
|
+
{
|
13
|
+
props.options.map((option: DropdownOption, index: number) => {
|
14
|
+
if (props.onSelect !== undefined) {
|
15
|
+
option.onSelect = props.onSelect
|
16
|
+
}
|
17
|
+
list.push(<DropdownItem option={option} key={index} />)
|
18
|
+
})
|
19
|
+
}
|
20
|
+
|
21
|
+
let ddClassName = 'dropdown'
|
22
|
+
if (props.subdropdown) ddClassName += ' subdropdown'
|
23
|
+
|
24
|
+
return (
|
25
|
+
<div className={ddClassName} onMouseEnter={() => setExpanded(true)} onMouseLeave={() => setExpanded(false)}>
|
26
|
+
<NavButton title={props.title} />
|
27
|
+
<ul style={{ visibility: expanded ? 'visible' : 'hidden' }}>{list}</ul>
|
28
|
+
</div>
|
29
|
+
)
|
30
|
+
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
// Libs
|
2
|
+
import { useState } from 'react'
|
3
|
+
// Components
|
4
|
+
import Draggable from './Draggable'
|
5
|
+
import Dropdown from './Dropdown'
|
6
|
+
import type { DropdownItemProps, DropdownOption } from './types'
|
7
|
+
// Utils
|
8
|
+
import { randomID } from '../utils'
|
9
|
+
|
10
|
+
export default function DropdownItem(props: DropdownItemProps) {
|
11
|
+
const { option } = props
|
12
|
+
const [selected, setSelected] = useState('')
|
13
|
+
|
14
|
+
let element = null
|
15
|
+
switch (option.type) {
|
16
|
+
case 'draggable':
|
17
|
+
element = (
|
18
|
+
<Draggable
|
19
|
+
title={option.title}
|
20
|
+
options={option.value as Array<string>}
|
21
|
+
onDragComplete={(options: string[]) => {
|
22
|
+
if (option.onDragComplete !== undefined) option.onDragComplete(options)
|
23
|
+
}}
|
24
|
+
subdropdown={true}
|
25
|
+
/>
|
26
|
+
)
|
27
|
+
break
|
28
|
+
case 'dropdown':
|
29
|
+
element = (
|
30
|
+
<Dropdown
|
31
|
+
title={option.title}
|
32
|
+
options={option.value as Array<DropdownOption>}
|
33
|
+
onSelect={option.onSelect}
|
34
|
+
subdropdown={true}
|
35
|
+
/>
|
36
|
+
)
|
37
|
+
break
|
38
|
+
case 'option':
|
39
|
+
element = (
|
40
|
+
<button
|
41
|
+
onClick={() => {
|
42
|
+
if (option.onSelect !== undefined) option.onSelect(option.value)
|
43
|
+
// Toggle selectable
|
44
|
+
if (option.selectable) {
|
45
|
+
if (selected !== option.title) {
|
46
|
+
setSelected(option.title)
|
47
|
+
} else {
|
48
|
+
setSelected('')
|
49
|
+
}
|
50
|
+
}
|
51
|
+
}}
|
52
|
+
>
|
53
|
+
{option.title}
|
54
|
+
</button>
|
55
|
+
)
|
56
|
+
break
|
57
|
+
}
|
58
|
+
|
59
|
+
return (
|
60
|
+
<li className={selected === option.title ? 'selected' : ''} key={randomID()}>
|
61
|
+
{element}
|
62
|
+
</li>
|
63
|
+
)
|
64
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
type NavButtonProps = {
|
2
|
+
title: string
|
3
|
+
}
|
4
|
+
|
5
|
+
export default function NavButton(props: NavButtonProps) {
|
6
|
+
return props.title.search('<') > -1 ? (
|
7
|
+
<button className="svg" dangerouslySetInnerHTML={{ __html: props.title }}></button>
|
8
|
+
) : (
|
9
|
+
<button>{props.title}</button>
|
10
|
+
)
|
11
|
+
}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
export default (
|
2
|
+
<svg className="dragIcon" width="14" height="14" fill="#666666" stroke="none">
|
3
|
+
<path
|
4
|
+
d="M10.43,4H3.57C3.26,4,3,4.22,3,4.5v1C3,5.78,3.26,6,3.57,6h6.86C10.74,6,11,5.78,11,5.5v-1
|
5
|
+
C11,4.22,10.74,4,10.43,4z M10.43,8H3.57C3.26,8,3,8.22,3,8.5v1C3,9.78,3.26,10,3.57,10h6.86C10.74,10,11,9.78,11,9.5v-1
|
6
|
+
C11,8.22,10.74,8,10.43,8z"
|
7
|
+
/>
|
8
|
+
</svg>
|
9
|
+
)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
export type DropdownType = 'option' | 'dropdown' | 'draggable'
|
2
|
+
|
3
|
+
export interface DropdownOption {
|
4
|
+
title: string
|
5
|
+
value: any | Array<DropdownOption>
|
6
|
+
type: DropdownType
|
7
|
+
// Option
|
8
|
+
onSelect?: (value: any) => void
|
9
|
+
selectable?: boolean
|
10
|
+
// Draggable
|
11
|
+
onDragComplete?: (options: Array<string>) => void
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface DropdownProps {
|
15
|
+
title: string
|
16
|
+
options: Array<DropdownOption>
|
17
|
+
onSelect?: (value: any) => void
|
18
|
+
subdropdown?: boolean
|
19
|
+
}
|
20
|
+
|
21
|
+
export interface DropdownItemProps {
|
22
|
+
option: DropdownOption
|
23
|
+
onSelect?: (value: any) => void
|
24
|
+
// Draggable
|
25
|
+
onDragComplete?: (options: Array<string>) => void
|
26
|
+
}
|
27
|
+
|
28
|
+
// Draggable
|
29
|
+
|
30
|
+
export interface DraggableItemProps {
|
31
|
+
index: number
|
32
|
+
title: string
|
33
|
+
onDelete: (index: number) => void
|
34
|
+
}
|
35
|
+
|
36
|
+
export interface DraggableProps {
|
37
|
+
title: string
|
38
|
+
options: Array<string>
|
39
|
+
onDragComplete: (options: Array<string>) => void
|
40
|
+
subdropdown?: boolean
|
41
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { EventDispatcher } from 'three';
|
2
|
+
|
3
|
+
export const debugDispatcher = new EventDispatcher()
|
4
|
+
|
5
|
+
export const ToolEvents = {
|
6
|
+
// Components
|
7
|
+
SELECT_DROPDOWN: 'ToolEvents::selectDropdown',
|
8
|
+
DRAG_UPDATE: 'ToolEvents::dragUpdate',
|
9
|
+
// SceneHierarchy
|
10
|
+
INSPECT_ITEM: 'ToolEvents::inspectItem',
|
11
|
+
REFRESH_SCENE: 'ToolEvents::refreshScene',
|
12
|
+
SET_SCENE: 'ToolEvents::setScene',
|
13
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
// Libs
|
2
|
+
import { useState } from 'react'
|
3
|
+
import { Object3D } from 'three'
|
4
|
+
// Models
|
5
|
+
import { debugDispatcher, ToolEvents } from '../global'
|
6
|
+
import { ChildObjectProps } from './types'
|
7
|
+
// Utils
|
8
|
+
import { determineIcon } from './utils'
|
9
|
+
|
10
|
+
export default function ChildObject(props: ChildObjectProps) {
|
11
|
+
const [open, setOpen] = useState(false)
|
12
|
+
|
13
|
+
let container = null
|
14
|
+
let hasChildren = false
|
15
|
+
if (props.child.children.length > 0) {
|
16
|
+
hasChildren = true
|
17
|
+
const children: Array<any> = []
|
18
|
+
props.child.children.map((child: Object3D) => {
|
19
|
+
children.push(<ChildObject child={child} key={Math.random()} />)
|
20
|
+
})
|
21
|
+
container = <div className={`container ${!open ? 'closed' : ''}`}>{children}</div>
|
22
|
+
}
|
23
|
+
|
24
|
+
return (
|
25
|
+
<div className="childObject" key={Math.random()}>
|
26
|
+
<div className="child">
|
27
|
+
{hasChildren ? (
|
28
|
+
<button
|
29
|
+
className="status"
|
30
|
+
style={{
|
31
|
+
backgroundPositionX: open ? '-14px' : '2px',
|
32
|
+
}}
|
33
|
+
onClick={() => {
|
34
|
+
setOpen(!open)
|
35
|
+
}}
|
36
|
+
></button>
|
37
|
+
) : null}
|
38
|
+
<button
|
39
|
+
className="name"
|
40
|
+
style={{
|
41
|
+
left: hasChildren ? '20px' : '5px',
|
42
|
+
}}
|
43
|
+
onClick={() => {
|
44
|
+
debugDispatcher.dispatchEvent({ type: ToolEvents.INSPECT_ITEM, value: props.child })
|
45
|
+
}}
|
46
|
+
>
|
47
|
+
{props.child.name.length > 0
|
48
|
+
? `${props.child.name} (${props.child.type})`
|
49
|
+
: `${props.child.type}::${props.child.uuid}`}
|
50
|
+
</button>
|
51
|
+
<div className={`icon ${determineIcon(props.child)}`}></div>
|
52
|
+
</div>
|
53
|
+
{container}
|
54
|
+
</div>
|
55
|
+
)
|
56
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
import { Object3D } from 'three'
|
2
|
+
import ChildObject from './ChildObject'
|
3
|
+
import type { ChildObjectProps } from './types'
|
4
|
+
|
5
|
+
export default function ContainerObject(props: ChildObjectProps) {
|
6
|
+
const children: Array<any> = []
|
7
|
+
props.child.children.map((child: Object3D) => {
|
8
|
+
children.push(<ChildObject child={child} key={Math.random()} />)
|
9
|
+
})
|
10
|
+
|
11
|
+
return <div className="scene">{children}</div>
|
12
|
+
}
|
@@ -0,0 +1,77 @@
|
|
1
|
+
// Libs
|
2
|
+
import { Component, ReactNode } from 'react'
|
3
|
+
// Models
|
4
|
+
import { debugDispatcher, ToolEvents } from '../global'
|
5
|
+
// Components
|
6
|
+
import '../scss/_sceneHierarchy.scss'
|
7
|
+
import ContainerObject from './ContainerObject'
|
8
|
+
import { SceneHierarchyState } from './types'
|
9
|
+
|
10
|
+
export default class SceneHierarchy extends Component {
|
11
|
+
constructor(props: object | SceneHierarchyState) {
|
12
|
+
super(props)
|
13
|
+
this.state = {
|
14
|
+
open: false,
|
15
|
+
scene: null,
|
16
|
+
} as SceneHierarchyState
|
17
|
+
|
18
|
+
debugDispatcher.addEventListener(ToolEvents.REFRESH_SCENE, this.onUpdate)
|
19
|
+
debugDispatcher.addEventListener(ToolEvents.SET_SCENE, this.onSetScene)
|
20
|
+
}
|
21
|
+
|
22
|
+
componentWillUnmount(): void {
|
23
|
+
debugDispatcher.removeEventListener(ToolEvents.REFRESH_SCENE, this.onUpdate)
|
24
|
+
debugDispatcher.removeEventListener(ToolEvents.SET_SCENE, this.onSetScene)
|
25
|
+
}
|
26
|
+
|
27
|
+
render(): ReactNode {
|
28
|
+
const headerTitle = this.componentState.scene !== null ? `Hierarchy: ${this.componentState.scene.name}` : 'Hierarchy'
|
29
|
+
return (
|
30
|
+
<div id="SceneHierarchy">
|
31
|
+
<div className="header">
|
32
|
+
<button
|
33
|
+
className="status"
|
34
|
+
style={{
|
35
|
+
backgroundPositionX: this.componentState.open ? '-14px' : '2px',
|
36
|
+
}}
|
37
|
+
onClick={this.toggleOpen}
|
38
|
+
></button>
|
39
|
+
<span>{headerTitle}</span>
|
40
|
+
<button className="refresh hideText" onClick={this.onRefresh}>
|
41
|
+
Refresh
|
42
|
+
</button>
|
43
|
+
</div>
|
44
|
+
{this.componentState.scene !== null && this.componentState.open ? <ContainerObject child={this.componentState.scene} /> : null}
|
45
|
+
</div>
|
46
|
+
)
|
47
|
+
}
|
48
|
+
|
49
|
+
// Private
|
50
|
+
|
51
|
+
private onUpdate = () => {
|
52
|
+
//
|
53
|
+
}
|
54
|
+
|
55
|
+
private toggleOpen = () => {
|
56
|
+
this.setState(()=> ({
|
57
|
+
open: !this.componentState.open,
|
58
|
+
}))
|
59
|
+
}
|
60
|
+
|
61
|
+
private onRefresh = () => {
|
62
|
+
debugDispatcher.dispatchEvent({ type: ToolEvents.INSPECT_ITEM, value: this.componentState.scene })
|
63
|
+
}
|
64
|
+
|
65
|
+
private onSetScene = (evt: any) => {
|
66
|
+
console.log('SceneHierarchy::onSetScene', evt)
|
67
|
+
this.setState(() => ({
|
68
|
+
scene: evt.value
|
69
|
+
}))
|
70
|
+
}
|
71
|
+
|
72
|
+
// Getters / Setters
|
73
|
+
|
74
|
+
get componentState(): SceneHierarchyState {
|
75
|
+
return this.state as SceneHierarchyState
|
76
|
+
}
|
77
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { Object3D } from 'three'
|
2
|
+
|
3
|
+
export function determineIcon(obj: Object3D): string {
|
4
|
+
if (obj.name === 'cameras') {
|
5
|
+
return 'camera'
|
6
|
+
} else if (obj.name === 'interactive') {
|
7
|
+
return 'interactive'
|
8
|
+
} else if (obj.name === 'lights') {
|
9
|
+
return 'light'
|
10
|
+
} else if (obj.name === 'ui') {
|
11
|
+
return 'ui'
|
12
|
+
} else if (obj.name === 'utils') {
|
13
|
+
return 'utils'
|
14
|
+
}
|
15
|
+
|
16
|
+
const type = obj.type
|
17
|
+
if (type.search('Helper') > -1) {
|
18
|
+
return 'icon_utils'
|
19
|
+
} else if (type.search('Camera') > -1) {
|
20
|
+
return 'camera'
|
21
|
+
} else if (type.search('Light') > -1) {
|
22
|
+
return 'light'
|
23
|
+
}
|
24
|
+
return 'obj3D'
|
25
|
+
}
|