feat: update ui

This commit is contained in:
2024-11-08 18:53:30 +00:00
parent 887cb64878
commit 334d90e691
32 changed files with 829 additions and 262 deletions
@@ -22,11 +22,10 @@ type IncusSessionProps = {
};
};
export type InteractiveSessionProps = { params: { hostId: string } } & (
| SSHSessionProps
| PVESessionProps
| IncusSessionProps
);
export type InteractiveSessionProps = {
label: string;
params: { hostId: string };
} & (SSHSessionProps | PVESessionProps | IncusSessionProps);
const InteractiveSession = ({ type, params }: InteractiveSessionProps) => {
const query = new URLSearchParams(params);
+16 -56
View File
@@ -1,15 +1,9 @@
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";
import { ScrollView, Text, TextStyle, View } from "tamagui";
import Pressable from "../ui/pressable";
import Icons from "../ui/icons";
const Keys = {
ArrowLeft: "\x1b[D",
@@ -45,7 +39,7 @@ const Terminal = ({ client = "xtermjs", style, ...props }: TerminalProps) => {
};
return (
<View style={[styles.container, style]} {...props}>
<View flex={1} bg="$background" {...props}>
{client === "xtermjs" && (
<XTermJs
ref={xtermRef}
@@ -56,32 +50,32 @@ const Terminal = ({ client = "xtermjs", style, ...props }: TerminalProps) => {
<ScrollView
horizontal
style={{ flexGrow: 0 }}
contentContainerStyle={styles.buttons}
flexGrow={0}
contentContainerStyle={{ flexDirection: "row" }}
>
<TerminalButton
title={<Ionicons name="swap-horizontal" color="white" size={16} />}
onPress={() => send(Keys.Tab)}
title={<Icons name="swap-horizontal" size={16} />}
// onPress={() => send(Keys.Tab)}
/>
<TerminalButton title="ESC" onPress={() => send(Keys.Escape)} />
<TerminalButton
title={<Ionicons name="home" color="white" size={16} />}
title={<Icons name="home" size={16} />}
onPress={() => send(Keys.Home)}
/>
<TerminalButton
title={<Ionicons name="arrow-back" color="white" size={18} />}
title={<Icons name="arrow-left" size={18} />}
onPress={() => send(Keys.ArrowLeft)}
/>
<TerminalButton
title={<Ionicons name="arrow-up" color="white" size={18} />}
title={<Icons name="arrow-up" size={18} />}
onPress={() => send(Keys.ArrowUp)}
/>
<TerminalButton
title={<Ionicons name="arrow-down" color="white" size={18} />}
title={<Icons name="arrow-down" size={18} />}
onPress={() => send(Keys.ArrowDown)}
/>
<TerminalButton
title={<Ionicons name="arrow-forward" color="white" size={18} />}
title={<Icons name="arrow-right" size={18} />}
onPress={() => send(Keys.ArrowRight)}
/>
<TerminalButton title="Enter" onPress={() => send(Keys.Enter)} />
@@ -108,45 +102,11 @@ const TerminalButton = ({
...props
}: ComponentPropsWithoutRef<typeof Pressable> & {
title: string | React.ReactNode;
textStyle?: StyleProp<TextStyle>;
textStyle?: TextStyle;
}) => (
<Pressable
style={({ pressed }) => [styles.btn, pressed && styles.btnPressed]}
{...props}
>
{typeof title === "string" ? (
<Text style={[styles.btnText, textStyle]}>{title}</Text>
) : (
title
)}
<Pressable px="$4" py="$3" $hover={{ bg: "$blue3" }} {...props}>
{typeof title === "string" ? <Text {...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;
+30 -1
View File
@@ -20,6 +20,29 @@ type XTermJsProps = {
wsUrl: string;
};
// vscode-snazzy https://github.com/Tyriar/vscode-snazzy
const snazzyTheme = {
foreground: "#eff0eb",
background: "#282a36",
selection: "#97979b33",
black: "#282a36",
brightBlack: "#686868",
red: "#ff5c57",
brightRed: "#ff5c57",
green: "#5af78e",
brightGreen: "#5af78e",
yellow: "#f3f99d",
brightYellow: "#f3f99d",
blue: "#57c7ff",
brightBlue: "#57c7ff",
magenta: "#ff6ac1",
brightMagenta: "#ff6ac1",
cyan: "#9aedfe",
brightCyan: "#9aedfe",
white: "#f1f1f0",
brightWhite: "#eff0eb",
};
export interface XTermRef extends DOMImperativeFactory {
send: (...args: JSONValue[]) => void;
}
@@ -35,7 +58,11 @@ const XTermJs = forwardRef<XTermRef, XTermJsProps>((props, ref) => {
return;
}
const xterm = new XTerm();
const xterm = new XTerm({
fontFamily: '"Cascadia Code", Menlo, monospace',
theme: snazzyTheme,
cursorBlink: true,
});
xterm.open(container);
const fitAddon = new FitAddon();
@@ -131,6 +158,8 @@ const XTermJs = forwardRef<XTermRef, XTermJsProps>((props, ref) => {
<div
ref={containerRef}
style={{
background: snazzyTheme.background,
padding: 12,
flex: !IS_DOM ? 1 : undefined,
width: "100%",
height: IS_DOM ? "100vh" : undefined,
+30 -5
View File
@@ -1,26 +1,51 @@
import { useDebounceCallback } from "@/hooks/useDebounce";
import React, { ComponentPropsWithoutRef, useEffect, useRef } from "react";
import RNPagerView from "react-native-pager-view";
export type PagerViewProps = ComponentPropsWithoutRef<typeof RNPagerView> & {
page?: number;
onChangePage?: (page: number) => void;
EmptyComponent?: () => JSX.Element;
};
const PagerView = ({ page, onChangePage, ...props }: PagerViewProps) => {
const PagerView = ({
page,
onChangePage,
EmptyComponent,
children,
...props
}: PagerViewProps) => {
const ref = useRef<RNPagerView>(null);
const [onPageSelect, clearPageSelectDebounce] = useDebounceCallback(
(page) => onChangePage?.(page),
100
);
const [setPage] = useDebounceCallback((page) => {
ref.current?.setPage(page);
clearPageSelectDebounce();
}, 300);
useEffect(() => {
if (page != null) {
ref.current?.setPage(page);
const npage = EmptyComponent != null ? page + 1 : page;
setPage(npage);
}
}, [page]);
}, [page, EmptyComponent]);
return (
<RNPagerView
ref={ref}
{...props}
onPageSelected={(e) => onChangePage?.(e.nativeEvent.position)}
/>
onPageSelected={(e) => {
const pos = e.nativeEvent.position;
onPageSelect(EmptyComponent ? pos - 1 : pos);
}}
>
{EmptyComponent ? <EmptyComponent key="-1" /> : null}
{children}
</RNPagerView>
);
};
+11 -2
View File
@@ -3,7 +3,7 @@ import { View } from "react-native";
import { PagerViewProps } from "./pager-view";
const PagerView = ({
className,
EmptyComponent,
children,
page,
initialPage,
@@ -33,7 +33,16 @@ const PagerView = ({
});
}, [curPage, children]);
return content;
const pageElement = useMemo(() => {
return Array.isArray(children) ? children[curPage] : null;
}, [curPage, children]);
return (
<>
{!pageElement && EmptyComponent ? <EmptyComponent key="-1" /> : null}
{content}
</>
);
};
export default PagerView;
+56 -4
View File
@@ -1,7 +1,17 @@
import { Pressable as BasePressable } from "react-native";
import { GetProps, styled, ViewStyle } from "tamagui";
import { useRef } from "react";
import {
TapGestureHandler,
State as GestureState,
} from "react-native-gesture-handler";
import { Button, GetProps, styled, View, ViewStyle } from "tamagui";
const StyledPressable = styled(Button, {
unstyled: true,
backgroundColor: "$colorTransparent",
borderWidth: 0,
cursor: "pointer",
});
const StyledPressable = styled(BasePressable);
export type PressableProps = GetProps<typeof StyledPressable> & {
$hover?: ViewStyle;
$pressed?: ViewStyle;
@@ -13,7 +23,49 @@ const Pressable = ({
...props
}: PressableProps) => {
return (
<StyledPressable pressStyle={$pressed} hoverStyle={$hover} {...props} />
<StyledPressable
hoverStyle={$hover}
pressStyle={$pressed}
{...(props as any)}
/>
);
};
type MultiTapPressableProps = GetProps<typeof View> & {
numberOfTaps: number;
onTap?: () => void;
onMultiTap?: () => void;
};
export const MultiTapPressable = ({
numberOfTaps,
onTap,
onMultiTap,
...props
}: MultiTapPressableProps) => {
const tapRef = useRef<any>();
return (
<TapGestureHandler
onHandlerStateChange={(e) => {
if (e.nativeEvent.state === GestureState.ACTIVE) {
onTap?.();
}
}}
waitFor={tapRef}
>
<TapGestureHandler
onHandlerStateChange={(e) => {
if (e.nativeEvent.state === GestureState.ACTIVE) {
onMultiTap?.();
}
}}
numberOfTaps={numberOfTaps}
ref={tapRef}
>
<View pressStyle={{ opacity: 0.5 }} {...props} />
</TapGestureHandler>
</TapGestureHandler>
);
};
+25
View File
@@ -0,0 +1,25 @@
import React from "react";
import { GetProps, Input, View, ViewStyle } from "tamagui";
import Icons from "./icons";
type SearchInputProps = GetProps<typeof Input> & {
_container?: ViewStyle;
};
const SearchInput = ({ _container, ...props }: SearchInputProps) => {
return (
<View position="relative" {..._container}>
<Icons
name="magnify"
size={20}
position="absolute"
top={11}
left="$3"
zIndex={1}
/>
<Input pl="$7" placeholder="Search..." {...props} />
</View>
);
};
export default SearchInput;