diff --git a/.gitea/workflows/build-windows.yml b/.gitea/workflows/build-windows.yml index 97269d4..e4cadb7 100644 --- a/.gitea/workflows/build-windows.yml +++ b/.gitea/workflows/build-windows.yml @@ -14,6 +14,8 @@ jobs: GH_TOKEN: ${{ secrets.REGISTRY_TOKEN }} GITHUB_TOKEN: ${{ secrets.REGISTRY_TOKEN }} REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + CSC_LINK: ${{ secrets.WINDOWS_CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.WINDOWS_CSC_KEY_PASSWORD }} steps: - name: Checkout uses: actions/checkout@v4 diff --git a/README.md b/README.md index 38fc75e..f948fcc 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,19 @@ Dann wird im Output immer dieser Wert gesetzt, auch wenn die Vorlage bereits ein EnvHelper arbeitet lokal. Die Werte werden im Renderer mit Web-Crypto erzeugt und nicht an externe Dienste gesendet. Die App ist ein Helfer für Vorlagen, ersetzt aber keine zentrale Secret-Verwaltung für produktive Infrastruktur. +### Windows Defender und SmartScreen + +Windows kann unsignierte `.exe` Dateien als unbekannten Herausgeber einstufen. Das ist kein EnvHelper-spezifischer Fehler, sondern die normale Windows-Sicherheitsprüfung für Apps ohne vertrauenswürdige Authenticode-Signatur. + +Für signierte Builds kann der Gitea Runner später diese Secrets verwenden: + +```text +WINDOWS_CSC_LINK +WINDOWS_CSC_KEY_PASSWORD +``` + +`WINDOWS_CSC_LINK` ist das Code-Signing-Zertifikat, zum Beispiel als Base64-kodierte `.pfx` Datei oder als erreichbare Zertifikats-URL. `WINDOWS_CSC_KEY_PASSWORD` ist das Passwort des Zertifikats. Ohne ein gültiges Code-Signing-Zertifikat bleibt der Herausgeber für Windows unbekannt. + ## Download und Artefakte Der Windows-Build erzeugt zwei Dateien: diff --git a/electron/main.ts b/electron/main.ts index 12d3409..281dde4 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, dialog, ipcMain } from "electron"; +import { app, BrowserWindow, Menu, dialog, ipcMain } from "electron"; import isDev from "electron-is-dev"; import { readFile, writeFile } from "node:fs/promises"; import path from "node:path"; @@ -13,6 +13,8 @@ async function createWindow() { minWidth: 980, minHeight: 680, title: "EnvHelper", + frame: false, + autoHideMenuBar: true, backgroundColor: "#f6f7f4", webPreferences: { preload: path.join(__dirname, "preload.js"), @@ -21,6 +23,8 @@ async function createWindow() { } }); + Menu.setApplicationMenu(null); + if (isDev) { await win.loadURL("http://127.0.0.1:5173"); } else { @@ -45,6 +49,27 @@ ipcMain.handle("envhelper:open-file", async () => { }; }); +ipcMain.handle("envhelper:window-minimize", (event) => { + BrowserWindow.fromWebContents(event.sender)?.minimize(); +}); + +ipcMain.handle("envhelper:window-toggle-maximize", (event) => { + const win = BrowserWindow.fromWebContents(event.sender); + if (!win) { + return; + } + + if (win.isMaximized()) { + win.unmaximize(); + } else { + win.maximize(); + } +}); + +ipcMain.handle("envhelper:window-close", (event) => { + BrowserWindow.fromWebContents(event.sender)?.close(); +}); + ipcMain.handle("envhelper:save-file", async (_event, content: string) => { const result = await dialog.showSaveDialog({ defaultPath: ".env", diff --git a/electron/preload.ts b/electron/preload.ts index cf5fa72..9b7b055 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -2,5 +2,8 @@ import { contextBridge, ipcRenderer } from "electron"; contextBridge.exposeInMainWorld("envHelper", { openFile: () => ipcRenderer.invoke("envhelper:open-file"), - saveFile: (content: string) => ipcRenderer.invoke("envhelper:save-file", content) + saveFile: (content: string) => ipcRenderer.invoke("envhelper:save-file", content), + minimizeWindow: () => ipcRenderer.invoke("envhelper:window-minimize"), + toggleMaximizeWindow: () => ipcRenderer.invoke("envhelper:window-toggle-maximize"), + closeWindow: () => ipcRenderer.invoke("envhelper:window-close") }); diff --git a/package.json b/package.json index 9f5a7c0..6c3a720 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "package.json" ], "win": { + "publisherName": "MrSphay", "target": [ "nsis", "portable" diff --git a/src/App.tsx b/src/App.tsx index 9cb61a7..2cb37d3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,8 @@ import { FileDown, FileInput, Languages, + Maximize2, + Minus, Plus, RefreshCcw, Settings, @@ -328,6 +330,24 @@ export default function App() { return (
+
+
+
EH
+ EnvHelper +
+
+ + + +
+
+
EH
diff --git a/src/styles.css b/src/styles.css index b91b57b..8f1262e 100644 --- a/src/styles.css +++ b/src/styles.css @@ -140,9 +140,71 @@ textarea:focus { .appShell { display: grid; gap: 12px; - grid-template-rows: auto auto minmax(260px, 1fr) minmax(160px, 0.52fr); + grid-template-rows: 34px auto auto minmax(260px, 1fr) minmax(160px, 0.52fr); height: 100%; padding: 14px; + padding-top: 8px; +} + +.titlebar { + -webkit-app-region: drag; + align-items: center; + background: color-mix(in srgb, var(--surface) 88%, var(--bg)); + border: 1px solid var(--border); + border-radius: 8px; + display: flex; + justify-content: space-between; + overflow: hidden; +} + +.titlebarBrand { + align-items: center; + color: var(--muted); + display: flex; + font-size: 0.82rem; + font-weight: 800; + gap: 8px; + padding-left: 10px; +} + +.titlebarMark { + align-items: center; + background: var(--accent-soft); + border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border)); + border-radius: 6px; + color: var(--accent-strong); + display: flex; + font-size: 0.66rem; + height: 20px; + justify-content: center; + width: 24px; +} + +.windowControls { + -webkit-app-region: no-drag; + align-items: stretch; + display: flex; + height: 100%; +} + +.windowControls button { + background: transparent; + border-radius: 0; + color: var(--muted); + min-height: 0; + min-width: 44px; + padding: 0; +} + +.windowControls button:hover { + background: var(--surface-subtle); + color: var(--text); + transform: none; +} + +.windowControls .closeButton:hover { + background: #c93d32; + color: #fff; } .toolbar { @@ -504,7 +566,7 @@ textarea::selection { @media (max-width: 1100px) { .appShell { - grid-template-rows: auto auto minmax(420px, 1fr) minmax(320px, auto); + grid-template-rows: 34px auto auto minmax(420px, 1fr) minmax(320px, auto); overflow: auto; } diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index d1c7072..970e386 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -9,5 +9,8 @@ interface Window { envHelper?: { openFile: () => Promise; saveFile: (content: string) => Promise; + minimizeWindow: () => Promise; + toggleMaximizeWindow: () => Promise; + closeWindow: () => Promise; }; }