Merged PR 740936: Support PeoplePicker
Support PeoplePicker
This commit is contained in:
Родитель
dfdee71720
Коммит
b5ec41e080
|
@ -13,28 +13,26 @@ import { Guid } from '../../Shared/Guid';
|
|||
export class Touchable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.testId = this.props.testId + Guid.newGuid();
|
||||
}
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
DeviceEventEmitter.addListener('KeyEnter' + this.props.testId, this.props.onPress);
|
||||
DeviceEventEmitter.addListener('KeyEnter' + this.testId, this.props.onPress);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (Platform.OS === 'android') {
|
||||
DeviceEventEmitter.removeListener('KeyEnter' + this.props.testId, this.props.onPress);
|
||||
DeviceEventEmitter.removeListener('KeyEnter' + this.testId, this.props.onPress);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const _a = this.props, { onPress, onLongPress, disabled, accessibilityLabel, accessibilityTraits, accessibilityComponentType, activeOpacity, hitSlop, style } = _a, otherProps = __rest(_a, ["onPress", "onLongPress", "disabled", "accessibilityLabel", "accessibilityTraits", "accessibilityComponentType", "activeOpacity", "hitSlop", "style"]);
|
||||
if (Platform.OS === 'android') {
|
||||
return (React.createElement(TouchableNativeFeedback, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.uniqueTestId, useForeground: true, hitSlop: hitSlop, background: TouchableNativeFeedback.SelectableBackground(), accessibilityLabel: accessibilityLabel },
|
||||
return (React.createElement(TouchableNativeFeedback, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.testId, useForeground: true, hitSlop: hitSlop, background: TouchableNativeFeedback.SelectableBackground(), accessibilityLabel: accessibilityLabel },
|
||||
React.createElement(View, Object.assign({ style: style, onLayout: this.props.onLayout }, otherProps))));
|
||||
}
|
||||
else {
|
||||
return (React.createElement(TouchableOpacity, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.uniqueTestId, activeOpacity: activeOpacity, style: style, hitSlop: hitSlop, accessibilityLabel: accessibilityLabel, onLayout: this.props.onLayout }, otherProps.children));
|
||||
return (React.createElement(TouchableOpacity, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.testId, activeOpacity: activeOpacity, style: style, hitSlop: hitSlop, accessibilityLabel: accessibilityLabel, onLayout: this.props.onLayout }, otherProps.children));
|
||||
}
|
||||
}
|
||||
get uniqueTestId() {
|
||||
return this.props.testId + Guid.newGuid();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import * as React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { StyleManager } from '../../Styles/StyleManager';
|
||||
import { ImageBlock } from '../Basic/ImageBlock';
|
||||
import { Touchable } from '../Basic/Touchable';
|
||||
export class Contact extends React.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.onPress = () => {
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(this.props.hiddenFields);
|
||||
}
|
||||
};
|
||||
}
|
||||
render() {
|
||||
if (this.props.onSelect) {
|
||||
return this.renderTouchableBlock();
|
||||
}
|
||||
else {
|
||||
return this.renderNonTouchableBlock();
|
||||
}
|
||||
}
|
||||
renderTouchableBlock() {
|
||||
return (React.createElement(Touchable, { onPress: this.onPress, style: {
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row'
|
||||
} }, this.renderContent()));
|
||||
}
|
||||
renderNonTouchableBlock() {
|
||||
return (React.createElement(View, { alignSelf: 'stretch', flexDirection: 'row' }, this.renderContent()));
|
||||
}
|
||||
renderContent() {
|
||||
return [
|
||||
React.createElement(ImageBlock, { url: this.props.avatar, mode: 'avatar', width: StyleManager.getImageSize('medium'), height: StyleManager.getImageSize('medium') }),
|
||||
React.createElement(View, null,
|
||||
React.createElement(Text, { accessible: true, style: {
|
||||
color: StyleManager.getColor('default', this.props.theme, false),
|
||||
fontSize: StyleManager.getFontSize('default'),
|
||||
fontWeight: StyleManager.getFontWeight('default'),
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: StyleManager.getTextAlign('left'),
|
||||
flexWrap: StyleManager.getWrap(true),
|
||||
} }, this.props.mainInfo),
|
||||
React.createElement(Text, { accessible: true, style: {
|
||||
color: StyleManager.getColor('default', this.props.theme, true),
|
||||
fontSize: StyleManager.getFontSize('small'),
|
||||
fontWeight: StyleManager.getFontWeight('default'),
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: StyleManager.getTextAlign('left'),
|
||||
flexWrap: StyleManager.getWrap(true),
|
||||
} }, this.props.subInfo))
|
||||
];
|
||||
}
|
||||
}
|
|
@ -87,7 +87,7 @@ export class InputBox extends React.Component {
|
|||
return 1;
|
||||
}
|
||||
get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
import * as React from 'react';
|
||||
import { ScrollView, Text, TextInput, View } from 'react-native';
|
||||
import { StyleManager } from '../../Styles/StyleManager';
|
||||
import { SeparateLine } from '../Basic/SeparateLine';
|
||||
export class LabelInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onValueChange = (value) => {
|
||||
if (this.props.onRequestSuggestion) {
|
||||
this.props.onRequestSuggestion(value);
|
||||
}
|
||||
};
|
||||
this.onBlur = () => {
|
||||
this.setState({ focused: false }, () => {
|
||||
this.validateInput();
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur();
|
||||
}
|
||||
});
|
||||
};
|
||||
this.onFocus = () => {
|
||||
this.setState({
|
||||
focused: true
|
||||
}, () => {
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus();
|
||||
}
|
||||
});
|
||||
};
|
||||
this.state = {
|
||||
focused: this.props.focused,
|
||||
};
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevState.focused && this.props.focused) {
|
||||
this.setState({
|
||||
focused: true,
|
||||
}, () => {
|
||||
if (this.inputBox) {
|
||||
this.inputBox.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (React.createElement(View, { style: {
|
||||
flex: this.props.flex,
|
||||
} },
|
||||
this.renderInputArea(),
|
||||
this.renderSuggestions()));
|
||||
}
|
||||
renderInputArea() {
|
||||
return (React.createElement(View, { style: {
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
backgroundColor: this.backgroundColor,
|
||||
borderColor: this.borderColor,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
width: this.props.width,
|
||||
marginTop: this.props.marginTop,
|
||||
marginRight: this.props.marginRight,
|
||||
marginBottom: this.props.marginBottom,
|
||||
marginLeft: this.props.marginLeft,
|
||||
} },
|
||||
this.renderLabels(),
|
||||
this.renderInputBox()));
|
||||
}
|
||||
renderLabels() {
|
||||
if (this.props.labels) {
|
||||
return this.props.labels.map((label, index) => {
|
||||
return (React.createElement(Text, { key: 'Label' + index, style: {
|
||||
fontSize: this.fontSize,
|
||||
fontWeight: this.fontWeight,
|
||||
color: this.backgroundColor,
|
||||
backgroundColor: this.color,
|
||||
paddingTop: this.paddingVertical - 6,
|
||||
paddingBottom: this.paddingVertical - 6,
|
||||
borderRadius: 4,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
marginTop: 6,
|
||||
marginBottom: 6,
|
||||
marginLeft: 6,
|
||||
} }, label.title));
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
renderInputBox() {
|
||||
return (React.createElement(TextInput, { ref: ref => this.inputBox = ref, style: [
|
||||
{
|
||||
flex: 1,
|
||||
color: this.color,
|
||||
fontSize: this.fontSize,
|
||||
fontWeight: this.fontWeight,
|
||||
paddingTop: this.paddingVertical,
|
||||
paddingRight: this.paddingHorizontal,
|
||||
paddingBottom: this.paddingVertical,
|
||||
paddingLeft: this.paddingHorizontal,
|
||||
},
|
||||
this.props.style
|
||||
], multiline: this.isMultiLine, numberOfLines: this.props.numberOfLines, keyboardType: this.props.keyboardType, blurOnSubmit: !this.isMultiLine, placeholder: this.props.placeholder, value: this.props.value, returnKeyType: this.props.returnKeyType, underlineColorAndroid: 'transparent', importantForAccessibility: 'no-hide-descendants', onChangeText: this.onValueChange, onFocus: this.onFocus, onBlur: this.onBlur }));
|
||||
}
|
||||
renderSuggestions() {
|
||||
if (this.props.suggestionView) {
|
||||
return [
|
||||
React.createElement(SeparateLine, { key: 0 }),
|
||||
React.createElement(ScrollView, { key: 1, style: {
|
||||
maxHeight: 200
|
||||
} }, this.props.suggestionView)
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
validateInput() {
|
||||
if (this.props.validateInput) {
|
||||
if (this.props.validateInput(this.props.value)) {
|
||||
console.log('Input: valid');
|
||||
}
|
||||
else {
|
||||
console.log('Input: invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
get isMultiLine() {
|
||||
return this.props.numberOfLines && this.props.numberOfLines > 1;
|
||||
}
|
||||
get fontSize() {
|
||||
return StyleManager.getFontSize('default');
|
||||
}
|
||||
get fontWeight() {
|
||||
return StyleManager.getFontWeight('default');
|
||||
}
|
||||
get paddingVertical() {
|
||||
return 12;
|
||||
}
|
||||
get paddingHorizontal() {
|
||||
return 12;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusColor(this.props.theme);
|
||||
}
|
||||
else {
|
||||
return StyleManager.getInputColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
get backgroundColor() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusBackgroundColor(this.props.theme);
|
||||
}
|
||||
else {
|
||||
return StyleManager.getInputBackgroundColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
get borderColor() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusBorderColor(this.props.theme);
|
||||
}
|
||||
else {
|
||||
return StyleManager.getInputBorderColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export class CardContext {
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
export class FormContext {
|
||||
constructor() {
|
||||
this.formFields = {};
|
||||
this.fieldListeners = {};
|
||||
}
|
||||
static getInstance() {
|
||||
if (FormContext.sharedInstance === undefined) {
|
||||
|
@ -14,6 +15,7 @@ export class FormContext {
|
|||
value: value,
|
||||
validate: validate
|
||||
};
|
||||
this.getFieldListeners(id).forEach((listener) => listener(value));
|
||||
}
|
||||
}
|
||||
getField(id) {
|
||||
|
@ -54,17 +56,6 @@ export class FormContext {
|
|||
}
|
||||
return {};
|
||||
}
|
||||
getCallbackParamData(params) {
|
||||
if (params) {
|
||||
return Object.keys(params).reduce((prev, current) => {
|
||||
let formIndex = params[current];
|
||||
console.log(formIndex);
|
||||
prev[current] = this.getFieldValue(formIndex);
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
return {};
|
||||
}
|
||||
validateField(id) {
|
||||
let field = this.getField(id);
|
||||
if (field) {
|
||||
|
@ -80,4 +71,19 @@ export class FormContext {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
registerFieldListener(id, listener) {
|
||||
if (!this.fieldListeners[id]) {
|
||||
this.fieldListeners[id] = [listener];
|
||||
}
|
||||
else {
|
||||
this.fieldListeners[id].push(listener);
|
||||
}
|
||||
}
|
||||
getFieldListeners(id) {
|
||||
let result = this.fieldListeners[id];
|
||||
if (!result) {
|
||||
result = [];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ export class HostContext {
|
|||
registerCallbackHandler(handler) {
|
||||
this.onCallback = handler;
|
||||
}
|
||||
registerSelectActionHandler(handler) {
|
||||
this.onSelectAction = handler;
|
||||
}
|
||||
applyConfig(configJson) {
|
||||
this.config.combine(new HostConfig(configJson));
|
||||
}
|
||||
|
@ -59,6 +62,9 @@ export class HostContext {
|
|||
case ActionType.Submit:
|
||||
callback = this.onSubmit;
|
||||
break;
|
||||
case ActionType.Select:
|
||||
callback = this.onSelectAction;
|
||||
break;
|
||||
case 'focus':
|
||||
callback = this.onFocus;
|
||||
break;
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { Image } from 'react-native';
|
||||
export class MediaContext {
|
||||
constructor() {
|
||||
this.mediaSizes = {};
|
||||
}
|
||||
static getInstance() {
|
||||
if (MediaContext.sharedInstance === undefined) {
|
||||
MediaContext.sharedInstance = new MediaContext();
|
||||
}
|
||||
return MediaContext.sharedInstance;
|
||||
}
|
||||
fetchImageSize(url, onSuccess, onFailure) {
|
||||
let cache = this.getSize(url);
|
||||
if (cache) {
|
||||
onSuccess(cache.width, cache.height);
|
||||
}
|
||||
else {
|
||||
Image.getSize(url, (width, height) => {
|
||||
this.cacheSize(url, { width: width, height: height });
|
||||
onSuccess(width, height);
|
||||
}, onFailure);
|
||||
}
|
||||
}
|
||||
cacheSize(url, size) {
|
||||
this.mediaSizes[url] = size;
|
||||
}
|
||||
getSize(url) {
|
||||
return this.mediaSizes[url];
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import { AbstractElement } from './AbstractElement';
|
|||
export var ActionType;
|
||||
(function (ActionType) {
|
||||
ActionType["OpenUrl"] = "Action.OpenUrl";
|
||||
ActionType["Select"] = "Action.Select";
|
||||
ActionType["Submit"] = "Action.Submit";
|
||||
ActionType["ShowCard"] = "Action.ShowCard";
|
||||
ActionType["Callback"] = "Action.Callback";
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { ElementUtils } from '../../Utils/ElementUtils';
|
||||
import { ActionElement } from '../Abstract/ActionElement';
|
||||
export class SelectActionElement extends ActionElement {
|
||||
constructor(json, parent) {
|
||||
super(json, parent);
|
||||
this.children = [];
|
||||
if (this.isValid) {
|
||||
this.title = json.selectedTextTitle;
|
||||
this.subTitle = json.selectedTextSubTitle;
|
||||
this.data = json.data;
|
||||
}
|
||||
}
|
||||
get targetFormField() {
|
||||
let targetInput = this.ancestorsAndSelf.find(element => ElementUtils.isSelectActionTarget(element.type));
|
||||
if (targetInput) {
|
||||
return targetInput.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
get scope() {
|
||||
return this.ancestorsAndSelf.find(element => element.parent === undefined);
|
||||
}
|
||||
get requiredProperties() {
|
||||
return ['type', 'selectedTextTitle', 'selectedTextSubTitle', 'data'];
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { ActionType } from '../Abstract/ActionElement';
|
||||
import { OpenUrlActionElement } from '../Actions/OpenUrlAction';
|
||||
import { SelectActionElement } from '../Actions/SelectAction';
|
||||
import { ShowCardActionElement } from '../Actions/ShowCardAction';
|
||||
import { SubmitActionElement } from '../Actions/SubmitAction';
|
||||
export class ActionFactory {
|
||||
|
@ -18,6 +19,9 @@ export class ActionFactory {
|
|||
case ActionType.ShowCard:
|
||||
action = new ShowCardActionElement(json, parent);
|
||||
break;
|
||||
case ActionType.Select:
|
||||
action = new SelectActionElement(json, parent);
|
||||
break;
|
||||
default:
|
||||
action = undefined;
|
||||
break;
|
||||
|
|
|
@ -5,6 +5,10 @@ export class ElementUtils {
|
|||
static isValue(type) {
|
||||
return ElementUtils.valueTypes.indexOf(type) >= 0;
|
||||
}
|
||||
static isSelectActionTarget(type) {
|
||||
return ElementUtils.selectActionTargetTypes.indexOf(type) >= 0;
|
||||
}
|
||||
}
|
||||
ElementUtils.inputTypes = ['Input.Text', 'Input.Number', 'Input.Date', 'Input.Time', 'Input.Toggle', 'Input.ChoiceSet'];
|
||||
ElementUtils.valueTypes = ['Fact', 'Input.Choice'];
|
||||
ElementUtils.selectActionTargetTypes = ['Input.PeoplePicker'];
|
||||
|
|
|
@ -11,6 +11,7 @@ import { FactSetView } from '../Containers/FactSet';
|
|||
import { ImageSetView } from '../Containers/ImageSet';
|
||||
import { DateInputView } from '../Inputs/DateInput';
|
||||
import { NumberInputView } from '../Inputs/NumberInput';
|
||||
import { PeoplePickerView } from '../Inputs/PeoplePicker';
|
||||
import { TextInputView } from '../Inputs/TextInput';
|
||||
import { TimeInputView } from '../Inputs/TimeInput';
|
||||
export class ContentFactory {
|
||||
|
@ -61,6 +62,8 @@ export class ContentFactory {
|
|||
return (React.createElement(DateInputView, { key: 'DateInputView' + index, element: element, index: index, theme: theme }));
|
||||
case ContentElementType.TimeInput:
|
||||
return (React.createElement(TimeInputView, { key: 'TimeInputView' + index, element: element, index: index, theme: theme }));
|
||||
case ContentElementType.PeoplePicker:
|
||||
return (React.createElement(PeoplePickerView, { key: 'PeoplePickerView' + index, element: element, index: index, theme: theme }));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ export class DateInputView extends React.Component {
|
|||
return 1;
|
||||
}
|
||||
get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import * as React from 'react';
|
||||
import { LabelInput } from '../../Components/Inputs/LabelInput';
|
||||
import { ActionContext } from '../../Contexts/ActionContext';
|
||||
import { FormContext } from '../../Contexts/FormContext';
|
||||
import { ActionType } from '../../Schema/Abstract/ActionElement';
|
||||
import { CardElement } from '../../Schema/Cards/Card';
|
||||
import { ContentFactory } from '../Factories/ContentFactory';
|
||||
import { DebugOutputFactory } from '../Factories/DebugOutputFactory';
|
||||
export class PeoplePickerView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onBlur = () => {
|
||||
this.setState({ inputFocused: false });
|
||||
};
|
||||
this.onFocus = () => {
|
||||
this.setState({ inputFocused: true });
|
||||
};
|
||||
this.onSuggestionCallback = (data) => {
|
||||
this.setState({
|
||||
suggestionCard: new CardElement(data, this.props.element),
|
||||
});
|
||||
};
|
||||
this.onRequestSuggestion = (input) => {
|
||||
this.setState({
|
||||
value: input,
|
||||
}, () => {
|
||||
const { element } = this.props;
|
||||
if (element.callback) {
|
||||
let callback = ActionContext.getGlobalInstance().getActionEventHandler(element.callback, this.onSuggestionCallback);
|
||||
if (callback) {
|
||||
callback({
|
||||
actionType: ActionType.Callback,
|
||||
func: this.populateApiParams,
|
||||
name: 'populateApiParams'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
this.populateApiParams = (args) => {
|
||||
if (args && args.formValidate) {
|
||||
args.formData = this.extractParamData();
|
||||
}
|
||||
return args;
|
||||
};
|
||||
this.extractParamData = () => {
|
||||
const { element } = this.props;
|
||||
if (element.callback) {
|
||||
const params = element.callback.parameters;
|
||||
if (params) {
|
||||
return Object.keys(params).reduce((prev, current) => {
|
||||
let formIndex = params[current];
|
||||
if (formIndex === element.id) {
|
||||
prev[current] = this.state.value;
|
||||
}
|
||||
else {
|
||||
prev[current] = FormContext.getInstance().getFieldValue(formIndex);
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
this.storeListener = (value) => {
|
||||
this.setState({
|
||||
selected: JSON.parse(value),
|
||||
suggestionCard: undefined,
|
||||
inputFocused: true,
|
||||
value: '',
|
||||
});
|
||||
};
|
||||
const { element } = this.props;
|
||||
if (element && element.isValid) {
|
||||
this.state = {
|
||||
value: '',
|
||||
inputFocused: false,
|
||||
selected: [],
|
||||
suggestionCard: undefined,
|
||||
};
|
||||
FormContext.getInstance().updateField(element.id, JSON.stringify([]), true);
|
||||
FormContext.getInstance().registerFieldListener(element.id, this.storeListener);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { element, theme } = this.props;
|
||||
if (!element || !element.isValid) {
|
||||
return DebugOutputFactory.createDebugOutputBanner(element.type + '>>' + element.id + ' is not valid', theme, 'error');
|
||||
}
|
||||
return (React.createElement(LabelInput, { placeholder: element.placeholder, value: this.state.value, focused: this.state.inputFocused, labels: this.labels, suggestionView: ContentFactory.createElement(this.state.suggestionCard, 0, theme), onRequestSuggestion: this.onRequestSuggestion, onFocus: this.onFocus, onBlur: this.onBlur }));
|
||||
}
|
||||
get labels() {
|
||||
return this.state.selected.map((contact) => {
|
||||
return {
|
||||
title: contact.Name
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -75,7 +75,7 @@ export class TimeInputView extends React.Component {
|
|||
return 1;
|
||||
}
|
||||
get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
|
|
|
@ -50,6 +50,15 @@ export class CardRootView extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
};
|
||||
this.onSelectAction = (args) => {
|
||||
if (args) {
|
||||
let currentValue = JSON.parse(FormContext.getInstance().getFieldValue(args.action.targetFormField));
|
||||
if (currentValue) {
|
||||
currentValue.push(args.formData);
|
||||
FormContext.getInstance().updateField(args.action.targetFormField, JSON.stringify(currentValue), true);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.validateForm = (args) => {
|
||||
if (args) {
|
||||
args.formValidate = args.action.scope.validateScope();
|
||||
|
@ -68,9 +77,9 @@ export class CardRootView extends React.PureComponent {
|
|||
}
|
||||
return args;
|
||||
};
|
||||
this.populateCallbackParamData = (args) => {
|
||||
if (args && args.formValidate) {
|
||||
args.formData = FormContext.getInstance().getCallbackParamData(args.action.parameters);
|
||||
this.populateSelectActionData = (args) => {
|
||||
if (args) {
|
||||
args.formData = Object.assign({}, (args.action.data || {}));
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
@ -80,6 +89,7 @@ export class CardRootView extends React.PureComponent {
|
|||
hostContext.registerOpenUrlHandler(this.onOpenUrl);
|
||||
hostContext.registerSubmitHandler(this.onSubmit);
|
||||
hostContext.registerCallbackHandler(this.onCallback);
|
||||
hostContext.registerSelectActionHandler(this.onSelectAction);
|
||||
hostContext.registerFocusHandler(this.props.onFocus);
|
||||
hostContext.registerBlurHandler(this.props.onBlur);
|
||||
hostContext.registerErrorHandler(this.props.onError);
|
||||
|
@ -102,9 +112,9 @@ export class CardRootView extends React.PureComponent {
|
|||
actionType: ActionType.Submit
|
||||
});
|
||||
actionContext.registerHook({
|
||||
func: this.populateCallbackParamData,
|
||||
name: 'populateCallbackParamData',
|
||||
actionType: ActionType.Callback
|
||||
func: this.populateSelectActionData,
|
||||
name: 'populateSelectActionData',
|
||||
actionType: ActionType.Select
|
||||
});
|
||||
}
|
||||
render() {
|
||||
|
|
|
@ -13,28 +13,26 @@ import { Guid } from '../../Shared/Guid';
|
|||
export class Touchable extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.testId = this.props.testId + Guid.newGuid();
|
||||
}
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
DeviceEventEmitter.addListener('KeyEnter' + this.props.testId, this.props.onPress);
|
||||
DeviceEventEmitter.addListener('KeyEnter' + this.testId, this.props.onPress);
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (Platform.OS === 'android') {
|
||||
DeviceEventEmitter.removeListener('KeyEnter' + this.props.testId, this.props.onPress);
|
||||
DeviceEventEmitter.removeListener('KeyEnter' + this.testId, this.props.onPress);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const _a = this.props, { onPress, onLongPress, disabled, accessibilityLabel, accessibilityTraits, accessibilityComponentType, activeOpacity, hitSlop, style } = _a, otherProps = __rest(_a, ["onPress", "onLongPress", "disabled", "accessibilityLabel", "accessibilityTraits", "accessibilityComponentType", "activeOpacity", "hitSlop", "style"]);
|
||||
if (Platform.OS === 'android') {
|
||||
return (React.createElement(TouchableNativeFeedback, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.uniqueTestId, useForeground: true, hitSlop: hitSlop, background: TouchableNativeFeedback.SelectableBackground(), accessibilityLabel: accessibilityLabel },
|
||||
return (React.createElement(TouchableNativeFeedback, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.testId, useForeground: true, hitSlop: hitSlop, background: TouchableNativeFeedback.SelectableBackground(), accessibilityLabel: accessibilityLabel },
|
||||
React.createElement(View, Object.assign({ style: style, onLayout: this.props.onLayout }, otherProps))));
|
||||
}
|
||||
else {
|
||||
return (React.createElement(TouchableOpacity, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.uniqueTestId, activeOpacity: activeOpacity, style: style, hitSlop: hitSlop, accessibilityLabel: accessibilityLabel, onLayout: this.props.onLayout }, otherProps.children));
|
||||
return (React.createElement(TouchableOpacity, { disabled: disabled, onPress: onPress, onLongPress: onLongPress, accessible: true, testID: this.testId, activeOpacity: activeOpacity, style: style, hitSlop: hitSlop, accessibilityLabel: accessibilityLabel, onLayout: this.props.onLayout }, otherProps.children));
|
||||
}
|
||||
}
|
||||
get uniqueTestId() {
|
||||
return this.props.testId + Guid.newGuid();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import * as React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { StyleManager } from '../../Styles/StyleManager';
|
||||
import { ImageBlock } from '../Basic/ImageBlock';
|
||||
import { Touchable } from '../Basic/Touchable';
|
||||
export class Contact extends React.Component {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.onPress = () => {
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(this.props.hiddenFields);
|
||||
}
|
||||
};
|
||||
}
|
||||
render() {
|
||||
if (this.props.onSelect) {
|
||||
return this.renderTouchableBlock();
|
||||
}
|
||||
else {
|
||||
return this.renderNonTouchableBlock();
|
||||
}
|
||||
}
|
||||
renderTouchableBlock() {
|
||||
return (React.createElement(Touchable, { onPress: this.onPress, style: {
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row'
|
||||
} }, this.renderContent()));
|
||||
}
|
||||
renderNonTouchableBlock() {
|
||||
return (React.createElement(View, { alignSelf: 'stretch', flexDirection: 'row' }, this.renderContent()));
|
||||
}
|
||||
renderContent() {
|
||||
return [
|
||||
React.createElement(ImageBlock, { url: this.props.avatar, mode: 'avatar', width: StyleManager.getImageSize('medium'), height: StyleManager.getImageSize('medium') }),
|
||||
React.createElement(View, null,
|
||||
React.createElement(Text, { accessible: true, style: {
|
||||
color: StyleManager.getColor('default', this.props.theme, false),
|
||||
fontSize: StyleManager.getFontSize('default'),
|
||||
fontWeight: StyleManager.getFontWeight('default'),
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: StyleManager.getTextAlign('left'),
|
||||
flexWrap: StyleManager.getWrap(true),
|
||||
} }, this.props.mainInfo),
|
||||
React.createElement(Text, { accessible: true, style: {
|
||||
color: StyleManager.getColor('default', this.props.theme, true),
|
||||
fontSize: StyleManager.getFontSize('small'),
|
||||
fontWeight: StyleManager.getFontWeight('default'),
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: StyleManager.getTextAlign('left'),
|
||||
flexWrap: StyleManager.getWrap(true),
|
||||
} }, this.props.subInfo))
|
||||
];
|
||||
}
|
||||
}
|
|
@ -87,7 +87,7 @@ export class InputBox extends React.Component {
|
|||
return 1;
|
||||
}
|
||||
get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
import * as React from 'react';
|
||||
import { ScrollView, Text, TextInput, View } from 'react-native';
|
||||
import { StyleManager } from '../../Styles/StyleManager';
|
||||
import { SeparateLine } from '../Basic/SeparateLine';
|
||||
export class LabelInput extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onValueChange = (value) => {
|
||||
if (this.props.onRequestSuggestion) {
|
||||
this.props.onRequestSuggestion(value);
|
||||
}
|
||||
};
|
||||
this.onBlur = () => {
|
||||
this.setState({ focused: false }, () => {
|
||||
this.validateInput();
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur();
|
||||
}
|
||||
});
|
||||
};
|
||||
this.onFocus = () => {
|
||||
this.setState({
|
||||
focused: true
|
||||
}, () => {
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus();
|
||||
}
|
||||
});
|
||||
};
|
||||
this.state = {
|
||||
focused: this.props.focused,
|
||||
};
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevState.focused && this.props.focused) {
|
||||
this.setState({
|
||||
focused: true,
|
||||
}, () => {
|
||||
if (this.inputBox) {
|
||||
this.inputBox.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return (React.createElement(View, { style: {
|
||||
flex: this.props.flex,
|
||||
} },
|
||||
this.renderInputArea(),
|
||||
this.renderSuggestions()));
|
||||
}
|
||||
renderInputArea() {
|
||||
return (React.createElement(View, { style: {
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
backgroundColor: this.backgroundColor,
|
||||
borderColor: this.borderColor,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
width: this.props.width,
|
||||
marginTop: this.props.marginTop,
|
||||
marginRight: this.props.marginRight,
|
||||
marginBottom: this.props.marginBottom,
|
||||
marginLeft: this.props.marginLeft,
|
||||
} },
|
||||
this.renderLabels(),
|
||||
this.renderInputBox()));
|
||||
}
|
||||
renderLabels() {
|
||||
if (this.props.labels) {
|
||||
return this.props.labels.map((label, index) => {
|
||||
return (React.createElement(Text, { key: 'Label' + index, style: {
|
||||
fontSize: this.fontSize,
|
||||
fontWeight: this.fontWeight,
|
||||
color: this.backgroundColor,
|
||||
backgroundColor: this.color,
|
||||
paddingTop: this.paddingVertical - 6,
|
||||
paddingBottom: this.paddingVertical - 6,
|
||||
borderRadius: 4,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
marginTop: 6,
|
||||
marginBottom: 6,
|
||||
marginLeft: 6,
|
||||
} }, label.title));
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
renderInputBox() {
|
||||
return (React.createElement(TextInput, { ref: ref => this.inputBox = ref, style: [
|
||||
{
|
||||
flex: 1,
|
||||
color: this.color,
|
||||
fontSize: this.fontSize,
|
||||
fontWeight: this.fontWeight,
|
||||
paddingTop: this.paddingVertical,
|
||||
paddingRight: this.paddingHorizontal,
|
||||
paddingBottom: this.paddingVertical,
|
||||
paddingLeft: this.paddingHorizontal,
|
||||
},
|
||||
this.props.style
|
||||
], multiline: this.isMultiLine, numberOfLines: this.props.numberOfLines, keyboardType: this.props.keyboardType, blurOnSubmit: !this.isMultiLine, placeholder: this.props.placeholder, value: this.props.value, returnKeyType: this.props.returnKeyType, underlineColorAndroid: 'transparent', importantForAccessibility: 'no-hide-descendants', onChangeText: this.onValueChange, onFocus: this.onFocus, onBlur: this.onBlur }));
|
||||
}
|
||||
renderSuggestions() {
|
||||
if (this.props.suggestionView) {
|
||||
return [
|
||||
React.createElement(SeparateLine, { key: 0 }),
|
||||
React.createElement(ScrollView, { key: 1, style: {
|
||||
maxHeight: 200
|
||||
} }, this.props.suggestionView)
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
validateInput() {
|
||||
if (this.props.validateInput) {
|
||||
if (this.props.validateInput(this.props.value)) {
|
||||
console.log('Input: valid');
|
||||
}
|
||||
else {
|
||||
console.log('Input: invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
get isMultiLine() {
|
||||
return this.props.numberOfLines && this.props.numberOfLines > 1;
|
||||
}
|
||||
get fontSize() {
|
||||
return StyleManager.getFontSize('default');
|
||||
}
|
||||
get fontWeight() {
|
||||
return StyleManager.getFontWeight('default');
|
||||
}
|
||||
get paddingVertical() {
|
||||
return 12;
|
||||
}
|
||||
get paddingHorizontal() {
|
||||
return 12;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusColor(this.props.theme);
|
||||
}
|
||||
else {
|
||||
return StyleManager.getInputColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
get backgroundColor() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusBackgroundColor(this.props.theme);
|
||||
}
|
||||
else {
|
||||
return StyleManager.getInputBackgroundColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
get borderColor() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusBorderColor(this.props.theme);
|
||||
}
|
||||
else {
|
||||
return StyleManager.getInputBorderColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export class CardContext {
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
export class FormContext {
|
||||
constructor() {
|
||||
this.formFields = {};
|
||||
this.fieldListeners = {};
|
||||
}
|
||||
static getInstance() {
|
||||
if (FormContext.sharedInstance === undefined) {
|
||||
|
@ -14,6 +15,7 @@ export class FormContext {
|
|||
value: value,
|
||||
validate: validate
|
||||
};
|
||||
this.getFieldListeners(id).forEach((listener) => listener(value));
|
||||
}
|
||||
}
|
||||
getField(id) {
|
||||
|
@ -54,17 +56,6 @@ export class FormContext {
|
|||
}
|
||||
return {};
|
||||
}
|
||||
getCallbackParamData(params) {
|
||||
if (params) {
|
||||
return Object.keys(params).reduce((prev, current) => {
|
||||
let formIndex = params[current];
|
||||
console.log(formIndex);
|
||||
prev[current] = this.getFieldValue(formIndex);
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
return {};
|
||||
}
|
||||
validateField(id) {
|
||||
let field = this.getField(id);
|
||||
if (field) {
|
||||
|
@ -80,4 +71,19 @@ export class FormContext {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
registerFieldListener(id, listener) {
|
||||
if (!this.fieldListeners[id]) {
|
||||
this.fieldListeners[id] = [listener];
|
||||
}
|
||||
else {
|
||||
this.fieldListeners[id].push(listener);
|
||||
}
|
||||
}
|
||||
getFieldListeners(id) {
|
||||
let result = this.fieldListeners[id];
|
||||
if (!result) {
|
||||
result = [];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,9 @@ export class HostContext {
|
|||
registerCallbackHandler(handler) {
|
||||
this.onCallback = handler;
|
||||
}
|
||||
registerSelectActionHandler(handler) {
|
||||
this.onSelectAction = handler;
|
||||
}
|
||||
applyConfig(configJson) {
|
||||
this.config.combine(new HostConfig(configJson));
|
||||
}
|
||||
|
@ -59,6 +62,9 @@ export class HostContext {
|
|||
case ActionType.Submit:
|
||||
callback = this.onSubmit;
|
||||
break;
|
||||
case ActionType.Select:
|
||||
callback = this.onSelectAction;
|
||||
break;
|
||||
case 'focus':
|
||||
callback = this.onFocus;
|
||||
break;
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
import { Image } from 'react-native';
|
||||
export class MediaContext {
|
||||
constructor() {
|
||||
this.mediaSizes = {};
|
||||
}
|
||||
static getInstance() {
|
||||
if (MediaContext.sharedInstance === undefined) {
|
||||
MediaContext.sharedInstance = new MediaContext();
|
||||
}
|
||||
return MediaContext.sharedInstance;
|
||||
}
|
||||
fetchImageSize(url, onSuccess, onFailure) {
|
||||
let cache = this.getSize(url);
|
||||
if (cache) {
|
||||
onSuccess(cache.width, cache.height);
|
||||
}
|
||||
else {
|
||||
Image.getSize(url, (width, height) => {
|
||||
this.cacheSize(url, { width: width, height: height });
|
||||
onSuccess(width, height);
|
||||
}, onFailure);
|
||||
}
|
||||
}
|
||||
cacheSize(url, size) {
|
||||
this.mediaSizes[url] = size;
|
||||
}
|
||||
getSize(url) {
|
||||
return this.mediaSizes[url];
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ import { AbstractElement } from './AbstractElement';
|
|||
export var ActionType;
|
||||
(function (ActionType) {
|
||||
ActionType["OpenUrl"] = "Action.OpenUrl";
|
||||
ActionType["Select"] = "Action.Select";
|
||||
ActionType["Submit"] = "Action.Submit";
|
||||
ActionType["ShowCard"] = "Action.ShowCard";
|
||||
ActionType["Callback"] = "Action.Callback";
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import { ElementUtils } from '../../Utils/ElementUtils';
|
||||
import { ActionElement } from '../Abstract/ActionElement';
|
||||
export class SelectActionElement extends ActionElement {
|
||||
constructor(json, parent) {
|
||||
super(json, parent);
|
||||
this.children = [];
|
||||
if (this.isValid) {
|
||||
this.title = json.selectedTextTitle;
|
||||
this.subTitle = json.selectedTextSubTitle;
|
||||
this.data = json.data;
|
||||
}
|
||||
}
|
||||
get targetFormField() {
|
||||
let targetInput = this.ancestorsAndSelf.find(element => ElementUtils.isSelectActionTarget(element.type));
|
||||
if (targetInput) {
|
||||
return targetInput.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
get scope() {
|
||||
return this.ancestorsAndSelf.find(element => element.parent === undefined);
|
||||
}
|
||||
get requiredProperties() {
|
||||
return ['type', 'selectedTextTitle', 'selectedTextSubTitle', 'data'];
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { ActionType } from '../Abstract/ActionElement';
|
||||
import { OpenUrlActionElement } from '../Actions/OpenUrlAction';
|
||||
import { SelectActionElement } from '../Actions/SelectAction';
|
||||
import { ShowCardActionElement } from '../Actions/ShowCardAction';
|
||||
import { SubmitActionElement } from '../Actions/SubmitAction';
|
||||
export class ActionFactory {
|
||||
|
@ -18,6 +19,9 @@ export class ActionFactory {
|
|||
case ActionType.ShowCard:
|
||||
action = new ShowCardActionElement(json, parent);
|
||||
break;
|
||||
case ActionType.Select:
|
||||
action = new SelectActionElement(json, parent);
|
||||
break;
|
||||
default:
|
||||
action = undefined;
|
||||
break;
|
||||
|
|
|
@ -5,6 +5,10 @@ export class ElementUtils {
|
|||
static isValue(type) {
|
||||
return ElementUtils.valueTypes.indexOf(type) >= 0;
|
||||
}
|
||||
static isSelectActionTarget(type) {
|
||||
return ElementUtils.selectActionTargetTypes.indexOf(type) >= 0;
|
||||
}
|
||||
}
|
||||
ElementUtils.inputTypes = ['Input.Text', 'Input.Number', 'Input.Date', 'Input.Time', 'Input.Toggle', 'Input.ChoiceSet'];
|
||||
ElementUtils.valueTypes = ['Fact', 'Input.Choice'];
|
||||
ElementUtils.selectActionTargetTypes = ['Input.PeoplePicker'];
|
||||
|
|
|
@ -11,6 +11,7 @@ import { FactSetView } from '../Containers/FactSet';
|
|||
import { ImageSetView } from '../Containers/ImageSet';
|
||||
import { DateInputView } from '../Inputs/DateInput';
|
||||
import { NumberInputView } from '../Inputs/NumberInput';
|
||||
import { PeoplePickerView } from '../Inputs/PeoplePicker';
|
||||
import { TextInputView } from '../Inputs/TextInput';
|
||||
import { TimeInputView } from '../Inputs/TimeInput';
|
||||
export class ContentFactory {
|
||||
|
@ -61,6 +62,8 @@ export class ContentFactory {
|
|||
return (React.createElement(DateInputView, { key: 'DateInputView' + index, element: element, index: index, theme: theme }));
|
||||
case ContentElementType.TimeInput:
|
||||
return (React.createElement(TimeInputView, { key: 'TimeInputView' + index, element: element, index: index, theme: theme }));
|
||||
case ContentElementType.PeoplePicker:
|
||||
return (React.createElement(PeoplePickerView, { key: 'PeoplePickerView' + index, element: element, index: index, theme: theme }));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ export class DateInputView extends React.Component {
|
|||
return 1;
|
||||
}
|
||||
get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
import * as React from 'react';
|
||||
import { LabelInput } from '../../Components/Inputs/LabelInput';
|
||||
import { ActionContext } from '../../Contexts/ActionContext';
|
||||
import { FormContext } from '../../Contexts/FormContext';
|
||||
import { ActionType } from '../../Schema/Abstract/ActionElement';
|
||||
import { CardElement } from '../../Schema/Cards/Card';
|
||||
import { ContentFactory } from '../Factories/ContentFactory';
|
||||
import { DebugOutputFactory } from '../Factories/DebugOutputFactory';
|
||||
export class PeoplePickerView extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onBlur = () => {
|
||||
this.setState({ inputFocused: false });
|
||||
};
|
||||
this.onFocus = () => {
|
||||
this.setState({ inputFocused: true });
|
||||
};
|
||||
this.onSuggestionCallback = (data) => {
|
||||
this.setState({
|
||||
suggestionCard: new CardElement(data, this.props.element),
|
||||
});
|
||||
};
|
||||
this.onRequestSuggestion = (input) => {
|
||||
this.setState({
|
||||
value: input,
|
||||
}, () => {
|
||||
const { element } = this.props;
|
||||
if (element.callback) {
|
||||
let callback = ActionContext.getGlobalInstance().getActionEventHandler(element.callback, this.onSuggestionCallback);
|
||||
if (callback) {
|
||||
callback({
|
||||
actionType: ActionType.Callback,
|
||||
func: this.populateApiParams,
|
||||
name: 'populateApiParams'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
this.populateApiParams = (args) => {
|
||||
if (args && args.formValidate) {
|
||||
args.formData = this.extractParamData();
|
||||
}
|
||||
return args;
|
||||
};
|
||||
this.extractParamData = () => {
|
||||
const { element } = this.props;
|
||||
if (element.callback) {
|
||||
const params = element.callback.parameters;
|
||||
if (params) {
|
||||
return Object.keys(params).reduce((prev, current) => {
|
||||
let formIndex = params[current];
|
||||
if (formIndex === element.id) {
|
||||
prev[current] = this.state.value;
|
||||
}
|
||||
else {
|
||||
prev[current] = FormContext.getInstance().getFieldValue(formIndex);
|
||||
}
|
||||
return prev;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
this.storeListener = (value) => {
|
||||
this.setState({
|
||||
selected: JSON.parse(value),
|
||||
suggestionCard: undefined,
|
||||
inputFocused: true,
|
||||
value: '',
|
||||
});
|
||||
};
|
||||
const { element } = this.props;
|
||||
if (element && element.isValid) {
|
||||
this.state = {
|
||||
value: '',
|
||||
inputFocused: false,
|
||||
selected: [],
|
||||
suggestionCard: undefined,
|
||||
};
|
||||
FormContext.getInstance().updateField(element.id, JSON.stringify([]), true);
|
||||
FormContext.getInstance().registerFieldListener(element.id, this.storeListener);
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { element, theme } = this.props;
|
||||
if (!element || !element.isValid) {
|
||||
return DebugOutputFactory.createDebugOutputBanner(element.type + '>>' + element.id + ' is not valid', theme, 'error');
|
||||
}
|
||||
return (React.createElement(LabelInput, { placeholder: element.placeholder, value: this.state.value, focused: this.state.inputFocused, labels: this.labels, suggestionView: ContentFactory.createElement(this.state.suggestionCard, 0, theme), onRequestSuggestion: this.onRequestSuggestion, onFocus: this.onFocus, onBlur: this.onBlur }));
|
||||
}
|
||||
get labels() {
|
||||
return this.state.selected.map((contact) => {
|
||||
return {
|
||||
title: contact.Name
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -75,7 +75,7 @@ export class TimeInputView extends React.Component {
|
|||
return 1;
|
||||
}
|
||||
get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
get color() {
|
||||
if (this.state.focused) {
|
||||
|
|
|
@ -50,6 +50,15 @@ export class CardRootView extends React.PureComponent {
|
|||
}
|
||||
}
|
||||
};
|
||||
this.onSelectAction = (args) => {
|
||||
if (args) {
|
||||
let currentValue = JSON.parse(FormContext.getInstance().getFieldValue(args.action.targetFormField));
|
||||
if (currentValue) {
|
||||
currentValue.push(args.formData);
|
||||
FormContext.getInstance().updateField(args.action.targetFormField, JSON.stringify(currentValue), true);
|
||||
}
|
||||
}
|
||||
};
|
||||
this.validateForm = (args) => {
|
||||
if (args) {
|
||||
args.formValidate = args.action.scope.validateScope();
|
||||
|
@ -68,9 +77,9 @@ export class CardRootView extends React.PureComponent {
|
|||
}
|
||||
return args;
|
||||
};
|
||||
this.populateCallbackParamData = (args) => {
|
||||
if (args && args.formValidate) {
|
||||
args.formData = FormContext.getInstance().getCallbackParamData(args.action.parameters);
|
||||
this.populateSelectActionData = (args) => {
|
||||
if (args) {
|
||||
args.formData = Object.assign({}, (args.action.data || {}));
|
||||
}
|
||||
return args;
|
||||
};
|
||||
|
@ -80,6 +89,7 @@ export class CardRootView extends React.PureComponent {
|
|||
hostContext.registerOpenUrlHandler(this.onOpenUrl);
|
||||
hostContext.registerSubmitHandler(this.onSubmit);
|
||||
hostContext.registerCallbackHandler(this.onCallback);
|
||||
hostContext.registerSelectActionHandler(this.onSelectAction);
|
||||
hostContext.registerFocusHandler(this.props.onFocus);
|
||||
hostContext.registerBlurHandler(this.props.onBlur);
|
||||
hostContext.registerErrorHandler(this.props.onError);
|
||||
|
@ -102,9 +112,9 @@ export class CardRootView extends React.PureComponent {
|
|||
actionType: ActionType.Submit
|
||||
});
|
||||
actionContext.registerHook({
|
||||
func: this.populateCallbackParamData,
|
||||
name: 'populateCallbackParamData',
|
||||
actionType: ActionType.Callback
|
||||
func: this.populateSelectActionData,
|
||||
name: 'populateSelectActionData',
|
||||
actionType: ActionType.Select
|
||||
});
|
||||
}
|
||||
render() {
|
||||
|
|
|
@ -31,67 +31,7 @@ export default class App extends React.Component {
|
|||
padding: 10,
|
||||
backgroundColor: 'white',
|
||||
}}>
|
||||
<AdaptiveCards adaptiveCard={mockData.bingMap} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.dinning} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.vocabulary} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.fact} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.showVideo} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.weatherCompact} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={BingAnswer.heightOfEiffelTower} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={BingAnswer.highestMountionInTheWorld} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={BingAnswer.latestNews} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={BingAnswer.microsoftStock} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={BingAnswer.showMeFunnyVideo} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={BingAnswer.timeInLondon} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={BingAnswer.whatIsTheWeather} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.adaptiveUpdate} overrideStyle={cardOverrideStyle} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.peoplePicker} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.emailSent} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.searchEmail} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.readEmail} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.news} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.adaptiveUpdate} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.flightItinerary} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.flightUpdate} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.foodOrder} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.imageGallery} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.inputForm} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.inputs} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.restaurant} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.solitaire} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.sportsGameUpdate} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.stockUpdate} />
|
||||
{this.renderGap()}
|
||||
<AdaptiveCards adaptiveCard={mockData.weatherLarge} />
|
||||
<AdaptiveCards adaptiveCard={mockData.peoplePicker} onCallback={this.onCallback}/>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
@ -99,4 +39,8 @@ export default class App extends React.Component {
|
|||
renderGap() {
|
||||
return <View style={{ height: cardGap }} />;
|
||||
};
|
||||
|
||||
onCallback = (url, data) => {
|
||||
return Promise.resolve(mockData.peopleSuggestion);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ var searchEmail = require('./skills/search_email.json');
|
|||
var emailSent = require('./skills/email_sent.json');
|
||||
var sendText = require('./skills/send_text.json');
|
||||
var sendTextContact = require('./skills/send_text_contact_disam.json');
|
||||
var peoplePicker = require('./peoplepicker.json');
|
||||
var peoplePicker = require('./peoplePicker.json');
|
||||
var peopleSuggestion = require('./peopleSuggestion.json');
|
||||
var showVideo = require('./showVideo.json');
|
||||
var fact = require('./fact.json');
|
||||
var vocabulary = require('./vocabulary.json');
|
||||
|
@ -52,4 +53,5 @@ exports["default"] = {
|
|||
vocabulary: vocabulary,
|
||||
dinning: dinning,
|
||||
bingMap: bingMap,
|
||||
peopleSuggestion: peopleSuggestion,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,397 @@
|
|||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.0",
|
||||
"body": [
|
||||
{
|
||||
"type": "Container",
|
||||
"id": "peoplePickerContent",
|
||||
"items": [
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=Richard.Zhao%40microsoft.com",
|
||||
"altText": "RZ"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "Richard Zhao",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "Richard.Zhao@microsoft.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "Richard Zhao",
|
||||
"selectedTextSubTitle": "@microsoft.com",
|
||||
"data": {
|
||||
"Name": "Richard Zhao",
|
||||
"Address": "Richard.Zhao@microsoft.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=quanliu%40microsoft.com",
|
||||
"altText": "RL"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "Richard Liu",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "quanliu@microsoft.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "Richard Liu",
|
||||
"selectedTextSubTitle": "@microsoft.com",
|
||||
"data": {
|
||||
"Name": "Richard Liu",
|
||||
"Address": "quanliu@microsoft.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=richzdir%40microsoft.com",
|
||||
"altText": "RD"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "RichZ's directs",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "richzdir@microsoft.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "RichZ's directs",
|
||||
"selectedTextSubTitle": "@microsoft.com",
|
||||
"data": {
|
||||
"Name": "RichZ's directs",
|
||||
"Address": "richzdir@microsoft.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=jordir%40microsoft.com",
|
||||
"altText": "JR"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "Jordi Ribas",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "jordir@microsoft.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "Jordi Ribas",
|
||||
"selectedTextSubTitle": "@microsoft.com",
|
||||
"data": {
|
||||
"Name": "Jordi Ribas",
|
||||
"Address": "jordir@microsoft.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=richq%40microsoft.com",
|
||||
"altText": "RQ"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "Richard Qian",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "richq@microsoft.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "Richard Qian",
|
||||
"selectedTextSubTitle": "@microsoft.com",
|
||||
"data": {
|
||||
"Name": "Richard Qian",
|
||||
"Address": "richq@microsoft.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=richard.zhao%40outlook.com",
|
||||
"altText": "RZ"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "Richard Zhao",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "richard.zhao@outlook.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "Richard Zhao",
|
||||
"selectedTextSubTitle": "@outlook.com",
|
||||
"data": {
|
||||
"Name": "Richard Zhao",
|
||||
"Address": "richard.zhao@outlook.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=Eric.Carter%40microsoft.com",
|
||||
"altText": "EC"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "Eric Carter",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "Eric.Carter@microsoft.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "Eric Carter",
|
||||
"selectedTextSubTitle": "@microsoft.com",
|
||||
"data": {
|
||||
"Name": "Eric Carter",
|
||||
"Address": "Eric.Carter@microsoft.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ColumnSet",
|
||||
"separator": true,
|
||||
"columns": [
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Auto",
|
||||
"items": [
|
||||
{
|
||||
"type": "Image",
|
||||
"size": "medium",
|
||||
"style": "person",
|
||||
"url": "/cortana/autosuggest/people/photo?provider=Office365&uid=eryang%40microsoft.com",
|
||||
"altText": "EY"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Column",
|
||||
"width": "Stretch",
|
||||
"items": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"weight": "bolder",
|
||||
"text": "Eric Yang",
|
||||
"wrap": true
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"isSubtle": true,
|
||||
"text": "eryang@microsoft.com",
|
||||
"wrap": true,
|
||||
"spacing": "none",
|
||||
"separation": "none"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"selectAction": {
|
||||
"type": "Action.Select",
|
||||
"selectedTextTitle": "Eric Yang",
|
||||
"selectedTextSubTitle": "@microsoft.com",
|
||||
"data": {
|
||||
"Name": "Eric Yang",
|
||||
"Address": "eryang@microsoft.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
24
package.json
24
package.json
|
@ -23,20 +23,20 @@
|
|||
"silent": true
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.116",
|
||||
"@types/react": "^16.4.8",
|
||||
"@types/react-native": "^0.56.6",
|
||||
"del": "^3.0.0",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
"gulp-tslint": "^8.1.3",
|
||||
"gulp-typescript": "^5.0.0-alpha.3",
|
||||
"@types/lodash": "latest",
|
||||
"@types/react": "latest",
|
||||
"@types/react-native": "latest",
|
||||
"del": "latest",
|
||||
"gulp": "latest",
|
||||
"gulp-imagemin": "latest",
|
||||
"gulp-tslint": "latest",
|
||||
"gulp-typescript": "latest",
|
||||
"react-devtools": "latest",
|
||||
"run-sequence": "^2.2.1",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^3.0.1"
|
||||
"run-sequence": "latest",
|
||||
"tslint": "latest",
|
||||
"typescript": "latest"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.10"
|
||||
"lodash": "latest"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,19 +25,23 @@ interface IProps {
|
|||
}
|
||||
|
||||
export class Touchable extends React.Component<IProps> {
|
||||
private testId: string;
|
||||
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.testId = this.props.testId + Guid.newGuid();
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (Platform.OS === 'android') {
|
||||
DeviceEventEmitter.addListener('KeyEnter' + this.props.testId, this.props.onPress);
|
||||
DeviceEventEmitter.addListener('KeyEnter' + this.testId, this.props.onPress);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
if (Platform.OS === 'android') {
|
||||
DeviceEventEmitter.removeListener('KeyEnter' + this.props.testId, this.props.onPress);
|
||||
DeviceEventEmitter.removeListener('KeyEnter' + this.testId, this.props.onPress);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +66,7 @@ export class Touchable extends React.Component<IProps> {
|
|||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessible={true}
|
||||
testID={this.uniqueTestId}
|
||||
testID={this.testId}
|
||||
useForeground={true}
|
||||
hitSlop={hitSlop}
|
||||
background={TouchableNativeFeedback.SelectableBackground()}
|
||||
|
@ -81,7 +85,7 @@ export class Touchable extends React.Component<IProps> {
|
|||
onPress={onPress}
|
||||
onLongPress={onLongPress}
|
||||
accessible={true}
|
||||
testID={this.uniqueTestId}
|
||||
testID={this.testId}
|
||||
activeOpacity={activeOpacity}
|
||||
style={style}
|
||||
hitSlop={hitSlop}
|
||||
|
@ -93,8 +97,4 @@ export class Touchable extends React.Component<IProps> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
private get uniqueTestId() {
|
||||
return this.props.testId + Guid.newGuid();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import * as React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { StyleManager } from '../../Styles/StyleManager';
|
||||
import { ImageBlock } from '../Basic/ImageBlock';
|
||||
import { Touchable } from '../Basic/Touchable';
|
||||
|
||||
interface IProps {
|
||||
avatar: string;
|
||||
mainInfo: string;
|
||||
subInfo: string;
|
||||
hiddenFields: any;
|
||||
theme: 'default' | 'emphasis';
|
||||
onSelect?: (data: any) => void;
|
||||
}
|
||||
|
||||
export class Contact extends React.Component<IProps> {
|
||||
public render() {
|
||||
if (this.props.onSelect) {
|
||||
return this.renderTouchableBlock();
|
||||
} else {
|
||||
return this.renderNonTouchableBlock();
|
||||
}
|
||||
}
|
||||
|
||||
private renderTouchableBlock() {
|
||||
return (
|
||||
<Touchable
|
||||
onPress={this.onPress}
|
||||
style={{
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row'
|
||||
}}
|
||||
>
|
||||
{this.renderContent()}
|
||||
</Touchable>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNonTouchableBlock() {
|
||||
return (
|
||||
<View
|
||||
alignSelf='stretch'
|
||||
flexDirection='row'
|
||||
>
|
||||
{this.renderContent()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
private renderContent() {
|
||||
return [
|
||||
<ImageBlock
|
||||
url={this.props.avatar}
|
||||
mode='avatar'
|
||||
width={StyleManager.getImageSize('medium') as number}
|
||||
height={StyleManager.getImageSize('medium') as number}
|
||||
/>,
|
||||
<View>
|
||||
<Text
|
||||
accessible={true}
|
||||
style={{
|
||||
color: StyleManager.getColor('default', this.props.theme, false),
|
||||
fontSize: StyleManager.getFontSize('default'),
|
||||
fontWeight: StyleManager.getFontWeight('default'),
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: StyleManager.getTextAlign('left'),
|
||||
flexWrap: StyleManager.getWrap(true),
|
||||
}}
|
||||
>
|
||||
{this.props.mainInfo}
|
||||
</Text>
|
||||
<Text
|
||||
accessible={true}
|
||||
style={{
|
||||
color: StyleManager.getColor('default', this.props.theme, true),
|
||||
fontSize: StyleManager.getFontSize('small'),
|
||||
fontWeight: StyleManager.getFontWeight('default'),
|
||||
backgroundColor: 'transparent',
|
||||
textAlign: StyleManager.getTextAlign('left'),
|
||||
flexWrap: StyleManager.getWrap(true),
|
||||
}}
|
||||
>
|
||||
{this.props.subInfo}
|
||||
</Text>
|
||||
</View>
|
||||
];
|
||||
}
|
||||
|
||||
private onPress = () => {
|
||||
if (this.props.onSelect) {
|
||||
this.props.onSelect(this.props.hiddenFields);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -147,7 +147,7 @@ export class InputBox extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
|
||||
private get color() {
|
||||
|
|
|
@ -0,0 +1,260 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
KeyboardTypeOptions,
|
||||
ReturnKeyTypeOptions,
|
||||
ScrollView,
|
||||
StyleProp,
|
||||
Text,
|
||||
TextInput,
|
||||
TextStyle,
|
||||
View
|
||||
} from 'react-native';
|
||||
import { StyleManager } from '../../Styles/StyleManager';
|
||||
import { SeparateLine } from '../Basic/SeparateLine';
|
||||
|
||||
interface IProps {
|
||||
placeholder: string;
|
||||
value: string;
|
||||
labels: Array<{ title: string, }>;
|
||||
suggestionView: JSX.Element;
|
||||
focused: boolean;
|
||||
keyboardType?: KeyboardTypeOptions;
|
||||
returnKeyType?: ReturnKeyTypeOptions;
|
||||
numberOfLines?: number;
|
||||
theme?: 'default' | 'emphasis';
|
||||
flex?: number;
|
||||
width?: number;
|
||||
marginTop?: number;
|
||||
marginBottom?: number;
|
||||
marginLeft?: number;
|
||||
marginRight?: number;
|
||||
style?: StyleProp<TextStyle>;
|
||||
onRequestSuggestion?: (input: string) => void;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
validateInput?: (input: string) => boolean;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
focused: boolean;
|
||||
}
|
||||
|
||||
export class LabelInput extends React.Component<IProps, IState> {
|
||||
private inputBox: TextInput;
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
focused: this.props.focused,
|
||||
};
|
||||
}
|
||||
|
||||
public componentDidUpdate(prevProps: IProps, prevState: IState) {
|
||||
if (!prevState.focused && this.props.focused) {
|
||||
this.setState({
|
||||
focused: true,
|
||||
}, () => {
|
||||
if (this.inputBox) {
|
||||
this.inputBox.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
flex: this.props.flex,
|
||||
}}
|
||||
>
|
||||
{this.renderInputArea()}
|
||||
{this.renderSuggestions()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
private renderInputArea() {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
alignSelf: 'stretch',
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
backgroundColor: this.backgroundColor,
|
||||
borderColor: this.borderColor,
|
||||
borderWidth: 1,
|
||||
borderRadius: 4,
|
||||
width: this.props.width,
|
||||
marginTop: this.props.marginTop,
|
||||
marginRight: this.props.marginRight,
|
||||
marginBottom: this.props.marginBottom,
|
||||
marginLeft: this.props.marginLeft,
|
||||
}}
|
||||
>
|
||||
{this.renderLabels()}
|
||||
{this.renderInputBox()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
private renderLabels() {
|
||||
if (this.props.labels) {
|
||||
return this.props.labels.map((label, index) => {
|
||||
return (
|
||||
<Text
|
||||
key={'Label' + index}
|
||||
style={{
|
||||
fontSize: this.fontSize,
|
||||
fontWeight: this.fontWeight,
|
||||
color: this.backgroundColor,
|
||||
backgroundColor: this.color,
|
||||
paddingTop: this.paddingVertical - 6,
|
||||
paddingBottom: this.paddingVertical - 6,
|
||||
borderRadius: 4,
|
||||
paddingLeft: 6,
|
||||
paddingRight: 6,
|
||||
marginTop: 6,
|
||||
marginBottom: 6,
|
||||
marginLeft: 6,
|
||||
}}
|
||||
>
|
||||
{label.title}
|
||||
</Text>
|
||||
);
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private renderInputBox() {
|
||||
return (
|
||||
<TextInput
|
||||
ref={ref => this.inputBox = ref}
|
||||
style={[
|
||||
{
|
||||
flex: 1,
|
||||
color: this.color,
|
||||
fontSize: this.fontSize,
|
||||
fontWeight: this.fontWeight,
|
||||
paddingTop: this.paddingVertical,
|
||||
paddingRight: this.paddingHorizontal,
|
||||
paddingBottom: this.paddingVertical,
|
||||
paddingLeft: this.paddingHorizontal,
|
||||
},
|
||||
this.props.style
|
||||
]}
|
||||
multiline={this.isMultiLine}
|
||||
numberOfLines={this.props.numberOfLines}
|
||||
keyboardType={this.props.keyboardType}
|
||||
blurOnSubmit={!this.isMultiLine}
|
||||
placeholder={this.props.placeholder}
|
||||
value={this.props.value}
|
||||
returnKeyType={this.props.returnKeyType}
|
||||
underlineColorAndroid={'transparent'}
|
||||
importantForAccessibility={'no-hide-descendants'}
|
||||
onChangeText={this.onValueChange}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderSuggestions() {
|
||||
if (this.props.suggestionView) {
|
||||
return [
|
||||
<SeparateLine
|
||||
key={0}
|
||||
/>,
|
||||
<ScrollView
|
||||
key={1}
|
||||
style={{
|
||||
maxHeight: 200
|
||||
}}
|
||||
>
|
||||
{this.props.suggestionView}
|
||||
</ScrollView>
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private onValueChange = (value: string) => {
|
||||
if (this.props.onRequestSuggestion) {
|
||||
this.props.onRequestSuggestion(value);
|
||||
}
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.setState({ focused: false }, () => {
|
||||
this.validateInput();
|
||||
if (this.props.onBlur) {
|
||||
this.props.onBlur();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
this.setState({
|
||||
focused: true
|
||||
}, () => {
|
||||
if (this.props.onFocus) {
|
||||
this.props.onFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private validateInput() {
|
||||
if (this.props.validateInput) {
|
||||
if (this.props.validateInput(this.props.value)) {
|
||||
console.log('Input: valid');
|
||||
} else {
|
||||
console.log('Input: invalid');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private get isMultiLine() {
|
||||
return this.props.numberOfLines && this.props.numberOfLines > 1;
|
||||
}
|
||||
|
||||
private get fontSize() {
|
||||
return StyleManager.getFontSize('default');
|
||||
}
|
||||
|
||||
private get fontWeight() {
|
||||
return StyleManager.getFontWeight('default');
|
||||
}
|
||||
|
||||
private get paddingVertical() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
private get paddingHorizontal() {
|
||||
return 12;
|
||||
}
|
||||
|
||||
private get color() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusColor(this.props.theme);
|
||||
} else {
|
||||
return StyleManager.getInputColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
|
||||
private get backgroundColor() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusBackgroundColor(this.props.theme);
|
||||
} else {
|
||||
return StyleManager.getInputBackgroundColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
|
||||
private get borderColor() {
|
||||
if (this.state.focused) {
|
||||
return StyleManager.getInputFocusBorderColor(this.props.theme);
|
||||
} else {
|
||||
return StyleManager.getInputBorderColor(this.props.theme);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export class CardContext {
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ export interface FormField {
|
|||
|
||||
export class FormContext {
|
||||
private formFields: { [id: string]: FormField } = {};
|
||||
private fieldListeners: { [id: string]: Array<(value: string) => void> } = {};
|
||||
|
||||
private static sharedInstance: FormContext;
|
||||
|
||||
|
@ -23,6 +24,7 @@ export class FormContext {
|
|||
value: value,
|
||||
validate: validate
|
||||
};
|
||||
this.getFieldListeners(id).forEach((listener) => listener(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,18 +69,6 @@ export class FormContext {
|
|||
return {};
|
||||
}
|
||||
|
||||
public getCallbackParamData(params: { [key: string]: string }): { [id: string]: string } {
|
||||
if (params) {
|
||||
return Object.keys(params).reduce((prev, current) => {
|
||||
let formIndex = params[current];
|
||||
console.log(formIndex);
|
||||
prev[current] = this.getFieldValue(formIndex);
|
||||
return prev;
|
||||
}, {} as { [key: string]: string });
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
public validateField(id: string): boolean {
|
||||
let field = this.getField(id);
|
||||
if (field) {
|
||||
|
@ -95,4 +85,20 @@ export class FormContext {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public registerFieldListener(id: string, listener: (value: string) => void) {
|
||||
if (!this.fieldListeners[id]) {
|
||||
this.fieldListeners[id] = [listener];
|
||||
} else {
|
||||
this.fieldListeners[id].push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
public getFieldListeners(id: string) {
|
||||
let result = this.fieldListeners[id];
|
||||
if (!result) {
|
||||
result = [];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { ConfigManager } from '../Config/ConfigManager';
|
|||
import { HostConfig } from '../Config/Types';
|
||||
import { ActionType } from '../Schema/Abstract/ActionElement';
|
||||
import { OpenUrlActionElement } from '../Schema/Actions/OpenUrlAction';
|
||||
import { SelectActionElement } from '../Schema/Actions/SelectAction';
|
||||
import { ShowCardActionElement } from '../Schema/Actions/ShowCardAction';
|
||||
import { SubmitActionElement } from '../Schema/Actions/SubmitAction';
|
||||
import { IElement } from '../Schema/Interfaces/IElement';
|
||||
|
@ -19,6 +20,7 @@ export class HostContext {
|
|||
private onShowCard: (args?: ActionEventHandlerArgs<ShowCardActionElement>) => void;
|
||||
private onSubmit: (args?: ActionEventHandlerArgs<SubmitActionElement>) => void;
|
||||
private onCallback: (args?: ActionEventHandlerArgs<CallbackAction>) => void;
|
||||
private onSelectAction: (args?: ActionEventHandlerArgs<SelectActionElement>) => void;
|
||||
|
||||
private static sharedInstance: HostContext;
|
||||
|
||||
|
@ -69,6 +71,10 @@ export class HostContext {
|
|||
this.onCallback = handler;
|
||||
}
|
||||
|
||||
public registerSelectActionHandler(handler: (args?: ActionEventHandlerArgs<SelectActionElement>) => void) {
|
||||
this.onSelectAction = handler;
|
||||
}
|
||||
|
||||
public applyConfig(configJson: any) {
|
||||
this.config.combine(new HostConfig(configJson));
|
||||
}
|
||||
|
@ -92,6 +98,9 @@ export class HostContext {
|
|||
case ActionType.Submit:
|
||||
callback = this.onSubmit;
|
||||
break;
|
||||
case ActionType.Select:
|
||||
callback = this.onSelectAction;
|
||||
break;
|
||||
case 'focus':
|
||||
callback = this.onFocus;
|
||||
break;
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
import { Image } from 'react-native';
|
||||
import { Dimension } from '../Shared/Types';
|
||||
|
||||
export class MediaContext {
|
||||
private mediaSizes: { [url: string]: Dimension } = {};
|
||||
private static sharedInstance: MediaContext;
|
||||
|
||||
private constructor() { }
|
||||
|
||||
public static getInstance() {
|
||||
if (MediaContext.sharedInstance === undefined) {
|
||||
MediaContext.sharedInstance = new MediaContext();
|
||||
}
|
||||
return MediaContext.sharedInstance;
|
||||
}
|
||||
|
||||
public fetchImageSize(url: string, onSuccess: (width: number, height: number) => void, onFailure: (error: any) => void) {
|
||||
let cache = this.getSize(url);
|
||||
if (cache) {
|
||||
onSuccess(cache.width, cache.height);
|
||||
} else {
|
||||
Image.getSize(
|
||||
url,
|
||||
(width, height) => {
|
||||
this.cacheSize(url, { width: width, height: height });
|
||||
onSuccess(width, height);
|
||||
},
|
||||
onFailure
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public cacheSize(url: string, size: Dimension) {
|
||||
this.mediaSizes[url] = size;
|
||||
}
|
||||
|
||||
public getSize(url: string) {
|
||||
return this.mediaSizes[url];
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import { AbstractElement } from './AbstractElement';
|
|||
|
||||
export enum ActionType {
|
||||
OpenUrl = 'Action.OpenUrl',
|
||||
Select = 'Action.Select',
|
||||
Submit = 'Action.Submit',
|
||||
ShowCard = 'Action.ShowCard',
|
||||
Callback = 'Action.Callback',
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { ElementUtils } from '../../Utils/ElementUtils';
|
||||
import { ActionElement } from '../Abstract/ActionElement';
|
||||
import { IElement } from '../Interfaces/IElement';
|
||||
import { IInput } from '../Interfaces/IInput';
|
||||
import { IScope } from '../Interfaces/IScope';
|
||||
|
||||
export class SelectActionElement extends ActionElement {
|
||||
// Required
|
||||
public readonly title: string;
|
||||
public readonly subTitle: string;
|
||||
public readonly data: any;
|
||||
public readonly children: IElement[] = [];
|
||||
|
||||
constructor(json: any, parent: IElement) {
|
||||
super(json, parent);
|
||||
|
||||
if (this.isValid) {
|
||||
this.title = json.selectedTextTitle;
|
||||
this.subTitle = json.selectedTextSubTitle;
|
||||
this.data = json.data;
|
||||
}
|
||||
}
|
||||
|
||||
public get targetFormField() {
|
||||
let targetInput = this.ancestorsAndSelf.find(element => ElementUtils.isSelectActionTarget(element.type)) as IInput;
|
||||
if (targetInput) {
|
||||
return targetInput.id;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get scope(): IScope {
|
||||
return this.ancestorsAndSelf.find(element => element.parent === undefined) as IScope;
|
||||
}
|
||||
|
||||
public get requiredProperties() {
|
||||
return ['type', 'selectedTextTitle', 'selectedTextSubTitle', 'data'];
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ export class CardElement extends ScopeElement {
|
|||
public readonly actions?: IAction[];
|
||||
public readonly body?: IContent[];
|
||||
public readonly backgroundImage?: string;
|
||||
public readonly cardSource?: string;
|
||||
|
||||
constructor(json: any, parent: IElement) {
|
||||
super(json, parent);
|
||||
|
|
|
@ -11,6 +11,7 @@ export class ColumnSetElement extends ScopeElement {
|
|||
|
||||
if (this.isValid) {
|
||||
this.columns = [];
|
||||
|
||||
if (json.columns) {
|
||||
json.columns.forEach((item: any) => {
|
||||
let column: ColumnElement = new ColumnElement(item, this);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { ActionElement, ActionType } from '../Abstract/ActionElement';
|
||||
import { OpenUrlActionElement } from '../Actions/OpenUrlAction';
|
||||
import { SelectActionElement } from '../Actions/SelectAction';
|
||||
import { ShowCardActionElement } from '../Actions/ShowCardAction';
|
||||
import { SubmitActionElement } from '../Actions/SubmitAction';
|
||||
import { IAction } from '../Interfaces/IAction';
|
||||
|
@ -23,6 +24,9 @@ export class ActionFactory {
|
|||
case ActionType.ShowCard:
|
||||
action = new ShowCardActionElement(json, parent);
|
||||
break;
|
||||
case ActionType.Select:
|
||||
action = new SelectActionElement(json, parent);
|
||||
break;
|
||||
default:
|
||||
action = undefined;
|
||||
break;
|
||||
|
|
|
@ -2,7 +2,7 @@ import { IAction } from './IAction';
|
|||
import { IContent } from './IContent';
|
||||
|
||||
export interface IScope extends IContent {
|
||||
readonly selectAction?: IContent;
|
||||
readonly selectAction?: IAction;
|
||||
readonly backgroundImage?: string | { url: string };
|
||||
readonly action: IAction;
|
||||
readonly inputFields: string[];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export class ElementUtils {
|
||||
private static inputTypes = ['Input.Text', 'Input.Number', 'Input.Date', 'Input.Time', 'Input.Toggle', 'Input.ChoiceSet'];
|
||||
private static valueTypes = ['Fact', 'Input.Choice'];
|
||||
private static selectActionTargetTypes = ['Input.PeoplePicker'];
|
||||
|
||||
public static isInput(type: string) {
|
||||
return ElementUtils.inputTypes.indexOf(type) >= 0;
|
||||
|
@ -9,4 +10,8 @@ export class ElementUtils {
|
|||
public static isValue(type: string) {
|
||||
return ElementUtils.valueTypes.indexOf(type) >= 0;
|
||||
}
|
||||
|
||||
public static isSelectActionTarget(type: string) {
|
||||
return ElementUtils.selectActionTargetTypes.indexOf(type) >= 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import { FactSetElement } from '../../Schema/Containers/FactSet';
|
|||
import { ImageSetElement } from '../../Schema/Containers/ImageSet';
|
||||
import { DateInputElement } from '../../Schema/Inputs/DateInput';
|
||||
import { NumberInputElement } from '../../Schema/Inputs/NumberInput';
|
||||
import { PeoplePickerElement } from '../../Schema/Inputs/PeoplePicker';
|
||||
import { TextInputElement } from '../../Schema/Inputs/TextInput';
|
||||
import { TimeInputElement } from '../../Schema/Inputs/TimeInput';
|
||||
import { ImageView } from '../CardElements/Image';
|
||||
|
@ -22,6 +23,7 @@ import { FactSetView } from '../Containers/FactSet';
|
|||
import { ImageSetView } from '../Containers/ImageSet';
|
||||
import { DateInputView } from '../Inputs/DateInput';
|
||||
import { NumberInputView } from '../Inputs/NumberInput';
|
||||
import { PeoplePickerView } from '../Inputs/PeoplePicker';
|
||||
import { TextInputView } from '../Inputs/TextInput';
|
||||
import { TimeInputView } from '../Inputs/TimeInput';
|
||||
|
||||
|
@ -160,6 +162,15 @@ export class ContentFactory {
|
|||
theme={theme}
|
||||
/>
|
||||
);
|
||||
case ContentElementType.PeoplePicker:
|
||||
return (
|
||||
<PeoplePickerView
|
||||
key={'PeoplePickerView' + index}
|
||||
element={element as PeoplePickerElement}
|
||||
index={index}
|
||||
theme={theme}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ export class DateInputView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
|
||||
private get color() {
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
import * as React from 'react';
|
||||
import { LabelInput } from '../../Components/Inputs/LabelInput';
|
||||
import { ActionContext } from '../../Contexts/ActionContext';
|
||||
import { FormContext } from '../../Contexts/FormContext';
|
||||
import { ActionType } from '../../Schema/Abstract/ActionElement';
|
||||
import { CardElement } from '../../Schema/Cards/Card';
|
||||
import { PeoplePickerElement } from '../../Schema/Inputs/PeoplePicker';
|
||||
import { CallbackAction } from '../../Schema/Internal/CallbackAction';
|
||||
import { ActionEventHandlerArgs } from '../../Shared/Types';
|
||||
import { ContentFactory } from '../Factories/ContentFactory';
|
||||
import { DebugOutputFactory } from '../Factories/DebugOutputFactory';
|
||||
|
||||
interface IProps {
|
||||
index: number;
|
||||
element: PeoplePickerElement;
|
||||
theme: 'default' | 'emphasis';
|
||||
}
|
||||
|
||||
interface IState {
|
||||
value: string;
|
||||
selected: Array<{ Name: string, Address: string }>;
|
||||
inputFocused: boolean;
|
||||
suggestionCard: CardElement;
|
||||
}
|
||||
|
||||
export class PeoplePickerView extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
|
||||
const { element } = this.props;
|
||||
|
||||
if (element && element.isValid) {
|
||||
this.state = {
|
||||
value: '',
|
||||
inputFocused: false,
|
||||
selected: [],
|
||||
suggestionCard: undefined,
|
||||
};
|
||||
FormContext.getInstance().updateField(element.id, JSON.stringify([]), true);
|
||||
FormContext.getInstance().registerFieldListener(element.id, this.storeListener);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
const { element, theme } = this.props;
|
||||
|
||||
if (!element || !element.isValid) {
|
||||
return DebugOutputFactory.createDebugOutputBanner(element.type + '>>' + element.id + ' is not valid', theme, 'error');
|
||||
}
|
||||
|
||||
return (
|
||||
<LabelInput
|
||||
placeholder={element.placeholder}
|
||||
value={this.state.value}
|
||||
focused={this.state.inputFocused}
|
||||
labels={this.labels}
|
||||
suggestionView={ContentFactory.createElement(this.state.suggestionCard, 0, theme)}
|
||||
onRequestSuggestion={this.onRequestSuggestion}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private onBlur = () => {
|
||||
this.setState({ inputFocused: false });
|
||||
}
|
||||
|
||||
private onFocus = () => {
|
||||
this.setState({ inputFocused: true });
|
||||
}
|
||||
|
||||
private onSuggestionCallback = (data: any) => {
|
||||
this.setState({
|
||||
suggestionCard: new CardElement(data, this.props.element),
|
||||
});
|
||||
}
|
||||
|
||||
private onRequestSuggestion = (input: string) => {
|
||||
this.setState({
|
||||
value: input,
|
||||
}, () => {
|
||||
const { element } = this.props;
|
||||
|
||||
if (element.callback) {
|
||||
let callback = ActionContext.getGlobalInstance().getActionEventHandler(element.callback, this.onSuggestionCallback);
|
||||
if (callback) {
|
||||
callback({
|
||||
actionType: ActionType.Callback,
|
||||
func: this.populateApiParams,
|
||||
name: 'populateApiParams'
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private populateApiParams = (args: ActionEventHandlerArgs<CallbackAction>) => {
|
||||
if (args && args.formValidate) {
|
||||
args.formData = this.extractParamData();
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
private extractParamData = () => {
|
||||
const { element } = this.props;
|
||||
|
||||
if (element.callback) {
|
||||
const params = element.callback.parameters;
|
||||
if (params) {
|
||||
return Object.keys(params).reduce((prev, current) => {
|
||||
let formIndex = params[current];
|
||||
if (formIndex === element.id) {
|
||||
prev[current] = this.state.value;
|
||||
} else {
|
||||
prev[current] = FormContext.getInstance().getFieldValue(formIndex);
|
||||
}
|
||||
return prev;
|
||||
}, {} as { [key: string]: string });
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private storeListener = (value: string) => {
|
||||
this.setState({
|
||||
selected: JSON.parse(value),
|
||||
suggestionCard: undefined,
|
||||
inputFocused: true,
|
||||
value: '',
|
||||
});
|
||||
}
|
||||
|
||||
private get labels() {
|
||||
return this.state.selected.map((contact) => {
|
||||
return {
|
||||
title: contact.Name
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -137,7 +137,7 @@ export class TimeInputView extends React.Component<IProps, IState> {
|
|||
}
|
||||
|
||||
private get height() {
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2;
|
||||
return this.fontSize * this.numberOfLine + this.paddingVertical * 2 + 2;
|
||||
}
|
||||
|
||||
private get color() {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { FormContext } from '../Contexts/FormContext';
|
|||
import { HostContext } from '../Contexts/HostContext';
|
||||
import { ActionType } from '../Schema/Abstract/ActionElement';
|
||||
import { OpenUrlActionElement } from '../Schema/Actions/OpenUrlAction';
|
||||
import { SelectActionElement } from '../Schema/Actions/SelectAction';
|
||||
import { SubmitActionElement } from '../Schema/Actions/SubmitAction';
|
||||
import { CardElement } from '../Schema/Cards/Card';
|
||||
import { CallbackAction } from '../Schema/Internal/CallbackAction';
|
||||
|
@ -47,6 +48,7 @@ export class CardRootView extends React.PureComponent<IAdaptiveCardProps> {
|
|||
hostContext.registerOpenUrlHandler(this.onOpenUrl);
|
||||
hostContext.registerSubmitHandler(this.onSubmit);
|
||||
hostContext.registerCallbackHandler(this.onCallback);
|
||||
hostContext.registerSelectActionHandler(this.onSelectAction);
|
||||
|
||||
hostContext.registerFocusHandler(this.props.onFocus);
|
||||
hostContext.registerBlurHandler(this.props.onBlur);
|
||||
|
@ -76,9 +78,9 @@ export class CardRootView extends React.PureComponent<IAdaptiveCardProps> {
|
|||
});
|
||||
|
||||
actionContext.registerHook({
|
||||
func: this.populateCallbackParamData,
|
||||
name: 'populateCallbackParamData',
|
||||
actionType: ActionType.Callback
|
||||
func: this.populateSelectActionData,
|
||||
name: 'populateSelectActionData',
|
||||
actionType: ActionType.Select
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -140,6 +142,16 @@ export class CardRootView extends React.PureComponent<IAdaptiveCardProps> {
|
|||
}
|
||||
}
|
||||
|
||||
private onSelectAction = (args: ActionEventHandlerArgs<SelectActionElement>) => {
|
||||
if (args) {
|
||||
let currentValue = JSON.parse(FormContext.getInstance().getFieldValue(args.action.targetFormField)) as Array<any>;
|
||||
if (currentValue) {
|
||||
currentValue.push(args.formData);
|
||||
FormContext.getInstance().updateField(args.action.targetFormField, JSON.stringify(currentValue), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private validateForm = (args: ActionEventHandlerArgs<SubmitActionElement>) => {
|
||||
if (args) {
|
||||
args.formValidate = args.action.scope.validateScope();
|
||||
|
@ -164,9 +176,11 @@ export class CardRootView extends React.PureComponent<IAdaptiveCardProps> {
|
|||
return args;
|
||||
}
|
||||
|
||||
private populateCallbackParamData = (args: ActionEventHandlerArgs<CallbackAction>) => {
|
||||
if (args && args.formValidate) {
|
||||
args.formData = FormContext.getInstance().getCallbackParamData(args.action.parameters);
|
||||
private populateSelectActionData = (args: ActionEventHandlerArgs<SelectActionElement>) => {
|
||||
if (args) {
|
||||
args.formData = {
|
||||
...(args.action.data || {}),
|
||||
};
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
|
35
yarn.lock
35
yarn.lock
|
@ -6,7 +6,7 @@
|
|||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.0.tgz#a61ab476e5e628cd07a846330df53b85e05c8ce0"
|
||||
|
||||
"@types/lodash@^4.14.116":
|
||||
"@types/lodash@latest":
|
||||
version "4.14.116"
|
||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9"
|
||||
|
||||
|
@ -20,19 +20,26 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-native@^0.56.6":
|
||||
version "0.56.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.56.6.tgz#d9c60e1df95ec0402fa956225558c8a48f37f23f"
|
||||
"@types/react-native@latest":
|
||||
version "0.56.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.56.9.tgz#87e5877b98aed2b1dcd1def6f69927b24c2bd7e5"
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^16.4.8":
|
||||
"@types/react@*":
|
||||
version "16.4.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.8.tgz#ff0440429783df0927bdcd430fa1225f7c08cf36"
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
"@types/react@latest":
|
||||
version "16.4.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.4.11.tgz#330f3d864300f71150dc2d125e48644c098f8770"
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
csstype "^2.2.0"
|
||||
|
||||
ajv@^5.1.0:
|
||||
version "5.5.2"
|
||||
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965"
|
||||
|
@ -825,7 +832,7 @@ define-property@^2.0.2:
|
|||
is-descriptor "^1.0.2"
|
||||
isobject "^3.0.1"
|
||||
|
||||
del@^3.0.0:
|
||||
del@latest:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
|
||||
dependencies:
|
||||
|
@ -1598,7 +1605,7 @@ gulp-decompress@^1.2.0:
|
|||
gulp-util "^3.0.1"
|
||||
readable-stream "^2.0.2"
|
||||
|
||||
gulp-imagemin@^4.1.0:
|
||||
gulp-imagemin@latest:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/gulp-imagemin/-/gulp-imagemin-4.1.0.tgz#5ce347f1d1706fed3cc8f1777ca9094a583b50b7"
|
||||
dependencies:
|
||||
|
@ -1629,7 +1636,7 @@ gulp-sourcemaps@1.6.0:
|
|||
through2 "^2.0.0"
|
||||
vinyl "^1.0.0"
|
||||
|
||||
gulp-tslint@^8.1.3:
|
||||
gulp-tslint@latest:
|
||||
version "8.1.3"
|
||||
resolved "https://registry.yarnpkg.com/gulp-tslint/-/gulp-tslint-8.1.3.tgz#a89ed144038ae861ee7bfea9528272d126a93da1"
|
||||
dependencies:
|
||||
|
@ -1640,7 +1647,7 @@ gulp-tslint@^8.1.3:
|
|||
plugin-error "1.0.1"
|
||||
through "~2.3.8"
|
||||
|
||||
gulp-typescript@^5.0.0-alpha.3:
|
||||
gulp-typescript@latest:
|
||||
version "5.0.0-alpha.3"
|
||||
resolved "https://registry.yarnpkg.com/gulp-typescript/-/gulp-typescript-5.0.0-alpha.3.tgz#c49a306cbbb8c97f5fe8a79208671b6642ef9861"
|
||||
dependencies:
|
||||
|
@ -1674,7 +1681,7 @@ gulp-util@^3.0.0, gulp-util@^3.0.1:
|
|||
through2 "^2.0.0"
|
||||
vinyl "^0.5.0"
|
||||
|
||||
gulp@^3.9.1:
|
||||
gulp@latest:
|
||||
version "3.9.1"
|
||||
resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4"
|
||||
dependencies:
|
||||
|
@ -2437,7 +2444,7 @@ lodash.templatesettings@^3.0.0:
|
|||
lodash._reinterpolate "^3.0.0"
|
||||
lodash.escape "^3.0.0"
|
||||
|
||||
lodash@^4.17.10:
|
||||
lodash@latest:
|
||||
version "4.17.10"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
||||
|
||||
|
@ -3309,7 +3316,7 @@ rimraf@^2.2.6, rimraf@^2.2.8, rimraf@^2.5.4:
|
|||
dependencies:
|
||||
glob "^7.0.5"
|
||||
|
||||
run-sequence@^2.2.1:
|
||||
run-sequence@latest:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/run-sequence/-/run-sequence-2.2.1.tgz#1ce643da36fd8c7ea7e1a9329da33fc2b8898495"
|
||||
dependencies:
|
||||
|
@ -3857,7 +3864,7 @@ tslib@^1.8.0, tslib@^1.8.1:
|
|||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
|
||||
tslint@^5.11.0:
|
||||
tslint@latest:
|
||||
version "5.11.0"
|
||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed"
|
||||
dependencies:
|
||||
|
@ -3898,7 +3905,7 @@ typedarray@^0.0.6:
|
|||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
|
||||
typescript@^3.0.1:
|
||||
typescript@latest:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.1.tgz#43738f29585d3a87575520a4b93ab6026ef11fdb"
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче