Adapt the web version to Electron

This commit is contained in:
2025-08-24 23:15:20 +03:00
Unverified
parent 3bbfafa9e5
commit 8348547baa
12 changed files with 286 additions and 175 deletions
+12
View File
@@ -0,0 +1,12 @@
export type Platform = "win32" | "darwin" | "linux"
export interface ElectronInterface {
desktop: true,
platform: Platform
}
declare global {
interface Window {
electronInterface: ElectronInterface
}
}
+12 -1
View File
@@ -1,10 +1,21 @@
import { app, BrowserWindow } from 'electron'; import { app, BrowserWindow } from 'electron';
import path from "node:path";
app.whenReady().then(() => { app.whenReady().then(() => {
const win = new BrowserWindow({ const win = new BrowserWindow({
title: 'Main window', title: 'Main window',
minWidth: 650, minWidth: 650,
minHeight: 420 minHeight: 420,
webPreferences: {
preload: path.join(import.meta.dirname, "preload.mjs")
},
titleBarStyle: "hidden",
...(process.platform !== 'darwin' ? { titleBarOverlay: true } : {}),
trafficLightPosition: {
x: 16 - 4,
y: 16 - 4
},
titleBarOverlay: process.platform !== "darwin"
}) })
// You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve` // You can use `process.env.VITE_DEV_SERVER_URL` when the vite command is called `serve`
+9
View File
@@ -0,0 +1,9 @@
import { contextBridge } from "electron";
import type { ElectronInterface, Platform } from "../electron";
const electronInterface: ElectronInterface = {
desktop: true,
platform: process.platform as Platform
}
contextBridge.exposeInMainWorld("electronInterface", electronInterface);
+181 -169
View File
@@ -7,185 +7,197 @@
<link rel="icon" href="./src/images/logo.png" /> <link rel="icon" href="./src/images/logo.png" />
</head> </head>
<body> <body>
<!-- Login Form --> <div id="electron-title-bar">
<div id="login-form" class="auth-container"> <div class="macos-padding"></div>
<div class="auth-card fade-in"> <div id="window-title"></div>
<div class="auth-header"> <!-- <div class="window-controls">
<h2> <mdui-button-icon icon="remove" id="window-minimize"></mdui-button-icon>
<span class="material-symbols filled large">login</span> <mdui-button-icon icon="stack--outlined" id="window-restore" class="hidden"></mdui-button-icon>
Добро пожаловать! <mdui-button-icon icon="ad--outlined" id="window-maximize"></mdui-button-icon>
</h2> <mdui-button-icon icon="close" id="window-close"></mdui-button-icon>
<p>Войдите в свой аккаунт</p> </div> -->
</div>
<div class="auth-body">
<div id="login-alerts"></div>
<form id="login-form-element">
<mdui-text-field
label="Имя пользователя"
id="login-username"
name="username"
variant="outlined"
icon="person--filled"
autocomplete="username"
required>
</mdui-text-field>
<mdui-text-field
label="Пароль"
id="login-password"
name="password"
variant="outlined"
type="password"
toggle-password
icon="password--filled"
autocomplete="current-password"
required>
</mdui-text-field>
<mdui-button type="submit">Войти</mdui-button>
</form>
<div class="text-center">
<p>Ещё нет аккаунта? <a href="#" id="register-link" class="link">Зарегистрируйтесь</a></p>
</div>
</div>
</div>
</div> </div>
<div id="main-wrapper">
<!-- Register Form --> <!-- Login Form -->
<div id="register-form" class="auth-container" style="display: none;"> <div id="login-form" class="auth-container">
<div class="auth-card fade-in"> <div class="auth-card fade-in">
<div class="auth-header"> <div class="auth-header">
<h2> <h2>
<span class="material-symbols filled large">person_add</span> <span class="material-symbols filled large">login</span>
Регистрация Добро пожаловать!
</h2> </h2>
<p>Создайте новый аккаунт</p> <p>Войдите в свой аккаунт</p>
</div>
<div class="auth-body">
<div id="register-alerts"></div>
<form id="register-form-element">
<mdui-text-field
label="Имя пользователя"
id="register-username"
name="username"
variant="outlined"
icon="person--filled"
autocomplete="username"
maxlength="20"
counter
required>
</mdui-text-field>
<mdui-text-field
label="Пароль"
id="register-password"
name="password"
variant="outlined"
type="password"
toggle-password
icon="password--filled"
autocomplete="new-password"
required>
</mdui-text-field>
<mdui-text-field
label="Подтвердите пароль"
id="register-confirm-password"
name="confirm_password"
variant="outlined"
type="password"
toggle-password
icon="password--filled"
autocomplete="new-password"
required>
</mdui-text-field>
<mdui-button type="submit">Зарегистрироваться</mdui-button>
</form>
<div class="text-center">
<p>
Уже есть аккаунт?
<a href="#" id="login-link" class="link">Войдите</a>
</p>
</div> </div>
</div> <div class="auth-body">
</div> <div id="login-alerts"></div>
</div>
<!-- Chat Interface --> <form id="login-form-element">
<div id="chat-interface" style="display: none;"> <mdui-text-field
<div class="all-container"> label="Имя пользователя"
<div class="chat-list" id="chat-list"> id="login-username"
<header class="chat-header-left"> name="username"
<div id="productname">Loading...</div> variant="outlined"
<div class="profile"> icon="person--filled"
<a href="#" id="profile-open"> autocomplete="username"
<img src="./src/images/default-avatar.png" alt="" id="preview1" /> required>
</a> </mdui-text-field>
<mdui-text-field
label="Пароль"
id="login-password"
name="password"
variant="outlined"
type="password"
toggle-password
icon="password--filled"
autocomplete="current-password"
required>
</mdui-text-field>
<mdui-button type="submit">Войти</mdui-button>
</form>
<div class="text-center">
<p>Ещё нет аккаунта? <a href="#" id="register-link" class="link">Зарегистрируйтесь</a></p>
</div> </div>
</header>
<div class="chat-tabs">
<mdui-tabs value="chats" full-width>
<mdui-tab value="chats">
Чаты
</mdui-tab>
<mdui-tab value="channels">
Каналы
</mdui-tab>
<mdui-tab value="contacts">
Контакты
</mdui-tab>
<mdui-tab-panel slot="panel" value="chats">
<mdui-list>
<mdui-list-item headline="Общий чат" description="Вы: Последнее сообщение" id="chat-list-chat-1">
<img src="./src/images/default-avatar.png" alt="" slot="icon" />
</mdui-list-item>
<mdui-list-item headline="Общий чат 2" description="Вы: Последнее сообщение" id="chat-list-chat-2">
<img src="./src/images/default-avatar.png" alt="" slot="icon" />
</mdui-list-item>
</mdui-list>
</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="channels">Скоро будет...</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="contacts">Скоро будет...</mdui-tab-panel>
</mdui-tabs>
</div> </div>
<mdui-bottom-app-bar>
<mdui-button-icon icon="settings--filled" id="settings-open"></mdui-button-icon>
<mdui-button-icon icon="group_add--filled"></mdui-button-icon>
<div style="flex-grow: 1"></div>
<mdui-fab icon="edit--filled"></mdui-fab>
</mdui-bottom-app-bar>
</div> </div>
<div class="chat-container"> </div>
<div class="chat-main" id="chat-inner">
<div class="chat-header"> <!-- Register Form -->
<img src="src/images/default-avatar.png" alt="Avatar" class="chat-header-avatar"> <div id="register-form" class="auth-container" style="display: none;">
<div class="chat-header-info"> <div class="auth-card fade-in">
<div class="info-chat"> <div class="auth-header">
<h4 id="chat-name">Общий чат</h4> <h2>
<p> <span class="material-symbols filled large">person_add</span>
<span class="online-status"></span> Регистрация
Онлайн </h2>
</p> <p>Создайте новый аккаунт</p>
</div> </div>
<a href="#" id="hide-chat">Свернуть чат</a> <div class="auth-body">
<div id="register-alerts"></div>
<form id="register-form-element">
<mdui-text-field
label="Имя пользователя"
id="register-username"
name="username"
variant="outlined"
icon="person--filled"
autocomplete="username"
maxlength="20"
counter
required>
</mdui-text-field>
<mdui-text-field
label="Пароль"
id="register-password"
name="password"
variant="outlined"
type="password"
toggle-password
icon="password--filled"
autocomplete="new-password"
required>
</mdui-text-field>
<mdui-text-field
label="Подтвердите пароль"
id="register-confirm-password"
name="confirm_password"
variant="outlined"
type="password"
toggle-password
icon="password--filled"
autocomplete="new-password"
required>
</mdui-text-field>
<mdui-button type="submit">Зарегистрироваться</mdui-button>
</form>
<div class="text-center">
<p>
Уже есть аккаунт?
<a href="#" id="login-link" class="link">Войдите</a>
</p>
</div>
</div>
</div>
</div>
<!-- Chat Interface -->
<div id="chat-interface" style="display: none;">
<div class="all-container">
<div class="chat-list" id="chat-list">
<header class="chat-header-left">
<div id="productname">Loading...</div>
<div class="profile">
<a href="#" id="profile-open">
<img src="./src/images/default-avatar.png" alt="" id="preview1" />
</a>
</div> </div>
</div> </header>
<div class="chat-tabs">
<mdui-tabs value="chats" full-width>
<mdui-tab value="chats">
Чаты
</mdui-tab>
<mdui-tab value="channels">
Каналы
</mdui-tab>
<mdui-tab value="contacts">
Контакты
</mdui-tab>
<div class="chat-messages" id="chat-messages"> <mdui-tab-panel slot="panel" value="chats">
<!-- Messages will be loaded here dynamically --> <mdui-list>
<mdui-list-item headline="Общий чат" description="Вы: Последнее сообщение" id="chat-list-chat-1">
<img src="./src/images/default-avatar.png" alt="" slot="icon" />
</mdui-list-item>
<mdui-list-item headline="Общий чат 2" description="Вы: Последнее сообщение" id="chat-list-chat-2">
<img src="./src/images/default-avatar.png" alt="" slot="icon" />
</mdui-list-item>
</mdui-list>
</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="channels">Скоро будет...</mdui-tab-panel>
<mdui-tab-panel slot="panel" value="contacts">Скоро будет...</mdui-tab-panel>
</mdui-tabs>
</div> </div>
<mdui-bottom-app-bar>
<mdui-button-icon icon="settings--filled" id="settings-open"></mdui-button-icon>
<mdui-button-icon icon="group_add--filled"></mdui-button-icon>
<div style="flex-grow: 1"></div>
<mdui-fab icon="edit--filled"></mdui-fab>
</mdui-bottom-app-bar>
</div>
<div class="chat-container">
<div class="chat-main" id="chat-inner">
<div class="chat-header">
<img src="src/images/default-avatar.png" alt="Avatar" class="chat-header-avatar">
<div class="chat-header-info">
<div class="info-chat">
<h4 id="chat-name">Общий чат</h4>
<p>
<span class="online-status"></span>
Онлайн
</p>
</div>
<a href="#" id="hide-chat">Свернуть чат</a>
</div>
</div>
<div class="chat-input-wrapper"> <div class="chat-messages" id="chat-messages">
<div class="chat-input"> <!-- Messages will be loaded here dynamically -->
<form class="input-group" id="message-form"> </div>
<input type="text" class="message-input" id="message-input" placeholder="Напишите сообщение..." autocomplete="off">
<button type="submit" class="send-btn"> <div class="chat-input-wrapper">
<span class="material-symbols filled">send</span> <div class="chat-input">
</button> <form class="input-group" id="message-form">
</form> <input type="text" class="message-input" id="message-input" placeholder="Напишите сообщение..." autocomplete="off">
<button type="submit" class="send-btn">
<span class="material-symbols filled">send</span>
</button>
</form>
</div>
</div> </div>
</div> </div>
</div> </div>
+3
View File
@@ -55,6 +55,7 @@ export function showLogin(): void {
document.getElementById('register-form')!.style.display = 'none'; document.getElementById('register-form')!.style.display = 'none';
document.getElementById('chat-interface')!.style.display = 'none'; document.getElementById('chat-interface')!.style.display = 'none';
clearAlerts(); clearAlerts();
document.getElementById("electron-title-bar")!.classList.add("color-surface");
} }
/** /**
@@ -68,6 +69,7 @@ export function showRegister(): void {
document.getElementById('register-form')!.style.display = 'flex'; document.getElementById('register-form')!.style.display = 'flex';
document.getElementById('chat-interface')!.style.display = 'none'; document.getElementById('chat-interface')!.style.display = 'none';
clearAlerts(); clearAlerts();
document.getElementById("electron-title-bar")!.classList.add("color-surface");
} }
/** /**
@@ -81,6 +83,7 @@ export function showChat(): void {
document.getElementById('register-form')!.style.display = 'none'; document.getElementById('register-form')!.style.display = 'none';
document.getElementById('chat-interface')!.style.display = 'block'; document.getElementById('chat-interface')!.style.display = 'block';
loadMessages(); loadMessages();
document.getElementById("electron-title-bar")!.classList.remove("color-surface");
} }
/** /**
+1 -1
View File
@@ -5,7 +5,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
min-height: 100vh; height: 100%;
padding: 2rem; padding: 2rem;
background-color: $color-dark-surface; background-color: $color-dark-surface;
+2
View File
@@ -2,6 +2,8 @@
@use "common/material" as *; @use "common/material" as *;
#chat-interface { #chat-interface {
height: 100%;
.header { .header {
display: flex; display: flex;
background-color: $color-dark-surface-container; background-color: $color-dark-surface-container;
+43
View File
@@ -0,0 +1,43 @@
@use "common/material" as *;
#electron-title-bar {
display: none;
}
html.electron {
#electron-title-bar {
display: flex;
flex-direction: row;
gap: 8px;
height: 40px;
background-color: $color-dark-surface-container;
width: 100%;
-webkit-app-region: drag;
user-select: none;
z-index: 10;
transition: background-color 0.5s ease;
&.color-surface {
background-color: $color-dark-surface;
}
}
#window-title {
flex: 1;
display: flex;
align-items: center;
font-weight: 500;
}
&.platform-darwin .macos-padding {
width: 70px;
}
// .window-controls {
// -webkit-app-region: no-drag;
// .hidden {
// display: none;
// }
// }
}
+1 -1
View File
@@ -7,7 +7,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
height: 100vh; height: 100%;
} }
#profile { #profile {
+8
View File
@@ -7,6 +7,7 @@
@use "common/components"; @use "common/components";
@use "common/colors" as *; @use "common/colors" as *;
@use "common/material" as *; @use "common/material" as *;
@use "electron";
@use "lib/fonts/montserrat"; @use "lib/fonts/montserrat";
@use "lib/fonts/material-symbols"; @use "lib/fonts/material-symbols";
@@ -24,6 +25,13 @@ body {
height: 100vh; height: 100vh;
position: relative; position: relative;
margin: 0; margin: 0;
display: flex;
flex-direction: column;
#main-wrapper {
flex: 1;
position: relative;
}
} }
mdui-dialog { mdui-dialog {
+10
View File
@@ -0,0 +1,10 @@
import "../electron.d.ts";
import { PRODUCT_NAME } from "./config";
if (window.electronInterface !== undefined) {
console.log("Running in Electron");
document.documentElement.classList.add("electron", `platform-${window.electronInterface.platform}`);
document.getElementById("window-title")!.textContent = PRODUCT_NAME;
} else {
console.log("Running in normal browser");
}
+1
View File
@@ -16,3 +16,4 @@ import "./init";
import "./profile"; import "./profile";
import "./message-context-menu"; import "./message-context-menu";
import "./user-profile-dialog"; import "./user-profile-dialog";
import "./electron";