Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
2 changes: 1 addition & 1 deletion Composer/cypress/integration/Breadcrumb.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ context('breadcrumb', () => {
it('can show action name in breadcrumb', () => {
cy.wait(100);
cy.get('[data-testid="ProjectTree"]').within(() => {
cy.getByText('ToDoBot.Main').click();
cy.getByText('Handle ConversationUpdate').click();
cy.wait(500);
});

Expand Down
12 changes: 7 additions & 5 deletions Composer/packages/client/src/ShellApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface ShellData {
dialogId: string;
focusedEvent: string;
focusedSteps: string[];
focusedTab?: string;
}

const apiClient = new ApiClient();
Expand Down Expand Up @@ -78,7 +79,7 @@ export const ShellApi: React.FC = () => {
const createLuFile = actions.createLuFile;
const createLgFile = actions.createLgFile;

const { dialogId, selected, focused } = designPageLocation;
const { dialogId, selected, focused, promptTab } = designPageLocation;

const { LG, LU } = FileTargetTypes;
const { CREATE, UPDATE } = FileChangeTypes;
Expand Down Expand Up @@ -140,7 +141,7 @@ export const ShellApi: React.FC = () => {
const editorWindow = window.frames[FORM_EDITOR];
apiClient.apiCall('reset', getState(FORM_EDITOR), editorWindow);
}
}, [dialogs, lgFiles, luFiles, focusPath, selected, focused]);
}, [dialogs, lgFiles, luFiles, focusPath, selected, focused, promptTab]);

useEffect(() => {
const schemaError = get(schemas, 'diagnostics', []);
Expand Down Expand Up @@ -176,6 +177,7 @@ export const ShellApi: React.FC = () => {
dialogId,
focusedEvent: selected,
focusedSteps: focused ? [focused] : selected ? [selected] : [],
focusedTab: promptTab,
};
}

Expand Down Expand Up @@ -304,13 +306,13 @@ export const ShellApi: React.FC = () => {
actions.selectTo(subPath);
}

function focusSteps({ subPaths = [] }, event) {
function focusSteps({ subPaths = [], fragment }, event) {
cleanData();
let dataPath: string = subPaths[0];
if (event.source.name === FORM_EDITOR && focused) {
if (event.source.name === FORM_EDITOR && focused && dataPath !== focused) {
dataPath = `${focused}.${dataPath}`;
}
actions.focusTo(dataPath);
actions.focusTo(dataPath, fragment);
}

function onSelect(ids) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ const shellApi = {
return apiClient.apiCall('onFocusEvent', { subPath });
},

onFocusSteps: (subPaths: string[]) => {
return apiClient.apiCall('onFocusSteps', { subPaths });
onFocusSteps: (subPaths: string[], fragment?: string) => {
return apiClient.apiCall('onFocusSteps', { subPaths, fragment });
},

onSelect: (ids: string[]) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import React, { Fragment, useContext, useEffect, useMemo, useState } from 'react';
import { ActionButton, Breadcrumb, Icon } from 'office-ui-fabric-react';
import { ActionButton, Breadcrumb, Icon, IBreadcrumbItem } from 'office-ui-fabric-react';
import formatMessage from 'format-message';
import { globalHistory } from '@reach/router';
import { toLower, get } from 'lodash';
import { PromptTab } from 'shared-menus';

import { VisualEditorAPI } from '../../messenger/FrameAPI';
import { TestController } from '../../TestController';
import { BASEPATH, DialogDeleting } from '../../constants';
import { createSelectedPath, deleteTrigger, getbreadcrumbLabel } from '../../utils';
import { TriggerCreationModal } from '../../components/ProjectTree/TriggerCreationModal';
import { Conversation } from '../../components/Conversation';
import { DialogStyle } from '../../components/Modal/styles';
import { OpenConfirmModal } from '../../components/Modal/Confirm';
import { ProjectTree } from '../../components/ProjectTree';
import { StoreContext } from '../../store';
import { ToolBar } from '../../components/ToolBar/index';
import { clearBreadcrumb } from '../../utils/navigation';
import { getNewDesigner } from '../../utils/dialogUtil';
import undoHistory from '../../store/middlewares/undo/history';

import { Conversation } from './../../components/Conversation';
import { DialogStyle } from './../../components/Modal/styles';
import NewDialogModal from './new-dialog-modal';
import { OpenConfirmModal } from './../../components/Modal/Confirm';
import { ProjectTree } from './../../components/ProjectTree';
import { StoreContext } from './../../store';
import { ToolBar } from './../../components/ToolBar/index';
import { clearBreadcrumb } from './../../utils/navigation';
import { getNewDesigner } from './../../utils/dialogUtil';
import undoHistory from './../../store/middlewares/undo/history';
import {
breadcrumbClass,
contentWrapper,
Expand Down Expand Up @@ -86,7 +87,7 @@ function onRenderBlankVisual(isTriggerEmpty, onClickAddTrigger) {
}

function getAllRef(targetId, dialogs) {
let refs = [];
let refs: string[] = [];
dialogs.forEach(dialog => {
if (dialog.id === targetId) {
refs = refs.concat(dialog.referredDialogs);
Expand All @@ -97,6 +98,14 @@ function getAllRef(targetId, dialogs) {
return refs;
}

const getTabFromFragment = () => {
const tab = window.location.hash.substring(1);

if (Object.values(PromptTab).includes(tab)) {
return tab;
}
};

const rootPath = BASEPATH.replace(/\/+$/g, '');

function DesignPage(props) {
Expand Down Expand Up @@ -127,7 +136,9 @@ function DesignPage(props) {
focused: params.get('focused'),
breadcrumb: location.state ? location.state.breadcrumb || [] : [],
onBreadcrumbItemClick: handleBreadcrumbItemClick,
promptTab: getTabFromFragment(),
});
// @ts-ignore
globalHistory._onTransitionComplete();
} else {
//leave design page should clear the history
Expand Down Expand Up @@ -262,21 +273,34 @@ function DesignPage(props) {
},
];

function handleBreadcrumbItemClick(_event, { dialogId, selected, focused, index }) {
setectAndfocus(dialogId, selected, focused, clearBreadcrumb(breadcrumb, index));
function handleBreadcrumbItemClick(_event, item) {
if (item) {
const { dialogId, selected, focused, index } = item;
setectAndfocus(dialogId, selected, focused, clearBreadcrumb(breadcrumb, index));
}
}

const breadcrumbItems = useMemo(() => {
const items =
dialogs.length > 0
? breadcrumb.reduce((result, item, index) => {
const { dialogId, selected, focused } = item;
const text = getbreadcrumbLabel(dialogs, dialogId, selected, focused);
if (text) {
result.push({ text, isRoot: !selected && !focused, ...item, index, onClick: handleBreadcrumbItemClick });
}
return result;
}, [])
? breadcrumb.reduce(
(result, item, index) => {
const { dialogId, selected, focused } = item;
const text = getbreadcrumbLabel(dialogs, dialogId, selected, focused);
if (text) {
result.push({
// @ts-ignore
index,
isRoot: !selected && !focused,
text,
...item,
onClick: handleBreadcrumbItemClick,
});
}
return result;
},
[] as IBreadcrumbItem[]
)
: [];
return (
<Breadcrumb
Expand All @@ -296,7 +320,7 @@ function DesignPage(props) {

async function handleDeleteDialog(id) {
const refs = getAllRef(id, dialogs);
let setting = {
let setting: any = {
confirmBtnText: formatMessage('Yes'),
cancelBtnText: formatMessage('Cancel'),
};
Expand All @@ -306,7 +330,7 @@ function DesignPage(props) {
title = DialogDeleting.TITLE;
subTitle = `${refs.reduce((result, item) => `${result} ${item} \n`, '')}`;
setting = {
onRenderContent: onRenderContent,
onRenderContent,
style: DialogStyle.Console,
};
} else {
Expand All @@ -323,20 +347,21 @@ function DesignPage(props) {
const content = deleteTrigger(dialogs, id, index);
if (content) {
await updateDialog({ id, content });
let current = /\[(\d+)\]/g.exec(selected)[1];
const match = /\[(\d+)\]/g.exec(selected);
const current = match && match[1];
if (!current) return;
current = parseInt(current);
if (index === current) {
if (current - 1 >= 0) {
const currentIdx = parseInt(current);
if (index === currentIdx) {
if (currentIdx - 1 >= 0) {
//if the deleted node is selected and the selected one is not the first one, navTo the previous trigger;
selectTo(createSelectedPath(current - 1));
selectTo(createSelectedPath(currentIdx - 1));
} else {
//if the deleted node is selected and the selected one is the first one, navTo the first trigger;
navTo(id);
}
} else if (index < current) {
} else if (index < currentIdx) {
//if the deleted node is at the front, navTo the current one;
selectTo(createSelectedPath(current - 1));
selectTo(createSelectedPath(currentIdx - 1));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export default function CodeEditor(props) {
options={{
lineNumbers: 'on',
minimap: 'on',
lineDecorationsWidth: undefined,
lineNumbersMinChars: false,
}}
errorMsg={errorMsg}
value={content}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export default function CodeEditor(props) {
options={{
lineNumbers: 'on',
minimap: 'on',
lineDecorationsWidth: undefined,
lineNumbersMinChars: false,
}}
errorMsg={errorMsg}
value={content}
Expand Down
9 changes: 6 additions & 3 deletions Composer/packages/client/src/store/action/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import { updateBreadcrumb, navigateTo, checkUrl, getUrlSearch, BreadcrumbUpdateT

export const setDesignPageLocation: ActionCreator = (
{ dispatch },
{ dialogId = '', selected = '', focused = '', breadcrumb = [], onBreadcrumbItemClick }
{ dialogId = '', selected = '', focused = '', breadcrumb = [], onBreadcrumbItemClick, promptTab }
) => {
dispatch({
type: ActionTypes.SET_DESIGN_PAGE_LOCATION,
payload: { dialogId, focused, selected, breadcrumb, onBreadcrumbItemClick },
payload: { dialogId, focused, selected, breadcrumb, onBreadcrumbItemClick, promptTab },
});
};

Expand All @@ -35,7 +35,7 @@ export const selectTo: ActionCreator = ({ getState }, selectPath) => {
navigateTo(currentUri, { state: { breadcrumb: updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected) } });
};

export const focusTo: ActionCreator = ({ getState }, focusPath) => {
export const focusTo: ActionCreator = ({ getState }, focusPath, fragment) => {
const state = getState();
const { dialogId, selected } = state.designPageLocation;
let { breadcrumb } = state;
Expand All @@ -54,6 +54,9 @@ export const focusTo: ActionCreator = ({ getState }, focusPath) => {
breadcrumb = updateBreadcrumb(breadcrumb, BreadcrumbUpdateType.Selected);
}

if (fragment && typeof fragment === 'string') {
currentUri += `#${fragment}`;
}
if (checkUrl(currentUri, state.designPageLocation)) return;
navigateTo(currentUri, { state: { breadcrumb } });
};
Expand Down
4 changes: 2 additions & 2 deletions Composer/packages/client/src/store/reducer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ const setError: ReducerFunc = (state, payload) => {
return state;
};

const setDesignPageLocation: ReducerFunc = (state, { dialogId, selected, focused, breadcrumb }) => {
const setDesignPageLocation: ReducerFunc = (state, { dialogId, selected, focused, breadcrumb, promptTab }) => {
//generate focusedPath. This will remove when all focusPath related is removed
state.focusPath = dialogId + '#';
if (focused) {
Expand All @@ -162,7 +162,7 @@ const setDesignPageLocation: ReducerFunc = (state, { dialogId, selected, focused
breadcrumb.push({ dialogId, selected, focused });

state.breadcrumb = breadcrumb;
state.designPageLocation = { dialogId, selected, focused };
state.designPageLocation = { dialogId, selected, focused, promptTab };
return state;
};
const syncEnvSetting: ReducerFunc = (state, { settings }) => {
Expand Down
2 changes: 2 additions & 0 deletions Composer/packages/client/src/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// TODO: remove this once we can expand the types
/* eslint-disable @typescript-eslint/no-explicit-any */
import React from 'react';
import { PromptTab } from 'shared-menus';

import { CreationFlowStatus, BotStatus } from '../constants';

Expand Down Expand Up @@ -151,4 +152,5 @@ export interface DesignPageLocation {
dialogId: string;
selected: string;
focused: string;
promptTab?: PromptTab;
}
12 changes: 6 additions & 6 deletions Composer/packages/client/src/utils/navigation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { cloneDeep } from 'lodash';
import { navigate, NavigateOptions } from '@reach/router';

import { BreadcrumbItem } from '../store/types';
import { BreadcrumbItem, DesignPageLocation } from '../store/types';

import { BASEPATH } from './../constants/index';
import { resolveToBasePath } from './fileUtil';
Expand Down Expand Up @@ -61,11 +61,11 @@ export function getUrlSearch(selected: string, focused: string): string {
return result;
}

export function checkUrl(
currentUri: string,
{ dialogId, selected, focused }: { dialogId: string; selected: string; focused: string }
) {
const lastUri = `/dialogs/${dialogId}${getUrlSearch(selected, focused)}`;
export function checkUrl(currentUri: string, { dialogId, selected, focused, promptTab }: DesignPageLocation) {
let lastUri = `/dialogs/${dialogId}${getUrlSearch(selected, focused)}`;
if (promptTab) {
lastUri += `#${promptTab}`;
}
return lastUri === currentUri;
}

Expand Down
Loading