pxt-calliope/pxtrunner.js
Matthias L. Jugel b028b1df5b Auto-push
2017-08-19 17:16:35 +02:00

1148 lines
57 KiB
JavaScript

var pxt;
(function (pxt) {
var runner;
(function (runner) {
/**
* Starts the simulator and injects it into the provided container.
* the simulator will attempt to establish a websocket connection
* to the debugger's user interface on port 3234.
*
* @param container The container to inject the simulator into
*/
function startDebuggerAsync(container) {
var debugRunner = new DebugRunner(container);
debugRunner.start();
}
runner.startDebuggerAsync = startDebuggerAsync;
/**
* Runner for the debugger that handles communication with the user
* interface. Also talks to the server for anything to do with
* the filesystem (like reading code)
*/
var DebugRunner = (function () {
function DebugRunner(container) {
this.container = container;
this.pkgLoaded = false;
this.intervalRunning = false;
}
DebugRunner.prototype.start = function () {
var _this = this;
this.initializeWebsocket();
if (!this.intervalRunning) {
this.intervalRunning = true;
this.intervalId = setInterval(function () {
if (!_this.ws) {
try {
_this.initializeWebsocket();
}
catch (e) {
console.warn("Connection to server failed, retrying in " + DebugRunner.RETRY_MS + " ms");
}
}
}, DebugRunner.RETRY_MS);
}
this.session = new pxsim.SimDebugSession(this.container);
this.session.start(this);
};
DebugRunner.prototype.initializeWebsocket = function () {
var _this = this;
if (!pxt.Cloud.isLocalHost() || !pxt.Cloud.localToken)
return;
pxt.debug('initializing debug pipe');
this.ws = new WebSocket('ws://localhost:3234/' + pxt.Cloud.localToken + '/simdebug');
this.ws.onopen = function (ev) {
pxt.debug('debug: socket opened');
};
this.ws.onclose = function (ev) {
pxt.debug('debug: socket closed');
if (_this.closeListener) {
_this.closeListener();
}
_this.session.stopSimulator();
_this.ws = undefined;
};
this.ws.onerror = function (ev) {
pxt.debug('debug: socket closed due to error');
if (_this.errorListener) {
_this.errorListener(ev.type);
}
_this.session.stopSimulator();
_this.ws = undefined;
};
this.ws.onmessage = function (ev) {
var message;
try {
message = JSON.parse(ev.data);
}
catch (e) {
pxt.debug('debug: could not parse message');
}
if (message) {
// FIXME: ideally, we should just open two websockets instead of adding to the
// debug protocol. One for the debugger, one for meta-information and file
// system requests
if (message.type === 'runner') {
_this.handleRunnerMessage(message);
}
else {
// Intercept the launch configuration and notify the server-side debug runner
if (message.type === "request" && message.command === "launch") {
_this.sendRunnerMessage("configure", {
projectDir: message.arguments.projectDir
});
}
_this.dataListener(message);
}
}
};
};
DebugRunner.prototype.send = function (msg) {
this.ws.send(msg);
};
DebugRunner.prototype.onData = function (cb) {
this.dataListener = cb;
};
DebugRunner.prototype.onError = function (cb) {
this.errorListener = cb;
};
DebugRunner.prototype.onClose = function (cb) {
this.closeListener = cb;
};
DebugRunner.prototype.close = function () {
if (this.session) {
this.session.stopSimulator(true);
}
if (this.intervalRunning) {
clearInterval(this.intervalId);
this.intervalId = undefined;
}
if (this.ws) {
this.ws.close();
}
};
DebugRunner.prototype.handleRunnerMessage = function (msg) {
switch (msg.subtype) {
case "ready":
this.sendRunnerMessage("ready");
break;
case "runcode":
this.runCode(msg);
break;
}
};
DebugRunner.prototype.runCode = function (msg) {
var breakpoints = [];
// The breakpoints are in the format returned by the compiler
// and need to be converted to the format used by the DebugProtocol
msg.breakpoints.forEach(function (bp) {
breakpoints.push([bp.id, {
verified: true,
line: bp.line,
column: bp.column,
endLine: bp.endLine,
endColumn: bp.endColumn,
source: {
path: bp.fileName
}
}]);
});
this.session.runCode(msg.code, msg.usedParts, msg.usedArguments, new pxsim.BreakpointMap(breakpoints), pxt.appTarget.simulator.boardDefinition);
};
DebugRunner.prototype.sendRunnerMessage = function (subtype, msg) {
if (msg === void 0) { msg = {}; }
msg["subtype"] = subtype;
msg["type"] = "runner";
this.send(JSON.stringify(msg));
};
DebugRunner.RETRY_MS = 2500;
return DebugRunner;
}());
runner.DebugRunner = DebugRunner;
})(runner = pxt.runner || (pxt.runner = {}));
})(pxt || (pxt = {}));
var pxt;
(function (pxt) {
var runner;
(function (runner) {
function appendBlocks($parent, $svg) {
$parent.append($('<div class="ui content blocks"/>').append($svg));
}
function appendJs($parent, $js, woptions) {
$parent.append($('<div class="ui content js"/>').append($js));
$('code.highlight').each(function (i, block) {
var hljs = pxt.docs.requireHighlightJs();
if (hljs)
hljs.highlightBlock(block);
});
}
function fillWithWidget(options, $container, $js, $svg, woptions) {
if (woptions === void 0) { woptions = {}; }
if (!$svg || !$svg[0]) {
var $c_1 = $('<div class="ui segment"></div>');
$c_1.append($js);
$container.replaceWith($c_1);
return;
}
var cdn = pxt.webConfig.pxtCdnUrl;
var images = cdn + "images";
var $h = $('<div class="ui bottom attached tabular icon small compact menu">'
+ ' <div class="right icon menu"></div></div>');
var $c = $('<div class="ui top attached segment"></div>');
var $menu = $h.find('.right.menu');
if (options.showJavaScript) {
// blocks
$c.append($js);
// js menu
if ($svg) {
var $svgBtn = $('<a class="item blocks"><i aria-label="Blocks" class="puzzle icon"></i></a>').click(function () {
if ($c.find('.blocks')[0])
$c.find('.blocks').remove();
else {
if ($js)
appendBlocks($js.parent(), $svg);
else
appendBlocks($c, $svg);
}
});
$menu.append($svgBtn);
}
}
else {
// blocks
$c.append($svg);
// js menu
if (woptions.showJs) {
appendJs($c, $js, woptions);
}
else {
var $jsBtn = $('<a class="item js"><i aria-label="JavaScript" class="align left icon"></i></a>').click(function () {
if ($c.find('.js')[0])
$c.find('.js').remove();
else {
if ($svg)
appendJs($svg.parent(), $js, woptions);
else
appendJs($c, $js, woptions);
}
});
$menu.append($jsBtn);
}
}
// runner menu
if (woptions.run) {
var $runBtn = $('<a class="item"><i aria-label="run" class="play icon"></i></a>').click(function () {
if ($c.find('.sim')[0])
$c.find('.sim').remove(); // remove previous simulators
else {
var padding = '81.97%';
if (pxt.appTarget.simulator)
padding = (100 / pxt.appTarget.simulator.aspectRatio) + '%';
var $embed = $("<div class=\"ui card sim\"><div class=\"ui content\"><div style=\"position:relative;height:0;padding-bottom:" + padding + ";overflow:hidden;\"><iframe style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" src=\"" + (getRunUrl(options) + "#nofooter=1&code=" + encodeURIComponent($js.text())) + "\" allowfullscreen=\"allowfullscreen\" sandbox=\"allow-popups allow-scripts allow-same-origin\" frameborder=\"0\"></iframe></div></div></div>");
$c.append($embed);
}
});
$menu.append($runBtn);
}
if (woptions.hexname && woptions.hex) {
var $hexBtn = $('<a class="item"><i aria-label="download" class="download icon"></i></a>').click(function () {
pxt.BrowserUtils.browserDownloadBinText(woptions.hex, woptions.hexname, pxt.appTarget.compile.hexMimeType);
});
$menu.append($hexBtn);
}
var r = [$c];
// don't add menu if empty
if ($menu.children().length)
r.push($h);
// inject container
$container.replaceWith(r);
// download screenshots
if (options.downloadScreenshots && woptions.hexname) {
pxt.debug("Downloading screenshot for: " + woptions.hexname);
var filename_1 = woptions.hexname.substr(0, woptions.hexname.lastIndexOf('.'));
var fontSize = window.getComputedStyle($svg.get(0).querySelector(".blocklyText")).getPropertyValue("font-size");
var customCss = "\n.blocklyMainBackground {\n stroke:none !important;\n}\n\n.blocklyText {\n font-family:'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace !important;\n font-size:" + fontSize + " !important; \n}\n\n.blocklyCheckbox,\n.blocklyLed {\n fill: #ff3030 !important;\n text-shadow: 0px 0px 6px #f00;\n font-size: 17pt !important;\n}";
var svgElement = $svg.get(0);
var bbox = $svg.get(0).getBoundingClientRect();
pxt.blocks.layout.svgToPngAsync(svgElement, customCss, 0, 0, bbox.width, bbox.height)
.done(function (uri) {
if (uri)
pxt.BrowserUtils.browserDownloadDataUri(uri, (name || (pxt.appTarget.nickname || pxt.appTarget.id) + "-" + filename_1) + ".png");
});
}
}
function renderNextSnippetAsync(cls, render, options) {
if (!cls)
return Promise.resolve();
var $el = $("." + cls).first();
if (!$el[0])
return Promise.resolve();
if (!options.emPixels)
options.emPixels = 14;
if (!options.layout)
options.layout = pxt.blocks.BlockLayout.Flow;
return pxt.runner.decompileToBlocksAsync($el.text(), options)
.then(function (r) {
try {
render($el, r);
}
catch (e) {
console.error('error while rendering ' + $el.html());
$el.append($('<div/>').addClass("ui segment warning").text(e.message));
}
$el.removeClass(cls);
return Promise.delay(1, renderNextSnippetAsync(cls, render, options));
});
}
function renderSnippetsAsync(options) {
if (options.tutorial) {
// don't render chrome for tutorials
return renderNextSnippetAsync(options.snippetClass, function (c, r) {
var s = r.blocksSvg;
if (options.snippetReplaceParent)
c = c.parent();
var segment = $('<div class="ui segment"/>').append(s);
c.replaceWith(segment);
}, { package: options.package, snippetMode: false });
}
var snippetCount = 0;
return renderNextSnippetAsync(options.snippetClass, function (c, r) {
var s = r.compileBlocks && r.compileBlocks.success ? $(r.blocksSvg) : undefined;
var js = $('<code class="lang-typescript highlight"/>').text(c.text().trim());
if (options.snippetReplaceParent)
c = c.parent();
var compiled = r.compileJS && r.compileJS.success;
var hex = options.hex && compiled && r.compileJS.outfiles[pxtc.BINARY_HEX]
? r.compileJS.outfiles[pxtc.BINARY_HEX] : undefined;
var hexname = (pxt.appTarget.nickname || pxt.appTarget.id) + "-" + (options.hexName || '') + "-" + snippetCount++ + ".hex";
fillWithWidget(options, c, js, s, {
run: options.simulator && compiled,
hexname: hexname,
hex: hex,
});
}, { package: options.package });
}
function decompileCallInfo(stmt) {
if (!stmt || stmt.kind != ts.SyntaxKind.ExpressionStatement)
return null;
var estmt = stmt;
if (!estmt.expression || estmt.expression.kind != ts.SyntaxKind.CallExpression)
return null;
var call = estmt.expression;
var info = call.callInfo;
return info;
}
function renderSignaturesAsync(options) {
return renderNextSnippetAsync(options.signatureClass, function (c, r) {
var cjs = r.compileJS;
if (!cjs)
return;
var file = r.compileJS.ast.getSourceFile("main.ts");
var info = decompileCallInfo(file.statements[0]);
if (!info)
return;
var s = r.compileBlocks && r.compileBlocks.success ? $(r.blocksSvg) : undefined;
var sig = info.decl.getText().replace(/^export/, '');
sig = sig.slice(0, sig.indexOf('{')).trim() + ';';
var js = $('<code class="lang-typescript highlight"/>').text(sig);
if (options.snippetReplaceParent)
c = c.parent();
fillWithWidget(options, c, js, s, { showJs: true, hideGutter: true });
}, { package: options.package, snippetMode: true });
}
function renderShuffleAsync(options) {
return renderNextSnippetAsync(options.shuffleClass, function (c, r) {
var s = r.blocksSvg;
if (options.snippetReplaceParent)
c = c.parent();
var segment = $('<div class="ui segment"/>').append(s);
c.replaceWith(segment);
}, {
emPixels: 14, layout: pxt.blocks.BlockLayout.Shuffle, aspectRatio: options.blocksAspectRatio,
package: options.package
});
}
function renderBlocksAsync(options) {
return renderNextSnippetAsync(options.blocksClass, function (c, r) {
var s = r.blocksSvg;
if (options.snippetReplaceParent)
c = c.parent();
var segment = $('<div class="ui segment"/>').append(s);
c.replaceWith(segment);
}, { package: options.package, snippetMode: true });
}
function renderInlineBlocksAsync(options) {
options = pxt.Util.clone(options);
options.emPixels = 18;
options.snippetMode = true;
var $els = $(":not(pre) > code");
var i = 0;
function renderNextAsync() {
if (i >= $els.length)
return Promise.resolve();
var $el = $($els[i++]);
var text = $el.text();
var mbtn = /^(\|+)([^\|]+)\|+$/.exec(text);
if (mbtn) {
var lev = mbtn[1].length == 1 ? "primary" : "";
var txt = mbtn[2];
$el.replaceWith($("<button class=\"ui button " + lev + "\"/>").text(pxt.U.rlf(txt)));
return renderNextAsync();
}
var m = /^\[([^\]]+)\]$/.exec(text);
if (!m)
return renderNextAsync();
var code = m[1];
return pxt.runner.decompileToBlocksAsync(code, options)
.then(function (r) {
if (r.blocksSvg) {
var $newel = $('<span class="block"/>').append(r.blocksSvg);
var file = r.compileJS.ast.getSourceFile("main.ts");
var stmt = file.statements[0];
var info = decompileCallInfo(stmt);
if (info && info.attrs.help)
$newel = $("<a class=\"ui link\"/>").attr("href", "/reference/" + info.attrs.help).append($newel);
$el.replaceWith($newel);
}
return Promise.delay(1, renderNextAsync());
});
}
return renderNextAsync();
}
function renderProjectAsync(options) {
if (!options.projectClass)
return Promise.resolve();
function render() {
var $el = $("." + options.projectClass).first();
var e = $el[0];
if (!e)
return Promise.resolve();
$el.removeClass(options.projectClass);
var id = pxt.Cloud.parseScriptId(e.innerText);
if (id) {
if (options.snippetReplaceParent) {
e = e.parentElement;
// create a new div to host the rendered code
var d = document.createElement("div");
e.parentElement.insertBefore(d, e);
e.parentElement.removeChild(e);
e = d;
}
return pxt.runner.renderProjectAsync(e, id)
.then(function () { return render(); });
}
else
return render();
}
return render();
}
function renderLinksAsync(options, cls, replaceParent, ns) {
return renderNextSnippetAsync(cls, function (c, r) {
var cjs = r.compileJS;
if (!cjs)
return;
var file = r.compileJS.ast.getSourceFile("main.ts");
var stmts = file.statements.slice(0).reverse();
var ul = $('<div />').addClass('ui cards');
var addItem = function (card) {
if (!card)
return;
ul.append(pxt.docs.codeCard.render(card, { hideHeader: true, shortName: true }));
};
stmts.forEach(function (stmt) {
var info = decompileCallInfo(stmt);
if (info) {
var block = Blockly.Blocks[info.attrs.blockId];
if (ns) {
var ii = r.compileBlocks.blocksInfo.apis.byQName[info.qName];
var nsi = r.compileBlocks.blocksInfo.apis.byQName[ii.namespace];
addItem({
name: nsi.name,
url: nsi.attributes.help || ("reference/" + nsi.name),
description: nsi.attributes.jsDoc,
blocksXml: block && block.codeCard
? block.codeCard.blocksXml
: info.attrs.blockId
? "<xml xmlns=\"http://www.w3.org/1999/xhtml\"><block type=\"" + info.attrs.blockId + "\"></block></xml>"
: undefined
});
}
else if (block) {
var card = pxt.U.clone(block.codeCard);
if (card) {
addItem(card);
}
}
else {
// no block available here
addItem({
name: info.qName,
description: info.attrs.jsDoc,
url: info.attrs.help || undefined
});
}
}
else
switch (stmt.kind) {
case ts.SyntaxKind.ExpressionStatement:
var es = stmt;
switch (es.expression.kind) {
case ts.SyntaxKind.TrueKeyword:
case ts.SyntaxKind.FalseKeyword:
addItem({
name: "Boolean",
url: "blocks/logic/boolean",
description: lf("True or false values"),
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="logic_boolean"><field name="BOOL">TRUE</field></block></xml>'
});
break;
default:
pxt.debug("card expr kind: " + es.expression.kind);
break;
}
break;
case ts.SyntaxKind.IfStatement:
addItem({
name: ns ? "Logic" : "if",
url: "blocks/logic" + (ns ? "" : "/if"),
description: ns ? lf("Logic operators and constants") : lf("Conditional statement"),
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_if"></block></xml>'
});
break;
case ts.SyntaxKind.WhileStatement:
addItem({
name: ns ? "Loops" : "while",
url: "blocks/loops" + (ns ? "" : "/while"),
description: ns ? lf("Loops and repetition") : lf("Repeat code while a condition is true."),
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="device_while"></block></xml>'
});
break;
case ts.SyntaxKind.ForStatement:
addItem({
name: ns ? "Loops" : "for",
url: "blocks/loops" + (ns ? "" : "/for"),
description: ns ? lf("Loops and repetition") : lf("Repeat code for a given number of times."),
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_simple_for"></block></xml>'
});
break;
case ts.SyntaxKind.VariableStatement:
addItem({
name: ns ? "Variables" : "variable declaration",
url: "blocks/variables" + (ns ? "" : "/assign"),
description: ns ? lf("Variables") : lf("Assign a value to a named variable."),
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="variables_set"></block></xml>'
});
break;
default:
pxt.debug("card kind: " + stmt.kind);
}
});
if (replaceParent)
c = c.parent();
c.replaceWith(ul);
}, { package: options.package });
}
function fillCodeCardAsync(c, cards, options) {
if (!cards || cards.length == 0)
return Promise.resolve();
if (cards.length == 0) {
var cc = pxt.docs.codeCard.render(cards[0], options);
c.replaceWith(cc);
}
else {
var cd_1 = document.createElement("div");
cd_1.className = "ui cards";
cards.forEach(function (card) { return cd_1.appendChild(pxt.docs.codeCard.render(card, options)); });
c.replaceWith(cd_1);
}
return Promise.resolve();
}
function renderNextCodeCardAsync(cls, options) {
if (!cls)
return Promise.resolve();
var $el = $("." + cls).first();
if (!$el[0])
return Promise.resolve();
$el.removeClass(cls);
var cards;
try {
var js = JSON.parse($el.text());
if (!Array.isArray(js))
js = [js];
cards = js;
}
catch (e) {
console.error('error while rendering ' + $el.html());
$el.append($('<div/>').addClass("ui segment warning").text(e.messageText));
}
if (options.snippetReplaceParent)
$el = $el.parent();
return fillCodeCardAsync($el, cards, { hideHeader: true })
.then(function () { return Promise.delay(1, renderNextCodeCardAsync(cls, options)); });
}
function getRunUrl(options) {
return options.pxtUrl ? options.pxtUrl + '/--run' : pxt.webConfig && pxt.webConfig.runUrl ? pxt.webConfig.runUrl : '/--run';
}
function mergeConfig(options) {
// additional config options
if (!options.packageClass)
return;
$('.' + options.packageClass).each(function (i, c) {
var $c = $(c);
var name = $c.text().split('\n').map(function (s) { return s.replace(/\s*/g, ''); }).filter(function (s) { return !!s; }).join(',');
options.package = options.package ? options.package + "," + name : name;
if (options.snippetReplaceParent)
$c = $c.parent();
$c.remove();
});
}
function renderAsync(options) {
if (!options)
options = {};
if (options.pxtUrl)
options.pxtUrl = options.pxtUrl.replace(/\/$/, '');
mergeConfig(options);
if (options.simulatorClass) {
// simulators
$('.' + options.simulatorClass).each(function (i, c) {
var $c = $(c);
var padding = '81.97%';
if (pxt.appTarget.simulator)
padding = (100 / pxt.appTarget.simulator.aspectRatio) + '%';
var $sim = $("<div class=\"ui centered card\"><div class=\"ui content\">\n <div style=\"position:relative;height:0;padding-bottom:" + padding + ";overflow:hidden;\">\n <iframe style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" allowfullscreen=\"allowfullscreen\" frameborder=\"0\" sandbox=\"allow-popups allow-scripts allow-same-origin\"></iframe>\n </div>\n </div></div>");
$sim.find("iframe").attr("src", getRunUrl(options) + "#nofooter=1&code=" + encodeURIComponent($c.text().trim()));
if (options.snippetReplaceParent)
$c = $c.parent();
$c.replaceWith($sim);
});
}
return Promise.resolve()
.then(function () { return renderInlineBlocksAsync(options); })
.then(function () { return renderShuffleAsync(options); })
.then(function () { return renderLinksAsync(options, options.linksClass, options.snippetReplaceParent, false); })
.then(function () { return renderLinksAsync(options, options.namespacesClass, options.snippetReplaceParent, true); })
.then(function () { return renderSignaturesAsync(options); })
.then(function () { return renderNextCodeCardAsync(options.codeCardClass, options); })
.then(function () { return renderSnippetsAsync(options); })
.then(function () { return renderBlocksAsync(options); })
.then(function () { return renderProjectAsync(options); });
}
runner.renderAsync = renderAsync;
})(runner = pxt.runner || (pxt.runner = {}));
})(pxt || (pxt = {}));
/// <reference path="../built/pxtlib.d.ts" />
/// <reference path="../built/pxtblocks.d.ts" />
/// <reference path="../built/pxtsim.d.ts" />
var pxt;
(function (pxt) {
var runner;
(function (runner) {
var EditorPackage = (function () {
function EditorPackage(ksPkg, topPkg) {
this.ksPkg = ksPkg;
this.topPkg = topPkg;
this.files = {};
}
EditorPackage.prototype.getKsPkg = function () {
return this.ksPkg;
};
EditorPackage.prototype.getPkgId = function () {
return this.ksPkg ? this.ksPkg.id : this.id;
};
EditorPackage.prototype.isTopLevel = function () {
return this.ksPkg && this.ksPkg.level == 0;
};
EditorPackage.prototype.setFiles = function (files) {
this.files = files;
};
EditorPackage.prototype.getAllFiles = function () {
return pxt.Util.mapMap(this.files, function (k, f) { return f; });
};
return EditorPackage;
}());
var Host = (function () {
function Host() {
}
Host.prototype.readFile = function (module, filename) {
var epkg = getEditorPkg(module);
return pxt.U.lookup(epkg.files, filename);
};
Host.prototype.writeFile = function (module, filename, contents) {
if (filename == pxt.CONFIG_NAME)
return; // ignore config writes
throw pxt.Util.oops("trying to write " + module + " / " + filename);
};
Host.prototype.getHexInfoAsync = function (extInfo) {
return pxt.hex.getHexInfoAsync(this, extInfo);
};
Host.prototype.cacheStoreAsync = function (id, val) {
return Promise.resolve();
};
Host.prototype.cacheGetAsync = function (id) {
return Promise.resolve(null);
};
Host.prototype.downloadPackageAsync = function (pkg) {
var proto = pkg.verProtocol();
var epkg = getEditorPkg(pkg);
return pkg.commonDownloadAsync()
.then(function (resp) {
if (resp) {
epkg.setFiles(resp);
return Promise.resolve();
}
if (proto == "empty") {
epkg.setFiles(emptyPrjFiles());
return Promise.resolve();
}
else if (proto == "docs") {
var files = emptyPrjFiles();
var cfg_1 = JSON.parse(files[pxt.CONFIG_NAME]);
pkg.verArgument().split(',').forEach(function (d) {
var m = /^([a-zA-Z0-9_-]+)(=(.+))?$/.exec(d);
if (m)
cfg_1.dependencies[m[1]] = m[3] || "*";
else
console.warn("unknown package syntax " + d);
});
if (!cfg_1.yotta)
cfg_1.yotta = {};
cfg_1.yotta.ignoreConflicts = true;
files[pxt.CONFIG_NAME] = JSON.stringify(cfg_1, null, 4);
epkg.setFiles(files);
return Promise.resolve();
}
else {
return Promise.reject("Cannot download " + pkg.version() + "; unknown protocol");
}
});
};
return Host;
}());
function getEditorPkg(p) {
var r = p._editorPkg;
if (r)
return r;
var top = null;
if (p != runner.mainPkg)
top = getEditorPkg(runner.mainPkg);
var newOne = new EditorPackage(p, top);
if (p == runner.mainPkg)
newOne.topPkg = newOne;
p._editorPkg = newOne;
return newOne;
}
function emptyPrjFiles() {
var p = pxt.appTarget.tsprj;
var files = pxt.U.clone(p.files);
files[pxt.CONFIG_NAME] = JSON.stringify(p.config, null, 4) + "\n";
files["main.blocks"] = "";
return files;
}
function patchSemantic() {
if ($ && $.fn && $.fn.embed && $.fn.embed.settings && $.fn.embed.settings.sources && $.fn.embed.settings.sources.youtube) {
$.fn.embed.settings.sources.youtube.url = '//www.youtube.com/embed/{id}?rel=0';
}
}
function initInnerAsync() {
pxt.setAppTarget(window.pxtTargetBundle);
pxt.Util.assert(!!pxt.appTarget);
var mlang = /(live)?lang=([a-z]{2,}(-[A-Z]+)?)/i.exec(window.location.href);
var lang = mlang ? mlang[2] : (pxt.appTarget.appTheme.defaultLocale || navigator.userLanguage || navigator.language);
var live = mlang && !!mlang[1];
patchSemantic();
var cfg = pxt.webConfig;
return pxt.Util.updateLocalizationAsync(cfg.pxtCdnUrl, lang, live)
.then(function () {
runner.mainPkg = new pxt.MainPackage(new Host());
});
}
function initFooter(footer, shareId) {
if (!footer)
return;
var theme = pxt.appTarget.appTheme;
var body = $('body');
var $footer = $(footer);
var footera = $('<a/>').attr('href', theme.homeUrl)
.attr('target', '_blank');
$footer.append(footera);
if (theme.organizationLogo)
footera.append($('<img/>').attr('src', pxt.Util.toDataUri(theme.organizationLogo)));
else
footera.append(lf("powered by {0}", theme.title));
body.mouseenter(function (ev) { return $footer.fadeOut(); });
body.mouseleave(function (ev) { return $footer.fadeIn(); });
}
runner.initFooter = initFooter;
function showError(msg) {
console.error(msg);
}
runner.showError = showError;
function loadPackageAsync(id, code) {
var host = runner.mainPkg.host();
runner.mainPkg = new pxt.MainPackage(host);
runner.mainPkg._verspec = id ? /\w+:\w+/.test(id) ? id : "pub:" + id : "empty:tsprj";
return host.downloadPackageAsync(runner.mainPkg)
.then(function () { return host.readFile(runner.mainPkg, pxt.CONFIG_NAME); })
.then(function (str) {
if (!str)
return Promise.resolve();
return runner.mainPkg.installAllAsync().then(function () {
if (code) {
//Set the custom code if provided for docs.
var epkg = getEditorPkg(runner.mainPkg);
epkg.files["main.ts"] = code;
//set the custom doc name from the URL.
var cfg = JSON.parse(epkg.files[pxt.CONFIG_NAME]);
cfg.name = window.location.href.split('/').pop().split(/[?#]/)[0];
;
epkg.files[pxt.CONFIG_NAME] = JSON.stringify(cfg, null, 4);
//Propgate the change to main package
runner.mainPkg.config.name = cfg.name;
if (runner.mainPkg.config.files.indexOf("main.blocks") == -1) {
runner.mainPkg.config.files.push("main.blocks");
}
}
}).catch(function (e) {
showError(lf("Cannot load package: {0}", e.message));
});
});
}
function getCompileOptionsAsync(hex) {
var trg = runner.mainPkg.getTargetOptions();
trg.isNative = !!hex;
trg.hasHex = !!hex;
return runner.mainPkg.getCompileOptionsAsync(trg);
}
function compileAsync(hex, updateOptions) {
return getCompileOptionsAsync()
.then(function (opts) {
if (updateOptions)
updateOptions(opts);
var resp = pxtc.compile(opts);
if (resp.diagnostics && resp.diagnostics.length > 0) {
resp.diagnostics.forEach(function (diag) {
console.error(diag.messageText);
});
}
return resp;
});
}
function generateHexFileAsync(options) {
return loadPackageAsync(options.id)
.then(function () { return compileAsync(true, function (opts) {
if (options.code)
opts.fileSystem["main.ts"] = options.code;
}); })
.then(function (resp) {
if (resp.diagnostics && resp.diagnostics.length > 0) {
console.error("Diagnostics", resp.diagnostics);
}
return resp.outfiles[pxtc.BINARY_HEX];
});
}
runner.generateHexFileAsync = generateHexFileAsync;
function simulateAsync(container, simOptions) {
return loadPackageAsync(simOptions.id)
.then(function () { return compileAsync(false, function (opts) {
if (simOptions.code)
opts.fileSystem["main.ts"] = simOptions.code;
}); })
.then(function (resp) {
if (resp.diagnostics && resp.diagnostics.length > 0) {
console.error("Diagnostics", resp.diagnostics);
}
var js = resp.outfiles[pxtc.BINARY_JS];
if (js) {
var options_1 = {};
var driver = new pxsim.SimulatorDriver(container, options_1);
var fnArgs = resp.usedArguments;
var board = pxt.appTarget.simulator.boardDefinition;
var parts = pxtc.computeUsedParts(resp, true);
var runOptions = {
boardDefinition: board,
parts: parts,
fnArgs: fnArgs
};
if (pxt.appTarget.simulator)
runOptions.aspectRatio = parts.length && pxt.appTarget.simulator.partsAspectRatio
? pxt.appTarget.simulator.partsAspectRatio
: pxt.appTarget.simulator.aspectRatio;
driver.run(js, runOptions);
}
});
}
runner.simulateAsync = simulateAsync;
(function (LanguageMode) {
LanguageMode[LanguageMode["Blocks"] = 0] = "Blocks";
LanguageMode[LanguageMode["TypeScript"] = 1] = "TypeScript";
})(runner.LanguageMode || (runner.LanguageMode = {}));
var LanguageMode = runner.LanguageMode;
runner.languageMode = LanguageMode.Blocks;
runner.editorLocale = "en";
function setEditorContextAsync(mode, locale) {
runner.languageMode = mode;
if (locale != runner.editorLocale) {
var localeLiveRx = /^live-/;
runner.editorLocale = locale;
return pxt.Util.updateLocalizationAsync(pxt.webConfig.pxtCdnUrl, runner.editorLocale.replace(localeLiveRx, ''), localeLiveRx.test(runner.editorLocale));
}
return Promise.resolve();
}
runner.setEditorContextAsync = setEditorContextAsync;
function receiveDocMessage(e) {
var m = e.data;
if (!m)
return;
switch (m.type) {
case "fileloaded":
var fm = m;
var name_1 = fm.name;
setEditorContextAsync(/\.ts$/i.test(name_1) ? LanguageMode.TypeScript : LanguageMode.Blocks, fm.locale).done();
break;
case "popout":
var mp = /#(doc|md):([^&?:]+)/i.exec(window.location.href);
if (mp) {
var docsUrl = pxt.webConfig.docsUrl || '/--docs';
var url = mp[1] == "doc" ? "" + mp[2] : docsUrl + "?md=" + mp[2];
window.open(url, "_blank");
}
break;
case "tutorial":
var t = m;
switch (t.subtype) {
case "stepchange":
var ts_1 = t;
var loading_1 = document.getElementById('loading');
var content_1 = document.getElementById('content');
$(content_1).hide();
$(loading_1).show();
renderTutorialAsync(content_1, ts_1.tutorial, ts_1.step)
.finally(function () {
$(loading_1).hide();
$(content_1).show();
});
break;
}
break;
case "localtoken":
var dm = m;
if (dm && dm.localToken) {
pxt.Cloud.localToken = dm.localToken;
}
break;
}
}
function startDocsServer(loading, content) {
function render(doctype, src) {
pxt.debug("rendering " + doctype);
$(content).hide();
$(loading).show();
Promise.delay(100) // allow UI to update
.then(function () {
switch (doctype) {
case "doc":
return renderDocAsync(content, src);
case "tutorial":
var body = $('body');
body.addClass('tutorial');
return renderTutorialAsync(content, src, 0);
default:
return renderMarkdownAsync(content, src);
}
})
.catch(function (e) {
$(content).html("\n <img style=\"height:4em;\" src=\"" + pxt.appTarget.appTheme.docsLogo + "\" />\n <h1>" + lf("Oops") + "</h1>\n <h3>" + lf("We could not load the documentation, please check your internet connection.") + "</h3>\n <button class=\"ui button primary\" id=\"tryagain\">" + lf("Try Again") + "</button>");
$(content).find('#tryagain').click(function () {
render(doctype, src);
});
// notify parent iframe that docs weren't loaded
if (window.parent)
window.parent.postMessage({
type: "docfailed",
docType: doctype,
src: src
}, "*");
}).finally(function () {
$(loading).hide();
$(content).show();
})
.done(function () { });
}
function renderHash() {
var m = /^#(doc|md|tutorial):([^&?:]+)(:([^&?:]+):([^&?:]+))?/i.exec(window.location.hash);
if (m) {
// navigation occured
var p = m[4] ? setEditorContextAsync(/^blocks$/.test(m[4]) ? LanguageMode.Blocks : LanguageMode.TypeScript, m[5]) : Promise.resolve();
p.then(function () { return render(m[1], decodeURIComponent(m[2])); });
}
}
window.addEventListener("message", receiveDocMessage, false);
window.addEventListener("hashchange", function () {
renderHash();
}, false);
parent.postMessage({ type: "sidedocready" }, "*");
// delay load doc page to allow simulator to load first
setTimeout(function () { return renderHash(); }, 1);
}
runner.startDocsServer = startDocsServer;
function renderProjectAsync(content, projectid, template) {
if (template === void 0) { template = "blocks"; }
return pxt.Cloud.privateGetTextAsync(projectid + "/text")
.then(function (txt) { return JSON.parse(txt); })
.then(function (files) {
var md = "```" + template + "\n" + files["main.ts"] + "\n```";
return renderMarkdownAsync(content, md);
});
}
runner.renderProjectAsync = renderProjectAsync;
function renderDocAsync(content, docid) {
docid = docid.replace(/^\//, "");
return pxt.Cloud.downloadMarkdownAsync(docid, runner.editorLocale, pxt.Util.localizeLive)
.then(function (md) { return renderMarkdownAsync(content, md, { path: docid }); });
}
var template = "\n<aside id=button class=box>\n <a class=\"ui primary button\" href=\"@ARGS@\">@BODY@</a>\n</aside>\n\n<aside id=vimeo>\n<div class=\"ui two column stackable grid container\">\n<div class=\"column\">\n <div class=\"ui embed mdvid\" data-source=\"vimeo\" data-id=\"@ARGS@\" data-placeholder=\"/thumbnail/1024/vimeo/@ARGS@\" data-icon=\"video play\">\n </div>\n</div></div>\n</aside>\n\n<aside id=youtube>\n<div class=\"ui two column stackable grid container\">\n<div class=\"column\">\n <div class=\"ui embed mdvid\" data-source=\"youtube\" data-id=\"@ARGS@\" data-placeholder=\"https://img.youtube.com/vi/@ARGS@/maxresdefault.jpg\">\n </div>\n</div></div>\n</aside>\n\n<aside id=section>\n <!-- section @ARGS@ -->\n</aside>\n\n<aside id=hide class=box>\n <div style='display:none'>\n @BODY@\n </div>\n</aside>\n\n<aside id=avatar class=box>\n <div class='avatar @ARGS@'>\n <div class='avatar-image'></div>\n <div class='ui compact message'>\n @BODY@\n </div>\n </div>\n</aside>\n\n<aside id=hint class=box>\n <div class=\"ui icon green message\">\n <div class=\"content\">\n <div class=\"header\">Hint</div>\n @BODY@\n </div>\n </div>\n</aside>\n\n<!-- wrapped around ordinary content -->\n<aside id=main-container class=box>\n <div class=\"ui text\">\n @BODY@\n </div>\n</aside>\n\n<!-- used for 'column' box - they are collected and wrapped in 'column-container' -->\n<aside id=column class=aside>\n <div class='column'>\n @BODY@\n </div>\n</aside>\n<aside id=column-container class=box>\n <div class=\"ui three column stackable grid text\">\n @BODY@\n </div>\n</aside>\n@breadcrumb@\n@body@";
function renderMarkdownAsync(content, md, options) {
if (options === void 0) { options = {}; }
var path = options.path;
var parts = (path || '').split('/');
var bc = !path ? null : parts.map(function (e, i) {
return {
href: "/" + parts.slice(0, i + 1).join("/"),
name: e
};
});
var html = pxt.docs.renderMarkdown(template, md, pxt.appTarget.appTheme, null, bc);
$(content).html(html);
$(content).find('a').attr('target', '_blank');
return pxt.runner.renderAsync({
blocksAspectRatio: 0.5,
snippetClass: 'lang-blocks',
signatureClass: 'lang-sig',
blocksClass: 'lang-block',
shuffleClass: 'lang-shuffle',
simulatorClass: 'lang-sim',
linksClass: 'lang-cards',
namespacesClass: 'lang-namespaces',
codeCardClass: 'lang-codecard',
packageClass: 'lang-package',
projectClass: 'lang-project',
snippetReplaceParent: true,
simulator: true,
hex: true,
tutorial: !!options.tutorial,
showJavaScript: runner.languageMode == LanguageMode.TypeScript,
hexName: pxt.appTarget.id
}).then(function () {
// patch a elements
$(content).find('a[href^="/"]').removeAttr('target').each(function (i, a) {
$(a).attr('href', '#doc:' + $(a).attr('href').replace(/^\//, ''));
});
// enable embeds
$(content).find('.ui.embed').embed();
});
}
runner.renderMarkdownAsync = renderMarkdownAsync;
function renderTutorialAsync(content, tutorialid, step) {
tutorialid = tutorialid.replace(/^\//, "");
return pxt.Cloud.downloadMarkdownAsync(tutorialid, runner.editorLocale, pxt.Util.localizeLive)
.then(function (tutorialmd) {
var steps = tutorialmd.split(/\###.*(?!$)/i);
if (steps.length < 1)
return;
var options = steps[0];
steps = steps.slice(1, steps.length);
// Extract toolbox block ids
var uptoSteps = steps.slice(0, step + 1).join();
uptoSteps = uptoSteps.replace(/((?!.)\s)+/g, "\n");
var regex = /```(sim|block|blocks|shuffle)\n([\s\S]*?)\n```/gmi;
var match;
var code = '';
while ((match = regex.exec(uptoSteps)) != null) {
code += match[2] + "\n";
}
// Render current step
return renderMarkdownAsync(content, steps[step], { tutorial: true })
.then(function () {
if (code == '')
return;
// Convert all blocks to blocks
return pxt.runner.decompileToBlocksAsync(code, {
emPixels: 14,
layout: pxt.blocks.BlockLayout.Flow,
package: undefined
}).then(function (r) {
var blocksxml = r.compileBlocks.outfiles['main.blocks'];
var toolboxSubset = {};
if (blocksxml) {
var headless = pxt.blocks.loadWorkspaceXml(blocksxml);
var allblocks = headless.getAllBlocks();
for (var bi = 0; bi < allblocks.length; ++bi) {
var blk = allblocks[bi];
toolboxSubset[blk.type] = 1;
}
}
if (toolboxSubset != {}) {
window.parent.postMessage({
type: "tutorial",
tutorial: tutorialid,
subtype: "steploaded",
data: toolboxSubset,
location: "bottom"
}, "*");
}
});
});
});
}
runner.renderTutorialAsync = renderTutorialAsync;
function decompileToBlocksAsync(code, options) {
return loadPackageAsync(options && options.package ? "docs:" + options.package : null, code)
.then(function () { return getCompileOptionsAsync(pxt.appTarget.compile ? pxt.appTarget.compile.hasHex : false); })
.then(function (opts) {
// compile
opts.fileSystem["main.ts"] = code;
opts.ast = true;
var resp = pxtc.compile(opts);
if (resp.diagnostics && resp.diagnostics.length > 0)
resp.diagnostics.forEach(function (diag) { return console.error(diag.messageText); });
if (!resp.success)
return Promise.resolve({ compileJS: resp });
// decompile to blocks
var apis = pxtc.getApiInfo(resp.ast);
return ts.pxtc.localizeApisAsync(apis, runner.mainPkg)
.then(function () {
var blocksInfo = pxtc.getBlocksInfo(apis);
pxt.blocks.initBlocks(blocksInfo);
var bresp = pxtc.decompiler.decompileToBlocks(blocksInfo, resp.ast.getSourceFile("main.ts"), { snippetMode: options && options.snippetMode });
if (bresp.diagnostics && bresp.diagnostics.length > 0)
bresp.diagnostics.forEach(function (diag) { return console.error(diag.messageText); });
if (!bresp.success)
return { compileJS: resp, compileBlocks: bresp };
pxt.debug(bresp.outfiles["main.blocks"]);
return {
compileJS: resp,
compileBlocks: bresp,
blocksSvg: pxt.blocks.render(bresp.outfiles["main.blocks"], options)
};
});
});
}
runner.decompileToBlocksAsync = decompileToBlocksAsync;
runner.initCallbacks = [];
function init() {
initInnerAsync()
.done(function () {
for (var i = 0; i < runner.initCallbacks.length; ++i) {
runner.initCallbacks[i]();
}
});
}
runner.init = init;
function windowLoad() {
var f = window.ksRunnerWhenLoaded;
if (f)
f();
}
windowLoad();
})(runner = pxt.runner || (pxt.runner = {}));
})(pxt || (pxt = {}));