Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39551
1
##############################################################################
2
#
3
# CoCalc: Collaborative Calculation in the Cloud
4
#
5
# Copyright (C) 2015 -- 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
$ = window.$
23
24
# standard non-CoCalc libraries
25
immutable = require('immutable')
26
underscore = require('underscore')
27
28
# CoCalc libraries
29
misc = require('smc-util/misc')
30
{defaults, required} = misc
31
{webapp_client} = require('./webapp_client')
32
33
{synchronized_string} = require('./syncdoc')
34
35
# React libraries
36
{React, ReactDOM, rclass, rtypes, redux, Redux, Actions, Store} = require('./smc-react')
37
{Loading} = require('r_misc')
38
{Input} = require('react-bootstrap')
39
40
redux_name = (project_id, filename) ->
41
return "editor-#{project_id}-#{filename}"
42
43
class CodemirrorActions extends Actions
44
report_error: (mesg) =>
45
@setState(error:mesg)
46
47
sync: =>
48
@set_value(@syncstring.live())
49
50
set_style: (style) =>
51
@setState
52
style: misc.merge(style, @redux.getStore(@name).get('style').toJS())
53
54
set_value: (value) =>
55
if @redux.getStore(@name).get('value') != value
56
@setState(value: value)
57
@syncstring.live(value)
58
@syncstring.sync()
59
60
set_scroll_info: (scroll_info) =>
61
@setState(scroll_info: scroll_info)
62
63
# This is used to save the state of the document (scroll positions, etc.)
64
# This does *NOT* change the document to have this doc.
65
set_codemirror_doc: (doc) =>
66
@setState(doc : doc)
67
68
default_store_state =
69
style :
70
border : '1px solid grey'
71
value : ''
72
options : {}
73
74
init_redux = (path, redux, project_id) ->
75
name = redux_name(project_id, path)
76
if redux.getActions(name)?
77
return # already initialized
78
actions = redux.createActions(name, CodemirrorActions)
79
store = redux.createStore(name, default_store_state)
80
81
console.log("getting syncstring for '#{path}'")
82
synchronized_string
83
project_id : project_id
84
path : path
85
sync_interval : 100
86
cb : (err, syncstring) ->
87
if err
88
actions.report_error("unable to open #{@path}")
89
else
90
syncstring.on('sync', actions.sync)
91
store.syncstring = actions.syncstring = syncstring
92
actions.set_value(syncstring.live())
93
94
return name
95
96
remove_redux(path, redux, project_id) ->
97
name = redux_name(project_id, path)
98
store = redux.getStore(name)
99
if not store?
100
return
101
store.syncstring?.destroy()
102
delete store.state
103
# It is *critical* to first unmount the store, then the actions,
104
# or there will be a huge memory leak.
105
redux.removeStore(name)
106
redux.removeActions(name)
107
return name
108
109
CodemirrorEditor = rclass ({name}) ->
110
reduxProps :
111
"#{name}" :
112
value : rtypes.string
113
options : rtypes.object
114
style : rtypes.object
115
scroll_info : rtypes.object
116
doc : rtypes.object
117
118
propTypes :
119
actions : rtypes.object
120
121
_cm_destroy: ->
122
if @cm?
123
@cm.toTextArea()
124
@cm.off('change', @_cm_change)
125
@cm.off('scroll', @_cm_scroll)
126
delete @cm
127
128
init_codemirror: (options, style, value) ->
129
# console.log("init_codemirror", options)
130
@_cm_destroy()
131
132
node = $(ReactDOM.findDOMNode(@)).find("textarea")[0]
133
@cm = CodeMirror.fromTextArea(node, options)
134
if @props.doc?
135
@cm.swapDoc(@props.doc)
136
if value? and value != @props.doc?.getValue()
137
@cm.setValueNoJump(value)
138
if style?
139
$(@cm.getWrapperElement()).css(style)
140
if @props.scroll_info?
141
# console.log("setting scroll_info to ", @props.scroll_info)
142
@cm.scrollTo(@props.scroll_info.left, @props.scroll_info.top)
143
144
@cm.on('change', @_cm_change)
145
@cm.on('scroll', @_cm_scroll)
146
147
_cm_change: ->
148
# console.log("_cm_change")
149
@_cm_set_value = @cm.getValue()
150
@props.actions.set_value(@_cm_set_value)
151
152
_cm_scroll: ->
153
@_cm_scroll_info = @cm.getScrollInfo()
154
155
componentDidMount: ->
156
#console.log("componentDidMount")
157
#window.c = @
158
@init_codemirror(@props.options, @props.style, @props.value)
159
160
componentWillReceiveProps: (newProps) ->
161
if not @cm? or not underscore.isEqual(@props.options, newProps.options) or not underscore.isEqual(@props.style, newProps.style)
162
@init_codemirror(newProps.options, newProps.style, newProps.value)
163
else if newProps.value != @props.value and newProps.value != @_cm_set_value
164
@cm?.setValueNoJump(newProps.value)
165
166
componentWillUnmount: ->
167
# console.log("componentWillUnmount")
168
if @cm?
169
if @_cm_scroll_info?
170
@props.actions?.set_scroll_info(@_cm_scroll_info)
171
doc = @cm.getDoc()
172
delete doc.cm # so @cm gets freed from memory when destroyed and doc is not attached to it.
173
@props.actions?.set_codemirror_doc(doc)
174
@_cm_destroy()
175
176
render_info: ->
177
if @props.value?
178
<span>Buffer length: {@props.value.length}</span>
179
180
render: ->
181
<div>
182
<h4>A React/Redux/Codemirror Editor</h4>
183
{@render_info()}
184
<textarea />
185
</div>
186
187
require('project_file').register_file_editor
188
ext : ['txt', '']
189
icon : 'file-code-o'
190
init : init_redux
191
component : CodemirrorEditor
192
remove : remove_redux
193
194