From 267169948f9d53c60d577e6bd1787862c4139183 Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Mon, 27 Sep 2021 13:24:58 -0700 Subject: [PATCH] Added solution toolbar to Canvas --- .../application/top_nav/editor_menu.tsx | 22 ++- .../solution_toolbar/items/popover.tsx | 15 +- .../solution_toolbar.stories.tsx | 96 +++++----- .../components/workpad_app/workpad_app.scss | 2 +- .../element_menu/element_menu.component.tsx | 69 ++----- .../element_menu/element_menu.scss | 3 - .../element_menu/element_menu.tsx | 42 +---- .../workpad_header.component.tsx | 176 ++++++++++++------ .../workpad_header/workpad_header.tsx | 50 ++++- x-pack/plugins/canvas/public/style/index.scss | 1 - x-pack/plugins/canvas/tsconfig.json | 1 + 11 files changed, 256 insertions(+), 221 deletions(-) delete mode 100644 x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.scss diff --git a/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx index 0ddd0902b719f..34988cdd012a5 100644 --- a/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx +++ b/src/plugins/dashboard/public/application/top_nav/editor_menu.tsx @@ -235,16 +235,18 @@ export const EditorMenu = ({ dashboardContainer, createNewVisType }: Props) => { panelPaddingSize="none" data-test-subj="dashboardEditorMenuButton" > - + {() => ( + + )} ); }; diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx b/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx index 33850005b498b..e227fc9fa12fa 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx +++ b/src/plugins/presentation_util/public/components/solution_toolbar/items/popover.tsx @@ -18,13 +18,17 @@ type AllowedPopoverProps = Omit< 'button' | 'isOpen' | 'closePopover' | 'anchorPosition' >; -export type Props = AllowedButtonProps & AllowedPopoverProps; +export type Props = AllowedButtonProps & + AllowedPopoverProps & { + children: (arg: { closePopover: () => void }) => React.ReactNode; + }; export const SolutionToolbarPopover = ({ label, iconType, primary, iconSide, + children, ...popover }: Props) => { const [isOpen, setIsOpen] = useState(false); @@ -37,6 +41,13 @@ export const SolutionToolbarPopover = ({ ); return ( - + + {children({ closePopover })} + ); }; diff --git a/src/plugins/presentation_util/public/components/solution_toolbar/solution_toolbar.stories.tsx b/src/plugins/presentation_util/public/components/solution_toolbar/solution_toolbar.stories.tsx index fa33f53f9ae4f..3a04a4c974538 100644 --- a/src/plugins/presentation_util/public/components/solution_toolbar/solution_toolbar.stories.tsx +++ b/src/plugins/presentation_util/public/components/solution_toolbar/solution_toolbar.stories.tsx @@ -54,29 +54,31 @@ const primaryButtonConfigs = { panelPaddingSize="none" primary={true} > - + {() => ( + + )} ), Dashboard: ( @@ -93,29 +95,31 @@ const extraButtonConfigs = { Canvas: undefined, Dashboard: [ - + {() => ( + + )} , ], }; diff --git a/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.scss b/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.scss index 3f6d6887e0c80..4acdca10d61cc 100644 --- a/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.scss +++ b/x-pack/plugins/canvas/public/components/workpad_app/workpad_app.scss @@ -31,7 +31,7 @@ $canvasLayoutFontSize: $euiFontSizeS; .canvasLayout__stageHeader { flex-grow: 0; flex-basis: auto; - padding: 1px $euiSize 0; + padding: $euiSizeS; font-size: $canvasLayoutFontSize; border-bottom: $euiBorderThin; background: $euiColorLightestShade; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx index 937912570b77f..14e2d1339aaaa 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.component.tsx @@ -6,17 +6,13 @@ */ import { sortBy } from 'lodash'; -import React, { Fragment, FunctionComponent, useState } from 'react'; +import React, { FunctionComponent, useState } from 'react'; import PropTypes from 'prop-types'; -import { - EuiButton, - EuiContextMenu, - EuiIcon, - EuiContextMenuPanelItemDescriptor, -} from '@elastic/eui'; +import { EuiContextMenu, EuiIcon, EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { PrimaryActionPopover } from '../../../../../../../src/plugins/presentation_util/public'; import { getId } from '../../../lib/get_id'; -import { Popover, ClosePopoverFn } from '../../popover'; +import { ClosePopoverFn } from '../../popover'; import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib'; import { ElementSpec } from '../../../../types'; import { flattenPanelTree } from '../../../lib/flatten_panel_tree'; @@ -116,34 +112,23 @@ const categorizeElementsByType = (elements: ElementSpec[]): { [key: string]: Ele return categories; }; -export interface Props { +interface Props { /** * Dictionary of elements from elements registry */ - elements: { [key: string]: ElementSpec }; + elementsRegistry: { [key: string]: ElementSpec }; /** * Handler for adding a selected element to the workpad */ addElement: (element: ElementSpec) => void; - /** - * Renders embeddable flyout - */ - renderEmbedPanel: (onClose: () => void) => JSX.Element; } -export const ElementMenu: FunctionComponent = ({ - elements, - addElement, - renderEmbedPanel, -}) => { +export const ElementMenu: FunctionComponent = ({ elementsRegistry, addElement }) => { const [isAssetModalVisible, setAssetModalVisible] = useState(false); - const [isEmbedPanelVisible, setEmbedPanelVisible] = useState(false); const [isSavedElementsModalVisible, setSavedElementsModalVisible] = useState(false); const hideAssetModal = () => setAssetModalVisible(false); const showAssetModal = () => setAssetModalVisible(true); - const hideEmbedPanel = () => setEmbedPanelVisible(false); - const showEmbedPanel = () => setEmbedPanelVisible(true); const hideSavedElementsModal = () => setSavedElementsModalVisible(false); const showSavedElementsModal = () => setSavedElementsModalVisible(true); @@ -155,7 +140,7 @@ export const ElementMenu: FunctionComponent = ({ progress: progressElements, shape: shapeElements, text: textElements, - } = categorizeElementsByType(Object.values(elements)); + } = categorizeElementsByType(Object.values(elementsRegistry)); const getPanelTree = (closePopover: ClosePopoverFn) => { const elementToMenuItem = (element: ElementSpec): EuiContextMenuPanelItemDescriptor => ({ @@ -214,51 +199,31 @@ export const ElementMenu: FunctionComponent = ({ closePopover(); }, }, - { - name: strings.getEmbedObjectMenuItemLabel(), - className: CONTEXT_MENU_TOP_BORDER_CLASSNAME, - icon: , - onClick: () => { - showEmbedPanel(); - closePopover(); - }, - }, ], }; }; - const exportControl = (togglePopover: React.MouseEventHandler) => ( - - {strings.getElementMenuButtonLabel()} - - ); - return ( - - + <> + {({ closePopover }: { closePopover: ClosePopoverFn }) => ( )} - + {isAssetModalVisible ? : null} - {isEmbedPanelVisible ? renderEmbedPanel(hideEmbedPanel) : null} {isSavedElementsModalVisible ? : null} - + ); }; ElementMenu.propTypes = { - elements: PropTypes.object, + elementsRegistry: PropTypes.object, addElement: PropTypes.func.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.scss b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.scss deleted file mode 100644 index a946ee5519ce4..0000000000000 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.scss +++ /dev/null @@ -1,3 +0,0 @@ -.canvasElementMenu__popoverButton { - margin-right: $euiSizeS; -} \ No newline at end of file diff --git a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx index 5b5491a7c6454..d43d13a65a5d7 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/element_menu/element_menu.tsx @@ -5,44 +5,4 @@ * 2.0. */ -import React from 'react'; -import { connect } from 'react-redux'; -import { compose, withProps } from 'recompose'; -import { Dispatch } from 'redux'; -import { State, ElementSpec } from '../../../../types'; -// @ts-expect-error untyped local -import { elementsRegistry } from '../../../lib/elements_registry'; -import { ElementMenu as Component, Props as ComponentProps } from './element_menu.component'; -// @ts-expect-error untyped local -import { addElement } from '../../../state/actions/elements'; -import { getSelectedPage } from '../../../state/selectors/workpad'; -import { AddEmbeddablePanel } from '../../embeddable_flyout'; - -interface StateProps { - pageId: string; -} - -interface DispatchProps { - addElement: (pageId: string) => (partialElement: ElementSpec) => void; -} - -const mapStateToProps = (state: State) => ({ - pageId: getSelectedPage(state), -}); - -const mapDispatchToProps = (dispatch: Dispatch) => ({ - addElement: (pageId: string) => (element: ElementSpec) => dispatch(addElement(pageId, element)), -}); - -const mergeProps = (stateProps: StateProps, dispatchProps: DispatchProps) => ({ - ...stateProps, - ...dispatchProps, - addElement: dispatchProps.addElement(stateProps.pageId), - // Moved this section out of the main component to enable stories - renderEmbedPanel: (onClose: () => void) => , -}); - -export const ElementMenu = compose( - connect(mapStateToProps, mapDispatchToProps, mergeProps), - withProps(() => ({ elements: elementsRegistry.toJS() })) -)(Component); +export * from './element_menu.component'; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx index 5320a65a90408..5d6b822841920 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.component.tsx @@ -5,13 +5,19 @@ * 2.0. */ -import React, { FunctionComponent } from 'react'; +import React, { FC, useCallback, useState } from 'react'; import PropTypes from 'prop-types'; // @ts-expect-error no @types definition import { Shortcuts } from 'react-shortcuts'; import { EuiFlexItem, EuiFlexGroup, EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; - +import { + AddFromLibraryButton, + QuickButtonGroup, + SolutionToolbar, +} from '../../../../../../src/plugins/presentation_util/public'; +import { getElementStrings } from '../../../i18n'; +import { CommitFn, ElementSpec } from '../../../types'; import { ToolTipShortcut } from '../tool_tip_shortcut/'; import { RefreshControl } from './refresh_control'; // @ts-expect-error untyped local @@ -21,7 +27,6 @@ import { ElementMenu } from './element_menu'; import { ShareMenu } from './share_menu'; import { ViewMenu } from './view_menu'; import { LabsControl } from './labs_control'; -import { CommitFn } from '../../../types'; const strings = { getFullScreenButtonAriaLabel: () => @@ -46,19 +51,30 @@ const strings = { }), }; +const elementStrings = getElementStrings(); + export interface Props { isWriteable: boolean; canUserWrite: boolean; commit: CommitFn; onSetWriteable?: (writeable: boolean) => void; + renderEmbedPanel: (onClick: () => void) => JSX.Element; + elementsRegistry: { [key: string]: ElementSpec }; + addElement: (element: Partial) => void; } -export const WorkpadHeader: FunctionComponent = ({ +export const WorkpadHeader: FC = ({ isWriteable, canUserWrite, commit, onSetWriteable = () => {}, + renderEmbedPanel, + elementsRegistry, + addElement, }) => { + const [isEmbedPanelVisible, setEmbedPanelVisible] = useState(false); + const hideEmbedPanel = () => setEmbedPanelVisible(false); + const showEmbedPanel = () => setEmbedPanelVisible(true); const toggleWriteable = () => onSetWriteable(!isWriteable); const keyHandler = (action: string) => { @@ -111,65 +127,104 @@ export const WorkpadHeader: FunctionComponent = ({ ); }; + const createElement = useCallback( + (elementName: string) => () => { + const elementSpec = elementsRegistry[elementName]; + if (elementSpec) { + addElement(elementsRegistry[elementName]); + } + }, + [addElement, elementsRegistry] + ); + + const quickButtons = [ + { + iconType: 'visText', + createType: elementStrings.markdown.displayName, + onClick: createElement('markdown'), + }, + { + iconType: 'node', + createType: elementStrings.shape.displayName, + onClick: createElement('shape'), + }, + { + iconType: 'image', + createType: elementStrings.image.displayName, + onClick: createElement('image'), + }, + ]; + return ( - - - - {isWriteable && ( + <> + + + + {isWriteable && ( + + + {{ + primaryActionButton: ( + + ), + quickButtonGroup: , + addFromLibraryButton: , + }} + + + )} - + - )} - - - - - - - - - - - - - - - - - - {canUserWrite && ( - - )} - - - - - - - - - {fullscreenButton} - - - - + + + + + + + + + + + + + + + {canUserWrite && ( + + )} + + + + + + + + + {fullscreenButton} + + + + + {isEmbedPanelVisible ? renderEmbedPanel(hideEmbedPanel) : null} + ); }; @@ -178,4 +233,5 @@ WorkpadHeader.propTypes = { commit: PropTypes.func.isRequired, onSetWriteable: PropTypes.func, canUserWrite: PropTypes.bool, + renderEmbedPanel: PropTypes.func.isRequired, }; diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx index 0521df0f09196..a1830bf49fce0 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx +++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.tsx @@ -5,22 +5,62 @@ * 2.0. */ +import React from 'react'; import { connect } from 'react-redux'; import { Dispatch } from 'redux'; +import { Action } from 'redux-actions'; +// @ts-expect-error untyped local +import { elementsRegistry } from '../../lib/elements_registry'; import { canUserWrite } from '../../state/selectors/app'; import { getSelectedPage, isWriteable } from '../../state/selectors/workpad'; import { setWriteable } from '../../state/actions/workpad'; -import { State } from '../../../types'; -import { WorkpadHeader as Component } from './workpad_header.component'; +// @ts-expect-error untyped local +import { addElement } from '../../state/actions/elements'; +import { CommitFn, ElementSpec, State } from '../../../types'; +import { WorkpadHeader as Component, Props as ComponentProps } from './workpad_header.component'; +import { AddEmbeddablePanel } from '../embeddable_flyout'; -const mapStateToProps = (state: State) => ({ +interface Props { + commit: CommitFn; +} + +interface StateProps { + isWriteable: boolean; + canUserWrite: boolean; + selectedPage: string; + pageId: string; +} + +interface DispatchProps { + onSetWriteable: (isWorkpadWriteable: boolean) => Action; + addElement: (pageId: string) => (partialElement: Partial) => void; +} + +const mapStateToProps = (state: State): StateProps => ({ isWriteable: isWriteable(state) && canUserWrite(state), canUserWrite: canUserWrite(state), selectedPage: getSelectedPage(state), + pageId: getSelectedPage(state), }); -const mapDispatchToProps = (dispatch: Dispatch) => ({ +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ onSetWriteable: (isWorkpadWriteable: boolean) => dispatch(setWriteable(isWorkpadWriteable)), + addElement: (pageId: string) => (element: Partial) => + dispatch(addElement(pageId, element)), +}); + +const mergeProps = ( + stateProps: StateProps, + dispatchProps: DispatchProps, + ownProps: Props +): ComponentProps => ({ + ...stateProps, + ...dispatchProps, + ...ownProps, + // Moved this section out of the main component to enable stories + renderEmbedPanel: (onClose: () => void) => , + addElement: dispatchProps.addElement(stateProps.pageId), + elementsRegistry: elementsRegistry.toJS(), }); -export const WorkpadHeader = connect(mapStateToProps, mapDispatchToProps)(Component); +export const WorkpadHeader = connect(mapStateToProps, mapDispatchToProps, mergeProps)(Component); diff --git a/x-pack/plugins/canvas/public/style/index.scss b/x-pack/plugins/canvas/public/style/index.scss index 0860bfd5afe6a..e03405f6226c7 100644 --- a/x-pack/plugins/canvas/public/style/index.scss +++ b/x-pack/plugins/canvas/public/style/index.scss @@ -36,7 +36,6 @@ @import '../components/toolbar/toolbar'; @import '../components/toolbar/tray/tray'; @import '../components/workpad/workpad'; -@import '../components/workpad_header/element_menu/element_menu'; @import '../components/workpad_header/share_menu/share_menu'; @import '../components/workpad_header/view_menu/view_menu'; @import '../components/workpad_page/workpad_page'; diff --git a/x-pack/plugins/canvas/tsconfig.json b/x-pack/plugins/canvas/tsconfig.json index 5a5a1883240b7..a2e4225834ea8 100644 --- a/x-pack/plugins/canvas/tsconfig.json +++ b/x-pack/plugins/canvas/tsconfig.json @@ -42,6 +42,7 @@ { "path": "../../../src/plugins/kibana_legacy/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../../../src/plugins/presentation_util/tsconfig.json" }, { "path": "../../../src/plugins/saved_objects/tsconfig.json" }, { "path": "../../../src/plugins/ui_actions/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" },