TouchDevelop/runner/main.ts

646 строки
27 KiB
TypeScript

///<reference path='refs.ts'/>
module TDev {
export var runnerHost : RunnerHost = null;
export class RunnerHost
extends RuntimeHostBase {
currentRt: Runtime;
runMain: () =>void;
constructor () {
super();
var script = (<any>TDev).precompiledScript;
this.currentRt = new Runtime();
this.currentRt.devMode = false;
}
runAsync() {
RunnerSettings.reportLaunch();
SizeMgr.applySizes();
var rt = this.currentRt;
var cs = new CompiledScript();
cs.initFromPrecompiled();
if (!cs.baseScriptId)
cs.baseScriptId = cs.scriptId;
if (!cs.scriptTitle)
cs.scriptTitle = (<any>window).webAppName;
rt.initFrom(cs);
rt.setHost(this);
this.showWall();
rt.initPageStack();
rt.applyPageAttributes();
SizeMgr.applySizes();
this.runMain = () => {
var fn = cs.actionsByName[cs.mainActionName];
if (cs.pagesByName[cs.mainActionName] !== undefined) {
fn = Runtime.syntheticFrame((s) => s.rt.postAutoPage("this", cs.mainActionName));
}
rt.run(fn, null);
};
this.runMain();
return Promise.as();
}
notifyHideWall() {
this.runMain();
}
public agreeTermsOfUseAsync(): Promise {
return RunnerSettings.agreeTermsAsync();
}
public tweakMsg()
{
var webAppId = (<any>window).webAppId || "";
var txt = webAppId && Math.random() < 0.2 ? "tweak this web app on touchdevelop.com" : "find more on touchdevelop.com";
var d = div("copyright-text", txt);
if (SizeMgr.phoneMode)
d.innerHTML = "<span class='beta-underline'>more</span>";
return d.withClick(() => {
var link = (text:string, lnk:string) =>
HTML.mkButton(text,
() => { window.open(Cloud.getServiceUrl() + lnk) });
var m = new ModalDialog();
m.add(div("wall-dialog-header", lf("Create apps at TouchDevelop.com")));
m.addHTML("With TouchDevelop you can create and publish apps. "+
"You can also explore, use and edit apps published by others.");
m.addHTML("TouchDevelop was brought to you by Microsoft Research and runs on " +
"Windows, Windows Phone, Mac, Linux, iPhone, iPad, Android, etc. (and soon your toaster :-)");
if (webAppId) {
m.add(div("wall-dialog-buttons",
link("tweak this web app", "/" + webAppId),
link("explore TouchDevelop", "/app")));
} else {
m.add(div("wall-dialog-buttons",
link("run TouchDevelop now!", "/app")));
}
m.show();
})
}
}
function initEditor() {
SizeMgr.earlyInit();
document.onkeypress = Util.catchErrors("documentKeyPress", (e) => KeyboardMgr.instance.processKey(e));
document.onkeydown = Util.catchErrors("documentKeyDown", (e) => KeyboardMgr.instance.processKey(e));
document.onkeyup = Util.catchErrors("documentKeyUp", (e) => KeyboardMgr.instance.keyUp(e));
document.onselectstart = () => { return <any> false; };
window.onunload = () => {
};
function saveState() {
Ticker.saveCurrent()
RT.Perf.saveCurrent();
}
(<any>window).tdevSaveState = saveState;
var e = elt("loading");
if (e) e.removeSelf();
var r = divId("scriptEditor", null,
divId("wallOverlay", null));
elt("root").appendChild(r);
}
function initWebRunnerApis() {
// api keys needed to make maps work
TDev.RT.ApiManager.bingMapsKey = 'AsnQk63tYReqttLHcIL1RUsc_0h0BwCOib6j0Zvk8QjWs4FQjM9JRM9wEKescphX';
TDev.RT.ArtCache.isArtResource = (url: string) => false; // disable art caching
TDev.RT.ApiManager.getKeyAsync = function (url: string): Promise { return Promise.as(undefined); }
TDev.RT.AdManager.initialize = (el) => el.style.display = 'none'; // do not show adds
//TDev.RT.Web.create_request = function (url: string): TDev.RT.WebRequest { return TDev.RT.WebRequest.mk(url, undefined);};
//TDev.RT.Web.proxy = function (url: string): string { return url; }
TDev.RT.BingServices.searchAsync = function (
kind: string,
query: string,
loc: TDev.RT.Location_): Promise { return Promise.as([]); };
(<any>TDev.RT.Languages).picture_to_text = function picture_to_text(lang: string, pic: TDev.RT.Picture, r: ResumeCtx) { r.resumeVal(undefined); }
if (!Browser.webRunner) {
TDev.RT.Sound.patchLocalArtUrl = function (url: string): string {
if (/\.\/art\//.test(url) && !/\.(wav|m4a)\?a=/.test(url)) {
// adding 'a' argument to trigger caching
url = url + (Browser.audioWav ? '.wav' : '.m4a') + "?a=" + (<any>window).webAppGuid;
url = Util.toAbsoluteUrl(url); // enable local caching of these sounds
Util.log('patched local art sound: ' + url);
}
return url;
}
TDev.RT.Picture.patchLocalArtUrl = function (url: string): string {
if (/\.\/art\//.test(url) && !/\?a=/.test(url)) {
// adding 'a' argument to trigger caching
url += "?a=" + (<any>window).webAppGuid;
url = Util.toAbsoluteUrl(url); // enable local caching of these pictures
Util.log('patched local art picture: ' + url);
}
return url;
}
}
// office mix
if (TDev.Browser.webAppImplicit) {
// do not ask the authenticate
var auth = TDev.Cloud.authenticateAsync;
TDev.Cloud.authenticateAsync = (reason: string) => {
if (reason == "leaderboard")
return Promise.as(!Cloud.isAccessTokenExpired());
return auth(reason);
}
}
}
function initCompiledApp() {
TDev.RuntimeSettings.askSourceAccess = false;
TDev.Cloud.authenticateAsync = (reason:string) => Promise.as(true);
TDev.RT.ArtCache.isArtResource = (url: string) => false; // disable art caching
TDev.RT.ApiManager.getKeyAsync = (url: string): Promise => Promise.as(TDev.RT.ApiManager.keys[url] || undefined);
TDev.RT.BingServices.searchAsync = function (
kind: string,
query: string,
loc: TDev.RT.Location_): Promise {
if (!TDev.RT.ApiManager.bingSearchKey) {
TDev.RT.Time.log('Missing API Key for Bing search. Please edit /js/apikeys.js.');
return Promise.as([]);
}
var url = 'https://api.datamarket.azure.com/Bing/Search/v1/'
+ encodeURIComponent(kind) + "?Adult='Strict'&Query=" + encodeURIComponent("'" + query + "'");
if (loc) {
url += '&Latitude=' + encodeURIComponent(loc.latitude().toString()) + '&Longitude=' + encodeURIComponent(loc.longitude().toString());
}
var request = TDev.RT.WebRequest.mk(url, undefined);
request.set_credentials('', TDev.RT.ApiManager.bingSearchKey);
return request.sendAsync()
.then((response) => {
var r: TDev.RT.BingSearchResult[] = [];
var feed = TDev.RT.Web.feed(response.content());
for (var i = 0; i < feed.count(); ++i) {
var msg = feed.at(i);
var kind = msg.title();
var values = msg.values();
if (/^webresult$/i.test(kind)) {
r.push({
name: values.at('Title'),
url: values.at('Url'),
thumbUrl: null,
web: values.at('DisplayUrl')
});
} else if (/^imageresult$/i.test(kind)) {
r.push({
name: values.at('Title'),
url: values.at('MediaUrl'),
thumbUrl: null,
web: values.at('Url')
});
} else if (/^newsresult/i.test(kind)) {
var dt = Date.parse(values.at('Date'));
r.push({
name: values.at('Title') + ' - ' + values.at('Source') + ' - ' + Util.timeSince(dt),
url: values.at('Url'),
thumbUrl: null,
web: null
});
}
}
return r;
});
};
(<any>TDev.RT.Languages).translate = function translate(source_lang: string, target_lang: string, text: string, r : ResumeCtx)//: string
{
Util.log('translate: called');
if (!target_lang) {
TDev.RT.Time.log('languages->translate: no target language');
r.resumeVal(undefined);
return;
}
if (!text || source_lang === target_lang) {
r.resumeVal(text);
return;
}
if (!TDev.RT.ApiManager.microsoftTranslatorKey) {
TDev.RT.Time.log('Missing Microsoft Translator API Key.');
r.resumeVal(undefined);
return;
}
Util.log('translating text...');
var url = 'https://api.datamarket.azure.com/Bing/MicrosoftTranslator/v1/Translate?'
+ 'Text=%27' + encodeURIComponent(text) + "%27"
+ '&To=%27' + encodeURIComponent(target_lang) + "%27";
if (source_lang)
url += '&From=%27' + encodeURIComponent(source_lang) + "%27";
TDev.RT.Time.log('languages->translate: sending request');
var request = TDev.RT.WebRequest.mk(url, undefined);
request.set_credentials('', TDev.RT.ApiManager.microsoftTranslatorKey);
request.sendAsync()
.done((response: TDev.RT.WebResponse) => {
var translated = TDev.RT.Web.feed(response.content());
if (translated && translated.count() > 0) {
var tr = translated.at(0);
var rts = tr.values().at('Text');
r.resumeVal(rts);
return;
}
r.resumeVal(undefined);
}, e => {
TDev.RT.Time.log('error while translating');
r.resumeVal(undefined);
});
};
(<any>TDev.RT.Languages).detect_language = function detect_language(text: string, r: ResumeCtx) //: string
{
if (!text) {
r.resumeVal(undefined);
return;
}
if (!TDev.RT.ApiManager.microsoftTranslatorKey) {
TDev.RT.Time.log('Missing Microsoft Translator API Key. Please edit /js/apikeys.js.');
r.resumeVal(undefined);
return;
}
var url = 'https://api.datamarket.azure.com/Bing/MicrosoftTranslator/v1/Detect?'
+ 'Text=%27' + encodeURIComponent(text) + "%27";
var request = TDev.RT.WebRequest.mk(url, undefined);
request.set_credentials('', TDev.RT.ApiManager.microsoftTranslatorKey);
request.sendAsync()
.done((response: TDev.RT.WebResponse) => {
var translated = TDev.RT.Web.feed(response.content());
if (translated && translated.count() > 0) {
var tr = translated.at(0);
var code = tr.values().at('Code');
r.resumeVal(code);
return;
}
r.resumeVal(undefined);
}, e => {
TDev.RT.Time.log('error while detecting language');
r.resumeVal(undefined);
});
};
TDev.RT.MicrosoftTranslator.speak = function (lang, text) {
if (lang.length == 0 || text.length == 0) return undefined;
if (!TDev.RT.ApiManager.microsoftTranslatorClientId ||
!TDev.RT.ApiManager.microsoftTranslatorClientSecret) {
TDev.RT.Time.log('Missing Microsoft Translator Client ID and Client Secret. Please edit /js/apikeys.js.');
return undefined;
}
var url = 'http://api.microsofttranslator.com/V2/Http.svc/Speak?'
+ 'language=' + encodeURIComponent(lang)
+ '&text=' + encodeURIComponent(text)
+ '&format=' + encodeURIComponent('audio/mp3')
+ '&options=MaxQuality';
var snd = TDev.RT.Sound.mk(url, TDev.RT.SoundUrlTokenDomain.MicrosoftTranslator, 'audio/mp4');
return snd;
}
}
function initAsync() : Promise
{
if (RT.Wab)
return RT.Wab.initAsync().then(() => init2Async());
else
return init2Async();
}
function init2Async() : Promise
{
Ticker.disable();
tick(Ticks.mainInit);
Util.initHtmlExtensions();
Util.initGenericExtensions();
initEditor();
RT.RTValue.initApis();
if (Browser.webRunner)
initWebRunnerApis();
else if (Browser.isCompiledApp)
initCompiledApp();
var h = new HistoryMgr();
window.addEventListener("hashchange", Util.catchErrors("hashchange", function (ev) {
h.hashChange();
}), false);
window.addEventListener("popstate", Util.catchErrors("popState", function (ev) {
if (h.popState) {
h.popState(ev);
}
}), false);
runnerHost = new RunnerHost();
runnerHost.currentGuid = (<any>window).webAppGuid || "6B4CD5BD-8C23-458D-9422-E329520060AE";
window.addEventListener("resize", Util.catchErrors("windowResize", () => {
SizeMgr.applySizes();
runnerHost.currentRt.forcePageRefresh();
runnerHost.publishSizeUpdate();
}));
return runnerHost.runAsync();
}
function initJs()
{
window.onload = Util.catchErrors("windowOnLoad", () => {
initAsync().done();
});
}
export function globalInit()
{
window.onerror = (errMsg, url, lineNumber) => {
Util.log("error " + lineNumber + ":" + errMsg);
return false;
};
if ((<any>TDev).isWebserviceOnly) {
Util.navigateInWindow((<any>window).errorUrl + "#webservice")
return;
}
TDev.Browser.isCompiledApp = true;
TDev.Browser.detect();
Ticker.fillEditorInfoBugReport = (b: BugReport) => {
try {
b.currentUrl = "runner";
b.scriptId = "";
b.userAgent = Browser.isNodeJS ? "node" : window.navigator.userAgent;
b.resolution = "";
} catch (e) {
//debugger;
}
};
Runtime.initialUrl = document.URL
if (Browser.isNodeJS) {
if ((<any>TDev.RT).Node)
((<any>TDev.RT).Node).setup()
} else if (Browser.inCordova) {
TDev.RT.Cordova.setup(() => initAsync().done())
} else {
initJs();
}
}
export module RunnerSettings
{
// specified when baking the app
var _appid: string = undefined;
var _storeid: string = undefined;
export var showTermsOfUse = false;
export var showFeedback = false;
export var showSettings = false;
export var privacyStatement = "";
export var privacyStatementUrl = "";
export var termsOfUse = "";
export var termsOfUseUrl = "";
export var title = "";
export var author = "";
export var description = "";
export var isGame = false;
export function agreeTermsAsync(): Promise {
Util.log('checking agreed terms...');
if (!termsOfUseUrl && !privacyStatementUrl) return Promise.as();
var agreed = !!RuntimeSettings.readSetting("td.agreed.termsofuse");
if (agreed) return Promise.as();
Util.log('asking user to agree terms...');
return new Promise((onSuccess, onError, onProgress) => {
var m = new ModalDialog();
m.onDismiss = () => {
RuntimeSettings.storeSetting("td.agreed.termsofuse", true);
onSuccess(undefined);
}
m.canDismiss = false;
m.fullWhite();
m.addHTML("<div class='wall-dialog-body'>To use this app, you must agree to the <a href='" + RunnerSettings.termsOfUseUrl + "'>Terms of use</a> and <a href='" + RunnerSettings.termsOfUseUrl + "'>Privacy Statement</a>.</div>");
m.add(div('wall-dialog-buttons', HTML.mkButton(lf("I Agree"), () => {
m.canDismiss = true;
m.dismiss();
})));
m.show();
});
}
export function showPrivacyStatementDialog()
{
var m = new ModalDialog();
m.add(div('wall-dialog-header', 'privacy statement'));
m.add(div('wall-dialog-body', RunnerSettings.privacyStatement));
m.setScroll()
m.addOk();
m.show();
}
export function showTermsOfUseDialog()
{
var m = new ModalDialog();
m.add(div('wall-dialog-header', 'terms of use'));
m.add(div('wall-dialog-body', RunnerSettings.termsOfUse));
m.setScroll()
m.addOk();
m.show();
}
export function showAboutDialog() {
var m = new ModalDialog();
m.add(div('wall-dialog-header', 'about'));
m.add(div('wall-dialog-body', RunnerSettings.title));
m.add(div('wall-dialog-body', 'by ' + RunnerSettings.author));
m.add(div('wall-dialog-body', RunnerSettings.description));
m.setScroll()
m.addOk();
m.show();
}
export function showSettingsDialog() {
var locationcb = HTML.mkCheckBox(lf("access and use your location"), (b) => RuntimeSettings.setLocation(b), RuntimeSettings.location());
var soundcb = HTML.mkCheckBox(lf("sounds"), (b) => RuntimeSettings.setSounds(b), RuntimeSettings.sounds());
var m = new ModalDialog();
m.add(div('wall-dialog-header', lf("settings")));
m.add(div('wall-dialog-body', locationcb));
m.add(div('wall-dialog-body', soundcb));
m.addOk();
m.show();
}
var _launchReported = false;
export function reportLaunch() {
if (_appid && window.navigator.onLine && !_launchReported) {
TDev.RT.Bazaar.storeidAsync()
.done((sid : string) => {
if (sid) {
// analytics
var url = Cloud.getPrivateApiUrl("app/" + encodeURIComponent(_appid)
+ "/" + encodeURIComponent(userid())
+ "/launch/" + encodeURIComponent(sid) + "?store=" + encodeURIComponent(_storeid));
Util.log('runner: report launch ' + url);
var req = TDev.RT.WebRequest.mk(url, undefined);
req.show_notifications(false);
req.sendAsync().done(() => {
_launchReported = true;
}, e => {
Util.log('runner: launch report failed');
});
}
}, e => {});
}
}
export function launch(appid: string, storeid: string) {
_appid = appid || "invalid";
_storeid = storeid || "unknown";
RunnerSettings.reportLaunch();
// override leaderboards
(<any>TDev.RT.Bazaar).loadLeaderboardItemsAsync = function (scriptId: string) {
return Promise.as([]);
};
(<any>TDev.RT.Bazaar).leaderboard_score = function (r: ResumeCtx) {
if (window.navigator.onLine && (Browser.webRunner || Browser.webAppImplicit)) {
var url = Cloud.getPrivateApiUrl("app/" + encodeURIComponent(_appid)
+ "/" + encodeURIComponent(userid()) + "/leaderboardscored");
var req = TDev.RT.WebRequest.mk(url, undefined);
req.sendAsync()
.done((resp: TDev.RT.WebResponse) => {
updateScoreFromResponse(resp.content_as_json());
r.resumeVal(RunnerSettings.score());
}, (e) => r.resumeVal(RunnerSettings.score()));
}
else
r.resumeVal(RunnerSettings.score());
};
(<any>TDev.RT.Bazaar).post_leaderboard_score = function (score: number, r: ResumeCtx) {
var curr = RunnerSettings.score();
if (score < curr) {
r.resume();
}
else {
setScore(score);
if (window.navigator.onLine && (Browser.webRunner || Browser.webAppImplicit)) {
var url = Cloud.getPrivateApiUrl("app/" + encodeURIComponent(_appid)
+ "/" + encodeURIComponent(userid()) + "/leaderboard");
var req = TDev.RT.WebRequest.mk(url, undefined);
req.set_method('post');
req.set_content(JSON.stringify({ kind: "leaderboardscore", score: score }));
req.sendAsync()
.done((resp: TDev.RT.WebResponse) => {
updateScoreFromResponse(resp.content_as_json());
r.resume();
}, (e) => r.resume());
} else {
r.resume();
}
}
};
(<any>TDev.RT.Bazaar).post_leaderboard_to_wall = function (r: ResumeCtx) //: void
{
if (!Browser.webRunner && !Browser.webAppImplicit) {
TDev.RT.App.restart("", r);
return;
}
var leaderboardDiv = div('item', [div('item-title', 'leaderboards')]);
var rt = r.rt;
if (window.navigator.onLine) {
r.progress(lf("Loading leaderboards..."));
var url = Cloud.getPrivateApiUrl("app/" + encodeURIComponent(_appid) + "/" + encodeURIComponent(userid()) + "/leaderboard");
var request = TDev.RT.WebRequest.mk(url, undefined);
request.sendAsync()
.done((response: TDev.RT.WebResponse) =>
{
var json = response.content_as_json();
if (json) {
var items = json.field('items');
if (items) {
for (var i = 0; i < items.count(); ++i) {
var item = items.at(i);
if (item.string('kind') === 'leaderboardscore') {
var userid = item.string('userid');
var username = item.string('username');
var userscore = (item.number('score') || 0).toString();
var time = Util.timeSince(item.number('time'));
var imgDiv = div('leaderboard-img');
imgDiv.innerHTML = TDev.Util.svgGravatar(userid);
var scoreDiv = div('item leaderboard-item', [
imgDiv,
div('leaderboards-score', userscore),
div('leaderboard-center', [
div('item-title', username),
div('item-subtle', time)
])
]);
leaderboardDiv.appendChild(scoreDiv);
}
}
}
}
rt.postBoxedHtml(leaderboardDiv, rt.current.pc);
r.resume();
});
} else {
rt.postText(lf("Please connect to internet to see the leaderboard."), rt.current.pc);
r.resume();
}
}
}
function updateScoreFromResponse(response : TDev.RT.JsonObject) {
if (response) {
var score = response.number('score');
if (score)
RunnerSettings.setScore(score);
}
}
export function score() : Number {
return Number(RuntimeSettings.readSetting("td.score") || 0);
}
export function setScore(score: Number) {
if (score > RunnerSettings.score())
RuntimeSettings.storeSetting("td.score", score);
}
export function userid() : string {
var userid = RuntimeSettings.readSetting("td.userid");
if (!userid) {
// need new user id
userid = TDev.Util.guidGen();
RuntimeSettings.storeSetting("td.userid", userid);
}
return userid;
}
}
}
TDev.globalInit();