diff --git a/native/find-webclient.css b/native/find-webclient.css index 9e68657..3bd8471 100644 --- a/native/find-webclient.css +++ b/native/find-webclient.css @@ -3,14 +3,15 @@ } body { background-color: #101010; - color: #fff; - font-family: sans-serif; - font-size: 1.2em; + color: #ddd; + color: rgba(255, 255, 255, 0.8); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-size: 1em; padding: 1em; } .logo { max-width: 500px; - margin-bottom: 1em; + margin-bottom: 2em; margin-left: auto; margin-right: auto; display: block; @@ -19,44 +20,47 @@ input[type=text] { display: block; width: 100%; font-size: 1em; - background-color: #292929; - color: #fff; - border: 2px solid #292929; + color: inherit; + background: #292929; + border: 0.16em solid #292929; border-radius: 0.2em; - padding: 0.5em; + padding: 0.6em 0.8em; + font-family: inherit; } input[type=text]:focus { - border: 2px solid #00a4dc; - border-radius: 0.2em; + border-color: #00a4dc; outline: none; } +input[type=text]::placeholder { + color: #666; + color: rgba(255, 255, 255, 0.3); + opacity: 1; +} button { width: 100%; color: #fff; - background-color: #00a4dc; + background: #00a4dc; border: none; border-radius: 0.2em; font-size: 1em; - padding: 0.5em; + padding: 0.75em 1.5em; cursor: pointer; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.025em; + outline: none; + font-family: inherit; +} +button:hover:not(:disabled) { + background: #0cb0e8; } button:disabled { background-color: #292929; cursor: default; + opacity: 0.6; } -#address-label { - display: block; - margin-bottom: 0.5em; -} -#main { - max-width: 1000px; - margin-top: 3rem; - margin-left: auto; - margin-right: auto; - display: none; -} -#splash { +#container { position: absolute; top: 0; left: 0; @@ -65,31 +69,70 @@ button:disabled { display: flex; flex-direction: column; justify-content: center; + align-items: center; } -#backdrop { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.5); - z-index: 99; - display: none; -} -#connect-fail { - text-align: center; - background-color: #101010; - max-width: 600px; - padding: 25px; - margin-top: calc(50vh - 100px); +.logo { + max-width: 500px; + margin-bottom: 2em; margin-left: auto; margin-right: auto; - opacity: 1; + display: block; } -#connect-fail h1 { +#title { + margin-top: 1em; + margin-bottom: 0; font-size: 1.5em; - margin-top: 0; + font-weight: 400; + color: #ddd; + color: rgba(255, 255, 255, 0.8); + min-height: 1.5em; + height: 1.5em; + max-width: 450px; + width: 100%; + text-align: left; + visibility: hidden; +} +#connect-form { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + position: relative; +} +#address { + margin-bottom: 1em; + max-width: 450px; + height: 2.5em; + visibility: hidden; +} +#address.connecting { + visibility: hidden; +} +#spinner { + border: 6px solid #292929; + border-top: 6px solid #00a4dc; + border-radius: 50%; + width: 80px; + height: 80px; + animation: spin 1s linear infinite; + display: none; + position: absolute; + top: calc(1em + 1.5em + 1.25em); + left: 50%; + margin-left: -40px; +} +#connect-button { + max-width: 450px; + min-height: 2.5em; + visibility: hidden; +} +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} +#connect-button.cancel { + background: #cb272a; +} +#connect-button.cancel:hover:not(:disabled) { + background: #d93134; } -#helper-text { - margin-top: 0.3em; -} \ No newline at end of file diff --git a/native/find-webclient.html b/native/find-webclient.html index 49d6486..e4aeece 100644 --- a/native/find-webclient.html +++ b/native/find-webclient.html @@ -6,26 +6,15 @@ -
+
-
-
- -

- +

+
-

-
-
-

-

- -
-
\ No newline at end of file diff --git a/native/find-webclient.js b/native/find-webclient.js index 752f5de..39bc5e6 100644 --- a/native/find-webclient.js +++ b/native/find-webclient.js @@ -1,19 +1,16 @@ async function tryConnect(server) { - document.getElementById('connect-button').disabled = true; - - AbortSignal.timeout = function timeout(ms) { - const ctrl = new AbortController() - setTimeout(() => ctrl.abort(), ms) - return ctrl.signal - } - try { if (!server.startsWith("http")) { server = "http://" + server; } serverBaseURL = server.replace(/\/+$/, ""); const url = serverBaseURL + "/System/Info/Public"; - const response = await fetch(url, { cache: 'no-cache', signal: AbortSignal.timeout(5000) }); + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 60000); + + const response = await fetch(url, { cache: 'no-cache', signal: controller.signal }); + clearTimeout(timeoutId); if (response.ok && (await response.json()).Id) { const htmlResponse = await fetch(server, { cache: 'no-cache' }); if (!htmlResponse.ok) { @@ -21,9 +18,6 @@ async function tryConnect(server) { } if (response.headers.get("content-security-policy")) { - // Sigh... If we just navigate to the URL, the server's CSP will block us loading other resources. - // So we have to parse the HTML, set a new base href, and then write it back to the page. - // We also have to override the history functions to make sure they use the correct URL. console.log("Using CSP workaround"); const webUrl = htmlResponse.url.replace(/\/[^\/]*$/, "/"); const realUrl = window.location.href; @@ -60,36 +54,139 @@ async function tryConnect(server) { return true; } + return false; } catch (e) { console.error(e); - document.getElementById('connect-button').disabled = false; return false; } } -document.getElementById('connect-form').addEventListener('submit', async (e) => { +let isConnecting = false; + +const updateButtonState = () => { + const address = document.getElementById('address'); + const button = document.getElementById('connect-button'); + const hasValue = address.value.trim().length > 0; + + if (!isConnecting) { + button.disabled = !hasValue; + } +}; + +const cancelOnEscape = (e) => { + if (isConnecting && e.key === 'Escape') { + cancelConnection(); + } +}; + +const startConnecting = async () => { + const address = document.getElementById('address'); + const title = document.getElementById('title'); + const spinner = document.getElementById('spinner'); + const button = document.getElementById('connect-button'); + const server = address.value; + + isConnecting = true; + title.textContent = ''; + title.style.visibility = 'hidden'; + address.classList.add('connecting'); + address.style.visibility = 'hidden'; + address.disabled = true; + spinner.style.display = 'block'; + button.style.visibility = 'hidden'; + document.addEventListener('keydown', cancelOnEscape); + + let connected = false; + + while (!connected && isConnecting) { + connected = await tryConnect(server); + + if (!connected && isConnecting) { + // Wait 5 seconds before retrying + await new Promise(resolve => setTimeout(resolve, 5000)); + } + } + + if (!connected) { + isConnecting = false; + title.textContent = document.getElementById('title').getAttribute('data-original-text'); + title.style.visibility = 'visible'; + address.classList.remove('connecting'); + address.style.visibility = 'visible'; + address.disabled = false; + spinner.style.display = 'none'; + button.style.visibility = 'visible'; + document.removeEventListener('keydown', cancelOnEscape); + updateButtonState(); + } +}; + +const cancelConnection = () => { + if (!isConnecting) return; + + isConnecting = false; + + const address = document.getElementById('address'); + const title = document.getElementById('title'); + const spinner = document.getElementById('spinner'); + const button = document.getElementById('connect-button'); + + title.textContent = document.getElementById('title').getAttribute('data-original-text'); + title.style.visibility = 'visible'; + address.classList.remove('connecting'); + address.style.visibility = 'visible'; + address.disabled = false; + spinner.style.display = 'none'; + button.style.visibility = 'visible'; + document.removeEventListener('keydown', cancelOnEscape); + updateButtonState(); +}; + +// Button click handler +document.getElementById('connect-button').addEventListener('click', (e) => { e.preventDefault(); - const server = document.getElementById('address').value; - const result = await tryConnect(server); - if (!result) { - document.getElementById('backdrop').style.display = 'block'; + e.stopPropagation(); + + if (!e.target.disabled) { + startConnecting(); } }); -document.getElementById('connect-fail-button').addEventListener('click', () => { - document.getElementById('backdrop').style.display = 'none'; +// Form submit handler +document.getElementById('connect-form').addEventListener('submit', (e) => { + e.preventDefault(); + if (!isConnecting) { + startConnecting(); + } }); +// Input change handler +document.getElementById('address').addEventListener('input', updateButtonState); -// load the server if we have one + +// Enter key handler +document.addEventListener('keydown', (e) => { + const address = document.getElementById('address'); + if (e.key === 'Enter' && !isConnecting && !address.disabled && address.value.trim()) { + e.preventDefault(); + startConnecting(); + } +}); + +// Auto-connect on load (async() => { + const title = document.getElementById('title'); + const address = document.getElementById('address'); + const button = document.getElementById('connect-button'); + const savedServer = window.jmpInfo.settings.main.userWebClient; if (savedServer) { - document.getElementById('address').value = savedServer; - } - - if (!savedServer || !(await tryConnect(savedServer))) { - document.getElementById('splash').style.display = 'none'; - document.getElementById('main').style.display = 'block'; + address.value = savedServer; + await startConnecting(); + } else { + title.style.visibility = 'visible'; + address.style.visibility = 'visible'; + button.style.visibility = 'visible'; + updateButtonState(); } })(); diff --git a/native/find-webclient.lang.js b/native/find-webclient.lang.js index 0cec7c1..8b81095 100644 --- a/native/find-webclient.lang.js +++ b/native/find-webclient.lang.js @@ -146,7 +146,7 @@ const languages = [ { "lang": "en-gb", "HeaderConnectToServer": "Connect to Server", - "LabelServerHost": "Host", + "LabelServerHost": "Server Address", "LabelServerHostHelp": "192.168.1.100:8096 or https://myserver.com", "Connect": "Connect", "HeaderConnectionFailure": "Connection Failure", @@ -156,7 +156,7 @@ const languages = [ { "lang": "en-us", "HeaderConnectToServer": "Connect to Server", - "LabelServerHost": "Host", + "LabelServerHost": "Server Address", "LabelServerHostHelp": "192.168.1.100:8096 or https://myserver.com", "Connect": "Connect", "HeaderConnectionFailure": "Connection Failure", @@ -965,10 +965,12 @@ if (!languages.find(l => l.lang === language)) { const languageStrings = languages.find(l => l.lang === language); const fallbackStrings = languages.find(l => l.lang === fallbackLanguage); -document.getElementById('title').innerText = languageStrings.HeaderConnectToServer || fallbackStrings.HeaderConnectToServer; -document.getElementById('address-label').innerText = languageStrings.LabelServerHost || fallbackStrings.LabelServerHost; -document.getElementById('helper-text').innerText = languageStrings.LabelServerHostHelp || fallbackStrings.LabelServerHostHelp; -document.getElementById('connect-button').innerText = languageStrings.Connect || fallbackStrings.Connect; -document.getElementById('connect-fail-title').innerText = languageStrings.HeaderConnectionFailure || fallbackStrings.HeaderConnectionFailure; -document.getElementById('connect-fail-text').innerText = languageStrings.MessageUnableToConnectToServer || fallbackStrings.MessageUnableToConnectToServer; -document.getElementById('connect-fail-button').innerText = languageStrings.ButtonGotIt || fallbackStrings.ButtonGotIt; +const titleText = languageStrings.LabelServerHost || fallbackStrings.LabelServerHost || 'Server Address'; +const connectText = languageStrings.Connect || fallbackStrings.Connect; + +document.getElementById('title').innerText = titleText; +document.getElementById('title').setAttribute('data-original-text', titleText); +document.getElementById('address').placeholder = languageStrings.LabelServerHostHelp || fallbackStrings.LabelServerHostHelp; +document.getElementById('connect-button').innerText = connectText; +document.getElementById('connect-button').setAttribute('data-original-text', connectText); +window.cancelButtonText = 'Cancel';