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';