diff --git a/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx b/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx index 972b3d9617..fd184571cb 100644 --- a/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx +++ b/Composer/packages/client/src/components/GetStarted/GetStartedNextSteps.tsx @@ -88,7 +88,7 @@ export const GetStartedNextSteps: React.FC = (props) => { const linkToPackageManager = `/bot/${rootBotProjectId}/plugin/package-manager/package-manager`; const linkToConnections = `/bot/${rootBotProjectId}/botProjectsSettings/#connections`; - const linkToPublishProfile = `/bot/${rootBotProjectId}/botProjectsSettings/#addNewPublishProfile`; + const linkToPublishProfile = `/bot/${rootBotProjectId}/publish/all#addNewPublishProfile`; const linkToLUISSettings = `/bot/${rootBotProjectId}/botProjectsSettings/#luisKey`; const linktoQNASettings = `/bot/${rootBotProjectId}/botProjectsSettings/#qnaKey`; const linkToLGEditor = `/bot/${rootBotProjectId}/language-generation`; diff --git a/Composer/packages/client/src/pages/botProject/BotProjectsSettingsTabView.tsx b/Composer/packages/client/src/pages/botProject/BotProjectsSettingsTabView.tsx index cc56fbc9fc..9254a12d1e 100644 --- a/Composer/packages/client/src/pages/botProject/BotProjectsSettingsTabView.tsx +++ b/Composer/packages/client/src/pages/botProject/BotProjectsSettingsTabView.tsx @@ -17,7 +17,6 @@ import { AppIdAndPassword } from './AppIdAndPassword'; import { ExternalService } from './ExternalService'; import { BotLanguage } from './BotLanguage'; import { RuntimeSettings } from './RuntimeSettings'; -import { PublishTargets } from './PublishTargets'; import AdapterSection from './adapters/AdapterSection'; // -------------------- Styles -------------------- // @@ -29,10 +28,6 @@ const container = css` height: 100%; `; -const publishTargetsWrap = (isLastComponent) => css` - margin-bottom: ${isLastComponent ? '120px' : 0}; -`; - const idsInTab: Record = { Basics: ['runtimeSettings'], LuisQna: [], @@ -93,10 +88,7 @@ export const BotProjectSettingsTabView: React.FC -
- - {isRootBot && } -
+ {isRootBot && } = (props) => { return ( - -
-
-
{formatMessage('Name')}
-
{formatMessage('Target')}
-
-
- {publishTargets?.map((p, index) => { - return ( -
-
- {p.name} -
-
- {p.type} -
-
- { - setCurrent({ item: p, index: index }); - if (isShowAuthDialog(true)) { - setShowAuthDialog(true); - } else { - setDialogHidden(false); - } - }} - > - {formatMessage('Edit')} - -
-
- onDeletePublishTarget(p)} - > - {formatMessage('Delete')} - -
-
- ); - })} - { - if (isShowAuthDialog(true)) { - setShowAuthDialog(true); - } else { - setDialogHidden(false); - } - }} - > - {formatMessage('Add new')} - +
+
+
{formatMessage('Name')}
+
{formatMessage('Target')}
+
- + {publishTargets?.map((p, index) => { + return ( +
+
+ {p.name} +
+
+ {p.type} +
+
+ { + setCurrent({ item: p, index: index }); + if (isShowAuthDialog(true)) { + setShowAuthDialog(true); + } else { + setDialogHidden(false); + } + }} + > + {formatMessage('Edit')} + +
+
+ onDeletePublishTarget(p)} + > + {formatMessage('Delete')} + +
+
+ ); + })} + { + if (isShowAuthDialog(true)) { + setShowAuthDialog(true); + } else { + setDialogHidden(false); + } + }} + > + {formatMessage('Add new')} + +
{showAuthDialog && ( > = (props) => { const { projectId = '' } = props; - const botProjectData = useRecoilValue(localBotsDataSelector); const publishHistoryList = useRecoilValue(localBotPublishHistorySelector); const { @@ -57,10 +60,13 @@ const Publish: React.FC(); const showNotificationsRef = useRef>({}); + const [activeTab, setActiveTab] = useState('publish'); + const [provisionProject, setProvisionProject] = useState(projectId); const [currentBotList, setCurrentBotList] = useState([]); const [publishDialogVisible, setPublishDialogVisiblity] = useState(false); const [pullDialogVisible, setPullDialogVisiblity] = useState(false); @@ -227,13 +233,17 @@ const Publish: React.FC { - const url = - skillId === projectId - ? `/bot/${projectId}/botProjectsSettings/#addNewPublishProfile` - : `bot/${projectId}/skill/${skillId}/botProjectsSettings/#addNewPublishProfile`; - navigateTo(url); + setActiveTab('provision'); + setProvisionProject(skillId); }; + // pop out get started if #getstarted is in the URL + useEffect(() => { + if (location.hash === '#addNewPublishProfile') { + setActiveTab('provision'); + } + }, [location]); + const isPublishingToAzure = (target?: PublishTarget) => { return target?.type === 'azurePublish' || target?.type === 'azureFunctionsPublish'; }; @@ -412,20 +422,45 @@ const Publish: React.FC

{formatMessage('Publish your bots')}

-
-
- -
-
+ + setActiveTab(link?.props?.itemKey || '')} + > + +
+
+ +
+
+
+ + + {botProjectData && botProjectData.length > 1 && ( + + setProvisionProject(link.projectId)} + /> + + )} + + + + + +
); }; diff --git a/Composer/packages/client/src/pages/publish/components/projectList/ListItem.tsx b/Composer/packages/client/src/pages/publish/components/projectList/ListItem.tsx new file mode 100644 index 0000000000..a9cd74825d --- /dev/null +++ b/Composer/packages/client/src/pages/publish/components/projectList/ListItem.tsx @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import { jsx, css } from '@emotion/core'; +import React from 'react'; +import { Icon } from 'office-ui-fabric-react/lib/Icon'; +import { NeutralColors } from '@uifabric/fluent-theme'; + +import { ListLink } from './ProjectList'; + +// -------------------- Styles -------------------- // + +const navItem = (isActive: boolean, padLeft: number) => css` + label: navItem; + position: relative; + height: 24px; + font-size: 12px; + font-weight: 600; + padding-left: ${padLeft}px; + color: '#545454'; + background: ${isActive ? 'rgb(243, 242, 241)' : 'transparent'}; + + display: flex; + flex-direction: row; + align-items: center; + + &:focus { + outline: rgb(102, 102, 102) solid 1px; + z-index: 1; + .ms-Fabric--isFocusVisible &::after { + top: 0px; + right: 1px; + bottom: 0px; + left: 1px; + content: ''; + position: absolute; + z-index: 1; + border: 1px solid ${NeutralColors.white}; + border-image: initial; + outline: rgb(102, 102, 102) solid 1px; + } + } +`; + +export const overflowSet = css` + width: 100%; + height: 100%; + box-sizing: border-box; + line-height: 24px; + justify-content: space-between; + display: flex; +`; + +const itemName = (nameWidth: number) => css` + max-width: ${nameWidth}px; + overflow: hidden; + text-overflow: ellipsis; + flex-shrink: 1; +`; + +// -------------------- ListItem -------------------- // + +interface Props { + link: ListLink; + isActive?: boolean; + icon?: string; + onSelect?: (link: ListLink) => void; + textWidth?: number; + padLeft?: number; +} + +export const ListItem: React.FC = ({ + link, + isActive = false, + icon, + onSelect, + textWidth = 100, + padLeft = 16, +}) => { + const linkString = `${link.projectId}_ListItem`; + + return ( +
{ + onSelect?.(link); + }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + onSelect?.(link); + } + }} + > + {icon != null && ( + + )} + {link.displayName} +
+ ); +}; diff --git a/Composer/packages/client/src/pages/publish/components/projectList/ProjectList.tsx b/Composer/packages/client/src/pages/publish/components/projectList/ProjectList.tsx new file mode 100644 index 0000000000..bf9c491a70 --- /dev/null +++ b/Composer/packages/client/src/pages/publish/components/projectList/ProjectList.tsx @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** @jsx jsx */ +import React, { useState, useRef } from 'react'; +import { jsx, css } from '@emotion/core'; +import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZone'; +import formatMessage from 'format-message'; + +import { ListItem } from './ListItem'; +// -------------------- Styles -------------------- // + +const root = css` + width: 100%; + height: 100%; + box-sizing: border-box; + overflow-x: hidden; + .ms-List-cell { + min-height: 36px; + } +`; + +const icons = { + BOT: 'CubeShape', + EXTERNAL_SKILL: 'Globe', +}; + +const listCSS = css` + height: 100%; + label: list; +`; + +const headerCSS = (label: string) => css` + margin-top: -6px; + width: 100%; + label: ${label}; +`; + +// -------------------- ProjectList -------------------- // + +export type ListLink = { + displayName?: string; + projectId: string; +}; + +type BotInProject = { + projectId: string; + name: string; + isRemote: boolean; +}; + +type Props = { + onSelect?: (link: ListLink) => void; + defaultSelected?: string; + projectCollection: BotInProject[]; +}; + +export const ProjectList: React.FC = ({ onSelect, defaultSelected, projectCollection }) => { + const listRef = useRef(null); + + const [selectedLink, setSelectedLink] = useState(defaultSelected); + + const createProjectList = () => { + return projectCollection.filter((p) => !p.isRemote).map(renderBotHeader); + }; + + const handleOnSelect = (link: ListLink) => { + // Skip state change when link not changed. + if (link.projectId === selectedLink) return; + + setSelectedLink(link.projectId); + onSelect?.(link); + }; + + const renderBotHeader = (bot: BotInProject) => { + const link: ListLink = { + displayName: bot.name, + projectId: bot.projectId, + }; + + return ( + + + + ); + }; + + const projectList = createProjectList(); + + return ( +
+ +
{projectList}
+
+
+ ); +};