diff --git a/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx b/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx index 02e92a496f..5f076217b0 100644 --- a/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx +++ b/Composer/packages/adaptive-flow/src/adaptive-flow-renderer/components/ElementMeasurer.tsx @@ -20,11 +20,12 @@ export const ElementMeasurer: React.FC = ({ children, styl return ( { + onResize={({ bounds }) => { /** * As a parent node, mounted before children mounted. * Avoid flickering issue by filtering out the first onResize event. */ + const { width, height } = bounds ?? { width: 0, height: 0 }; if (width === 0 && height === 0) return; onResize(new Boundary(width, height)); }} diff --git a/Composer/packages/client/package.json b/Composer/packages/client/package.json index 8bb8e2289a..9f8aed2dc9 100644 --- a/Composer/packages/client/package.json +++ b/Composer/packages/client/package.json @@ -26,10 +26,10 @@ "@bfc/indexers": "*", "@bfc/shared": "*", "@bfc/ui-plugin-composer": "*", + "@bfc/ui-plugin-cross-trained": "*", "@bfc/ui-plugin-dialog-schema-editor": "*", "@bfc/ui-plugin-lg": "*", "@bfc/ui-plugin-luis": "*", - "@bfc/ui-plugin-cross-trained": "*", "@bfc/ui-plugin-prompts": "*", "@bfc/ui-plugin-select-dialog": "*", "@bfc/ui-plugin-select-skill-dialog": "*", @@ -49,6 +49,7 @@ "office-ui-fabric-react": "^7.121.11", "prop-types": "^15.7.2", "query-string": "^6.8.2", + "react-measure": "^2.3.0", "re-resizable": "^6.3.2", "react": "16.13.0", "react-app-polyfill": "^0.2.1", diff --git a/Composer/packages/client/src/components/NavTree.tsx b/Composer/packages/client/src/components/NavTree.tsx index d23544cb33..6bf12de255 100644 --- a/Composer/packages/client/src/components/NavTree.tsx +++ b/Composer/packages/client/src/components/NavTree.tsx @@ -3,23 +3,19 @@ /** @jsx jsx */ import { jsx, css } from '@emotion/core'; -import { Resizable, ResizeCallback } from 're-resizable'; import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; import { FontWeights, FontSizes } from 'office-ui-fabric-react/lib/Styling'; import { NeutralColors } from '@uifabric/fluent-theme'; import { IButtonStyles } from 'office-ui-fabric-react/lib/Button'; import { mergeStyleSets } from 'office-ui-fabric-react/lib/Styling'; -import { useRecoilValue } from 'recoil'; import { navigateTo } from '../utils/navigation'; -import { dispatcherState, userSettingsState } from '../recoilModel'; // -------------------- Styles -------------------- // const root = css` width: 100%; height: 100%; - border-right: 1px solid #c4c4c4; box-sizing: border-box; overflow-y: auto; overflow-x: hidden; @@ -75,43 +71,27 @@ interface INavTreeProps { const NavTree: React.FC = (props) => { const { navLinks, regionName } = props; - const { updateUserSettings } = useRecoilValue(dispatcherState); - const { dialogNavWidth: currentWidth } = useRecoilValue(userSettingsState); - - const handleResize: ResizeCallback = (_e, _dir, _ref, d) => { - updateUserSettings({ dialogNavWidth: currentWidth + d.width }); - }; return ( - -
- {navLinks.map((item) => { - const isSelected = location.pathname.includes(item.url); +
+ {navLinks.map((item) => { + const isSelected = location.pathname.includes(item.url); - return ( - { - e.preventDefault(); - navigateTo(item.url); - }} - /> - ); - })} -
- + return ( + { + e.preventDefault(); + navigateTo(item.url); + }} + /> + ); + })} +
); }; diff --git a/Composer/packages/client/src/components/Page.tsx b/Composer/packages/client/src/components/Page.tsx index e631a2aef8..a8bcb2b962 100644 --- a/Composer/packages/client/src/components/Page.tsx +++ b/Composer/packages/client/src/components/Page.tsx @@ -6,6 +6,8 @@ import { jsx, css } from '@emotion/core'; import React from 'react'; import { FontWeights, FontSizes } from 'office-ui-fabric-react/lib/Styling'; +import { LeftRightSplit } from '../components/Split/LeftRightSplit'; + import { Toolbar, IToolbarItem } from './Toolbar'; import { NavTree, INavTreeItem } from './NavTree'; @@ -72,7 +74,7 @@ export const content = css` padding: 20px; position: relative; overflow: auto; - + height: 100%; label: PageContent; `; @@ -101,10 +103,12 @@ const Page: React.FC = (props) => { {onRenderHeaderContent &&
{onRenderHeaderContent()}
}
- -
- {children} -
+ + +
+ {children} +
+
diff --git a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx index 65399c06b3..45d4910a26 100644 --- a/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx +++ b/Composer/packages/client/src/components/ProjectTree/ProjectTree.tsx @@ -16,13 +16,12 @@ import { FocusZone, FocusZoneDirection } from 'office-ui-fabric-react/lib/FocusZ import cloneDeep from 'lodash/cloneDeep'; import formatMessage from 'format-message'; import { DialogInfo, ITrigger } from '@bfc/shared'; -import { Resizable, ResizeCallback } from 're-resizable'; import debounce from 'lodash/debounce'; import { useRecoilValue } from 'recoil'; import { IGroupedListStyles } from 'office-ui-fabric-react/lib/GroupedList'; import { ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox'; -import { dispatcherState, userSettingsState } from '../../recoilModel'; +import { dispatcherState } from '../../recoilModel'; import { createSelectedPath, getFriendlyName } from '../../utils/dialogUtil'; import { containUnsupportedTriggers, triggerNotSupported } from '../../utils/dialogValidator'; @@ -48,7 +47,6 @@ const searchBox: ISearchBoxStyles = { const root = css` width: 100%; height: 100%; - border-right: 1px solid #c4c4c4; box-sizing: border-box; overflow-y: auto; overflow-x: hidden; @@ -130,8 +128,7 @@ interface IProjectTreeProps { } export const ProjectTree: React.FC = (props) => { - const { onboardingAddCoachMarkRef, updateUserSettings } = useRecoilValue(dispatcherState); - const { dialogNavWidth: currentWidth } = useRecoilValue(userSettingsState); + const { onboardingAddCoachMarkRef } = useRecoilValue(dispatcherState); const groupRef: React.RefObject = useRef(null); const { dialogs, dialogId, selected, onSelect, onDeleteTrigger, onDeleteDialog } = props; @@ -185,41 +182,28 @@ export const ProjectTree: React.FC = (props) => { } }; - const handleResize: ResizeCallback = (_e, _dir, _ref, d) => { - updateUserSettings({ dialogNavWidth: currentWidth + d.width }); - }; - const itemsAndGroups: { items: any[]; groups: IGroup[] } = createItemsAndGroups(sortedDialogs, dialogId, filter); return ( - -
- - -
+ +
= (props) => { 0 {} other {Press down arrow key to navigate the search results} }`, - { dialogNum: itemsAndGroups.groups.length } - )} - aria-live={'polite'} - /> - - } - styles={groupListStyle} - onRenderCell={onRenderCell} - /> - -
- + { dialogNum: itemsAndGroups.groups.length } + )} + aria-live={'polite'} + /> + + } + styles={groupListStyle} + onRenderCell={onRenderCell} + /> + +
); }; diff --git a/Composer/packages/client/src/components/Split/LeftRightSplit.tsx b/Composer/packages/client/src/components/Split/LeftRightSplit.tsx new file mode 100644 index 0000000000..8b88b48e3d --- /dev/null +++ b/Composer/packages/client/src/components/Split/LeftRightSplit.tsx @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as React from 'react'; +import styled from '@emotion/styled'; +import { default as Measure, ContentRect } from 'react-measure'; + +const defaultSplitterWidth = 5; + +const MeasureDiv = styled.div` + width: 100%; + height: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; +`; + +const Root = styled.div(({ leftColWidth, splitterWidth }: { leftColWidth: string; splitterWidth: number }) => ({ + width: '100%', + height: '100%', + boxSizing: 'border-box', + outline: 'none', + overflow: 'hidden', + display: 'grid', + gridTemplateRows: '1fr', + gridTemplateAreas: `'left split right'`, + gridTemplateColumns: `${leftColWidth} ${splitterWidth}px 1fr`, +})); + +const Left = styled.div` + height: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; + grid-area: left; +`; + +const Split = styled.div` + height: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; + grid-area: split; + cursor: col-resize; + background: transparent; + &:hover .default-split-visual { + background: gray; + } +`; + +const DefaultSplitVisual = styled.div(({ splitterWidth }: { splitterWidth: number }) => ({ + height: '100%', + width: '1px', + boxSizing: 'border-box', + outline: 'none', + overflow: 'hidden', + background: 'silver', + cursor: 'col-resize', + marginLeft: `${splitterWidth / 2}px`, +})); + +const Right = styled.div` + height: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; + grid-area: right; +`; + +// ensures a value can be used in gridTemplateColumns +// defaults to 'auto' +const toGridWidth = (value?: string | number) => { + if (value === undefined || value === null) { + return 'auto'; + } + + if (typeof value === 'string') { + if (value.trim().length === 0) { + return 'auto'; + } + + if (value.endsWith('px') || value.endsWith('%') || value.endsWith('fr')) { + return value; + } + } + + return `${value}px`; +}; + +// constrains the extend of a pane given split constraints +const constrainPaneExtent = ( + /** + * the requested extend of the primary pane + */ + primary: number, + constraints: { + /** + * the total extent available for primary, splitter, and secondary + */ + total: number; + /** + * the extent of the splitter + */ + splitter: number; + /** + * the minimum extend of the primary pane + */ + minPrimary?: number; + /** + * the minimum extend of the secondary pane + */ + minSecondary?: number; + } +): number => { + const { total, splitter, minPrimary, minSecondary } = constraints; + + // ensure within total bounds + let newPrimary = Math.max(0, Math.min(primary, total - splitter)); + + // adjust satisfy minSecondary + const secondary = total - (newPrimary + splitter); + if (minSecondary && secondary < minSecondary) { + newPrimary = newPrimary - Math.max(0, minSecondary - secondary); + } + + // adjust to satisfy minPrimary + if (minPrimary && newPrimary < minPrimary) { + newPrimary = minPrimary; + } + + // ensure within total bounds after adjustments + return Math.max(0, Math.min(newPrimary, total - splitter)); +}; + +type Props = { + initialLeftGridWidth?: string | number; + minLeftPixels?: number; + minRightPixels?: number; + splitterWidth?: number; + renderSplitter?: () => React.ReactNode; +}; + +export const LeftRightSplit = (props: React.PropsWithChildren) => { + const { + initialLeftGridWidth: defaultLeftWidth, + minRightPixels, + minLeftPixels, + splitterWidth = defaultSplitterWidth, + renderSplitter, + } = props; + + const [currentContentWidth, setCurrentContentWidth] = React.useState(0); + const [currentLeftWidth, setCurrentLeftWidth] = React.useState(0); + + const [leftWidth, setLeftWidth] = React.useState(() => { + // If the default is a number, then use it or the left minimum as a value. + const numericValue = Number(defaultLeftWidth); + return isNaN(numericValue) ? -1 : Math.max(numericValue, minLeftPixels ?? numericValue); + }); + + const [leftStart, setLeftStart] = React.useState(0); + const [screenStart, setScreenStart] = React.useState(0); + + const constrainLeft = (value: number): number => { + return constrainPaneExtent(value, { + total: currentContentWidth, + splitter: splitterWidth, + minPrimary: minLeftPixels, + minSecondary: minRightPixels, + }); + }; + + React.useEffect(() => { + if (leftWidth !== -1) { + const newLeft = constrainLeft(leftWidth); + setLeftWidth(newLeft); + } + }, [currentContentWidth, splitterWidth]); + + const onContentMeasure = (contentRect: ContentRect) => { + contentRect.bounds && setCurrentContentWidth(contentRect.bounds.width); + }; + + const onLeftMeasure = (contentRect: ContentRect) => { + contentRect.bounds && setCurrentLeftWidth(contentRect.bounds.width); + }; + + const onSplitMouseDown = (event: React.MouseEvent) => { + event.currentTarget.setPointerCapture(1); + setScreenStart(event.screenX); + setLeftStart(currentLeftWidth); + }; + + const onSplitMouseMove = (event: React.MouseEvent) => { + if (event.currentTarget.hasPointerCapture(1)) { + // calculate candidate left + const newLeft = constrainLeft(leftStart + (event.screenX - screenStart)); + setLeftWidth(newLeft); + } + }; + + const onSplitMouseUp = (event: React.MouseEvent) => { + event.currentTarget.releasePointerCapture(1); + }; + + // If the left width has never been set + // try using the default as a number and constraining it + let renderLeftWidth = `${leftWidth}px`; + if (leftWidth < 0) { + renderLeftWidth = toGridWidth(defaultLeftWidth); + } + + const children = React.Children.toArray(props.children); + const leftChild = children.length > 0 ? children[0] :
; + const rightChild = children.length > 1 ? children[1] :
; + + const renderSplitVisual = + renderSplitter ?? + (() => { + return ; + }); + + return ( + + {({ measureRef }) => ( + + + + + {({ measureRef: leftRef }) => {leftChild}} + + + + {renderSplitVisual()} + + {rightChild} + + + )} + + ); +}; diff --git a/Composer/packages/client/src/components/Split/TopBottomSplit.tsx b/Composer/packages/client/src/components/Split/TopBottomSplit.tsx new file mode 100644 index 0000000000..f221bd4642 --- /dev/null +++ b/Composer/packages/client/src/components/Split/TopBottomSplit.tsx @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as React from 'react'; +import styled from '@emotion/styled'; +import { default as Measure, ContentRect } from 'react-measure'; + +const defaultSplitterHeight = 5; + +const MeasureDiv = styled.div` + width: 100%; + height: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; +`; + +const Root = styled.div(({ topRowHeight, splitterHeight }: { topRowHeight: string; splitterHeight: number }) => ({ + width: '100%', + height: '100%', + boxSizing: 'border-box', + outline: 'none', + overflow: 'hidden', + display: 'grid', + gridTemplateColumns: '1fr', + gridTemplateAreas: `'top' 'split' 'bottom'`, + gridTemplateRows: `${topRowHeight} ${splitterHeight}px 1fr`, +})); + +const Top = styled.div` + width: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; + grid-area: top; +`; + +const Split = styled.div` + width: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; + grid-area: split; + cursor: row-resize; + background: transparent; + &:hover .default-split-visual { + background: gray; + } +`; + +const DefaultSplitVisual = styled.div(({ splitterHeight }: { splitterHeight: number }) => ({ + width: '100%', + height: '1px', + boxSizing: 'border-box', + outline: 'none', + overflow: 'hidden', + background: 'silver', + cursor: 'row-resize', + marginTop: `${splitterHeight / 2}px`, +})); + +const Bottom = styled.div` + width: 100%; + box-sizing: border-box; + outline: none; + overflow: hidden; + grid-area: bottom; +`; + +// ensures a value can be used in gridTemplateColumns +// defaults to '1fr' +const toGridExtent = (value?: string | number) => { + if (value === undefined || value === null) { + return '1fr'; + } + + if (typeof value === 'string') { + if (value.trim().length === 0) { + return '1fr'; + } + + if (value.endsWith('px') || value.endsWith('%') || value.endsWith('fr')) { + return value; + } + } + + return `${value}px`; +}; + +// constrains the extend of a pane given split constraints +const constrainPaneExtent = ( + /** + * the requested extend of the primary pane + */ + primary: number, + constraints: { + /** + * the total extent available for primary, splitter, and secondary + */ + total: number; + /** + * the extent of the splitter + */ + splitter: number; + /** + * the minimum extend of the primary pane + */ + minPrimary?: number; + /** + * the minimum extend of the secondary pane + */ + minSecondary?: number; + } +): number => { + const { total, splitter, minPrimary, minSecondary } = constraints; + + // ensure within total bounds + let newPrimary = Math.max(0, Math.min(primary, total - splitter)); + + // adjust satisfy minSecondary + const secondary = total - (newPrimary + splitter); + if (minSecondary && secondary < minSecondary) { + newPrimary = newPrimary - Math.max(0, minSecondary - secondary); + } + + // adjust to satisfy minPrimary + if (minPrimary && newPrimary < minPrimary) { + newPrimary = minPrimary; + } + + // ensure within total bounds after adjustments + return Math.max(0, Math.min(newPrimary, total - splitter)); +}; + +type Props = { + initialTopGridHeight?: string | number; + minTopPixels?: number; + minBottomPixels?: number; + splitterHeight?: number; + renderSplitter?: () => React.ReactNode; +}; + +export const TopBottomSplit = (props: React.PropsWithChildren) => { + const { + initialTopGridHeight, + minBottomPixels, + minTopPixels, + splitterHeight = defaultSplitterHeight, + renderSplitter, + } = props; + + const [currentContentHeight, setCurrentContentHeight] = React.useState(0); + const [currentTopHeight, setCurrentTopHeight] = React.useState(0); + + const [topHeight, setTopHeight] = React.useState(() => { + // If the default is a number, then use it or the top minimum as a value. + const numericValue = Number(initialTopGridHeight); + return isNaN(numericValue) ? -1 : Math.max(numericValue, minTopPixels ?? numericValue); + }); + + const [topStart, setTopStart] = React.useState(0); + const [screenStart, setScreenStart] = React.useState(0); + + const constrainTop = (value: number): number => { + return constrainPaneExtent(value, { + total: currentContentHeight, + splitter: splitterHeight, + minPrimary: minTopPixels, + minSecondary: minBottomPixels, + }); + }; + + React.useEffect(() => { + if (topHeight != -1) { + const newTop = constrainTop(topHeight); + setTopHeight(newTop); + } + }, [currentContentHeight, splitterHeight]); + + const onContentMeasure = (contentRect: ContentRect) => { + contentRect.bounds && setCurrentContentHeight(contentRect.bounds.height); + }; + + const onTopMeasure = (contentRect: ContentRect) => { + contentRect.bounds && setCurrentTopHeight(contentRect.bounds.height); + }; + + const onSplitMouseDown = (event: React.MouseEvent) => { + event.currentTarget.setPointerCapture(1); + setScreenStart(event.screenY); + setTopStart(currentTopHeight); + }; + + const onSplitMouseMove = (event: React.MouseEvent) => { + if (event.currentTarget.hasPointerCapture(1)) { + // calculate candidate top + const newTop = constrainTop(topStart + (event.screenY - screenStart)); + setTopHeight(newTop); + } + }; + + const onSplitMouseUp = (event: React.MouseEvent) => { + event.currentTarget.releasePointerCapture(1); + }; + + // If the top height has never been set + // try using the default as a number and constraining it + let renderTopHeight = `${topHeight}px`; + if (topHeight < 0) { + renderTopHeight = toGridExtent(initialTopGridHeight); + } + + const children = React.Children.toArray(props.children); + const topChild = children.length > 0 ? children[0] :
; + const bottomChild = children.length > 1 ? children[1] :
; + + const renderSplitVisual = + renderSplitter ?? + (() => { + return ; + }); + + return ( + + {({ measureRef }) => ( + + + + + {({ measureRef: topRef }) => {topChild}} + + + + {renderSplitVisual()} + + {bottomChild} + + + )} + + ); +}; diff --git a/Composer/packages/client/src/pages/design/DesignPage.tsx b/Composer/packages/client/src/pages/design/DesignPage.tsx index 5fc854d838..7c0664e0c3 100644 --- a/Composer/packages/client/src/pages/design/DesignPage.tsx +++ b/Composer/packages/client/src/pages/design/DesignPage.tsx @@ -14,6 +14,7 @@ import { JsonEditor } from '@bfc/code-editor'; import { EditorExtension, useTriggerApi, PluginConfig } from '@bfc/extension-client'; import { useRecoilValue } from 'recoil'; +import { LeftRightSplit } from '../../components/Split/LeftRightSplit'; import { LoadingSpinner } from '../../components/LoadingSpinner'; import { TestController } from '../../components/TestController/TestController'; import { DialogDeleting } from '../../constants'; @@ -586,64 +587,68 @@ const DesignPage: React.FC
- handleSelect(projectId, ...props)} - /> -
-
- - -
- -
-
- {breadcrumbItems} - {dialogJsonVisible ? ( - { - updateDialog({ id: currentDialog.id, content: data, projectId }); - }} - /> - ) : withWarning ? ( - warningIsVisible && ( - { - setWarningIsVisible(false); - }} - onOk={() => navigateTo(`/bot/${projectId}/knowledge-base/all`)} - /> - ) - ) : ( - - setFlowEditorFocused(false)} - onFocus={() => setFlowEditorFocused(true)} - /> + + handleSelect(projectId, ...props)} + /> +
+
+ + +
+ +
+ +
+ {breadcrumbItems} + {dialogJsonVisible ? ( + { + updateDialog({ id: currentDialog.id, content: data, projectId }); + }} + /> + ) : withWarning ? ( + warningIsVisible && ( + { + setWarningIsVisible(false); + }} + onOk={() => navigateTo(`/bot/${projectId}/knowledge-base/all`)} + /> + ) + ) : ( + + setFlowEditorFocused(false)} + onFocus={() => setFlowEditorFocused(true)} + /> + + )} +
+ + - )} +
- - - -
- -
+ +
+
}> {showCreateDialogModal && ( diff --git a/Composer/packages/client/src/pages/design/PropertyEditor.tsx b/Composer/packages/client/src/pages/design/PropertyEditor.tsx index e99598f147..4c605dc421 100644 --- a/Composer/packages/client/src/pages/design/PropertyEditor.tsx +++ b/Composer/packages/client/src/pages/design/PropertyEditor.tsx @@ -9,7 +9,6 @@ import { FormErrors, JSONSchema7, useFormConfig, useShellApi } from '@bfc/extens import formatMessage from 'format-message'; import isEqual from 'lodash/isEqual'; import debounce from 'lodash/debounce'; -import { Resizable, ResizeCallback } from 're-resizable'; import { MicrosoftAdaptiveDialog } from '@bfc/shared'; import { formEditor } from './styles'; @@ -28,12 +27,6 @@ const PropertyEditor: React.FC = () => { const { shellApi, ...shellData } = useShellApi(); const { currentDialog, data: formData = {}, focusPath, focusedSteps, schemas } = shellData; - const currentWidth = shellData?.userSettings?.propertyEditorWidth || 400; - - const handleResize: ResizeCallback = (_e, _dir, _ref, d) => { - shellApi.updateUserSettings({ propertyEditorWidth: currentWidth + d.width }); - }; - const [localData, setLocalData] = useState(formData as MicrosoftAdaptiveDialog); const syncData = useRef( @@ -116,25 +109,15 @@ const PropertyEditor: React.FC = () => { }; return ( - -
- -
-
+
+ +
); }; diff --git a/Composer/packages/client/src/pages/design/styles.ts b/Composer/packages/client/src/pages/design/styles.ts index b8cb102df2..98733aa47f 100644 --- a/Composer/packages/client/src/pages/design/styles.ts +++ b/Composer/packages/client/src/pages/design/styles.ts @@ -17,7 +17,7 @@ export const contentWrapper = css` display: flex; flex-direction: column; flex-grow: 1; - + height: 100%; label: DesignPageContent; `; @@ -26,10 +26,10 @@ export const projectContainer = css` flex-direction: column; flex-grow: 0; flex-shrink: 0; - width: 255px; + width: 100%; + height: 100%; overflow: auto; - border-right: 1px solid #c4c4c4; - + border-right: 1px solid red; label: DesignPageProjectContent; `; @@ -73,14 +73,14 @@ export const editorWrapper = css` flex-direction: row; flex-grow: 1; overflow: hidden; + height: 100%; `; export const visualPanel = css` display: flex; flex-direction: column; flex: 1; - border-right: 1px solid #c4c4c4; - + height: 100%; label: DesignPageVisualPanel; `; @@ -99,6 +99,7 @@ export const formEditor = css` transition: width 0.2s ease-in-out; overflow-y: scroll; height: 100%; + min-width: 300px; `; export const breadcrumbClass = mergeStyleSets({ diff --git a/Composer/packages/client/src/pages/publish/Publish.tsx b/Composer/packages/client/src/pages/publish/Publish.tsx index 5edbfac6ed..a378885ed1 100644 --- a/Composer/packages/client/src/pages/publish/Publish.tsx +++ b/Composer/packages/client/src/pages/publish/Publish.tsx @@ -11,6 +11,7 @@ import { TextField } from 'office-ui-fabric-react/lib/TextField'; import { PublishTarget } from '@bfc/shared'; import { useRecoilValue } from 'recoil'; +import { LeftRightSplit } from '../../components/Split/LeftRightSplit'; import settingsStorage from '../../utils/dialogSettingStorage'; import { projectContainer } from '../design/styles'; import { @@ -403,52 +404,54 @@ const Publish: React.FC{selectedTarget ? selectedTargetName : formatMessage('Publish Profiles')}
-
+
{ - setSelectedTarget(undefined); - onSelectTarget('all'); - }} + aria-label={formatMessage('Navigation panel')} + css={projectContainer} + data-testid="target-list" + role="region" > - {formatMessage('All profiles')} -
- {settings && settings.publishTargets && ( - await onDelete(index)} - onEdit={async (item, target) => await onEdit(item, target)} - onSelect={(item) => { - setSelectedTarget(item); - onSelectTarget(item.name); +
{ + setSelectedTarget(undefined); + onSelectTarget('all'); }} - /> - )} -
-
- - - {!thisPublishHistory || thisPublishHistory.length === 0 ? ( -
No publish history
- ) : null} -
-
+ > + {formatMessage('All profiles')} +
+ {settings && settings.publishTargets && ( + await onDelete(index)} + onEdit={async (item, target) => await onEdit(item, target)} + onSelect={(item) => { + setSelectedTarget(item); + onSelectTarget(item.name); + }} + /> + )} +
+
+ + + {!thisPublishHistory || thisPublishHistory.length === 0 ? ( +
No publish history
+ ) : null} +
+
+
); diff --git a/Composer/packages/client/src/pages/publish/styles.ts b/Composer/packages/client/src/pages/publish/styles.ts index d8b32bb160..908cab1f8b 100644 --- a/Composer/packages/client/src/pages/publish/styles.ts +++ b/Composer/packages/client/src/pages/publish/styles.ts @@ -21,6 +21,7 @@ export const HeaderText = css` export const ContentStyle = css` margin-left: 2px; display: flex; + height: 100%; border-top: 1px solid #dddddd; flex: 1; position: relative; diff --git a/Composer/yarn.lock b/Composer/yarn.lock index 3e8beffec5..7dc835e529 100644 --- a/Composer/yarn.lock +++ b/Composer/yarn.lock @@ -274,6 +274,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.11.5": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" + integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== + dependencies: + "@babel/types" "^7.11.5" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.2.2", "@babel/generator@^7.3.4", "@babel/generator@^7.4.0": version "7.4.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.0.tgz#c230e79589ae7a729fd4631b9ded4dc220418196" @@ -757,6 +766,13 @@ dependencies: "@babel/types" "^7.10.4" +"@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + "@babel/helper-split-export-declaration@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz#57292af60443c4a3622cf74040ddc28e68336fd8" @@ -864,7 +880,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.11.3", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.0", "@babel/parser@^7.7.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0", "@babel/parser@^7.9.6": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.10.5", "@babel/parser@^7.11.3", "@babel/parser@^7.11.5", "@babel/parser@^7.2.2", "@babel/parser@^7.3.4", "@babel/parser@^7.4.0", "@babel/parser@^7.7.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0", "@babel/parser@^7.9.6": version "7.11.5" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== @@ -2178,6 +2194,21 @@ "@babel/parser" "^7.7.4" "@babel/types" "^7.7.4" +"@babel/traverse@^7.0.0": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" + integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.5" + "@babel/types" "^7.11.5" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + "@babel/traverse@^7.1.0", "@babel/traverse@^7.2.2", "@babel/traverse@^7.3.4", "@babel/traverse@^7.4.0": version "7.4.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.0.tgz#14006967dd1d2b3494cdd650c686db9daf0ddada" @@ -2271,6 +2302,15 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@babel/types@^7.11.0", "@babel/types@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" + integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + "@babel/types@^7.3.3", "@babel/types@^7.7.0", "@babel/types@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.6.tgz#2c5502b427251e9de1bd2dff95add646d95cc9f7" @@ -2480,12 +2520,12 @@ dependencies: "@emotion/memoize" "0.7.4" -"@emotion/is-prop-valid@^0.7.3": - version "0.7.3" - resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.7.3.tgz#a6bf4fa5387cbba59d44e698a4680f481a8da6cc" - integrity sha512-uxJqm/sqwXw3YPA5GXX365OBcJGFtxUVkB6WyezqFHlNe9jqUWH5ur2O2M8dGBz61kn1g3ZBlzUunFQXQIClhA== +"@emotion/is-prop-valid@^0.8.1": + version "0.8.8" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" + integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA== dependencies: - "@emotion/memoize" "0.7.1" + "@emotion/memoize" "0.7.4" "@emotion/memoize@0.7.1": version "0.7.1" @@ -2563,7 +2603,7 @@ resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== -"@emotion/unitless@0.7.3", "@emotion/unitless@^0.7.0": +"@emotion/unitless@0.7.3": version "0.7.3" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.3.tgz#6310a047f12d21a1036fb031317219892440416f" integrity sha512-4zAPlpDEh2VwXswwr/t8xGNDGg8RQiPxtxZ3qQEXyQsBV39ptTdESCjuBvGze1nLMVrxmTIKmnO/nAV8Tqjjzg== @@ -2573,7 +2613,7 @@ resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.4.tgz#a87b4b04e5ae14a88d48ebef15015f6b7d1f5677" integrity sha512-kBa+cDHOR9jpRJ+kcGMsysrls0leukrm68DmFQoMIWQcXdr2cZvyvypWuGYT7U+9kAExUE7+T7r6G3C3A6L8MQ== -"@emotion/unitless@0.7.5": +"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.0": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== @@ -5481,8 +5521,8 @@ binary-extensions@^2.0.0: bl@^2.2.1, bl@^4.0.3: version "2.2.1" - resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" - integrity sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g== + resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" + integrity sha1-jBGntzBlXF1WiYzchxIk9A/ZAdU= dependencies: readable-stream "^2.3.5" safe-buffer "^5.1.1" @@ -7170,9 +7210,9 @@ css-select@^2.0.0: nth-check "^1.0.2" css-to-react-native@^2.2.2: - version "2.3.0" - resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.3.0.tgz#bf80d24ec4a08e430306ef429c0586e6ed5485f7" - integrity sha512-IhR7bNIrCFwbJbKZOAjNDZdwpsbjTN6f1agXeELHDqg1wHPA8c2QLruttKOW7hgMGetkfraRJCIEMrptifBfVw== + version "2.3.2" + resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-2.3.2.tgz#e75e2f8f7aa385b4c3611c52b074b70a002f2e7d" + integrity sha512-VOFaeZA053BqvvvqIA8c9n0+9vFppVBAHCp6JgFTtTMU3Mzi+XnelJ9XC9ul3BqFzZyQ5N+H0SnwsWT2Ebchxw== dependencies: camelize "^1.0.0" css-color-keywords "^1.0.0" @@ -11640,6 +11680,11 @@ is-typedarray@^1.0.0, is-typedarray@~1.0.0: resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= +is-what@^3.3.1: + version "3.11.2" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.11.2.tgz#4ca0c91b236acea48dd6af0a072d6a84aec7a1d4" + integrity sha512-m7LzBsC9TqUhkBrozSmmWfVO7VYnjk9UHu0U+Y8BiJRnc1TYIK/3Qv4DteuiBpn2S4K7n3N4WNC4pe6wEx2xYg== + is-windows@^1.0.1, is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -13171,10 +13216,10 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" -memoize-one@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906" - integrity sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA== +memoize-one@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" + integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA== memory-fs@^0.4.0, memory-fs@^0.4.1: version "0.4.1" @@ -13184,6 +13229,13 @@ memory-fs@^0.4.0, memory-fs@^0.4.1: errno "^0.1.3" readable-stream "^2.0.1" +merge-anything@^2.2.4: + version "2.4.4" + resolved "https://registry.yarnpkg.com/merge-anything/-/merge-anything-2.4.4.tgz#6226b2ac3d3d3fc5fb9e8d23aa400df25f98fdf0" + integrity sha512-l5XlriUDJKQT12bH+rVhAHjwIuXWdAIecGwsYjv2LJo+dA1AeRTmeQS+3QBpO6lEthBMDi2IUMpLC1yyRvGlwQ== + dependencies: + is-what "^3.3.1" + merge-deep@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" @@ -13674,8 +13726,8 @@ node-fetch@^2.1.2, node-fetch@^2.6.0, node-fetch@~2.6.0: node-forge@^0.10.0: version "0.10.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" - integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== + resolved "https://botbuilder.myget.org/F/botbuilder-v4-js-daily/npm/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" + integrity sha1-Mt6ir7Ppkm8C7lzoeUkCaRpna/M= node-int64@^0.4.0: version "0.4.0" @@ -15933,16 +15985,11 @@ react-frame-component@^4.0.2: resolved "https://registry.yarnpkg.com/react-frame-component/-/react-frame-component-4.1.0.tgz#bef04039c6af687314f27b20ef9893d85eefe3e6" integrity sha512-2HkO0iccSjd+xRA+aOxq7Mm50WUmCjdmhbQhOiG6gQTChaW//Y3mdkGeUfVA3YkXvDVbigRDvJd/VTUlqaZWSw== -react-is@^16.12.0, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.6.0, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^16.6.0: - version "16.8.4" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2" - integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA== - react-is@^16.8.1: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" @@ -17788,16 +17835,18 @@ style-loader@^0.23.1: schema-utils "^1.0.0" styled-components@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.1.3.tgz#4472447208e618b57e84deaaeb6acd34a5e0fe9b" - integrity sha512-0quV4KnSfvq5iMtT0RzpMGl/Dg3XIxIxOl9eJpiqiq4SrAmR1l1DLzNpMzoy3DyzdXVDMJS2HzROnXscWA3SEw== + version "4.4.1" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-4.4.1.tgz#e0631e889f01db67df4de576fedaca463f05c2f2" + integrity sha512-RNqj14kYzw++6Sr38n7197xG33ipEOktGElty4I70IKzQF1jzaD1U4xQ+Ny/i03UUhHlC5NWEO+d8olRCDji6g== dependencies: "@babel/helper-module-imports" "^7.0.0" - "@emotion/is-prop-valid" "^0.7.3" + "@babel/traverse" "^7.0.0" + "@emotion/is-prop-valid" "^0.8.1" "@emotion/unitless" "^0.7.0" babel-plugin-styled-components ">= 1" css-to-react-native "^2.2.2" - memoize-one "^4.0.0" + memoize-one "^5.0.0" + merge-anything "^2.2.4" prop-types "^15.5.4" react-is "^16.6.0" stylis "^3.5.0"