From c29df5e75ab0357c21b9861f835d4b8036085a07 Mon Sep 17 00:00:00 2001 From: Vivek Kozhisseri Date: Wed, 29 May 2019 15:45:07 +0530 Subject: [PATCH] Reactnative/develop (#2990) * Reactnative/experimental (#2918) * Support for HeroCard and ThumbnailCard as an experiment * Added react-native navigation support for Visualizer * Updated visualizer with experimental items * Revert "Added react-native navigation support for Visualizer" This reverts commit 80688e5444b32caceef130fdcb0a18550084d520. * Updated Visualiser with tab to show the experimental items * windows more option icon * Show payloads tab by default * Reactnative/fallback - Model layer and fallback feature (#2988) * Support for HeroCard and ThumbnailCard as an experiment * Added react-native navigation support for Visualizer * Updated visualizer with experimental items * Revert "Added react-native navigation support for Visualizer" This reverts commit 80688e5444b32caceef130fdcb0a18550084d520. * Updated Visualiser with tab to show the experimental items * windows more option icon * Show payloads tab by default * Model layer added and changes for fallback feature * Media element updated in model * Review comment fixes * Fixed column visible issue and number input issue * Added isNumber check for w2hRatio for Image --- .../AdaptiveCardBuilder.js | 196 ++++++++++++ .../adaptive-card-builder/README.md | 25 ++ .../adaptive-card-builder/constants.js | 32 ++ .../payloads/Hero.Card.2.json | 23 ++ .../payloads/Hero.Card.json | 30 ++ .../payloads/Thumbnail.card.2.json | 27 ++ .../payloads/Thumbnail.card.json | 32 ++ .../adaptive-card-builder/payloads/index.js | 18 ++ .../reactnative/src/adaptive-card.js | 94 +++--- .../src/components/actions/action-button.js | 2 +- .../src/components/containers/column.js | 17 +- .../src/components/containers/container.js | 8 + .../src/components/elements/image.js | 6 +- .../src/components/inputs/number-input.js | 2 +- .../src/components/registration/registry.js | 35 ++- .../reactnative/src/models/action-model.js | 50 +++ .../reactnative/src/models/base-model.js | 39 +++ .../reactnative/src/models/container-model.js | 155 ++++++++++ .../reactnative/src/models/element-model.js | 75 +++++ .../community/reactnative/src/models/index.js | 6 + .../reactnative/src/models/input-model.js | 130 ++++++++ .../reactnative/src/models/model-factory.js | 84 +++++ .../community/reactnative/src/utils/enums.js | 27 ++ .../community/reactnative/src/utils/util.js | 11 + .../src/visualizer/assets/more-android.png | Bin 0 -> 297 bytes .../src/visualizer/assets/more-ios.png | Bin 0 -> 283 bytes .../src/visualizer/assets/right_arrow.png | Bin 0 -> 27960 bytes .../reactnative/src/visualizer/constants.js | 7 + .../src/visualizer/more-options.js | 77 +++++ .../reactnative/src/visualizer/payloads.js | 111 +++++++ .../payloads/payloads/Fallback.json | 79 +++++ .../src/visualizer/payloads/payloads/index.js | 4 + .../reactnative/src/visualizer/visualizer.js | 290 +++++++++++------- 33 files changed, 1503 insertions(+), 189 deletions(-) create mode 100644 source/community/reactnative/experimental/adaptive-card-builder/AdaptiveCardBuilder.js create mode 100644 source/community/reactnative/experimental/adaptive-card-builder/README.md create mode 100644 source/community/reactnative/experimental/adaptive-card-builder/constants.js create mode 100644 source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.2.json create mode 100644 source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.json create mode 100755 source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.2.json create mode 100755 source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.json create mode 100644 source/community/reactnative/experimental/adaptive-card-builder/payloads/index.js create mode 100644 source/community/reactnative/src/models/action-model.js create mode 100644 source/community/reactnative/src/models/base-model.js create mode 100644 source/community/reactnative/src/models/container-model.js create mode 100644 source/community/reactnative/src/models/element-model.js create mode 100644 source/community/reactnative/src/models/index.js create mode 100644 source/community/reactnative/src/models/input-model.js create mode 100644 source/community/reactnative/src/models/model-factory.js create mode 100644 source/community/reactnative/src/visualizer/assets/more-android.png create mode 100644 source/community/reactnative/src/visualizer/assets/more-ios.png create mode 100644 source/community/reactnative/src/visualizer/assets/right_arrow.png create mode 100644 source/community/reactnative/src/visualizer/constants.js create mode 100644 source/community/reactnative/src/visualizer/more-options.js create mode 100644 source/community/reactnative/src/visualizer/payloads.js create mode 100644 source/community/reactnative/src/visualizer/payloads/payloads/Fallback.json diff --git a/source/community/reactnative/experimental/adaptive-card-builder/AdaptiveCardBuilder.js b/source/community/reactnative/experimental/adaptive-card-builder/AdaptiveCardBuilder.js new file mode 100644 index 0000000000..2d902d2c9c --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/AdaptiveCardBuilder.js @@ -0,0 +1,196 @@ + +import * as Constants from './constants'; + +/** + * @description - This builder creates adaptive card from `HeroCard` / `ThumbnailCard` . + * @param cardContent - Content of the card to be converted + * @param type - Type of the card. + */ +export const buildAdaptiveCard = (cardContent, type) => { + + const AdaptiveCard = { + "type": Constants.TypeAdaptiveCard, + "version": Constants.AdaptiveCardVersion, + "body": [], + "actions": [] + } + + const ColumnSet = { + "type": Constants.TypeColumnSet, + "columns": [ + { + "type": Constants.TypeColumn, + "width": Constants.ThumbNailWidth, + "items": [] + }, + { + "type": Constants.TypeColumn, + "width": Constants.WidthStretch, + "items": [] + } + ] + } + + if (cardContent && type) { + switch (type) { + case Constants.TypeHeroCard: + pushTextBlocks(cardContent, AdaptiveCard.body); + pushImages(cardContent, AdaptiveCard.body); + pushActions(cardContent, AdaptiveCard.actions); + break; + case Constants.TypeThumbnailCard: + pushImages(cardContent, ColumnSet.columns[0].items); + pushTextBlocks(cardContent, ColumnSet.columns[1].items); + pushActions(cardContent, ColumnSet.columns[1].items, true); + AdaptiveCard.body.push(ColumnSet); + delete AdaptiveCard.actions; + break; + default: + elementsContainer = []; + break; + } + + /** + * `tap` to container `selectAction` + */ + if (cardContent.tap && cardContent.tap.type && cardContent.tap.value) { + const body = AdaptiveCard.body; + AdaptiveCard.body = []; + const containerBody = {}; + containerBody.type = Constants.TypeContainer; + containerBody.items = body; + containerBody.selectAction = cardAction(cardContent.tap); + AdaptiveCard.body[0] = containerBody; + } + + } + return AdaptiveCard; +} + +/** + * @description - This method pushes text blocks to the adaptive card container. + * @param cardContent - Content of the card to be converted. + * @param textBlockContainer - Container where the tex blocks to be inserted + */ +pushTextBlocks = (cardContent, textBlockContainer) => { + if (isNotEmpty(cardContent.title)) + textBlockContainer.push(textBlock(cardContent.title, Constants.TypeTitle)); + if (isNotEmpty(cardContent.subtitle)) + textBlockContainer.push(textBlock(cardContent.subtitle, Constants.TypeSubTitle)); + if (isNotEmpty(cardContent.text)) + textBlockContainer.push(textBlock(cardContent.text)); + return textBlockContainer; +} + +/** + * @description - This method pushes images to the adaptive card container. + * @param cardContent - Content of the card to be converted. + * @param imageContainer - Container where the images to be inserted + */ +pushImages = (cardContent, imageContainer) => { + if (cardContent.images && cardContent.images.length > 0) { + cardContent.images.forEach(image => { + imageContainer.push(cardImage(image)); + }) + } +} + +/** + * @description - This method pushes actions to the adaptive card container. + * @param cardContent - Content of the card to be converted. + * @param actionContainer - Container where the actions to be inserted + * @param isNested - A boolean decides where the actions need to be added in the container + */ + pushActions = (cardContent, actionContainer, isNested) => { + if (cardContent.buttons && cardContent.buttons.length > 0) { + if (!isNested) { + cardContent.buttons.forEach(button => { + actionContainer.push(cardAction(button)); + }) + } else { + const nestedContainer = { + "type": Constants.TypeColumnSet + } + const columns = []; + cardContent.buttons.forEach(button => { + const column = { + "type": Constants.TypeColumn, + "items": [] + }; + column.items.push(cardAction(button)); + columns.push(column); + }) + nestedContainer.columns = columns; + actionContainer.push(nestedContainer);; + } + } +} + +/** + * @description - Convert card types `title, subtitle and text` to Adaptive card type format. + * @param content - The content to be displayed + * @param type - Element type to be converted. + */ +textBlock = (content, type) => { + const textBlock = {}; + textBlock.type = Constants.TypeTextBlock; + textBlock.text = content; + switch (type) { + case Constants.TypeTitle: + textBlock.size = Constants.SizeMedium; + textBlock.weight = Constants.WeightBold; + break; + case Constants.TypeSubTitle: + textBlock.isSubtle = true; + textBlock.wrap = true; + break; + default: + textBlock.wrap = true; + break; + } + return textBlock; + +} + +/** + * @description - Convert `button` to `actions` + * @param button - button content. + */ +cardAction = (button) => { + const action = Object.assign({}, button); + if (button.type === Constants.TypeOpenUrl) { + action.type = Constants.ActionOpenUrl; + action.url = button.value; + } else { + action.data = { + "type": button.type, + "data": button.value + }; + action.type = Constants.ActionSubmit; + } + delete action.value; + return action; +} + +/** + * @description - Format `image` properties to adaptive card format + * @param image - image content. + */ +cardImage = (image) => { + const cardImage = Object.assign({}, image); + cardImage.type = Constants.ImageType; + cardImage.size = Constants.ImageSize; + if (image.tap) { + cardImage.selectAction = cardAction(image.tap); + delete cardImage.tap; + } + return cardImage; +} + +/** + * @description - Checks whether a given value is empty or not. + * @param param - param need to be checked. + */ +isNotEmpty = (param) => { + return param && param !== Constants.EmptyString && param.length > 0; +} \ No newline at end of file diff --git a/source/community/reactnative/experimental/adaptive-card-builder/README.md b/source/community/reactnative/experimental/adaptive-card-builder/README.md new file mode 100644 index 0000000000..b57205dd8c --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/README.md @@ -0,0 +1,25 @@ + +## Description + +This module uses our [AdaptiveCards renderer package](https://www.npmjs.com/package/adaptivecards-reactnative) and a mapper to support card types other than AdaptiveCards. Here we map the payloads of MS cards such as HeroCard, ThumbnailCard, etc to Adaptive card payload format. + +## Supported Cards + +As of now, this mapper supports below types of cards. + +* [ Hero Card ](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/cards/cards-reference#example-hero-card) +* [ Thumbnail Card](https://docs.microsoft.com/en-us/microsoftteams/platform/concepts/cards/cards-reference#thumbnail-card) + +## Payload Mapper + +The payload mapper file `AdaptiveCardBuilder.js` builds the payload for AdaptiveCard from any HeroCard/ThumbnailCard payload content. + +## Test + +Follow the below steps to run the sample project in local machine. + +* Clone the Repo `https://github.com/microsoft/AdaptiveCards.git` +* Navigate to source/reactnative/ **>** Run **`npm install`** +* iOS **> `react-native run-ios`** +* Android **> `react-native run-android`** +* Click on **`Other Cards`** section \ No newline at end of file diff --git a/source/community/reactnative/experimental/adaptive-card-builder/constants.js b/source/community/reactnative/experimental/adaptive-card-builder/constants.js new file mode 100644 index 0000000000..61525addec --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/constants.js @@ -0,0 +1,32 @@ +export const TypeAdaptiveCard = "AdaptiveCard"; +export const AdaptiveCardVersion = "1.2"; + +export const TypeHeroCard = "application/vnd.microsoft.card.hero"; +export const TypeThumbnailCard = "application/vnd.microsoft.card.thumbnail"; + +export const EmptyString = ""; + +export const FullWidth = '100%'; + +export const CenterString = "center"; + +export const ActionOpenUrl = "Action.OpenUrl"; +export const ActionSubmit = "Action.Submit"; +export const TypeOpenUrl = "openUrl"; + +export const ImageType = "Image"; +export const ImageSize = "auto"; + +export const TypeTextBlock = "TextBlock"; +export const TypeTitle = "Title"; +export const TypeSubTitle = "SubTitle"; +export const TypeContainer = "Container"; +export const TypeColumnSet = "ColumnSet"; +export const TypeColumn = "Column"; + + +export const SizeMedium = "medium"; +export const WeightBold = "bold"; + +export const ThumbNailWidth = "100px"; +export const WidthStretch = "stretch"; \ No newline at end of file diff --git a/source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.2.json b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.2.json new file mode 100644 index 0000000000..13e2f660a5 --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.2.json @@ -0,0 +1,23 @@ +{ + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "Seattle Center Monorail", + "images": [ + { + "url":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Seattle_monorail01_2008-02-25.jpg/1024px-Seattle_monorail01_2008-02-25.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Official website", + "value": "https://www.seattlemonorail.com" + }, + { + "type": "Imback", + "title": "Okay", + "value": "Test Okay" + } + ] + } + } \ No newline at end of file diff --git a/source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.json b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.json new file mode 100644 index 0000000000..04475f0408 --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Hero.Card.json @@ -0,0 +1,30 @@ +{ + "contentType": "application/vnd.microsoft.card.hero", + "content": { + "title": "Seattle Center Monorail", + "subtitle": "Seattle Center Monorail", + "text": "The Seattle Center Monorail is an elevated train line between Seattle Center (near the Space Needle) and downtown Seattle. It was built for the 1962 World's Fair. Its original two trains, completed in 1961, are still in service.", + "images": [ + { + "url":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Seattle_monorail01_2008-02-25.jpg/1024px-Seattle_monorail01_2008-02-25.jpg" + } + ], + "buttons": [ + { + "type": "openUrl", + "title": "Official website", + "value": "https://www.seattlemonorail.com" + }, + { + "type": "openUrl", + "title": "Wikipeda page", + "value": "https://en.wikipedia.org/wiki/Seattle_Center_Monorail" + } + ], + "tap" :{ + "type": "Submit", + "title": "Official website", + "value": "It's a Container Select Action" + } + } + } \ No newline at end of file diff --git a/source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.2.json b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.2.json new file mode 100755 index 0000000000..2edcf62aae --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.2.json @@ -0,0 +1,27 @@ +{ + "contentType": "application/vnd.microsoft.card.thumbnail", + "content": { + "title": "Bender", + "text": "Bender Bending Rodríguez is a main character in the animated television series Futurama. He was created by series creators Matt Groening and David X. Cohen, and is voiced by John DiMaggio", + "images": [ + { + "url": "https://upload.wikimedia.org/wikipedia/en/a/a6/Bender_Rodriguez.png", + "alt": "Bender Rodríguez" + } + ], + "buttons": [ + { + "type": "imBack", + "title": "Thumbs Up", + "image": "http://moopz.com/assets_c/2012/06/emoji-thumbs-up-150-thumb-autox125-140616.jpg", + "value": "I like it" + }, + { + "type": "imBack", + "title": "Thumbs Down", + "image": "http://yourfaceisstupid.com/wp-content/uploads/2014/08/thumbs-down.png", + "value": "I don't like it" + } + ] + } + } \ No newline at end of file diff --git a/source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.json b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.json new file mode 100755 index 0000000000..b7b7a5031e --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/payloads/Thumbnail.card.json @@ -0,0 +1,32 @@ +{ + "contentType": "application/vnd.microsoft.card.thumbnail", + "content": { + "title": "Bender", + "subtitle": "Tale of a robot who dared to love.", + "text": "Bender Bending Rodríguez is a main character in the animated television series Futurama. He was created by series creators Matt Groening and David X. Cohen, and is voiced by John DiMaggio", + "images": [ + { + "url": "https://upload.wikimedia.org/wikipedia/en/a/a6/Bender_Rodriguez.png", + "alt": "Bender Rodríguez" + } + ], + "buttons": [ + { + "type": "imBack", + "title": "Thumbs Up", + "image": "http://moopz.com/assets_c/2012/06/emoji-thumbs-up-150-thumb-autox125-140616.jpg", + "value": "I like it" + }, + { + "type": "imBack", + "title": "Thumbs Down", + "image": "http://yourfaceisstupid.com/wp-content/uploads/2014/08/thumbs-down.png", + "value": "I don't like it" + } + ], + "tap": { + "type": "imBack", + "value": "Tapped it!" + } + } + } \ No newline at end of file diff --git a/source/community/reactnative/experimental/adaptive-card-builder/payloads/index.js b/source/community/reactnative/experimental/adaptive-card-builder/payloads/index.js new file mode 100644 index 0000000000..c96ed190ab --- /dev/null +++ b/source/community/reactnative/experimental/adaptive-card-builder/payloads/index.js @@ -0,0 +1,18 @@ +export default payloads = [ + { + "title": "Hero card with all props", + "json": require('./Hero.Card.json') + }, + { + "title": "Hero card", + "json": require('./Hero.Card.2.json') + }, + { + "title": "Thumbnail card with all props", + "json": require('./Thumbnail.card.json') + }, + { + "title": "Thumbnail card", + "json": require('./Thumbnail.card.2.json') + } +] \ No newline at end of file diff --git a/source/community/reactnative/src/adaptive-card.js b/source/community/reactnative/src/adaptive-card.js index f9fc54eb6e..60be683749 100644 --- a/source/community/reactnative/src/adaptive-card.js +++ b/source/community/reactnative/src/adaptive-card.js @@ -20,6 +20,7 @@ import { SelectAction } from './components/actions'; import ResourceInformation from './utils/resource-information'; import { ContainerWrapper } from './components/containers'; import { ThemeConfigManager } from './utils/theme-config'; +import { ModelFactory } from './models'; export default class AdaptiveCard extends React.Component { @@ -32,9 +33,15 @@ export default class AdaptiveCard extends React.Component { this.payload = props.payload; + if (this.props.isActionShowCard) { + this.cardModel = props.payload; + }else{ + this.cardModel = ModelFactory.createElement(props.payload); + } this.state = { showErrors: false, payload: this.payload, + cardModel: this.cardModel, } // hostConfig @@ -53,31 +60,12 @@ export default class AdaptiveCard extends React.Component { } toggleVisibilityForElementWithID = (idArray) => { - this.toggleObjectWithIDArray(this.payload, [...idArray]); + this.toggleCardModelObject(this.cardModel, [...idArray]); this.setState({ - payload: this.payload, + cardModel: this.cardModel, }) } - /** - * @description Toggles the visibility of the components by their ids recursively - * @param {Object} object - the object to be searched for ids - * @param {Array} idArrayValue - the array of IDs to be toggled - */ - toggleObjectWithIDArray = (object, idArrayValue) => { - if (idArrayValue.length === 0) return - if (object.hasOwnProperty('id')) { - this.checkTargetElementsForID(object, idArrayValue); - if (idArrayValue.length === 0) return - } - Object.keys(object).forEach(element => { - if (idArrayValue.length === 0) return - if (typeof object[element] == "object") { - this.toggleObjectWithIDArray(object[element], idArrayValue); - } - }); - return; - } /** * @description Checks the elements recursively to change the isVisible property @@ -88,7 +76,7 @@ export default class AdaptiveCard extends React.Component { targetElements.forEach(target => { if (target instanceof String || typeof target === 'string'){ if(target == object["id"]){ - this.toggleObjectVisibility(object); + object.isVisible = !object.isVisible; var index = targetElements.indexOf(object["id"]); if (index !== -1) targetElements.splice(index, 1); return @@ -98,7 +86,7 @@ export default class AdaptiveCard extends React.Component { if (!Utils.isNullOrEmpty(target["isVisible"])) { object.isVisible = target["isVisible"] } else { - this.toggleObjectVisibility(object); + object.isVisible = !object.isVisible; } var index = targetElements.indexOf(target); if (index !== -1) targetElements.splice(index, 1); @@ -109,36 +97,32 @@ export default class AdaptiveCard extends React.Component { } /** - * @description Toggles the isVisible property of an Object - * @param {Object} object - the object to be toggles - */ - toggleObjectVisibility = (object) => { - if (!Utils.isNullOrEmpty(object.isVisible)) { - object.isVisible = !object.isVisible - } else { - object.isVisible = false; - } - } - - /** - * @description Conveniece method to toggle the visibility of the component by a single id recursively + * @description Method to toggle the visibility of the component by looking in its children * @param {Object} object - the object to be searched for ids * @param {string} idValue - the id of the component to be toggled */ - toggleObjectWithID = (object, idValue) => { - if (object.hasOwnProperty('id') && object["id"] == idValue) { - if (!Utils.isNullOrEmpty(object.isVisible)) { - object.isVisible = !object.isVisible - } else { - object.isVisible = false; - } - return; + + toggleCardModelObject = (object,idArrayValue) => { + if (idArrayValue.length === 0) return + if (object.hasOwnProperty('id')) { + this.checkTargetElementsForID(object, idArrayValue); + if (idArrayValue.length === 0) return + } + if((object.children !== undefined) && object.children.length !== 0 ){ + object.children.forEach(element => { + if (idArrayValue.length === 0) return + this.toggleCardModelObject(element, idArrayValue); + }); } - Object.keys(object).forEach(element => { - if (typeof object[element] == "object") { - this.toggleObjectWithID(object[element], idValue); + //Adaptive cards has actions array in addition to the body which is added as children + if(object.type === 'AdaptiveCard'){ + if((object.actions !== undefined) && object.actions.length !== 0 ){ + object.actions.forEach(element => { + if (idArrayValue.length === 0) return + this.toggleCardModelObject(element, idArrayValue); + }); } - }); + } return; } @@ -174,23 +158,21 @@ export default class AdaptiveCard extends React.Component { */ parsePayload = () => { let children = []; - const { body } = this.state.payload; - if (!body) + if (this.state.cardModel.children.length === 0) return children; - - children = Registry.getManager().parseRegistryComponents(body, this.onParseError); - return children.map((ChildElement, index) => React.cloneElement(ChildElement, { containerStyle: this.state.payload.style, isFirst: index === 0 })); + children = Registry.getManager().parseRegistryComponents(this.state.cardModel.children, this.onParseError); + return children.map((ChildElement, index) => React.cloneElement(ChildElement, { containerStyle: this.state.cardModel.style, isFirst: index === 0 })); } getAdaptiveCardContent() { var adaptiveCardContent = ( - + {this.parsePayload()} - {!Utils.isNullOrEmpty(this.state.payload.actions) && - } + {!Utils.isNullOrEmpty(this.state.cardModel.actions) && + } ); diff --git a/source/community/reactnative/src/components/actions/action-button.js b/source/community/reactnative/src/components/actions/action-button.js index 12c029d8cf..d525646a47 100644 --- a/source/community/reactnative/src/components/actions/action-button.js +++ b/source/community/reactnative/src/components/actions/action-button.js @@ -132,7 +132,7 @@ export class ActionButton extends React.Component { } changeShowCardState = () => { - this.showCardHandler(this.payload.card); + this.showCardHandler(this.payload.children[0]); } parseHostConfig() { diff --git a/source/community/reactnative/src/components/containers/column.js b/source/community/reactnative/src/components/containers/column.js index 0217ceeac2..0cd27ee086 100644 --- a/source/community/reactnative/src/components/containers/column.js +++ b/source/community/reactnative/src/components/containers/column.js @@ -44,6 +44,13 @@ export class Column extends React.Component { if (!this.column) return children; + if (this.column.isFallbackActivated) { + if (this.column.fallbackType == "drop") { + return null; + } else if (!Utils.isNullOrEmpty(element.fallback)) { + return Registry.getManager().parseComponent(this.column.fallback, this.context.onParseError); + } + } // parse elements if (!Utils.isNullOrEmpty(this.column.items) && (this.column.isVisible !== false)) { children = Registry.getManager().parseRegistryComponents(this.column.items, this.context.onParseError); @@ -138,11 +145,11 @@ export class Column extends React.Component { widthPercentage = (pixelWidth / deviceWidth) * 100 } else if (width == Constants.AlignStretch) { - containerStyle.push({ flex: 1 }) + containerStyle.push({ flex: 2}); } else if (width == Constants.Auto) { if (!containsNumber) { - containerStyle.push({ alignSelf: 'auto' }) + containerStyle.push({ flex: 1 }) } else { widthPercentage = defaultWidthPercentage } @@ -213,12 +220,12 @@ export class Column extends React.Component { spacingStyle.push({ marginLeft: this.spacing }) } spacingStyle.push({ flexGrow: 1 }); - + let widthPercentage = this.calculateWidthPercentage(containerViewStyle); if (!Utils.isNullOrEmpty(widthPercentage)) { let spacePercentage = widthPercentage; - if (!this.isForemostElement()) - spacePercentage = (this.spacing / deviceWidth) * 100 +spacePercentage; + if (!this.isForemostElement()) + spacePercentage = (this.spacing / deviceWidth) * 100 + spacePercentage; containerViewStyle.push({ width: spacePercentage.toString() + '%' }); } diff --git a/source/community/reactnative/src/components/containers/container.js b/source/community/reactnative/src/components/containers/container.js index 415228c7ab..0c72e649a0 100644 --- a/source/community/reactnative/src/components/containers/container.js +++ b/source/community/reactnative/src/components/containers/container.js @@ -37,6 +37,14 @@ export class Container extends React.Component { return children; } + if (this.payload.isFallbackActivated){ + if(this.payload.fallbackType == "drop"){ + return null; + }else if(!Utils.isNullOrEmpty(element.fallback)){ + return Registry.getManager().parseComponent(this.payload.fallback,this.context.onParseError); + } + } + children = Registry.getManager().parseRegistryComponents(this.payload.items, this.context.onParseError); return children.map((ChildElement, index) => React.cloneElement(ChildElement, { containerStyle: this.payload.style, isFirst: index === 0 })); } diff --git a/source/community/reactnative/src/components/elements/image.js b/source/community/reactnative/src/components/elements/image.js index 462b5c2d8f..50f3bd0375 100644 --- a/source/community/reactnative/src/components/elements/image.js +++ b/source/community/reactnative/src/components/elements/image.js @@ -111,8 +111,10 @@ export class Img extends React.Component { let sizeStyle = []; let sizeValue = Utils.parseHostConfigEnum(Enums.Size, this.payload.size, Enums.Size.Auto) /* This W2H ratio is calculated to determine the height required w.r.to pre-determined sizes */ - const w2hratio = this.state.imageHeight / this.state.imageWidth; - + var w2hratio = this.state.imageHeight / this.state.imageWidth; + if (!Utils.isaNumber(w2hratio)) { + w2hratio = 1; + } /** * Scenario 1 : Either height or width has string value (Ex: '80px'), * use the integer portion. diff --git a/source/community/reactnative/src/components/inputs/number-input.js b/source/community/reactnative/src/components/inputs/number-input.js index cbbf20d2ff..0ce12b56d1 100644 --- a/source/community/reactnative/src/components/inputs/number-input.js +++ b/source/community/reactnative/src/components/inputs/number-input.js @@ -24,7 +24,7 @@ export class NumberInput extends React.Component { this.parse(); this.state = { isError: this.isInvalid(this.payload.value), - numberValue: this.payload.value.toString(), + numberValue: this.payload.value ? this.payload.value.toString() : Constants.EmptyString } } diff --git a/source/community/reactnative/src/components/registration/registry.js b/source/community/reactnative/src/components/registration/registry.js index f13c1b18fa..b4f4b7e326 100644 --- a/source/community/reactnative/src/components/registration/registry.js +++ b/source/community/reactnative/src/components/registration/registry.js @@ -102,11 +102,11 @@ export class Registry { } } RequiredPropertySchema = { - 'Container': { 'type': 'Container', 'items': 'Array' }, + 'Container': { 'type': 'Container', 'children': 'Array' }, 'ColumnSet': { 'type': 'ColumnSet' }, 'Column': { 'items': 'Array' }, - 'FactSet': { 'type': 'FactSet', 'facts': 'Array' }, - 'ImageSet': { 'type': 'ImageSet', 'images': 'Array' }, + 'FactSet': { 'type': 'FactSet', 'children': 'Array' }, + 'ImageSet': { 'type': 'ImageSet', 'children': 'Array' }, 'TextBlock': { 'type': 'TextBlock' }, 'Image': { 'type': 'Image', 'url': 'String' }, @@ -123,7 +123,7 @@ export class Registry { 'Action.ShowCard': { 'type': 'Action.ShowCard', 'card': 'Object' }, 'Action.Submit': { 'type': 'Action.Submit' }, 'Action.OpenUrl': { 'type': 'Action.OpenUrl', 'url': 'String' }, - 'ActionSet': { 'type': 'ActionSet', 'actions': 'Array' }, + 'ActionSet': { 'type': 'ActionSet' }, }; /** @@ -143,12 +143,33 @@ export class Registry { if (!componentArray) return parsedElement; componentArray.map((element, index) => { - const Element = this.getComponentOfType(element.type); + const currentElement = this.parseComponent(element,onParseError,index); + if (currentElement){ + parsedElement.push(currentElement); + } + }); + return parsedElement; + } + + /** + * @description Parse an individual component + * @param {Array} element - Json + */ + parseComponent = (element, onParseError, index = 0) => { + const Element = this.getComponentOfType(element.type); + if (Element) { /** * Validate the schema and invoke onParseError handler incase of any error. */ let isValid = true; + if (element.isFallbackActivated){ + if(element.fallbackType == "drop"){ + return null; + }else if(!Utils.isNullOrEmpty(element.fallback)){ + return this.parseComponent(element.fallback,onParseError); + } + } for (var key in this.validateSchemaForType(element.type)) { if (!element.hasOwnProperty(key)) { let error = { "error": Enums.ValidationError.PropertyCantBeNull, "message": `Required property ${key} for ${element.type} is missing` }; @@ -159,7 +180,7 @@ export class Registry { if (isValid) { if (element.isVisible !== false) { const elementKey = Utils.isNullOrEmpty(element.id) ? `${element.type}-${index}` : `${element.type}-${index}-${element.id}`; - parsedElement.push(); + return (); } } } else { @@ -167,8 +188,6 @@ export class Registry { onParseError(error); return null; } - }); - return parsedElement; } } diff --git a/source/community/reactnative/src/models/action-model.js b/source/community/reactnative/src/models/action-model.js new file mode 100644 index 0000000000..a0a859d2e5 --- /dev/null +++ b/source/community/reactnative/src/models/action-model.js @@ -0,0 +1,50 @@ +import {BaseModel} from './base-model' +import { ElementType } from '../utils/enums' +import { ModelFactory } from './model-factory' + +export class BaseActionModel extends BaseModel{ + constructor(payload, parent) { + super(payload, parent); + this.title = payload.title; + this.iconUrl = payload.iconUrl; + this.sentiment = payload.sentiment; + this.ignoreInputValidation = payload.ignoreInputValidation; + } +} + +export class SubmitActionModel extends BaseActionModel{ + data; + type = ElementType.ActionSubmit; + constructor(payload, parent) { + super(payload, parent); + this.data = payload.data; + } +} + +export class OpenUrlActionModel extends BaseActionModel{ + url; + type = ElementType.ActionOpenUrl; + constructor(payload, parent) { + super(payload, parent); + this.url = payload.url; + } +} + +export class ShowCardActionModel extends BaseActionModel{ + card; + type = ElementType.ActionShowCard; + constructor(payload, parent) { + super(payload, parent); + this.card = ModelFactory.createElement(payload.card, this); + this.children = [this.card]; + } +} + +export class ToggleVisibilityActionModel extends BaseActionModel{ + targetElements; + type = ElementType.ActionToggleVisibility; + constructor(payload, parent) { + super(payload, parent); + this.targetElements = payload.targetElements; + } +} diff --git a/source/community/reactnative/src/models/base-model.js b/source/community/reactnative/src/models/base-model.js new file mode 100644 index 0000000000..1b6dabedf3 --- /dev/null +++ b/source/community/reactnative/src/models/base-model.js @@ -0,0 +1,39 @@ +import * as Utils from '../utils/util'; +import { ModelFactory } from './model-factory' + +export class BaseModel { + id; + type; + parent; + children = []; + payload; + selectAction; + isVisible = true; + isFallbackActivated = false; + fallback; + fallbackType; + + constructor(payload, parent) { + this.parent = parent; + this.id = payload.id; + this.spacing = payload.spacing; + this.separator = payload.separator; + if (this.id === undefined) { + this.id = Utils.generateID(); + } + if (payload.selectAction) { + this.selectAction = payload.selectAction; + } + if (payload.isVisible){ + this.isVisible = payload.isVisible; + } + if (payload.fallback){ + if (payload.fallback == "drop"){ + this.fallbackType = "drop" + }else{ + this.fallback = ModelFactory.createElement(payload.fallback, parent); + } + } + + } +} diff --git a/source/community/reactnative/src/models/container-model.js b/source/community/reactnative/src/models/container-model.js new file mode 100644 index 0000000000..36acf5cc42 --- /dev/null +++ b/source/community/reactnative/src/models/container-model.js @@ -0,0 +1,155 @@ +import { BaseModel } from './base-model' +import { ModelFactory } from './model-factory'; +import { ElementType } from '../utils/enums' +import {ImageModel} from './element-model' + +class BaseContainerModel extends BaseModel { + constructor(payload, parent) { + super(payload, parent); + if (payload.backgroundImage) { + this.backgroundImage = payload.backgroundImage;; + } + this.verticalContentAlignment = payload.verticalContentAlignment; + this.style = payload.style; + this.bleed = payload.bleed; + } +} + +export class AdaptiveCardModel extends BaseContainerModel { + constructor(payload, parent) { + super(payload, parent); + this.type = ElementType.AdaptiveCard; + this.fallbackText = payload.fallbackText; + this.version = payload.version; + this.speak = payload.speak; + this.children = []; + this.actions = []; + this.children.push(...ModelFactory.createGroup(payload.body, this)); + this.actions.push(...ModelFactory.createGroup(payload.actions, this)); + this.show = true; + } +} + +export class ContainerModel extends BaseContainerModel { + constructor(payload, parent) { + super(payload, parent); + this.type = ElementType.Container; + this.children = []; + this.children.push(...ModelFactory.createGroup(payload.items, this)); + this.height = payload.height; + } + + get items(){ + return this.children; + } +} + +export class ColumnSetModel extends BaseContainerModel { + constructor(payload, parent) { + super(payload, parent); + this.type = ElementType.ColumnSet; + this.children = []; + if (payload.columns) { + payload.columns.forEach((item) => { + let column = new ColumnModel(item, this); + if (column) { + this.children.push(column); + } + }); + } + this.height = payload.height; + } + get columns() { + return this.children; + } +} + +export class ColumnModel extends BaseContainerModel { + constructor(payload, parent) { + super(payload, parent); + this.type = ElementType.Column; + this.children = []; + this.children.push(...ModelFactory.createGroup(payload.items, this)); + this.height = payload.height; + if (payload.width) { + if (payload.width === 'auto' || payload.width === 'stretch') { + this.width = payload.width; + } + else { + let columnWidth = parseInt(payload.width, 10); + if (columnWidth < 0) { + columnWidth = 0; + } + this.width = columnWidth; + } + } + } + get items() { + return this.children; + } +} + +export class FactModel { + constructor(payload) { + this.type = 'Fact'; + this.title = payload.title; + this.value = payload.value; + } +} + +export class FactSetModel extends BaseContainerModel { + constructor(payload, parent) { + super(payload, parent); + this.type = ElementType.FactSet; + this.children = []; + if (payload.facts) { + payload.facts.forEach((item) => { + let fact = new FactModel(item); + if (fact) { + this.children.push(fact); + } + }); + } + } + get facts() { + return this.children; + } +} + +export class ImageSetModel extends BaseContainerModel { + constructor(payload, parent) { + super(payload, parent); + this.type = ElementType.ImageSet; + this.children = []; + this.imageSize = payload.imageSize; + if (payload.images) { + payload.images.forEach((item) => { + let image = new ImageModel(item, this); + if (image) { + this.children.push(image); + } + }); + } + } + get images() { + return this.children; + } +} + +export class ActionSetModel extends BaseContainerModel{ + constructor(payload, parent) { + super(payload, parent); + this.type = ElementType.ActionSet; + this.children = []; + this.children.push(...ModelFactory.createGroup(payload.actions, this)); + this.height = payload.height; + } + + get actions() { + return this.children; + } + +} + + + diff --git a/source/community/reactnative/src/models/element-model.js b/source/community/reactnative/src/models/element-model.js new file mode 100644 index 0000000000..c7bc1adac5 --- /dev/null +++ b/source/community/reactnative/src/models/element-model.js @@ -0,0 +1,75 @@ +import {BaseModel} from './base-model' +import { ElementType } from '../utils/enums' + + +export class TextBlockModel extends BaseModel { + type = ElementType.TextBlock; + + constructor(payload, parent) { + super(payload, parent); + this.text = payload.text; + this.color = payload.color; + this.horizontalAlignment = payload.horizontalAlignment; + this.isSubtle = payload.isSubtle || false; + this.maxLines = payload.maxLines; + this.size = payload.size; + this.weight = payload.weight; + this.wrap = payload.wrap || false; + this.fontStyle = payload.fontStyle; + } +} + +export class ImageModel extends BaseModel { + type = ElementType.Image; + + constructor(payload, parent) { + super(payload, parent); + this.url = payload.url; + this.altText = payload.altText; + this.horizontalAlignment = payload.horizontalAlignment; + this.size = payload.size; + this.style = payload.style; + this.backgroundColor = payload.backgroundColor; + this.size = payload.size; + this.width = payload.width; + this.height = payload.height; + } +} + +export class MediaModel extends BaseModel { + type = ElementType.Media; + sources = []; + + constructor(payload, parent) { + super(payload, parent); + + if (payload.sources) { + payload.sources.forEach((item) => { + if (item) { + this.sources.push(item); + } + }); + } + this.poster = payload.poster; + this.altText = payload.altText; + } +} + +export class RichTextBlockModel extends BaseModel { + type = ElementType.RichTextBlock; + + constructor(payload, parent) { + super(payload, parent); + this.text = payload.text; + this.color = payload.color; + this.horizontalAlignment = payload.horizontalAlignment; + this.isSubtle = payload.isSubtle || false; + this.maxLines = payload.maxLines; + this.size = payload.size; + this.weight = payload.weight; + this.wrap = payload.wrap || false; + this.paragraphs = payload.paragraphs; + this.fontStyle = payload.fontStyle; + } +} + diff --git a/source/community/reactnative/src/models/index.js b/source/community/reactnative/src/models/index.js new file mode 100644 index 0000000000..1080ae405d --- /dev/null +++ b/source/community/reactnative/src/models/index.js @@ -0,0 +1,6 @@ +export * from './base-model' +export * from './action-model' +export * from './container-model' +export * from './element-model' +export * from './input-model' +export * from './model-factory' \ No newline at end of file diff --git a/source/community/reactnative/src/models/input-model.js b/source/community/reactnative/src/models/input-model.js new file mode 100644 index 0000000000..63683ab20d --- /dev/null +++ b/source/community/reactnative/src/models/input-model.js @@ -0,0 +1,130 @@ +import {BaseModel} from './base-model' +import { ElementType } from '../utils/enums' + +export class BaseInputModel extends BaseModel{ + constructor(payload, parent) { + super(payload, parent); + this.placeholder = payload.placeholder; + this.value = payload.value; + this.inlineAction = payload.inlineAction; + this.validation = payload.validation; + } +} + +export class TextInputModel extends BaseInputModel { + type = ElementType.TextInput; + + constructor(payload, parent) { + super(payload, parent); + this.isMultiline = payload.isMultiline || false; + this.maxLength = payload.maxLength; + this.style = payload.style; + } + +} + +export class NumberInputModel extends BaseInputModel { + type = ElementType.NumberInput; + + constructor(payload, parent) { + super(payload, parent); + this.max = payload.max; + this.min = payload.min; + } +} + +export class DateInputModel extends BaseInputModel { + type = ElementType.DateInput; + + constructor(payload, parent) { + super(payload, parent); + this.max = payload.max; + this.min = payload.min; + } +} + +export class TimeInputModel extends BaseInputModel { + type = ElementType.TimeInput; + + constructor(payload, parent) { + super(payload, parent); + this.max = payload.max; + this.min = payload.min; + } + +} + +export class ToggleInputModel extends BaseInputModel { + type = ElementType.ToggleInput; + + constructor(payload, parent) { + super(payload, parent); + this.title = payload.title; + this.valueOff = payload.valueOff; + this.valueOn = payload.valueOn; + this.value = payload.value === payload.valueOn; + this.wrap = payload.wrap; + } +} + +export class ChoiceSetModel extends BaseInputModel { + type = ElementType.ChoiceSetInput; + + constructor(payload, parent) { + super(payload, parent); + this.isMultiSelect = payload.isMultiSelect; + this.style = payload.style; + this.wrap = payload.wrap; + if (payload.choices) { + payload.choices.forEach((item, index) => { + let choice = new ChoiceModel(item, this); + if (choice) { + this.children.push(choice); + } + }); + } + + if (payload.value) { + let selected = (payload.value).split(','); + if (selected) { + selected.forEach(current => { + let choice = this.choices.find(c => c.value === current); + if (choice) { + choice.selected = true; + } + }); + } + } + } + + get choices() { + return this.children; + } + + +} + +export class ChoiceModel extends BaseInputModel { + parent; + type = 'Input.Choice'; + title; + value; + selected; + + constructor(payload, parent) { + super(payload, parent); + + this.title = payload.title; + this.value = payload.value; + this.selected = false; + } +} + + + + + + + + + diff --git a/source/community/reactnative/src/models/model-factory.js b/source/community/reactnative/src/models/model-factory.js new file mode 100644 index 0000000000..325759c69b --- /dev/null +++ b/source/community/reactnative/src/models/model-factory.js @@ -0,0 +1,84 @@ +import * as Models from './index' +import { ElementType } from '../utils/enums' +import * as Utils from '../utils/util' + +export class ModelFactory { + static createElement(payload, parent) { + if (!payload) { + return undefined; + } + switch (payload.type) { + case ElementType.Image: + return new Models.ImageModel(payload, parent); + case ElementType.Media: + return new Models.MediaModel(payload, parent); + case ElementType.TextBlock: + return new Models.TextBlockModel(payload, parent); + case ElementType.RichTextBlock: + return new Models.RichTextBlockModel(payload, parent); + case ElementType.Column: + return new Models.ColumnModel(payload, parent); + case ElementType.ColumnSet: + return new Models.ColumnSetModel(payload, parent); + case ElementType.Container: + return new Models.ContainerModel(payload, parent); + case ElementType.FactSet: + return new Models.FactSetModel(payload, parent); + case ElementType.ImageSet: + return new Models.ImageSetModel(payload, parent); + case ElementType.TextInput: + return new Models.TextInputModel(payload, parent); + case ElementType.DateInput: + return new Models.DateInputModel(payload, parent); + case ElementType.TimeInput: + return new Models.TimeInputModel(payload, parent); + case ElementType.NumberInput: + return new Models.NumberInputModel(payload, parent); + case ElementType.ChoiceSetInput: + return new Models.ChoiceSetModel(payload, parent); + case ElementType.ToggleInput: + return new Models.ToggleInputModel(payload, parent); + case ElementType.AdaptiveCard: + return new Models.AdaptiveCardModel(payload, parent); + case ElementType.ActionOpenUrl: + return new Models.OpenUrlActionModel(payload, parent); + case ElementType.ActionSubmit: + return new Models.SubmitActionModel(payload, parent); + case ElementType.ActionShowCard: + return new Models.ShowCardActionModel(payload, parent); + case ElementType.ActionToggleVisibility: + return new Models.ToggleVisibilityActionModel(payload, parent); + case ElementType.ActionSet: + return new Models.ActionSetModel(payload, parent); + default: + return ModelFactory.checkForFallBack(payload, parent); + } + } + static createGroup(payload, parent) { + let modelGroup = []; + if (payload && payload.length > 0) { + payload.forEach((item) => { + let model = ModelFactory.createElement(item, parent); + if (model) { + modelGroup.push(model); + } + }); + } + return modelGroup; + } + + static checkForFallBack (payload, parent) { + if (!Utils.isNullOrEmpty(payload.fallback)){ + if (payload.fallback !== "drop"){ + return ModelFactory.createElement(payload.fallback, parent); + } + else{ + return undefined; + } + } + else{ + parent.isFallbackActivated = true; + return undefined; + } + } +} \ No newline at end of file diff --git a/source/community/reactnative/src/utils/enums.js b/source/community/reactnative/src/utils/enums.js index 1ef58198c3..441f946d79 100644 --- a/source/community/reactnative/src/utils/enums.js +++ b/source/community/reactnative/src/utils/enums.js @@ -150,4 +150,31 @@ export const Sentiment = Object.freeze({ export const FontStyle = Object.freeze({ Default: 0, Monospace: 1, +}); + +export const ElementType = Object.freeze({ + AdaptiveCard: 'AdaptiveCard', + Container: 'Container', + ColumnSet: 'ColumnSet', + ImageSet: 'ImageSet', + Column: 'Column', + FactSet: 'FactSet', + + TextInput: 'Input.Text', + NumberInput: 'Input.Number', + ToggleInput: 'Input.Toggle', + DateInput: 'Input.Date', + TimeInput: 'Input.Time', + ChoiceSetInput: 'Input.ChoiceSet', + + TextBlock: 'TextBlock', + Media: 'Media', + Image: 'Image', + RichTextBlock: 'RichTextBlock', + + ActionShowCard: 'Action.ShowCard', + ActionSubmit: 'Action.Submit', + ActionOpenUrl: 'Action.OpenUrl', + ActionToggleVisibility: 'Action.ToggleVisibility', + ActionSet: 'ActionSet' }); \ No newline at end of file diff --git a/source/community/reactnative/src/utils/util.js b/source/community/reactnative/src/utils/util.js index 996731b182..5e983ea35e 100644 --- a/source/community/reactnative/src/utils/util.js +++ b/source/community/reactnative/src/utils/util.js @@ -211,4 +211,15 @@ export function hexToRGB(color) { else { return color; } +} + +/** + * @description Generates an unique ID for the element if its not part of the payload + * @return {string} ID as string + */ +/** + * argb in hex to css rgba + */ +export function generateID() { + return Math.random().toString(36).substr(2, 9); } \ No newline at end of file diff --git a/source/community/reactnative/src/visualizer/assets/more-android.png b/source/community/reactnative/src/visualizer/assets/more-android.png new file mode 100644 index 0000000000000000000000000000000000000000..20314351c0605244f0f98ba23d2eace283705b83 GIT binary patch literal 297 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpUt51uZLAr*{oryt}xWFX-3S+VN- zWETBdKZ>(N4L@#phWcJrim^bCO?6BsyW2;bl@753gQTeRBZ z4sGZ_}2g&h`2|gYUDz z?EMG4XHEO6q&LCK-uv>VV@cHx##ise9v2l0lXCApdhGtmk{Qz_^aQgTe~DWM4fi*tNJ literal 0 HcmV?d00001 diff --git a/source/community/reactnative/src/visualizer/assets/more-ios.png b/source/community/reactnative/src/visualizer/assets/more-ios.png new file mode 100644 index 0000000000000000000000000000000000000000..a5030293894adf2ff283b6c18a8c79f0bd74c256 GIT binary patch literal 283 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 + {options.map((option, index) => (this.renderOption(option, index)))} + + ); + } + + /** + * @description This renders each options + */ + renderOption = (option, key) => ( + this.onPress(option)}> + + {option} + + + ); + + /** + * @description Click action for options + */ + onPress = (option) => { + if (option == Constants.DialogFlow) + Alert.alert( + Constants.InProgress, + Constants.InProgressText, + [ + { text: "Okay", onPress: () => this.props.closeMoreOptions() }, + ], + { cancelable: false } + ) + else this.props.moreOptionClick(option); + } +} + +const styles = StyleSheet.create({ + container: { + marginRight: 10, + marginTop: Platform.OS === "ios" ? 50 : 10, + alignSelf: "flex-end", + paddingHorizontal: 20, + backgroundColor: "white" + }, + moreOption: { + paddingTop: 10, + paddingBottom: 10, + }, + option: { + color: "black", + fontWeight: "300", + fontSize: 17 + } +}); \ No newline at end of file diff --git a/source/community/reactnative/src/visualizer/payloads.js b/source/community/reactnative/src/visualizer/payloads.js new file mode 100644 index 0000000000..d71d38c269 --- /dev/null +++ b/source/community/reactnative/src/visualizer/payloads.js @@ -0,0 +1,111 @@ + +import AdaptiveCardPayloads from './payloads/payloads'; +import OtherCardPayloads from '../../experimental/adaptive-card-builder/payloads'; + +// sample scenarios +const calendarReminderPayload = require('./payloads/scenarios/calendar-reminder.json'); +const flightUpdatePayload = require('./payloads/scenarios/flight-update.json'); +const inputFormPayload = require('./payloads/scenarios/input-form.json'); +const restaurantPayload = require('./payloads/scenarios/restaurant.json'); +const containerPayload = require('./payloads/scenarios/container-item.json'); +const weatherPayload = require('./payloads/scenarios/weather-large.json'); +const activityUpdatePayload = require('./payloads/scenarios/activity-update.json'); +const foodOrderPayload = require('./payloads/scenarios/food-order.json'); +const imageGalleryPayload = require('./payloads/scenarios/image-gallery.json'); +const sportingEventPayload = require('./payloads/scenarios/sporting-event.json'); +const mediaPayload = require('./payloads/scenarios/media.json'); +const markdownPayload = require('./payloads/scenarios/markdown.json'); + +/** + * @description Return the unique element types present in the given payload json + * @param {object} json - payload json + * @return {Array} - Array of element types +*/ +const getTags = (json) => { + let tags = new Set(); + // elements + json.body.map(element => { + tags.add(element.type); + }); + // actions + if (json.actions && json.actions.length > 0) { + tags.add("Actions"); + } + return Array.from(tags); +} + +const AdaptiveCardScenarios = [{ + title: 'Calendar reminder', + json: calendarReminderPayload, + tags: getTags(calendarReminderPayload), + icon: require('./assets/calendar.png') +}, { + title: 'Flight update', + json: flightUpdatePayload, + tags: getTags(flightUpdatePayload), + icon: require('./assets/flight.png') +}, { + title: 'Weather Large', + json: weatherPayload, + tags: getTags(weatherPayload), + icon: require('./assets/cloud.png') +}, { + title: 'Activity Update', + json: activityUpdatePayload, + tags: getTags(activityUpdatePayload), + icon: require('./assets/done.png') +}, +{ + title: 'Food order', + json: foodOrderPayload, + tags: getTags(foodOrderPayload), + icon: require('./assets/fastfood.png') +}, +{ + title: 'Image gallery', + json: imageGalleryPayload, + tags: getTags(imageGalleryPayload), + icon: require('./assets/photo_library.png') +}, +{ + title: 'Sporting event', + json: sportingEventPayload, + tags: getTags(sportingEventPayload), + icon: require('./assets/run.png') +}, { + title: 'Restaurant', + json: restaurantPayload, + tags: getTags(restaurantPayload), + icon: require('./assets/restaurant.png') +}, +{ + title: 'Input form', + json: inputFormPayload, + tags: getTags(inputFormPayload), + icon: require('./assets/form.png') +}, +{ + title: 'Media', + json: mediaPayload, + tags: getTags(mediaPayload), + icon: require('./assets/video_library.png') +}, +{ + title: 'Stock Update', + json: containerPayload, + tags: getTags(containerPayload), + icon: require('./assets/square.png') +}, +{ + title: 'Markdown', + json: markdownPayload, + tags: getTags(markdownPayload), + icon: require('./assets/code.png') +}]; + + +export { AdaptiveCardPayloads, AdaptiveCardScenarios, OtherCardPayloads }; + + + + diff --git a/source/community/reactnative/src/visualizer/payloads/payloads/Fallback.json b/source/community/reactnative/src/visualizer/payloads/payloads/Fallback.json new file mode 100644 index 0000000000..4fd7dc74d7 --- /dev/null +++ b/source/community/reactnative/src/visualizer/payloads/payloads/Fallback.json @@ -0,0 +1,79 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.1", + "body": [ + { + "type": "Graph", + "poster": "https://...", + "sources": [], + "fallback": { + "type": "TextBlock", + "text": "This is a graph text", + "wrap": true + } + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "This text block makes no sense without the graph below it." + }, + { + "type": "Graph", + "xAxis": "Profit" + } + ], + "fallback": "drop" + }, + { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "This text block makes no sense without the graph below it." + }, + { + "type": "Graph", + "xAxis": "Profit" + } + ], + "fallback": { + "type": "Container", + "items": [ + { + "type": "TextBlock", + "text": "To view a graph, click this card" + } + ] + } + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "View", + "url": "https://msn.com" + }, + { + "type": "Action.ShowCard", + "title": "Set due date", + "card": { + "type": "AdaptiveCard", + "body": [ + { + "type": "Input.Date", + "id": "dueDate" + } + ], + "actions": [ + { + "type": "Action.Submit", + "title": "OK" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/source/community/reactnative/src/visualizer/payloads/payloads/index.js b/source/community/reactnative/src/visualizer/payloads/payloads/index.js index f0d945941d..43eb410b03 100644 --- a/source/community/reactnative/src/visualizer/payloads/payloads/index.js +++ b/source/community/reactnative/src/visualizer/payloads/payloads/index.js @@ -155,6 +155,10 @@ export default payloads = [ "title": "FactSetWrapping.json", "json": require('./FactSetWrapping.json') }, + { + "title": "Fallback.json", + "json": require('./Fallback.json') + }, { "title": "Feedback.json", "json": require('./Feedback.json') diff --git a/source/community/reactnative/src/visualizer/visualizer.js b/source/community/reactnative/src/visualizer/visualizer.js index d12ac4f627..73fffbedaf 100644 --- a/source/community/reactnative/src/visualizer/visualizer.js +++ b/source/community/reactnative/src/visualizer/visualizer.js @@ -8,123 +8,55 @@ import { FlatList, View, StyleSheet, + Image, + Text, + Platform, + TouchableOpacity, Modal } from 'react-native'; import Renderer from './renderer'; import { PayloadItem } from './payload-item.js'; import SegmentedControl from './segmented-control'; +import MoreOptions from './more-options'; +import * as Constants from './constants'; +import * as Payloads from './payloads'; +import * as AdaptiveCardBuilder from "../../experimental/adaptive-card-builder/AdaptiveCardBuilder"; + + +const moreIcon = Platform.select({ + ios: require("./assets/more-ios.png"), + android: require("./assets/more-android.png"), + windows: require("./assets/more-android.png") +}) -// sample scenarios -const calendarReminderPayload = require('./payloads/scenarios/calendar-reminder.json'); -const flightUpdatePayload = require('./payloads/scenarios/flight-update.json'); -const inputFormPayload = require('./payloads/scenarios/input-form.json'); -const restaturantPayload = require('./payloads/scenarios/restaurant.json'); -const containerPayload = require('./payloads/scenarios/container-item.json'); -const weatherPayload = require('./payloads/scenarios/weather-large.json'); -const activityUpdatePayload = require('./payloads/scenarios/activity-update.json'); -const foodOrderPayload = require('./payloads/scenarios/food-order.json'); -const imageGalleryPayload = require('./payloads/scenarios/image-gallery.json'); -const sportingEventPayload = require('./payloads/scenarios/sporting-event.json'); -const mediaPayload = require('./payloads/scenarios/media.json'); -const markdownPayload = require('./payloads/scenarios/markdown.json'); - -import payloads from '../visualizer/payloads/payloads/'; export default class Visualizer extends React.Component { + state = { isModalVisible: false, + isMoreVisible: false, selectedPayload: null, + activeOption: Constants.AdaptiveCards, + payloads: Payloads.AdaptiveCardPayloads, + scenarios: Payloads.AdaptiveCardScenarios, activeIndex: 0 // payload - scenarios selector }; constructor(props) { super(props); - - this.scenarios = [{ - title: 'Calendar reminder', - json: calendarReminderPayload, - tags: this.getTags(calendarReminderPayload), - icon: require('./assets/calendar.png') - }, { - title: 'Flight update', - json: flightUpdatePayload, - tags: this.getTags(flightUpdatePayload), - icon: require('./assets/flight.png') - }, { - title: 'Weather Large', - json: weatherPayload, - tags: this.getTags(weatherPayload), - icon: require('./assets/cloud.png') - }, { - title: 'Activity Update', - json: activityUpdatePayload, - tags: this.getTags(activityUpdatePayload), - icon: require('./assets/done.png') - }, - { - title: 'Food order', - json: foodOrderPayload, - tags: this.getTags(foodOrderPayload), - icon: require('./assets/fastfood.png') - }, - { - title: 'Image gallery', - json: imageGalleryPayload, - tags: this.getTags(imageGalleryPayload), - icon: require('./assets/photo_library.png') - }, - { - title: 'Sporting event', - json: sportingEventPayload, - tags: this.getTags(sportingEventPayload), - icon: require('./assets/run.png') - }, { - title: 'Restaurant', - json: restaturantPayload, - tags: this.getTags(restaturantPayload), - icon: require('./assets/restaurant.png') - }, - { - title: 'Input form', - json: inputFormPayload, - tags: this.getTags(inputFormPayload), - icon: require('./assets/form.png') - }, - { - title: 'Media', - json: mediaPayload, - tags: this.getTags(mediaPayload), - icon: require('./assets/video_library.png') - }, - { - title: 'Stock Update', - json: containerPayload, - tags: this.getTags(containerPayload), - icon: require('./assets/square.png') - }, - { - title: 'Markdown', - json: markdownPayload, - tags: this.getTags(markdownPayload), - icon: require('./assets/code.png') - }]; } render() { - const segmentedItems = [ - { title: 'Payloads', value: 'payloads' }, - { title: 'Scenarios', value: 'scenarios' } - ]; - - const items = this.state.activeIndex === 0 ? payloads : this.scenarios; + const { activeIndex, payloads, scenarios } = this.state; + const items = activeIndex === 0 ? payloads : scenarios; return ( - this.segmentedControlStatusDidChange(index)} - /> + this.onMoreOptionsClick()} style={styles.moreContainer}> + + + {scenarios && scenarios.length > 0 ? this.segmentedControl() : this.header()} index.toString()} @@ -147,18 +79,58 @@ export default class Visualizer extends React.Component { onModalClose={this.closeModal} /> + this.closeMoreOptions()}> + {this.modalLayout()} + - ) + ); } + /** + * @description Segment control for payloads and scenarios. + */ + segmentedControl = () => { + const segmentedItems = [ + { title: 'Payloads', value: 'payloads' }, + { title: 'Scenarios', value: 'scenarios' } + ]; + return ( + this.segmentedControlStatusDidChange(index)} + /> + ); + } + + /** + * @description Add header for payloads as there is no scenarios + */ + header = () => ( + + + {Constants.PayloadHeader} + + + ); + /** * @description Present the modal * @param {object} payload - Selected payload */ payloadDidSelect = (payload) => { + /* Check if the payload is HeroCard / ThumbnailCard */ + const notAdaptiveCard = payload.json.contentType && + (payload.json.contentType === "application/vnd.microsoft.card.hero" || + payload.json.contentType === "application/vnd.microsoft.card.thumbnail"); + this.setState({ isModalVisible: true, - selectedPayload: payload.json + selectedPayload: notAdaptiveCard ? AdaptiveCardBuilder.buildAdaptiveCard(payload.json.content, payload.json.contentType) : payload.json }) } @@ -172,31 +144,90 @@ export default class Visualizer extends React.Component { } /** - * @description Return the unique element types present in the given payload json - * @param {object} json - payload json - * @return {Array} - Array of element types + * @description Invoked on payload type segmented control status change + * @param {number} index - index of the selected item */ - getTags = (json) => { - let tags = new Set(); - // elements - json.body.map(element => { - tags.add(element.type); + segmentedControlStatusDidChange = (index) => { + this.setState({ + activeIndex: index }); - // actions - if (json.actions && json.actions.length > 0) { - tags.add("Actions"); + } + + /** + * @description Layout of the more option modal + */ + modalLayout = () => ( + this.closeMoreOptions()} + style={styles.moreOptionsModal} + > + + + ); + + /** + * @description Click action for more option + * @param option - selected option + */ + moreOptionClick = (option) => { + const { activeOption } = this.state; + switch (option) { + case Constants.AdaptiveCards: + if (activeOption !== Constants.AdaptiveCards) + this.setState({ + isMoreVisible: false, + activeOption: Constants.AdaptiveCards, + payloads: Payloads.AdaptiveCardPayloads, + scenarios: Payloads.AdaptiveCardScenarios, + activeIndex : 0 + }); + else this.closeMoreOptions(); + break; + case Constants.OtherCards: + if (activeOption !== Constants.OtherCards) + this.setState({ + isMoreVisible: false, + activeOption: Constants.OtherCards, + payloads: Payloads.OtherCardPayloads, + scenarios: [], + activeIndex : 0 + }); + else this.closeMoreOptions(); + break; + default: + if (activeOption !== Constants.AdaptiveCards) + this.setState({ + isMoreVisible: false, + activeOption: Constants.AdaptiveCards, + payloads: Payloads.AdaptiveCardPayloads, + scenarios: Payloads.AdaptiveCardScenarios, + activeIndex : 0 + }); + else this.closeMoreOptions(); + break; } - return Array.from(tags); } /** - * @description Invoked on payload type segmented control status change - * @param {number} index - index of the selected item + * @description Click action for more icon, shows the options modal */ - segmentedControlStatusDidChange = (index) => { + onMoreOptionsClick = () => { this.setState({ - activeIndex: index - }); + isMoreVisible: true + }) + } + + /** + * @description Dismiss the more options modal + */ + closeMoreOptions = () => { + this.setState({ + isMoreVisible: false + }) } } @@ -209,9 +240,36 @@ const styles = StyleSheet.create({ height: 150, backgroundColor: '#f7f7f7', }, - title: { - fontSize: 20, - fontWeight: 'bold', + header: { marginVertical: 12, + justifyContent: "center", + height: 34, + backgroundColor: '#0078D7' + }, + title: { + fontSize: 17, + fontWeight: '400', + color: "white", + alignSelf: "center" + }, + moreContainer: { + alignSelf: "flex-end" + }, + moreIcon: { + padding: 10, + width: 25, + height: 25 + }, + moreOptionsModal: { + flex: 1, + backgroundColor: "rgba(0, 0, 0, 0.1)" + }, + more: { + marginTop: 50, + marginRight: 10, + alignSelf: "flex-end", + width: 100, + height: 100, + backgroundColor: "white" } }); \ No newline at end of file