@tomorrowevening/hermes 0.0.35 → 0.0.37
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/dist/hermes.cjs.js +144 -0
- package/dist/hermes.esm.js +3849 -0
- package/dist/hermes.umd.js +144 -0
- package/dist/style.css +1 -1
- package/package.json +20 -16
- package/src/core/Application.ts +111 -0
- package/src/core/RemoteController.ts +60 -0
- package/src/core/remote/BaseRemote.ts +16 -0
- package/src/core/remote/RemoteComponents.ts +45 -0
- package/src/core/remote/RemoteTheatre.ts +300 -0
- package/src/core/remote/RemoteThree.ts +143 -0
- package/src/core/remote/RemoteTweakpane.ts +194 -0
- package/src/core/types.ts +56 -0
- package/src/editor/Editor.tsx +20 -0
- package/src/editor/components/Draggable.tsx +40 -0
- package/src/editor/components/DraggableItem.tsx +22 -0
- package/src/editor/components/Dropdown.tsx +38 -0
- package/src/editor/components/DropdownItem.tsx +64 -0
- package/src/editor/components/NavButton.tsx +11 -0
- package/src/editor/components/content.ts +2 -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 +20 -0
- package/src/editor/multiView/CameraWindow.tsx +74 -0
- package/src/editor/multiView/InfiniteGridHelper.ts +24 -0
- package/src/editor/multiView/InfiniteGridMaterial.ts +127 -0
- package/src/editor/multiView/MultiView.scss +101 -0
- package/src/editor/multiView/MultiView.tsx +636 -0
- package/src/editor/multiView/MultiViewData.ts +59 -0
- package/src/editor/multiView/UVMaterial.ts +55 -0
- package/src/editor/scss/_debug.scss +58 -0
- package/src/editor/scss/_draggable.scss +43 -0
- package/src/editor/scss/_dropdown.scss +84 -0
- package/src/editor/scss/_sidePanel.scss +278 -0
- package/src/editor/scss/_theme.scss +9 -0
- package/src/editor/scss/index.scss +67 -0
- package/src/editor/sidePanel/Accordion.tsx +41 -0
- package/src/editor/sidePanel/ChildObject.tsx +57 -0
- package/src/editor/sidePanel/ContainerObject.tsx +11 -0
- package/src/editor/sidePanel/SidePanel.tsx +64 -0
- package/src/editor/sidePanel/ToggleBtn.tsx +27 -0
- package/src/editor/sidePanel/inspector/Inspector.tsx +119 -0
- package/src/editor/sidePanel/inspector/InspectorField.tsx +198 -0
- package/src/editor/sidePanel/inspector/InspectorGroup.tsx +50 -0
- package/src/editor/sidePanel/inspector/SceneInspector.tsx +84 -0
- package/src/editor/sidePanel/inspector/inspector.scss +161 -0
- package/src/editor/sidePanel/inspector/utils/InspectAnimation.tsx +102 -0
- package/src/editor/sidePanel/inspector/utils/InspectCamera.tsx +75 -0
- package/src/editor/sidePanel/inspector/utils/InspectLight.tsx +62 -0
- package/src/editor/sidePanel/inspector/utils/InspectMaterial.tsx +710 -0
- package/src/editor/sidePanel/inspector/utils/InspectTransform.tsx +113 -0
- package/src/editor/sidePanel/types.ts +130 -0
- package/src/editor/sidePanel/utils.ts +278 -0
- package/src/editor/utils.ts +117 -0
- package/src/example/CustomEditor.tsx +78 -0
- package/src/example/components/App.css +6 -0
- package/src/example/components/App.tsx +246 -0
- package/src/example/constants.ts +52 -0
- package/src/example/index.scss +45 -0
- package/src/example/main.tsx +37 -0
- package/src/example/three/BaseScene.ts +42 -0
- package/src/example/three/CustomMaterial.ts +72 -0
- package/src/example/three/FBXAnimation.ts +26 -0
- package/src/example/three/Scene1.ts +225 -0
- package/src/example/three/Scene2.ts +138 -0
- package/src/example/three/loader.ts +110 -0
- package/src/index.ts +27 -0
- package/src/vite-env.d.ts +1 -0
- package/dist/hermes.js +0 -3901
- package/dist/hermes.umd.cjs +0 -144
@@ -0,0 +1,194 @@
|
|
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 { BroadcastData, DataUpdateCallback, VoidCallback, noop } from '../types';
|
8
|
+
|
9
|
+
export default class RemoteTweakpane extends BaseRemote {
|
10
|
+
bindCBs: Map<string, DataUpdateCallback>;
|
11
|
+
buttonCBs: Map<string, VoidCallback>;
|
12
|
+
|
13
|
+
protected pane?: Pane | undefined = undefined;
|
14
|
+
protected appCallbacks = 0;
|
15
|
+
protected editorCallbacks = 0;
|
16
|
+
protected inspectorFolder: any = undefined;
|
17
|
+
|
18
|
+
constructor(app: Application) {
|
19
|
+
super(app);
|
20
|
+
this.bindCBs = new Map();
|
21
|
+
this.buttonCBs = new Map();
|
22
|
+
|
23
|
+
if (app.editor) this.createGUI();
|
24
|
+
}
|
25
|
+
|
26
|
+
protected createGUI() {
|
27
|
+
this.pane = new Pane({ title: 'GUI' });
|
28
|
+
this.pane.registerPlugin(EssentialsPlugin);
|
29
|
+
}
|
30
|
+
|
31
|
+
override dispose(): void {
|
32
|
+
this.bindCBs.clear();
|
33
|
+
this.buttonCBs.clear();
|
34
|
+
this.appCallbacks = 0;
|
35
|
+
this.editorCallbacks = 0;
|
36
|
+
|
37
|
+
if (this.app.editor) {
|
38
|
+
this.pane?.dispose();
|
39
|
+
this.pane = undefined;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
addFolder(name: string, params: any = undefined, parent: any = undefined) {
|
44
|
+
if (this.app.editor) {
|
45
|
+
if (this.pane === undefined) this.createGUI();
|
46
|
+
|
47
|
+
const container = parent !== undefined ? parent : this.pane;
|
48
|
+
return container.addFolder({
|
49
|
+
title: name,
|
50
|
+
...params,
|
51
|
+
});
|
52
|
+
} else {
|
53
|
+
this.app.send({
|
54
|
+
event: 'addFolder',
|
55
|
+
target: 'app',
|
56
|
+
data: {
|
57
|
+
name,
|
58
|
+
params,
|
59
|
+
parent
|
60
|
+
}
|
61
|
+
});
|
62
|
+
}
|
63
|
+
}
|
64
|
+
|
65
|
+
get bindID(): string {
|
66
|
+
return `debug_${Math.max(this.appCallbacks, this.editorCallbacks)}`;
|
67
|
+
}
|
68
|
+
|
69
|
+
// Binding
|
70
|
+
|
71
|
+
bind(obj: any, name: string, params: any, parent: any = undefined) {
|
72
|
+
const bindID = this.bindID;
|
73
|
+
const callback = params.onChange !== undefined ? params.onChange : noop;
|
74
|
+
this.bindCBs.set(bindID, callback);
|
75
|
+
|
76
|
+
if (this.app.editor) {
|
77
|
+
if (this.pane === undefined) this.createGUI();
|
78
|
+
|
79
|
+
const container = parent !== undefined ? parent : this.pane;
|
80
|
+
container
|
81
|
+
.addBinding(obj, name, params)
|
82
|
+
.on('change', (evt: any) => {
|
83
|
+
this.app.send({
|
84
|
+
event: 'updateBind',
|
85
|
+
target: 'app',
|
86
|
+
data: {
|
87
|
+
id: bindID,
|
88
|
+
value: evt.value,
|
89
|
+
}
|
90
|
+
});
|
91
|
+
});
|
92
|
+
this.editorCallbacks++;
|
93
|
+
} else {
|
94
|
+
this.app.send({
|
95
|
+
event: 'bindObject',
|
96
|
+
target: 'app',
|
97
|
+
data: {
|
98
|
+
id: bindID,
|
99
|
+
name,
|
100
|
+
params,
|
101
|
+
parent
|
102
|
+
}
|
103
|
+
});
|
104
|
+
this.appCallbacks++;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
triggerBind(id: string, data: any) {
|
109
|
+
const cb = this.bindCBs.get(id);
|
110
|
+
if (cb !== undefined) cb(data);
|
111
|
+
else console.warn(`No callback for: ${id}`, data);
|
112
|
+
}
|
113
|
+
|
114
|
+
// Buttons
|
115
|
+
|
116
|
+
button(name: string, callback: VoidCallback, parent: any = undefined) {
|
117
|
+
const bindID = this.bindID;
|
118
|
+
this.buttonCBs.set(bindID, callback);
|
119
|
+
|
120
|
+
if (this.app.editor) {
|
121
|
+
if (this.pane === undefined) this.createGUI();
|
122
|
+
|
123
|
+
const container = parent !== undefined ? parent : this.pane;
|
124
|
+
container
|
125
|
+
.addButton({ title: name })
|
126
|
+
.on('click', () => {
|
127
|
+
this.app.send({
|
128
|
+
event: 'clickButton',
|
129
|
+
target: 'app',
|
130
|
+
data: {
|
131
|
+
id: bindID,
|
132
|
+
}
|
133
|
+
});
|
134
|
+
});
|
135
|
+
this.editorCallbacks++;
|
136
|
+
} else {
|
137
|
+
this.app.send({
|
138
|
+
event: 'addButton',
|
139
|
+
target: 'app',
|
140
|
+
data: {
|
141
|
+
id: bindID,
|
142
|
+
name,
|
143
|
+
callback,
|
144
|
+
parent
|
145
|
+
}
|
146
|
+
});
|
147
|
+
this.appCallbacks++;
|
148
|
+
}
|
149
|
+
}
|
150
|
+
|
151
|
+
triggerButton(id: string) {
|
152
|
+
const cb = this.buttonCBs.get(id);
|
153
|
+
if (cb !== undefined) cb();
|
154
|
+
}
|
155
|
+
|
156
|
+
// Inspector
|
157
|
+
|
158
|
+
createInspector() {
|
159
|
+
this.inspectorFolder = this.addFolder('Inspector', this.pane);
|
160
|
+
}
|
161
|
+
|
162
|
+
clearInspector() {
|
163
|
+
const total = this.inspectorFolder.children.length - 1;
|
164
|
+
for (let i = total; i > -1; --i) {
|
165
|
+
this.inspectorFolder.remove(this.inspectorFolder.children[i]);
|
166
|
+
}
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
export function HandleAppRemoteTweakpane(app: Application, msg: BroadcastData) {
|
171
|
+
app.components.forEach((component: any) => {
|
172
|
+
if (component instanceof RemoteTweakpane) {
|
173
|
+
const tweakpane: RemoteTweakpane = component;
|
174
|
+
switch (msg.event) {
|
175
|
+
case 'addFolder':
|
176
|
+
tweakpane.addFolder(msg.data.name, msg.data.params, msg.data.parent);
|
177
|
+
break;
|
178
|
+
case 'bindObject':
|
179
|
+
tweakpane.bind(msg.data.name, msg.data.params, msg.data.parent);
|
180
|
+
break;
|
181
|
+
case 'updateBind':
|
182
|
+
tweakpane.triggerBind(msg.data.id, msg.data.value);
|
183
|
+
break;
|
184
|
+
case 'addButton':
|
185
|
+
tweakpane.button(msg.data.name, msg.data.callback, msg.data.parent);
|
186
|
+
break;
|
187
|
+
case 'clickButton':
|
188
|
+
tweakpane.triggerButton(msg.data.id);
|
189
|
+
break;
|
190
|
+
}
|
191
|
+
return;
|
192
|
+
}
|
193
|
+
});
|
194
|
+
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
// Interfaces
|
2
|
+
|
3
|
+
export interface BroadcastData {
|
4
|
+
target: ApplicationMode
|
5
|
+
event: EditorEvent
|
6
|
+
data?: any
|
7
|
+
}
|
8
|
+
|
9
|
+
// Types
|
10
|
+
|
11
|
+
export type ApplicationMode = 'app' | 'editor'
|
12
|
+
|
13
|
+
export type VoidCallback = () => void
|
14
|
+
|
15
|
+
export type DataUpdateCallback = (data: any) => void
|
16
|
+
|
17
|
+
export type EditorEvent =
|
18
|
+
| 'custom'
|
19
|
+
// Theatre
|
20
|
+
| 'setSheet'
|
21
|
+
| 'setSheetObject'
|
22
|
+
| 'updateSheetObject'
|
23
|
+
| 'updateTimeline'
|
24
|
+
| 'playSheet'
|
25
|
+
| 'pauseSheet'
|
26
|
+
// Three
|
27
|
+
| 'getObject'
|
28
|
+
| 'setObject'
|
29
|
+
| 'updateObject'
|
30
|
+
| 'setScene'
|
31
|
+
| 'createTexture'
|
32
|
+
| 'requestMethod'
|
33
|
+
| 'addCamera'
|
34
|
+
| 'removeCamera'
|
35
|
+
// GUI
|
36
|
+
| 'addFolder'
|
37
|
+
| 'bindObject'
|
38
|
+
| 'updateBind'
|
39
|
+
| 'addButton'
|
40
|
+
| 'clickButton'
|
41
|
+
// Components
|
42
|
+
| 'selectComponent'
|
43
|
+
| 'draggableListUpdate'
|
44
|
+
|
45
|
+
export type VoidFunc = () => void
|
46
|
+
|
47
|
+
export type BroadcastCallback = (data: BroadcastData) => void
|
48
|
+
|
49
|
+
export type TheatreUpdateCallback = (data: any) => void
|
50
|
+
|
51
|
+
// Consts
|
52
|
+
|
53
|
+
export const noop = () => {};
|
54
|
+
|
55
|
+
export const defaultTheatreCallback: TheatreUpdateCallback = () => {};
|
56
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import { CSSProperties, Ref } from 'react';
|
2
|
+
import './scss/index.scss';
|
3
|
+
|
4
|
+
type EditorProps = {
|
5
|
+
header?: JSX.Element | JSX.Element[]
|
6
|
+
children?: JSX.Element | JSX.Element[]
|
7
|
+
footer?: JSX.Element | JSX.Element[]
|
8
|
+
ref?: Ref<any>
|
9
|
+
style?: CSSProperties
|
10
|
+
}
|
11
|
+
|
12
|
+
export default function Editor(props: EditorProps) {
|
13
|
+
return (
|
14
|
+
<div className='editor' ref={props.ref} style={props.style}>
|
15
|
+
<header>{props.header}</header>
|
16
|
+
{props.children}
|
17
|
+
<footer>{props.footer}</footer>
|
18
|
+
</div>
|
19
|
+
);
|
20
|
+
}
|
@@ -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,38 @@
|
|
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
|
26
|
+
className={ddClassName}
|
27
|
+
onMouseEnter={() => setExpanded(true)}
|
28
|
+
onMouseLeave={() => setExpanded(false)}
|
29
|
+
>
|
30
|
+
<NavButton title={props.title} />
|
31
|
+
<ul
|
32
|
+
style={{ visibility: expanded ? 'visible' : 'hidden' }}
|
33
|
+
>
|
34
|
+
{list}
|
35
|
+
</ul>
|
36
|
+
</div>
|
37
|
+
);
|
38
|
+
}
|
@@ -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 { 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;
|
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,2 @@
|
|
1
|
+
export const gridImage = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAABkCAYAAAA8AQ3AAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEoklEQVR4nO3aQc4aMRAFYR+AOQv3Px3ZRYoSoh8YxvX61YJdSdPuD++81lqPZ7/b7fa43++P4zieNnbPu+M40PO1dXqwujc9WIeY0nk5WJ0erO4DD84hpnReDlanB6v70INxiCmdl4PV6cHqTvDYf4gpnZeD1enB6k7yyDgsvfNysDo9WN2JHvzD0jsvB6vTg9Wd6kE/LL3zcrA6PVjd2R6LfFh65+VgdXqwum94LOph6Z2Xg9Xpweq+5bGIh6V3Xg5Wpwer+7IH67D0zsvB6vRgdRd4cA5L77wcrE4PVneRB+Ow9M7Lwer0YHUXeuw/LL3zcrA6PVjdxR4ZSynBsNMjqtvgwV9KEYadHjHdFg/6Uqow7PQI6XZ5+NIdhGGnR0K308OX7iAMOz3o3W6PRVxKK4adHuQO4sFaSjmGnR7IDuTBWYoYdnrwOpgHYyli2OnB64Ae+5cihp0evA7qkbG8EozaTg9WB/bgL68Io7LTg9WhPejLq8Io7PRgdXSPqpfudIy2Tg9Wl+BR89J9F0bCn6BpL3qw9vLqdxdxebuXkv5deqcHqwvzYC0PspTY79I7PVhdoAdneaClRH6X3unB6kI9GMuDLSXuu/ROD1YX7LF/ecClRH2X3unB6sI9MpZcgjGu04PVDfDgL7kIY1SnB6sb4UFfchXGoE4PVjfFY8RL9ykYUzo9WN0kj/iX7pMwJnR6sLppHou45N1LoX6X3unB6oZ6sJYMWQruu/ROD1Y32IOzZNBSUN+ld3qwuuEejCXTOy8Hq9OD1V3osf+w9M7Lwer0YHUXe2QspQTDTo+oboMHfylFGHZ6xHRbPOhLqcKw0yOk2+Ux4qX7FAw7PRK6nR7xL90nYdjpQe92eyziUlox7PQgdxAP1lLKMez0QHYgD85SxLDTg9fBPBhLEcNOD14H9Ni/FDHs9OB1UI+M5ZVg1HZ6sDqwB395RRiVnR6sDu1BX14VRmGnB6uje1S9dKdjtHV6sLoEj5qX7gkYTZ0erC7FY5GHa8No6fRgdWEe6OHaMMZ3erC6QA/0cG0Yozs9WF2oB3q4NoyxnR6sLtgDPVwbxshOD1YX7oEerg1jXKcHqxvggR6uDWNUpwerG+GBHq4NY1CnB6ub4jHipfsUjCmdHqxukkf8S/dJGBM6PVjdNI9FHq4NI73Tg9UN9UAP14YR2+nB6gZ7oIdrw4js9GB1wz3Qw7VhxHV6sLoCD/RwbRhRnR6srsQDPVwbRkynB6sr8kAP14YR0enB6qo80MO1YQR0erC6Ng/0S/c2DHqnB6tr9MC+dG/EIHd6sLpWj0UcrhWD2unB6so9WMOVY+A6PVidHqDhxGB1erA6PdZjUYYTg9Xpwer0+P3bP5wYrE4PVqfHHz8x7PSgdnr89RPDTg9ip8c/fmLY6cHr9HjyE8NOD1anx/Pu8pfuYrA6PVidHv/vlhi9nR6sTo8fdWI0dnqwOj1+3InR1unB6vR4qROjqdOD1enxcidGS6cHq9PjrU6Mhk4PVqfH250Y0zs9WJ0eH3RizO70YHV6fNad+tJdDFanB6vT4/PutJfuYrA6PVidHud0vwA7LKl1AiiR5gAAAABJRU5ErkJggg==`;
|
2
|
+
export const noImage = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA5klEQVRoge2Y0Q6EIAwE6cX//+X6cCFpSMEKVTdk501OpRNKiyelFC0b8Ps6gCwoggZF0KAIGhRBgyJoUAQNiqCxjciR9SLV//eZiAyvK3U8i/QVaQO2YyLSFVvlkdTKDjJCukh2ykR5ZEW+kHmlatl90RaBtDkK/w7CYhuRUEO0ee3l+J3m55Vm+17vtwjTnV1V3QA8qfbeUXCzRWDpiLLS+OyzvRW7IzW9R+okvclsqR09743bo0yUpc1+lSJvNsa002+Euk9GKzV7SmZDRIMiaFAEDYqgQRE0KIIGRdCgCBoUQeMEMERadX7YUz8AAAAASUVORK5CYII=`;
|
@@ -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,20 @@
|
|
1
|
+
import { EventDispatcher } from 'three';
|
2
|
+
|
3
|
+
export const debugDispatcher = new EventDispatcher();
|
4
|
+
|
5
|
+
export const ToolEvents = {
|
6
|
+
CUSTOM: 'ToolEvents::custom',
|
7
|
+
// Components
|
8
|
+
SELECT_DROPDOWN: 'ToolEvents::selectDropdown',
|
9
|
+
DRAG_UPDATE: 'ToolEvents::dragUpdate',
|
10
|
+
// SceneHierarchy
|
11
|
+
SET_SCENE: 'ToolEvents::setScene',
|
12
|
+
GET_OBJECT: 'ToolEvents::getObject',
|
13
|
+
SET_OBJECT: 'ToolEvents::setObject',
|
14
|
+
UPDATE_OBJECT: 'ToolEvents::updateObject',
|
15
|
+
CREATE_TEXTURE: 'ToolEvents::createTexture',
|
16
|
+
REQUEST_METHOD: 'ToolEvents::requestMethod',
|
17
|
+
// MultiView
|
18
|
+
ADD_CAMERA: 'ToolEvents::addCamera',
|
19
|
+
REMOVE_CAMERA: 'ToolEvents::removeCamera',
|
20
|
+
};
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import { ForwardedRef, forwardRef, useState } from 'react';
|
2
|
+
import { Camera } from 'three';
|
3
|
+
|
4
|
+
interface DropdownProps {
|
5
|
+
index: number;
|
6
|
+
open: boolean;
|
7
|
+
onToggle: (value: boolean) => void;
|
8
|
+
onSelect: (value: string) => void;
|
9
|
+
options: string[];
|
10
|
+
up?: boolean;
|
11
|
+
}
|
12
|
+
|
13
|
+
export const Dropdown = (props: DropdownProps) => {
|
14
|
+
const [selectedOption, setSelectedOption] = useState(props.options[props.index]);
|
15
|
+
|
16
|
+
const handleToggle = () => {
|
17
|
+
props.onToggle(!props.open);
|
18
|
+
};
|
19
|
+
|
20
|
+
const handleSelect = (option: any) => {
|
21
|
+
if (option !== selectedOption) {
|
22
|
+
props.onSelect(option);
|
23
|
+
setSelectedOption(option);
|
24
|
+
}
|
25
|
+
props.onToggle(false);
|
26
|
+
};
|
27
|
+
|
28
|
+
return (
|
29
|
+
<div className={`dropdown ${props.up === true ? 'up' : ''}`}>
|
30
|
+
<div className='dropdown-toggle' onClick={handleToggle}>
|
31
|
+
{selectedOption}
|
32
|
+
</div>
|
33
|
+
{props.open && (
|
34
|
+
<ul className='dropdown-menu'>
|
35
|
+
{props.options.map((option) => (
|
36
|
+
<li key={option} onClick={() => handleSelect(option)}>
|
37
|
+
{option}
|
38
|
+
</li>
|
39
|
+
))}
|
40
|
+
</ul>
|
41
|
+
)}
|
42
|
+
</div>
|
43
|
+
);
|
44
|
+
};
|
45
|
+
|
46
|
+
interface CameraWindowProps {
|
47
|
+
camera: Camera
|
48
|
+
onSelect: (value: string) => void;
|
49
|
+
options: string[];
|
50
|
+
}
|
51
|
+
|
52
|
+
const CameraWindow = forwardRef(function CameraWindow(props: CameraWindowProps, ref: ForwardedRef<HTMLDivElement>) {
|
53
|
+
const [open, setOpen] = useState(false);
|
54
|
+
const index = props.options.indexOf(props.camera.name);
|
55
|
+
return (
|
56
|
+
<div className='CameraWindow'>
|
57
|
+
<div ref={ref} className='clickable' onClick={() => {
|
58
|
+
if (open) setOpen(false);
|
59
|
+
}} />
|
60
|
+
<Dropdown
|
61
|
+
index={index}
|
62
|
+
open={open}
|
63
|
+
options={props.options}
|
64
|
+
onSelect={props.onSelect}
|
65
|
+
onToggle={(value: boolean) => {
|
66
|
+
setOpen(value);
|
67
|
+
}}
|
68
|
+
up={true}
|
69
|
+
/>
|
70
|
+
</div>
|
71
|
+
);
|
72
|
+
});
|
73
|
+
|
74
|
+
export default CameraWindow;
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { Mesh, PlaneGeometry } from 'three';
|
2
|
+
import InfiniteGridMaterial from './InfiniteGridMaterial';
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Copied from:
|
6
|
+
* https://github.com/theatre-js/theatre/blob/main/packages/r3f/src/extension/InfiniteGridHelper/index.ts
|
7
|
+
*/
|
8
|
+
|
9
|
+
export default class InfiniteGridHelper extends Mesh {
|
10
|
+
gridMaterial: InfiniteGridMaterial;
|
11
|
+
|
12
|
+
constructor() {
|
13
|
+
const material = new InfiniteGridMaterial();
|
14
|
+
super(new PlaneGeometry(2, 2), material);
|
15
|
+
this.gridMaterial = material;
|
16
|
+
this.frustumCulled = false;
|
17
|
+
this.name = 'InfiniteGridHelper';
|
18
|
+
this.position.y = 0.1;
|
19
|
+
}
|
20
|
+
|
21
|
+
update() {
|
22
|
+
this.gridMaterial.needsUpdate = true;
|
23
|
+
}
|
24
|
+
}
|