mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[pre-req] Convert Page Manager, Page Preview, DOM Preview (#70370)
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Corey Robertson <corey.robertson@elastic.co>
This commit is contained in:
parent
4b06a4eb41
commit
eb71e599ce
15 changed files with 295 additions and 227 deletions
|
@ -567,6 +567,22 @@ export const ComponentStrings = {
|
|||
pageNumber,
|
||||
},
|
||||
}),
|
||||
getAddPageTooltip: () =>
|
||||
i18n.translate('xpack.canvas.pageManager.addPageTooltip', {
|
||||
defaultMessage: 'Add a new page to this workpad',
|
||||
}),
|
||||
getConfirmRemoveTitle: () =>
|
||||
i18n.translate('xpack.canvas.pageManager.confirmRemoveTitle', {
|
||||
defaultMessage: 'Remove Page',
|
||||
}),
|
||||
getConfirmRemoveDescription: () =>
|
||||
i18n.translate('xpack.canvas.pageManager.confirmRemoveDescription', {
|
||||
defaultMessage: 'Are you sure you want to remove this page?',
|
||||
}),
|
||||
getConfirmRemoveButtonLabel: () =>
|
||||
i18n.translate('xpack.canvas.pageManager.removeButtonLabel', {
|
||||
defaultMessage: 'Remove',
|
||||
}),
|
||||
},
|
||||
PagePreviewPageControls: {
|
||||
getClonePageAriaLabel: () =>
|
||||
|
|
|
@ -4,30 +4,38 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export class DomPreview extends React.Component {
|
||||
interface Props {
|
||||
elementId: string;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export class DomPreview extends PureComponent<Props> {
|
||||
static propTypes = {
|
||||
elementId: PropTypes.string.isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
_container: HTMLDivElement | null = null;
|
||||
_content: HTMLDivElement | null = null;
|
||||
_observer: MutationObserver | null = null;
|
||||
_original: Element | null = null;
|
||||
_updateTimeout: number = 0;
|
||||
|
||||
componentDidMount() {
|
||||
this.update();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this._updateTimeout);
|
||||
this._observer && this._observer.disconnect(); // observer not guaranteed to exist
|
||||
}
|
||||
|
||||
_container = null;
|
||||
_content = null;
|
||||
_observer = null;
|
||||
_original = null;
|
||||
_updateTimeout = null;
|
||||
if (this._observer) {
|
||||
this._observer.disconnect(); // observer not guaranteed to exist
|
||||
}
|
||||
}
|
||||
|
||||
update = () => {
|
||||
if (!this._content || !this._container) {
|
||||
|
@ -38,7 +46,10 @@ export class DomPreview extends React.Component {
|
|||
const originalChanged = currentOriginal !== this._original;
|
||||
|
||||
if (originalChanged) {
|
||||
this._observer && this._observer.disconnect();
|
||||
if (this._observer) {
|
||||
this._observer.disconnect();
|
||||
}
|
||||
|
||||
this._original = currentOriginal;
|
||||
|
||||
if (this._original) {
|
||||
|
@ -50,12 +61,16 @@ export class DomPreview extends React.Component {
|
|||
this._observer.observe(this._original, config);
|
||||
} else {
|
||||
clearTimeout(this._updateTimeout); // to avoid the assumption that we fully control when `update` is called
|
||||
this._updateTimeout = setTimeout(this.update, 30);
|
||||
this._updateTimeout = window.setTimeout(this.update, 30);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const thumb = this._original.cloneNode(true);
|
||||
if (!this._original) {
|
||||
return;
|
||||
}
|
||||
|
||||
const thumb = this._original.cloneNode(true) as HTMLDivElement;
|
||||
thumb.id += '-thumb';
|
||||
|
||||
const originalStyle = window.getComputedStyle(this._original, null);
|
||||
|
@ -66,9 +81,10 @@ export class DomPreview extends React.Component {
|
|||
const scale = thumbHeight / originalHeight;
|
||||
const thumbWidth = originalWidth * scale;
|
||||
|
||||
if (this._content.hasChildNodes()) {
|
||||
if (this._content.firstChild) {
|
||||
this._content.removeChild(this._content.firstChild);
|
||||
}
|
||||
|
||||
this._content.appendChild(thumb);
|
||||
|
||||
this._content.style.cssText = `transform: scale(${scale}); transform-origin: top left;`;
|
||||
|
@ -76,13 +92,16 @@ export class DomPreview extends React.Component {
|
|||
|
||||
// Copy canvas data
|
||||
const originalCanvas = this._original.querySelectorAll('canvas');
|
||||
const thumbCanvas = thumb.querySelectorAll('canvas');
|
||||
const thumbCanvas = (thumb as Element).querySelectorAll('canvas');
|
||||
|
||||
// Cloned canvas elements are blank and need to be explicitly redrawn
|
||||
if (originalCanvas.length > 0) {
|
||||
Array.from(originalCanvas).map((img, i) =>
|
||||
thumbCanvas[i].getContext('2d').drawImage(img, 0, 0)
|
||||
);
|
||||
Array.from(originalCanvas).map((img, i) => {
|
||||
const context = thumbCanvas[i].getContext('2d');
|
||||
if (context) {
|
||||
context.drawImage(img, 0, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
/*
|
||||
* 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 { DomPreview as Component } from './dom_preview';
|
||||
|
||||
export const DomPreview = Component;
|
|
@ -4,4 +4,4 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export { PagePreview } from './page_preview';
|
||||
export { DomPreview } from './dom_preview';
|
|
@ -1,63 +0,0 @@
|
|||
/*
|
||||
* 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 from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
|
||||
const { Link: strings } = ComponentStrings;
|
||||
|
||||
const isModifiedEvent = (ev) => !!(ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey);
|
||||
|
||||
export class Link extends React.PureComponent {
|
||||
static propTypes = {
|
||||
target: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
name: PropTypes.string.isRequired,
|
||||
params: PropTypes.object,
|
||||
children: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.node]).isRequired,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
navigateTo = (name, params) => (ev) => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(ev);
|
||||
}
|
||||
|
||||
if (
|
||||
!ev.defaultPrevented && // onClick prevented default
|
||||
ev.button === 0 && // ignore everything but left clicks
|
||||
!this.props.target && // let browser handle "target=_blank" etc.
|
||||
!isModifiedEvent(ev) // ignore clicks with modifier keys
|
||||
) {
|
||||
ev.preventDefault();
|
||||
this.context.router.navigateTo(name, params);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
try {
|
||||
const { name, params, children, ...linkArgs } = this.props;
|
||||
const { router } = this.context;
|
||||
const href = router.getFullPath(router.create(name, params));
|
||||
const props = {
|
||||
...linkArgs,
|
||||
href,
|
||||
onClick: this.navigateTo(name, params),
|
||||
};
|
||||
|
||||
return <EuiLink {...props}>{children}</EuiLink>;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return <div>{strings.getErrorMessage(e.message)}</div>;
|
||||
}
|
||||
}
|
||||
}
|
72
x-pack/plugins/canvas/public/components/link/link.tsx
Normal file
72
x-pack/plugins/canvas/public/components/link/link.tsx
Normal file
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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, { FC, MouseEvent, useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiLink, EuiLinkProps } from '@elastic/eui';
|
||||
import { RouterContext } from '../router';
|
||||
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
|
||||
const { Link: strings } = ComponentStrings;
|
||||
|
||||
const isModifiedEvent = (ev: MouseEvent) =>
|
||||
!!(ev.metaKey || ev.altKey || ev.ctrlKey || ev.shiftKey);
|
||||
|
||||
interface Props {
|
||||
name: string;
|
||||
params: Record<string, any>;
|
||||
}
|
||||
|
||||
export const Link: FC<Props & EuiLinkProps> = ({
|
||||
onClick,
|
||||
target,
|
||||
name,
|
||||
params,
|
||||
children,
|
||||
...linkArgs
|
||||
}) => {
|
||||
const router = useContext(RouterContext);
|
||||
|
||||
if (router) {
|
||||
const navigateTo = (ev: MouseEvent<HTMLButtonElement, globalThis.MouseEvent>) => {
|
||||
if (onClick) {
|
||||
onClick(ev);
|
||||
}
|
||||
|
||||
if (
|
||||
!ev.defaultPrevented && // onClick prevented default
|
||||
ev.button === 0 && // ignore everything but left clicks
|
||||
!target && // let browser handle "target=_blank" etc.
|
||||
!isModifiedEvent(ev) // ignore clicks with modifier keys
|
||||
) {
|
||||
ev.preventDefault();
|
||||
router.navigateTo(name, params);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
return (
|
||||
<EuiLink {...linkArgs} target={target} onClick={navigateTo}>
|
||||
{children}
|
||||
</EuiLink>
|
||||
);
|
||||
} catch (e) {
|
||||
return <div>{strings.getErrorMessage(e.message)}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
return <div>{strings.getErrorMessage('Router Undefined')}</div>;
|
||||
};
|
||||
|
||||
Link.contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
Link.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
params: PropTypes.object,
|
||||
};
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* 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, withState } from 'recompose';
|
||||
import * as pageActions from '../../state/actions/pages';
|
||||
import { canUserWrite } from '../../state/selectors/app';
|
||||
import { getSelectedPage, getWorkpad, getPages, isWriteable } from '../../state/selectors/workpad';
|
||||
import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants';
|
||||
import { PageManager as Component } from './page_manager';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { id, css } = getWorkpad(state);
|
||||
|
||||
return {
|
||||
isWriteable: isWriteable(state) && canUserWrite(state),
|
||||
pages: getPages(state),
|
||||
selectedPage: getSelectedPage(state),
|
||||
workpadId: id,
|
||||
workpadCSS: css || DEFAULT_WORKPAD_CSS,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
addPage: () => dispatch(pageActions.addPage()),
|
||||
movePage: (id, position) => dispatch(pageActions.movePage(id, position)),
|
||||
duplicatePage: (id) => dispatch(pageActions.duplicatePage(id)),
|
||||
removePage: (id) => dispatch(pageActions.removePage(id)),
|
||||
});
|
||||
|
||||
export const PageManager = compose(
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
withState('deleteId', 'setDeleteId', null)
|
||||
)(Component);
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { Dispatch } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
// @ts-expect-error untyped local
|
||||
import * as pageActions from '../../state/actions/pages';
|
||||
import { canUserWrite } from '../../state/selectors/app';
|
||||
import { getSelectedPage, getWorkpad, getPages, isWriteable } from '../../state/selectors/workpad';
|
||||
import { DEFAULT_WORKPAD_CSS } from '../../../common/lib/constants';
|
||||
import { PageManager as Component } from './page_manager';
|
||||
import { State } from '../../../types';
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
isWriteable: isWriteable(state) && canUserWrite(state),
|
||||
pages: getPages(state),
|
||||
selectedPage: getSelectedPage(state),
|
||||
workpadId: getWorkpad(state).id,
|
||||
workpadCSS: getWorkpad(state).css || DEFAULT_WORKPAD_CSS,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
onAddPage: () => dispatch(pageActions.addPage()),
|
||||
onMovePage: (id: string, position: number) => dispatch(pageActions.movePage(id, position)),
|
||||
onRemovePage: (id: string) => dispatch(pageActions.removePage(id)),
|
||||
});
|
||||
|
||||
export const PageManager = connect(mapStateToProps, mapDispatchToProps)(Component);
|
|
@ -4,38 +4,63 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiText, EuiToolTip } from '@elastic/eui';
|
||||
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
|
||||
import { DragDropContext, Droppable, Draggable, DragDropContextProps } from 'react-beautiful-dnd';
|
||||
// @ts-expect-error untyped dependency
|
||||
import Style from 'style-it';
|
||||
|
||||
import { ConfirmModal } from '../confirm_modal';
|
||||
import { Link } from '../link';
|
||||
import { PagePreview } from '../page_preview';
|
||||
|
||||
import { ComponentStrings } from '../../../i18n';
|
||||
import { CanvasPage } from '../../../types';
|
||||
|
||||
const { PageManager: strings } = ComponentStrings;
|
||||
|
||||
export class PageManager extends React.PureComponent {
|
||||
export interface Props {
|
||||
isWriteable: boolean;
|
||||
onAddPage: () => void;
|
||||
onMovePage: (pageId: string, position: number) => void;
|
||||
onPreviousPage: () => void;
|
||||
onRemovePage: (pageId: string) => void;
|
||||
pages: CanvasPage[];
|
||||
selectedPage?: string;
|
||||
workpadCSS?: string;
|
||||
workpadId: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
showTrayPop: boolean;
|
||||
removeId: string | null;
|
||||
}
|
||||
|
||||
export class PageManager extends Component<Props, State> {
|
||||
static propTypes = {
|
||||
isWriteable: PropTypes.bool.isRequired,
|
||||
onAddPage: PropTypes.func.isRequired,
|
||||
onMovePage: PropTypes.func.isRequired,
|
||||
onPreviousPage: PropTypes.func.isRequired,
|
||||
onRemovePage: PropTypes.func.isRequired,
|
||||
pages: PropTypes.array.isRequired,
|
||||
workpadId: PropTypes.string.isRequired,
|
||||
addPage: PropTypes.func.isRequired,
|
||||
movePage: PropTypes.func.isRequired,
|
||||
previousPage: PropTypes.func.isRequired,
|
||||
duplicatePage: PropTypes.func.isRequired,
|
||||
removePage: PropTypes.func.isRequired,
|
||||
selectedPage: PropTypes.string,
|
||||
deleteId: PropTypes.string,
|
||||
setDeleteId: PropTypes.func.isRequired,
|
||||
workpadCSS: PropTypes.string,
|
||||
workpadId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
showTrayPop: true,
|
||||
};
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showTrayPop: true,
|
||||
removeId: null,
|
||||
};
|
||||
}
|
||||
|
||||
_isMounted: boolean = false;
|
||||
_activePageRef: HTMLDivElement | null = null;
|
||||
_pageListRef: HTMLDivElement | null = null;
|
||||
|
||||
componentDidMount() {
|
||||
// keep track of whether or not the component is mounted, to prevent rogue setState calls
|
||||
|
@ -44,11 +69,13 @@ export class PageManager extends React.PureComponent {
|
|||
// gives the tray pop animation time to finish
|
||||
setTimeout(() => {
|
||||
this.scrollToActivePage();
|
||||
this._isMounted && this.setState({ showTrayPop: false });
|
||||
if (this._isMounted) {
|
||||
this.setState({ showTrayPop: false });
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
// scrolls to the active page on the next tick, otherwise new pages don't scroll completely into view
|
||||
if (prevProps.selectedPage !== this.props.selectedPage) {
|
||||
setTimeout(this.scrollToActivePage, 0);
|
||||
|
@ -60,33 +87,33 @@ export class PageManager extends React.PureComponent {
|
|||
}
|
||||
|
||||
scrollToActivePage = () => {
|
||||
if (this.activePageRef && this.pageListRef) {
|
||||
if (this._activePageRef && this._pageListRef) {
|
||||
// not all target browsers support element.scrollTo
|
||||
// TODO: replace this with something more cross-browser, maybe scrollIntoView
|
||||
if (!this.pageListRef.scrollTo) {
|
||||
if (!this._pageListRef.scrollTo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pageOffset = this.activePageRef.offsetLeft;
|
||||
const pageOffset = this._activePageRef.offsetLeft;
|
||||
const {
|
||||
left: pageLeft,
|
||||
right: pageRight,
|
||||
width: pageWidth,
|
||||
} = this.activePageRef.getBoundingClientRect();
|
||||
} = this._activePageRef.getBoundingClientRect();
|
||||
const {
|
||||
left: listLeft,
|
||||
right: listRight,
|
||||
width: listWidth,
|
||||
} = this.pageListRef.getBoundingClientRect();
|
||||
} = this._pageListRef.getBoundingClientRect();
|
||||
|
||||
if (pageLeft < listLeft) {
|
||||
this.pageListRef.scrollTo({
|
||||
this._pageListRef.scrollTo({
|
||||
left: pageOffset,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
if (pageRight > listRight) {
|
||||
this.pageListRef.scrollTo({
|
||||
this._pageListRef.scrollTo({
|
||||
left: pageOffset - listWidth + pageWidth,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
|
@ -94,22 +121,29 @@ export class PageManager extends React.PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
confirmDelete = (pageId) => {
|
||||
this._isMounted && this.props.setDeleteId(pageId);
|
||||
};
|
||||
|
||||
resetDelete = () => this._isMounted && this.props.setDeleteId(null);
|
||||
|
||||
doDelete = () => {
|
||||
const { previousPage, removePage, deleteId, selectedPage } = this.props;
|
||||
this.resetDelete();
|
||||
if (deleteId === selectedPage) {
|
||||
previousPage();
|
||||
onConfirmRemove = (removeId: string) => {
|
||||
if (this._isMounted) {
|
||||
this.setState({ removeId });
|
||||
}
|
||||
removePage(deleteId);
|
||||
};
|
||||
|
||||
onDragEnd = ({ draggableId: pageId, source, destination }) => {
|
||||
resetRemove = () => this._isMounted && this.setState({ removeId: null });
|
||||
|
||||
doRemove = () => {
|
||||
const { onPreviousPage, onRemovePage, selectedPage } = this.props;
|
||||
const { removeId } = this.state;
|
||||
this.resetRemove();
|
||||
|
||||
if (removeId === selectedPage) {
|
||||
onPreviousPage();
|
||||
}
|
||||
|
||||
if (removeId !== null) {
|
||||
onRemovePage(removeId);
|
||||
}
|
||||
};
|
||||
|
||||
onDragEnd: DragDropContextProps['onDragEnd'] = ({ draggableId: pageId, source, destination }) => {
|
||||
// dropped outside the list
|
||||
if (!destination) {
|
||||
return;
|
||||
|
@ -117,18 +151,11 @@ export class PageManager extends React.PureComponent {
|
|||
|
||||
const position = destination.index - source.index;
|
||||
|
||||
this.props.movePage(pageId, position);
|
||||
this.props.onMovePage(pageId, position);
|
||||
};
|
||||
|
||||
renderPage = (page, i) => {
|
||||
const {
|
||||
isWriteable,
|
||||
selectedPage,
|
||||
workpadId,
|
||||
movePage,
|
||||
duplicatePage,
|
||||
workpadCSS,
|
||||
} = this.props;
|
||||
renderPage = (page: CanvasPage, i: number) => {
|
||||
const { isWriteable, selectedPage, workpadId, workpadCSS } = this.props;
|
||||
const pageNumber = i + 1;
|
||||
|
||||
return (
|
||||
|
@ -141,7 +168,7 @@ export class PageManager extends React.PureComponent {
|
|||
}`}
|
||||
ref={(el) => {
|
||||
if (page.id === selectedPage) {
|
||||
this.activePageRef = el;
|
||||
this._activePageRef = el;
|
||||
}
|
||||
provided.innerRef(el);
|
||||
}}
|
||||
|
@ -163,16 +190,7 @@ export class PageManager extends React.PureComponent {
|
|||
{Style.it(
|
||||
workpadCSS,
|
||||
<div>
|
||||
<PagePreview
|
||||
isWriteable={isWriteable}
|
||||
page={page}
|
||||
height={100}
|
||||
pageNumber={pageNumber}
|
||||
movePage={movePage}
|
||||
selectedPage={selectedPage}
|
||||
duplicatePage={duplicatePage}
|
||||
confirmDelete={this.confirmDelete}
|
||||
/>
|
||||
<PagePreview height={100} page={page} onRemove={this.onConfirmRemove} />
|
||||
</div>
|
||||
)}
|
||||
</Link>
|
||||
|
@ -185,8 +203,8 @@ export class PageManager extends React.PureComponent {
|
|||
};
|
||||
|
||||
render() {
|
||||
const { pages, addPage, deleteId, isWriteable } = this.props;
|
||||
const { showTrayPop } = this.state;
|
||||
const { pages, onAddPage, isWriteable } = this.props;
|
||||
const { showTrayPop, removeId } = this.state;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
|
@ -200,7 +218,7 @@ export class PageManager extends React.PureComponent {
|
|||
showTrayPop ? 'canvasPageManager--trayPop' : ''
|
||||
}`}
|
||||
ref={(el) => {
|
||||
this.pageListRef = el;
|
||||
this._pageListRef = el;
|
||||
provided.innerRef(el);
|
||||
}}
|
||||
{...provided.droppableProps}
|
||||
|
@ -216,11 +234,11 @@ export class PageManager extends React.PureComponent {
|
|||
<EuiFlexItem grow={false}>
|
||||
<EuiToolTip
|
||||
anchorClassName="canvasPageManager__addPageTip"
|
||||
content="Add a new page to this workpad"
|
||||
content={strings.getAddPageTooltip()}
|
||||
position="left"
|
||||
>
|
||||
<button
|
||||
onClick={addPage}
|
||||
onClick={onAddPage}
|
||||
className="canvasPageManager__addPage kbn-resetFocusState"
|
||||
>
|
||||
<EuiIcon color="ghost" type="plusInCircle" size="l" />
|
||||
|
@ -230,12 +248,12 @@ export class PageManager extends React.PureComponent {
|
|||
)}
|
||||
</EuiFlexGroup>
|
||||
<ConfirmModal
|
||||
isOpen={deleteId != null}
|
||||
title="Remove Page"
|
||||
message="Are you sure you want to remove this page?"
|
||||
confirmButtonText="Remove"
|
||||
onConfirm={this.doDelete}
|
||||
onCancel={this.resetDelete}
|
||||
isOpen={removeId !== null}
|
||||
title={strings.getConfirmRemoveTitle()}
|
||||
message={strings.getConfirmRemoveDescription()}
|
||||
confirmButtonText={strings.getConfirmRemoveButtonLabel()}
|
||||
onConfirm={this.doRemove}
|
||||
onCancel={this.resetRemove}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { Dispatch } from 'redux';
|
||||
import { connect } from 'react-redux';
|
||||
// @ts-expect-error untyped local
|
||||
import * as pageActions from '../../state/actions/pages';
|
||||
import { canUserWrite } from '../../state/selectors/app';
|
||||
import { isWriteable } from '../../state/selectors/workpad';
|
||||
import { PagePreview as Component } from './page_preview';
|
||||
import { State } from '../../../types';
|
||||
|
||||
const mapStateToProps = (state: State) => ({
|
||||
isWriteable: isWriteable(state) && canUserWrite(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
onDuplicate: (id: string) => dispatch(pageActions.duplicatePage(id)),
|
||||
});
|
||||
|
||||
export const PagePreview = connect(mapStateToProps, mapDispatchToProps)(Component);
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { FC, ReactEventHandler } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
|
@ -12,15 +12,21 @@ import { ComponentStrings } from '../../../i18n';
|
|||
|
||||
const { PagePreviewPageControls: strings } = ComponentStrings;
|
||||
|
||||
export const PageControls = ({ pageId, onDelete, onDuplicate }) => {
|
||||
const handleDuplicate = (ev) => {
|
||||
interface Props {
|
||||
pageId: string;
|
||||
onRemove: (pageId: string) => void;
|
||||
onDuplicate: (pageId: string) => void;
|
||||
}
|
||||
|
||||
export const PageControls: FC<Props> = ({ pageId, onRemove, onDuplicate }) => {
|
||||
const handleDuplicate: ReactEventHandler = (ev) => {
|
||||
ev.preventDefault();
|
||||
onDuplicate(pageId);
|
||||
};
|
||||
|
||||
const handleDelete = (ev) => {
|
||||
const handleRemove: ReactEventHandler = (ev) => {
|
||||
ev.preventDefault();
|
||||
onDelete(pageId);
|
||||
onRemove(pageId);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -44,7 +50,7 @@ export const PageControls = ({ pageId, onDelete, onDuplicate }) => {
|
|||
color="danger"
|
||||
iconType="trash"
|
||||
aria-label={strings.getDeletePageAriaLabel()}
|
||||
onClick={handleDelete}
|
||||
onClick={handleRemove}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
|
@ -54,7 +60,6 @@ export const PageControls = ({ pageId, onDelete, onDuplicate }) => {
|
|||
|
||||
PageControls.propTypes = {
|
||||
pageId: PropTypes.string.isRequired,
|
||||
pageNumber: PropTypes.number.isRequired,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
onDuplicate: PropTypes.func.isRequired,
|
||||
};
|
|
@ -4,32 +4,26 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { FC } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { DomPreview } from '../dom_preview';
|
||||
import { PageControls } from './page_controls';
|
||||
import { CanvasPage } from '../../../types';
|
||||
|
||||
export const PagePreview = ({
|
||||
isWriteable,
|
||||
page,
|
||||
pageNumber,
|
||||
height,
|
||||
duplicatePage,
|
||||
confirmDelete,
|
||||
}) => (
|
||||
interface Props {
|
||||
isWriteable: boolean;
|
||||
page: Pick<CanvasPage, 'id' | 'style'>;
|
||||
height: number;
|
||||
onDuplicate: (pageId: string) => void;
|
||||
onRemove: (pageId: string) => void;
|
||||
}
|
||||
export const PagePreview: FC<Props> = ({ isWriteable, page, height, onDuplicate, onRemove }) => (
|
||||
<div
|
||||
className="canvasPageManager__pagePreview"
|
||||
style={{ backgroundColor: page.style.background }}
|
||||
>
|
||||
<DomPreview elementId={page.id} pageNumber={pageNumber} height={height} />
|
||||
{isWriteable && (
|
||||
<PageControls
|
||||
pageId={page.id}
|
||||
pageNumber={pageNumber}
|
||||
onDuplicate={duplicatePage}
|
||||
onDelete={confirmDelete}
|
||||
/>
|
||||
)}
|
||||
<DomPreview elementId={page.id} height={height} />
|
||||
{isWriteable && <PageControls pageId={page.id} onDuplicate={onDuplicate} onRemove={onRemove} />}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
@ -42,7 +36,6 @@ PagePreview.propTypes = {
|
|||
}).isRequired,
|
||||
}).isRequired,
|
||||
height: PropTypes.number.isRequired,
|
||||
pageNumber: PropTypes.number.isRequired,
|
||||
duplicatePage: PropTypes.func.isRequired,
|
||||
confirmDelete: PropTypes.func.isRequired,
|
||||
onDuplicate: PropTypes.func.isRequired,
|
||||
onRemove: PropTypes.func.isRequired,
|
||||
};
|
|
@ -24,7 +24,6 @@ import { ComponentStrings } from '../../../i18n';
|
|||
import { Navbar } from '../navbar';
|
||||
// @ts-expect-error untyped local
|
||||
import { WorkpadManager } from '../workpad_manager';
|
||||
// @ts-expect-error untyped local
|
||||
import { PageManager } from '../page_manager';
|
||||
// @ts-expect-error untyped local
|
||||
import { Expression } from '../expression';
|
||||
|
@ -102,7 +101,7 @@ export const Toolbar = (props: Props) => {
|
|||
);
|
||||
|
||||
const trays = {
|
||||
pageManager: <PageManager previousPage={previousPage} />,
|
||||
pageManager: <PageManager onPreviousPage={previousPage} />,
|
||||
expression: !elementIsSelected ? null : <Expression done={done} />,
|
||||
};
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
// @ts-ignore untyped local
|
||||
// @ts-expect-error untyped local
|
||||
import { setFilter } from '../state/actions/elements';
|
||||
import { updateEmbeddableExpression, fetchEmbeddableRenderable } from '../state/actions/embeddable';
|
||||
import { RendererHandlers, CanvasElement } from '../../types';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue