in pxteditor/editorcontroller.ts [397:632]
export function bindEditorMessages(getEditorAsync: () => Promise<IProjectView>) {
const allowEditorMessages = (pxt.appTarget.appTheme.allowParentController || pxt.shell.isControllerMode())
&& pxt.BrowserUtils.isIFrame();
const allowExtensionMessages = pxt.appTarget.appTheme.allowPackageExtensions;
const allowSimTelemetry = pxt.appTarget.appTheme.allowSimulatorTelemetry;
if (!allowEditorMessages && !allowExtensionMessages && !allowSimTelemetry) return;
window.addEventListener("message", (msg: MessageEvent) => {
const data = msg.data as EditorMessage;
if (!data || !/^pxt(host|editor|pkgext|sim)$/.test(data.type)) return false;
if (data.type === "pxtpkgext" && allowExtensionMessages) {
// Messages sent to the editor iframe from a child iframe containing an extension
getEditorAsync().then(projectView => {
projectView.handleExtensionRequest(data as ExtensionRequest);
})
}
else if (data.type === "pxtsim" && allowSimTelemetry) {
const event = data as EditorMessageEventRequest;
if (event.action === "event") {
if (event.category || event.message) {
pxt.reportError(event.category, event.message, event.data as Map<string>)
}
else {
pxt.tickEvent(event.tick, event.data);
}
}
}
else if (allowEditorMessages) {
// Messages sent to the editor from the parent frame
let p = Promise.resolve();
let resp: any = undefined;
if (data.type == "pxthost") { // response from the host
const req = pendingRequests[data.id];
if (!req) {
pxt.debug(`pxthost: unknown request ${data.id}`);
} else {
p = p.then(() => req.resolve(data as EditorMessageResponse));
}
} else if (data.type == "pxteditor") { // request from the editor
p = p.then(() => {
return getEditorAsync().then(projectView => {
const req = data as EditorMessageRequest;
pxt.debug(`pxteditor: ${req.action}`);
switch (req.action.toLowerCase()) {
case "switchjavascript": return Promise.resolve().then(() => projectView.openJavaScript());
case "switchpython": return Promise.resolve().then(() => projectView.openPython());
case "switchblocks": return Promise.resolve().then(() => projectView.openBlocks());
case "startsimulator": return Promise.resolve().then(() => projectView.startSimulator());
case "restartsimulator": return Promise.resolve().then(() => projectView.restartSimulator());
case "hidesimulator": return Promise.resolve().then(() => projectView.collapseSimulator());
case "showsimulator": return Promise.resolve().then(() => projectView.expandSimulator());
case "closeflyout": return Promise.resolve().then(() => projectView.closeFlyout());
case "unloadproject": return Promise.resolve().then(() => projectView.unloadProjectAsync());
case "saveproject": return projectView.saveProjectAsync();
case "redo": return Promise.resolve()
.then(() => {
const editor = projectView.editor;
if (editor && editor.hasRedo())
editor.redo();
});
case "undo": return Promise.resolve()
.then(() => {
const editor = projectView.editor;
if (editor && editor.hasUndo())
editor.undo();
});
case "setscale": {
const zoommsg = data as EditorMessageSetScaleRequest;
return Promise.resolve()
.then(() => projectView.editor.setScale(zoommsg.scale));
}
case "stopsimulator": {
const stop = data as EditorMessageStopRequest;
return Promise.resolve()
.then(() => projectView.stopSimulator(stop.unload));
}
case "newproject": {
const create = data as EditorMessageNewProjectRequest;
return Promise.resolve()
.then(() => projectView.newProject(create.options));
}
case "importproject": {
const load = data as EditorMessageImportProjectRequest;
return Promise.resolve()
.then(() => projectView.importProjectAsync(load.project, {
filters: load.filters,
searchBar: load.searchBar
}));
}
case "openheader": {
const open = data as EditorMessageOpenHeaderRequest;
return projectView.openProjectByHeaderIdAsync(open.headerId)
}
case "startactivity": {
const msg = data as EditorMessageStartActivity;
let tutorialPath = msg.path;
let editorProjectName: string = undefined;
if (/^([jt]s|py|blocks?):/i.test(tutorialPath)) {
if (/^py:/i.test(tutorialPath))
editorProjectName = pxt.PYTHON_PROJECT_NAME;
else if (/^[jt]s:/i.test(tutorialPath))
editorProjectName = pxt.JAVASCRIPT_PROJECT_NAME;
else
editorProjectName = pxt.BLOCKS_PROJECT_NAME;
tutorialPath = tutorialPath.substr(tutorialPath.indexOf(':') + 1)
}
return Promise.resolve()
.then(() => projectView.startActivity({
activity: msg.activityType,
path: tutorialPath,
title: msg.title,
editor: editorProjectName,
previousProjectHeaderId: msg.previousProjectHeaderId,
carryoverPreviousCode: msg.carryoverPreviousCode
}));
}
case "importtutorial": {
const load = data as EditorMessageImportTutorialRequest;
return Promise.resolve()
.then(() => projectView.importTutorialAsync(load.markdown));
}
case "proxytosim": {
const simmsg = data as EditorMessageSimulatorMessageProxyRequest;
return Promise.resolve()
.then(() => projectView.proxySimulatorMessage(simmsg.content));
}
case "renderblocks": {
const rendermsg = data as EditorMessageRenderBlocksRequest;
return Promise.resolve()
.then(() => projectView.renderBlocksAsync(rendermsg))
.then(r => {
return r.xml.then((svg: any) => {
resp = svg.xml;
})
});
}
case "renderpython": {
const rendermsg = data as EditorMessageRenderPythonRequest;
return Promise.resolve()
.then(() => projectView.renderPythonAsync(rendermsg))
.then(r => {
resp = r.python;
});
}
case "toggletrace": {
const togglemsg = data as EditorMessageToggleTraceRequest;
return Promise.resolve()
.then(() => projectView.toggleTrace(togglemsg.intervalSpeed));
}
case "settracestate": {
const trcmsg = data as EditorMessageSetTraceStateRequest;
return Promise.resolve()
.then(() => projectView.setTrace(trcmsg.enabled, trcmsg.intervalSpeed));
}
case "setsimulatorfullscreen": {
const fsmsg = data as EditorMessageSetSimulatorFullScreenRequest;
return Promise.resolve()
.then(() => projectView.setSimulatorFullScreen(fsmsg.enabled));
}
case "togglehighcontrast": {
return Promise.resolve()
.then(() => projectView.toggleHighContrast());
}
case "sethighcontrast": {
const hcmsg = data as EditorMessageSetHighContrastRequest;
return Promise.resolve()
.then(() => projectView.setHighContrast(hcmsg.on));
}
case "togglegreenscreen": {
return Promise.resolve()
.then(() => projectView.toggleGreenScreen());
}
case "print": {
return Promise.resolve()
.then(() => projectView.printCode());
}
case "pair": {
return projectView.pairAsync();
}
case "info": {
return Promise.resolve()
.then(() => {
resp = <editor.InfoMessage>{
versions: pxt.appTarget.versions,
locale: ts.pxtc.Util.userLanguage(),
availableLocales: pxt.appTarget.appTheme.availableLocales
}
});
}
case "shareproject": {
const msg = data as EditorShareRequest;
return projectView.anonymousPublishHeaderByIdAsync(msg.headerId)
.then(scriptInfo => {
resp = scriptInfo;
});
}
case "savelocalprojectstocloud": {
const msg = data as EditorMessageSaveLocalProjectsToCloud;
return projectView.saveLocalProjectsToCloudAsync(msg.headerIds)
.then(guidMap => {
resp = <EditorMessageSaveLocalProjectsToCloudResponse>{
headerIdMap: guidMap
};
})
}
case "requestprojectcloudstatus": {
// Responses are sent as separate "projectcloudstatus" messages.
const msg = data as EditorMessageRequestProjectCloudStatus;
return projectView.requestProjectCloudStatus(msg.headerIds);
}
case "convertcloudprojectstolocal": {
const msg = data as EditorMessageConvertCloudProjectsToLocal;
return projectView.convertCloudProjectsToLocal(msg.userId);
}
case "setlanguagerestriction": {
const msg = data as EditorSetLanguageRestriction;
if (msg.restriction === "no-blocks") {
console.warn("no-blocks language restriction is not supported");
throw new Error("no-blocks language restriction is not supported")
}
return projectView.setLanguageRestrictionAsync(msg.restriction);
}
}
return Promise.resolve();
});
})
}
p.then(() => sendResponse(data, resp, true, undefined),
(err) => sendResponse(data, resp, false, err))
}
return true;
}, false)
}