diff --git a/Composer/packages/extensions/visual-designer/src/components/nodes/index.tsx b/Composer/packages/extensions/visual-designer/src/components/nodes/index.tsx index 375569a664..64c16234e9 100644 --- a/Composer/packages/extensions/visual-designer/src/components/nodes/index.tsx +++ b/Composer/packages/extensions/visual-designer/src/components/nodes/index.tsx @@ -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'; diff --git a/Composer/packages/extensions/visual-designer/src/components/renderers/StepRenderer.tsx b/Composer/packages/extensions/visual-designer/src/components/renderers/StepRenderer.tsx index 0d2fd5a9b7..db3ca5ef57 100644 --- a/Composer/packages/extensions/visual-designer/src/components/renderers/StepRenderer.tsx +++ b/Composer/packages/extensions/visual-designer/src/components/renderers/StepRenderer.tsx @@ -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'; @@ -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; diff --git a/Composer/packages/extensions/visual-designer/src/schema/uischema.tsx b/Composer/packages/extensions/visual-designer/src/schema/uischema.tsx index 667af73b7d..5f0f49dccd 100644 --- a/Composer/packages/extensions/visual-designer/src/schema/uischema.tsx +++ b/Composer/packages/extensions/visual-designer/src/schema/uischema.tsx @@ -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: '', + 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 || '', + colors: { + theme: ObiColors.LightBlue, + icon: ObiColors.AzureBlue, + }, + }, +}; export const uiSchema: UISchema = { default: { @@ -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, diff --git a/Composer/packages/extensions/visual-designer/src/schema/uischema.types.ts b/Composer/packages/extensions/visual-designer/src/schema/uischema.types.ts index 546980b1ab..6fb2a37d7e 100644 --- a/Composer/packages/extensions/visual-designer/src/schema/uischema.types.ts +++ b/Composer/packages/extensions/visual-designer/src/schema/uischema.types.ts @@ -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; diff --git a/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx b/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx index a088031c80..0b27608df9 100644 --- a/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx +++ b/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx @@ -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 ; }; -export interface UISchemaRendererProps { +export interface UISchemaContext { /** The uniq id of current schema data. Usually a json path. */ id: string; @@ -46,9 +53,8 @@ export interface UISchemaRendererProps { onEvent: WidgetEventHandler; } -export const UISchemaRenderer: FC = ({ id, data, onEvent, ...contextProps }): JSX.Element => { - const $type = get(data, '$type'); +export const UISchemaRenderer: FC = (props): JSX.Element => { + const $type = get(props.data, '$type'); const schema = get(uiSchema, $type, uiSchema.default); - const { Widget, props } = parseWidgetSchema(data, schema); - return ; + return renderUISchema(schema, props); }; diff --git a/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx b/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx index 6f5a2fc331..21d71b47c5 100644 --- a/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx +++ b/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx @@ -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; @@ -29,16 +31,18 @@ export const ActionCard: WidgetComponent = ({ 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 ( } + corner={menu === 'none' ? null : menu || } icon={icon} label={content} nodeColors={nodeColors} diff --git a/Composer/packages/extensions/visual-designer/src/widgets/ActivityRenderer.tsx b/Composer/packages/extensions/visual-designer/src/widgets/ActivityRenderer.tsx index 7532c21eaa..40ba5283a9 100644 --- a/Composer/packages/extensions/visual-designer/src/widgets/ActivityRenderer.tsx +++ b/Composer/packages/extensions/visual-designer/src/widgets/ActivityRenderer.tsx @@ -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; @@ -31,7 +34,10 @@ export const ActivityRenderer: React.FC = ({ id, data, onEvent, + title, + disableSDKTitle, field, + defaultContent, icon = ElementIcon.MessageBot, colors = DefaultThemeColor, }) => { @@ -43,8 +49,8 @@ export const ActivityRenderer: React.FC = ({ return ( } nodeColors={nodeColors} diff --git a/Composer/packages/extensions/visual-designer/src/components/nodes/layout-steps/BaseInput.tsx b/Composer/packages/extensions/visual-designer/src/widgets/PromptWidget.tsx similarity index 59% rename from Composer/packages/extensions/visual-designer/src/components/nodes/layout-steps/BaseInput.tsx rename to Composer/packages/extensions/visual-designer/src/widgets/PromptWidget.tsx index 1254e18c35..359797fbca 100644 --- a/Composer/packages/extensions/visual-designer/src/components/nodes/layout-steps/BaseInput.tsx +++ b/Composer/packages/extensions/visual-designer/src/widgets/PromptWidget.tsx @@ -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); @@ -26,7 +25,12 @@ const calculateNodes = (data, jsonpath: string) => { }; }; -export const BaseInput: FC = ({ id, data, onEvent, onResize }): JSX.Element => { +export interface PromptWdigetProps extends WidgetContainerProps { + botAsks: JSX.Element; + userInput: JSX.Element; +} + +export const PromptWidget: FC = ({ id, data, onEvent, botAsks, userInput }): JSX.Element => { const nodes = calculateNodes(data, id); const layout = baseInputLayouter(nodes.botAsksNode, nodes.userAnswersNode, nodes.invalidPromptNode); @@ -34,20 +38,20 @@ export const BaseInput: FC = ({ id, data, onEvent, onResize }): JSX.E const { botAsksNode, userAnswersNode, invalidPromptNode: brickNode } = nodeMap; return ( -
+
- + {botAsks} - + {userInput} - + onEvent(NodeEventTypes.Focus, { id, tab: PromptTab.OTHER })} /> {edges ? edges.map(x => ) : null} diff --git a/Composer/packages/lib/shared/src/viewUtils.ts b/Composer/packages/lib/shared/src/viewUtils.ts index 8280b72e62..df4b202ea6 100644 --- a/Composer/packages/lib/shared/src/viewUtils.ts +++ b/Composer/packages/lib/shared/src/viewUtils.ts @@ -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'); +}