mirror of
https://github.com/EthanShoeDev/fressh.git
synced 2026-01-11 06:12:51 +00:00
ommit
This commit is contained in:
@@ -8,7 +8,8 @@
|
||||
"**/node_modules/**",
|
||||
"**/android/**",
|
||||
"**/generated/**",
|
||||
"**/rust/target/**"
|
||||
"**/rust/target/**",
|
||||
"**/mobile/ios/**"
|
||||
],
|
||||
"threshold": 0,
|
||||
"minTokens": 50,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'tsx/cjs';
|
||||
import { type ExpoConfig } from 'expo/config';
|
||||
import 'tsx/cjs';
|
||||
import packageJson from './package.json';
|
||||
|
||||
const config: ExpoConfig = {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-image": "~3.0.8",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-router": "6.0.4",
|
||||
"expo-router": "6.0.5",
|
||||
"expo-secure-store": "~15.0.7",
|
||||
"expo-splash-screen": "~31.0.10",
|
||||
"expo-status-bar": "~3.0.8",
|
||||
@@ -75,6 +75,7 @@
|
||||
"jiti": "^2.5.1",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-organize-imports": "^4.2.0",
|
||||
"tsx": "^4.20.5",
|
||||
"typescript": "~5.9.2"
|
||||
},
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import epicConfig from '@epic-web/config/prettier';
|
||||
// Sometimes this plugin can remove imports that are being edited.
|
||||
// As a workaround we will only use this in the cli. (pnpm run fmt)
|
||||
// const sortImports = process.env.SORT_IMPORTS === "true";
|
||||
const sortImports = process.env.SORT_IMPORTS === 'true-never';
|
||||
|
||||
/** @type {import("prettier").Options} */
|
||||
export default {
|
||||
...epicConfig,
|
||||
semi: true,
|
||||
plugins: [
|
||||
// ...(sortImports ? ["prettier-plugin-organize-imports"] : []),
|
||||
...(sortImports ? ['prettier-plugin-organize-imports'] : []),
|
||||
...(epicConfig.plugins || []),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
import * as fsp from 'fs/promises';
|
||||
import * as path from 'path';
|
||||
import { command, run, option, oneOf, boolean, flag } from 'cmd-ts';
|
||||
import { boolean, command, flag, oneOf, option, run } from 'cmd-ts';
|
||||
import { z } from 'zod';
|
||||
import packageJson from '../package.json' with { type: 'json' };
|
||||
import { cmd } from './script-lib';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { NativeTabs, Icon, Label } from 'expo-router/unstable-native-tabs';
|
||||
import { Icon, Label, NativeTabs } from 'expo-router/unstable-native-tabs';
|
||||
import React from 'react';
|
||||
import { useTheme } from '@/lib/theme';
|
||||
|
||||
@@ -15,10 +15,10 @@ export default function TabsLayout() {
|
||||
// android
|
||||
backBehavior="initialRoute"
|
||||
indicatorColor={theme.colors.primary}
|
||||
labelVisibilityMode="labeled"
|
||||
// labelVisibilityMode="labeled"
|
||||
// rippleColor={theme.colors.transparent}
|
||||
// ios
|
||||
blurEffect="systemDefault"
|
||||
// blurEffect="systemChromeMaterial"
|
||||
>
|
||||
<NativeTabs.Trigger name="index">
|
||||
<Label selectedStyle={{ color: theme.colors.textPrimary }}>Hosts</Label>
|
||||
|
||||
@@ -3,24 +3,21 @@ import { useStore } from '@tanstack/react-form';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
Modal,
|
||||
} from 'react-native';
|
||||
import {
|
||||
SafeAreaView,
|
||||
useSafeAreaInsets,
|
||||
} from 'react-native-safe-area-context';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { useAppForm, useFieldContext } from '@/components/form-components';
|
||||
import { KeyList } from '@/components/key-manager/KeyList';
|
||||
import { useSshConnMutation } from '@/lib/query-fns';
|
||||
import {
|
||||
type ConnectionDetails,
|
||||
connectionDetailsSchema,
|
||||
secretsManager,
|
||||
type ConnectionDetails,
|
||||
} from '@/lib/secrets-manager';
|
||||
import { useTheme, type AppTheme } from '@/lib/theme';
|
||||
|
||||
@@ -41,7 +38,7 @@ const defaultValues: ConnectionDetails = {
|
||||
function Host() {
|
||||
const theme = useTheme();
|
||||
const styles = React.useMemo(() => makeStyles(theme), [theme]);
|
||||
const insets = useSafeAreaInsets();
|
||||
// const insets = useSafeAreaInsets();
|
||||
const sshConnMutation = useSshConnMutation();
|
||||
const connectionForm = useAppForm({
|
||||
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
|
||||
@@ -65,10 +62,7 @@ function Host() {
|
||||
return (
|
||||
<SafeAreaView style={{ flex: 1, backgroundColor: theme.colors.background }}>
|
||||
<ScrollView
|
||||
contentContainerStyle={[
|
||||
styles.scrollContent,
|
||||
{ paddingBottom: Math.max(32, insets.bottom + 24) },
|
||||
]}
|
||||
contentContainerStyle={[styles.scrollContent]}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
style={{ backgroundColor: theme.colors.background }}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Link } from 'expo-router';
|
||||
import React from 'react';
|
||||
import { Pressable, View, Text, StyleSheet } from 'react-native';
|
||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { useTheme, useThemeControls, type AppTheme } from '@/lib/theme';
|
||||
|
||||
export default function Tab() {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import {
|
||||
RnRussh,
|
||||
type RnRussh,
|
||||
type SshConnection,
|
||||
type SshShellSession,
|
||||
} from '@fressh/react-native-uniffi-russh';
|
||||
import { FlashList } from '@shopify/flash-list';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
@@ -11,7 +10,14 @@ import { Link, Stack, useRouter } from 'expo-router';
|
||||
import React from 'react';
|
||||
import { Modal, Pressable, StyleSheet, Text, View } from 'react-native';
|
||||
import { SafeAreaView } from 'react-native-safe-area-context';
|
||||
import { listSshShellsQueryOptions } from '@/lib/query-fns';
|
||||
import { IconSegmentedControl } from '@/components/icon-segmented-control';
|
||||
import { preferences } from '@/lib/preferences';
|
||||
import {
|
||||
closeSshShellAndInvalidateQuery,
|
||||
disconnectSshConnectionAndInvalidateQuery,
|
||||
listSshShellsQueryOptions,
|
||||
type ShellWithConnection,
|
||||
} from '@/lib/query-fns';
|
||||
import { useTheme, type AppTheme } from '@/lib/theme';
|
||||
|
||||
export default function TabsShellList() {
|
||||
@@ -23,28 +29,24 @@ export default function TabsShellList() {
|
||||
);
|
||||
}
|
||||
|
||||
type ShellWithConnection = SshShellSession & { connection: SshConnection };
|
||||
|
||||
function ShellContent() {
|
||||
const [viewIndex, setViewIndex] = React.useState(0);
|
||||
const connectionsWithShells = useQuery(listSshShellsQueryOptions);
|
||||
const connectionsQuery = useQuery(listSshShellsQueryOptions);
|
||||
|
||||
console.log('DEBUG connectionsQuery.data', !!connectionsQuery.data);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Stack.Screen
|
||||
options={{
|
||||
headerRight: () => (
|
||||
<TopBarToggle viewIndex={viewIndex} onChange={setViewIndex} />
|
||||
),
|
||||
headerRight: () => <TopBarToggle />,
|
||||
}}
|
||||
/>
|
||||
{connectionsWithShells.isLoading || !connectionsWithShells.data ? (
|
||||
{!connectionsQuery.data ? (
|
||||
<LoadingState />
|
||||
) : connectionsQuery.data.length === 0 ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<LoadedState
|
||||
connectionsWithShells={connectionsWithShells.data}
|
||||
viewIndex={viewIndex}
|
||||
/>
|
||||
<LoadedState connections={connectionsQuery.data} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
@@ -60,171 +62,171 @@ function LoadingState() {
|
||||
);
|
||||
}
|
||||
|
||||
type ActionTarget =
|
||||
| {
|
||||
shell: ShellWithConnection;
|
||||
}
|
||||
| {
|
||||
connection: SshConnection;
|
||||
};
|
||||
|
||||
function LoadedState({
|
||||
connections,
|
||||
}: {
|
||||
connections: ReturnType<typeof RnRussh.listSshConnectionsWithShells>;
|
||||
}) {
|
||||
const [actionTarget, setActionTarget] = React.useState<null | ActionTarget>(
|
||||
null,
|
||||
);
|
||||
const queryClient = useQueryClient();
|
||||
const [shellListViewMode] =
|
||||
preferences.shellListViewMode.useShellListViewModePref();
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
{shellListViewMode === 'flat' ? (
|
||||
<FlatView
|
||||
connectionsWithShells={connections}
|
||||
setActionTarget={setActionTarget}
|
||||
/>
|
||||
) : (
|
||||
<GroupedView
|
||||
connectionsWithShells={connections}
|
||||
setActionTarget={setActionTarget}
|
||||
/>
|
||||
)}
|
||||
<ActionsSheet
|
||||
target={actionTarget}
|
||||
onClose={() => setActionTarget(null)}
|
||||
onCloseShell={() => {
|
||||
if (!actionTarget) return;
|
||||
if (!('shell' in actionTarget)) return;
|
||||
void closeSshShellAndInvalidateQuery({
|
||||
channelId: actionTarget.shell.channelId,
|
||||
connectionId: actionTarget.shell.connectionId,
|
||||
queryClient: queryClient,
|
||||
});
|
||||
}}
|
||||
onDisconnect={() => {
|
||||
if (!actionTarget) return;
|
||||
const connectionId =
|
||||
'connection' in actionTarget
|
||||
? actionTarget.connection.connectionId
|
||||
: actionTarget.shell.connectionId;
|
||||
void disconnectSshConnectionAndInvalidateQuery({
|
||||
connectionId: connectionId,
|
||||
queryClient: queryClient,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function FlatView({
|
||||
connectionsWithShells,
|
||||
viewIndex,
|
||||
setActionTarget,
|
||||
}: {
|
||||
connectionsWithShells: ReturnType<
|
||||
typeof RnRussh.listSshConnectionsWithShells
|
||||
>;
|
||||
viewIndex: number;
|
||||
setActionTarget: (target: ActionTarget) => void;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const styles = React.useMemo(() => makeStyles(theme), [theme]);
|
||||
const [actionTarget, setActionTarget] = React.useState<null | {
|
||||
connectionId: string;
|
||||
channelId: number;
|
||||
}>(null);
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const flatShells = React.useMemo(() => {
|
||||
return connectionsWithShells.reduce<ShellWithConnection[]>((acc, curr) => {
|
||||
acc.push(...curr.shells.map((shell) => ({ ...shell, connection: curr })));
|
||||
return acc;
|
||||
}, []);
|
||||
}, [connectionsWithShells]);
|
||||
|
||||
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({});
|
||||
React.useEffect(() => {
|
||||
const init: Record<string, boolean> = {};
|
||||
for (const c of connectionsWithShells) init[c.connectionId] = true;
|
||||
setExpanded(init);
|
||||
}, [connectionsWithShells]);
|
||||
|
||||
async function handleCloseShell(connId: string, channelId: number) {
|
||||
try {
|
||||
const shell = RnRussh.getSshShell(connId, channelId);
|
||||
await (shell as any)?.close?.();
|
||||
} catch {}
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: listSshShellsQueryOptions.queryKey,
|
||||
});
|
||||
setActionTarget(null);
|
||||
}
|
||||
|
||||
async function handleDisconnect(connId: string) {
|
||||
try {
|
||||
const conn = RnRussh.getSshConnection(connId);
|
||||
await conn?.disconnect();
|
||||
} catch {}
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: listSshShellsQueryOptions.queryKey,
|
||||
});
|
||||
setActionTarget(null);
|
||||
}
|
||||
|
||||
if (viewIndex === 0) {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
{flatShells.length === 0 ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<FlashList
|
||||
data={flatShells}
|
||||
keyExtractor={(item) => `${item.connectionId}:${item.channelId}`}
|
||||
renderItem={({ item }) => (
|
||||
<ShellCard
|
||||
shell={item}
|
||||
onLongPress={() =>
|
||||
setActionTarget({
|
||||
connectionId: item.connectionId as string,
|
||||
channelId: item.channelId as number,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
ItemSeparatorComponent={() => <View style={{ height: 12 }} />}
|
||||
contentContainerStyle={{
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 16,
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
)}
|
||||
<ActionsSheet
|
||||
target={actionTarget}
|
||||
onClose={() => setActionTarget(null)}
|
||||
onCloseShell={() =>
|
||||
actionTarget &&
|
||||
handleCloseShell(actionTarget.connectionId, actionTarget.channelId)
|
||||
}
|
||||
onDisconnect={() =>
|
||||
actionTarget && handleDisconnect(actionTarget.connectionId)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
{connectionsWithShells.length === 0 ? (
|
||||
<EmptyState />
|
||||
) : (
|
||||
<FlashList
|
||||
data={connectionsWithShells}
|
||||
// estimatedItemSize={80}
|
||||
keyExtractor={(item) => item.connectionId}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.groupContainer}>
|
||||
<Pressable
|
||||
style={styles.groupHeader}
|
||||
onPress={() =>
|
||||
setExpanded((prev) => ({
|
||||
...prev,
|
||||
[item.connectionId]: !prev[item.connectionId],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text style={styles.groupTitle}>
|
||||
{item.connectionDetails.username}@
|
||||
{item.connectionDetails.host}
|
||||
</Text>
|
||||
<Text style={styles.groupSubtitle}>
|
||||
Port {item.connectionDetails.port} • {item.shells.length}{' '}
|
||||
shell{item.shells.length === 1 ? '' : 's'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.groupChevron}>
|
||||
{expanded[item.connectionId] ? '▾' : '▸'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{expanded[item.connectionId] && (
|
||||
<View style={{ gap: 12 }}>
|
||||
{item.shells.map((sh) => (
|
||||
<ShellCard
|
||||
key={`${sh.connectionId}:${sh.channelId}`}
|
||||
shell={{ ...sh, connection: item }}
|
||||
onLongPress={() =>
|
||||
setActionTarget({
|
||||
connectionId: sh.connectionId as string,
|
||||
channelId: sh.channelId as number,
|
||||
})
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
ItemSeparatorComponent={() => <View style={{ height: 16 }} />}
|
||||
contentContainerStyle={{ paddingVertical: 16, paddingHorizontal: 16 }}
|
||||
style={{ flex: 1 }}
|
||||
<FlashList
|
||||
data={flatShells}
|
||||
keyExtractor={(item) => `${item.connectionId}:${item.channelId}`}
|
||||
renderItem={({ item }) => (
|
||||
<ShellCard
|
||||
shell={item}
|
||||
onLongPress={() =>
|
||||
setActionTarget({
|
||||
shell: item,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<ActionsSheet
|
||||
target={actionTarget}
|
||||
onClose={() => setActionTarget(null)}
|
||||
onCloseShell={() =>
|
||||
actionTarget &&
|
||||
handleCloseShell(actionTarget.connectionId, actionTarget.channelId)
|
||||
}
|
||||
onDisconnect={() =>
|
||||
actionTarget && handleDisconnect(actionTarget.connectionId)
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
ItemSeparatorComponent={() => <View style={{ height: 12 }} />}
|
||||
contentContainerStyle={{
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 16,
|
||||
}}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function GroupedView({
|
||||
connectionsWithShells,
|
||||
setActionTarget,
|
||||
}: {
|
||||
connectionsWithShells: ReturnType<
|
||||
typeof RnRussh.listSshConnectionsWithShells
|
||||
>;
|
||||
setActionTarget: (target: ActionTarget) => void;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const styles = React.useMemo(() => makeStyles(theme), [theme]);
|
||||
const [expanded, setExpanded] = React.useState<Record<string, boolean>>({});
|
||||
return (
|
||||
<FlashList
|
||||
data={connectionsWithShells}
|
||||
// estimatedItemSize={80}
|
||||
keyExtractor={(item) => item.connectionId}
|
||||
renderItem={({ item }) => (
|
||||
<View style={styles.groupContainer}>
|
||||
<Pressable
|
||||
style={styles.groupHeader}
|
||||
onPress={() =>
|
||||
setExpanded((prev) => ({
|
||||
...prev,
|
||||
[item.connectionId]: !prev[item.connectionId],
|
||||
}))
|
||||
}
|
||||
>
|
||||
<View>
|
||||
<Text style={styles.groupTitle}>
|
||||
{item.connectionDetails.username}@{item.connectionDetails.host}
|
||||
</Text>
|
||||
<Text style={styles.groupSubtitle}>
|
||||
Port {item.connectionDetails.port} • {item.shells.length} shell
|
||||
{item.shells.length === 1 ? '' : 's'}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.groupChevron}>
|
||||
{expanded[item.connectionId] ? '▾' : '▸'}
|
||||
</Text>
|
||||
</Pressable>
|
||||
{expanded[item.connectionId] && (
|
||||
<View style={{ gap: 12 }}>
|
||||
{item.shells.map((sh) => {
|
||||
const shellWithConnection = { ...sh, connection: item };
|
||||
return (
|
||||
<ShellCard
|
||||
key={`${sh.connectionId}:${sh.channelId}`}
|
||||
shell={shellWithConnection}
|
||||
onLongPress={() =>
|
||||
setActionTarget({
|
||||
shell: shellWithConnection,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
ItemSeparatorComponent={() => <View style={{ height: 16 }} />}
|
||||
contentContainerStyle={{ paddingVertical: 16, paddingHorizontal: 16 }}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -239,6 +241,7 @@ function EmptyState() {
|
||||
<Link href="/" style={styles.link}>
|
||||
Go to Hosts
|
||||
</Link>
|
||||
<TopBarToggle />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -291,7 +294,7 @@ function ActionsSheet({
|
||||
onCloseShell,
|
||||
onDisconnect,
|
||||
}: {
|
||||
target: null | { connectionId: string; channelId: number };
|
||||
target: null | ActionTarget;
|
||||
onClose: () => void;
|
||||
onCloseShell: () => void;
|
||||
onDisconnect: () => void;
|
||||
@@ -329,6 +332,37 @@ function ActionsSheet({
|
||||
);
|
||||
}
|
||||
|
||||
function TopBarToggle() {
|
||||
const theme = useTheme();
|
||||
const iconStyle = (isActive: boolean) => ({
|
||||
color: isActive ? theme.colors.textPrimary : theme.colors.muted,
|
||||
});
|
||||
const [shellListViewMode, setShellListViewMode] =
|
||||
preferences.shellListViewMode.useShellListViewModePref();
|
||||
return (
|
||||
<IconSegmentedControl
|
||||
values={[
|
||||
{
|
||||
child: ({ isActive }) => (
|
||||
<Ionicons name="list" size={18} style={iconStyle(isActive)} />
|
||||
),
|
||||
accessibilityLabel: 'Flat list',
|
||||
value: 'flat',
|
||||
},
|
||||
{
|
||||
child: ({ isActive }) => (
|
||||
<Ionicons name="git-branch" size={18} style={iconStyle(isActive)} />
|
||||
),
|
||||
accessibilityLabel: 'Grouped by connection',
|
||||
value: 'grouped',
|
||||
},
|
||||
]}
|
||||
value={shellListViewMode}
|
||||
onChange={setShellListViewMode}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function makeStyles(theme: AppTheme) {
|
||||
return StyleSheet.create({
|
||||
centerContent: {
|
||||
@@ -450,73 +484,3 @@ function makeStyles(theme: AppTheme) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function TopBarToggle({
|
||||
viewIndex,
|
||||
onChange,
|
||||
}: {
|
||||
viewIndex: number;
|
||||
onChange: (index: number) => void;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const styles = React.useMemo(
|
||||
() =>
|
||||
StyleSheet.create({
|
||||
container: {
|
||||
paddingHorizontal: 16,
|
||||
paddingTop: 12,
|
||||
paddingBottom: 8,
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
toggle: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: theme.colors.surface,
|
||||
borderWidth: 1,
|
||||
borderColor: theme.colors.border,
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
},
|
||||
segment: {
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
active: {
|
||||
backgroundColor: theme.colors.inputBackground,
|
||||
},
|
||||
iconActive: { color: theme.colors.textPrimary },
|
||||
iconInactive: { color: theme.colors.muted },
|
||||
}),
|
||||
[theme],
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<View style={styles.toggle}>
|
||||
<Pressable
|
||||
accessibilityLabel="Flat list"
|
||||
onPress={() => onChange(0)}
|
||||
style={[styles.segment, viewIndex === 0 && styles.active]}
|
||||
>
|
||||
<Ionicons
|
||||
name="list"
|
||||
size={18}
|
||||
style={viewIndex === 0 ? styles.iconActive : styles.iconInactive}
|
||||
/>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
accessibilityLabel="Grouped by connection"
|
||||
onPress={() => onChange(1)}
|
||||
style={[styles.segment, viewIndex === 1 && styles.active]}
|
||||
>
|
||||
<Ionicons
|
||||
name="git-branch"
|
||||
size={18}
|
||||
style={viewIndex === 1 ? styles.iconActive : styles.iconInactive}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
63
apps/mobile/src/components/icon-segmented-control.tsx
Normal file
63
apps/mobile/src/components/icon-segmented-control.tsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { Pressable, View } from 'react-native';
|
||||
import { useTheme } from '@/lib/theme';
|
||||
|
||||
export function IconSegmentedControl<T extends string>(props: {
|
||||
values: {
|
||||
child: (props: { isActive: boolean }) => React.ReactNode;
|
||||
accessibilityLabel: string;
|
||||
value: T;
|
||||
}[];
|
||||
value: T;
|
||||
onChange: (value: T) => void;
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
backgroundColor: theme.colors.surface,
|
||||
// borderWidth: 1,
|
||||
// borderColor: theme.colors.border,
|
||||
borderRadius: 10,
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{props.values.map((item) => (
|
||||
<Pressable
|
||||
key={item.value}
|
||||
accessibilityLabel={item.accessibilityLabel}
|
||||
onPress={() => {
|
||||
console.log('DEBUG onPress', {
|
||||
currentValue: props.value,
|
||||
itemValue: item.value,
|
||||
});
|
||||
if (props.values.length === 2) {
|
||||
const newValue = props.values.find(
|
||||
(v) => v.value !== props.value,
|
||||
)!.value;
|
||||
console.log('DEBUG newValue', newValue);
|
||||
props.onChange(newValue);
|
||||
} else {
|
||||
props.onChange(item.value);
|
||||
}
|
||||
}}
|
||||
style={[
|
||||
{
|
||||
paddingHorizontal: 10,
|
||||
paddingVertical: 6,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
props.value === item.value && {
|
||||
backgroundColor: theme.colors.inputBackground,
|
||||
},
|
||||
]}
|
||||
>
|
||||
{item.child({ isActive: props.value === item.value })}
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import React from 'react';
|
||||
import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
Pressable,
|
||||
} from 'react-native';
|
||||
import { secretsManager } from '@/lib/secrets-manager';
|
||||
|
||||
|
||||
50
apps/mobile/src/lib/preferences.tsx
Normal file
50
apps/mobile/src/lib/preferences.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { MMKV, useMMKVString } from 'react-native-mmkv';
|
||||
import { type ThemeName } from './theme';
|
||||
|
||||
const storage = new MMKV({ id: 'settings' });
|
||||
|
||||
type ShellListViewMode = 'flat' | 'grouped';
|
||||
|
||||
export const preferences = {
|
||||
theme: {
|
||||
_key: 'theme',
|
||||
_resolve: (rawTheme: string | undefined): ThemeName =>
|
||||
rawTheme === 'light' ? 'light' : 'dark',
|
||||
get: (): ThemeName =>
|
||||
preferences.theme._resolve(storage.getString(preferences.theme._key)),
|
||||
set: (name: ThemeName) => storage.set(preferences.theme._key, name),
|
||||
useThemePref: (): [ThemeName, (name: ThemeName) => void] => {
|
||||
const [theme, setTheme] = useMMKVString(preferences.theme._key);
|
||||
return [
|
||||
preferences.theme._resolve(theme),
|
||||
(name: ThemeName) => {
|
||||
setTheme(name);
|
||||
},
|
||||
] as const;
|
||||
},
|
||||
},
|
||||
shellListViewMode: {
|
||||
_key: 'shellListViewMode',
|
||||
_resolve: (rawMode: string | undefined): ShellListViewMode =>
|
||||
rawMode === 'grouped' ? 'grouped' : 'flat',
|
||||
get: (): ShellListViewMode =>
|
||||
preferences.shellListViewMode._resolve(
|
||||
storage.getString(preferences.shellListViewMode._key),
|
||||
),
|
||||
set: (mode: ShellListViewMode) =>
|
||||
storage.set(preferences.shellListViewMode._key, mode),
|
||||
|
||||
useShellListViewModePref: (): [
|
||||
ShellListViewMode,
|
||||
(mode: ShellListViewMode) => void,
|
||||
] => {
|
||||
const [mode, setMode] = useMMKVString(preferences.shellListViewMode._key);
|
||||
return [
|
||||
preferences.shellListViewMode._resolve(mode),
|
||||
(mode: 'flat' | 'grouped') => {
|
||||
setMode(mode);
|
||||
},
|
||||
] as const;
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -1,8 +1,13 @@
|
||||
import { RnRussh } from '@fressh/react-native-uniffi-russh';
|
||||
import {
|
||||
RnRussh,
|
||||
type SshConnection,
|
||||
type SshShellSession,
|
||||
} from '@fressh/react-native-uniffi-russh';
|
||||
import {
|
||||
queryOptions,
|
||||
useMutation,
|
||||
useQueryClient,
|
||||
type QueryClient,
|
||||
} from '@tanstack/react-query';
|
||||
import { useRouter } from 'expo-router';
|
||||
import { secretsManager, type ConnectionDetails } from './secrets-manager';
|
||||
@@ -74,3 +79,38 @@ export const listSshShellsQueryOptions = queryOptions({
|
||||
queryKey: ['ssh-shells'],
|
||||
queryFn: () => RnRussh.listSshConnectionsWithShells(),
|
||||
});
|
||||
|
||||
export type ShellWithConnection = SshShellSession & {
|
||||
connection: SshConnection;
|
||||
};
|
||||
|
||||
export const closeSshShellAndInvalidateQuery = async (params: {
|
||||
channelId: number;
|
||||
connectionId: string;
|
||||
queryClient: QueryClient;
|
||||
}) => {
|
||||
const currentActiveShells = RnRussh.listSshConnectionsWithShells();
|
||||
const connection = currentActiveShells.find(
|
||||
(c) => c.connectionId === params.connectionId,
|
||||
);
|
||||
if (!connection) throw new Error('Connection not found');
|
||||
const shell = connection.shells.find((s) => s.channelId === params.channelId);
|
||||
if (!shell) throw new Error('Shell not found');
|
||||
await shell.close();
|
||||
if (connection.shells.length <= 1) await connection.disconnect();
|
||||
await params.queryClient.invalidateQueries({
|
||||
queryKey: listSshShellsQueryOptions.queryKey,
|
||||
});
|
||||
};
|
||||
|
||||
export const disconnectSshConnectionAndInvalidateQuery = async (params: {
|
||||
connectionId: string;
|
||||
queryClient: QueryClient;
|
||||
}) => {
|
||||
const connection = RnRussh.getSshConnection(params.connectionId);
|
||||
if (!connection) throw new Error('Connection not found');
|
||||
await connection.disconnect();
|
||||
await params.queryClient.invalidateQueries({
|
||||
queryKey: listSshShellsQueryOptions.queryKey,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { MMKV } from 'react-native-mmkv';
|
||||
import { preferences } from './preferences';
|
||||
|
||||
export type AppTheme = {
|
||||
colors: {
|
||||
@@ -74,38 +74,23 @@ type ThemeContextValue = {
|
||||
theme: AppTheme;
|
||||
themeName: ThemeName;
|
||||
setThemeName: (name: ThemeName) => void;
|
||||
// Back-compat; not used externally but kept to avoid breaking imports
|
||||
setTheme: (theme: AppTheme) => void;
|
||||
};
|
||||
|
||||
const ThemeContext = React.createContext<ThemeContextValue | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const storage = new MMKV({ id: 'settings' });
|
||||
const THEME_KEY = 'theme';
|
||||
|
||||
export function ThemeProvider(props: { children: React.ReactNode }) {
|
||||
const [themeName, setThemeName] = React.useState<ThemeName>(() => {
|
||||
const stored = storage.getString(THEME_KEY);
|
||||
return stored === 'light' ? 'light' : 'dark';
|
||||
});
|
||||
|
||||
const [themeName, setThemeName] = preferences.theme.useThemePref();
|
||||
const theme = themes[themeName];
|
||||
|
||||
const handleSetThemeName = React.useCallback((name: ThemeName) => {
|
||||
setThemeName(name);
|
||||
storage.set(THEME_KEY, name);
|
||||
}, []);
|
||||
|
||||
const value = React.useMemo<ThemeContextValue>(
|
||||
() => ({
|
||||
theme,
|
||||
themeName,
|
||||
setThemeName: handleSetThemeName,
|
||||
setTheme: () => {},
|
||||
setThemeName,
|
||||
}),
|
||||
[theme, themeName, handleSetThemeName],
|
||||
[theme, themeName, setThemeName],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
78
pnpm-lock.yaml
generated
78
pnpm-lock.yaml
generated
@@ -75,7 +75,7 @@ importers:
|
||||
version: 4.1.0
|
||||
expo:
|
||||
specifier: 54.0.7
|
||||
version: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
version: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-clipboard:
|
||||
specifier: ~8.0.7
|
||||
version: 8.0.7(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
@@ -110,8 +110,8 @@ importers:
|
||||
specifier: ~8.0.8
|
||||
version: 8.0.8(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-router:
|
||||
specifier: 6.0.4
|
||||
version: 6.0.4(750b4158402c81723fe42a215de7942a)
|
||||
specifier: 6.0.5
|
||||
version: 6.0.5(750b4158402c81723fe42a215de7942a)
|
||||
expo-secure-store:
|
||||
specifier: ~15.0.7
|
||||
version: 15.0.7(expo@54.0.7)
|
||||
@@ -185,6 +185,9 @@ importers:
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
prettier-plugin-organize-imports:
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.0(prettier@3.6.2)(typescript@5.9.2)
|
||||
tsx:
|
||||
specifier: ^4.20.5
|
||||
version: 4.20.5
|
||||
@@ -4800,8 +4803,8 @@ packages:
|
||||
react: '*'
|
||||
react-native: '*'
|
||||
|
||||
expo-router@6.0.4:
|
||||
resolution: {integrity: sha512-RZRHAhUCCU6QNTnVhoy0PAJxbLy7OF78F4/4fwJna3cHGTtokPvJYUwz8kOZOOub/7l8jqgxnT7eKAk1dw9uXQ==}
|
||||
expo-router@6.0.5:
|
||||
resolution: {integrity: sha512-FK5y/55ppv54WjW7W7X4g5J3r+hiMKHukRYjyS6KI4i92qOWtVF42yssD/Ty90EpjKuZ8N1F72FJGdx9A1UQNA==}
|
||||
peerDependencies:
|
||||
'@expo/metro-runtime': ^6.1.2
|
||||
'@react-navigation/drawer': ^7.5.0
|
||||
@@ -9895,7 +9898,7 @@ snapshots:
|
||||
'@eslint/core': 0.15.2
|
||||
levn: 0.4.1
|
||||
|
||||
'@expo/cli@54.0.5(expo-router@6.0.4)(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))':
|
||||
'@expo/cli@54.0.5(expo-router@6.0.5)(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))':
|
||||
dependencies:
|
||||
'@0no-co/graphql.web': 1.2.0
|
||||
'@expo/code-signing-certificates': 0.0.5
|
||||
@@ -9930,7 +9933,7 @@ snapshots:
|
||||
connect: 3.7.0
|
||||
debug: 4.4.1
|
||||
env-editor: 0.4.2
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
freeport-async: 2.0.0
|
||||
getenv: 2.0.0
|
||||
glob: 10.4.5
|
||||
@@ -9962,7 +9965,7 @@ snapshots:
|
||||
wrap-ansi: 7.0.0
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
expo-router: 6.0.4(750b4158402c81723fe42a215de7942a)
|
||||
expo-router: 6.0.5(750b4158402c81723fe42a215de7942a)
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
@@ -10141,7 +10144,7 @@ snapshots:
|
||||
postcss: 8.4.49
|
||||
resolve-from: 5.0.0
|
||||
optionalDependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
@@ -10150,7 +10153,7 @@ snapshots:
|
||||
'@expo/metro-runtime@6.1.1(expo@54.0.7)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
anser: 1.4.10
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
pretty-format: 29.7.0
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
@@ -10213,7 +10216,7 @@ snapshots:
|
||||
'@expo/json-file': 10.0.7
|
||||
'@react-native/normalize-colors': 0.81.4
|
||||
debug: 4.4.1
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
resolve-from: 5.0.0
|
||||
semver: 7.7.2
|
||||
xml2js: 0.6.0
|
||||
@@ -12814,7 +12817,7 @@ snapshots:
|
||||
resolve-from: 5.0.0
|
||||
optionalDependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
@@ -14151,7 +14154,7 @@ snapshots:
|
||||
expo-asset@12.0.8(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@expo/image-utils': 0.8.7
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-constants: 18.0.8(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
@@ -14160,7 +14163,7 @@ snapshots:
|
||||
|
||||
expo-clipboard@8.0.7(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
|
||||
@@ -14168,7 +14171,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@expo/config': 12.0.8
|
||||
'@expo/env': 2.0.7
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -14176,11 +14179,11 @@ snapshots:
|
||||
expo-crypto@15.0.7(expo@54.0.7):
|
||||
dependencies:
|
||||
base64-js: 1.5.1
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
expo-dev-client@6.0.12(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-dev-launcher: 6.0.11(expo@54.0.7)
|
||||
expo-dev-menu: 7.0.11(expo@54.0.7)
|
||||
expo-dev-menu-interface: 2.0.0(expo@54.0.7)
|
||||
@@ -14191,7 +14194,7 @@ snapshots:
|
||||
|
||||
expo-dev-launcher@6.0.11(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-dev-menu: 7.0.11(expo@54.0.7)
|
||||
expo-manifests: 1.0.8(expo@54.0.7)
|
||||
transitivePeerDependencies:
|
||||
@@ -14199,42 +14202,42 @@ snapshots:
|
||||
|
||||
expo-dev-menu-interface@2.0.0(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
expo-dev-menu@7.0.11(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-dev-menu-interface: 2.0.0(expo@54.0.7)
|
||||
|
||||
expo-document-picker@14.0.7(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
expo-file-system@19.0.14(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
|
||||
expo-font@14.0.8(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
fontfaceobserver: 2.3.0
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
|
||||
expo-glass-effect@0.1.3(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
|
||||
expo-haptics@15.0.7(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
expo-image@3.0.8(expo@54.0.7)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
optionalDependencies:
|
||||
@@ -14244,7 +14247,7 @@ snapshots:
|
||||
|
||||
expo-keep-awake@15.0.7(expo@54.0.7)(react@19.1.0):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react: 19.1.0
|
||||
|
||||
expo-linking@8.0.8(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
@@ -14260,7 +14263,7 @@ snapshots:
|
||||
expo-manifests@1.0.8(expo@54.0.7):
|
||||
dependencies:
|
||||
'@expo/config': 12.0.8
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-json-utils: 0.15.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -14280,7 +14283,7 @@ snapshots:
|
||||
react: 19.1.0
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
|
||||
expo-router@6.0.4(750b4158402c81723fe42a215de7942a):
|
||||
expo-router@6.0.5(750b4158402c81723fe42a215de7942a):
|
||||
dependencies:
|
||||
'@expo/metro-runtime': 6.1.1(expo@54.0.7)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
'@expo/schema-utils': 0.1.7
|
||||
@@ -14293,7 +14296,7 @@ snapshots:
|
||||
client-only: 0.0.1
|
||||
debug: 4.4.1
|
||||
escape-string-regexp: 4.0.0
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo-constants: 18.0.8(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))
|
||||
expo-linking: 8.0.8(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
fast-deep-equal: 3.1.3
|
||||
@@ -14325,12 +14328,12 @@ snapshots:
|
||||
|
||||
expo-secure-store@15.0.7(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
expo-splash-screen@31.0.10(expo@54.0.7):
|
||||
dependencies:
|
||||
'@expo/prebuild-config': 54.0.3(expo@54.0.7)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -14342,7 +14345,7 @@ snapshots:
|
||||
|
||||
expo-symbols@1.0.7(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
sf-symbols-typescript: 2.1.0
|
||||
|
||||
@@ -14350,7 +14353,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@react-native/normalize-colors': 0.81.4
|
||||
debug: 4.4.1
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
react-native: 0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)
|
||||
optionalDependencies:
|
||||
react-native-web: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -14359,12 +14362,12 @@ snapshots:
|
||||
|
||||
expo-updates-interface@2.0.0(expo@54.0.7):
|
||||
dependencies:
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
expo: 54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
|
||||
expo@54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.4)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
expo@54.0.7(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.5)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@expo/cli': 54.0.5(expo-router@6.0.4)(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))
|
||||
'@expo/cli': 54.0.5(expo-router@6.0.5)(expo@54.0.7)(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))
|
||||
'@expo/config': 12.0.9
|
||||
'@expo/config-plugins': 54.0.1
|
||||
'@expo/devtools': 0.1.7(react-native@0.81.4(@babel/core@7.28.3)(@react-native-community/cli@20.0.2(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
|
||||
@@ -17045,7 +17048,6 @@ snapshots:
|
||||
dependencies:
|
||||
prettier: 3.6.2
|
||||
typescript: 5.9.2
|
||||
optional: true
|
||||
|
||||
prettier-plugin-tailwindcss@0.6.14(prettier-plugin-astro@0.14.1)(prettier-plugin-organize-imports@4.2.0(prettier@3.6.2)(typescript@5.9.2))(prettier@3.6.2):
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user