WebSockUrl = "8013/P22671-Demo-9302/v0/StateChanged"; RestUrl = "8000/P24999-Example/v0/"; //ABOVE AUTOGENERATED //lines above is inserted by Mint when starting web server; do noi delete line ABOVE AUTOGENERATE IsRequestDone = true; IsOffline = false; FailedCalls = 0; PoolingPeriod = 1500; function HandleFailedCall() { FailedCalls++; if (FailedCalls > 3) { setOffline(); } } function setOffline() { console.log('setting offline'); IsOffline = true; if (window.location.hash == "#offline") return; setHash('#offline'); } function setOnline() { FailedCalls = 0; IsOffline = false; if (window.location.hash != "#offline") return; setHash(""); } function prepareWebSocketAddress() { var a = window.location.origin; var ndx = a.indexOf(":") + 1; var afterHttp = a.substr(ndx, a.length - ndx); var ndx2 = afterHttp.indexOf(":"); cleaned = afterHttp; if (ndx2 != -1) { cleaned = afterHttp.substr(0, ndx2);//webpage url port isn't 80 and is included in url } var result = "ws:" + cleaned + ":" + WebSockUrl; return result; } function setHash(newHash) { $.mobile.navigate(newHash); } function moveMotorRelative(motorIndex, steps) { makeCorsRequest('Motors/TargetPositionRelative?id=' + motorIndex.toString(), 'PUT', steps.toString(), doNothing); } function doNothing(state) { } LastPerfCall = null; window.onload = function start() { //$("#MessagesInnerDiv").empty().append("OKK"); //var container = document.getElementById("MessagesInnerDiv"); //var content = container.innerHTML //container.innerHTML = "OKKKK"; var webSockAddress = prepareWebSocketAddress(); console.log(webSockAddress); //https://github.com/joewalnes/reconnecting-websocket WebSock = new ReconnectingWebSocket(webSockAddress); WebSock.debug = false; WebSock.timeoutInterval = 3000; WebSock.reconnectInterval = 2000; WebSock.onmessage = function (evt) { //auto-generated from C# static \Topas4\Web\Generators\WebSocketMessageIds.linq, todo put in build ? //todo move out from function WebSockIds = {}; WebSockIds["Shutter"] = 100; WebSockIds["Interlock"] = 110; WebSockIds["WavelengthOutputFullData"] = 202; var messages = $.parseJSON(evt.data).Messages; //console.log((performance.now() - LastPerfCall) + " ms"); for (var i = 0; i < messages.length; i++) { var m = messages[i]; var d = m.Data; switch (m.Id) { case WebSockIds["Shutter"]: ShutterVue.IsShutterOpen = d; MessagesVue.IsShutterOpen = d; break; case WebSockIds["Interlock"]: ShutterVue.IsInterlockOpen = d; MessagesVue.IsInterlockOpen = d; break; case WebSockIds["WavelengthOutputFullData"]: console.log("wl state"); WavelengthVue.updateWavelengthState(d.Output); break; } } }; WebSock.onclose = function (event) { console.log("WebSock is closed"); setOffline(); } WebSock.onopen = function (event) { console.log("WebSock is open"); LoadAllData();//maybe we missed something while there was no connection? setOnline(); }; LoadAllData(); initPooling(); } function LoadAllData() { //used when page is loaded and when websocket is reconnected to catch any changes makeCorsRequest('GeneralInformation/GeneralInformation', 'GET', '', setDeviceSerialNumber); MotorsVue.update();//build, no updates later ShutterVue.update();//initial state, later on websocket will push all notificationson shutter and interlock MessagesVue.update();//initial state, later on websocket will push all notificationson shutter and interlock WavelengthVue.update();//intitial state, later websocket AuthenticationVue.update(); } function setDeviceSerialNumber(state) { $("#SerialNumberHeader").text(state.SerialNumber); } function initPooling() { window.setInterval(function () { if (IsRequestDone == true) { IsRequestDone = false; if (AuthenticationVue.IsAuthenticated) { //if authenticated, websocket is used, todo move this loop to authentication } else { AuthenticationVue.update();//can't use websocket for unauthenticated devices, because wheter caller has access depends on caller } } }, PoolingPeriod); // repeat forever } MotorsVueClass = Vue.component('motors-class', { template: '#motors-template', data: function () { return { Index: 0, ActiveMotor: null, Motors: [] } } , computed: { MotorsByTwo: function () { var chunked = _.chunk(this.Motors, 2); return chunked; } }, methods: { setActiveMotor: function (index) { var matchIndex = _.findIndex(this.Motors, function (x) { return x.Index == index; }); this.Index = matchIndex; this.ActiveMotor = this.Motors[matchIndex]; try { $("#selectMotorDiv").panel("close"); //todo nasty, IE does not like at all and even does not work in IE } catch (err) { } }, moveMotorRelative: function (steps) { makeCorsRequest('Motors/TargetPositionRelative?id=' + this.ActiveMotor.Index.toString(), 'PUT', steps.toString(), doNothing); }, moveMotorAbsolute: function (position) { //named positions use steps makeCorsRequest('Motors/TargetPosition?id=' + this.ActiveMotor.Index.toString(), 'PUT', position.toString(), doNothing); }, constructMotors: function (state) { this.ActiveMotor = null; this.Motors.length = 0; for (i = 0; i < state.Motors.length; i++) { var m = state.Motors[i]; this.Motors.push(m); } makeCorsRequest('GeneralInformation/DefaultUserPreferences', 'GET', "", this.setUserPrefernces); }, setUserPrefernces: function (state) { for (var i = 0; i < state.Motors.length; i++) { var m = state.Motors[i]; var matches = _.filter(this.Motors, function (x) { return x.Index == m.Index; }); for (var k = 0; k < matches.length; k++) { //find matching motor var matchingMotor = matches[k]; matchingMotor.StepSize = m.SliderStepSize; if (m.ShowMotorAs == null) { matchingMotor.ShowNamed = false; } else { matchingMotor.ShowNamed = m.ShowMotorAs != "0";// todo C# serializes enum as int } } } for (i = 0; i < this.Motors.length; i++) { var m = this.Motors[i]; if (m.StepSize == null) m.StepSize = state.MotorSliderStepSizeFallback;//not all motors may have explicit step size if (m.ShowNamed == null) m.ShowNamed = false; if (m.ShowNamed) { console.log("show motor " + m.Index + " with name positions: " + m.NamedPositions.length); } } this.setActiveMotor(this.Motors[0].Index); }, update: function () { makeCorsRequest('Motors/', 'GET', "", this.constructMotors); } } }) ShutterVueClass = Vue.component('shutter-class', { template: '#shutter-template', data: function () { return { IsInterlockOpen: false, IsShutterOpen: true } } , methods: { switchShutter: function () { if (this.IsShutterOpen) { makeCorsRequest('ShutterInterlock/CloseShutter', 'PUT', "", doNothing); } else { LastPerfCall = performance.now(); makeCorsRequest('ShutterInterlock/OpenShutter', 'PUT', "", doNothing); } }, updateShutterState: function (state) { this.IsInterlockOpen = state.IsInterlockOpen; this.IsShutterOpen = state.IsShutterOpen; }, update: function () { makeCorsRequest('ShutterInterlock/State', 'GET', "", this.updateShutterState); } } }) PublicAPIVueClass = Vue.component('PublicAPI-class', { template: '#PublicAPI-template', data: function () { return { ShutterControl: "", WavelengthControl: "", MotorsControl: "", SmoothWavelengthSetter: "", Authentiction: "", SavedMotorPositions: "" } } , methods: { getUrlStart: function () { var urlStart = window.location.origin; var portIndex = urlStart.lastIndexOf(":"); if (portIndex > 5) { urlStart = urlStart.substring(0, portIndex); } var fullUrl = urlStart + ":" + RestUrl; return fullUrl; }, setUrls: function () { var u = this.getUrlStart(); this.ShutterControl = u + "PublicAPI/ShutterInterlock/help" this.WavelengthControl = u + "PublicAPI/Optical/WavelengthControl/help" this.MotorsControl = u + "PublicAPI/Motors/help" this.SmoothWavelengthSetter = u + "PublicAPI/SmoothWavelengthSetter/help" this.Authentiction = u + "PublicAPI/Authentication/help" this.SavedMotorPositions = u + "PublicAPI/SavedMotorPositions/help" }, openWavelengthLink: function () { this.openLink(this.WavelengthControl); }, openShutterLink: function () { this.openLink(this.ShutterControl); }, openAuthenticationLink: function () { this.openLink(this.Authentiction); }, openDocumentationLink: function () { this.openLink("http://topas4api.lightcon.com/current/index.html?utm_source=Software&utm_medium=Topas4WebApp"); }, openMotorsLink: function () { this.openLink(this.MotorsControl); }, openSmoothSetterLink: function () { this.openLink(this.SmoothWavelengthSetter); }, openSavedMotorPositionsLink: function () { this.openLink(this.SavedMotorPositions); }, openLink: function (url) { var win = window.open(url, '_blank'); win.focus(); } } }) MessagesVueClass = Vue.component('messages-class', { template: '#messages-template', data: function () { return { IsInterlockOpen: false, IsShutterOpen: true, MessagesMarkdown: "" } } , methods: { switchShutter: function () { if (this.IsShutterOpen) { makeCorsRequest('ShutterInterlock/CloseShutter', 'PUT', "", doNothing); } else { makeCorsRequest('ShutterInterlock/OpenShutter', 'PUT', "", doNothing); } }, updateShutterState: function (state) { this.IsInterlockOpen = state.IsInterlockOpen; this.IsShutterOpen = state.IsShutterOpen; }, messagesAreDone: function () { var options = { RestoreShutter: true }; makeCorsRequest('Optical/WavelengthControl/FinishOutputSettingAfterUserActions', 'PUT', JSON.stringify(options), doNothing); setHash('#main'); }, update: function () { makeCorsRequest('ShutterInterlock/State', 'GET', "", this.updateShutterState); } } }) WavelengthVueClass = Vue.component('wavelength-class', { template: '#wavelength-template', data: function () { return { IsWavelengthSettingInProgress: false, ChangesAreMine: false, Wavelength: 0, LastWavelength: -9 } } , methods: { setWavelength: function () { var data = { Wavelength: this.Wavelength, Interaction: "*" }; this.LastWavelength = this.Wavelength; makeCorsRequest('Optical/WavelengthControl/Output', 'PUT', JSON.stringify(data), this.checkSetWavelengthOk); this.ChangesAreMine = true; }, setWavelengthRelative: function (stepSize) { this.Wavelength += stepSize; this.setWavelength(); }, updateWavelengthState: function (state) { this.IsWavelengthSettingInProgress = state.IsWavelengthSettingInProgress; if (this.LastWavelength != state.Wavelength) {//if user is entering wavelength Vue value will be different and we will revert this.Wavelength = state.Wavelength; } this.LastWavelength = state.Wavelength; if (this.ChangesAreMine) { if (state.IsWavelengthSettingInProgress == false || state.IsWaitingForUserAction) { this.ChangesAreMine = false; } this.checkWavelength(state); } }, toHtml: function (markdown) { var md = window.markdownit(); return md.render(markdown); }, checkSetWavelengthOk: function (answer) { if (answer.IsSuccess == false) { this.update(); } }, checkWavelength: function (state) { if (state.IsWaitingForUserAction) { var md = window.markdownit(); var newM = _.filter(state.Messages, 'IsNew'); var nonHiddenNewM = _.filter(newM, function (x) { return x.Text.startsWith("//") == false; });//filter off hidden messages var hiddenMessages = _.filter(newM, function (x) { return x.Text.startsWith("//") == true; }); if (nonHiddenNewM.length > 0) { var messages = _.map(nonHiddenNewM, 'Text'); var markdowns = _.map(messages, this.toHtml); var result = markdowns.join("
"); result = result.substr(3, result.length - 3 - 5); setHash('#messages'); MessagesVue.MessagesMarkdown = result; console.log("messages are: " + this.MessagesMarkdown); //var mDiv = document.getElementById("MessagesInnerDiv"); //while (mDiv.firstChild) { // mDiv.removeChild(mDiv.firstChild); //} //var newdiv = document.createElement("div"); //newdiv.innerHTML = "whatever"; //mDiv.appendChild(newdiv); //$("#MessagesInnerDiv").empty().append(result);//does not work on IE11 } else if (hiddenMessages.length > 0) { //copy-paste from messages Vue var options = { RestoreShutter: true }; makeCorsRequest('Optical/WavelengthControl/FinishOutputSettingAfterUserActions', 'PUT', JSON.stringify(options), doNothing); } //if (result.IsSuccess && result.Messages.Any (x => x.IsNew)) { // UserActionsRequiredWavelengthCalculationResult = result; //} this.update(); } }, update: function () { makeCorsRequest('Optical/WavelengthControl/Output', 'GET', "", this.updateWavelengthState); }, } }) AuthenticationVueClass = Vue.component('authentication-class', { template: '#authentication-template', data: function () { return { IsAuthenticated: false, IsAuthenticationInProgress: false, IsWaitingForAuthenticationToStart: false, AuthenticationProgress: 0 } } , methods: { startAuthentication: function () { this.IsWaitingForAuthenticationToStart = true; makeCorsRequest('Authentication/StartAuthenticationByInterlock', 'POST', "", doNothing); window.setTimeout(this.update, 200); }, update: function () { makeCorsRequest('Authentication/AuthenticationStatus', 'GET', "", this.updateAuthentication); }, updateAuthentication: function (state) { this.IsAuthenticated = state.CallerHasAccess; this.IsAuthenticationInProgress = state.IsAuthenticationInProgress; if (state.IsAuthenticationInProgress) { this.IsWaitingForAuthenticationToStart = false; window.setTimeout(this.update, 80);//update often, so that bar fills up nicely } else { } this.AuthenticationProgress = state.AuthenticationProgressPercent; var authSection = '#authenticate'; if (this.IsAuthenticated == false && window.location.hash != authSection) { setHash(authSection); return; } else if (this.IsAuthenticated == true && window.location.hash == authSection) { setHash('#main'); return; } } } }) ShutterVue = new ShutterVueClass({ el: '#shutterDiv' }); WavelengthVue = new WavelengthVueClass({ el: '#wavelengthDiv' }); AuthenticationVue = new AuthenticationVueClass({ el: '#authenticationDiv' }); MessagesVue = new MessagesVueClass({ el: '#messagesDiv' }) MotorsVue = new MotorsVueClass({ el: '#motorsDiv' }); PublicAPIVue = new PublicAPIVueClass({ el: '#PublicAPIDiv' }); PublicAPIVue.setUrls(); // Create the XHR object. function createCORSRequest(method, url) { var xhr = new XMLHttpRequest(); var urlWithRandom = url; if (method == "GET") { if (url.indexOf("?") == -1) { urlWithRandom = url + "?time=" + new Date().getTime(); } else { urlWithRandom = url + "&time=" + new Date().getTime(); } } if ("withCredentials" in xhr) { // XHR for Chrome/Firefox/Opera/Safari. xhr.open(method, urlWithRandom, true); } else if (typeof XDomainRequest != "undefined") { // XDomainRequest for IE. xhr = new XDomainRequest(); xhr.open(method, urlWithRandom); } else { // CORS not supported. xhr = null; alert("Can't make Cors Ajax request. Please use another browser."); setOffline(); } xhr.timeout = 4000; return xhr; } // Helper method to parse the title tag from the response. function getTitle(text) { return text.match('(.*)?')[1]; } // Make the actual CORS request. function makeCorsRequest(url, method, payload, okCallback) { var urlStart = window.location.origin; var portIndex = urlStart.lastIndexOf(":"); if (portIndex > 5) { urlStart = urlStart.substring(0, portIndex); } var fullUrl = urlStart + ":" + RestUrl + url; var xhr = createCORSRequest(method, fullUrl, payload); if (!xhr) { alert('CORS not supported'); return; } //if (payload != "") xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xhr.setRequestHeader("Accept", "*/*"); xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); //xhr.setRequestHeader("Content-Type", "application/json;"); // Response handlers. xhr.onload = function () { IsRequestDone = true; setOnline(); if (xhr.responseText == null || xhr.responseText == "") { okCallback(null); return; } var d = null; try { d = JSON.parse(xhr.responseText); } catch (e) { throw e;//place for breakpoint } okCallback(d); }; xhr.ontimeout = function () { IsRequestDone = true; FailedCalls++; HandleFailedCall(); console.log("xhr timeout"); }; xhr.onerror = function () { console.log("xhr failed"); IsRequestDone = true; HandleFailedCall(); }; if (payload == "") { xhr.send(); } else { xhr.send(payload); } }