api fix and script fe improv
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
This commit is contained in:
@@ -493,11 +493,7 @@ async def upload_script(
|
|||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logger.warning("Invalid name provided: %s", name)
|
logger.warning("Invalid name provided: %s", name)
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
if not script.filename.endswith(".py"):
|
|
||||||
logger.warning("Upload rejected: script not .py (name=%s original=%s)", name, script.filename)
|
|
||||||
raise HTTPException(status_code=400, detail="only .py scripts allowed")
|
|
||||||
|
|
||||||
spath = script_path_for(name)
|
spath = script_path_for(name)
|
||||||
if os.path.exists(spath):
|
if os.path.exists(spath):
|
||||||
logger.warning("Upload rejected: script with name already exists: %s", name)
|
logger.warning("Upload rejected: script with name already exists: %s", name)
|
||||||
|
|||||||
@@ -1,76 +1,111 @@
|
|||||||
|
// src/components/PythonEditor.tsx
|
||||||
import Prism from 'prismjs';
|
import Prism from 'prismjs';
|
||||||
import 'prismjs/components/prism-python';
|
import 'prismjs/components/prism-python';
|
||||||
import 'prismjs/themes/prism.css';
|
import 'prismjs/themes/prism.css';
|
||||||
import { useCallback, useMemo } from 'react';
|
import React, { useEffect, useMemo, useRef } from 'react';
|
||||||
import Editor from 'react-simple-code-editor';
|
|
||||||
|
|
||||||
// Props for the editor
|
|
||||||
export type PythonEditorProps = {
|
export type PythonEditorProps = {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (code: string) => void;
|
onChange: (code: string) => void;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
className?: string;
|
height?: string | number;
|
||||||
height?: string | number; // e.g. '200px' or 200
|
wrap?: boolean;
|
||||||
wrap?: boolean; // if true, long lines will wrap; default false (no wrap)
|
fontSize?: number;
|
||||||
|
tabSize?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_FONT =
|
||||||
|
'"JetBrains Mono", "Fira Code", "Source Code Pro", ui-monospace, SFMono-Regular, Menlo, Monaco, monospace';
|
||||||
|
|
||||||
export default function PythonEditor({
|
export default function PythonEditor({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
readOnly = false,
|
readOnly = false,
|
||||||
height = 480,
|
height = 480,
|
||||||
className = '',
|
|
||||||
wrap = false,
|
wrap = false,
|
||||||
|
fontSize = 14,
|
||||||
|
tabSize = 4,
|
||||||
}: PythonEditorProps) {
|
}: PythonEditorProps) {
|
||||||
const highlight = useCallback((code: string) => {
|
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||||
try {
|
const preRef = useRef<HTMLPreElement | null>(null);
|
||||||
return Prism.highlight(code, Prism.languages.python, 'python');
|
|
||||||
} catch (e) {
|
const h = typeof height === 'number' ? `${height}px` : height;
|
||||||
return code;
|
|
||||||
|
const lineHeight = Math.round(fontSize * 1.6);
|
||||||
|
|
||||||
|
// Highlight whenever value changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (preRef.current) {
|
||||||
|
preRef.current.innerHTML = Prism.highlight(value, Prism.languages.python, 'python');
|
||||||
}
|
}
|
||||||
}, []);
|
}, [value]);
|
||||||
|
|
||||||
// container style: fixed height + scrolling
|
// Scroll sync
|
||||||
const containerStyle = useMemo(() => {
|
const handleScroll = () => {
|
||||||
const h = typeof height === 'number' ? `${height}px` : height;
|
if (!textareaRef.current || !preRef.current) return;
|
||||||
return {
|
preRef.current.scrollTop = textareaRef.current.scrollTop;
|
||||||
height: h,
|
preRef.current.scrollLeft = textareaRef.current.scrollLeft;
|
||||||
maxHeight: h,
|
};
|
||||||
overflow: 'auto' as const, // enable scrolling
|
|
||||||
position: 'relative' as const,
|
|
||||||
borderRadius: 6,
|
|
||||||
border: '1px solid rgba(0,0,0,0.06)',
|
|
||||||
background: 'var(--code-bg, white)',
|
|
||||||
};
|
|
||||||
}, [height]);
|
|
||||||
|
|
||||||
// editor style: fill container and use monospace font
|
const sharedStyle: React.CSSProperties = useMemo(
|
||||||
const editorStyle = useMemo(
|
|
||||||
() => ({
|
() => ({
|
||||||
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
|
fontFamily: DEFAULT_FONT,
|
||||||
fontSize: 14,
|
fontSize: `${fontSize}px`,
|
||||||
// make editor fill the container (pre/textarea inside will take up height)
|
lineHeight: `${lineHeight}px`,
|
||||||
minHeight: '100%',
|
tabSize,
|
||||||
boxSizing: 'border-box' as const,
|
whiteSpace: wrap ? 'pre-wrap' : 'pre',
|
||||||
|
wordBreak: 'break-word',
|
||||||
padding: 12,
|
padding: 12,
|
||||||
outline: 'none',
|
boxSizing: 'border-box',
|
||||||
whiteSpace: wrap ? 'pre-wrap' : 'pre', // pre-wrap if wrap requested
|
|
||||||
wordBreak: 'break-word' as const,
|
|
||||||
}),
|
}),
|
||||||
[wrap],
|
[fontSize, lineHeight, tabSize, wrap],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`python-editor ${className}`} style={containerStyle}>
|
<div
|
||||||
<Editor
|
style={{
|
||||||
|
position: 'relative',
|
||||||
|
height: h,
|
||||||
|
borderRadius: 6,
|
||||||
|
border: '1px solid rgba(0,0,0,0.06)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Highlight layer */}
|
||||||
|
<pre
|
||||||
|
ref={preRef}
|
||||||
|
aria-hidden="true"
|
||||||
|
className="language-python"
|
||||||
|
style={{
|
||||||
|
...sharedStyle,
|
||||||
|
margin: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
overflow: 'auto',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Editable layer */}
|
||||||
|
<textarea
|
||||||
|
ref={textareaRef}
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(code) => onChange(code)}
|
|
||||||
highlight={highlight}
|
|
||||||
padding={0} // padding handled by editorStyle
|
|
||||||
preClassName="language-python"
|
|
||||||
textareaId="python-editor"
|
|
||||||
style={editorStyle}
|
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
onScroll={handleScroll}
|
||||||
|
spellCheck={false}
|
||||||
|
style={{
|
||||||
|
...sharedStyle,
|
||||||
|
position: 'absolute',
|
||||||
|
inset: 0,
|
||||||
|
resize: 'none',
|
||||||
|
border: 'none',
|
||||||
|
outline: 'none',
|
||||||
|
background: 'transparent',
|
||||||
|
color: 'transparent', // hide textarea text
|
||||||
|
caretColor: 'black', // show caret
|
||||||
|
overflow: 'auto',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user