From cc0e2d0d7df487601b0922785a1f8f9c5dc7cadb Mon Sep 17 00:00:00 2001 From: Catherine Liu Date: Wed, 30 Jan 2019 13:05:06 -0700 Subject: [PATCH] Removed file_upload component (#29007) Moved filepicker style to main.scss. Removed workpad_upload component Added filepicker to asset manager Removed check for assets in workpad header to always show asset manager button Deduped image uploads in asset manager. Added loading indicator to asset manager Added empty prompt to display when there are no assets Adds additional image upload type checking Updated verbiage Undid CSS changes to workpad_loader filepicker --- .../uis/arguments/image_upload/forms/file.js | 16 +++- .../uis/arguments/image_upload/index.js | 26 +++--- x-pack/plugins/canvas/common/lib/constants.js | 1 + .../components/asset_manager/asset_manager.js | 86 +++++++++++++++---- .../asset_manager/asset_manager.scss | 17 +++- .../public/components/asset_manager/index.js | 33 ++++++- .../components/file_upload/file_upload.js | 19 ---- .../public/components/function_form/index.js | 5 +- .../public/components/workpad_header/index.js | 2 - .../workpad_header/workpad_header.js | 10 +-- .../workpad_loader/workpad_loader.js | 16 +++- .../workpad_loader/workpad_upload.js | 24 ------ .../index.js => lib/find_existing_asset.js} | 7 +- 13 files changed, 166 insertions(+), 96 deletions(-) delete mode 100644 x-pack/plugins/canvas/public/components/file_upload/file_upload.js delete mode 100644 x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js rename x-pack/plugins/canvas/public/{components/file_upload/index.js => lib/find_existing_asset.js} (52%) diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js index af8dc1cf37d13b..c51c949568dc2d 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/forms/file.js @@ -5,11 +5,21 @@ */ import React from 'react'; import PropTypes from 'prop-types'; +import { EuiFilePicker } from '@elastic/eui'; import { Loading } from '../../../../../public/components/loading/loading'; -import { FileUpload } from '../../../../../public/components/file_upload'; -export const FileForm = ({ loading, onUpload }) => - loading ? : ; +export const FileForm = ({ loading, onChange }) => + loading ? ( + + ) : ( + + ); FileForm.propTypes = { loading: PropTypes.bool, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index d40182a1121cd3..e667169b998158 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -7,6 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { EuiSpacer, EuiButtonGroup } from '@elastic/eui'; +import { get } from 'lodash'; import { AssetPicker } from '../../../../public/components/asset_picker'; import { elasticOutline } from '../../../lib/elastic_outline'; import { resolveFromArgs } from '../../../../common/lib/resolve_dataurl'; @@ -14,6 +15,7 @@ import { isValidHttpUrl } from '../../../../common/lib/httpurl'; import { encode } from '../../../../common/lib/dataurl'; import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import './image_upload.scss'; +import { VALID_IMAGE_TYPES } from '../../../../common/lib/constants'; import { FileForm, LinkForm } from './forms'; class ImageUpload extends React.Component { @@ -71,17 +73,21 @@ class ImageUpload extends React.Component { handleUpload = files => { const { onAssetAdd } = this.props; - const [upload] = files; - this.setState({ loading: true }); // start loading indicator + const [file] = files; - encode(upload) - .then(dataurl => onAssetAdd('dataurl', dataurl)) - .then(assetId => { - this.updateAST(assetId); + const [type, subtype] = get(file, 'type', '').split('/'); + if (type === 'image' && VALID_IMAGE_TYPES.indexOf(subtype) >= 0) { + this.setState({ loading: true }); // start loading indicator - // this component can go away when onValueChange is called, check for _isMounted - this._isMounted && this.setState({ loading: false }); // set loading state back to false - }); + encode(file) + .then(dataurl => onAssetAdd('dataurl', dataurl)) + .then(assetId => { + this.updateAST(assetId); + + // this component can go away when onValueChange is called, check for _isMounted + this._isMounted && this.setState({ loading: false }); // set loading state back to false + }); + } }; changeUrlType = optionId => { @@ -119,7 +125,7 @@ class ImageUpload extends React.Component { ); const forms = { - file: , + file: , link: ( this.setState({ isModalVisible: true }); closeModal = () => this.setState({ isModalVisible: false }); @@ -52,6 +59,13 @@ export class AssetManager extends React.PureComponent { this.props.removeAsset(this.state.deleteId); }; + handleFileUpload = files => { + this.setState({ loading: true }); + Promise.all(Array.from(files).map(file => this.props.onAssetAdd(file))).finally(() => { + this._isMounted && this.setState({ loading: false }); + }); + }; + addElement = assetId => { this.props.addImageElement(assetId); }; @@ -132,16 +146,32 @@ export class AssetManager extends React.PureComponent { ); render() { - const { isModalVisible } = this.state; + const { isModalVisible, loading } = this.state; + const { assets } = this.props; const assetMaxLimit = 25000; const assetsTotal = Math.round( - this.props.assets.reduce((total, asset) => total + asset.value.length, 0) / 1024 + assets.reduce((total, asset) => total + asset.value.length, 0) / 1024 ); const percentageUsed = Math.round((assetsTotal / assetMaxLimit) * 100); + const emptyAssets = ( + + No available assets} + titleSize="s" + body={ + +

Upload your assets above to get started

+
+ } + /> +
+ ); + const assetModal = isModalVisible ? ( Manage workpad assets + + + {loading ? ( + + ) : ( + + )} + + + + + +

+ Below are the image assets that you added to this workpad. To reclaim space, delete + assets that you no longer need. Unfortunately, any assets that are actually in use + cannot be determined at this time. +

+
+ + {assets.length ? ( + + {assets.map(this.renderAsset)} + + ) : ( + emptyAssets + )} +
+ {percentageUsed}% space used - - - -

- Below are the image assets that you added to this workpad. To reclaim space, delete - assets that you no longer need. Unfortunately, any assets that are actually in use - cannot be determined at this time. -

-
- - {this.props.assets.map(this.renderAsset)} - -
- Close diff --git a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss index 80ab585c963c23..84534971d0ec57 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss +++ b/x-pack/plugins/canvas/public/components/asset_manager/asset_manager.scss @@ -1,5 +1,4 @@ .canvasAssetManager { - .canvasAssetManager__modalHeader { flex-wrap: wrap; } @@ -15,8 +14,6 @@ flex-grow: 0; min-width: 40%; align-items: center; - justify-content: flex-end; - padding-right: $euiSize; @include euiBreakpoint('xs', 's') { flex-grow: 1; @@ -27,6 +24,11 @@ margin: 0; } + .canvasAssetManager__fileUploadWrapper { + justify-content: flex-end; + padding-right: $euiSize; + } + // ASSETS LIST .canvasAssetManager__asset { @@ -34,6 +36,11 @@ overflow: hidden; // hides image from outer panel boundaries } + .canvasAssetManager__emptyPanel { + max-width: 400px; + margin: 0 auto; + } + .canvasAssetManager__thumb { margin: -$euiSizeS; margin-bottom: 0; @@ -52,4 +59,8 @@ opacity: 0; // only show the background image (which will properly keep proportions) } } + + .canvasAssetManager__modalFooter { + justify-content: space-between; + } } diff --git a/x-pack/plugins/canvas/public/components/asset_manager/index.js b/x-pack/plugins/canvas/public/components/asset_manager/index.js index 2417e572e1a02b..878928a452c56a 100644 --- a/x-pack/plugins/canvas/public/components/asset_manager/index.js +++ b/x-pack/plugins/canvas/public/components/asset_manager/index.js @@ -6,14 +6,18 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; -import { set } from 'lodash'; +import { set, get } from 'lodash'; import { fromExpression, toExpression } from '@kbn/interpreter/common'; import { notify } from '../../lib/notify'; import { getAssets } from '../../state/selectors/assets'; -import { removeAsset } from '../../state/actions/assets'; +import { removeAsset, createAsset } from '../../state/actions/assets'; import { elementsRegistry } from '../../lib/elements_registry'; import { addElement } from '../../state/actions/elements'; import { getSelectedPage } from '../../state/selectors/workpad'; +import { encode } from '../../../common/lib/dataurl'; +import { getId } from '../../lib/get_id'; +import { findExistingAsset } from '../../lib/find_existing_asset'; +import { VALID_IMAGE_TYPES } from '../../../common/lib/constants'; import { AssetManager as Component } from './asset_manager'; const mapStateToProps = state => ({ @@ -44,15 +48,40 @@ const mapDispatchToProps = dispatch => ({ imageElement.expression = toExpression(newAST); dispatch(addElement(pageId, imageElement)); }, + onAssetAdd: (type, content) => { + // make the ID here and pass it into the action + const assetId = getId('asset'); + dispatch(createAsset(type, content, assetId)); + + // then return the id, so the caller knows the id that will be created + return assetId; + }, removeAsset: assetId => dispatch(removeAsset(assetId)), }); const mergeProps = (stateProps, dispatchProps, ownProps) => { + const { assets } = stateProps; + const { onAssetAdd } = dispatchProps; return { ...ownProps, ...stateProps, ...dispatchProps, 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); + if (existingId) { + return existingId; + } + return onAssetAdd(type, dataurl); + }); + } + + return false; + }, }; }; diff --git a/x-pack/plugins/canvas/public/components/file_upload/file_upload.js b/x-pack/plugins/canvas/public/components/file_upload/file_upload.js deleted file mode 100644 index 9640ab01bd158f..00000000000000 --- a/x-pack/plugins/canvas/public/components/file_upload/file_upload.js +++ /dev/null @@ -1,19 +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 { EuiFilePicker } from '@elastic/eui'; - -export const FileUpload = ({ id = '', className = 'canvasFileUpload', onUpload }) => ( - -); - -FileUpload.propTypes = { - id: PropTypes.string, - className: PropTypes.string, - onUpload: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/function_form/index.js b/x-pack/plugins/canvas/public/components/function_form/index.js index fbde896361cb63..5ca12b26e48c7e 100644 --- a/x-pack/plugins/canvas/public/components/function_form/index.js +++ b/x-pack/plugins/canvas/public/components/function_form/index.js @@ -21,6 +21,7 @@ import { getContextForIndex, } from '../../state/selectors/workpad'; import { getAssets } from '../../state/selectors/assets'; +import { findExistingAsset } from '../../lib/find_existing_asset'; import { FunctionForm as Component } from './function_form'; const mapStateToProps = (state, { expressionIndex }) => ({ @@ -93,9 +94,7 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => { onValueAdd: addArgument(element, pageId), onValueRemove: deleteArgument(element, pageId), onAssetAdd: (type, content) => { - const existingId = Object.keys(assets).find( - assetId => assets[assetId].type === type && assets[assetId].value === content - ); + const existingId = findExistingAsset(type, content, assets); if (existingId) { return existingId; } 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 fec9c874744f33..e724b97b0bb4f5 100644 --- a/x-pack/plugins/canvas/public/components/workpad_header/index.js +++ b/x-pack/plugins/canvas/public/components/workpad_header/index.js @@ -9,7 +9,6 @@ import { connect } from 'react-redux'; import { canUserWrite } from '../../state/selectors/app'; import { getWorkpadName, getSelectedPage, isWriteable } from '../../state/selectors/workpad'; import { setWriteable } from '../../state/actions/workpad'; -import { getAssets } from '../../state/selectors/assets'; import { addElement } from '../../state/actions/elements'; import { WorkpadHeader as Component } from './workpad_header'; @@ -18,7 +17,6 @@ const mapStateToProps = state => ({ canUserWrite: canUserWrite(state), workpadName: getWorkpadName(state), selectedPage: getSelectedPage(state), - hasAssets: Object.keys(getAssets(state)).length ? true : false, }); const mapDispatchToProps = dispatch => ({ 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 c4fa1ce7062291..88f92f7eb69e6d 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 @@ -27,7 +27,6 @@ export const WorkpadHeader = ({ isWriteable, canUserWrite, toggleWriteable, - hasAssets, addElement, setShowElementModal, showElementModal, @@ -115,11 +114,9 @@ export const WorkpadHeader = ({ {isWriteable ? ( - {hasAssets && ( - - - - )} + + + date && moment(date).format('MMM D, YYYY @ h:mma'); @@ -79,7 +80,7 @@ export class WorkpadLoader extends React.PureComponent { }; // create new workpad from uploaded JSON - uploadWorkpad = async workpad => { + onUpload = async workpad => { this.setState({ createPending: true }); await this.props.createWorkpad(workpad); this._isMounted && this.setState({ createPending: false }); @@ -232,7 +233,7 @@ export class WorkpadLoader extends React.PureComponent { return ( - + + uploadWorkpad(file, this.onUpload)} + accept="application/json" + disabled={createPending || !canUserWrite} + /> ); if (!canUserWrite) { diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js deleted file mode 100644 index 9c4372175b89ff..00000000000000 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_upload.js +++ /dev/null @@ -1,24 +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 { EuiFilePicker } from '@elastic/eui'; -import { uploadWorkpad } from './upload_workpad'; - -export const WorkpadUpload = ({ onUpload, ...rest }) => ( - uploadWorkpad(file, onUpload)} - /> -); - -WorkpadUpload.propTypes = { - onUpload: PropTypes.func.isRequired, -}; diff --git a/x-pack/plugins/canvas/public/components/file_upload/index.js b/x-pack/plugins/canvas/public/lib/find_existing_asset.js similarity index 52% rename from x-pack/plugins/canvas/public/components/file_upload/index.js rename to x-pack/plugins/canvas/public/lib/find_existing_asset.js index 68c0f6150521b0..470d7797e6febd 100644 --- a/x-pack/plugins/canvas/public/components/file_upload/index.js +++ b/x-pack/plugins/canvas/public/lib/find_existing_asset.js @@ -4,4 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -export { FileUpload } from './file_upload'; +export const findExistingAsset = (type, content, assets) => { + const existingId = Object.keys(assets).find( + assetId => assets[assetId].type === type && assets[assetId].value === content + ); + return existingId; +};