@minnai/create-aura-app 0.0.12 → 0.0.14
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/package.json +1 -1
- package/templates/starter/package.json +2 -2
- package/templates/starter/src/src/App.css +0 -32
- package/templates/starter/src/src/App.tsx +0 -82
- package/templates/starter/src/src/ambiance/currency-air/index.tsx +0 -25
- package/templates/starter/src/src/ambiance/currency-air/logic.ts +0 -49
- package/templates/starter/src/src/ambiance/currency-air/manifest.ts +0 -15
- package/templates/starter/src/src/ambiance/currency-air/resources.ts +0 -16
- package/templates/starter/src/src/ambiance/currency-air/ui/index.tsx +0 -42
- package/templates/starter/src/src/ambiance/index.ts +0 -48
- package/templates/starter/src/src/ambiance/stocks-air/index.ts +0 -3
- package/templates/starter/src/src/ambiance/stocks-air/index.tsx +0 -28
- package/templates/starter/src/src/ambiance/stocks-air/logic.ts +0 -87
- package/templates/starter/src/src/ambiance/stocks-air/manifest.ts +0 -15
- package/templates/starter/src/src/ambiance/stocks-air/resources.ts +0 -23
- package/templates/starter/src/src/ambiance/stocks-air/ui/index.tsx +0 -67
- package/templates/starter/src/src/assets/react.svg +0 -1
- package/templates/starter/src/src/components/AnalyticsTracker.tsx +0 -13
- package/templates/starter/src/src/components/Playground/CodeEditor.tsx +0 -121
- package/templates/starter/src/src/components/Playground/Debugger.tsx +0 -71
- package/templates/starter/src/src/components/Playground/Playground.tsx +0 -221
- package/templates/starter/src/src/components/Playground/Sidebar.tsx +0 -68
- package/templates/starter/src/src/components/ProjectSidebar/ProjectSidebar.tsx +0 -219
- package/templates/starter/src/src/components/TourGuide/TourGuide.tsx +0 -16
- package/templates/starter/src/src/components/TourGuide/index.ts +0 -1
- package/templates/starter/src/src/components/TourGuide/tour-flow.yaml +0 -137
- package/templates/starter/src/src/components/TourGuide/useTourEngine.ts +0 -376
- package/templates/starter/src/src/index.css +0 -68
- package/templates/starter/src/src/main.tsx +0 -10
- package/templates/starter/src/src/services/AnalyticsService.ts +0 -181
- package/templates/starter/src/src/types/ContextHandler.ts +0 -13
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import Editor from '@monaco-editor/react';
|
|
3
|
-
import { atmosphere } from '@minnai/aura/atmosphere';
|
|
4
|
-
|
|
5
|
-
interface CodeEditorProps {
|
|
6
|
-
airId: string;
|
|
7
|
-
filePath: string;
|
|
8
|
-
initialCode: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function CodeEditor({ airId, filePath, initialCode }: CodeEditorProps) {
|
|
12
|
-
const [isDirty, setIsDirty] = useState(false);
|
|
13
|
-
const [status, setStatus] = useState<'idle' | 'saving' | 'saved'>('idle');
|
|
14
|
-
const editorRef = useRef<any>(null);
|
|
15
|
-
|
|
16
|
-
function handleEditorDidMount(editor: any, monaco: any) {
|
|
17
|
-
editorRef.current = editor;
|
|
18
|
-
|
|
19
|
-
// Disable internal linting/errors
|
|
20
|
-
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
|
|
21
|
-
noSemanticValidation: true,
|
|
22
|
-
noSyntaxValidation: true,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const handleSave = async () => {
|
|
27
|
-
const content = editorRef.current?.getValue() || '';
|
|
28
|
-
setStatus('saving');
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
// 1. Persist to Filesystem
|
|
32
|
-
const response = await fetch('/api/save-code', {
|
|
33
|
-
method: 'POST',
|
|
34
|
-
headers: { 'Content-Type': 'application/json' },
|
|
35
|
-
body: JSON.stringify({ filePath, content })
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
if (!response.ok) throw new Error("Failed to save to disk");
|
|
39
|
-
|
|
40
|
-
setStatus('saved');
|
|
41
|
-
setIsDirty(false);
|
|
42
|
-
setTimeout(() => setStatus('idle'), 3000);
|
|
43
|
-
|
|
44
|
-
// Note: Since we saved to the filesystem, Vite should ideally hot-reload.
|
|
45
|
-
// But if user wants a manual reload of the AIR, we could trigger flux dispatch.
|
|
46
|
-
// For now, persistence is verified.
|
|
47
|
-
} catch (err: any) {
|
|
48
|
-
console.error(err);
|
|
49
|
-
alert("Save failed: " + err.message);
|
|
50
|
-
setStatus('idle');
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: '#1e1e1e' }}>
|
|
56
|
-
<div style={{
|
|
57
|
-
padding: '10px 20px',
|
|
58
|
-
background: '#2d2d2d',
|
|
59
|
-
color: '#ddd',
|
|
60
|
-
display: 'flex',
|
|
61
|
-
justifyContent: 'space-between',
|
|
62
|
-
alignItems: 'center',
|
|
63
|
-
borderBottom: '1px solid #333',
|
|
64
|
-
height: '40px',
|
|
65
|
-
userSelect: 'none'
|
|
66
|
-
}}>
|
|
67
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
68
|
-
<span style={{ fontSize: '0.75rem', opacity: 0.7, fontFamily: 'monospace' }}>{filePath}</span>
|
|
69
|
-
{isDirty && <span style={{ width: 8, height: 8, borderRadius: '50%', background: '#ff9500' }} title="Unsaved changes" />}
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: 15 }}>
|
|
73
|
-
{status === 'saved' && (
|
|
74
|
-
<span style={{ fontSize: '0.75rem', color: '#4cd964', fontWeight: 600 }}>Saved ✔</span>
|
|
75
|
-
)}
|
|
76
|
-
{status === 'saving' && (
|
|
77
|
-
<span style={{ fontSize: '0.75rem', color: '#888' }}>Saving...</span>
|
|
78
|
-
)}
|
|
79
|
-
<button
|
|
80
|
-
onClick={handleSave}
|
|
81
|
-
style={{
|
|
82
|
-
padding: '4px 12px',
|
|
83
|
-
background: isDirty ? '#007aff' : '#444',
|
|
84
|
-
color: 'white',
|
|
85
|
-
border: 'none',
|
|
86
|
-
borderRadius: 4,
|
|
87
|
-
cursor: isDirty ? 'pointer' : 'default',
|
|
88
|
-
fontSize: '0.75rem',
|
|
89
|
-
fontWeight: 600,
|
|
90
|
-
transition: 'all 0.2s',
|
|
91
|
-
opacity: isDirty ? 1 : 0.6
|
|
92
|
-
}}
|
|
93
|
-
>
|
|
94
|
-
Save & Reload
|
|
95
|
-
</button>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
<div style={{ flex: 1 }}>
|
|
99
|
-
<Editor
|
|
100
|
-
key={filePath} // Force re-mount when file changes
|
|
101
|
-
height="100%"
|
|
102
|
-
defaultLanguage="typescript"
|
|
103
|
-
theme="vs-dark"
|
|
104
|
-
value={initialCode}
|
|
105
|
-
onChange={() => setIsDirty(true)}
|
|
106
|
-
onMount={handleEditorDidMount}
|
|
107
|
-
options={{
|
|
108
|
-
minimap: { enabled: false },
|
|
109
|
-
fontSize: 13,
|
|
110
|
-
scrollBeyondLastLine: false,
|
|
111
|
-
automaticLayout: true,
|
|
112
|
-
tabSize: 4,
|
|
113
|
-
padding: { top: 10 },
|
|
114
|
-
wordWrap: 'on'
|
|
115
|
-
}}
|
|
116
|
-
/>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import { flux } from '@minnai/aura/flux/index';
|
|
3
|
-
|
|
4
|
-
interface DebuggerProps {
|
|
5
|
-
airId: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function Debugger({ airId }: DebuggerProps) {
|
|
9
|
-
const [state] = useState<any>(null);
|
|
10
|
-
const [history, setHistory] = useState<any[]>([]);
|
|
11
|
-
|
|
12
|
-
useEffect(() => {
|
|
13
|
-
// Listen to all flux actions (simplified for playground)
|
|
14
|
-
const unsubscribe = flux.subscribe((action) => {
|
|
15
|
-
if (action.to === airId || action.type.includes(airId.toUpperCase())) {
|
|
16
|
-
setHistory(prev => [action, ...prev].slice(0, 20));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Attempt to get current state (this requires a specific flux pattern or selector)
|
|
20
|
-
// For now, we assume the state is exposed or we track updates
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
return () => unsubscribe();
|
|
24
|
-
}, [airId]);
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div style={{
|
|
28
|
-
width: '350px',
|
|
29
|
-
borderLeft: '1px solid #ddd',
|
|
30
|
-
background: 'white',
|
|
31
|
-
padding: '20px',
|
|
32
|
-
display: 'flex',
|
|
33
|
-
flexDirection: 'column',
|
|
34
|
-
gap: '20px',
|
|
35
|
-
overflowY: 'auto'
|
|
36
|
-
}}>
|
|
37
|
-
<section>
|
|
38
|
-
<h3 style={{ margin: '0 0 10px 0', fontSize: '0.9rem', color: '#666', textTransform: 'uppercase' }}>State (Live)</h3>
|
|
39
|
-
<pre style={{
|
|
40
|
-
background: '#f8f8f8',
|
|
41
|
-
padding: '10px',
|
|
42
|
-
borderRadius: '6px',
|
|
43
|
-
fontSize: '0.8rem',
|
|
44
|
-
overflowX: 'auto',
|
|
45
|
-
border: '1px solid #eee'
|
|
46
|
-
}}>
|
|
47
|
-
{JSON.stringify(state || { message: 'State tracking pending flux integration' }, null, 2)}
|
|
48
|
-
</pre>
|
|
49
|
-
</section>
|
|
50
|
-
|
|
51
|
-
<section>
|
|
52
|
-
<h3 style={{ margin: '0 0 10px 0', fontSize: '0.9rem', color: '#666', textTransform: 'uppercase' }}>Action History</h3>
|
|
53
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
54
|
-
{history.length === 0 && <div style={{ fontSize: '0.8rem', color: '#999' }}>No recent actions</div>}
|
|
55
|
-
{history.map((action, i) => (
|
|
56
|
-
<div key={i} style={{
|
|
57
|
-
fontSize: '0.75rem',
|
|
58
|
-
padding: '8px',
|
|
59
|
-
background: '#f0f0f0',
|
|
60
|
-
borderRadius: '4px',
|
|
61
|
-
borderLeft: '3px solid #007aff'
|
|
62
|
-
}}>
|
|
63
|
-
<div style={{ fontWeight: 'bold' }}>{action.type}</div>
|
|
64
|
-
<div style={{ color: '#666', marginTop: '4px' }}>{JSON.stringify(action.payload)}</div>
|
|
65
|
-
</div>
|
|
66
|
-
))}
|
|
67
|
-
</div>
|
|
68
|
-
</section>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
}
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { flux } from '@minnai/aura/flux/index';
|
|
3
|
-
import { Sidebar } from './Sidebar';
|
|
4
|
-
import { Debugger } from './Debugger';
|
|
5
|
-
import { Space } from '@minnai/aura/controller/Space';
|
|
6
|
-
import { CodeEditor } from './CodeEditor';
|
|
7
|
-
|
|
8
|
-
export function Playground() {
|
|
9
|
-
const [selectedId, setSelectedId] = useState<string | undefined>();
|
|
10
|
-
const [editorHeight, setEditorHeight] = useState(350);
|
|
11
|
-
const [isResizing, setIsResizing] = useState(false);
|
|
12
|
-
const [files, setFiles] = useState<string[]>([]);
|
|
13
|
-
const [activeFile, setActiveFile] = useState<string | null>(null);
|
|
14
|
-
const [fileContent, setFileContent] = useState<string>('');
|
|
15
|
-
|
|
16
|
-
const BUILTIN_AIRS = ['note-taker-air', 'youtube-player-air', 'tasks-air'];
|
|
17
|
-
|
|
18
|
-
const handleSelect = async (id: string) => {
|
|
19
|
-
setSelectedId(id);
|
|
20
|
-
flux.dispatch({ type: 'SPAWN_AIR', payload: { id }, to: 'controller' });
|
|
21
|
-
|
|
22
|
-
if (BUILTIN_AIRS.includes(id)) {
|
|
23
|
-
setFiles([]);
|
|
24
|
-
setActiveFile(null);
|
|
25
|
-
setFileContent('');
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// Fetch files for this AIR
|
|
30
|
-
try {
|
|
31
|
-
const res = await fetch(`/api/list-files?airId=${id}`);
|
|
32
|
-
const data = await res.json();
|
|
33
|
-
setFiles(data.files || []);
|
|
34
|
-
if (data.files && data.files.length > 0) {
|
|
35
|
-
// Default to index.tsx or first file
|
|
36
|
-
const defaultFile = data.files.find((f: string) => f.endsWith('index.tsx')) || data.files[0];
|
|
37
|
-
handleFileSelect(defaultFile);
|
|
38
|
-
}
|
|
39
|
-
} catch (err) {
|
|
40
|
-
console.error("Failed to list files:", err);
|
|
41
|
-
setFiles([]);
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const handleFileSelect = async (filePath: string) => {
|
|
46
|
-
setActiveFile(filePath);
|
|
47
|
-
try {
|
|
48
|
-
const url = filePath.startsWith('/') ? filePath : '/' + filePath;
|
|
49
|
-
const res = await fetch(url);
|
|
50
|
-
if (!res.ok) throw new Error("File not found");
|
|
51
|
-
const text = await res.text();
|
|
52
|
-
setFileContent(text);
|
|
53
|
-
} catch (err) {
|
|
54
|
-
console.error("Failed to load file content:", err);
|
|
55
|
-
setFileContent("// Error loading file: " + filePath);
|
|
56
|
-
}
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const handleAddAIR = async () => {
|
|
60
|
-
const name = window.prompt("Enter AIR Name (e.g. My Custom Tool):");
|
|
61
|
-
if (!name) return;
|
|
62
|
-
const id = name.toLowerCase().replace(/\s+/g, '-') + '-air';
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const res = await fetch('/api/scaffold-air', {
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: { 'Content-Type': 'application/json' },
|
|
68
|
-
body: JSON.stringify({ airId: id, name })
|
|
69
|
-
});
|
|
70
|
-
const data = await res.json();
|
|
71
|
-
if (data.success) {
|
|
72
|
-
alert("Scaffolded successfully! Refreshing...");
|
|
73
|
-
window.location.reload();
|
|
74
|
-
} else {
|
|
75
|
-
alert("Scaffold failed: " + data.error);
|
|
76
|
-
}
|
|
77
|
-
} catch (err) {
|
|
78
|
-
alert("Scaffold error");
|
|
79
|
-
}
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const handleMouseDown = (e: React.MouseEvent) => {
|
|
83
|
-
setIsResizing(true);
|
|
84
|
-
e.preventDefault();
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
useEffect(() => {
|
|
88
|
-
const handleMouseMove = (e: MouseEvent) => {
|
|
89
|
-
if (!isResizing) return;
|
|
90
|
-
const newHeight = window.innerHeight - e.clientY;
|
|
91
|
-
setEditorHeight(Math.max(100, Math.min(newHeight, window.innerHeight - 200)));
|
|
92
|
-
};
|
|
93
|
-
const handleMouseUp = () => setIsResizing(false);
|
|
94
|
-
if (isResizing) {
|
|
95
|
-
window.addEventListener('mousemove', handleMouseMove);
|
|
96
|
-
window.addEventListener('mouseup', handleMouseUp);
|
|
97
|
-
}
|
|
98
|
-
return () => {
|
|
99
|
-
window.removeEventListener('mousemove', handleMouseMove);
|
|
100
|
-
window.removeEventListener('mouseup', handleMouseUp);
|
|
101
|
-
};
|
|
102
|
-
}, [isResizing]);
|
|
103
|
-
|
|
104
|
-
const isBuiltIn = selectedId && BUILTIN_AIRS.includes(selectedId);
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<div style={{ display: 'flex', height: 'calc(100vh - 60px)', width: '100vw', overflow: 'hidden' }}>
|
|
108
|
-
<div style={{
|
|
109
|
-
width: '250px',
|
|
110
|
-
height: '100%',
|
|
111
|
-
borderRight: '1px solid #ddd',
|
|
112
|
-
background: 'white',
|
|
113
|
-
display: 'flex',
|
|
114
|
-
flexDirection: 'column'
|
|
115
|
-
}}>
|
|
116
|
-
<Sidebar onSelect={handleSelect} onAddAIR={handleAddAIR} selectedId={selectedId} />
|
|
117
|
-
</div>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
<main style={{ flex: 1, display: 'flex', flexDirection: 'column', background: '#fafafa' }}>
|
|
121
|
-
<div style={{ flex: 1, position: 'relative', overflow: 'hidden' }}>
|
|
122
|
-
{!selectedId ? (
|
|
123
|
-
<div style={{ display: 'flex', height: '100%', alignItems: 'center', justifyContent: 'center', color: '#999' }}>
|
|
124
|
-
Select an AIR from the sidebar to begin testing
|
|
125
|
-
</div>
|
|
126
|
-
) : (
|
|
127
|
-
<Space />
|
|
128
|
-
)}
|
|
129
|
-
</div>
|
|
130
|
-
|
|
131
|
-
{selectedId && (
|
|
132
|
-
<div style={{ height: editorHeight, display: 'flex', flexDirection: 'column', borderTop: '1px solid #ddd', position: 'relative', background: '#fff' }}>
|
|
133
|
-
<div
|
|
134
|
-
onMouseDown={handleMouseDown}
|
|
135
|
-
style={{
|
|
136
|
-
height: '4px',
|
|
137
|
-
width: '100%',
|
|
138
|
-
position: 'absolute',
|
|
139
|
-
top: '-2px',
|
|
140
|
-
cursor: 'ns-resize',
|
|
141
|
-
zIndex: 10,
|
|
142
|
-
background: isResizing ? '#007aff' : 'transparent',
|
|
143
|
-
transition: 'background 0.2s'
|
|
144
|
-
}}
|
|
145
|
-
/>
|
|
146
|
-
|
|
147
|
-
<div style={{ display: 'flex', flex: 1, overflow: 'hidden' }}>
|
|
148
|
-
{/* File Explorer */}
|
|
149
|
-
<div style={{
|
|
150
|
-
width: '250px',
|
|
151
|
-
borderRight: '1px solid #eee',
|
|
152
|
-
background: '#fcfcfc',
|
|
153
|
-
display: 'flex',
|
|
154
|
-
flexDirection: 'column',
|
|
155
|
-
overflowY: 'auto'
|
|
156
|
-
}}>
|
|
157
|
-
<div style={{ padding: '10px 15px', fontSize: '0.7rem', color: '#999', fontWeight: 600, borderBottom: '1px solid #f0f0f0', textTransform: 'uppercase' }}>
|
|
158
|
-
File Explorer
|
|
159
|
-
</div>
|
|
160
|
-
{isBuiltIn ? (
|
|
161
|
-
<div style={{ padding: 20, color: '#999', fontSize: '0.8rem', textAlign: 'center', fontStyle: 'italic' }}>
|
|
162
|
-
Built-in Atmosphere AIR
|
|
163
|
-
</div>
|
|
164
|
-
) : (
|
|
165
|
-
files.map(file => (
|
|
166
|
-
<div
|
|
167
|
-
key={file}
|
|
168
|
-
onClick={() => handleFileSelect(file)}
|
|
169
|
-
style={{
|
|
170
|
-
padding: '8px 15px',
|
|
171
|
-
fontSize: '0.8rem',
|
|
172
|
-
cursor: 'pointer',
|
|
173
|
-
background: activeFile === file ? '#eef6ff' : 'transparent',
|
|
174
|
-
color: activeFile === file ? '#007aff' : '#555',
|
|
175
|
-
fontFamily: 'monospace',
|
|
176
|
-
textOverflow: 'ellipsis',
|
|
177
|
-
whiteSpace: 'nowrap',
|
|
178
|
-
overflow: 'hidden',
|
|
179
|
-
borderRight: activeFile === file ? '2px solid #007aff' : 'none'
|
|
180
|
-
}}
|
|
181
|
-
title={file}
|
|
182
|
-
>
|
|
183
|
-
{file.split(/[\\\/]/).pop()}
|
|
184
|
-
</div>
|
|
185
|
-
))
|
|
186
|
-
)}
|
|
187
|
-
</div>
|
|
188
|
-
|
|
189
|
-
{/* Editor */}
|
|
190
|
-
<div style={{ flex: 1 }}>
|
|
191
|
-
{isBuiltIn ? (
|
|
192
|
-
<div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#1e1e1e', color: '#888', flexDirection: 'column', gap: 10 }}>
|
|
193
|
-
<div style={{ fontSize: '2rem' }}>🔒</div>
|
|
194
|
-
<div style={{ fontWeight: 600 }}>Built-in Atmosphere AIR</div>
|
|
195
|
-
<div style={{ fontSize: '0.8rem', opacity: 0.7 }}>Source code is managed by the main Aura package.</div>
|
|
196
|
-
</div>
|
|
197
|
-
) : activeFile ? (
|
|
198
|
-
<CodeEditor
|
|
199
|
-
airId={selectedId}
|
|
200
|
-
filePath={activeFile}
|
|
201
|
-
initialCode={fileContent}
|
|
202
|
-
/>
|
|
203
|
-
) : (
|
|
204
|
-
<div style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', background: '#f5f5f5', color: '#999' }}>
|
|
205
|
-
Select a file to edit
|
|
206
|
-
</div>
|
|
207
|
-
)}
|
|
208
|
-
</div>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
)}
|
|
212
|
-
</main>
|
|
213
|
-
|
|
214
|
-
{selectedId && (
|
|
215
|
-
<div style={{ width: '300px', borderLeft: '1px solid #ddd', background: 'white', overflowY: 'auto' }}>
|
|
216
|
-
<Debugger airId={selectedId} />
|
|
217
|
-
</div>
|
|
218
|
-
)}
|
|
219
|
-
</div>
|
|
220
|
-
);
|
|
221
|
-
}
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { EXAMPLE_AIRS } from '../../ambiance';
|
|
2
|
-
|
|
3
|
-
interface SidebarProps {
|
|
4
|
-
onSelect: (id: string) => void;
|
|
5
|
-
onAddAIR: () => void;
|
|
6
|
-
selectedId?: string;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function Sidebar({ onSelect, onAddAIR, selectedId }: SidebarProps) {
|
|
10
|
-
return (
|
|
11
|
-
<div style={{
|
|
12
|
-
flex: 1,
|
|
13
|
-
padding: '20px',
|
|
14
|
-
display: 'flex',
|
|
15
|
-
flexDirection: 'column',
|
|
16
|
-
gap: '10px',
|
|
17
|
-
overflowY: 'auto'
|
|
18
|
-
}}>
|
|
19
|
-
<h3 style={{ margin: '0 0 10px 0', fontSize: '0.9rem', color: '#666', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
|
|
20
|
-
Available AIRs
|
|
21
|
-
</h3>
|
|
22
|
-
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', flex: 1 }}>
|
|
23
|
-
{EXAMPLE_AIRS.map(air => (
|
|
24
|
-
<button
|
|
25
|
-
key={air.id}
|
|
26
|
-
onClick={() => onSelect(air.id)}
|
|
27
|
-
style={{
|
|
28
|
-
padding: '12px 15px',
|
|
29
|
-
textAlign: 'left',
|
|
30
|
-
borderRadius: '8px',
|
|
31
|
-
border: selectedId === air.id ? '1px solid #007aff' : '1px solid #eee',
|
|
32
|
-
background: selectedId === air.id ? '#f0f7ff' : 'white',
|
|
33
|
-
color: selectedId === air.id ? '#007aff' : '#1d1d1f',
|
|
34
|
-
cursor: 'pointer',
|
|
35
|
-
fontSize: '0.9rem',
|
|
36
|
-
fontWeight: selectedId === air.id ? '600' : '400',
|
|
37
|
-
transition: 'all 0.2s ease'
|
|
38
|
-
}}
|
|
39
|
-
>
|
|
40
|
-
{typeof air.meta.title === 'function' ? air.meta.title('en') : air.meta.title}
|
|
41
|
-
</button>
|
|
42
|
-
))}
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<button
|
|
46
|
-
onClick={onAddAIR}
|
|
47
|
-
style={{
|
|
48
|
-
marginTop: '20px',
|
|
49
|
-
padding: '12px',
|
|
50
|
-
background: '#007aff',
|
|
51
|
-
color: 'white',
|
|
52
|
-
border: 'none',
|
|
53
|
-
borderRadius: 8,
|
|
54
|
-
cursor: 'pointer',
|
|
55
|
-
fontSize: '0.85rem',
|
|
56
|
-
fontWeight: 600,
|
|
57
|
-
display: 'flex',
|
|
58
|
-
alignItems: 'center',
|
|
59
|
-
justifyContent: 'center',
|
|
60
|
-
gap: '8px',
|
|
61
|
-
boxShadow: '0 2px 8px rgba(0,122,255,0.2)'
|
|
62
|
-
}}
|
|
63
|
-
>
|
|
64
|
-
<span>+</span> Add Custom AIR
|
|
65
|
-
</button>
|
|
66
|
-
</div>
|
|
67
|
-
);
|
|
68
|
-
}
|