mirror of
https://github.com/Impostor/Impostor.git
synced 2026-10-19 15:51:42 +00:00
Some changes I should have split up
Fix the layout on mobile Make the select input transparent Enable type checking
This commit is contained in:
7 files changed
+158
-81
No files matched your search
@@ -6,9 +6,13 @@ jobs:
|
||||
format:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: biomejs/setup-biome@v2
|
||||
with:
|
||||
version: latest
|
||||
- uses: actions/checkout@v4
|
||||
- name: Lint and check formatting
|
||||
run: biome ci .
|
||||
- uses: pnpm/action-setup@v2
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: |
|
||||
pnpm biome ci .
|
||||
pnpm tsc --noEmit
|
||||
+15
-23
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Impostor | Among Us Private Server</title>
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
@@ -22,38 +22,30 @@
|
||||
|
||||
<section class="container">
|
||||
<div class="mb-4 mb-md-3">
|
||||
<h3 class="text-center text-md-left mb-4 mb-md-3 w-100">Connect to a Server</h3>
|
||||
<h3 class="text-center mb-4 mb-md-3 w-100">Connect to a Server</h3>
|
||||
</div>
|
||||
<form id="serverFileForm" class="container p-0" onsubmit="return false;">
|
||||
<div class="row justify-content-center mb-5 mb-md-4">
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<select name="serverProtocol" id="protocol" class="custom-select" style="height: 100%">
|
||||
<option value="http" selected>http://</option>
|
||||
<option value="https">https://</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input class="form-control form-control-lg bg-transparent text-light text-center mr-2"
|
||||
type="text" placeholder="Server address" id="address" name="address" required>
|
||||
</div>
|
||||
<div class="col">
|
||||
<input class="form-control form-control-lg bg-transparent text-light" style="max-width: 110px"
|
||||
type="number" placeholder="Port" id="port" name="port" min="1" max="65535" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row justify-content-center mb-4 mx-md-5">
|
||||
<select name="serverProtocol" id="protocol" class="col custom-select text-light" style="height: unset;" onchange="onProtocolChange()">
|
||||
<option value="http" selected>http://</option>
|
||||
<option value="https">https://</option>
|
||||
</select>
|
||||
<input class="col-6 col-md-8 mx-1 mx-md-2 form-control form-control-lg bg-transparent text-light text-center"
|
||||
type="text" placeholder="Server address" id="address" name="address" required>
|
||||
<input class="col form-control form-control-lg bg-transparent text-light" style="max-width: 110px"
|
||||
type="number" placeholder="Port" id="port" name="port" min="1" max="65535" required>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center mb-5 mb-md-4">
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="row justify-content-center flex-nowrap">
|
||||
<input class="form-control form-control-lg bg-transparent text-light text-center mr-2"
|
||||
type="text" placeholder="Server name" id="name" name="name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<button class="btn btn-primary btn-lg support-text desktop-support" onclick="downloadAsync()">Download Server File</button>
|
||||
<button class="btn btn-primary btn-lg support-text android-support ios-support" onclick="openApp()">Open in Among Us</button>
|
||||
<button class="btn btn-secondary btn-lg ml-5" onclick="setLocationHash()">Create sharable link</button>
|
||||
<button class="col-md-4 btn btn-primary btn-lg support-text desktop-support" onclick="downloadAsync()">Download server file</button>
|
||||
<button class="col-md-4 btn btn-primary btn-lg support-text android-support ios-support" onclick="openApp()">Open in Among Us</button>
|
||||
<button class="col-md-4 offset-md-1 mt-1 mt-md-0 btn btn-secondary btn-lg" onclick="setLocationHash()">Create sharable link</button>
|
||||
</div>
|
||||
</form>
|
||||
<div class="mt-4">
|
||||
|
||||
+4
-2
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.5.2"
|
||||
}
|
||||
"@biomejs/biome": "^1",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"packageManager": "pnpm@8.14.1"
|
||||
}
|
||||
Generated
+10
-1
@@ -6,8 +6,11 @@ settings:
|
||||
|
||||
devDependencies:
|
||||
'@biomejs/biome':
|
||||
specifier: 1.5.2
|
||||
specifier: ^1
|
||||
version: 1.5.2
|
||||
typescript:
|
||||
specifier: ^5
|
||||
version: 5.3.3
|
||||
|
||||
packages:
|
||||
|
||||
@@ -98,3 +101,9 @@ packages:
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/typescript@5.3.3:
|
||||
resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
@@ -1,52 +1,103 @@
|
||||
const DEFAULT_PORT_HTTP = "22023";
|
||||
const DEFAULT_PORT_HTTPS = "443";
|
||||
const DEFAULT_PORT_HTTP = 22023;
|
||||
const DEFAULT_PORT_HTTPS = 443;
|
||||
|
||||
/**
|
||||
* @template {HTMLElement} T
|
||||
* @param {string} elementId
|
||||
* @returns {T}
|
||||
*/
|
||||
function getElementByIdOrThrow(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element === null) throw new Error(`Couldn't find the element with id ${elementId}`);
|
||||
return /** @type {T} */ (element);
|
||||
}
|
||||
|
||||
const addressInput = /** @type {HTMLInputElement} */ (getElementByIdOrThrow("address"));
|
||||
const portInput = /** @type {HTMLInputElement} */ (getElementByIdOrThrow("port"));
|
||||
const nameInput = /** @type {HTMLInputElement} */ (getElementByIdOrThrow("name"));
|
||||
const protocolSelect =
|
||||
/** @type {Omit<HTMLSelectElement, "value"> & { value: Protocol; }} */
|
||||
(getElementByIdOrThrow("protocol"));
|
||||
|
||||
/**
|
||||
* @typedef {{ protocol: Protocol, address: string, port: number, name: string, url: string }} ServerInfo
|
||||
* @typedef {"desktop" | "android" | "ios"} Platform
|
||||
* @typedef {"https" | "http"} Protocol
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ address: string, port: number, name: string, protocol: string, url: string }} ServerInfo
|
||||
* @returns {ServerInfo}
|
||||
*/
|
||||
function parseForm() {
|
||||
const address = document.getElementById("address").value.trim();
|
||||
const port = parseInt(document.getElementById("port").value) ?? DEFAULT_PORT_HTTP;
|
||||
const name = document.getElementById("name").value || "Impostor";
|
||||
const protocol = document.querySelector("select[name=serverProtocol]").value || "http";
|
||||
const protocol = protocolSelect.value;
|
||||
const address = addressInput.value.trim();
|
||||
const port = parseInt(portInput.value) || (protocol === "http" ? DEFAULT_PORT_HTTP : DEFAULT_PORT_HTTPS);
|
||||
const name = nameInput.value || "Impostor";
|
||||
|
||||
return { address, port, name, protocol, url: `${protocol}://${address}` };
|
||||
return { protocol, address, port, name, url: `${protocol}://${address}` };
|
||||
}
|
||||
|
||||
async function downloadAsync() {
|
||||
function downloadAsync() {
|
||||
const serverInfo = parseForm();
|
||||
if (!serverInfo.address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const json = generateRegionInfo(serverInfo);
|
||||
|
||||
console.log(`Saving ${json}`);
|
||||
|
||||
const blob = new Blob([json], { type: "text/plain" });
|
||||
saveFile(blob, "regionInfo.json");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function openApp() {
|
||||
function openApp() {
|
||||
const serverInfo = parseForm();
|
||||
if (!serverInfo.address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({
|
||||
servername: serverInfo.name,
|
||||
serverport: serverInfo.port,
|
||||
serverport: serverInfo.port.toString(),
|
||||
serverip: serverInfo.url,
|
||||
usedtls: false,
|
||||
usedtls: false.toString(),
|
||||
});
|
||||
window.location = `amongus://init?${params.toString()}`;
|
||||
|
||||
const url = `amongus://init?${params.toString()}`;
|
||||
|
||||
console.log(`Opening ${url}`);
|
||||
window.location.href = url;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
let currentPlatform;
|
||||
/** @type {Record<Platform, HTMLAnchorElement>} */
|
||||
const platformButtons = {
|
||||
desktop: getElementByIdOrThrow("desktop"),
|
||||
android: getElementByIdOrThrow("android"),
|
||||
ios: getElementByIdOrThrow("ios"),
|
||||
};
|
||||
|
||||
/** @type {?Platform} */
|
||||
let currentPlatform = null;
|
||||
let httpsSetExplicitly = false;
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {boolean} value
|
||||
*/
|
||||
function setEnabled(platform, value) {
|
||||
for (const e of document.querySelectorAll(`.${platform}-support`)) {
|
||||
for (const e of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`.${platform}-support`))) {
|
||||
e.style.display = value ? "block" : "none";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
*/
|
||||
function setPlatform(platform) {
|
||||
if (currentPlatform === platform) {
|
||||
return;
|
||||
@@ -54,39 +105,44 @@ function setPlatform(platform) {
|
||||
|
||||
if (currentPlatform) {
|
||||
setEnabled(currentPlatform, false);
|
||||
document.getElementById(currentPlatform).classList.remove("text-primary");
|
||||
platformButtons[currentPlatform].classList.remove("text-primary");
|
||||
}
|
||||
|
||||
// HTTPS is mandatory on ios/android
|
||||
const protocol = document.getElementById("protocol");
|
||||
if (platform === "android" || platform === "ios") {
|
||||
httpsSetExplicitly = protocol.value === "https";
|
||||
protocol.value = "https";
|
||||
protocol.disabled = true;
|
||||
httpsSetExplicitly = protocolSelect.value === "https";
|
||||
protocolSelect.value = "https";
|
||||
protocolSelect.disabled = true;
|
||||
setPortIfDefault("https");
|
||||
} else {
|
||||
protocol.disabled = false;
|
||||
protocolSelect.disabled = false;
|
||||
if (!httpsSetExplicitly) {
|
||||
protocol.value = "http";
|
||||
protocolSelect.value = "http";
|
||||
setPortIfDefault("http");
|
||||
}
|
||||
}
|
||||
|
||||
setEnabled(platform, true);
|
||||
document.getElementById(platform).classList.add("text-primary");
|
||||
platformButtons[platform].classList.add("text-primary");
|
||||
|
||||
currentPlatform = platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Protocol} protocol
|
||||
*/
|
||||
function setPortIfDefault(protocol) {
|
||||
const oldPort = protocol === "http" ? DEFAULT_PORT_HTTPS : DEFAULT_PORT_HTTP;
|
||||
const newPort = protocol === "http" ? DEFAULT_PORT_HTTP : DEFAULT_PORT_HTTPS;
|
||||
const portField = document.getElementById("port");
|
||||
if (portField.value === oldPort) {
|
||||
portField.value = newPort;
|
||||
if (!portInput.value || portInput.value === oldPort.toString()) {
|
||||
portInput.value = newPort.toString();
|
||||
}
|
||||
}
|
||||
|
||||
function onProtocolChange() {
|
||||
setPortIfDefault(protocolSelect.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ServerInfo} serverInfo
|
||||
* @returns {string}
|
||||
@@ -114,9 +170,13 @@ function generateRegionInfo(serverInfo) {
|
||||
Regions: regions,
|
||||
};
|
||||
|
||||
return JSON.stringify(jsonServerData);
|
||||
return JSON.stringify(jsonServerData, null, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Blob} blob
|
||||
* @param {string} fileName
|
||||
*/
|
||||
function saveFile(blob, fileName) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
@@ -132,26 +192,26 @@ function saveFile(blob, fileName) {
|
||||
|
||||
function fillFromLocationHash() {
|
||||
const urlServerAddress = document.location.hash.substring(1).split(":");
|
||||
const serverAddress = urlServerAddress[0];
|
||||
const serverPort = urlServerAddress.length > 1 ? urlServerAddress[1] : DEFAULT_PORT_HTTP.toString();
|
||||
let protocol = urlServerAddress.length > 2 ? urlServerAddress[2] : "http";
|
||||
const serverName = urlServerAddress.length > 3 ? urlServerAddress[3] : "";
|
||||
const serverAddress = urlServerAddress.length > 0 ? urlServerAddress[0] : null;
|
||||
const serverPort = urlServerAddress.length > 1 ? urlServerAddress[1] : null;
|
||||
const protocol = urlServerAddress.length > 2 ? urlServerAddress[2] : null;
|
||||
const serverName = urlServerAddress.length > 3 ? urlServerAddress[3] : null;
|
||||
|
||||
if (serverAddress) {
|
||||
document.getElementById("address").value = serverAddress;
|
||||
addressInput.value = serverAddress;
|
||||
}
|
||||
|
||||
if (!Number.IsNan(parseInt(serverPort))) {
|
||||
document.getElementById("port").value = serverPort;
|
||||
if (serverPort && !Number.isNaN(parseInt(serverPort))) {
|
||||
portInput.value = serverPort;
|
||||
}
|
||||
|
||||
// Set the default protocol to http
|
||||
if (protocol !== "http" && protocol !== "https") {
|
||||
protocol = "http";
|
||||
if (protocol === "http" || protocol === "https") {
|
||||
protocolSelect.value = protocol;
|
||||
}
|
||||
document.querySelector("select[name=serverProtocol]").value = protocol;
|
||||
|
||||
document.getElementById("name").value = serverName;
|
||||
if (serverName) {
|
||||
nameInput.value = serverName;
|
||||
}
|
||||
}
|
||||
|
||||
function setLocationHash() {
|
||||
@@ -159,6 +219,8 @@ function setLocationHash() {
|
||||
document.location.hash = [serverInfo.address, serverInfo.port, serverInfo.protocol, serverInfo.name].join(":");
|
||||
}
|
||||
|
||||
fillFromLocationHash();
|
||||
|
||||
if (["iPhone", "iPad", "iPod"].indexOf(window.navigator.platform) !== -1) {
|
||||
setPlatform("ios");
|
||||
} else if (/Android/.test(window.navigator.userAgent)) {
|
||||
@@ -166,5 +228,3 @@ if (["iPhone", "iPad", "iPod"].indexOf(window.navigator.platform) !== -1) {
|
||||
} else {
|
||||
setPlatform("desktop");
|
||||
}
|
||||
|
||||
fillFromLocationHash();
|
||||
@@ -33,15 +33,16 @@ body {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 287px;
|
||||
.custom-select {
|
||||
background-color: transparent;
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23f8f9fa' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
select.form-control {
|
||||
appearance: none;
|
||||
.custom-select:disabled {
|
||||
background-color: #e9ecef19;
|
||||
}
|
||||
|
||||
select.form-control option {
|
||||
.custom-select option {
|
||||
background-color: #252525;
|
||||
font-size: 16px;
|
||||
}
|
||||
@@ -65,10 +66,6 @@ select.form-control option {
|
||||
height: 1.25em;
|
||||
}
|
||||
|
||||
code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.platform {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "es2022",
|
||||
"module": "es2022",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"checkJs": true
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user