From eb5ac73990213b3b0dbb5ddc4fe8552a33eedaad Mon Sep 17 00:00:00 2001 From: ameer2468 <33054370+ameer2468@users.noreply.github.com> Date: Fri, 14 Jun 2024 17:02:26 +0300 Subject: [PATCH] cleanups and cloud desktop design improvements --- .../components/modal/ImportLibraryModal.tsx | 159 +++++------ .../src/navigation/tabs/SettingsStack.tsx | 2 +- .../settings/library/CloudSettings.tsx | 249 ------------------ .../library/CloudSettings/CloudSettings.tsx | 127 +++++++++ .../library/CloudSettings/Instance.tsx | 44 ++++ .../library/CloudSettings/Library.tsx | 66 +++++ .../settings/library/CloudSettings/Login.tsx | 40 +++ .../library/CloudSettings/ThisInstance.tsx | 54 ++++ .../screens/settings/library/SyncSettings.tsx | 96 ++++--- interface/app/$libraryId/debug/cloud.tsx | 221 ++++++++++++---- interface/util/hardware.ts | 4 + 11 files changed, 644 insertions(+), 418 deletions(-) delete mode 100644 apps/mobile/src/screens/settings/library/CloudSettings.tsx create mode 100644 apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx create mode 100644 apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx create mode 100644 apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx create mode 100644 apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx create mode 100644 apps/mobile/src/screens/settings/library/CloudSettings/ThisInstance.tsx diff --git a/apps/mobile/src/components/modal/ImportLibraryModal.tsx b/apps/mobile/src/components/modal/ImportLibraryModal.tsx index 323c843ed..0e47d20ad 100644 --- a/apps/mobile/src/components/modal/ImportLibraryModal.tsx +++ b/apps/mobile/src/components/modal/ImportLibraryModal.tsx @@ -1,5 +1,7 @@ import { BottomSheetFlatList } from '@gorhom/bottom-sheet'; import { NavigationProp, useNavigation } from '@react-navigation/native'; +import { forwardRef } from 'react'; +import { ActivityIndicator, Text, View } from 'react-native'; import { CloudLibrary, useBridgeMutation, @@ -7,14 +9,13 @@ import { useClientContext, useRspcContext } from '@sd/client'; -import { forwardRef } from 'react'; -import { Text, View } from 'react-native'; import { Modal, ModalRef } from '~/components/layout/Modal'; import { Button } from '~/components/primitive/Button'; import useForwardedRef from '~/hooks/useForwardedRef'; import { tw } from '~/lib/tailwind'; import { RootStackParamList } from '~/navigation'; import { currentLibraryStore } from '~/utils/nav'; + import Empty from '../layout/Empty'; import Fade from '../layout/Fade'; @@ -27,7 +28,7 @@ const ImportModalLibrary = forwardRef((_, ref) => { const cloudLibraries = useBridgeQuery(['cloud.library.list']); const cloudLibrariesData = cloudLibraries.data?.filter( (cloudLibrary) => !libraries.data?.find((l) => l.uuid === cloudLibrary.uuid) - ) + ); return ( ((_, ref) => { showCloseButton > - - } - ListEmptyComponent={ - + + + ) : ( + + } + ListEmptyComponent={ + + } + keyExtractor={(item) => item.uuid} + showsVerticalScrollIndicator={false} + renderItem={({ item }) => ( + + )} /> - } - keyExtractor={(item) => item.uuid} - showsVerticalScrollIndicator={false} - renderItem={({ item }) => ( - - )} - /> - - + + )} + ); }); - interface Props { - data: CloudLibrary - modalRef: React.RefObject - navigation: NavigationProp + data: CloudLibrary; + modalRef: React.RefObject; + navigation: NavigationProp; } -const CloudLibraryCard = ({data, modalRef, navigation}: Props) => { +const CloudLibraryCard = ({ data, modalRef, navigation }: Props) => { const rspc = useRspcContext().queryClient; const joinLibrary = useBridgeMutation(['cloud.library.join']); -return ( - - {data.name} - - -) -} + modalRef.current?.dismiss(); + }} + > + + {joinLibrary.isLoading && joinLibrary.variables === data.uuid + ? 'Joining...' + : 'Join'} + + + + ); +}; export default ImportModalLibrary; diff --git a/apps/mobile/src/navigation/tabs/SettingsStack.tsx b/apps/mobile/src/navigation/tabs/SettingsStack.tsx index 6cae2eda3..8fb152147 100644 --- a/apps/mobile/src/navigation/tabs/SettingsStack.tsx +++ b/apps/mobile/src/navigation/tabs/SettingsStack.tsx @@ -12,6 +12,7 @@ import PrivacySettingsScreen from '~/screens/settings/client/PrivacySettings'; import AboutScreen from '~/screens/settings/info/About'; import DebugScreen from '~/screens/settings/info/Debug'; import SupportScreen from '~/screens/settings/info/Support'; +import CloudSettings from '~/screens/settings/library/CloudSettings/CloudSettings'; import EditLocationSettingsScreen from '~/screens/settings/library/EditLocationSettings'; import LibraryGeneralSettingsScreen from '~/screens/settings/library/LibraryGeneralSettings'; import LocationSettingsScreen from '~/screens/settings/library/LocationSettings'; @@ -20,7 +21,6 @@ import SyncSettingsScreen from '~/screens/settings/library/SyncSettings'; import TagsSettingsScreen from '~/screens/settings/library/TagsSettings'; import SettingsScreen from '~/screens/settings/Settings'; -import CloudSettings from '~/screens/settings/library/CloudSettings'; import { TabScreenProps } from '../TabNavigator'; const Stack = createNativeStackNavigator(); diff --git a/apps/mobile/src/screens/settings/library/CloudSettings.tsx b/apps/mobile/src/screens/settings/library/CloudSettings.tsx deleted file mode 100644 index 7a79c5f7d..000000000 --- a/apps/mobile/src/screens/settings/library/CloudSettings.tsx +++ /dev/null @@ -1,249 +0,0 @@ -import { CloudInstance, useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client'; -import { CheckCircle } from 'phosphor-react-native'; -import { useMemo } from 'react'; -import { ActivityIndicator, FlatList, Text, View } from 'react-native'; -import Card from '~/components/layout/Card'; -import Empty from '~/components/layout/Empty'; -import ScreenContainer from '~/components/layout/ScreenContainer'; -import VirtualizedListWrapper from '~/components/layout/VirtualizedListWrapper'; -import { Button } from '~/components/primitive/Button'; -import { Divider } from '~/components/primitive/Divider'; -import { SettingsTitle } from '~/components/settings/SettingsContainer'; -import { styled, tw, twStyle } from '~/lib/tailwind'; -import { cancel, login, logout, useAuthStateSnapshot } from '~/stores/auth'; - -const InfoBox = styled(View, 'rounded-md border border-app bg-transparent p-2'); - -const CloudSettings = () => { - return ( - - - - ); -}; - -const AuthSensitiveChild = () => { - const authState = useAuthStateSnapshot(); - if (authState.status === 'loggedIn') return ; - if (authState.status === 'notLoggedIn' || authState.status === 'loggingIn') return ; - - return null; -}; - -const Authenticated = () => { - const { library } = useLibraryContext(); - const authState = useAuthStateSnapshot(); - - const cloudLibrary = useLibraryQuery(['cloud.library.get'], { retry: false }); - - const createLibrary = useLibraryMutation(['cloud.library.create']); - const syncLibrary = useLibraryMutation(['cloud.library.sync']); - - const thisInstance = useMemo(() => cloudLibrary.data?.instances.find( - (instance) => instance.uuid === library.instance_id - ), [cloudLibrary.data, library.instance_id]); - - const cloudInstances = useMemo(() => - cloudLibrary.data?.instances.filter( - (instance) => instance.uuid !== library.instance_id - ), [cloudLibrary.data, library.instance_id]); - - const isLibrarySynced = useMemo(() => - cloudLibrary.data?.instances.some( - (instance) => instance.uuid === library.instance_id - ),[cloudLibrary.data, library]); - - if (cloudLibrary.isLoading) { - return ( - - - - ); - } - - return ( - - {cloudLibrary.data ? ( - - - - Library - {authState.status === 'loggedIn' && ( - - )} - - - Name - - {cloudLibrary.data.name} - - - - {thisInstance && ( - - - This Instance - - - - Id - - {thisInstance.id} - - - - UUID - - {thisInstance.uuid} - - - - Public Key - - - {thisInstance.identity} - - - - - )} - - - - - {cloudInstances?.length} - - - Instances - - - - } - contentContainerStyle={twStyle(cloudInstances?.length === 0 && 'flex-row')} - showsHorizontalScrollIndicator={false} - ItemSeparatorComponent={() => } - renderItem={({ item }) => } - keyExtractor={(item) => item.id} - numColumns={(cloudInstances?.length ?? 0) > 1 ? 2 : 1} - {...(cloudInstances?.length ?? 0) > 1 ? {columnWrapperStyle: tw`w-full justify-between`} : {}} - /> - - - - ) : ( - - - - )} - - ); -}; - -interface Props { - data: CloudInstance; - length: number; -} - -const Instance = ({data, length}: Props) => { - return ( - 1 ? 'w-[49%]' : 'w-full', 'gap-4')}> - - Id - - {data.id} - - - - UUID - - {data.uuid} - - - - Public Key - - {data.identity} - - - - ); -}; - -const Login = () => { - const authState = useAuthStateSnapshot(); - const buttonText = { - notLoggedIn: 'Login', - loggingIn: 'Cancel', - } - return ( - - - - To access cloud related features, please login - - {(authState.status === 'notLoggedIn' || authState.status === 'loggingIn') && ( - - )} - - - ); -}; - -export default CloudSettings; diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx new file mode 100644 index 000000000..e26e7a7f5 --- /dev/null +++ b/apps/mobile/src/screens/settings/library/CloudSettings/CloudSettings.tsx @@ -0,0 +1,127 @@ +import { useMemo } from 'react'; +import { ActivityIndicator, FlatList, Text, View } from 'react-native'; +import { useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client'; +import Card from '~/components/layout/Card'; +import Empty from '~/components/layout/Empty'; +import ScreenContainer from '~/components/layout/ScreenContainer'; +import VirtualizedListWrapper from '~/components/layout/VirtualizedListWrapper'; +import { Button } from '~/components/primitive/Button'; +import { Divider } from '~/components/primitive/Divider'; +import { styled, tw, twStyle } from '~/lib/tailwind'; +import { useAuthStateSnapshot } from '~/stores/auth'; + +import Instance from './Instance'; +import Library from './Library'; +import Login from './Login'; +import ThisInstance from './ThisInstance'; + +export const InfoBox = styled(View, 'rounded-md border border-app bg-transparent p-2'); + +const CloudSettings = () => { + return ( + + + + ); +}; + +const AuthSensitiveChild = () => { + const authState = useAuthStateSnapshot(); + if (authState.status === 'loggedIn') return ; + if (authState.status === 'notLoggedIn' || authState.status === 'loggingIn') return ; + + return null; +}; + +const Authenticated = () => { + const { library } = useLibraryContext(); + const cloudLibrary = useLibraryQuery(['cloud.library.get'], { retry: false }); + const createLibrary = useLibraryMutation(['cloud.library.create']); + + const cloudInstances = useMemo( + () => + cloudLibrary.data?.instances.filter( + (instance) => instance.uuid !== library.instance_id + ), + [cloudLibrary.data, library.instance_id] + ); + + if (cloudLibrary.isLoading) { + return ( + + + + ); + } + + return ( + + {cloudLibrary.data ? ( + + + + + + + + + {cloudInstances?.length} + + + Instances + + + + + } + contentContainerStyle={twStyle( + cloudInstances?.length === 0 && 'flex-row' + )} + showsHorizontalScrollIndicator={false} + ItemSeparatorComponent={() => } + renderItem={({ item }) => ( + + )} + keyExtractor={(item) => item.id} + numColumns={(cloudInstances?.length ?? 0) > 1 ? 2 : 1} + {...((cloudInstances?.length ?? 0) > 1 + ? { columnWrapperStyle: tw`w-full justify-between` } + : {})} + /> + + + + ) : ( + + + + )} + + ); +}; + +export default CloudSettings; diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx new file mode 100644 index 000000000..6ae58e587 --- /dev/null +++ b/apps/mobile/src/screens/settings/library/CloudSettings/Instance.tsx @@ -0,0 +1,44 @@ +import { Text, View } from 'react-native'; +import { CloudInstance } from '@sd/client'; +import { SettingsTitle } from '~/components/settings/SettingsContainer'; +import { tw, twStyle } from '~/lib/tailwind'; + +import { InfoBox } from './CloudSettings'; + +interface Props { + data: CloudInstance; + length: number; +} + +const Instance = ({ data, length }: Props) => { + return ( + 1 ? 'w-[49%]' : 'w-full', 'gap-4')}> + + Id + + + {data.id} + + + + + UUID + + + {data.uuid} + + + + + Public Key + + + {data.identity} + + + + + ); +}; + +export default Instance; diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx new file mode 100644 index 000000000..965b5be27 --- /dev/null +++ b/apps/mobile/src/screens/settings/library/CloudSettings/Library.tsx @@ -0,0 +1,66 @@ +import { CheckCircle, XCircle } from 'phosphor-react-native'; +import { useMemo } from 'react'; +import { Text, View } from 'react-native'; +import { CloudLibrary, useLibraryContext, useLibraryMutation } from '@sd/client'; +import Card from '~/components/layout/Card'; +import { Button } from '~/components/primitive/Button'; +import { Divider } from '~/components/primitive/Divider'; +import { SettingsTitle } from '~/components/settings/SettingsContainer'; +import { tw } from '~/lib/tailwind'; +import { logout, useAuthStateSnapshot } from '~/stores/auth'; + +import { InfoBox } from './CloudSettings'; + +interface LibraryProps { + cloudLibrary?: CloudLibrary; +} + +const Library = ({ cloudLibrary }: LibraryProps) => { + const authState = useAuthStateSnapshot(); + const { library } = useLibraryContext(); + const syncLibrary = useLibraryMutation(['cloud.library.sync']); + const thisInstance = useMemo( + () => cloudLibrary?.instances.find((instance) => instance.uuid === library.instance_id), + [cloudLibrary, library.instance_id] + ); + + return ( + + + Library + {authState.status === 'loggedIn' && ( + + )} + + + Name + + {cloudLibrary?.name} + + + + ); +}; + +export default Library; diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx new file mode 100644 index 000000000..f993c968f --- /dev/null +++ b/apps/mobile/src/screens/settings/library/CloudSettings/Login.tsx @@ -0,0 +1,40 @@ +import { Text, View } from 'react-native'; +import Card from '~/components/layout/Card'; +import { Button } from '~/components/primitive/Button'; +import { tw } from '~/lib/tailwind'; +import { cancel, login, useAuthStateSnapshot } from '~/stores/auth'; + +const Login = () => { + const authState = useAuthStateSnapshot(); + const buttonText = { + notLoggedIn: 'Login', + loggingIn: 'Cancel' + }; + return ( + + + + To access cloud related features, please login + + {(authState.status === 'notLoggedIn' || authState.status === 'loggingIn') && ( + + )} + + + ); +}; + +export default Login; diff --git a/apps/mobile/src/screens/settings/library/CloudSettings/ThisInstance.tsx b/apps/mobile/src/screens/settings/library/CloudSettings/ThisInstance.tsx new file mode 100644 index 000000000..1a2ab3697 --- /dev/null +++ b/apps/mobile/src/screens/settings/library/CloudSettings/ThisInstance.tsx @@ -0,0 +1,54 @@ +import { useMemo } from 'react'; +import { Text, View } from 'react-native'; +import { CloudLibrary, useLibraryContext } from '@sd/client'; +import Card from '~/components/layout/Card'; +import { Divider } from '~/components/primitive/Divider'; +import { SettingsTitle } from '~/components/settings/SettingsContainer'; +import { tw } from '~/lib/tailwind'; + +import { InfoBox } from './CloudSettings'; + +interface ThisInstanceProps { + cloudLibrary?: CloudLibrary; +} + +const ThisInstance = ({ cloudLibrary }: ThisInstanceProps) => { + const { library } = useLibraryContext(); + const thisInstance = useMemo( + () => cloudLibrary?.instances.find((instance) => instance.uuid === library.instance_id), + [cloudLibrary, library.instance_id] + ); + + if (!thisInstance) return null; + + return ( + + + This Instance + + + + Id + + {thisInstance.id} + + + + UUID + + {thisInstance.uuid} + + + + Public Key + + + {thisInstance.identity} + + + + + ); +}; + +export default ThisInstance; diff --git a/apps/mobile/src/screens/settings/library/SyncSettings.tsx b/apps/mobile/src/screens/settings/library/SyncSettings.tsx index cd00ecc57..c2ac34dfa 100644 --- a/apps/mobile/src/screens/settings/library/SyncSettings.tsx +++ b/apps/mobile/src/screens/settings/library/SyncSettings.tsx @@ -1,14 +1,14 @@ import { inferSubscriptionResult } from '@oscartbeaumont-sd/rspc-client'; +import { MotiView } from 'moti'; +import { Circle } from 'phosphor-react-native'; +import React, { useEffect, useState } from 'react'; +import { Text, View } from 'react-native'; import { Procedures, useLibraryMutation, useLibraryQuery, useLibrarySubscription } from '@sd/client'; -import { MotiView } from 'moti'; -import { Circle } from 'phosphor-react-native'; -import React, { useEffect, useState } from 'react'; -import { Text, View } from 'react-native'; import Card from '~/components/layout/Card'; import ScreenContainer from '~/components/layout/ScreenContainer'; import { Button } from '~/components/primitive/Button'; @@ -21,7 +21,7 @@ const SyncSettingsScreen = ({ navigation }: SettingsStackScreenProps<'SyncSettin const [startBackfill, setStart] = useState(false); - useLibrarySubscription(['library.actors'], { onData: setData}); + useLibrarySubscription(['library.actors'], { onData: setData }); useEffect(() => { if (startBackfill === true) { @@ -34,58 +34,70 @@ const SyncSettingsScreen = ({ navigation }: SettingsStackScreenProps<'SyncSettin return ( {syncEnabled.data === false ? ( - + + + + + ) : ( {Object.keys(data).map((key) => { return ( - - - {key} - - {data[key] ? ( - - ) : ( - - )} + + + {key} + + {data[key] ? : } - ) - })} + ); + })} - )} - + )} + ); -} +}; export default SyncSettingsScreen; function OnlineIndicator({ online }: { online: boolean }) { const size = 6; return ( - - {online ? ( - - - + + {online ? ( + + + + + ) : ( + + )} - ) : ( - - )} - - ) + ); } function StartButton({ name }: { name: string }) { diff --git a/interface/app/$libraryId/debug/cloud.tsx b/interface/app/$libraryId/debug/cloud.tsx index aa1ba6d3b..db11dfe30 100644 --- a/interface/app/$libraryId/debug/cloud.tsx +++ b/interface/app/$libraryId/debug/cloud.tsx @@ -1,8 +1,20 @@ -import { auth, useLibraryContext, useLibraryMutation, useLibraryQuery } from '@sd/client'; -import { Button } from '@sd/ui'; +import { CheckCircle, XCircle } from '@phosphor-icons/react'; +import { Suspense, useMemo } from 'react'; +import { + auth, + CloudInstance, + CloudLibrary, + HardwareModel, + useLibraryContext, + useLibraryMutation, + useLibraryQuery +} from '@sd/client'; +import { Button, Card, Loader, tw } from '@sd/ui'; +import { Icon } from '~/components'; import { AuthRequiredOverlay } from '~/components/AuthRequiredOverlay'; import { LoginButton } from '~/components/LoginButton'; import { useRouteTitle } from '~/hooks'; +import { hardwareModelToIcon } from '~/util/hardware'; export const Component = () => { useRouteTitle('Cloud'); @@ -20,64 +32,41 @@ export const Component = () => { return
{authSensitiveChild()}
; }; +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]`; + function Authenticated() { const { library } = useLibraryContext(); - const cloudLibrary = useLibraryQuery(['cloud.library.get'], { suspense: true, retry: false }); - const createLibrary = useLibraryMutation(['cloud.library.create']); - const syncLibrary = useLibraryMutation(['cloud.library.sync']); - const thisInstance = cloudLibrary.data?.instances.find( - (instance) => instance.uuid === library.instance_id - ); + const thisInstance = useMemo(() => { + if (!cloudLibrary.data) return undefined; + return cloudLibrary.data.instances.find( + (instance) => instance.uuid === library.instance_id + ); + }, [cloudLibrary.data, library.instance_id]); return ( - <> + + + + } + > {cloudLibrary.data ? ( -
-
-

Library

-

Name: {cloudLibrary.data.name}

-
- - - - {thisInstance && ( -
-

This Instance

-

Id: {thisInstance.id}

-

UUID: {thisInstance.uuid}

-

Public Key: {thisInstance.identity}

-
- )} -
-

Instances

-
    - {cloudLibrary.data.instances - .filter((instance) => instance.uuid !== library.instance_id) - .map((instance) => ( -
  • -

    Id: {instance.id}

    -

    UUID: {instance.uuid}

    -

    Public Key: {instance.identity}

    -
  • - ))} -
-
+
+ + {thisInstance && } +
) : ( -
+
)} - + ); } + +const Instances = ({ instances }: { instances: CloudInstance[] }) => { + const { library } = useLibraryContext(); + const filteredInstances = instances.filter((instance) => instance.uuid !== library.instance_id); + return ( +
+
+

Instances

+ {filteredInstances.length} +
+
+ {filteredInstances.map((instance) => ( + +
+ +

+ {instance.metadata.name} +

+
+
+ +

+ Id:{' '} + {instance.id} +

+
+ +

+ UUID:{' '} + + {instance.uuid} + +

+
+ +

+ Public Key:{' '} + + {instance.identity} + +

+
+
+
+ ))} +
+
+ ); +}; + +interface LibraryProps { + cloudLibrary: CloudLibrary; + thisInstance: CloudInstance | undefined; +} + +const Library = ({ thisInstance, cloudLibrary }: LibraryProps) => { + const syncLibrary = useLibraryMutation(['cloud.library.sync']); + return ( +
+

Library

+ +

+ Name: {cloudLibrary.name} +

+ +
+
+ ); +}; + +interface ThisInstanceProps { + instance: CloudInstance; +} + +const ThisInstance = ({ instance }: ThisInstanceProps) => { + return ( +
+

This Instance

+ +
+ +

+ {instance.metadata.name} +

+
+
+ +

+ Id: {instance.id} +

+
+ +

+ UUID: {instance.uuid} +

+
+ +

+ Public Key:{' '} + {instance.identity} +

+
+
+
+
+ ); +}; diff --git a/interface/util/hardware.ts b/interface/util/hardware.ts index a4c37c20e..1d34821d2 100644 --- a/interface/util/hardware.ts +++ b/interface/util/hardware.ts @@ -8,6 +8,10 @@ export function hardwareModelToIcon(hardwareModel: HardwareModel) { return 'Laptop'; case 'MacStudio': return 'SilverBox'; + case 'IPhone': + return 'Mobile'; + case 'Android': + return 'Mobile-Android'; case 'MacMini': return 'MiniSilverBox'; case 'Other':