import React, { ComponentPropsWithoutRef } from "react"; import XTermJs, { XTermRef } from "./xtermjs"; import Ionicons from "@expo/vector-icons/Ionicons"; import { Pressable, ScrollView, StyleProp, StyleSheet, Text, TextStyle, View, } from "react-native"; const Keys = { ArrowLeft: "\x1b[D", ArrowRight: "\x1b[C", ArrowUp: "\x1b[A", ArrowDown: "\x1b[B", Enter: "\x0D", Escape: "\x1b", Home: "\x1b[H", End: "\x1b[F", PageUp: "\x1b[5~", PageDown: "\x1b[6~", Alt: "\x1b", Tab: "\x09", }; type XTermJsProps = { client: "xtermjs"; wsUrl: string; }; type TerminalProps = ComponentPropsWithoutRef<typeof View> & XTermJsProps; const Terminal = ({ client, style, ...props }: TerminalProps) => { const xtermRef = React.useRef<XTermRef>(null); const send = (data: string) => { switch (client) { case "xtermjs": xtermRef.current?.send(data); break; } }; return ( <View style={[styles.container, style]} {...props}> {client === "xtermjs" && ( <XTermJs ref={xtermRef} dom={{ scrollEnabled: false }} wsUrl={props.wsUrl} /> )} <ScrollView horizontal style={{ flexGrow: 0 }} contentContainerStyle={styles.buttons} > <TerminalButton title={<Ionicons name="swap-horizontal" color="white" size={16} />} onPress={() => send(Keys.Tab)} /> <TerminalButton title="ESC" onPress={() => send(Keys.Escape)} /> <TerminalButton title={<Ionicons name="home" color="white" size={16} />} onPress={() => send(Keys.Home)} /> <TerminalButton title={<Ionicons name="arrow-back" color="white" size={18} />} onPress={() => send(Keys.ArrowLeft)} /> <TerminalButton title={<Ionicons name="arrow-up" color="white" size={18} />} onPress={() => send(Keys.ArrowUp)} /> <TerminalButton title={<Ionicons name="arrow-down" color="white" size={18} />} onPress={() => send(Keys.ArrowDown)} /> <TerminalButton title={<Ionicons name="arrow-forward" color="white" size={18} />} onPress={() => send(Keys.ArrowRight)} /> <TerminalButton title="Enter" onPress={() => send(Keys.Enter)} /> <TerminalButton title="End" onPress={() => send(Keys.End)} /> <TerminalButton title="PgUp" onPress={() => send(Keys.PageUp)} /> <TerminalButton title="PgDn" onPress={() => send(Keys.PageDown)} /> {/* <TerminalButton title="Alt" onPress={() => send(Keys.Alt)} /> */} <TerminalButton title="^C" onPress={() => send("\x03")} /> <TerminalButton title="^D" onPress={() => send("\x04")} /> <TerminalButton title="^Q" onPress={() => send("\x11")} /> <TerminalButton title="^V" onPress={() => send("\x11")} /> <TerminalButton title="^S" onPress={() => send("\x13")} /> <TerminalButton title="^W" onPress={() => send("\x18")} /> <TerminalButton title="^X" onPress={() => send("\x18")} /> <TerminalButton title="^Z" onPress={() => send("\x1a")} /> </ScrollView> </View> ); }; const TerminalButton = ({ title, textStyle, ...props }: ComponentPropsWithoutRef<typeof Pressable> & { title: string | React.ReactNode; textStyle?: StyleProp<TextStyle>; }) => ( <Pressable style={({ pressed }) => [styles.btn, pressed && styles.btnPressed]} {...props} > {typeof title === "string" ? ( <Text style={[styles.btnText, textStyle]}>{title}</Text> ) : ( title )} </Pressable> ); const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#232323", }, buttons: { display: "flex", flexDirection: "row", alignItems: "stretch", backgroundColor: "#232323", }, btn: { display: "flex", justifyContent: "center", alignItems: "center", paddingHorizontal: 14, paddingVertical: 10, }, btnPressed: { backgroundColor: "#3a3a3a", }, btnText: { color: "white", fontSize: 16, }, }); export default Terminal;