[ENG-1798] Improve cloud sync ui (#2554)

improve cloud sync ui

cleanup

use better values

lint
This commit is contained in:
ameer2468 2024-06-18 19:15:11 +03:00 committed by GitHub
parent 0688406adc
commit 6e3559b7cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 90 additions and 58 deletions

View file

@ -1,7 +1,5 @@
import { BottomSheetFlatList } from '@gorhom/bottom-sheet'; import { BottomSheetFlatList } from '@gorhom/bottom-sheet';
import { NavigationProp, useNavigation } from '@react-navigation/native'; import { NavigationProp, useNavigation } from '@react-navigation/native';
import { forwardRef } from 'react';
import { ActivityIndicator, Text, View } from 'react-native';
import { import {
CloudLibrary, CloudLibrary,
useBridgeMutation, useBridgeMutation,
@ -9,6 +7,8 @@ import {
useClientContext, useClientContext,
useRspcContext useRspcContext
} from '@sd/client'; } from '@sd/client';
import { forwardRef } from 'react';
import { ActivityIndicator, Text, View } from 'react-native';
import { Modal, ModalRef } from '~/components/layout/Modal'; import { Modal, ModalRef } from '~/components/layout/Modal';
import { Button } from '~/components/primitive/Button'; import { Button } from '~/components/primitive/Button';
import useForwardedRef from '~/hooks/useForwardedRef'; import useForwardedRef from '~/hooks/useForwardedRef';
@ -36,6 +36,7 @@ const ImportModalLibrary = forwardRef<ModalRef, unknown>((_, ref) => {
snapPoints={cloudLibrariesData?.length !== 0 ? ['30', '50'] : ['30']} snapPoints={cloudLibrariesData?.length !== 0 ? ['30', '50'] : ['30']}
title="Join a Cloud Library" title="Join a Cloud Library"
showCloseButton showCloseButton
onDismiss={() => cloudLibraries.refetch()}
> >
<View style={tw`relative flex-1`}> <View style={tw`relative flex-1`}>
{cloudLibraries.isLoading ? ( {cloudLibraries.isLoading ? (
@ -59,7 +60,7 @@ const ImportModalLibrary = forwardRef<ModalRef, unknown>((_, ref) => {
icon="Drive" icon="Drive"
style={tw`mt-2 border-0`} style={tw`mt-2 border-0`}
iconSize={46} iconSize={46}
description="You don't have any cloud libraries" description="No cloud libraries available to join"
/> />
} }
keyExtractor={(item) => item.uuid} keyExtractor={(item) => item.uuid}

View file

@ -1,6 +1,6 @@
import { useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ActivityIndicator, FlatList, Text, View } from 'react-native'; import { ActivityIndicator, FlatList, Text, View } from 'react-native';
import { useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client';
import Card from '~/components/layout/Card'; import Card from '~/components/layout/Card';
import Empty from '~/components/layout/Empty'; import Empty from '~/components/layout/Empty';
import ScreenContainer from '~/components/layout/ScreenContainer'; import ScreenContainer from '~/components/layout/ScreenContainer';
@ -15,7 +15,7 @@ import Library from './Library';
import Login from './Login'; import Login from './Login';
import ThisInstance from './ThisInstance'; import ThisInstance from './ThisInstance';
export const InfoBox = styled(View, 'rounded-md border border-app bg-transparent p-2'); export const InfoBox = styled(View, 'rounded-md border gap-1 border-app bg-transparent p-2');
const CloudSettings = () => { const CloudSettings = () => {
return ( return (
@ -60,7 +60,6 @@ const Authenticated = () => {
<View style={tw`flex-col items-start gap-5`}> <View style={tw`flex-col items-start gap-5`}>
<Library cloudLibrary={cloudLibrary.data} /> <Library cloudLibrary={cloudLibrary.data} />
<ThisInstance cloudLibrary={cloudLibrary.data} /> <ThisInstance cloudLibrary={cloudLibrary.data} />
<Card style={tw`w-full`}> <Card style={tw`w-full`}>
<View style={tw`flex-row items-center gap-2`}> <View style={tw`flex-row items-center gap-2`}>
<View <View
@ -90,13 +89,10 @@ const Authenticated = () => {
showsHorizontalScrollIndicator={false} showsHorizontalScrollIndicator={false}
ItemSeparatorComponent={() => <View style={tw`h-2`} />} ItemSeparatorComponent={() => <View style={tw`h-2`} />}
renderItem={({ item }) => ( renderItem={({ item }) => (
<Instance data={item} length={cloudInstances?.length ?? 0} /> <Instance data={item} />
)} )}
keyExtractor={(item) => item.id} keyExtractor={(item) => item.id}
numColumns={(cloudInstances?.length ?? 0) > 1 ? 2 : 1} numColumns={1}
{...((cloudInstances?.length ?? 0) > 1
? { columnWrapperStyle: tw`w-full justify-between` }
: {})}
/> />
</VirtualizedListWrapper> </VirtualizedListWrapper>
</Card> </Card>

View file

@ -1,40 +1,55 @@
import { CloudInstance, HardwareModel } from '@sd/client';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { CloudInstance } from '@sd/client'; import { tw } from '~/lib/tailwind';
import { SettingsTitle } from '~/components/settings/SettingsContainer';
import { tw, twStyle } from '~/lib/tailwind';
import { Icon } from '~/components/icons/Icon';
import { hardwareModelToIcon } from '~/components/overview/Devices';
import { InfoBox } from './CloudSettings'; import { InfoBox } from './CloudSettings';
interface Props { interface Props {
data: CloudInstance; data: CloudInstance;
length: number;
} }
const Instance = ({ data, length }: Props) => { const Instance = ({ data }: Props) => {
return ( return (
<InfoBox style={twStyle(length > 1 ? 'w-[49%]' : 'w-full', 'gap-4')}> <InfoBox style={tw`w-full gap-2`}>
<View> <View>
<SettingsTitle style={tw`mb-2`}>Id</SettingsTitle> <View style={tw`mx-auto my-2`}>
<Icon
name={
hardwareModelToIcon(
data.metadata.device_model as HardwareModel) as any}
size={60}
/>
</View>
<Text numberOfLines={1} style={tw`mb-3 px-1 text-center text-sm font-medium font-semibold text-ink`}>{data.metadata.name}</Text>
<InfoBox> <InfoBox>
<Text numberOfLines={1} style={tw`text-ink-dull`}> <View style={tw`flex-row items-center gap-1`}>
<Text style={tw`text-sm font-medium text-ink`}>Id:</Text>
<Text numberOfLines={1} style={tw`max-w-[250px] text-ink-dull`}>
{data.id} {data.id}
</Text> </Text>
</View>
</InfoBox> </InfoBox>
</View> </View>
<View> <View>
<SettingsTitle style={tw`mb-2`}>UUID</SettingsTitle>
<InfoBox> <InfoBox>
<Text numberOfLines={1} style={tw`text-ink-dull`}> <View style={tw`flex-row items-center gap-1`}>
<Text style={tw`text-sm font-medium text-ink`}>UUID:</Text>
<Text numberOfLines={1} style={tw`max-w-[85%] text-ink-dull`}>
{data.uuid} {data.uuid}
</Text> </Text>
</View>
</InfoBox> </InfoBox>
</View> </View>
<View> <View>
<SettingsTitle style={tw`mb-2`}>Public Key</SettingsTitle>
<InfoBox> <InfoBox>
<Text numberOfLines={1} style={tw`text-ink-dull`}> <View style={tw`flex-row items-center gap-1`}>
<Text style={tw`text-sm font-medium text-ink`}>Public key:</Text>
<Text numberOfLines={1} style={tw`max-w-3/4 text-ink-dull`}>
{data.identity} {data.identity}
</Text> </Text>
</View>
</InfoBox> </InfoBox>
</View> </View>
</InfoBox> </InfoBox>

View file

@ -1,7 +1,7 @@
import { CloudLibrary, useLibraryContext, useLibraryMutation } from '@sd/client';
import { CheckCircle, XCircle } from 'phosphor-react-native'; import { CheckCircle, XCircle } from 'phosphor-react-native';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { CloudLibrary, useLibraryContext, useLibraryMutation } from '@sd/client';
import Card from '~/components/layout/Card'; import Card from '~/components/layout/Card';
import { Button } from '~/components/primitive/Button'; import { Button } from '~/components/primitive/Button';
import { Divider } from '~/components/primitive/Divider'; import { Divider } from '~/components/primitive/Divider';
@ -41,21 +41,21 @@ const Library = ({ cloudLibrary }: LibraryProps) => {
</InfoBox> </InfoBox>
<Button <Button
disabled={syncLibrary.isLoading || thisInstance !== undefined} disabled={syncLibrary.isLoading || thisInstance !== undefined}
variant={thisInstance ? 'dashed' : 'accent'} variant={thisInstance ? 'gray' : 'accent'}
onPress={() => syncLibrary.mutate(null)} onPress={() => syncLibrary.mutate(null)}
style={tw`mt-2 flex-row gap-1 py-2`} style={tw`mt-2 flex-row gap-1 py-2`}
> >
{thisInstance ? ( {thisInstance ? (
<CheckCircle size={13} weight="fill" color={tw.color('green-500')} /> <CheckCircle size={16} weight="fill" color={tw.color('green-400')} />
) : ( ) : (
<XCircle <XCircle
style={tw`rounded-full`} style={tw`rounded-full`}
size={13} size={16}
weight="fill" weight="fill"
color={tw.color('red-500')} color={tw.color('red-500')}
/> />
)} )}
<Text style={tw`text-xs font-semibold text-ink`}> <Text style={tw`text-sm font-semibold text-ink`}>
{thisInstance !== undefined ? 'Library synced' : 'Library not synced'} {thisInstance !== undefined ? 'Library synced' : 'Library not synced'}
</Text> </Text>
</Button> </Button>

View file

@ -1,11 +1,12 @@
import { CloudLibrary, HardwareModel, useLibraryContext } from '@sd/client';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { Text, View } from 'react-native'; import { Text, View } from 'react-native';
import { CloudLibrary, useLibraryContext } from '@sd/client';
import Card from '~/components/layout/Card'; import Card from '~/components/layout/Card';
import { Divider } from '~/components/primitive/Divider'; import { Divider } from '~/components/primitive/Divider';
import { SettingsTitle } from '~/components/settings/SettingsContainer';
import { tw } from '~/lib/tailwind'; import { tw } from '~/lib/tailwind';
import { Icon } from '~/components/icons/Icon';
import { hardwareModelToIcon } from '~/components/overview/Devices';
import { InfoBox } from './CloudSettings'; import { InfoBox } from './CloudSettings';
interface ThisInstanceProps { interface ThisInstanceProps {
@ -22,29 +23,44 @@ const ThisInstance = ({ cloudLibrary }: ThisInstanceProps) => {
if (!thisInstance) return null; if (!thisInstance) return null;
return ( return (
<Card style={tw`w-full gap-4`}> <Card style={tw`w-full gap-2`}>
<View> <View>
<Text style={tw`mb-1 font-semibold text-ink`}>This Instance</Text> <Text style={tw`mb-1 font-semibold text-ink`}>This Instance</Text>
<Divider /> <Divider />
</View> </View>
<View style={tw`mx-auto my-2 items-center`}>
<Icon
name={
hardwareModelToIcon(
thisInstance.metadata.device_model as HardwareModel) as any}
size={60}
/>
<Text numberOfLines={1} style={tw`px-1 font-semibold text-ink`}>{thisInstance.metadata.name}</Text>
</View>
<View> <View>
<SettingsTitle style={tw`mb-2 text-ink`}>Id</SettingsTitle>
<InfoBox> <InfoBox>
<Text style={tw`text-ink-dull`}>{thisInstance.id}</Text> <View style={tw`flex-row items-center gap-1`}>
<Text style={tw`text-sm font-medium text-ink`}>Id:</Text>
<Text style={tw`max-w-[250px] text-ink-dull`}>{thisInstance.id}</Text>
</View>
</InfoBox> </InfoBox>
</View> </View>
<View> <View>
<SettingsTitle style={tw`mb-2`}>UUID</SettingsTitle>
<InfoBox> <InfoBox>
<Text style={tw`text-ink-dull`}>{thisInstance.uuid}</Text> <View style={tw`flex-row items-center gap-1`}>
<Text style={tw`text-sm font-medium text-ink`}>UUID:</Text>
<Text numberOfLines={1} style={tw`max-w-[85%] text-ink-dull`}>{thisInstance.uuid}</Text>
</View>
</InfoBox> </InfoBox>
</View> </View>
<View> <View>
<SettingsTitle style={tw`mb-2`}>Public Key</SettingsTitle>
<InfoBox> <InfoBox>
<Text numberOfLines={1} style={tw`text-ink-dull`}> <View style={tw`flex-row items-center gap-1`}>
<Text style={tw`text-sm font-medium text-ink`}>Publc Key:</Text>
<Text numberOfLines={1} style={tw`max-w-3/4 text-ink-dull`}>
{thisInstance.identity} {thisInstance.identity}
</Text> </Text>
</View>
</InfoBox> </InfoBox>
</View> </View>
</Card> </Card>

View file

@ -1,15 +1,15 @@
import { CheckCircle, XCircle } from '@phosphor-icons/react'; import { CheckCircle, XCircle } from '@phosphor-icons/react';
import { Suspense, useMemo } from 'react';
import { import {
auth,
CloudInstance, CloudInstance,
CloudLibrary, CloudLibrary,
HardwareModel, HardwareModel,
auth,
useLibraryContext, useLibraryContext,
useLibraryMutation, useLibraryMutation,
useLibraryQuery useLibraryQuery
} from '@sd/client'; } from '@sd/client';
import { Button, Card, Loader, tw } from '@sd/ui'; import { Button, Card, Loader, tw } from '@sd/ui';
import { Suspense, useMemo } from 'react';
import { Icon } from '~/components'; import { Icon } from '~/components';
import { AuthRequiredOverlay } from '~/components/AuthRequiredOverlay'; import { AuthRequiredOverlay } from '~/components/AuthRequiredOverlay';
import { LoginButton } from '~/components/LoginButton'; import { LoginButton } from '~/components/LoginButton';
@ -25,7 +25,7 @@ export const Component = () => {
if (authState.status === 'loggedIn') return <Authenticated />; if (authState.status === 'loggedIn') return <Authenticated />;
if (authState.status === 'notLoggedIn' || authState.status === 'loggingIn') if (authState.status === 'notLoggedIn' || authState.status === 'loggingIn')
return ( return (
<div className="flex items-center justify-center size-full"> <div className="flex size-full items-center justify-center">
<Card className="flex flex-col gap-4 !p-6"> <Card className="flex flex-col gap-4 !p-6">
<p>To access cloud related features, please login</p> <p>To access cloud related features, please login</p>
<LoginButton /> <LoginButton />
@ -36,12 +36,13 @@ export const Component = () => {
return null; return null;
}; };
return <div className="flex flex-col items-start p-4 size-full">{authSensitiveChild()}</div>; return <div className="flex size-full flex-col items-start p-4">{authSensitiveChild()}</div>;
}; };
const DataBox = tw.div`max-w-[300px] rounded-md border border-app-line/50 bg-app-lightBox/20 p-2`; const DataBox = tw.div`max-w-[300px] rounded-md border border-app-line/50 bg-app-lightBox/20 p-2`;
const Count = tw.div`min-w-[20px] flex h-[20px] px-1 items-center justify-center rounded-full border border-app-button/40 text-[9px]`; const Count = tw.div`min-w-[20px] flex h-[20px] px-1 items-center justify-center rounded-full border border-app-button/40 text-[9px]`;
// million-ignore
function Authenticated() { function Authenticated() {
const { library } = useLibraryContext(); const { library } = useLibraryContext();
const cloudLibrary = useLibraryQuery(['cloud.library.get'], { suspense: true, retry: false }); const cloudLibrary = useLibraryQuery(['cloud.library.get'], { suspense: true, retry: false });
@ -58,7 +59,7 @@ function Authenticated() {
return ( return (
<Suspense <Suspense
fallback={ fallback={
<div className="flex items-center justify-center size-full"> <div className="flex size-full items-center justify-center">
<Loader /> <Loader />
</div> </div>
} }
@ -70,7 +71,7 @@ function Authenticated() {
<Instances instances={cloudLibrary.data.instances} /> <Instances instances={cloudLibrary.data.instances} />
</div> </div>
) : ( ) : (
<div className="relative flex flex-col items-center justify-center size-full"> <div className="relative flex size-full flex-col items-center justify-center">
<AuthRequiredOverlay /> <AuthRequiredOverlay />
<Button <Button
disabled={createLibrary.isLoading} disabled={createLibrary.isLoading}
@ -89,20 +90,21 @@ function Authenticated() {
); );
} }
// million-ignore
const Instances = ({ instances }: { instances: CloudInstance[] }) => { const Instances = ({ instances }: { instances: CloudInstance[] }) => {
const { library } = useLibraryContext(); const { library } = useLibraryContext();
const filteredInstances = instances.filter((instance) => instance.uuid !== library.instance_id); const filteredInstances = instances.filter((instance) => instance.uuid !== library.instance_id);
return ( return (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<div className="flex flex-row items-center gap-3"> <div className="flex flex-row items-center gap-3">
<p className="font-bold text-medium">Instances</p> <p className="text-medium font-bold">Instances</p>
<Count>{filteredInstances.length}</Count> <Count>{filteredInstances.length}</Count>
</div> </div>
<div className="flex flex-row flex-wrap gap-2"> <div className="flex flex-row flex-wrap gap-2">
{filteredInstances.map((instance) => ( {filteredInstances.map((instance) => (
<Card <Card
key={instance.id} key={instance.id}
className="flex-row items-center gap-8 bg-app-box/50 !p-5" className="flex-col items-center gap-4 bg-app-box/50 !p-5"
> >
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<Icon <Icon
@ -113,19 +115,19 @@ const Instances = ({ instances }: { instances: CloudInstance[] }) => {
} }
size={70} size={70}
/> />
<p className="max-w-[160px] truncate text-xs font-medium"> <p className="max-w-[250px] truncate text-xs font-medium">
{instance.metadata.name} {instance.metadata.name}
</p> </p>
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<DataBox> <DataBox>
<p className="text-xs font-medium truncate"> <p className="truncate text-xs font-medium">
Id:{' '} Id:{' '}
<span className="font-normal text-ink-dull">{instance.id}</span> <span className="font-normal text-ink-dull">{instance.id}</span>
</p> </p>
</DataBox> </DataBox>
<DataBox> <DataBox>
<p className="text-xs font-medium truncate"> <p className="truncate text-xs font-medium">
UUID:{' '} UUID:{' '}
<span className="font-normal text-ink-dull"> <span className="font-normal text-ink-dull">
{instance.uuid} {instance.uuid}
@ -133,7 +135,7 @@ const Instances = ({ instances }: { instances: CloudInstance[] }) => {
</p> </p>
</DataBox> </DataBox>
<DataBox> <DataBox>
<p className="text-xs font-medium truncate"> <p className="truncate text-xs font-medium">
Public Key:{' '} Public Key:{' '}
<span className="font-normal text-ink-dull"> <span className="font-normal text-ink-dull">
{instance.identity} {instance.identity}
@ -153,12 +155,13 @@ interface LibraryProps {
thisInstance: CloudInstance | undefined; thisInstance: CloudInstance | undefined;
} }
// million-ignore
const Library = ({ thisInstance, cloudLibrary }: LibraryProps) => { const Library = ({ thisInstance, cloudLibrary }: LibraryProps) => {
const syncLibrary = useLibraryMutation(['cloud.library.sync']); const syncLibrary = useLibraryMutation(['cloud.library.sync']);
return ( return (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<p className="font-bold text-medium">Library</p> <p className="text-medium font-bold">Library</p>
<Card className="flex-row items-center gap-10 !px-2"> <Card className="flex-row items-center gap-6 !px-2">
<p className="font-medium"> <p className="font-medium">
Name: <span className="font-normal text-ink-dull">{cloudLibrary.name}</span> Name: <span className="font-normal text-ink-dull">{cloudLibrary.name}</span>
</p> </p>
@ -173,7 +176,7 @@ const Library = ({ thisInstance, cloudLibrary }: LibraryProps) => {
) : ( ) : (
<CheckCircle weight="fill" size={15} className="text-green-400" /> <CheckCircle weight="fill" size={15} className="text-green-400" />
)} )}
{thisInstance === undefined ? 'Sync Library' : 'Library is synced'} {thisInstance === undefined ? 'Sync Library' : 'Library synced'}
</Button> </Button>
</Card> </Card>
</div> </div>
@ -184,11 +187,12 @@ interface ThisInstanceProps {
instance: CloudInstance; instance: CloudInstance;
} }
// million-ignore
const ThisInstance = ({ instance }: ThisInstanceProps) => { const ThisInstance = ({ instance }: ThisInstanceProps) => {
return ( return (
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<p className="font-bold text-medium">This Instance</p> <p className="text-medium font-bold">This Instance</p>
<Card className="flex-row items-center gap-8 bg-app-box/50 !p-5"> <Card className="flex-col items-center gap-4 bg-app-box/50 !p-5">
<div className="flex flex-col items-center gap-2"> <div className="flex flex-col items-center gap-2">
<Icon <Icon
name={ name={
@ -204,17 +208,17 @@ const ThisInstance = ({ instance }: ThisInstanceProps) => {
</div> </div>
<div className="flex flex-col gap-1.5"> <div className="flex flex-col gap-1.5">
<DataBox> <DataBox>
<p className="text-xs font-medium truncate"> <p className="truncate text-xs font-medium">
Id: <span className="font-normal text-ink-dull">{instance.id}</span> Id: <span className="font-normal text-ink-dull">{instance.id}</span>
</p> </p>
</DataBox> </DataBox>
<DataBox> <DataBox>
<p className="text-xs font-medium truncate"> <p className="truncate text-xs font-medium">
UUID: <span className="font-normal text-ink-dull">{instance.uuid}</span> UUID: <span className="font-normal text-ink-dull">{instance.uuid}</span>
</p> </p>
</DataBox> </DataBox>
<DataBox> <DataBox>
<p className="text-xs font-medium truncate"> <p className="truncate text-xs font-medium">
Public Key:{' '} Public Key:{' '}
<span className="font-normal text-ink-dull">{instance.identity}</span> <span className="font-normal text-ink-dull">{instance.identity}</span>
</p> </p>

View file

@ -27,7 +27,7 @@ export function JoinLibrary() {
<div className="mt-2"> <div className="mt-2">
<span>Cloud Libraries</span> <span>Cloud Libraries</span>
<ul className="relative flex flex-col w-48 h-32 p-2 border rounded border-app-frame"> <ul className="relative flex h-32 w-48 flex-col rounded border border-app-frame p-2">
<CloudLibraries /> <CloudLibraries />
<AuthRequiredOverlay /> <AuthRequiredOverlay />
</ul> </ul>