diff --git a/MirrorSharp.WebAssets/wwwroot/css/mirrorsharp.css b/MirrorSharp.WebAssets/wwwroot/css/mirrorsharp.css index 7e309fe1..79c9f107 100644 --- a/MirrorSharp.WebAssets/wwwroot/css/mirrorsharp.css +++ b/MirrorSharp.WebAssets/wwwroot/css/mirrorsharp.css @@ -6,6 +6,47 @@ font-size: inherit; } +/*.mirrorsharp-status { + position: absolute; + width: 0.7em; + height: 0.7em; + right: 0.35em; + top: 0.35em; + transform: rotate(45deg); + opacity: 0.7; +} + +.mirrorsharp-status-connected { + background-color: #2ecc71; +}*/ + +.mirrorsharp-connection-issue { + display: none; + position: absolute; + top: 0; + background: #aaa; + right: 0; + left: 0; + color: #fff; + padding: 0.2em 0.4em; +} + +.mirrorsharp-connection-has-issue .mirrorsharp-connection-issue { + display: block; +} + +.CodeMirror.mirrorsharp-connection-has-issue { + padding-top: 1.6em; +} + +.CodeMirror.mirrorsharp-connection-has-issue .CodeMirror-code { + color: #aaa; +} + +.CodeMirror.mirrorsharp-connection-has-issue .CodeMirror-code * { + color: inherit; +} + .mirrorsharp-hint { display: flex; padding-left: 0; diff --git a/MirrorSharp.WebAssets/wwwroot/js/mirrorsharp.js b/MirrorSharp.WebAssets/wwwroot/js/mirrorsharp.js index 3f62a713..c7786937 100644 --- a/MirrorSharp.WebAssets/wwwroot/js/mirrorsharp.js +++ b/MirrorSharp.WebAssets/wwwroot/js/mirrorsharp.js @@ -1,4 +1,5 @@ -(function (root, factory) { +/* globals define:false */ +(function (root, factory) { if (typeof define === 'function' && define.amd) { define(['CodeMirror'], factory); } else if (typeof module === 'object' && module.exports) { @@ -7,14 +8,46 @@ root.mirrorsharp = factory(root.CodeMirror); } }(this, function (CodeMirror) { - function Connection(socket) { - const openPromise = new Promise(function(resolve) { - socket.addEventListener('open', function (e) { - //console.debug("[open]"); - resolve(); - }); + 'use strict'; + + function Connection(openSocket) { + var socket; + var openPromise; + const handlers = { + open: [], + message: [], + close: [] + }; + + open(); + on('close', function() { + setTimeout(function() { open(); }, 1000); }); + function open() { + socket = openSocket(); + openPromise = new Promise(function (resolve) { + socket.addEventListener('open', function() { + resolve(); + }); + }); + + for (var key in handlers) { + const keyFixed = key; + const handlersByKey = handlers[key]; + socket.addEventListener(key, function (e) { + var argument = (keyFixed === 'message') ? JSON.parse(e.data) : undefined; + for (var handler of handlersByKey) { + handler(argument); + } + }); + } + } + + function on(key, handler) { + handlers[key].push(handler); + } + function sendWhenOpen(command) { openPromise.then(function () { //console.debug("[=>]", command); @@ -22,6 +55,8 @@ }); } + this.on = on; + this.sendReplaceText = function (isLastOrOnly, start, length, newText, cursorIndexAfter) { const command = isLastOrOnly ? 'R' : 'P'; return sendWhenOpen(command + start + ':' + length + ':' + cursorIndexAfter + ':' + newText); @@ -42,14 +77,6 @@ this.sendGetDiagnostics = function () { return sendWhenOpen('D'); } - - this.onMessage = function(handler) { - socket.addEventListener('message', function (e) { - //console.debug("[<=]", e.data); - const message = JSON.parse(e.data); - handler(message); - }); - } } function Editor(textarea, connection, options) { @@ -58,20 +85,39 @@ cmOptions.gutters.push('CodeMirror-lint-markers'); const cm = CodeMirror.fromTextArea(textarea, cmOptions); - var initialTextSent = false; + /*(function createStatusElement() { + const cmWrapper = cm.getWrapperElement(); + const element = document.createElement('div'); + element.className = 'mirrorsharp-status'; + cmWrapper.appendChild(element); + connection.onOpen(function () { + element.classList.add('mirrorsharp-status-connected'); + }); + + return element; + })();*/ + + var lintingSuspended = true; var updateLinting; - (function sendOnStart() { + connection.on('open', function () { + hideConnectionLoss(); + const text = cm.getValue(); if (text === '' || text == null) { - initialTextSent = true; + lintingSuspended = false; return; } - connection.sendReplaceText(true, 0, 0, text, 0); - initialTextSent = true; + connection.sendReplaceText(true, 0, 0, text, getCursorIndex(cm)); + lintingSuspended = false; if (updateLinting) requestDiagnostics(text, updateLinting); - })(); + }); + + connection.on('close', function () { + lintingSuspended = true; + showConnectionLoss(); + }); const indexKey = '$mirrorsharp-index'; var changePending = false; @@ -106,7 +152,7 @@ } }); - connection.onMessage(function (message) { + connection.on('message', function (message) { switch (message.type) { case 'changes': applyChangesFromServer(message.changes); @@ -174,7 +220,7 @@ function requestDiagnostics(text, updateLintingValue) { updateLinting = updateLintingValue; - if (initialTextSent) + if (!lintingSuspended) connection.sendGetDiagnostics(); } @@ -201,10 +247,30 @@ if (clientCursorIndex !== serverCursorIndex) console.error('Client cursor position does not match server position:', { clientPosition: clientCursorIndex, serverPosition: serverCursorIndex }); } + + var connectionLossElement; + function showConnectionLoss() { + const wrapper = cm.getWrapperElement(); + if (!connectionLossElement) { + connectionLossElement = document.createElement("div"); + connectionLossElement.setAttribute('class', 'mirrorsharp-connection-issue'); + connectionLossElement.innerText = 'Server connection lost, reconnecting…'; + wrapper.appendChild(connectionLossElement); + } + + wrapper.classList.add('mirrorsharp-connection-has-issue'); + } + + function hideConnectionLoss() { + cm.getWrapperElement().classList.remove('mirrorsharp-connection-has-issue'); + } } return function(textarea, options) { - const connection = new Connection(new WebSocket(options.serviceUrl)); + const connection = new Connection(function() { + return new WebSocket(options.serviceUrl); + }); + return new Editor(textarea, connection, options); } })); \ No newline at end of file diff --git a/MirrorSharp.sln.DotSettings b/MirrorSharp.sln.DotSettings index e13f67bc..bc74da31 100644 --- a/MirrorSharp.sln.DotSettings +++ b/MirrorSharp.sln.DotSettings @@ -2,6 +2,8 @@ C27+,E12+,FF21+,IE11+,S8+ ExplicitlyExcluded ExplicitlyExcluded + HINT + HINT END_OF_LINE END_OF_LINE END_OF_LINE @@ -11,6 +13,8 @@ END_OF_LINE False END_OF_LINE + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="" Suffix="" Style="AaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" />