Skip to content

Commit 23a5091

Browse files
pwjablonskioutoftime
authored andcommitted
Create classroom assignment (#1511)
* update package lock * delete package lock * Combined Commits for Assignment Creator update package lock update package lock update package lock add assignment selector updates test add validation updates add redux form changes stash changes update redux-form remove legacy actions and reducers remove leftover code fix i18next update redux form update AssignmentCreatorForm container update files update package lock again update package locl start remote collection added tests update tests Generate fresh package-lock.json fix packages modified package-lock update packages again update getCurrentProjectPreview yarn changes lazy load add react lazy and suspense update redux-form update redux-form * update trailing comma * remove react-coroutine * update packckages * move suspense and remove key from fontawesome * add experimental mode
1 parent 4fe1c3b commit 23a5091

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+1255
-23
lines changed

locales/en/translation.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
{
2+
"assignment-creator": {
3+
"assign-button": "Assign",
4+
"draft-button": "Draft",
5+
"cancel-button": "Cancel",
6+
"title": "Create Assignment",
7+
"select-class": "Select a class",
8+
"no-class-selected": "Please select a class",
9+
"date-not-valid": "Please enter a valid date",
10+
"past-date-not-valid": "Please pick a future date",
11+
"input-placeholder": "When is it due?",
12+
"value-label": "{{valueLabel, formatDate}}"
13+
},
214
"top-bar": {
315
"create-snapshot": "Create Snapshot",
16+
"create-assignment": "Create Assignment",
417
"export-gist": "Export Gist",
518
"export-repo": "Export Repo",
619
"update-repo": "Update Repo",
@@ -53,7 +66,10 @@
5366
"identity-linked": "Your GitHub account is now linked!",
5467
"link-identity-failed": "There was a problem linking your GitHub account.",
5568
"identity-unlinked": "The linked GitHub account has been unlinked.",
56-
"gapi-client-unavailable": "It looks like you are having trouble with your internet connection. You can continue to work in Popcode but you won’t be able to log in or save your work. Proceed carefully!"
69+
"gapi-client-unavailable": "It looks like you are having trouble with your internet connection. You can continue to work in Popcode but you won’t be able to log in or save your work. Proceed carefully!",
70+
"assignment-export-complete": "Your assignment is created on Google Classroom",
71+
"assignment-draft-export-complete": "Your assignment draft has been created on Google Classroom",
72+
"assignment-not-created": "Your assignment was not created - make sure you are a teacher of this classroom to post assignments"
5773
},
5874
"languages": {
5975
"html": "HTML",

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@
194194
"array-to-sentence": "^2.0.0",
195195
"bowser": "^2.1.2",
196196
"brace": "^0.11.0",
197+
"chrono-node": "^1.3.11",
197198
"classnames": "^2.2.6",
198199
"css": "^2.2.4",
199200
"enum": "^2.5.0",
@@ -240,6 +241,7 @@
240241
"reduce-reducers": "^0.4.3",
241242
"redux": "^4.0.1",
242243
"redux-actions": "^2.6.5",
244+
"redux-form": "^8.1.0",
243245
"redux-immutable": "^4.0.0",
244246
"redux-logic": "^2.1.1",
245247
"redux-saga": "^1.0.2",

src/actions/assignments.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {createAction} from 'redux-actions';
2+
3+
export const createAssignment = createAction(
4+
'CREATE_ASSIGNMENT',
5+
(selectedCourseId, dueDate, assignmentState) =>
6+
({selectedCourseId, dueDate, assignmentState}),
7+
);
8+
9+
export const assignmentCreated = createAction(
10+
'ASSIGNMENT_CREATED',
11+
assignment => ({assignment}),
12+
);
13+
14+
export const assignmentNotCreated = createAction(
15+
'ASSIGNMENT_NOT_CREATED',
16+
);

src/actions/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import applicationLoaded from './applicationLoaded';
22

3+
import {
4+
createAssignment,
5+
} from './assignments';
6+
37
import {
48
createSnapshot,
59
exportProject,
@@ -38,6 +42,10 @@ import {
3842
cancelEditingInstructions,
3943
showSaveIndicator,
4044
hideSaveIndicator,
45+
openAssignmentCreator,
46+
closeAssignmentCreator,
47+
coursesLoaded,
48+
coursesFullyLoaded,
4149
} from './ui';
4250

4351
import {
@@ -130,4 +138,9 @@ export {
130138
updateResizableFlex,
131139
startAccountMigration,
132140
dismissAccountMigration,
141+
createAssignment,
142+
openAssignmentCreator,
143+
closeAssignmentCreator,
144+
coursesLoaded,
145+
coursesFullyLoaded,
133146
};

src/actions/ui.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,20 @@ export const showSaveIndicator = createAction(
6868
export const hideSaveIndicator = createAction(
6969
'HIDE_SAVE_INDICATOR',
7070
);
71+
72+
export const openAssignmentCreator = createAction(
73+
'OPEN_ASSIGNMENT_CREATOR',
74+
);
75+
76+
export const closeAssignmentCreator = createAction(
77+
'CLOSE_ASSIGNMENT_CREATOR',
78+
);
79+
80+
export const coursesLoaded = createAction(
81+
'COURSES_LOADED',
82+
courses => ({courses}),
83+
);
84+
85+
export const coursesFullyLoaded = createAction(
86+
'COURSES_FULLY_LOADED',
87+
);

src/clients/googleClassroom.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import qs from 'qs';
22

3+
import {AssignmentState} from '../enums';
4+
import {
5+
loadAndConfigureGapi,
6+
} from '../services/gapi';
7+
38
const BASE_URL = 'https://classroom.google.com/u/0/share?';
49

510
export function createShareToClassroomUrl(snapshotKey, title) {
@@ -8,3 +13,45 @@ export function createShareToClassroomUrl(snapshotKey, title) {
813
uri.search = `snapshot=${snapshotKey}`;
914
return BASE_URL + qs.stringify({url: uri.href, title});
1015
}
16+
17+
export async function getCourses(pageToken) {
18+
const gapi = await loadAndConfigureGapi();
19+
const {result} = await gapi.client.classroom.courses.list({pageToken});
20+
return result;
21+
}
22+
23+
export async function createClassroomAssignment({
24+
courseId,
25+
dueDate,
26+
url,
27+
title,
28+
state,
29+
}) {
30+
const gapi = await loadAndConfigureGapi();
31+
32+
const newAssignment = await gapi.client.classroom.courses.courseWork.create({
33+
courseId,
34+
title,
35+
workType: AssignmentState.ASSIGNMENT,
36+
state,
37+
dueDate: {
38+
year: dueDate.getUTCFullYear(),
39+
month: dueDate.getUTCMonth() + 1,
40+
day: dueDate.getUTCDate(),
41+
},
42+
dueTime: {
43+
hours: dueDate.getUTCHours(),
44+
minutes: dueDate.getUTCMinutes(),
45+
seconds: 0,
46+
nanos: 0,
47+
},
48+
materials: {
49+
link: {
50+
url,
51+
title,
52+
thumbnailUrl: 'N/A',
53+
},
54+
},
55+
});
56+
return newAssignment.result;
57+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {t} from 'i18next';
2+
import React, {lazy, Suspense} from 'react';
3+
import PropTypes from 'prop-types';
4+
import ImmutablePropTypes from 'react-immutable-proptypes';
5+
import {faSpinner} from '@fortawesome/free-solid-svg-icons';
6+
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
7+
8+
import Modal from './Modal';
9+
10+
const AssignmentCreatorForm = lazy(
11+
() => import(
12+
/* webpackChunkName: "mainAsync" */
13+
'../containers/AssignmentCreatorForm' // eslint-disable-line comma-dangle
14+
),
15+
);
16+
17+
export default function AssignmentCreator({
18+
areCoursesLoaded,
19+
courses,
20+
isOpen,
21+
parsedDate,
22+
projectTitle,
23+
onAssignAssignment,
24+
onCloseAssignmentCreator,
25+
onDraftAssignment,
26+
}) {
27+
if (!isOpen) {
28+
return null;
29+
}
30+
31+
return (
32+
<Modal>
33+
<Suspense fallback="Loading...">
34+
<div className="assignment-creator">
35+
<h1 className="assignment-creator__title">
36+
{t('assignment-creator.title')}
37+
</h1>
38+
<h3 className="assignment-creator__project-name">
39+
{projectTitle}
40+
</h3>
41+
{
42+
areCoursesLoaded ?
43+
<AssignmentCreatorForm
44+
courses={courses}
45+
parsedDate={parsedDate}
46+
onAssignAssignment={onAssignAssignment}
47+
onCloseAssignmentCreator={onCloseAssignmentCreator}
48+
onDraftAssignment={onDraftAssignment}
49+
/> :
50+
<FontAwesomeIcon icon={faSpinner} />
51+
}
52+
</div>
53+
</Suspense>
54+
</Modal>
55+
);
56+
}
57+
58+
AssignmentCreator.propTypes = {
59+
areCoursesLoaded: PropTypes.bool.isRequired,
60+
courses: ImmutablePropTypes.iterable.isRequired,
61+
isOpen: PropTypes.bool.isRequired,
62+
parsedDate: PropTypes.instanceOf(Date),
63+
projectTitle: PropTypes.string,
64+
onAssignAssignment: PropTypes.func.isRequired,
65+
onCloseAssignmentCreator: PropTypes.func.isRequired,
66+
onDraftAssignment: PropTypes.func.isRequired,
67+
};
68+
69+
AssignmentCreator.defaultProps = {
70+
parsedDate: null,
71+
projectTitle: null,
72+
};
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React from 'react';
2+
import {t} from 'i18next';
3+
import classnames from 'classnames';
4+
import chrono from 'chrono-node';
5+
import PropTypes from 'prop-types';
6+
import ImmutablePropTypes from 'react-immutable-proptypes';
7+
import {Field} from 'redux-form/immutable';
8+
9+
import FormDate from '../records/FormDate';
10+
11+
import AssignmentCreatorTextField from './AssignmentCreatorTextField';
12+
import AssignmentCreatorSelectField from './AssignmentCreatorSelectField';
13+
14+
function parseDate(value) {
15+
const [parsedResponse] = chrono.parse(value);
16+
let parsedDate;
17+
if (parsedResponse) {
18+
parsedDate = parsedResponse.start.date();
19+
}
20+
return new FormDate({
21+
string: value,
22+
parsedDate,
23+
});
24+
}
25+
26+
function formatDate(value) {
27+
if (value) {
28+
return value.string;
29+
}
30+
return '';
31+
}
32+
33+
export default function AssignmentCreatorForm({
34+
courses,
35+
handleSubmit,
36+
parsedDate,
37+
onAssignAssignment,
38+
onDraftAssignment,
39+
onCloseAssignmentCreator,
40+
}) {
41+
return (
42+
<form>
43+
<div>
44+
<Field
45+
component={AssignmentCreatorSelectField}
46+
name="course"
47+
>
48+
<option value="">{t('assignment-creator.select-class')}</option>
49+
{
50+
courses.map(course => (
51+
<option
52+
key={course.id}
53+
value={course.id}
54+
>
55+
{course.name}
56+
</option>
57+
)).valueSeq()
58+
}
59+
</Field>
60+
</div>
61+
<div>
62+
<Field
63+
component={AssignmentCreatorTextField}
64+
format={formatDate}
65+
name="date"
66+
parse={parseDate}
67+
placeholder={t('assignment-creator.input-placeholder')}
68+
type="text"
69+
valueLabel={parsedDate}
70+
/>
71+
</div>
72+
<button
73+
className={classnames(
74+
'assignment-creator__button',
75+
'assignment-creator__button_reject',
76+
)}
77+
type="button"
78+
onClick={onCloseAssignmentCreator}
79+
>
80+
{t('assignment-creator.cancel-button')}
81+
</button>
82+
<button
83+
className={classnames(
84+
'assignment-creator__button',
85+
'assignment-creator__button_confirm',
86+
)}
87+
type="button"
88+
onClick={handleSubmit(onDraftAssignment)}
89+
>
90+
{t('assignment-creator.draft-button')}
91+
</button>
92+
<button
93+
className={classnames(
94+
'assignment-creator__button',
95+
'assignment-creator__button_confirm',
96+
)}
97+
type="button"
98+
onClick={handleSubmit(onAssignAssignment)}
99+
>
100+
{t('assignment-creator.assign-button')}
101+
</button>
102+
</form>
103+
);
104+
}
105+
106+
AssignmentCreatorForm.propTypes = {
107+
courses: ImmutablePropTypes.iterable.isRequired,
108+
handleSubmit: PropTypes.func.isRequired,
109+
parsedDate: PropTypes.instanceOf(Date),
110+
onAssignAssignment: PropTypes.func.isRequired,
111+
onCloseAssignmentCreator: PropTypes.func.isRequired,
112+
onDraftAssignment: PropTypes.func.isRequired,
113+
};
114+
115+
AssignmentCreatorForm.defaultProps = {
116+
parsedDate: null,
117+
};

0 commit comments

Comments
 (0)