mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
some things working
This commit is contained in:
@@ -5,22 +5,15 @@ import {
|
||||
type XtermWebViewHandle,
|
||||
} from '@fressh/react-native-xtermjs-webview';
|
||||
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Platform,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import PQueue from 'p-queue';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { Pressable, View } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { disconnectSshConnectionAndInvalidateQuery } from '@/lib/query-fns';
|
||||
import { useTheme } from '@/lib/theme';
|
||||
|
||||
const renderer: 'xtermjs' | 'rn-text' = 'xtermjs';
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
|
||||
export default function TabsShellDetail() {
|
||||
return <ShellDetail />;
|
||||
}
|
||||
@@ -43,29 +36,42 @@ function ShellDetail() {
|
||||
? RnRussh.getSshShell(String(connectionId), channelIdNum)
|
||||
: undefined;
|
||||
|
||||
const [shellData, setShellData] = useState('');
|
||||
function sendDataToXterm(data: ArrayBuffer) {
|
||||
try {
|
||||
const bytes = new Uint8Array(data);
|
||||
console.log('sendDataToXterm', new TextDecoder().decode(bytes));
|
||||
xtermWebViewRef.current?.write(bytes);
|
||||
} catch (e) {
|
||||
console.warn('Failed to decode shell data', e);
|
||||
}
|
||||
}
|
||||
|
||||
const queueRef = useRef<PQueue>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connection) return;
|
||||
if (!queueRef.current)
|
||||
queueRef.current = new PQueue({
|
||||
concurrency: 1,
|
||||
intervalCap: 1, // <= one task per interval
|
||||
interval: 100, // <= 100ms between tasks
|
||||
autoStart: false, // <= buffer until we start()
|
||||
});
|
||||
const xtermQueue = queueRef.current;
|
||||
if (!connection || !xtermQueue) return;
|
||||
const listenerId = connection.addChannelListener((data: ArrayBuffer) => {
|
||||
try {
|
||||
const bytes = new Uint8Array(data);
|
||||
xtermWebViewRef.current?.write(bytes);
|
||||
const chunk = decoder.decode(bytes);
|
||||
setShellData((prev) => prev + chunk);
|
||||
} catch (e) {
|
||||
console.warn('Failed to decode shell data', e);
|
||||
}
|
||||
console.log('ssh.onData', new TextDecoder().decode(new Uint8Array(data)));
|
||||
void xtermQueue.add(() => {
|
||||
sendDataToXterm(data);
|
||||
});
|
||||
});
|
||||
return () => {
|
||||
connection.removeChannelListener(listenerId);
|
||||
xtermQueue.pause();
|
||||
xtermQueue.clear();
|
||||
};
|
||||
}, [connection]);
|
||||
}, [connection, queueRef]);
|
||||
|
||||
const scrollViewRef = useRef<ScrollView | null>(null);
|
||||
useEffect(() => {
|
||||
scrollViewRef.current?.scrollToEnd({ animated: true });
|
||||
}, [shellData]);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: theme.colors.background }}>
|
||||
@@ -77,9 +83,15 @@ function ShellDetail() {
|
||||
accessibilityLabel="Disconnect"
|
||||
hitSlop={10}
|
||||
onPress={async () => {
|
||||
if (!connection) return;
|
||||
try {
|
||||
await connection?.disconnect();
|
||||
} catch {}
|
||||
await disconnectSshConnectionAndInvalidateQuery({
|
||||
connectionId: connection.connectionId,
|
||||
queryClient: queryClient,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('Failed to disconnect', e);
|
||||
}
|
||||
router.replace('/shell');
|
||||
}}
|
||||
>
|
||||
@@ -94,140 +106,30 @@ function ShellDetail() {
|
||||
{ backgroundColor: theme.colors.background },
|
||||
]}
|
||||
>
|
||||
<ScrollView>
|
||||
{renderer === 'xtermjs' ? (
|
||||
<XtermJsWebView
|
||||
ref={xtermWebViewRef}
|
||||
style={{ flex: 1, height: 400 }}
|
||||
// textZoom={0}
|
||||
// injectedJavaScript={`
|
||||
// setTimeout(() => {
|
||||
// document.body.style.backgroundColor = '${theme.colors.background}';
|
||||
// document.body.style.color = '${theme.colors.textPrimary}';
|
||||
// document.body.style.fontSize = '80px';
|
||||
// const termDiv = document.getElementById('terminal');
|
||||
// termDiv.style.backgroundColor = '${theme.colors.background}';
|
||||
// termDiv.style.color = '${theme.colors.textPrimary}';
|
||||
// window.terminal.options.fontSize = 50;
|
||||
// }, 50);
|
||||
// `}
|
||||
onMessage={(event) => {
|
||||
console.log('onMessage', event.nativeEvent.data);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: '#0E172B',
|
||||
borderRadius: 12,
|
||||
height: 400,
|
||||
borderWidth: 1,
|
||||
borderColor: '#2A3655',
|
||||
overflow: 'hidden',
|
||||
marginBottom: 12,
|
||||
}}
|
||||
>
|
||||
<ScrollView
|
||||
ref={scrollViewRef}
|
||||
contentContainerStyle={{
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 4,
|
||||
paddingBottom: 12,
|
||||
}}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<Text
|
||||
selectable
|
||||
style={{
|
||||
color: '#D1D5DB',
|
||||
fontSize: 14,
|
||||
lineHeight: 18,
|
||||
fontFamily: Platform.select({
|
||||
ios: 'Menlo',
|
||||
android: 'monospace',
|
||||
default: 'monospace',
|
||||
}),
|
||||
}}
|
||||
>
|
||||
{shellData || 'Connected. Output will appear here...'}
|
||||
</Text>
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
<CommandInput
|
||||
executeCommand={async (command) => {
|
||||
await shell?.sendData(
|
||||
Uint8Array.from(new TextEncoder().encode(command + '\n'))
|
||||
.buffer,
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ScrollView>
|
||||
<XtermJsWebView
|
||||
ref={xtermWebViewRef}
|
||||
style={{ flex: 1, height: 400 }}
|
||||
// textZoom={0}
|
||||
injectedJavaScript={`
|
||||
document.body.style.backgroundColor = '${theme.colors.background}';
|
||||
const termDiv = document.getElementById('terminal');
|
||||
window.terminal.options.fontSize = 50;
|
||||
setTimeout(() => {
|
||||
window.fitAddon?.fit();
|
||||
}, 1_000);
|
||||
`}
|
||||
onMessage={(message) => {
|
||||
if (message.type === 'initialized') {
|
||||
console.log('xterm.onMessage initialized');
|
||||
queueRef.current?.start();
|
||||
return;
|
||||
}
|
||||
const data = message.data;
|
||||
console.log('xterm.onMessage', new TextDecoder().decode(data));
|
||||
void shell?.sendData(data.buffer as ArrayBuffer);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
function CommandInput(props: {
|
||||
executeCommand: (command: string) => Promise<void>;
|
||||
}) {
|
||||
const [command, setCommand] = useState('');
|
||||
|
||||
async function handleExecute() {
|
||||
if (!command.trim()) return;
|
||||
await props.executeCommand(command);
|
||||
setCommand('');
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>
|
||||
<TextInput
|
||||
testID="command-input"
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: '#0E172B',
|
||||
borderWidth: 1,
|
||||
borderColor: '#2A3655',
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
color: '#E5E7EB',
|
||||
fontSize: 16,
|
||||
fontFamily: Platform.select({
|
||||
ios: 'Menlo',
|
||||
android: 'monospace',
|
||||
default: 'monospace',
|
||||
}),
|
||||
}}
|
||||
value={command}
|
||||
onChangeText={setCommand}
|
||||
placeholder="Type a command and press Enter or Execute"
|
||||
placeholderTextColor="#9AA0A6"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
returnKeyType="send"
|
||||
onSubmitEditing={handleExecute}
|
||||
/>
|
||||
<Pressable
|
||||
style={[
|
||||
{
|
||||
backgroundColor: '#2563EB',
|
||||
borderRadius: 10,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 12,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
{ marginTop: 8 },
|
||||
]}
|
||||
onPress={handleExecute}
|
||||
testID="execute-button"
|
||||
>
|
||||
<Text style={{ color: '#FFFFFF', fontWeight: '700', fontSize: 14 }}>
|
||||
Execute
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user