diff --git a/src/css/index.css b/src/css/index.css index b2a42b4..e445bd3 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -3,6 +3,10 @@ color: white; } +:root { + --window-frame-height: 36px; +} + p { margin: 0; } @@ -30,6 +34,65 @@ body { padding: clamp(24px, 4vw, 48px); } +.window-frame { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: var(--window-frame-height); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 12px; + background: rgba(15, 23, 42, 0.9); + border-bottom: 1px solid rgba(30, 41, 59, 0.7); + -webkit-app-region: drag; +} + +.window-frame-title { + font-size: 12px; + letter-spacing: 0.3px; + color: #cbd5f5; + text-transform: uppercase; +} + +.window-frame-controls { + display: flex; + align-items: center; + gap: 6px; + -webkit-app-region: no-drag; +} + +.window-control { + width: 30px; + height: 24px; + padding: 0; + border-radius: 6px; + background: rgba(148, 163, 184, 0.2); + border: 1px solid rgba(148, 163, 184, 0.25); + color: #e2e8f0; + font-size: 16px; + line-height: 1; + box-shadow: none; +} + +.window-control:hover { + background: rgba(148, 163, 184, 0.35); +} + +.window-control:active { + transform: none; +} + +.window-control-close { + background: rgba(239, 68, 68, 0.3); + border-color: rgba(239, 68, 68, 0.4); +} + +.window-control-close:hover { + background: rgba(239, 68, 68, 0.5); +} + .channels-shell { width: 100%; display: flex; diff --git a/src/index.html b/src/index.html index befe6d0..3f48019 100644 --- a/src/index.html +++ b/src/index.html @@ -6,6 +6,22 @@ +
@@ -54,6 +70,7 @@ const form = document.getElementById("channel-search-form"); const input = document.getElementById("channel-search"); const domainInput = document.getElementById("domain-input"); + const windowActionButtons = document.querySelectorAll("[data-window-action]"); window.store.loadBroadcaster().then((value) => { if (value && input.value === "") { @@ -82,13 +99,25 @@ const fallbackDomain = domainInput.placeholder || ""; const domain = domainInput.value.trim() || fallbackDomain; if (!channel) { - return - }; + return; + } const params = new URLSearchParams({ broadcaster: channel }); window.store.saveDomain(domain); window.location.href = `${domain}/view/${encodeURIComponent(channel)}/broadcast`; }); + + windowActionButtons.forEach((button) => { + button.addEventListener("click", () => { + const action = button.dataset.windowAction; + if (action === "minimize") { + window.store.minimizeWindow(); + } + if (action === "close") { + window.store.closeWindow(); + } + }); + }); diff --git a/src/main.js b/src/main.js index 70cb49f..2f1aa60 100644 --- a/src/main.js +++ b/src/main.js @@ -24,49 +24,29 @@ function resolveDefaultDomain() { return normalizeDomain(buildTimeDomain); } -function createWindowOptionsForPlatform(platform) { - switch (platform) { - case "darwin": - case "linux": - return { - width: INITIAL_WINDOW_WIDTH_PX, - height: INITIAL_WINDOW_HEIGHT_PX, - transparent: true, - frame: true, - backgroundColor: "#00000000", - alwaysOnTop: false, - icon: path.join(__dirname, "../res/icon/appicon.ico"), - webPreferences: { - backgroundThrottling: false, - preload: path.join(__dirname, "preload.js"), - }, - }; - case "win32": - return { - width: INITIAL_WINDOW_WIDTH_PX, - height: INITIAL_WINDOW_HEIGHT_PX, - transparent: true, - frame: false, - backgroundColor: "#00000000", - alwaysOnTop: false, - icon: path.join(__dirname, "../res/icon/appicon.ico"), - webPreferences: { - backgroundThrottling: false, - preload: path.join(__dirname, "preload.js"), - }, - }; - default: - throw new Error(`Unsupported platform: ${platform}`); - } +function createWindowOptions() { + return { + width: INITIAL_WINDOW_WIDTH_PX, + height: INITIAL_WINDOW_HEIGHT_PX, + transparent: true, + frame: false, + backgroundColor: "#00000000", + alwaysOnTop: false, + icon: path.join(__dirname, "../res/icon/appicon.ico"), + webPreferences: { + backgroundThrottling: false, + preload: path.join(__dirname, "preload.js"), + }, + }; } function createWindow(version) { - const windowOptions = createWindowOptionsForPlatform(process.platform); + const windowOptions = createWindowOptions(); const win = new BrowserWindow(windowOptions); win.setMenu(null); - win.setFullScreenable(false) - win.setFullScreen(false) - win.setResizable(false) + win.setFullScreenable(false); + win.setFullScreen(false); + win.setResizable(false); win.setTitle(`Imgfloat Client v${version}`); return win; @@ -78,6 +58,18 @@ ipcMain.handle("set-window-size", (_, width, height) => { } }); +ipcMain.handle("minimize-window", () => { + if (ELECTRON_WINDOW && !ELECTRON_WINDOW.isDestroyed()) { + ELECTRON_WINDOW.minimize(); + } +}); + +ipcMain.handle("close-window", () => { + if (ELECTRON_WINDOW && !ELECTRON_WINDOW.isDestroyed()) { + ELECTRON_WINDOW.close(); + } +}); + ipcMain.handle("save-broadcaster", (_, broadcaster) => { const store = readStore(STORE_PATH); store.lastBroadcaster = broadcaster; diff --git a/src/preload.js b/src/preload.js index f127af1..1488e30 100644 --- a/src/preload.js +++ b/src/preload.js @@ -7,4 +7,6 @@ contextBridge.exposeInMainWorld("store", { loadDomain: () => ipcRenderer.invoke("load-domain"), loadDefaultDomain: () => ipcRenderer.invoke("load-default-domain"), setWindowSize: (width, height) => ipcRenderer.invoke("set-window-size", width, height), + minimizeWindow: () => ipcRenderer.invoke("minimize-window"), + closeWindow: () => ipcRenderer.invoke("close-window"), });