Add custom domain option

This commit is contained in:
2026-01-13 10:44:05 +01:00
parent 2cd2c265a3
commit b5dda31360
11 changed files with 86 additions and 12 deletions

1
.gitattributes vendored
View File

@@ -10,3 +10,4 @@ res/icon/linux/32x32.png filter=lfs diff=lfs merge=lfs -text
res/icon/linux/48x48.png filter=lfs diff=lfs merge=lfs -text res/icon/linux/48x48.png filter=lfs diff=lfs merge=lfs -text
res/icon/linux/512x512.png filter=lfs diff=lfs merge=lfs -text res/icon/linux/512x512.png filter=lfs diff=lfs merge=lfs -text
res/icon/linux/64x64.png filter=lfs diff=lfs merge=lfs -text res/icon/linux/64x64.png filter=lfs diff=lfs merge=lfs -text
res/icon/brand.png filter=lfs diff=lfs merge=lfs -text

View File

@@ -1,5 +1,5 @@
{ {
"plugins": ["prettier-plugin-java"], "plugins": [],
"printWidth":120, "printWidth":120,
"tabWidth":4, "tabWidth":4,
"useTabs":false, "useTabs":false,

View File

@@ -15,17 +15,16 @@ node_modules: package-lock.json
.PHONY: run .PHONY: run
run: run:
$(ELECTRON) src/main.js LOCAL_DOMAIN=1 $(ELECTRON) src/main.js
.PHONY: run-x .PHONY: run-x
run-x: run-x:
./util/run-xorg $(ELECTRON) LOCAL_DOMAIN=1 ./util/run-xorg $(ELECTRON)
.PHONY: run-wl .PHONY: run-wl
run-wl: run-wl:
./util/run-wl $(ELECTRON) LOCAL_DOMAIN=1 ./util/run-wl $(ELECTRON)
.PHONY: fix .PHONY: fix
fix: node_modules fix: node_modules
./node_modules/.bin/prettier --write src ./node_modules/.bin/prettier --write src

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "imgfloat-client", "name": "imgfloat-client",
"version": "1.0.0", "version": "1.0.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "imgfloat-client", "name": "imgfloat-client",
"version": "1.0.0", "version": "1.0.2",
"dependencies": { "dependencies": {
"electron-updater": "^6.7.3" "electron-updater": "^6.7.3"
}, },

View File

@@ -7,7 +7,7 @@
"appId": "dev.kruhlmann.imgfloat", "appId": "dev.kruhlmann.imgfloat",
"productName": "Imgfloat", "productName": "Imgfloat",
"files": [ "files": [
"src/main.js", "src/**",
"res/**" "res/**"
], ],
"publish": { "publish": {

BIN
res/icon/brand.png LFS Normal file

Binary file not shown.

View File

@@ -9,7 +9,7 @@
<div class="channels-shell"> <div class="channels-shell">
<header class="channels-header"> <header class="channels-header">
<div class="brand"> <div class="brand">
<img class="brand-mark" alt="brand" src="https://imgfloat.kruhlmann.dev/img/brand.png" /> <img class="brand-mark" alt="brand" src="../res/icon/brand.png" />
<div> <div>
<div class="brand-title">Imgfloat</div> <div class="brand-title">Imgfloat</div>
<div class="brand-subtitle">Twitch overlay manager</div> <div class="brand-subtitle">Twitch overlay manager</div>
@@ -35,6 +35,15 @@
autofocus autofocus
spellcheck="false" spellcheck="false"
/> />
<label class="sr-only" for="domain-input">Server domain</label>
<input
id="domain-input"
name="domain"
class="text-input"
type="url"
autocomplete="off"
spellcheck="false"
/>
<datalist id="channel-suggestions"></datalist> <datalist id="channel-suggestions"></datalist>
<button type="submit" class="button block">Open overlay</button> <button type="submit" class="button block">Open overlay</button>
</form> </form>
@@ -44,6 +53,7 @@
<script> <script>
const form = document.getElementById("channel-search-form"); const form = document.getElementById("channel-search-form");
const input = document.getElementById("channel-search"); const input = document.getElementById("channel-search");
const domainInput = document.getElementById("domain-input");
window.store.loadBroadcaster().then((value) => { window.store.loadBroadcaster().then((value) => {
if (value && input.value === "") { if (value && input.value === "") {
@@ -51,13 +61,35 @@
} }
}); });
Promise.all([window.store.loadDomain(), window.store.loadDefaultDomain()]).then(
([savedDomain, defaultDomain]) => {
domainInput.value = savedDomain || defaultDomain;
domainInput.placeholder = defaultDomain;
},
);
domainInput.addEventListener("change", () => {
const trimmedDomain = domainInput.value.trim();
if (trimmedDomain) {
window.store.saveDomain(trimmedDomain);
}
});
form.addEventListener("submit", (e) => { form.addEventListener("submit", (e) => {
e.preventDefault(); e.preventDefault();
const channel = input.value.trim(); const channel = input.value.trim();
const fallbackDomain = domainInput.placeholder || "";
const domain = domainInput.value.trim() || fallbackDomain;
if (!channel) return; if (!channel) return;
if (domain) {
window.store.saveDomain(domain);
}
const params = new URLSearchParams({ broadcaster: channel }); const params = new URLSearchParams({ broadcaster: channel });
if (domain) {
params.set("domain", domain);
}
window.location.href = `broadcast.html?${params.toString()}`; window.location.href = `broadcast.html?${params.toString()}`;
}); });
</script> </script>

View File

@@ -2,7 +2,11 @@ import { BroadcastRenderer } from "./broadcast/renderer.js";
import { saveSelectedBroadcaster } from "./ipc.js"; import { saveSelectedBroadcaster } from "./ipc.js";
import { showToast } from "./toast.js"; import { showToast } from "./toast.js";
const domain = "https://imgfloat.kruhlmann.dev"; const normalizeDomain = (value) => value?.trim()?.replace(/\/+$/, "");
const domainParam = new URL(window.location.href).searchParams.get("domain");
const defaultDomain = await window.store.loadDefaultDomain();
const savedDomain = await window.store.loadDomain();
const domain = normalizeDomain(domainParam || savedDomain || defaultDomain);
globalThis.onerror = (error, url, line) => { globalThis.onerror = (error, url, line) => {
console.error(error); console.error(error);
@@ -17,6 +21,9 @@ const broadcaster = new URL(window.location.href).searchParams.get("broadcaster"
if (!broadcaster) { if (!broadcaster) {
throw new Error("No broadcaster"); throw new Error("No broadcaster");
} }
if (!domain) {
throw new Error("No domain configured");
}
saveSelectedBroadcaster(broadcaster); saveSelectedBroadcaster(broadcaster);
const renderer = new BroadcastRenderer({ const renderer = new BroadcastRenderer({

View File

@@ -62,7 +62,7 @@ export class BroadcastRenderer {
const body = JSON.parse(payload.body); const body = JSON.parse(payload.body);
this.handleEvent(body); this.handleEvent(body);
}); });
fetch(`https://imgfloat.kruhlmann.dev/api/channels/${this.broadcaster}/assets`) fetch(`${this.domain}/api/channels/${this.broadcaster}/assets`)
.then((r) => { .then((r) => {
if (!r.ok) { if (!r.ok) {
throw new Error("Failed to load assets"); throw new Error("Failed to load assets");
@@ -121,7 +121,7 @@ export class BroadcastRenderer {
} }
async fetchCanvasSettings() { async fetchCanvasSettings() {
return fetch(`https://imgfloat.kruhlmann.dev/api/channels/${encodeURIComponent(this.broadcaster)}/canvas`) return fetch(`${this.domain}/api/channels/${encodeURIComponent(this.broadcaster)}/canvas`)
.then((r) => { .then((r) => {
if (!r.ok) { if (!r.ok) {
throw new Error("Failed to load canvas"); throw new Error("Failed to load canvas");

View File

@@ -7,6 +7,22 @@ const { readStore, writeStore } = require("./store.js");
const STORE_PATH = path.join(app.getPath("userData"), "settings.json"); const STORE_PATH = path.join(app.getPath("userData"), "settings.json");
const INITIAL_WINDOW_WIDTH_PX = 960; const INITIAL_WINDOW_WIDTH_PX = 960;
const INITIAL_WINDOW_HEIGHT_PX = 640; const INITIAL_WINDOW_HEIGHT_PX = 640;
const LOCAL_DOMAIN = "http://localhost:8080";
const DEFAULT_DOMAIN = "https://imgfloat.kruhlmann.dev";
function normalizeDomain(domain) {
return domain?.trim()?.replace(/\/+$/, "");
}
function resolveDefaultDomain() {
if (process.env.DEVTOOLS || process.env.LOCAL_DOMAIN) {
return normalizeDomain(LOCAL_DOMAIN);
}
const buildTimeDomain = process.env.IMGFLOAT_DOMAIN || DEFAULT_DOMAIN;
return normalizeDomain(buildTimeDomain);
}
const runtimeDefaultDomain = resolveDefaultDomain();
let ELECTRON_WINDOW; let ELECTRON_WINDOW;
@@ -74,6 +90,19 @@ ipcMain.handle("load-broadcaster", () => {
return store.lastBroadcaster ?? ""; return store.lastBroadcaster ?? "";
}); });
ipcMain.handle("save-domain", (_, domain) => {
const store = readStore(STORE_PATH);
store.lastDomain = normalizeDomain(domain);
writeStore(STORE_PATH, store);
});
ipcMain.handle("load-domain", () => {
const store = readStore(STORE_PATH);
return normalizeDomain(store.lastDomain) || runtimeDefaultDomain;
});
ipcMain.handle("load-default-domain", () => runtimeDefaultDomain);
app.whenReady().then(() => { app.whenReady().then(() => {
if (process.env.CI) { if (process.env.CI) {
process.on("uncaughtException", (err) => { process.on("uncaughtException", (err) => {

View File

@@ -3,5 +3,8 @@ const { contextBridge, ipcRenderer } = require("electron");
contextBridge.exposeInMainWorld("store", { contextBridge.exposeInMainWorld("store", {
saveBroadcaster: (value) => ipcRenderer.invoke("save-broadcaster", value), saveBroadcaster: (value) => ipcRenderer.invoke("save-broadcaster", value),
loadBroadcaster: () => ipcRenderer.invoke("load-broadcaster"), loadBroadcaster: () => ipcRenderer.invoke("load-broadcaster"),
saveDomain: (value) => ipcRenderer.invoke("save-domain", value),
loadDomain: () => ipcRenderer.invoke("load-domain"),
loadDefaultDomain: () => ipcRenderer.invoke("load-default-domain"),
setWindowSize: (width, height) => ipcRenderer.invoke("set-window-size", width, height), setWindowSize: (width, height) => ipcRenderer.invoke("set-window-size", width, height),
}); });