[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:
Clint Andrew Hall 2020-07-21 14:12:56 -04:00 committed by GitHub
parent 4b06a4eb41
commit eb71e599ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 295 additions and 227 deletions

View file

@ -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: () =>

View file

@ -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);
}
});
}
};

View file

@ -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;

View file

@ -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';

View file

@ -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>;
}
}
}

View 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,
};

View file

@ -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);

View file

@ -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);

View file

@ -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>
);

View file

@ -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);

View file

@ -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,
};

View file

@ -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,
};

View file

@ -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} />,
};

View file

@ -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';