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

Commit eed968a

Browse files
author
Brad Miller
committed
Merge branch 'master' into page_progress
2 parents 227e2b8 + c686020 commit eed968a

7 files changed

Lines changed: 390 additions & 6 deletions

File tree

runestone/activecode/activecode.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ def setup(app):
5050
app.add_autoversioned_javascript('activecode.js')
5151
app.add_autoversioned_javascript('clike.js')
5252
app.add_autoversioned_javascript('timed_activecode.js')
53+
app.add_autoversioned_javascript('sql-wasm.js') # todo: only load if we need it
5354

5455

5556

@@ -68,7 +69,7 @@ def setup(app):
6869
TEMPLATE_END = """
6970
<textarea data-component="activecode" id=%(divid)s data-lang="%(language)s" %(autorun)s
7071
%(hidecode)s %(include)s %(timelimit)s %(coach)s %(codelens)s %(enabledownload)s %(chatcodes)s
71-
data-audio='%(ctext)s' %(sourcefile)s %(datafile)s %(stdin)s %(tie)s %(nopair)s
72+
data-audio='%(ctext)s' %(sourcefile)s %(datafile)s %(stdin)s %(tie)s %(dburl)s %(nopair)s
7273
%(cargs)s %(largs)s %(rargs)s %(iargs)s %(gradebutton)s %(caption)s %(hidehistory)s>
7374
%(initialcode)s
7475
</textarea>
@@ -154,6 +155,7 @@ class ActiveCode(RunestoneIdDirective):
154155
:available_files: : other additional files (java, python2, python3)
155156
:enabledownload: -- allow textfield contents to be downloaded as *.py file
156157
:nopair: -- disable pair programming features
158+
:dburl: url to load database for sql mode
157159
158160
If this is a homework problem instead of an example in the text
159161
then the assignment text should go here. The assignment text ends with
@@ -201,7 +203,8 @@ class ActiveCode(RunestoneIdDirective):
201203
'interpreterargs': directives.unchanged,
202204
'runargs': directives.unchanged,
203205
'tie': directives.unchanged,
204-
'nopair': directives.flag
206+
'nopair': directives.flag,
207+
'dburl': directives.unchanged
205208
})
206209

207210

@@ -330,6 +333,11 @@ def run(self):
330333
else:
331334
self.options['tie'] = ""
332335

336+
if 'dburl' in self.options:
337+
self.options['dburl'] = "data-dburl='{}'".format(self.options['dburl'])
338+
else:
339+
self.options['dburl'] = ""
340+
333341
for opt,tp in [('compileargs','cargs'),('linkargs','largs'),('runargs','rargs'),('interpreterargs','iargs')]:
334342
if opt in self.options:
335343
self.options[tp] = 'data-{}="{}"'.format(opt, escape(self.options[opt]))

runestone/activecode/js/activecode.js

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ActiveCode.prototype.init = function(opts) {
3838
this.chatcodes = $(orig).data('chatcodes');
3939
this.hidehistory = $(orig).data('hidehistory');
4040
this.tie = $(orig).data('tie')
41+
this.dburl = $(orig).data('dburl');
4142
this.runButton = null;
4243
this.enabledownload = $(orig).data('enabledownload');
4344
this.downloadButton = null;
@@ -475,7 +476,7 @@ ActiveCode.prototype.createOutput = function () {
475476
// to hold turtle graphics output. We use a div in case the turtle changes from
476477
// using a canvas to using some other element like svg in the future.
477478
var outDiv = document.createElement("div");
478-
$(outDiv).addClass("ac_output col-md-5");
479+
$(outDiv).addClass("ac_output col-md-12");
479480
this.outDiv = outDiv;
480481
this.output = document.createElement('pre');
481482
this.output.id = this.divid+'_stdout';
@@ -2358,6 +2359,153 @@ LiveCode.prototype.pushDataFile = function (file, resolve, reject) {
23582359
return classes;
23592360
}
23602361

2362+
//
2363+
// SQL
2364+
//
2365+
2366+
SQLActiveCode.prototype = new ActiveCode();
2367+
2368+
function SQLActiveCode(opts) {
2369+
if (opts) {
2370+
this.init(opts)
2371+
}
2372+
}
2373+
2374+
SQLActiveCode.prototype.init = function(opts) {
2375+
2376+
ActiveCode.prototype.init.apply(this,arguments);
2377+
2378+
if (eBookConfig.useRunestoneServices) {
2379+
var fnprefix = '/runestone/books/published/' + eBookConfig.basecourse + '/_static';
2380+
} else {
2381+
var fnprefix = '/_static';
2382+
}
2383+
this.config = {
2384+
locateFile: filename => `${fnprefix}/${filename}`
2385+
}
2386+
2387+
var self = this;
2388+
2389+
initSqlJs(this.config).then(function (SQL) {
2390+
// set up call to load database asynchronously if given
2391+
if (self.dburl) {
2392+
var xhr = new XMLHttpRequest();
2393+
$(self.runButton).attr('disabled','disabled')
2394+
// For example: https://github.com/lerocha/chinook-database/raw/master/ChinookDatabase/DataSources/Chinook_Sqlite.sqlite
2395+
xhr.open('GET', self.dburl, true);
2396+
xhr.responseType = 'arraybuffer';
2397+
2398+
xhr.onload = e => {
2399+
var uInt8Array = new Uint8Array(xhr.response);
2400+
self.db = new SQL.Database(uInt8Array);
2401+
$(self.runButton).removeAttr('disabled')
2402+
// contents is now [{columns:['col1','col2',...], values:[[first row], [second row], ...]}]
2403+
};
2404+
xhr.send();
2405+
} else {
2406+
self.db = new SQL.Database();
2407+
}
2408+
});
2409+
};
2410+
2411+
2412+
SQLActiveCode.prototype.runProg = function() {
2413+
var result_mess = "success"
2414+
var scrubber_dfd, history_dfd, saveCode
2415+
// Clear any old results
2416+
saveCode = "True"
2417+
let divid = this.divid+'_sql_out';
2418+
let respDiv = document.getElementById(divid);
2419+
if (respDiv) {
2420+
respDiv.parentElement.removeChild(respDiv)
2421+
}
2422+
$(this.output).text("")
2423+
// Run this query
2424+
let query = this.buildProg();
2425+
try {
2426+
var res = this.db.exec(query);
2427+
} catch(error) {
2428+
result_mess = error.toString();
2429+
$(this.output).text(error);
2430+
$(this.outDiv).show();
2431+
2432+
}
2433+
this.logRunEvent({
2434+
'div_id': this.divid,
2435+
'code': this.editor.getValue(),
2436+
'lang': this.language,
2437+
'errinfo': result_mess,
2438+
'to_save': saveCode,
2439+
'prefix': this.pretext,
2440+
'suffix': this.suffix,
2441+
'partner': this.partner
2442+
}); // Log the run event
2443+
2444+
var __ret = this.manage_scrubber(scrubber_dfd, history_dfd, saveCode);
2445+
history_dfd = __ret.history_dfd;
2446+
saveCode = __ret.saveCode;
2447+
2448+
history_dfd.then(function() {
2449+
if (this.slideit) {
2450+
$(this.historyScrubber).on("slidechange", this.slideit.bind(this));
2451+
}
2452+
$(this.historyScrubber).slider("enable");
2453+
});
2454+
2455+
if (result_mess != "success") {
2456+
return;
2457+
}
2458+
// Create a nice table to show the result of the query
2459+
if (res[0].values.length > 100) {
2460+
$(this.output).text("Result set is longer than 100 rows limiting output to first 100")
2461+
}
2462+
let table = createTable(res[0]);
2463+
respDiv = document.createElement('div')
2464+
respDiv.id = divid;
2465+
$(respDiv).addClass('table-responsive-md')
2466+
$(respDiv).css('max-height', '500px')
2467+
$(respDiv).css('overflow', 'scroll')
2468+
this.outDiv.appendChild(respDiv)
2469+
respDiv.appendChild(table)
2470+
$(this.outDiv).show()
2471+
2472+
}
2473+
2474+
function createTable(tableData) {
2475+
var table = document.createElement('table');
2476+
var head = document.createElement('thead');
2477+
var tableBody = document.createElement('tbody');
2478+
var theads = document.createElement('tr')
2479+
2480+
tableData.columns.forEach(function(colData) {
2481+
let th = document.createElement('th');
2482+
th.appendChild(document.createTextNode(colData));
2483+
theads.appendChild(th);
2484+
});
2485+
table.appendChild(head);
2486+
head.appendChild(theads);
2487+
tableData.values.slice(0,100).forEach(function(rowData) {
2488+
var row = document.createElement('tr');
2489+
2490+
rowData.forEach(function(cellData) {
2491+
var cell = document.createElement('td');
2492+
cell.appendChild(document.createTextNode(cellData));
2493+
row.appendChild(cell);
2494+
});
2495+
2496+
tableBody.appendChild(row);
2497+
});
2498+
2499+
table.appendChild(tableBody);
2500+
$(table).css('background', 'white');
2501+
$(table).addClass('table-striped table-light thead-dark')
2502+
return table;
2503+
}
2504+
2505+
2506+
//
2507+
// ActiveCode Factory Class
2508+
//
23612509

23622510
ACFactory = {};
23632511

@@ -2372,6 +2520,8 @@ ACFactory.createActiveCode = function (orig, lang, addopts) {
23722520
return new JSActiveCode(opts);
23732521
} else if (lang === 'htmlmixed') {
23742522
return new HTMLActiveCode(opts);
2523+
} else if (lang === 'sql') {
2524+
return new SQLActiveCode(opts);
23752525
} else if (['java', 'cpp', 'c', 'python3', 'python2'].indexOf(lang) > -1) {
23762526
return new LiveCode(opts);
23772527
} else { // default is python
@@ -2380,6 +2530,7 @@ ACFactory.createActiveCode = function (orig, lang, addopts) {
23802530

23812531
};
23822532

2533+
23832534
// used by web2py controller(s)
23842535
ACFactory.addActiveCodeToDiv = function(outerdivid, acdivid, sid, initialcode, language) {
23852536
var thepre, newac;
@@ -2462,6 +2613,11 @@ ACFactory.toggleScratchActivecode = function () {
24622613

24632614
};
24642615

2616+
2617+
//
2618+
// Page Initialization
2619+
//
2620+
24652621
$(document).ready(function() {
24662622
ACFactory.createScratchActivecode();
24672623
$('[data-component=activecode]').each( function(index ) {

0 commit comments

Comments
 (0)