From 0424961d46cbb5ba6f2fe097ab3996a00795845f Mon Sep 17 00:00:00 2001 From: MacRimi Date: Wed, 10 Dec 2025 17:08:41 +0100 Subject: [PATCH] Update AppImage --- AppImage/components/script-terminal-modal.tsx | 170 ++++++++++-------- AppImage/components/ui/dialog.tsx | 14 +- 2 files changed, 100 insertions(+), 84 deletions(-) diff --git a/AppImage/components/script-terminal-modal.tsx b/AppImage/components/script-terminal-modal.tsx index 4030ed8..659b93d 100644 --- a/AppImage/components/script-terminal-modal.tsx +++ b/AppImage/components/script-terminal-modal.tsx @@ -2,15 +2,13 @@ import type React from "react" import { useState, useEffect, useRef, useCallback } from "react" -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { Loader2, GripHorizontal, X } from "lucide-react" -import { API_PORT } from "@/lib/api-config" +import { Loader2, Activity, GripHorizontal } from "lucide-react" +import { API_PORT } from "../lib/api-config" import { useIsMobile } from "@/hooks/use-mobile" -import { Terminal as XTerm, FitAddon } from "xterm" -import "xterm/css/xterm.css" interface WebInteraction { type: "yesno" | "menu" | "msgbox" | "input" | "inputbox" @@ -31,15 +29,8 @@ interface ScriptTerminalModalProps { description: string } -const processMessageText = (text: string): string => { - return text - .replace(/\\r\\n/g, "\n") // Windows line endings - .replace(/\\n/g, "\n") // Unix line endings - .replace(/\n\n+/g, "\n\n") // Multiple newlines to double newline -} - -export default function ScriptTerminalModal({ - open, +export function ScriptTerminalModal({ + open: isOpen, onClose, scriptPath, scriptName, @@ -47,46 +38,49 @@ export default function ScriptTerminalModal({ title, description, }: ScriptTerminalModalProps) { - const termRef = useRef(null) - const fitAddonRef = useRef(null) + const termRef = useRef(null) const wsRef = useRef(null) + const fitAddonRef = useRef(null) const sessionIdRef = useRef(Math.random().toString(36).substring(2, 8)) - const [exitCode, setExitCode] = useState(null) - const [isComplete, setIsComplete] = useState(false) const [isConnected, setIsConnected] = useState(false) + const [isComplete, setIsComplete] = useState(false) + const [exitCode, setExitCode] = useState(null) const [currentInteraction, setCurrentInteraction] = useState(null) - const [isWaitingNextInteraction, setIsWaitingNextInteraction] = useState(false) - const [showingInteraction, setShowingInteraction] = useState(false) + const [interactionInput, setInteractionInput] = useState("") + const checkConnectionInterval = useRef(null) + const isMobile = useIsMobile() - const [modalHeight, setModalHeight] = useState(() => { - if (typeof window !== "undefined") { - const saved = localStorage.getItem("scriptModalHeight") - return saved ? Number.parseInt(saved) : 600 - } - return 600 - }) + const [isWaitingNextInteraction, setIsWaitingNextInteraction] = useState(false) + const waitingTimeoutRef = useRef(null) + + const [modalHeight, setModalHeight] = useState(600) const [isResizing, setIsResizing] = useState(false) const resizeHandlersRef = useRef<{ handleMove: ((e: MouseEvent | TouchEvent) => void) | null handleEnd: (() => void) | null }>({ handleMove: null, handleEnd: null }) - const isMobile = useIsMobile() - const terminalContainerRef = useCallback( (node: HTMLDivElement | null) => { - if (!node || !open || termRef.current) { + if (!node || !isOpen || termRef.current) { return } console.log("[v0] Terminal container mounted, initializing...") const initializeTerminal = async () => { + console.log("[v0] Loading xterm modules...") + const [TerminalClass, FitAddonClass] = await Promise.all([ + import("xterm").then((mod) => mod.Terminal), + import("xterm-addon-fit").then((mod) => mod.FitAddon), + import("xterm/css/xterm.css"), + ]) + console.log("[v0] Creating terminal instance...") const fontSize = window.innerWidth < 768 ? 12 : 16 - const term = new XTerm({ + const term = new TerminalClass({ rendererType: "dom", fontFamily: '"Courier", "Courier New", "Liberation Mono", "DejaVu Sans Mono", monospace', fontSize: fontSize, @@ -121,7 +115,7 @@ export default function ScriptTerminalModal({ }, }) - const fitAddon = new FitAddon() + const fitAddon = new FitAddonClass() term.loadAddon(fitAddon) console.log("[v0] Opening terminal in container...") term.open(node) @@ -185,8 +179,8 @@ export default function ScriptTerminalModal({ if (msg.type === "web_interaction" && msg.interaction) { console.log("[v0] Web interaction detected:", msg.interaction.type) setIsWaitingNextInteraction(false) - if (resizeHandlersRef.current.handleMove) { - clearTimeout(resizeHandlersRef.current.handleMove) + if (waitingTimeoutRef.current) { + clearTimeout(waitingTimeoutRef.current) } setCurrentInteraction({ type: msg.interaction.type, @@ -196,7 +190,6 @@ export default function ScriptTerminalModal({ options: msg.interaction.options, default: msg.interaction.default, }) - setShowingInteraction(true) return } @@ -212,8 +205,8 @@ export default function ScriptTerminalModal({ term.write(event.data) setIsWaitingNextInteraction(false) - if (resizeHandlersRef.current.handleMove) { - clearTimeout(resizeHandlersRef.current.handleMove) + if (waitingTimeoutRef.current) { + clearTimeout(waitingTimeoutRef.current) } } @@ -228,7 +221,7 @@ export default function ScriptTerminalModal({ setIsConnected(false) term.writeln("\x1b[33mConnection closed\x1b[0m") - if (!isComplete && event.code !== 1006) { + if (!isComplete) { setIsComplete(true) setExitCode(event.code === 1000 ? 0 : 1) } @@ -240,7 +233,7 @@ export default function ScriptTerminalModal({ } }) - const checkConnectionInterval = setInterval(() => { + checkConnectionInterval.current = setInterval(() => { if (ws) { setIsConnected(ws.readyState === WebSocket.OPEN) } @@ -269,20 +262,26 @@ export default function ScriptTerminalModal({ }) resizeObserver.observe(node) - - return () => { - clearInterval(checkConnectionInterval) - resizeObserver.disconnect() - } } initializeTerminal() }, - [open, scriptPath, params], + [isOpen, scriptPath, params], ) useEffect(() => { - if (!open) { + const savedHeight = localStorage.getItem("scriptModalHeight") + if (savedHeight) { + setModalHeight(Number.parseInt(savedHeight, 10)) + } + + if (!isOpen) { + if (checkConnectionInterval.current) { + clearInterval(checkConnectionInterval.current) + } + if (waitingTimeoutRef.current) { + clearTimeout(waitingTimeoutRef.current) + } if (wsRef.current) { wsRef.current.close() wsRef.current = null @@ -301,20 +300,15 @@ export default function ScriptTerminalModal({ } resizeHandlersRef.current = { handleMove: null, handleEnd: null } - setModalHeight(() => { - if (typeof window !== "undefined") { - return Number.parseInt(localStorage.getItem("scriptModalHeight") || "600") - } - return 600 - }) + sessionIdRef.current = Math.random().toString(36).substring(2, 8) setIsComplete(false) setExitCode(null) - setShowingInteraction(false) + setInteractionInput("") setCurrentInteraction(null) setIsWaitingNextInteraction(false) setIsConnected(false) } - }, [open]) + }, [isOpen]) const getScriptWebSocketUrl = (sid: string): string => { if (typeof window === "undefined") { @@ -333,7 +327,7 @@ export default function ScriptTerminalModal({ if (value === "cancel" || value === "") { setCurrentInteraction(null) - setShowingInteraction(false) + setInteractionInput("") handleCloseModal() return } @@ -349,15 +343,20 @@ export default function ScriptTerminalModal({ } setCurrentInteraction(null) - setShowingInteraction(false) + setInteractionInput("") - setIsWaitingNextInteraction(true) + waitingTimeoutRef.current = setTimeout(() => { + setIsWaitingNextInteraction(true) + }, 50) } const handleCloseModal = () => { if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) { wsRef.current.close() } + if (checkConnectionInterval.current) { + clearInterval(checkConnectionInterval.current) + } if (termRef.current) { termRef.current.dispose() } @@ -400,6 +399,8 @@ export default function ScriptTerminalModal({ const handleEnd = () => { setIsResizing(false) + localStorage.setItem("scriptModalHeight", modalHeight.toString()) + if (fitAddonRef.current && termRef.current && wsRef.current?.readyState === WebSocket.OPEN) { try { setTimeout(() => { @@ -417,8 +418,6 @@ export default function ScriptTerminalModal({ } } - localStorage.setItem("scriptModalHeight", modalHeight.toString()) - document.removeEventListener("mousemove", handleMove as any) document.removeEventListener("touchmove", handleMove as any) document.removeEventListener("mouseup", handleEnd) @@ -437,22 +436,21 @@ export default function ScriptTerminalModal({ return ( <> - + e.preventDefault()} onEscapeKeyDown={(e) => e.preventDefault()} > - - {scriptName} - - + {title} + +
+
+

{title}

+ {description &&

{description}

} +
+
@@ -483,6 +481,11 @@ export default function ScriptTerminalModal({
+ +
{isConnected ? "Online" : "Offline"}
@@ -497,7 +500,7 @@ export default function ScriptTerminalModal({
- {showingInteraction && currentInteraction && ( + {currentInteraction && ( {currentInteraction.title}
-

{processMessageText(currentInteraction.message)}

+

{currentInteraction.message}

{currentInteraction.type === "yesno" && (
@@ -554,16 +557,31 @@ export default function ScriptTerminalModal({
handleInteractionResponse(e.target.value)} + value={interactionInput} + onChange={(e) => setInteractionInput(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { - handleInteractionResponse(currentInteraction.default || "") + handleInteractionResponse(interactionInput) } }} placeholder={currentInteraction.default || ""} className="transition-all duration-150" /> +
+ + +
)} diff --git a/AppImage/components/ui/dialog.tsx b/AppImage/components/ui/dialog.tsx index 7bb807c..5c95c56 100644 --- a/AppImage/components/ui/dialog.tsx +++ b/AppImage/components/ui/dialog.tsx @@ -31,8 +31,8 @@ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName const DialogContent = React.forwardRef< React.ElementRef, - React.ComponentPropsWithoutRef & { hideClose?: boolean } ->(({ className, children, hideClose, ...props }, ref) => ( + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( {children} - {!hideClose && ( - - - Close - - )} + + + Close + ))