570 строки
19 KiB
TypeScript
570 строки
19 KiB
TypeScript
///<reference path='refs.ts'/>
|
|
module TDev
|
|
{
|
|
function fetchConfigAsync()
|
|
{
|
|
if (Cloud.lite && !Cloud.config.liteVersion) {
|
|
var storeCfg = r => Object.keys(r).forEach(k => Cloud.config[k] = r[k]);
|
|
var p = Cloud.getPublicApiAsync("clientconfig")
|
|
.then(r => {
|
|
localStorage['clientconfig'] = JSON.stringify(r)
|
|
storeCfg(r)
|
|
})
|
|
|
|
if (localStorage['clientconfig']) {
|
|
storeCfg(JSON.parse(localStorage['clientconfig']))
|
|
p.done()
|
|
return Promise.as()
|
|
} else return p.then(() => {}, e => { Util.log("cannot download client config: " + e.message) });
|
|
}
|
|
else return Promise.as()
|
|
}
|
|
|
|
function initEditorAsync()
|
|
{
|
|
SizeMgr.earlyInit();
|
|
|
|
Util.log("initialize editor");
|
|
TheLoadingScreen = new LoadingScreen();
|
|
TheEditor = new Editor();
|
|
Browser.TheHost = new Browser.Host();
|
|
Browser.TheHub = new Browser.Hub();
|
|
Browser.TheApiCacheMgr = new Browser.ApiCacheMgr();
|
|
|
|
allScreens = [TheEditor, Browser.TheHost, Browser.TheHub, TheLoadingScreen];
|
|
|
|
SVG.loadScriptIcons(ScriptIcons.getScriptIcons());
|
|
TDev.Browser.EditorSettings.init();
|
|
|
|
Util.log("initialize api cache");
|
|
return Promise.as()
|
|
.then(() => LocalProxy.updateShellAsync())
|
|
.then(() => LocalProxy.loadCachesAsync())
|
|
.then(() => fetchConfigAsync())
|
|
.then(() => Browser.TheApiCacheMgr.initAsync())
|
|
.then(() => {
|
|
initScreens();
|
|
|
|
var upd:HTMLElement = null;
|
|
|
|
if (window.localStorage["lastExceptionMessage"]) {
|
|
var msg = window.localStorage["lastExceptionMessage"];
|
|
window.localStorage["lastExceptionMessage"] = "";
|
|
if (TDev.Browser.EditorSettings.widgets().notifyAppReloaded)
|
|
upd = div("app-updated", lf("Something went wrong and we reloaded the app"));
|
|
}
|
|
|
|
if (!upd && window.localStorage["appUpdated"]) {
|
|
window.localStorage["appUpdated"] = "";
|
|
if (dbg)
|
|
upd = div("app-updated", lf("The TouchDevelop app has been updated."));
|
|
}
|
|
|
|
if (upd) {
|
|
elt("root").appendChild(upd)
|
|
upd.withClick(() => { upd.removeSelf() })
|
|
Util.setTimeout(10000, () => { upd.removeSelf() })
|
|
}
|
|
|
|
TheEditor.historyMgr.initialHash();
|
|
|
|
// needs to be done again after login
|
|
Browser.TheApiCacheMgr.initWebsocketAsync().done();
|
|
|
|
window.addEventListener("message", event => {
|
|
if (External.TheChannel)
|
|
External.TheChannel.receive(event);
|
|
});
|
|
});
|
|
}
|
|
|
|
function onlyOneTab()
|
|
{
|
|
// implicit web apps don't use the database to
|
|
// avoid multiple tabs issues
|
|
if (!TDev.Storage.temporary) {
|
|
// to avoid races with database storage,
|
|
// detect multiple tabs and prevent it
|
|
var id = Random.uniqueId();
|
|
window.localStorage["currentTabId"] = id;
|
|
window.setInterval(() => {
|
|
if (window.localStorage["currentTabId"] != id && !Util.navigatingAway) {
|
|
Util.navigateInWindow((<any>window).errorUrl + "#oneTab");
|
|
}
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
function initScreens():void
|
|
{
|
|
allScreens.forEach((s) => s.init());
|
|
|
|
// Debug.enableFirstChanceException(true);
|
|
|
|
window.addEventListener("resize", Util.catchErrors("windowResize", function () {
|
|
SizeMgr.applySizes();
|
|
|
|
}));
|
|
|
|
window.addEventListener("hashchange", Util.catchErrors("hashchange", function (ev) {
|
|
TheEditor.historyMgr.hashChange();
|
|
}), false);
|
|
|
|
window.addEventListener("popstate", Util.catchErrors("popState", function (ev) {
|
|
if (TheEditor.historyMgr.popState) {
|
|
TheEditor.historyMgr.popState(ev);
|
|
}
|
|
}), false);
|
|
|
|
document.onkeypress = Util.catchErrors("documentKeyPress", (e) => TheEditor.keyMgr.processKey(e));
|
|
document.onkeydown = Util.catchErrors("documentKeyDown", (e) => TheEditor.keyMgr.processKey(e));
|
|
document.onkeyup = Util.catchErrors("documentKeyUp", (e) => TheEditor.keyMgr.keyUp(e));
|
|
|
|
function saveState() {
|
|
TheEditor.saveStateAsync({ forReal: true }).done();
|
|
Browser.TheApiCacheMgr.save();
|
|
Ticker.saveCurrent()
|
|
RT.Perf.saveCurrentAsync().done();
|
|
}
|
|
|
|
(<any>window).tdevSaveState = saveState;
|
|
window.onunload = saveState;
|
|
|
|
/*
|
|
// bad idea for development - refresh key triggers that
|
|
if (false) {
|
|
window.onbeforeunload = (e) => {
|
|
var s = "Any changes will be lost.";
|
|
if (e) {
|
|
e.returnValue = s;
|
|
}
|
|
return s;
|
|
};
|
|
}
|
|
*/
|
|
|
|
if (Browser.mobileWebkit) {
|
|
window.scrollTo(0,1);
|
|
}
|
|
SizeMgr.applySizes();
|
|
|
|
var appCache = window.applicationCache;
|
|
|
|
function markUpdate()
|
|
{
|
|
tick(Ticks.appUpdateAvailable);
|
|
Browser.Host.updateIsWaiting = true;
|
|
}
|
|
|
|
(<any>window).tdevMarkRefresh = markUpdate;
|
|
if (appCache.status == appCache.UPDATEREADY) {
|
|
markUpdate();
|
|
tick(Ticks.appQuickUpdate)
|
|
Browser.Host.tryUpdate();
|
|
return;
|
|
}
|
|
|
|
appCache.addEventListener('updateready', () => {
|
|
if (appCache.status == appCache.UPDATEREADY) {
|
|
markUpdate();
|
|
} else {
|
|
tick(Ticks.appNoUpdate);
|
|
}
|
|
}, false);
|
|
|
|
onlyOneTab();
|
|
}
|
|
|
|
export function initAsync(): Promise {
|
|
Util.log("baseUrl0: {0}", baseUrl);
|
|
Util.log("userAgent: {0}", window.navigator.userAgent);
|
|
Util.log("browser: {0}/{1}/{2}", Browser.browserShortName, Browser.browserVersion, Browser.browserVersion2);
|
|
if (Browser.isWebkit) {
|
|
Util.log("Browser: webkit/{0}", Browser.webkitVersion);
|
|
if (Browser.isMobileSafari) Util.log("Browser: mobileSafari");
|
|
}
|
|
|
|
if ((<any>window.navigator).standalone) Util.log("standalone");
|
|
statusMsg("page loaded, initalizing");
|
|
if (RT.Wab)
|
|
return RT.Wab.initAsync().then(() => init2Async());
|
|
else
|
|
return init2Async();
|
|
}
|
|
|
|
function init2Async(): Promise {
|
|
Util.initHtmlExtensions();
|
|
Util.initGenericExtensions();
|
|
|
|
Util.sendPendingBugReports();
|
|
|
|
Util.log("baseUrl: {0}", baseUrl);
|
|
|
|
var localStorage = window.localStorage;
|
|
var experimentalVersion = "8";
|
|
if (localStorage["experimentalVersion"] != experimentalVersion) {
|
|
Util.log("updating local storage, '{0}' to '{1}'", localStorage["experimentalVersion"], experimentalVersion);
|
|
return Storage.clearAsync().then(() => { // hard reset of all storage
|
|
Util.log("storage clear");
|
|
localStorage["experimentalVersion"] = experimentalVersion;
|
|
return initCoreAsync();
|
|
});
|
|
}
|
|
|
|
return initCoreAsync();
|
|
}
|
|
|
|
function initCoreAsync()
|
|
{
|
|
Util.log("setting up flags and knobs");
|
|
|
|
var url = document.URL;
|
|
// in debuggerExceptions mode erros are displayed inline in the page, not in window.alert() kind of thing
|
|
// the "debugger" statement in the handler is also not triggered - we're are assuming the debugger
|
|
// was attached to begin with and it caught the exception
|
|
if (/debuggerExceptions/.test(url)) debuggerExceptions = true;
|
|
if (/withTracing/.test(url)) withTracing = true;
|
|
if (/dbg=[1t]/.test(url) || window.localStorage["dbg"]) dbg = true;
|
|
if (/nodbg/.test(url)) dbg = false;
|
|
if (/enableUndo/.test(url)) TDev.Collab.enableUndo = true;
|
|
if (/nohub/.test(url)) TDev.noHub = true;
|
|
if (/lowMemory/.test(url)) Browser.lowMemory = true;
|
|
|
|
//if (/endKeywords/.test(url)) Renderer.useEndKeywords = true;
|
|
if (/lfDebug/.test(url)) Util.translationDebug = true;
|
|
if (Browser.noStorage || /temporaryStorage/.test(url)) {
|
|
Browser.supportMemoryTable(true);
|
|
Storage.temporary = true;
|
|
}
|
|
if (/noAnim/.test(url)) {
|
|
Browser.noAnimations = true;
|
|
}
|
|
|
|
var m = /lang=([a-zA-Z\-]+)/.exec(url)
|
|
if (m) {
|
|
Util.setTranslationLanguage(m[1])
|
|
} else if (!Util.loadUserLanguageSetting()) {
|
|
var lang = window.navigator.language || window.navigator.userLanguage
|
|
if (lang)
|
|
Util.setTranslationLanguage(lang)
|
|
}
|
|
|
|
if (Math.random() < 0.05 || /translationTracking/.test(url))
|
|
Util.enableTranslationTracking()
|
|
|
|
if (/localTranslationTracking/.test(url))
|
|
Util.enableTranslationTracking(true)
|
|
|
|
var m = /translationTracking=([a-zA-Z0-9]+)/.exec(url)
|
|
if (m) {
|
|
Util.enableTranslationTracking()
|
|
Util.translationToken = m[1]
|
|
}
|
|
|
|
Revisions.parseUrlParameters(url);
|
|
|
|
if ((<any>window).betaFriendlyId)
|
|
isBeta = true;
|
|
if (dbg || /localhost/.test(url))
|
|
isBeta = true;
|
|
|
|
if (/nobeta/.test(url))
|
|
isBeta = false;
|
|
|
|
if (isBeta) {
|
|
//Browser.compilerBlockChaining = true;
|
|
//Browser.compilerInlining = true;
|
|
}
|
|
|
|
Ticker.init()
|
|
RT.Perf.init(TDev.AST.Compiler.version, Cloud.currentReleaseId);
|
|
|
|
tick(Ticks.mainInit);
|
|
|
|
AST.Lexer.init();
|
|
|
|
var appCache = window.applicationCache;
|
|
function logAppCacheEvent(ev:Event) {
|
|
Ticker.dbg("app cache event: {0}, status={1}", ev.type, appCache.status);
|
|
}
|
|
[ 'cached', 'checking', 'downloading', 'error', 'noupdate', 'obsolete', 'progress', 'updateready'
|
|
].forEach((ev) => appCache.addEventListener(ev, logAppCacheEvent, false));
|
|
|
|
World.getScriptMeta = (script) => {
|
|
var s = AST.Parser.parseScript(script);
|
|
return s.toMeta();
|
|
};
|
|
|
|
World.mergeScripts = (o, a, b) => AST.mergeScripts(o, a, b).serialize();
|
|
World.sanitizeScriptTextForCloud = AST.App.sanitizeScriptTextForCloud;
|
|
|
|
var onBoxSelected = () => {
|
|
if (!(currentScreen instanceof Editor)) return;
|
|
|
|
var editor: Editor = <Editor>currentScreen;
|
|
var box = LayoutMgr.instance.getSelectedBox();
|
|
if (box === null) return;
|
|
var id = box.getAstNodeId();
|
|
|
|
// Live view in the code editor
|
|
if (!editor.isWallVisible()) {
|
|
if (id)
|
|
editor.goToNodeId(id);
|
|
|
|
// Paused view on the main wall
|
|
} else {
|
|
LayoutMgr.instance.showBoxMenu(() => {
|
|
if (id) editor.goToNodeId(id);
|
|
LayoutMgr.instance.hideBoxMenu();
|
|
});
|
|
}
|
|
}
|
|
LayoutMgr.instance.onBoxSelected = onBoxSelected;
|
|
|
|
var onRendered = () => {
|
|
if (!(currentScreen instanceof Editor)) return;
|
|
|
|
var editor: Editor = <Editor>currentScreen;
|
|
var rt = editor.currentRt;
|
|
|
|
// Live view in the code editor
|
|
if (!editor.isWallVisible()) {
|
|
LayoutMgr.instance.highlightSelectedBox(); // GUI selection
|
|
LayoutMgr.instance.highlightRelatedBoxes(); // Code selection
|
|
|
|
// Paused view on the main wall
|
|
} else if (rt.isStopped()) {
|
|
LayoutMgr.instance.highlightSelectedBox(); // GUI selection
|
|
LayoutMgr.instance.refreshBoxMenu(); // Box edit menu
|
|
}
|
|
|
|
// Otherwise, program is running on the main wall
|
|
}
|
|
LayoutMgr.instance.onRendered = onRendered;
|
|
|
|
Util.log("initialize apis");
|
|
api.initFrom();
|
|
ArtEditor.initEditors();
|
|
|
|
try {
|
|
var testProto = div(null, "test");
|
|
} catch (e) {
|
|
Util.reportError("protoError", e, false);
|
|
Util.setTimeout(1000, () => {
|
|
Util.navigateInWindow((<any>window).errorUrl + "#prototype");
|
|
})
|
|
return Promise.as();
|
|
}
|
|
|
|
if (/livelang/.test(url)) {
|
|
return Util.httpGetJsonAsync("https://touchdeveloptranslator.azurewebsites.net/api/Svc/export"
|
|
+ "?user=" + encodeURIComponent(Cloud.getUserId())
|
|
+ "&lang=" + encodeURIComponent(Util.getTranslationLanguage()))
|
|
.then(resp => {
|
|
if (resp && resp.translations) {
|
|
var tr = resp.translations[Util.getTranslationLanguage()]
|
|
if (tr) {
|
|
Util.setTranslationTable(tr)
|
|
return initEditorAsync()
|
|
}
|
|
}
|
|
HTML.showErrorNotification("cannot load language " + Util.getTranslationLanguage())
|
|
return initEditorAsync()
|
|
})
|
|
|
|
} else
|
|
return initEditorAsync();
|
|
}
|
|
|
|
function search(query: string)
|
|
{
|
|
Browser.TheHost.startSearch(query)
|
|
}
|
|
|
|
// return at most 5 results
|
|
function searchResultSuggestions(query: string) : string[]
|
|
{
|
|
return Browser.TheHost.quickSearch(query)
|
|
}
|
|
|
|
function searchPaneVisible(visible: boolean) {
|
|
// TODO: handle visibility changes
|
|
}
|
|
|
|
function initJs()
|
|
{
|
|
statusMsg("setting up load hook");
|
|
window.onload = Util.catchErrors("windowOnLoad", () => { initAsync().done(); });
|
|
}
|
|
|
|
function statusMsg(m:string)
|
|
{
|
|
if (/dbg/.test(document.URL)) {
|
|
var e = elt("statusMsg");
|
|
if (e) Browser.setInnerHTML(e, m);
|
|
}
|
|
}
|
|
|
|
export function updateLoop(id:string, msg:string)
|
|
{
|
|
Cloud.transientOfflineMode = true;
|
|
|
|
var updateCnt = 0;
|
|
var last = window.localStorage["lastForcedUpdate"]
|
|
if (last) {
|
|
var diff = Date.now() - parseInt(last)
|
|
if (diff < 8*3600*1000) {
|
|
HTML.showProgressNotification(msg + " failed");
|
|
return;
|
|
}
|
|
}
|
|
|
|
ProgressOverlay.lockAndShow(msg);
|
|
|
|
function checkUpdate() {
|
|
Browser.Host.tryUpdate();
|
|
if (updateCnt++ == 20) {
|
|
var bug = Ticker.mkBugReport("waitForUpdate", "Cannot update")
|
|
bug.jsUrl = "fake://" + id + "/main.js";
|
|
Util.sendErrorReport(bug);
|
|
}
|
|
if (updateCnt >= 30) {
|
|
ProgressOverlay.hide()
|
|
window.localStorage["lastForcedUpdate"] = Date.now()
|
|
ModalDialog.info("couldn't connect to cloud services",
|
|
msg + " failed; we are now using offline mode; make sure your internet connection working")
|
|
} else {
|
|
Util.setTimeout(1000, checkUpdate)
|
|
}
|
|
}
|
|
checkUpdate();
|
|
}
|
|
|
|
export function globalInit()
|
|
{
|
|
statusMsg("global init 0");
|
|
|
|
if ((typeof window == "object" && (<any>window).isWebWorker) || !(typeof window == "object" && typeof document == "object" && window.document == document)) {
|
|
isWebWorker = true;
|
|
Browser.isHeadless = true;
|
|
Plugins.initWebWorker();
|
|
return;
|
|
}
|
|
|
|
window.onerror = (errMsg:any, url, lineNumber) => {
|
|
if (errMsg == "Script error.") return true; // ignore cross domain errors
|
|
if (url == "chrome://global/content/bindings/videocontrols.xml") return true; // FF bug; ignore
|
|
if (errMsg == "InvalidStateError") return true; // FF "bug" when running in "private" method and one tries to access IndexedDB
|
|
Util.reportError(url + ":" + lineNumber, errMsg, false);
|
|
return true;
|
|
};
|
|
|
|
function waitForUpdate(id:string)
|
|
{
|
|
if (/releaseid=/.test(document.URL))
|
|
return; // this will never update
|
|
|
|
//if (!Cloud.isOnline()) return;
|
|
|
|
try {
|
|
window.applicationCache.update();
|
|
} catch (e) {
|
|
}
|
|
updateLoop(id, "updating the web app");
|
|
|
|
return true;
|
|
}
|
|
|
|
var mx = /lite=([0-9a-z\.]+)/.exec(document.URL)
|
|
|
|
if (mx && mx[1] != "0") {
|
|
Cloud.lite = true;
|
|
if (/\./.test(mx[1]))
|
|
Cloud.config.rootUrl = "https://" + mx[1]
|
|
else
|
|
Cloud.config.rootUrl = "http://" + mx[1] + ".cloudapp.net"
|
|
}
|
|
|
|
if ((<any>window).tdlite) {
|
|
Cloud.lite = true;
|
|
if ((<any>window).tdlite == "url") {
|
|
mx = /^(https?:\/\/[^\/]+)/.exec(document.URL);
|
|
Cloud.config.rootUrl = mx[1]
|
|
} else {
|
|
Cloud.config.rootUrl = (<any>window).tdlite;
|
|
}
|
|
var cfg = (<any>window).tdConfig
|
|
if (cfg) Object.keys(cfg).forEach(k => Cloud.config[k] = cfg[k])
|
|
}
|
|
|
|
if (Cloud.lite) (<any>window).rootUrl = Cloud.config.rootUrl;
|
|
|
|
|
|
if (/httplog=1/.test(document.URL)) {
|
|
HttpLog.enabled = true;
|
|
}
|
|
|
|
var ms = document.getElementById("mainScript");
|
|
if (ms && (<HTMLScriptElement>ms).src) {
|
|
Ticker.mainJsName = (<HTMLScriptElement>ms).src;
|
|
baseUrl = Ticker.mainJsName.replace(/[^\/]*$/, "");
|
|
var mm = /\/([0-9]{18}[^\/]*)/.exec(Ticker.mainJsName);
|
|
if (mm) {
|
|
Cloud.currentReleaseId = mm[1];
|
|
}
|
|
}
|
|
World.waitForUpdate = waitForUpdate;
|
|
|
|
statusMsg("global init 1");
|
|
|
|
Ticker.fillEditorInfoBugReport = (b:BugReport) => {
|
|
try {
|
|
b.currentUrl = TheEditor && TheEditor.historyMgr ? TheEditor.historyMgr.currentHash() : "";
|
|
b.scriptId = Script ? Script.localGuid : "";
|
|
b.userAgent = window.navigator.userAgent;
|
|
b.resolution = SizeMgr.windowWidth + "x" + SizeMgr.windowHeight;
|
|
b.platform = Browser.platformCaps;
|
|
b.worldId = Cloud.getWorldId();
|
|
if (TheEditor && TheEditor.undoMgr) {
|
|
var src = TheEditor.undoMgr.getScriptSource();
|
|
if (src)
|
|
b.attachments.push(src)
|
|
}
|
|
} catch (e) {
|
|
debugger;
|
|
}
|
|
};
|
|
Ticker.fillEditorInfoTicksReport = (b: TicksReport) => {
|
|
try {
|
|
b.worldId = Cloud.getWorldId();
|
|
} catch (e) {
|
|
debugger;
|
|
}
|
|
};
|
|
|
|
// init API keys
|
|
TDev.RT.ApiManager.bingMapsKey = 'AsnQk63tYReqttLHcIL1RUsc_0h0BwCOib6j0Zvk8QjWs4FQjM9JRM9wEKescphX';
|
|
TDev.RT.ApiManager.liveConnectClientId = '0000000040118292';
|
|
TDev.RT.ApiManager.liveConnectRedirectDomainId = 'cloudservices';
|
|
TDev.RT.ApiManager.liveConnectUserId = 'touchdevelop';
|
|
|
|
Browser.inEditor = true;
|
|
|
|
statusMsg("global init 2");
|
|
if (Browser.inCordova) {
|
|
// TODO: move all TD code inside of this handler, including browser.js
|
|
TDev.RT.Cordova.setup(() => {
|
|
statusMsg("global init deviceready");
|
|
initAsync().done();
|
|
});
|
|
} else if ((<any>window).browserSupported) {
|
|
statusMsg("global init 4");
|
|
initJs();
|
|
} else {
|
|
statusMsg("global init 5");
|
|
}
|
|
}
|
|
}
|
|
|
|
TDev.globalInit();
|