Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export * from './steps/InvalidPromptBrick';
export * from './layout-steps/Foreach';
export * from './layout-steps/IfCondition';
export * from './layout-steps/SwitchCondition';
export * from './layout-steps/BaseInput';

export * from './events/EventRule';
export * from './events/IntentRule';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { SDKTypes } from '@bfc/shared';
import get from 'lodash/get';

import { ObiTypes } from '../../constants/ObiTypes';
import { IfCondition, SwitchCondition, Foreach, BaseInput } from '../nodes/index';
import { IfCondition, SwitchCondition, Foreach } from '../nodes/index';
import { NodeProps, defaultNodeProps } from '../nodes/nodeProps';
import { UISchemaRenderer } from '../../schema/uischemaRenderer';

Expand All @@ -19,12 +19,6 @@ const rendererByObiType = {
[ObiTypes.SwitchCondition]: SwitchCondition,
[ObiTypes.Foreach]: Foreach,
[ObiTypes.ForeachPage]: Foreach,
[ObiTypes.AttachmentInput]: BaseInput,
[ObiTypes.ConfirmInput]: BaseInput,
[ObiTypes.DateTimeInput]: BaseInput,
[ObiTypes.NumberInput]: BaseInput,
[ObiTypes.TextInput]: BaseInput,
[ObiTypes.ChoiceInput]: BaseInput,
};
const DEFAULT_RENDERER = UISchemaRenderer;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,44 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { SDKTypes } from '@bfc/shared';
import { SDKTypes, getInputType } from '@bfc/shared';
import React from 'react';

import { ActionCard } from '../widgets/ActionCard';
import { ActivityRenderer } from '../widgets/ActivityRenderer';
import { DialogRefCard } from '../widgets/DialogRefCard';
import { PromptWidget } from '../widgets/PromptWidget';
import { ElementIcon } from '../utils/obiPropertyResolver';
import { ObiColors } from '../constants/ElementColors';

import { UISchema } from './uischema.types';
import { UISchema, UIWidget } from './uischema.types';

const BaseInputSchema: UIWidget = {
'ui:widget': PromptWidget,
botAsks: {
'ui:widget': ActivityRenderer,
title: data => `Bot Asks (${getInputType(data.$type)})`,
field: 'prompt',
defaultContent: '<prompt>',
icon: ElementIcon.MessageBot,
colors: {
theme: ObiColors.BlueMagenta20,
icon: ObiColors.BlueMagenta30,
},
},
userInput: {
'ui:widget': ActionCard,
title: data => `User Answers (${getInputType(data.$type)})`,
disableSDKTitle: true,
icon: ElementIcon.User,
menu: 'none',
content: data => data.property || '<property>',
colors: {
theme: ObiColors.LightBlue,
icon: ObiColors.AzureBlue,
},
},
};

export const uiSchema: UISchema = {
default: {
Expand All @@ -25,6 +53,12 @@ export const uiSchema: UISchema = {
icon: ObiColors.BlueMagenta30,
},
},
[SDKTypes.AttachmentInput]: BaseInputSchema,
[SDKTypes.ConfirmInput]: BaseInputSchema,
[SDKTypes.DateTimeInput]: BaseInputSchema,
[SDKTypes.NumberInput]: BaseInputSchema,
[SDKTypes.TextInput]: BaseInputSchema,
[SDKTypes.ChoiceInput]: BaseInputSchema,
[SDKTypes.BeginDialog]: {
'ui:widget': DialogRefCard,
dialog: data => data.dialog,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,6 @@ export interface WidgetContainerProps {
[propKey: string]: any;
}

export type UIWidgetProp = Value | PropGenerator;
export type UIWidgetProp = Value | PropGenerator | UIWidget;
type Value = string | number | { [key: string]: any };
type PropGenerator = (data: any) => string | number | object | JSX.Element;
Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,41 @@ import get from 'lodash/get';
import { uiSchema } from './uischema';
import { UIWidget, UI_WIDGET_KEY, UIWidgetProp, WidgetEventHandler } from './uischema.types';

const buildWidgetProp = (data: BaseSchema, rawPropValue: UIWidgetProp) => {
const parseWidgetSchema = (widgetSchema: UIWidget) => {
const { [UI_WIDGET_KEY]: Widget, ...props } = widgetSchema;
return {
Widget,
props,
};
};

const buildWidgetProp = (rawPropValue: UIWidgetProp, context: UISchemaContext) => {
if (typeof rawPropValue === 'function') {
const dataTransformer = rawPropValue;
const element = dataTransformer(data);
const element = dataTransformer(context.data);
return element;
}

if (typeof rawPropValue === 'object' && rawPropValue[UI_WIDGET_KEY]) {
const widgetSchema = rawPropValue as UIWidget;
return renderUISchema(widgetSchema, context);
}

return rawPropValue;
};

const buildWidgetProps = (data: BaseSchema, rawProps) => {
return Object.keys(rawProps).reduce((props, propName) => {
const renderUISchema = (schema: UIWidget, context: UISchemaContext): JSX.Element => {
const { Widget, props: rawProps } = parseWidgetSchema(schema);
const widgetProps = Object.keys(rawProps).reduce((props, propName) => {
const propValue = rawProps[propName];
props[propName] = buildWidgetProp(data, propValue);
props[propName] = buildWidgetProp(propValue, context);
return props;
}, {});
};

const parseWidgetSchema = (data: BaseSchema, widgetSchema: UIWidget) => {
const { [UI_WIDGET_KEY]: Widget, ...rawProps } = widgetSchema;
const props = buildWidgetProps(data, rawProps);
return {
Widget,
props,
};
return <Widget {...context} {...widgetProps} />;
};

export interface UISchemaRendererProps {
export interface UISchemaContext {
/** The uniq id of current schema data. Usually a json path. */
id: string;

Expand All @@ -46,9 +53,8 @@ export interface UISchemaRendererProps {
onEvent: WidgetEventHandler;
}

export const UISchemaRenderer: FC<UISchemaRendererProps> = ({ id, data, onEvent, ...contextProps }): JSX.Element => {
const $type = get(data, '$type');
export const UISchemaRenderer: FC<UISchemaContext> = (props): JSX.Element => {
const $type = get(props.data, '$type');
const schema = get(uiSchema, $type, uiSchema.default);
const { Widget, props } = parseWidgetSchema(data, schema);
return <Widget id={id} data={data} onEvent={onEvent} {...contextProps} {...props} />;
return renderUISchema(schema, props);
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import { NodeMenu } from '../components/menus/NodeMenu';

export interface ActionCardProps extends WidgetContainerProps {
title: string;
disableSDKTitle?: boolean;
icon: string;
content: string | number | JSX.Element;
menu?: JSX.Element | string;
colors?: {
theme: string;
icon: string;
Expand All @@ -29,16 +31,18 @@ export const ActionCard: WidgetComponent<ActionCardProps> = ({
data,
onEvent,
title,
disableSDKTitle,
icon,
menu,
content,
colors = DefaultCardColor,
}) => {
const header = generateSDKTitle(data, title);
const header = disableSDKTitle ? title : generateSDKTitle(data, title);
const nodeColors = { themeColor: colors.theme, iconColor: colors.icon };
return (
<FormCard
header={header}
corner={<NodeMenu id={id} onEvent={onEvent} />}
corner={menu === 'none' ? null : menu || <NodeMenu id={id} onEvent={onEvent} />}
icon={icon}
label={content}
nodeColors={nodeColors}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface ActivityRenderer extends WidgetContainerProps {
/** indicates which field contains lg activity. ('activity', 'prompt', 'invalidPropmt'...) */
field: string;
icon: ElementIcon;
title?: string;
disableSDKTitle?: boolean;
defaultContent?: string;
colors?: {
theme: string;
icon: string;
Expand All @@ -31,7 +34,10 @@ export const ActivityRenderer: React.FC<ActivityRenderer> = ({
id,
data,
onEvent,
title,
disableSDKTitle,
field,
defaultContent,
icon = ElementIcon.MessageBot,
colors = DefaultThemeColor,
}) => {
Expand All @@ -43,8 +49,8 @@ export const ActivityRenderer: React.FC<ActivityRenderer> = ({

return (
<FormCard
header={generateSDKTitle(data)}
label={templateText}
header={disableSDKTitle ? title : generateSDKTitle(data, title)}
label={templateText || defaultContent}
icon={icon}
corner={<NodeMenu id={id} onEvent={onEvent} />}
nodeColors={nodeColors}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import { jsx } from '@emotion/core';
import { FC } from 'react';
import { PromptTab } from '@bfc/shared';

import { baseInputLayouter } from '../../../layouters/baseInputLayouter';
import { NodeProps } from '../nodeProps';
import { OffsetContainer } from '../../lib/OffsetContainer';
import { Edge } from '../../lib/EdgeComponents';
import { GraphNode } from '../../../models/GraphNode';
import { transformBaseInput } from '../../../transformers/transformBaseInput';
import { ElementWrapper } from '../../renderers/ElementWrapper';
import { BotAsks } from '../steps/BotAsks';
import { UserInput } from '../steps/UserInput';
import { InvalidPromptBrick } from '../steps/InvalidPromptBrick';
import { baseInputLayouter } from '../layouters/baseInputLayouter';
import { transformBaseInput } from '../transformers/transformBaseInput';
import { GraphNode } from '../models/GraphNode';
import { OffsetContainer } from '../components/lib/OffsetContainer';
import { ElementWrapper } from '../components/renderers/ElementWrapper';
import { Edge } from '../components/lib/EdgeComponents';
import { WidgetContainerProps } from '../schema/uischema.types';
import { NodeEventTypes } from '../constants/NodeEventTypes';
import { IconBrick } from '../components/decorations/IconBrick';

const calculateNodes = (data, jsonpath: string) => {
const { botAsks, userAnswers, invalidPrompt } = transformBaseInput(data, jsonpath);
Expand All @@ -26,28 +25,33 @@ const calculateNodes = (data, jsonpath: string) => {
};
};

export const BaseInput: FC<NodeProps> = ({ id, data, onEvent, onResize }): JSX.Element => {
export interface PromptWdigetProps extends WidgetContainerProps {
botAsks: JSX.Element;
userInput: JSX.Element;
}

export const PromptWidget: FC<PromptWdigetProps> = ({ id, data, onEvent, botAsks, userInput }): JSX.Element => {
const nodes = calculateNodes(data, id);
const layout = baseInputLayouter(nodes.botAsksNode, nodes.userAnswersNode, nodes.invalidPromptNode);

const { boundary, nodeMap, edges } = layout;
const { botAsksNode, userAnswersNode, invalidPromptNode: brickNode } = nodeMap;

return (
<div className="Action-BaseInput" css={{ width: boundary.width, height: boundary.height }}>
<div className="Action-BaseInput" css={{ width: boundary.width, height: boundary.height, position: 'relative' }}>
<OffsetContainer offset={botAsksNode.offset}>
<ElementWrapper id={botAsksNode.id} tab={PromptTab.BOT_ASKS} onEvent={onEvent}>
<BotAsks id={botAsksNode.id} data={botAsksNode.data} onEvent={onEvent} onResize={onResize} />
{botAsks}
</ElementWrapper>
</OffsetContainer>
<OffsetContainer offset={userAnswersNode.offset}>
<ElementWrapper id={userAnswersNode.id} tab={PromptTab.USER_INPUT} onEvent={onEvent}>
<UserInput id={userAnswersNode.id} data={userAnswersNode.data} onEvent={onEvent} onResize={onResize} />
{userInput}
</ElementWrapper>
</OffsetContainer>
<OffsetContainer offset={brickNode.offset}>
<ElementWrapper id={brickNode.id} tab={PromptTab.OTHER} onEvent={onEvent}>
<InvalidPromptBrick id={brickNode.id} data={brickNode.data} onEvent={onEvent} onResize={onResize} />
<IconBrick onClick={() => onEvent(NodeEventTypes.Focus, { id, tab: PromptTab.OTHER })} />
</ElementWrapper>
</OffsetContainer>
{edges ? edges.map(x => <Edge key={x.id} {...x} />) : null}
Expand Down
5 changes: 5 additions & 0 deletions Composer/packages/lib/shared/src/viewUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,8 @@ export function generateSDKTitle(data, customizedTitile?: string) {
const titleFrom$type = truncateSDKType($type);
return titleFrom$designer || customizedTitile || titleFromShared || titleFrom$type;
}

export function getInputType($type: string): string {
if (!$type) return '';
return $type.replace(/Microsoft.(.*)Input/, '$1');
}