pxt-core 9.3.13 → 9.3.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/built/cli.js +7 -2
- package/built/pxt.js +57 -3
- package/built/pxtblockly.js +97 -57
- package/built/pxtblocks.d.ts +59 -21
- package/built/pxtblocks.js +97 -57
- package/built/pxtlib.d.ts +22 -0
- package/built/pxtlib.js +50 -1
- package/built/target.js +1 -1
- package/built/tests/blocksrunner.js +1 -1
- package/built/web/main.js +1 -1
- package/built/web/multiplayer/js/{main.78cecdcb.js → main.75ca8c58.js} +2 -2
- package/built/web/pxtapp.js +1 -1
- package/built/web/pxtasseteditor.js +1 -1
- package/built/web/pxtblockly.js +2 -2
- package/built/web/pxtblocks.js +1 -1
- package/built/web/pxtembed.js +2 -2
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/runnerembed.js +1 -0
- package/built/web/semantic.css +1 -1
- package/built/web/skillmap/js/{main.8222bb34.js → main.236bd49e.js} +2 -2
- package/built/web/teachertool/css/main.e9386f28.css +1 -0
- package/built/web/teachertool/js/{main.3a94a341.js → main.8aa6604c.js} +2 -2
- package/localtypings/projectheader.d.ts +21 -0
- package/{built → localtypings}/pxteditor.d.ts +1095 -1090
- package/package.json +1 -1
- package/react-common/components/controls/MenuDropdown.tsx +5 -2
- package/react-common/components/util.tsx +1 -1
- package/theme/pxt.less +1 -0
- package/theme/themepacks.less +41 -0
- package/webapp/public/embed.js +1 -1
- package/webapp/public/multiplayer.html +1 -1
- package/webapp/public/skillmap.html +1 -1
- package/webapp/public/teachertool.html +1 -3
- package/built/pxteditor.js +0 -1834
- package/built/pxtrunner.d.ts +0 -151
- package/built/pxtrunner.js +0 -2626
- package/built/web/pxteditor.js +0 -1
- package/built/web/pxtrunner.js +0 -1
- package/built/web/teachertool/css/main.59776cd1.css +0 -1
package/built/pxtrunner.js
DELETED
|
@@ -1,2626 +0,0 @@
|
|
|
1
|
-
var pxt;
|
|
2
|
-
(function (pxt) {
|
|
3
|
-
var runner;
|
|
4
|
-
(function (runner) {
|
|
5
|
-
/**
|
|
6
|
-
* Starts the simulator and injects it into the provided container.
|
|
7
|
-
* the simulator will attempt to establish a websocket connection
|
|
8
|
-
* to the debugger's user interface on port 3234.
|
|
9
|
-
*
|
|
10
|
-
* @param container The container to inject the simulator into
|
|
11
|
-
*/
|
|
12
|
-
function startDebuggerAsync(container) {
|
|
13
|
-
const debugRunner = new DebugRunner(container);
|
|
14
|
-
debugRunner.start();
|
|
15
|
-
}
|
|
16
|
-
runner.startDebuggerAsync = startDebuggerAsync;
|
|
17
|
-
/**
|
|
18
|
-
* Runner for the debugger that handles communication with the user
|
|
19
|
-
* interface. Also talks to the server for anything to do with
|
|
20
|
-
* the filesystem (like reading code)
|
|
21
|
-
*/
|
|
22
|
-
class DebugRunner {
|
|
23
|
-
constructor(container) {
|
|
24
|
-
this.container = container;
|
|
25
|
-
this.pkgLoaded = false;
|
|
26
|
-
this.intervalRunning = false;
|
|
27
|
-
}
|
|
28
|
-
start() {
|
|
29
|
-
this.initializeWebsocket();
|
|
30
|
-
if (!this.intervalRunning) {
|
|
31
|
-
this.intervalRunning = true;
|
|
32
|
-
this.intervalId = setInterval(() => {
|
|
33
|
-
if (!this.ws) {
|
|
34
|
-
try {
|
|
35
|
-
this.initializeWebsocket();
|
|
36
|
-
}
|
|
37
|
-
catch (e) {
|
|
38
|
-
console.warn(`Connection to server failed, retrying in ${DebugRunner.RETRY_MS} ms`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}, DebugRunner.RETRY_MS);
|
|
42
|
-
}
|
|
43
|
-
this.session = new pxsim.SimDebugSession(this.container);
|
|
44
|
-
this.session.start(this);
|
|
45
|
-
}
|
|
46
|
-
initializeWebsocket() {
|
|
47
|
-
if (!pxt.BrowserUtils.isLocalHost() || !pxt.Cloud.localToken)
|
|
48
|
-
return;
|
|
49
|
-
pxt.debug('initializing debug pipe');
|
|
50
|
-
this.ws = new WebSocket('ws://localhost:3234/' + pxt.Cloud.localToken + '/simdebug');
|
|
51
|
-
this.ws.onopen = ev => {
|
|
52
|
-
pxt.debug('debug: socket opened');
|
|
53
|
-
};
|
|
54
|
-
this.ws.onclose = ev => {
|
|
55
|
-
pxt.debug('debug: socket closed');
|
|
56
|
-
if (this.closeListener) {
|
|
57
|
-
this.closeListener();
|
|
58
|
-
}
|
|
59
|
-
this.session.stopSimulator();
|
|
60
|
-
this.ws = undefined;
|
|
61
|
-
};
|
|
62
|
-
this.ws.onerror = ev => {
|
|
63
|
-
pxt.debug('debug: socket closed due to error');
|
|
64
|
-
if (this.errorListener) {
|
|
65
|
-
this.errorListener(ev.type);
|
|
66
|
-
}
|
|
67
|
-
this.session.stopSimulator();
|
|
68
|
-
this.ws = undefined;
|
|
69
|
-
};
|
|
70
|
-
this.ws.onmessage = ev => {
|
|
71
|
-
let message;
|
|
72
|
-
try {
|
|
73
|
-
message = JSON.parse(ev.data);
|
|
74
|
-
}
|
|
75
|
-
catch (e) {
|
|
76
|
-
pxt.debug('debug: could not parse message');
|
|
77
|
-
}
|
|
78
|
-
if (message) {
|
|
79
|
-
// FIXME: ideally, we should just open two websockets instead of adding to the
|
|
80
|
-
// debug protocol. One for the debugger, one for meta-information and file
|
|
81
|
-
// system requests
|
|
82
|
-
if (message.type === 'runner') {
|
|
83
|
-
this.handleRunnerMessage(message);
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// Intercept the launch configuration and notify the server-side debug runner
|
|
87
|
-
if (message.type === "request" && message.command === "launch") {
|
|
88
|
-
this.sendRunnerMessage("configure", {
|
|
89
|
-
projectDir: message.arguments.projectDir
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
this.dataListener(message);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
send(msg) {
|
|
98
|
-
this.ws.send(msg);
|
|
99
|
-
}
|
|
100
|
-
onData(cb) {
|
|
101
|
-
this.dataListener = cb;
|
|
102
|
-
}
|
|
103
|
-
onError(cb) {
|
|
104
|
-
this.errorListener = cb;
|
|
105
|
-
}
|
|
106
|
-
onClose(cb) {
|
|
107
|
-
this.closeListener = cb;
|
|
108
|
-
}
|
|
109
|
-
close() {
|
|
110
|
-
if (this.session) {
|
|
111
|
-
this.session.stopSimulator(true);
|
|
112
|
-
}
|
|
113
|
-
if (this.intervalRunning) {
|
|
114
|
-
clearInterval(this.intervalId);
|
|
115
|
-
this.intervalId = undefined;
|
|
116
|
-
}
|
|
117
|
-
if (this.ws) {
|
|
118
|
-
this.ws.close();
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
handleRunnerMessage(msg) {
|
|
122
|
-
switch (msg.subtype) {
|
|
123
|
-
case "ready":
|
|
124
|
-
this.sendRunnerMessage("ready");
|
|
125
|
-
break;
|
|
126
|
-
case "runcode":
|
|
127
|
-
this.runCode(msg);
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
runCode(msg) {
|
|
132
|
-
const breakpoints = [];
|
|
133
|
-
// The breakpoints are in the format returned by the compiler
|
|
134
|
-
// and need to be converted to the format used by the DebugProtocol
|
|
135
|
-
msg.breakpoints.forEach(bp => {
|
|
136
|
-
breakpoints.push([bp.id, {
|
|
137
|
-
verified: true,
|
|
138
|
-
line: bp.line,
|
|
139
|
-
column: bp.column,
|
|
140
|
-
endLine: bp.endLine,
|
|
141
|
-
endColumn: bp.endColumn,
|
|
142
|
-
source: {
|
|
143
|
-
path: bp.fileName
|
|
144
|
-
}
|
|
145
|
-
}]);
|
|
146
|
-
});
|
|
147
|
-
this.session.runCode(msg.code, msg.usedParts, msg.usedArguments, new pxsim.BreakpointMap(breakpoints), pxt.appTarget.simulator.boardDefinition);
|
|
148
|
-
}
|
|
149
|
-
sendRunnerMessage(subtype, msg = {}) {
|
|
150
|
-
msg["subtype"] = subtype;
|
|
151
|
-
msg["type"] = "runner";
|
|
152
|
-
this.send(JSON.stringify(msg));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
DebugRunner.RETRY_MS = 2500;
|
|
156
|
-
runner.DebugRunner = DebugRunner;
|
|
157
|
-
})(runner = pxt.runner || (pxt.runner = {}));
|
|
158
|
-
})(pxt || (pxt = {}));
|
|
159
|
-
var pxt;
|
|
160
|
-
(function (pxt) {
|
|
161
|
-
var runner;
|
|
162
|
-
(function (runner) {
|
|
163
|
-
const JS_ICON = "icon xicon js";
|
|
164
|
-
const PY_ICON = "icon xicon python";
|
|
165
|
-
const BLOCKS_ICON = "icon xicon blocks";
|
|
166
|
-
function defaultClientRenderOptions() {
|
|
167
|
-
const renderOptions = {
|
|
168
|
-
blocksAspectRatio: window.innerHeight < window.innerWidth ? 1.62 : 1 / 1.62,
|
|
169
|
-
snippetClass: 'lang-blocks',
|
|
170
|
-
signatureClass: 'lang-sig',
|
|
171
|
-
blocksClass: 'lang-block',
|
|
172
|
-
blocksXmlClass: 'lang-blocksxml',
|
|
173
|
-
diffBlocksXmlClass: 'lang-diffblocksxml',
|
|
174
|
-
diffClass: 'lang-diff',
|
|
175
|
-
diffStaticPythonClass: 'lang-diffspy',
|
|
176
|
-
diffBlocksClass: 'lang-diffblocks',
|
|
177
|
-
staticPythonClass: 'lang-spy',
|
|
178
|
-
simulatorClass: 'lang-sim',
|
|
179
|
-
linksClass: 'lang-cards',
|
|
180
|
-
namespacesClass: 'lang-namespaces',
|
|
181
|
-
apisClass: 'lang-apis',
|
|
182
|
-
codeCardClass: 'lang-codecard',
|
|
183
|
-
packageClass: 'lang-package',
|
|
184
|
-
jresClass: 'lang-jres',
|
|
185
|
-
assetJSONClass: 'lang-assetsjson',
|
|
186
|
-
projectClass: 'lang-project',
|
|
187
|
-
snippetReplaceParent: true,
|
|
188
|
-
simulator: true,
|
|
189
|
-
showEdit: true,
|
|
190
|
-
hex: true,
|
|
191
|
-
tutorial: false,
|
|
192
|
-
showJavaScript: false,
|
|
193
|
-
hexName: pxt.appTarget.id
|
|
194
|
-
};
|
|
195
|
-
return renderOptions;
|
|
196
|
-
}
|
|
197
|
-
runner.defaultClientRenderOptions = defaultClientRenderOptions;
|
|
198
|
-
function highlight($js) {
|
|
199
|
-
if (typeof hljs !== "undefined") {
|
|
200
|
-
if ($js.hasClass("highlight")) {
|
|
201
|
-
hljs.highlightBlock($js[0]);
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
$js.find('code.highlight').each(function (i, block) {
|
|
205
|
-
hljs.highlightBlock(block);
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
highlightLine($js);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function highlightLine($js) {
|
|
212
|
-
// apply line highlighting
|
|
213
|
-
$js.find("span.hljs-comment:contains(@highlight)")
|
|
214
|
-
.each((i, el) => {
|
|
215
|
-
try {
|
|
216
|
-
highlightLineElement(el);
|
|
217
|
-
}
|
|
218
|
-
catch (e) {
|
|
219
|
-
pxt.reportException(e);
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
function highlightLineElement(el) {
|
|
224
|
-
const $el = $(el);
|
|
225
|
-
const span = document.createElement("span");
|
|
226
|
-
span.className = "highlight-line";
|
|
227
|
-
// find new line and split text node
|
|
228
|
-
let next = el.nextSibling;
|
|
229
|
-
if (!next || next.nodeType != Node.TEXT_NODE)
|
|
230
|
-
return; // end of snippet?
|
|
231
|
-
let text = next.textContent;
|
|
232
|
-
let inewline = text.indexOf('\n');
|
|
233
|
-
if (inewline < 0)
|
|
234
|
-
return; // there should have been a new line here
|
|
235
|
-
// split the next node
|
|
236
|
-
next.textContent = text.substring(0, inewline + 1);
|
|
237
|
-
$(document.createTextNode(text.substring(inewline + 1).replace(/^\s+/, ''))).insertAfter($(next));
|
|
238
|
-
// process and highlight new line
|
|
239
|
-
next = next.nextSibling;
|
|
240
|
-
while (next) {
|
|
241
|
-
let nextnext = next.nextSibling; // before we hoist it from the tree
|
|
242
|
-
if (next.nodeType == Node.TEXT_NODE) {
|
|
243
|
-
text = next.textContent;
|
|
244
|
-
const inewline = text.indexOf('\n');
|
|
245
|
-
if (inewline < 0) {
|
|
246
|
-
span.appendChild(next);
|
|
247
|
-
next = nextnext;
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
// we've hit the end of the line... split node in two
|
|
251
|
-
span.appendChild(document.createTextNode(text.substring(0, inewline)));
|
|
252
|
-
next.textContent = text.substring(inewline + 1);
|
|
253
|
-
break;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
else {
|
|
257
|
-
span.appendChild(next);
|
|
258
|
-
next = nextnext;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// insert back
|
|
262
|
-
$(span).insertAfter($el);
|
|
263
|
-
// remove line entry
|
|
264
|
-
$el.remove();
|
|
265
|
-
}
|
|
266
|
-
function appendBlocks($parent, $svg) {
|
|
267
|
-
$parent.append($(`<div class="ui content blocks"/>`).append($svg));
|
|
268
|
-
}
|
|
269
|
-
function appendJs($parent, $js, woptions) {
|
|
270
|
-
$parent.append($(`<div class="ui content js"><div class="subheading"><i class="ui icon xicon js"></i>JavaScript</div></div>`).append($js));
|
|
271
|
-
highlight($js);
|
|
272
|
-
}
|
|
273
|
-
function appendPy($parent, $py, woptions) {
|
|
274
|
-
$parent.append($(`<div class="ui content py"><div class="subheading"><i class="ui icon xicon python"></i>Python</div></div>`).append($py));
|
|
275
|
-
highlight($py);
|
|
276
|
-
}
|
|
277
|
-
function snippetBtn(label, icon) {
|
|
278
|
-
const $btn = $(`<a class="item" role="button" tabindex="0"><i role="presentation" aria-hidden="true"></i><span class="ui desktop only"></span></a>`);
|
|
279
|
-
$btn.attr("aria-label", label);
|
|
280
|
-
$btn.attr("title", label);
|
|
281
|
-
$btn.find('i').attr("class", icon);
|
|
282
|
-
$btn.find('span').text(label);
|
|
283
|
-
addFireClickOnEnter($btn);
|
|
284
|
-
return $btn;
|
|
285
|
-
}
|
|
286
|
-
function addFireClickOnEnter(el) {
|
|
287
|
-
el.keypress(e => {
|
|
288
|
-
const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
|
|
289
|
-
if (charCode === 13 /* enter */ || charCode === 32 /* space */) {
|
|
290
|
-
e.preventDefault();
|
|
291
|
-
e.currentTarget.click();
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
function fillWithWidget(options, $container, $js, $py, $svg, decompileResult, woptions = {}) {
|
|
296
|
-
let $h = $('<div class="ui bottom attached tabular icon small compact menu hideprint">'
|
|
297
|
-
+ ' <div class="right icon menu"></div></div>');
|
|
298
|
-
let $c = $('<div class="ui top attached segment codewidget"></div>');
|
|
299
|
-
let $menu = $h.find('.right.menu');
|
|
300
|
-
const theme = pxt.appTarget.appTheme || {};
|
|
301
|
-
if (woptions.showEdit && !theme.hideDocsEdit && decompileResult) { // edit button
|
|
302
|
-
const $editBtn = snippetBtn(lf("Edit"), "edit icon");
|
|
303
|
-
const { package: pkg, compileBlocks, compilePython } = decompileResult;
|
|
304
|
-
const host = pkg.host();
|
|
305
|
-
if ($svg && compileBlocks) {
|
|
306
|
-
pkg.setPreferredEditor(pxt.BLOCKS_PROJECT_NAME);
|
|
307
|
-
host.writeFile(pkg, pxt.MAIN_BLOCKS, compileBlocks.outfiles[pxt.MAIN_BLOCKS]);
|
|
308
|
-
}
|
|
309
|
-
else if ($py && compilePython) {
|
|
310
|
-
pkg.setPreferredEditor(pxt.PYTHON_PROJECT_NAME);
|
|
311
|
-
host.writeFile(pkg, pxt.MAIN_PY, compileBlocks.outfiles[pxt.MAIN_PY]);
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
pkg.setPreferredEditor(pxt.JAVASCRIPT_PROJECT_NAME);
|
|
315
|
-
}
|
|
316
|
-
if (options.assetJSON) {
|
|
317
|
-
for (const key of Object.keys(options.assetJSON)) {
|
|
318
|
-
if (pkg.config.files.indexOf(key) < 0) {
|
|
319
|
-
pkg.config.files.push(key);
|
|
320
|
-
}
|
|
321
|
-
host.writeFile(pkg, key, options.assetJSON[key]);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
const compressed = pkg.compressToFileAsync();
|
|
325
|
-
$editBtn.click(() => {
|
|
326
|
-
pxt.tickEvent("docs.btn", { button: "edit" });
|
|
327
|
-
compressed.then(buf => {
|
|
328
|
-
window.open(`${getEditUrl(options)}/#project:${ts.pxtc.encodeBase64(pxt.Util.uint8ArrayToString(buf))}`, 'pxt');
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
$menu.append($editBtn);
|
|
332
|
-
}
|
|
333
|
-
if (options.showJavaScript || (!$svg && !$py)) {
|
|
334
|
-
// js
|
|
335
|
-
$c.append($js);
|
|
336
|
-
appendBlocksButton();
|
|
337
|
-
appendPyButton();
|
|
338
|
-
}
|
|
339
|
-
else if ($svg) {
|
|
340
|
-
// blocks
|
|
341
|
-
$c.append($svg);
|
|
342
|
-
appendJsButton();
|
|
343
|
-
appendPyButton();
|
|
344
|
-
}
|
|
345
|
-
else if ($py) {
|
|
346
|
-
$c.append($py);
|
|
347
|
-
appendBlocksButton();
|
|
348
|
-
appendJsButton();
|
|
349
|
-
}
|
|
350
|
-
// runner menu
|
|
351
|
-
if (woptions.run && !theme.hideDocsSimulator) {
|
|
352
|
-
let $runBtn = snippetBtn(lf("Run"), "play icon").click(() => {
|
|
353
|
-
pxt.tickEvent("docs.btn", { button: "sim" });
|
|
354
|
-
if ($c.find('.sim')[0]) {
|
|
355
|
-
$c.find('.sim').remove(); // remove previous simulators
|
|
356
|
-
scrollJQueryIntoView($c);
|
|
357
|
-
}
|
|
358
|
-
else {
|
|
359
|
-
let padding = '81.97%';
|
|
360
|
-
if (pxt.appTarget.simulator)
|
|
361
|
-
padding = (100 / pxt.appTarget.simulator.aspectRatio) + '%';
|
|
362
|
-
const deps = options.package ? "&deps=" + encodeURIComponent(options.package) : "";
|
|
363
|
-
const url = getRunUrl(options) + "#nofooter=1" + deps;
|
|
364
|
-
const assets = options.assetJSON ? `data-assets="${encodeURIComponent(JSON.stringify(options.assetJSON))}"` : "";
|
|
365
|
-
const data = encodeURIComponent($js.text());
|
|
366
|
-
let $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="${url}" data-code="${data}" ${assets} allowfullscreen="allowfullscreen" sandbox="allow-popups allow-forms allow-scripts allow-same-origin" frameborder="0"></iframe></div></div></div>`);
|
|
367
|
-
$c.append($embed);
|
|
368
|
-
scrollJQueryIntoView($embed);
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
$menu.append($runBtn);
|
|
372
|
-
}
|
|
373
|
-
if (woptions.hexname && woptions.hex) {
|
|
374
|
-
let $hexBtn = snippetBtn(lf("Download"), "download icon").click(() => {
|
|
375
|
-
pxt.tickEvent("docs.btn", { button: "hex" });
|
|
376
|
-
pxt.BrowserUtils.browserDownloadBinText(woptions.hex, woptions.hexname, { contentType: pxt.appTarget.compile.hexMimeType });
|
|
377
|
-
});
|
|
378
|
-
$menu.append($hexBtn);
|
|
379
|
-
}
|
|
380
|
-
let r = $(`<div class=codesnippet></div>`);
|
|
381
|
-
// don't add menu if empty
|
|
382
|
-
if ($menu.children().length)
|
|
383
|
-
r.append($h);
|
|
384
|
-
r.append($c);
|
|
385
|
-
// inject container
|
|
386
|
-
$container.replaceWith(r);
|
|
387
|
-
function appendBlocksButton() {
|
|
388
|
-
if (!$svg)
|
|
389
|
-
return;
|
|
390
|
-
const $svgBtn = snippetBtn(lf("Blocks"), BLOCKS_ICON).click(() => {
|
|
391
|
-
pxt.tickEvent("docs.btn", { button: "blocks" });
|
|
392
|
-
if ($c.find('.blocks')[0]) {
|
|
393
|
-
$c.find('.blocks').remove();
|
|
394
|
-
scrollJQueryIntoView($c);
|
|
395
|
-
}
|
|
396
|
-
else {
|
|
397
|
-
if ($js)
|
|
398
|
-
appendBlocks($js.parent(), $svg);
|
|
399
|
-
else
|
|
400
|
-
appendBlocks($c, $svg);
|
|
401
|
-
scrollJQueryIntoView($svg);
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
$menu.append($svgBtn);
|
|
405
|
-
}
|
|
406
|
-
function appendJsButton() {
|
|
407
|
-
if (!$js)
|
|
408
|
-
return;
|
|
409
|
-
if (woptions.showJs)
|
|
410
|
-
appendJs($c, $js, woptions);
|
|
411
|
-
else {
|
|
412
|
-
const $jsBtn = snippetBtn("JavaScript", JS_ICON).click(() => {
|
|
413
|
-
pxt.tickEvent("docs.btn", { button: "js" });
|
|
414
|
-
if ($c.find('.js')[0]) {
|
|
415
|
-
$c.find('.js').remove();
|
|
416
|
-
scrollJQueryIntoView($c);
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
if ($svg)
|
|
420
|
-
appendJs($svg.parent(), $js, woptions);
|
|
421
|
-
else
|
|
422
|
-
appendJs($c, $js, woptions);
|
|
423
|
-
scrollJQueryIntoView($js);
|
|
424
|
-
}
|
|
425
|
-
});
|
|
426
|
-
$menu.append($jsBtn);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
function appendPyButton() {
|
|
430
|
-
if (!$py)
|
|
431
|
-
return;
|
|
432
|
-
if (woptions.showPy) {
|
|
433
|
-
appendPy($c, $py, woptions);
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
const $pyBtn = snippetBtn("Python", PY_ICON).click(() => {
|
|
437
|
-
pxt.tickEvent("docs.btn", { button: "py" });
|
|
438
|
-
if ($c.find('.py')[0]) {
|
|
439
|
-
$c.find('.py').remove();
|
|
440
|
-
scrollJQueryIntoView($c);
|
|
441
|
-
}
|
|
442
|
-
else {
|
|
443
|
-
if ($svg)
|
|
444
|
-
appendPy($svg.parent(), $py, woptions);
|
|
445
|
-
else
|
|
446
|
-
appendPy($c, $py, woptions);
|
|
447
|
-
scrollJQueryIntoView($py);
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
$menu.append($pyBtn);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
function scrollJQueryIntoView($toScrollTo) {
|
|
454
|
-
var _a;
|
|
455
|
-
(_a = $toScrollTo[0]) === null || _a === void 0 ? void 0 : _a.scrollIntoView({
|
|
456
|
-
behavior: "smooth",
|
|
457
|
-
block: "center"
|
|
458
|
-
});
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
let renderQueue = [];
|
|
462
|
-
function consumeRenderQueueAsync() {
|
|
463
|
-
const existingFilters = {};
|
|
464
|
-
return consumeNext()
|
|
465
|
-
.then(() => {
|
|
466
|
-
Blockly.Workspace.getAll().forEach(el => el.dispose());
|
|
467
|
-
pxt.blocks.cleanRenderingWorkspace();
|
|
468
|
-
});
|
|
469
|
-
function consumeNext() {
|
|
470
|
-
const job = renderQueue.shift();
|
|
471
|
-
if (!job)
|
|
472
|
-
return Promise.resolve(); // done
|
|
473
|
-
const { el, options, render } = job;
|
|
474
|
-
return pxt.runner.decompileSnippetAsync(el.text(), options)
|
|
475
|
-
.then(r => {
|
|
476
|
-
const errors = r.compileJS && r.compileJS.diagnostics && r.compileJS.diagnostics.filter(d => d.category == pxtc.DiagnosticCategory.Error);
|
|
477
|
-
if (errors && errors.length) {
|
|
478
|
-
errors.forEach(diag => pxt.reportError("docs.decompile", "" + diag.messageText, { "code": diag.code + "" }));
|
|
479
|
-
}
|
|
480
|
-
// filter out any blockly definitions from the svg that would be duplicates on the page
|
|
481
|
-
r.blocksSvg.querySelectorAll("defs *").forEach(el => {
|
|
482
|
-
if (existingFilters[el.id]) {
|
|
483
|
-
el.remove();
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
existingFilters[el.id] = true;
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
render(el, r);
|
|
490
|
-
}, e => {
|
|
491
|
-
pxt.reportException(e);
|
|
492
|
-
el.append($('<div/>').addClass("ui segment warning").text(e.message));
|
|
493
|
-
}).finally(() => {
|
|
494
|
-
el.removeClass("lang-shadow");
|
|
495
|
-
return consumeNext();
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
function renderNextSnippetAsync(cls, render, options) {
|
|
500
|
-
if (!cls)
|
|
501
|
-
return Promise.resolve();
|
|
502
|
-
let $el = $("." + cls).first();
|
|
503
|
-
if (!$el[0])
|
|
504
|
-
return Promise.resolve();
|
|
505
|
-
if (!options.emPixels)
|
|
506
|
-
options.emPixels = 18;
|
|
507
|
-
if (!options.layout)
|
|
508
|
-
options.layout = pxt.blocks.BlockLayout.Align;
|
|
509
|
-
options.splitSvg = true;
|
|
510
|
-
renderQueue.push({ el: $el, source: $el.text(), options, render });
|
|
511
|
-
$el.addClass("lang-shadow");
|
|
512
|
-
$el.removeClass(cls);
|
|
513
|
-
return renderNextSnippetAsync(cls, render, options);
|
|
514
|
-
}
|
|
515
|
-
function renderSnippetsAsync(options) {
|
|
516
|
-
if (options.tutorial) {
|
|
517
|
-
// don't render chrome for tutorials
|
|
518
|
-
return renderNextSnippetAsync(options.snippetClass, (c, r) => {
|
|
519
|
-
const s = r.blocksSvg;
|
|
520
|
-
if (options.snippetReplaceParent)
|
|
521
|
-
c = c.parent();
|
|
522
|
-
const segment = $('<div class="ui segment codewidget"/>').append(s);
|
|
523
|
-
c.replaceWith(segment);
|
|
524
|
-
}, { package: options.package, snippetMode: false, aspectRatio: options.blocksAspectRatio, assets: options.assetJSON });
|
|
525
|
-
}
|
|
526
|
-
let snippetCount = 0;
|
|
527
|
-
return renderNextSnippetAsync(options.snippetClass, (c, r) => {
|
|
528
|
-
const s = r.compileBlocks && r.compileBlocks.success ? $(r.blocksSvg) : undefined;
|
|
529
|
-
const p = r.compilePython && r.compilePython.success && r.compilePython.outfiles[pxt.MAIN_PY];
|
|
530
|
-
const js = $('<code class="lang-typescript highlight"/>').text(c.text().trim());
|
|
531
|
-
const py = p ? $('<code class="lang-python highlight"/>').text(p.trim()) : undefined;
|
|
532
|
-
if (options.snippetReplaceParent)
|
|
533
|
-
c = c.parent();
|
|
534
|
-
const compiled = r.compileJS && r.compileJS.success;
|
|
535
|
-
// TODO should this use pxt.outputName() and not pxtc.BINARY_HEX
|
|
536
|
-
const hex = options.hex && compiled && r.compileJS.outfiles[pxtc.BINARY_HEX]
|
|
537
|
-
? r.compileJS.outfiles[pxtc.BINARY_HEX] : undefined;
|
|
538
|
-
const hexname = `${pxt.appTarget.nickname || pxt.appTarget.id}-${options.hexName || ''}-${snippetCount++}.hex`;
|
|
539
|
-
fillWithWidget(options, c, js, py, s, r, {
|
|
540
|
-
showEdit: options.showEdit,
|
|
541
|
-
run: options.simulator,
|
|
542
|
-
hexname: hexname,
|
|
543
|
-
hex: hex,
|
|
544
|
-
});
|
|
545
|
-
}, { package: options.package, aspectRatio: options.blocksAspectRatio, assets: options.assetJSON });
|
|
546
|
-
}
|
|
547
|
-
function decompileCallInfo(stmt) {
|
|
548
|
-
if (!stmt || stmt.kind != ts.SyntaxKind.ExpressionStatement)
|
|
549
|
-
return null;
|
|
550
|
-
let estmt = stmt;
|
|
551
|
-
if (!estmt.expression || estmt.expression.kind != ts.SyntaxKind.CallExpression)
|
|
552
|
-
return null;
|
|
553
|
-
let call = estmt.expression;
|
|
554
|
-
let info = pxtc.pxtInfo(call).callInfo;
|
|
555
|
-
return info;
|
|
556
|
-
}
|
|
557
|
-
function renderSignaturesAsync(options) {
|
|
558
|
-
return renderNextSnippetAsync(options.signatureClass, (c, r) => {
|
|
559
|
-
var _a, _b, _c, _d;
|
|
560
|
-
let cjs = r.compileProgram;
|
|
561
|
-
if (!cjs)
|
|
562
|
-
return;
|
|
563
|
-
let file = cjs.getSourceFile(pxt.MAIN_TS);
|
|
564
|
-
let info = decompileCallInfo(file.statements[0]);
|
|
565
|
-
if (!info || !r.apiInfo)
|
|
566
|
-
return;
|
|
567
|
-
const symbolInfo = r.apiInfo.byQName[info.qName];
|
|
568
|
-
if (!symbolInfo)
|
|
569
|
-
return;
|
|
570
|
-
let block = Blockly.Blocks[symbolInfo.attributes.blockId];
|
|
571
|
-
let xml = ((_a = block === null || block === void 0 ? void 0 : block.codeCard) === null || _a === void 0 ? void 0 : _a.blocksXml) || undefined;
|
|
572
|
-
const blocksHtml = xml ? pxt.blocks.render(xml) : ((_b = r.compileBlocks) === null || _b === void 0 ? void 0 : _b.success) ? r.blocksSvg : undefined;
|
|
573
|
-
const s = blocksHtml ? $(blocksHtml) : undefined;
|
|
574
|
-
let jsSig = ts.pxtc.service.displayStringForSymbol(symbolInfo, /** python **/ false, r.apiInfo)
|
|
575
|
-
.split("\n")[1] + ";";
|
|
576
|
-
const js = $('<code class="lang-typescript highlight"/>').text(jsSig);
|
|
577
|
-
const pySig = ((_d = (_c = pxt.appTarget) === null || _c === void 0 ? void 0 : _c.appTheme) === null || _d === void 0 ? void 0 : _d.python) && ts.pxtc.service.displayStringForSymbol(symbolInfo, /** python **/ true, r.apiInfo).split("\n")[1];
|
|
578
|
-
const py = pySig && $('<code class="lang-python highlight"/>').text(pySig);
|
|
579
|
-
if (options.snippetReplaceParent)
|
|
580
|
-
c = c.parent();
|
|
581
|
-
// add an html widge that allows to translate the block
|
|
582
|
-
if (pxt.Util.isTranslationMode()) {
|
|
583
|
-
const trs = $('<div class="ui segment" />');
|
|
584
|
-
trs.append($(`<div class="ui header"><i class="ui xicon globe"></i></div>`));
|
|
585
|
-
if (symbolInfo.attributes.translationId)
|
|
586
|
-
trs.append($('<div class="ui message">').text(symbolInfo.attributes.translationId));
|
|
587
|
-
if (symbolInfo.attributes.jsDoc)
|
|
588
|
-
trs.append($('<div class="ui message">').text(symbolInfo.attributes.jsDoc));
|
|
589
|
-
trs.insertAfter(c);
|
|
590
|
-
}
|
|
591
|
-
fillWithWidget(options, c, js, py, s, r, { showJs: true, showPy: true, hideGutter: true });
|
|
592
|
-
}, { package: options.package, snippetMode: true, aspectRatio: options.blocksAspectRatio, assets: options.assetJSON });
|
|
593
|
-
}
|
|
594
|
-
function renderBlocksAsync(options) {
|
|
595
|
-
return renderNextSnippetAsync(options.blocksClass, (c, r) => {
|
|
596
|
-
const s = r.blocksSvg;
|
|
597
|
-
if (options.snippetReplaceParent)
|
|
598
|
-
c = c.parent();
|
|
599
|
-
const segment = $('<div class="ui segment codewidget"/>').append(s);
|
|
600
|
-
c.replaceWith(segment);
|
|
601
|
-
}, { package: options.package, snippetMode: true, aspectRatio: options.blocksAspectRatio, assets: options.assetJSON });
|
|
602
|
-
}
|
|
603
|
-
function renderStaticPythonAsync(options) {
|
|
604
|
-
// Highlight python snippets if the snippet has compile python
|
|
605
|
-
const woptions = {
|
|
606
|
-
showEdit: !!options.showEdit,
|
|
607
|
-
run: !!options.simulator
|
|
608
|
-
};
|
|
609
|
-
return renderNextSnippetAsync(options.staticPythonClass, (c, r) => {
|
|
610
|
-
const s = r.compilePython;
|
|
611
|
-
if (s && s.success) {
|
|
612
|
-
const $js = c.clone().removeClass('lang-shadow').addClass('highlight');
|
|
613
|
-
const $py = $js.clone().addClass('lang-python').text(s.outfiles[pxt.MAIN_PY]);
|
|
614
|
-
$js.addClass('lang-typescript');
|
|
615
|
-
highlight($py);
|
|
616
|
-
fillWithWidget(options, c.parent(), /* js */ $js, /* py */ $py, /* svg */ undefined, r, woptions);
|
|
617
|
-
}
|
|
618
|
-
}, { package: options.package, snippetMode: true, assets: options.assetJSON });
|
|
619
|
-
}
|
|
620
|
-
function renderBlocksXmlAsync(opts) {
|
|
621
|
-
if (!opts.blocksXmlClass)
|
|
622
|
-
return Promise.resolve();
|
|
623
|
-
const cls = opts.blocksXmlClass;
|
|
624
|
-
function renderNextXmlAsync(cls, render, options) {
|
|
625
|
-
let $el = $("." + cls).first();
|
|
626
|
-
if (!$el[0])
|
|
627
|
-
return Promise.resolve();
|
|
628
|
-
if (!options.emPixels)
|
|
629
|
-
options.emPixels = 18;
|
|
630
|
-
options.splitSvg = true;
|
|
631
|
-
return pxt.runner.compileBlocksAsync($el.text(), options)
|
|
632
|
-
.then((r) => {
|
|
633
|
-
try {
|
|
634
|
-
render($el, r);
|
|
635
|
-
}
|
|
636
|
-
catch (e) {
|
|
637
|
-
pxt.reportException(e);
|
|
638
|
-
$el.append($('<div/>').addClass("ui segment warning").text(e.message));
|
|
639
|
-
}
|
|
640
|
-
$el.removeClass(cls);
|
|
641
|
-
return pxt.U.delay(1, renderNextXmlAsync(cls, render, options));
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
|
-
return renderNextXmlAsync(cls, (c, r) => {
|
|
645
|
-
const s = r.blocksSvg;
|
|
646
|
-
if (opts.snippetReplaceParent)
|
|
647
|
-
c = c.parent();
|
|
648
|
-
const segment = $('<div class="ui segment codewidget"/>').append(s);
|
|
649
|
-
c.replaceWith(segment);
|
|
650
|
-
}, { package: opts.package, snippetMode: true, aspectRatio: opts.blocksAspectRatio, assets: opts.assetJSON });
|
|
651
|
-
}
|
|
652
|
-
function renderDiffBlocksXmlAsync(opts) {
|
|
653
|
-
if (!opts.diffBlocksXmlClass)
|
|
654
|
-
return Promise.resolve();
|
|
655
|
-
const cls = opts.diffBlocksXmlClass;
|
|
656
|
-
function renderNextXmlAsync(cls, render, options) {
|
|
657
|
-
let $el = $("." + cls).first();
|
|
658
|
-
if (!$el[0])
|
|
659
|
-
return Promise.resolve();
|
|
660
|
-
if (!options.emPixels)
|
|
661
|
-
options.emPixels = 18;
|
|
662
|
-
options.splitSvg = true;
|
|
663
|
-
const xml = $el.text().split(/-{10,}/);
|
|
664
|
-
const oldXml = xml[0];
|
|
665
|
-
const newXml = xml[1];
|
|
666
|
-
return pxt.runner.compileBlocksAsync("", options) // force loading blocks
|
|
667
|
-
.then(r => {
|
|
668
|
-
$el.removeClass(cls);
|
|
669
|
-
try {
|
|
670
|
-
const diff = pxt.blocks.diffXml(oldXml, newXml);
|
|
671
|
-
if (!diff)
|
|
672
|
-
$el.text("no changes");
|
|
673
|
-
else {
|
|
674
|
-
r.blocksSvg = diff.svg;
|
|
675
|
-
render($el, r);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
catch (e) {
|
|
679
|
-
pxt.reportException(e);
|
|
680
|
-
$el.append($('<div/>').addClass("ui segment warning").text(e.message));
|
|
681
|
-
}
|
|
682
|
-
return pxt.U.delay(1, renderNextXmlAsync(cls, render, options));
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
return renderNextXmlAsync(cls, (c, r) => {
|
|
686
|
-
const s = r.blocksSvg;
|
|
687
|
-
if (opts.snippetReplaceParent)
|
|
688
|
-
c = c.parent();
|
|
689
|
-
const segment = $('<div class="ui segment codewidget"/>').append(s);
|
|
690
|
-
c.replaceWith(segment);
|
|
691
|
-
}, { package: opts.package, snippetMode: true, aspectRatio: opts.blocksAspectRatio, assets: opts.assetJSON });
|
|
692
|
-
}
|
|
693
|
-
function renderDiffAsync(opts) {
|
|
694
|
-
if (!opts.diffClass)
|
|
695
|
-
return Promise.resolve();
|
|
696
|
-
const cls = opts.diffClass;
|
|
697
|
-
function renderNextDiffAsync(cls) {
|
|
698
|
-
let $el = $("." + cls).first();
|
|
699
|
-
if (!$el[0])
|
|
700
|
-
return Promise.resolve();
|
|
701
|
-
const { fileA: oldSrc, fileB: newSrc } = pxt.diff.split($el.text());
|
|
702
|
-
try {
|
|
703
|
-
const diffEl = pxt.diff.render(oldSrc, newSrc, {
|
|
704
|
-
hideLineNumbers: true,
|
|
705
|
-
hideMarkerLine: true,
|
|
706
|
-
hideMarker: true,
|
|
707
|
-
hideRemoved: true,
|
|
708
|
-
update: true,
|
|
709
|
-
ignoreWhitespace: true,
|
|
710
|
-
});
|
|
711
|
-
if (opts.snippetReplaceParent)
|
|
712
|
-
$el = $el.parent();
|
|
713
|
-
const segment = $('<div class="ui segment codewidget"/>').append(diffEl);
|
|
714
|
-
$el.removeClass(cls);
|
|
715
|
-
$el.replaceWith(segment);
|
|
716
|
-
}
|
|
717
|
-
catch (e) {
|
|
718
|
-
pxt.reportException(e);
|
|
719
|
-
$el.append($('<div/>').addClass("ui segment warning").text(e.message));
|
|
720
|
-
}
|
|
721
|
-
return pxt.U.delay(1, renderNextDiffAsync(cls));
|
|
722
|
-
}
|
|
723
|
-
return renderNextDiffAsync(cls);
|
|
724
|
-
}
|
|
725
|
-
function renderDiffBlocksAsync(opts) {
|
|
726
|
-
if (!opts.diffBlocksClass)
|
|
727
|
-
return Promise.resolve();
|
|
728
|
-
const cls = opts.diffBlocksClass;
|
|
729
|
-
function renderNextDiffAsync(cls) {
|
|
730
|
-
let $el = $("." + cls).first();
|
|
731
|
-
if (!$el[0])
|
|
732
|
-
return Promise.resolve();
|
|
733
|
-
const { fileA: oldSrc, fileB: newSrc } = pxt.diff.split($el.text(), {
|
|
734
|
-
removeTrailingSemiColumns: true
|
|
735
|
-
});
|
|
736
|
-
return pxt.U.promiseMapAllSeries([oldSrc, newSrc], src => pxt.runner.decompileSnippetAsync(src, {
|
|
737
|
-
generateSourceMap: true
|
|
738
|
-
}))
|
|
739
|
-
.then(resps => {
|
|
740
|
-
try {
|
|
741
|
-
const diffBlocks = pxt.blocks.decompiledDiffAsync(oldSrc, resps[0].compileBlocks, newSrc, resps[1].compileBlocks, {
|
|
742
|
-
hideDeletedTopBlocks: true,
|
|
743
|
-
hideDeletedBlocks: true
|
|
744
|
-
});
|
|
745
|
-
const diffJs = pxt.diff.render(oldSrc, newSrc, {
|
|
746
|
-
hideLineNumbers: true,
|
|
747
|
-
hideMarkerLine: true,
|
|
748
|
-
hideMarker: true,
|
|
749
|
-
hideRemoved: true,
|
|
750
|
-
update: true,
|
|
751
|
-
ignoreWhitespace: true
|
|
752
|
-
});
|
|
753
|
-
let diffPy;
|
|
754
|
-
const [oldPy, newPy] = resps.map(resp => resp.compilePython
|
|
755
|
-
&& resp.compilePython.outfiles
|
|
756
|
-
&& resp.compilePython.outfiles[pxt.MAIN_PY]);
|
|
757
|
-
if (oldPy && newPy) {
|
|
758
|
-
diffPy = pxt.diff.render(oldPy, newPy, {
|
|
759
|
-
hideLineNumbers: true,
|
|
760
|
-
hideMarkerLine: true,
|
|
761
|
-
hideMarker: true,
|
|
762
|
-
hideRemoved: true,
|
|
763
|
-
update: true,
|
|
764
|
-
ignoreWhitespace: true
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
fillWithWidget(opts, $el.parent(), $(diffJs), diffPy && $(diffPy), $(diffBlocks.svg), undefined, {
|
|
768
|
-
showEdit: false,
|
|
769
|
-
run: false,
|
|
770
|
-
hexname: undefined,
|
|
771
|
-
hex: undefined
|
|
772
|
-
});
|
|
773
|
-
}
|
|
774
|
-
catch (e) {
|
|
775
|
-
pxt.reportException(e);
|
|
776
|
-
$el.append($('<div/>').addClass("ui segment warning").text(e.message));
|
|
777
|
-
}
|
|
778
|
-
return pxt.U.delay(1, renderNextDiffAsync(cls));
|
|
779
|
-
});
|
|
780
|
-
}
|
|
781
|
-
return renderNextDiffAsync(cls);
|
|
782
|
-
}
|
|
783
|
-
let decompileApiPromise;
|
|
784
|
-
function decompileApiAsync(options) {
|
|
785
|
-
if (!decompileApiPromise)
|
|
786
|
-
decompileApiPromise = pxt.runner.decompileSnippetAsync('', options);
|
|
787
|
-
return decompileApiPromise;
|
|
788
|
-
}
|
|
789
|
-
function renderNamespaces(options) {
|
|
790
|
-
if (pxt.appTarget.id == "core")
|
|
791
|
-
return Promise.resolve();
|
|
792
|
-
return decompileApiAsync(options)
|
|
793
|
-
.then((r) => {
|
|
794
|
-
let res = {};
|
|
795
|
-
const info = r.compileBlocks.blocksInfo;
|
|
796
|
-
info.blocks.forEach(fn => {
|
|
797
|
-
const ns = (fn.attributes.blockNamespace || fn.namespace).split('.')[0];
|
|
798
|
-
if (!res[ns]) {
|
|
799
|
-
const nsn = info.apis.byQName[ns];
|
|
800
|
-
if (nsn && nsn.attributes.color)
|
|
801
|
-
res[ns] = nsn.attributes.color;
|
|
802
|
-
}
|
|
803
|
-
});
|
|
804
|
-
let nsStyleBuffer = '';
|
|
805
|
-
Object.keys(res).forEach(ns => {
|
|
806
|
-
const color = res[ns] || '#dddddd';
|
|
807
|
-
nsStyleBuffer += `
|
|
808
|
-
span.docs.${ns.toLowerCase()} {
|
|
809
|
-
background-color: ${color} !important;
|
|
810
|
-
border-color: ${pxt.toolbox.fadeColor(color, 0.1, false)} !important;
|
|
811
|
-
}
|
|
812
|
-
`;
|
|
813
|
-
});
|
|
814
|
-
return nsStyleBuffer;
|
|
815
|
-
})
|
|
816
|
-
.then((nsStyleBuffer) => {
|
|
817
|
-
Object.keys(pxt.toolbox.blockColors).forEach((ns) => {
|
|
818
|
-
const color = pxt.toolbox.getNamespaceColor(ns);
|
|
819
|
-
nsStyleBuffer += `
|
|
820
|
-
span.docs.${ns.toLowerCase()} {
|
|
821
|
-
background-color: ${color} !important;
|
|
822
|
-
border-color: ${pxt.toolbox.fadeColor(color, 0.1, false)} !important;
|
|
823
|
-
}
|
|
824
|
-
`;
|
|
825
|
-
});
|
|
826
|
-
return nsStyleBuffer;
|
|
827
|
-
})
|
|
828
|
-
.then((nsStyleBuffer) => {
|
|
829
|
-
// Inject css
|
|
830
|
-
let nsStyle = document.createElement('style');
|
|
831
|
-
nsStyle.id = "namespaceColors";
|
|
832
|
-
nsStyle.type = 'text/css';
|
|
833
|
-
let head = document.head || document.getElementsByTagName('head')[0];
|
|
834
|
-
head.appendChild(nsStyle);
|
|
835
|
-
nsStyle.appendChild(document.createTextNode(nsStyleBuffer));
|
|
836
|
-
});
|
|
837
|
-
}
|
|
838
|
-
function renderInlineBlocksAsync(options) {
|
|
839
|
-
options = pxt.Util.clone(options);
|
|
840
|
-
options.emPixels = 18;
|
|
841
|
-
options.snippetMode = true;
|
|
842
|
-
const $els = $(`:not(pre) > code`);
|
|
843
|
-
let i = 0;
|
|
844
|
-
function renderNextAsync() {
|
|
845
|
-
if (i >= $els.length)
|
|
846
|
-
return Promise.resolve();
|
|
847
|
-
const $el = $($els[i++]);
|
|
848
|
-
const text = $el.text();
|
|
849
|
-
const mbtn = /^(\|+)([^\|]+)\|+$/.exec(text);
|
|
850
|
-
if (mbtn) {
|
|
851
|
-
const mtxt = /^(([^\:\.]*?)[\:\.])?(.*)$/.exec(mbtn[2]);
|
|
852
|
-
const ns = mtxt[2] ? mtxt[2].trim().toLowerCase() : '';
|
|
853
|
-
const lev = mbtn[1].length == 1 ? `docs inlinebutton ${ns}` : `docs inlineblock ${ns}`;
|
|
854
|
-
const txt = mtxt[3].trim();
|
|
855
|
-
$el.replaceWith($(`<span class="${lev}"/>`).text(pxt.U.rlf(txt)));
|
|
856
|
-
return renderNextAsync();
|
|
857
|
-
}
|
|
858
|
-
const m = /^\[(.+)\]$/.exec(text);
|
|
859
|
-
if (!m)
|
|
860
|
-
return renderNextAsync();
|
|
861
|
-
const code = m[1];
|
|
862
|
-
return pxt.runner.decompileSnippetAsync(code, options)
|
|
863
|
-
.then(r => {
|
|
864
|
-
if (r.blocksSvg) {
|
|
865
|
-
let $newel = $('<span class="block"/>').append(r.blocksSvg);
|
|
866
|
-
const file = r.compileProgram.getSourceFile(pxt.MAIN_TS);
|
|
867
|
-
const stmt = file.statements[0];
|
|
868
|
-
const info = decompileCallInfo(stmt);
|
|
869
|
-
if (info && r.apiInfo) {
|
|
870
|
-
const symbolInfo = r.apiInfo.byQName[info.qName];
|
|
871
|
-
if (symbolInfo && symbolInfo.attributes.help) {
|
|
872
|
-
$newel = $(`<a class="ui link"/>`).attr("href", `/reference/${symbolInfo.attributes.help}`).append($newel);
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
$el.replaceWith($newel);
|
|
876
|
-
}
|
|
877
|
-
return pxt.U.delay(1, renderNextAsync());
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
return renderNextAsync();
|
|
881
|
-
}
|
|
882
|
-
function renderProjectAsync(options) {
|
|
883
|
-
if (!options.projectClass)
|
|
884
|
-
return Promise.resolve();
|
|
885
|
-
function render() {
|
|
886
|
-
let $el = $("." + options.projectClass).first();
|
|
887
|
-
let e = $el[0];
|
|
888
|
-
if (!e)
|
|
889
|
-
return Promise.resolve();
|
|
890
|
-
$el.removeClass(options.projectClass);
|
|
891
|
-
let id = pxt.Cloud.parseScriptId(e.innerText);
|
|
892
|
-
if (id) {
|
|
893
|
-
if (options.snippetReplaceParent) {
|
|
894
|
-
e = e.parentElement;
|
|
895
|
-
// create a new div to host the rendered code
|
|
896
|
-
let d = document.createElement("div");
|
|
897
|
-
e.parentElement.insertBefore(d, e);
|
|
898
|
-
e.parentElement.removeChild(e);
|
|
899
|
-
e = d;
|
|
900
|
-
}
|
|
901
|
-
return pxt.runner.renderProjectAsync(e, id)
|
|
902
|
-
.then(() => render());
|
|
903
|
-
}
|
|
904
|
-
else
|
|
905
|
-
return render();
|
|
906
|
-
}
|
|
907
|
-
return render();
|
|
908
|
-
}
|
|
909
|
-
function renderApisAsync(options, replaceParent) {
|
|
910
|
-
const cls = options.apisClass;
|
|
911
|
-
if (!cls)
|
|
912
|
-
return Promise.resolve();
|
|
913
|
-
const apisEl = $('.' + cls);
|
|
914
|
-
if (!apisEl.length)
|
|
915
|
-
return Promise.resolve();
|
|
916
|
-
return decompileApiAsync(options)
|
|
917
|
-
.then((r) => {
|
|
918
|
-
const info = r.compileBlocks.blocksInfo;
|
|
919
|
-
const symbols = pxt.Util.values(info.apis.byQName)
|
|
920
|
-
.filter(symbol => !symbol.attributes.hidden
|
|
921
|
-
&& !symbol.attributes.deprecated
|
|
922
|
-
&& !symbol.attributes.blockAliasFor
|
|
923
|
-
&& !!symbol.attributes.jsDoc
|
|
924
|
-
&& !!symbol.attributes.block
|
|
925
|
-
&& !/^__/.test(symbol.name));
|
|
926
|
-
apisEl.each((i, e) => {
|
|
927
|
-
let c = $(e);
|
|
928
|
-
const namespaces = pxt.Util.toDictionary(c.text().split('\n'), n => n); // list of namespace to list apis for.
|
|
929
|
-
const csymbols = symbols.filter(symbol => !!namespaces[symbol.attributes.blockNamespace || symbol.namespace]);
|
|
930
|
-
if (!csymbols.length)
|
|
931
|
-
return;
|
|
932
|
-
csymbols.sort((l, r) => {
|
|
933
|
-
// render cards first
|
|
934
|
-
const lcard = !l.attributes.blockHidden && Blockly.Blocks[l.attributes.blockId];
|
|
935
|
-
const rcard = !r.attributes.blockHidden && Blockly.Blocks[r.attributes.blockId];
|
|
936
|
-
if (!!lcard != !!rcard)
|
|
937
|
-
return -(lcard ? 1 : 0) + (rcard ? 1 : 0);
|
|
938
|
-
// sort alphabetically
|
|
939
|
-
return l.name.localeCompare(r.name);
|
|
940
|
-
});
|
|
941
|
-
const ul = $('<div />').addClass('ui divided items');
|
|
942
|
-
ul.attr("role", "listbox");
|
|
943
|
-
csymbols.forEach(symbol => addSymbolCardItem(ul, symbol, "item"));
|
|
944
|
-
if (replaceParent)
|
|
945
|
-
c = c.parent();
|
|
946
|
-
c.replaceWith(ul);
|
|
947
|
-
});
|
|
948
|
-
});
|
|
949
|
-
}
|
|
950
|
-
function addCardItem(ul, card) {
|
|
951
|
-
if (!card)
|
|
952
|
-
return;
|
|
953
|
-
const mC = /^\/(v\d+)/.exec(card.url);
|
|
954
|
-
const mP = /^\/(v\d+)/.exec(window.location.pathname);
|
|
955
|
-
const inEditor = /#doc/i.test(window.location.href);
|
|
956
|
-
if (card.url && !mC && mP && !inEditor)
|
|
957
|
-
card.url = `/${mP[1]}/${card.url}`;
|
|
958
|
-
ul.append(pxt.docs.codeCard.render(card, { hideHeader: true, shortName: true }));
|
|
959
|
-
}
|
|
960
|
-
function addSymbolCardItem(ul, symbol, cardStyle) {
|
|
961
|
-
const attributes = symbol.attributes;
|
|
962
|
-
const block = !attributes.blockHidden && Blockly.Blocks[attributes.blockId];
|
|
963
|
-
const card = block === null || block === void 0 ? void 0 : block.codeCard;
|
|
964
|
-
if (card) {
|
|
965
|
-
const ccard = pxt.U.clone(block.codeCard);
|
|
966
|
-
if (cardStyle)
|
|
967
|
-
ccard.style = cardStyle;
|
|
968
|
-
addCardItem(ul, ccard);
|
|
969
|
-
}
|
|
970
|
-
else {
|
|
971
|
-
// default to text
|
|
972
|
-
// no block available here
|
|
973
|
-
addCardItem(ul, {
|
|
974
|
-
name: symbol.qName,
|
|
975
|
-
description: attributes.jsDoc,
|
|
976
|
-
url: attributes.help || undefined,
|
|
977
|
-
style: cardStyle
|
|
978
|
-
});
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
function renderLinksAsync(options, cls, replaceParent, ns) {
|
|
982
|
-
return renderNextSnippetAsync(cls, (c, r) => {
|
|
983
|
-
const cjs = r.compileProgram;
|
|
984
|
-
if (!cjs)
|
|
985
|
-
return;
|
|
986
|
-
const file = cjs.getSourceFile(pxt.MAIN_TS);
|
|
987
|
-
const stmts = file.statements.slice(0);
|
|
988
|
-
const ul = $('<div />').addClass('ui cards');
|
|
989
|
-
ul.attr("role", "listbox");
|
|
990
|
-
stmts.forEach(stmt => {
|
|
991
|
-
const kind = stmt.kind;
|
|
992
|
-
const info = decompileCallInfo(stmt);
|
|
993
|
-
if (info && r.apiInfo && r.apiInfo.byQName[info.qName]) {
|
|
994
|
-
const symbol = r.apiInfo.byQName[info.qName];
|
|
995
|
-
const attributes = symbol.attributes;
|
|
996
|
-
const block = Blockly.Blocks[attributes.blockId];
|
|
997
|
-
if (ns) {
|
|
998
|
-
const ii = symbol;
|
|
999
|
-
const nsi = r.compileBlocks.blocksInfo.apis.byQName[ii.namespace];
|
|
1000
|
-
addCardItem(ul, {
|
|
1001
|
-
name: nsi.attributes.blockNamespace || nsi.name,
|
|
1002
|
-
url: nsi.attributes.help || ("reference/" + (nsi.attributes.blockNamespace || nsi.name).toLowerCase()),
|
|
1003
|
-
description: nsi.attributes.jsDoc,
|
|
1004
|
-
blocksXml: block && block.codeCard
|
|
1005
|
-
? block.codeCard.blocksXml
|
|
1006
|
-
: attributes.blockId
|
|
1007
|
-
? `<xml xmlns="http://www.w3.org/1999/xhtml"><block type="${attributes.blockId}"></block></xml>`
|
|
1008
|
-
: undefined
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
else {
|
|
1012
|
-
addSymbolCardItem(ul, symbol);
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
1015
|
-
else
|
|
1016
|
-
switch (kind) {
|
|
1017
|
-
case ts.SyntaxKind.ExpressionStatement: {
|
|
1018
|
-
const es = stmt;
|
|
1019
|
-
switch (es.expression.kind) {
|
|
1020
|
-
case ts.SyntaxKind.TrueKeyword:
|
|
1021
|
-
case ts.SyntaxKind.FalseKeyword:
|
|
1022
|
-
addCardItem(ul, {
|
|
1023
|
-
name: "Boolean",
|
|
1024
|
-
url: "blocks/logic/boolean",
|
|
1025
|
-
description: lf("True or false values"),
|
|
1026
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="logic_boolean"><field name="BOOL">TRUE</field></block></xml>'
|
|
1027
|
-
});
|
|
1028
|
-
break;
|
|
1029
|
-
default:
|
|
1030
|
-
pxt.debug(`card expr kind: ${es.expression.kind}`);
|
|
1031
|
-
break;
|
|
1032
|
-
}
|
|
1033
|
-
break;
|
|
1034
|
-
}
|
|
1035
|
-
case ts.SyntaxKind.IfStatement:
|
|
1036
|
-
addCardItem(ul, {
|
|
1037
|
-
name: ns ? "Logic" : "if",
|
|
1038
|
-
url: "blocks/logic" + (ns ? "" : "/if"),
|
|
1039
|
-
description: ns ? lf("Logic operators and constants") : lf("Conditional statement"),
|
|
1040
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_if"></block></xml>'
|
|
1041
|
-
});
|
|
1042
|
-
break;
|
|
1043
|
-
case ts.SyntaxKind.WhileStatement:
|
|
1044
|
-
addCardItem(ul, {
|
|
1045
|
-
name: ns ? "Loops" : "while",
|
|
1046
|
-
url: "blocks/loops" + (ns ? "" : "/while"),
|
|
1047
|
-
description: ns ? lf("Loops and repetition") : lf("Repeat code while a condition is true."),
|
|
1048
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="device_while"></block></xml>'
|
|
1049
|
-
});
|
|
1050
|
-
break;
|
|
1051
|
-
case ts.SyntaxKind.ForOfStatement:
|
|
1052
|
-
addCardItem(ul, {
|
|
1053
|
-
name: ns ? "Loops" : "for of",
|
|
1054
|
-
url: "blocks/loops" + (ns ? "" : "/for-of"),
|
|
1055
|
-
description: ns ? lf("Loops and repetition") : lf("Repeat code for each item in a list."),
|
|
1056
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_for_of"></block></xml>'
|
|
1057
|
-
});
|
|
1058
|
-
break;
|
|
1059
|
-
case ts.SyntaxKind.BreakStatement:
|
|
1060
|
-
addCardItem(ul, {
|
|
1061
|
-
name: ns ? "Loops" : "break",
|
|
1062
|
-
url: "blocks/loops" + (ns ? "" : "/break"),
|
|
1063
|
-
description: ns ? lf("Loops and repetition") : lf("Break out of the current loop."),
|
|
1064
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="break_keyword"></block></xml>'
|
|
1065
|
-
});
|
|
1066
|
-
break;
|
|
1067
|
-
case ts.SyntaxKind.ContinueStatement:
|
|
1068
|
-
addCardItem(ul, {
|
|
1069
|
-
name: ns ? "Loops" : "continue",
|
|
1070
|
-
url: "blocks/loops" + (ns ? "" : "/continue"),
|
|
1071
|
-
description: ns ? lf("Loops and repetition") : lf("Skip iteration and continue the current loop."),
|
|
1072
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="continue_keyboard"></block></xml>'
|
|
1073
|
-
});
|
|
1074
|
-
break;
|
|
1075
|
-
case ts.SyntaxKind.ForStatement: {
|
|
1076
|
-
let fs = stmt;
|
|
1077
|
-
// look for the 'repeat' loop style signature in the condition expression, explicitly: (let i = 0; i < X; i++)
|
|
1078
|
-
// for loops will have the '<=' conditional.
|
|
1079
|
-
let forloop = true;
|
|
1080
|
-
if (fs.condition.getChildCount() == 3) {
|
|
1081
|
-
forloop = !(fs.condition.getChildAt(0).getText() == "0" ||
|
|
1082
|
-
fs.condition.getChildAt(1).kind == ts.SyntaxKind.LessThanToken);
|
|
1083
|
-
}
|
|
1084
|
-
if (forloop) {
|
|
1085
|
-
addCardItem(ul, {
|
|
1086
|
-
name: ns ? "Loops" : "for",
|
|
1087
|
-
url: "blocks/loops" + (ns ? "" : "/for"),
|
|
1088
|
-
description: ns ? lf("Loops and repetition") : lf("Repeat code for a given number of times using an index."),
|
|
1089
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_simple_for"></block></xml>'
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
else {
|
|
1093
|
-
addCardItem(ul, {
|
|
1094
|
-
name: ns ? "Loops" : "repeat",
|
|
1095
|
-
url: "blocks/loops" + (ns ? "" : "/repeat"),
|
|
1096
|
-
description: ns ? lf("Loops and repetition") : lf("Repeat code for a given number of times."),
|
|
1097
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="controls_repeat_ext"></block></xml>'
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
break;
|
|
1101
|
-
}
|
|
1102
|
-
case ts.SyntaxKind.VariableStatement:
|
|
1103
|
-
addCardItem(ul, {
|
|
1104
|
-
name: ns ? "Variables" : "variable declaration",
|
|
1105
|
-
url: "blocks/variables" + (ns ? "" : "/assign"),
|
|
1106
|
-
description: ns ? lf("Variables") : lf("Assign a value to a named variable."),
|
|
1107
|
-
blocksXml: '<xml xmlns="http://www.w3.org/1999/xhtml"><block type="variables_set"></block></xml>'
|
|
1108
|
-
});
|
|
1109
|
-
break;
|
|
1110
|
-
default:
|
|
1111
|
-
pxt.debug(`card kind: ${kind}`);
|
|
1112
|
-
}
|
|
1113
|
-
});
|
|
1114
|
-
if (replaceParent)
|
|
1115
|
-
c = c.parent();
|
|
1116
|
-
c.replaceWith(ul);
|
|
1117
|
-
}, { package: options.package, aspectRatio: options.blocksAspectRatio, assets: options.assetJSON });
|
|
1118
|
-
}
|
|
1119
|
-
function fillCodeCardAsync(c, cards, options) {
|
|
1120
|
-
if (!cards || cards.length == 0)
|
|
1121
|
-
return Promise.resolve();
|
|
1122
|
-
if (cards.length == 0) {
|
|
1123
|
-
let cc = pxt.docs.codeCard.render(cards[0], options);
|
|
1124
|
-
c.replaceWith(cc);
|
|
1125
|
-
}
|
|
1126
|
-
else {
|
|
1127
|
-
let cd = document.createElement("div");
|
|
1128
|
-
cd.className = "ui cards";
|
|
1129
|
-
cd.setAttribute("role", "listbox");
|
|
1130
|
-
cards.forEach(card => {
|
|
1131
|
-
// patch card url with version if necessary, we don't do this in the editor because that goes through the backend and passes the targetVersion then
|
|
1132
|
-
const mC = /^\/(v\d+)/.exec(card.url);
|
|
1133
|
-
const mP = /^\/(v\d+)/.exec(window.location.pathname);
|
|
1134
|
-
const inEditor = /#doc/i.test(window.location.href);
|
|
1135
|
-
if (card.url && !mC && mP && !inEditor)
|
|
1136
|
-
card.url = `/${mP[1]}${card.url}`;
|
|
1137
|
-
const cardEl = pxt.docs.codeCard.render(card, options);
|
|
1138
|
-
cd.appendChild(cardEl);
|
|
1139
|
-
// automitcally display package icon for approved packages
|
|
1140
|
-
if (card.cardType == "package") {
|
|
1141
|
-
const repoId = pxt.github.parseRepoId((card.url || "").replace(/^\/pkg\//, ''));
|
|
1142
|
-
if (repoId) {
|
|
1143
|
-
pxt.packagesConfigAsync()
|
|
1144
|
-
.then(pkgConfig => {
|
|
1145
|
-
const status = pxt.github.repoStatus(repoId, pkgConfig);
|
|
1146
|
-
switch (status) {
|
|
1147
|
-
case pxt.github.GitRepoStatus.Banned:
|
|
1148
|
-
cardEl.remove();
|
|
1149
|
-
break;
|
|
1150
|
-
case pxt.github.GitRepoStatus.Approved:
|
|
1151
|
-
// update card info
|
|
1152
|
-
card.imageUrl = pxt.github.mkRepoIconUrl(repoId);
|
|
1153
|
-
// inject
|
|
1154
|
-
cd.insertBefore(pxt.docs.codeCard.render(card, options), cardEl);
|
|
1155
|
-
cardEl.remove();
|
|
1156
|
-
break;
|
|
1157
|
-
}
|
|
1158
|
-
})
|
|
1159
|
-
.catch(e => {
|
|
1160
|
-
// swallow
|
|
1161
|
-
pxt.reportException(e);
|
|
1162
|
-
pxt.debug(`failed to load repo ${card.url}`);
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
});
|
|
1167
|
-
c.replaceWith(cd);
|
|
1168
|
-
}
|
|
1169
|
-
return Promise.resolve();
|
|
1170
|
-
}
|
|
1171
|
-
function renderNextCodeCardAsync(cls, options) {
|
|
1172
|
-
if (!cls)
|
|
1173
|
-
return Promise.resolve();
|
|
1174
|
-
let $el = $("." + cls).first();
|
|
1175
|
-
if (!$el[0])
|
|
1176
|
-
return Promise.resolve();
|
|
1177
|
-
$el.removeClass(cls);
|
|
1178
|
-
// try parsing the card as json
|
|
1179
|
-
const cards = pxt.gallery.parseCodeCardsHtml($el[0]);
|
|
1180
|
-
if (!cards) {
|
|
1181
|
-
$el.append($('<div/>').addClass("ui segment warning").text("invalid codecard format"));
|
|
1182
|
-
}
|
|
1183
|
-
if (options.snippetReplaceParent)
|
|
1184
|
-
$el = $el.parent();
|
|
1185
|
-
return fillCodeCardAsync($el, cards, { hideHeader: true })
|
|
1186
|
-
.then(() => pxt.U.delay(1, renderNextCodeCardAsync(cls, options)));
|
|
1187
|
-
}
|
|
1188
|
-
function getRunUrl(options) {
|
|
1189
|
-
return options.pxtUrl ? options.pxtUrl + '/--run' : pxt.webConfig && pxt.webConfig.runUrl ? pxt.webConfig.runUrl : '/--run';
|
|
1190
|
-
}
|
|
1191
|
-
function getEditUrl(options) {
|
|
1192
|
-
const url = options.pxtUrl || pxt.appTarget.appTheme.homeUrl;
|
|
1193
|
-
return (url || "").replace(/\/$/, '');
|
|
1194
|
-
}
|
|
1195
|
-
function mergeConfig(options) {
|
|
1196
|
-
// additional config options
|
|
1197
|
-
if (!options.packageClass)
|
|
1198
|
-
return;
|
|
1199
|
-
$('.' + options.packageClass).each((i, c) => {
|
|
1200
|
-
let $c = $(c);
|
|
1201
|
-
let name = $c.text().split('\n').map(s => s.replace(/\s*/g, '')).filter(s => !!s).join(',');
|
|
1202
|
-
options.package = options.package ? `${options.package},${name}` : name;
|
|
1203
|
-
if (options.snippetReplaceParent)
|
|
1204
|
-
$c = $c.parent();
|
|
1205
|
-
$c.remove();
|
|
1206
|
-
});
|
|
1207
|
-
$('.lang-config').each((i, c) => {
|
|
1208
|
-
let $c = $(c);
|
|
1209
|
-
if (options.snippetReplaceParent)
|
|
1210
|
-
$c = $c.parent();
|
|
1211
|
-
$c.remove();
|
|
1212
|
-
});
|
|
1213
|
-
}
|
|
1214
|
-
function readAssetJson(options) {
|
|
1215
|
-
let assetJson;
|
|
1216
|
-
let tilemapJres;
|
|
1217
|
-
if (options.jresClass) {
|
|
1218
|
-
$(`.${options.jresClass}`).each((i, c) => {
|
|
1219
|
-
const $c = $(c);
|
|
1220
|
-
tilemapJres = $c.text();
|
|
1221
|
-
c.parentElement.remove();
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1224
|
-
if (options.assetJSONClass) {
|
|
1225
|
-
$(`.${options.assetJSONClass}`).each((i, c) => {
|
|
1226
|
-
const $c = $(c);
|
|
1227
|
-
assetJson = $c.text();
|
|
1228
|
-
c.parentElement.remove();
|
|
1229
|
-
});
|
|
1230
|
-
}
|
|
1231
|
-
options.assetJSON = mergeAssetJson(assetJson, tilemapJres);
|
|
1232
|
-
function mergeAssetJson(assetJSON, tilemapJres) {
|
|
1233
|
-
if (!assetJSON && !tilemapJres)
|
|
1234
|
-
return undefined;
|
|
1235
|
-
const mergedJson = pxt.tutorial.parseAssetJson(assetJSON) || {};
|
|
1236
|
-
if (tilemapJres) {
|
|
1237
|
-
const parsedTmapJres = JSON.parse(tilemapJres);
|
|
1238
|
-
mergedJson[pxt.TILEMAP_JRES] = JSON.stringify(parsedTmapJres);
|
|
1239
|
-
mergedJson[pxt.TILEMAP_CODE] = pxt.emitTilemapsFromJRes(parsedTmapJres);
|
|
1240
|
-
}
|
|
1241
|
-
return mergedJson;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
function renderDirectPython(options) {
|
|
1245
|
-
// Highlight python snippets written with the ```python
|
|
1246
|
-
// language tag (as opposed to the ```spy tag, see renderStaticPythonAsync for that)
|
|
1247
|
-
const woptions = {
|
|
1248
|
-
showEdit: !!options.showEdit,
|
|
1249
|
-
run: !!options.simulator
|
|
1250
|
-
};
|
|
1251
|
-
function render(e, ignored) {
|
|
1252
|
-
if (typeof hljs !== "undefined") {
|
|
1253
|
-
$(e).text($(e).text().replace(/^\s*\r?\n/, ''));
|
|
1254
|
-
hljs.highlightBlock(e);
|
|
1255
|
-
highlightLine($(e));
|
|
1256
|
-
}
|
|
1257
|
-
const opts = pxt.U.clone(woptions);
|
|
1258
|
-
if (ignored) {
|
|
1259
|
-
opts.run = false;
|
|
1260
|
-
opts.showEdit = false;
|
|
1261
|
-
}
|
|
1262
|
-
fillWithWidget(options, $(e).parent(), $(e), /* py */ undefined, /* JQuery */ undefined, /* decompileResult */ undefined, opts);
|
|
1263
|
-
}
|
|
1264
|
-
$('code.lang-python').each((i, e) => {
|
|
1265
|
-
render(e, false);
|
|
1266
|
-
$(e).removeClass('lang-python');
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
|
-
function renderTypeScript(options) {
|
|
1270
|
-
const woptions = {
|
|
1271
|
-
showEdit: !!options.showEdit,
|
|
1272
|
-
run: !!options.simulator
|
|
1273
|
-
};
|
|
1274
|
-
function render(e, ignored) {
|
|
1275
|
-
if (typeof hljs !== "undefined") {
|
|
1276
|
-
$(e).text($(e).text().replace(/^\s*\r?\n/, ''));
|
|
1277
|
-
hljs.highlightBlock(e);
|
|
1278
|
-
highlightLine($(e));
|
|
1279
|
-
}
|
|
1280
|
-
const opts = pxt.U.clone(woptions);
|
|
1281
|
-
if (ignored) {
|
|
1282
|
-
opts.run = false;
|
|
1283
|
-
opts.showEdit = false;
|
|
1284
|
-
}
|
|
1285
|
-
fillWithWidget(options, $(e).parent(), $(e), /* py */ undefined, /* JQuery */ undefined, /* decompileResult */ undefined, opts);
|
|
1286
|
-
}
|
|
1287
|
-
$('code.lang-typescript').each((i, e) => {
|
|
1288
|
-
render(e, false);
|
|
1289
|
-
$(e).removeClass('lang-typescript');
|
|
1290
|
-
});
|
|
1291
|
-
$('code.lang-typescript-ignore').each((i, e) => {
|
|
1292
|
-
$(e).removeClass('lang-typescript-ignore');
|
|
1293
|
-
$(e).addClass('lang-typescript');
|
|
1294
|
-
render(e, true);
|
|
1295
|
-
$(e).removeClass('lang-typescript');
|
|
1296
|
-
});
|
|
1297
|
-
$('code.lang-typescript-invalid').each((i, e) => {
|
|
1298
|
-
$(e).removeClass('lang-typescript-invalid');
|
|
1299
|
-
$(e).addClass('lang-typescript');
|
|
1300
|
-
render(e, true);
|
|
1301
|
-
$(e).removeClass('lang-typescript');
|
|
1302
|
-
$(e).parent('div').addClass('invalid');
|
|
1303
|
-
$(e).parent('div').prepend($("<i>", { "class": "icon ban" }));
|
|
1304
|
-
$(e).addClass('invalid');
|
|
1305
|
-
});
|
|
1306
|
-
$('code.lang-typescript-valid').each((i, e) => {
|
|
1307
|
-
$(e).removeClass('lang-typescript-valid');
|
|
1308
|
-
$(e).addClass('lang-typescript');
|
|
1309
|
-
render(e, true);
|
|
1310
|
-
$(e).removeClass('lang-typescript');
|
|
1311
|
-
$(e).parent('div').addClass('valid');
|
|
1312
|
-
$(e).parent('div').prepend($("<i>", { "class": "icon check" }));
|
|
1313
|
-
$(e).addClass('valid');
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1316
|
-
function renderGhost(options) {
|
|
1317
|
-
let c = $('code.lang-ghost');
|
|
1318
|
-
if (options.snippetReplaceParent)
|
|
1319
|
-
c = c.parent();
|
|
1320
|
-
c.remove();
|
|
1321
|
-
}
|
|
1322
|
-
function renderBlockConfig(options) {
|
|
1323
|
-
function render(scope) {
|
|
1324
|
-
$(`code.lang-blockconfig.${scope}`).each((i, c) => {
|
|
1325
|
-
let $c = $(c);
|
|
1326
|
-
if (options.snippetReplaceParent)
|
|
1327
|
-
$c = $c.parent();
|
|
1328
|
-
$c.remove();
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
render("local");
|
|
1332
|
-
render("global");
|
|
1333
|
-
}
|
|
1334
|
-
function renderSims(options) {
|
|
1335
|
-
if (!options.simulatorClass)
|
|
1336
|
-
return;
|
|
1337
|
-
// simulators
|
|
1338
|
-
$('.' + options.simulatorClass).each((i, c) => {
|
|
1339
|
-
let $c = $(c);
|
|
1340
|
-
let padding = '81.97%';
|
|
1341
|
-
if (pxt.appTarget.simulator)
|
|
1342
|
-
padding = (100 / pxt.appTarget.simulator.aspectRatio) + '%';
|
|
1343
|
-
let $sim = $(`<div class="ui card"><div class="ui content">
|
|
1344
|
-
<div style="position:relative;height:0;padding-bottom:${padding};overflow:hidden;">
|
|
1345
|
-
<iframe style="position:absolute;top:0;left:0;width:100%;height:100%;" allowfullscreen="allowfullscreen" frameborder="0" sandbox="allow-popups allow-forms allow-scripts allow-same-origin"></iframe>
|
|
1346
|
-
</div>
|
|
1347
|
-
</div></div>`);
|
|
1348
|
-
const deps = options.package ? "&deps=" + encodeURIComponent(options.package) : "";
|
|
1349
|
-
const url = getRunUrl(options) + "#nofooter=1" + deps;
|
|
1350
|
-
const data = encodeURIComponent($c.text().trim());
|
|
1351
|
-
const $simIFrame = $sim.find("iframe");
|
|
1352
|
-
$simIFrame.attr("src", url);
|
|
1353
|
-
$simIFrame.attr("data-code", data);
|
|
1354
|
-
if (options.assetJSON) {
|
|
1355
|
-
$simIFrame.attr("data-assets", JSON.stringify(options.assetJSON));
|
|
1356
|
-
}
|
|
1357
|
-
if (options.snippetReplaceParent)
|
|
1358
|
-
$c = $c.parent();
|
|
1359
|
-
$c.replaceWith($sim);
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
function renderAsync(options) {
|
|
1363
|
-
pxt.analytics.enable(pxt.Util.userLanguage());
|
|
1364
|
-
if (!options)
|
|
1365
|
-
options = defaultClientRenderOptions();
|
|
1366
|
-
if (options.pxtUrl)
|
|
1367
|
-
options.pxtUrl = options.pxtUrl.replace(/\/$/, '');
|
|
1368
|
-
if (options.showEdit)
|
|
1369
|
-
options.showEdit = !pxt.BrowserUtils.isIFrame();
|
|
1370
|
-
mergeConfig(options);
|
|
1371
|
-
readAssetJson(options);
|
|
1372
|
-
renderQueue = [];
|
|
1373
|
-
renderGhost(options);
|
|
1374
|
-
renderBlockConfig(options);
|
|
1375
|
-
renderSims(options);
|
|
1376
|
-
renderTypeScript(options);
|
|
1377
|
-
renderDirectPython(options);
|
|
1378
|
-
return Promise.resolve()
|
|
1379
|
-
.then(() => renderNextCodeCardAsync(options.codeCardClass, options))
|
|
1380
|
-
.then(() => renderNamespaces(options))
|
|
1381
|
-
.then(() => renderInlineBlocksAsync(options))
|
|
1382
|
-
.then(() => renderLinksAsync(options, options.linksClass, options.snippetReplaceParent, false))
|
|
1383
|
-
.then(() => renderLinksAsync(options, options.namespacesClass, options.snippetReplaceParent, true))
|
|
1384
|
-
.then(() => renderApisAsync(options, options.snippetReplaceParent))
|
|
1385
|
-
.then(() => renderSignaturesAsync(options))
|
|
1386
|
-
.then(() => renderSnippetsAsync(options))
|
|
1387
|
-
.then(() => renderBlocksAsync(options))
|
|
1388
|
-
.then(() => renderBlocksXmlAsync(options))
|
|
1389
|
-
.then(() => renderDiffBlocksXmlAsync(options))
|
|
1390
|
-
.then(() => renderDiffBlocksAsync(options))
|
|
1391
|
-
.then(() => renderDiffAsync(options))
|
|
1392
|
-
.then(() => renderStaticPythonAsync(options))
|
|
1393
|
-
.then(() => renderProjectAsync(options))
|
|
1394
|
-
.then(() => consumeRenderQueueAsync());
|
|
1395
|
-
}
|
|
1396
|
-
runner.renderAsync = renderAsync;
|
|
1397
|
-
})(runner = pxt.runner || (pxt.runner = {}));
|
|
1398
|
-
})(pxt || (pxt = {}));
|
|
1399
|
-
/* TODO(tslint): get rid of jquery html() calls */
|
|
1400
|
-
/// <reference path="../built/pxtlib.d.ts" />
|
|
1401
|
-
/// <reference path="../built/pxteditor.d.ts" />
|
|
1402
|
-
/// <reference path="../built/pxtcompiler.d.ts" />
|
|
1403
|
-
/// <reference path="../built/pxtblocks.d.ts" />
|
|
1404
|
-
/// <reference path="../built/pxtsim.d.ts" />
|
|
1405
|
-
var pxt;
|
|
1406
|
-
(function (pxt) {
|
|
1407
|
-
var runner;
|
|
1408
|
-
(function (runner) {
|
|
1409
|
-
class EditorPackage {
|
|
1410
|
-
constructor(ksPkg, topPkg) {
|
|
1411
|
-
this.ksPkg = ksPkg;
|
|
1412
|
-
this.topPkg = topPkg;
|
|
1413
|
-
this.files = {};
|
|
1414
|
-
}
|
|
1415
|
-
getKsPkg() {
|
|
1416
|
-
return this.ksPkg;
|
|
1417
|
-
}
|
|
1418
|
-
getPkgId() {
|
|
1419
|
-
return this.ksPkg ? this.ksPkg.id : this.id;
|
|
1420
|
-
}
|
|
1421
|
-
isTopLevel() {
|
|
1422
|
-
return this.ksPkg && this.ksPkg.level == 0;
|
|
1423
|
-
}
|
|
1424
|
-
setFiles(files) {
|
|
1425
|
-
this.files = files;
|
|
1426
|
-
}
|
|
1427
|
-
getAllFiles() {
|
|
1428
|
-
return pxt.Util.mapMap(this.files, (k, f) => f);
|
|
1429
|
-
}
|
|
1430
|
-
}
|
|
1431
|
-
class Host {
|
|
1432
|
-
constructor() {
|
|
1433
|
-
this.githubPackageCache = {};
|
|
1434
|
-
}
|
|
1435
|
-
readFile(module, filename) {
|
|
1436
|
-
let epkg = getEditorPkg(module);
|
|
1437
|
-
return pxt.U.lookup(epkg.files, filename);
|
|
1438
|
-
}
|
|
1439
|
-
writeFile(module, filename, contents) {
|
|
1440
|
-
const epkg = getEditorPkg(module);
|
|
1441
|
-
epkg.files[filename] = contents;
|
|
1442
|
-
}
|
|
1443
|
-
getHexInfoAsync(extInfo) {
|
|
1444
|
-
return pxt.hexloader.getHexInfoAsync(this, extInfo);
|
|
1445
|
-
}
|
|
1446
|
-
cacheStoreAsync(id, val) {
|
|
1447
|
-
return Promise.resolve();
|
|
1448
|
-
}
|
|
1449
|
-
cacheGetAsync(id) {
|
|
1450
|
-
return Promise.resolve(null);
|
|
1451
|
-
}
|
|
1452
|
-
patchDependencies(cfg, name, repoId) {
|
|
1453
|
-
if (!repoId)
|
|
1454
|
-
return false;
|
|
1455
|
-
// check that the same package hasn't been added yet
|
|
1456
|
-
const repo = pxt.github.parseRepoId(repoId);
|
|
1457
|
-
if (!repo)
|
|
1458
|
-
return false;
|
|
1459
|
-
for (const k of Object.keys(cfg.dependencies)) {
|
|
1460
|
-
const v = cfg.dependencies[k];
|
|
1461
|
-
const kv = pxt.github.parseRepoId(v);
|
|
1462
|
-
if (kv && repo.fullName == kv.fullName) {
|
|
1463
|
-
if (pxt.semver.strcmp(repo.tag, kv.tag) < 0) {
|
|
1464
|
-
// we have a later tag, use this one
|
|
1465
|
-
cfg.dependencies[k] = repoId;
|
|
1466
|
-
}
|
|
1467
|
-
return true;
|
|
1468
|
-
}
|
|
1469
|
-
}
|
|
1470
|
-
return false;
|
|
1471
|
-
}
|
|
1472
|
-
downloadPackageAsync(pkg, dependencies) {
|
|
1473
|
-
let proto = pkg.verProtocol();
|
|
1474
|
-
let cached = undefined;
|
|
1475
|
-
// cache resolve github packages
|
|
1476
|
-
if (proto == "github")
|
|
1477
|
-
cached = this.githubPackageCache[pkg._verspec];
|
|
1478
|
-
let epkg = getEditorPkg(pkg);
|
|
1479
|
-
return (cached ? Promise.resolve(cached) : pkg.commonDownloadAsync())
|
|
1480
|
-
.then(resp => {
|
|
1481
|
-
if (resp) {
|
|
1482
|
-
if (proto == "github" && !cached)
|
|
1483
|
-
this.githubPackageCache[pkg._verspec] = pxt.Util.clone(resp);
|
|
1484
|
-
epkg.setFiles(resp);
|
|
1485
|
-
return Promise.resolve();
|
|
1486
|
-
}
|
|
1487
|
-
if (proto == "empty") {
|
|
1488
|
-
if (Object.keys(epkg.files).length == 0) {
|
|
1489
|
-
epkg.setFiles(emptyPrjFiles());
|
|
1490
|
-
}
|
|
1491
|
-
if (dependencies && dependencies.length) {
|
|
1492
|
-
const files = getEditorPkg(pkg).files;
|
|
1493
|
-
const cfg = JSON.parse(files[pxt.CONFIG_NAME]);
|
|
1494
|
-
dependencies.forEach((d) => {
|
|
1495
|
-
addPackageToConfig(cfg, d);
|
|
1496
|
-
});
|
|
1497
|
-
files[pxt.CONFIG_NAME] = pxt.Package.stringifyConfig(cfg);
|
|
1498
|
-
}
|
|
1499
|
-
return Promise.resolve();
|
|
1500
|
-
}
|
|
1501
|
-
else if (proto == "docs") {
|
|
1502
|
-
let files = emptyPrjFiles();
|
|
1503
|
-
let cfg = JSON.parse(files[pxt.CONFIG_NAME]);
|
|
1504
|
-
// load all dependencies
|
|
1505
|
-
pkg.verArgument().split(',').forEach(d => {
|
|
1506
|
-
if (!addPackageToConfig(cfg, d)) {
|
|
1507
|
-
return;
|
|
1508
|
-
}
|
|
1509
|
-
});
|
|
1510
|
-
if (!cfg.yotta)
|
|
1511
|
-
cfg.yotta = {};
|
|
1512
|
-
cfg.yotta.ignoreConflicts = true;
|
|
1513
|
-
files[pxt.CONFIG_NAME] = pxt.Package.stringifyConfig(cfg);
|
|
1514
|
-
epkg.setFiles(files);
|
|
1515
|
-
return Promise.resolve();
|
|
1516
|
-
}
|
|
1517
|
-
else if (proto == "invalid") {
|
|
1518
|
-
pxt.log(`skipping invalid pkg ${pkg.id}`);
|
|
1519
|
-
return Promise.resolve();
|
|
1520
|
-
}
|
|
1521
|
-
else {
|
|
1522
|
-
return Promise.reject(`Cannot download ${pkg.version()}; unknown protocol`);
|
|
1523
|
-
}
|
|
1524
|
-
});
|
|
1525
|
-
}
|
|
1526
|
-
}
|
|
1527
|
-
let tilemapProject;
|
|
1528
|
-
if (!pxt.react.getTilemapProject) {
|
|
1529
|
-
pxt.react.getTilemapProject = () => {
|
|
1530
|
-
if (!tilemapProject) {
|
|
1531
|
-
tilemapProject = new pxt.TilemapProject();
|
|
1532
|
-
tilemapProject.loadPackage(runner.mainPkg);
|
|
1533
|
-
}
|
|
1534
|
-
return tilemapProject;
|
|
1535
|
-
};
|
|
1536
|
-
}
|
|
1537
|
-
function addPackageToConfig(cfg, dep) {
|
|
1538
|
-
let m = /^([a-zA-Z0-9_-]+)(=(.+))?$/.exec(dep);
|
|
1539
|
-
if (m) {
|
|
1540
|
-
// TODO this line seems bad, patchdependencies is on host not this?
|
|
1541
|
-
// looks like this should be a method in host
|
|
1542
|
-
if (m[3] && this && this.patchDependencies(cfg, m[1], m[3]))
|
|
1543
|
-
return false;
|
|
1544
|
-
cfg.dependencies[m[1]] = m[3] || "*";
|
|
1545
|
-
}
|
|
1546
|
-
else
|
|
1547
|
-
console.warn(`unknown package syntax ${dep}`);
|
|
1548
|
-
return true;
|
|
1549
|
-
}
|
|
1550
|
-
function getEditorPkg(p) {
|
|
1551
|
-
let r = p._editorPkg;
|
|
1552
|
-
if (r)
|
|
1553
|
-
return r;
|
|
1554
|
-
let top = null;
|
|
1555
|
-
if (p != runner.mainPkg)
|
|
1556
|
-
top = getEditorPkg(runner.mainPkg);
|
|
1557
|
-
let newOne = new EditorPackage(p, top);
|
|
1558
|
-
if (p == runner.mainPkg)
|
|
1559
|
-
newOne.topPkg = newOne;
|
|
1560
|
-
p._editorPkg = newOne;
|
|
1561
|
-
return newOne;
|
|
1562
|
-
}
|
|
1563
|
-
function emptyPrjFiles() {
|
|
1564
|
-
let p = pxt.appTarget.tsprj;
|
|
1565
|
-
let files = pxt.U.clone(p.files);
|
|
1566
|
-
files[pxt.CONFIG_NAME] = pxt.Package.stringifyConfig(p.config);
|
|
1567
|
-
files[pxt.MAIN_BLOCKS] = "";
|
|
1568
|
-
return files;
|
|
1569
|
-
}
|
|
1570
|
-
function patchSemantic() {
|
|
1571
|
-
if ($ && $.fn && $.fn.embed && $.fn.embed.settings && $.fn.embed.settings.sources && $.fn.embed.settings.sources.youtube) {
|
|
1572
|
-
$.fn.embed.settings.sources.youtube.url = '//www.youtube.com/embed/{id}?rel=0';
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
function initInnerAsync() {
|
|
1576
|
-
pxt.setAppTarget(window.pxtTargetBundle);
|
|
1577
|
-
pxt.analytics.enable(pxt.Util.userLanguage());
|
|
1578
|
-
pxt.Util.assert(!!pxt.appTarget);
|
|
1579
|
-
const href = window.location.href;
|
|
1580
|
-
let force = false;
|
|
1581
|
-
let lang = undefined;
|
|
1582
|
-
if (/[&?]translate=1/.test(href) && !pxt.BrowserUtils.isIE()) {
|
|
1583
|
-
lang = ts.pxtc.Util.TRANSLATION_LOCALE;
|
|
1584
|
-
force = true;
|
|
1585
|
-
pxt.Util.enableLiveLocalizationUpdates();
|
|
1586
|
-
}
|
|
1587
|
-
else {
|
|
1588
|
-
const cookieValue = /PXT_LANG=(.*?)(?:;|$)/.exec(document.cookie);
|
|
1589
|
-
const mlang = /(live)?(force)?lang=([a-z]{2,}(-[A-Z]+)?)/i.exec(href);
|
|
1590
|
-
lang = mlang ? mlang[3] : (cookieValue && cookieValue[1] || pxt.appTarget.appTheme.defaultLocale || navigator.userLanguage || navigator.language);
|
|
1591
|
-
const defLocale = pxt.appTarget.appTheme.defaultLocale;
|
|
1592
|
-
const langLowerCase = lang === null || lang === void 0 ? void 0 : lang.toLocaleLowerCase();
|
|
1593
|
-
const localDevServe = pxt.BrowserUtils.isLocalHostDev()
|
|
1594
|
-
&& (!langLowerCase || (defLocale
|
|
1595
|
-
? defLocale.toLocaleLowerCase() === langLowerCase
|
|
1596
|
-
: "en" === langLowerCase || "en-us" === langLowerCase));
|
|
1597
|
-
const serveLocal = pxt.BrowserUtils.isPxtElectron() || localDevServe;
|
|
1598
|
-
const liveTranslationsDisabled = serveLocal || pxt.appTarget.appTheme.disableLiveTranslations;
|
|
1599
|
-
if (!liveTranslationsDisabled || !!(mlang === null || mlang === void 0 ? void 0 : mlang[1])) {
|
|
1600
|
-
pxt.Util.enableLiveLocalizationUpdates();
|
|
1601
|
-
}
|
|
1602
|
-
force = !!mlang && !!mlang[2];
|
|
1603
|
-
}
|
|
1604
|
-
const versions = pxt.appTarget.versions;
|
|
1605
|
-
patchSemantic();
|
|
1606
|
-
const cfg = pxt.webConfig;
|
|
1607
|
-
return pxt.Util.updateLocalizationAsync({
|
|
1608
|
-
targetId: pxt.appTarget.id,
|
|
1609
|
-
baseUrl: cfg.commitCdnUrl,
|
|
1610
|
-
code: lang,
|
|
1611
|
-
pxtBranch: versions ? versions.pxtCrowdinBranch : "",
|
|
1612
|
-
targetBranch: versions ? versions.targetCrowdinBranch : "",
|
|
1613
|
-
force: force,
|
|
1614
|
-
})
|
|
1615
|
-
.then(() => initHost());
|
|
1616
|
-
}
|
|
1617
|
-
function initHost() {
|
|
1618
|
-
runner.mainPkg = new pxt.MainPackage(new Host());
|
|
1619
|
-
}
|
|
1620
|
-
runner.initHost = initHost;
|
|
1621
|
-
function initFooter(footer, shareId) {
|
|
1622
|
-
if (!footer)
|
|
1623
|
-
return;
|
|
1624
|
-
let theme = pxt.appTarget.appTheme;
|
|
1625
|
-
let body = $('body');
|
|
1626
|
-
let $footer = $(footer);
|
|
1627
|
-
let footera = $('<a/>').attr('href', theme.homeUrl)
|
|
1628
|
-
.attr('target', '_blank');
|
|
1629
|
-
$footer.append(footera);
|
|
1630
|
-
if (theme.organizationLogo)
|
|
1631
|
-
footera.append($('<img/>').attr('src', pxt.Util.toDataUri(theme.organizationLogo)));
|
|
1632
|
-
else
|
|
1633
|
-
footera.append(lf("powered by {0}", theme.title));
|
|
1634
|
-
body.mouseenter(ev => $footer.fadeOut());
|
|
1635
|
-
body.mouseleave(ev => $footer.fadeIn());
|
|
1636
|
-
}
|
|
1637
|
-
runner.initFooter = initFooter;
|
|
1638
|
-
function showError(msg) {
|
|
1639
|
-
console.error(msg);
|
|
1640
|
-
}
|
|
1641
|
-
runner.showError = showError;
|
|
1642
|
-
let previousMainPackage = undefined;
|
|
1643
|
-
function loadPackageAsync(id, code, dependencies) {
|
|
1644
|
-
const verspec = id ? /\w+:\w+/.test(id) ? id : "pub:" + id : "empty:tsprj";
|
|
1645
|
-
let host;
|
|
1646
|
-
let downloadPackagePromise;
|
|
1647
|
-
let installPromise;
|
|
1648
|
-
if (previousMainPackage && previousMainPackage._verspec == verspec) {
|
|
1649
|
-
runner.mainPkg = previousMainPackage;
|
|
1650
|
-
host = runner.mainPkg.host();
|
|
1651
|
-
downloadPackagePromise = Promise.resolve();
|
|
1652
|
-
installPromise = Promise.resolve();
|
|
1653
|
-
}
|
|
1654
|
-
else {
|
|
1655
|
-
host = runner.mainPkg.host();
|
|
1656
|
-
runner.mainPkg = new pxt.MainPackage(host);
|
|
1657
|
-
runner.mainPkg._verspec = id ? /\w+:\w+/.test(id) ? id : "pub:" + id : "empty:tsprj";
|
|
1658
|
-
downloadPackagePromise = host.downloadPackageAsync(runner.mainPkg, dependencies);
|
|
1659
|
-
installPromise = runner.mainPkg.installAllAsync();
|
|
1660
|
-
// cache previous package
|
|
1661
|
-
previousMainPackage = runner.mainPkg;
|
|
1662
|
-
}
|
|
1663
|
-
return downloadPackagePromise
|
|
1664
|
-
.then(() => host.readFile(runner.mainPkg, pxt.CONFIG_NAME))
|
|
1665
|
-
.then(str => {
|
|
1666
|
-
if (!str)
|
|
1667
|
-
return Promise.resolve();
|
|
1668
|
-
return installPromise.then(() => {
|
|
1669
|
-
if (code) {
|
|
1670
|
-
//Set the custom code if provided for docs.
|
|
1671
|
-
let epkg = getEditorPkg(runner.mainPkg);
|
|
1672
|
-
epkg.files[pxt.MAIN_TS] = code;
|
|
1673
|
-
//set the custom doc name from the URL.
|
|
1674
|
-
let cfg = JSON.parse(epkg.files[pxt.CONFIG_NAME]);
|
|
1675
|
-
cfg.name = window.location.href.split('/').pop().split(/[?#]/)[0];
|
|
1676
|
-
;
|
|
1677
|
-
epkg.files[pxt.CONFIG_NAME] = pxt.Package.stringifyConfig(cfg);
|
|
1678
|
-
//Propgate the change to main package
|
|
1679
|
-
runner.mainPkg.config.name = cfg.name;
|
|
1680
|
-
if (runner.mainPkg.config.files.indexOf(pxt.MAIN_BLOCKS) == -1) {
|
|
1681
|
-
runner.mainPkg.config.files.push(pxt.MAIN_BLOCKS);
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
}).catch(e => {
|
|
1685
|
-
showError(lf("Cannot load extension: {0}", e.message));
|
|
1686
|
-
});
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
function getCompileOptionsAsync(hex) {
|
|
1690
|
-
let trg = runner.mainPkg.getTargetOptions();
|
|
1691
|
-
trg.isNative = !!hex;
|
|
1692
|
-
trg.hasHex = !!hex;
|
|
1693
|
-
return runner.mainPkg.getCompileOptionsAsync(trg);
|
|
1694
|
-
}
|
|
1695
|
-
function compileAsync(hex, updateOptions) {
|
|
1696
|
-
return getCompileOptionsAsync(hex)
|
|
1697
|
-
.then(opts => {
|
|
1698
|
-
if (updateOptions)
|
|
1699
|
-
updateOptions(opts);
|
|
1700
|
-
let resp = pxtc.compile(opts);
|
|
1701
|
-
if (resp.diagnostics && resp.diagnostics.length > 0) {
|
|
1702
|
-
resp.diagnostics.forEach(diag => {
|
|
1703
|
-
console.error(diag.messageText);
|
|
1704
|
-
});
|
|
1705
|
-
}
|
|
1706
|
-
return resp;
|
|
1707
|
-
});
|
|
1708
|
-
}
|
|
1709
|
-
function generateHexFileAsync(options) {
|
|
1710
|
-
return loadPackageAsync(options.id)
|
|
1711
|
-
.then(() => compileAsync(true, opts => {
|
|
1712
|
-
if (options.code)
|
|
1713
|
-
opts.fileSystem[pxt.MAIN_TS] = options.code;
|
|
1714
|
-
}))
|
|
1715
|
-
.then(resp => {
|
|
1716
|
-
if (resp.diagnostics && resp.diagnostics.length > 0) {
|
|
1717
|
-
console.error("Diagnostics", resp.diagnostics);
|
|
1718
|
-
}
|
|
1719
|
-
return resp.outfiles[pxtc.BINARY_HEX];
|
|
1720
|
-
});
|
|
1721
|
-
}
|
|
1722
|
-
runner.generateHexFileAsync = generateHexFileAsync;
|
|
1723
|
-
function generateVMFileAsync(options) {
|
|
1724
|
-
pxt.setHwVariant("vm");
|
|
1725
|
-
return loadPackageAsync(options.id)
|
|
1726
|
-
.then(() => compileAsync(true, opts => {
|
|
1727
|
-
if (options.code)
|
|
1728
|
-
opts.fileSystem[pxt.MAIN_TS] = options.code;
|
|
1729
|
-
}))
|
|
1730
|
-
.then(resp => {
|
|
1731
|
-
console.log(resp);
|
|
1732
|
-
return resp;
|
|
1733
|
-
});
|
|
1734
|
-
}
|
|
1735
|
-
runner.generateVMFileAsync = generateVMFileAsync;
|
|
1736
|
-
async function simulateAsync(container, simOptions) {
|
|
1737
|
-
var _a, _b;
|
|
1738
|
-
const builtSimJS = simOptions.builtJsInfo || await fetchSimJsInfo(simOptions) || await buildSimJsInfo(simOptions);
|
|
1739
|
-
const { js } = builtSimJS;
|
|
1740
|
-
if (!js) {
|
|
1741
|
-
console.error("Program failed to compile");
|
|
1742
|
-
return undefined;
|
|
1743
|
-
}
|
|
1744
|
-
const runOptions = initDriverAndOptions(container, simOptions, builtSimJS);
|
|
1745
|
-
simDriver.options.messageSimulators = (_b = (_a = pxt.appTarget) === null || _a === void 0 ? void 0 : _a.simulator) === null || _b === void 0 ? void 0 : _b.messageSimulators;
|
|
1746
|
-
simDriver.options.onSimulatorCommand = msg => {
|
|
1747
|
-
if (msg.command === "restart") {
|
|
1748
|
-
runOptions.storedState = getStoredState(simOptions.id);
|
|
1749
|
-
simDriver.run(js, runOptions);
|
|
1750
|
-
}
|
|
1751
|
-
if (msg.command == "setstate") {
|
|
1752
|
-
if (msg.stateKey) {
|
|
1753
|
-
setStoredState(simOptions.id, msg.stateKey, msg.stateValue);
|
|
1754
|
-
}
|
|
1755
|
-
}
|
|
1756
|
-
};
|
|
1757
|
-
if (builtSimJS.breakpoints && simOptions.debug) {
|
|
1758
|
-
simDriver.setBreakpoints(builtSimJS.breakpoints);
|
|
1759
|
-
}
|
|
1760
|
-
simDriver.run(js, runOptions);
|
|
1761
|
-
return builtSimJS;
|
|
1762
|
-
}
|
|
1763
|
-
runner.simulateAsync = simulateAsync;
|
|
1764
|
-
let simDriver;
|
|
1765
|
-
// iff matches and truthy, reuse existing simdriver
|
|
1766
|
-
let currDriverId;
|
|
1767
|
-
function initDriverAndOptions(container, simOptions, compileInfo) {
|
|
1768
|
-
var _a;
|
|
1769
|
-
if (!simDriver || !simOptions.embedId || currDriverId !== simOptions.embedId) {
|
|
1770
|
-
simDriver = new pxsim.SimulatorDriver(container);
|
|
1771
|
-
currDriverId = simOptions.embedId;
|
|
1772
|
-
}
|
|
1773
|
-
else {
|
|
1774
|
-
simDriver.container = container;
|
|
1775
|
-
}
|
|
1776
|
-
const { fnArgs, parts, usedBuiltinParts, } = compileInfo || {};
|
|
1777
|
-
let board = pxt.appTarget.simulator.boardDefinition;
|
|
1778
|
-
let storedState = getStoredState(simOptions.id);
|
|
1779
|
-
let runOptions = {
|
|
1780
|
-
debug: simOptions.debug,
|
|
1781
|
-
mute: simOptions.mute,
|
|
1782
|
-
boardDefinition: board,
|
|
1783
|
-
parts: parts,
|
|
1784
|
-
builtinParts: usedBuiltinParts,
|
|
1785
|
-
fnArgs: fnArgs,
|
|
1786
|
-
cdnUrl: pxt.webConfig.commitCdnUrl,
|
|
1787
|
-
localizedStrings: pxt.Util.getLocalizedStrings(),
|
|
1788
|
-
highContrast: simOptions.highContrast,
|
|
1789
|
-
storedState: storedState,
|
|
1790
|
-
light: simOptions.light,
|
|
1791
|
-
single: simOptions.single,
|
|
1792
|
-
hideSimButtons: simOptions.hideSimButtons,
|
|
1793
|
-
autofocus: simOptions.autofocus,
|
|
1794
|
-
queryParameters: simOptions.additionalQueryParameters,
|
|
1795
|
-
mpRole: simOptions.mpRole,
|
|
1796
|
-
theme: (_a = runner.mainPkg.config) === null || _a === void 0 ? void 0 : _a.theme,
|
|
1797
|
-
};
|
|
1798
|
-
if (pxt.appTarget.simulator && !simOptions.fullScreen)
|
|
1799
|
-
runOptions.aspectRatio = parts.length && pxt.appTarget.simulator.partsAspectRatio
|
|
1800
|
-
? pxt.appTarget.simulator.partsAspectRatio
|
|
1801
|
-
: pxt.appTarget.simulator.aspectRatio;
|
|
1802
|
-
simDriver.setRunOptions(runOptions);
|
|
1803
|
-
return runOptions;
|
|
1804
|
-
}
|
|
1805
|
-
function preloadSim(container, simOpts) {
|
|
1806
|
-
var _a, _b;
|
|
1807
|
-
initDriverAndOptions(container, simOpts);
|
|
1808
|
-
simDriver.preload(((_b = (_a = pxt.appTarget) === null || _a === void 0 ? void 0 : _a.simulator) === null || _b === void 0 ? void 0 : _b.aspectRatio) || 1, true /** no auto run **/);
|
|
1809
|
-
}
|
|
1810
|
-
runner.preloadSim = preloadSim;
|
|
1811
|
-
function currentDriver() {
|
|
1812
|
-
return simDriver;
|
|
1813
|
-
}
|
|
1814
|
-
runner.currentDriver = currentDriver;
|
|
1815
|
-
function postSimMessage(msg) {
|
|
1816
|
-
simDriver === null || simDriver === void 0 ? void 0 : simDriver.postMessage(msg);
|
|
1817
|
-
}
|
|
1818
|
-
runner.postSimMessage = postSimMessage;
|
|
1819
|
-
async function fetchSimJsInfo(simOptions) {
|
|
1820
|
-
try {
|
|
1821
|
-
const start = Date.now();
|
|
1822
|
-
const result = await pxt.Cloud.downloadBuiltSimJsInfoAsync(simOptions.id);
|
|
1823
|
-
pxt.tickEvent("perfMeasurement", {
|
|
1824
|
-
durationMs: Date.now() - start,
|
|
1825
|
-
operation: "fetchSimJsInfo",
|
|
1826
|
-
});
|
|
1827
|
-
return result;
|
|
1828
|
-
}
|
|
1829
|
-
catch (e) {
|
|
1830
|
-
// This exception will happen in the majority of cases, so we don't want to log it unless for debugging.
|
|
1831
|
-
pxt.debug(e.toString());
|
|
1832
|
-
return undefined;
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
runner.fetchSimJsInfo = fetchSimJsInfo;
|
|
1836
|
-
async function buildSimJsInfo(simOptions) {
|
|
1837
|
-
var _a;
|
|
1838
|
-
const start = Date.now();
|
|
1839
|
-
await loadPackageAsync(simOptions.id, simOptions.code, simOptions.dependencies);
|
|
1840
|
-
let didUpgrade = false;
|
|
1841
|
-
const currentTargetVersion = pxt.appTarget.versions.target;
|
|
1842
|
-
let compileResult = await compileAsync(false, opts => {
|
|
1843
|
-
var _a;
|
|
1844
|
-
opts.computeUsedParts = true;
|
|
1845
|
-
if (simOptions.debug)
|
|
1846
|
-
opts.breakpoints = true;
|
|
1847
|
-
if (simOptions.assets) {
|
|
1848
|
-
const parsedAssets = JSON.parse(simOptions.assets);
|
|
1849
|
-
for (const key of Object.keys(parsedAssets)) {
|
|
1850
|
-
const el = parsedAssets[key];
|
|
1851
|
-
opts.fileSystem[key] = el;
|
|
1852
|
-
if (opts.sourceFiles.indexOf(key) < 0) {
|
|
1853
|
-
opts.sourceFiles.push(key);
|
|
1854
|
-
}
|
|
1855
|
-
if (/\.jres$/.test(key)) {
|
|
1856
|
-
const parsedJres = JSON.parse(el);
|
|
1857
|
-
opts.jres = pxt.inflateJRes(parsedJres, opts.jres);
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
if (simOptions.code)
|
|
1862
|
-
opts.fileSystem[pxt.MAIN_TS] = simOptions.code;
|
|
1863
|
-
// Api info needed for py2ts conversion, if project is shared in Python
|
|
1864
|
-
if (opts.target.preferredEditor === pxt.PYTHON_PROJECT_NAME) {
|
|
1865
|
-
opts.target.preferredEditor = pxt.JAVASCRIPT_PROJECT_NAME;
|
|
1866
|
-
opts.ast = true;
|
|
1867
|
-
const resp = pxtc.compile(opts);
|
|
1868
|
-
const apis = getApiInfo(resp.ast, opts);
|
|
1869
|
-
opts.apisInfo = apis;
|
|
1870
|
-
opts.target.preferredEditor = pxt.PYTHON_PROJECT_NAME;
|
|
1871
|
-
}
|
|
1872
|
-
// Apply upgrade rules if necessary
|
|
1873
|
-
const sharedTargetVersion = (_a = runner.mainPkg.config.targetVersions) === null || _a === void 0 ? void 0 : _a.target;
|
|
1874
|
-
if (sharedTargetVersion && currentTargetVersion &&
|
|
1875
|
-
pxt.semver.cmp(pxt.semver.parse(sharedTargetVersion), pxt.semver.parse(currentTargetVersion)) < 0) {
|
|
1876
|
-
for (const fileName of Object.keys(opts.fileSystem)) {
|
|
1877
|
-
if (!pxt.Util.startsWith(fileName, "pxt_modules") && pxt.Util.endsWith(fileName, ".ts")) {
|
|
1878
|
-
didUpgrade = true;
|
|
1879
|
-
opts.fileSystem[fileName] = pxt.patching.patchJavaScript(sharedTargetVersion, opts.fileSystem[fileName]);
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
}
|
|
1883
|
-
});
|
|
1884
|
-
if (((_a = compileResult.diagnostics) === null || _a === void 0 ? void 0 : _a.length) > 0 && didUpgrade) {
|
|
1885
|
-
pxt.log("Compile with upgrade rules failed, trying again with original code");
|
|
1886
|
-
compileResult = await compileAsync(false, opts => {
|
|
1887
|
-
if (simOptions.code)
|
|
1888
|
-
opts.fileSystem[pxt.MAIN_TS] = simOptions.code;
|
|
1889
|
-
});
|
|
1890
|
-
}
|
|
1891
|
-
if (compileResult.diagnostics && compileResult.diagnostics.length > 0) {
|
|
1892
|
-
console.error("Diagnostics", compileResult.diagnostics);
|
|
1893
|
-
}
|
|
1894
|
-
const res = pxtc.buildSimJsInfo(compileResult);
|
|
1895
|
-
res.parts = compileResult.usedParts;
|
|
1896
|
-
pxt.tickEvent("perfMeasurement", {
|
|
1897
|
-
durationMs: Date.now() - start,
|
|
1898
|
-
operation: "buildSimJsInfo",
|
|
1899
|
-
});
|
|
1900
|
-
return res;
|
|
1901
|
-
}
|
|
1902
|
-
runner.buildSimJsInfo = buildSimJsInfo;
|
|
1903
|
-
function getStoredState(id) {
|
|
1904
|
-
let storedState = {};
|
|
1905
|
-
try {
|
|
1906
|
-
let projectStorage = window.localStorage.getItem(id);
|
|
1907
|
-
if (projectStorage) {
|
|
1908
|
-
storedState = JSON.parse(projectStorage);
|
|
1909
|
-
}
|
|
1910
|
-
}
|
|
1911
|
-
catch (e) { }
|
|
1912
|
-
return storedState;
|
|
1913
|
-
}
|
|
1914
|
-
function setStoredState(id, key, value) {
|
|
1915
|
-
let storedState = getStoredState(id);
|
|
1916
|
-
if (!id) {
|
|
1917
|
-
return;
|
|
1918
|
-
}
|
|
1919
|
-
if (value != null)
|
|
1920
|
-
storedState[key] = value;
|
|
1921
|
-
else
|
|
1922
|
-
delete storedState[key];
|
|
1923
|
-
try {
|
|
1924
|
-
window.localStorage.setItem(id, JSON.stringify(storedState));
|
|
1925
|
-
}
|
|
1926
|
-
catch (e) { }
|
|
1927
|
-
}
|
|
1928
|
-
let LanguageMode;
|
|
1929
|
-
(function (LanguageMode) {
|
|
1930
|
-
LanguageMode[LanguageMode["Blocks"] = 0] = "Blocks";
|
|
1931
|
-
LanguageMode[LanguageMode["TypeScript"] = 1] = "TypeScript";
|
|
1932
|
-
LanguageMode[LanguageMode["Python"] = 2] = "Python";
|
|
1933
|
-
})(LanguageMode = runner.LanguageMode || (runner.LanguageMode = {}));
|
|
1934
|
-
runner.editorLanguageMode = LanguageMode.Blocks;
|
|
1935
|
-
function setEditorContextAsync(mode, localeInfo) {
|
|
1936
|
-
runner.editorLanguageMode = mode;
|
|
1937
|
-
if (localeInfo != pxt.Util.localeInfo()) {
|
|
1938
|
-
const localeLiveRx = /^live-/;
|
|
1939
|
-
const fetchLive = localeLiveRx.test(localeInfo);
|
|
1940
|
-
if (fetchLive) {
|
|
1941
|
-
pxt.Util.enableLiveLocalizationUpdates();
|
|
1942
|
-
}
|
|
1943
|
-
return pxt.Util.updateLocalizationAsync({
|
|
1944
|
-
targetId: pxt.appTarget.id,
|
|
1945
|
-
baseUrl: pxt.webConfig.commitCdnUrl,
|
|
1946
|
-
code: localeInfo.replace(localeLiveRx, ''),
|
|
1947
|
-
pxtBranch: pxt.appTarget.versions.pxtCrowdinBranch,
|
|
1948
|
-
targetBranch: pxt.appTarget.versions.targetCrowdinBranch,
|
|
1949
|
-
});
|
|
1950
|
-
}
|
|
1951
|
-
return Promise.resolve();
|
|
1952
|
-
}
|
|
1953
|
-
runner.setEditorContextAsync = setEditorContextAsync;
|
|
1954
|
-
function receiveDocMessage(e) {
|
|
1955
|
-
let m = e.data;
|
|
1956
|
-
if (!m)
|
|
1957
|
-
return;
|
|
1958
|
-
switch (m.type) {
|
|
1959
|
-
case "fileloaded":
|
|
1960
|
-
let fm = m;
|
|
1961
|
-
let name = fm.name;
|
|
1962
|
-
let mode = LanguageMode.Blocks;
|
|
1963
|
-
if (/\.ts$/i.test(name)) {
|
|
1964
|
-
mode = LanguageMode.TypeScript;
|
|
1965
|
-
}
|
|
1966
|
-
else if (/\.py$/i.test(name)) {
|
|
1967
|
-
mode = LanguageMode.Python;
|
|
1968
|
-
}
|
|
1969
|
-
setEditorContextAsync(mode, fm.locale);
|
|
1970
|
-
break;
|
|
1971
|
-
case "popout":
|
|
1972
|
-
let mp = /((\/v[0-9+])\/)?[^\/]*#(doc|md):([^&?:]+)/i.exec(window.location.href);
|
|
1973
|
-
if (mp) {
|
|
1974
|
-
const docsUrl = pxt.webConfig.docsUrl || '/--docs';
|
|
1975
|
-
let verPrefix = mp[2] || '';
|
|
1976
|
-
let url = mp[3] == "doc" ? (pxt.webConfig.isStatic ? `/docs${mp[4]}.html` : `${mp[4]}`) : `${docsUrl}?md=${mp[4]}`;
|
|
1977
|
-
// notify parent iframe that we have completed the popout
|
|
1978
|
-
if (window.parent)
|
|
1979
|
-
window.parent.postMessage({
|
|
1980
|
-
type: "opendoc",
|
|
1981
|
-
url: pxt.BrowserUtils.urlJoin(verPrefix, url)
|
|
1982
|
-
}, "*");
|
|
1983
|
-
}
|
|
1984
|
-
break;
|
|
1985
|
-
case "localtoken":
|
|
1986
|
-
let dm = m;
|
|
1987
|
-
if (dm && dm.localToken) {
|
|
1988
|
-
pxt.Cloud.localToken = dm.localToken;
|
|
1989
|
-
pendingLocalToken.forEach(p => p());
|
|
1990
|
-
pendingLocalToken = [];
|
|
1991
|
-
}
|
|
1992
|
-
break;
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
function startRenderServer() {
|
|
1996
|
-
pxt.tickEvent("renderer.ready");
|
|
1997
|
-
const jobQueue = [];
|
|
1998
|
-
let jobPromise = undefined;
|
|
1999
|
-
function consumeQueue() {
|
|
2000
|
-
if (jobPromise)
|
|
2001
|
-
return; // other worker already in action
|
|
2002
|
-
const msg = jobQueue.shift();
|
|
2003
|
-
if (!msg)
|
|
2004
|
-
return; // no more work
|
|
2005
|
-
const options = (msg.options || {});
|
|
2006
|
-
options.splitSvg = false; // don't split when requesting rendered images
|
|
2007
|
-
pxt.tickEvent("renderer.job");
|
|
2008
|
-
const isXml = /^\s*<xml/.test(msg.code);
|
|
2009
|
-
const doWork = async () => {
|
|
2010
|
-
await pxt.BrowserUtils.loadBlocklyAsync();
|
|
2011
|
-
const result = isXml
|
|
2012
|
-
? await pxt.runner.compileBlocksAsync(msg.code, options)
|
|
2013
|
-
: await runner.decompileSnippetAsync(msg.code, msg.options);
|
|
2014
|
-
const blocksSvg = result.blocksSvg;
|
|
2015
|
-
const width = blocksSvg.viewBox.baseVal.width;
|
|
2016
|
-
const height = blocksSvg.viewBox.baseVal.height;
|
|
2017
|
-
const res = blocksSvg
|
|
2018
|
-
? await pxt.blocks.layout.blocklyToSvgAsync(blocksSvg, 0, 0, width, height)
|
|
2019
|
-
: undefined;
|
|
2020
|
-
// try to render to png
|
|
2021
|
-
let png;
|
|
2022
|
-
try {
|
|
2023
|
-
png = res
|
|
2024
|
-
? await pxt.BrowserUtils.encodeToPngAsync(res.xml, { width, height })
|
|
2025
|
-
: undefined;
|
|
2026
|
-
}
|
|
2027
|
-
catch (e) {
|
|
2028
|
-
console.warn(e);
|
|
2029
|
-
}
|
|
2030
|
-
window.parent.postMessage({
|
|
2031
|
-
source: "makecode",
|
|
2032
|
-
type: "renderblocks",
|
|
2033
|
-
id: msg.id,
|
|
2034
|
-
width: res === null || res === void 0 ? void 0 : res.width,
|
|
2035
|
-
height: res === null || res === void 0 ? void 0 : res.height,
|
|
2036
|
-
svg: res === null || res === void 0 ? void 0 : res.svg,
|
|
2037
|
-
uri: png || (res === null || res === void 0 ? void 0 : res.xml),
|
|
2038
|
-
css: res === null || res === void 0 ? void 0 : res.css
|
|
2039
|
-
}, "*");
|
|
2040
|
-
};
|
|
2041
|
-
jobPromise = doWork()
|
|
2042
|
-
.catch(e => {
|
|
2043
|
-
window.parent.postMessage({
|
|
2044
|
-
source: "makecode",
|
|
2045
|
-
type: "renderblocks",
|
|
2046
|
-
id: msg.id,
|
|
2047
|
-
error: e.message
|
|
2048
|
-
}, "*");
|
|
2049
|
-
})
|
|
2050
|
-
.finally(() => {
|
|
2051
|
-
jobPromise = undefined;
|
|
2052
|
-
consumeQueue();
|
|
2053
|
-
});
|
|
2054
|
-
}
|
|
2055
|
-
pxt.editor.initEditorExtensionsAsync()
|
|
2056
|
-
.then(() => {
|
|
2057
|
-
// notify parent that render engine is loaded
|
|
2058
|
-
window.addEventListener("message", function (ev) {
|
|
2059
|
-
const msg = ev.data;
|
|
2060
|
-
if (msg.type == "renderblocks") {
|
|
2061
|
-
jobQueue.push(msg);
|
|
2062
|
-
consumeQueue();
|
|
2063
|
-
}
|
|
2064
|
-
}, false);
|
|
2065
|
-
window.parent.postMessage({
|
|
2066
|
-
source: "makecode",
|
|
2067
|
-
type: "renderready",
|
|
2068
|
-
versions: pxt.appTarget.versions
|
|
2069
|
-
}, "*");
|
|
2070
|
-
});
|
|
2071
|
-
}
|
|
2072
|
-
runner.startRenderServer = startRenderServer;
|
|
2073
|
-
function startDocsServer(loading, content, backButton) {
|
|
2074
|
-
pxt.tickEvent("docrenderer.ready");
|
|
2075
|
-
const history = [];
|
|
2076
|
-
if (backButton) {
|
|
2077
|
-
backButton.addEventListener("click", () => {
|
|
2078
|
-
goBack();
|
|
2079
|
-
});
|
|
2080
|
-
setElementDisabled(backButton, true);
|
|
2081
|
-
}
|
|
2082
|
-
function render(doctype, src) {
|
|
2083
|
-
pxt.debug(`rendering ${doctype}`);
|
|
2084
|
-
if (backButton)
|
|
2085
|
-
$(backButton).hide();
|
|
2086
|
-
$(content).hide();
|
|
2087
|
-
$(loading).show();
|
|
2088
|
-
pxt.U.delay(100) // allow UI to update
|
|
2089
|
-
.then(() => {
|
|
2090
|
-
switch (doctype) {
|
|
2091
|
-
case "print":
|
|
2092
|
-
const data = window.localStorage["printjob"];
|
|
2093
|
-
delete window.localStorage["printjob"];
|
|
2094
|
-
return renderProjectFilesAsync(content, JSON.parse(data), undefined, true)
|
|
2095
|
-
.then(() => pxsim.print(1000));
|
|
2096
|
-
case "project":
|
|
2097
|
-
return renderProjectFilesAsync(content, JSON.parse(src))
|
|
2098
|
-
.then(() => pxsim.print(1000));
|
|
2099
|
-
case "projectid":
|
|
2100
|
-
return renderProjectAsync(content, JSON.parse(src))
|
|
2101
|
-
.then(() => pxsim.print(1000));
|
|
2102
|
-
case "doc":
|
|
2103
|
-
return renderDocAsync(content, src);
|
|
2104
|
-
case "book":
|
|
2105
|
-
return renderBookAsync(content, src);
|
|
2106
|
-
default:
|
|
2107
|
-
return renderMarkdownAsync(content, src);
|
|
2108
|
-
}
|
|
2109
|
-
})
|
|
2110
|
-
.catch(e => {
|
|
2111
|
-
$(content).html(`
|
|
2112
|
-
<img style="height:4em;" src="${pxt.appTarget.appTheme.docsLogo}" />
|
|
2113
|
-
<h1>${lf("Oops")}</h1>
|
|
2114
|
-
<h3>${lf("We could not load the documentation, please check your internet connection.")}</h3>
|
|
2115
|
-
<button class="ui button primary" id="tryagain">${lf("Try Again")}</button>`);
|
|
2116
|
-
$(content).find('#tryagain').click(() => {
|
|
2117
|
-
render(doctype, src);
|
|
2118
|
-
});
|
|
2119
|
-
// notify parent iframe that docs weren't loaded
|
|
2120
|
-
if (window.parent)
|
|
2121
|
-
window.parent.postMessage({
|
|
2122
|
-
type: "docfailed",
|
|
2123
|
-
docType: doctype,
|
|
2124
|
-
src: src
|
|
2125
|
-
}, "*");
|
|
2126
|
-
}).finally(() => {
|
|
2127
|
-
$(loading).hide();
|
|
2128
|
-
if (backButton)
|
|
2129
|
-
$(backButton).show();
|
|
2130
|
-
$(content).show();
|
|
2131
|
-
})
|
|
2132
|
-
.then(() => { });
|
|
2133
|
-
}
|
|
2134
|
-
function pushHistory() {
|
|
2135
|
-
if (!backButton)
|
|
2136
|
-
return;
|
|
2137
|
-
history.push(window.location.hash);
|
|
2138
|
-
if (history.length > 10) {
|
|
2139
|
-
history.shift();
|
|
2140
|
-
}
|
|
2141
|
-
if (history.length > 1) {
|
|
2142
|
-
setElementDisabled(backButton, false);
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
function goBack() {
|
|
2146
|
-
if (!backButton)
|
|
2147
|
-
return;
|
|
2148
|
-
if (history.length > 1) {
|
|
2149
|
-
// Top is current page
|
|
2150
|
-
history.pop();
|
|
2151
|
-
window.location.hash = history.pop();
|
|
2152
|
-
}
|
|
2153
|
-
if (history.length <= 1) {
|
|
2154
|
-
setElementDisabled(backButton, true);
|
|
2155
|
-
}
|
|
2156
|
-
}
|
|
2157
|
-
function setElementDisabled(el, disabled) {
|
|
2158
|
-
if (disabled) {
|
|
2159
|
-
pxsim.U.addClass(el, "disabled");
|
|
2160
|
-
el.setAttribute("aria-disabled", "true");
|
|
2161
|
-
}
|
|
2162
|
-
else {
|
|
2163
|
-
pxsim.U.removeClass(el, "disabled");
|
|
2164
|
-
el.setAttribute("aria-disabled", "false");
|
|
2165
|
-
}
|
|
2166
|
-
}
|
|
2167
|
-
async function renderHashAsync() {
|
|
2168
|
-
let m = /^#(doc|md|tutorial|book|project|projectid|print):([^&?:]+)(:([^&?:]+):([^&?:]+))?/i.exec(window.location.hash);
|
|
2169
|
-
if (m) {
|
|
2170
|
-
pushHistory();
|
|
2171
|
-
if (m[4]) {
|
|
2172
|
-
let mode = LanguageMode.TypeScript;
|
|
2173
|
-
if (/^blocks$/i.test(m[4])) {
|
|
2174
|
-
mode = LanguageMode.Blocks;
|
|
2175
|
-
}
|
|
2176
|
-
else if (/^python$/i.test(m[4])) {
|
|
2177
|
-
mode = LanguageMode.Python;
|
|
2178
|
-
}
|
|
2179
|
-
await setEditorContextAsync(mode, m[5]);
|
|
2180
|
-
}
|
|
2181
|
-
// navigation occured
|
|
2182
|
-
render(m[1], decodeURIComponent(m[2]));
|
|
2183
|
-
}
|
|
2184
|
-
}
|
|
2185
|
-
let promise = pxt.editor.initEditorExtensionsAsync();
|
|
2186
|
-
promise.then(() => {
|
|
2187
|
-
window.addEventListener("message", receiveDocMessage, false);
|
|
2188
|
-
window.addEventListener("hashchange", () => {
|
|
2189
|
-
renderHashAsync();
|
|
2190
|
-
}, false);
|
|
2191
|
-
parent.postMessage({ type: "sidedocready" }, "*");
|
|
2192
|
-
// delay load doc page to allow simulator to load first
|
|
2193
|
-
setTimeout(() => renderHashAsync(), 1);
|
|
2194
|
-
});
|
|
2195
|
-
}
|
|
2196
|
-
runner.startDocsServer = startDocsServer;
|
|
2197
|
-
function renderProjectAsync(content, projectid) {
|
|
2198
|
-
return pxt.Cloud.privateGetTextAsync(projectid + "/text")
|
|
2199
|
-
.then(txt => JSON.parse(txt))
|
|
2200
|
-
.then(files => renderProjectFilesAsync(content, files, projectid));
|
|
2201
|
-
}
|
|
2202
|
-
runner.renderProjectAsync = renderProjectAsync;
|
|
2203
|
-
function renderProjectFilesAsync(content, files, projectid = null, escapeLinks = false) {
|
|
2204
|
-
const cfg = (JSON.parse(files[pxt.CONFIG_NAME]) || {});
|
|
2205
|
-
let md = `# ${cfg.name} ${cfg.version ? cfg.version : ''}
|
|
2206
|
-
|
|
2207
|
-
`;
|
|
2208
|
-
const readme = "README.md";
|
|
2209
|
-
if (files[readme])
|
|
2210
|
-
md += files[readme].replace(/^#+/, "$0#") + '\n'; // bump all headers down 1
|
|
2211
|
-
cfg.files.filter(f => f != pxt.CONFIG_NAME && f != readme)
|
|
2212
|
-
.filter(f => matchesLanguageMode(f, runner.editorLanguageMode))
|
|
2213
|
-
.forEach(f => {
|
|
2214
|
-
if (!/^main\.(ts|blocks)$/.test(f))
|
|
2215
|
-
md += `
|
|
2216
|
-
## ${f}
|
|
2217
|
-
`;
|
|
2218
|
-
if (/\.ts$/.test(f)) {
|
|
2219
|
-
md += `\`\`\`typescript
|
|
2220
|
-
${files[f]}
|
|
2221
|
-
\`\`\`
|
|
2222
|
-
`;
|
|
2223
|
-
}
|
|
2224
|
-
else if (/\.blocks?$/.test(f)) {
|
|
2225
|
-
md += `\`\`\`blocksxml
|
|
2226
|
-
${files[f]}
|
|
2227
|
-
\`\`\`
|
|
2228
|
-
`;
|
|
2229
|
-
}
|
|
2230
|
-
else {
|
|
2231
|
-
md += `\`\`\`${f.substr(f.indexOf('.'))}
|
|
2232
|
-
${files[f]}
|
|
2233
|
-
\`\`\`
|
|
2234
|
-
`;
|
|
2235
|
-
}
|
|
2236
|
-
});
|
|
2237
|
-
const deps = cfg && cfg.dependencies && Object.keys(cfg.dependencies).filter(k => k != pxt.appTarget.corepkg);
|
|
2238
|
-
if (deps && deps.length) {
|
|
2239
|
-
md += `
|
|
2240
|
-
## ${lf("Extensions")} #extensions
|
|
2241
|
-
|
|
2242
|
-
${deps.map(k => `* ${k}, ${cfg.dependencies[k]}`).join('\n')}
|
|
2243
|
-
|
|
2244
|
-
\`\`\`package
|
|
2245
|
-
${deps.map(k => `${k}=${cfg.dependencies[k]}`).join('\n')}
|
|
2246
|
-
\`\`\`
|
|
2247
|
-
`;
|
|
2248
|
-
}
|
|
2249
|
-
if (projectid) {
|
|
2250
|
-
let linkString = (pxt.appTarget.appTheme.shareUrl || "https://makecode.com/") + projectid;
|
|
2251
|
-
if (escapeLinks) {
|
|
2252
|
-
// If printing the link will show up twice if it's an actual link
|
|
2253
|
-
linkString = "`" + linkString + "`";
|
|
2254
|
-
}
|
|
2255
|
-
md += `
|
|
2256
|
-
${linkString}
|
|
2257
|
-
|
|
2258
|
-
`;
|
|
2259
|
-
}
|
|
2260
|
-
console.debug(`print md: ${md}`);
|
|
2261
|
-
const options = {
|
|
2262
|
-
print: true
|
|
2263
|
-
};
|
|
2264
|
-
return renderMarkdownAsync(content, md, options);
|
|
2265
|
-
}
|
|
2266
|
-
runner.renderProjectFilesAsync = renderProjectFilesAsync;
|
|
2267
|
-
function matchesLanguageMode(filename, mode) {
|
|
2268
|
-
switch (mode) {
|
|
2269
|
-
case LanguageMode.Blocks:
|
|
2270
|
-
return /\.blocks?$/.test(filename);
|
|
2271
|
-
case LanguageMode.TypeScript:
|
|
2272
|
-
return /\.ts?$/.test(filename);
|
|
2273
|
-
case LanguageMode.Python:
|
|
2274
|
-
return /\.py?$/.test(filename);
|
|
2275
|
-
}
|
|
2276
|
-
}
|
|
2277
|
-
async function renderDocAsync(content, docid) {
|
|
2278
|
-
docid = docid.replace(/^\//, "");
|
|
2279
|
-
// if it fails on requesting, propagate failed promise
|
|
2280
|
-
const md = await pxt.Cloud.markdownAsync(docid, undefined, true /** don't suppress exception **/);
|
|
2281
|
-
try {
|
|
2282
|
-
// just log exceptions that occur during rendering,
|
|
2283
|
-
// similar to how normal docs handle them.
|
|
2284
|
-
await renderMarkdownAsync(content, md, { path: docid });
|
|
2285
|
-
}
|
|
2286
|
-
catch (e) {
|
|
2287
|
-
console.warn(e);
|
|
2288
|
-
}
|
|
2289
|
-
}
|
|
2290
|
-
function renderBookAsync(content, summaryid) {
|
|
2291
|
-
summaryid = summaryid.replace(/^\//, "");
|
|
2292
|
-
pxt.tickEvent('book', { id: summaryid });
|
|
2293
|
-
pxt.log(`rendering book from ${summaryid}`);
|
|
2294
|
-
// display loader
|
|
2295
|
-
const $loader = $("#loading").find(".loader");
|
|
2296
|
-
$loader.addClass("text").text(lf("Compiling your book (this may take a minute)"));
|
|
2297
|
-
// start the work
|
|
2298
|
-
let toc;
|
|
2299
|
-
return pxt.U.delay(100)
|
|
2300
|
-
.then(() => pxt.Cloud.markdownAsync(summaryid, undefined, true))
|
|
2301
|
-
.then(summary => {
|
|
2302
|
-
toc = pxt.docs.buildTOC(summary);
|
|
2303
|
-
pxt.log(`TOC: ${JSON.stringify(toc, null, 2)}`);
|
|
2304
|
-
const tocsp = [];
|
|
2305
|
-
pxt.docs.visitTOC(toc, entry => {
|
|
2306
|
-
if (/^\//.test(entry.path) && !/^\/pkg\//.test(entry.path))
|
|
2307
|
-
tocsp.push(entry);
|
|
2308
|
-
});
|
|
2309
|
-
return pxt.U.promisePoolAsync(4, tocsp, async (entry) => {
|
|
2310
|
-
try {
|
|
2311
|
-
const md = await pxt.Cloud.markdownAsync(entry.path, undefined, true);
|
|
2312
|
-
entry.markdown = md;
|
|
2313
|
-
}
|
|
2314
|
-
catch (e) {
|
|
2315
|
-
entry.markdown = `_${entry.path} failed to load._`;
|
|
2316
|
-
}
|
|
2317
|
-
});
|
|
2318
|
-
})
|
|
2319
|
-
.then(pages => {
|
|
2320
|
-
let md = toc[0].name;
|
|
2321
|
-
pxt.docs.visitTOC(toc, entry => {
|
|
2322
|
-
if (entry.markdown)
|
|
2323
|
-
md += '\n\n' + entry.markdown;
|
|
2324
|
-
});
|
|
2325
|
-
return renderMarkdownAsync(content, md);
|
|
2326
|
-
});
|
|
2327
|
-
}
|
|
2328
|
-
const template = `
|
|
2329
|
-
<aside id=button class=box>
|
|
2330
|
-
<a class="ui primary button" href="@ARGS@">@BODY@</a>
|
|
2331
|
-
</aside>
|
|
2332
|
-
|
|
2333
|
-
<aside id=vimeo>
|
|
2334
|
-
<div class="ui two column stackable grid container">
|
|
2335
|
-
<div class="column">
|
|
2336
|
-
<div class="ui embed mdvid" data-source="vimeo" data-id="@ARGS@" data-placeholder="/thumbnail/1024/vimeo/@ARGS@" data-icon="video play">
|
|
2337
|
-
</div>
|
|
2338
|
-
</div></div>
|
|
2339
|
-
</aside>
|
|
2340
|
-
|
|
2341
|
-
<aside id=youtube>
|
|
2342
|
-
<div class="ui two column stackable grid container">
|
|
2343
|
-
<div class="column">
|
|
2344
|
-
<div class="ui embed mdvid" data-source="youtube" data-id="@ARGS@" data-placeholder="https://img.youtube.com/vi/@ARGS@/0.jpg">
|
|
2345
|
-
</div>
|
|
2346
|
-
</div></div>
|
|
2347
|
-
</aside>
|
|
2348
|
-
|
|
2349
|
-
<aside id=section>
|
|
2350
|
-
<!-- section @ARGS@ -->
|
|
2351
|
-
</aside>
|
|
2352
|
-
|
|
2353
|
-
<aside id=hide class=box>
|
|
2354
|
-
<div style='display:none'>
|
|
2355
|
-
@BODY@
|
|
2356
|
-
</div>
|
|
2357
|
-
</aside>
|
|
2358
|
-
|
|
2359
|
-
<aside id=avatar class=box>
|
|
2360
|
-
<div class='avatar @ARGS@'>
|
|
2361
|
-
<div class='avatar-image'></div>
|
|
2362
|
-
<div class='ui compact message'>
|
|
2363
|
-
@BODY@
|
|
2364
|
-
</div>
|
|
2365
|
-
</div>
|
|
2366
|
-
</aside>
|
|
2367
|
-
|
|
2368
|
-
<aside id=hint class=box>
|
|
2369
|
-
<div class="ui info message">
|
|
2370
|
-
<div class="content">
|
|
2371
|
-
@BODY@
|
|
2372
|
-
</div>
|
|
2373
|
-
</div>
|
|
2374
|
-
</aside>
|
|
2375
|
-
|
|
2376
|
-
<aside id=codecard class=box>
|
|
2377
|
-
<pre><code class="lang-codecard">@BODY@</code></pre>
|
|
2378
|
-
</aside>
|
|
2379
|
-
|
|
2380
|
-
<aside id=tutorialhint class=box>
|
|
2381
|
-
<div class="ui hint message">
|
|
2382
|
-
<div class="content">
|
|
2383
|
-
@BODY@
|
|
2384
|
-
</div>
|
|
2385
|
-
</div>
|
|
2386
|
-
</aside>
|
|
2387
|
-
|
|
2388
|
-
<aside id=reminder class=box>
|
|
2389
|
-
<div class="ui warning message">
|
|
2390
|
-
<div class="content">
|
|
2391
|
-
@BODY@
|
|
2392
|
-
</div>
|
|
2393
|
-
</div>
|
|
2394
|
-
</aside>
|
|
2395
|
-
|
|
2396
|
-
<aside id=alert class=box>
|
|
2397
|
-
<div class="ui negative message">
|
|
2398
|
-
<div class="content">
|
|
2399
|
-
@BODY@
|
|
2400
|
-
</div>
|
|
2401
|
-
</div>
|
|
2402
|
-
</aside>
|
|
2403
|
-
|
|
2404
|
-
<aside id=tip class=box>
|
|
2405
|
-
<div class="ui positive message">
|
|
2406
|
-
<div class="content">
|
|
2407
|
-
@BODY@
|
|
2408
|
-
</div>
|
|
2409
|
-
</div>
|
|
2410
|
-
</aside>
|
|
2411
|
-
|
|
2412
|
-
<!-- wrapped around ordinary content -->
|
|
2413
|
-
<aside id=main-container class=box>
|
|
2414
|
-
<div class="ui text">
|
|
2415
|
-
@BODY@
|
|
2416
|
-
</div>
|
|
2417
|
-
</aside>
|
|
2418
|
-
|
|
2419
|
-
<!-- used for 'column' box - they are collected and wrapped in 'column-container' -->
|
|
2420
|
-
<aside id=column class=aside>
|
|
2421
|
-
<div class='column'>
|
|
2422
|
-
@BODY@
|
|
2423
|
-
</div>
|
|
2424
|
-
</aside>
|
|
2425
|
-
<aside id=column-container class=box>
|
|
2426
|
-
<div class="ui three column stackable grid text">
|
|
2427
|
-
@BODY@
|
|
2428
|
-
</div>
|
|
2429
|
-
</aside>
|
|
2430
|
-
@breadcrumb@
|
|
2431
|
-
@body@`;
|
|
2432
|
-
function renderMarkdownAsync(content, md, options = {}) {
|
|
2433
|
-
const html = pxt.docs.renderMarkdown({
|
|
2434
|
-
template: template,
|
|
2435
|
-
markdown: md,
|
|
2436
|
-
theme: pxt.appTarget.appTheme
|
|
2437
|
-
});
|
|
2438
|
-
let blocksAspectRatio = options.blocksAspectRatio
|
|
2439
|
-
|| window.innerHeight < window.innerWidth ? 1.62 : 1 / 1.62;
|
|
2440
|
-
$(content).html(html);
|
|
2441
|
-
$(content).find('a').attr('target', '_blank');
|
|
2442
|
-
const renderOptions = pxt.runner.defaultClientRenderOptions();
|
|
2443
|
-
renderOptions.tutorial = !!options.tutorial;
|
|
2444
|
-
renderOptions.blocksAspectRatio = blocksAspectRatio || renderOptions.blocksAspectRatio;
|
|
2445
|
-
renderOptions.showJavaScript = runner.editorLanguageMode == LanguageMode.TypeScript;
|
|
2446
|
-
if (options.print) {
|
|
2447
|
-
renderOptions.showEdit = false;
|
|
2448
|
-
renderOptions.simulator = false;
|
|
2449
|
-
}
|
|
2450
|
-
return pxt.runner.renderAsync(renderOptions).then(() => {
|
|
2451
|
-
// patch a elements
|
|
2452
|
-
$(content).find('a[href^="/"]').removeAttr('target').each((i, a) => {
|
|
2453
|
-
$(a).attr('href', '#doc:' + $(a).attr('href').replace(/^\//, ''));
|
|
2454
|
-
});
|
|
2455
|
-
// enable embeds
|
|
2456
|
-
$(content).find('.ui.embed').embed();
|
|
2457
|
-
});
|
|
2458
|
-
}
|
|
2459
|
-
runner.renderMarkdownAsync = renderMarkdownAsync;
|
|
2460
|
-
let programCache;
|
|
2461
|
-
let apiCache;
|
|
2462
|
-
function decompileSnippetAsync(code, options) {
|
|
2463
|
-
const { assets, forceCompilation, snippetMode, generateSourceMap } = options || {};
|
|
2464
|
-
// code may be undefined or empty!!!
|
|
2465
|
-
const packageid = options && options.packageId ? "pub:" + options.packageId :
|
|
2466
|
-
options && options.package ? "docs:" + options.package
|
|
2467
|
-
: null;
|
|
2468
|
-
return loadPackageAsync(packageid, code)
|
|
2469
|
-
.then(() => getCompileOptionsAsync(pxt.appTarget.compile ? pxt.appTarget.compile.hasHex : false))
|
|
2470
|
-
.then(opts => {
|
|
2471
|
-
// compile
|
|
2472
|
-
if (code)
|
|
2473
|
-
opts.fileSystem[pxt.MAIN_TS] = code;
|
|
2474
|
-
opts.ast = true;
|
|
2475
|
-
if (assets) {
|
|
2476
|
-
for (const key of Object.keys(assets)) {
|
|
2477
|
-
if (opts.sourceFiles.indexOf(key) < 0) {
|
|
2478
|
-
opts.sourceFiles.push(key);
|
|
2479
|
-
}
|
|
2480
|
-
opts.fileSystem[key] = assets[key];
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
let compileJS = undefined;
|
|
2484
|
-
let program;
|
|
2485
|
-
if (forceCompilation) {
|
|
2486
|
-
compileJS = pxtc.compile(opts);
|
|
2487
|
-
program = compileJS && compileJS.ast;
|
|
2488
|
-
}
|
|
2489
|
-
else {
|
|
2490
|
-
program = pxtc.getTSProgram(opts, programCache);
|
|
2491
|
-
}
|
|
2492
|
-
programCache = program;
|
|
2493
|
-
// decompile to python
|
|
2494
|
-
let compilePython = undefined;
|
|
2495
|
-
if (pxt.appTarget.appTheme.python) {
|
|
2496
|
-
compilePython = ts.pxtc.transpile.tsToPy(program, pxt.MAIN_TS);
|
|
2497
|
-
}
|
|
2498
|
-
// decompile to blocks
|
|
2499
|
-
let apis = getApiInfo(program, opts);
|
|
2500
|
-
return ts.pxtc.localizeApisAsync(apis, runner.mainPkg)
|
|
2501
|
-
.then(() => {
|
|
2502
|
-
let blocksInfo = pxtc.getBlocksInfo(apis);
|
|
2503
|
-
pxt.blocks.initializeAndInject(blocksInfo);
|
|
2504
|
-
const tilemapJres = assets === null || assets === void 0 ? void 0 : assets[pxt.TILEMAP_JRES];
|
|
2505
|
-
const assetsJres = assets === null || assets === void 0 ? void 0 : assets[pxt.IMAGES_JRES];
|
|
2506
|
-
if (tilemapJres || assetsJres) {
|
|
2507
|
-
tilemapProject = new pxt.TilemapProject();
|
|
2508
|
-
tilemapProject.loadPackage(runner.mainPkg);
|
|
2509
|
-
if (tilemapJres)
|
|
2510
|
-
tilemapProject.loadTilemapJRes(JSON.parse(tilemapJres), true);
|
|
2511
|
-
if (assetsJres)
|
|
2512
|
-
tilemapProject.loadAssetsJRes(JSON.parse(assetsJres));
|
|
2513
|
-
}
|
|
2514
|
-
let bresp = pxtc.decompiler.decompileToBlocks(blocksInfo, program.getSourceFile(pxt.MAIN_TS), {
|
|
2515
|
-
snippetMode,
|
|
2516
|
-
generateSourceMap
|
|
2517
|
-
});
|
|
2518
|
-
if (bresp.diagnostics && bresp.diagnostics.length > 0)
|
|
2519
|
-
bresp.diagnostics.forEach(diag => console.error(diag.messageText));
|
|
2520
|
-
if (!bresp.success)
|
|
2521
|
-
return {
|
|
2522
|
-
package: runner.mainPkg,
|
|
2523
|
-
compileProgram: program,
|
|
2524
|
-
compileJS,
|
|
2525
|
-
compileBlocks: bresp,
|
|
2526
|
-
apiInfo: apis
|
|
2527
|
-
};
|
|
2528
|
-
pxt.debug(bresp.outfiles[pxt.MAIN_BLOCKS]);
|
|
2529
|
-
const blocksSvg = pxt.blocks.render(bresp.outfiles[pxt.MAIN_BLOCKS], options);
|
|
2530
|
-
if (tilemapJres || assetsJres) {
|
|
2531
|
-
tilemapProject = null;
|
|
2532
|
-
}
|
|
2533
|
-
return {
|
|
2534
|
-
package: runner.mainPkg,
|
|
2535
|
-
compileProgram: program,
|
|
2536
|
-
compileJS,
|
|
2537
|
-
compileBlocks: bresp,
|
|
2538
|
-
compilePython,
|
|
2539
|
-
apiInfo: apis,
|
|
2540
|
-
blocksSvg
|
|
2541
|
-
};
|
|
2542
|
-
});
|
|
2543
|
-
});
|
|
2544
|
-
}
|
|
2545
|
-
runner.decompileSnippetAsync = decompileSnippetAsync;
|
|
2546
|
-
function getApiInfo(program, opts) {
|
|
2547
|
-
if (!apiCache)
|
|
2548
|
-
apiCache = {};
|
|
2549
|
-
const key = Object.keys(opts.fileSystem).sort().join(";");
|
|
2550
|
-
if (!apiCache[key])
|
|
2551
|
-
apiCache[key] = pxtc.getApiInfo(program, opts.jres);
|
|
2552
|
-
return apiCache[key];
|
|
2553
|
-
}
|
|
2554
|
-
function compileBlocksAsync(code, options) {
|
|
2555
|
-
const { assets } = options || {};
|
|
2556
|
-
const packageid = options && options.packageId ? "pub:" + options.packageId :
|
|
2557
|
-
options && options.package ? "docs:" + options.package
|
|
2558
|
-
: null;
|
|
2559
|
-
return loadPackageAsync(packageid, "")
|
|
2560
|
-
.then(() => getCompileOptionsAsync(pxt.appTarget.compile ? pxt.appTarget.compile.hasHex : false))
|
|
2561
|
-
.then(opts => {
|
|
2562
|
-
opts.ast = true;
|
|
2563
|
-
if (assets) {
|
|
2564
|
-
for (const key of Object.keys(assets)) {
|
|
2565
|
-
if (opts.sourceFiles.indexOf(key) < 0) {
|
|
2566
|
-
opts.sourceFiles.push(key);
|
|
2567
|
-
}
|
|
2568
|
-
opts.fileSystem[key] = assets[key];
|
|
2569
|
-
}
|
|
2570
|
-
}
|
|
2571
|
-
const resp = pxtc.compile(opts);
|
|
2572
|
-
const apis = getApiInfo(resp.ast, opts);
|
|
2573
|
-
return ts.pxtc.localizeApisAsync(apis, runner.mainPkg)
|
|
2574
|
-
.then(() => {
|
|
2575
|
-
const blocksInfo = pxtc.getBlocksInfo(apis);
|
|
2576
|
-
pxt.blocks.initializeAndInject(blocksInfo);
|
|
2577
|
-
const tilemapJres = assets === null || assets === void 0 ? void 0 : assets[pxt.TILEMAP_JRES];
|
|
2578
|
-
const assetsJres = assets === null || assets === void 0 ? void 0 : assets[pxt.IMAGES_JRES];
|
|
2579
|
-
if (tilemapJres || assetsJres) {
|
|
2580
|
-
tilemapProject = new pxt.TilemapProject();
|
|
2581
|
-
tilemapProject.loadPackage(runner.mainPkg);
|
|
2582
|
-
if (tilemapJres)
|
|
2583
|
-
tilemapProject.loadTilemapJRes(JSON.parse(tilemapJres), true);
|
|
2584
|
-
if (assetsJres)
|
|
2585
|
-
tilemapProject.loadAssetsJRes(JSON.parse(assetsJres));
|
|
2586
|
-
}
|
|
2587
|
-
const blockSvg = pxt.blocks.render(code, options);
|
|
2588
|
-
if (tilemapJres || assetsJres) {
|
|
2589
|
-
tilemapProject = null;
|
|
2590
|
-
}
|
|
2591
|
-
return {
|
|
2592
|
-
package: runner.mainPkg,
|
|
2593
|
-
blocksSvg: blockSvg,
|
|
2594
|
-
apiInfo: apis
|
|
2595
|
-
};
|
|
2596
|
-
});
|
|
2597
|
-
});
|
|
2598
|
-
}
|
|
2599
|
-
runner.compileBlocksAsync = compileBlocksAsync;
|
|
2600
|
-
let pendingLocalToken = [];
|
|
2601
|
-
function waitForLocalTokenAsync() {
|
|
2602
|
-
if (pxt.Cloud.localToken) {
|
|
2603
|
-
return Promise.resolve();
|
|
2604
|
-
}
|
|
2605
|
-
return new Promise((resolve, reject) => {
|
|
2606
|
-
pendingLocalToken.push(resolve);
|
|
2607
|
-
});
|
|
2608
|
-
}
|
|
2609
|
-
runner.initCallbacks = [];
|
|
2610
|
-
function init() {
|
|
2611
|
-
initInnerAsync()
|
|
2612
|
-
.then(() => {
|
|
2613
|
-
for (let i = 0; i < runner.initCallbacks.length; ++i) {
|
|
2614
|
-
runner.initCallbacks[i]();
|
|
2615
|
-
}
|
|
2616
|
-
});
|
|
2617
|
-
}
|
|
2618
|
-
runner.init = init;
|
|
2619
|
-
function windowLoad() {
|
|
2620
|
-
let f = window.ksRunnerWhenLoaded;
|
|
2621
|
-
if (f)
|
|
2622
|
-
f();
|
|
2623
|
-
}
|
|
2624
|
-
windowLoad();
|
|
2625
|
-
})(runner = pxt.runner || (pxt.runner = {}));
|
|
2626
|
-
})(pxt || (pxt = {}));
|