feat: Implemented listing

This commit is contained in:
Fándly Gergő 2022-07-02 12:33:50 +03:00
parent 7bf0d428b8
commit 0d1df64c7f
10 changed files with 365 additions and 31 deletions

View File

@ -10,14 +10,13 @@ import useLoggedIn from './hooks/useLoggedIn';
import SignUp from './screens/SignUp'; import SignUp from './screens/SignUp';
import Buckets from './screens/Buckets'; import Buckets from './screens/Buckets';
import Settings from './screens/Settings'; import Settings from './screens/Settings';
import DirectoryDetails from './screens/DirectoryDetails';
const Stack = createNativeStackNavigator(); const Stack = createNativeStackNavigator();
const Navigation = () => { const Navigation = () => {
const [isInitializing, user] = useLoggedIn(); const [isInitializing, user] = useLoggedIn();
console.log('[user]', user);
if (isInitializing) { if (isInitializing) {
return <Text>Loading...</Text>; return <Text>Loading...</Text>;
} }
@ -35,27 +34,30 @@ const Navigation = () => {
component={SignUp} component={SignUp}
options={{title: 'Sign Up'}} options={{title: 'Sign Up'}}
/> />
{!!user ? ( <Stack.Screen
<React.Fragment> name="Buckets"
<Stack.Screen component={Buckets}
name="Buckets" options={({navigation}) => ({
component={Buckets} title: 'S3 Buckets',
options={({navigation}) => ({ headerRight: () => (
title: 'S3 Buckets', <Pressable onPress={() => navigation.navigate('Settings')}>
headerRight: () => ( <Text style={{fontSize: 24, color: '#000000'}}></Text>
<Pressable onPress={() => navigation.navigate('Settings')}> </Pressable>
<Text style={{fontSize: 24, color: '#000000'}}></Text> ),
</Pressable> })}
), />
})} <Stack.Screen
/> name="DirectoryDetails"
<Stack.Screen component={DirectoryDetails}
name="Settings" options={({route}) => ({
component={Settings} title: route.params.dir,
options={{title: 'Settings'}} })}
/> />
</React.Fragment> <Stack.Screen
) : null} name="Settings"
component={Settings}
options={{title: 'Settings'}}
/>
</Stack.Navigator> </Stack.Navigator>
</NavigationContainer> </NavigationContainer>
); );

41
src/components/Bucket.js Normal file
View File

@ -0,0 +1,41 @@
import React, {useCallback} from 'react';
import {StyleSheet, Text, Pressable} from 'react-native';
import {useNavigation} from '@react-navigation/native';
const Bucket = ({bucket}) => {
const navigation = useNavigation();
const onPress = useCallback(() => {
navigation.navigate('DirectoryDetails', {bucket: bucket.Name, dir: '/'});
}, [navigation, bucket]);
return (
<Pressable onPress={onPress} style={styles.root}>
<Text style={styles.name}>{bucket.Name}</Text>
<Text style={styles.createdAt}>
Created at: {bucket.CreationDate.toISOString()}
</Text>
</Pressable>
);
};
const styles = StyleSheet.create({
root: {
margin: 4,
backgroundColor: '#CCCCCC',
borderRadius: 5,
padding: 12,
},
name: {
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
color: '#000000',
},
createdAt: {
fontSize: 12,
color: '#444444',
},
});
export default Bucket;

View File

@ -0,0 +1,39 @@
import React, {useCallback} from 'react';
import {StyleSheet, Text, Pressable} from 'react-native';
import {useNavigation} from '@react-navigation/native';
const Directory = ({bucket, dir}) => {
const navigation = useNavigation();
const onPress = useCallback(() => {
navigation.push('DirectoryDetails', {
bucket: bucket,
dir: dir.Prefix,
});
}, [navigation, bucket]);
const slashPos = dir.Prefix.lastIndexOf('/', dir.Prefix.length - 2);
return (
<Pressable onPress={onPress} style={styles.root}>
<Text style={styles.name}>
{slashPos > 0 ? dir.Prefix.slice(slashPos + 1) : dir.Prefix}
</Text>
</Pressable>
);
};
const styles = StyleSheet.create({
root: {
margin: 4,
backgroundColor: '#CCCCCC',
borderRadius: 5,
padding: 12,
},
name: {
fontSize: 14,
color: '#000000',
},
});
export default Directory;

39
src/components/File.js Normal file
View File

@ -0,0 +1,39 @@
import React, {useCallback} from 'react';
import {StyleSheet, Text, Pressable} from 'react-native';
import {useNavigation} from '@react-navigation/native';
const File = ({bucket, file}) => {
const navigation = useNavigation();
const onPress = useCallback(() => {
navigation.push('FileDetails', {
bucket: bucket,
file: file.Key,
});
}, [navigation, bucket]);
const slashPos = file.Key.lastIndexOf('/', file.Key.length - 2);
return (
<Pressable onPress={onPress} style={styles.root}>
<Text style={styles.name}>
{slashPos > 0 ? file.Key.slice(slashPos + 1) : file.Key}
</Text>
</Pressable>
);
};
const styles = StyleSheet.create({
root: {
margin: 4,
backgroundColor: '#CCCCCC',
borderRadius: 5,
padding: 12,
},
name: {
fontSize: 14,
color: '#000000',
},
});
export default File;

34
src/hooks/useS3.js Normal file
View File

@ -0,0 +1,34 @@
import {useEffect, useState} from 'react';
import S3 from 'aws-sdk/clients/s3';
import useSettings from './useSettings';
const useS3 = () => {
const [
loaded,
_save,
awsKeyId,
_setAwsKeyId,
awsSecretAccessKey,
_setAwsSecretAccessKey,
] = useSettings();
const [s3, setS3] = useState(null);
useEffect(() => {
if (loaded && !s3) {
setS3(
new S3({
apiVersion: '2006-03-01',
region: 'eu-west-1',
credentials: {
accessKeyId: awsKeyId,
secretAccessKey: awsSecretAccessKey,
},
}),
);
}
}, [loaded]);
return s3;
};
export default useS3;

60
src/hooks/useSettings.js Normal file
View File

@ -0,0 +1,60 @@
import {useState, useCallback, useEffect} from 'react';
import {ToastAndroid} from 'react-native';
import firestore from '@react-native-firebase/firestore';
import useLoggedIn from './useLoggedIn';
const useSettings = () => {
const [ready, user] = useLoggedIn();
const [awsKeyId, setAwsKeyId] = useState('');
const [awsSecretAccessKey, setAwsSecretAccessKey] = useState('');
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
if (!isLoaded && user) {
firestore()
.collection('user-settings')
.doc(user.uid)
.get()
.then(doc => {
if (doc) {
const data = doc.data();
setAwsKeyId(data['aws.keyId']);
setAwsSecretAccessKey(data['aws.secretAccessKey']);
}
})
.catch(() => {})
.finally(() => {
setIsLoaded(true);
});
}
}, [user]);
const save = useCallback(() => {
return firestore()
.collection('user-settings')
.doc(user.uid)
.set({
'aws.keyId': awsKeyId,
'aws.secretAccessKey': awsSecretAccessKey,
})
.then(() => {
ToastAndroid.show('Successfully saved settings', ToastAndroid.SHORT);
})
.catch(e => {
console.log(e);
ToastAndroid.show('Failed to save settings', ToastAndroid.SHORT);
throw e;
});
}, [user, awsKeyId, awsSecretAccessKey]);
return [
isLoaded,
save,
awsKeyId,
setAwsKeyId,
awsSecretAccessKey,
setAwsSecretAccessKey,
];
};
export default useSettings;

View File

@ -1,10 +1,32 @@
import React from 'react'; import React, {useState, useEffect} from 'react';
import {StyleSheet, View, Text, ScrollView} from 'react-native'; import {ScrollView, ToastAndroid} from 'react-native';
import useS3 from '../hooks/useS3';
import Bucket from '../components/Bucket';
const Buckets = () => { const Buckets = () => {
return ( const s3 = useS3();
<Text>Signed in!!!</Text> const [buckets, setBuckets] = useState([]);
)
}
export default Buckets; useEffect(() => {
if (s3 && !buckets.length) {
s3.listBuckets({}, (err, data) => {
if (err) {
ToastAndroid.show('Failed to fetch buckets', ToastAndroid.SHORT);
return;
}
setBuckets(data.Buckets);
});
}
}, [s3]);
return (
<ScrollView>
{buckets.map(bucket => (
<Bucket key={bucket.Name} bucket={bucket} />
))}
</ScrollView>
);
};
export default Buckets;

View File

@ -0,0 +1,58 @@
import React, {useState, useEffect} from 'react';
import {ScrollView, ToastAndroid} from 'react-native';
import {useRoute} from '@react-navigation/native';
import useS3 from '../hooks/useS3';
import Directory from '../components/Directory';
import File from '../components/File';
const DirectoryDetails = () => {
const route = useRoute();
const s3 = useS3();
const [contents, setContents] = useState({dirs: [], files: []});
useEffect(() => {
if (s3 && !contents.length) {
s3.listObjects(
{
Bucket: route.params.bucket,
Prefix: route.params.dir == '/' ? '' : route.params.dir,
Delimiter: '/',
MaxKeys: 5000,
},
(err, data) => {
if (err) {
console.log(err);
ToastAndroid.show(
'Failed to fetch directory contents',
ToastAndroid.SHORT,
);
return;
}
setContents({dirs: data.CommonPrefixes, files: data.Contents});
},
);
}
}, [s3]);
return (
<ScrollView>
{contents.dirs.map(dir => (
<Directory
key={'dir-' + dir.Prefix}
bucket={route.params.bucket}
dir={dir}
/>
))}
{contents.files.map(file => (
<File
key={'file-' + file.Key}
bucket={route.params.bucket}
file={file}
/>
))}
</ScrollView>
);
};
export default DirectoryDetails;

View File

@ -3,10 +3,19 @@ import {StyleSheet, View, Text, Button, TextInput} from 'react-native';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view'; import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
import useLogout from '../hooks/useLogout'; import useLogout from '../hooks/useLogout';
import {useNavigation} from '@react-navigation/native'; import {useNavigation} from '@react-navigation/native';
import useSettings from '../hooks/useSettings';
const Settings = () => { const Settings = () => {
const logOut = useLogout(); const logOut = useLogout();
const navigation = useNavigation(); const navigation = useNavigation();
const [
isLoaded,
save,
awsKeyId,
setAwsKeyId,
awsSecretAccessKey,
setAwsSecretAccessKey,
] = useSettings();
const onLogout = useCallback(() => { const onLogout = useCallback(() => {
logOut().then(() => { logOut().then(() => {
@ -14,9 +23,32 @@ const Settings = () => {
}); });
}, [logOut]); }, [logOut]);
const onSave = useCallback(() => {
save().then(() => navigation.goBack());
}, [save, navigation]);
if (!isLoaded) {
return <Text>Loading...</Text>;
}
return ( return (
<KeyboardAwareScrollView style={styles.root}> <KeyboardAwareScrollView style={styles.root}>
<Text style={styles.label}>AWS Key ID</Text>
<TextInput
style={styles.input}
value={awsKeyId}
onChangeText={setAwsKeyId}
/>
<Text style={styles.label}>AWS Secret Access Key</Text>
<TextInput
style={styles.input}
value={awsSecretAccessKey}
onChangeText={setAwsSecretAccessKey}
/>
<View style={styles.button}> <View style={styles.button}>
<Button onPress={onSave} title="Save" />
</View>
<View style={[styles.button, styles.logout]}>
<Button onPress={onLogout} title="Sign out" /> <Button onPress={onLogout} title="Sign out" />
</View> </View>
</KeyboardAwareScrollView> </KeyboardAwareScrollView>
@ -29,9 +61,14 @@ const styles = StyleSheet.create({
backgroundColor: '#FEFEFE', backgroundColor: '#FEFEFE',
padding: 10, padding: 10,
}, },
label: {
fontSize: 14,
color: '#333333',
},
input: { input: {
height: 40, height: 40,
margin: 12, margin: 12,
marginBottom: 24,
borderWidth: 1, borderWidth: 1,
padding: 10, padding: 10,
borderRadius: 3, borderRadius: 3,
@ -42,6 +79,9 @@ const styles = StyleSheet.create({
margin: 12, margin: 12,
marginBottom: 32, marginBottom: 32,
}, },
logout: {
marginTop: 64,
},
}); });
export default Settings; export default Settings;

View File

@ -29,7 +29,6 @@ const SignIn = () => {
logIn(email, pass) logIn(email, pass)
.then(() => { .then(() => {
console.log('asdasd');
navigation.reset({index: 0, routes: [{name: 'Buckets'}]}); navigation.reset({index: 0, routes: [{name: 'Buckets'}]});
}) })
.catch(() => {}); .catch(() => {});