@viji-dev/sdk 1.0.0 → 1.0.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/README.md +70 -63
- package/bin/viji.js +9 -29
- package/dist/assets/artist-dts-BHUsvSI6.js +613 -0
- package/dist/assets/artist-dts-p5-Cyw8vmy_.js +736 -0
- package/dist/assets/core-CiQx3w0t.js +12 -0
- package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
- package/dist/assets/docs-api-PBLtY4Ni.js +12381 -0
- package/dist/assets/engine-javascript-CXyY7cc8.js +141 -0
- package/dist/assets/essentia-wasm.web-0S-sW98u-CYV1l1zv.js +38 -0
- package/dist/assets/essentia.js-core.es-DnrJE0uR-DOSrF5_G.js +32 -0
- package/dist/assets/glsl-DMyvO4G4.js +1 -0
- package/dist/assets/index-BhFxsauQ.js +215 -0
- package/dist/assets/index-BqhVeA7U.css +1 -0
- package/dist/assets/index-T4TOjvD0.js +1 -0
- package/dist/assets/index-Wz9WqGqz.js +52 -0
- package/dist/assets/index-t24aGwla.js +1 -0
- package/dist/assets/javascript-wDzz0qaB.js +1 -0
- package/dist/assets/shader-uniforms-GdaUkQPK.js +1 -0
- package/dist/assets/typescript-BPQ3VLAy.js +1 -0
- package/dist/assets/viji.worker-CQSJ0SiO-ljtBlcNZ.js +27018 -0
- package/{index.html → dist/index.html} +2 -1
- package/package.json +31 -35
- package/src/cli/commands/build.js +50 -99
- package/src/cli/commands/create.js +49 -46
- package/src/cli/commands/dev.js +30 -97
- package/src/cli/server/dev-server.js +233 -0
- package/src/cli/server/scene-scanner.js +93 -0
- package/src/cli/server/vite-scene-plugin.d.ts +2 -0
- package/src/cli/server/vite-scene-plugin.js +134 -0
- package/src/cli/utils/cli-utils.js +29 -139
- package/src/cli/utils/scene-compiler.js +10 -17
- package/src/templates/scene-templates.js +85 -0
- package/.gitignore +0 -29
- package/eslint.config.js +0 -37
- package/postcss.config.js +0 -6
- package/scenes/audio-visualizer/main.js +0 -287
- package/scenes/core-demo/main.js +0 -532
- package/scenes/demo-scene/main.js +0 -619
- package/scenes/global.d.ts +0 -15
- package/scenes/particle-system/main.js +0 -349
- package/scenes/tsconfig.json +0 -12
- package/scenes/video-mirror/main.ts +0 -436
- package/src/App.css +0 -42
- package/src/App.tsx +0 -279
- package/src/cli/commands/init.js +0 -262
- package/src/components/SDKPage.tsx +0 -337
- package/src/components/core/CoreContainer.tsx +0 -126
- package/src/components/ui/DeviceSelectionList.tsx +0 -137
- package/src/components/ui/FPSCounter.tsx +0 -78
- package/src/components/ui/FileDropzonePanel.tsx +0 -120
- package/src/components/ui/FileListPanel.tsx +0 -285
- package/src/components/ui/InputExpansionPanel.tsx +0 -31
- package/src/components/ui/MediaPlayerControls.tsx +0 -191
- package/src/components/ui/MenuContainer.tsx +0 -71
- package/src/components/ui/ParametersMenu.tsx +0 -797
- package/src/components/ui/ProjectSwitcherMenu.tsx +0 -192
- package/src/components/ui/QuickInputControls.tsx +0 -542
- package/src/components/ui/SDKMenuSystem.tsx +0 -96
- package/src/components/ui/SettingsMenu.tsx +0 -346
- package/src/components/ui/SimpleInputControls.tsx +0 -137
- package/src/index.css +0 -68
- package/src/main.tsx +0 -10
- package/src/scenes-hmr.ts +0 -158
- package/src/services/project-filesystem.ts +0 -436
- package/src/stores/scene-player/index.ts +0 -3
- package/src/stores/scene-player/input-manager.store.ts +0 -1045
- package/src/stores/scene-player/scene-session.store.ts +0 -659
- package/src/styles/globals.css +0 -111
- package/src/templates/minimal-template.js +0 -11
- package/src/utils/debounce.js +0 -34
- package/src/vite-env.d.ts +0 -1
- package/tailwind.config.js +0 -18
- package/tsconfig.app.json +0 -27
- package/tsconfig.json +0 -27
- package/tsconfig.node.json +0 -27
- package/vite.config.ts +0 -54
- /package/{public → dist}/favicon.png +0 -0
|
@@ -1,542 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect, useRef } from 'react';
|
|
2
|
-
import { Button, Popover, PopoverTrigger, PopoverContent, Card, CardBody } from '@heroui/react';
|
|
3
|
-
import {
|
|
4
|
-
VideoCameraIcon,
|
|
5
|
-
MicrophoneIcon,
|
|
6
|
-
CursorArrowRaysIcon,
|
|
7
|
-
VideoCameraSlashIcon,
|
|
8
|
-
NoSymbolIcon
|
|
9
|
-
} from '@heroicons/react/24/outline';
|
|
10
|
-
import {
|
|
11
|
-
ComputerDesktopIcon,
|
|
12
|
-
DocumentIcon
|
|
13
|
-
} from '@heroicons/react/24/solid';
|
|
14
|
-
import { useInputManagerStore, AudioInputType, VideoInputType } from '../../stores/scene-player/input-manager.store';
|
|
15
|
-
import { useSceneSessionStore } from '../../stores/scene-player/scene-session.store';
|
|
16
|
-
import InputExpansionPanel from './InputExpansionPanel';
|
|
17
|
-
import DeviceSelectionList from './DeviceSelectionList';
|
|
18
|
-
import FileDropzonePanel from './FileDropzonePanel';
|
|
19
|
-
import FileListPanel from './FileListPanel';
|
|
20
|
-
|
|
21
|
-
interface QuickInputControlsProps {
|
|
22
|
-
className?: string;
|
|
23
|
-
onSetControlsPinned?: (pinned: boolean) => void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
type PanelType = 'devices' | 'files' | null;
|
|
27
|
-
|
|
28
|
-
const QuickInputControls: React.FC<QuickInputControlsProps> = ({ className = "", onSetControlsPinned }) => {
|
|
29
|
-
// Debug refs
|
|
30
|
-
const videoContentRef = useRef<HTMLDivElement | null>(null);
|
|
31
|
-
const audioContentRef = useRef<HTMLDivElement | null>(null);
|
|
32
|
-
|
|
33
|
-
const [showVideoMenu, setShowVideoMenu] = useState(false);
|
|
34
|
-
const [showAudioMenu, setShowAudioMenu] = useState(false);
|
|
35
|
-
const videoOpenedAtRef = useRef<number>(0);
|
|
36
|
-
const audioOpenedAtRef = useRef<number>(0);
|
|
37
|
-
const clickInsideVideoRef = useRef<boolean>(false);
|
|
38
|
-
const clickInsideAudioRef = useRef<boolean>(false);
|
|
39
|
-
const [activeVideoPanel, setActiveVideoPanel] = useState<PanelType>(null);
|
|
40
|
-
const [activeAudioPanel, setActiveAudioPanel] = useState<PanelType>(null);
|
|
41
|
-
const [showFileList, setShowFileList] = useState(false);
|
|
42
|
-
|
|
43
|
-
// Toggle helpers to mirror frontend behavior (only one menu open at a time)
|
|
44
|
-
const openVideoMenu = () => {
|
|
45
|
-
setShowAudioMenu(false);
|
|
46
|
-
videoOpenedAtRef.current = Date.now();
|
|
47
|
-
setShowVideoMenu(true);
|
|
48
|
-
};
|
|
49
|
-
const openAudioMenu = () => {
|
|
50
|
-
setShowVideoMenu(false);
|
|
51
|
-
audioOpenedAtRef.current = Date.now();
|
|
52
|
-
setShowAudioMenu(true);
|
|
53
|
-
};
|
|
54
|
-
const closeVideoMenu = () => setShowVideoMenu(false);
|
|
55
|
-
const closeAudioMenu = () => setShowAudioMenu(false);
|
|
56
|
-
|
|
57
|
-
const {
|
|
58
|
-
inputConfiguration,
|
|
59
|
-
streamError,
|
|
60
|
-
setInteractionEnabled,
|
|
61
|
-
setAudioInputType,
|
|
62
|
-
setVideoInputType,
|
|
63
|
-
updateStreamsForCore,
|
|
64
|
-
setStreamError
|
|
65
|
-
} = useInputManagerStore();
|
|
66
|
-
|
|
67
|
-
const { updateCoreConfig } = useSceneSessionStore();
|
|
68
|
-
|
|
69
|
-
// Get current input state
|
|
70
|
-
const hasVideoInput = inputConfiguration.video.type !== VideoInputType.NONE;
|
|
71
|
-
const hasAudioInput = inputConfiguration.audio.type !== AudioInputType.NONE;
|
|
72
|
-
const interactionEnabled = inputConfiguration.interactionEnabled;
|
|
73
|
-
|
|
74
|
-
// Focus video popover on open (minimal)
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (showVideoMenu) {
|
|
77
|
-
requestAnimationFrame(() => {
|
|
78
|
-
try { videoContentRef.current?.focus?.(); } catch {}
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}, [showVideoMenu]);
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (showAudioMenu) {
|
|
85
|
-
requestAnimationFrame(() => {
|
|
86
|
-
try { audioContentRef.current?.focus?.(); } catch {}
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
}, [showAudioMenu]);
|
|
90
|
-
|
|
91
|
-
// Minimal initial outside-click guard (like visualizer)
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
if (!showVideoMenu && !showAudioMenu) return;
|
|
94
|
-
const handler = (e: Event) => {
|
|
95
|
-
const now = Date.now();
|
|
96
|
-
const withinGuard = (showVideoMenu && now - (videoOpenedAtRef.current || 0) < 200) || (showAudioMenu && now - (audioOpenedAtRef.current || 0) < 200);
|
|
97
|
-
if ((e.type === 'pointerdown' || e.type === 'click') && withinGuard) {
|
|
98
|
-
e.stopPropagation();
|
|
99
|
-
e.preventDefault();
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
document.addEventListener('pointerdown', handler, true);
|
|
103
|
-
document.addEventListener('click', handler, true);
|
|
104
|
-
return () => {
|
|
105
|
-
document.removeEventListener('pointerdown', handler, true);
|
|
106
|
-
document.removeEventListener('click', handler, true);
|
|
107
|
-
};
|
|
108
|
-
}, [showVideoMenu, showAudioMenu]);
|
|
109
|
-
|
|
110
|
-
// Ensure panels close when input types change to non-panel types
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (inputConfiguration.video.type !== VideoInputType.CAMERA &&
|
|
113
|
-
inputConfiguration.video.type !== VideoInputType.FILES) {
|
|
114
|
-
setActiveVideoPanel(null);
|
|
115
|
-
}
|
|
116
|
-
}, [inputConfiguration.video.type]);
|
|
117
|
-
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
if (inputConfiguration.audio.type !== AudioInputType.MICROPHONE &&
|
|
120
|
-
inputConfiguration.audio.type !== AudioInputType.FILES) {
|
|
121
|
-
setActiveAudioPanel(null);
|
|
122
|
-
}
|
|
123
|
-
}, [inputConfiguration.audio.type]);
|
|
124
|
-
|
|
125
|
-
// Pin/unpin controls while file panels are active (both dropzone and list views)
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
const filesPanelActive = activeAudioPanel === 'files' || activeVideoPanel === 'files';
|
|
128
|
-
const isAnyFilesSubpanelOpen = filesPanelActive; // dropzone or list both count
|
|
129
|
-
onSetControlsPinned?.(isAnyFilesSubpanelOpen);
|
|
130
|
-
}, [activeAudioPanel, activeVideoPanel, showFileList, onSetControlsPinned]);
|
|
131
|
-
|
|
132
|
-
const handleInteractionToggle = async () => {
|
|
133
|
-
const newState = !interactionEnabled;
|
|
134
|
-
setInteractionEnabled(newState);
|
|
135
|
-
|
|
136
|
-
// Update VijiCore interaction state (now works at runtime!)
|
|
137
|
-
try {
|
|
138
|
-
await updateCoreConfig({
|
|
139
|
-
allowUserInteraction: newState
|
|
140
|
-
});
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error('Failed to update interaction state:', error);
|
|
143
|
-
// Revert local state if core update failed
|
|
144
|
-
setInteractionEnabled(!newState);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const handleVideoInputChange = async (type: VideoInputType) => {
|
|
149
|
-
setShowVideoMenu(false);
|
|
150
|
-
|
|
151
|
-
// Close audio panel when opening video panel
|
|
152
|
-
setActiveAudioPanel(null);
|
|
153
|
-
|
|
154
|
-
// Manage panel state based on input type FIRST (before async operations)
|
|
155
|
-
if (type === VideoInputType.CAMERA) {
|
|
156
|
-
setActiveVideoPanel('devices');
|
|
157
|
-
} else if (type === VideoInputType.FILES) {
|
|
158
|
-
const hasFiles = inputConfiguration.video.files.length > 0;
|
|
159
|
-
setActiveVideoPanel('files');
|
|
160
|
-
setShowFileList(hasFiles);
|
|
161
|
-
} else {
|
|
162
|
-
// Close panel for NONE, SCREEN_VIDEO, or any other type
|
|
163
|
-
setActiveVideoPanel(null);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Then update the input type only if changed
|
|
167
|
-
if (inputConfiguration.video.type !== type) {
|
|
168
|
-
await setVideoInputType(type);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Update core only for video when type changed (avoid touching audio)
|
|
172
|
-
if (inputConfiguration.video.type !== type) {
|
|
173
|
-
try {
|
|
174
|
-
const streams = await updateStreamsForCore();
|
|
175
|
-
await updateCoreConfig({
|
|
176
|
-
videoStream: streams.videoStream
|
|
177
|
-
});
|
|
178
|
-
} catch (error) {
|
|
179
|
-
console.error('Failed to update video input:', error);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const handleAudioInputChange = async (type: AudioInputType) => {
|
|
185
|
-
setShowAudioMenu(false);
|
|
186
|
-
|
|
187
|
-
// Close video panel when opening audio panel
|
|
188
|
-
setActiveVideoPanel(null);
|
|
189
|
-
|
|
190
|
-
// Manage panel state based on input type FIRST (before async operations)
|
|
191
|
-
if (type === AudioInputType.MICROPHONE) {
|
|
192
|
-
setActiveAudioPanel('devices');
|
|
193
|
-
} else if (type === AudioInputType.FILES) {
|
|
194
|
-
const hasFiles = inputConfiguration.audio.files.length > 0;
|
|
195
|
-
setActiveAudioPanel('files');
|
|
196
|
-
setShowFileList(hasFiles);
|
|
197
|
-
} else {
|
|
198
|
-
// Close panel for NONE, SCREEN_AUDIO, or any other type
|
|
199
|
-
setActiveAudioPanel(null);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Then update the input type only if changed
|
|
203
|
-
if (inputConfiguration.audio.type !== type) {
|
|
204
|
-
await setAudioInputType(type);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Update core only for audio when type changed (avoid touching video)
|
|
208
|
-
if (inputConfiguration.audio.type !== type) {
|
|
209
|
-
try {
|
|
210
|
-
const streams = await updateStreamsForCore();
|
|
211
|
-
await updateCoreConfig({
|
|
212
|
-
audioStream: streams.audioStream
|
|
213
|
-
});
|
|
214
|
-
} catch (error) {
|
|
215
|
-
console.error('Failed to update audio input:', error);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const getVideoIcon = () => {
|
|
221
|
-
switch (inputConfiguration.video.type) {
|
|
222
|
-
case VideoInputType.CAMERA:
|
|
223
|
-
return <VideoCameraIcon className="w-5 h-5" />;
|
|
224
|
-
case VideoInputType.SCREEN_VIDEO:
|
|
225
|
-
return <ComputerDesktopIcon className="w-5 h-5" />;
|
|
226
|
-
case VideoInputType.FILES:
|
|
227
|
-
return <DocumentIcon className="w-5 h-5" />;
|
|
228
|
-
default:
|
|
229
|
-
return <VideoCameraSlashIcon className="w-5 h-5" />;
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
|
|
233
|
-
const getAudioIcon = () => {
|
|
234
|
-
switch (inputConfiguration.audio.type) {
|
|
235
|
-
case AudioInputType.MICROPHONE:
|
|
236
|
-
return <MicrophoneIcon className="w-5 h-5" />;
|
|
237
|
-
case AudioInputType.SCREEN_AUDIO:
|
|
238
|
-
return <ComputerDesktopIcon className="w-5 h-5" />;
|
|
239
|
-
case AudioInputType.FILES:
|
|
240
|
-
return <DocumentIcon className="w-5 h-5" />;
|
|
241
|
-
default:
|
|
242
|
-
return <NoSymbolIcon className="w-5 h-5" />;
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
// Panel management handlers
|
|
247
|
-
const handleFilesAdded = () => {
|
|
248
|
-
setShowFileList(true);
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const handleBackToDropzone = () => {
|
|
252
|
-
setShowFileList(false);
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
// const closePanels = () => {
|
|
256
|
-
// setActiveVideoPanel(null);
|
|
257
|
-
// setActiveAudioPanel(null);
|
|
258
|
-
// setShowFileList(false);
|
|
259
|
-
// };
|
|
260
|
-
|
|
261
|
-
// Close panels when clicking outside or when menus close
|
|
262
|
-
// Note: menu close now controlled by toggles
|
|
263
|
-
|
|
264
|
-
// Panel content rendering
|
|
265
|
-
const renderVideoPanelContent = () => {
|
|
266
|
-
if (activeVideoPanel === 'devices') {
|
|
267
|
-
const handleDeviceSelect = async () => {
|
|
268
|
-
try {
|
|
269
|
-
const streams = await updateStreamsForCore();
|
|
270
|
-
await updateCoreConfig({ videoStream: streams.videoStream });
|
|
271
|
-
} catch (error) {
|
|
272
|
-
console.error('Failed to apply video device stream to core:', error);
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
return <DeviceSelectionList deviceType="video" onDeviceSelect={handleDeviceSelect} />;
|
|
276
|
-
} else if (activeVideoPanel === 'files') {
|
|
277
|
-
return showFileList ? (
|
|
278
|
-
<FileListPanel fileType="video" onBackToDropzone={handleBackToDropzone} />
|
|
279
|
-
) : (
|
|
280
|
-
<FileDropzonePanel fileType="video" onFilesAdded={handleFilesAdded} />
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
return null;
|
|
284
|
-
};
|
|
285
|
-
|
|
286
|
-
const renderAudioPanelContent = () => {
|
|
287
|
-
if (activeAudioPanel === 'devices') {
|
|
288
|
-
const handleDeviceSelect = async () => {
|
|
289
|
-
try {
|
|
290
|
-
const streams = await updateStreamsForCore();
|
|
291
|
-
await updateCoreConfig({ audioStream: streams.audioStream });
|
|
292
|
-
} catch (error) {
|
|
293
|
-
console.error('Failed to apply audio device stream to core:', error);
|
|
294
|
-
}
|
|
295
|
-
};
|
|
296
|
-
return <DeviceSelectionList deviceType="audio" onDeviceSelect={handleDeviceSelect} />;
|
|
297
|
-
} else if (activeAudioPanel === 'files') {
|
|
298
|
-
return showFileList ? (
|
|
299
|
-
<FileListPanel fileType="audio" onBackToDropzone={handleBackToDropzone} />
|
|
300
|
-
) : (
|
|
301
|
-
<FileDropzonePanel fileType="audio" onFilesAdded={handleFilesAdded} />
|
|
302
|
-
);
|
|
303
|
-
}
|
|
304
|
-
return null;
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
return (
|
|
308
|
-
<div className={`absolute ${className}`}>
|
|
309
|
-
{/* Video Input Expansion Panel */}
|
|
310
|
-
<InputExpansionPanel
|
|
311
|
-
isOpen={activeVideoPanel !== null}
|
|
312
|
-
title={activeVideoPanel === 'devices' ? 'Camera' : 'Files'}
|
|
313
|
-
className="bottom-0"
|
|
314
|
-
>
|
|
315
|
-
{renderVideoPanelContent()}
|
|
316
|
-
</InputExpansionPanel>
|
|
317
|
-
|
|
318
|
-
{/* Audio Input Expansion Panel */}
|
|
319
|
-
<InputExpansionPanel
|
|
320
|
-
isOpen={activeAudioPanel !== null}
|
|
321
|
-
title={activeAudioPanel === 'devices' ? 'Microphone' : 'Files'}
|
|
322
|
-
className="bottom-0"
|
|
323
|
-
>
|
|
324
|
-
{renderAudioPanelContent()}
|
|
325
|
-
</InputExpansionPanel>
|
|
326
|
-
|
|
327
|
-
<div className="flex flex-col space-y-2 pointer-events-auto">
|
|
328
|
-
{/* Video Control */}
|
|
329
|
-
<Popover
|
|
330
|
-
isOpen={showVideoMenu}
|
|
331
|
-
onOpenChange={(isOpen) => {
|
|
332
|
-
if (isOpen) {
|
|
333
|
-
openVideoMenu();
|
|
334
|
-
} else {
|
|
335
|
-
// Ignore auto-closes; we close via explicit actions
|
|
336
|
-
setTimeout(() => { /* swallow */ }, 0);
|
|
337
|
-
}
|
|
338
|
-
}}
|
|
339
|
-
placement="left"
|
|
340
|
-
shouldCloseOnBlur={false}
|
|
341
|
-
isDismissable={false}
|
|
342
|
-
shouldCloseOnInteractOutside={() => false}
|
|
343
|
-
isKeyboardDismissDisabled
|
|
344
|
-
portalContainer={typeof document !== 'undefined' ? document.getElementById('root') ?? undefined : undefined}
|
|
345
|
-
disableAnimation
|
|
346
|
-
>
|
|
347
|
-
<PopoverTrigger>
|
|
348
|
-
<Button
|
|
349
|
-
isIconOnly
|
|
350
|
-
variant="flat"
|
|
351
|
-
className={`bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10 ${
|
|
352
|
-
hasVideoInput ? 'ring-2 ring-primary-400' : ''
|
|
353
|
-
}`}
|
|
354
|
-
aria-label={hasVideoInput ? "Video input active" : "No video input"}
|
|
355
|
-
data-role="video-trigger"
|
|
356
|
-
onPointerDownCapture={(e) => { e.stopPropagation(); clickInsideVideoRef.current = true; setTimeout(() => { clickInsideVideoRef.current = false; }, 0); }}
|
|
357
|
-
onPress={(e: any) => { e?.preventDefault?.(); e?.stopPropagation?.();
|
|
358
|
-
if (showVideoMenu) { closeVideoMenu(); } else { openVideoMenu(); }
|
|
359
|
-
}}
|
|
360
|
-
>
|
|
361
|
-
{getVideoIcon()}
|
|
362
|
-
</Button>
|
|
363
|
-
</PopoverTrigger>
|
|
364
|
-
<PopoverContent
|
|
365
|
-
className="bg-black/90 backdrop-blur-sm border-white/20"
|
|
366
|
-
tabIndex={-1}
|
|
367
|
-
onPointerDownCapture={() => { clickInsideVideoRef.current = true; setTimeout(() => { clickInsideVideoRef.current = false; }, 0); }}
|
|
368
|
-
>
|
|
369
|
-
<Card className="bg-transparent border-0 shadow-none">
|
|
370
|
-
<CardBody className="p-3">
|
|
371
|
-
<div className="flex flex-col space-y-2">
|
|
372
|
-
<Button
|
|
373
|
-
size="sm"
|
|
374
|
-
variant="flat"
|
|
375
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
376
|
-
startContent={<ComputerDesktopIcon className="w-4 h-4" />}
|
|
377
|
-
onPress={() => handleVideoInputChange(VideoInputType.SCREEN_VIDEO)}
|
|
378
|
-
>
|
|
379
|
-
Screen Share
|
|
380
|
-
</Button>
|
|
381
|
-
<Button
|
|
382
|
-
size="sm"
|
|
383
|
-
variant="flat"
|
|
384
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
385
|
-
startContent={<DocumentIcon className="w-4 h-4" />}
|
|
386
|
-
onPress={() => handleVideoInputChange(VideoInputType.FILES)}
|
|
387
|
-
>
|
|
388
|
-
Video File
|
|
389
|
-
</Button>
|
|
390
|
-
<Button
|
|
391
|
-
size="sm"
|
|
392
|
-
variant="flat"
|
|
393
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
394
|
-
startContent={<VideoCameraIcon className="w-4 h-4" />}
|
|
395
|
-
onPress={() => handleVideoInputChange(VideoInputType.CAMERA)}
|
|
396
|
-
>
|
|
397
|
-
Camera
|
|
398
|
-
</Button>
|
|
399
|
-
<Button
|
|
400
|
-
size="sm"
|
|
401
|
-
variant="flat"
|
|
402
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
403
|
-
startContent={<NoSymbolIcon className="w-4 h-4" />}
|
|
404
|
-
onPress={() => handleVideoInputChange(VideoInputType.NONE)}
|
|
405
|
-
>
|
|
406
|
-
No Input
|
|
407
|
-
</Button>
|
|
408
|
-
</div>
|
|
409
|
-
</CardBody>
|
|
410
|
-
</Card>
|
|
411
|
-
</PopoverContent>
|
|
412
|
-
</Popover>
|
|
413
|
-
|
|
414
|
-
{/* Audio Control */}
|
|
415
|
-
<Popover
|
|
416
|
-
isOpen={showAudioMenu}
|
|
417
|
-
onOpenChange={(isOpen) => {
|
|
418
|
-
if (isOpen) {
|
|
419
|
-
openAudioMenu();
|
|
420
|
-
} else {
|
|
421
|
-
setTimeout(() => { /* swallow */ }, 0);
|
|
422
|
-
}
|
|
423
|
-
}}
|
|
424
|
-
placement="left"
|
|
425
|
-
shouldCloseOnBlur={false}
|
|
426
|
-
isDismissable={false}
|
|
427
|
-
shouldCloseOnInteractOutside={() => false}
|
|
428
|
-
isKeyboardDismissDisabled
|
|
429
|
-
portalContainer={typeof document !== 'undefined' ? document.getElementById('root') ?? undefined : undefined}
|
|
430
|
-
disableAnimation
|
|
431
|
-
>
|
|
432
|
-
<PopoverTrigger>
|
|
433
|
-
<Button
|
|
434
|
-
isIconOnly
|
|
435
|
-
variant="flat"
|
|
436
|
-
className={`bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10 ${
|
|
437
|
-
hasAudioInput ? 'ring-2 ring-primary-400' : ''
|
|
438
|
-
}`}
|
|
439
|
-
aria-label={hasAudioInput ? "Audio input active" : "No audio input"}
|
|
440
|
-
data-role="audio-trigger"
|
|
441
|
-
onPointerDownCapture={(e) => { e.stopPropagation(); clickInsideAudioRef.current = true; setTimeout(() => { clickInsideAudioRef.current = false; }, 0); }}
|
|
442
|
-
onPress={(e: any) => { e?.preventDefault?.(); e?.stopPropagation?.();
|
|
443
|
-
if (showAudioMenu) { closeAudioMenu(); } else { openAudioMenu(); }
|
|
444
|
-
}}
|
|
445
|
-
>
|
|
446
|
-
{getAudioIcon()}
|
|
447
|
-
</Button>
|
|
448
|
-
</PopoverTrigger>
|
|
449
|
-
<PopoverContent
|
|
450
|
-
className="bg-black/90 backdrop-blur-sm border-white/20"
|
|
451
|
-
tabIndex={-1}
|
|
452
|
-
onPointerDownCapture={() => { clickInsideAudioRef.current = true; setTimeout(() => { clickInsideAudioRef.current = false; }, 0); }}
|
|
453
|
-
>
|
|
454
|
-
<Card className="bg-transparent border-0 shadow-none">
|
|
455
|
-
<CardBody className="p-3">
|
|
456
|
-
<div className="flex flex-col space-y-2">
|
|
457
|
-
<Button
|
|
458
|
-
size="sm"
|
|
459
|
-
variant="flat"
|
|
460
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
461
|
-
startContent={<ComputerDesktopIcon className="w-4 h-4" />}
|
|
462
|
-
onPress={() => handleAudioInputChange(AudioInputType.SCREEN_AUDIO)}
|
|
463
|
-
>
|
|
464
|
-
Screen Audio
|
|
465
|
-
</Button>
|
|
466
|
-
<Button
|
|
467
|
-
size="sm"
|
|
468
|
-
variant="flat"
|
|
469
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
470
|
-
startContent={<DocumentIcon className="w-4 h-4" />}
|
|
471
|
-
onPress={() => handleAudioInputChange(AudioInputType.FILES)}
|
|
472
|
-
>
|
|
473
|
-
Audio File
|
|
474
|
-
</Button>
|
|
475
|
-
<Button
|
|
476
|
-
size="sm"
|
|
477
|
-
variant="flat"
|
|
478
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
479
|
-
startContent={<MicrophoneIcon className="w-4 h-4" />}
|
|
480
|
-
onPress={() => handleAudioInputChange(AudioInputType.MICROPHONE)}
|
|
481
|
-
>
|
|
482
|
-
Microphone
|
|
483
|
-
</Button>
|
|
484
|
-
<Button
|
|
485
|
-
size="sm"
|
|
486
|
-
variant="flat"
|
|
487
|
-
className="bg-white/10 text-white hover:bg-white/20 justify-start"
|
|
488
|
-
startContent={<NoSymbolIcon className="w-4 h-4" />}
|
|
489
|
-
onPress={() => handleAudioInputChange(AudioInputType.NONE)}
|
|
490
|
-
>
|
|
491
|
-
No Input
|
|
492
|
-
</Button>
|
|
493
|
-
</div>
|
|
494
|
-
</CardBody>
|
|
495
|
-
</Card>
|
|
496
|
-
</PopoverContent>
|
|
497
|
-
</Popover>
|
|
498
|
-
|
|
499
|
-
{/* Interaction Control */}
|
|
500
|
-
<Button
|
|
501
|
-
isIconOnly
|
|
502
|
-
variant="flat"
|
|
503
|
-
className={`bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10 ${
|
|
504
|
-
interactionEnabled ? 'ring-2 ring-primary-400' : ''
|
|
505
|
-
}`}
|
|
506
|
-
onPress={handleInteractionToggle}
|
|
507
|
-
aria-label={interactionEnabled ? "Disable interactions" : "Enable interactions"}
|
|
508
|
-
>
|
|
509
|
-
<CursorArrowRaysIcon className="w-5 h-5" />
|
|
510
|
-
</Button>
|
|
511
|
-
</div>
|
|
512
|
-
|
|
513
|
-
{/* Error Display */}
|
|
514
|
-
{streamError && (
|
|
515
|
-
<div className="absolute top-16 right-0 w-80 z-50">
|
|
516
|
-
<Card className="bg-red-900/90 backdrop-blur-sm border border-red-500/50 rounded-xl">
|
|
517
|
-
<CardBody className="p-4">
|
|
518
|
-
<div className="flex items-start justify-between">
|
|
519
|
-
<div className="flex-1">
|
|
520
|
-
<h4 className="text-red-100 font-medium text-sm mb-1">Device Error</h4>
|
|
521
|
-
<p className="text-red-200 text-xs">{streamError}</p>
|
|
522
|
-
</div>
|
|
523
|
-
<Button
|
|
524
|
-
isIconOnly
|
|
525
|
-
size="sm"
|
|
526
|
-
variant="light"
|
|
527
|
-
className="text-red-200 hover:text-red-100 min-w-6 w-6 h-6"
|
|
528
|
-
onPress={() => setStreamError('')}
|
|
529
|
-
aria-label="Dismiss error"
|
|
530
|
-
>
|
|
531
|
-
<NoSymbolIcon className="w-4 h-4" />
|
|
532
|
-
</Button>
|
|
533
|
-
</div>
|
|
534
|
-
</CardBody>
|
|
535
|
-
</Card>
|
|
536
|
-
</div>
|
|
537
|
-
)}
|
|
538
|
-
</div>
|
|
539
|
-
);
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
export default QuickInputControls;
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Button, Tooltip } from '@heroui/react';
|
|
3
|
-
import {
|
|
4
|
-
FolderOpenIcon,
|
|
5
|
-
AdjustmentsHorizontalIcon,
|
|
6
|
-
Cog6ToothIcon
|
|
7
|
-
} from '@heroicons/react/24/outline';
|
|
8
|
-
import MenuContainer from './MenuContainer';
|
|
9
|
-
import ProjectSwitcherMenu from './ProjectSwitcherMenu';
|
|
10
|
-
import ParametersMenu from './ParametersMenu';
|
|
11
|
-
import SettingsMenu from './SettingsMenu';
|
|
12
|
-
|
|
13
|
-
type MenuType = 'projects' | 'parameters' | 'settings' | null;
|
|
14
|
-
|
|
15
|
-
interface SDKMenuSystemProps {
|
|
16
|
-
currentProject?: any;
|
|
17
|
-
onProjectSwitch?: (project: any) => void;
|
|
18
|
-
// Note: UI CRUD removed from ProjectSwitcherMenu - onProjectCreate, onProjectDelete, onProjectRename no longer used
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const SDKMenuSystem: React.FC<SDKMenuSystemProps> = ({
|
|
22
|
-
currentProject,
|
|
23
|
-
onProjectSwitch,
|
|
24
|
-
}) => {
|
|
25
|
-
// Projects menu should be open by default as specified by user
|
|
26
|
-
const [active, setActive] = useState<MenuType>('projects');
|
|
27
|
-
|
|
28
|
-
const toggle = (type: MenuType) => setActive((prev) => (prev === type ? null : type));
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
<>
|
|
32
|
-
<div className="absolute top-4 left-4 z-60 flex space-x-2 pointer-events-auto">
|
|
33
|
-
{/* Project Switcher Button - First and opened by default */}
|
|
34
|
-
<Tooltip content="Projects" placement="bottom" className="bg-black/80 text-white">
|
|
35
|
-
<Button
|
|
36
|
-
isIconOnly
|
|
37
|
-
variant={active === 'projects' ? 'solid' : 'flat'}
|
|
38
|
-
color={active === 'projects' ? 'primary' : 'default'}
|
|
39
|
-
className="bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10"
|
|
40
|
-
onPress={() => toggle('projects')}
|
|
41
|
-
aria-label="Toggle projects"
|
|
42
|
-
>
|
|
43
|
-
<FolderOpenIcon className="w-5 h-5" />
|
|
44
|
-
</Button>
|
|
45
|
-
</Tooltip>
|
|
46
|
-
|
|
47
|
-
{/* Parameters Button */}
|
|
48
|
-
<Tooltip content="Parameters" placement="bottom" className="bg-black/80 text-white">
|
|
49
|
-
<Button
|
|
50
|
-
isIconOnly
|
|
51
|
-
variant={active === 'parameters' ? 'solid' : 'flat'}
|
|
52
|
-
color={active === 'parameters' ? 'primary' : 'default'}
|
|
53
|
-
className="bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10"
|
|
54
|
-
onPress={() => toggle('parameters')}
|
|
55
|
-
aria-label="Toggle parameters"
|
|
56
|
-
>
|
|
57
|
-
<AdjustmentsHorizontalIcon className="w-5 h-5" />
|
|
58
|
-
</Button>
|
|
59
|
-
</Tooltip>
|
|
60
|
-
|
|
61
|
-
{/* Settings Button */}
|
|
62
|
-
<Tooltip content="Settings" placement="bottom" className="bg-black/80 text-white">
|
|
63
|
-
<Button
|
|
64
|
-
isIconOnly
|
|
65
|
-
variant={active === 'settings' ? 'solid' : 'flat'}
|
|
66
|
-
color={active === 'settings' ? 'primary' : 'default'}
|
|
67
|
-
className="bg-black/20 backdrop-blur-sm border border-white/10 text-white hover:bg-white/10"
|
|
68
|
-
onPress={() => toggle('settings')}
|
|
69
|
-
aria-label="Toggle settings"
|
|
70
|
-
>
|
|
71
|
-
<Cog6ToothIcon className="w-5 h-5" />
|
|
72
|
-
</Button>
|
|
73
|
-
</Tooltip>
|
|
74
|
-
</div>
|
|
75
|
-
|
|
76
|
-
{active && (
|
|
77
|
-
<MenuContainer isOpen={!!active} activeMenu={active as any} onClose={() => setActive(null)}>
|
|
78
|
-
{active === 'projects' && (
|
|
79
|
-
<ProjectSwitcherMenu
|
|
80
|
-
currentProject={currentProject}
|
|
81
|
-
onProjectSwitch={onProjectSwitch}
|
|
82
|
-
/>
|
|
83
|
-
)}
|
|
84
|
-
{active === 'parameters' && (
|
|
85
|
-
<ParametersMenu hidePresetsControls />
|
|
86
|
-
)}
|
|
87
|
-
{active === 'settings' && (
|
|
88
|
-
<SettingsMenu />
|
|
89
|
-
)}
|
|
90
|
-
</MenuContainer>
|
|
91
|
-
)}
|
|
92
|
-
</>
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
export default SDKMenuSystem;
|