diff --git a/client/modules/IDE/components/AssetList.jsx b/client/modules/IDE/components/AssetList.jsx
index 559f60c580..584cd57238 100644
--- a/client/modules/IDE/components/AssetList.jsx
+++ b/client/modules/IDE/components/AssetList.jsx
@@ -1,220 +1,182 @@
-import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useState, useEffect } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import prettyBytes from 'pretty-bytes';
import { withTranslation } from 'react-i18next';
+import PropTypes from 'prop-types'; // Import PropTypes
import Loader from '../../App/components/loader';
import * as AssetActions from '../actions/assets';
import DownFilledTriangleIcon from '../../../images/down-filled-triangle.svg';
-class AssetListRowBase extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- isFocused: false,
- optionsOpen: false
- };
- }
-
- onFocusComponent = () => {
- this.setState({ isFocused: true });
+function AssetListRowBase(props) {
+ const [isFocused, setIsFocused] = useState(false);
+ const [optionsOpen, setOptionsOpen] = useState(false);
+
+ const onFocusComponent = () => {
+ setIsFocused(true);
+ };
+
+ const closeOptions = () => {
+ setOptionsOpen(false);
};
- onBlurComponent = () => {
- this.setState({ isFocused: false });
+ const onBlurComponent = () => {
+ setIsFocused(false);
setTimeout(() => {
- if (!this.state.isFocused) {
- this.closeOptions();
+ if (!isFocused) {
+ closeOptions();
}
}, 200);
};
- openOptions = () => {
- this.setState({
- optionsOpen: true
- });
- };
-
- closeOptions = () => {
- this.setState({
- optionsOpen: false
- });
+ const openOptions = () => {
+ setOptionsOpen(true);
};
- toggleOptions = () => {
- if (this.state.optionsOpen) {
- this.closeOptions();
+ const toggleOptions = () => {
+ if (optionsOpen) {
+ closeOptions();
} else {
- this.openOptions();
+ openOptions();
}
};
- handleDropdownOpen = () => {
- this.closeOptions();
- this.openOptions();
- };
-
- handleAssetDelete = () => {
- const { key, name } = this.props.asset;
- this.closeOptions();
- if (window.confirm(this.props.t('Common.DeleteConfirmation', { name }))) {
- this.props.deleteAssetRequest(key);
+ const handleAssetDelete = () => {
+ const { key, name } = props.asset;
+ closeOptions();
+ if (window.confirm(props.t('Common.DeleteConfirmation', { name }))) {
+ props.deleteAssetRequest(key);
}
};
- render() {
- const { asset, username, t } = this.props;
- const { optionsOpen } = this.state;
- return (
-
-
-
- {asset.name}
+ const { asset, username, t } = props;
+ return (
+ |
+
+
+ {asset.name}
+
+ |
+ {prettyBytes(asset.size)} |
+
+ {asset.sketchId && (
+
+ {asset.sketchName}
-
- | {prettyBytes(asset.size)} |
-
- {asset.sketchId && (
-
- {asset.sketchName}
-
- )}
- |
-
-
- {optionsOpen && (
-
- -
-
-
- -
-
- {t('AssetList.OpenNewTab')}
-
-
-
- )}
- |
-
- );
- }
-}
-
-AssetListRowBase.propTypes = {
- asset: PropTypes.shape({
- key: PropTypes.string.isRequired,
- url: PropTypes.string.isRequired,
- sketchId: PropTypes.string,
- sketchName: PropTypes.string,
- name: PropTypes.string.isRequired,
- size: PropTypes.number.isRequired
- }).isRequired,
- deleteAssetRequest: PropTypes.func.isRequired,
- username: PropTypes.string.isRequired,
- t: PropTypes.func.isRequired
-};
-
-function mapStateToPropsAssetListRow(state) {
- return {
- username: state.user.username
- };
-}
-
-function mapDispatchToPropsAssetListRow(dispatch) {
- return bindActionCreators(AssetActions, dispatch);
+ )}
+
+
+
+ {optionsOpen && (
+
+ -
+
+
+ -
+
+ {t('AssetList.OpenNewTab')}
+
+
+
+ )}
+ |
+
+ );
}
-const AssetListRow = connect(
- mapStateToPropsAssetListRow,
- mapDispatchToPropsAssetListRow
-)(AssetListRowBase);
+function AssetList(props) {
+ useEffect(() => {
+ props.getAssets();
+ }, []);
-class AssetList extends React.Component {
- constructor(props) {
- super(props);
- this.props.getAssets();
- }
+ const getAssetsTitle = () => props.t('AssetList.Title');
- getAssetsTitle() {
- return this.props.t('AssetList.Title');
- }
+ const hasAssets = () => !props.loading && props.assetList.length > 0;
- hasAssets() {
- return !this.props.loading && this.props.assetList.length > 0;
- }
-
- renderLoader() {
- if (this.props.loading) return ;
+ const renderLoader = () => {
+ if (props.loading) return ;
return null;
- }
+ };
- renderEmptyTable() {
- if (!this.props.loading && this.props.assetList.length === 0) {
+ const renderEmptyTable = () => {
+ if (!props.loading && props.assetList.length === 0) {
return (
- {this.props.t('AssetList.NoUploadedAssets')}
+ {props.t('AssetList.NoUploadedAssets')}
);
}
return null;
- }
-
- render() {
- const { assetList, t } = this.props;
- return (
-
-
- {this.getAssetsTitle()}
-
- {this.renderLoader()}
- {this.renderEmptyTable()}
- {this.hasAssets() && (
-
-
-
- {t('AssetList.HeaderName')} |
- {t('AssetList.HeaderSize')} |
- {t('AssetList.HeaderSketch')} |
- |
-
-
-
- {assetList.map((asset) => (
-
- ))}
-
-
- )}
-
- );
- }
+ };
+
+ const { assetList, t } = props;
+ return (
+
+
+ {getAssetsTitle()}
+
+ {renderLoader()}
+ {renderEmptyTable()}
+ {hasAssets() && (
+
+
+
+ {t('AssetList.HeaderName')} |
+ {t('AssetList.HeaderSize')} |
+ {t('AssetList.HeaderSketch')} |
+ |
+
+
+
+ {assetList.map((asset) => (
+
+ ))}
+
+
+ )}
+
+ );
}
+// AssetListRowBase component PropTypes
+AssetListRowBase.propTypes = {
+ asset: PropTypes.string.isRequired,
+ key: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ url: PropTypes.string.isRequired,
+ sketchName: PropTypes.string.isRequired,
+ sketchId: PropTypes.string.isRequired,
+ size: PropTypes.number.isRequired,
+ username: PropTypes.string.isRequired,
+ t: PropTypes.string.isRequired,
+ deleteAssetRequest: PropTypes.string.isRequired
+};
+
+// AssetList component PropTypes
AssetList.propTypes = {
user: PropTypes.shape({
username: PropTypes.string
@@ -225,7 +187,8 @@ AssetList.propTypes = {
name: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
sketchName: PropTypes.string,
- sketchId: PropTypes.string
+ sketchId: PropTypes.string,
+ size: PropTypes.number.isRequired
})
).isRequired,
getAssets: PropTypes.func.isRequired,
diff --git a/client/modules/IDE/components/CollectionList/CollectionListRow.jsx b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx
index 16421f7a6b..646b9824b5 100644
--- a/client/modules/IDE/components/CollectionList/CollectionListRow.jsx
+++ b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx
@@ -1,299 +1,314 @@
import PropTypes from 'prop-types';
import React from 'react';
+import { Helmet } from 'react-helmet';
+import { withTranslation } from 'react-i18next';
import { connect } from 'react-redux';
-import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
-import { withTranslation } from 'react-i18next';
+import classNames from 'classnames';
+import find from 'lodash/find';
import * as ProjectActions from '../../actions/project';
+import * as ProjectsActions from '../../actions/projects';
import * as CollectionsActions from '../../actions/collections';
-import * as IdeActions from '../../actions/ide';
import * as ToastActions from '../../actions/toast';
-import dates from '../../../../utils/formatDate';
+import * as SortingActions from '../../actions/sorting';
+import getSortedCollections from '../../selectors/collections';
+import Loader from '../../../App/components/loader';
+import Overlay from '../../../App/components/Overlay';
+import AddToCollectionSketchList from '../AddToCollectionSketchList';
+import { SketchSearchbar } from '../Searchbar';
-import DownFilledTriangleIcon from '../../../../images/down-filled-triangle.svg';
-import MoreIconSvg from '../../../../images/more.svg';
+import CollectionListRow from './CollectionListRow';
-const formatDateCell = (date, mobile = false) =>
- dates.format(date, { showTime: !mobile });
-
-class CollectionListRowBase extends React.Component {
- static projectInCollection(project, collection) {
- return (
- collection.items.find((item) => item.project.id === project.id) != null
- );
- }
+import ArrowUpIcon from '../../../../images/sort-arrow-up.svg';
+import ArrowDownIcon from '../../../../images/sort-arrow-down.svg';
+class CollectionList extends React.Component {
constructor(props) {
super(props);
+
+ if (props.projectId) {
+ props.getProject(props.projectId);
+ }
+
+ this.props.getCollections(this.props.username);
+ this.props.resetSorting();
+
this.state = {
- optionsOpen: false,
- isFocused: false,
- renameOpen: false,
- renameValue: ''
+ hasLoadedData: false,
+ addingSketchesToCollectionId: null
};
- this.renameInput = React.createRef();
}
- onFocusComponent = () => {
- this.setState({ isFocused: true });
- };
-
- onBlurComponent = () => {
- this.setState({ isFocused: false });
- setTimeout(() => {
- if (!this.state.isFocused) {
- this.closeAll();
- }
- }, 200);
- };
+ componentDidUpdate(prevProps, prevState) {
+ if (prevProps.loading === true && this.props.loading === false) {
+ // eslint-disable-next-line react/no-did-update-set-state
+ this.setState({
+ hasLoadedData: true
+ });
+ }
+ }
- openOptions = () => {
- this.setState({
- optionsOpen: true
+ getTitle() {
+ if (this.props.username === this.props.user.username) {
+ return this.props.t('CollectionList.Title');
+ }
+ return this.props.t('CollectionList.AnothersTitle', {
+ anotheruser: this.props.username
});
- };
+ }
- closeOptions = () => {
+ showAddSketches = (collectionId) => {
this.setState({
- optionsOpen: false
+ addingSketchesToCollectionId: collectionId
});
};
- toggleOptions = () => {
- if (this.state.optionsOpen) {
- this.closeOptions();
- } else {
- this.openOptions();
- }
- };
-
- closeAll = () => {
+ hideAddSketches = () => {
this.setState({
- optionsOpen: false,
- renameOpen: false
+ addingSketchesToCollectionId: null
});
};
- handleAddSketches = () => {
- this.closeAll();
- this.props.onAddSketches();
- };
-
- handleDropdownOpen = () => {
- this.closeAll();
- this.openOptions();
- };
-
- handleCollectionDelete = () => {
- this.closeAll();
- if (
- window.confirm(
- this.props.t('Common.DeleteConfirmation', {
- name: this.props.collection.name
- })
- )
- ) {
- this.props.deleteCollection(this.props.collection.id);
- }
- };
-
- handleRenameOpen = () => {
- this.closeAll();
- this.setState(
- {
- renameOpen: true,
- renameValue: this.props.collection.name
- },
- () => this.renameInput.current.focus()
+ hasCollections() {
+ return (
+ (!this.props.loading || this.state.hasLoadedData) &&
+ this.props.collections.length > 0
);
- };
+ }
- handleRenameChange = (e) => {
- this.setState({
- renameValue: e.target.value
- });
- };
+ _renderLoader() {
+ if (this.props.loading && !this.state.hasLoadedData) return ;
+ return null;
+ }
- handleRenameEnter = (e) => {
- if (e.key === 'Enter') {
- this.updateName();
- this.closeAll();
+ _renderEmptyTable() {
+ if (!this.props.loading && this.props.collections.length === 0) {
+ return (
+
+ {this.props.t('CollectionList.NoCollections')}
+
+ );
}
- };
-
- handleRenameBlur = () => {
- this.updateName();
- this.closeAll();
- };
+ return null;
+ }
- updateName = () => {
- const isValid = this.state.renameValue.trim().length !== 0;
- if (isValid) {
- this.props.editCollection(this.props.collection.id, {
- name: this.state.renameValue.trim()
+ _getButtonLabel = (fieldName, displayName) => {
+ const { field, direction } = this.props.sorting;
+ let buttonLabel;
+ if (field !== fieldName) {
+ if (field === 'name') {
+ buttonLabel = this.props.t('CollectionList.ButtonLabelAscendingARIA', {
+ displayName
+ });
+ } else {
+ buttonLabel = this.props.t('CollectionList.ButtonLabelDescendingARIA', {
+ displayName
+ });
+ }
+ } else if (direction === SortingActions.DIRECTION.ASC) {
+ buttonLabel = this.props.t('CollectionList.ButtonLabelDescendingARIA', {
+ displayName
+ });
+ } else {
+ buttonLabel = this.props.t('CollectionList.ButtonLabelAscendingARIA', {
+ displayName
});
}
+ return buttonLabel;
};
- renderActions = () => {
- const { optionsOpen } = this.state;
- const userIsOwner = this.props.user.username === this.props.username;
-
+ _renderFieldHeader = (fieldName, displayName) => {
+ const { field, direction } = this.props.sorting;
+ const headerClass = classNames({
+ 'sketches-table__header': true,
+ 'sketches-table__header--selected': field === fieldName
+ });
+ const buttonLabel = this._getButtonLabel(fieldName, displayName);
return (
-
+
- {optionsOpen && (
-
- -
-
-
- {userIsOwner && (
- -
-
-
+ {displayName}
+ {field === fieldName &&
+ direction === SortingActions.DIRECTION.ASC && (
+
)}
- {userIsOwner && (
- -
-
-
+ {field === fieldName &&
+ direction === SortingActions.DIRECTION.DESC && (
+
)}
-
- )}
-
- );
- };
-
- renderCollectionName = () => {
- const { collection, username } = this.props;
- const { renameOpen, renameValue } = this.state;
-
- return (
-
-
- {renameOpen ? '' : collection.name}
-
- {renameOpen && (
- e.stopPropagation()}
- ref={this.renameInput}
- />
- )}
-
+
+ |
);
};
render() {
- const { collection, mobile } = this.props;
+ const username =
+ this.props.username !== undefined
+ ? this.props.username
+ : this.props.user.username;
+ const { mobile } = this.props;
return (
-
-
-
- {this.renderCollectionName()}
-
- |
- {formatDateCell(collection.createdAt, mobile)} |
- {formatDateCell(collection.updatedAt, mobile)} |
-
- {mobile && 'sketches: '}
- {(collection.items || []).length}
- |
- {this.renderActions()} |
-
+
+
+ {this.getTitle()}
+
+
+ {this._renderLoader()}
+ {this._renderEmptyTable()}
+ {this.hasCollections() && (
+
+
+
+ {this._renderFieldHeader(
+ 'name',
+ this.props.t('CollectionList.HeaderName')
+ )}
+ {this._renderFieldHeader(
+ 'createdAt',
+ this.props.t('CollectionList.HeaderCreatedAt', {
+ context: mobile ? 'mobile' : ''
+ })
+ )}
+ {this._renderFieldHeader(
+ 'updatedAt',
+ this.props.t('CollectionList.HeaderUpdatedAt', {
+ context: mobile ? 'mobile' : ''
+ })
+ )}
+ {this._renderFieldHeader(
+ 'numItems',
+ this.props.t('CollectionList.HeaderNumItems', {
+ context: mobile ? 'mobile' : ''
+ })
+ )}
+ |
+
+
+
+ {this.props.collections.map((collection) => (
+ this.showAddSketches(collection.id)}
+ />
+ ))}
+
+
+ )}
+ {this.state.addingSketchesToCollectionId && (
+ }
+ closeOverlay={this.hideAddSketches}
+ isFixedHeight
+ >
+
+
+ )}
+
);
}
}
-CollectionListRowBase.propTypes = {
- collection: PropTypes.shape({
- id: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- owner: PropTypes.shape({
- username: PropTypes.string.isRequired
- }).isRequired,
- createdAt: PropTypes.string.isRequired,
- updatedAt: PropTypes.string.isRequired,
- items: PropTypes.arrayOf(
- PropTypes.shape({
- project: PropTypes.shape({
- id: PropTypes.string.isRequired
- })
- })
- )
- }).isRequired,
- username: PropTypes.string.isRequired,
+CollectionList.propTypes = {
user: PropTypes.shape({
username: PropTypes.string,
authenticated: PropTypes.bool.isRequired
}).isRequired,
- deleteCollection: PropTypes.func.isRequired,
- editCollection: PropTypes.func.isRequired,
- onAddSketches: PropTypes.func.isRequired,
- mobile: PropTypes.bool,
- t: PropTypes.func.isRequired
+ projectId: PropTypes.string,
+ getCollections: PropTypes.func.isRequired,
+ getProject: PropTypes.func.isRequired,
+ collections: PropTypes.arrayOf(
+ PropTypes.shape({
+ id: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ description: PropTypes.string,
+ createdAt: PropTypes.string.isRequired,
+ updatedAt: PropTypes.string.isRequired
+ })
+ ).isRequired,
+ username: PropTypes.string,
+ loading: PropTypes.bool.isRequired,
+ toggleDirectionForField: PropTypes.func.isRequired,
+ resetSorting: PropTypes.func.isRequired,
+ sorting: PropTypes.shape({
+ field: PropTypes.string.isRequired,
+ direction: PropTypes.string.isRequired
+ }).isRequired,
+ project: PropTypes.shape({
+ id: PropTypes.string,
+ owner: PropTypes.shape({
+ id: PropTypes.string
+ })
+ }),
+ t: PropTypes.func.isRequired,
+ mobile: PropTypes.bool
};
-CollectionListRowBase.defaultProps = {
+CollectionList.defaultProps = {
+ projectId: undefined,
+ project: {
+ id: undefined,
+ owner: undefined
+ },
+ username: undefined,
mobile: false
};
-function mapDispatchToPropsSketchListRow(dispatch) {
+function mapStateToProps(state, ownProps) {
+ return {
+ user: state.user,
+ collections: getSortedCollections(state),
+ sorting: state.sorting,
+ loading: state.loading,
+ project: state.project,
+ projectId: ownProps && ownProps.params ? ownProps.params.project_id : null
+ };
+}
+
+function mapDispatchToProps(dispatch) {
return bindActionCreators(
Object.assign(
{},
CollectionsActions,
+ ProjectsActions,
ProjectActions,
- IdeActions,
- ToastActions
+ ToastActions,
+ SortingActions
),
dispatch
);
}
export default withTranslation()(
- connect(null, mapDispatchToPropsSketchListRow)(CollectionListRowBase)
+ connect(mapStateToProps, mapDispatchToProps)(CollectionList)
);