diff --git a/src/app/browser/components/icons/util.ts b/src/app/browser/components/icons/util.ts new file mode 100644 index 0000000..4f00ee9 --- /dev/null +++ b/src/app/browser/components/icons/util.ts @@ -0,0 +1,7 @@ +import React from 'react'; + +export const svg_styles: React.CSSProperties = { + height: '1em', + width: 'auto', + fill: 'currentColor', +}; diff --git a/src/app/browser/components/icons/warning.tsx b/src/app/browser/components/icons/warning.tsx new file mode 100644 index 0000000..d1a2e15 --- /dev/null +++ b/src/app/browser/components/icons/warning.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import { Platform, Text } from 'react-native'; +import { svg_styles } from './util.js'; + +const IconWeb = React.memo(() => + ionicons-v5-r +); + +export default Platform.OS === 'web' ? IconWeb : React.memo(() => null); diff --git a/src/app/browser/main/event.tsx b/src/app/browser/main/event.tsx index e334ba4..f7c2727 100644 --- a/src/app/browser/main/event.tsx +++ b/src/app/browser/main/event.tsx @@ -11,6 +11,7 @@ export default function Event(props: { user: User; event: ActiveEvent; loading?: boolean; + error?: Error; }) { const theme = useColourScheme() === 'light' ? light : dark; const accent_colour = useAccentColour(); @@ -18,7 +19,7 @@ export default function Event(props: { const event_members = props.event.members.filter(m => m.isPlaying).length; const voip_members = props.event.members.filter(m => m.isJoinedVoip).length; - return
+ return
diff --git a/src/app/browser/main/friends.tsx b/src/app/browser/main/friends.tsx index 17ffbc9..ae84e91 100644 --- a/src/app/browser/main/friends.tsx +++ b/src/app/browser/main/friends.tsx @@ -10,6 +10,7 @@ export default function Friends(props: { user: User; friends: Friend[]; loading?: boolean; + error?: Error; }) { const theme = useColourScheme() === 'light' ? light : dark; @@ -23,7 +24,7 @@ export default function Friends(props: { onContextMenu={onFriendCodeContextMenu} >SW-{props.user.nso!.nsoAccount.user.links.friendCode.id}; - return
+ return
{props.friends.length ? {props.friends.map(f => )} diff --git a/src/app/browser/main/main.tsx b/src/app/browser/main/main.tsx index 547ed1d..d97b382 100644 --- a/src/app/browser/main/main.tsx +++ b/src/app/browser/main/main.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect } from 'react'; -import { ActivityIndicator, StyleSheet, Text, View } from 'react-native'; +import { ActivityIndicator, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import ipc, { events } from '../ipc.js'; import { RequestState, useAccentColour, useAsync, useColourScheme, useEventListener, User } from '../util.js'; import Friends from './friends.js'; @@ -9,20 +9,22 @@ import Section from './section.js'; import { TEXT_COLOUR_DARK, TEXT_COLOUR_LIGHT } from '../constants.js'; import SetupDiscordPresence from './discord-setup.js'; import { Button } from '../components/index.js'; +import { hrlist } from '../../../util/misc.js'; export default function Main(props: { user: User; autoRefresh?: number; }) { + const theme = useColourScheme() === 'light' ? light : dark; const accent_colour = useAccentColour(); - const [announcements, , announcements_state] = useAsync(useCallback(() => props.user.nsotoken ? + const [announcements, announcements_error, announcements_state] = useAsync(useCallback(() => props.user.nsotoken ? ipc.getNsoAnnouncements(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken])); - const [friends, , friends_state, forceRefreshFriends] = useAsync(useCallback(() => props.user.nsotoken ? + const [friends, friends_error, friends_state, forceRefreshFriends] = useAsync(useCallback(() => props.user.nsotoken ? ipc.getNsoFriends(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken])); - const [webservices, , webservices_state, forceRefreshWebServices] = useAsync(useCallback(() => props.user.nsotoken ? + const [webservices, webservices_error, webservices_state, forceRefreshWebServices] = useAsync(useCallback(() => props.user.nsotoken ? ipc.getNsoWebServices(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken])); - const [active_event, , active_event_state, forceRefreshActiveEvent] = useAsync(useCallback(() => props.user.nsotoken ? + const [active_event, active_event_error, active_event_state, forceRefreshActiveEvent] = useAsync(useCallback(() => props.user.nsotoken ? ipc.getNsoActiveEvent(props.user.nsotoken) : Promise.resolve(null), [ipc, props.user.nsotoken])); const loading = announcements_state === RequestState.LOADING || @@ -41,10 +43,37 @@ export default function Main(props: { useEventListener(events, 'window:refresh', refresh, []); - if (loading && (!announcements || !friends || !webservices || !active_event)) { - return - - ; + const showErrorDetails = useCallback(() => { + if (friends_error) alert(friends_error.stack ?? friends_error.message); + if (webservices_error) alert(webservices_error.stack ?? webservices_error.message); + if (active_event_error) alert(active_event_error.stack ?? active_event_error.message); + }, [friends_error, webservices_error, active_event_error]); + + if (!announcements || !friends || !webservices || !active_event) { + if (loading) { + return + + ; + } + + if (friends_error || webservices_error || active_event_error) { + const errors = []; + if (friends_error) errors.push('friends'); + if (webservices_error) errors.push('game-specific services'); + if (active_event_error) errors.push('voice chat'); + const errors_text = hrlist(errors); + + return + Error loading data + An error occured while loading {errors_text} data. + +