-
-
{
- this.node = node;
- }}
- className="overlay__body"
- >
-
- {title}
-
- {actions}
-
-
-
-
-
- {children}
-
-
+ useModalClose(close, ref);
+
+ return (
+
+
+
+
+ {title}
+
+ {actions}
+
+
+
+
+
+ {children}
+
- );
- }
-}
+
+ );
+};
Overlay.propTypes = {
children: PropTypes.element,
@@ -103,9 +77,7 @@ Overlay.propTypes = {
closeOverlay: PropTypes.func,
title: PropTypes.string,
ariaLabel: PropTypes.string,
- previousPath: PropTypes.string,
- isFixedHeight: PropTypes.bool,
- t: PropTypes.func.isRequired
+ isFixedHeight: PropTypes.bool
};
Overlay.defaultProps = {
@@ -114,8 +86,7 @@ Overlay.defaultProps = {
title: 'Modal',
closeOverlay: null,
ariaLabel: 'modal',
- previousPath: '/',
isFixedHeight: false
};
-export default withTranslation()(Overlay);
+export default Overlay;
diff --git a/client/modules/IDE/actions/collections.js b/client/modules/IDE/actions/collections.js
index 5a9218520b..dbd2cdae48 100644
--- a/client/modules/IDE/actions/collections.js
+++ b/client/modules/IDE/actions/collections.js
@@ -6,7 +6,6 @@ import { setToastText, showToast } from './toast';
const TOAST_DISPLAY_TIME_MS = 1500;
-// eslint-disable-next-line
export function getCollections(username) {
return (dispatch) => {
dispatch(startLoader());
@@ -16,8 +15,7 @@ export function getCollections(username) {
} else {
url = '/collections';
}
- console.log(url);
- apiClient
+ return apiClient
.get(url)
.then((response) => {
dispatch({
@@ -27,10 +25,9 @@ export function getCollections(username) {
dispatch(stopLoader());
})
.catch((error) => {
- const { response } = error;
dispatch({
type: ActionTypes.ERROR,
- error: response.data
+ error: error?.response?.data
});
dispatch(stopLoader());
});
diff --git a/client/modules/IDE/actions/projects.js b/client/modules/IDE/actions/projects.js
index 4072429af4..eb9984cf54 100644
--- a/client/modules/IDE/actions/projects.js
+++ b/client/modules/IDE/actions/projects.js
@@ -22,10 +22,9 @@ export function getProjects(username) {
dispatch(stopLoader());
})
.catch((error) => {
- const { response } = error;
dispatch({
type: ActionTypes.ERROR,
- error: response.data
+ error: error?.response?.data
});
dispatch(stopLoader());
});
diff --git a/client/modules/IDE/actions/sorting.js b/client/modules/IDE/actions/sorting.js
index b9aa0354cb..07e040b896 100644
--- a/client/modules/IDE/actions/sorting.js
+++ b/client/modules/IDE/actions/sorting.js
@@ -5,27 +5,6 @@ export const DIRECTION = {
DESC: 'DESCENDING'
};
-export function setSorting(field, direction) {
- return {
- type: ActionTypes.SET_SORTING,
- payload: {
- field,
- direction
- }
- };
-}
-
-export function resetSorting() {
- return setSorting('createdAt', DIRECTION.DESC);
-}
-
-export function toggleDirectionForField(field) {
- return {
- type: ActionTypes.TOGGLE_DIRECTION,
- field
- };
-}
-
export function setSearchTerm(scope, searchTerm) {
return {
type: ActionTypes.SET_SEARCH_TERM,
diff --git a/client/modules/IDE/components/AddToCollectionList.jsx b/client/modules/IDE/components/AddToCollectionList.jsx
index fc5c161fdc..97f9e82c9c 100644
--- a/client/modules/IDE/components/AddToCollectionList.jsx
+++ b/client/modules/IDE/components/AddToCollectionList.jsx
@@ -9,7 +9,6 @@ import * as ProjectActions from '../actions/project';
import * as ProjectsActions from '../actions/projects';
import * as CollectionsActions from '../actions/collections';
import * as ToastActions from '../actions/toast';
-import * as SortingActions from '../actions/sorting';
import getSortedCollections from '../selectors/collections';
import Loader from '../../App/components/loader';
import QuickAddList from './QuickAddList';
@@ -170,7 +169,6 @@ function mapStateToProps(state, ownProps) {
return {
user: state.user,
collections: getSortedCollections(state),
- sorting: state.sorting,
loading: state.loading,
project: ownProps.project || state.project,
projectId: ownProps && ownProps.params ? ownProps.prams.project_id : null
@@ -184,8 +182,7 @@ function mapDispatchToProps(dispatch) {
CollectionsActions,
ProjectsActions,
ProjectActions,
- ToastActions,
- SortingActions
+ ToastActions
),
dispatch
);
diff --git a/client/modules/IDE/components/AddToCollectionSketchList.jsx b/client/modules/IDE/components/AddToCollectionSketchList.jsx
index f6cdf3abbf..523766c5e5 100644
--- a/client/modules/IDE/components/AddToCollectionSketchList.jsx
+++ b/client/modules/IDE/components/AddToCollectionSketchList.jsx
@@ -8,7 +8,6 @@ import { withTranslation } from 'react-i18next';
import * as ProjectsActions from '../actions/projects';
import * as CollectionsActions from '../actions/collections';
import * as ToastActions from '../actions/toast';
-import * as SortingActions from '../actions/sorting';
import getSortedSketches from '../selectors/projects';
import Loader from '../../App/components/loader';
import QuickAddList from './QuickAddList';
@@ -124,10 +123,6 @@ SketchList.propTypes = {
}).isRequired,
username: PropTypes.string,
loading: PropTypes.bool.isRequired,
- sorting: PropTypes.shape({
- field: PropTypes.string.isRequired,
- direction: PropTypes.string.isRequired
- }).isRequired,
addToCollection: PropTypes.func.isRequired,
removeFromCollection: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
@@ -141,7 +136,6 @@ function mapStateToProps(state) {
return {
user: state.user,
sketches: getSortedSketches(state),
- sorting: state.sorting,
loading: state.loading,
project: state.project
};
@@ -149,13 +143,7 @@ function mapStateToProps(state) {
function mapDispatchToProps(dispatch) {
return bindActionCreators(
- Object.assign(
- {},
- ProjectsActions,
- CollectionsActions,
- ToastActions,
- SortingActions
- ),
+ Object.assign({}, ProjectsActions, CollectionsActions, ToastActions),
dispatch
);
}
diff --git a/client/modules/IDE/components/AssetList.jsx b/client/modules/IDE/components/AssetList.jsx
index 559f60c580..eda07e36b4 100644
--- a/client/modules/IDE/components/AssetList.jsx
+++ b/client/modules/IDE/components/AssetList.jsx
@@ -1,129 +1,66 @@
+import prettyBytes from 'pretty-bytes';
import PropTypes from 'prop-types';
-import React from 'react';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-import { Link } from 'react-router-dom';
+import React, { useEffect, useMemo } from 'react';
import { Helmet } from 'react-helmet';
-import prettyBytes from 'pretty-bytes';
-import { withTranslation } from 'react-i18next';
-
-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 });
- };
-
- onBlurComponent = () => {
- this.setState({ isFocused: false });
- setTimeout(() => {
- if (!this.state.isFocused) {
- this.closeOptions();
- }
- }, 200);
- };
+import { useTranslation } from 'react-i18next';
+import { connect, useDispatch, useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
+import TableBase from '../../../common/Table/TableBase';
+import MenuItem from '../../../components/Dropdown/MenuItem';
+import TableDropdown from '../../../components/Dropdown/TableDropdown';
+import { deleteAssetRequest, getAssets } from '../actions/assets';
+import { DIRECTION } from '../actions/sorting';
- openOptions = () => {
- this.setState({
- optionsOpen: true
- });
- };
+const AssetMenu = ({ item: asset }) => {
+ const { t } = useTranslation();
- closeOptions = () => {
- this.setState({
- optionsOpen: false
- });
- };
+ const dispatch = useDispatch();
- toggleOptions = () => {
- if (this.state.optionsOpen) {
- this.closeOptions();
- } else {
- this.openOptions();
+ const handleAssetDelete = () => {
+ const { key, name } = asset;
+ if (window.confirm(t('Common.DeleteConfirmation', { name }))) {
+ dispatch(deleteAssetRequest(key));
}
};
- handleDropdownOpen = () => {
- this.closeOptions();
- this.openOptions();
- };
+ return (
+
+ {t('AssetList.Delete')}
+
+ {t('AssetList.OpenNewTab')}
+
+
+ );
+};
- handleAssetDelete = () => {
- const { key, name } = this.props.asset;
- this.closeOptions();
- if (window.confirm(this.props.t('Common.DeleteConfirmation', { name }))) {
- this.props.deleteAssetRequest(key);
- }
- };
+AssetMenu.propTypes = {
+ item: PropTypes.shape({
+ key: PropTypes.string.isRequired,
+ url: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired
+ }).isRequired
+};
- render() {
- const { asset, username, t } = this.props;
- const { optionsOpen } = this.state;
- return (
-
-
-
- {asset.name}
-
-
- {prettyBytes(asset.size)}
-
- {asset.sketchId && (
-
- {asset.sketchName}
-
- )}
-
-
-
-
-
- {optionsOpen && (
-
-
-
- {t('AssetList.Delete')}
-
-
-
-
- {t('AssetList.OpenNewTab')}
-
-
-
- )}
-
-
- );
- }
-}
+const AssetListRowBase = ({ asset, username }) => (
+
+
+
+ {asset.name}
+
+
+ {prettyBytes(asset.size)}
+
+ {asset.sketchId && (
+
+ {asset.sketchName}
+
+ )}
+
+
+
+
+
+);
AssetListRowBase.propTypes = {
asset: PropTypes.shape({
@@ -134,9 +71,7 @@ AssetListRowBase.propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.number.isRequired
}).isRequired,
- deleteAssetRequest: PropTypes.func.isRequired,
- username: PropTypes.string.isRequired,
- t: PropTypes.func.isRequired
+ username: PropTypes.string.isRequired
};
function mapStateToPropsAssetListRow(state) {
@@ -145,106 +80,67 @@ function mapStateToPropsAssetListRow(state) {
};
}
-function mapDispatchToPropsAssetListRow(dispatch) {
- return bindActionCreators(AssetActions, dispatch);
-}
-
-const AssetListRow = connect(
- mapStateToPropsAssetListRow,
- mapDispatchToPropsAssetListRow
-)(AssetListRowBase);
-
-class AssetList extends React.Component {
- constructor(props) {
- super(props);
- this.props.getAssets();
- }
-
- getAssetsTitle() {
- return this.props.t('AssetList.Title');
- }
-
- hasAssets() {
- return !this.props.loading && this.props.assetList.length > 0;
- }
-
- renderLoader() {
- if (this.props.loading) return
;
- return null;
- }
-
- renderEmptyTable() {
- if (!this.props.loading && this.props.assetList.length === 0) {
- return (
-
- {this.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 AssetListRow = connect(mapStateToPropsAssetListRow, {
+ deleteAssetRequest
+})(AssetListRowBase);
+
+const AssetList = () => {
+ const { t } = useTranslation();
+
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(getAssets());
+ }, []);
+
+ const isLoading = useSelector((state) => state.loading);
+
+ const assetList = useSelector((state) => state.assets.list);
+
+ const items = useMemo(
+ // This is a hack to use the order from the API as the initial sort
+ () => assetList?.map((asset, i) => ({ ...asset, index: i, id: asset.key })),
+ [assetList]
+ );
+
+ return (
+
+
+ {t('AssetList.Title')}
+
+ (
+
)}
-
- );
- }
-}
-
-AssetList.propTypes = {
- user: PropTypes.shape({
- username: PropTypes.string
- }).isRequired,
- assetList: PropTypes.arrayOf(
- PropTypes.shape({
- key: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- url: PropTypes.string.isRequired,
- sketchName: PropTypes.string,
- sketchId: PropTypes.string
- })
- ).isRequired,
- getAssets: PropTypes.func.isRequired,
- loading: PropTypes.bool.isRequired,
- t: PropTypes.func.isRequired
+ />
+
+ );
};
-function mapStateToProps(state) {
- return {
- user: state.user,
- assetList: state.assets.list,
- loading: state.loading
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return bindActionCreators(Object.assign({}, AssetActions), dispatch);
-}
-
-export default withTranslation()(
- connect(mapStateToProps, mapDispatchToProps)(AssetList)
-);
+export default AssetList;
diff --git a/client/modules/IDE/components/CollectionList/CollectionList.jsx b/client/modules/IDE/components/CollectionList/CollectionList.jsx
index 646b9824b5..38a3dae8e8 100644
--- a/client/modules/IDE/components/CollectionList/CollectionList.jsx
+++ b/client/modules/IDE/components/CollectionList/CollectionList.jsx
@@ -1,314 +1,130 @@
+import find from 'lodash/find';
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
-import { withTranslation } from 'react-i18next';
-import { connect } from 'react-redux';
-import { bindActionCreators } from 'redux';
-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 ToastActions from '../../actions/toast';
-import * as SortingActions from '../../actions/sorting';
-import getSortedCollections from '../../selectors/collections';
-import Loader from '../../../App/components/loader';
+import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
+import TableBase from '../../../../common/Table/TableBase';
import Overlay from '../../../App/components/Overlay';
+import { getCollections } from '../../actions/collections';
+import { DIRECTION } from '../../actions/sorting';
+import getSortedCollections from '../../selectors/collections';
+import { selectCurrentUsername } from '../../selectors/users';
import AddToCollectionSketchList from '../AddToCollectionSketchList';
import { SketchSearchbar } from '../Searchbar';
import CollectionListRow from './CollectionListRow';
-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 = {
- hasLoadedData: false,
- addingSketchesToCollectionId: null
- };
- }
-
- 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
- });
- }
- }
-
- 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
- });
- }
-
- showAddSketches = (collectionId) => {
- this.setState({
- addingSketchesToCollectionId: collectionId
- });
- };
-
- hideAddSketches = () => {
- this.setState({
- addingSketchesToCollectionId: null
- });
- };
-
- hasCollections() {
- return (
- (!this.props.loading || this.state.hasLoadedData) &&
- this.props.collections.length > 0
- );
- }
-
- _renderLoader() {
- if (this.props.loading && !this.state.hasLoadedData) return
;
- return null;
- }
-
- _renderEmptyTable() {
- if (!this.props.loading && this.props.collections.length === 0) {
- return (
-
- {this.props.t('CollectionList.NoCollections')}
-
- );
- }
- return null;
- }
-
- _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;
- };
-
- _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 (
-
- this.props.toggleDirectionForField(fieldName)}
- aria-label={buttonLabel}
- >
- {displayName}
- {field === fieldName &&
- direction === SortingActions.DIRECTION.ASC && (
-
- )}
- {field === fieldName &&
- direction === SortingActions.DIRECTION.DESC && (
-
- )}
-
-
- );
- };
-
- render() {
- const username =
- this.props.username !== undefined
- ? this.props.username
- : this.props.user.username;
- const { mobile } = this.props;
-
- return (
-
-
- {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
- >
- {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
+
+ const collections = useSelector(getSortedCollections);
+
+ // TODO: combine with AddToCollectionList
+ const loading = useSelector((state) => state.loading);
+ const [hasLoadedData, setHasLoadedData] = useState(false);
+ const showLoader = loading && !hasLoadedData;
+
+ useEffect(() => {
+ dispatch(getCollections(username)).then(() => setHasLoadedData(true));
+ }, [dispatch, username]);
+
+ const currentUser = useSelector(selectCurrentUsername);
+ const userIsOwner = username === currentUser;
+
+ const [
+ addingSketchesToCollectionId,
+ setAddingSketchesToCollectionId
+ ] = useState(null);
+
+ return (
+
+
+
+ {userIsOwner
+ ? t('CollectionList.Title')
+ : t('CollectionList.AnothersTitle', {
+ anotheruser: username
})}
- />
-
+
+
+
+ (
+ setAddingSketchesToCollectionId(collection.id)}
+ />
)}
-
- );
- }
-}
+ />
+ {addingSketchesToCollectionId && (
+ }
+ closeOverlay={() => setAddingSketchesToCollectionId(null)}
+ isFixedHeight
+ >
+
+
+ )}
+
+ );
+};
CollectionList.propTypes = {
- user: PropTypes.shape({
- username: PropTypes.string,
- authenticated: PropTypes.bool.isRequired
- }).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,
+ username: PropTypes.string.isRequired,
mobile: PropTypes.bool
};
CollectionList.defaultProps = {
- projectId: undefined,
- project: {
- id: undefined,
- owner: undefined
- },
- username: undefined,
mobile: false
};
-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,
- ToastActions,
- SortingActions
- ),
- dispatch
- );
-}
-
-export default withTranslation()(
- connect(mapStateToProps, mapDispatchToProps)(CollectionList)
-);
+export default CollectionList;
diff --git a/client/modules/IDE/components/CollectionList/CollectionListRow.jsx b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx
index ed109141d7..4e401b316c 100644
--- a/client/modules/IDE/components/CollectionList/CollectionListRow.jsx
+++ b/client/modules/IDE/components/CollectionList/CollectionListRow.jsx
@@ -4,84 +4,35 @@ import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { withTranslation } from 'react-i18next';
+import MenuItem from '../../../../components/Dropdown/MenuItem';
+import TableDropdown from '../../../../components/Dropdown/TableDropdown';
import * as ProjectActions from '../../actions/project';
import * as CollectionsActions from '../../actions/collections';
import * as IdeActions from '../../actions/ide';
import * as ToastActions from '../../actions/toast';
import dates from '../../../../utils/formatDate';
-import DownFilledTriangleIcon from '../../../../images/down-filled-triangle.svg';
-
class CollectionListRowBase extends React.Component {
- static projectInCollection(project, collection) {
- return (
- collection.items.find((item) => item.project.id === project.id) != null
- );
- }
-
constructor(props) {
super(props);
this.state = {
- optionsOpen: false,
- isFocused: false,
renameOpen: false,
renameValue: ''
};
this.renameInput = React.createRef();
}
- onFocusComponent = () => {
- this.setState({ isFocused: true });
- };
-
- onBlurComponent = () => {
- this.setState({ isFocused: false });
- setTimeout(() => {
- if (!this.state.isFocused) {
- this.closeAll();
- }
- }, 200);
- };
-
- openOptions = () => {
- this.setState({
- optionsOpen: true
- });
- };
-
- closeOptions = () => {
- this.setState({
- optionsOpen: false
- });
- };
-
- toggleOptions = () => {
- if (this.state.optionsOpen) {
- this.closeOptions();
- } else {
- this.openOptions();
- }
- };
-
closeAll = () => {
this.setState({
- optionsOpen: false,
renameOpen: false
});
};
handleAddSketches = () => {
- this.closeAll();
this.props.onAddSketches();
};
- handleDropdownOpen = () => {
- this.closeAll();
- this.openOptions();
- };
-
handleCollectionDelete = () => {
- this.closeAll();
if (
window.confirm(
this.props.t('Common.DeleteConfirmation', {
@@ -94,7 +45,6 @@ class CollectionListRowBase extends React.Component {
};
handleRenameOpen = () => {
- this.closeAll();
this.setState(
{
renameOpen: true,
@@ -132,61 +82,24 @@ class CollectionListRowBase extends React.Component {
};
renderActions = () => {
- const { optionsOpen } = this.state;
- const userIsOwner = this.props.user.username === this.props.username;
+ const { userIsOwner } = this.props;
return (
-
-
-
-
- {optionsOpen && (
-
-
-
- {this.props.t('CollectionListRow.AddSketch')}
-
-
- {userIsOwner && (
-
-
- {this.props.t('CollectionListRow.Delete')}
-
-
- )}
- {userIsOwner && (
-
-
- {this.props.t('CollectionListRow.Rename')}
-
-
- )}
-
+
+ >
+
+ {this.props.t('CollectionListRow.AddSketch')}
+
+
+ {this.props.t('CollectionListRow.Delete')}
+
+
+ {this.props.t('CollectionListRow.Rename')}
+
+
);
};
@@ -264,10 +177,7 @@ CollectionListRowBase.propTypes = {
)
}).isRequired,
username: PropTypes.string.isRequired,
- user: PropTypes.shape({
- username: PropTypes.string,
- authenticated: PropTypes.bool.isRequired
- }).isRequired,
+ userIsOwner: PropTypes.bool.isRequired,
deleteCollection: PropTypes.func.isRequired,
editCollection: PropTypes.func.isRequired,
onAddSketches: PropTypes.func.isRequired,
diff --git a/client/modules/IDE/components/Modal.jsx b/client/modules/IDE/components/Modal.jsx
index 5d1bbc88e0..831527b266 100644
--- a/client/modules/IDE/components/Modal.jsx
+++ b/client/modules/IDE/components/Modal.jsx
@@ -1,6 +1,7 @@
import classNames from 'classnames';
import PropTypes from 'prop-types';
-import React, { useEffect, useRef } from 'react';
+import React from 'react';
+import useModalClose from '../../../common/useModalClose';
import ExitIcon from '../../../images/exit.svg';
// Common logic from NewFolderModal, NewFileModal, UploadFileModal
@@ -12,23 +13,7 @@ const Modal = ({
contentClassName,
children
}) => {
- const modalRef = useRef(null);
-
- const handleOutsideClick = (e) => {
- // ignore clicks on the component itself
- if (modalRef.current?.contains?.(e.target)) return;
-
- onClose();
- };
-
- useEffect(() => {
- modalRef.current?.focus();
- document.addEventListener('click', handleOutsideClick, false);
-
- return () => {
- document.removeEventListener('click', handleOutsideClick, false);
- };
- }, []);
+ const modalRef = useModalClose(onClose);
return (
diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx
index af03453e0d..2512553047 100644
--- a/client/modules/IDE/components/SketchList.jsx
+++ b/client/modules/IDE/components/SketchList.jsx
@@ -1,149 +1,58 @@
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
-import { withTranslation } from 'react-i18next';
-import { connect } from 'react-redux';
+import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
-import { bindActionCreators } from 'redux';
-import classNames from 'classnames';
import slugify from 'slugify';
+
+import TableWithRename from '../../../common/Table/TableWithRename';
+import MenuItem from '../../../components/Dropdown/MenuItem';
+import TableDropdown from '../../../components/Dropdown/TableDropdown';
import dates from '../../../utils/formatDate';
-import * as ProjectActions from '../actions/project';
-import * as ProjectsActions from '../actions/projects';
-import * as CollectionsActions from '../actions/collections';
-import * as ToastActions from '../actions/toast';
-import * as SortingActions from '../actions/sorting';
-import * as IdeActions from '../actions/ide';
-import getSortedSketches from '../selectors/projects';
-import Loader from '../../App/components/loader';
import Overlay from '../../App/components/Overlay';
+import {
+ changeProjectName,
+ cloneProject,
+ deleteProject
+} from '../actions/project';
+import { getProjects } from '../actions/projects';
+import { DIRECTION } from '../actions/sorting';
+import getSortedSketches from '../selectors/projects';
+import { getAuthenticated, selectCurrentUsername } from '../selectors/users';
import AddToCollectionList from './AddToCollectionList';
import getConfig from '../../../utils/getConfig';
-import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
-import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
-import DownFilledTriangleIcon from '../../../images/down-filled-triangle.svg';
-
const ROOT_URL = getConfig('API_URL');
const formatDateCell = (date, mobile = false) =>
dates.format(date, { showTime: !mobile });
-class SketchListRowBase extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- optionsOpen: false,
- renameOpen: false,
- renameValue: props.sketch.name,
- isFocused: false
- };
- this.renameInput = React.createRef();
+// TODO: move to a util file and use this in share modals.
+const sketchUrl = (sketch, username) => {
+ if (username === 'p5') {
+ return `/${username}/sketches/${slugify(sketch.name, '_')}`;
}
+ return `/${username}/sketches/${sketch.id}`;
+};
- onFocusComponent = () => {
- this.setState({ isFocused: true });
- };
-
- onBlurComponent = () => {
- this.setState({ isFocused: false });
- setTimeout(() => {
- if (!this.state.isFocused) {
- this.closeAll();
- }
- }, 200);
- };
-
- openOptions = () => {
- this.setState({
- optionsOpen: true
- });
- };
-
- closeOptions = () => {
- this.setState({
- optionsOpen: false
- });
- };
-
- toggleOptions = () => {
- if (this.state.optionsOpen) {
- this.closeOptions();
- } else {
- this.openOptions();
- }
- };
-
- openRename = () => {
- this.setState(
- {
- renameOpen: true,
- renameValue: this.props.sketch.name
- },
- () => this.renameInput.current.focus()
- );
- };
-
- closeRename = () => {
- this.setState({
- renameOpen: false
- });
- };
-
- closeAll = () => {
- this.setState({
- renameOpen: false,
- optionsOpen: false
- });
- };
-
- handleRenameChange = (e) => {
- this.setState({
- renameValue: e.target.value
- });
- };
+const SketchDropdown = ({
+ row: sketch,
+ onClickRename,
+ ownerUsername,
+ setSketchToAddToCollection
+}) => {
+ const { t } = useTranslation();
- handleRenameEnter = (e) => {
- if (e.key === 'Enter') {
- this.updateName();
- this.closeAll();
- }
- };
+ const dispatch = useDispatch();
- handleRenameBlur = () => {
- this.updateName();
- this.closeAll();
- };
+ const isAuthenticated = useSelector(getAuthenticated);
- updateName = () => {
- const isValid = this.state.renameValue.trim().length !== 0;
- if (isValid) {
- this.props.changeProjectName(
- this.props.sketch.id,
- this.state.renameValue.trim()
- );
- }
- };
+ const currentUser = useSelector((state) => state.user.username);
- resetSketchName = () => {
- this.setState({
- renameValue: this.props.sketch.name,
- renameOpen: false
- });
- };
-
- handleDropdownOpen = () => {
- this.closeAll();
- this.openOptions();
- };
+ const userIsOwner = ownerUsername === currentUser;
- handleRenameOpen = () => {
- this.closeAll();
- this.openRename();
- };
-
- handleSketchDownload = () => {
- const { sketch } = this.props;
+ const handleSketchDownload = () => {
const downloadLink = document.createElement('a');
downloadLink.href = `${ROOT_URL}/projects/${sketch.id}/zip`;
downloadLink.download = `${sketch.name}.zip`;
@@ -152,464 +61,174 @@ class SketchListRowBase extends React.Component {
document.body.removeChild(downloadLink);
};
- handleSketchDuplicate = () => {
- this.closeAll();
- this.props.cloneProject(this.props.sketch);
+ const handleSketchDuplicate = () => {
+ dispatch(cloneProject(sketch));
};
- handleSketchShare = () => {
- this.closeAll();
- this.props.showShareModal(
- this.props.sketch.id,
- this.props.sketch.name,
- this.props.username
- );
- };
-
- handleSketchDelete = () => {
- this.closeAll();
+ const handleSketchDelete = () => {
if (
window.confirm(
- this.props.t('Common.DeleteConfirmation', {
- name: this.props.sketch.name
+ t('Common.DeleteConfirmation', {
+ name: sketch.name
})
)
) {
- this.props.deleteProject(this.props.sketch.id);
+ dispatch(deleteProject(sketch.id));
}
};
- renderViewButton = (sketchURL) => (
-
- {this.props.t('SketchList.View')}
-
+ return (
+
+
+ {t('SketchList.DropdownRename')}
+
+
+ {t('SketchList.DropdownDownload')}
+
+
+ {t('SketchList.DropdownDuplicate')}
+
+ {
+ setSketchToAddToCollection(sketch);
+ }}
+ >
+ {t('SketchList.DropdownAddToCollection')}
+
+
+ {/*
+
+ Share
+
+ */}
+
+ {t('SketchList.DropdownDelete')}
+
+
);
+};
- renderDropdown = () => {
- const { optionsOpen } = this.state;
- const userIsOwner = this.props.user.username === this.props.username;
-
- return (
-
-
-
-
- {optionsOpen && (
-
- {userIsOwner && (
-
-
- {this.props.t('SketchList.DropdownRename')}
-
-
- )}
-
-
- {this.props.t('SketchList.DropdownDownload')}
-
-
- {this.props.user.authenticated && (
-
-
- {this.props.t('SketchList.DropdownDuplicate')}
-
-
- )}
- {this.props.user.authenticated && (
-
- {
- this.props.onAddToCollection();
- this.closeAll();
- }}
- onBlur={this.onBlurComponent}
- onFocus={this.onFocusComponent}
- >
- {this.props.t('SketchList.DropdownAddToCollection')}
-
-
- )}
- {/*
-
- Share
-
- */}
- {userIsOwner && (
-
-
- {this.props.t('SketchList.DropdownDelete')}
-
-
- )}
-
- )}
-
- );
- };
-
- render() {
- const { sketch, username, mobile } = this.props;
- const { renameOpen, renameValue } = this.state;
- let url = `/${username}/sketches/${sketch.id}`;
- if (username === 'p5') {
- url = `/${username}/sketches/${slugify(sketch.name, '_')}`;
- }
-
- const name = (
-
- {renameOpen ? '' : sketch.name}
- {renameOpen && (
- e.stopPropagation()}
- ref={this.renameInput}
- />
- )}
-
- );
-
- return (
-
-
- {name}
-
- {mobile && 'Created: '}
- {formatDateCell(sketch.createdAt, mobile)}
-
-
- {mobile && 'Updated: '}
- {formatDateCell(sketch.updatedAt, mobile)}
-
- {this.renderDropdown()}
-
-
- );
- }
-}
-
-SketchListRowBase.propTypes = {
- sketch: PropTypes.shape({
+SketchDropdown.propTypes = {
+ row: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
createdAt: PropTypes.string.isRequired,
updatedAt: PropTypes.string.isRequired
}).isRequired,
- username: PropTypes.string.isRequired,
- user: PropTypes.shape({
- username: PropTypes.string,
- authenticated: PropTypes.bool.isRequired
- }).isRequired,
- deleteProject: PropTypes.func.isRequired,
- showShareModal: PropTypes.func.isRequired,
- cloneProject: PropTypes.func.isRequired,
- changeProjectName: PropTypes.func.isRequired,
- onAddToCollection: PropTypes.func.isRequired,
- mobile: PropTypes.bool,
- t: PropTypes.func.isRequired
-};
-
-SketchListRowBase.defaultProps = {
- mobile: false
+ ownerUsername: PropTypes.string.isRequired,
+ onClickRename: PropTypes.func.isRequired,
+ setSketchToAddToCollection: PropTypes.func.isRequired
};
-function mapDispatchToPropsSketchListRow(dispatch) {
- return bindActionCreators(
- Object.assign({}, ProjectActions, IdeActions),
- dispatch
- );
-}
-
-const SketchListRow = connect(
- null,
- mapDispatchToPropsSketchListRow
-)(SketchListRowBase);
-
-class SketchList extends React.Component {
- constructor(props) {
- super(props);
- this.props.getProjects(this.props.username);
- this.props.resetSorting();
-
- this.state = {
- isInitialDataLoad: true
- };
- }
-
- componentDidUpdate(prevProps) {
- if (
- this.props.sketches !== prevProps.sketches &&
- Array.isArray(this.props.sketches)
- ) {
- // eslint-disable-next-line react/no-did-update-set-state
- this.setState({
- isInitialDataLoad: false
- });
- }
- }
+const SketchList = ({ username, mobile }) => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
- getSketchesTitle() {
- if (this.props.username === this.props.user.username) {
- return this.props.t('SketchList.Title');
- }
- return this.props.t('SketchList.AnothersTitle', {
- anotheruser: this.props.username
- });
- }
+ const currentUser = useSelector(selectCurrentUsername);
- hasSketches() {
- return !this.isLoading() && this.props.sketches.length > 0;
- }
+ const sketches = useSelector(getSortedSketches);
- isLoading() {
- return this.props.loading && this.state.isInitialDataLoad;
- }
+ // TODO: combine with AddToCollectionSketchList
+ const loading = useSelector((state) => state.loading);
+ const [hasLoadedData, setHasLoadedData] = useState(false);
+ const showLoader = loading && !hasLoadedData;
- _renderLoader() {
- if (this.isLoading()) return ;
- return null;
- }
+ useEffect(() => {
+ dispatch(getProjects(username)).then(() => setHasLoadedData(true));
+ }, [dispatch, username]);
- _renderEmptyTable() {
- if (!this.isLoading() && this.props.sketches.length === 0) {
- return (
-
- {this.props.t('SketchList.NoSketches')}
-
- );
- }
- return null;
- }
+ const [sketchToAddToCollection, setSketchToAddToCollection] = useState(null);
- _getButtonLabel = (fieldName, displayName) => {
- const { field, direction } = this.props.sorting;
- let buttonLabel;
- if (field !== fieldName) {
- if (field === 'name') {
- buttonLabel = this.props.t('SketchList.ButtonLabelAscendingARIA', {
- displayName
- });
- } else {
- buttonLabel = this.props.t('SketchList.ButtonLabelDescendingARIA', {
- displayName
- });
- }
- } else if (direction === SortingActions.DIRECTION.ASC) {
- buttonLabel = this.props.t('SketchList.ButtonLabelDescendingARIA', {
- displayName
- });
- } else {
- buttonLabel = this.props.t('SketchList.ButtonLabelAscendingARIA', {
- displayName
- });
+ const handleRename = (newName, sketchId) => {
+ const isValid = newName.trim().length !== 0;
+ if (isValid) {
+ dispatch(changeProjectName(sketchId, newName.trim()));
}
- return buttonLabel;
};
- _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 (
-
- this.props.toggleDirectionForField(fieldName)}
- aria-label={buttonLabel}
+ return (
+
+
+
+ {username === currentUser
+ ? t('SketchList.Title')
+ : t('SketchList.AnothersTitle', {
+ anotheruser: username
+ })}
+
+
+ (
+ {name}
+ )
+ },
+ {
+ field: 'createdAt',
+ defaultOrder: DIRECTION.DESC,
+ title: t('SketchList.HeaderCreatedAt', {
+ context: mobile ? 'mobile' : ''
+ }),
+ formatValue: (value) =>
+ (mobile ? 'Created: ' : '') + formatDateCell(value, mobile)
+ },
+ {
+ field: 'updatedAt',
+ defaultOrder: DIRECTION.DESC,
+ title: t('SketchList.HeaderUpdatedAt', {
+ context: mobile ? 'mobile' : ''
+ }),
+ formatValue: (value) =>
+ (mobile ? 'Updated: ' : '') + formatDateCell(value, mobile)
+ }
+ ]}
+ addDropdownColumn
+ initialSort={{
+ field: 'createdAt',
+ direction: DIRECTION.DESC
+ }}
+ emptyMessage={t('SketchList.NoSketches')}
+ caption={t('SketchList.TableSummary')}
+ // TODO: figure out how to use the StandardTable -- needs dropdown and styling
+ handleRename={handleRename}
+ Dropdown={SketchDropdown}
+ dropdownProps={{
+ setSketchToAddToCollection,
+ ownerUsername: username
+ }}
+ />
+ {sketchToAddToCollection && (
+ {
+ setSketchToAddToCollection(null);
+ }}
>
- {displayName}
- {field === fieldName &&
- direction === SortingActions.DIRECTION.ASC && (
-
- )}
- {field === fieldName &&
- direction === SortingActions.DIRECTION.DESC && (
-
- )}
-
-
- );
- };
-
- render() {
- const username =
- this.props.username !== undefined
- ? this.props.username
- : this.props.user.username;
- const { mobile } = this.props;
- return (
-
-
- {this.getSketchesTitle()}
-
- {this._renderLoader()}
- {this._renderEmptyTable()}
- {this.hasSketches() && (
-
-
-
- {this._renderFieldHeader(
- 'name',
- this.props.t('SketchList.HeaderName')
- )}
- {this._renderFieldHeader(
- 'createdAt',
- this.props.t('SketchList.HeaderCreatedAt', {
- context: mobile ? 'mobile' : ''
- })
- )}
- {this._renderFieldHeader(
- 'updatedAt',
- this.props.t('SketchList.HeaderUpdatedAt', {
- context: mobile ? 'mobile' : ''
- })
- )}
-
-
-
-
- {this.props.sketches.map((sketch) => (
- {
- this.setState({ sketchToAddToCollection: sketch });
- }}
- t={this.props.t}
- />
- ))}
-
-
- )}
- {this.state.sketchToAddToCollection && (
-
- this.setState({ sketchToAddToCollection: null })
- }
- >
-
-
- )}
-
- );
- }
-}
+
+
+ )}
+
+ );
+};
SketchList.propTypes = {
- user: PropTypes.shape({
- username: PropTypes.string,
- authenticated: PropTypes.bool.isRequired
- }).isRequired,
- getProjects: PropTypes.func.isRequired,
- sketches: PropTypes.arrayOf(
- PropTypes.shape({
- id: PropTypes.string.isRequired,
- name: PropTypes.string.isRequired,
- 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,
- mobile: PropTypes.bool,
- t: PropTypes.func.isRequired
+ username: PropTypes.string.isRequired,
+ mobile: PropTypes.bool
};
SketchList.defaultProps = {
- username: undefined,
mobile: false
};
-function mapStateToProps(state) {
- return {
- user: state.user,
- sketches: getSortedSketches(state),
- sorting: state.sorting,
- loading: state.loading,
- project: state.project
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return bindActionCreators(
- Object.assign(
- {},
- ProjectsActions,
- CollectionsActions,
- ToastActions,
- SortingActions
- ),
- dispatch
- );
-}
-
-export default withTranslation()(
- connect(mapStateToProps, mapDispatchToProps)(SketchList)
-);
+export default SketchList;
diff --git a/client/modules/IDE/components/SketchList.unit.test.jsx b/client/modules/IDE/components/SketchList.unit.test.jsx
index 162d12bcc1..b72dcaeb3f 100644
--- a/client/modules/IDE/components/SketchList.unit.test.jsx
+++ b/client/modules/IDE/components/SketchList.unit.test.jsx
@@ -44,17 +44,32 @@ describe(' ', () => {
expect(screen.getByText('testsketch2')).toBeInTheDocument();
});
- it('clicking on date created row header dispatches a reordering action', () => {
+ it('clicking on date created row header sorts the table', () => {
act(() => {
subject();
});
+ expect.assertions(6);
+
+ const rowsBefore = screen.getAllByRole('row');
+ expect(within(rowsBefore[1]).getByText('testsketch1')).toBeInTheDocument();
+ expect(within(rowsBefore[2]).getByText('testsketch2')).toBeInTheDocument();
+
+ expect(
+ screen.getByLabelText(/Sort by Date Created ascending/i)
+ ).toBeInTheDocument();
+
act(() => {
fireEvent.click(screen.getByText(/date created/i));
});
- const expectedAction = [{ type: 'TOGGLE_DIRECTION', field: 'createdAt' }];
- expect(store.getActions()).toEqual(expect.arrayContaining(expectedAction));
+ expect(
+ screen.getByLabelText(/Sort by Date Created descending/i)
+ ).toBeInTheDocument();
+
+ const rowsAfter = screen.getAllByRole('row');
+ expect(within(rowsAfter[1]).getByText('testsketch2')).toBeInTheDocument();
+ expect(within(rowsAfter[2]).getByText('testsketch1')).toBeInTheDocument();
});
it('clicking on dropdown arrow opens sketch options - sketches belong to user', () => {
diff --git a/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap
index bd7475ebf9..46749f7ee3 100644
--- a/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap
+++ b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap
@@ -12,53 +12,59 @@ exports[` snapshot testing 1`] = `
@@ -85,15 +91,19 @@ exports[` snapshot testing 1`] = `
-
-
-
+
+
+
+
snapshot testing 1`] = `
-
-
-
+
+
+
+
diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx
index 19b1da2ce2..4a095aa429 100644
--- a/client/modules/IDE/pages/IDEView.jsx
+++ b/client/modules/IDE/pages/IDEView.jsx
@@ -227,14 +227,6 @@ class IDEView extends React.Component {
} else {
this.props.expandConsole();
}
- } else if (e.keyCode === 27) {
- if (this.props.ide.newFolderModalVisible) {
- this.props.closeNewFolderModal();
- } else if (this.props.ide.uploadFileModalVisible) {
- this.props.closeUploadFileModal();
- } else if (this.props.ide.modalIsVisible) {
- this.props.closeNewFileModal();
- }
}
}
@@ -499,8 +491,6 @@ IDEView.propTypes = {
expandConsole: PropTypes.func.isRequired,
collapseConsole: PropTypes.func.isRequired,
updateFileContent: PropTypes.func.isRequired,
- closeNewFolderModal: PropTypes.func.isRequired,
- closeNewFileModal: PropTypes.func.isRequired,
closeShareModal: PropTypes.func.isRequired,
closeKeyboardShortcutModal: PropTypes.func.isRequired,
autosaveProject: PropTypes.func.isRequired,
@@ -509,7 +499,6 @@ IDEView.propTypes = {
hideErrorModal: PropTypes.func.isRequired,
clearPersistedState: PropTypes.func.isRequired,
startSketch: PropTypes.func.isRequired,
- closeUploadFileModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
isUserOwner: PropTypes.bool.isRequired
};
diff --git a/client/modules/IDE/reducers/sorting.js b/client/modules/IDE/reducers/sorting.js
deleted file mode 100644
index 747d16c80a..0000000000
--- a/client/modules/IDE/reducers/sorting.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import * as ActionTypes from '../../../constants';
-import { DIRECTION } from '../actions/sorting';
-
-const initialState = {
- field: 'createdAt',
- direction: DIRECTION.DESC
-};
-
-const sorting = (state = initialState, action) => {
- switch (action.type) {
- case ActionTypes.TOGGLE_DIRECTION:
- if (action.field && action.field !== state.field) {
- if (action.field === 'name') {
- return { ...state, field: action.field, direction: DIRECTION.ASC };
- }
- return { ...state, field: action.field, direction: DIRECTION.DESC };
- }
- if (state.direction === DIRECTION.ASC) {
- return { ...state, direction: DIRECTION.DESC };
- }
- return { ...state, direction: DIRECTION.ASC };
- case ActionTypes.SET_SORTING:
- return {
- ...state,
- field: action.payload.field,
- direction: action.payload.direction
- };
- default:
- return state;
- }
-};
-
-export default sorting;
diff --git a/client/modules/IDE/selectors/collections.js b/client/modules/IDE/selectors/collections.js
index 207dce39a9..b1be7a20e6 100644
--- a/client/modules/IDE/selectors/collections.js
+++ b/client/modules/IDE/selectors/collections.js
@@ -1,12 +1,7 @@
import { createSelector } from 'reselect';
-import differenceInMilliseconds from 'date-fns/differenceInMilliseconds';
import find from 'lodash/find';
-import orderBy from 'lodash/orderBy';
-import { DIRECTION } from '../actions/sorting';
const getCollections = (state) => state.collections;
-const getField = (state) => state.sorting.field;
-const getDirection = (state) => state.sorting.direction;
const getSearchTerm = (state) => state.search.collectionSearchTerm;
const getFilteredCollections = createSelector(
@@ -31,35 +26,8 @@ const getFilteredCollections = createSelector(
}
);
-const getSortedCollections = createSelector(
- getFilteredCollections,
- getField,
- getDirection,
- (collections, field, direction) => {
- if (field === 'name') {
- if (direction === DIRECTION.DESC) {
- return orderBy(collections, 'name', 'desc');
- }
- return orderBy(collections, 'name', 'asc');
- } else if (field === 'numItems') {
- if (direction === DIRECTION.DESC) {
- return orderBy(collections, 'items.length', 'desc');
- }
- return orderBy(collections, 'items.length', 'asc');
- }
- const sortedCollections = [...collections].sort((a, b) => {
- const result =
- direction === DIRECTION.ASC
- ? differenceInMilliseconds(new Date(a[field]), new Date(b[field]))
- : differenceInMilliseconds(new Date(b[field]), new Date(a[field]));
- return result;
- });
- return sortedCollections;
- }
-);
-
export function getCollection(state, id) {
return find(getCollections(state), { id });
}
-export default getSortedCollections;
+export default getFilteredCollections;
diff --git a/client/modules/IDE/selectors/projects.js b/client/modules/IDE/selectors/projects.js
index 08701d211c..bb76ec1797 100644
--- a/client/modules/IDE/selectors/projects.js
+++ b/client/modules/IDE/selectors/projects.js
@@ -1,11 +1,6 @@
import { createSelector } from 'reselect';
-import differenceInMilliseconds from 'date-fns/differenceInMilliseconds';
-import orderBy from 'lodash/orderBy';
-import { DIRECTION } from '../actions/sorting';
const getSketches = (state) => state.sketches;
-const getField = (state) => state.sorting.field;
-const getDirection = (state) => state.sorting.direction;
const getSearchTerm = (state) => state.search.sketchSearchTerm;
const getFilteredSketches = createSelector(
@@ -30,26 +25,4 @@ const getFilteredSketches = createSelector(
}
);
-const getSortedSketches = createSelector(
- getFilteredSketches,
- getField,
- getDirection,
- (sketches, field, direction) => {
- if (field === 'name') {
- if (direction === DIRECTION.DESC) {
- return orderBy(sketches, 'name', 'desc');
- }
- return orderBy(sketches, 'name', 'asc');
- }
- const sortedSketches = [...sketches].sort((a, b) => {
- const result =
- direction === DIRECTION.ASC
- ? differenceInMilliseconds(new Date(a[field]), new Date(b[field]))
- : differenceInMilliseconds(new Date(b[field]), new Date(a[field]));
- return result;
- });
- return sortedSketches;
- }
-);
-
-export default getSortedSketches;
+export default getFilteredSketches;
diff --git a/client/modules/IDE/selectors/users.js b/client/modules/IDE/selectors/users.js
index 4bda34ef11..bde1d238d6 100644
--- a/client/modules/IDE/selectors/users.js
+++ b/client/modules/IDE/selectors/users.js
@@ -6,6 +6,8 @@ const getTotalSize = (state) => state.user.totalSize;
const getAssetsTotalSize = (state) => state.assets.totalSize;
const getSketchOwner = (state) => state.project.owner;
const getUserId = (state) => state.user.id;
+export const selectCurrentUsername = (state) => state.user.username;
+
const limit = getConfig('UPLOAD_LIMIT') || 250000000;
export const getCanUploadMedia = createSelector(
diff --git a/client/modules/User/components/Collection.jsx b/client/modules/User/components/Collection.jsx
index 7379bd36f1..5d70e2463a 100644
--- a/client/modules/User/components/Collection.jsx
+++ b/client/modules/User/components/Collection.jsx
@@ -1,85 +1,26 @@
import PropTypes from 'prop-types';
-import React, { useState, useRef, useEffect } from 'react';
+import React, { useMemo, useEffect } from 'react';
import { Helmet } from 'react-helmet';
-import { connect } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
-import { bindActionCreators } from 'redux';
-import { useTranslation, withTranslation } from 'react-i18next';
-import classNames from 'classnames';
-
-import Button from '../../../common/Button';
-import { DropdownArrowIcon } from '../../../common/icons';
-import * as ProjectActions from '../../IDE/actions/project';
-import * as ProjectsActions from '../../IDE/actions/projects';
-import * as CollectionsActions from '../../IDE/actions/collections';
-import * as ToastActions from '../../IDE/actions/toast';
-import * as SortingActions from '../../IDE/actions/sorting';
-import * as IdeActions from '../../IDE/actions/ide';
+import { useTranslation } from 'react-i18next';
+import TableBase from '../../../common/Table/TableBase';
+import {
+ getCollections,
+ removeFromCollection
+} from '../../IDE/actions/collections';
+import { DIRECTION } from '../../IDE/actions/sorting';
import { getCollection } from '../../IDE/selectors/collections';
import Loader from '../../App/components/loader';
-import EditableInput from '../../IDE/components/EditableInput';
-import Overlay from '../../App/components/Overlay';
-import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList';
-import CopyableInput from '../../IDE/components/CopyableInput';
-import { SketchSearchbar } from '../../IDE/components/Searchbar';
import dates from '../../../utils/formatDate';
-import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
-import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
import RemoveIcon from '../../../images/close.svg';
+import CollectionMetadata from './CollectionMetadata';
-const ShareURL = ({ value }) => {
- const [showURL, setShowURL] = useState(false);
- const node = useRef();
+const CollectionItemRow = ({ collection, item, isOwner }) => {
const { t } = useTranslation();
- const handleClickOutside = (e) => {
- if (node.current.contains(e.target)) {
- return;
- }
- setShowURL(false);
- };
-
- useEffect(() => {
- if (showURL) {
- document.addEventListener('mousedown', handleClickOutside);
- } else {
- document.removeEventListener('mousedown', handleClickOutside);
- }
-
- return () => {
- document.removeEventListener('mousedown', handleClickOutside);
- };
- }, [showURL]);
-
- return (
-
-
setShowURL(!showURL)}
- iconAfter={ }
- >
- {t('Collection.Share')}
-
- {showURL && (
-
-
-
- )}
-
- );
-};
-
-ShareURL.propTypes = {
- value: PropTypes.string.isRequired
-};
-
-const CollectionItemRowBase = ({
- collection,
- item,
- isOwner,
- removeFromCollection
-}) => {
- const { t } = useTranslation();
+ const dispatch = useDispatch();
const projectIsDeleted = item.isDeleted;
@@ -91,7 +32,7 @@ const CollectionItemRowBase = ({
t('Collection.DeleteFromCollection', { name_sketch: name })
)
) {
- removeFromCollection(collection.id, item.projectId);
+ dispatch(removeFromCollection(collection.id, item.projectId));
}
};
@@ -129,7 +70,7 @@ const CollectionItemRowBase = ({
);
};
-CollectionItemRowBase.propTypes = {
+CollectionItemRow.propTypes = {
collection: PropTypes.shape({
id: PropTypes.string.isRequired,
name: PropTypes.string.isRequired
@@ -146,408 +87,106 @@ CollectionItemRowBase.propTypes = {
})
}).isRequired
}).isRequired,
- isOwner: PropTypes.bool.isRequired,
- user: PropTypes.shape({
- username: PropTypes.string,
- authenticated: PropTypes.bool.isRequired
- }).isRequired,
- removeFromCollection: PropTypes.func.isRequired
+ isOwner: PropTypes.bool.isRequired
};
-function mapDispatchToPropsSketchListRow(dispatch) {
- return bindActionCreators(
- Object.assign({}, CollectionsActions, ProjectActions, IdeActions),
- dispatch
- );
-}
-
-const CollectionItemRow = connect(
- null,
- mapDispatchToPropsSketchListRow
-)(CollectionItemRowBase);
-
-class Collection extends React.Component {
- constructor(props) {
- super(props);
- this.props.getCollections(this.props.username);
- this.props.resetSorting();
- this._renderFieldHeader = this._renderFieldHeader.bind(this);
- this.showAddSketches = this.showAddSketches.bind(this);
- this.hideAddSketches = this.hideAddSketches.bind(this);
-
- this.state = {
- isAddingSketches: false
- };
- }
-
- getTitle() {
- if (this.props.username === this.props.user.username) {
- return this.props.t('Collection.Title');
- }
- return this.props.t('Collection.AnothersTitle', {
- anotheruser: this.props.username
- });
- }
+const Collection = ({ username, collectionId }) => {
+ const { t } = useTranslation();
+ const dispatch = useDispatch();
- getUsername() {
- return this.props.username !== undefined
- ? this.props.username
- : this.props.user.username;
- }
+ const collection = useSelector((state) => getCollection(state, collectionId));
- getCollectionName() {
- return this.props.collection.name;
- }
+ const loading = useSelector((state) => state.loading);
+ const showLoader = loading && !collection;
- isOwner() {
- let isOwner = false;
+ const currentUsername = useSelector((state) => state.user.username);
+ const isOwner = username === currentUsername;
- if (
- this.props.user != null &&
- this.props.user.username &&
- this.props.collection.owner.username === this.props.user.username
- ) {
- isOwner = true;
+ useEffect(() => {
+ if (!collection) {
+ dispatch(getCollections(username));
}
+ }, [username, collection]);
+
+ // Need top-level string fields in order to sort.
+ const items = useMemo(
+ () =>
+ collection?.items?.map((item) => ({
+ ...item,
+ // 'zz' is a dumb hack to put deleted items last in the sort order
+ name: item.isDeleted ? 'zz' : item.project?.name,
+ owner: item.isDeleted ? 'zz' : item.project?.user?.username
+ })),
+ [collection]
+ );
- return isOwner;
- }
-
- hasCollection() {
- return !this.props.loading && this.props.collection != null;
- }
-
- hasCollectionItems() {
- return this.hasCollection() && this.props.collection.items.length > 0;
- }
-
- _renderLoader() {
- if (this.props.loading) return ;
- return null;
- }
-
- _renderCollectionMetadata() {
- const { id, name, description, items, owner } = this.props.collection;
-
- const hostname = window.location.origin;
- const { username } = this.props;
-
- const baseURL = `${hostname}/${username}/collections/`;
-
- const handleEditCollectionName = (value) => {
- if (value === name) {
- return;
- }
-
- this.props.editCollection(id, { name: value });
- };
-
- const handleEditCollectionDescription = (value) => {
- if (value === description) {
- return;
- }
-
- this.props.editCollection(id, { description: value });
- };
-
- //
- // TODO: Implement UI for editing slug
- //
- // const handleEditCollectionSlug = (value) => {
- // if (value === slug) {
- // return;
- // }
- //
- // this.props.editCollection(id, { slug: value });
- // };
-
- return (
-
-
-
-
- {this.isOwner() ? (
- value !== ''}
- />
- ) : (
- name
- )}
-
-
-
- {this.isOwner() ? (
- 0 ? 'true' : 'false'}
+ >
+
+
+
+ {isOwner
+ ? t('Collection.Title')
+ : t('Collection.AnothersTitle', {
+ anotheruser: username
+ })}
+
+
+ {showLoader && }
+ {collection && (
+
+ )}
+
+
+
(
+
- ) : (
- description
)}
-
-
-
- {this.props.t('Collection.By')}
-
- {owner.username}
-
-
-
-
- {this.props.t('Collection.NumSketches', { count: items.length })}
-
-
-
-
-
-
-
- {this.isOwner() && (
-
- {this.props.t('Collection.AddSketch')}
-
- )}
+ />
-
-
- );
- }
-
- showAddSketches() {
- this.setState({
- isAddingSketches: true
- });
- }
-
- hideAddSketches() {
- this.setState({
- isAddingSketches: false
- });
- }
-
- _renderEmptyTable() {
- const isLoading = this.props.loading;
- const hasCollectionItems =
- this.props.collection != null && this.props.collection.items.length > 0;
-
- if (!isLoading && !hasCollectionItems) {
- return (
-
- {this.props.t('Collection.NoSketches')}
-
- );
- }
- return null;
- }
-
- _getButtonLabel = (fieldName, displayName) => {
- const { field, direction } = this.props.sorting;
- let buttonLabel;
- if (field !== fieldName) {
- if (field === 'name') {
- buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', {
- displayName
- });
- } else {
- buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', {
- displayName
- });
- }
- } else if (direction === SortingActions.DIRECTION.ASC) {
- buttonLabel = this.props.t('Collection.ButtonLabelDescendingARIA', {
- displayName
- });
- } else {
- buttonLabel = this.props.t('Collection.ButtonLabelAscendingARIA', {
- displayName
- });
- }
- return buttonLabel;
- };
-
- _renderFieldHeader(fieldName, displayName) {
- const { field, direction } = this.props.sorting;
- const headerClass = classNames({
- arrowDown: true,
- 'sketches-table__header--selected': field === fieldName
- });
- const buttonLabel = this._getButtonLabel(fieldName, displayName);
- return (
-
- this.props.toggleDirectionForField(fieldName)}
- aria-label={buttonLabel}
- >
- {displayName}
- {field === fieldName &&
- direction === SortingActions.DIRECTION.ASC && (
-
- )}
- {field === fieldName &&
- direction === SortingActions.DIRECTION.DESC && (
-
- )}
-
-
- );
- }
-
- render() {
- const title = this.hasCollection() ? this.getCollectionName() : null;
- const isOwner = this.isOwner();
-
- return (
-
-
-
- {this.getTitle()}
-
- {this._renderLoader()}
- {this.hasCollection() && this._renderCollectionMetadata()}
-
-
- {this._renderEmptyTable()}
- {this.hasCollectionItems() && (
-
-
-
- {this._renderFieldHeader(
- 'name',
- this.props.t('Collection.HeaderName')
- )}
- {this._renderFieldHeader(
- 'createdAt',
- this.props.t('Collection.HeaderCreatedAt')
- )}
- {this._renderFieldHeader(
- 'user',
- this.props.t('Collection.HeaderUser')
- )}
-
-
-
-
- {this.props.collection.items.map((item) => (
-
- ))}
-
-
- )}
- {this.state.isAddingSketches && (
-
}
- closeOverlay={this.hideAddSketches}
- isFixedHeight
- >
-
-
- )}
-
-
-
- );
- }
-}
-
-Collection.propTypes = {
- user: PropTypes.shape({
- username: PropTypes.string,
- authenticated: PropTypes.bool.isRequired
- }).isRequired,
- getCollections: PropTypes.func.isRequired,
- collection: PropTypes.shape({
- id: PropTypes.string,
- name: PropTypes.string,
- slug: PropTypes.string,
- description: PropTypes.string,
- owner: PropTypes.shape({
- username: PropTypes.string
- }).isRequired,
- items: PropTypes.arrayOf(PropTypes.shape({}))
- }),
- username: PropTypes.string,
- loading: PropTypes.bool.isRequired,
- toggleDirectionForField: PropTypes.func.isRequired,
- editCollection: PropTypes.func.isRequired,
- resetSorting: PropTypes.func.isRequired,
- sorting: PropTypes.shape({
- field: PropTypes.string.isRequired,
- direction: PropTypes.string.isRequired
- }).isRequired,
- t: PropTypes.func.isRequired
+
+
+ );
};
-Collection.defaultProps = {
- username: undefined,
- collection: {
- id: undefined,
- items: [],
- owner: {
- username: undefined
- }
- }
+Collection.propTypes = {
+ collectionId: PropTypes.string.isRequired,
+ username: PropTypes.string.isRequired
};
-function mapStateToProps(state, ownProps) {
- return {
- user: state.user,
- collection: getCollection(state, ownProps.collectionId),
- sorting: state.sorting,
- loading: state.loading,
- project: state.project
- };
-}
-
-function mapDispatchToProps(dispatch) {
- return bindActionCreators(
- Object.assign(
- {},
- CollectionsActions,
- ProjectsActions,
- ToastActions,
- SortingActions
- ),
- dispatch
- );
-}
-
-export default withTranslation()(
- connect(mapStateToProps, mapDispatchToProps)(Collection)
-);
+export default Collection;
diff --git a/client/modules/User/components/CollectionMetadata.jsx b/client/modules/User/components/CollectionMetadata.jsx
new file mode 100644
index 0000000000..9d6df781d2
--- /dev/null
+++ b/client/modules/User/components/CollectionMetadata.jsx
@@ -0,0 +1,129 @@
+import classNames from 'classnames';
+import PropTypes from 'prop-types';
+import React, { useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useDispatch, useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
+import Button from '../../../common/Button';
+import Overlay from '../../App/components/Overlay';
+import { editCollection } from '../../IDE/actions/collections';
+import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList';
+import EditableInput from '../../IDE/components/EditableInput';
+import { SketchSearchbar } from '../../IDE/components/Searchbar';
+import ShareURL from './CollectionShareButton';
+
+function CollectionMetadata({ collection, isOwner }) {
+ const { t } = useTranslation();
+
+ const dispatch = useDispatch();
+
+ const currentUsername = useSelector((state) => state.user.username);
+
+ const [isAddingSketches, setIsAddingSketches] = useState(false);
+
+ const { id, name, description, items, owner } = collection;
+ const { username: ownerUsername } = owner;
+
+ const hostname = window.location.origin;
+
+ const handleEditCollectionName = (value) => {
+ if (value === name) {
+ return;
+ }
+ dispatch(editCollection(id, { name: value }));
+ };
+
+ const handleEditCollectionDescription = (value) => {
+ if (value === description) {
+ return;
+ }
+ dispatch(editCollection(id, { description: value }));
+ };
+
+ // TODO: Implement UI for editing slug
+
+ return (
+
+ );
+}
+
+CollectionMetadata.propTypes = {
+ collection: PropTypes.shape({
+ id: PropTypes.string,
+ name: PropTypes.string,
+ slug: PropTypes.string,
+ description: PropTypes.string,
+ owner: PropTypes.shape({
+ username: PropTypes.string
+ }).isRequired,
+ items: PropTypes.arrayOf(PropTypes.shape({}))
+ }).isRequired,
+ isOwner: PropTypes.bool.isRequired
+};
+
+export default CollectionMetadata;
diff --git a/client/modules/User/components/CollectionShareButton.jsx b/client/modules/User/components/CollectionShareButton.jsx
new file mode 100644
index 0000000000..c4e0bba915
--- /dev/null
+++ b/client/modules/User/components/CollectionShareButton.jsx
@@ -0,0 +1,54 @@
+import PropTypes from 'prop-types';
+import React, { useEffect, useRef, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Button from '../../../common/Button';
+import { DropdownArrowIcon } from '../../../common/icons';
+import CopyableInput from '../../IDE/components/CopyableInput';
+
+const ShareURL = ({ value }) => {
+ const [showURL, setShowURL] = useState(false);
+ const node = useRef();
+ const { t } = useTranslation();
+
+ const handleClickOutside = (e) => {
+ if (node.current?.contains(e.target)) {
+ return;
+ }
+ setShowURL(false);
+ };
+
+ useEffect(() => {
+ if (showURL) {
+ document.addEventListener('mousedown', handleClickOutside);
+ } else {
+ document.removeEventListener('mousedown', handleClickOutside);
+ }
+
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, [showURL]);
+
+ return (
+
+
setShowURL(!showURL)}
+ iconAfter={ }
+ >
+ {t('Collection.Share')}
+
+ {showURL && (
+
+
+
+ )}
+
+ );
+};
+
+ShareURL.propTypes = {
+ value: PropTypes.string.isRequired
+};
+
+export default ShareURL;
diff --git a/client/reducers.js b/client/reducers.js
index f61d2585d6..e95a1d5061 100644
--- a/client/reducers.js
+++ b/client/reducers.js
@@ -10,7 +10,6 @@ import toast from './modules/IDE/reducers/toast';
import console from './modules/IDE/reducers/console';
import assets from './modules/IDE/reducers/assets';
import search from './modules/IDE/reducers/search';
-import sorting from './modules/IDE/reducers/sorting';
import loading from './modules/IDE/reducers/loading';
import collections from './modules/IDE/reducers/collections';
@@ -22,7 +21,6 @@ const rootReducer = combineReducers({
project,
sketches,
search,
- sorting,
editorAccessibility,
toast,
console,
diff --git a/client/styles/components/_asset-list.scss b/client/styles/components/_asset-list.scss
index 6f7c035993..531e3944fe 100644
--- a/client/styles/components/_asset-list.scss
+++ b/client/styles/components/_asset-list.scss
@@ -5,28 +5,7 @@
}
.asset-table {
- width: 100%;
-
- max-height: 100%;
- border-spacing: 0;
position: relative;
- & .asset-table__dropdown-column {
- width: #{60 / $base-font-size}rem;
- position: relative;
- }
-}
-
-.asset-table thead th {
- height: #{32 / $base-font-size}rem;
- position: sticky;
- top: 0;
- @include themify() {
- background-color: getThemifyVariable('background-color');
- }
-}
-
-.asset-table thead th:nth-child(1){
- padding-left: #{12 / $base-font-size}rem;
}
.asset-table__row {
@@ -51,23 +30,6 @@
}
}
-.asset-table thead {
- font-size: #{12 / $base-font-size}rem;
- @include themify() {
- color: getThemifyVariable('inactive-text-color')
- }
-}
-
-.asset-table th {
- font-weight: normal;
-}
-
-.asset-table__empty {
- text-align: center;
- font-size: #{16 / $base-font-size}rem;
- padding: #{42 / $base-font-size}rem 0;
-}
-
.asset-table__total {
padding: 0 #{20 / $base-font-size}rem;
position: sticky;
@@ -76,24 +38,3 @@
background-color: getThemifyVariable('background-color');
}
}
-
-.asset-table__dropdown-button {
- width:#{25 / $base-font-size}rem;
- height:#{25 / $base-font-size}rem;
-
- @include themify() {
- & polygon, & path {
- fill: getThemifyVariable('inactive-text-color');
- }
- }
-}
-
-.asset-table__action-dialogue {
- @extend %dropdown-open-right;
- top: 63%;
- right: calc(100% - 26px);
-}
-
-.asset-table__action-option {
- font-size: #{12 / $base-font-size}rem;
-}
diff --git a/client/styles/components/_collection.scss b/client/styles/components/_collection.scss
index 0c8707ecf6..e1062739a7 100644
--- a/client/styles/components/_collection.scss
+++ b/client/styles/components/_collection.scss
@@ -127,11 +127,6 @@
align-items: center;
}
-.collection-empty-message {
- text-align: center;
- font-size: #{16 / $base-font-size}rem;
-}
-
.collection-row__action-column {
width: #{60 / $base-font-size}rem;
position: relative;
diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss
index b03381b091..a2460f599d 100644
--- a/client/styles/components/_sketch-list.scss
+++ b/client/styles/components/_sketch-list.scss
@@ -15,50 +15,6 @@
}
}
-.sketches-table thead th {
- height: #{32 / $base-font-size}rem;
- position: sticky;
- top: 0;
- z-index: 1;
- @include themify() {
- background-color: getThemifyVariable('background-color');
- }
-}
-
-.sketch-list__sort-button {
- display: flex;
- align-items: center;
- height: #{35 / $base-font-size}rem;
-
- & .isvg {
- margin-left: #{8 / $base-font-size}rem;
- }
-
- & svg {
- @include themify() {
- fill: getThemifyVariable('inactive-text-color')
- }
- }
-}
-
-.sketches-table__header {
- border-bottom: 2px dashed transparent;
- padding: #{3 / $base-font-size}rem 0;
- @include themify() {
- color: getThemifyVariable('inactive-text-color')
- }
-}
-
-.sketches-table__header--selected {
- @include themify() {
- border-color: getThemifyVariable('logo-color');
- }
-}
-
-.sketches-table thead th:nth-child(1){
- padding-left: #{12 / $base-font-size}rem;
-}
-
.sketches-table__row {
margin: #{10 / $base-font-size}rem;
height: #{72 / $base-font-size}rem;
@@ -100,17 +56,6 @@
font-weight: normal;
}
-
-.sketch-list__dropdown-button {
- width:#{25 / $base-font-size}rem;
- height:#{25 / $base-font-size}rem;
- @include themify() {
- & polygon, & path {
- fill: getThemifyVariable('inactive-text-color');
- }
- }
-}
-
.sketches-table__name {
display: flex;
align-items: center;
@@ -119,15 +64,3 @@
.sketches-table__icon-cell {
width: #{35 / $base-font-size}rem;
}
-
-.sketch-list__action-dialogue {
- @extend %dropdown-open-right;
- top: 63%;
- right: calc(100% - 26px);
-}
-
-.sketches-table__empty {
- text-align: center;
- font-size: #{16 / $base-font-size}rem;
- padding: #{42 / $base-font-size}rem 0;
-}
diff --git a/client/theme.js b/client/theme.js
index 7d3dad8fab..ed8af098ae 100644
--- a/client/theme.js
+++ b/client/theme.js
@@ -74,6 +74,7 @@ const baseThemes = {
primaryTextColor: grays.dark,
inactiveTextColor: grays.middleDark,
backgroundColor: grays.lighter,
+ accentColor: colors.p5jsPink,
Button: {
primary: {
@@ -156,6 +157,7 @@ const baseThemes = {
primaryTextColor: grays.lightest,
inactiveTextColor: grays.middleLight,
backgroundColor: grays.darker,
+ accentColor: colors.p5jsPink,
Button: {
primary: {
@@ -238,6 +240,7 @@ export default {
...baseThemes,
[Theme.contrast]: extend(baseThemes[Theme.dark], {
inactiveTextColor: grays.light,
+ accentColor: colors.yellow,
Button: {
primary: {