diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..0899df5 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +registry=https://msasg.pkgs.visualstudio.com/_packaging/react-native-cortana-conversation/npm/registry/ +always-auth=true \ No newline at end of file diff --git a/dist/Components/Basic/ImageBlock.d.ts b/dist/Components/Basic/ImageBlock.d.ts index 90ff8d5..d7d6dad 100644 --- a/dist/Components/Basic/ImageBlock.d.ts +++ b/dist/Components/Basic/ImageBlock.d.ts @@ -21,6 +21,7 @@ interface IProps { onLayout?: (event: LayoutChangeEvent) => void; onLoad?: (data: any) => void; onError?: (error: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => React.ReactNode; } interface IState { loaded: boolean; diff --git a/dist/Components/Basic/ImageBlock.js b/dist/Components/Basic/ImageBlock.js index f7dc72e..8472f45 100644 --- a/dist/Components/Basic/ImageBlock.js +++ b/dist/Components/Basic/ImageBlock.js @@ -88,7 +88,18 @@ export class ImageBlock extends React.Component { ], resizeMethod: 'resize', resizeMode: 'contain' })); } renderImage() { - if (UrlUtils.isSvgXml(this.props.url)) { + if (this.props.mode === 'avatar' && this.props.avatarFallbackRender) { + return React.createElement(View, { style: [ + { + width: this.props.width, + height: this.props.height, + overflow: 'hidden', + }, + this.borderRadius, + this.props.style + ] }, this.props.avatarFallbackRender(this.props.width, this.props.alt, this.props.url)); + } + else if (UrlUtils.isSvgXml(this.props.url)) { return (React.createElement(View, { style: [ { width: this.props.width, diff --git a/dist/Contexts/CardContext.d.ts b/dist/Contexts/CardContext.d.ts index 5fa3d41..3d5dc18 100644 --- a/dist/Contexts/CardContext.d.ts +++ b/dist/Contexts/CardContext.d.ts @@ -1,3 +1,4 @@ +import { ReactNode } from 'react'; import { CardModel } from '../Models/Cards/Card'; import { TreeNode } from '../Shared/Types'; import { FormStore } from './FormStore'; @@ -13,6 +14,7 @@ export declare class CardContext extends TreeNode { private onSubmitAction?; private onCallbackAction?; private onSelectAction?; + private avatarFallbackRender?; readonly form: FormStore; readonly schemas: SchemaStore; readonly children: CardContext[]; @@ -31,6 +33,7 @@ export declare class CardContext extends TreeNode { [key: string]: string; }) => Promise): void; registerSelectActionHandler(handler: (data: any) => Promise): void; + registerAvatarFallbackRenderHandler(handler: (diameter: number, altText: string, url: string) => ReactNode): void; private findRequiredContext; readonly errorHandler: (error: any) => void; readonly infoHandler: (info: any) => void; @@ -44,4 +47,5 @@ export declare class CardContext extends TreeNode { [key: string]: string; }) => Promise; readonly selectActionHandler: (data: any) => Promise; + readonly avatarFallbackRenderHandler: (diameter: number, altText: string, url: string) => ReactNode; } diff --git a/dist/Contexts/CardContext.js b/dist/Contexts/CardContext.js index dc985fb..fc2b163 100644 --- a/dist/Contexts/CardContext.js +++ b/dist/Contexts/CardContext.js @@ -48,6 +48,9 @@ export class CardContext extends TreeNode { registerSelectActionHandler(handler) { this.onSelectAction = handler; } + registerAvatarFallbackRenderHandler(handler) { + this.avatarFallbackRender = handler; + } findRequiredContext(selector) { if (selector) { if (selector(this)) { @@ -129,4 +132,11 @@ export class CardContext extends TreeNode { } return undefined; } + get avatarFallbackRenderHandler() { + let context = this.findRequiredContext(context => context.avatarFallbackRender !== undefined); + if (context) { + return context.avatarFallbackRender; + } + return undefined; + } } diff --git a/dist/Views/CardElements/Image.js b/dist/Views/CardElements/Image.js index c0c6f11..5300c35 100644 --- a/dist/Views/CardElements/Image.js +++ b/dist/Views/CardElements/Image.js @@ -80,7 +80,21 @@ export class ImageView extends React.Component { handler(`AdaptiveCard >> Start Load Image >> ${model.url}`); } } - ImageUtils.fetchSize(model.url, size || model.size, { width: maxWidth, height: maxHeight }, this.onImageSize, this.onImageSizeError); + if (model.style === 'person' && model.context.avatarFallbackRenderHandler) { + const definedSize = StyleManager.getImageSize(size || model.size); + if (typeof definedSize === 'number') { + this.onImageSize({ + width: definedSize, + height: definedSize + }); + } + else { + this.onImageSizeError(new Error('Avatar image size should be actual number.')); + } + } + else { + ImageUtils.fetchSize(model.url, size || model.size, { width: maxWidth, height: maxHeight }, this.onImageSize, this.onImageSizeError); + } } }; this.state = { @@ -107,7 +121,7 @@ export class ImageView extends React.Component { return DebugOutputFactory.createDebugOutputBanner(model.type + '>>' + model.url + ' is not valid', theme, 'error'); } if (this.state.loaded) { - return (React.createElement(ImageBlock, { url: model.url, alt: model.alt, flex: this.flex, alignSelf: StyleManager.getHorizontalAlign(model.horizontalAlignment), width: this.state.width, height: this.state.height, onPress: model.selectAction ? this.onPress : undefined, onLayout: this.onLayout, onLoad: this.onImageLoad, onError: this.onImageError, marginTop: this.spacing, marginLeft: spacing, mode: model.style === 'person' ? 'avatar' : 'default' })); + return (React.createElement(ImageBlock, { url: model.url, alt: model.alt, flex: this.flex, alignSelf: StyleManager.getHorizontalAlign(model.horizontalAlignment), width: this.state.width, height: this.state.height, onPress: model.selectAction ? this.onPress : undefined, onLayout: this.onLayout, onLoad: this.onImageLoad, onError: this.onImageError, marginTop: this.spacing, marginLeft: spacing, mode: model.style === 'person' ? 'avatar' : 'default', avatarFallbackRender: model.context && model.context.avatarFallbackRenderHandler })); } return null; } diff --git a/dist/Views/Root.d.ts b/dist/Views/Root.d.ts index 45534f8..1d4fbb4 100644 --- a/dist/Views/Root.d.ts +++ b/dist/Views/Root.d.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { CardContext } from '../Contexts/CardContext'; export interface IAdaptiveCardProps { adaptiveCard: any; @@ -14,6 +14,7 @@ export interface IAdaptiveCardProps { onError?: (error: any) => void; onInfo?: (info: any) => void; onWarning?: (warning: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => ReactNode; } export declare class CardRootView extends React.Component { rootCardContext: CardContext; diff --git a/dist/Views/Root.js b/dist/Views/Root.js index d8b0490..4ccdd73 100644 --- a/dist/Views/Root.js +++ b/dist/Views/Root.js @@ -18,6 +18,7 @@ export class CardRootView extends React.Component { this.rootCardContext.registerErrorHandler(this.props.onError); this.rootCardContext.registerInfoHandler(this.props.onInfo); this.rootCardContext.registerWarningHandler(this.props.onWarning); + this.rootCardContext.registerAvatarFallbackRenderHandler(this.props.avatarFallbackRender); } shouldComponentUpdate(nextProps) { if (JSON.stringify(nextProps) !== JSON.stringify(this.props)) { diff --git a/examples/AdaptiveCards/Components/Basic/ImageBlock.d.ts b/examples/AdaptiveCards/Components/Basic/ImageBlock.d.ts index 90ff8d5..d7d6dad 100644 --- a/examples/AdaptiveCards/Components/Basic/ImageBlock.d.ts +++ b/examples/AdaptiveCards/Components/Basic/ImageBlock.d.ts @@ -21,6 +21,7 @@ interface IProps { onLayout?: (event: LayoutChangeEvent) => void; onLoad?: (data: any) => void; onError?: (error: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => React.ReactNode; } interface IState { loaded: boolean; diff --git a/examples/AdaptiveCards/Components/Basic/ImageBlock.js b/examples/AdaptiveCards/Components/Basic/ImageBlock.js index f7dc72e..8472f45 100644 --- a/examples/AdaptiveCards/Components/Basic/ImageBlock.js +++ b/examples/AdaptiveCards/Components/Basic/ImageBlock.js @@ -88,7 +88,18 @@ export class ImageBlock extends React.Component { ], resizeMethod: 'resize', resizeMode: 'contain' })); } renderImage() { - if (UrlUtils.isSvgXml(this.props.url)) { + if (this.props.mode === 'avatar' && this.props.avatarFallbackRender) { + return React.createElement(View, { style: [ + { + width: this.props.width, + height: this.props.height, + overflow: 'hidden', + }, + this.borderRadius, + this.props.style + ] }, this.props.avatarFallbackRender(this.props.width, this.props.alt, this.props.url)); + } + else if (UrlUtils.isSvgXml(this.props.url)) { return (React.createElement(View, { style: [ { width: this.props.width, diff --git a/examples/AdaptiveCards/Contexts/CardContext.d.ts b/examples/AdaptiveCards/Contexts/CardContext.d.ts index 5fa3d41..3d5dc18 100644 --- a/examples/AdaptiveCards/Contexts/CardContext.d.ts +++ b/examples/AdaptiveCards/Contexts/CardContext.d.ts @@ -1,3 +1,4 @@ +import { ReactNode } from 'react'; import { CardModel } from '../Models/Cards/Card'; import { TreeNode } from '../Shared/Types'; import { FormStore } from './FormStore'; @@ -13,6 +14,7 @@ export declare class CardContext extends TreeNode { private onSubmitAction?; private onCallbackAction?; private onSelectAction?; + private avatarFallbackRender?; readonly form: FormStore; readonly schemas: SchemaStore; readonly children: CardContext[]; @@ -31,6 +33,7 @@ export declare class CardContext extends TreeNode { [key: string]: string; }) => Promise): void; registerSelectActionHandler(handler: (data: any) => Promise): void; + registerAvatarFallbackRenderHandler(handler: (diameter: number, altText: string, url: string) => ReactNode): void; private findRequiredContext; readonly errorHandler: (error: any) => void; readonly infoHandler: (info: any) => void; @@ -44,4 +47,5 @@ export declare class CardContext extends TreeNode { [key: string]: string; }) => Promise; readonly selectActionHandler: (data: any) => Promise; + readonly avatarFallbackRenderHandler: (diameter: number, altText: string, url: string) => ReactNode; } diff --git a/examples/AdaptiveCards/Contexts/CardContext.js b/examples/AdaptiveCards/Contexts/CardContext.js index dc985fb..fc2b163 100644 --- a/examples/AdaptiveCards/Contexts/CardContext.js +++ b/examples/AdaptiveCards/Contexts/CardContext.js @@ -48,6 +48,9 @@ export class CardContext extends TreeNode { registerSelectActionHandler(handler) { this.onSelectAction = handler; } + registerAvatarFallbackRenderHandler(handler) { + this.avatarFallbackRender = handler; + } findRequiredContext(selector) { if (selector) { if (selector(this)) { @@ -129,4 +132,11 @@ export class CardContext extends TreeNode { } return undefined; } + get avatarFallbackRenderHandler() { + let context = this.findRequiredContext(context => context.avatarFallbackRender !== undefined); + if (context) { + return context.avatarFallbackRender; + } + return undefined; + } } diff --git a/examples/AdaptiveCards/Views/CardElements/Image.js b/examples/AdaptiveCards/Views/CardElements/Image.js index c0c6f11..5300c35 100644 --- a/examples/AdaptiveCards/Views/CardElements/Image.js +++ b/examples/AdaptiveCards/Views/CardElements/Image.js @@ -80,7 +80,21 @@ export class ImageView extends React.Component { handler(`AdaptiveCard >> Start Load Image >> ${model.url}`); } } - ImageUtils.fetchSize(model.url, size || model.size, { width: maxWidth, height: maxHeight }, this.onImageSize, this.onImageSizeError); + if (model.style === 'person' && model.context.avatarFallbackRenderHandler) { + const definedSize = StyleManager.getImageSize(size || model.size); + if (typeof definedSize === 'number') { + this.onImageSize({ + width: definedSize, + height: definedSize + }); + } + else { + this.onImageSizeError(new Error('Avatar image size should be actual number.')); + } + } + else { + ImageUtils.fetchSize(model.url, size || model.size, { width: maxWidth, height: maxHeight }, this.onImageSize, this.onImageSizeError); + } } }; this.state = { @@ -107,7 +121,7 @@ export class ImageView extends React.Component { return DebugOutputFactory.createDebugOutputBanner(model.type + '>>' + model.url + ' is not valid', theme, 'error'); } if (this.state.loaded) { - return (React.createElement(ImageBlock, { url: model.url, alt: model.alt, flex: this.flex, alignSelf: StyleManager.getHorizontalAlign(model.horizontalAlignment), width: this.state.width, height: this.state.height, onPress: model.selectAction ? this.onPress : undefined, onLayout: this.onLayout, onLoad: this.onImageLoad, onError: this.onImageError, marginTop: this.spacing, marginLeft: spacing, mode: model.style === 'person' ? 'avatar' : 'default' })); + return (React.createElement(ImageBlock, { url: model.url, alt: model.alt, flex: this.flex, alignSelf: StyleManager.getHorizontalAlign(model.horizontalAlignment), width: this.state.width, height: this.state.height, onPress: model.selectAction ? this.onPress : undefined, onLayout: this.onLayout, onLoad: this.onImageLoad, onError: this.onImageError, marginTop: this.spacing, marginLeft: spacing, mode: model.style === 'person' ? 'avatar' : 'default', avatarFallbackRender: model.context && model.context.avatarFallbackRenderHandler })); } return null; } diff --git a/examples/AdaptiveCards/Views/Root.d.ts b/examples/AdaptiveCards/Views/Root.d.ts index 45534f8..1d4fbb4 100644 --- a/examples/AdaptiveCards/Views/Root.d.ts +++ b/examples/AdaptiveCards/Views/Root.d.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { CardContext } from '../Contexts/CardContext'; export interface IAdaptiveCardProps { adaptiveCard: any; @@ -14,6 +14,7 @@ export interface IAdaptiveCardProps { onError?: (error: any) => void; onInfo?: (info: any) => void; onWarning?: (warning: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => ReactNode; } export declare class CardRootView extends React.Component { rootCardContext: CardContext; diff --git a/examples/AdaptiveCards/Views/Root.js b/examples/AdaptiveCards/Views/Root.js index d8b0490..4ccdd73 100644 --- a/examples/AdaptiveCards/Views/Root.js +++ b/examples/AdaptiveCards/Views/Root.js @@ -18,6 +18,7 @@ export class CardRootView extends React.Component { this.rootCardContext.registerErrorHandler(this.props.onError); this.rootCardContext.registerInfoHandler(this.props.onInfo); this.rootCardContext.registerWarningHandler(this.props.onWarning); + this.rootCardContext.registerAvatarFallbackRenderHandler(this.props.avatarFallbackRender); } shouldComponentUpdate(nextProps) { if (JSON.stringify(nextProps) !== JSON.stringify(this.props)) { diff --git a/package.json b/package.json index fe1a924..fdf48a4 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-adaptivecards", "description": "React Native implementation of AdaptiveCards.", - "version": "0.1.37", + "version": "0.1.38", "repository": { "url": "git+https://github.com/Microsoft/react-native-adaptivecards", "type": "git" diff --git a/src/Components/Basic/ImageBlock.tsx b/src/Components/Basic/ImageBlock.tsx index 5c3ac49..aee70e2 100644 --- a/src/Components/Basic/ImageBlock.tsx +++ b/src/Components/Basic/ImageBlock.tsx @@ -31,6 +31,7 @@ interface IProps { onLayout?: (event: LayoutChangeEvent) => void; onLoad?: (data: any) => void; onError?: (error: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => React.ReactNode; } interface IState { @@ -137,7 +138,21 @@ export class ImageBlock extends React.Component { } private renderImage() { - if (UrlUtils.isSvgXml(this.props.url)) { + if (this.props.mode === 'avatar' && this.props.avatarFallbackRender) { + return + {this.props.avatarFallbackRender(this.props.width, this.props.alt, this.props.url)} + ; + } else if (UrlUtils.isSvgXml(this.props.url)) { return ( { private onSubmitAction?: (data: any) => Promise; private onCallbackAction?: (url: string, parameters: { [key: string]: string }) => Promise; private onSelectAction?: (data: any) => Promise; + private avatarFallbackRender?: (diameter: number, altText: string, url: string) => ReactNode; public readonly form: FormStore; public readonly schemas: SchemaStore; public readonly children: CardContext[] = []; @@ -75,6 +77,10 @@ export class CardContext extends TreeNode { this.onSelectAction = handler; } + public registerAvatarFallbackRenderHandler(handler: (diameter: number, altText: string, url: string) => ReactNode) { + this.avatarFallbackRender = handler; + } + private findRequiredContext(selector: (context: CardContext) => boolean): CardContext { if (selector) { if (selector(this)) { @@ -95,7 +101,7 @@ export class CardContext extends TreeNode { return undefined; } - public get infoHandler() : (info: any) => void { + public get infoHandler(): (info: any) => void { let context = this.findRequiredContext(context => context.onInfo !== undefined); if (context) { return context.onInfo; @@ -103,7 +109,7 @@ export class CardContext extends TreeNode { return undefined; } - public get warningHandler() : (warning: any) => void { + public get warningHandler(): (warning: any) => void { let context = this.findRequiredContext(context => context.onWarning !== undefined); if (context) { return context.onWarning; @@ -111,7 +117,7 @@ export class CardContext extends TreeNode { return undefined; } - public get focusHandler() : () => void { + public get focusHandler(): () => void { let context = this.findRequiredContext(context => context.onFocus !== undefined); if (context) { return context.onFocus; @@ -166,4 +172,12 @@ export class CardContext extends TreeNode { } return undefined; } + + public get avatarFallbackRenderHandler(): (diameter: number, altText: string, url: string) => ReactNode { + let context = this.findRequiredContext(context => context.avatarFallbackRender !== undefined); + if (context) { + return context.avatarFallbackRender; + } + return undefined; + } } diff --git a/src/Views/CardElements/Image.tsx b/src/Views/CardElements/Image.tsx index 61deb76..733b8b9 100644 --- a/src/Views/CardElements/Image.tsx +++ b/src/Views/CardElements/Image.tsx @@ -76,6 +76,7 @@ export class ImageView extends React.Component { marginTop={this.spacing} marginLeft={spacing} mode={model.style === 'person' ? 'avatar' : 'default'} + avatarFallbackRender={model.context && model.context.avatarFallbackRenderHandler} /> ); } @@ -169,13 +170,25 @@ export class ImageView extends React.Component { } } - ImageUtils.fetchSize( - model.url, - size || model.size, - { width: maxWidth, height: maxHeight }, - this.onImageSize, - this.onImageSizeError - ); + if (model.style === 'person' && model.context.avatarFallbackRenderHandler) { + const definedSize = StyleManager.getImageSize(size || model.size); + if (typeof definedSize === 'number') { + this.onImageSize({ + width: definedSize, + height: definedSize + }); + } else { + this.onImageSizeError(new Error('Avatar image size should be actual number.')); + } + } else { + ImageUtils.fetchSize( + model.url, + size || model.size, + { width: maxWidth, height: maxHeight }, + this.onImageSize, + this.onImageSizeError + ); + } } } diff --git a/src/Views/Root.tsx b/src/Views/Root.tsx index c7510d7..2e86e5a 100644 --- a/src/Views/Root.tsx +++ b/src/Views/Root.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { View, } from 'react-native'; @@ -20,6 +20,7 @@ export interface IAdaptiveCardProps { onError?: (error: any) => void; onInfo?: (info: any) => void; onWarning?: (warning: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => ReactNode; } export class CardRootView extends React.Component { @@ -48,6 +49,7 @@ export class CardRootView extends React.Component { this.rootCardContext.registerErrorHandler(this.props.onError); this.rootCardContext.registerInfoHandler(this.props.onInfo); this.rootCardContext.registerWarningHandler(this.props.onWarning); + this.rootCardContext.registerAvatarFallbackRenderHandler(this.props.avatarFallbackRender); } diff --git a/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.d.ts b/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.d.ts index 90ff8d5..d7d6dad 100644 --- a/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.d.ts +++ b/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.d.ts @@ -21,6 +21,7 @@ interface IProps { onLayout?: (event: LayoutChangeEvent) => void; onLoad?: (data: any) => void; onError?: (error: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => React.ReactNode; } interface IState { loaded: boolean; diff --git a/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.js b/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.js index f7dc72e..8472f45 100644 --- a/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.js +++ b/tool/src/assets/AdaptiveCards/Components/Basic/ImageBlock.js @@ -88,7 +88,18 @@ export class ImageBlock extends React.Component { ], resizeMethod: 'resize', resizeMode: 'contain' })); } renderImage() { - if (UrlUtils.isSvgXml(this.props.url)) { + if (this.props.mode === 'avatar' && this.props.avatarFallbackRender) { + return React.createElement(View, { style: [ + { + width: this.props.width, + height: this.props.height, + overflow: 'hidden', + }, + this.borderRadius, + this.props.style + ] }, this.props.avatarFallbackRender(this.props.width, this.props.alt, this.props.url)); + } + else if (UrlUtils.isSvgXml(this.props.url)) { return (React.createElement(View, { style: [ { width: this.props.width, diff --git a/tool/src/assets/AdaptiveCards/Contexts/CardContext.d.ts b/tool/src/assets/AdaptiveCards/Contexts/CardContext.d.ts index 5fa3d41..3d5dc18 100644 --- a/tool/src/assets/AdaptiveCards/Contexts/CardContext.d.ts +++ b/tool/src/assets/AdaptiveCards/Contexts/CardContext.d.ts @@ -1,3 +1,4 @@ +import { ReactNode } from 'react'; import { CardModel } from '../Models/Cards/Card'; import { TreeNode } from '../Shared/Types'; import { FormStore } from './FormStore'; @@ -13,6 +14,7 @@ export declare class CardContext extends TreeNode { private onSubmitAction?; private onCallbackAction?; private onSelectAction?; + private avatarFallbackRender?; readonly form: FormStore; readonly schemas: SchemaStore; readonly children: CardContext[]; @@ -31,6 +33,7 @@ export declare class CardContext extends TreeNode { [key: string]: string; }) => Promise): void; registerSelectActionHandler(handler: (data: any) => Promise): void; + registerAvatarFallbackRenderHandler(handler: (diameter: number, altText: string, url: string) => ReactNode): void; private findRequiredContext; readonly errorHandler: (error: any) => void; readonly infoHandler: (info: any) => void; @@ -44,4 +47,5 @@ export declare class CardContext extends TreeNode { [key: string]: string; }) => Promise; readonly selectActionHandler: (data: any) => Promise; + readonly avatarFallbackRenderHandler: (diameter: number, altText: string, url: string) => ReactNode; } diff --git a/tool/src/assets/AdaptiveCards/Contexts/CardContext.js b/tool/src/assets/AdaptiveCards/Contexts/CardContext.js index dc985fb..fc2b163 100644 --- a/tool/src/assets/AdaptiveCards/Contexts/CardContext.js +++ b/tool/src/assets/AdaptiveCards/Contexts/CardContext.js @@ -48,6 +48,9 @@ export class CardContext extends TreeNode { registerSelectActionHandler(handler) { this.onSelectAction = handler; } + registerAvatarFallbackRenderHandler(handler) { + this.avatarFallbackRender = handler; + } findRequiredContext(selector) { if (selector) { if (selector(this)) { @@ -129,4 +132,11 @@ export class CardContext extends TreeNode { } return undefined; } + get avatarFallbackRenderHandler() { + let context = this.findRequiredContext(context => context.avatarFallbackRender !== undefined); + if (context) { + return context.avatarFallbackRender; + } + return undefined; + } } diff --git a/tool/src/assets/AdaptiveCards/Views/CardElements/Image.js b/tool/src/assets/AdaptiveCards/Views/CardElements/Image.js index c0c6f11..5300c35 100644 --- a/tool/src/assets/AdaptiveCards/Views/CardElements/Image.js +++ b/tool/src/assets/AdaptiveCards/Views/CardElements/Image.js @@ -80,7 +80,21 @@ export class ImageView extends React.Component { handler(`AdaptiveCard >> Start Load Image >> ${model.url}`); } } - ImageUtils.fetchSize(model.url, size || model.size, { width: maxWidth, height: maxHeight }, this.onImageSize, this.onImageSizeError); + if (model.style === 'person' && model.context.avatarFallbackRenderHandler) { + const definedSize = StyleManager.getImageSize(size || model.size); + if (typeof definedSize === 'number') { + this.onImageSize({ + width: definedSize, + height: definedSize + }); + } + else { + this.onImageSizeError(new Error('Avatar image size should be actual number.')); + } + } + else { + ImageUtils.fetchSize(model.url, size || model.size, { width: maxWidth, height: maxHeight }, this.onImageSize, this.onImageSizeError); + } } }; this.state = { @@ -107,7 +121,7 @@ export class ImageView extends React.Component { return DebugOutputFactory.createDebugOutputBanner(model.type + '>>' + model.url + ' is not valid', theme, 'error'); } if (this.state.loaded) { - return (React.createElement(ImageBlock, { url: model.url, alt: model.alt, flex: this.flex, alignSelf: StyleManager.getHorizontalAlign(model.horizontalAlignment), width: this.state.width, height: this.state.height, onPress: model.selectAction ? this.onPress : undefined, onLayout: this.onLayout, onLoad: this.onImageLoad, onError: this.onImageError, marginTop: this.spacing, marginLeft: spacing, mode: model.style === 'person' ? 'avatar' : 'default' })); + return (React.createElement(ImageBlock, { url: model.url, alt: model.alt, flex: this.flex, alignSelf: StyleManager.getHorizontalAlign(model.horizontalAlignment), width: this.state.width, height: this.state.height, onPress: model.selectAction ? this.onPress : undefined, onLayout: this.onLayout, onLoad: this.onImageLoad, onError: this.onImageError, marginTop: this.spacing, marginLeft: spacing, mode: model.style === 'person' ? 'avatar' : 'default', avatarFallbackRender: model.context && model.context.avatarFallbackRenderHandler })); } return null; } diff --git a/tool/src/assets/AdaptiveCards/Views/Root.d.ts b/tool/src/assets/AdaptiveCards/Views/Root.d.ts index 45534f8..1d4fbb4 100644 --- a/tool/src/assets/AdaptiveCards/Views/Root.d.ts +++ b/tool/src/assets/AdaptiveCards/Views/Root.d.ts @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import { CardContext } from '../Contexts/CardContext'; export interface IAdaptiveCardProps { adaptiveCard: any; @@ -14,6 +14,7 @@ export interface IAdaptiveCardProps { onError?: (error: any) => void; onInfo?: (info: any) => void; onWarning?: (warning: any) => void; + avatarFallbackRender?: (diameter: number, altText: string, url: string) => ReactNode; } export declare class CardRootView extends React.Component { rootCardContext: CardContext; diff --git a/tool/src/assets/AdaptiveCards/Views/Root.js b/tool/src/assets/AdaptiveCards/Views/Root.js index d8b0490..4ccdd73 100644 --- a/tool/src/assets/AdaptiveCards/Views/Root.js +++ b/tool/src/assets/AdaptiveCards/Views/Root.js @@ -18,6 +18,7 @@ export class CardRootView extends React.Component { this.rootCardContext.registerErrorHandler(this.props.onError); this.rootCardContext.registerInfoHandler(this.props.onInfo); this.rootCardContext.registerWarningHandler(this.props.onWarning); + this.rootCardContext.registerAvatarFallbackRenderHandler(this.props.avatarFallbackRender); } shouldComponentUpdate(nextProps) { if (JSON.stringify(nextProps) !== JSON.stringify(this.props)) {