diff --git a/Composer/packages/extensions/visual-designer/demo/src/index.js b/Composer/packages/extensions/visual-designer/demo/src/index.js index 34e853d250..c9f24d18fe 100644 --- a/Composer/packages/extensions/visual-designer/demo/src/index.js +++ b/Composer/packages/extensions/visual-designer/demo/src/index.js @@ -36,7 +36,7 @@ const DemoMaps = { class Demo extends Component { state = { - selectedItem: DemoMaps.VisualEditorDemo.key, + selectedItem: DemoMaps.VisualSDKDemo.key, }; renderNav() { diff --git a/Composer/packages/extensions/visual-designer/demo/src/stories/VisualSDKDemo.js b/Composer/packages/extensions/visual-designer/demo/src/stories/VisualSDKDemo.js index af63d7d274..b3bab73ad5 100644 --- a/Composer/packages/extensions/visual-designer/demo/src/stories/VisualSDKDemo.js +++ b/Composer/packages/extensions/visual-designer/demo/src/stories/VisualSDKDemo.js @@ -1,7 +1,7 @@ import React, { Component } from 'react'; import { seedNewDialog, SDKTypes } from '@bfc/shared'; -import { renderSDKType } from '../../../src/schema/uischemaRenderer'; +import { UISchemaRenderer } from '../../../src/schema/uischemaRenderer'; import { EdgeMenu } from '../../../src/components/menus/EdgeMenu'; import { JsonBlock } from '../components/json-block'; @@ -14,13 +14,17 @@ export class VisualSDKDemo extends Component { seedInitialActions() { const initialTypes = [ + SDKTypes.SendActivity, SDKTypes.EditArray, SDKTypes.InitProperty, SDKTypes.SetProperties, SDKTypes.SetProperty, SDKTypes.DeleteProperties, SDKTypes.DeleteProperty, + SDKTypes.BeginDialog, SDKTypes.EndDialog, + SDKTypes.RepeatDialog, + SDKTypes.ReplaceDialog, SDKTypes.CancelAllDialogs, SDKTypes.EmitEvent, ]; @@ -58,7 +62,9 @@ export class VisualSDKDemo extends Component { }} /> -
{renderSDKType(action)}
+
+ null} /> +
); } 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 15dcc39c9f..cee9d240e0 100644 --- a/Composer/packages/extensions/visual-designer/src/components/nodes/index.tsx +++ b/Composer/packages/extensions/visual-designer/src/components/nodes/index.tsx @@ -1,9 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -export * from './steps/ActivityRenderer'; export * from './steps/BeginDialog'; -export * from './steps/DefaultRenderer'; export * from './steps/ReplaceDialog'; export * from './steps/ChoiceInput'; export * from './steps/TextInput'; diff --git a/Composer/packages/extensions/visual-designer/src/components/nodes/steps/ActivityRenderer.tsx b/Composer/packages/extensions/visual-designer/src/components/nodes/steps/ActivityRenderer.tsx deleted file mode 100644 index 1bcb8248b6..0000000000 --- a/Composer/packages/extensions/visual-designer/src/components/nodes/steps/ActivityRenderer.tsx +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import React from 'react'; -import formatMessage from 'format-message'; -import { generateSDKTitle } from '@bfc/shared'; - -import { NodeEventTypes } from '../../../constants/NodeEventTypes'; -import { getElementColor, ElementIcon } from '../../../utils/obiPropertyResolver'; -import { NodeMenu } from '../../menus/NodeMenu'; -import { FormCard } from '../templates/FormCard'; -import { NodeProps, defaultNodeProps } from '../nodeProps'; -import { useLgTemplate } from '../../../utils/hooks'; - -export const ActivityRenderer: React.FC = props => { - const { id, data, onEvent } = props; - const templateText = useLgTemplate(data.activity, data.$designer && data.$designer.id); - - const nodeColors = getElementColor(data.$type); - const header = formatMessage('Activity'); - return ( - } - nodeColors={nodeColors} - onClick={() => { - onEvent(NodeEventTypes.Focus, { id }); - }} - /> - ); -}; - -ActivityRenderer.defaultProps = defaultNodeProps; diff --git a/Composer/packages/extensions/visual-designer/src/components/nodes/steps/DefaultRenderer.tsx b/Composer/packages/extensions/visual-designer/src/components/nodes/steps/DefaultRenderer.tsx deleted file mode 100644 index a40a063063..0000000000 --- a/Composer/packages/extensions/visual-designer/src/components/nodes/steps/DefaultRenderer.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import React, { FC } from 'react'; - -import { NodeProps } from '../nodeProps'; -import { renderSDKType } from '../../../schema/uischemaRenderer'; -import { NodeMenu } from '../../menus/NodeMenu'; -import { NodeEventTypes } from '../../../constants/NodeEventTypes'; - -export const DefaultRenderer: FC = ({ id, data, onEvent }) => { - return renderSDKType(data, { - menu: , - onClick: () => onEvent(NodeEventTypes.Focus, { id }), - }); -}; diff --git a/Composer/packages/extensions/visual-designer/src/components/renderers/ElementRenderer.tsx b/Composer/packages/extensions/visual-designer/src/components/renderers/ElementRenderer.tsx index ec5ef11b60..9403d61328 100644 --- a/Composer/packages/extensions/visual-designer/src/components/renderers/ElementRenderer.tsx +++ b/Composer/packages/extensions/visual-designer/src/components/renderers/ElementRenderer.tsx @@ -10,20 +10,12 @@ import { ObiTypes } from '../../constants/ObiTypes'; import { AttrNames } from '../../constants/ElementAttributes'; import { NodeRendererContext } from '../../store/NodeRendererContext'; import { SelectionContext } from '../../store/SelectionContext'; -import { - DefaultRenderer, - BeginDialog, - ReplaceDialog, - ActivityRenderer, - ChoiceInput, - BotAsks, - UserInput, - InvalidPromptBrick, -} from '../nodes/index'; +import { BeginDialog, ReplaceDialog, ChoiceInput, BotAsks, UserInput, InvalidPromptBrick } from '../nodes/index'; import { ConditionNode } from '../nodes/steps/ConditionNode'; import { ForeachDetail } from '../nodes/steps/ForeachDetail'; import { ForeachPageDetail } from '../nodes/steps/ForeachPageDetail'; import { NodeProps, defaultNodeProps } from '../nodes/nodeProps'; +import { UISchemaRenderer } from '../../schema/uischemaRenderer'; const rendererByObiType = { [ObiTypes.BeginDialog]: BeginDialog, @@ -31,13 +23,13 @@ const rendererByObiType = { [ObiTypes.ForeachDetail]: ForeachDetail, [ObiTypes.ForeachPageDetail]: ForeachPageDetail, [ObiTypes.ReplaceDialog]: ReplaceDialog, - [ObiTypes.SendActivity]: ActivityRenderer, + [ObiTypes.SendActivity]: UISchemaRenderer, [ObiTypes.ChoiceInputDetail]: ChoiceInput, [ObiTypes.BotAsks]: BotAsks, [ObiTypes.UserAnswers]: UserInput, [ObiTypes.InvalidPromptBrick]: InvalidPromptBrick, }; -const DEFAULT_RENDERER = DefaultRenderer; +const DEFAULT_RENDERER = UISchemaRenderer; function chooseRendererByType($type): FC | ComponentClass { const renderer = rendererByObiType[$type] || DEFAULT_RENDERER; diff --git a/Composer/packages/extensions/visual-designer/src/schema/uischema.ts b/Composer/packages/extensions/visual-designer/src/schema/uischema.tsx similarity index 73% rename from Composer/packages/extensions/visual-designer/src/schema/uischema.ts rename to Composer/packages/extensions/visual-designer/src/schema/uischema.tsx index 620105afaf..667af73b7d 100644 --- a/Composer/packages/extensions/visual-designer/src/schema/uischema.ts +++ b/Composer/packages/extensions/visual-designer/src/schema/uischema.tsx @@ -2,8 +2,13 @@ // Licensed under the MIT License. import { SDKTypes } from '@bfc/shared'; +import React from 'react'; import { ActionCard } from '../widgets/ActionCard'; +import { ActivityRenderer } from '../widgets/ActivityRenderer'; +import { DialogRefCard } from '../widgets/DialogRefCard'; +import { ElementIcon } from '../utils/obiPropertyResolver'; +import { ObiColors } from '../constants/ElementColors'; import { UISchema } from './uischema.types'; @@ -11,6 +16,24 @@ export const uiSchema: UISchema = { default: { 'ui:widget': ActionCard, }, + [SDKTypes.SendActivity]: { + 'ui:widget': ActivityRenderer, + field: 'activity', + icon: ElementIcon.MessageBot, + colors: { + theme: ObiColors.BlueMagenta20, + icon: ObiColors.BlueMagenta30, + }, + }, + [SDKTypes.BeginDialog]: { + 'ui:widget': DialogRefCard, + dialog: data => data.dialog, + }, + [SDKTypes.ReplaceDialog]: { + 'ui:widget': DialogRefCard, + dialog: data => data.dialog, + getRefContent: data => dialogRef => <>Switch to {dialogRef}, + }, [SDKTypes.EditArray]: { 'ui:widget': ActionCard, content: data => `${data.changeType} {${data.itemsProperty || '?'}}`, 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 465d09b1a2..546980b1ab 100644 --- a/Composer/packages/extensions/visual-designer/src/schema/uischema.types.ts +++ b/Composer/packages/extensions/visual-designer/src/schema/uischema.types.ts @@ -4,6 +4,8 @@ import { FC, ComponentClass } from 'react'; import { BaseSchema, SDKTypes } from '@bfc/shared'; +import { NodeEventTypes } from '../constants/NodeEventTypes'; + export enum UISchemaBuiltinKeys { default = 'default', } @@ -23,8 +25,12 @@ export interface UIWidget { export type WidgetComponent = FC | ComponentClass; +export type WidgetEventHandler = (eventName: NodeEventTypes, eventData?: any) => void; + export interface WidgetContainerProps { + id: string; data: BaseSchema; + onEvent: WidgetEventHandler; [propKey: string]: any; } diff --git a/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx b/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx index d7d5fa73f4..a088031c80 100644 --- a/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx +++ b/Composer/packages/extensions/visual-designer/src/schema/uischemaRenderer.tsx @@ -1,12 +1,12 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -import React from 'react'; +import React, { FC } from 'react'; import { BaseSchema } from '@bfc/shared'; import get from 'lodash/get'; import { uiSchema } from './uischema'; -import { UIWidget, UI_WIDGET_KEY, UIWidgetProp } from './uischema.types'; +import { UIWidget, UI_WIDGET_KEY, UIWidgetProp, WidgetEventHandler } from './uischema.types'; const buildWidgetProp = (data: BaseSchema, rawPropValue: UIWidgetProp) => { if (typeof rawPropValue === 'function') { @@ -35,14 +35,20 @@ const parseWidgetSchema = (data: BaseSchema, widgetSchema: UIWidget) => { }; }; -const renderWidget = (inputData, schema: UIWidget, contextProps = {}): JSX.Element => { - const { Widget, props } = parseWidgetSchema(inputData, schema); - return ; -}; +export interface UISchemaRendererProps { + /** The uniq id of current schema data. Usually a json path. */ + id: string; + + /** Declarative json with a $type field. */ + data: BaseSchema; -export const renderSDKType = (data: BaseSchema, context?: { menu: JSX.Element; onClick }): JSX.Element => { + /** Handle UI events */ + onEvent: WidgetEventHandler; +} + +export const UISchemaRenderer: FC = ({ id, data, onEvent, ...contextProps }): JSX.Element => { const $type = get(data, '$type'); const schema = get(uiSchema, $type, uiSchema.default); - - return renderWidget(data, schema, context); + const { Widget, props } = parseWidgetSchema(data, schema); + return ; }; diff --git a/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx b/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx index a8200df902..fc01237ecd 100644 --- a/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx +++ b/Composer/packages/extensions/visual-designer/src/widgets/ActionCard.tsx @@ -7,17 +7,17 @@ import { generateSDKTitle } from '@bfc/shared'; import { FormCard } from '../components/nodes/templates/FormCard'; import { WidgetContainerProps, WidgetComponent } from '../schema/uischema.types'; import { ObiColors } from '../constants/ElementColors'; +import { NodeEventTypes } from '../constants/NodeEventTypes'; +import { NodeMenu } from '../components/menus/NodeMenu'; export interface ActionCardProps extends WidgetContainerProps { title: string; icon: string; content: string | number | JSX.Element; - menu: JSX.Element; colors?: { theme: string; icon: string; }; - onClick: () => any; } const DefaultCardColor = { @@ -26,17 +26,24 @@ const DefaultCardColor = { }; export const ActionCard: WidgetComponent = ({ + id, data, + onEvent, title, icon, content, - menu, - onClick, colors = DefaultCardColor, }) => { const header = generateSDKTitle(data, title); const nodeColors = { themeColor: colors.theme, iconColor: colors.icon }; return ( - + } + icon={icon} + label={content} + nodeColors={nodeColors} + onClick={() => onEvent(NodeEventTypes.Focus, { id })} + /> ); }; diff --git a/Composer/packages/extensions/visual-designer/src/widgets/ActivityRenderer.tsx b/Composer/packages/extensions/visual-designer/src/widgets/ActivityRenderer.tsx new file mode 100644 index 0000000000..9fd5dd66f9 --- /dev/null +++ b/Composer/packages/extensions/visual-designer/src/widgets/ActivityRenderer.tsx @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React from 'react'; +import { generateSDKTitle } from '@bfc/shared'; +import get from 'lodash/get'; + +import { NodeEventTypes } from '../constants/NodeEventTypes'; +import { ElementIcon } from '../utils/obiPropertyResolver'; +import { NodeMenu } from '../components/menus/NodeMenu'; +import { FormCard } from '../components/nodes/templates/FormCard'; +import { useLgTemplate } from '../utils/hooks'; +import { WidgetContainerProps } from '../schema/uischema.types'; +import { ObiColors } from '../constants/ElementColors'; + +export interface ActivityRenderer extends WidgetContainerProps { + /** indicates which field contains lg activity. ('activity', 'prompt', 'invalidPropmt'...) */ + field: string; + icon: ElementIcon; + colors?: { + theme: string; + icon: string; + }; +} + +const DefaultThemeColor = { + theme: ObiColors.BlueMagenta20, + icon: ObiColors.BlueMagenta30, +}; + +export const ActivityRenderer: React.FC = ({ + id, + data, + onEvent, + field, + icon = ElementIcon.MessageBot, + colors = DefaultThemeColor, +}) => { + const designerId = get(data, '$designer.id'); + const activityTemplate = get(data, field, ''); + + const templateText = useLgTemplate(activityTemplate, designerId); + const nodeColors = { themeColor: colors.theme, iconColor: colors.icon }; + + return ( + } + nodeColors={nodeColors} + onClick={() => { + onEvent(NodeEventTypes.Focus, { id }); + }} + /> + ); +}; diff --git a/Composer/packages/extensions/visual-designer/src/widgets/DialogRefCard.tsx b/Composer/packages/extensions/visual-designer/src/widgets/DialogRefCard.tsx new file mode 100644 index 0000000000..59850494de --- /dev/null +++ b/Composer/packages/extensions/visual-designer/src/widgets/DialogRefCard.tsx @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { jsx } from '@emotion/core'; +import { generateSDKTitle } from '@bfc/shared'; + +import { FormCard } from '../components/nodes/templates/FormCard'; +import { WidgetContainerProps, WidgetComponent } from '../schema/uischema.types'; +import { ObiColors } from '../constants/ElementColors'; +import { NodeEventTypes } from '../constants/NodeEventTypes'; +import { NodeMenu } from '../components/menus/NodeMenu'; +import get from 'lodash/get'; + +export interface DialogRefCardProps extends WidgetContainerProps { + dialog: string | object; + getRefContent?: (dialogRef: JSX.Element) => JSX.Element; + colors?: { + theme: string; + icon: string; + }; +} + +const DefaultCardColor = { + theme: ObiColors.AzureGray3, + icon: ObiColors.AzureGray2, +}; + +export const DialogRefCard: WidgetComponent = ({ + id, + data, + onEvent, + dialog, + getRefContent, + colors = DefaultCardColor, +}) => { + const header = generateSDKTitle(data); + const nodeColors = { themeColor: colors.theme, iconColor: colors.icon }; + const calleeDialog = typeof dialog === 'object' ? get(dialog, '$ref') : dialog; + const dialogRef = ( + { + e.stopPropagation(); + onEvent(NodeEventTypes.OpenDialog, { caller: id, callee: calleeDialog }); + }} + > + {calleeDialog} + + ); + return ( + } + label={typeof getRefContent === 'function' ? getRefContent(dialogRef) : dialogRef} + nodeColors={nodeColors} + onClick={() => onEvent(NodeEventTypes.Focus, { id })} + /> + ); +};