mirror of
https://github.com/khairul169/vaulterm.git
synced 2025-04-28 16:49:39 +07:00
feat: update ui for android
This commit is contained in:
parent
d03c11fdb1
commit
f2c0ab0945
@ -34,7 +34,6 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
|
||||
experiments: {
|
||||
typedRoutes: true,
|
||||
},
|
||||
owner: "khairul169",
|
||||
extra: {
|
||||
eas: {
|
||||
projectId: "3e0112c1-f0ed-423c-b5cf-95633f23f6dc",
|
||||
|
@ -20,9 +20,12 @@ export default function Layout() {
|
||||
drawerContent={DrawerContent}
|
||||
screenOptions={{
|
||||
drawerType: media.sm ? "front" : "permanent",
|
||||
drawerStyle: { width: 250 },
|
||||
drawerStyle: {
|
||||
width: !media.sm ? 250 : "80%",
|
||||
padding: 0,
|
||||
},
|
||||
headerLeft: media.sm ? undefined : () => null,
|
||||
headerStyle: {elevation: 0, borderBottomWidth: 0}
|
||||
headerStyle: { elevation: 0, borderBottomWidth: 0 },
|
||||
}}
|
||||
>
|
||||
<Drawer.Screen
|
||||
|
@ -3,7 +3,7 @@ import tamaguiConfig from "@/tamagui.config";
|
||||
import {
|
||||
DarkTheme,
|
||||
DefaultTheme,
|
||||
ThemeProvider,
|
||||
ThemeProvider as NavThemeProvider,
|
||||
} from "@react-navigation/native";
|
||||
import { TamaguiProvider, Theme } from "@tamagui/core";
|
||||
import useThemeStore from "@/stores/theme";
|
||||
@ -15,9 +15,19 @@ import { useServer } from "@/stores/app";
|
||||
import queryClient from "@/lib/queryClient";
|
||||
import DialogMessageProvider from "@/components/containers/dialog-message";
|
||||
|
||||
type Props = PropsWithChildren;
|
||||
const Providers = ({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider />
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
<DialogMessageProvider />
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const Providers = ({ children }: Props) => {
|
||||
const ThemeProvider = ({ children }: PropsWithChildren) => {
|
||||
const colorScheme = useThemeStore((i) => i.theme);
|
||||
|
||||
const theme = useMemo(() => {
|
||||
@ -40,17 +50,13 @@ const Providers = ({ children }: Props) => {
|
||||
}, [theme, colorScheme]);
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AuthProvider />
|
||||
<ThemeProvider value={navTheme}>
|
||||
<NavThemeProvider value={navTheme}>
|
||||
<TamaguiProvider config={tamaguiConfig} defaultTheme={colorScheme}>
|
||||
<Theme name="blue">
|
||||
<PortalProvider shouldAddRootHost>{children}</PortalProvider>
|
||||
<DialogMessageProvider />
|
||||
</Theme>
|
||||
</TamaguiProvider>
|
||||
</ThemeProvider>
|
||||
</QueryClientProvider>
|
||||
</NavThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
DrawerContentScrollView,
|
||||
DrawerNavigationOptions as NavProps,
|
||||
} from "@react-navigation/drawer";
|
||||
import { Button, View } from "tamagui";
|
||||
import { Button, Text, View } from "tamagui";
|
||||
import {
|
||||
CommonActions,
|
||||
DrawerActions,
|
||||
@ -24,19 +24,25 @@ const Drawer = (props: DrawerContentComponentProps) => {
|
||||
|
||||
return (
|
||||
<View pt={insets.top} flex={1}>
|
||||
<View p="$4">
|
||||
<View py="$4" px="$2">
|
||||
<UserMenuButton />
|
||||
</View>
|
||||
|
||||
<DrawerContentScrollView
|
||||
contentContainerStyle={{ padding: 18, paddingTop: 0 }}
|
||||
contentContainerStyle={{
|
||||
paddingTop: 0,
|
||||
paddingLeft: 0,
|
||||
paddingStart: 0,
|
||||
paddingRight: 18,
|
||||
paddingBottom: 18,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<DrawerItemList {...props} />
|
||||
</DrawerContentScrollView>
|
||||
|
||||
<View px="$4" py="$2">
|
||||
<ThemeSwitcher />
|
||||
<ThemeSwitcher $xs={{ alignSelf: "flex-start" }} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@ -85,7 +91,11 @@ const DrawerItemList = ({
|
||||
onPress={onPress}
|
||||
icon={drawerIcon?.({ size: 16, color: "$color", focused }) as never}
|
||||
size="$4"
|
||||
$xs={{ size: "$5", borderRadius: 999, borderWidth: 0 }}
|
||||
$xs={{ size: "$5" }}
|
||||
borderRadius={0}
|
||||
borderTopRightRadius="$10"
|
||||
borderBottomRightRadius="$10"
|
||||
borderWidth={0}
|
||||
>
|
||||
{drawerLabel !== undefined
|
||||
? drawerLabel
|
||||
|
@ -61,6 +61,7 @@ const ServerStatsBar = ({ url }: Props) => {
|
||||
return (
|
||||
<ScrollView
|
||||
horizontal
|
||||
flexGrow={0}
|
||||
contentContainerStyle={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
|
@ -55,6 +55,7 @@ const Terminal = ({ client = "xtermjs", style, ...props }: TerminalProps) => {
|
||||
horizontal
|
||||
flexGrow={0}
|
||||
contentContainerStyle={{ flexDirection: "row" }}
|
||||
$gtSm={{ display: "none" }}
|
||||
>
|
||||
<TerminalButton
|
||||
title={<Icons name="swap-horizontal" size={16} />}
|
||||
|
@ -12,21 +12,28 @@ const ThemeSwitcher = ({ iconSize = 18, ...props }: Props) => {
|
||||
const id = useId();
|
||||
|
||||
return (
|
||||
<XStack
|
||||
alignItems="center"
|
||||
gap="$4"
|
||||
w="auto"
|
||||
justifyContent="space-between"
|
||||
{...props}
|
||||
>
|
||||
<XStack alignItems="center" gap="$2">
|
||||
<Ionicons
|
||||
name={theme === "light" ? "moon-outline" : "sunny-outline"}
|
||||
size={iconSize}
|
||||
/>
|
||||
<Label htmlFor={id} flex={1} cursor="pointer">
|
||||
<Label htmlFor={id} cursor="pointer">
|
||||
Dark Mode
|
||||
</Label>
|
||||
</XStack>
|
||||
<Switch
|
||||
id={id}
|
||||
onPress={toggle}
|
||||
checked={theme === "dark"}
|
||||
size="$2"
|
||||
cursor="pointer"
|
||||
{...props}
|
||||
>
|
||||
<Switch.Thumb animation="quicker" />
|
||||
</Switch>
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
Text,
|
||||
useMedia,
|
||||
View,
|
||||
YGroup,
|
||||
} from "tamagui";
|
||||
import MenuButton from "../ui/menu-button";
|
||||
import Icons from "../ui/icons";
|
||||
@ -29,8 +28,11 @@ const UserMenuButton = () => {
|
||||
trigger={
|
||||
<Button
|
||||
bg="$colorTransparent"
|
||||
borderWidth={0}
|
||||
justifyContent="flex-start"
|
||||
p={0}
|
||||
borderRadius="$10"
|
||||
py={0}
|
||||
px="$2"
|
||||
gap="$1"
|
||||
>
|
||||
<Avatar circular size="$3">
|
||||
@ -42,7 +44,7 @@ const UserMenuButton = () => {
|
||||
{team ? `${team.icon} ${team.name}` : "Personal"}
|
||||
</Text>
|
||||
</View>
|
||||
<Icons name="chevron-down" size={16} />
|
||||
<Icons name="chevron-down" size={16} mr="$2" />
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
@ -59,6 +61,7 @@ const UserMenuButton = () => {
|
||||
title="Logout"
|
||||
/>
|
||||
</MenuButton>
|
||||
|
||||
<TeamForm />
|
||||
</>
|
||||
);
|
||||
|
@ -25,7 +25,7 @@ const MenuButtonFrame = ({
|
||||
<Popover size="$1" {...props}>
|
||||
<Popover.Trigger asChild={asChild}>{trigger}</Popover.Trigger>
|
||||
|
||||
<Adapt when="sm" platform="touch">
|
||||
{/* <Adapt when="sm" platform="touch">
|
||||
<Popover.Sheet modal dismissOnSnapToBottom snapPointsMode="fit">
|
||||
<Popover.Sheet.Overlay
|
||||
animation="quickest"
|
||||
@ -33,11 +33,10 @@ const MenuButtonFrame = ({
|
||||
exitStyle={{ opacity: 0 }}
|
||||
/>
|
||||
<Popover.Sheet.Frame padding="$4">
|
||||
{/* <Adapt.Contents /> */}
|
||||
{children}
|
||||
<Adapt.Contents />
|
||||
</Popover.Sheet.Frame>
|
||||
</Popover.Sheet>
|
||||
</Adapt>
|
||||
</Adapt> */}
|
||||
|
||||
<Popover.Content
|
||||
bordered
|
||||
@ -53,12 +52,8 @@ const MenuButtonFrame = ({
|
||||
};
|
||||
|
||||
const MenuButtonItem = (props: GetProps<typeof ListItem>) => {
|
||||
if (Platform.OS === "android" || Platform.OS === "ios") {
|
||||
return <ListItem hoverTheme pressTheme {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover.Close asChild>
|
||||
<Popover.Close flexDirection="row" asChild>
|
||||
<ListItem hoverTheme pressTheme {...props} />
|
||||
</Popover.Close>
|
||||
);
|
||||
|
@ -1,52 +1,30 @@
|
||||
import { useDebounceCallback } from "@/hooks/useDebounce";
|
||||
import React, { ComponentPropsWithoutRef, useEffect, useRef } from "react";
|
||||
import React, { ComponentPropsWithoutRef, forwardRef } 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,
|
||||
EmptyComponent,
|
||||
children,
|
||||
...props
|
||||
}: PagerViewProps) => {
|
||||
const ref = useRef<RNPagerView>(null);
|
||||
|
||||
const [onPageSelect, clearPageSelectDebounce] = useDebounceCallback(
|
||||
(page) => onChangePage?.(page),
|
||||
300
|
||||
);
|
||||
|
||||
const [setPage] = useDebounceCallback((page) => {
|
||||
ref.current?.setPage(page);
|
||||
clearPageSelectDebounce();
|
||||
}, 100);
|
||||
|
||||
useEffect(() => {
|
||||
if (page != null) {
|
||||
const npage = EmptyComponent != null ? page + 1 : page;
|
||||
setPage(npage);
|
||||
}
|
||||
}, [page, EmptyComponent]);
|
||||
export type PagerViewRef = {
|
||||
setPage: (page: number) => void;
|
||||
setPageWithoutAnimation: (page: number) => void;
|
||||
};
|
||||
|
||||
const PagerView = forwardRef<PagerViewRef, PagerViewProps>(
|
||||
({ onChangePage, children, ...props }, ref) => {
|
||||
return (
|
||||
<RNPagerView
|
||||
ref={ref}
|
||||
ref={ref as never}
|
||||
{...props}
|
||||
onPageSelected={(e) => {
|
||||
const pos = e.nativeEvent.position;
|
||||
onPageSelect(EmptyComponent ? pos - 1 : pos);
|
||||
onChangePage?.(pos);
|
||||
}}
|
||||
>
|
||||
{EmptyComponent ? <EmptyComponent key="-1" /> : null}
|
||||
{children}
|
||||
</RNPagerView>
|
||||
);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
export default PagerView;
|
||||
|
@ -1,20 +1,20 @@
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import React, {
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { View } from "react-native";
|
||||
import { PagerViewProps } from "./pager-view";
|
||||
import { PagerViewProps, PagerViewRef } from "./pager-view";
|
||||
|
||||
const PagerView = ({
|
||||
EmptyComponent,
|
||||
children,
|
||||
page,
|
||||
initialPage,
|
||||
}: PagerViewProps) => {
|
||||
const [curPage, setPage] = useState<number>(page || initialPage || 0);
|
||||
const PagerView = forwardRef<PagerViewRef, PagerViewProps>(
|
||||
({ children, initialPage }, ref) => {
|
||||
const [curPage, setPage] = useState<number>(initialPage || 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (page != null) {
|
||||
setPage(page);
|
||||
}
|
||||
}, [page]);
|
||||
useImperativeHandle(ref, () => ({
|
||||
setPage,
|
||||
setPageWithoutAnimation: setPage,
|
||||
}));
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (!Array.isArray(children)) {
|
||||
@ -33,16 +33,8 @@ const PagerView = ({
|
||||
});
|
||||
}, [curPage, children]);
|
||||
|
||||
const pageElement = useMemo(() => {
|
||||
return Array.isArray(children) ? children[curPage] : null;
|
||||
}, [curPage, children]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!pageElement && EmptyComponent ? <EmptyComponent key="-1" /> : null}
|
||||
{content}
|
||||
</>
|
||||
);
|
||||
};
|
||||
return content;
|
||||
}
|
||||
);
|
||||
|
||||
export default PagerView;
|
||||
|
@ -11,9 +11,6 @@
|
||||
"distribution": "internal",
|
||||
"android": {
|
||||
"buildType": "apk"
|
||||
},
|
||||
"env": {
|
||||
"EXPO_PUBLIC_API_URL": "https://vaulterm-dev.rul.sh"
|
||||
}
|
||||
},
|
||||
"production": {}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useRef } from "react";
|
||||
import { useCallback, useRef } from "react";
|
||||
|
||||
export const useDebounceCallback = <T extends (...args: any[]) => any>(
|
||||
callback: T,
|
||||
@ -24,5 +24,5 @@ export const useDebounceCallback = <T extends (...args: any[]) => any>(
|
||||
[delay, clear]
|
||||
);
|
||||
|
||||
return [fn, clear] as const;
|
||||
return fn;
|
||||
};
|
||||
|
@ -24,7 +24,7 @@
|
||||
"@react-native-async-storage/async-storage": "1.23.1",
|
||||
"@react-navigation/drawer": "7.0.0",
|
||||
"@react-navigation/native": "7.0.0",
|
||||
"@tamagui/config": "^1.116.14",
|
||||
"@tamagui/config": "^1.116.15",
|
||||
"@tanstack/react-query": "^5.59.20",
|
||||
"@xterm/addon-attach": "^0.11.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
@ -53,7 +53,7 @@
|
||||
"react-native-screens": "4.0.0",
|
||||
"react-native-web": "~0.19.13",
|
||||
"react-native-webview": "^13.12.2",
|
||||
"tamagui": "^1.116.14",
|
||||
"tamagui": "^1.116.15",
|
||||
"zod": "3.23.8",
|
||||
"zustand": "^5.0.1"
|
||||
},
|
||||
|
@ -33,9 +33,7 @@ export default function LoginPage() {
|
||||
},
|
||||
title: "Login",
|
||||
headerTitle: "",
|
||||
headerRight: () => (
|
||||
<ThemeSwitcher bg="$colorTransparent" $gtSm={{ mr: "$3" }} />
|
||||
),
|
||||
headerRight: () => <ThemeSwitcher $gtSm={{ mr: "$3" }} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
|
@ -46,9 +46,7 @@ export default function ServerPage() {
|
||||
marginHorizontal: "auto",
|
||||
},
|
||||
title: "Vaulterm",
|
||||
headerRight: () => (
|
||||
<ThemeSwitcher bg="$colorTransparent" $gtSm={{ mr: "$3" }} />
|
||||
),
|
||||
headerRight: () => <ThemeSwitcher $gtSm={{ mr: "$3" }} />,
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -66,7 +64,13 @@ export default function ServerPage() {
|
||||
<ErrorAlert error={serverConnect.error} />
|
||||
|
||||
<FormField vertical label="URL">
|
||||
<InputField form={form} name="url" placeholder="https://" />
|
||||
<InputField
|
||||
form={form}
|
||||
name="url"
|
||||
autoCapitalize="none"
|
||||
keyboardType="url"
|
||||
placeholder="https://"
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
<Button onPress={onSubmit} isLoading={serverConnect.isPending}>
|
||||
|
@ -2,6 +2,7 @@ import React from "react";
|
||||
import { useTermSession } from "@/stores/terminal-sessions";
|
||||
import { Button, ScrollView, View } from "tamagui";
|
||||
import Icons from "@/components/ui/icons";
|
||||
import { router } from "expo-router";
|
||||
|
||||
const SessionTabs = () => {
|
||||
const { sessions, curSession, setSession, remove } = useTermSession();
|
||||
@ -50,7 +51,7 @@ const SessionTabs = () => {
|
||||
))}
|
||||
|
||||
<Button
|
||||
onPress={() => setSession(-1)}
|
||||
onPress={() => router.push("/hosts")}
|
||||
size="$2.5"
|
||||
bg="$colorTransparent"
|
||||
circular
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import InteractiveSession from "@/components/containers/interactive-session";
|
||||
import PagerView from "@/components/ui/pager-view";
|
||||
import PagerView, { PagerViewRef } from "@/components/ui/pager-view";
|
||||
import { useTermSession } from "@/stores/terminal-sessions";
|
||||
import { Button, useMedia } from "tamagui";
|
||||
import SessionTabs from "./components/session-tabs";
|
||||
@ -8,12 +8,40 @@ import HostList from "../hosts/components/host-list";
|
||||
import Drawer from "expo-router/drawer";
|
||||
import { router } from "expo-router";
|
||||
import Icons from "@/components/ui/icons";
|
||||
import { useDebounceCallback } from "@/hooks/useDebounce";
|
||||
|
||||
const TerminalPage = () => {
|
||||
const pagerViewRef = useRef<PagerViewRef>(null!);
|
||||
const { sessions, curSession, setSession } = useTermSession();
|
||||
const session = sessions[curSession];
|
||||
const media = useMedia();
|
||||
|
||||
const setCurSession = useDebounceCallback((idx: number) => {
|
||||
pagerViewRef.current?.setPage(idx);
|
||||
}, 100);
|
||||
|
||||
useEffect(() => {
|
||||
setCurSession(curSession);
|
||||
}, [curSession]);
|
||||
|
||||
const pagerView = useMemo(() => {
|
||||
if (!sessions.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<PagerView
|
||||
ref={pagerViewRef}
|
||||
style={{ flex: 1 }}
|
||||
onChangePage={setSession}
|
||||
initialPage={0}
|
||||
>
|
||||
{sessions.map((session) => (
|
||||
<InteractiveSession key={session.id} {...session} />
|
||||
))}
|
||||
</PagerView>
|
||||
);
|
||||
}, [sessions]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer.Screen
|
||||
@ -30,17 +58,7 @@ const TerminalPage = () => {
|
||||
/>
|
||||
|
||||
{sessions.length > 0 && media.gtSm ? <SessionTabs /> : null}
|
||||
|
||||
<PagerView
|
||||
style={{ flex: 1 }}
|
||||
page={curSession}
|
||||
onChangePage={setSession}
|
||||
EmptyComponent={() => <HostList allowEdit={false} />}
|
||||
>
|
||||
{sessions.map((session) => (
|
||||
<InteractiveSession key={session.id} {...session} />
|
||||
))}
|
||||
</PagerView>
|
||||
{!sessions.length ? <HostList allowEdit={false} /> : pagerView}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -63,7 +63,7 @@ const SessionsPage = () => {
|
||||
icon={<Icons name="connection" size={16} />}
|
||||
onPress={() => {
|
||||
router.back();
|
||||
setTimeout(() => setSession(idx), 20);
|
||||
setSession(idx);
|
||||
}}
|
||||
>
|
||||
{session.label}
|
||||
|
1482
frontend/pnpm-lock.yaml
generated
1482
frontend/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
import { createTamagui } from "@tamagui/core";
|
||||
import { createTamagui } from "tamagui";
|
||||
import { config } from "@tamagui/config/v3";
|
||||
|
||||
// you usually export this from a tamagui.config.ts file
|
||||
|
Loading…
x
Reference in New Issue
Block a user