/ simulated_instruments / woodblock
Sign in
simulated_instruments / woodblock
woodblock / webapp / src / components / CodePanel.tsx
109 lines · 3.5 KBBlameHistoryRaw
1// Overlay for reading and editing the MATLAB implementation. Edits take
2// effect on the next debounced render and live only as long as the page.
4import { useState } from 'react';
5import { DEFAULT_MFILES, EXECUTED_BY_APP, type MFile } from '../mfiles';
6import type { RenderError } from '../hooks/useRenderer';
7import CodeEditor from './CodeEditor';
9interface Props {
10 mfiles: MFile[];
11 onChangeFile: (name: string, source: string) => void;
12 onResetFile: (name: string) => void;
13 onResetAll: () => void;
14 onClose: () => void;
15 onRenderFull: () => void;
16 error: RenderError | null;
17 busy: boolean;
20function defaultSourceOf(name: string): string {
21 return DEFAULT_MFILES.find((f) => f.name === name)?.source ?? '';
24export default function CodePanel({
25 mfiles,
26 onChangeFile,
27 onResetFile,
28 onResetAll,
29 onClose,
30 onRenderFull,
31 error,
32 busy,
33}: Props) {
34 const [selected, setSelected] = useState(
35 mfiles.find((f) => EXECUTED_BY_APP.has(f.name))?.name ?? mfiles[0]?.name ?? '',
36 );
37 const file = mfiles.find((f) => f.name === selected) ?? mfiles[0];
38 const dirty = (f: MFile) => f.source !== defaultSourceOf(f.name);
39 const anyDirty = mfiles.some(dirty);
41 return (
42 <div className="modal-backdrop" onClick={onClose}>
43 <div className="code-panel" onClick={(e) => e.stopPropagation()}>
44 <div className="code-header">
45 <h2>MATLAB source</h2>
46 <span className="code-note">
47 Executed by numbl. Edits re-render as you type and are discarded on reload.
48 </span>
49 <button onClick={onRenderFull} disabled={busy}>
50 Render full quality
51 </button>
52 <button onClick={onResetAll} disabled={!anyDirty}>
53 Revert all
54 </button>
55 <button onClick={onClose}>Close</button>
56 </div>
57 <div className="code-body">
58 <div className="code-files">
59 {mfiles.map((f) => (
60 <button
61 key={f.name}
62 className={f.name === file?.name ? 'active' : ''}
63 onClick={() => setSelected(f.name)}
64 >
65 <span className="fname">
66 {f.name}
67 {dirty(f) ? <span className="dot" title="edited" /> : null}
68 </span>
69 <span className="ftag">{EXECUTED_BY_APP.has(f.name) ? 'in app' : 'CLI'}</span>
70 </button>
71 ))}
72 </div>
73 <div className="code-main">
74 {file && (
75 <>
76 <div className="code-toolbar">
77 <span>{file.name}</span>
78 {!EXECUTED_BY_APP.has(file.name) && (
79 <span className="code-hint">
80 entry point for the numbl CLI; the app does not run this file
81 </span>
82 )}
83 <button
84 onClick={() => onResetFile(file.name)}
85 disabled={!dirty(file)}
86 style={{ marginLeft: 'auto' }}
87 >
88 Revert file
89 </button>
90 </div>
91 <CodeEditor
92 key={file.name}
93 source={file.source}
94 onChange={(src) => onChangeFile(file.name, src)}
95 />
96 </>
97 )}
98 </div>
99 </div>
100 {error && (
101 <div className="code-error">
102 <strong>{error.file ? `${error.file}${error.line ? `:${error.line}` : ''}` : 'error'}</strong>{' '}
103 {error.message}
104 </div>
105 )}
106 </div>
107 </div>
108 );
↑↓ move↵openescclose