Skip to content
This repository was archived by the owner on Jul 9, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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 @@ -7,7 +7,7 @@ import { useContext, useState } from 'react';
import formatMessage from 'format-message';
import { DefinitionSummary } from '@bfc/shared';
import { TooltipHost, DirectionalHint } from 'office-ui-fabric-react/lib/Tooltip';
import { useMenuConfig, MenuUISchema } from '@bfc/extension-client';
import { useMenuConfig } from '@bfc/extension-client';

// TODO: leak of visual-sdk domain (EdgeAddButtonSize)
import { EdgeAddButtonSize } from '../../../adaptive-flow-renderer/constants/ElementSizes';
Expand Down Expand Up @@ -51,7 +51,7 @@ export const EdgeMenu: React.FC<EdgeMenuProps> = ({ id, onClick }) => {
setMenuSelected(menuSelected);
};

const menuSchema: MenuUISchema = useMenuConfig();
const { menuSchema, forceDisabledActions } = useMenuConfig();
const menuItems = createActionMenu(
(item) => {
if (!item) return;
Expand All @@ -61,6 +61,7 @@ export const EdgeMenu: React.FC<EdgeMenuProps> = ({ id, onClick }) => {
isSelfHosted: selfHosted,
enablePaste: Array.isArray(clipboardActions) && !!clipboardActions.length,
},
forceDisabledActions,
menuSchema,
// Custom Action 'oneOf' arrays from schema file
customSchemas.map((x) => x.oneOf).filter((oneOf) => Array.isArray(oneOf) && oneOf.length) as DefinitionSummary[][]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,29 @@ import {
IContextualMenuItem,
ContextualMenuItemType,
} from 'office-ui-fabric-react/lib/components/ContextualMenu/ContextualMenu.types';
import { SDKKinds, DefinitionSummary } from '@bfc/shared';
import { NeutralColors } from '@uifabric/fluent-theme';
import { SDKKinds, DefinitionSummary, DisabledMenuActions } from '@bfc/shared';
import { FontIcon } from 'office-ui-fabric-react/lib/Icon';
import formatMessage from 'format-message';
import { MenuUISchema, MenuOptions } from '@bfc/extension-client';
import set from 'lodash/set';
import { ITooltipHostStyles, TooltipHost } from 'office-ui-fabric-react/lib/Tooltip';

import { MenuEventTypes } from '../../constants/MenuTypes';

import { menuOrderMap } from './defaultMenuOrder';

const toolTipHostStyles: Partial<ITooltipHostStyles> = {
root: {
display: 'flex',
justifyContent: 'flex-start',
alignItems: 'center',
padding: '0px 7px',
height: '38px',
background: NeutralColors.gray20,
},
};

type ActionMenuItemClickHandler = (item?: IContextualMenuItem) => any;
type ActionKindFilter = ($kind: SDKKinds) => boolean;

Expand All @@ -25,6 +38,7 @@ type MenuTree = { [key: string]: SDKKinds | MenuTree };
const createBaseActionMenu = (
menuSchema: MenuUISchema,
onClick: ActionMenuItemClickHandler,
forceDisabledActions: DisabledMenuActions[],
filter?: ActionKindFilter
): IContextualMenuItem[] => {
const menuTree: MenuTree = Object.entries(menuSchema).reduce((result, [$kind, options]) => {
Expand Down Expand Up @@ -62,7 +76,7 @@ const createBaseActionMenu = (
return order1 - order2;
})
.map((sublabelName) => buildMenuItemFromMenuTree(sublabelName, labelData[sublabelName]));
return createSubMenu(labelName, onClick, subMenuItems);
return createSubMenu(labelName, onClick, subMenuItems, forceDisabledActions);
}
};

Expand Down Expand Up @@ -174,13 +188,41 @@ interface ActionMenuOptions {
const createSubMenu = (
label: string,
onClick: ActionMenuItemClickHandler,
subItems: IContextualMenuItem[]
subItems: IContextualMenuItem[],
forceDisabledActions: DisabledMenuActions[]
): IContextualMenuItem => {
const subMenuItems = subItems.map((subMenuItem: IContextualMenuItem) => {
let additionalProps: Partial<IContextualMenuItem> = {};
const disabledAction = forceDisabledActions.find((action) => action.kind === subMenuItem.key);
if (disabledAction) {
additionalProps = {
title: disabledAction.reason,
disabled: true,
onRender: (item) => {
const tooltipId = `tooltip-${disabledAction.kind}`;
return (
<TooltipHost
calloutProps={{ gapSpace: 0 }}
content={disabledAction.reason}
id={tooltipId}
styles={toolTipHostStyles}
>
{item.name}
</TooltipHost>
);
},
};
}
return {
...subMenuItem,
...additionalProps,
};
});
return {
key: label,
text: label,
subMenuProps: {
items: subItems,
items: subMenuItems,
onItemClick: (e, itemData) => onClick(itemData),
},
};
Expand All @@ -189,6 +231,7 @@ const createSubMenu = (
export const createActionMenu = (
onClick: ActionMenuItemClickHandler,
options: ActionMenuOptions,
forceDisabledActions: DisabledMenuActions[],
menuSchema?: MenuUISchema,
customActionGroups?: DefinitionSummary[][]
) => {
Expand All @@ -199,6 +242,7 @@ export const createActionMenu = (
const baseMenuItems = createBaseActionMenu(
menuOptions,
onClick,
forceDisabledActions,
options.isSelfHosted ? ($kind: SDKKinds) => $kind !== SDKKinds.LogAction : undefined
);
resultItems.push(...baseMenuItems);
Expand All @@ -217,7 +261,7 @@ export const createActionMenu = (
submenuWithDuplicatedName.subMenuProps?.items.push(...customActionItems);
} else {
// Otherwise create a new submenu named as 'Custom Actions'.
resultItems.push(createSubMenu(customActionGroupName, onClick, customActionItems));
resultItems.push(createSubMenu(customActionGroupName, onClick, customActionItems, []));
}
}
}
Expand Down
13 changes: 12 additions & 1 deletion Composer/packages/client/src/shell/useShell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Licensed under the MIT License.

import { useMemo, useRef } from 'react';
import { ShellApi, ShellData, Shell, DialogSchemaFile, DialogInfo } from '@botframework-composer/types';
import { ShellApi, ShellData, Shell, DialogSchemaFile, DialogInfo, SDKKinds } from '@botframework-composer/types';
import { useRecoilValue } from 'recoil';
import formatMessage from 'format-message';

Expand All @@ -27,6 +27,7 @@ import {
lgFilesState,
luFilesState,
rateInfoState,
rootBotProjectIdSelector,
} from '../recoilModel';
import { undoFunctionState } from '../recoilModel/undo/history';

Expand Down Expand Up @@ -78,6 +79,8 @@ export function useShell(source: EventSource, projectId: string): Shell {
const botName = useRecoilValue(botDisplayNameState(projectId));
const settings = useRecoilValue(settingsState(projectId));
const flowZoomRate = useRecoilValue(rateInfoState);
const rootBotProjectId = useRecoilValue(rootBotProjectIdSelector);
const isRootBot = rootBotProjectId === projectId;

const userSettings = useRecoilValue(userSettingsState);
const clipboardActions = useRecoilValue(clipboardActionsState);
Expand Down Expand Up @@ -271,6 +274,14 @@ export function useShell(source: EventSource, projectId: string): Shell {
skills,
skillsSettings: settings.skill || {},
flowZoomRate,
forceDisabledActions: isRootBot
? []
: [
{
kind: SDKKinds.BeginSkill,
reason: formatMessage('You can only connect to a skill in the root bot.'),
},
],
};

return {
Expand Down
6 changes: 4 additions & 2 deletions Composer/packages/extension-client/src/hooks/useMenuConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@

import { useContext, useMemo } from 'react';
import mapValues from 'lodash/mapValues';
import { DisabledMenuActions } from '@botframework-composer/types';

import { EditorExtensionContext } from '../EditorExtensionContext';
import { MenuUISchema } from '../types';

export function useMenuConfig(): MenuUISchema {
export function useMenuConfig(): { menuSchema: MenuUISchema; forceDisabledActions: DisabledMenuActions[] } {
const { plugins, shellData } = useContext(EditorExtensionContext);
const uiSchema = plugins.uiSchema || {};
const sdkSchema = shellData.schemas?.sdk;
const sdkDefinitions = sdkSchema?.content?.definitions || {};
const forceDisabledActions = shellData.forceDisabledActions;

return useMemo(() => {
const menuSchema = mapValues(uiSchema, 'menu') as MenuUISchema;
Expand All @@ -24,6 +26,6 @@ export function useMenuConfig(): MenuUISchema {
}
});

return implementedMenuSchema;
return { menuSchema: implementedMenuSchema, forceDisabledActions };
}, [plugins.uiSchema, sdkSchema]);
}
8 changes: 7 additions & 1 deletion Composer/packages/types/src/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import type { DialogInfo, LuFile, LgFile, QnAFile, LuIntentSection, LgTemplate, DialogSchemaFile } from './indexers';
import type { ILUFeaturesConfig, SkillSetting, UserSettings } from './settings';
import type { JSONSchema7 } from './schema';
import type { JSONSchema7, SDKKinds } from './schema';
import { MicrosoftIDialog } from './sdk';

/** Recursively marks all properties as optional. */
Expand Down Expand Up @@ -41,6 +41,11 @@ export type BotSchemas = {
diagnostics?: any[];
};

export type DisabledMenuActions = {
kind: SDKKinds;
reason: string;
};

export type ApplicationContextApi = {
navTo: (path: string, rest?: any) => void;
updateUserSettings: (settings: AllPartial<UserSettings>) => void;
Expand Down Expand Up @@ -108,6 +113,7 @@ export type ProjectContext = {
skills: any[];
skillsSettings: Record<string, SkillSetting>;
schemas: BotSchemas;
forceDisabledActions: DisabledMenuActions[];
};

export type ActionContextApi = {
Expand Down