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: show topics in left nav #6987

Merged
merged 9 commits into from
Apr 16, 2021
Merged
4 changes: 2 additions & 2 deletions Composer/packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

import React, { Fragment, useEffect } from 'react';
import { initializeIcons } from 'office-ui-fabric-react/lib/Icons';
import { useRecoilValue } from 'recoil';

import { Header } from './components/Header';
Expand All @@ -11,8 +10,9 @@ import { MainContainer } from './components/AppComponents/MainContainer';
import { dispatcherState, userSettingsState } from './recoilModel';
import { loadLocale } from './utils/fileUtil';
import { useInitializeLogger } from './telemetry/useInitializeLogger';
import { setupIcons } from './setupIcons';

initializeIcons(undefined, { disableWarnings: true });
setupIcons();

const Logger = () => {
useInitializeLogger();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ import { getBaseName } from '../../utils/fileUtil';

import { TreeItem } from './treeItem';
import { ExpandableNode } from './ExpandableNode';
import { INDENT_PER_LEVEL } from './constants';
import { INDENT_PER_LEVEL, LEVEL_PADDING, TREE_PADDING } from './constants';
import { ProjectTreeHeader, ProjectTreeHeaderMenuItem } from './ProjectTreeHeader';
import { isChildTriggerLinkSelected, doesLinkMatch } from './helpers';
import { ProjectHeader } from './ProjectHeader';
import { ProjectTreeOptions, TreeLink, TreeMenuItem } from './types';
import { TopicsList } from './TopicsList';

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

Expand All @@ -59,7 +60,7 @@ const tree = css`
label: tree;
`;

const headerCSS = (label: string, isActive?: boolean) => css`
export const headerCSS = (label: string, isActive?: boolean) => css`
margin-top: -6px;
width: 100%;
label: ${label};
Expand Down Expand Up @@ -111,9 +112,6 @@ type Props = {
headerPlaceholder?: string;
};

const TREE_PADDING = 100; // the horizontal space taken up by stuff in the tree other than text or indentation
const LEVEL_PADDING = 44; // the size of a reveal-triangle and the space around it

export const ProjectTree: React.FC<Props> = ({
headerMenu = [],
onBotDeleteDialog = () => {},
Expand Down Expand Up @@ -624,6 +622,7 @@ export const ProjectTree: React.FC<Props> = ({
const createDetailsTree = (bot: TreeDataPerProject, startDepth: number) => {
const { projectId, lgImportsList, luImportsList } = bot;
const dialogs = bot.sortedDialogs;
const topics = bot.topics ?? [];

const filteredDialogs =
filter == null || filter.length === 0
Expand All @@ -632,6 +631,9 @@ export const ProjectTree: React.FC<Props> = ({
(dialog) =>
filterMatch(dialog.displayName) || dialog.triggers.some((trigger) => filterMatch(getTriggerName(trigger)))
);
// eventually we will filter on topic trigger phrases
const filteredTopics =
filter == null || filter.length === 0 ? topics : topics.filter((topic) => filterMatch(topic.displayName));
const commonLink = options.showCommonLinks ? [renderCommonDialogHeader(projectId, 1)] : [];

const importedLgLinks = options.showLgImports
Expand Down Expand Up @@ -701,6 +703,15 @@ export const ProjectTree: React.FC<Props> = ({
return renderDialogHeader(projectId, dialog, 1, bot.isPvaSchema).summaryElement;
}
}),
filteredTopics.length > 0 && (
<TopicsList
key={`pva-topics-${projectId}`}
projectId={projectId}
textWidth={leftSplitWidth - TREE_PADDING}
topics={filteredTopics}
onToggle={(newState) => setPageElement('pva-topics', newState)}
/>
),
];
};

Expand Down
84 changes: 84 additions & 0 deletions Composer/packages/client/src/components/ProjectTree/TopicsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

/** @jsx jsx */
import { jsx } from '@emotion/core';
import React from 'react';
import { DialogInfo } from '@bfc/shared';
import formatMessage from 'format-message';
import get from 'lodash/get';

import { ExpandableNode } from './ExpandableNode';
import { TreeItem } from './treeItem';
import { LEVEL_PADDING, INDENT_PER_LEVEL } from './constants';
import { headerCSS } from './ProjectTree';

type TopicsListProps = {
onToggle: (newState: boolean) => void;
topics: DialogInfo[];
textWidth: number;
projectId: string;
};

export const TopicsList: React.FC<TopicsListProps> = ({ topics, onToggle, textWidth, projectId }) => {
const linkTooltip = formatMessage('Open in Power Virtual Agents');

const renderTopic = (topic: DialogInfo) => {
const isSystemTopic = get(topic.content, 'isSystemTopic', false);

return (
<TreeItem
key={topic.id}
dialogName={topic.displayName}
extraSpace={INDENT_PER_LEVEL}
isActive={false}
isMenuOpen={false}
itemType={isSystemTopic ? 'system topic' : 'topic'}
link={{
projectId,
dialogId: topic.id,
displayName: topic.displayName,
href: get(topic.content, '$designer.link'),
tooltip: linkTooltip,
}}
marginLeft={1 * INDENT_PER_LEVEL}
role="treeitem"
textWidth={textWidth}
onSelect={(link) => {
if (link.href) {
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(link.href, '_blank');
}
}}
/>
);
};

return (
<ExpandableNode
key="pva-topics"
depth={1}
summary={
<span css={headerCSS('pva-topics')}>
<TreeItem
hasChildren
isActive={false}
isChildSelected={false}
isMenuOpen={false}
itemType="topic"
link={{
displayName: formatMessage('Power Virtual Agents Topics ({count})', { count: topics.length }),
projectId,
}}
padLeft={0 * LEVEL_PADDING}
showErrors={false}
textWidth={textWidth}
/>
</span>
}
onToggle={onToggle}
>
<div>{topics.map(renderTopic)}</div>
</ExpandableNode>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import React from 'react';
import { TooltipHost, DirectionalHint } from 'office-ui-fabric-react/lib/Tooltip';

type TreeItemContentProps = {
tooltip?: string | JSX.Element | JSX.Element[];
};

export const TreeItemContent: React.FC<TreeItemContentProps> = ({ children, tooltip }) => {
if (tooltip) {
return (
<TooltipHost content={tooltip} directionalHint={DirectionalHint.bottomCenter}>
{children}
</TooltipHost>
);
}

return <React.Fragment>{children}</React.Fragment>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ export const SUMMARY_ARROW_SPACE = 28; // the rough pixel size of the dropdown a
export const INDENT_PER_LEVEL = 16;
export const ACTION_ICON_WIDTH = 28;
export const THREE_DOTS_ICON_WIDTH = 28;
export const TREE_PADDING = 100; // the horizontal space taken up by stuff in the tree other than text or indentation
export const LEVEL_PADDING = 44; // the size of a reveal-triangle and the space around it
117 changes: 73 additions & 44 deletions Composer/packages/client/src/components/ProjectTree/treeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import uniqueId from 'lodash/uniqueId';

import { SUMMARY_ARROW_SPACE, THREE_DOTS_ICON_WIDTH } from './constants';
import { TreeLink, TreeMenuItem } from './types';
import { TreeItemContent } from './TreeItemContent';

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

Expand Down Expand Up @@ -112,7 +113,10 @@ const navContainer = (
.treeItem-text {
max-width: ${textWidth}px;
}
}`};
.external-link {
visibility: visible;
}
}`};

background: ${isActive ? NeutralColors.gray30 : menuOpenHere ? '#f2f2f2' : 'transparent'};

Expand Down Expand Up @@ -214,7 +218,7 @@ const diagnosticWarningIcon = {
color: '#8A8780',
background: '#FFF4CE',
};
const itemName = (nameWidth: number) => css`
export const itemName = (nameWidth: number) => css`
max-width: ${nameWidth}px;
overflow: hidden;
text-overflow: ellipsis;
Expand All @@ -228,6 +232,8 @@ const calloutRootStyle = css`
type TreeObject =
| 'bot'
| 'dialog'
| 'topic'
| 'system topic'
| 'trigger' // basic ProjectTree elements
| 'trigger group'
| 'form dialog'
Expand All @@ -241,6 +247,8 @@ const TreeIcons: { [key in TreeObject]: string | null } = {
bot: Icons.BOT,
dialog: Icons.DIALOG,
trigger: Icons.TRIGGER,
topic: Icons.TOPIC,
'system topic': Icons.SYSTEM_TOPIC,
'trigger group': null,
'form dialog': Icons.FORM_DIALOG,
'form field': Icons.FORM_FIELD, // x in parentheses
Expand All @@ -253,6 +261,8 @@ const TreeIcons: { [key in TreeObject]: string | null } = {
const objectNames: { [key in TreeObject]: () => string } = {
trigger: () => formatMessage('Trigger'),
dialog: () => formatMessage('Dialog'),
topic: () => formatMessage('User Topic'),
'system topic': () => formatMessage('System Topic'),
'trigger group': () => formatMessage('Trigger group'),
'form dialog': () => formatMessage('Form dialog'),
'form field': () => formatMessage('Form field'),
Expand Down Expand Up @@ -428,6 +438,7 @@ export const TreeItem: React.FC<ITreeItemProps> = ({

const ariaLabel = `${objectNames[itemType]()} ${link.displayName}`;
const dataTestId = `${dialogName ?? '$Root'}_${link.displayName}`;
const isExternal = Boolean(link.href);

const overflowMenu = menu.map(renderTreeMenuItem(link));

Expand Down Expand Up @@ -460,41 +471,50 @@ export const TreeItem: React.FC<ITreeItemProps> = ({
}

return (
<div
data-is-focusable
aria-label={`${ariaLabel} ${warningContent} ${errorContent}`}
css={projectTreeItemContainer}
tabIndex={0}
onBlur={item.onBlur}
onFocus={item.onFocus}
>
<div css={projectTreeItem} role="presentation" tabIndex={-1}>
{item.itemType != null && TreeIcons[item.itemType] != null && (
<Icon
iconName={TreeIcons[item.itemType]}
styles={{
root: {
width: '12px',
marginRight: '8px',
outline: 'none',
},
}}
tabIndex={-1}
/>
)}
<span className={'treeItem-text'} css={itemName(maxTextWidth)}>
{item.displayName}
</span>
{showErrors && (
<DiagnosticIcons
diagnostics={diagnostics}
projectId={projectId}
skillId={skillId}
onErrorClick={onErrorClick}
/>
)}
<TreeItemContent tooltip={link.tooltip}>
<div
data-is-focusable
aria-label={`${ariaLabel} ${warningContent} ${errorContent}`}
css={projectTreeItemContainer}
tabIndex={0}
onBlur={item.onBlur}
onFocus={item.onFocus}
>
<div css={projectTreeItem} role="presentation" tabIndex={-1}>
{item.itemType != null && TreeIcons[item.itemType] != null && (
<Icon
iconName={TreeIcons[item.itemType]}
styles={{
root: {
width: '12px',
marginRight: '8px',
outline: 'none',
},
}}
tabIndex={-1}
/>
)}
<span className={'treeItem-text'} css={itemName(maxTextWidth)}>
{item.displayName}
</span>
{isExternal && (
<Icon
className="external-link"
iconName="NavigateExternalInline"
styles={{ root: { visibility: 'hidden', width: '12px', marginLeft: '4px', outline: 'none' } }}
/>
)}
{showErrors && (
<DiagnosticIcons
diagnostics={diagnostics}
projectId={projectId}
skillId={skillId}
onErrorClick={onErrorClick}
/>
)}
</div>
</div>
</div>
</TreeItemContent>
);
},
[textWidth, spacerWidth, extraSpace, overflowIconWidthActiveOrChildSelected, showErrors]
Expand Down Expand Up @@ -566,14 +586,23 @@ export const TreeItem: React.FC<ITreeItemProps> = ({
data-testid={dataTestId}
role={role}
tabIndex={0}
onClick={() => {
onSelect?.(link);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onSelect?.(link);
}
}}
onClick={
onSelect
? () => {
onSelect(link);
}
: undefined
}
onKeyDown={
onSelect
? (e) => {
if (e.key === 'Enter') {
onSelect(link);
e.stopPropagation();
}
}
: undefined
}
>
<div style={{ minWidth: `${spacerWidth}px` }} />
<OverflowSet
Expand Down
Loading