Chore: Migrate NewServerView to Typescript (#3431)

* Chore: Migrate NewServerView to Typescript

* fix one alert lgtm

* Item.tsx

* export interface and try to rewrite write instead action

* minor tweak

* minor tweak

* refactor: change the type of username to connectServer

* refactor scaling

* minor tweak

Co-authored-by: AlexAlexandre <alexalexandrejr@gmail.com>
This commit is contained in:
Reinaldo Neto 2021-10-20 15:04:15 -03:00 committed by GitHub
parent 744893fa31
commit 27db881962
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 116 additions and 91 deletions

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import { ScrollView, StyleSheet, View } from 'react-native'; import { ScrollView, ScrollViewProps, StyleSheet, View } from 'react-native';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import sharedStyles from '../views/Styles'; import sharedStyles from '../views/Styles';
@ -10,10 +10,10 @@ import AppVersion from './AppVersion';
import { isTablet } from '../utils/deviceInfo'; import { isTablet } from '../utils/deviceInfo';
import SafeAreaView from './SafeAreaView'; import SafeAreaView from './SafeAreaView';
interface IFormContainer { interface IFormContainer extends ScrollViewProps {
theme: string; theme: string;
testID: string; testID: string;
children: JSX.Element; children: React.ReactNode;
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({

View File

@ -1,11 +0,0 @@
import { isTablet } from './deviceInfo';
const guidelineBaseWidth = isTablet ? 600 : 375;
const guidelineBaseHeight = isTablet ? 800 : 667;
// TODO: we need to refactor this
const scale = (size, width) => (width / guidelineBaseWidth) * size;
const verticalScale = (size, height) => (height / guidelineBaseHeight) * size;
const moderateScale = (size, factor = 0.5, width) => size + (scale(size, width) - size) * factor;
export { scale, verticalScale, moderateScale };

16
app/utils/scaling.ts Normal file
View File

@ -0,0 +1,16 @@
import { isTablet } from './deviceInfo';
const guidelineBaseWidth = isTablet ? 600 : 375;
const guidelineBaseHeight = isTablet ? 800 : 667;
function scale({ size, width }: { size: number; width: number }): number {
return (width / guidelineBaseWidth) * size;
}
function verticalScale({ size, height }: { size: number; height: number }): number {
return (height / guidelineBaseHeight) * size;
}
function moderateScale({ size, factor = 0.5, width }: { size: number; factor?: number; width: number }): number {
return size + (scale({ size, width }) - size) * factor;
}
export { scale, verticalScale, moderateScale };

View File

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { StyleSheet, Text, View } from 'react-native'; import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import { BorderlessButton } from 'react-native-gesture-handler'; import { BorderlessButton } from 'react-native-gesture-handler';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons'; import { CustomIcon } from '../../../lib/Icons';
import sharedStyles from '../../Styles'; import sharedStyles from '../../Styles';
import Touch from '../../../utils/touch'; import Touch from '../../../utils/touch';
import { IServer } from '../index';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -27,13 +27,20 @@ const styles = StyleSheet.create({
} }
}); });
const Item = ({ item, theme, onPress, onDelete }) => ( interface IItem {
item: IServer;
theme: string;
onPress(url: string): void;
onDelete(item: IServer): void;
}
const Item = ({ item, theme, onPress, onDelete }: IItem): JSX.Element => (
<Touch style={styles.container} onPress={() => onPress(item.url)} theme={theme} testID={`server-history-${item.url}`}> <Touch style={styles.container} onPress={() => onPress(item.url)} theme={theme} testID={`server-history-${item.url}`}>
<View style={styles.content}> <View style={styles.content}>
<Text numberOfLines={1} style={[styles.server, { color: themes[theme].bodyText }]}> <Text numberOfLines={1} style={[styles.server, { color: themes[theme].bodyText }]}>
{item.url} {item.url}
</Text> </Text>
<Text numberOfLines={1} style={[styles.username, { color: themes[theme].auxiliaryText }]}> <Text numberOfLines={1} style={{ color: themes[theme].auxiliaryText }}>
{item.username} {item.username}
</Text> </Text>
</View> </View>
@ -43,11 +50,4 @@ const Item = ({ item, theme, onPress, onDelete }) => (
</Touch> </Touch>
); );
Item.propTypes = {
item: PropTypes.object,
theme: PropTypes.string,
onPress: PropTypes.func,
onDelete: PropTypes.func
};
export default Item; export default Item;

View File

@ -1,12 +1,12 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { FlatList, StyleSheet, View } from 'react-native'; import { FlatList, StyleSheet, View } from 'react-native';
import PropTypes from 'prop-types';
import TextInput from '../../../containers/TextInput'; import TextInput from '../../../containers/TextInput';
import * as List from '../../../containers/List'; import * as List from '../../../containers/List';
import { themes } from '../../../constants/colors'; import { themes } from '../../../constants/colors';
import I18n from '../../../i18n'; import I18n from '../../../i18n';
import Item from './Item'; import Item from './Item';
import { IServer } from '../index';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -28,7 +28,25 @@ const styles = StyleSheet.create({
} }
}); });
const ServerInput = ({ text, theme, serversHistory, onChangeText, onSubmit, onDelete, onPressServerHistory }) => { interface IServerInput {
text: string;
theme: string;
serversHistory: any[];
onChangeText(text: string): void;
onSubmit(): void;
onDelete(item: IServer): void;
onPressServerHistory(serverHistory: IServer): void;
}
const ServerInput = ({
text,
theme,
serversHistory,
onChangeText,
onSubmit,
onDelete,
onPressServerHistory
}: IServerInput): JSX.Element => {
const [focused, setFocused] = useState(false); const [focused, setFocused] = useState(false);
return ( return (
<View style={styles.container}> <View style={styles.container}>
@ -68,14 +86,4 @@ const ServerInput = ({ text, theme, serversHistory, onChangeText, onSubmit, onDe
); );
}; };
ServerInput.propTypes = {
text: PropTypes.string,
theme: PropTypes.string,
serversHistory: PropTypes.array,
onChangeText: PropTypes.func,
onSubmit: PropTypes.func,
onDelete: PropTypes.func,
onPressServerHistory: PropTypes.func
};
export default ServerInput; export default ServerInput;

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Text, Keyboard, StyleSheet, View, BackHandler, Image } from 'react-native'; import { Text, Keyboard, StyleSheet, View, BackHandler, Image } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { Base64 } from 'js-base64'; import { Base64 } from 'js-base64';
@ -7,6 +6,9 @@ import parse from 'url-parse';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { TouchableOpacity } from 'react-native-gesture-handler'; import { TouchableOpacity } from 'react-native-gesture-handler';
import Orientation from 'react-native-orientation-locker'; import Orientation from 'react-native-orientation-locker';
import { StackNavigationProp } from '@react-navigation/stack';
import { Dispatch } from 'redux';
import Model from '@nozbe/watermelondb/Model';
import UserPreferences from '../../lib/userPreferences'; import UserPreferences from '../../lib/userPreferences';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
@ -19,7 +21,6 @@ import FormContainer, { FormContainerInner } from '../../containers/FormContaine
import I18n from '../../i18n'; import I18n from '../../i18n';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { events, logEvent } from '../../utils/log'; import { events, logEvent } from '../../utils/log';
import { animateNextTransition } from '../../utils/layoutAnimation';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { BASIC_AUTH_KEY, setBasicAuth } from '../../utils/fetch'; import { BASIC_AUTH_KEY, setBasicAuth } from '../../utils/fetch';
import * as HeaderButton from '../../containers/HeaderButton'; import * as HeaderButton from '../../containers/HeaderButton';
@ -66,19 +67,32 @@ const styles = StyleSheet.create({
} }
}); });
class NewServerView extends React.Component { export interface IServer extends Model {
static propTypes = { url: string;
navigation: PropTypes.object, username: string;
theme: PropTypes.string, }
connecting: PropTypes.bool.isRequired, interface INewServerView {
connectServer: PropTypes.func.isRequired, navigation: StackNavigationProp<any, 'NewServerView'>;
selectServer: PropTypes.func.isRequired, theme: string;
previousServer: PropTypes.string, connecting: boolean;
inviteLinksClear: PropTypes.func, connectServer(server: string, username?: string, fromServerHistory?: boolean): void;
serverFinishAdd: PropTypes.func selectServer(server: string): void;
}; previousServer: string;
inviteLinksClear(): void;
serverFinishAdd(): void;
width: number;
height: number;
}
constructor(props) { interface IState {
text: string;
connectingOpen: boolean;
certificate: any;
serversHistory: IServer[];
}
class NewServerView extends React.Component<INewServerView, IState> {
constructor(props: INewServerView) {
super(props); super(props);
if (!isTablet) { if (!isTablet) {
Orientation.lockToPortrait(); Orientation.lockToPortrait();
@ -131,21 +145,21 @@ class NewServerView extends React.Component {
return false; return false;
}; };
onChangeText = text => { onChangeText = (text: string) => {
this.setState({ text }); this.setState({ text });
this.queryServerHistory(text); this.queryServerHistory(text);
}; };
queryServerHistory = async text => { queryServerHistory = async (text?: string) => {
const db = database.servers; const db = database.servers;
try { try {
const serversHistoryCollection = db.get('servers_history'); const serversHistoryCollection = db.get('servers_history');
let whereClause = [Q.where('username', Q.notEq(null)), Q.experimentalSortBy('updated_at', Q.desc), Q.experimentalTake(3)]; let whereClause = [Q.where('username', Q.notEq(null)), Q.experimentalSortBy('updated_at', Q.desc), Q.experimentalTake(3)];
const likeString = sanitizeLikeString(text);
if (text) { if (text) {
const likeString = sanitizeLikeString(text);
whereClause = [...whereClause, Q.where('url', Q.like(`%${likeString}%`))]; whereClause = [...whereClause, Q.where('url', Q.like(`%${likeString}%`))];
} }
const serversHistory = await serversHistoryCollection.query(...whereClause).fetch(); const serversHistory = (await serversHistoryCollection.query(...whereClause).fetch()) as IServer[];
this.setState({ serversHistory }); this.setState({ serversHistory });
} catch { } catch {
// Do nothing // Do nothing
@ -158,7 +172,7 @@ class NewServerView extends React.Component {
selectServer(previousServer); selectServer(previousServer);
}; };
handleNewServerEvent = event => { handleNewServerEvent = (event: { server: string }) => {
let { server } = event; let { server } = event;
if (!server) { if (!server) {
return; return;
@ -169,13 +183,11 @@ class NewServerView extends React.Component {
connectServer(server); connectServer(server);
}; };
onPressServerHistory = serverHistory => { onPressServerHistory = (serverHistory: IServer) => {
this.setState({ text: serverHistory?.url }, () => this.setState({ text: serverHistory.url }, () => this.submit(true, serverHistory?.username));
this.submit({ fromServerHistory: true, username: serverHistory?.username })
);
}; };
submit = async ({ fromServerHistory = false, username }) => { submit = async (fromServerHistory?: boolean, username?: string) => {
logEvent(events.NS_CONNECT_TO_WORKSPACE); logEvent(events.NS_CONNECT_TO_WORKSPACE);
const { text, certificate } = this.state; const { text, certificate } = this.state;
const { connectServer } = this.props; const { connectServer } = this.props;
@ -207,7 +219,7 @@ class NewServerView extends React.Component {
connectServer('https://open.rocket.chat'); connectServer('https://open.rocket.chat');
}; };
basicAuth = async (server, text) => { basicAuth = async (server: string, text: string) => {
try { try {
const parsedUrl = parse(text, true); const parsedUrl = parse(text, true);
if (parsedUrl.auth.length) { if (parsedUrl.auth.length) {
@ -222,14 +234,14 @@ class NewServerView extends React.Component {
chooseCertificate = async () => { chooseCertificate = async () => {
try { try {
const certificate = await SSLPinning.pickCertificate(); const certificate = await SSLPinning?.pickCertificate();
this.setState({ certificate }); this.setState({ certificate });
} catch { } catch {
// Do nothing // Do nothing
} }
}; };
completeUrl = url => { completeUrl = (url: string) => {
const parsedUrl = parse(url, true); const parsedUrl = parse(url, true);
if (parsedUrl.auth.length) { if (parsedUrl.auth.length) {
url = parsedUrl.origin; url = parsedUrl.origin;
@ -252,14 +264,11 @@ class NewServerView extends React.Component {
return url.replace(/\/+$/, '').replace(/\\/g, '/'); return url.replace(/\/+$/, '').replace(/\\/g, '/');
}; };
uriToPath = uri => uri.replace('file://', ''); uriToPath = (uri: string) => uri.replace('file://', '');
saveCertificate = certificate => {
animateNextTransition();
this.setState({ certificate });
};
handleRemove = () => { handleRemove = () => {
// TODO: Remove ts-ignore when migrate the showConfirmationAlert
// @ts-ignore
showConfirmationAlert({ showConfirmationAlert({
message: I18n.t('You_will_unset_a_certificate_for_this_server'), message: I18n.t('You_will_unset_a_certificate_for_this_server'),
confirmationText: I18n.t('Remove'), confirmationText: I18n.t('Remove'),
@ -267,14 +276,15 @@ class NewServerView extends React.Component {
}); });
}; };
deleteServerHistory = async item => { deleteServerHistory = async (item: IServer) => {
const { serversHistory } = this.state;
const db = database.servers; const db = database.servers;
try { try {
await db.action(async () => { await db.write(async () => {
await item.destroyPermanently(); await item.destroyPermanently();
}); });
this.setState({ serversHistory: serversHistory.filter(server => server.id !== item.id) }); this.setState((prevstate: IState) => ({
serversHistory: prevstate.serversHistory.filter((server: IServer) => server.id !== item.id)
}));
} catch { } catch {
// Nothing // Nothing
} }
@ -288,20 +298,21 @@ class NewServerView extends React.Component {
style={[ style={[
styles.certificatePicker, styles.certificatePicker,
{ {
marginBottom: verticalScale(previousServer && !isTablet ? 10 : 30, height) marginBottom: verticalScale({ size: previousServer && !isTablet ? 10 : 30, height })
} }
]}> ]}>
<Text <Text
style={[ style={[
styles.chooseCertificateTitle, styles.chooseCertificateTitle,
{ color: themes[theme].auxiliaryText, fontSize: moderateScale(13, null, width) } { color: themes[theme].auxiliaryText, fontSize: moderateScale({ size: 13, width }) }
]}> ]}>
{certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')} {certificate ? I18n.t('Your_certificate') : I18n.t('Do_you_have_a_certificate')}
</Text> </Text>
<TouchableOpacity <TouchableOpacity
onPress={certificate ? this.handleRemove : this.chooseCertificate} onPress={certificate ? this.handleRemove : this.chooseCertificate}
testID='new-server-choose-certificate'> testID='new-server-choose-certificate'>
<Text style={[styles.chooseCertificate, { color: themes[theme].tintColor, fontSize: moderateScale(13, null, width) }]}> <Text
style={[styles.chooseCertificate, { color: themes[theme].tintColor, fontSize: moderateScale({ size: 13, width }) }]}>
{certificate ?? I18n.t('Apply_Your_Certificate')} {certificate ?? I18n.t('Apply_Your_Certificate')}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@ -321,10 +332,10 @@ class NewServerView extends React.Component {
style={[ style={[
styles.onboardingImage, styles.onboardingImage,
{ {
marginBottom: verticalScale(10, height), marginBottom: verticalScale({ size: 10, height }),
marginTop: isTablet ? 0 : verticalScale(marginTop, height), marginTop: isTablet ? 0 : verticalScale({ size: marginTop, height }),
width: verticalScale(100, height), width: verticalScale({ size: 100, height }),
height: verticalScale(100, height) height: verticalScale({ size: 100, height })
} }
]} ]}
source={require('../../static/images/logo.png')} source={require('../../static/images/logo.png')}
@ -335,8 +346,8 @@ class NewServerView extends React.Component {
styles.title, styles.title,
{ {
color: themes[theme].titleText, color: themes[theme].titleText,
fontSize: moderateScale(22, null, width), fontSize: moderateScale({ size: 22, width }),
marginBottom: verticalScale(8, height) marginBottom: verticalScale({ size: 8, height })
} }
]}> ]}>
Rocket.Chat Rocket.Chat
@ -346,8 +357,8 @@ class NewServerView extends React.Component {
styles.subtitle, styles.subtitle,
{ {
color: themes[theme].controlText, color: themes[theme].controlText,
fontSize: moderateScale(16, null, width), fontSize: moderateScale({ size: 16, width }),
marginBottom: verticalScale(30, height) marginBottom: verticalScale({ size: 30, height })
} }
]}> ]}>
{I18n.t('Onboarding_subtitle')} {I18n.t('Onboarding_subtitle')}
@ -367,7 +378,7 @@ class NewServerView extends React.Component {
onPress={this.submit} onPress={this.submit}
disabled={!text || connecting} disabled={!text || connecting}
loading={!connectingOpen && connecting} loading={!connectingOpen && connecting}
style={[styles.connectButton, { marginTop: verticalScale(16, height) }]} style={[styles.connectButton, { marginTop: verticalScale({ size: 16, height }) }]}
theme={theme} theme={theme}
testID='new-server-view-button' testID='new-server-view-button'
/> />
@ -377,8 +388,8 @@ class NewServerView extends React.Component {
styles.description, styles.description,
{ {
color: themes[theme].auxiliaryText, color: themes[theme].auxiliaryText,
fontSize: moderateScale(14, null, width), fontSize: moderateScale({ size: 14, width }),
marginBottom: verticalScale(16, height) marginBottom: verticalScale({ size: 16, height })
} }
]}> ]}>
{I18n.t('Onboarding_join_open_description')} {I18n.t('Onboarding_join_open_description')}
@ -400,14 +411,15 @@ class NewServerView extends React.Component {
} }
} }
const mapStateToProps = state => ({ const mapStateToProps = (state: any) => ({
connecting: state.server.connecting, connecting: state.server.connecting,
previousServer: state.server.previousServer previousServer: state.server.previousServer
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = (dispatch: Dispatch) => ({
connectServer: (...params) => dispatch(serverRequest(...params)), connectServer: (server: string, username: string & null, fromServerHistory?: boolean) =>
selectServer: server => dispatch(selectServerRequest(server)), dispatch(serverRequest(server, username, fromServerHistory)),
selectServer: (server: string) => dispatch(selectServerRequest(server)),
inviteLinksClear: () => dispatch(inviteLinksClearAction()), inviteLinksClear: () => dispatch(inviteLinksClearAction()),
serverFinishAdd: () => dispatch(serverFinishAddAction()) serverFinishAdd: () => dispatch(serverFinishAddAction())
}); });