Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
| Download
Views: 39598
1
##############################################################################
2
#
3
# CoCalc: Collaborative Calculation in the Cloud
4
#
5
# Copyright (C) 2016, Sagemath Inc.
6
#
7
# This program is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
#
20
###############################################################################
21
22
###
23
Course Management
24
###
25
26
# standard non-CoCalc libraries
27
immutable = require('immutable')
28
29
# CoCalc libraries
30
misc = require('smc-util/misc')
31
32
# React libraries
33
{React, rclass, rtypes} = require('../smc-react')
34
35
{Button, ButtonToolbar, ButtonGroup, Row, Col, Panel, Tabs, Tab} = require('react-bootstrap')
36
37
{ActivityDisplay, ErrorDisplay, Icon, Loading, SaveButton} = require('../r_misc')
38
39
# Course components
40
{CourseStore} = require('./store')
41
{CourseActions} = require('./actions')
42
CourseSync = require('./sync')
43
{StudentsPanel} = require('./students_panel')
44
{AssignmentsPanel} = require('./assignments_panel')
45
{HandoutsPanel} = require('./handouts_panel')
46
{SettingsPanel} = require('./settings_panel')
47
{SharedProjectPanel} = require('./shared_project_panel')
48
{STEPS, previous_step, step_direction, step_verb, step_ready} = require('./util')
49
50
redux_name = (project_id, course_filename) ->
51
return "editor-#{project_id}-#{course_filename}"
52
53
syncdbs = {}
54
init_redux = (course_filename, redux, course_project_id) ->
55
the_redux_name = redux_name(course_project_id, course_filename)
56
get_actions = -> redux.getActions(the_redux_name)
57
if get_actions()?
58
# already initalized
59
return
60
61
initial_store_state =
62
course_filename : course_filename
63
course_project_id : course_project_id
64
expanded_students : immutable.Set() # Set of student id's (string) which should be expanded on render
65
expanded_assignments : immutable.Set() # Set of assignment id's (string) which should be expanded on render
66
expanded_handouts : immutable.Set() # Set of handout id's (string) which should be expanded on render
67
expanded_peer_configs : immutable.Set() # Set of assignment configs (key = assignment_id) which should be expanded on render
68
active_student_sort : {column_name : "last_name", is_descending : false}
69
active_assignment_sort : {column_name : "due_date", is_descending : false}
70
settings : {allow_collabs : true}
71
72
actions = redux.createActions(the_redux_name, CourseActions)
73
store = redux.createStore(the_redux_name, CourseStore, initial_store_state)
74
actions.syncdb = syncdbs[the_redux_name] = CourseSync.create_sync_db(redux, actions, store)
75
76
return the_redux_name
77
78
remove_redux = (course_filename, redux, course_project_id) ->
79
the_redux_name = redux_name(course_project_id, course_filename)
80
81
# Remove the listener for changes in the collaborators on this project.
82
actions = redux.getActions(the_redux_name)
83
if not actions?
84
# already cleaned up and removed.
85
return
86
redux.getStore('projects').removeListener('change', actions.handle_projects_store_update)
87
88
# Remove the store and actions.
89
redux.removeStore(the_redux_name)
90
redux.removeActions(the_redux_name)
91
syncdbs[the_redux_name]?.close()
92
delete syncdbs[the_redux_name]
93
return the_redux_name
94
95
CourseEditor = rclass ({name}) ->
96
displayName : "CourseEditor-Main"
97
98
reduxProps :
99
"#{name}" :
100
error : rtypes.string
101
tab : rtypes.string
102
activity : rtypes.object # status messages about current activity happening (e.g., things being assigned)
103
students : rtypes.immutable
104
assignments : rtypes.immutable
105
handouts : rtypes.immutable
106
settings : rtypes.immutable
107
unsaved : rtypes.bool
108
users :
109
user_map : rtypes.immutable
110
projects :
111
project_map : rtypes.immutable # gets updated when student is active on their project
112
113
propTypes :
114
redux : rtypes.object
115
name : rtypes.string.isRequired
116
project_id : rtypes.string.isRequired
117
path : rtypes.string.isRequired
118
119
render_activity: ->
120
<ActivityDisplay activity={misc.values(@props.activity)} trunc=80
121
on_clear={=>@props.redux.getActions(@props.name).clear_activity()} />
122
123
render_error: ->
124
<ErrorDisplay error={@props.error}
125
onClose={=>@props.redux.getActions(@props.name).set_error('')} />
126
127
render_save_button: ->
128
<SaveButton saving={@props.saving} unsaved={true} on_click={=>@props.redux.getActions(@props.name).save()}/>
129
130
show_files: ->
131
@props.redux?.getProjectActions(@props.project_id).set_active_tab('files')
132
133
render_files_button: ->
134
<Button className='smc-small-only' style={float:'right', marginLeft:'15px'}
135
onClick={@show_files}><Icon name='toggle-up'/> Files</Button>
136
137
render_title: ->
138
<h4 className='smc-big-only' style={float:'right', marginTop: '5px', marginBottom: '0px'}>
139
{misc.trunc(@props.settings?.get('title'),40)}
140
</h4>
141
142
show_timetravel: ->
143
@props.redux?.getProjectActions(@props.project_id).open_file
144
path : misc.history_path(@props.path)
145
foreground : true
146
foreground_project : true
147
148
save_to_disk: ->
149
@props.redux?.getActions(@props.name).save()
150
151
render_save_timetravel: ->
152
<div style={float:'right', marginRight:'15px'}>
153
<ButtonGroup>
154
<Button onClick={@save_to_disk} bsStyle='success' disabled={not @props.unsaved}>
155
<Icon name='save'/> Save
156
</Button>
157
<Button onClick={@show_timetravel} bsStyle='info'>
158
<Icon name='history'/> TimeTravel
159
</Button>
160
</ButtonGroup>
161
</div>
162
163
num_students: ->
164
@props.redux.getStore(@props.name)?.num_students()
165
166
num_assignments: ->
167
@props.redux.getStore(@props.name)?.num_assignments()
168
169
num_handouts: ->
170
@props.redux.getStore(@props.name)?.num_handouts()
171
172
render_students: ->
173
if @props.redux? and @props.students? and @props.user_map? and @props.project_map?
174
<StudentsPanel redux={@props.redux} students={@props.students}
175
name={@props.name} project_id={@props.project_id}
176
user_map={@props.user_map} project_map={@props.project_map}
177
assignments={@props.assignments}
178
/>
179
else
180
return <Loading />
181
182
render_assignments: ->
183
if @props.redux? and @props.assignments? and @props.user_map? and @props.students?
184
<AssignmentsPanel actions={@props.redux.getActions(@props.name)} redux={@props.redux} all_assignments={@props.assignments}
185
name={@props.name} project_id={@props.project_id} user_map={@props.user_map} students={@props.students} />
186
else
187
return <Loading />
188
189
render_handouts: ->
190
if @props.redux? and @props.assignments? and @props.user_map? and @props.students?
191
<HandoutsPanel actions={@props.redux.getActions(@props.name)} all_handouts={@props.handouts}
192
project_id={@props.project_id} user_map={@props.user_map} students={@props.students}
193
store_object={@props.redux.getStore(@props.name)} project_actions={@props.redux.getProjectActions(@props.project_id)}
194
name={@props.name}
195
/>
196
else
197
return <Loading />
198
199
render_settings: ->
200
if @props.redux? and @props.settings?
201
<SettingsPanel redux={@props.redux} settings={@props.settings}
202
name={@props.name} project_id={@props.project_id}
203
path={@props.path}
204
project_map={@props.project_map} />
205
else
206
return <Loading />
207
208
render_shared_project: ->
209
if @props.redux? and @props.settings?
210
<SharedProjectPanel redux={@props.redux} name={@props.name}
211
shared_project_id={@props.settings?.get('shared_project_id')}/>
212
else
213
return <Loading />
214
215
render: ->
216
<div style={padding:"7px 7px 7px 7px", borderTop: '1px solid rgb(170, 170, 170)'}>
217
{@render_save_button() if @props.show_save_button}
218
{@render_error() if @props.error}
219
{@render_activity() if @props.activity?}
220
{@render_files_button()}
221
{@render_title()}
222
{@render_save_timetravel()}
223
<Tabs id='course-tabs' animation={false} activeKey={@props.tab} onSelect={(key)=>@props.redux?.getActions(@props.name).set_tab(key)}>
224
<Tab eventKey={'students'} title={<StudentsPanel.Header n={@num_students()} />}>
225
{@render_students()}
226
</Tab>
227
<Tab eventKey={'assignments'} title={<AssignmentsPanel.Header n={@num_assignments()}/>}>
228
{@render_assignments()}
229
</Tab>
230
<Tab eventKey={'handouts'} title={<HandoutsPanel.Header n={@num_handouts()}/>}>
231
{@render_handouts()}
232
</Tab>
233
<Tab eventKey={'settings'} title={<SettingsPanel.Header />}>
234
<div style={marginTop:'1em'}></div>
235
{@render_settings()}
236
</Tab>
237
<Tab eventKey={'shared_project'} title={<SharedProjectPanel.Header project_exists={[email protected]?.get('shared_project_id')}/>}>
238
<div style={marginTop:'1em'}></div>
239
{@render_shared_project()}
240
</Tab>
241
</Tabs>
242
</div>
243
244
require('project_file').register_file_editor
245
ext : 'course'
246
icon : 'graduation-cap'
247
init : init_redux
248
component : CourseEditor
249
remove : remove_redux
250
251