Merged PR 740936: Support PeoplePicker

Support PeoplePicker
This commit is contained in:
Dongyu Zhao 2018-08-21 07:14:25 +00:00
Родитель dfdee71720
Коммит b5ec41e080
59 изменённых файлов: 1869 добавлений и 272 удалений

12
dist/Components/Basic/Touchable.js поставляемый
Просмотреть файл

@ -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();
}
}

54
dist/Components/Inputs/Contact.js поставляемый Normal file
Просмотреть файл

@ -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))
];
}
}

2
dist/Components/Inputs/InputBox.js поставляемый
Просмотреть файл

@ -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) {

166
dist/Components/Inputs/LabelInput.js поставляемый Normal file
Просмотреть файл

@ -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);
}
}
}

2
dist/Contexts/CardContext.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
export class CardContext {
}

28
dist/Contexts/FormContext.js поставляемый
Просмотреть файл

@ -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;
}
}

6
dist/Contexts/HostContext.js поставляемый
Просмотреть файл

@ -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;

30
dist/Contexts/MediaContext.js поставляемый
Просмотреть файл

@ -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];
}
}

1
dist/Schema/Abstract/ActionElement.js поставляемый
Просмотреть файл

@ -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";

26
dist/Schema/Actions/SelectAction.js поставляемый Normal file
Просмотреть файл

@ -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'];
}
}

4
dist/Schema/Factories/ActionFactory.js поставляемый
Просмотреть файл

@ -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;

4
dist/Utils/ElementUtils.js поставляемый
Просмотреть файл

@ -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'];

3
dist/Views/Factories/ContentFactory.js поставляемый
Просмотреть файл

@ -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;
}

2
dist/Views/Inputs/DateInput.js поставляемый
Просмотреть файл

@ -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) {

99
dist/Views/Inputs/PeoplePicker.js поставляемый Normal file
Просмотреть файл

@ -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
};
});
}
}

2
dist/Views/Inputs/TimeInput.js поставляемый
Просмотреть файл

@ -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) {

22
dist/Views/Root.js поставляемый
Просмотреть файл

@ -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"
}
}
}
]
}
]
}

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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"