diff --git a/x-pack/plugins/canvas/common/lib/constants.js b/x-pack/plugins/canvas/common/lib/constants.js index 339a0e48d762b..8c3c5d5dcff30 100644 --- a/x-pack/plugins/canvas/common/lib/constants.js +++ b/x-pack/plugins/canvas/common/lib/constants.js @@ -20,3 +20,4 @@ export const FETCH_TIMEOUT = 30000; // 30 seconds export const CANVAS_USAGE_TYPE = 'canvas'; export const DEFAULT_WORKPAD_CSS = '.canvasPage {\n\n}'; export const VALID_IMAGE_TYPES = ['gif', 'jpeg', 'png', 'svg+xml']; +export const ASSET_MAX_SIZE = 25000; diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.js b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.js index bbde11f8aedac..fc32e64e667eb 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.js +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.js @@ -33,10 +33,11 @@ import { ConfirmModal } from '../confirm_modal'; import { Clipboard } from '../clipboard'; import { Download } from '../download'; import { Loading } from '../loading'; +import { ASSET_MAX_SIZE } from '../../../common/lib/constants'; export class AssetManager extends React.PureComponent { static propTypes = { - assets: PropTypes.array, + assetValues: PropTypes.array, addImageElement: PropTypes.func, removeAsset: PropTypes.func.isRequired, copyAsset: PropTypes.func.isRequired, @@ -147,15 +148,13 @@ export class AssetManager extends React.PureComponent { render() { const { isModalVisible, loading } = this.state; - const { assets } = this.props; - - const assetMaxLimit = 25000; + const { assetValues } = this.props; const assetsTotal = Math.round( - assets.reduce((total, asset) => total + asset.value.length, 0) / 1024 + assetValues.reduce((total, { value }) => total + value.length, 0) / 1024 ); - const percentageUsed = Math.round((assetsTotal / assetMaxLimit) * 100); + const percentageUsed = Math.round((assetsTotal / ASSET_MAX_SIZE) * 100); const emptyAssets = ( @@ -208,9 +207,9 @@ export class AssetManager extends React.PureComponent {

- {assets.length ? ( + {assetValues.length ? ( - {assets.map(this.renderAsset)} + {assetValues.map(this.renderAsset)} ) : ( emptyAssets @@ -221,7 +220,7 @@ export class AssetManager extends React.PureComponent { ({ - assets: Object.values(getAssets(state)), // pull values out of assets object + assets: getAssets(state), selectedPage: getSelectedPage(state), }); @@ -60,19 +60,22 @@ const mapDispatchToProps = dispatch => ({ }); const mergeProps = (stateProps, dispatchProps, ownProps) => { - const { assets } = stateProps; + const { assets, selectedPage } = stateProps; const { onAssetAdd } = dispatchProps; + const assetValues = Object.values(assets); // pull values out of assets object + return { ...ownProps, - ...stateProps, ...dispatchProps, + selectedPage, + assetValues, addImageElement: dispatchProps.addImageElement(stateProps.selectedPage), onAssetAdd: file => { const [type, subtype] = get(file, 'type', '').split('/'); if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { return encode(file).then(dataurl => { const type = 'dataurl'; - const existingId = findExistingAsset(type, dataurl, assets); + const existingId = findExistingAsset(type, dataurl, assetValues); if (existingId) { return existingId; } diff --git a/x-pack/plugins/canvas/public/components/fullscreen_control/fullscreen_control.js b/x-pack/plugins/canvas/public/components/fullscreen_control/fullscreen_control.js index 5a4f73775f6ca..4e2bc84deef7d 100644 --- a/x-pack/plugins/canvas/public/components/fullscreen_control/fullscreen_control.js +++ b/x-pack/plugins/canvas/public/components/fullscreen_control/fullscreen_control.js @@ -9,6 +9,15 @@ import PropTypes from 'prop-types'; import { Shortcuts } from 'react-shortcuts'; export class FullscreenControl extends React.PureComponent { + keyHandler = action => { + const enterFullscreen = action === 'FULLSCREEN'; + const exitFullscreen = this.props.isFullscreen && action === 'FULLSCREEN_EXIT'; + + if (enterFullscreen || exitFullscreen) { + this.toggleFullscreen(); + } + }; + toggleFullscreen = () => { const { setFullscreen, isFullscreen } = this.props; setFullscreen(!isFullscreen); @@ -17,17 +26,11 @@ export class FullscreenControl extends React.PureComponent { render() { const { children, isFullscreen } = this.props; - const keyHandler = action => { - if (action === 'FULLSCREEN' || (isFullscreen && action === 'FULLSCREEN_EXIT')) { - this.toggleFullscreen(); - } - }; - return ( ({ }); export const Toolbar = compose( + pure, connect(mapStateToProps), getContext({ router: PropTypes.object, diff --git a/x-pack/plugins/canvas/public/components/workpad/index.js b/x-pack/plugins/canvas/public/components/workpad/index.js index 40ae5e1d1a664..c030cb1cabcbc 100644 --- a/x-pack/plugins/canvas/public/components/workpad/index.js +++ b/x-pack/plugins/canvas/public/components/workpad/index.js @@ -6,7 +6,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { compose, withState, withProps, getContext, withHandlers } from 'recompose'; +import { pure, compose, withState, withProps, getContext, withHandlers } from 'recompose'; import { transitionsRegistry } from '../../lib/transitions_registry'; import { undoHistory, redoHistory } from '../../state/actions/history'; import { fetchAllRenderables } from '../../state/actions/elements'; @@ -19,13 +19,19 @@ import { } from '../../state/selectors/workpad'; import { Workpad as Component } from './workpad'; -const mapStateToProps = state => ({ - pages: getPages(state), - selectedPageNumber: getSelectedPageIndex(state) + 1, - totalElementCount: getAllElements(state).length, - workpad: getWorkpad(state), - isFullscreen: getFullscreen(state), -}); +const mapStateToProps = state => { + const { width, height, id: workpadId, css: workpadCss } = getWorkpad(state); + return { + pages: getPages(state), + selectedPageNumber: getSelectedPageIndex(state) + 1, + totalElementCount: getAllElements(state).length, + width, + height, + workpadCss, + workpadId, + isFullscreen: getFullscreen(state), + }; +}; const mapDispatchToProps = { undoHistory, @@ -34,6 +40,7 @@ const mapDispatchToProps = { }; export const Workpad = compose( + pure, getContext({ router: PropTypes.object, }), @@ -68,17 +75,17 @@ export const Workpad = compose( } props.setPrevSelectedPageNumber(props.selectedPageNumber); const transitionPage = Math.max(props.selectedPageNumber, pageNumber) - 1; - const { transition } = props.workpad.pages[transitionPage]; + const { transition } = props.pages[transitionPage]; if (transition) { props.setTransition(transition); } - props.router.navigateTo('loadWorkpad', { id: props.workpad.id, page: pageNumber }); + props.router.navigateTo('loadWorkpad', { id: props.workpadId, page: pageNumber }); }, }), withHandlers({ onTransitionEnd: ({ setTransition }) => () => setTransition(null), nextPage: props => () => { - const pageNumber = Math.min(props.selectedPageNumber + 1, props.workpad.pages.length); + const pageNumber = Math.min(props.selectedPageNumber + 1, props.pages.length); props.onPageChange(pageNumber); }, previousPage: props => () => { diff --git a/x-pack/plugins/canvas/public/components/workpad/workpad.js b/x-pack/plugins/canvas/public/components/workpad/workpad.js index dcdbed0ecea65..956d8a0dbdb63 100644 --- a/x-pack/plugins/canvas/public/components/workpad/workpad.js +++ b/x-pack/plugins/canvas/public/components/workpad/workpad.js @@ -10,33 +10,41 @@ import { Shortcuts } from 'react-shortcuts'; import Style from 'style-it'; import { WorkpadPage } from '../workpad_page'; import { Fullscreen } from '../fullscreen'; -import { setDocTitle } from '../../lib/doc_title'; -export const Workpad = props => { - const { - selectedPageNumber, - getAnimation, - onTransitionEnd, - pages, - totalElementCount, - workpad, - fetchAllRenderables, - undoHistory, - redoHistory, - setGrid, // TODO: Get rid of grid when we improve the layout engine - grid, - nextPage, - previousPage, - isFullscreen, - } = props; +const WORKPAD_CANVAS_BUFFER = 32; // 32px padding around the workpad - const { height, width } = workpad; - const bufferStyle = { - height: isFullscreen ? height : height + 32, - width: isFullscreen ? width : width + 32, +export class Workpad extends React.PureComponent { + static propTypes = { + selectedPageNumber: PropTypes.number.isRequired, + getAnimation: PropTypes.func.isRequired, + onTransitionEnd: PropTypes.func.isRequired, + grid: PropTypes.bool.isRequired, + setGrid: PropTypes.func.isRequired, + pages: PropTypes.array.isRequired, + totalElementCount: PropTypes.number.isRequired, + isFullscreen: PropTypes.bool.isRequired, + width: PropTypes.number.isRequired, + height: PropTypes.number.isRequired, + workpadCss: PropTypes.string, + undoHistory: PropTypes.func.isRequired, + redoHistory: PropTypes.func.isRequired, + nextPage: PropTypes.func.isRequired, + previousPage: PropTypes.func.isRequired, + fetchAllRenderables: PropTypes.func.isRequired, + css: PropTypes.object, }; - const keyHandler = action => { + keyHandler = action => { + const { + fetchAllRenderables, + undoHistory, + redoHistory, + nextPage, + previousPage, + grid, // TODO: Get rid of grid when we improve the layout engine + setGrid, + } = this.props; + // handle keypress events for editor and presentation events // this exists in both contexts if (action === 'REFRESH') { @@ -63,84 +71,84 @@ export const Workpad = props => { } }; - setDocTitle(workpad.name); + render() { + const { + selectedPageNumber, + getAnimation, + onTransitionEnd, + pages, + totalElementCount, + width, + height, + workpadCss, + grid, + isFullscreen, + } = this.props; - return ( -
-
- {!isFullscreen && ( - - )} + const bufferStyle = { + height: isFullscreen ? height : height + WORKPAD_CANVAS_BUFFER, + width: isFullscreen ? width : width + WORKPAD_CANVAS_BUFFER, + }; - - {({ isFullscreen, windowSize }) => { - const scale = Math.min(windowSize.height / height, windowSize.width / width); - const fsStyle = isFullscreen - ? { - transform: `scale3d(${scale}, ${scale}, 1)`, - WebkitTransform: `scale3d(${scale}, ${scale}, 1)`, - msTransform: `scale3d(${scale}, ${scale}, 1)`, - // height, - // width, - height: windowSize.height < height ? 'auto' : height, - width: windowSize.width < width ? 'auto' : width, - } - : {}; + return ( +
+
+ {!isFullscreen && ( + + )} - // NOTE: the data-shared-* attributes here are used for reporting - return Style.it( - workpad.css, -
- {isFullscreen && ( - - )} - {pages.map((page, i) => ( - - ))} + + {({ isFullscreen, windowSize }) => { + const scale = Math.min(windowSize.height / height, windowSize.width / width); + const fsStyle = isFullscreen + ? { + transform: `scale3d(${scale}, ${scale}, 1)`, + WebkitTransform: `scale3d(${scale}, ${scale}, 1)`, + msTransform: `scale3d(${scale}, ${scale}, 1)`, + // height, + // width, + height: windowSize.height < height ? 'auto' : height, + width: windowSize.width < width ? 'auto' : width, + } + : {}; + + // NOTE: the data-shared-* attributes here are used for reporting + return Style.it( + workpadCss,
-
- ); - }} -
+ className={`canvasWorkpad ${isFullscreen ? 'fullscreen' : ''}`} + style={fsStyle} + data-shared-items-count={totalElementCount} + > + {isFullscreen && ( + + )} + {pages.map((page, i) => ( + + ))} +
+
+ ); + }} + +
-
- ); -}; - -Workpad.propTypes = { - selectedPageNumber: PropTypes.number.isRequired, - getAnimation: PropTypes.func.isRequired, - onTransitionEnd: PropTypes.func.isRequired, - grid: PropTypes.bool.isRequired, - setGrid: PropTypes.func.isRequired, - pages: PropTypes.array.isRequired, - totalElementCount: PropTypes.number.isRequired, - isFullscreen: PropTypes.bool.isRequired, - workpad: PropTypes.object.isRequired, - undoHistory: PropTypes.func.isRequired, - redoHistory: PropTypes.func.isRequired, - nextPage: PropTypes.func.isRequired, - previousPage: PropTypes.func.isRequired, - fetchAllRenderables: PropTypes.func.isRequired, - css: PropTypes.object, -}; + ); + } +} diff --git a/x-pack/plugins/canvas/public/components/workpad_header/index.js b/x-pack/plugins/canvas/public/components/workpad_header/index.js index e724b97b0bb4f..66b1988c83c83 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/index.js @@ -7,7 +7,7 @@ import { compose, withState } from 'recompose'; import { connect } from 'react-redux'; import { canUserWrite } from '../../state/selectors/app'; -import { getWorkpadName, getSelectedPage, isWriteable } from '../../state/selectors/workpad'; +import { getSelectedPage, isWriteable } from '../../state/selectors/workpad'; import { setWriteable } from '../../state/actions/workpad'; import { addElement } from '../../state/actions/elements'; import { WorkpadHeader as Component } from './workpad_header'; @@ -15,7 +15,6 @@ import { WorkpadHeader as Component } from './workpad_header'; const mapStateToProps = state => ({ isWriteable: isWriteable(state) && canUserWrite(state), canUserWrite: canUserWrite(state), - workpadName: getWorkpadName(state), selectedPage: getSelectedPage(state), }); @@ -33,10 +32,10 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => ({ }); export const WorkpadHeader = compose( - withState('showElementModal', 'setShowElementModal', false), connect( mapStateToProps, mapDispatchToProps, mergeProps - ) + ), + withState('showElementModal', 'setShowElementModal', false) )(Component); diff --git a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js index 507f7a1a90d98..846c3f1ff5b1e 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/workpad_header.js @@ -23,122 +23,134 @@ import { WorkpadExport } from '../workpad_export'; import { FullscreenControl } from '../fullscreen_control'; import { RefreshControl } from '../refresh_control'; -export const WorkpadHeader = ({ - isWriteable, - canUserWrite, - toggleWriteable, - addElement, - setShowElementModal, - showElementModal, -}) => { - const keyHandler = action => { +export class WorkpadHeader extends React.PureComponent { + static propTypes = { + isWriteable: PropTypes.bool, + toggleWriteable: PropTypes.func, + addElement: PropTypes.func.isRequired, + showElementModal: PropTypes.bool, + setShowElementModal: PropTypes.func, + }; + + fullscreenButton = ({ toggleFullscreen }) => ( + + + + ); + + keyHandler = action => { if (action === 'EDITING') { - toggleWriteable(); + this.props.toggleWriteable(); } }; - const elementAdd = ( - - setShowElementModal(false)} - className="canvasModal--fixedSize" - maxWidth="1000px" - initialFocus=".canvasElements__filter" - > - { - addElement(element); - setShowElementModal(false); - }} - /> - - setShowElementModal(false)}> - Dismiss - - - - - ); + elementAdd = () => { + const { addElement, setShowElementModal } = this.props; - let readOnlyToolTip = ''; + return ( + + setShowElementModal(false)} + className="canvasModal--fixedSize" + maxWidth="1000px" + initialFocus=".canvasElements__filter" + > + { + addElement(element); + setShowElementModal(false); + }} + /> + + setShowElementModal(false)}> + Dismiss + + + + + ); + }; - if (!canUserWrite) { - readOnlyToolTip = "You don't have permission to edit this workpad"; - } else { - readOnlyToolTip = isWriteable ? 'Hide editing controls' : 'Show editing controls'; - } + getTooltipText = () => { + if (!this.props.canUserWrite) { + return "You don't have permission to edit this workpad"; + } else { + return this.props.isWriteable ? 'Hide editing controls' : 'Show editing controls'; + } + }; - return ( -
- {showElementModal ? elementAdd : null} - - - - - - - - - {({ toggleFullscreen }) => ( - - - - )} - - - - - - - {canUserWrite && ( - - )} - - { - toggleWriteable(); - }} - size="s" - aria-label={readOnlyToolTip} - isDisabled={!canUserWrite} - /> - - - - - {isWriteable ? ( + render() { + const { + isWriteable, + canUserWrite, + toggleWriteable, + setShowElementModal, + showElementModal, + } = this.props; + + return ( +
+ {showElementModal ? this.elementAdd() : null} + - + + + + + + {this.fullscreenButton} + - + - setShowElementModal(true)} - > - Add element - + {canUserWrite && ( + + )} + + { + toggleWriteable(); + }} + size="s" + aria-label={this.getTooltipText()} + isDisabled={!canUserWrite} + /> + - ) : null} - -
- ); -}; - -WorkpadHeader.propTypes = { - isWriteable: PropTypes.bool, - toggleWriteable: PropTypes.func, - addElement: PropTypes.func.isRequired, - showElementModal: PropTypes.bool, - setShowElementModal: PropTypes.func, -}; + {isWriteable ? ( + + + + + + + setShowElementModal(true)} + > + Add element + + + + + ) : null} +
+
+ ); + } +} diff --git a/x-pack/plugins/canvas/public/state/middleware/workpad_update.js b/x-pack/plugins/canvas/public/state/middleware/workpad_update.js index 574168b110b55..cfb588048ecb3 100644 --- a/x-pack/plugins/canvas/public/state/middleware/workpad_update.js +++ b/x-pack/plugins/canvas/public/state/middleware/workpad_update.js @@ -7,14 +7,21 @@ import { duplicatePage } from '../actions/pages'; import { fetchRenderable } from '../actions/elements'; import { setWriteable } from '../actions/workpad'; -import { getPages, isWriteable } from '../selectors/workpad'; +import { getPages, getWorkpadName, isWriteable } from '../selectors/workpad'; import { getWindow } from '../../lib/get_window'; +import { setDocTitle } from '../../lib/doc_title'; export const workpadUpdate = ({ dispatch, getState }) => next => action => { const oldIsWriteable = isWriteable(getState()); + const oldName = getWorkpadName(getState()); next(action); + // This middleware updates the page title when the workpad name changes + if (getWorkpadName(getState()) !== oldName) { + setDocTitle(getWorkpadName(getState())); + } + // This middleware fetches all of the renderable elements on new, duplicate page if (action.type === duplicatePage.toString()) { // When a page has been duplicated, it will be added as the last page, so fetch it @@ -22,17 +29,16 @@ export const workpadUpdate = ({ dispatch, getState }) => next => action => { const newPage = pages[pages.length - 1]; // For each element on that page, dispatch the action to update it - return newPage.elements.forEach(element => dispatch(fetchRenderable(element))); + newPage.elements.forEach(element => dispatch(fetchRenderable(element))); } // This middleware clears any page selection when the writeable mode changes if (action.type === setWriteable.toString() && oldIsWriteable !== isWriteable(getState())) { const win = getWindow(); - if (typeof win.getSelection !== 'function') { - return; + // check for browser feature before using it + if (typeof win.getSelection === 'function') { + win.getSelection().collapse(document.querySelector('body'), 0); } - - win.getSelection().collapse(document.querySelector('body'), 0); } };