Compare commits

..

No commits in common. "521fb8f613a4641d3171d20063c0868a37efb8a9" and "1234ba30d9d5027cf139a6e9aff362c93df20ca0" have entirely different histories.

5 changed files with 5 additions and 134 deletions

View File

@ -52,8 +52,6 @@
"@types/parallax-js": "^3.1.3", "@types/parallax-js": "^3.1.3",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"@uiw/react-codemirror": "^4.25.10", "@uiw/react-codemirror": "^4.25.10",
"@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0",
"canvas-confetti": "1.9.4", "canvas-confetti": "1.9.4",
"class-variance-authority": "0.7.1", "class-variance-authority": "0.7.1",
"clsx": "2.1.1", "clsx": "2.1.1",

View File

@ -1,5 +1,5 @@
import { lazy, Suspense, useEffect, useState } from 'react'; import { lazy, Suspense, useEffect, useState } from 'react';
import { Cpu, Settings, Wifi, Network, HardDrive, Activity, Search, Wrench, User, LogOut, Bell, FileText, AppWindow, Folder, Edit, Eye, Database, Upload, Download, Code2, LayoutList, Image, ChevronLeft, Loader2, Terminal, Link, Printer, Maximize2, Minimize2, Info } from 'lucide-react'; import { Cpu, Settings, Wifi, Network, HardDrive, Activity, Search, Wrench, User, LogOut, Bell, FileText, AppWindow, Folder, Edit, Eye, Database, Upload, Download, Code2, LayoutList, Image, ChevronLeft, Loader2, Terminal, Link, Printer, Maximize2, Minimize2 } from 'lucide-react';
import { Toaster, toast } from 'sonner'; import { Toaster, toast } from 'sonner';
import StatusPage from './components/StatusPage'; import StatusPage from './components/StatusPage';
import DevicesPage from './components/DevicesPage'; import DevicesPage from './components/DevicesPage';
@ -17,9 +17,7 @@ import { WsProvider } from './ws';
// Three.js lives only in RealityOverridePage — keep lazy so it doesn't load on startup. // Three.js lives only in RealityOverridePage — keep lazy so it doesn't load on startup.
// CodeMirror/syntax-highlighter/ReactMarkdown live in MediaViewerEditor — lazy-loaded // CodeMirror/syntax-highlighter/ReactMarkdown live in MediaViewerEditor — lazy-loaded
// inside MediaManager when the user first opens a file to view or edit. // inside MediaManager when the user first opens a file to view or edit.
// xterm.js lives only in SerialConsolePage — keep lazy.
const RealityOverridePage = lazy(() => import('./components/RealityOverridePage')); const RealityOverridePage = lazy(() => import('./components/RealityOverridePage'));
const SerialConsolePage = lazy(() => import('./components/SerialConsolePage'));
type Page = 'status' | 'devices' | 'iec' | 'network' | 'general' | 'tools' | 'apps' | AppId; type Page = 'status' | 'devices' | 'iec' | 'network' | 'general' | 'tools' | 'apps' | AppId;
@ -178,7 +176,7 @@ export default function App() {
config={config} config={config}
setConfig={setConfig} setConfig={setConfig}
/>, />,
'serial-console': <SerialConsolePage onBack={() => setCurrentPage('apps')} />, 'serial-console': <AppPage title="Serial Console" onBack={() => setCurrentPage('apps')} />,
'directory-editor': <AppPage title="Directory Editor" onBack={() => setCurrentPage('apps')} />, 'directory-editor': <AppPage title="Directory Editor" onBack={() => setCurrentPage('apps')} />,
'sector-editor': <AppPage title="Sector Editor" onBack={() => setCurrentPage('apps')} />, 'sector-editor': <AppPage title="Sector Editor" onBack={() => setCurrentPage('apps')} />,
'bam-editor': <AppPage title="BAM Editor" onBack={() => setCurrentPage('apps')} />, 'bam-editor': <AppPage title="BAM Editor" onBack={() => setCurrentPage('apps')} />,
@ -275,7 +273,7 @@ function AppPage({ title, onBack }: { title: string; onBack: () => void }) {
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2" className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
> >
<Settings className="w-4 h-4 text-[#4d4d4d]" /> <Settings className="w-4 h-4 text-[#4d4d4d]" />
Preferences Settings
</button> </button>
<button <button
onClick={() => setShowProfileMenu(false)} onClick={() => setShowProfileMenu(false)}
@ -291,13 +289,6 @@ function AppPage({ title, onBack }: { title: string; onBack: () => void }) {
<FileText className="w-4 h-4 text-[#4d4d4d]" /> <FileText className="w-4 h-4 text-[#4d4d4d]" />
Documentation Documentation
</button> </button>
<button
onClick={() => setShowProfileMenu(false)}
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
>
<Info className="w-4 h-4 text-[#4d4d4d]" />
About Meatloaf
</button>
<div className="border-t border-neutral-200 my-2" /> <div className="border-t border-neutral-200 my-2" />
<button <button
onClick={() => setShowProfileMenu(false)} onClick={() => setShowProfileMenu(false)}

View File

@ -42,7 +42,7 @@ export default function GeneralPage({ config, setConfig }: GeneralPageProps) {
return ( return (
<div className="p-4 space-y-4"> <div className="p-4 space-y-4">
<h2 className="text-sm text-neutral-500 flex items-center gap-2"><Settings className="w-4 h-4" /> Preferences</h2> <h2 className="text-sm text-neutral-500 flex items-center gap-2"><Settings className="w-4 h-4" /> General Settings</h2>
<div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200"> <div className="bg-white border border-neutral-200 rounded-lg divide-y divide-neutral-200">

View File

@ -1,118 +0,0 @@
import { useEffect, useRef } from 'react';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css';
import { ChevronLeft, Radio, RotateCcw } from 'lucide-react';
import { useWs } from '../ws';
interface Props { onBack: () => void; }
export default function SerialConsolePage({ onBack }: Props) {
const containerRef = useRef<HTMLDivElement>(null);
const termRef = useRef<Terminal | null>(null);
const fitRef = useRef<FitAddon | null>(null);
const { status, send, subscribe } = useWs();
// Keep a stable ref to `send` so the xterm onData closure never goes stale
const sendRef = useRef(send);
useEffect(() => { sendRef.current = send; }, [send]);
// Initialize xterm once on mount
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const term = new Terminal({
cursorBlink: true,
fontSize: 14,
fontFamily: '"Cascadia Code","Fira Code",Menlo,Monaco,"Courier New",monospace',
theme: {
background: '#0a0a0a',
foreground: '#d4d4d4',
cursor: '#d4d4d4',
selectionBackground: '#264f78',
},
scrollback: 5000,
convertEol: true,
});
const fit = new FitAddon();
term.loadAddon(fit);
term.open(el);
fit.fit();
termRef.current = term;
fitRef.current = fit;
term.writeln('\x1b[2m── Meatloaf Serial Console ──\x1b[0m');
term.writeln('');
term.onData((data) => sendRef.current(data));
const onResize = () => fitRef.current?.fit();
window.addEventListener('resize', onResize);
return () => {
window.removeEventListener('resize', onResize);
term.dispose();
termRef.current = null;
fitRef.current = null;
};
}, []);
// Forward all incoming WS messages to the terminal
useEffect(() => {
return subscribe((msg) => {
termRef.current?.write(msg);
});
}, [subscribe]);
const statusColor =
status === 'connected' ? 'text-green-400' :
status === 'connecting' ? 'text-amber-400' : 'text-neutral-500';
const statusLabel =
status === 'connected' ? 'Connected' :
status === 'connecting' ? 'Connecting…' : 'Disconnected';
return (
<div className="fixed inset-0 bg-[#0a0a0a] z-40 flex flex-col">
{/* Header */}
<div
className="flex-none flex items-center gap-2 px-3 py-2 bg-neutral-900 border-b border-neutral-800"
style={{ paddingTop: 'max(0.5rem, env(safe-area-inset-top))' }}
>
<button
onClick={onBack}
className="p-1.5 rounded hover:bg-neutral-800 text-neutral-400 hover:text-white transition"
title="Back"
>
<ChevronLeft className="w-5 h-5" />
</button>
<span className="text-sm font-medium text-neutral-200">Serial Console</span>
<div className="flex-1" />
<div className={`flex items-center gap-1.5 text-xs ${statusColor}`}>
<Radio className="w-3.5 h-3.5" />
<span className="hidden sm:inline">{statusLabel}</span>
</div>
<button
onClick={() => termRef.current?.clear()}
className="p-1.5 rounded hover:bg-neutral-800 text-neutral-400 hover:text-neutral-200 transition"
title="Clear terminal"
>
<RotateCcw className="w-4 h-4" />
</button>
</div>
{/* Terminal */}
<div
ref={containerRef}
className="flex-1 min-h-0"
style={{ paddingBottom: 'env(safe-area-inset-bottom)' }}
/>
</div>
);
}

View File

@ -941,7 +941,7 @@ class DAVServer(ThreadingMixIn, HTTPServer):
pass pass
if __name__ == '__main__': if __name__ == '__main__':
# WebDav TCP Port # WebDav TCP Port
srvport = 80 srvport = 80
# Get local IP address # Get local IP address
myaddr = get_localip() myaddr = get_localip()