mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 14:22:51 +00:00
Some working buttons
This commit is contained in:
@@ -11,16 +11,20 @@ import {
|
|||||||
useRouter,
|
useRouter,
|
||||||
useFocusEffect,
|
useFocusEffect,
|
||||||
} from 'expo-router';
|
} from 'expo-router';
|
||||||
import React, { startTransition, useEffect, useRef, useState } from 'react';
|
import React, {
|
||||||
|
createContext,
|
||||||
|
startTransition,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { KeyboardAvoidingView, Pressable, Text, View } from 'react-native';
|
import { KeyboardAvoidingView, Pressable, Text, View } from 'react-native';
|
||||||
|
|
||||||
// import {
|
|
||||||
// // KeyboardAvoidingView,
|
|
||||||
// KeyboardToolbar,
|
|
||||||
// } from 'react-native-keyboard-controller';
|
|
||||||
import { useSshStore } from '@/lib/ssh-store';
|
import { useSshStore } from '@/lib/ssh-store';
|
||||||
import { useTheme } from '@/lib/theme';
|
import { useTheme } from '@/lib/theme';
|
||||||
import { useBottomTabSpacing } from '@/lib/useBottomTabSpacing';
|
import { useBottomTabSpacing } from '@/lib/useBottomTabSpacing';
|
||||||
|
import { useContextSafe } from '@/lib/utils';
|
||||||
|
|
||||||
|
type IconName = keyof typeof Ionicons.glyphMap;
|
||||||
|
|
||||||
export default function TabsShellDetail() {
|
export default function TabsShellDetail() {
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(false);
|
||||||
@@ -145,7 +149,14 @@ function ShellDetail() {
|
|||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
behavior="height"
|
behavior="height"
|
||||||
keyboardVerticalOffset={120}
|
keyboardVerticalOffset={120}
|
||||||
style={{ flex: 1, borderWidth: 2, borderColor: theme.colors.border }}
|
style={{ flex: 1, gap: 4 }}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<XtermJsWebView
|
<XtermJsWebView
|
||||||
ref={xtermRef}
|
ref={xtermRef}
|
||||||
@@ -217,6 +228,16 @@ function ShellDetail() {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
|
<KeyboardToolbar
|
||||||
|
sendBytes={(bytes) => {
|
||||||
|
if (!shell) return;
|
||||||
|
shell.sendData(bytes.buffer).catch((e: unknown) => {
|
||||||
|
console.warn('sendData failed', e);
|
||||||
|
router.back();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</View>
|
</View>
|
||||||
{/* <KeyboardToolbar
|
{/* <KeyboardToolbar
|
||||||
@@ -227,3 +248,163 @@ function ShellDetail() {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KeyboardToolbarProps = {
|
||||||
|
sendBytes: (bytes: Uint8Array<ArrayBuffer>) => void;
|
||||||
|
};
|
||||||
|
const KeyboardToolBarContext = createContext<KeyboardToolbarProps | null>(null);
|
||||||
|
|
||||||
|
function KeyboardToolbar(props: KeyboardToolbarProps) {
|
||||||
|
return (
|
||||||
|
<KeyboardToolBarContext value={props}>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
height: 100,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KeyboardToolbarRow>
|
||||||
|
<KeyboardToolbarButtonPreset preset="esc" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="/" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="|" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="home" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="up" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="end" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="pgup" />
|
||||||
|
</KeyboardToolbarRow>
|
||||||
|
<KeyboardToolbarRow>
|
||||||
|
<KeyboardToolbarButtonPreset preset="tab" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="ctrl" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="alt" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="left" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="down" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="right" />
|
||||||
|
<KeyboardToolbarButtonPreset preset="pgdn" />
|
||||||
|
</KeyboardToolbarRow>
|
||||||
|
</View>
|
||||||
|
</KeyboardToolBarContext>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function KeyboardToolbarRow({ children }: { children?: React.ReactNode }) {
|
||||||
|
return <View style={{ flexDirection: 'row', flex: 1 }}>{children}</View>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyboardToolbarButtonPresetType =
|
||||||
|
| 'esc'
|
||||||
|
| '/'
|
||||||
|
| '|'
|
||||||
|
| 'home'
|
||||||
|
| 'up'
|
||||||
|
| 'end'
|
||||||
|
| 'pgup'
|
||||||
|
| 'pgdn'
|
||||||
|
| 'fn'
|
||||||
|
| 'tab'
|
||||||
|
| 'ctrl'
|
||||||
|
| 'alt'
|
||||||
|
| 'left'
|
||||||
|
| 'down'
|
||||||
|
| 'right'
|
||||||
|
| 'insert'
|
||||||
|
| 'delete'
|
||||||
|
| 'pageup'
|
||||||
|
| 'pagedown'
|
||||||
|
| 'fn';
|
||||||
|
|
||||||
|
function KeyboardToolbarButtonPreset({
|
||||||
|
preset,
|
||||||
|
}: {
|
||||||
|
preset: KeyboardToolbarButtonPresetType;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<KeyboardToolbarButton {...keyboardToolbarButtonPresetToProps[preset]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const keyboardToolbarButtonPresetToProps: Record<
|
||||||
|
KeyboardToolbarButtonPresetType,
|
||||||
|
KeyboardToolbarButtonProps
|
||||||
|
> = {
|
||||||
|
esc: { label: 'ESC', sendBytes: new Uint8Array([27]) },
|
||||||
|
'/': { label: '/', sendBytes: new Uint8Array([47]) },
|
||||||
|
'|': { label: '|', sendBytes: new Uint8Array([124]) },
|
||||||
|
home: { label: 'HOME', sendBytes: new Uint8Array([27, 91, 72]) },
|
||||||
|
end: { label: 'END', sendBytes: new Uint8Array([27, 91, 70]) },
|
||||||
|
pgup: { label: 'PGUP', sendBytes: new Uint8Array([27, 91, 53, 126]) },
|
||||||
|
pgdn: { label: 'PGDN', sendBytes: new Uint8Array([27, 91, 54, 126]) },
|
||||||
|
fn: { label: 'FN', isModifier: true },
|
||||||
|
tab: { label: 'TAB', sendBytes: new Uint8Array([9]) },
|
||||||
|
ctrl: { label: 'CTRL', isModifier: true },
|
||||||
|
alt: { label: 'ALT', isModifier: true },
|
||||||
|
left: { iconName: 'arrow-back', sendBytes: new Uint8Array([27, 91, 68]) },
|
||||||
|
up: { iconName: 'arrow-up', sendBytes: new Uint8Array([27, 91, 65]) },
|
||||||
|
down: { iconName: 'arrow-down', sendBytes: new Uint8Array([27, 91, 66]) },
|
||||||
|
right: {
|
||||||
|
iconName: 'arrow-forward',
|
||||||
|
sendBytes: new Uint8Array([27, 91, 67]),
|
||||||
|
},
|
||||||
|
insert: { label: 'INSERT', sendBytes: new Uint8Array([27, 91, 50, 126]) },
|
||||||
|
delete: { label: 'DELETE', sendBytes: new Uint8Array([27, 91, 51, 126]) },
|
||||||
|
pageup: { label: 'PAGEUP', sendBytes: new Uint8Array([27, 91, 53, 126]) },
|
||||||
|
pagedown: { label: 'PAGEDOWN', sendBytes: new Uint8Array([27, 91, 54, 126]) },
|
||||||
|
};
|
||||||
|
|
||||||
|
type KeyboardToolbarButtonProps = (
|
||||||
|
| {
|
||||||
|
isModifier: true;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
sendBytes: Uint8Array;
|
||||||
|
}
|
||||||
|
) &
|
||||||
|
(
|
||||||
|
| {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
iconName: IconName;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
function KeyboardToolbarButton(props: KeyboardToolbarButtonProps) {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [modifierActive, setModifierActive] = useState(false);
|
||||||
|
const { sendBytes } = useContextSafe(KeyboardToolBarContext);
|
||||||
|
|
||||||
|
const isTextLabel = 'label' in props;
|
||||||
|
const children = isTextLabel ? (
|
||||||
|
<Text style={{ color: theme.colors.textPrimary }}>{props.label}</Text>
|
||||||
|
) : (
|
||||||
|
<Ionicons
|
||||||
|
name={props.iconName}
|
||||||
|
size={20}
|
||||||
|
color={theme.colors.textPrimary}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={() => {
|
||||||
|
console.log('button pressed');
|
||||||
|
if ('isModifier' in props && props.isModifier) {
|
||||||
|
setModifierActive((active) => !active);
|
||||||
|
} else if ('sendBytes' in props) {
|
||||||
|
// todo: send key press
|
||||||
|
sendBytes(new Uint8Array(props.sendBytes));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={[
|
||||||
|
{
|
||||||
|
flex: 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: theme.colors.border,
|
||||||
|
},
|
||||||
|
modifierActive && { backgroundColor: theme.colors.primary },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ export const useSshStore = create<SshRegistryStore>((set) => ({
|
|||||||
console.log('DEBUG shell closed', storeKey);
|
console.log('DEBUG shell closed', storeKey);
|
||||||
set((s) => {
|
set((s) => {
|
||||||
const { [storeKey]: _omit, ...rest } = s.shells;
|
const { [storeKey]: _omit, ...rest } = s.shells;
|
||||||
|
if (Object.keys(rest).length === 0) {
|
||||||
|
void connection.disconnect();
|
||||||
|
}
|
||||||
return { shells: rest };
|
return { shells: rest };
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { QueryClient } from '@tanstack/react-query';
|
import { QueryClient } from '@tanstack/react-query';
|
||||||
|
import { use, type Context } from 'react';
|
||||||
|
|
||||||
export const queryClient = new QueryClient();
|
export const queryClient = new QueryClient();
|
||||||
|
|
||||||
@@ -13,3 +14,12 @@ export const AbortSignalTimeout = (timeout: number) => {
|
|||||||
}, timeout);
|
}, timeout);
|
||||||
return controller.signal;
|
return controller.signal;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const useContextSafe = <T>(context: Context<T>) => {
|
||||||
|
const contextValue = use(context);
|
||||||
|
if (!contextValue) {
|
||||||
|
throw new Error('Context not found');
|
||||||
|
}
|
||||||
|
return contextValue;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user