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:
@@ -494,10 +494,6 @@ async def upload_script(
|
||||
logger.warning("Invalid name provided: %s", name)
|
||||
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)
|
||||
if os.path.exists(spath):
|
||||
logger.warning("Upload rejected: script with name already exists: %s", name)
|
||||
|
||||
@@ -1,76 +1,111 @@
|
||||
// src/components/PythonEditor.tsx
|
||||
import Prism from 'prismjs';
|
||||
import 'prismjs/components/prism-python';
|
||||
import 'prismjs/themes/prism.css';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import Editor from 'react-simple-code-editor';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
|
||||
// Props for the editor
|
||||
export type PythonEditorProps = {
|
||||
value: string;
|
||||
onChange: (code: string) => void;
|
||||
readOnly?: boolean;
|
||||
className?: string;
|
||||
height?: string | number; // e.g. '200px' or 200
|
||||
wrap?: boolean; // if true, long lines will wrap; default false (no wrap)
|
||||
height?: string | number;
|
||||
wrap?: boolean;
|
||||
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({
|
||||
value,
|
||||
onChange,
|
||||
readOnly = false,
|
||||
height = 480,
|
||||
className = '',
|
||||
wrap = false,
|
||||
fontSize = 14,
|
||||
tabSize = 4,
|
||||
}: PythonEditorProps) {
|
||||
const highlight = useCallback((code: string) => {
|
||||
try {
|
||||
return Prism.highlight(code, Prism.languages.python, 'python');
|
||||
} catch (e) {
|
||||
return code;
|
||||
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const preRef = useRef<HTMLPreElement | null>(null);
|
||||
|
||||
const h = typeof height === 'number' ? `${height}px` : height;
|
||||
|
||||
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
|
||||
const containerStyle = useMemo(() => {
|
||||
const h = typeof height === 'number' ? `${height}px` : height;
|
||||
return {
|
||||
height: h,
|
||||
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]);
|
||||
// Scroll sync
|
||||
const handleScroll = () => {
|
||||
if (!textareaRef.current || !preRef.current) return;
|
||||
preRef.current.scrollTop = textareaRef.current.scrollTop;
|
||||
preRef.current.scrollLeft = textareaRef.current.scrollLeft;
|
||||
};
|
||||
|
||||
// editor style: fill container and use monospace font
|
||||
const editorStyle = useMemo(
|
||||
const sharedStyle: React.CSSProperties = useMemo(
|
||||
() => ({
|
||||
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
|
||||
fontSize: 14,
|
||||
// make editor fill the container (pre/textarea inside will take up height)
|
||||
minHeight: '100%',
|
||||
boxSizing: 'border-box' as const,
|
||||
fontFamily: DEFAULT_FONT,
|
||||
fontSize: `${fontSize}px`,
|
||||
lineHeight: `${lineHeight}px`,
|
||||
tabSize,
|
||||
whiteSpace: wrap ? 'pre-wrap' : 'pre',
|
||||
wordBreak: 'break-word',
|
||||
padding: 12,
|
||||
outline: 'none',
|
||||
whiteSpace: wrap ? 'pre-wrap' : 'pre', // pre-wrap if wrap requested
|
||||
wordBreak: 'break-word' as const,
|
||||
boxSizing: 'border-box',
|
||||
}),
|
||||
[wrap],
|
||||
[fontSize, lineHeight, tabSize, wrap],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`python-editor ${className}`} style={containerStyle}>
|
||||
<Editor
|
||||
<div
|
||||
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}
|
||||
onValueChange={(code) => onChange(code)}
|
||||
highlight={highlight}
|
||||
padding={0} // padding handled by editorStyle
|
||||
preClassName="language-python"
|
||||
textareaId="python-editor"
|
||||
style={editorStyle}
|
||||
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>
|
||||
);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user