Skip to content

Commit 511f83e

Browse files
committed
Merge branch 'release-2.15.0' into release
2 parents e03bd40 + e96cbcf commit 511f83e

14 files changed

+1319
-1710
lines changed

client/modules/IDE/actions/files.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ import {
1111
import { setProjectSavedTime } from './project';
1212
import { createError } from './ide';
1313

14-
function appendToFilename(filename, string) {
14+
export function appendToFilename(filename, string) {
1515
const dotIndex = filename.lastIndexOf('.');
1616
if (dotIndex === -1) return filename + string;
1717
return (
1818
filename.substring(0, dotIndex) + string + filename.substring(dotIndex)
1919
);
2020
}
2121

22-
function createUniqueName(name, parentId, files) {
22+
export function createUniqueName(name, parentId, files) {
2323
const siblingFiles = files
2424
.find((file) => file.id === parentId)
2525
.children.map((childFileId) =>
+46-170
Original file line numberDiff line numberDiff line change
@@ -1,187 +1,63 @@
1-
import PropTypes from 'prop-types';
2-
import React from 'react';
3-
import { connect, useDispatch } from 'react-redux';
4-
import { bindActionCreators } from 'redux';
5-
import { Link } from 'react-router-dom';
1+
import React, { useEffect } from 'react';
2+
import { useDispatch, useSelector } from 'react-redux';
63
import { Helmet } from 'react-helmet';
7-
import prettyBytes from 'pretty-bytes';
8-
import { useTranslation, withTranslation } from 'react-i18next';
9-
import MenuItem from '../../../components/Dropdown/MenuItem';
10-
import TableDropdown from '../../../components/Dropdown/TableDropdown';
11-
4+
import { useTranslation } from 'react-i18next';
5+
import AssetListRow from './AssetListRow';
126
import Loader from '../../App/components/loader';
13-
import { deleteAssetRequest } from '../actions/assets';
147
import * as AssetActions from '../actions/assets';
158

16-
const AssetMenu = ({ item: asset }) => {
9+
const AssetList = () => {
1710
const { t } = useTranslation();
18-
1911
const dispatch = useDispatch();
12+
const { username, assetList, loading } = useSelector((state) => ({
13+
username: state.user.username,
14+
assetList: state.assets.list,
15+
loading: state.loading
16+
}));
2017

21-
const handleAssetDelete = () => {
22-
const { key, name } = asset;
23-
if (window.confirm(t('Common.DeleteConfirmation', { name }))) {
24-
dispatch(deleteAssetRequest(key));
25-
}
26-
};
27-
28-
return (
29-
<TableDropdown aria-label={t('AssetList.ToggleOpenCloseARIA')}>
30-
<MenuItem onClick={handleAssetDelete}>{t('AssetList.Delete')}</MenuItem>
31-
<MenuItem href={asset.url} target="_blank">
32-
{t('AssetList.OpenNewTab')}
33-
</MenuItem>
34-
</TableDropdown>
35-
);
36-
};
37-
38-
AssetMenu.propTypes = {
39-
item: PropTypes.shape({
40-
key: PropTypes.string.isRequired,
41-
url: PropTypes.string.isRequired,
42-
name: PropTypes.string.isRequired
43-
}).isRequired
44-
};
45-
46-
const AssetListRowBase = ({ asset, username }) => (
47-
<tr className="asset-table__row" key={asset.key}>
48-
<th scope="row">
49-
<a href={asset.url} target="_blank" rel="noopener noreferrer">
50-
{asset.name}
51-
</a>
52-
</th>
53-
<td>{prettyBytes(asset.size)}</td>
54-
<td>
55-
{asset.sketchId && (
56-
<Link to={`/${username}/sketches/${asset.sketchId}`}>
57-
{asset.sketchName}
58-
</Link>
59-
)}
60-
</td>
61-
<td className="asset-table__dropdown-column">
62-
<AssetMenu item={asset} />
63-
</td>
64-
</tr>
65-
);
66-
67-
AssetListRowBase.propTypes = {
68-
asset: PropTypes.shape({
69-
key: PropTypes.string.isRequired,
70-
url: PropTypes.string.isRequired,
71-
sketchId: PropTypes.string,
72-
sketchName: PropTypes.string,
73-
name: PropTypes.string.isRequired,
74-
size: PropTypes.number.isRequired
75-
}).isRequired,
76-
username: PropTypes.string.isRequired
77-
};
78-
79-
function mapStateToPropsAssetListRow(state) {
80-
return {
81-
username: state.user.username
82-
};
83-
}
84-
85-
function mapDispatchToPropsAssetListRow(dispatch) {
86-
return bindActionCreators(AssetActions, dispatch);
87-
}
88-
89-
const AssetListRow = connect(
90-
mapStateToPropsAssetListRow,
91-
mapDispatchToPropsAssetListRow
92-
)(AssetListRowBase);
93-
94-
class AssetList extends React.Component {
95-
constructor(props) {
96-
super(props);
97-
this.props.getAssets();
98-
}
99-
100-
getAssetsTitle() {
101-
return this.props.t('AssetList.Title');
102-
}
18+
useEffect(() => {
19+
dispatch(AssetActions.getAssets());
20+
}, []);
10321

104-
hasAssets() {
105-
return !this.props.loading && this.props.assetList.length > 0;
106-
}
22+
const hasAssets = () => !loading && assetList.length > 0;
10723

108-
renderLoader() {
109-
if (this.props.loading) return <Loader />;
110-
return null;
111-
}
24+
const renderLoader = () => (loading ? <Loader /> : null);
11225

113-
renderEmptyTable() {
114-
if (!this.props.loading && this.props.assetList.length === 0) {
26+
const renderEmptyTable = () => {
27+
if (!loading && assetList.length === 0) {
11528
return (
116-
<p className="asset-table__empty">
117-
{this.props.t('AssetList.NoUploadedAssets')}
118-
</p>
29+
<p className="asset-table__empty">{t('AssetList.NoUploadedAssets')}</p>
11930
);
12031
}
12132
return null;
122-
}
123-
124-
render() {
125-
const { assetList, t } = this.props;
126-
return (
127-
<article className="asset-table-container">
128-
<Helmet>
129-
<title>{this.getAssetsTitle()}</title>
130-
</Helmet>
131-
{this.renderLoader()}
132-
{this.renderEmptyTable()}
133-
{this.hasAssets() && (
134-
<table className="asset-table">
135-
<thead>
136-
<tr>
137-
<th>{t('AssetList.HeaderName')}</th>
138-
<th>{t('AssetList.HeaderSize')}</th>
139-
<th>{t('AssetList.HeaderSketch')}</th>
140-
<th scope="col"></th>
141-
</tr>
142-
</thead>
143-
<tbody>
144-
{assetList.map((asset) => (
145-
<AssetListRow asset={asset} key={asset.key} t={t} />
146-
))}
147-
</tbody>
148-
</table>
149-
)}
150-
</article>
151-
);
152-
}
153-
}
154-
155-
AssetList.propTypes = {
156-
user: PropTypes.shape({
157-
username: PropTypes.string
158-
}).isRequired,
159-
assetList: PropTypes.arrayOf(
160-
PropTypes.shape({
161-
key: PropTypes.string.isRequired,
162-
name: PropTypes.string.isRequired,
163-
url: PropTypes.string.isRequired,
164-
sketchName: PropTypes.string,
165-
sketchId: PropTypes.string
166-
})
167-
).isRequired,
168-
getAssets: PropTypes.func.isRequired,
169-
loading: PropTypes.bool.isRequired,
170-
t: PropTypes.func.isRequired
171-
};
172-
173-
function mapStateToProps(state) {
174-
return {
175-
user: state.user,
176-
assetList: state.assets.list,
177-
loading: state.loading
17833
};
179-
}
18034

181-
function mapDispatchToProps(dispatch) {
182-
return bindActionCreators(Object.assign({}, AssetActions), dispatch);
183-
}
35+
return (
36+
<article className="asset-table-container">
37+
<Helmet>
38+
<title>{t('AssetList.Title')}</title>
39+
</Helmet>
40+
{renderLoader()}
41+
{renderEmptyTable()}
42+
{hasAssets() && (
43+
<table className="asset-table">
44+
<thead>
45+
<tr>
46+
<th>{t('AssetList.HeaderName')}</th>
47+
<th>{t('AssetList.HeaderSize')}</th>
48+
<th>{t('AssetList.HeaderSketch')}</th>
49+
<th scope="col"></th>
50+
</tr>
51+
</thead>
52+
<tbody>
53+
{assetList.map((asset) => (
54+
<AssetListRow asset={asset} key={asset.key} username={username} />
55+
))}
56+
</tbody>
57+
</table>
58+
)}
59+
</article>
60+
);
61+
};
18462

185-
export default withTranslation()(
186-
connect(mapStateToProps, mapDispatchToProps)(AssetList)
187-
);
63+
export default AssetList;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import PropTypes from 'prop-types';
2+
import React from 'react';
3+
import { Link } from 'react-router-dom';
4+
import { useDispatch } from 'react-redux';
5+
import { useTranslation } from 'react-i18next';
6+
import prettyBytes from 'pretty-bytes';
7+
import MenuItem from '../../../components/Dropdown/MenuItem';
8+
import TableDropdown from '../../../components/Dropdown/TableDropdown';
9+
import { deleteAssetRequest } from '../actions/assets';
10+
11+
const AssetMenu = ({ item: asset }) => {
12+
const { t } = useTranslation();
13+
const dispatch = useDispatch();
14+
15+
const handleAssetDelete = () => {
16+
const { key, name } = asset;
17+
if (window.confirm(t('Common.DeleteConfirmation', { name }))) {
18+
dispatch(deleteAssetRequest(key));
19+
}
20+
};
21+
22+
return (
23+
<TableDropdown aria-label={t('AssetList.ToggleOpenCloseARIA')}>
24+
<MenuItem onClick={handleAssetDelete}>{t('AssetList.Delete')}</MenuItem>
25+
<MenuItem href={asset.url} target="_blank">
26+
{t('AssetList.OpenNewTab')}
27+
</MenuItem>
28+
</TableDropdown>
29+
);
30+
};
31+
32+
AssetMenu.propTypes = {
33+
item: PropTypes.shape({
34+
key: PropTypes.string.isRequired,
35+
url: PropTypes.string.isRequired,
36+
name: PropTypes.string.isRequired
37+
}).isRequired
38+
};
39+
40+
const AssetListRow = ({ asset, username }) => (
41+
<tr className="asset-table__row" key={asset.key}>
42+
<th scope="row">
43+
<a href={asset.url} target="_blank" rel="noopener noreferrer">
44+
{asset.name}
45+
</a>
46+
</th>
47+
<td>{prettyBytes(asset.size)}</td>
48+
<td>
49+
{asset.sketchId && (
50+
<Link to={`/${username}/sketches/${asset.sketchId}`}>
51+
{asset.sketchName}
52+
</Link>
53+
)}
54+
</td>
55+
<td className="asset-table__dropdown-column">
56+
<AssetMenu item={asset} />
57+
</td>
58+
</tr>
59+
);
60+
61+
AssetListRow.propTypes = {
62+
asset: PropTypes.shape({
63+
key: PropTypes.string.isRequired,
64+
url: PropTypes.string.isRequired,
65+
sketchId: PropTypes.string,
66+
sketchName: PropTypes.string,
67+
name: PropTypes.string.isRequired,
68+
size: PropTypes.number.isRequired
69+
}).isRequired,
70+
username: PropTypes.string.isRequired
71+
};
72+
73+
export default AssetListRow;

0 commit comments

Comments
 (0)