diff --git a/Composer/packages/client/src/ShellApi.ts b/Composer/packages/client/src/ShellApi.ts index 935a129c5d..ab5bb8945b 100644 --- a/Composer/packages/client/src/ShellApi.ts +++ b/Composer/packages/client/src/ShellApi.ts @@ -116,6 +116,8 @@ export const ShellApi: React.FC = () => { }); }); }); + apiClient.registerApi('undo', actions.undo); + apiClient.registerApi('redo', actions.redo); return () => { apiClient.disconnect(); diff --git a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx index 8542049eec..9a7ef7cfa2 100644 --- a/Composer/packages/client/src/extension-container/ExtensionContainer.tsx +++ b/Composer/packages/client/src/extension-container/ExtensionContainer.tsx @@ -99,6 +99,14 @@ const shellApi = { validateExpression: (expression: string) => { return apiClient.apiCall('isExpression', { expression }); }, + + undo: () => { + return apiClient.apiCall('undo'); + }, + + redo: () => { + return apiClient.apiCall('redo'); + }, }; function ExtensionContainer() { diff --git a/Composer/packages/extensions/visual-designer/src/constants/KeyboardCommandTypes.ts b/Composer/packages/extensions/visual-designer/src/constants/KeyboardCommandTypes.ts index b6984c31d9..617f8700db 100644 --- a/Composer/packages/extensions/visual-designer/src/constants/KeyboardCommandTypes.ts +++ b/Composer/packages/extensions/visual-designer/src/constants/KeyboardCommandTypes.ts @@ -19,6 +19,10 @@ export const KeyboardCommandTypes = { Cut: 'cut', Paste: 'paste', }, + Operation: { + Redo: 'redo', + Undo: 'undo', + }, }; const findCommandAreaByValue = (() => { @@ -40,6 +44,7 @@ const findCommandAreaByValue = (() => { export const KeyboardPrimaryTypes = { Cursor: 'Cursor', Node: 'Node', + Operation: 'Operation', }; const BasicShortcuts = { @@ -88,13 +93,26 @@ const KeyboardNodeEditingShortcuts = { 'Mac.Meta.x': KeyboardCommandTypes.Node.Cut, }; -const { arrowNavigation, tabNavigation, keyboardNodeEditing } = EditorConfig.features; +const KeyboardOperationEditingShortcuts = { + 'Windows.Control.Z': KeyboardCommandTypes.Operation.Undo, + 'Windows.Control.z': KeyboardCommandTypes.Operation.Undo, + 'Windows.Control.Shift.Z': KeyboardCommandTypes.Operation.Redo, + 'Windows.Control.Shift.z': KeyboardCommandTypes.Operation.Redo, + + 'Mac.Meta.Z': KeyboardCommandTypes.Operation.Undo, + 'Mac.Meta.z': KeyboardCommandTypes.Operation.Undo, + 'Mac.Meta.Shift.Z': KeyboardCommandTypes.Operation.Redo, + 'Mac.Meta.Shift.z': KeyboardCommandTypes.Operation.Redo, +}; + +const { arrowNavigation, tabNavigation, keyboardNodeEditing, keyboardOperationEditing } = EditorConfig.features; const SupportedShortcuts = { ...BasicShortcuts, ...(arrowNavigation ? ArrowMoveShortcuts : null), ...(tabNavigation ? TabNavShortcuts : null), ...(keyboardNodeEditing ? KeyboardNodeEditingShortcuts : null), + ...(keyboardOperationEditing ? KeyboardOperationEditingShortcuts : null), }; export function mapShortcutToKeyboardCommand(keyCode) { diff --git a/Composer/packages/extensions/visual-designer/src/constants/NodeEventTypes.ts b/Composer/packages/extensions/visual-designer/src/constants/NodeEventTypes.ts index 0ec9df5bc7..05e585d1d6 100644 --- a/Composer/packages/extensions/visual-designer/src/constants/NodeEventTypes.ts +++ b/Composer/packages/extensions/visual-designer/src/constants/NodeEventTypes.ts @@ -13,4 +13,6 @@ export enum NodeEventTypes { DeleteSelection = 'event.data.delete-selection', AppendSelection = 'event.data.paste-selection--keyboard', InsertSelection = 'event.data.paste-selection--menu', + Undo = 'event.operation.undo', + Redo = 'event.operation.redo', } diff --git a/Composer/packages/extensions/visual-designer/src/editors/ObiEditor.tsx b/Composer/packages/extensions/visual-designer/src/editors/ObiEditor.tsx index 790a6513b8..46284724f4 100644 --- a/Composer/packages/extensions/visual-designer/src/editors/ObiEditor.tsx +++ b/Composer/packages/extensions/visual-designer/src/editors/ObiEditor.tsx @@ -34,6 +34,8 @@ export const ObiEditor: FC = ({ onOpen, onChange, onSelect, + undo, + redo, }): JSX.Element | null => { let divRef; @@ -125,6 +127,12 @@ export const ObiEditor: FC = ({ onChange(dialog); }; break; + case NodeEventTypes.Undo: + handler = undo; + break; + case NodeEventTypes.Redo: + handler = redo; + break; default: handler = onFocusSteps; break; @@ -251,6 +259,17 @@ export const ObiEditor: FC = ({ focused && onFocusSteps([focused]); break; } + case KeyboardPrimaryTypes.Operation: { + switch (command) { + case KeyboardCommandTypes.Operation.Undo: + dispatchEvent(NodeEventTypes.Undo, {}); + break; + case KeyboardCommandTypes.Operation.Redo: + dispatchEvent(NodeEventTypes.Redo, {}); + break; + } + break; + } default: break; } @@ -310,6 +329,8 @@ ObiEditor.defaultProps = { onOpen: () => {}, onChange: () => {}, onSelect: () => {}, + undo: () => {}, + redo: () => {}, }; interface ObiEditorProps { @@ -323,4 +344,6 @@ interface ObiEditorProps { onOpen: (calleeDialog: string, callerId: string) => any; onChange: (newDialog: any) => any; onSelect: (selection: any) => any; + undo?: () => any; + redo?: () => any; } diff --git a/Composer/packages/extensions/visual-designer/src/editors/editorConfig.ts b/Composer/packages/extensions/visual-designer/src/editors/editorConfig.ts index 60e0c8c337..2ec3097bbf 100644 --- a/Composer/packages/extensions/visual-designer/src/editors/editorConfig.ts +++ b/Composer/packages/extensions/visual-designer/src/editors/editorConfig.ts @@ -4,5 +4,6 @@ export const EditorConfig = { arrowNavigation: true, tabNavigation: true, keyboardNodeEditing: false, + keyboardOperationEditing: true, }, }; diff --git a/Composer/packages/extensions/visual-designer/src/index.tsx b/Composer/packages/extensions/visual-designer/src/index.tsx index f7e2bd4f59..7bd0a29ddf 100644 --- a/Composer/packages/extensions/visual-designer/src/index.tsx +++ b/Composer/packages/extensions/visual-designer/src/index.tsx @@ -30,7 +30,17 @@ const VisualDesigner: React.FC = ({ } const data = dataCache.current; - const { navTo, onFocusEvent, onFocusSteps, onSelect, saveData, getLgTemplates, removeLgTemplate } = shellApi; + const { + navTo, + onFocusEvent, + onFocusSteps, + onSelect, + saveData, + getLgTemplates, + removeLgTemplate, + undo, + redo, + } = shellApi; const focusedId = Array.isArray(focusedSteps) && focusedSteps[0] ? focusedSteps[0] : ''; @@ -64,6 +74,8 @@ const VisualDesigner: React.FC = ({ onOpen={(x, rest) => navTo(x, rest)} onChange={x => saveData(x)} onSelect={onSelect} + undo={undo} + redo={redo} />