|
14 | 14 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
15 | 15 | # |
16 | 16 |
|
17 | | -__author__ = 'isaiahmayerchak' |
| 17 | +__author__ = 'bmiller' |
18 | 18 |
|
19 | 19 | from docutils import nodes |
20 | 20 | from docutils.parsers.rst import directives |
21 | 21 | from docutils.parsers.rst import Directive |
22 | | -from .textfield import * |
23 | 22 |
|
24 | 23 |
|
25 | 24 | def setup(app): |
26 | 25 | app.add_directive('activecode', ActiveCode) |
| 26 | + app.add_directive('actex', ActiveExercise) |
| 27 | + app.add_stylesheet('codemirror.css') |
27 | 28 | app.add_stylesheet('activecode.css') |
28 | 29 |
|
29 | | - app.add_javascript('skulpt.min.js') |
30 | | - app.add_javascript('skulpt-stdlib.js') |
| 30 | + app.add_javascript('jquery.highlight.js') |
| 31 | + app.add_javascript('bookfuncs.js') |
31 | 32 | app.add_javascript('codemirror.js') |
| 33 | + app.add_javascript('xml.js') |
| 34 | + app.add_javascript('css.js') |
| 35 | + app.add_javascript('htmlmixed.js') |
32 | 36 | app.add_javascript('python.js') |
| 37 | + app.add_javascript('javascript.js') |
33 | 38 | app.add_javascript('activecode.js') |
34 | | - |
35 | | - app.add_role('textfield', textfield_role) |
| 39 | + app.add_javascript('skulpt.min.js') |
| 40 | + app.add_javascript('skulpt-stdlib.js') |
36 | 41 |
|
37 | 42 | app.add_node(ActivcodeNode, html=(visit_ac_node, depart_ac_node)) |
38 | 43 |
|
39 | 44 | app.connect('doctree-resolved', process_activcode_nodes) |
40 | 45 | app.connect('env-purge-doc', purge_activecodes) |
41 | 46 |
|
42 | 47 |
|
43 | | -TEMPLATE = """ |
44 | | - <pre data-component="activecode" id=%(divid)s data-lang="%(language)s" %(autorun)s %(hidecode)s %(include)s %(timelimit)s %(coach)s %(codelens)s> |
45 | | - %(initialcode)s |
46 | | - </pre> |
47 | | - """ |
| 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 | +''' |
| 52 | + |
| 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"> |
| 58 | +%(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 | + |
48 | 187 |
|
49 | 188 | class ActivcodeNode(nodes.General, nodes.Element): |
50 | 189 | def __init__(self, content): |
51 | 190 | """ |
| 191 | +
|
52 | 192 | Arguments: |
53 | 193 | - `self`: |
54 | 194 | - `content`: |
55 | 195 | """ |
56 | 196 | super(ActivcodeNode, self).__init__() |
57 | | - self.ac_options = content |
| 197 | + self.ac_components = content |
58 | 198 |
|
| 199 | + |
| 200 | +# self for these functions is an instance of the writer class. For example |
| 201 | +# in html, self is sphinx.writers.html.SmartyPantsHTMLTranslator |
| 202 | +# The node that is passed as a parameter is an instance of our node class. |
59 | 203 | def visit_ac_node(self, node): |
60 | 204 | # print self.settings.env.activecodecounter |
61 | | - res = "" |
62 | | - |
63 | | - if 'language' not in node.ac_options: |
64 | | - node.ac_options['language'] = 'python' |
65 | | - |
66 | | - if 'autorun' in node.ac_options: |
67 | | - node.ac_options['autorun'] = 'data-autorun' |
68 | | - else: |
69 | | - node.ac_options['autorun'] = '' |
70 | | - |
71 | | - if 'hidecode' in node.ac_options: |
72 | | - node.ac_options['hidecode'] = 'data-hidecode' |
73 | | - else: |
74 | | - node.ac_options['hidecode'] = '' |
75 | | - |
76 | | - if 'include' in node.ac_options: |
77 | | - node.ac_options['include'] = 'data-include=' + str(node.ac_options['include']) |
| 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 |
78 | 210 | else: |
79 | | - node.ac_options['include'] = '' |
80 | | - |
81 | | - if 'timelimit' in node.ac_options: |
82 | | - node.ac_options['timelimit'] = 'data-timelimit=' + str(node.ac_options['timelimit']) |
83 | | - else: |
84 | | - node.ac_options['timelimit'] = '' |
85 | | - |
86 | | - if 'coach' in node.ac_options: |
87 | | - node.ac_options['coach'] = 'data-coach' |
88 | | - else: |
89 | | - node.ac_options['coach'] = '' |
90 | | - |
91 | | - if 'codelens' in node.ac_options: |
92 | | - node.ac_options['codelens'] = 'data-codelens' |
93 | | - else: |
94 | | - node.ac_options['codelens'] = '' |
95 | | - |
96 | | - res += TEMPLATE % node.ac_options |
97 | | - |
| 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 |
| 249 | + res = res % node.ac_components |
98 | 250 | res = res.replace("u'", "'") # hack: there must be a better way to include the list and avoid unicode strings |
99 | 251 |
|
100 | 252 | self.body.append(res) |
@@ -148,11 +300,80 @@ def run(self): |
148 | 300 |
|
149 | 301 | self.options['divid'] = self.arguments[0] |
150 | 302 |
|
151 | | - source = "\n".join(self.content) |
| 303 | + 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" |
| 311 | + else: |
| 312 | + source = '\n' |
| 313 | + suffix = '\n' |
| 314 | + |
152 | 315 | self.options['initialcode'] = source |
| 316 | + self.options['suffix'] = suffix |
| 317 | + str = source.replace("\n", "*nline*") |
| 318 | + str0 = str.replace("\"", "*doubleq*") |
| 319 | + str1 = str0.replace("(", "*open*") |
| 320 | + str2 = str1.replace(")", "*close*") |
| 321 | + str3 = str2.replace("'", "*singleq*") |
| 322 | + self.options['argu'] = str3 |
| 323 | + |
| 324 | + complete = "" |
| 325 | + no_of_buttons = 0 |
| 326 | + okeys = list(self.options.keys()) |
| 327 | + for k in okeys: |
| 328 | + if '_' in k: |
| 329 | + x, label = k.split('_') |
| 330 | + no_of_buttons = no_of_buttons + 1 |
| 331 | + complete = complete + self.options[k] + "*atype*" |
| 332 | + |
| 333 | + newcomplete = complete.replace("\"", "*doubleq*") |
| 334 | + self.options['ctext'] = newcomplete |
| 335 | + self.options['no_of_buttons'] = no_of_buttons |
| 336 | + |
| 337 | + if 'caption' not in self.options: |
| 338 | + self.options['caption'] = '' |
| 339 | + |
| 340 | + if 'include' not in self.options: |
| 341 | + self.options['include'] = 'undefined' |
| 342 | + else: |
| 343 | + lst = self.options['include'].split(',') |
| 344 | + lst = [x.strip() for x in lst] |
| 345 | + self.options['include'] = lst |
| 346 | + |
| 347 | + if 'hidecode' in self.options: |
| 348 | + self.options['hidecode'] = 'none' |
| 349 | + else: |
| 350 | + self.options['hidecode'] = 'block' |
| 351 | + |
| 352 | + if 'language' not in self.options: |
| 353 | + self.options['language'] = 'python' |
| 354 | + |
| 355 | + if 'nocodelens' in self.options or self.options['language'] != 'python': |
| 356 | + self.options['codelens'] = False |
| 357 | + else: |
| 358 | + self.options['codelens'] = True |
| 359 | + |
| 360 | + if 'timelimit' not in self.options: |
| 361 | + self.options['timelimit'] = '' |
153 | 362 |
|
154 | 363 | return [ActivcodeNode(self.options)] |
155 | 364 |
|
156 | 365 |
|
| 366 | +class ActiveExercise(ActiveCode): |
| 367 | + required_arguments = 1 |
| 368 | + optional_arguments = 0 |
| 369 | + has_content = True |
| 370 | + |
| 371 | + def run(self): |
| 372 | + self.options['hidecode'] = True |
| 373 | + self.options['gradebutton'] = True |
| 374 | + self.options['coach'] = True |
| 375 | + return super(ActiveExercise, self).run() |
| 376 | + |
| 377 | + |
157 | 378 | if __name__ == '__main__': |
158 | 379 | a = ActiveCode() |
0 commit comments