Unverified Commit b87472f1 authored by Djorkaeff Alexandre's avatar Djorkaeff Alexandre Committed by GitHub
Browse files

[NEW] UiKit Beta (#1497)

parent dc0cabf1
export default {
window: () => null
};
export const uiKitMessage = () => () => null;
export const uiKitModal = () => () => null;
export class UiKitParserMessage {}
export class UiKitParserModal {}
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { StyleSheet, Text } from 'react-native'; import { StyleSheet, Text } from 'react-native';
import { RectButton } from 'react-native-gesture-handler'; import Touchable from 'react-native-platform-touchable';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles'; import sharedStyles from '../../views/Styles';
import ActivityIndicator from '../ActivityIndicator'; import ActivityIndicator from '../ActivityIndicator';
/* eslint-disable react-native/no-unused-styles */
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
paddingHorizontal: 15, paddingHorizontal: 15,
...@@ -48,9 +47,9 @@ export default class Button extends React.PureComponent { ...@@ -48,9 +47,9 @@ export default class Button extends React.PureComponent {
} = this.props; } = this.props;
const isPrimary = type === 'primary'; const isPrimary = type === 'primary';
return ( return (
<RectButton <Touchable
onPress={onPress} onPress={onPress}
enabled={!(disabled || loading)} disabled={disabled || loading}
style={[ style={[
styles.container, styles.container,
backgroundColor backgroundColor
...@@ -76,7 +75,7 @@ export default class Button extends React.PureComponent { ...@@ -76,7 +75,7 @@ export default class Button extends React.PureComponent {
</Text> </Text>
) )
} }
</RectButton> </Touchable>
); );
} }
} }
...@@ -9,6 +9,7 @@ import DocumentPicker from 'react-native-document-picker'; ...@@ -9,6 +9,7 @@ import DocumentPicker from 'react-native-document-picker';
import ActionSheet from 'react-native-action-sheet'; import ActionSheet from 'react-native-action-sheet';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { generateTriggerId } from '../../lib/methods/actions';
import TextInput from '../../presentation/TextInput'; import TextInput from '../../presentation/TextInput';
import { userTyping as userTypingAction } from '../../actions/room'; import { userTyping as userTypingAction } from '../../actions/room';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
...@@ -104,7 +105,8 @@ class MessageBox extends Component { ...@@ -104,7 +105,8 @@ class MessageBox extends Component {
isVisible: false isVisible: false
}, },
commandPreview: [], commandPreview: [],
showCommandPreview: false showCommandPreview: false,
command: {}
}; };
this.text = ''; this.text = '';
this.focused = false; this.focused = false;
...@@ -280,7 +282,7 @@ class MessageBox extends Component { ...@@ -280,7 +282,7 @@ class MessageBox extends Component {
try { try {
const command = await commandsCollection.find(name); const command = await commandsCollection.find(name);
if (command.providesPreview) { if (command.providesPreview) {
return this.setCommandPreview(name, params); return this.setCommandPreview(command, name, params);
} }
} catch (e) { } catch (e) {
console.log('Slash command not found'); console.log('Slash command not found');
...@@ -339,16 +341,19 @@ class MessageBox extends Component { ...@@ -339,16 +341,19 @@ class MessageBox extends Component {
} }
onPressCommandPreview = (item) => { onPressCommandPreview = (item) => {
const { command } = this.state;
const { rid } = this.props; const { rid } = this.props;
const { text } = this; const { text } = this;
const command = text.substr(0, text.indexOf(' ')).slice(1); const name = text.substr(0, text.indexOf(' ')).slice(1);
const params = text.substr(text.indexOf(' ') + 1) || 'params'; const params = text.substr(text.indexOf(' ') + 1) || 'params';
this.setState({ commandPreview: [], showCommandPreview: false }); this.setState({ commandPreview: [], showCommandPreview: false, command: {} });
this.stopTrackingMention(); this.stopTrackingMention();
this.clearInput(); this.clearInput();
this.handleTyping(false); this.handleTyping(false);
try { try {
RocketChat.executeCommandPreview(command, params, rid, item); const { appId } = command;
const triggerId = generateTriggerId(appId);
RocketChat.executeCommandPreview(name, params, rid, item, triggerId);
} catch (e) { } catch (e) {
log(e); log(e);
} }
...@@ -452,13 +457,13 @@ class MessageBox extends Component { ...@@ -452,13 +457,13 @@ class MessageBox extends Component {
}, 1000); }, 1000);
} }
setCommandPreview = async(command, params) => { setCommandPreview = async(command, name, params) => {
const { rid } = this.props; const { rid } = this.props;
try { try {
const { preview } = await RocketChat.getCommandPreview(command, rid, params); const { preview } = await RocketChat.getCommandPreview(name, rid, params);
this.setState({ commandPreview: preview.items, showCommandPreview: true }); this.setState({ commandPreview: preview.items, showCommandPreview: true, command });
} catch (e) { } catch (e) {
this.setState({ commandPreview: [], showCommandPreview: true }); this.setState({ commandPreview: [], showCommandPreview: true, command: {} });
log(e); log(e);
} }
} }
...@@ -669,7 +674,9 @@ class MessageBox extends Component { ...@@ -669,7 +674,9 @@ class MessageBox extends Component {
if (slashCommand.length > 0) { if (slashCommand.length > 0) {
try { try {
const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim(); const messageWithoutCommand = message.replace(/([^\s]+)/, '').trim();
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand); const [{ appId }] = slashCommand;
const triggerId = generateTriggerId(appId);
RocketChat.runSlashCommand(command, roomId, messageWithoutCommand, triggerId);
} catch (e) { } catch (e) {
log(e); log(e);
} }
......
...@@ -7,6 +7,7 @@ import sharedStyles from '../views/Styles'; ...@@ -7,6 +7,7 @@ import sharedStyles from '../views/Styles';
import TextInput from '../presentation/TextInput'; import TextInput from '../presentation/TextInput';
import { themes } from '../constants/colors'; import { themes } from '../constants/colors';
import { CustomIcon } from '../lib/Icons'; import { CustomIcon } from '../lib/Icons';
import ActivityIndicator from './ActivityIndicator';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
error: { error: {
...@@ -56,6 +57,7 @@ export default class RCTextInput extends React.PureComponent { ...@@ -56,6 +57,7 @@ export default class RCTextInput extends React.PureComponent {
static propTypes = { static propTypes = {
label: PropTypes.string, label: PropTypes.string,
error: PropTypes.object, error: PropTypes.object,
loading: PropTypes.bool,
secureTextEntry: PropTypes.bool, secureTextEntry: PropTypes.bool,
containerStyle: PropTypes.any, containerStyle: PropTypes.any,
inputStyle: PropTypes.object, inputStyle: PropTypes.object,
...@@ -102,6 +104,11 @@ export default class RCTextInput extends React.PureComponent { ...@@ -102,6 +104,11 @@ export default class RCTextInput extends React.PureComponent {
); );
} }
get loading() {
const { theme } = this.props;
return <ActivityIndicator style={[styles.iconContainer, styles.iconRight, { color: themes[theme].bodyText }]} />;
}
tooglePassword = () => { tooglePassword = () => {
this.setState(prevState => ({ showPassword: !prevState.showPassword })); this.setState(prevState => ({ showPassword: !prevState.showPassword }));
} }
...@@ -109,7 +116,7 @@ export default class RCTextInput extends React.PureComponent { ...@@ -109,7 +116,7 @@ export default class RCTextInput extends React.PureComponent {
render() { render() {
const { showPassword } = this.state; const { showPassword } = this.state;
const { const {
label, error, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps label, error, loading, secureTextEntry, containerStyle, inputRef, iconLeft, inputStyle, testID, placeholder, theme, ...inputProps
} = this.props; } = this.props;
const { dangerColor } = themes[theme]; const { dangerColor } = themes[theme];
return ( return (
...@@ -131,10 +138,6 @@ export default class RCTextInput extends React.PureComponent { ...@@ -131,10 +138,6 @@ export default class RCTextInput extends React.PureComponent {
<TextInput <TextInput
style={[ style={[
styles.input, styles.input,
error.error && {
color: dangerColor,
borderColor: dangerColor
},
iconLeft && styles.inputIconLeft, iconLeft && styles.inputIconLeft,
secureTextEntry && styles.inputIconRight, secureTextEntry && styles.inputIconRight,
{ {
...@@ -142,6 +145,10 @@ export default class RCTextInput extends React.PureComponent { ...@@ -142,6 +145,10 @@ export default class RCTextInput extends React.PureComponent {
borderColor: themes[theme].separatorColor, borderColor: themes[theme].separatorColor,
color: themes[theme].titleText color: themes[theme].titleText
}, },
error.error && {
color: dangerColor,
borderColor: dangerColor
},
inputStyle inputStyle
]} ]}
ref={inputRef} ref={inputRef}
...@@ -158,8 +165,9 @@ export default class RCTextInput extends React.PureComponent { ...@@ -158,8 +165,9 @@ export default class RCTextInput extends React.PureComponent {
/> />
{iconLeft ? this.iconLeft : null} {iconLeft ? this.iconLeft : null}
{secureTextEntry ? this.iconPassword : null} {secureTextEntry ? this.iconPassword : null}
{loading ? this.loading : null}
</View> </View>
{error.error ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null} {error && error.reason ? <Text style={[styles.error, { color: dangerColor }]}>{error.reason}</Text> : null}
</View> </View>
); );
} }
......
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import Button from '../Button';
import I18n from '../../i18n';
export const Actions = ({
blockId, appId, elements, parser, theme
}) => {
const [showMoreVisible, setShowMoreVisible] = useState(() => elements.length > 5);
const renderedElements = showMoreVisible ? elements.slice(0, 5) : elements;
const Elements = () => renderedElements
.map(element => parser.renderActions({ blockId, appId, ...element }, BLOCK_CONTEXT.ACTION, parser));
return (
<>
<Elements />
{showMoreVisible && (<Button theme={theme} title={I18n.t('Show_more')} onPress={() => setShowMoreVisible(false)} />)}
</>
);
};
Actions.propTypes = {
blockId: PropTypes.string,
appId: PropTypes.string,
elements: PropTypes.array,
parser: PropTypes.object,
theme: PropTypes.string
};
import React from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
const styles = StyleSheet.create({
container: {
minHeight: 36,
alignItems: 'center',
flexDirection: 'row'
}
});
export const Context = ({ elements, parser }) => (
<View style={styles.container}>
{elements.map(element => parser.renderContext(element, BLOCK_CONTEXT.CONTEXT, parser))}
</View>
);
Context.propTypes = {
elements: PropTypes.array,
parser: PropTypes.object
};
import React, { useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import PropTypes from 'prop-types';
import DateTimePicker from '@react-native-community/datetimepicker';
import Touchable from 'react-native-platform-touchable';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import moment from 'moment';
import Button from '../Button';
import { textParser } from './utils';
import { themes } from '../../constants/colors';
import sharedStyles from '../../views/Styles';
import { CustomIcon } from '../../lib/Icons';
import { isAndroid } from '../../utils/deviceInfo';
import ActivityIndicator from '../ActivityIndicator';
const styles = StyleSheet.create({
input: {
height: 48,
paddingLeft: 16,
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 2,
alignItems: 'center',
flexDirection: 'row'
},
inputText: {
...sharedStyles.textRegular,
fontSize: 14
},
icon: {
right: 16,
position: 'absolute'
},
loading: {
padding: 0
}
});
export const DatePicker = ({
element, language, action, context, theme, loading, value, error
}) => {
const [show, onShow] = useState(false);
const { initial_date, placeholder } = element;
const [currentDate, onChangeDate] = useState(new Date(initial_date || value));
const onChange = ({ nativeEvent: { timestamp } }, date) => {
const newDate = date || new Date(timestamp);
onChangeDate(newDate);
action({ value: moment(newDate).format('YYYY-MM-DD') });
if (isAndroid) {
onShow(false);
}
};
let button = (
<Button
title={textParser([placeholder])}
onPress={() => onShow(!show)}
loading={loading}
theme={theme}
/>
);
if (context === BLOCK_CONTEXT.FORM) {
button = (
<Touchable
onPress={() => onShow(!show)}
style={{ backgroundColor: themes[theme].backgroundColor }}
background={Touchable.Ripple(themes[theme].bannerBackground)}
>
<View style={[styles.input, { borderColor: error ? themes[theme].dangerColor : themes[theme].separatorColor }]}>
<Text
style={[
styles.inputText,
{ color: error ? themes[theme].dangerColor : themes[theme].titleText }
]}
>
{currentDate.toLocaleDateString(language)}
</Text>
{
loading
? <ActivityIndicator style={[styles.loading, styles.icon]} />
: <CustomIcon name='calendar' size={20} color={error ? themes[theme].dangerColor : themes[theme].auxiliaryText} style={styles.icon} />
}
</View>
</Touchable>
);
}
const content = show ? (
<DateTimePicker
mode='date'
display='default'
value={currentDate}
onChange={onChange}
textColor={themes[theme].titleText}
/>
) : null;
return (
<>
{button}
{content}
</>
);
};
DatePicker.propTypes = {
element: PropTypes.object,
language: PropTypes.string,
action: PropTypes.func,
context: PropTypes.number,
loading: PropTypes.bool,
theme: PropTypes.string,
value: PropTypes.string,
error: PropTypes.string
};
import React from 'react';
import { StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import Separator from '../Separator';
const styles = StyleSheet.create({
separator: {
width: '100%',
alignSelf: 'center',
marginBottom: 16
}
});
export const Divider = ({ theme }) => <Separator style={styles.separator} theme={theme} />;
Divider.propTypes = {
theme: PropTypes.string
};
import React from 'react';
import { View, StyleSheet } from 'react-native';
import FastImage from 'react-native-fast-image';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImageContainer from '../message/Image';
import Navigation from '../../lib/Navigation';
const styles = StyleSheet.create({
image: {
borderRadius: 2
},
mediaContext: {
marginRight: 8
}
});
const ThumbContext = args => <View style={styles.mediaContext}><Thumb size={20} {...args} /></View>;
export const Thumb = ({ element, size = 88 }) => (
<FastImage
style={[{ width: size, height: size }, styles.image]}
source={{ uri: element.imageUrl }}
/>
);
Thumb.propTypes = {
element: PropTypes.object,
size: PropTypes.number
};
export const Media = ({ element, theme }) => {
const showAttachment = attachment => Navigation.navigate('AttachmentView', { attachment });
const { imageUrl } = element;
return (
<ImageContainer
file={{ image_url: imageUrl }}
imageUrl={imageUrl}
showAttachment={showAttachment}
theme={theme}
/>
);
};
Media.propTypes = {
element: PropTypes.object,
theme: PropTypes.string
};
const genericImage = (element, context, theme) => {
switch (context) {
case BLOCK_CONTEXT.SECTION:
return <Thumb element={element} />;
case BLOCK_CONTEXT.CONTEXT:
return <ThumbContext element={element} />;
default:
return <Media element={element} theme={theme} />;
}
};
export const Image = ({ element, context, theme }) => genericImage(element, context, theme);
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import PropTypes from 'prop-types';
import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import sharedStyles from '../../views/Styles';
import { themes } from '../../constants/colors';
const styles = StyleSheet.create({
container: {
marginBottom: 16
},
label: {
fontSize: 14,
marginVertical: 10,
...sharedStyles.textSemibold
},
description: {
marginBottom: 10,
fontSize: 15,
...sharedStyles.textRegular
},
error: {
marginTop: 8,
fontSize: 14,
...sharedStyles.textRegular,
...sharedStyles.textAlignCenter
},
hint: {
fontSize: 14,
...sharedStyles.textRegular
}
});
export const Input = ({
element, parser, label, description, error, hint, theme
}) => (
<View style={styles.container}>
{label ? <Text style={[styles.label, { color: error ? themes[theme].dangerColor : themes[theme].titleText }]}>{label}</Text> : null}
{description ? <Text style={[styles.description, { color: themes[theme].auxiliaryText }]}>{description}</Text> : null}
{parser.renderInputs({ ...element }, BLOCK_CONTEXT.FORM, parser)}
{error ? <Text style={[styles.error, { color: themes[theme].dangerColor }]}>{error}</Text> : null}
{hint ? <Text style={[styles.hint, { color: themes[theme].auxiliaryText }]}>{hint}</Text> : null}
</View>
);
Input.propTypes = {
element: PropTypes.object,
parser: PropTypes.object,
label: PropTypes.string,
description: PropTypes.string,
error: PropTypes.string,
hint: PropTypes.string,
theme: PropTypes.string
};
import React from 'react';
import PropTypes from 'prop-types';