Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Bulk Upload in Asset Manager #29007

Merged
merged 1 commit into from
Jan 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 ? <Loading animated text="Image uploading" /> : <FileUpload onUpload={onUpload} />;
export const FileForm = ({ loading, onChange }) =>
loading ? (
<Loading animated text="Image uploading" />
) : (
<EuiFilePicker
initialPromptText="Select or drag and drop an image"
onChange={onChange}
compressed
className="canvasImageUpload"
accept="image/*"
/>
);

FileForm.propTypes = {
loading: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
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';
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 {
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -119,7 +125,7 @@ class ImageUpload extends React.Component {
);

const forms = {
file: <FileForm loading={loading} onUpload={this.handleUpload} />,
file: <FileForm loading={loading} onChange={this.handleUpload} />,
link: (
<LinkForm
url={url}
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/canvas/common/lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export const FETCH_TIMEOUT = 30000; // 30 seconds
export const CANVAS_USAGE_TYPE = 'canvas';
export const SECURITY_AUTH_MESSAGE = 'Authentication failed';
export const DEFAULT_WORKPAD_CSS = '.canvasPage {\n\n}';
export const VALID_IMAGE_TYPES = ['gif', 'jpeg', 'png', 'svg+xml'];
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,31 @@ import {
EuiSpacer,
EuiTextColor,
EuiToolTip,
EuiFilePicker,
EuiEmptyPrompt,
} from '@elastic/eui';
import { ConfirmModal } from '../confirm_modal';
import { Clipboard } from '../clipboard';
import { Download } from '../download';
import { Loading } from '../loading';

export class AssetManager extends React.PureComponent {
static propTypes = {
assets: PropTypes.array,
addImageElement: PropTypes.func,
removeAsset: PropTypes.func,
copyAsset: PropTypes.func,
removeAsset: PropTypes.func.isRequired,
copyAsset: PropTypes.func.isRequired,
onAssetAdd: PropTypes.func.isRequired,
};

state = {
deleteId: null,
isModalVisible: false,
loading: false,
};

_isMounted = true;

showModal = () => this.setState({ isModalVisible: true });
closeModal = () => this.setState({ isModalVisible: false });

Expand All @@ -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);
};
Expand Down Expand Up @@ -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 = (
<EuiPanel className="canvasAssetManager__emptyPanel">
<EuiEmptyPrompt
iconType="importAction"
title={<h2>No available assets</h2>}
titleSize="s"
body={
<Fragment>
<p>Upload your assets above to get started</p>
</Fragment>
}
/>
</EuiPanel>
);

const assetModal = isModalVisible ? (
<EuiOverlayMask>
<EuiModal
Expand All @@ -153,6 +183,40 @@ export class AssetManager extends React.PureComponent {
<EuiModalHeaderTitle className="canvasAssetManager__modalHeaderTitle">
Manage workpad assets
</EuiModalHeaderTitle>
<EuiFlexGroup className="canvasAssetManager__fileUploadWrapper">
<EuiFlexItem grow={false}>
{loading ? (
<Loading animated text="Uploading images" />
) : (
<EuiFilePicker
initialPromptText="Select or drag and drop images"
compressed
multiple
onChange={this.handleFileUpload}
accept="image/*"
/>
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiModalHeader>
<EuiModalBody>
<EuiText size="s" color="subdued">
<p>
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.
</p>
</EuiText>
<EuiSpacer />
{assets.length ? (
<EuiFlexGrid responsive={false} columns={4}>
{assets.map(this.renderAsset)}
</EuiFlexGrid>
) : (
emptyAssets
)}
</EuiModalBody>
<EuiModalFooter className="canvasAssetManager__modalFooter">
<EuiFlexGroup className="canvasAssetManager__meterWrapper" responsive={false}>
<EuiFlexItem>
<EuiProgress
Expand All @@ -167,20 +231,6 @@ export class AssetManager extends React.PureComponent {
<EuiText id="CanvasAssetManagerLabel">{percentageUsed}% space used</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
</EuiModalHeader>
<EuiModalBody>
<EuiText size="s" color="subdued">
<p>
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.
</p>
</EuiText>
<EuiFlexGrid responsive={false} columns={4}>
{this.props.assets.map(this.renderAsset)}
</EuiFlexGrid>
</EuiModalBody>
<EuiModalFooter>
<EuiButton size="s" onClick={this.closeModal}>
Close
</EuiButton>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
.canvasAssetManager {

.canvasAssetManager__modalHeader {
flex-wrap: wrap;
}
Expand All @@ -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;
Expand All @@ -27,13 +24,23 @@
margin: 0;
}

.canvasAssetManager__fileUploadWrapper {
justify-content: flex-end;
padding-right: $euiSize;
}

// ASSETS LIST

.canvasAssetManager__asset {
text-align: center;
overflow: hidden; // hides image from outer panel boundaries
}

.canvasAssetManager__emptyPanel {
max-width: 400px;
margin: 0 auto;
}

.canvasAssetManager__thumb {
margin: -$euiSizeS;
margin-bottom: 0;
Expand All @@ -52,4 +59,8 @@
opacity: 0; // only show the background image (which will properly keep proportions)
}
}

.canvasAssetManager__modalFooter {
justify-content: space-between;
}
}
33 changes: 31 additions & 2 deletions x-pack/plugins/canvas/public/components/asset_manager/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => ({
Expand Down Expand Up @@ -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;
},
};
};

Expand Down
19 changes: 0 additions & 19 deletions x-pack/plugins/canvas/public/components/file_upload/file_upload.js

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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 }) => ({
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 => ({
Expand Down
Loading