Skip to content

Commit d985577

Browse files
authored
Merge pull request #229 from daft300punk/issue--170
Challenge Details - Checkpoints - Refactor the code #170 fixes #170
2 parents bac6a61 + ea2c6bc commit d985577

File tree

9 files changed

+138
-122
lines changed

9 files changed

+138
-122
lines changed

package-lock.json

Lines changed: 2 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
"identity-obj-proxy": "^3.0.0",
5757
"isomorphic-fetch": "^2.2.1",
5858
"jest": "^20.0.0",
59-
"jquery": "^3.2.1",
6059
"jstimezonedetect": "^1.0.6",
6160
"le_node": "^1.7.0",
6261
"lodash": "^4.17.4",

src/shared/actions/challenge.js

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,20 +96,39 @@ function fetchCheckpointsDone(tokenV2, challengeId) {
9696
return response.json();
9797
}
9898
})
99-
.then(response => ({
100-
challengeId: Number(challengeId),
101-
checkpoints: response,
102-
}))
99+
.then((response) => {
100+
// Expanded key is used for UI expand/collapse.
101+
response.checkpointResults.forEach((checkpoint, index) => {
102+
response.checkpointResults[index].expanded = false;
103+
});
104+
return {
105+
challengeId: Number(challengeId),
106+
checkpoints: response,
107+
};
108+
})
103109
.catch(error => ({
104110
error,
105111
challengeId: Number(challengeId),
106112
}));
107113
}
108114

115+
/**
116+
* Toggles checkpoint feedback. If second argument is provided, it
117+
* will just open / close the checkpoint depending on its value being
118+
* true or false.
119+
* @param {Number} id
120+
* @param {Boolean} open
121+
* @return {Object}
122+
*/
123+
function toggleCheckpointFeedback(id, open) {
124+
return { id, open };
125+
}
126+
109127
export default createActions({
110128
CHALLENGE: {
111129
FETCH_CHECKPOINTS_INIT: _.noop,
112130
FETCH_CHECKPOINTS_DONE: fetchCheckpointsDone,
131+
TOGGLE_CHECKPOINT_FEEDBACK: toggleCheckpointFeedback,
113132
GET_DETAILS_INIT: getDetailsInit,
114133
GET_DETAILS_DONE: getDetailsDone,
115134
GET_SUBMISSIONS_INIT: _.noop,
Lines changed: 63 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,82 @@
1-
2-
/* global window */
1+
/* global document */
32
import React from 'react';
43
import PT from 'prop-types';
54

6-
import jquery from 'jquery';
7-
85
import './styles.scss';
96

10-
const trimLimit = 768;
11-
12-
function decodeEscaped(escaped) {
13-
return jquery('<textarea />').html(escaped).text();
14-
}
15-
16-
export default class Checkpoints extends React.Component {
17-
constructor(props, context) {
18-
super(props, context);
19-
const trimContent = !((window && window.innerWidth > trimLimit));
20-
21-
this.state = {
22-
trimContent,
23-
};
24-
this.subTimeout = 0;
25-
this.windowResize = this.windowResize.bind(this);
26-
}
27-
28-
componentDidMount() {
29-
if (window) {
30-
this.subTimeout = setTimeout(() => {
31-
window.addEventListener('resize', this.windowResize);
32-
this.windowResize();
33-
}, 500);// Listen to resize event after a safe period of time
34-
}
35-
}
36-
37-
componentWillUnmount() {
38-
if (window) {
39-
clearTimeout(this.subTimeout);
40-
window.removeEventListener('resize', this.windowResize);
41-
}
42-
}
43-
44-
windowResize() {
45-
this.setState({
46-
trimContent: window.innerWidth <= trimLimit,
47-
});
48-
}
7+
const Checkpoints = (props) => {
8+
const {
9+
checkpointResults,
10+
generalFeedback,
11+
} = props.checkpoints;
4912

50-
expanderClicked(key) {
51-
const newExpanderValue = !this.state[key];
52-
this.setState({
53-
[key]: newExpanderValue,
54-
});
55-
}
56-
57-
render() {
58-
const {
59-
checkpointResults,
60-
generalFeedback,
61-
} = this.props.checkpoints;
62-
return (
63-
<div styleName="challenge-detail-checkpoints">
13+
return (
14+
<div styleName="challenge-detail-checkpoints">
15+
<div styleName="challenge-checkpoint-list">
16+
{
17+
checkpointResults && checkpointResults.map((item, index) => (
18+
<button
19+
key={item.submissionId}
20+
styleName="challenge-checkpoint-li"
21+
onClick={(e) => {
22+
e.preventDefault();
23+
document
24+
.getElementsByClassName('src-shared-components-challenge-detail-Checkpoints-styles___challenge-checkpoint-winners')[index]
25+
.scrollIntoView(true);
26+
props.toggleCheckpointFeedback(index, true);
27+
}}
28+
>
29+
#{item.submissionId}
30+
</button>
31+
))
32+
}
33+
</div>
34+
<div styleName="challenge-checkpoint-detail">
35+
<h2>Checkpoint Winners & General Feedback</h2>
36+
<p
37+
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
38+
__html: generalFeedback || '',
39+
}}
40+
/>
6441
{
65-
!this.state.trimContent && (
66-
<div styleName="challenge-checkpoint-list">
42+
checkpointResults && checkpointResults.map((item, index) => (
43+
<div key={item.submissionId} styleName="challenge-checkpoint-winners">
44+
<button
45+
onClick={(e) => {
46+
e.preventDefault();
47+
props.toggleCheckpointFeedback(index);
48+
}}
49+
styleName="challenge-checkpoint-submission"
50+
>
51+
<span><span styleName="feedback-text">Feedback </span>#{item.submissionId}</span>
52+
<span styleName="challenge-checkpoint-expander">
53+
{
54+
item.expanded ? '-' : '+'
55+
}
56+
</span>
57+
</button>
6758
{
68-
checkpointResults && checkpointResults.map(item => (
69-
<p key={item.submissionId} styleName="challenge-checkpoint-li">
70-
#{item.submissionId}
71-
</p>
72-
))
59+
item.expanded &&
60+
<p
61+
styleName="challenge-checkpoint-feedback"
62+
dangerouslySetInnerHTML={{ // eslint-disable-line react/no-danger
63+
__html: item.feedback,
64+
}}
65+
/>
7366
}
7467
</div>
75-
)
68+
))
7669
}
77-
<div styleName="challenge-checkpoint-detail">
78-
<h2>Checkpoint Winners & General Feedback</h2>
79-
<p>{decodeEscaped(generalFeedback || '')}</p>
80-
{
81-
checkpointResults && checkpointResults.map(item => (
82-
<div key={item.submissionId} styleName="challenge-checkpoint-winners">
83-
<button
84-
onClick={(e) => {
85-
e.preventDefault();
86-
this.expanderClicked(`${item.submissionId}_exp`);
87-
}}
88-
styleName="challenge-checkpoint-submission"
89-
>
90-
<span>{!this.state.trimContent && 'Feedback '}#{item.submissionId}</span>
91-
<span styleName="challenge-checkpoint-expander">
92-
{
93-
this.state[`${item.submissionId}_exp`] ? '-' : '+'
94-
}
95-
</span>
96-
</button>
97-
{
98-
this.state[`${item.submissionId}_exp`] &&
99-
<p styleName="challenge-checkpoint-feedback">
100-
{decodeEscaped(item.feedback)}
101-
</p>
102-
}
103-
</div>
104-
))
105-
}
106-
</div>
10770
</div>
108-
);
109-
}
110-
}
71+
</div>
72+
);
73+
};
11174

11275
Checkpoints.propTypes = {
11376
checkpoints: PT.shape({
11477
checkpointResults: PT.arrayOf(PT.shape()).isRequired,
11578
generalFeedback: PT.string,
11679
}).isRequired,
11780
};
81+
82+
export default Checkpoints;

src/shared/components/challenge-detail/Checkpoints/styles.scss

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ $tc-gray-border: #aaaab5;
77

88
.challenge-checkpoint-list {
99
margin: 50px 20px 50px 10px;
10+
display: flex;
11+
flex-direction: column;
12+
13+
@include xxs-to-xs {
14+
display: none;
15+
}
1016

1117
.challenge-checkpoint-li {
1218
background: $tc-white;
@@ -86,6 +92,12 @@ $tc-gray-border: #aaaab5;
8692
.challenge-checkpoint-expander {
8793
padding-right: 23px;
8894
}
95+
96+
.feedback-text {
97+
@include xxs-to-xs {
98+
display: none;
99+
}
100+
}
89101
}
90102

91103
.challenge-checkpoint-feedback {

src/shared/components/challenge-listing/Icons/.eslintrc.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@
4141
"es6": true,
4242
"browser": true,
4343
"node": true,
44-
"mocha": true,
45-
"jquery": true
44+
"mocha": true
4645
},
4746
"parserOptions": {
4847
"ecmaVersion": 6,

src/shared/containers/challenge-detail/index.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,10 @@ class ChallengeDetailPageContainer extends React.Component {
174174
}
175175
{
176176
!isEmpty && this.state.selectedView === 'CHECKPOINTS' &&
177-
<ChallengeCheckpoints checkpoints={this.props.checkpoints} />
177+
<ChallengeCheckpoints
178+
checkpoints={this.props.checkpoints}
179+
toggleCheckpointFeedback={this.props.toggleCheckpointFeedback}
180+
/>
178181
}
179182
{
180183
!isEmpty && this.state.selectedView === 'SUBMISSIONS' &&
@@ -261,6 +264,7 @@ ChallengeDetailPageContainer.propTypes = {
261264
unregistering: PT.bool.isRequired,
262265
loadingCheckpointResults: PT.bool,
263266
checkpointResults: PT.arrayOf(PT.shape()),
267+
toggleCheckpointFeedback: PT.func.isRequired,
264268
loadingResults: PT.bool,
265269
results: PT.arrayOf(PT.shape()),
266270
fetchCheckpoints: PT.func.isRequired,
@@ -434,6 +438,9 @@ const mapDispatchToProps = (dispatch) => {
434438
dispatch(a.fetchCheckpointsInit());
435439
dispatch(a.fetchCheckpointsDone(tokens.tokenV2, challengeId));
436440
},
441+
toggleCheckpointFeedback: (id, open) => {
442+
dispatch(a.toggleCheckpointFeedback(id, open));
443+
},
437444
loadTerms: (tokens, challengeId) => {
438445
dispatch(t.getTermsInit(challengeId));
439446
dispatch(t.getTermsDone(challengeId, tokens.tokenV2));

src/shared/reducers/challenge.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ function onFetchCheckpointsDone(state, action) {
9797
};
9898
}
9999
if ((state.details && state.details.id === action.payload.challengeId) ||
100-
(state.detailsV2 && state.detailsV2.challengeId === action.payload.challengeId)) {
100+
(state.detailsV2 && state.detailsV2.challengeId === action.payload.challengeId)) {
101101
return {
102102
...state,
103103
checkpoints: action.payload.checkpoints,
@@ -106,6 +106,25 @@ function onFetchCheckpointsDone(state, action) {
106106
}
107107
return state;
108108
}
109+
/**
110+
* Handles challengeActions.toggleCheckpointFeedback action.
111+
* @param {Object} state Previous state.
112+
* @param {Object} action Action.
113+
*/
114+
function onToggleCheckpointFeedback(state, action) {
115+
const { payload: { id, open } } = action;
116+
const newCheckpointResults = _.clone(state.checkpoints.checkpointResults);
117+
newCheckpointResults[id].expanded = _.isUndefined(open)
118+
? !newCheckpointResults[id].expanded : open;
119+
const newCheckpoints = {
120+
...state.checkpoints,
121+
checkpointResults: newCheckpointResults,
122+
};
123+
return {
124+
...state,
125+
checkpoints: newCheckpoints,
126+
};
127+
}
109128

110129
/**
111130
* Handles CHALLENGE/REGISTER_DONE action.
@@ -121,7 +140,8 @@ function onRegisterDone(state, action) {
121140
/* As a part of registration flow we silently update challenge details,
122141
* reusing for this purpose the corresponding action handler. Thus, we
123142
* should also reuse corresponding reducer to generate proper state. */
124-
return onGetDetailsDone({ ...state,
143+
return onGetDetailsDone({
144+
...state,
125145
registering: false,
126146
loadingDetailsForChallengeId: _.toString(state.details.id),
127147
}, action);
@@ -166,9 +186,11 @@ function create(initialState) {
166186
[a.getSubmissionsDone]: onGetSubmissionsDone,
167187
[smpActions.smp.deleteSubmissionDone]: (state, { payload }) => ({
168188
...state,
169-
mySubmissions: { v2: state.mySubmissions.v2.filter(subm => (
170-
subm.submissionId !== payload
171-
)) },
189+
mySubmissions: {
190+
v2: state.mySubmissions.v2.filter(subm => (
191+
subm.submissionId !== payload
192+
)),
193+
},
172194
}),
173195
[a.registerInit]: state => ({ ...state, registering: true }),
174196
[a.registerDone]: onRegisterDone,
@@ -189,6 +211,7 @@ function create(initialState) {
189211
loadingCheckpoints: true,
190212
}),
191213
[a.fetchCheckpointsDone]: onFetchCheckpointsDone,
214+
[a.toggleCheckpointFeedback]: onToggleCheckpointFeedback,
192215
[a.openTermsModal]: state => ({ ...state, showTermsModal: true }),
193216
[a.closeTermsModal]: state => ({ ...state, showTermsModal: false }),
194217
}, _.defaults(initialState, {

0 commit comments

Comments
 (0)