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