@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.
Files changed (77) hide show
  1. package/README.md +70 -63
  2. package/bin/viji.js +9 -29
  3. package/dist/assets/artist-dts-BHUsvSI6.js +613 -0
  4. package/dist/assets/artist-dts-p5-Cyw8vmy_.js +736 -0
  5. package/dist/assets/core-CiQx3w0t.js +12 -0
  6. package/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  7. package/dist/assets/docs-api-PBLtY4Ni.js +12381 -0
  8. package/dist/assets/engine-javascript-CXyY7cc8.js +141 -0
  9. package/dist/assets/essentia-wasm.web-0S-sW98u-CYV1l1zv.js +38 -0
  10. package/dist/assets/essentia.js-core.es-DnrJE0uR-DOSrF5_G.js +32 -0
  11. package/dist/assets/glsl-DMyvO4G4.js +1 -0
  12. package/dist/assets/index-BhFxsauQ.js +215 -0
  13. package/dist/assets/index-BqhVeA7U.css +1 -0
  14. package/dist/assets/index-T4TOjvD0.js +1 -0
  15. package/dist/assets/index-Wz9WqGqz.js +52 -0
  16. package/dist/assets/index-t24aGwla.js +1 -0
  17. package/dist/assets/javascript-wDzz0qaB.js +1 -0
  18. package/dist/assets/shader-uniforms-GdaUkQPK.js +1 -0
  19. package/dist/assets/typescript-BPQ3VLAy.js +1 -0
  20. package/dist/assets/viji.worker-CQSJ0SiO-ljtBlcNZ.js +27018 -0
  21. package/{index.html → dist/index.html} +2 -1
  22. package/package.json +31 -35
  23. package/src/cli/commands/build.js +50 -99
  24. package/src/cli/commands/create.js +49 -46
  25. package/src/cli/commands/dev.js +30 -97
  26. package/src/cli/server/dev-server.js +233 -0
  27. package/src/cli/server/scene-scanner.js +93 -0
  28. package/src/cli/server/vite-scene-plugin.d.ts +2 -0
  29. package/src/cli/server/vite-scene-plugin.js +134 -0
  30. package/src/cli/utils/cli-utils.js +29 -139
  31. package/src/cli/utils/scene-compiler.js +10 -17
  32. package/src/templates/scene-templates.js +85 -0
  33. package/.gitignore +0 -29
  34. package/eslint.config.js +0 -37
  35. package/postcss.config.js +0 -6
  36. package/scenes/audio-visualizer/main.js +0 -287
  37. package/scenes/core-demo/main.js +0 -532
  38. package/scenes/demo-scene/main.js +0 -619
  39. package/scenes/global.d.ts +0 -15
  40. package/scenes/particle-system/main.js +0 -349
  41. package/scenes/tsconfig.json +0 -12
  42. package/scenes/video-mirror/main.ts +0 -436
  43. package/src/App.css +0 -42
  44. package/src/App.tsx +0 -279
  45. package/src/cli/commands/init.js +0 -262
  46. package/src/components/SDKPage.tsx +0 -337
  47. package/src/components/core/CoreContainer.tsx +0 -126
  48. package/src/components/ui/DeviceSelectionList.tsx +0 -137
  49. package/src/components/ui/FPSCounter.tsx +0 -78
  50. package/src/components/ui/FileDropzonePanel.tsx +0 -120
  51. package/src/components/ui/FileListPanel.tsx +0 -285
  52. package/src/components/ui/InputExpansionPanel.tsx +0 -31
  53. package/src/components/ui/MediaPlayerControls.tsx +0 -191
  54. package/src/components/ui/MenuContainer.tsx +0 -71
  55. package/src/components/ui/ParametersMenu.tsx +0 -797
  56. package/src/components/ui/ProjectSwitcherMenu.tsx +0 -192
  57. package/src/components/ui/QuickInputControls.tsx +0 -542
  58. package/src/components/ui/SDKMenuSystem.tsx +0 -96
  59. package/src/components/ui/SettingsMenu.tsx +0 -346
  60. package/src/components/ui/SimpleInputControls.tsx +0 -137
  61. package/src/index.css +0 -68
  62. package/src/main.tsx +0 -10
  63. package/src/scenes-hmr.ts +0 -158
  64. package/src/services/project-filesystem.ts +0 -436
  65. package/src/stores/scene-player/index.ts +0 -3
  66. package/src/stores/scene-player/input-manager.store.ts +0 -1045
  67. package/src/stores/scene-player/scene-session.store.ts +0 -659
  68. package/src/styles/globals.css +0 -111
  69. package/src/templates/minimal-template.js +0 -11
  70. package/src/utils/debounce.js +0 -34
  71. package/src/vite-env.d.ts +0 -1
  72. package/tailwind.config.js +0 -18
  73. package/tsconfig.app.json +0 -27
  74. package/tsconfig.json +0 -27
  75. package/tsconfig.node.json +0 -27
  76. package/vite.config.ts +0 -54
  77. /package/{public → dist}/favicon.png +0 -0
@@ -1,120 +0,0 @@
1
- import React, { useRef, useState } from 'react';
2
- import { Button } from '@heroui/react';
3
- import { DocumentIcon, FolderIcon } from '@heroicons/react/24/outline';
4
- import { useInputManagerStore } from '../../stores/scene-player/input-manager.store';
5
-
6
- interface FileDropzonePanelProps {
7
- fileType: 'audio' | 'video';
8
- onFilesAdded?: () => void;
9
- }
10
-
11
- const FileDropzonePanel: React.FC<FileDropzonePanelProps> = ({
12
- fileType,
13
- onFilesAdded
14
- }) => {
15
- const fileInputRef = useRef<HTMLInputElement>(null);
16
- const [isDragOver, setIsDragOver] = useState(false);
17
- const [isLoading, setIsLoading] = useState(false);
18
-
19
- const { addAudioFiles, addVideoFiles } = useInputManagerStore();
20
-
21
- const acceptedTypes = fileType === 'audio'
22
- ? 'audio/*'
23
- : 'video/*,image/*';
24
-
25
- const handleFiles = async (files: FileList) => {
26
- if (files.length === 0) return;
27
-
28
- setIsLoading(true);
29
- try {
30
- const fileArray = Array.from(files);
31
-
32
- if (fileType === 'audio') {
33
- await addAudioFiles(fileArray);
34
- } else {
35
- await addVideoFiles(fileArray);
36
- }
37
-
38
- onFilesAdded?.();
39
- } catch (error) {
40
- console.error(`Failed to add ${fileType} files:`, error);
41
- } finally {
42
- setIsLoading(false);
43
- }
44
- };
45
-
46
- const handleDragOver = (e: React.DragEvent) => {
47
- e.preventDefault();
48
- setIsDragOver(true);
49
- };
50
-
51
- const handleDragLeave = (e: React.DragEvent) => {
52
- e.preventDefault();
53
- setIsDragOver(false);
54
- };
55
-
56
- const handleDrop = (e: React.DragEvent) => {
57
- e.preventDefault();
58
- setIsDragOver(false);
59
-
60
- const files = e.dataTransfer.files;
61
- handleFiles(files);
62
- };
63
-
64
- const handleBrowseClick = () => {
65
- fileInputRef.current?.click();
66
- };
67
-
68
- const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
69
- const files = e.target.files;
70
- if (files) {
71
- handleFiles(files);
72
- }
73
- };
74
-
75
- return (
76
- <div className="space-y-4">
77
- {/* Dropzone Area */}
78
- <div
79
- className={`border-2 border-dashed rounded-lg p-8 text-center transition-colors ${
80
- isDragOver
81
- ? 'border-primary-400 bg-primary-400/10'
82
- : 'border-gray-600 bg-gray-800/30'
83
- }`}
84
- onDragOver={handleDragOver}
85
- onDragLeave={handleDragLeave}
86
- onDrop={handleDrop}
87
- >
88
- <DocumentIcon className="w-12 h-12 mx-auto mb-4 text-gray-400" />
89
- <p className="text-gray-300 mb-2">
90
- Drag & drop {fileType} files here
91
- </p>
92
- <p className="text-gray-500 text-sm">or browse</p>
93
- </div>
94
-
95
- {/* Browse Button */}
96
- <Button
97
- variant="flat"
98
- className="w-full bg-gray-700 text-white hover:bg-gray-600"
99
- startContent={<FolderIcon className="w-4 h-4" />}
100
- onPress={handleBrowseClick}
101
- isLoading={isLoading}
102
- isDisabled={isLoading}
103
- >
104
- {isLoading ? `Loading ${fileType} files...` : 'Browse folder'}
105
- </Button>
106
-
107
- {/* Hidden File Input */}
108
- <input
109
- ref={fileInputRef}
110
- type="file"
111
- accept={acceptedTypes}
112
- multiple
113
- className="hidden"
114
- onChange={handleFileInputChange}
115
- />
116
- </div>
117
- );
118
- };
119
-
120
- export default FileDropzonePanel;
@@ -1,285 +0,0 @@
1
- import React, { useRef, useState, useEffect } from 'react';
2
- import { Button } from '@heroui/react';
3
- import { CheckIcon, TrashIcon } from '@heroicons/react/24/outline';
4
- import { useInputManagerStore } from '../../stores/scene-player/input-manager.store';
5
- import MediaPlayerControls from './MediaPlayerControls';
6
-
7
- interface ScrollingTextProps {
8
- text: string;
9
- className?: string;
10
- }
11
-
12
- const ScrollingText: React.FC<ScrollingTextProps> = ({ text, className = '' }) => {
13
- const containerRef = useRef<HTMLDivElement>(null);
14
- const contentRef = useRef<HTMLDivElement>(null);
15
- const [translateX, setTranslateX] = useState(0);
16
- const [durationMs, setDurationMs] = useState(0);
17
- const [isOverflowing, setIsOverflowing] = useState(false);
18
- const [isHovered, setIsHovered] = useState(false);
19
-
20
- useEffect(() => {
21
- const container = containerRef.current;
22
- const content = contentRef.current;
23
- if (!container || !content) return;
24
- const overflow = content.scrollWidth > container.clientWidth + 2; // tolerance
25
- setIsOverflowing(overflow);
26
- if (!overflow) {
27
- setTranslateX(0);
28
- setDurationMs(0);
29
- }
30
- }, [text]);
31
-
32
- const handleMouseEnter = () => {
33
- setIsHovered(true);
34
- const container = containerRef.current;
35
- const content = contentRef.current;
36
- if (!container || !content) return;
37
- const overflow = content.scrollWidth > container.clientWidth + 2;
38
- if (!overflow) return;
39
- const delta = content.scrollWidth - container.clientWidth;
40
- // speed ~ 60px/s
41
- const ms = Math.max(300, Math.round((delta / 60) * 1000));
42
- setDurationMs(ms);
43
- setTranslateX(-delta);
44
- };
45
-
46
- const handleMouseLeave = () => {
47
- setIsHovered(false);
48
- setDurationMs(250);
49
- setTranslateX(0);
50
- };
51
-
52
- return (
53
- <div
54
- ref={containerRef}
55
- className={`relative overflow-hidden ${className}`}
56
- onMouseEnter={handleMouseEnter}
57
- onMouseLeave={handleMouseLeave}
58
- title={text}
59
- >
60
- {/* Fade overlays */}
61
- {isOverflowing && (
62
- <>
63
- {isHovered && (
64
- <div className="pointer-events-none absolute inset-y-0 left-0 w-6 z-10" style={{
65
- background: 'linear-gradient(to right, rgba(0,0,0,0.9), rgba(0,0,0,0))'
66
- }} />
67
- )}
68
- <div className="pointer-events-none absolute inset-y-0 right-0 w-6 z-10" style={{
69
- background: 'linear-gradient(to left, rgba(0,0,0,0.9), rgba(0,0,0,0))'
70
- }} />
71
- </>
72
- )}
73
- <div
74
- ref={contentRef}
75
- className="whitespace-nowrap inline-block"
76
- style={{
77
- transform: `translateX(${translateX}px)`,
78
- transition: durationMs ? `transform ${durationMs}ms linear` : undefined,
79
- }}
80
- >
81
- {text}
82
- </div>
83
- </div>
84
- );
85
- };
86
-
87
- interface FileListPanelProps {
88
- fileType: 'audio' | 'video';
89
- onBackToDropzone?: () => void;
90
- }
91
-
92
- const FileListPanel: React.FC<FileListPanelProps> = ({
93
- fileType,
94
- onBackToDropzone
95
- }) => {
96
- const {
97
- inputConfiguration,
98
- removeAudioFile,
99
- removeVideoFile,
100
- playAudio,
101
- playVideo
102
- } = useInputManagerStore();
103
-
104
- // Files and selection are used directly from inputConfiguration below for strict typing
105
-
106
- const handleFileSelect = async (fileId: string) => {
107
- try {
108
- if (fileType === 'audio') {
109
- await playAudio(fileId);
110
- } else {
111
- await playVideo(fileId);
112
- }
113
- } catch (error) {
114
- console.error(`Failed to play ${fileType} file:`, error);
115
- }
116
- };
117
-
118
- const handleFileRemove = (fileId: string) => {
119
- if (fileType === 'audio') {
120
- removeAudioFile(fileId);
121
- } else {
122
- removeVideoFile(fileId);
123
- }
124
- };
125
-
126
- const formatDuration = (seconds: number) => {
127
- const mins = Math.floor(seconds / 60);
128
- const secs = Math.floor(seconds % 60);
129
- return `${mins}:${secs.toString().padStart(2, '0')}`;
130
- };
131
-
132
- const hasNoFiles = fileType === 'audio'
133
- ? inputConfiguration.audio.files.length === 0
134
- : inputConfiguration.video.files.length === 0;
135
-
136
- if (hasNoFiles) {
137
- return (
138
- <div className="text-center py-8 text-gray-400">
139
- <p>No {fileType} files loaded</p>
140
- <Button
141
- size="sm"
142
- variant="flat"
143
- className="mt-2 bg-white/10 text-white hover:bg-white/20"
144
- onPress={onBackToDropzone}
145
- >
146
- Add Files
147
- </Button>
148
- </div>
149
- );
150
- }
151
-
152
- return (
153
- <div className="space-y-4">
154
- {/* File List */}
155
- <div className="space-y-2 max-h-64 overflow-y-auto">
156
- {fileType === 'audio'
157
- ? (
158
- inputConfiguration.audio.files.map((file) => {
159
- const isSelected = file.id === inputConfiguration.audio.playerState.currentTrackId;
160
- const displayName = file.name;
161
- // Note: SDK AudioFile doesn't have title/artist properties
162
-
163
- return (
164
- <div
165
- key={file.id}
166
- className={`p-3 rounded-lg border transition-colors ${
167
- isSelected
168
- ? 'bg-white/10 border-primary-400'
169
- : 'bg-transparent border-gray-600 hover:bg-white/5'
170
- }`}
171
- >
172
- <div className="flex items-center w-full">
173
- <div
174
- role="button"
175
- tabIndex={0}
176
- className="flex items-center flex-1 justify-start px-0 py-0 h-auto min-h-0 bg-transparent hover:bg-transparent min-w-0"
177
- onClick={() => handleFileSelect(file.id)}
178
- onKeyDown={(e) => {
179
- if (e.key === 'Enter' || e.key === ' ') {
180
- e.preventDefault();
181
- handleFileSelect(file.id);
182
- }
183
- }}
184
- >
185
- <div className="flex-shrink-0 w-5 h-5 mr-3 flex items-center justify-center">
186
- {isSelected && (
187
- <CheckIcon className="w-4 h-4 text-green-400" />
188
- )}
189
- </div>
190
- <div className="flex-1 text-left min-w-0">
191
- <ScrollingText text={displayName} className="text-white font-medium" />
192
- {typeof file.duration === 'number' && (
193
- <div className="text-gray-500 text-xs">
194
- {formatDuration(file.duration)}
195
- </div>
196
- )}
197
- </div>
198
- </div>
199
- <Button
200
- isIconOnly
201
- size="sm"
202
- variant="flat"
203
- className="ml-2 bg-transparent hover:bg-red-500/20 text-gray-400 hover:text-red-400"
204
- onPress={() => handleFileRemove(file.id)}
205
- >
206
- <TrashIcon className="w-4 h-4" />
207
- </Button>
208
- </div>
209
- </div>
210
- );
211
- })
212
- ) : (
213
- inputConfiguration.video.files.map((file) => {
214
- const isSelected = file.id === inputConfiguration.video.playerState.currentFileId;
215
- const displayName = file.name;
216
-
217
- return (
218
- <div
219
- key={file.id}
220
- className={`p-3 rounded-lg border transition-colors ${
221
- isSelected
222
- ? 'bg-white/10 border-primary-400'
223
- : 'bg-transparent border-gray-600 hover:bg-white/5'
224
- }`}
225
- >
226
- <div className="flex items-center w-full">
227
- <div
228
- role="button"
229
- tabIndex={0}
230
- className="flex items-center flex-1 justify-start px-0 py-0 h-auto min-h-0 bg-transparent hover:bg-transparent min-w-0"
231
- onClick={() => handleFileSelect(file.id)}
232
- onKeyDown={(e) => {
233
- if (e.key === 'Enter' || e.key === ' ') {
234
- e.preventDefault();
235
- handleFileSelect(file.id);
236
- }
237
- }}
238
- >
239
- <div className="flex-shrink-0 w-5 h-5 mr-3 flex items-center justify-center">
240
- {isSelected && (
241
- <CheckIcon className="w-4 h-4 text-green-400" />
242
- )}
243
- </div>
244
- <div className="flex-1 text-left min-w-0">
245
- <ScrollingText text={displayName} className="text-white font-medium" />
246
- {typeof file.duration === 'number' && (
247
- <div className="text-gray-500 text-xs">
248
- {formatDuration(file.duration)}
249
- </div>
250
- )}
251
- </div>
252
- </div>
253
- <Button
254
- isIconOnly
255
- size="sm"
256
- variant="flat"
257
- className="ml-2 bg-transparent hover:bg-red-500/20 text-gray-400 hover:text-red-400"
258
- onPress={() => handleFileRemove(file.id)}
259
- >
260
- <TrashIcon className="w-4 h-4" />
261
- </Button>
262
- </div>
263
- </div>
264
- );
265
- })
266
- )}
267
- </div>
268
-
269
- {/* Media Player Controls (only for audio) */}
270
- {fileType === 'audio' && <MediaPlayerControls fileType={fileType} />}
271
-
272
- {/* Add More Files Button */}
273
- <Button
274
- size="sm"
275
- variant="flat"
276
- className="w-full bg-white/10 text-white hover:bg-white/20"
277
- onPress={onBackToDropzone}
278
- >
279
- Add More Files
280
- </Button>
281
- </div>
282
- );
283
- };
284
-
285
- export default FileListPanel;
@@ -1,31 +0,0 @@
1
- import React from 'react';
2
- import { Card } from '@heroui/react';
3
-
4
- interface InputExpansionPanelProps {
5
- isOpen: boolean;
6
- title: string;
7
- children: React.ReactNode;
8
- className?: string;
9
- }
10
-
11
- const InputExpansionPanel: React.FC<InputExpansionPanelProps> = ({
12
- isOpen,
13
- title,
14
- children,
15
- className = ""
16
- }) => {
17
- if (!isOpen) return null;
18
-
19
- return (
20
- <div className={`absolute right-16 w-80 pointer-events-auto z-[9999] ${className}`} style={{ zIndex: 9999 }}>
21
- <Card className="bg-black/90 backdrop-blur-sm border border-white/20 rounded-xl">
22
- <div className="p-4">
23
- <h3 className="text-white font-medium text-lg mb-4">{title}</h3>
24
- {children}
25
- </div>
26
- </Card>
27
- </div>
28
- );
29
- };
30
-
31
- export default InputExpansionPanel;
@@ -1,191 +0,0 @@
1
- import React from 'react';
2
- import { Button, Slider } from '@heroui/react';
3
- import {
4
- PlayIcon,
5
- PauseIcon,
6
- BackwardIcon,
7
- ForwardIcon,
8
- SpeakerWaveIcon,
9
- SpeakerXMarkIcon
10
- } from '@heroicons/react/24/solid';
11
- import { useInputManagerStore } from '../../stores/scene-player/input-manager.store';
12
-
13
- interface MediaPlayerControlsProps {
14
- fileType: 'audio' | 'video';
15
- }
16
-
17
- const MediaPlayerControls: React.FC<MediaPlayerControlsProps> = ({ fileType }) => {
18
- const {
19
- inputConfiguration,
20
- playAudio,
21
- pauseAudio,
22
- playVideo,
23
- pauseVideo,
24
- nextTrack,
25
- previousTrack,
26
- seekAudio,
27
- seekVideo,
28
- setAudioVolume,
29
- setAudioMuted
30
- } = useInputManagerStore();
31
-
32
- const playerState = fileType === 'audio'
33
- ? inputConfiguration.audio.playerState
34
- : inputConfiguration.video.playerState;
35
- const mediaDuration = fileType === 'audio'
36
- ? (inputConfiguration.audio.files.find(f => f.id === inputConfiguration.audio.playerState.currentTrackId)?.duration || 0)
37
- : (inputConfiguration.video.files.find(f => f.id === inputConfiguration.video.playerState.currentFileId)?.duration || 0);
38
-
39
- const volume = inputConfiguration.audio.volume;
40
- const isMuted = (inputConfiguration.audio as any).isMuted ?? (inputConfiguration.audio as any).muted;
41
-
42
- const handlePlayPause = async () => {
43
- try {
44
- if (playerState.isPlaying) {
45
- if (fileType === 'audio') {
46
- pauseAudio();
47
- } else {
48
- pauseVideo();
49
- }
50
- } else {
51
- if (fileType === 'audio') {
52
- await playAudio();
53
- } else {
54
- await playVideo();
55
- }
56
- }
57
- } catch (error) {
58
- console.error(`Failed to ${playerState.isPlaying ? 'pause' : 'play'} ${fileType}:`, error);
59
- }
60
- };
61
-
62
- const handleSeek = (time: number) => {
63
- if (fileType === 'audio') {
64
- seekAudio(time);
65
- } else {
66
- seekVideo(time);
67
- }
68
- };
69
-
70
- const handleVolumeChange = (newVolume: number) => {
71
- setAudioVolume(newVolume / 100);
72
- };
73
-
74
- const handleMuteToggle = () => {
75
- setAudioMuted(!isMuted);
76
- };
77
-
78
- const formatTime = (seconds: number) => {
79
- const mins = Math.floor(seconds / 60);
80
- const secs = Math.floor(seconds % 60);
81
- return `${mins}:${secs.toString().padStart(2, '0')}`;
82
- };
83
-
84
- const hasFiles = fileType === 'audio'
85
- ? inputConfiguration.audio.files.length > 0
86
- : inputConfiguration.video.files.length > 0;
87
-
88
- if (!hasFiles) {
89
- return null;
90
- }
91
-
92
- return (
93
- <div className="space-y-3 pt-3 border-t border-gray-600">
94
- {/* Progress Bar */}
95
- {mediaDuration > 0 && (
96
- <div className="space-y-2">
97
- <Slider
98
- aria-label="Playback progress"
99
- step={1}
100
- minValue={0}
101
- maxValue={mediaDuration}
102
- value={playerState.currentTime}
103
- onChange={(value) => handleSeek(value as number)}
104
- className="w-full"
105
- size="sm"
106
- color="primary"
107
- />
108
- <div className="flex justify-between text-xs text-gray-400">
109
- <span>{formatTime(playerState.currentTime)}</span>
110
- <span>{formatTime(mediaDuration)}</span>
111
- </div>
112
- </div>
113
- )}
114
-
115
- {/* Main Controls */}
116
- <div className="flex items-center justify-center space-x-2">
117
- <Button
118
- isIconOnly
119
- size="sm"
120
- variant="flat"
121
- className="bg-white/10 text-white hover:bg-white/20"
122
- onPress={previousTrack}
123
- isDisabled={!hasFiles}
124
- >
125
- <BackwardIcon className="w-4 h-4" />
126
- </Button>
127
-
128
- <Button
129
- isIconOnly
130
- variant="flat"
131
- className="bg-primary-500 text-white hover:bg-primary-600 w-10 h-10"
132
- onPress={handlePlayPause}
133
- isDisabled={!hasFiles}
134
- >
135
- {playerState.isPlaying ? (
136
- <PauseIcon className="w-5 h-5" />
137
- ) : (
138
- <PlayIcon className="w-5 h-5" />
139
- )}
140
- </Button>
141
-
142
- <Button
143
- isIconOnly
144
- size="sm"
145
- variant="flat"
146
- className="bg-white/10 text-white hover:bg-white/20"
147
- onPress={nextTrack}
148
- isDisabled={!hasFiles}
149
- >
150
- <ForwardIcon className="w-4 h-4" />
151
- </Button>
152
- </div>
153
-
154
- {/* Volume Control (Audio only) */}
155
- {fileType === 'audio' && (
156
- <div className="flex items-center space-x-2">
157
- <Button
158
- isIconOnly
159
- size="sm"
160
- variant="flat"
161
- className="bg-transparent text-white hover:bg-white/10"
162
- onPress={handleMuteToggle}
163
- >
164
- {isMuted ? (
165
- <SpeakerXMarkIcon className="w-4 h-4" />
166
- ) : (
167
- <SpeakerWaveIcon className="w-4 h-4" />
168
- )}
169
- </Button>
170
- <Slider
171
- aria-label="Volume"
172
- step={1}
173
- minValue={0}
174
- maxValue={100}
175
- value={isMuted ? 0 : volume * 100}
176
- onChange={(val) => {
177
- const numeric = Array.isArray(val) ? val[0] : val;
178
- handleVolumeChange(numeric as number);
179
- }}
180
- className="flex-1"
181
- size="sm"
182
- color="primary"
183
- isDisabled={isMuted}
184
- />
185
- </div>
186
- )}
187
- </div>
188
- );
189
- };
190
-
191
- export default MediaPlayerControls;
@@ -1,71 +0,0 @@
1
- import React from 'react';
2
- import { Button, Card, CardBody } from '@heroui/react';
3
- import { XMarkIcon } from '@heroicons/react/24/outline';
4
-
5
- interface MenuContainerProps {
6
- isOpen: boolean;
7
- activeMenu: 'projects' | 'parameters' | 'settings' | null;
8
- onClose: () => void;
9
- children: React.ReactNode;
10
- className?: string;
11
- }
12
-
13
- const MenuContainer: React.FC<MenuContainerProps> = ({
14
- isOpen,
15
- activeMenu,
16
- onClose,
17
- children,
18
- className = '',
19
- }) => {
20
- if (!isOpen || !activeMenu) {
21
- return null;
22
- }
23
-
24
- const getMenuTitle = (menu: string) => {
25
- switch (menu) {
26
- case 'projects': return 'Projects';
27
- case 'parameters': return 'Parameters';
28
- case 'settings': return 'Settings';
29
- default: return menu.charAt(0).toUpperCase() + menu.slice(1);
30
- }
31
- };
32
-
33
- return (
34
- <div className={`absolute left-4 z-50 pointer-events-auto ${className}`} style={{ top: '72px' }}>
35
- {/* Menu Panel - Using Card component like SceneInfoBar */}
36
- <Card className="bg-black/20 backdrop-blur-sm border-white/10 w-[28rem]" style={{ height: 'calc(100vh - 192px)' }}>
37
- <CardBody className="p-0 flex flex-col h-full">
38
- {/* Header */}
39
- <div className="flex items-center justify-between p-4 border-b border-white/10 flex-shrink-0">
40
- <h2 className="text-white font-semibold text-lg">
41
- {getMenuTitle(activeMenu)}
42
- </h2>
43
- <Button
44
- isIconOnly
45
- variant="flat"
46
- size="sm"
47
- className="bg-white/10 text-white hover:bg-white/20 border-white/20"
48
- onPress={onClose}
49
- aria-label="Close menu"
50
- >
51
- <XMarkIcon className="w-5 h-5" />
52
- </Button>
53
- </div>
54
-
55
- {/* Menu Content */}
56
- <div className="flex-1 overflow-y-auto overflow-x-hidden p-4">
57
- {children}
58
- </div>
59
- </CardBody>
60
- </Card>
61
-
62
- {/* Backdrop (for mobile) */}
63
- <div
64
- className="fixed inset-0 bg-black/20 backdrop-blur-sm -z-10 md:hidden"
65
- onClick={onClose}
66
- />
67
- </div>
68
- );
69
- };
70
-
71
- export default MenuContainer;