diff --git a/Composer/packages/client/src/components/ToolBar/ToolBar.tsx b/Composer/packages/client/src/components/ToolBar/ToolBar.tsx index a71335a284..361e7632c7 100644 --- a/Composer/packages/client/src/components/ToolBar/ToolBar.tsx +++ b/Composer/packages/client/src/components/ToolBar/ToolBar.tsx @@ -3,12 +3,14 @@ /** @jsx jsx */ import { jsx } from '@emotion/core'; -import { useCallback, Fragment } from 'react'; +import { useCallback, Fragment, useMemo } from 'react'; import formatMessage from 'format-message'; import { ActionButton, CommandButton } from 'office-ui-fabric-react/lib/Button'; import { DialogInfo } from '@bfc/shared'; +import get from 'lodash/get'; import { useStoreContext } from '../../hooks/useStoreContext'; +import { VisualEditorAPI } from '../../pages/design/FrameAPI'; import { headerSub, leftActions, rightActions, actionButton } from './styles'; @@ -67,8 +69,20 @@ export function ToolBar(props: ToolbarProps) { } = props; const { actions: { onboardingAddCoachMarkRef, createDialogBegin, exportToZip }, - state: { projectId }, + state: { projectId, visualEditorSelection }, } = useStoreContext(); + + const { actionSelected, showDisableBtn, showEnableBtn } = useMemo(() => { + const actionSelected = Array.isArray(visualEditorSelection) && visualEditorSelection.length > 0; + if (!actionSelected) { + return {}; + } + const selectedActions = visualEditorSelection.map((id) => get(currentDialog?.content, id)); + const showDisableBtn = selectedActions.some((x) => get(x, 'disabled') !== true); + const showEnableBtn = selectedActions.some((x) => get(x, 'disabled') === true); + return { actionSelected, showDisableBtn, showEnableBtn }; + }, [visualEditorSelection]); + const left: IToolBarItem[] = []; const right: IToolBarItem[] = []; @@ -122,6 +136,34 @@ export function ToolBar(props: ToolbarProps) { )} {left.map(itemList)}{' '} + {window.location.href.includes('/dialogs/') && ( + { + VisualEditorAPI.disableSelection(); + }, + }, + { + key: 'enable', + text: formatMessage('Enable'), + disabled: !showEnableBtn, + onClick: () => { + VisualEditorAPI.enableSelection(); + }, + }, + ], + }} + text={formatMessage('Disable')} + /> + )} {window.location.href.includes('/dialogs/') && ( { cutSelection: () => visualEditorFrameAPI.invoke('cutSelection'), moveSelection: () => visualEditorFrameAPI.invoke('moveSelection'), deleteSelection: () => visualEditorFrameAPI.invoke('deleteSelection'), + disableSelection: () => visualEditorFrameAPI.invoke('disableSelection'), + enableSelection: () => visualEditorFrameAPI.invoke('enableSelection'), }; })(); diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts index 87fd4bf7d9..40b6edeeb4 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/constants/ScreenReaderMessage.ts @@ -9,6 +9,8 @@ export enum ScreenReaderMessage { DialogOpened = 'Dialog opened', ActionDeleted = 'Action deleted', ActionsDeleted = 'Actions deleted', + ActionsDisabled = 'Actions disabled', + ActionsEnabled = 'Actions enabled', ActionCreated = 'Action created', ActionsCreated = 'Actions created', EventCreated = 'Event created', diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts index de87dba0b5..bbfa51d423 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-editor/hooks/useEditorEventApi.ts @@ -30,6 +30,8 @@ export const useEditorEventApi = ( cutSelectedActions, deleteSelectedAction, deleteSelectedActions, + disableSelectedActions, + enableSelectedActions, updateRecognizer, } = useDialogEditApi(shellApi); const { createDialog, readDialog, updateDialog } = useDialogApi(shellApi); @@ -267,6 +269,20 @@ export const useEditorEventApi = ( announce(ScreenReaderMessage.ActionsDeleted); }; break; + case NodeEventTypes.DisableSelection: + handler = () => { + const actionIds = getClipboardTargetsFromContext(); + onChange(disableSelectedActions(path, data, actionIds)); + announce(ScreenReaderMessage.ActionsDisabled); + }; + break; + case NodeEventTypes.EnableSelection: + handler = () => { + const actionIds = getClipboardTargetsFromContext(); + onChange(enableSelectedActions(path, data, actionIds)); + announce(ScreenReaderMessage.ActionsEnabled); + }; + break; case NodeEventTypes.AppendSelection: handler = (e) => { trackActionListChange(e.target); @@ -300,6 +316,8 @@ export const useEditorEventApi = ( (window as any).cutSelection = () => handleEditorEvent(NodeEventTypes.CutSelection); (window as any).moveSelection = () => handleEditorEvent(NodeEventTypes.MoveSelection); (window as any).deleteSelection = () => handleEditorEvent(NodeEventTypes.DeleteSelection); + (window as any).disableSelection = () => handleEditorEvent(NodeEventTypes.DisableSelection); + (window as any).enableSelection = () => handleEditorEvent(NodeEventTypes.EnableSelection); return { handleEditorEvent, diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/components/IconBrick.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/components/IconBrick.tsx index d384a7c0a3..a9c1129d08 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/components/IconBrick.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/components/IconBrick.tsx @@ -7,12 +7,15 @@ import { Icon } from 'office-ui-fabric-react/lib/Icon'; import { IconBrickSize } from '../constants/ElementSizes'; -export const IconBrick = ({ onClick }): JSX.Element => { +export const IconBrick = ({ onClick, disabled = false }): JSX.Element => { + const brickColor = disabled ? 'transparent' : '#FFFFFF'; + const iconColor = disabled ? '#ddd' : '#FED9CC'; + return (
{ display: 'flex', alignItems: 'center', justifyContent: 'center', - background: '#FED9CC', + background: iconColor, width: 16, height: 16, borderRadius: '8px', diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts index 795a295b70..e0f0ac5637 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/constants/NodeEventTypes.ts @@ -15,6 +15,8 @@ export enum NodeEventTypes { PasteSelection = 'event.data.paste-selection', MoveSelection = 'event.data.move-selection', DeleteSelection = 'event.data.delete-selection', + DisableSelection = 'event.data.disable-selection', + EnableSelection = 'event.data.enable-selection', AppendSelection = 'event.data.paste-selection--keyboard', InsertSelection = 'event.data.paste-selection--menu', Undo = 'event.operation.undo', diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/models/IndexedNode.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/models/IndexedNode.ts index 2fe1a9b742..ba5b4f4c79 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/models/IndexedNode.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/models/IndexedNode.ts @@ -1,11 +1,15 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +import clone from 'lodash/clone'; + export class IndexedNode { id: string; json: any; constructor(id, payload) { this.id = id; - this.json = payload; + // Adaptive Flow lib leverages origin json to store necessary UI states, + // thus shallow clone input json here to support immutable case. + this.json = clone(payload); } } diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/inheritParentProperty.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/inheritParentProperty.ts new file mode 100644 index 0000000000..d7b4b53e2c --- /dev/null +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/inheritParentProperty.ts @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { IndexedNode } from '../models/IndexedNode'; + +export const inheritParentProperties = (parentJson: any, childNodes: IndexedNode[]) => { + /** inherit the 'disabled' property */ + if (parentJson.disabled === true) { + // Action.disabled is typed with IExpressionString but could be a boolean. + // Composer only show disable effects when it's strictly set to static {true}. + childNodes.forEach((node) => (node.json.disabled = true)); + } +}; diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformForeach.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformForeach.ts index f745ff6e03..ade2b68703 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformForeach.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformForeach.ts @@ -5,6 +5,8 @@ import { AdaptiveFieldNames } from '../constants/AdaptiveFieldNames'; import { AdaptiveKinds } from '../constants/AdaptiveKinds'; import { IndexedNode } from '../models/IndexedNode'; +import { inheritParentProperties } from './inheritParentProperty'; + const StepsKey = AdaptiveFieldNames.Actions; export function transformForeach( @@ -24,10 +26,13 @@ export function transformForeach( children: steps, }); - return { + const result = { foreachDetail: foreachDetailNode, stepGroup: stepsNode, loopBegin: new IndexedNode(jsonpath, { $kind: AdaptiveKinds.LoopIndicator }), loopEnd: new IndexedNode(jsonpath, { $kind: AdaptiveKinds.LoopIndicator }), }; + + inheritParentProperties(input, Object.values(result)); + return result; } diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformIfCondition.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformIfCondition.ts index e4992c1feb..910fbda2c3 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformIfCondition.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformIfCondition.ts @@ -5,6 +5,8 @@ import { AdaptiveFieldNames } from '../constants/AdaptiveFieldNames'; import { AdaptiveKinds } from '../constants/AdaptiveKinds'; import { IndexedNode } from '../models/IndexedNode'; +import { inheritParentProperties } from './inheritParentProperty'; + const IfBranchKey = AdaptiveFieldNames.Actions; const ElseBranchKey = AdaptiveFieldNames.ElseActions; @@ -33,5 +35,6 @@ export function transformIfCondtion( }), }; + inheritParentProperties(input, Object.values(result)); return result; } diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformStepGroup.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformStepGroup.ts index 78e2d9368f..0e246a85ad 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformStepGroup.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformStepGroup.ts @@ -5,9 +5,13 @@ import { AdaptiveKinds } from '../constants/AdaptiveKinds'; import { IndexedNode } from '../models/IndexedNode'; import { normalizeObiStep } from '../utils/adaptive/stepBuilder'; +import { inheritParentProperties } from './inheritParentProperty'; + export function transformStepGroup(input, groupId): IndexedNode[] { if (!input || input.$kind !== AdaptiveKinds.StepGroup) return []; if (!input.children || !Array.isArray(input.children)) return []; - return input.children.map((step, index) => new IndexedNode(`${groupId}[${index}]`, normalizeObiStep(step))); + const results = input.children.map((step, index) => new IndexedNode(`${groupId}[${index}]`, normalizeObiStep(step))); + inheritParentProperties(input, results); + return results; } diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformSwitchCondition.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformSwitchCondition.ts index bcc23d3fa7..9041f98905 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformSwitchCondition.ts +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/transformers/transformSwitchCondition.ts @@ -5,6 +5,8 @@ import { AdaptiveFieldNames } from '../constants/AdaptiveFieldNames'; import { AdaptiveKinds } from '../constants/AdaptiveKinds'; import { IndexedNode } from '../models/IndexedNode'; +import { inheritParentProperties } from './inheritParentProperty'; + const ConditionKey = AdaptiveFieldNames.Condition; const CasesKey = AdaptiveFieldNames.Cases; const CaseStepKey = AdaptiveFieldNames.Actions; @@ -52,5 +54,7 @@ export function transformSwitchCondition( }); }) ); + + inheritParentProperties(input, [result.condition, result.choice, ...result.branches]); return result; } diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/ActionCard.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/ActionCard.tsx index d0ceae1d2b..013dc179d2 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/ActionCard.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/ActionCard.tsx @@ -15,5 +15,13 @@ export interface ActionCardProps extends WidgetContainerProps { } export const ActionCard: WidgetComponent = ({ header, body, footer, ...widgetContext }) => { - return } />; + const disabled = widgetContext.data.disabled === true; + return ( + } + /> + ); }; diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/CardTemplate.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/CardTemplate.tsx index 270fca6b69..dd45c59440 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/CardTemplate.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/CardTemplate.tsx @@ -2,32 +2,28 @@ // Licensed under the MIT License. /** @jsx jsx */ -import { jsx, css } from '@emotion/core'; +import { jsx } from '@emotion/core'; import { FC, ReactNode } from 'react'; import { TextDiv } from '@bfc/ui-shared'; -import { StandardNodeWidth, HeaderHeight, StandardSectionHeight } from '../../constants/ElementSizes'; +import { StandardNodeWidth } from '../../constants/ElementSizes'; import { ObiColors } from '../../constants/ElementColors'; import { ArrowLine } from '../../components/ArrowLine'; -const containerCSS = css` - font-size: 12px; - cursor: pointer; - overflow: hidden; - background-color: white; - border-radius: 2px 2px 0 0; - box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1); -`; - -const fullWidthSection = css` - width: 100%; - box-sizing: border-box; -`; +import { + HeaderCSS, + BodyCSS, + FooterCSS, + SeparateLineCSS, + CardContainerCSS, + DisabledCardContainerCSS, +} from './CardTemplateStyle'; export interface CardTemplateProps { header: ReactNode; body?: ReactNode; footer?: ReactNode; + disabled?: boolean; onClick?: (event: React.MouseEvent) => void; onClickHeader?: (event: React.MouseEvent) => void; onClickBody?: (event: React.MouseEvent) => void; @@ -38,61 +34,37 @@ export const CardTemplate: FC = ({ header, body, footer, + disabled, onClick, onClickHeader, onClickBody, onClickFooter, }) => { + const headerCSS = HeaderCSS; + const bodyCSS = BodyCSS; + const footerCSS = FooterCSS; + const containerCSS = disabled ? DisabledCardContainerCSS : CardContainerCSS; + const renderHeader = (header: ReactNode) => ( -
+
{header}
); const renderBody = (body: ReactNode) => ( -
+
{body}
); const renderFooter = (footer: ReactNode) => ( -
+
{footer}
); const renderSeparateline = () => ( -
+
); @@ -103,11 +75,7 @@ export const CardTemplate: FC = ({ return (
{ diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/CardTemplateStyle.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/CardTemplateStyle.ts new file mode 100644 index 0000000000..dd9c783ccb --- /dev/null +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionCard/CardTemplateStyle.ts @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { css } from '@emotion/core'; + +import { HeaderHeight, StandardSectionHeight, StandardNodeWidth } from '../../constants/ElementSizes'; +import { DisabledContainer } from '../styles/DisabledStyle'; + +const fullWidthSection = css` + width: 100%; + box-sizing: border-box; +`; + +export const HeaderCSS = css` + ${fullWidthSection}; + height: ${HeaderHeight}px; +`; + +export const BodyCSS = css` + ${fullWidthSection}; + min-height: ${StandardSectionHeight}px; + padding: 7px 8px; +`; + +export const FooterCSS = css` + ${fullWidthSection}; + min-height: ${StandardSectionHeight}px; + padding: 8px 8px; +`; + +export const SeparateLineCSS = css` + display: block; + height: 0px; + overflow: visible; +`; + +const containerCSS = css` + font-size: 12px; + cursor: pointer; + overflow: hidden; + border-radius: 2px 2px 0 0; + width: ${StandardNodeWidth}px; + min-height: ${HeaderHeight}px; +`; + +export const CardContainerCSS = css` + ${containerCSS}; + background-color: white; + box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1); +`; + +export const DisabledCardContainerCSS = css` + ${CardContainerCSS}; + ${DisabledContainer}; +`; diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionHeader/ActionHeader.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionHeader/ActionHeader.tsx index e50171076b..43194e4580 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionHeader/ActionHeader.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionHeader/ActionHeader.tsx @@ -3,16 +3,22 @@ /** @jsx jsx */ import { useContext } from 'react'; -import { jsx, css } from '@emotion/core'; +import { jsx } from '@emotion/core'; import { generateSDKTitle } from '@bfc/shared'; -import { TruncatedCSS, ColorlessFontCSS } from '@bfc/ui-shared'; import { WidgetComponent, WidgetContainerProps } from '../../types/flowRenderer.types'; -import { StandardNodeWidth, HeaderHeight } from '../../constants/ElementSizes'; import { DefaultColors } from '../../constants/ElementColors'; import { RendererContext } from '../../contexts/RendererContext'; +import { DisabledIconColor } from '../styles/DisabledStyle'; import { Icon, BuiltinIcons } from './icon'; +import { + HeaderContainerCSS, + HeaderBodyCSS, + HeaderTextCSS, + DisabledHeaderContainerCSS, + DisabledHeaderTextCSS, +} from './ActionHeaderStyle'; export interface ActionHeaderProps extends WidgetContainerProps { title?: string; @@ -26,13 +32,6 @@ export interface ActionHeaderProps extends WidgetContainerProps { }; } -const container = css` - cursor: pointer; - position: relative; - display: flex; - align-items: center; -`; - export const ActionHeader: WidgetComponent = ({ id, data, @@ -43,33 +42,21 @@ export const ActionHeader: WidgetComponent = ({ menu, colors = DefaultColors, }) => { - const headerContent = disableSDKTitle ? title : generateSDKTitle(data, title); + const disabled = data.disabled === true; + const containerCSS = disabled ? DisabledHeaderContainerCSS : HeaderContainerCSS(colors.theme); + const bodyCSS = HeaderBodyCSS; + const textCSS = disabled ? DisabledHeaderTextCSS : HeaderTextCSS(colors.color); + const iconColor = disabled ? DisabledIconColor : colors.icon; - const headerText = css` - ${ColorlessFontCSS}; - ${TruncatedCSS}; - `; + const headerContent = disableSDKTitle ? title : generateSDKTitle(data, title); const { NodeMenu } = useContext(RendererContext); const menuNode = menu === 'none' ? null : menu || ; return ( -
-
+
+
{icon && icon !== BuiltinIcons.None && (
= ({ marginRight: '5px', }} > - +
)} -
+
{headerContent}
diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionHeader/ActionHeaderStyle.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionHeader/ActionHeaderStyle.ts new file mode 100644 index 0000000000..366b2484db --- /dev/null +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/ActionHeader/ActionHeaderStyle.ts @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { css } from '@emotion/core'; +import { ColorlessFontCSS, TruncatedCSS } from '@bfc/ui-shared'; + +import { StandardNodeWidth, HeaderHeight } from '../../constants/ElementSizes'; +import { DisabledContainer, DisabledText } from '../styles/DisabledStyle'; + +const container = css` + cursor: pointer; + position: relative; + display: flex; + align-items: center; + width: ${StandardNodeWidth}px; + height: ${HeaderHeight}px; +`; + +export const HeaderContainerCSS = (backgroundColor = 'transparent') => css` + ${container}; + background-color: ${backgroundColor}; +`; + +export const DisabledHeaderContainerCSS = css` + ${container}; + ${DisabledContainer}; + ${DisabledText} +`; + +export const HeaderBodyCSS = css` + width: calc(100% - 40px); + padding: 4px 8px; + display: flex; +`; + +const headerText = css` + ${ColorlessFontCSS}; + ${TruncatedCSS}; + line-height: 16px; + transform: translateY(-1px); +`; + +export const HeaderTextCSS = (textColor = 'black') => css` + ${headerText}; + color: ${textColor}; +`; + +export const DisabledHeaderTextCSS = css` + ${headerText}; + ${DisabledText}; +`; diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/PromptWidget.tsx b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/PromptWidget.tsx index 337270239c..a8aa146fa4 100644 --- a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/PromptWidget.tsx +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/PromptWidget.tsx @@ -92,7 +92,10 @@ export const PromptWidget: FC = ({ - onEvent(NodeEventTypes.Focus, { id, tab: PromptTab.OTHER })} /> + onEvent(NodeEventTypes.Focus, { id, tab: PromptTab.OTHER })} + />
diff --git a/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/styles/DisabledStyle.ts b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/styles/DisabledStyle.ts new file mode 100644 index 0000000000..2307930ffb --- /dev/null +++ b/Composer/packages/extensions/adaptive-flow/src/adaptive-flow-renderer/widgets/styles/DisabledStyle.ts @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { css } from '@emotion/core'; + +const DisabledColor = '#656565'; + +export const DisabledContainer = css` + outline: 1px solid ${DisabledColor}; + background: transparent; +`; + +export const DisabledText = css` + color: ${DisabledColor}; +`; + +export const DisabledIconColor = DisabledColor; diff --git a/Composer/packages/extensions/extension/src/hooks/useDialogEditApi.ts b/Composer/packages/extensions/extension/src/hooks/useDialogEditApi.ts index 7c1fb3f07f..0474e00945 100644 --- a/Composer/packages/extensions/extension/src/hooks/useDialogEditApi.ts +++ b/Composer/packages/extensions/extension/src/hooks/useDialogEditApi.ts @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { BaseSchema, DialogUtils, ShellApi } from '@bfc/shared'; +import { disableNodes, enableNodes } from '@bfc/shared/lib/dialogUtils'; import { useActionApi } from './useActionApi'; @@ -53,6 +54,13 @@ export function useDialogEditApi(shellApi: ShellApi) { }); } + function disableSelectedActions(dialogId: string, dialogData, actionIds: string[]) { + return disableNodes(dialogData, actionIds); + } + + function enableSelectedActions(dialogId: string, dialogData, actionIds: string[]) { + return enableNodes(dialogData, actionIds); + } async function copySelectedActions(dialogId, dialogData, actionIds: string[]) { const actions = queryNodes(dialogData, actionIds); return copyActions(dialogId, actions); @@ -78,5 +86,7 @@ export function useDialogEditApi(shellApi: ShellApi) { copySelectedActions, cutSelectedActions, updateRecognizer, + disableSelectedActions, + enableSelectedActions, }; } diff --git a/Composer/packages/lib/shared/src/dialogUtils/jsonTracker.ts b/Composer/packages/lib/shared/src/dialogUtils/jsonTracker.ts index e2c55e9632..7bab544f65 100644 --- a/Composer/packages/lib/shared/src/dialogUtils/jsonTracker.ts +++ b/Composer/packages/lib/shared/src/dialogUtils/jsonTracker.ts @@ -5,6 +5,8 @@ import cloneDeep from 'lodash/cloneDeep'; import get from 'lodash/get'; import set from 'lodash/set'; +import { walkAdaptiveActionList } from '../walkerUtils'; + function parseSelector(path: string): null | string[] { if (!path) return null; @@ -155,6 +157,28 @@ export function deleteNodes(inputDialog, nodeIds: string[], callbackOnRemovedNod return dialog; } +export function disableNodes(inputDialog, nodeIds: string[]) { + const dialog = cloneDeep(inputDialog); + + const selectedActions = queryNodes(dialog, nodeIds); + walkAdaptiveActionList(selectedActions, (action: any) => { + action.disabled = true; + }); + + return dialog; +} + +export function enableNodes(inputDialog, nodeIds: string[]) { + const dialog = cloneDeep(inputDialog); + + const selectedActions = queryNodes(dialog, nodeIds); + walkAdaptiveActionList(selectedActions, (action: any) => { + delete action.disabled; + }); + + return dialog; +} + export function insertAction(inputDialog, arrayPath: string, position: number, newAction) { const dialog = cloneDeep(inputDialog); const current = get(dialog, arrayPath, []); diff --git a/Composer/packages/lib/shared/src/types/sdk.ts b/Composer/packages/lib/shared/src/types/sdk.ts index bd79db2d60..8846ad5eb2 100644 --- a/Composer/packages/lib/shared/src/types/sdk.ts +++ b/Composer/packages/lib/shared/src/types/sdk.ts @@ -18,6 +18,8 @@ export interface BaseSchema { $copy?: string; /** Extra information for the Bot Framework Composer. */ $designer?: DesignerData; + /** If 'disabled' set to true, runtime will skip this action. */ + disabled: any; } /* Union of components which implement the IActivityTemplate interface */