[Canvas] Adds edit menu (#64738)
This commit is contained in:
parent
127b324a5f
commit
f9c1033d41
|
@ -804,17 +804,6 @@ export const ComponentStrings = {
|
|||
}),
|
||||
},
|
||||
SidebarHeader: {
|
||||
getAlignmentMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.alignmentMenuItemLabel', {
|
||||
defaultMessage: 'Alignment',
|
||||
description:
|
||||
'This refers to the vertical (i.e. left, center, right) and horizontal (i.e. top, middle, bottom) ' +
|
||||
'alignment options of the selected elements',
|
||||
}),
|
||||
getBottomAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.bottomAlignMenuItemLabel', {
|
||||
defaultMessage: 'Bottom',
|
||||
}),
|
||||
getBringForwardAriaLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.bringForwardArialLabel', {
|
||||
defaultMessage: 'Move element up one layer',
|
||||
|
@ -823,56 +812,6 @@ export const ComponentStrings = {
|
|||
i18n.translate('xpack.canvas.sidebarHeader.bringToFrontArialLabel', {
|
||||
defaultMessage: 'Move element to top layer',
|
||||
}),
|
||||
getCenterAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.centerAlignMenuItemLabel', {
|
||||
defaultMessage: 'Center',
|
||||
description: 'This refers to alignment centered horizontally.',
|
||||
}),
|
||||
getContextMenuTitle: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.contextMenuAriaLabel', {
|
||||
defaultMessage: 'Element options',
|
||||
}),
|
||||
getCreateElementModalTitle: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.createElementModalTitle', {
|
||||
defaultMessage: 'Create new element',
|
||||
}),
|
||||
getDistributionMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.distributionMenutItemLabel', {
|
||||
defaultMessage: 'Distribution',
|
||||
description:
|
||||
'This refers to the options to evenly spacing the selected elements horizontall or vertically.',
|
||||
}),
|
||||
getGroupMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.groupMenuItemLabel', {
|
||||
defaultMessage: 'Group',
|
||||
description: 'This refers to grouping multiple selected elements.',
|
||||
}),
|
||||
getHorizontalDistributionMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.horizontalDistributionMenutItemLabel', {
|
||||
defaultMessage: 'Horizontal',
|
||||
}),
|
||||
getLeftAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.leftAlignMenuItemLabel', {
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
getMiddleAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.middleAlignMenuItemLabel', {
|
||||
defaultMessage: 'Middle',
|
||||
description: 'This refers to alignment centered vertically.',
|
||||
}),
|
||||
getOrderMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.orderMenuItemLabel', {
|
||||
defaultMessage: 'Order',
|
||||
description: 'Refers to the order of the elements displayed on the page from front to back',
|
||||
}),
|
||||
getRightAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.rightAlignMenuItemLabel', {
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
getSaveElementMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.savedElementMenuItemLabel', {
|
||||
defaultMessage: 'Save as new element',
|
||||
}),
|
||||
getSendBackwardAriaLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.sendBackwardArialLabel', {
|
||||
defaultMessage: 'Move element down one layer',
|
||||
|
@ -881,19 +820,6 @@ export const ComponentStrings = {
|
|||
i18n.translate('xpack.canvas.sidebarHeader.sendToBackArialLabel', {
|
||||
defaultMessage: 'Move element to bottom layer',
|
||||
}),
|
||||
getTopAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.topAlignMenuItemLabel', {
|
||||
defaultMessage: 'Top',
|
||||
}),
|
||||
getUngroupMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.ungroupMenuItemLabel', {
|
||||
defaultMessage: 'Ungroup',
|
||||
description: 'This refers to ungrouping a grouped element',
|
||||
}),
|
||||
getVerticalDistributionMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel', {
|
||||
defaultMessage: 'Vertical',
|
||||
}),
|
||||
},
|
||||
TextStylePicker: {
|
||||
getAlignCenterOption: () =>
|
||||
|
@ -1099,6 +1025,94 @@ export const ComponentStrings = {
|
|||
defaultMessage: 'Set a custom interval',
|
||||
}),
|
||||
},
|
||||
WorkpadHeaderEditMenu: {
|
||||
getAlignmentMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel', {
|
||||
defaultMessage: 'Alignment',
|
||||
description:
|
||||
'This refers to the vertical (i.e. left, center, right) and horizontal (i.e. top, middle, bottom) ' +
|
||||
'alignment options of the selected elements',
|
||||
}),
|
||||
getBottomAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel', {
|
||||
defaultMessage: 'Bottom',
|
||||
}),
|
||||
getCenterAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel', {
|
||||
defaultMessage: 'Center',
|
||||
description: 'This refers to alignment centered horizontally.',
|
||||
}),
|
||||
getCreateElementModalTitle: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.createElementModalTitle', {
|
||||
defaultMessage: 'Create new element',
|
||||
}),
|
||||
getDistributionMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel', {
|
||||
defaultMessage: 'Distribution',
|
||||
description:
|
||||
'This refers to the options to evenly spacing the selected elements horizontall or vertically.',
|
||||
}),
|
||||
getEditMenuButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuButtonLabel', {
|
||||
defaultMessage: 'Edit',
|
||||
}),
|
||||
getEditMenuLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.editMenuLabel', {
|
||||
defaultMessage: 'Edit options',
|
||||
}),
|
||||
getGroupMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel', {
|
||||
defaultMessage: 'Group',
|
||||
description: 'This refers to grouping multiple selected elements.',
|
||||
}),
|
||||
getHorizontalDistributionMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel', {
|
||||
defaultMessage: 'Horizontal',
|
||||
}),
|
||||
getLeftAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel', {
|
||||
defaultMessage: 'Left',
|
||||
}),
|
||||
getMiddleAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel', {
|
||||
defaultMessage: 'Middle',
|
||||
description: 'This refers to alignment centered vertically.',
|
||||
}),
|
||||
getOrderMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel', {
|
||||
defaultMessage: 'Order',
|
||||
description: 'Refers to the order of the elements displayed on the page from front to back',
|
||||
}),
|
||||
getRedoMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.redoMenuItemLabel', {
|
||||
defaultMessage: 'Redo',
|
||||
}),
|
||||
getRightAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel', {
|
||||
defaultMessage: 'Right',
|
||||
}),
|
||||
getSaveElementMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel', {
|
||||
defaultMessage: 'Save as new element',
|
||||
}),
|
||||
getTopAlignMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.topAlignMenuItemLabel', {
|
||||
defaultMessage: 'Top',
|
||||
}),
|
||||
getUndoMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.undoMenuItemLabel', {
|
||||
defaultMessage: 'Undo',
|
||||
}),
|
||||
getUngroupMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.ungroupMenuItemLabel', {
|
||||
defaultMessage: 'Ungroup',
|
||||
description: 'This refers to ungrouping a grouped element',
|
||||
}),
|
||||
getVerticalDistributionMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderEditMenu.verticalDistributionMenutItemLabel', {
|
||||
defaultMessage: 'Vertical',
|
||||
}),
|
||||
},
|
||||
WorkpadHeaderElementMenu: {
|
||||
getAssetsMenuItemLabel: () =>
|
||||
i18n.translate('xpack.canvas.workpadHeaderElementMenu.manageAssetsMenuItemLabel', {
|
||||
|
|
|
@ -42,10 +42,10 @@ export const ShortcutStrings = {
|
|||
defaultMessage: 'Delete',
|
||||
}),
|
||||
BRING_FORWARD: i18n.translate('xpack.canvas.keyboardShortcuts.bringFowardShortcutHelpText', {
|
||||
defaultMessage: 'Bring to front',
|
||||
defaultMessage: 'Bring forward',
|
||||
}),
|
||||
BRING_TO_FRONT: i18n.translate('xpack.canvas.keyboardShortcuts.bringToFrontShortcutHelpText', {
|
||||
defaultMessage: 'Bring forward',
|
||||
defaultMessage: 'Bring to front',
|
||||
}),
|
||||
SEND_BACKWARD: i18n.translate('xpack.canvas.keyboardShortcuts.sendBackwardShortcutHelpText', {
|
||||
defaultMessage: 'Send backward',
|
||||
|
|
|
@ -43,7 +43,7 @@ export class WorkpadApp extends React.PureComponent {
|
|||
<div className="canvasLayout__cols">
|
||||
<div className="canvasLayout__stage">
|
||||
<div className="canvasLayout__stageHeader">
|
||||
<WorkpadHeader />
|
||||
<WorkpadHeader commit={this.interactivePageLayout || (() => {})} />
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -66,7 +66,7 @@ export class WorkpadApp extends React.PureComponent {
|
|||
|
||||
{isWriteable && (
|
||||
<div className="canvasLayout__sidebar hide-for-sharing">
|
||||
<Sidebar commit={this.interactivePageLayout || (() => {})} />
|
||||
<Sidebar />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -212,7 +212,7 @@ exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = `
|
|||
<dt
|
||||
className="euiDescriptionList__title"
|
||||
>
|
||||
Bring forward
|
||||
Bring to front
|
||||
</dt>
|
||||
<dd
|
||||
className="euiDescriptionList__description"
|
||||
|
@ -234,7 +234,7 @@ exports[`Storyshots components/KeyboardShortcutsDoc default 1`] = `
|
|||
<dt
|
||||
className="euiDescriptionList__title"
|
||||
>
|
||||
Bring to front
|
||||
Bring forward
|
||||
</dt>
|
||||
<dd
|
||||
className="euiDescriptionList__description"
|
||||
|
|
|
@ -10,7 +10,6 @@ import { compose, branch, renderComponent } from 'recompose';
|
|||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { getSelectedToplevelNodes, getSelectedElementId } from '../../state/selectors/workpad';
|
||||
import { SidebarHeader } from '../sidebar_header';
|
||||
import { globalStateUpdater } from '../workpad_page/integration_utils';
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
import { MultiElementSettings } from './multi_element_settings';
|
||||
import { GroupSettings } from './group_settings';
|
||||
|
@ -22,45 +21,19 @@ const { SidebarContent: strings } = ComponentStrings;
|
|||
const mapStateToProps = state => ({
|
||||
selectedToplevelNodes: getSelectedToplevelNodes(state),
|
||||
selectedElementId: getSelectedElementId(state),
|
||||
state,
|
||||
});
|
||||
|
||||
const mergeProps = (
|
||||
{ state, ...restStateProps },
|
||||
{ dispatch, ...restDispatchProps },
|
||||
ownProps
|
||||
) => ({
|
||||
...ownProps,
|
||||
...restDispatchProps,
|
||||
...restStateProps,
|
||||
updateGlobalState: globalStateUpdater(dispatch, state),
|
||||
});
|
||||
|
||||
const withGlobalState = (commit, updateGlobalState) => (type, payload) => {
|
||||
const newLayoutState = commit(type, payload);
|
||||
if (newLayoutState.currentScene.gestureEnd) {
|
||||
updateGlobalState(newLayoutState);
|
||||
}
|
||||
};
|
||||
|
||||
const MultiElementSidebar = ({ commit, updateGlobalState }) => (
|
||||
const MultiElementSidebar = () => (
|
||||
<Fragment>
|
||||
<SidebarHeader
|
||||
title={strings.getMultiElementSidebarTitle()}
|
||||
commit={withGlobalState(commit, updateGlobalState)}
|
||||
/>
|
||||
<SidebarHeader title={strings.getMultiElementSidebarTitle()} />
|
||||
<EuiSpacer />
|
||||
<MultiElementSettings />
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const GroupedElementSidebar = ({ commit, updateGlobalState }) => (
|
||||
const GroupedElementSidebar = () => (
|
||||
<Fragment>
|
||||
<SidebarHeader
|
||||
title={strings.getGroupedElementSidebarTitle()}
|
||||
commit={withGlobalState(commit, updateGlobalState)}
|
||||
groupIsSelected
|
||||
/>
|
||||
<SidebarHeader title={strings.getGroupedElementSidebarTitle()} groupIsSelected />
|
||||
<EuiSpacer />
|
||||
<GroupSettings />
|
||||
</Fragment>
|
||||
|
@ -92,7 +65,4 @@ const branches = [
|
|||
),
|
||||
];
|
||||
|
||||
export const SidebarContent = compose(
|
||||
connect(mapStateToProps, null, mergeProps),
|
||||
...branches
|
||||
)(GlobalConfig);
|
||||
export const SidebarContent = compose(connect(mapStateToProps), ...branches)(GlobalConfig);
|
||||
|
|
|
@ -20,80 +20,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader default 1`] = `
|
|||
Selected layer
|
||||
</h3>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-label="Save as new element"
|
||||
className="euiButtonIcon euiButtonIcon--text"
|
||||
data-test-subj="canvasSidebarHeader__saveElementButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
data-euiicon-type="indexOpen"
|
||||
size="m"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownCenter canvasContextMenu"
|
||||
container={null}
|
||||
id="sidebar-context-menu-popover"
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-label="Element options"
|
||||
className="euiButtonIcon euiButtonIcon--text"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
data-euiicon-type="boxesVertical"
|
||||
size="m"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -224,72 +150,6 @@ exports[`Storyshots components/Sidebar/SidebarHeader with layer controls 1`] = `
|
|||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-label="Save as new element"
|
||||
className="euiButtonIcon euiButtonIcon--text"
|
||||
data-test-subj="canvasSidebarHeader__saveElementButton"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
data-euiicon-type="indexOpen"
|
||||
size="m"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="euiFlexItem euiFlexItem--flexGrowZero"
|
||||
>
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownCenter canvasContextMenu"
|
||||
container={null}
|
||||
id="sidebar-context-menu-popover"
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<span
|
||||
className="euiToolTipAnchor"
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
>
|
||||
<button
|
||||
aria-label="Element options"
|
||||
className="euiButtonIcon euiButtonIcon--text"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="euiButtonIcon__icon"
|
||||
data-euiicon-type="boxesVertical"
|
||||
size="m"
|
||||
/>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,26 +10,10 @@ import { action } from '@storybook/addon-actions';
|
|||
import { SidebarHeader } from '../sidebar_header';
|
||||
|
||||
const handlers = {
|
||||
cloneNodes: action('cloneNodes'),
|
||||
copyNodes: action('copyNodes'),
|
||||
cutNodes: action('cutNodes'),
|
||||
pasteNodes: action('pasteNodes'),
|
||||
deleteNodes: action('deleteNodes'),
|
||||
bringToFront: action('bringToFront'),
|
||||
bringForward: action('bringForward'),
|
||||
sendBackward: action('sendBackward'),
|
||||
sendToBack: action('sendToBack'),
|
||||
createCustomElement: action('createCustomElement'),
|
||||
groupNodes: action('groupNodes'),
|
||||
ungroupNodes: action('ungroupNodes'),
|
||||
alignLeft: action('alignLeft'),
|
||||
alignMiddle: action('alignMiddle'),
|
||||
alignRight: action('alignRight'),
|
||||
alignTop: action('alignTop'),
|
||||
alignCenter: action('alignCenter'),
|
||||
alignBottom: action('alignBottom'),
|
||||
distributeHorizontally: action('distributeHorizontally'),
|
||||
distributeVertically: action('distributeVertically'),
|
||||
};
|
||||
|
||||
storiesOf('components/Sidebar/SidebarHeader', module)
|
||||
|
|
|
@ -4,25 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiTitle,
|
||||
EuiButtonIcon,
|
||||
EuiContextMenu,
|
||||
EuiToolTip,
|
||||
EuiContextMenuPanelItemDescriptor,
|
||||
EuiContextMenuPanelDescriptor,
|
||||
EuiOverlayMask,
|
||||
} from '@elastic/eui';
|
||||
import { Popover } from '../popover';
|
||||
import { CustomElementModal } from '../custom_element_modal';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { ToolTipShortcut } from '../tool_tip_shortcut/';
|
||||
import { ComponentStrings } from '../../../i18n/components';
|
||||
import { ShortcutStrings } from '../../../i18n/shortcuts';
|
||||
import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../common/lib/constants';
|
||||
|
||||
const { SidebarHeader: strings } = ComponentStrings;
|
||||
const shortcutHelp = ShortcutStrings.getShortcutHelp();
|
||||
|
@ -36,26 +23,6 @@ interface Props {
|
|||
* indicated whether or not layer controls should be displayed
|
||||
*/
|
||||
showLayerControls?: boolean;
|
||||
/**
|
||||
* cuts selected elements
|
||||
*/
|
||||
cutNodes: () => void;
|
||||
/**
|
||||
* copies selected elements to clipboard
|
||||
*/
|
||||
copyNodes: () => void;
|
||||
/**
|
||||
* pastes elements stored in clipboard to page
|
||||
*/
|
||||
pasteNodes: () => void;
|
||||
/**
|
||||
* clones selected elements
|
||||
*/
|
||||
cloneNodes: () => void;
|
||||
/**
|
||||
* deletes selected elements
|
||||
*/
|
||||
deleteNodes: () => void;
|
||||
/**
|
||||
* moves selected element to top layer
|
||||
*/
|
||||
|
@ -72,493 +39,117 @@ interface Props {
|
|||
* moves selected element to bottom layer
|
||||
*/
|
||||
sendToBack: () => void;
|
||||
/**
|
||||
* saves the selected elements as an custom-element saved object
|
||||
*/
|
||||
createCustomElement: (name: string, description: string, image: string) => void;
|
||||
/**
|
||||
* indicated whether the selected element is a group or not
|
||||
*/
|
||||
groupIsSelected: boolean;
|
||||
/**
|
||||
* only more than one selected element can be grouped
|
||||
*/
|
||||
selectedNodes: string[];
|
||||
/**
|
||||
* groups selected elements
|
||||
*/
|
||||
groupNodes: () => void;
|
||||
/**
|
||||
* ungroups selected group
|
||||
*/
|
||||
ungroupNodes: () => void;
|
||||
/**
|
||||
* left align selected elements
|
||||
*/
|
||||
alignLeft: () => void;
|
||||
/**
|
||||
* center align selected elements
|
||||
*/
|
||||
alignCenter: () => void;
|
||||
/**
|
||||
* right align selected elements
|
||||
*/
|
||||
alignRight: () => void;
|
||||
/**
|
||||
* top align selected elements
|
||||
*/
|
||||
alignTop: () => void;
|
||||
/**
|
||||
* middle align selected elements
|
||||
*/
|
||||
alignMiddle: () => void;
|
||||
/**
|
||||
* bottom align selected elements
|
||||
*/
|
||||
alignBottom: () => void;
|
||||
/**
|
||||
* horizontally distribute selected elements
|
||||
*/
|
||||
distributeHorizontally: () => void;
|
||||
/**
|
||||
* vertically distribute selected elements
|
||||
*/
|
||||
distributeVertically: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
/**
|
||||
* indicates whether or not the custom element modal is open
|
||||
*/
|
||||
isModalVisible: boolean;
|
||||
}
|
||||
|
||||
interface MenuTuple {
|
||||
menuItem: EuiContextMenuPanelItemDescriptor;
|
||||
panel: EuiContextMenuPanelDescriptor;
|
||||
}
|
||||
|
||||
const contextMenuButton = (handleClick: React.MouseEventHandler<HTMLButtonElement>) => (
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="boxesVertical"
|
||||
onClick={handleClick}
|
||||
aria-label={strings.getContextMenuTitle()}
|
||||
/>
|
||||
);
|
||||
|
||||
export class SidebarHeader extends Component<Props, State> {
|
||||
public static propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
showLayerControls: PropTypes.bool, // TODO: remove when we support relayering multiple elements
|
||||
cutNodes: PropTypes.func.isRequired,
|
||||
copyNodes: PropTypes.func.isRequired,
|
||||
pasteNodes: PropTypes.func.isRequired,
|
||||
cloneNodes: PropTypes.func.isRequired,
|
||||
deleteNodes: PropTypes.func.isRequired,
|
||||
bringToFront: PropTypes.func.isRequired,
|
||||
bringForward: PropTypes.func.isRequired,
|
||||
sendBackward: PropTypes.func.isRequired,
|
||||
sendToBack: PropTypes.func.isRequired,
|
||||
createCustomElement: PropTypes.func.isRequired,
|
||||
groupIsSelected: PropTypes.bool,
|
||||
selectedNodes: PropTypes.array,
|
||||
groupNodes: PropTypes.func.isRequired,
|
||||
ungroupNodes: PropTypes.func.isRequired,
|
||||
alignLeft: PropTypes.func.isRequired,
|
||||
alignCenter: PropTypes.func.isRequired,
|
||||
alignRight: PropTypes.func.isRequired,
|
||||
alignTop: PropTypes.func.isRequired,
|
||||
alignMiddle: PropTypes.func.isRequired,
|
||||
alignBottom: PropTypes.func.isRequired,
|
||||
distributeHorizontally: PropTypes.func.isRequired,
|
||||
distributeVertically: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
public static defaultProps = {
|
||||
groupIsSelected: false,
|
||||
showLayerControls: false,
|
||||
selectedNodes: [],
|
||||
};
|
||||
|
||||
public state = {
|
||||
isModalVisible: false,
|
||||
};
|
||||
|
||||
private _isMounted = false;
|
||||
private _showModal = () => this._isMounted && this.setState({ isModalVisible: true });
|
||||
private _hideModal = () => this._isMounted && this.setState({ isModalVisible: false });
|
||||
|
||||
public componentDidMount() {
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
private _renderLayoutControls = () => {
|
||||
const { bringToFront, bringForward, sendBackward, sendToBack } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.BRING_TO_FRONT}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="BRING_TO_FRONT" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="sortUp"
|
||||
onClick={bringToFront}
|
||||
aria-label={strings.getBringToFrontAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.BRING_FORWARD}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="BRING_FORWARD" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="arrowUp"
|
||||
onClick={bringForward}
|
||||
aria-label={strings.getBringForwardAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.SEND_BACKWARD}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="SEND_BACKWARD" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="arrowDown"
|
||||
onClick={sendBackward}
|
||||
aria-label={strings.getSendBackwardAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.SEND_TO_BACK}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="SEND_TO_BACK" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="sortDown"
|
||||
onClick={sendToBack}
|
||||
aria-label={strings.getSendToBackAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
private _getLayerMenuItems = (): MenuTuple => {
|
||||
const { bringToFront, bringForward, sendBackward, sendToBack } = this.props;
|
||||
|
||||
return {
|
||||
menuItem: {
|
||||
name: strings.getOrderMenuItemLabel(),
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
panel: 1,
|
||||
},
|
||||
panel: {
|
||||
id: 1,
|
||||
title: strings.getOrderMenuItemLabel(),
|
||||
items: [
|
||||
{
|
||||
name: shortcutHelp.BRING_TO_FRONT, // TODO: check against current element position and disable if already top layer
|
||||
icon: 'sortUp',
|
||||
onClick: bringToFront,
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.BRING_TO_FRONT, // TODO: same as above
|
||||
icon: 'arrowUp',
|
||||
onClick: bringForward,
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.SEND_BACKWARD, // TODO: check against current element position and disable if already bottom layer
|
||||
icon: 'arrowDown',
|
||||
onClick: sendBackward,
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.SEND_TO_BACK, // TODO: same as above
|
||||
icon: 'sortDown',
|
||||
onClick: sendToBack,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
private _getAlignmentMenuItems = (close: (fn: () => void) => () => void): MenuTuple => {
|
||||
const { alignLeft, alignCenter, alignRight, alignTop, alignMiddle, alignBottom } = this.props;
|
||||
|
||||
return {
|
||||
menuItem: {
|
||||
name: strings.getAlignmentMenuItemLabel(),
|
||||
className: 'canvasContextMenu',
|
||||
panel: 2,
|
||||
},
|
||||
panel: {
|
||||
id: 2,
|
||||
title: strings.getAlignmentMenuItemLabel(),
|
||||
items: [
|
||||
{
|
||||
name: strings.getLeftAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignLeft',
|
||||
onClick: close(alignLeft),
|
||||
},
|
||||
{
|
||||
name: strings.getCenterAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignCenter',
|
||||
onClick: close(alignCenter),
|
||||
},
|
||||
{
|
||||
name: strings.getRightAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignRight',
|
||||
onClick: close(alignRight),
|
||||
},
|
||||
{
|
||||
name: strings.getTopAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignTop',
|
||||
onClick: close(alignTop),
|
||||
},
|
||||
{
|
||||
name: strings.getMiddleAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignMiddle',
|
||||
onClick: close(alignMiddle),
|
||||
},
|
||||
{
|
||||
name: strings.getBottomAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignBottom',
|
||||
onClick: close(alignBottom),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
private _getDistributionMenuItems = (close: (fn: () => void) => () => void): MenuTuple => {
|
||||
const { distributeHorizontally, distributeVertically } = this.props;
|
||||
|
||||
return {
|
||||
menuItem: {
|
||||
name: strings.getDistributionMenuItemLabel(),
|
||||
className: 'canvasContextMenu',
|
||||
panel: 3,
|
||||
},
|
||||
panel: {
|
||||
id: 3,
|
||||
title: strings.getDistributionMenuItemLabel(),
|
||||
items: [
|
||||
{
|
||||
name: strings.getHorizontalDistributionMenuItemLabel(),
|
||||
icon: 'editorDistributeHorizontal',
|
||||
onClick: close(distributeHorizontally),
|
||||
},
|
||||
{
|
||||
name: strings.getVerticalDistributionMenuItemLabel(),
|
||||
icon: 'editorDistributeVertical',
|
||||
onClick: close(distributeVertically),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
private _getGroupMenuItems = (
|
||||
close: (fn: () => void) => () => void
|
||||
): EuiContextMenuPanelItemDescriptor[] => {
|
||||
const { groupIsSelected, ungroupNodes, groupNodes, selectedNodes } = this.props;
|
||||
return groupIsSelected
|
||||
? [
|
||||
{
|
||||
name: strings.getUngroupMenuItemLabel(),
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
onClick: close(ungroupNodes),
|
||||
},
|
||||
]
|
||||
: selectedNodes.length > 1
|
||||
? [
|
||||
{
|
||||
name: strings.getGroupMenuItemLabel(),
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
onClick: close(groupNodes),
|
||||
},
|
||||
]
|
||||
: [];
|
||||
};
|
||||
|
||||
private _getPanels = (closePopover: () => void): EuiContextMenuPanelDescriptor[] => {
|
||||
const {
|
||||
showLayerControls,
|
||||
cutNodes,
|
||||
copyNodes,
|
||||
pasteNodes,
|
||||
deleteNodes,
|
||||
cloneNodes,
|
||||
} = this.props;
|
||||
|
||||
// closes popover after invoking fn
|
||||
const close = (fn: () => void) => () => {
|
||||
fn();
|
||||
closePopover();
|
||||
};
|
||||
|
||||
const items: EuiContextMenuPanelItemDescriptor[] = [
|
||||
{
|
||||
name: shortcutHelp.CUT,
|
||||
icon: 'cut',
|
||||
onClick: close(cutNodes),
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.COPY,
|
||||
icon: 'copy',
|
||||
onClick: copyNodes,
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.PASTE, // TODO: can this be disabled if clipboard is empty?
|
||||
icon: 'copyClipboard',
|
||||
onClick: close(pasteNodes),
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.DELETE,
|
||||
icon: 'trash',
|
||||
onClick: close(deleteNodes),
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.CLONE,
|
||||
onClick: close(cloneNodes),
|
||||
},
|
||||
...this._getGroupMenuItems(close),
|
||||
];
|
||||
|
||||
const panels: EuiContextMenuPanelDescriptor[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: strings.getContextMenuTitle(),
|
||||
items,
|
||||
},
|
||||
];
|
||||
|
||||
const fillMenu = ({ menuItem, panel }: MenuTuple) => {
|
||||
items.push(menuItem); // add Order menu item to first panel
|
||||
panels.push(panel); // add nested panel for layers controls
|
||||
};
|
||||
|
||||
if (showLayerControls) {
|
||||
fillMenu(this._getLayerMenuItems());
|
||||
}
|
||||
|
||||
if (this.props.selectedNodes.length > 1) {
|
||||
fillMenu(this._getAlignmentMenuItems(close));
|
||||
}
|
||||
|
||||
if (this.props.selectedNodes.length > 2) {
|
||||
fillMenu(this._getDistributionMenuItems(close));
|
||||
}
|
||||
|
||||
items.push({
|
||||
name: strings.getSaveElementMenuItemLabel(),
|
||||
icon: 'indexOpen',
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
onClick: this._showModal,
|
||||
});
|
||||
|
||||
return panels;
|
||||
};
|
||||
|
||||
private _renderContextMenu = () => (
|
||||
<Popover
|
||||
id="sidebar-context-menu-popover"
|
||||
className="canvasContextMenu"
|
||||
button={contextMenuButton}
|
||||
panelPaddingSize="none"
|
||||
tooltip={strings.getContextMenuTitle()}
|
||||
tooltipPosition="bottom"
|
||||
>
|
||||
{({ closePopover }: { closePopover: () => void }) => (
|
||||
<EuiContextMenu initialPanelId={0} panels={this._getPanels(closePopover)} />
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
|
||||
private _handleSave = (name: string, description: string, image: string) => {
|
||||
const { createCustomElement } = this.props;
|
||||
createCustomElement(name, description, image);
|
||||
this._hideModal();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { title, showLayerControls } = this.props;
|
||||
const { isModalVisible } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EuiFlexGroup
|
||||
className="canvasLayout__sidebarHeader"
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
export const SidebarHeader: FunctionComponent<Props> = ({
|
||||
title,
|
||||
showLayerControls,
|
||||
bringToFront,
|
||||
bringForward,
|
||||
sendBackward,
|
||||
sendToBack,
|
||||
}) => (
|
||||
<EuiFlexGroup
|
||||
className="canvasLayout__sidebarHeader"
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{showLayerControls ? (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle size="xs">
|
||||
<h3>{title}</h3>
|
||||
</EuiTitle>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.BRING_TO_FRONT}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="BRING_TO_FRONT" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="sortUp"
|
||||
onClick={bringToFront}
|
||||
aria-label={strings.getBringToFrontAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
{showLayerControls ? this._renderLayoutControls() : null}
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip position="bottom" content={strings.getSaveElementMenuItemLabel()}>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="indexOpen"
|
||||
onClick={this._showModal}
|
||||
data-test-subj="canvasSidebarHeader__saveElementButton"
|
||||
aria-label={strings.getSaveElementMenuItemLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>{this._renderContextMenu()}</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.BRING_FORWARD}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="BRING_FORWARD" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="arrowUp"
|
||||
onClick={bringForward}
|
||||
aria-label={strings.getBringForwardAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.SEND_BACKWARD}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="SEND_BACKWARD" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="arrowDown"
|
||||
onClick={sendBackward}
|
||||
aria-label={strings.getSendBackwardAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
position="bottom"
|
||||
content={
|
||||
<span>
|
||||
{shortcutHelp.SEND_TO_BACK}
|
||||
<ToolTipShortcut namespace="ELEMENT" action="SEND_TO_BACK" />
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
color="text"
|
||||
iconType="sortDown"
|
||||
onClick={sendToBack}
|
||||
aria-label={strings.getSendToBackAriaLabel()}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{isModalVisible ? (
|
||||
<EuiOverlayMask>
|
||||
<CustomElementModal
|
||||
title={strings.getCreateElementModalTitle()}
|
||||
onSave={this._handleSave}
|
||||
onCancel={this._hideModal}
|
||||
/>
|
||||
</EuiOverlayMask>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
</EuiFlexItem>
|
||||
) : null}
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
SidebarHeader.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
showLayerControls: PropTypes.bool, // TODO: remove when we support relayering multiple elements
|
||||
bringToFront: PropTypes.func.isRequired,
|
||||
bringForward: PropTypes.func.isRequired,
|
||||
sendBackward: PropTypes.func.isRequired,
|
||||
sendToBack: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
SidebarHeader.defaultProps = {
|
||||
showLayerControls: false,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Storyshots components/WorkpadHeader/EditMenu 2 elements selected 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownLeft"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit options"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
|
||||
data-test-subj="canvasWorkpadEditMenuButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Edit
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/WorkpadHeader/EditMenu 3+ elements selected 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownLeft"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit options"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
|
||||
data-test-subj="canvasWorkpadEditMenuButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Edit
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/WorkpadHeader/EditMenu clipboard data exists 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownLeft"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit options"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
|
||||
data-test-subj="canvasWorkpadEditMenuButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Edit
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/WorkpadHeader/EditMenu default 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownLeft"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit options"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
|
||||
data-test-subj="canvasWorkpadEditMenuButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Edit
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/WorkpadHeader/EditMenu single element selected 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownLeft"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit options"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
|
||||
data-test-subj="canvasWorkpadEditMenuButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Edit
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots components/WorkpadHeader/EditMenu single grouped element selected 1`] = `
|
||||
<div
|
||||
className="euiPopover euiPopover--anchorDownLeft"
|
||||
container={null}
|
||||
onKeyDown={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseUp={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
onTouchStart={[Function]}
|
||||
>
|
||||
<div
|
||||
className="euiPopover__anchor"
|
||||
>
|
||||
<button
|
||||
aria-label="Edit options"
|
||||
className="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--xSmall"
|
||||
data-test-subj="canvasWorkpadEditMenuButton"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__content"
|
||||
>
|
||||
<span
|
||||
className="euiButtonEmpty__text"
|
||||
>
|
||||
Edit
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { storiesOf } from '@storybook/react';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import React from 'react';
|
||||
import { EditMenu } from '../edit_menu';
|
||||
|
||||
const handlers = {
|
||||
cutNodes: action('cutNodes'),
|
||||
copyNodes: action('copyNodes'),
|
||||
pasteNodes: action('pasteNodes'),
|
||||
deleteNodes: action('deleteNodes'),
|
||||
cloneNodes: action('cloneNodes'),
|
||||
bringToFront: action('bringToFront'),
|
||||
bringForward: action('bringForward'),
|
||||
sendBackward: action('sendBackward'),
|
||||
sendToBack: action('sendToBack'),
|
||||
alignLeft: action('alignLeft'),
|
||||
alignCenter: action('alignCenter'),
|
||||
alignRight: action('alignRight'),
|
||||
alignTop: action('alignTop'),
|
||||
alignMiddle: action('alignMiddle'),
|
||||
alignBottom: action('alignBottom'),
|
||||
distributeHorizontally: action('distributeHorizontally'),
|
||||
distributeVertically: action('distributeVertically'),
|
||||
createCustomElement: action('createCustomElement'),
|
||||
groupNodes: action('groupNodes'),
|
||||
ungroupNodes: action('ungroupNodes'),
|
||||
undoHistory: action('undoHistory'),
|
||||
redoHistory: action('redoHistory'),
|
||||
};
|
||||
|
||||
storiesOf('components/WorkpadHeader/EditMenu', module)
|
||||
.add('default', () => (
|
||||
<EditMenu selectedNodes={[]} groupIsSelected={false} hasPasteData={false} {...handlers} />
|
||||
))
|
||||
.add('clipboard data exists', () => (
|
||||
<EditMenu selectedNodes={[]} groupIsSelected={false} hasPasteData={true} {...handlers} />
|
||||
))
|
||||
.add('single element selected', () => (
|
||||
<EditMenu selectedNodes={['foo']} groupIsSelected={false} hasPasteData={false} {...handlers} />
|
||||
))
|
||||
.add('single grouped element selected', () => (
|
||||
<EditMenu
|
||||
selectedNodes={['foo', 'bar']}
|
||||
groupIsSelected={true}
|
||||
hasPasteData={false}
|
||||
{...handlers}
|
||||
/>
|
||||
))
|
||||
.add('2 elements selected', () => (
|
||||
<EditMenu
|
||||
selectedNodes={['foo', 'bar']}
|
||||
groupIsSelected={false}
|
||||
hasPasteData={false}
|
||||
{...handlers}
|
||||
/>
|
||||
))
|
||||
.add('3+ elements selected', () => (
|
||||
<EditMenu
|
||||
selectedNodes={['foo', 'bar', 'fizz']}
|
||||
groupIsSelected={false}
|
||||
hasPasteData={false}
|
||||
{...handlers}
|
||||
/>
|
||||
));
|
|
@ -0,0 +1,448 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment, FunctionComponent, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiButtonEmpty, EuiContextMenu, EuiIcon, EuiOverlayMask } from '@elastic/eui';
|
||||
import { ComponentStrings } from '../../../../i18n/components';
|
||||
import { ShortcutStrings } from '../../../../i18n/shortcuts';
|
||||
import { flattenPanelTree } from '../../../lib/flatten_panel_tree';
|
||||
import { Popover, ClosePopoverFn } from '../../popover';
|
||||
import { CustomElementModal } from '../../custom_element_modal';
|
||||
import { CONTEXT_MENU_TOP_BORDER_CLASSNAME } from '../../../../common/lib/constants';
|
||||
|
||||
const { WorkpadHeaderEditMenu: strings } = ComponentStrings;
|
||||
const shortcutHelp = ShortcutStrings.getShortcutHelp();
|
||||
|
||||
export interface Props {
|
||||
/**
|
||||
* cuts selected elements
|
||||
*/
|
||||
cutNodes: () => void;
|
||||
/**
|
||||
* copies selected elements to clipboard
|
||||
*/
|
||||
copyNodes: () => void;
|
||||
/**
|
||||
* pastes elements stored in clipboard to page
|
||||
*/
|
||||
pasteNodes: () => void;
|
||||
/**
|
||||
* clones selected elements
|
||||
*/
|
||||
cloneNodes: () => void;
|
||||
/**
|
||||
* deletes selected elements
|
||||
*/
|
||||
deleteNodes: () => void;
|
||||
/**
|
||||
* moves selected element to top layer
|
||||
*/
|
||||
bringToFront: () => void;
|
||||
/**
|
||||
* moves selected element up one layer
|
||||
*/
|
||||
bringForward: () => void;
|
||||
/**
|
||||
* moves selected element down one layer
|
||||
*/
|
||||
sendBackward: () => void;
|
||||
/**
|
||||
* moves selected element to bottom layer
|
||||
*/
|
||||
sendToBack: () => void;
|
||||
/**
|
||||
* saves the selected elements as an custom-element saved object
|
||||
*/
|
||||
createCustomElement: (name: string, description: string, image: string) => void;
|
||||
/**
|
||||
* indicated whether the selected element is a group or not
|
||||
*/
|
||||
groupIsSelected: boolean;
|
||||
/**
|
||||
* only more than one selected element can be grouped
|
||||
*/
|
||||
selectedNodes: string[];
|
||||
/**
|
||||
* groups selected elements
|
||||
*/
|
||||
groupNodes: () => void;
|
||||
/**
|
||||
* ungroups selected group
|
||||
*/
|
||||
ungroupNodes: () => void;
|
||||
/**
|
||||
* left align selected elements
|
||||
*/
|
||||
alignLeft: () => void;
|
||||
/**
|
||||
* center align selected elements
|
||||
*/
|
||||
alignCenter: () => void;
|
||||
/**
|
||||
* right align selected elements
|
||||
*/
|
||||
alignRight: () => void;
|
||||
/**
|
||||
* top align selected elements
|
||||
*/
|
||||
alignTop: () => void;
|
||||
/**
|
||||
* middle align selected elements
|
||||
*/
|
||||
alignMiddle: () => void;
|
||||
/**
|
||||
* bottom align selected elements
|
||||
*/
|
||||
alignBottom: () => void;
|
||||
/**
|
||||
* horizontally distribute selected elements
|
||||
*/
|
||||
distributeHorizontally: () => void;
|
||||
/**
|
||||
* vertically distribute selected elements
|
||||
*/
|
||||
distributeVertically: () => void;
|
||||
/**
|
||||
* Reverts last change to the workpad
|
||||
*/
|
||||
undoHistory: () => void;
|
||||
/**
|
||||
* Reapplies last reverted change to the workpad
|
||||
*/
|
||||
redoHistory: () => void;
|
||||
/**
|
||||
* Is there element clipboard data to paste?
|
||||
*/
|
||||
hasPasteData: boolean;
|
||||
}
|
||||
|
||||
export const EditMenu: FunctionComponent<Props> = ({
|
||||
cutNodes,
|
||||
copyNodes,
|
||||
pasteNodes,
|
||||
deleteNodes,
|
||||
cloneNodes,
|
||||
bringToFront,
|
||||
bringForward,
|
||||
sendBackward,
|
||||
sendToBack,
|
||||
alignLeft,
|
||||
alignCenter,
|
||||
alignRight,
|
||||
alignTop,
|
||||
alignMiddle,
|
||||
alignBottom,
|
||||
distributeHorizontally,
|
||||
distributeVertically,
|
||||
createCustomElement,
|
||||
selectedNodes,
|
||||
groupIsSelected,
|
||||
groupNodes,
|
||||
ungroupNodes,
|
||||
undoHistory,
|
||||
redoHistory,
|
||||
hasPasteData,
|
||||
}) => {
|
||||
const [isModalVisible, setModalVisible] = useState(false);
|
||||
const showModal = () => setModalVisible(true);
|
||||
const hideModal = () => setModalVisible(false);
|
||||
|
||||
const handleSave = (name: string, description: string, image: string) => {
|
||||
createCustomElement(name, description, image);
|
||||
hideModal();
|
||||
};
|
||||
|
||||
const editControl = (togglePopover: React.MouseEventHandler<any>) => (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
aria-label={strings.getEditMenuLabel()}
|
||||
onClick={togglePopover}
|
||||
data-test-subj="canvasWorkpadEditMenuButton"
|
||||
>
|
||||
{strings.getEditMenuButtonLabel()}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
const getPanelTree = (closePopover: ClosePopoverFn) => {
|
||||
const groupMenuItem = groupIsSelected
|
||||
? {
|
||||
name: strings.getUngroupMenuItemLabel(),
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
icon: <EuiIcon type="empty" size="m" />,
|
||||
onClick: () => {
|
||||
ungroupNodes();
|
||||
closePopover();
|
||||
},
|
||||
}
|
||||
: {
|
||||
name: strings.getGroupMenuItemLabel(),
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
icon: <EuiIcon type="empty" size="m" />,
|
||||
disabled: selectedNodes.length < 2,
|
||||
onClick: () => {
|
||||
groupNodes();
|
||||
closePopover();
|
||||
},
|
||||
};
|
||||
|
||||
const orderMenuItem = {
|
||||
name: strings.getOrderMenuItemLabel(),
|
||||
disabled: selectedNodes.length !== 1, // TODO: change to === 0 when we support relayering multiple elements
|
||||
icon: <EuiIcon type="empty" size="m" />,
|
||||
panel: {
|
||||
id: 1,
|
||||
title: strings.getOrderMenuItemLabel(),
|
||||
items: [
|
||||
{
|
||||
name: shortcutHelp.BRING_TO_FRONT, // TODO: check against current element position and disable if already top layer
|
||||
icon: 'sortUp',
|
||||
onClick: bringToFront,
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.BRING_FORWARD, // TODO: same as above
|
||||
icon: 'arrowUp',
|
||||
onClick: bringForward,
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.SEND_BACKWARD, // TODO: check against current element position and disable if already bottom layer
|
||||
icon: 'arrowDown',
|
||||
onClick: sendBackward,
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.SEND_TO_BACK, // TODO: same as above
|
||||
icon: 'sortDown',
|
||||
onClick: sendToBack,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const alignmentMenuItem = {
|
||||
name: strings.getAlignmentMenuItemLabel(),
|
||||
className: 'canvasContextMenu',
|
||||
disabled: groupIsSelected || selectedNodes.length < 2,
|
||||
icon: <EuiIcon type="empty" size="m" />,
|
||||
panel: {
|
||||
id: 2,
|
||||
title: strings.getAlignmentMenuItemLabel(),
|
||||
items: [
|
||||
{
|
||||
name: strings.getLeftAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignLeft',
|
||||
onClick: () => {
|
||||
alignLeft();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: strings.getCenterAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignCenter',
|
||||
onClick: () => {
|
||||
alignCenter();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: strings.getRightAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignRight',
|
||||
onClick: () => {
|
||||
alignRight();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: strings.getTopAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignTop',
|
||||
onClick: () => {
|
||||
alignTop();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: strings.getMiddleAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignMiddle',
|
||||
onClick: () => {
|
||||
alignMiddle();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: strings.getBottomAlignMenuItemLabel(),
|
||||
icon: 'editorItemAlignBottom',
|
||||
onClick: () => {
|
||||
alignBottom();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const distributionMenuItem = {
|
||||
name: strings.getDistributionMenuItemLabel(),
|
||||
className: 'canvasContextMenu',
|
||||
disabled: groupIsSelected || selectedNodes.length < 3,
|
||||
icon: <EuiIcon type="empty" size="m" />,
|
||||
panel: {
|
||||
id: 3,
|
||||
title: strings.getAlignmentMenuItemLabel(),
|
||||
items: [
|
||||
{
|
||||
name: strings.getHorizontalDistributionMenuItemLabel(),
|
||||
icon: 'editorDistributeHorizontal',
|
||||
onClick: () => {
|
||||
distributeHorizontally();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: strings.getVerticalDistributionMenuItemLabel(),
|
||||
icon: 'editorDistributeVertical',
|
||||
onClick: () => {
|
||||
distributeVertically();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const savedElementMenuItem = {
|
||||
name: strings.getSaveElementMenuItemLabel(),
|
||||
icon: <EuiIcon type="indexOpen" size="m" />,
|
||||
disabled: selectedNodes.length < 1,
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
'data-test-subj': 'canvasWorkpadEditMenu__saveElementButton',
|
||||
onClick: () => {
|
||||
showModal();
|
||||
closePopover();
|
||||
},
|
||||
};
|
||||
|
||||
const items = [
|
||||
{
|
||||
// TODO: check history and disable when there are no more changes to revert
|
||||
name: strings.getUndoMenuItemLabel(),
|
||||
icon: <EuiIcon type="editorUndo" size="m" />,
|
||||
onClick: () => {
|
||||
undoHistory();
|
||||
},
|
||||
},
|
||||
{
|
||||
// TODO: check history and disable when there are no more changes to reapply
|
||||
name: strings.getRedoMenuItemLabel(),
|
||||
icon: <EuiIcon type="editorRedo" size="m" />,
|
||||
onClick: () => {
|
||||
redoHistory();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.CUT,
|
||||
icon: <EuiIcon type="cut" size="m" />,
|
||||
className: CONTEXT_MENU_TOP_BORDER_CLASSNAME,
|
||||
disabled: selectedNodes.length < 1,
|
||||
onClick: () => {
|
||||
cutNodes();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.COPY,
|
||||
disabled: selectedNodes.length < 1,
|
||||
icon: <EuiIcon type="copy" size="m" />,
|
||||
onClick: () => {
|
||||
copyNodes();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.PASTE, // TODO: can this be disabled if clipboard is empty?
|
||||
icon: <EuiIcon type="copyClipboard" size="m" />,
|
||||
disabled: !hasPasteData,
|
||||
onClick: () => {
|
||||
pasteNodes();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.DELETE,
|
||||
icon: <EuiIcon type="trash" size="m" />,
|
||||
disabled: selectedNodes.length < 1,
|
||||
onClick: () => {
|
||||
deleteNodes();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
{
|
||||
name: shortcutHelp.CLONE,
|
||||
icon: <EuiIcon type="empty" size="m" />,
|
||||
disabled: selectedNodes.length < 1,
|
||||
onClick: () => {
|
||||
cloneNodes();
|
||||
closePopover();
|
||||
},
|
||||
},
|
||||
groupMenuItem,
|
||||
orderMenuItem,
|
||||
alignmentMenuItem,
|
||||
distributionMenuItem,
|
||||
savedElementMenuItem,
|
||||
];
|
||||
|
||||
return {
|
||||
id: 0,
|
||||
// title: strings.getEditMenuLabel(),
|
||||
items,
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Popover button={editControl} panelPaddingSize="none" anchorPosition="downLeft">
|
||||
{({ closePopover }: { closePopover: ClosePopoverFn }) => (
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={flattenPanelTree(getPanelTree(closePopover))}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
{isModalVisible ? (
|
||||
<EuiOverlayMask>
|
||||
<CustomElementModal
|
||||
title={strings.getCreateElementModalTitle()}
|
||||
onSave={handleSave}
|
||||
onCancel={hideModal}
|
||||
/>
|
||||
</EuiOverlayMask>
|
||||
) : null}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
EditMenu.propTypes = {
|
||||
cutNodes: PropTypes.func.isRequired,
|
||||
copyNodes: PropTypes.func.isRequired,
|
||||
pasteNodes: PropTypes.func.isRequired,
|
||||
deleteNodes: PropTypes.func.isRequired,
|
||||
cloneNodes: PropTypes.func.isRequired,
|
||||
bringToFront: PropTypes.func.isRequired,
|
||||
bringForward: PropTypes.func.isRequired,
|
||||
sendBackward: PropTypes.func.isRequired,
|
||||
sendToBack: PropTypes.func.isRequired,
|
||||
alignLeft: PropTypes.func.isRequired,
|
||||
alignCenter: PropTypes.func.isRequired,
|
||||
alignRight: PropTypes.func.isRequired,
|
||||
alignTop: PropTypes.func.isRequired,
|
||||
alignMiddle: PropTypes.func.isRequired,
|
||||
alignBottom: PropTypes.func.isRequired,
|
||||
distributeHorizontally: PropTypes.func.isRequired,
|
||||
distributeVertically: PropTypes.func.isRequired,
|
||||
createCustomElement: PropTypes.func.isRequired,
|
||||
selectedNodes: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
groupIsSelected: PropTypes.bool.isRequired,
|
||||
groupNodes: PropTypes.func.isRequired,
|
||||
ungroupNodes: PropTypes.func.isRequired,
|
||||
};
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { compose, withHandlers, withProps } from 'recompose';
|
||||
import { Dispatch } from 'redux';
|
||||
import { State, PositionedElement } from '../../../../types';
|
||||
import { getClipboardData } from '../../../lib/clipboard';
|
||||
// @ts-ignore Untyped local
|
||||
import { flatten } from '../../../lib/aeroelastic/functional';
|
||||
// @ts-ignore Untyped local
|
||||
import { globalStateUpdater } from '../../workpad_page/integration_utils';
|
||||
// @ts-ignore Untyped local
|
||||
import { crawlTree } from '../../workpad_page/integration_utils';
|
||||
// @ts-ignore Untyped local
|
||||
import { insertNodes, elementLayer, removeElements } from '../../../state/actions/elements';
|
||||
// @ts-ignore Untyped local
|
||||
import { undoHistory, redoHistory } from '../../../state/actions/history';
|
||||
// @ts-ignore Untyped local
|
||||
import { selectToplevelNodes } from '../../../state/actions/transient';
|
||||
import {
|
||||
getSelectedPage,
|
||||
getNodes,
|
||||
getSelectedToplevelNodes,
|
||||
} from '../../../state/selectors/workpad';
|
||||
import {
|
||||
layerHandlerCreators,
|
||||
clipboardHandlerCreators,
|
||||
basicHandlerCreators,
|
||||
groupHandlerCreators,
|
||||
alignmentDistributionHandlerCreators,
|
||||
} from '../../../lib/element_handler_creators';
|
||||
import { EditMenu as Component, Props as ComponentProps } from './edit_menu';
|
||||
|
||||
type LayoutState = any;
|
||||
|
||||
type CommitFn = (type: string, payload: any) => LayoutState;
|
||||
|
||||
interface OwnProps {
|
||||
commit: CommitFn;
|
||||
}
|
||||
|
||||
const withGlobalState = (
|
||||
commit: CommitFn,
|
||||
updateGlobalState: (layoutState: LayoutState) => void
|
||||
) => (type: string, payload: any) => {
|
||||
const newLayoutState = commit(type, payload);
|
||||
if (newLayoutState.currentScene.gestureEnd) {
|
||||
updateGlobalState(newLayoutState);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO: this is all copied from interactive_workpad_page and workpad_shortcuts
|
||||
*/
|
||||
const mapStateToProps = (state: State) => {
|
||||
const pageId = getSelectedPage(state);
|
||||
const nodes = getNodes(state, pageId) as PositionedElement[];
|
||||
const selectedToplevelNodes = getSelectedToplevelNodes(state);
|
||||
const selectedPrimaryShapeObjects = selectedToplevelNodes
|
||||
.map((id: string) => nodes.find((s: PositionedElement) => s.id === id))
|
||||
.filter((shape?: PositionedElement) => shape) as PositionedElement[];
|
||||
const selectedPersistentPrimaryNodes = flatten(
|
||||
selectedPrimaryShapeObjects.map((shape: PositionedElement) =>
|
||||
nodes.find((n: PositionedElement) => n.id === shape.id) // is it a leaf or a persisted group?
|
||||
? [shape.id]
|
||||
: nodes.filter((s: PositionedElement) => s.position.parent === shape.id).map(s => s.id)
|
||||
)
|
||||
);
|
||||
const selectedNodeIds = flatten(selectedPersistentPrimaryNodes.map(crawlTree(nodes)));
|
||||
|
||||
return {
|
||||
pageId,
|
||||
selectedToplevelNodes,
|
||||
selectedNodes: selectedNodeIds.map((id: string) => nodes.find(s => s.id === id)),
|
||||
state,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
insertNodes: (selectedNodes: PositionedElement[], pageId: string) =>
|
||||
dispatch(insertNodes(selectedNodes, pageId)),
|
||||
removeNodes: (nodeIds: string[], pageId: string) => dispatch(removeElements(nodeIds, pageId)),
|
||||
selectToplevelNodes: (nodes: PositionedElement[]) =>
|
||||
dispatch(
|
||||
selectToplevelNodes(nodes.filter((e: PositionedElement) => !e.position.parent).map(e => e.id))
|
||||
),
|
||||
elementLayer: (pageId: string, elementId: string, movement: number) => {
|
||||
dispatch(elementLayer({ pageId, elementId, movement }));
|
||||
},
|
||||
undoHistory: () => dispatch(undoHistory()),
|
||||
redoHistory: () => dispatch(redoHistory()),
|
||||
dispatch,
|
||||
});
|
||||
|
||||
const mergeProps = (
|
||||
{ state, selectedToplevelNodes, ...restStateProps }: ReturnType<typeof mapStateToProps>,
|
||||
{ dispatch, ...restDispatchProps }: ReturnType<typeof mapDispatchToProps>,
|
||||
{ commit }: OwnProps
|
||||
) => {
|
||||
const updateGlobalState = globalStateUpdater(dispatch, state);
|
||||
|
||||
return {
|
||||
...restDispatchProps,
|
||||
...restStateProps,
|
||||
commit: withGlobalState(commit, updateGlobalState),
|
||||
groupIsSelected:
|
||||
selectedToplevelNodes.length === 1 && selectedToplevelNodes[0].includes('group'),
|
||||
};
|
||||
};
|
||||
|
||||
export const EditMenu = compose<ComponentProps, OwnProps>(
|
||||
connect(mapStateToProps, mapDispatchToProps, mergeProps),
|
||||
withProps(() => ({ hasPasteData: Boolean(getClipboardData()) })),
|
||||
withHandlers(basicHandlerCreators),
|
||||
withHandlers(clipboardHandlerCreators),
|
||||
withHandlers(layerHandlerCreators),
|
||||
withHandlers(groupHandlerCreators),
|
||||
withHandlers(alignmentDistributionHandlerCreators)
|
||||
)(Component);
|
|
@ -139,7 +139,6 @@ export const ElementMenu: FunctionComponent<Props> = ({
|
|||
|
||||
return {
|
||||
id: 0,
|
||||
title: strings.getElementMenuLabel(),
|
||||
items: [
|
||||
elementListToMenuItems(textElements),
|
||||
elementListToMenuItems(shapeElements),
|
||||
|
|
|
@ -62,7 +62,6 @@ export const ShareMenu: FunctionComponent<Props> = ({ onCopy, onExport, getExpor
|
|||
|
||||
const getPanelTree = (closePopover: ClosePopoverFn) => ({
|
||||
id: 0,
|
||||
title: strings.getShareWorkpadMessage(),
|
||||
items: [
|
||||
{
|
||||
name: strings.getShareDownloadJSONTitle(),
|
||||
|
|
|
@ -15,6 +15,7 @@ import { ToolTipShortcut } from '../tool_tip_shortcut/';
|
|||
import { RefreshControl } from './refresh_control';
|
||||
// @ts-ignore untyped local
|
||||
import { FullscreenControl } from './fullscreen_control';
|
||||
import { EditMenu } from './edit_menu';
|
||||
import { ElementMenu } from './element_menu';
|
||||
import { ShareMenu } from './share_menu';
|
||||
import { ViewMenu } from './view_menu';
|
||||
|
@ -25,12 +26,14 @@ export interface Props {
|
|||
isWriteable: boolean;
|
||||
toggleWriteable: () => void;
|
||||
canUserWrite: boolean;
|
||||
commit: (type: string, payload: any) => any;
|
||||
}
|
||||
|
||||
export const WorkpadHeader: FunctionComponent<Props> = ({
|
||||
isWriteable,
|
||||
canUserWrite,
|
||||
toggleWriteable,
|
||||
commit,
|
||||
}) => {
|
||||
const keyHandler = (action: string) => {
|
||||
if (action === 'EDITING') {
|
||||
|
@ -99,6 +102,9 @@ export const WorkpadHeader: FunctionComponent<Props> = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<ViewMenu />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EditMenu commit={commit} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ShareMenu />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -363,7 +363,11 @@ export function getNodesForPage(page: CanvasPage, withAst: boolean): CanvasEleme
|
|||
}
|
||||
|
||||
// todo unify or DRY up with `getElements`
|
||||
export function getNodes(state: State, pageId: string, withAst = true): CanvasElement[] {
|
||||
export function getNodes(
|
||||
state: State,
|
||||
pageId: string,
|
||||
withAst = true
|
||||
): CanvasElement[] | PositionedElement[] {
|
||||
const id = pageId || getSelectedPage(state);
|
||||
if (!id) {
|
||||
return [];
|
||||
|
|
|
@ -75,4 +75,8 @@ export interface ElementPosition {
|
|||
parent: string | null;
|
||||
}
|
||||
|
||||
export type PositionedElement = CanvasElement & { ast: ExpressionAstExpression };
|
||||
export type PositionedElement = CanvasElement & {
|
||||
ast: ExpressionAstExpression;
|
||||
} & {
|
||||
position: ElementPosition;
|
||||
};
|
||||
|
|
|
@ -5521,26 +5521,10 @@
|
|||
"xpack.canvas.sidebarContent.groupedElementSidebarTitle": "グループ化されたエレメント",
|
||||
"xpack.canvas.sidebarContent.multiElementSidebarTitle": "複数エレメント",
|
||||
"xpack.canvas.sidebarContent.singleElementSidebarTitle": "選択されたエレメント",
|
||||
"xpack.canvas.sidebarHeader.alignmentMenuItemLabel": "アラインメント",
|
||||
"xpack.canvas.sidebarHeader.bottomAlignMenuItemLabel": "一番下",
|
||||
"xpack.canvas.sidebarHeader.bringForwardArialLabel": "エレメントを 1 つ上のレイヤーに移動",
|
||||
"xpack.canvas.sidebarHeader.bringToFrontArialLabel": "エレメントを一番上のレイヤーに移動",
|
||||
"xpack.canvas.sidebarHeader.centerAlignMenuItemLabel": "中央",
|
||||
"xpack.canvas.sidebarHeader.contextMenuAriaLabel": "エレメントオプション",
|
||||
"xpack.canvas.sidebarHeader.createElementModalTitle": "新規エレメントの作成",
|
||||
"xpack.canvas.sidebarHeader.distributionMenutItemLabel": "分布",
|
||||
"xpack.canvas.sidebarHeader.groupMenuItemLabel": "グループ",
|
||||
"xpack.canvas.sidebarHeader.horizontalDistributionMenutItemLabel": "横",
|
||||
"xpack.canvas.sidebarHeader.leftAlignMenuItemLabel": "左",
|
||||
"xpack.canvas.sidebarHeader.middleAlignMenuItemLabel": "真ん中",
|
||||
"xpack.canvas.sidebarHeader.orderMenuItemLabel": "順序",
|
||||
"xpack.canvas.sidebarHeader.rightAlignMenuItemLabel": "右",
|
||||
"xpack.canvas.sidebarHeader.savedElementMenuItemLabel": "新規エレメントとして保存",
|
||||
"xpack.canvas.sidebarHeader.sendBackwardArialLabel": "エレメントを 1 つ下のレイヤーに移動",
|
||||
"xpack.canvas.sidebarHeader.sendToBackArialLabel": "エレメントを一番下のレイヤーに移動",
|
||||
"xpack.canvas.sidebarHeader.topAlignMenuItemLabel": "一番上",
|
||||
"xpack.canvas.sidebarHeader.ungroupMenuItemLabel": "グループ解除",
|
||||
"xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel": "縦",
|
||||
"xpack.canvas.tags.presentationTag": "プレゼンテーション",
|
||||
"xpack.canvas.tags.reportTag": "レポート",
|
||||
"xpack.canvas.templates.darkHelp": "ダークカラーテーマのプレゼンテーションデッキです",
|
||||
|
@ -5854,6 +5838,18 @@
|
|||
"xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel": "設定",
|
||||
"xpack.canvas.workpadHeaderCustomInterval.formDescription": "{secondsExample}、{minutesExample}、{hoursExample} のような短い表記を使用します",
|
||||
"xpack.canvas.workpadHeaderCustomInterval.formLabel": "カスタム間隔を設定",
|
||||
"xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel": "アラインメント",
|
||||
"xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel": "一番下",
|
||||
"xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel": "中央",
|
||||
"xpack.canvas.workpadHeaderEditMenu.createElementModalTitle": "新規エレメントの作成",
|
||||
"xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel": "分布",
|
||||
"xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel": "グループ",
|
||||
"xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel": "横",
|
||||
"xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel": "左",
|
||||
"xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel": "真ん中",
|
||||
"xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel": "順序",
|
||||
"xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel": "右",
|
||||
"xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel": "新規エレメントとして保存",
|
||||
"xpack.canvas.workpadHeaderKioskControl.controlTitle": "全画面ページのサイクル",
|
||||
"xpack.canvas.workpadHeaderKioskControl.cycleFormLabel": "サイクル間隔を変更",
|
||||
"xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "スライドを自動的にサイクル",
|
||||
|
|
|
@ -5522,26 +5522,10 @@
|
|||
"xpack.canvas.sidebarContent.groupedElementSidebarTitle": "已分组元素",
|
||||
"xpack.canvas.sidebarContent.multiElementSidebarTitle": "多个元素",
|
||||
"xpack.canvas.sidebarContent.singleElementSidebarTitle": "选定元素",
|
||||
"xpack.canvas.sidebarHeader.alignmentMenuItemLabel": "对齐方式",
|
||||
"xpack.canvas.sidebarHeader.bottomAlignMenuItemLabel": "下",
|
||||
"xpack.canvas.sidebarHeader.bringForwardArialLabel": "将元素上移一层",
|
||||
"xpack.canvas.sidebarHeader.bringToFrontArialLabel": "将元素移到顶层",
|
||||
"xpack.canvas.sidebarHeader.centerAlignMenuItemLabel": "中",
|
||||
"xpack.canvas.sidebarHeader.contextMenuAriaLabel": "元素选项",
|
||||
"xpack.canvas.sidebarHeader.createElementModalTitle": "创建新元素",
|
||||
"xpack.canvas.sidebarHeader.distributionMenutItemLabel": "分布",
|
||||
"xpack.canvas.sidebarHeader.groupMenuItemLabel": "分组",
|
||||
"xpack.canvas.sidebarHeader.horizontalDistributionMenutItemLabel": "水平",
|
||||
"xpack.canvas.sidebarHeader.leftAlignMenuItemLabel": "左",
|
||||
"xpack.canvas.sidebarHeader.middleAlignMenuItemLabel": "中",
|
||||
"xpack.canvas.sidebarHeader.orderMenuItemLabel": "顺序",
|
||||
"xpack.canvas.sidebarHeader.rightAlignMenuItemLabel": "右",
|
||||
"xpack.canvas.sidebarHeader.savedElementMenuItemLabel": "另存为新元素",
|
||||
"xpack.canvas.sidebarHeader.sendBackwardArialLabel": "将元素下移一层",
|
||||
"xpack.canvas.sidebarHeader.sendToBackArialLabel": "将元素移到底层",
|
||||
"xpack.canvas.sidebarHeader.topAlignMenuItemLabel": "上",
|
||||
"xpack.canvas.sidebarHeader.ungroupMenuItemLabel": "取消分组",
|
||||
"xpack.canvas.sidebarHeader.verticalDistributionMenutItemLabel": "垂直",
|
||||
"xpack.canvas.tags.presentationTag": "演示",
|
||||
"xpack.canvas.tags.reportTag": "报告",
|
||||
"xpack.canvas.templates.darkHelp": "深色主题的演示幻灯片",
|
||||
|
@ -5856,6 +5840,21 @@
|
|||
"xpack.canvas.workpadHeaderCustomInterval.confirmButtonLabel": "设置",
|
||||
"xpack.canvas.workpadHeaderCustomInterval.formDescription": "使用速记表示法,如 {secondsExample}、{minutesExample} 或 {hoursExample}",
|
||||
"xpack.canvas.workpadHeaderCustomInterval.formLabel": "设置定制时间间隔",
|
||||
"xpack.canvas.workpadHeaderEditMenu.alignmentMenuItemLabel": "对齐方式",
|
||||
"xpack.canvas.workpadHeaderEditMenu.bottomAlignMenuItemLabel": "下",
|
||||
"xpack.canvas.workpadHeaderEditMenu.centerAlignMenuItemLabel": "中",
|
||||
"xpack.canvas.workpadHeaderEditMenu.createElementModalTitle": "创建新元素",
|
||||
"xpack.canvas.workpadHeaderEditMenu.distributionMenutItemLabel": "分布",
|
||||
"xpack.canvas.workpadHeaderEditMenu.groupMenuItemLabel": "分组",
|
||||
"xpack.canvas.workpadHeaderEditMenu.horizontalDistributionMenutItemLabel": "水平",
|
||||
"xpack.canvas.workpadHeaderEditMenu.leftAlignMenuItemLabel": "左",
|
||||
"xpack.canvas.workpadHeaderEditMenu.middleAlignMenuItemLabel": "中",
|
||||
"xpack.canvas.workpadHeaderEditMenu.orderMenuItemLabel": "顺序",
|
||||
"xpack.canvas.workpadHeaderEditMenu.rightAlignMenuItemLabel": "右",
|
||||
"xpack.canvas.workpadHeaderEditMenu.savedElementMenuItemLabel": "另存为新元素",
|
||||
"xpack.canvas.workpadHeaderEditMenu.topAlignMenuItemLabel": "上",
|
||||
"xpack.canvas.workpadHeaderEditMenu.ungroupMenuItemLabel": "取消分组",
|
||||
"xpack.canvas.workpadHeaderEditMenu.verticalDistributionMenutItemLabel": "垂直",
|
||||
"xpack.canvas.workpadHeaderKioskControl.controlTitle": "循环播放全屏页面",
|
||||
"xpack.canvas.workpadHeaderKioskControl.cycleFormLabel": "更改循环播放时间间隔",
|
||||
"xpack.canvas.workpadHeaderKioskControl.cycleToggleSwitch": "自动循环播放幻灯片",
|
||||
|
|
|
@ -40,8 +40,11 @@ export default function canvasCustomElementTest({
|
|||
// find the first workpad element (a markdown element) and click it to select it
|
||||
await testSubjects.click('canvasWorkpadPage > canvasWorkpadPageElementContent', 20000);
|
||||
|
||||
// click "Edit" menu
|
||||
await testSubjects.click('canvasWorkpadEditMenuButton', 20000);
|
||||
|
||||
// click the "Save as new element" button
|
||||
await testSubjects.click('canvasSidebarHeader__saveElementButton', 20000);
|
||||
await testSubjects.click('canvasWorkpadEditMenu__saveElementButton', 20000);
|
||||
|
||||
// fill out the custom element form and submit it
|
||||
await PageObjects.canvas.fillOutCustomElementForm(
|
||||
|
|
Loading…
Reference in a new issue