Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit c10e3e3

Browse files
committed
Merge pull request #33 from RunestoneInteractive/master
updating
2 parents a7ad4bf + 0413090 commit c10e3e3

File tree

18 files changed

+1281
-1193
lines changed

18 files changed

+1281
-1193
lines changed

runestone/activecode/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
### activecode
2+
3+
```html
4+
<pre data-component="activecode" id="example1" data-lang="python">
5+
def main()
6+
print("hello world")
7+
8+
main()
9+
</pre>
10+
```
11+
12+
The body of the ``pre`` tag contains code to be loaded into the editor initially. The following attributes are options and control what pieces and parts of the component will be visible.
13+
14+
* ``data-component`` attribute identifies this as an activecode component
15+
* ``class`` The usual CSS class options
16+
* ``id`` must be unique in the document
17+
* ``data-lang`` for activecode can be python javascript or html
18+
* ``data-autorun`` run this activecode as soon as the page is loaded
19+
* ``data-hidecode`` make the editor hidden initially
20+
* ``data-include`` list of ids of other activecodes. The code form each will be prepended to the code to run
21+
* ``data-timelimit`` either False to turn off runtime limit checking or an integer representing the number of milliseconds until timeout.
22+
* ``data-coach`` add a button to display code coach information
23+
* ``data-codelens`` add a button "Run this in Codelens"
24+
25+
26+
For Python to work in the browser you must also obtain and include via a script tag ``skulpt.min.js`` and
27+
``skulpt-stdlib.js`` along with ``codemirror.js`` and of course ``activecode.js``
28+
29+
Soon, many of these requirements will be incorporated into one handy ``runestone.js`` file.
30+
31+
32+

runestone/activecode/activecode.py

Lines changed: 68 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@
1919
from docutils import nodes
2020
from docutils.parsers.rst import directives
2121
from docutils.parsers.rst import Directive
22+
from .textfield import *
2223

24+
try:
25+
from html import escape # py3
26+
except ImportError:
27+
from cgi import escape # py2
2328

2429
def setup(app):
2530
app.add_directive('activecode', ActiveCode)
2631
app.add_directive('actex', ActiveExercise)
32+
app.add_role('textfield',textfield_role)
2733
app.add_stylesheet('codemirror.css')
2834
app.add_stylesheet('activecode.css')
2935

@@ -38,152 +44,20 @@ def setup(app):
3844
app.add_javascript('activecode.js')
3945
app.add_javascript('skulpt.min.js')
4046
app.add_javascript('skulpt-stdlib.js')
47+
app.add_javascript('clike.js')
4148

4249
app.add_node(ActivcodeNode, html=(visit_ac_node, depart_ac_node))
4350

4451
app.connect('doctree-resolved', process_activcode_nodes)
4552
app.connect('env-purge-doc', purge_activecodes)
4653

4754

48-
START = '''
49-
<div id="cont"></div>
50-
<div id="%(divid)s" lang="%(language)s" time="%(timelimit)s" class="ac_section alert alert-warning" >
51-
'''
5255

53-
EDIT1 = '''
54-
</div>
55-
<br/>
56-
<div id="%(divid)s_code_div" style="display: %(hidecode)s" class="ac_code_div">
57-
<textarea cols="50" rows="12" id="%(divid)s_code" class="active_code" prefixcode="%(include)s" lang="%(language)s">
56+
TEMPLATE = """
57+
<pre data-component="activecode" id=%(divid)s data-lang="%(language)s" %(autorun)s %(hidecode)s %(include)s %(timelimit)s %(coach)s %(codelens)s data-audio='%(ctext)s' %(sourcefile)s %(datafile)s %(stdin)s %(gradebutton)s %(caption)s>
5858
%(initialcode)s
59-
</textarea>
60-
</div>
61-
'''
62-
63-
CAPTION = '''
64-
<div class="clearfix"></div>
65-
<p class="ac_caption"><span class="ac_caption_text">%(caption)s (%(divid)s)</span> </p>
66-
'''
67-
68-
UNHIDE = '''
69-
<span class="ac_sep"></span>
70-
<button class='btn btn-default' id="%(divid)s_showb" onclick="$('#%(divid)s_code_div').toggle();cm_editors['%(divid)s_code'].refresh();$('#%(divid)s_saveb').toggle();$('#%(divid)s_loadb').toggle()">Show/Hide Code</button>
71-
'''
72-
73-
GRADES = '''
74-
<span class="ac_sep"></span>
75-
<input type="button" class='btn btn-default ' id="gradeb" name="Show Feedback" value="Show Feedback" onclick="createGradeSummary('%(divid)s')"/>
76-
'''
77-
78-
AUDIO = '''
79-
<span class="ac_sep"></span>
80-
<input type="button" class='btn btn-default ' id="audiob" name="Play Audio" value="Start Audio Tour" onclick="createAudioTourHTML('%(divid)s','%(argu)s','%(no_of_buttons)s','%(ctext)s')"/>
81-
'''
82-
83-
EDIT2 = '''
84-
<div class="ac_actions">
85-
<button class='btn btn-success' id="%(divid)s_runb">Run</button>
86-
<button class="ac_opt btn btn-default" style="display: inline-block" id="%(divid)s_saveb" onclick="saveEditor('%(divid)s');">Save</button>
87-
<button class="ac_opt btn btn-default" style="display: inline-block" id="%(divid)s_loadb" onclick="requestCode('%(divid)s');">Load</button>
88-
'''
89-
90-
VIZB = '''<button class='btn btn-default' id="%(divid)s_vizb" onclick="injectCodelens(this,'%(divid)s');">Show in Codelens</button>
91-
'''
92-
93-
COACHB = '''<button class='ac_opt btn btn-default' id="%(divid)s_coach_b" onclick="injectCodeCoach('%(divid)s');">Code Coach</button>
94-
'''
95-
96-
SCRIPT = '''
97-
<script>
98-
if ('%(hidecode)s' == 'none') {
99-
// a hack to preserve the inline-block display style. Toggle() will use display: block
100-
// (instead of inline-block) if the previous display style was 'none'
101-
$('#%(divid)s_saveb').toggle();
102-
$('#%(divid)s_loadb').toggle();
103-
}
104-
if ($("#%(divid)s").attr("lang") !== "html" && $("#%(divid)s_code_div").parents(".admonition").length == 0 && $("#%(divid)s_code_div").parents("#exercises").length == 0){
105-
if ($(window).width() > 975){
106-
$("#%(divid)s_code_div").offset({
107-
left: $("#%(divid)s .clearfix").offset().left
108-
});
109-
}
110-
$("#%(divid)s_runb").one("click", function(){
111-
$({})
112-
.queue(function (next) {
113-
if ($(window).width() > 975){
114-
$("#%(divid)s_code_div").animate({
115-
left: 40
116-
}, 500, next);
117-
if (! Sk.TurtleGraphics ) {
118-
Sk.TurtleGraphics = {};
119-
}
120-
Sk.TurtleGraphics.height = 320;
121-
Sk.TurtleGraphics.width = 320;
122-
}
123-
else{
124-
next();
125-
}
126-
})
127-
.queue(function (next) {
128-
$("#%(divid)s_runb").parent().siblings(".ac_output").show();
129-
runit('%(divid)s',this, %(include)s);
130-
$("#%(divid)s_runb").on("click", function(){
131-
runit('%(divid)s',this, %(include)s);
132-
});
133-
})
134-
135-
});
136-
}
137-
else{
138-
$("#%(divid)s_code_div").css({float : "none", marginLeft : "auto", marginRight : "auto"});
139-
$("#%(divid)s_runb").parent().siblings(".ac_output").show().css({float : "none", right : "0px"});
140-
$("#%(divid)s_runb").on("click", function(){
141-
runit('%(divid)s',this, %(include)s);
142-
});
143-
}
144-
</script>
145-
'''
146-
OUTPUT_START = '''
147-
<div class="ac_output">'''
148-
149-
CANVAS = '''
150-
<div style="text-align: center">
151-
<div id="%(divid)s_canvas" class="ac-canvas" style="border-style: solid; text-align: center"></div>
152-
</div>
153-
'''
154-
155-
SUFF = '''<pre id="%(divid)s_suffix" style="display:none">%(suffix)s</pre>'''
156-
157-
PRE = '''<pre id="%(divid)s_pre" class="active_out"></pre>
158-
'''
159-
OUTPUT_END = '''
160-
</div> <!-- end output -->'''
161-
162-
VIZ = '''<div id="%(divid)s_codelens_div" style="display:none"></div>'''
163-
164-
# <iframe id="%(divid)s_codelens" width="800" height="500" style="display:block"src="#">
165-
# </iframe>
166-
167-
COACH = '''<div id="%(divid)s_coach_div" style="display:none;"></div>'''
168-
169-
HTMLOUT = '''<div id="%(divid)s_htmlout" style="display:none;" class="ac_htmlout"></div>'''
170-
171-
END = '''
172-
</div>
173-
174-
'''
175-
176-
AUTO = '''
177-
<script type="text/javascript">
178-
$(document).ready(function() {
179-
$(window).load(function() {
180-
var runb = document.getElementById("%(divid)s_runb");
181-
runit('%(divid)s',runb, %(include)s);
182-
});
183-
});
184-
</script>
185-
'''
186-
59+
</pre>
60+
"""
18761

18862
class ActivcodeNode(nodes.General, nodes.Element):
18963
def __init__(self, content):
@@ -202,50 +76,11 @@ def __init__(self, content):
20276
# The node that is passed as a parameter is an instance of our node class.
20377
def visit_ac_node(self, node):
20478
# print self.settings.env.activecodecounter
205-
res = START
206-
if 'above' in node.ac_components:
207-
res += CANVAS
208-
if 'tour_1' not in node.ac_components:
209-
res += EDIT2
210-
else:
211-
res += EDIT2 + AUDIO
212-
if node.ac_components['codelens']:
213-
res += VIZB
214-
215-
if 'coach' in node.ac_components:
216-
res += COACHB
217-
218-
if 'hidecode' not in node.ac_components:
219-
node.ac_components['hidecode'] = 'block'
220-
if node.ac_components['hidecode'] == 'none':
221-
res += UNHIDE
222-
if 'gradebutton' in node.ac_components:
223-
res += GRADES
224-
res += EDIT1
225-
res += OUTPUT_START
226-
if 'above' not in node.ac_components:
227-
if 'nocanvas' not in node.ac_components:
228-
res += CANVAS
229-
if 'suffix' in node.ac_components:
230-
res += SUFF
231-
if 'nopre' not in node.ac_components:
232-
res += PRE
233-
if 'autorun' in node.ac_components:
234-
res += AUTO
235-
res += OUTPUT_END
236-
res += CAPTION
237-
238-
if node.ac_components['codelens']:
239-
res += VIZ
240-
241-
if 'coach' in node.ac_components:
242-
res += COACH
243-
244-
if node.ac_components['language'] == 'html':
245-
res += HTMLOUT
246-
247-
res += SCRIPT
248-
res += END
79+
res = TEMPLATE
80+
#todo: handle above in node.ac_components
81+
#todo handle 'hidecode' not in node.ac_components:
82+
# todo: handle if 'gradebutton' in node.ac_components: res += GRADES
83+
24984
res = res % node.ac_components
25085
res = res.replace("u'", "'") # hack: there must be a better way to include the list and avoid unicode strings
25186

@@ -288,7 +123,10 @@ class ActiveCode(Directive):
288123
'tour_5': directives.unchanged,
289124
'nocodelens': directives.flag,
290125
'coach': directives.flag,
291-
'timelimit': directives.unchanged
126+
'timelimit': directives.unchanged,
127+
'stdin' : directives.unchanged,
128+
'datafile' : directives.unchanged,
129+
'sourcefile' : directives.unchanged
292130
}
293131

294132
def run(self):
@@ -301,19 +139,11 @@ def run(self):
301139
self.options['divid'] = self.arguments[0]
302140

303141
if self.content:
304-
if '====' in self.content:
305-
idx = self.content.index('====')
306-
source = "\n".join(self.content[:idx])
307-
suffix = "\n".join(self.content[idx + 1:])
308-
else:
309-
source = "\n".join(self.content)
310-
suffix = "\n"
142+
source = "\n".join(self.content)
311143
else:
312144
source = '\n'
313-
suffix = '\n'
314145

315146
self.options['initialcode'] = source
316-
self.options['suffix'] = suffix
317147
str = source.replace("\n", "*nline*")
318148
str0 = str.replace("\"", "*doubleq*")
319149
str1 = str0.replace("(", "*open*")
@@ -336,29 +166,66 @@ def run(self):
336166

337167
if 'caption' not in self.options:
338168
self.options['caption'] = ''
169+
else:
170+
self.options['caption'] = "data-caption='%s'" % self.options['caption']
339171

340172
if 'include' not in self.options:
341-
self.options['include'] = 'undefined'
173+
self.options['include'] = ''
342174
else:
343175
lst = self.options['include'].split(',')
344176
lst = [x.strip() for x in lst]
345-
self.options['include'] = lst
177+
self.options['include'] = 'data-include=' + " ".join(lst)
346178

347179
if 'hidecode' in self.options:
348-
self.options['hidecode'] = 'none'
180+
self.options['hidecode'] = 'data-hidecode="true"'
349181
else:
350-
self.options['hidecode'] = 'block'
182+
self.options['hidecode'] = ''
351183

352184
if 'language' not in self.options:
353185
self.options['language'] = 'python'
354186

187+
if self.options['language'] == 'html':
188+
self.options['language'] = 'htmlmixed'
189+
self.options['initialcode'] = escape(self.options['initialcode'])
190+
355191
if 'nocodelens' in self.options or self.options['language'] != 'python':
356-
self.options['codelens'] = False
192+
self.options['codelens'] = ''
357193
else:
358-
self.options['codelens'] = True
194+
self.options['codelens'] = 'data-codelens="true"'
359195

360196
if 'timelimit' not in self.options:
361-
self.options['timelimit'] = ''
197+
self.options['timelimit'] = 'data-timelimit=25000'
198+
else:
199+
self.options['timelimit'] = 'data-timelimit=%d' % self.options['timelimit']
200+
201+
if 'autorun' not in self.options:
202+
self.options['autorun'] = ''
203+
else:
204+
self.options['autorun'] = 'data-autorun="true"'
205+
206+
if 'coach' in self.options:
207+
self.options['coach'] = 'data-coach="true"'
208+
else:
209+
self.options['coach'] = ''
210+
211+
# livecode options
212+
if 'stdin' in self.options:
213+
self.options['stdin'] = "data-stdin='%s'" % self.options['stdin']
214+
else:
215+
self.options['stdin'] = ""
216+
217+
if 'datafile' not in self.options:
218+
self.options['datafile'] = ""
219+
else:
220+
self.options['datafile'] = "data-datafile='%s'" % self.options['datafile']
221+
222+
if 'sourcefile' not in self.options:
223+
self.options['sourcefile'] = ""
224+
else:
225+
self.options['sourcefile'] = "data-sourcefile='%s'" % self.options['sourcefile']
226+
227+
if 'gradebutton' not in self.options:
228+
self.options['gradebutton'] = ''
362229

363230
return [ActivcodeNode(self.options)]
364231

@@ -369,9 +236,9 @@ class ActiveExercise(ActiveCode):
369236
has_content = True
370237

371238
def run(self):
372-
self.options['hidecode'] = True
373-
self.options['gradebutton'] = True
374-
self.options['coach'] = True
239+
self.options['hidecode'] = "data-hidecode=true"
240+
self.options['gradebutton'] = "data-gradebutton=true"
241+
self.options['coach'] = "data-coach=true"
375242
return super(ActiveExercise, self).run()
376243

377244

0 commit comments

Comments
 (0)