Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add undo/redo for design page #3780

Merged
merged 28 commits into from
Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ae00544
add undo
lei9444 Jul 31, 2020
34b04d0
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
lei9444 Jul 31, 2020
5119d35
replace some async methods to sync
lei9444 Aug 4, 2020
cad80a2
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
lei9444 Aug 4, 2020
7a58afc
fix unit tests
lei9444 Aug 4, 2020
b163af7
fix shared unit tests
lei9444 Aug 4, 2020
84e9c7a
add undo unit tests
lei9444 Aug 4, 2020
3c8d918
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
lei9444 Aug 5, 2020
b171a33
update some naming
lei9444 Aug 5, 2020
09b423e
add some comments
lei9444 Aug 5, 2020
c1cd25e
Merge branch 'main' into undo
lei9444 Aug 5, 2020
6a75d58
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
lei9444 Aug 6, 2020
35737a1
Merge branch 'undo' of https://github.com/lei9444/BotFramework-Compos…
lei9444 Aug 6, 2020
0b27ad9
adapt undo/redo to Electron app menu
yeze322 Aug 6, 2020
bb14e86
watch external cahnges
zhixzhan Aug 6, 2020
59917e7
fix form empty string can not update
lei9444 Aug 6, 2020
af74c87
Merge branch 'undo' of https://github.com/lei9444/BotFramework-Compos…
lei9444 Aug 6, 2020
655ff65
use atom content as undo history stack value
lei9444 Aug 6, 2020
9e7dc6d
add unit test for undoHistory
lei9444 Aug 6, 2020
a21e96c
Merge branch 'main' into undo
lei9444 Aug 6, 2020
4d5d3db
Merge pull request #1 from yeze322/zeye/undo-menu
lei9444 Aug 7, 2020
1356a4c
Merge branch 'main' of https://github.com/microsoft/BotFramework-Comp…
lei9444 Aug 7, 2020
ce28be8
Merge branch 'undo' of https://github.com/lei9444/BotFramework-Compos…
lei9444 Aug 7, 2020
f09b600
Revert "watch external cahnges"
lei9444 Aug 7, 2020
c56737a
add commit version to force update the editor
lei9444 Aug 7, 2020
119ce5a
add number limite
lei9444 Aug 7, 2020
8579f86
add fragment for navigation
lei9444 Aug 7, 2020
31977ca
add breadcrumb
lei9444 Aug 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ import CodeEditor from '../../../src/pages/language-generation/code-editor';
import {
projectIdState,
localeState,
dialogsState,
luFilesState,
lgFilesState,
settingsState,
schemasState,
dialogsState,
} from '../../../src/recoilModel';
import mockProjectResponse from '../../../src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json';

const initialContent = `
# Greeting
Expand Down Expand Up @@ -52,6 +54,7 @@ const initRecoilState = ({ set }) => {
set(luFilesState, state.luFiles);
set(lgFilesState, state.lgFiles);
set(settingsState, state.settings);
set(schemasState, mockProjectResponse.schemas);
};

describe('LG page all up view', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
luFilesState,
lgFilesState,
settingsState,
schemasState,
} from '../../../src/recoilModel';
import mockProjectResponse from '../../../src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json';

const initialContent = `
# Greeting
Expand Down Expand Up @@ -45,6 +47,7 @@ const initRecoilState = ({ set }) => {
set(luFilesState, state.luFiles);
set(lgFilesState, state.lgFiles);
set(settingsState, state.settings);
set(schemasState, mockProjectResponse.schemas);
};

describe('LU page all up view', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import {
lgFilesState,
BotDiagnosticsState,
settingsState,
schemasState,
} from '../../../src/recoilModel';
import mockProjectResponse from '../../../src/recoilModel/dispatchers/__tests__/mocks/mockProjectResponse.json';

const state = {
projectId: 'test',
Expand All @@ -23,14 +25,6 @@ const state = {
content: 'test',
luFile: 'test',
referredLuIntents: [],
diagnostics: [
{
message: 'must be an expression',
path: 'test.triggers[0]#Microsoft.OnUnknownIntent#condition',
severity: 1,
source: 'test',
},
],
skills: ['https://yuesuemailskill0207-gjvga67.azurewebsites.net/manifest/manifest-1.0.json'],
},
],
Expand Down Expand Up @@ -110,6 +104,7 @@ const initRecoilState = ({ set }) => {
set(lgFilesState, state.lgFiles);
set(BotDiagnosticsState, state.diagnostics);
set(settingsState, state.settings);
set(schemasState, mockProjectResponse.schemas);
};

describe('useNotification hooks', () => {
Expand All @@ -127,7 +122,7 @@ describe('useNotification hooks', () => {
});

it('should return notifications', () => {
expect(renderedResult.current.length).toBe(5);
expect(renderedResult.current.length).toBe(4);
});

it('should return filtered notifications', () => {
Expand Down
1 change: 0 additions & 1 deletion Composer/packages/client/__tests__/shell/lgApi.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ describe('use lgApi hooks', () => {

it('should call update lg template action', () => {
result.current.updateLgTemplate('test.en-us', 'bar', 'update');
result.current.updateLgTemplate.flush();
expect(updateLgTemplateMock).toBeCalledTimes(1);
const arg = {
id: 'test.en-us',
Expand Down
4 changes: 3 additions & 1 deletion Composer/packages/client/__tests__/shell/luApi.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ describe('use luApi hooks', () => {
Body: '- test add',
Name: 'add',
},
projectId: 'test',
};
expect(updateLuFileMockMock).toBeCalledWith(arg);
});
Expand All @@ -79,6 +80,7 @@ describe('use luApi hooks', () => {
Name: 'update',
},
intentName: 'test',
projectId: 'test',
};
expect(updateLuFileMockMock).toBeCalledWith(arg);
});
Expand All @@ -88,7 +90,7 @@ describe('use luApi hooks', () => {
await result.current.updateLuIntent('test.en-us', 'test', { Body: '- test update', Name: 'update' });
await result.current.removeLuIntent('test.en-us', 'test', 'remove');
expect(updateLuFileMockMock).toBeCalledTimes(3);
const arg = { intentName: 'test', id: 'test.en-us' };
const arg = { intentName: 'test', id: 'test.en-us', projectId: 'test' };
expect(updateLuFileMockMock).toBeCalledWith(arg);
});

Expand Down
5 changes: 3 additions & 2 deletions Composer/packages/client/src/Onboarding/Onboarding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { useRecoilValue } from 'recoil';
import onboardingStorage from '../utils/onboardingStorage';
import { OpenConfirmModal } from '../components/Modal/ConfirmDialog';
import { useLocation } from '../utils/hooks';
import { dialogsState, projectIdState, dispatcherState, onboardingState } from '../recoilModel';
import { projectIdState, dispatcherState, onboardingState } from '../recoilModel';
import { validatedDialogs } from '../recoilModel/selectors/validatedDialogs';

import OnboardingContext from './OnboardingContext';
import TeachingBubbles from './TeachingBubbles/TeachingBubbles';
Expand All @@ -24,7 +25,7 @@ const Onboarding: React.FC = () => {
const onboarding = useRecoilValue(onboardingState);
const complete = onboarding.complete;

const dialogs = useRecoilValue(dialogsState);
const dialogs = useRecoilValue(validatedDialogs);
const projectId = useRecoilValue(projectIdState);
const rootDialogId = dialogs.find(({ isRoot }) => isRoot === true)?.id || 'Main';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import {
getActivityTypes,
regexRecognizerKey,
} from '../../utils/dialogUtil';
import { dialogsState, projectIdState, schemasState } from '../../recoilModel/atoms/botState';
import { projectIdState, schemasState } from '../../recoilModel/atoms/botState';
import { userSettingsState } from '../../recoilModel';
import { nameRegex } from '../../constants';
import { validatedDialogs } from '../../recoilModel/selectors/validatedDialogs';

// -------------------- Styles -------------------- //

Expand Down Expand Up @@ -204,7 +205,7 @@ interface TriggerCreationModalProps {

export const TriggerCreationModal: React.FC<TriggerCreationModalProps> = (props) => {
const { isOpen, onDismiss, onSubmit, dialogId } = props;
const dialogs = useRecoilValue(dialogsState);
const dialogs = useRecoilValue(validatedDialogs);

const projectId = useRecoilValue(projectIdState);
const schemas = useRecoilValue(schemasState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { DefaultPublishConfig } from '../../constants';
import {
botNameState,
botStatusState,
dialogsState,
luFilesState,
settingsState,
projectIdState,
Expand All @@ -26,6 +25,7 @@ import { BotStatus, LuisConfig } from '../../constants';
import { isAbsHosted } from '../../utils/envUtil';
import useNotifications from '../../pages/notifications/useNotifications';
import { navigateTo, openInEmulator } from '../../utils/navigation';
import { validatedDialogs } from '../../recoilModel/selectors/validatedDialogs';

import { getReferredFiles } from './../../utils/luUtil';
import { PublishLuisDialog } from './publishDialog';
Expand Down Expand Up @@ -57,7 +57,7 @@ export const TestController: React.FC = () => {
const notifications = useNotifications();
const botName = useRecoilValue(botNameState);
const botStatus = useRecoilValue(botStatusState);
const dialogs = useRecoilValue(dialogsState);
const dialogs = useRecoilValue(validatedDialogs);
const luFiles = useRecoilValue(luFilesState);
const settings = useRecoilValue(settingsState);
const projectId = useRecoilValue(projectIdState);
Expand Down
41 changes: 20 additions & 21 deletions Composer/packages/client/src/pages/design/DesignPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,12 @@ import { Conversation } from '../../components/Conversation';
import { dialogStyle } from '../../components/Modal/dialogStyle';
import { OpenConfirmModal } from '../../components/Modal/ConfirmDialog';
import { ProjectTree } from '../../components/ProjectTree/ProjectTree';
import { undoHistory } from '../../recoilModel/undo';
import { Toolbar, IToolbarItem } from '../../components/Toolbar';
import { clearBreadcrumb } from '../../utils/navigation';
import { navigateTo } from '../../utils/navigation';
import { useShell } from '../../shell';
import { undoFunctionState } from '../../recoilModel/undo/history';
import {
dialogsState,
projectIdState,
schemasState,
showCreateDialogModalState,
Expand All @@ -45,6 +44,7 @@ import {
luFilesState,
localeState,
} from '../../recoilModel';
import { validatedDialogs } from '../../recoilModel/selectors/validatedDialogs';
import plugins, { mergePluginConfigs } from '../../plugins';
import { useElectronFeatures } from '../../hooks/useElectronFeatures';

Expand Down Expand Up @@ -101,7 +101,7 @@ const getTabFromFragment = () => {
};

const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: string }>> = (props) => {
const dialogs = useRecoilValue(dialogsState);
const dialogs = useRecoilValue(validatedDialogs);
const projectId = useRecoilValue(projectIdState);
const schemas = useRecoilValue(schemasState);
const displaySkillManifest = useRecoilValue(displaySkillManifestState);
Expand All @@ -111,6 +111,7 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
const designPageLocation = useRecoilValue(designPageLocationState);
const showCreateDialogModal = useRecoilValue(showCreateDialogModalState);
const showAddSkillDialogModal = useRecoilValue(showAddSkillDialogModalState);
const { undo, redo, canRedo, canUndo, commitChanges, clearUndo } = useRecoilValue(undoFunctionState);
const skills = useRecoilValue(skillsState);
const actionsSeed = useRecoilValue(actionsSeedState);
const userSettings = useRecoilValue(userSettingsState);
Expand Down Expand Up @@ -182,12 +183,14 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
// @ts-ignore
globalHistory._onTransitionComplete();
/* eslint-enable */
} else {
//leave design page should clear the history
// clearUndoHistory(); //TODO: undo/redo
}
}, [location]);

useEffect(() => {
//leave design page should clear the history
return clearUndo;
}, []);

const onTriggerCreationDismiss = () => {
setTriggerModalVisibility(false);
};
Expand All @@ -203,7 +206,7 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
content: dialog.content,
};
if (luFile && intent) {
createLuIntent({ id: luFile.id, intent });
createLuIntent({ id: luFile.id, intent, projectId });
}

const index = get(dialog, 'content.triggers', []).length - 1;
Expand Down Expand Up @@ -284,20 +287,14 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
{
key: 'edit.undo',
text: formatMessage('Undo'),
disabled: !undoHistory.canUndo(),
onClick: () => {
// TODO: register EditorAPI.Editing.Undo()
//ToDo undo
},
disabled: !canUndo(),
onClick: undo,
},
{
key: 'edit.redo',
text: formatMessage('Redo'),
disabled: !undoHistory.canRedo(),
onClick: () => {
// TODO: register EditorAPI.Editing.Redo()
//ToDo redo
},
disabled: !canRedo(),
onClick: redo,
},
{
key: 'edit.cut',
Expand Down Expand Up @@ -450,15 +447,16 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
updateSkill({ projectId, targetId: -1, skillData });
}

function handleCreateDialogSubmit(data: { name: string; description: string }) {
async function handleCreateDialogSubmit(data: { name: string; description: string }) {
const seededContent = new DialogFactory(schemas.sdk?.content).create(SDKKinds.AdaptiveDialog, {
$designer: { name: data.name, description: data.description },
generator: `${data.name}.lg`,
});
if (seededContent.triggers?.[0]) {
seededContent.triggers[0].actions = actionsSeed;
}
createDialog({ id: data.name, content: seededContent });
await createDialog({ id: data.name, content: seededContent });
commitChanges();
}

async function handleDeleteDialog(id) {
Expand All @@ -482,15 +480,16 @@ const DesignPage: React.FC<RouteComponentProps<{ dialogId: string; projectId: st
const result = await OpenConfirmModal(title, subTitle, setting);

if (result) {
removeDialog(id);
await removeDialog(id);
commitChanges();
}
}

async function handleDeleteTrigger(id, index) {
const content = deleteTrigger(dialogs, id, index, (trigger) => triggerApi.deleteTrigger(id, trigger));

if (content) {
await updateDialog({ id, content });
updateDialog({ id, content });
const match = /\[(\d+)\]/g.exec(selected);
const current = match && match[1];
if (!current) return;
Expand Down
2 changes: 2 additions & 0 deletions Composer/packages/client/src/pages/design/PropertyEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ const PropertyEditor: React.FC = () => {
const id = setTimeout(() => {
if (!isEqual(formData, localData)) {
shellApi.saveData(localData, focusedSteps[0]);
} else {
shellApi.commitChanges();
}
}, 300);

Expand Down
5 changes: 3 additions & 2 deletions Composer/packages/client/src/pages/design/VisualEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import VisualDesigner from '@bfc/adaptive-flow';
import { useRecoilValue } from 'recoil';

import grayComposerIcon from '../../images/grayComposerIcon.svg';
import { schemasState, dialogsState, designPageLocationState, dispatcherState } from '../../recoilModel';
import { schemasState, designPageLocationState, dispatcherState } from '../../recoilModel';
import { validatedDialogs } from '../../recoilModel/selectors/validatedDialogs';

import { middleTriggerContainer, middleTriggerElements, triggerButton, visualEditor } from './styles';

Expand Down Expand Up @@ -56,7 +57,7 @@ const VisualEditor: React.FC<VisualEditorProps> = (props) => {
const [triggerButtonVisible, setTriggerButtonVisibility] = useState(false);
const designPageLocation = useRecoilValue(designPageLocationState);
const { onboardingAddCoachMarkRef } = useRecoilValue(dispatcherState);
const dialogs = useRecoilValue(dialogsState);
const dialogs = useRecoilValue(validatedDialogs);
const schemas = useRecoilValue(schemasState);
const { dialogId, selected } = designPageLocation;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { useRecoilValue } from 'recoil';

import { DialogCreationCopy, nameRegex } from '../../constants';
import { StorageFolder } from '../../recoilModel/types';
import { dialogsState } from '../../recoilModel/atoms/botState';
import { DialogWrapper, DialogTypes } from '../../components/DialogWrapper';
import { FieldConfig, useForm } from '../../hooks/useForm';
import { validatedDialogs } from '../../recoilModel/selectors/validatedDialogs';

import { name, description, styles as wizardStyles } from './styles';

Expand All @@ -30,7 +30,7 @@ interface CreateDialogModalProps {
}

export const CreateDialogModal: React.FC<CreateDialogModalProps> = (props) => {
const dialogs = useRecoilValue(dialogsState);
const dialogs = useRecoilValue(validatedDialogs);
const { onSubmit, onDismiss, isOpen } = props;
const formConfig: FieldConfig<DialogFormData> = {
name: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ import { Toggle } from 'office-ui-fabric-react/lib/Toggle';
import { RouteComponentProps, Router } from '@reach/router';
import { useRecoilValue } from 'recoil';

import { dialogsState, projectIdState } from '../../recoilModel/atoms/botState';
import { projectIdState } from '../../recoilModel/atoms/botState';
import { LoadingSpinner } from '../../components/LoadingSpinner';
import { actionButton } from '../language-understanding/styles';
import { navigateTo } from '../../utils/navigation';
import { TestController } from '../../components/TestController/TestController';
import { INavTreeItem } from '../../components/NavTree';
import { Page } from '../../components/Page';
import { validatedDialogs } from '../../recoilModel/selectors/validatedDialogs';

import TableView from './table-view';
const CodeEditor = React.lazy(() => import('./code-editor'));
Expand All @@ -25,7 +26,7 @@ interface LGPageProps extends RouteComponentProps<{}> {
}

const LGPage: React.FC<LGPageProps> = (props) => {
const dialogs = useRecoilValue(dialogsState);
const dialogs = useRecoilValue(validatedDialogs);
const projectId = useRecoilValue(projectIdState);

const path = props.location?.pathname ?? '';
Expand Down
Loading