Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 4eeb53e

Browse files
authoredNov 18, 2023
Merge branch 'develop' into liveloading-searchCount-fix-mhsh312
2 parents 93e9a8e + fb30a55 commit 4eeb53e

File tree

5 files changed

+218
-274
lines changed

5 files changed

+218
-274
lines changed
 

‎client/modules/IDE/reducers/files.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,28 @@ const files = (state, action) => {
158158
}
159159
return Object.assign({}, file, { blobURL: action.blobURL });
160160
});
161-
case ActionTypes.NEW_PROJECT:
162-
return setFilePaths(action.files);
163-
case ActionTypes.SET_PROJECT:
164-
return setFilePaths(action.files);
161+
case ActionTypes.NEW_PROJECT: {
162+
const newFiles = action.files.map((file) => {
163+
const corrospondingObj = state.find((obj) => obj.id === file.id);
164+
if (corrospondingObj && corrospondingObj.fileType === 'folder') {
165+
const isFolderClosed = corrospondingObj.isFolderClosed || false;
166+
return { ...file, isFolderClosed };
167+
}
168+
return file;
169+
});
170+
return setFilePaths(newFiles);
171+
}
172+
case ActionTypes.SET_PROJECT: {
173+
const newFiles = action.files.map((file) => {
174+
const corrospondingObj = state.find((obj) => obj.id === file.id);
175+
if (corrospondingObj && corrospondingObj.fileType === 'folder') {
176+
const isFolderClosed = corrospondingObj.isFolderClosed || false;
177+
return { ...file, isFolderClosed };
178+
}
179+
return file;
180+
});
181+
return setFilePaths(newFiles);
182+
}
165183
case ActionTypes.RESET_PROJECT:
166184
return initialState();
167185
case ActionTypes.CREATE_FILE: {
@@ -188,6 +206,7 @@ const files = (state, action) => {
188206
filePath
189207
}
190208
];
209+
191210
return newState.map((file) => {
192211
if (file.id === action.parentId) {
193212
file.children = sortedChildrenId(newState, file.children);

‎client/modules/User/components/Collection.jsx

Lines changed: 4 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
import PropTypes from 'prop-types';
2-
import React, { useState, useRef, useEffect } from 'react';
2+
import React from 'react';
33
import { Helmet } from 'react-helmet';
44
import { connect } from 'react-redux';
55
import { Link } from 'react-router-dom';
66
import { bindActionCreators } from 'redux';
77
import { useTranslation, withTranslation } from 'react-i18next';
88
import classNames from 'classnames';
99

10-
import Button from '../../../common/Button';
11-
import { DropdownArrowIcon } from '../../../common/icons';
1210
import * as ProjectActions from '../../IDE/actions/project';
1311
import * as ProjectsActions from '../../IDE/actions/projects';
1412
import * as CollectionsActions from '../../IDE/actions/collections';
@@ -17,61 +15,12 @@ import * as SortingActions from '../../IDE/actions/sorting';
1715
import * as IdeActions from '../../IDE/actions/ide';
1816
import { getCollection } from '../../IDE/selectors/collections';
1917
import Loader from '../../App/components/loader';
20-
import EditableInput from '../../IDE/components/EditableInput';
21-
import Overlay from '../../App/components/Overlay';
22-
import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList';
23-
import CopyableInput from '../../IDE/components/CopyableInput';
24-
import { SketchSearchbar } from '../../IDE/components/Searchbar';
2518
import dates from '../../../utils/formatDate';
2619

2720
import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
2821
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
2922
import RemoveIcon from '../../../images/close.svg';
30-
31-
const ShareURL = ({ value }) => {
32-
const [showURL, setShowURL] = useState(false);
33-
const node = useRef();
34-
const { t } = useTranslation();
35-
36-
const handleClickOutside = (e) => {
37-
if (node.current.contains(e.target)) {
38-
return;
39-
}
40-
setShowURL(false);
41-
};
42-
43-
useEffect(() => {
44-
if (showURL) {
45-
document.addEventListener('mousedown', handleClickOutside);
46-
} else {
47-
document.removeEventListener('mousedown', handleClickOutside);
48-
}
49-
50-
return () => {
51-
document.removeEventListener('mousedown', handleClickOutside);
52-
};
53-
}, [showURL]);
54-
55-
return (
56-
<div className="collection-share" ref={node}>
57-
<Button
58-
onClick={() => setShowURL(!showURL)}
59-
iconAfter={<DropdownArrowIcon />}
60-
>
61-
{t('Collection.Share')}
62-
</Button>
63-
{showURL && (
64-
<div className="collection__share-dropdown">
65-
<CopyableInput value={value} label={t('Collection.URLLink')} />
66-
</div>
67-
)}
68-
</div>
69-
);
70-
};
71-
72-
ShareURL.propTypes = {
73-
value: PropTypes.string.isRequired
74-
};
23+
import CollectionMetadata from './CollectionMetadata';
7524

7625
const CollectionItemRowBase = ({
7726
collection,
@@ -172,12 +121,6 @@ class Collection extends React.Component {
172121
this.props.getCollections(this.props.username);
173122
this.props.resetSorting();
174123
this._renderFieldHeader = this._renderFieldHeader.bind(this);
175-
this.showAddSketches = this.showAddSketches.bind(this);
176-
this.hideAddSketches = this.hideAddSketches.bind(this);
177-
178-
this.state = {
179-
isAddingSketches: false
180-
};
181124
}
182125

183126
getTitle() {
@@ -195,10 +138,6 @@ class Collection extends React.Component {
195138
: this.props.user.username;
196139
}
197140

198-
getCollectionName() {
199-
return this.props.collection.name;
200-
}
201-
202141
isOwner() {
203142
let isOwner = false;
204143

@@ -226,115 +165,6 @@ class Collection extends React.Component {
226165
return null;
227166
}
228167

229-
_renderCollectionMetadata() {
230-
const { id, name, description, items, owner } = this.props.collection;
231-
232-
const hostname = window.location.origin;
233-
const { username } = this.props;
234-
235-
const baseURL = `${hostname}/${username}/collections/`;
236-
237-
const handleEditCollectionName = (value) => {
238-
if (value === name) {
239-
return;
240-
}
241-
242-
this.props.editCollection(id, { name: value });
243-
};
244-
245-
const handleEditCollectionDescription = (value) => {
246-
if (value === description) {
247-
return;
248-
}
249-
250-
this.props.editCollection(id, { description: value });
251-
};
252-
253-
//
254-
// TODO: Implement UI for editing slug
255-
//
256-
// const handleEditCollectionSlug = (value) => {
257-
// if (value === slug) {
258-
// return;
259-
// }
260-
//
261-
// this.props.editCollection(id, { slug: value });
262-
// };
263-
264-
return (
265-
<header
266-
className={`collection-metadata ${
267-
this.isOwner() ? 'collection-metadata--is-owner' : ''
268-
}`}
269-
>
270-
<div className="collection-metadata__columns">
271-
<div className="collection-metadata__column--left">
272-
<h2 className="collection-metadata__name">
273-
{this.isOwner() ? (
274-
<EditableInput
275-
value={name}
276-
onChange={handleEditCollectionName}
277-
validate={(value) => value !== ''}
278-
/>
279-
) : (
280-
name
281-
)}
282-
</h2>
283-
284-
<p className="collection-metadata__description">
285-
{this.isOwner() ? (
286-
<EditableInput
287-
InputComponent="textarea"
288-
value={description}
289-
onChange={handleEditCollectionDescription}
290-
emptyPlaceholder={this.props.t(
291-
'Collection.DescriptionPlaceholder'
292-
)}
293-
/>
294-
) : (
295-
description
296-
)}
297-
</p>
298-
299-
<p className="collection-metadata__user">
300-
{this.props.t('Collection.By')}
301-
<Link to={`${hostname}/${username}/sketches`}>
302-
{owner.username}
303-
</Link>
304-
</p>
305-
306-
<p className="collection-metadata__user">
307-
{this.props.t('Collection.NumSketches', { count: items.length })}
308-
</p>
309-
</div>
310-
311-
<div className="collection-metadata__column--right">
312-
<p className="collection-metadata__share">
313-
<ShareURL value={`${baseURL}${id}`} />
314-
</p>
315-
{this.isOwner() && (
316-
<Button onClick={this.showAddSketches}>
317-
{this.props.t('Collection.AddSketch')}
318-
</Button>
319-
)}
320-
</div>
321-
</div>
322-
</header>
323-
);
324-
}
325-
326-
showAddSketches() {
327-
this.setState({
328-
isAddingSketches: true
329-
});
330-
}
331-
332-
hideAddSketches() {
333-
this.setState({
334-
isAddingSketches: false
335-
});
336-
}
337-
338168
_renderEmptyTable() {
339169
if (this.hasCollection() && !this.hasCollectionItems()) {
340170
return (
@@ -408,7 +238,6 @@ class Collection extends React.Component {
408238
}
409239

410240
render() {
411-
const title = this.hasCollection() ? this.getCollectionName() : null;
412241
const isOwner = this.isOwner();
413242

414243
return (
@@ -421,7 +250,7 @@ class Collection extends React.Component {
421250
<title>{this.getTitle()}</title>
422251
</Helmet>
423252
{this._renderLoader()}
424-
{this.hasCollection() && this._renderCollectionMetadata()}
253+
<CollectionMetadata collectionId={this.props.collectionId} />
425254
<article className="collection-content">
426255
<div className="collection-table-wrapper">
427256
{this._renderEmptyTable()}
@@ -461,19 +290,6 @@ class Collection extends React.Component {
461290
</tbody>
462291
</table>
463292
)}
464-
{this.state.isAddingSketches && (
465-
<Overlay
466-
title={this.props.t('Collection.AddSketch')}
467-
actions={<SketchSearchbar />}
468-
closeOverlay={this.hideAddSketches}
469-
isFixedHeight
470-
>
471-
<AddToCollectionSketchList
472-
username={this.props.username}
473-
collection={this.props.collection}
474-
/>
475-
</Overlay>
476-
)}
477293
</div>
478294
</article>
479295
</article>
@@ -483,6 +299,7 @@ class Collection extends React.Component {
483299
}
484300

485301
Collection.propTypes = {
302+
collectionId: PropTypes.string.isRequired,
486303
user: PropTypes.shape({
487304
username: PropTypes.string,
488305
authenticated: PropTypes.bool.isRequired
@@ -501,7 +318,6 @@ Collection.propTypes = {
501318
username: PropTypes.string,
502319
loading: PropTypes.bool.isRequired,
503320
toggleDirectionForField: PropTypes.func.isRequired,
504-
editCollection: PropTypes.func.isRequired,
505321
resetSorting: PropTypes.func.isRequired,
506322
sorting: PropTypes.shape({
507323
field: PropTypes.string.isRequired,
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import classNames from 'classnames';
2+
import PropTypes from 'prop-types';
3+
import React, { useState } from 'react';
4+
import { useTranslation } from 'react-i18next';
5+
import { useDispatch, useSelector } from 'react-redux';
6+
import { Link } from 'react-router-dom';
7+
import Button from '../../../common/Button';
8+
import Overlay from '../../App/components/Overlay';
9+
import { editCollection } from '../../IDE/actions/collections';
10+
import AddToCollectionSketchList from '../../IDE/components/AddToCollectionSketchList';
11+
import EditableInput from '../../IDE/components/EditableInput';
12+
import { SketchSearchbar } from '../../IDE/components/Searchbar';
13+
import { getCollection } from '../../IDE/selectors/collections';
14+
import ShareURL from './CollectionShareButton';
15+
16+
function CollectionMetadata({ collectionId }) {
17+
const { t } = useTranslation();
18+
19+
const dispatch = useDispatch();
20+
21+
const collection = useSelector((state) => getCollection(state, collectionId));
22+
const currentUsername = useSelector((state) => state.user.username);
23+
24+
const [isAddingSketches, setIsAddingSketches] = useState(false);
25+
26+
if (!collection) {
27+
return null;
28+
}
29+
30+
const { id, name, description, items, owner } = collection;
31+
const { username } = owner;
32+
const isOwner = !!currentUsername && currentUsername === username;
33+
34+
const hostname = window.location.origin;
35+
36+
const handleEditCollectionName = (value) => {
37+
if (value === name) {
38+
return;
39+
}
40+
dispatch(editCollection(id, { name: value }));
41+
};
42+
43+
const handleEditCollectionDescription = (value) => {
44+
if (value === description) {
45+
return;
46+
}
47+
dispatch(editCollection(id, { description: value }));
48+
};
49+
50+
// TODO: Implement UI for editing slug
51+
52+
return (
53+
<header
54+
className={classNames(
55+
'collection-metadata',
56+
isOwner && 'collection-metadata--is-owner'
57+
)}
58+
>
59+
<div className="collection-metadata__columns">
60+
<div className="collection-metadata__column--left">
61+
<h2 className="collection-metadata__name">
62+
{isOwner ? (
63+
<EditableInput
64+
value={name}
65+
onChange={handleEditCollectionName}
66+
validate={(value) => value !== ''}
67+
/>
68+
) : (
69+
name
70+
)}
71+
</h2>
72+
73+
<p className="collection-metadata__description">
74+
{isOwner ? (
75+
<EditableInput
76+
InputComponent="textarea"
77+
value={description}
78+
onChange={handleEditCollectionDescription}
79+
emptyPlaceholder={t('Collection.DescriptionPlaceholder')}
80+
/>
81+
) : (
82+
description
83+
)}
84+
</p>
85+
86+
<p className="collection-metadata__user">
87+
{t('Collection.By')}
88+
<Link to={`/${username}/sketches`}>{username}</Link>
89+
</p>
90+
91+
<p className="collection-metadata__user">
92+
{t('Collection.NumSketches', { count: items.length })}
93+
</p>
94+
</div>
95+
96+
<div className="collection-metadata__column--right">
97+
<ShareURL value={`${hostname}/${username}/collections/${id}`} />
98+
{isOwner && (
99+
<Button onClick={() => setIsAddingSketches(true)}>
100+
{t('Collection.AddSketch')}
101+
</Button>
102+
)}
103+
</div>
104+
</div>
105+
{isAddingSketches && (
106+
<Overlay
107+
title={t('Collection.AddSketch')}
108+
actions={<SketchSearchbar />}
109+
closeOverlay={() => setIsAddingSketches(false)}
110+
isFixedHeight
111+
>
112+
<AddToCollectionSketchList
113+
username={username}
114+
collection={collection}
115+
/>
116+
</Overlay>
117+
)}
118+
</header>
119+
);
120+
}
121+
122+
CollectionMetadata.propTypes = {
123+
collectionId: PropTypes.string.isRequired
124+
};
125+
126+
export default CollectionMetadata;
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import PropTypes from 'prop-types';
2+
import React, { useEffect, useRef, useState } from 'react';
3+
import { useTranslation } from 'react-i18next';
4+
5+
import Button from '../../../common/Button';
6+
import { DropdownArrowIcon } from '../../../common/icons';
7+
import CopyableInput from '../../IDE/components/CopyableInput';
8+
9+
const ShareURL = ({ value }) => {
10+
const [showURL, setShowURL] = useState(false);
11+
const node = useRef();
12+
const { t } = useTranslation();
13+
14+
const handleClickOutside = (e) => {
15+
if (node.current?.contains(e.target)) {
16+
return;
17+
}
18+
setShowURL(false);
19+
};
20+
21+
useEffect(() => {
22+
if (showURL) {
23+
document.addEventListener('mousedown', handleClickOutside);
24+
} else {
25+
document.removeEventListener('mousedown', handleClickOutside);
26+
}
27+
28+
return () => {
29+
document.removeEventListener('mousedown', handleClickOutside);
30+
};
31+
}, [showURL]);
32+
33+
return (
34+
<div className="collection-share" ref={node}>
35+
<Button
36+
onClick={() => setShowURL(!showURL)}
37+
iconAfter={<DropdownArrowIcon />}
38+
>
39+
{t('Collection.Share')}
40+
</Button>
41+
{showURL && (
42+
<div className="collection__share-dropdown">
43+
<CopyableInput value={value} label={t('Collection.URLLink')} />
44+
</div>
45+
)}
46+
</div>
47+
);
48+
};
49+
50+
ShareURL.propTypes = {
51+
value: PropTypes.string.isRequired
52+
};
53+
54+
export default ShareURL;
Lines changed: 11 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,21 @@
1-
import PropTypes from 'prop-types';
21
import React from 'react';
3-
import { connect } from 'react-redux';
4-
import { withTranslation } from 'react-i18next';
5-
2+
import { useParams } from 'react-router-dom';
63
import Nav from '../../IDE/components/Header/Nav';
74
import RootPage from '../../../components/RootPage';
8-
9-
import CollectionCreate from '../components/CollectionCreate';
105
import Collection from '../components/Collection';
116

12-
class CollectionView extends React.Component {
13-
static defaultProps = {
14-
user: null
15-
};
16-
17-
ownerName() {
18-
if (this.props.params.username) {
19-
return this.props.params.username;
20-
}
21-
22-
return this.props.user.username;
23-
}
24-
25-
pageTitle() {
26-
if (this.isCreatePage()) {
27-
return this.props.t('CollectionView.TitleCreate');
28-
}
29-
30-
return this.props.t('CollectionView.TitleDefault');
31-
}
32-
33-
isOwner() {
34-
return this.props.user.username === this.props.params.username;
35-
}
7+
const CollectionView = () => {
8+
const params = useParams();
369

37-
isCreatePage() {
38-
const path = this.props.location.pathname;
39-
return /create$/.test(path);
40-
}
41-
42-
renderContent() {
43-
if (this.isCreatePage() && this.isOwner()) {
44-
return <CollectionCreate />;
45-
}
46-
47-
return (
10+
return (
11+
<RootPage>
12+
<Nav layout="dashboard" />
4813
<Collection
49-
collectionId={this.props.params.collection_id}
50-
username={this.props.params.username}
14+
collectionId={params.collection_id}
15+
username={params.username}
5116
/>
52-
);
53-
}
54-
55-
render() {
56-
return (
57-
<RootPage>
58-
<Nav layout="dashboard" />
59-
60-
{this.renderContent()}
61-
</RootPage>
62-
);
63-
}
64-
}
65-
66-
function mapStateToProps(state) {
67-
return {
68-
user: state.user
69-
};
70-
}
71-
72-
function mapDispatchToProps(dispatch) {
73-
return {};
74-
}
75-
76-
CollectionView.propTypes = {
77-
location: PropTypes.shape({
78-
pathname: PropTypes.string.isRequired
79-
}).isRequired,
80-
params: PropTypes.shape({
81-
collection_id: PropTypes.string.isRequired,
82-
username: PropTypes.string.isRequired
83-
}).isRequired,
84-
user: PropTypes.shape({
85-
username: PropTypes.string
86-
}),
87-
t: PropTypes.func.isRequired
17+
</RootPage>
18+
);
8819
};
8920

90-
export default withTranslation()(
91-
connect(mapStateToProps, mapDispatchToProps)(CollectionView)
92-
);
21+
export default CollectionView;

0 commit comments

Comments
 (0)
Please sign in to comment.