mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Добавить новый дизайн
This commit is contained in:
+127
-53
@@ -1,35 +1,51 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru">
|
<html lang="ru" class="mdui-theme-dark">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Loading...</title>
|
<title>Loading...</title>
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
<link rel="icon" href="./src/images/logo.png" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Login Form -->
|
<!-- Login Form -->
|
||||||
<div id="login-form" class="auth-container">
|
<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><span class="material-symbols filled large">login</span> Добро пожаловать!</h2>
|
<h2>
|
||||||
|
<span class="material-symbols filled large">login</span>
|
||||||
|
Добро пожаловать!
|
||||||
|
</h2>
|
||||||
<p>Войдите в свой аккаунт</p>
|
<p>Войдите в свой аккаунт</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-body">
|
<div class="auth-body">
|
||||||
<div id="login-alerts"></div>
|
<div id="login-alerts"></div>
|
||||||
|
|
||||||
<form id="login-form-element">
|
<form id="login-form-element">
|
||||||
<div class="form-group">
|
<mdui-text-field
|
||||||
<label for="login-username">Имя пользователя</label>
|
label="Имя пользователя"
|
||||||
<input type="text" class="form-control" id="login-username" name="username" required>
|
id="login-username"
|
||||||
</div>
|
name="username"
|
||||||
<div class="form-group">
|
variant="outlined"
|
||||||
<label for="login-password">Пароль</label>
|
icon="person--filled"
|
||||||
<input type="password" class="form-control" id="login-password" name="password" required>
|
autocomplete="username"
|
||||||
</div>
|
required>
|
||||||
<button type="submit" class="btn btn-block">Войти</button>
|
</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>
|
</form>
|
||||||
|
|
||||||
<div class="text-center mt-3">
|
<div class="text-center">
|
||||||
<p>Ещё нет аккаунта? <a href="#" id="register-link" class="link">Зарегистрируйтесь</a></p>
|
<p>Ещё нет аккаунта? <a href="#" id="register-link" class="link">Зарегистрируйтесь</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,31 +56,54 @@
|
|||||||
<div id="register-form" class="auth-container" style="display: none;">
|
<div id="register-form" class="auth-container" style="display: none;">
|
||||||
<div class="auth-card fade-in">
|
<div class="auth-card fade-in">
|
||||||
<div class="auth-header">
|
<div class="auth-header">
|
||||||
<h2><span class="material-symbols filled large">person_add</span> Регистрация</h2>
|
<h2>
|
||||||
|
<span class="material-symbols filled large">person_add</span>
|
||||||
|
Регистрация
|
||||||
|
</h2>
|
||||||
<p>Создайте новый аккаунт</p>
|
<p>Создайте новый аккаунт</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="auth-body">
|
<div class="auth-body">
|
||||||
<div id="register-alerts"></div>
|
<div id="register-alerts"></div>
|
||||||
|
|
||||||
<form id="register-form-element">
|
<form id="register-form-element">
|
||||||
<div class="form-group">
|
<mdui-text-field
|
||||||
<label for="register-username">Имя пользователя</label>
|
label="Имя пользователя"
|
||||||
<input type="text" class="form-control" id="register-username" name="username" required>
|
id="register-username"
|
||||||
<small class="text-muted">От 3 до 20 символов, без пробелов</small>
|
name="username"
|
||||||
</div>
|
variant="outlined"
|
||||||
<div class="form-group">
|
icon="person--filled"
|
||||||
<label for="register-password">Пароль</label>
|
autocomplete="username"
|
||||||
<input type="password" class="form-control" id="register-password" name="password" required>
|
maxlength="20"
|
||||||
<small class="text-muted">Минимум 5 символов, без пробелов</small>
|
counter
|
||||||
</div>
|
required>
|
||||||
<div class="form-group">
|
</mdui-text-field>
|
||||||
<label for="register-confirm-password">Подтвердите пароль</label>
|
<mdui-text-field
|
||||||
<input type="password" class="form-control" id="register-confirm-password" name="confirm_password" required>
|
label="Пароль"
|
||||||
</div>
|
id="register-password"
|
||||||
<button type="submit" class="btn btn-block">Зарегистрироваться</button>
|
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>
|
</form>
|
||||||
|
|
||||||
<div class="text-center mt-3">
|
<div class="text-center">
|
||||||
<p>
|
<p>
|
||||||
Уже есть аккаунт?
|
Уже есть аккаунт?
|
||||||
<a href="#" id="login-link" class="link">Войдите</a>
|
<a href="#" id="login-link" class="link">Войдите</a>
|
||||||
@@ -76,33 +115,59 @@
|
|||||||
|
|
||||||
<!-- Chat Interface -->
|
<!-- Chat Interface -->
|
||||||
<div id="chat-interface" style="display: none;">
|
<div id="chat-interface" style="display: none;">
|
||||||
<div class="header">
|
<div class="all-container">
|
||||||
<div class="header-content">
|
<div class="chat-list" id="chat-list">
|
||||||
<div class="logo">
|
<header class="chat-header-left">
|
||||||
<span id="productname">Loading...</span>
|
<div id="productname">Loading...</div>
|
||||||
</div>
|
<div class="profile">
|
||||||
<ul class="nav-links">
|
<a href="#" id="profbut"><img src="./src/images/default-avatar.png" alt=""></a>
|
||||||
<li>
|
|
||||||
<a href="#" id="chat-link">
|
|
||||||
<span class="material-symbols filled">chat_bubble</span> Общий чат
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="#" id="logout-link">
|
|
||||||
<span class="material-symbols filled">logout</span> Выйти
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</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>
|
||||||
|
|
||||||
|
<mdui-tab-panel slot="panel" value="chats">
|
||||||
|
<mdui-list>
|
||||||
|
<mdui-list-item headline="Общий чат" description="Вы: Последнее сообщение" id="chat1but">
|
||||||
|
<img src="./src/images/default-avatar.png" alt="" slot="icon" />
|
||||||
|
</mdui-list-item>
|
||||||
|
<mdui-list-item headline="Общий чат 2" description="Вы: Последнее сообщение" id="chat1but2">
|
||||||
|
<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>
|
||||||
|
<mdui-bottom-app-bar>
|
||||||
|
<mdui-button-icon icon="settings--filled"></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-container">
|
||||||
<div class="chat-main">
|
<div class="chat-main" id="conteinerchat">
|
||||||
<div class="chat-header">
|
<div class="chat-header">
|
||||||
<img src="src/images/default-avatar.png" alt="Avatar" class="chat-header-avatar">
|
<img src="src/images/default-avatar.png" alt="Avatar" class="chat-header-avatar">
|
||||||
<div class="chat-header-info">
|
<div class="chat-header-info">
|
||||||
<h4>Общий чат</h4>
|
<div class="info-chat">
|
||||||
<p><span class="online-status"></span> Онлайн</p>
|
<h4 id="namechat">Общий чат</h4>
|
||||||
|
<p>
|
||||||
|
<span class="online-status"></span> Онлайн
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a href="#" id="chat-recrol">Свернуть чат</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -123,7 +188,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<mdui-dialog id="profile-dialog" close-on-overlay-click close-on-esc>
|
||||||
|
<p>
|
||||||
|
Скоро будет...
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<mdui-button id="profile-dialog-close">Закрыть</mdui-button>
|
||||||
|
</p>
|
||||||
|
</mdui-dialog>
|
||||||
<script src="src/main.ts" type="module"></script>
|
<script src="src/main.ts" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { loadMessages } from "./main";
|
import { loadMessages } from "./chat";
|
||||||
import type { Headers, ErrorResponse, User, LoginResponse, LoginRequest, RegisterRequest } from "./types";
|
import type { Headers, ErrorResponse, User, LoginResponse, LoginRequest, RegisterRequest } from "./types";
|
||||||
import { API_BASE_URL } from "./config";
|
import { API_BASE_URL } from "./config";
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { getAuthHeaders, currentUser, authToken } from "./auth";
|
||||||
|
import { API_BASE_URL } from "./config";
|
||||||
|
import { websocket } from "./websocket";
|
||||||
|
import type { Message, Messages, WebSocketMessage } from "./types";
|
||||||
|
import { formatTime } from "./utils";
|
||||||
|
|
||||||
|
|
||||||
|
export function addMessage(message: Message, isAuthor: boolean) {
|
||||||
|
const messagesContainer = document.querySelector('.chat-messages') as HTMLElement;
|
||||||
|
const messageDiv = document.createElement('div');
|
||||||
|
messageDiv.classList.add("message");
|
||||||
|
if (isAuthor) {
|
||||||
|
messageDiv.classList.add("sent");
|
||||||
|
} else {
|
||||||
|
messageDiv.classList.add("received");
|
||||||
|
}
|
||||||
|
messageDiv.dataset.id = `${message.id}`;
|
||||||
|
|
||||||
|
const messageInner = document.createElement('div');
|
||||||
|
messageInner.classList.add('message-inner');
|
||||||
|
|
||||||
|
if (!isAuthor) {
|
||||||
|
const usernameDiv = document.createElement('div');
|
||||||
|
usernameDiv.classList.add('message-username');
|
||||||
|
usernameDiv.textContent = message.username;
|
||||||
|
messageInner.appendChild(usernameDiv);
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentDiv = document.createElement('div');
|
||||||
|
contentDiv.textContent = message.content;
|
||||||
|
messageInner.appendChild(contentDiv);
|
||||||
|
|
||||||
|
const timeDiv = document.createElement('div');
|
||||||
|
timeDiv.classList.add('message-time');
|
||||||
|
timeDiv.textContent = formatTime(message.timestamp);
|
||||||
|
|
||||||
|
if (isAuthor && message.is_read) {
|
||||||
|
const checkIcon = document.createElement('span');
|
||||||
|
checkIcon.classList.add("material-symbols", "outlined");
|
||||||
|
timeDiv.appendChild(checkIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
messageInner.appendChild(timeDiv);
|
||||||
|
messageDiv.appendChild(messageInner);
|
||||||
|
messagesContainer.appendChild(messageDiv);
|
||||||
|
|
||||||
|
// Прокрутка к новому сообщению
|
||||||
|
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadMessages() {
|
||||||
|
fetch(`${API_BASE_URL}/get_messages`, {
|
||||||
|
headers: getAuthHeaders()
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data: Messages) => {
|
||||||
|
if (data.messages && data.messages.length > 0) {
|
||||||
|
const messagesContainer = document.querySelector('.chat-messages') as HTMLElement;
|
||||||
|
|
||||||
|
const lastMessage = messagesContainer.lastElementChild as HTMLElement
|
||||||
|
let lastMessageId: number = 0
|
||||||
|
if (lastMessage) {
|
||||||
|
lastMessageId = Number(lastMessage.dataset.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем только новые сообщения
|
||||||
|
data.messages.forEach(msg => {
|
||||||
|
if (msg.id > lastMessageId) {
|
||||||
|
addMessage(msg, msg.username == currentUser!.username);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendMessage() {
|
||||||
|
const input = document.querySelector('.message-input') as HTMLInputElement;
|
||||||
|
const message = input.value.trim();
|
||||||
|
|
||||||
|
if (message) {
|
||||||
|
const payload: WebSocketMessage = {
|
||||||
|
data: {
|
||||||
|
content: message
|
||||||
|
},
|
||||||
|
credentials: {
|
||||||
|
scheme: "Bearer",
|
||||||
|
credentials: authToken!
|
||||||
|
},
|
||||||
|
type: "sendMessage"
|
||||||
|
}
|
||||||
|
|
||||||
|
let callback: ((e: MessageEvent) => void) | null = null
|
||||||
|
callback = (e) => {
|
||||||
|
websocket.removeEventListener("message", callback!);
|
||||||
|
const response: WebSocketMessage = JSON.parse(e.data)
|
||||||
|
console.log(response)
|
||||||
|
if (!response.error) {
|
||||||
|
input.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
websocket.addEventListener("message", callback);
|
||||||
|
|
||||||
|
websocket.send(JSON.stringify(payload));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
document.getElementById('message-form')!.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
sendMessage();
|
||||||
|
});
|
||||||
+16
-11
@@ -1,4 +1,5 @@
|
|||||||
@use "common/colors" as *;
|
@use "common/colors" as *;
|
||||||
|
@use "common/material" as *;
|
||||||
|
|
||||||
.auth-container {
|
.auth-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -6,30 +7,27 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
background-color: $color-dark-surface;
|
||||||
|
|
||||||
.auth-card {
|
.auth-card {
|
||||||
background: white;
|
background-color: $color-dark-surface-container;
|
||||||
|
color: $color-dark-on-surface;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.auth-header {
|
.auth-header {
|
||||||
background: linear-gradient(135deg, $primary, $primary-dark);
|
margin: 0;
|
||||||
color: white;
|
padding: 16px;
|
||||||
padding: 1.5rem;
|
padding-bottom: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
|
margin: 0;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -40,6 +38,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.auth-body {
|
.auth-body {
|
||||||
padding: 2rem;
|
padding: 25px;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+40
-10
@@ -2,15 +2,18 @@
|
|||||||
@use "common/material" as *;
|
@use "common/material" as *;
|
||||||
|
|
||||||
#chat-interface {
|
#chat-interface {
|
||||||
/* Header */
|
|
||||||
.header {
|
.header {
|
||||||
|
display: flex;
|
||||||
background-color: $color-dark-surface-container;
|
background-color: $color-dark-surface-container;
|
||||||
color: white;
|
color: white;
|
||||||
padding: 16px 16px;
|
padding: 16px 16px;
|
||||||
position: fixed;
|
position: static;
|
||||||
width: 100%;
|
justify-content: end;
|
||||||
top: 0;
|
width: fit-content;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -24,8 +27,8 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links {
|
#logouts {
|
||||||
display: flex;
|
display: none;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
@@ -53,13 +56,14 @@
|
|||||||
|
|
||||||
.chat-container {
|
.chat-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 70px);
|
width: 100%;
|
||||||
margin-top: 70px;
|
flex-direction: column;
|
||||||
|
|
||||||
.chat-main {
|
.chat-main {
|
||||||
flex: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.chat-header {
|
.chat-header {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
@@ -71,21 +75,30 @@
|
|||||||
.chat-header-avatar {
|
.chat-header-avatar {
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
border-radius: 50%;
|
border-radius: 20%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-header-info {
|
.chat-header-info {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
.info-chat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
margin: 0;
|
||||||
margin-bottom: 0.2rem;
|
margin-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
|
margin: 0;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: #718096;
|
color: #718096;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.online-status {
|
.online-status {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -95,6 +108,23 @@
|
|||||||
background-color: $success;
|
background-color: $success;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
justify-content: end;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: 2%;
|
||||||
|
top: 2%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
@use "common/colors" as *;
|
||||||
|
@use "common/material" as *;
|
||||||
|
|
||||||
|
|
||||||
|
// контейнер чата и панели с чатами
|
||||||
|
.all-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#profile {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 2000;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
position: fixed;
|
||||||
|
height: 100vh;
|
||||||
|
width: 27%;
|
||||||
|
background-color: $color-dark-surface-container;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.profileheader {
|
||||||
|
display: flex;
|
||||||
|
gap: 200px;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
border: solid 2px $color-dark-on-surface-variant;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.241);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#chat-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 0;
|
||||||
|
width: 40%;
|
||||||
|
background-color: $color-dark-surface-container;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.chat-header-left {
|
||||||
|
display: flex;
|
||||||
|
color: white;
|
||||||
|
font-size: 25px;
|
||||||
|
background-color: $color-dark-surface-container;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 110px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
|
||||||
|
#productname {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile {
|
||||||
|
font-size: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: start;
|
||||||
|
flex-shrink: 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
|
||||||
|
#closeprofile a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: white;
|
||||||
|
border: solid 2px $color-dark-on-surface-variant;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.241);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
$size: 45px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: $size;
|
||||||
|
height: $size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-tabs {
|
||||||
|
margin-top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
--mdui-color-surface: $color-dark-surface-container;
|
||||||
|
--mdui-color-surface-variant: transparent;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
border-radius: 20%;
|
||||||
|
object-fit: cover;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mdui-bottom-app-bar {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,2 @@
|
|||||||
$primary: #6C63FF;
|
|
||||||
$primary-dark: #564FD9;
|
|
||||||
$secondary: #FF6584;
|
|
||||||
$dark: #2D3748;
|
|
||||||
$light: #F7FAFC;
|
|
||||||
$gray: #E2E8F0;
|
|
||||||
$success: #48BB78;
|
$success: #48BB78;
|
||||||
$danger: #F56565;
|
$danger: #F56565;
|
||||||
@@ -1,62 +1,4 @@
|
|||||||
@use "colors" as *;
|
@use "material" as *;
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
color: $dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.8rem 1rem;
|
|
||||||
border: 1px solid $gray;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 1rem;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: $primary;
|
|
||||||
box-shadow: 0 0 0 3px rgba(108, 99, 255, 0.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
background: linear-gradient(135deg, $primary, $primary-dark);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 0.8rem 1.5rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: linear-gradient(135deg, $primary-dark, $primary);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-block {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -83,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: $primary;
|
color: $color-dark-primary;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,11 @@ $color-dark-surface-container-low: rgb(24 28 31);
|
|||||||
$color-dark-surface-container: rgb(28 32 36);
|
$color-dark-surface-container: rgb(28 32 36);
|
||||||
$color-dark-surface-container-high: rgb(38 43 46);
|
$color-dark-surface-container-high: rgb(38 43 46);
|
||||||
$color-dark-surface-container-highest: rgb(49 53 57);
|
$color-dark-surface-container-highest: rgb(49 53 57);
|
||||||
|
// custom colors
|
||||||
|
$color-1: rgb(82, 109, 246);
|
||||||
|
$color-2: rgb(65, 11, 113);
|
||||||
|
$color-4: rgb(95, 26, 198);
|
||||||
|
$color-3: rgb(49, 71, 179);
|
||||||
// Light
|
// Light
|
||||||
$color-light-primary: rgb(31 101 134);
|
$color-light-primary: rgb(31 101 134);
|
||||||
$color-light-surface-tint: rgb(31 101 134);
|
$color-light-surface-tint: rgb(31 101 134);
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
/* fallback */
|
@mixin iconsFontStyle() {
|
||||||
@font-face {
|
|
||||||
font-family: 'Material Symbols Outlined';
|
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
src: url(material-symbols.woff2) format('woff2');
|
src: url(material-symbols.woff2) format('woff2');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* fallback */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Symbols Outlined';
|
||||||
|
@include iconsFontStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons Outlined';
|
||||||
|
@include iconsFontStyle();
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Material Icons';
|
||||||
|
@include iconsFontStyle();
|
||||||
|
}
|
||||||
|
|
||||||
.material-symbols {
|
.material-symbols {
|
||||||
font-family: 'Material Symbols Outlined';
|
font-family: 'Material Symbols Outlined';
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
@use "auth";
|
@use "auth";
|
||||||
@use "chat";
|
@use "chat";
|
||||||
|
@use "panelchat";
|
||||||
@use "common/animations";
|
@use "common/animations";
|
||||||
@use "common/components";
|
@use "common/components";
|
||||||
@use "common/colors" as *;
|
@use "common/colors" as *;
|
||||||
@@ -8,9 +9,8 @@
|
|||||||
@use "lib/fonts/montserrat";
|
@use "lib/fonts/montserrat";
|
||||||
@use "lib/fonts/material-symbols";
|
@use "lib/fonts/material-symbols";
|
||||||
|
|
||||||
|
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,4 +19,17 @@ body {
|
|||||||
background-color: $color-dark-surface;
|
background-color: $color-dark-surface;
|
||||||
color: $color-dark-on-surface;
|
color: $color-dark-on-surface;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
height: 100vh;
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mdui-dialog {
|
||||||
|
> *:first-child {
|
||||||
|
margin-block-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> *:last-child {
|
||||||
|
margin-block-end: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
import { showLogin } from "./auth";
|
||||||
|
import { PRODUCT_NAME } from "./config";
|
||||||
|
|
||||||
|
showLogin();
|
||||||
|
|
||||||
|
document.getElementById("productname")!.textContent = PRODUCT_NAME;
|
||||||
|
document.title = PRODUCT_NAME;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import type { Dialog } from "mdui/components/dialog";
|
||||||
|
|
||||||
|
// сварачивание и разворачивание чата
|
||||||
|
const but = document.getElementById('chat-recrol')!;
|
||||||
|
const but_list1 = document.getElementById('chat1but')!;
|
||||||
|
const but_list2 = document.getElementById('chat1but2')!;
|
||||||
|
const cont1 = document.getElementById('conteinerchat')!;
|
||||||
|
const namechat = document.getElementById('namechat')!;
|
||||||
|
|
||||||
|
but.addEventListener('click', () => {
|
||||||
|
but.style.display = 'none';
|
||||||
|
cont1.style.display = 'none';
|
||||||
|
});
|
||||||
|
but_list1.addEventListener('click', () => {
|
||||||
|
but.style.display = 'flex';
|
||||||
|
cont1.style.display = 'flex';
|
||||||
|
namechat.textContent = 'общий чат';
|
||||||
|
});
|
||||||
|
but_list2.addEventListener('click', () => {
|
||||||
|
but.style.display = 'flex';
|
||||||
|
cont1.style.display = 'flex';
|
||||||
|
namechat.textContent = 'общий чат 2';
|
||||||
|
});
|
||||||
|
|
||||||
|
// открытие профиля
|
||||||
|
const butprofile = document.getElementById('profbut')!;
|
||||||
|
const dialog = document.getElementById("profile-dialog") as Dialog;
|
||||||
|
const dialogClose = document.getElementById("profile-dialog-close")!;
|
||||||
|
|
||||||
|
butprofile.addEventListener('click', () => {
|
||||||
|
dialog.open = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogClose.addEventListener("click", () => {
|
||||||
|
dialog.open = false;
|
||||||
|
});
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { loadChat, logout, showLogin, showRegister } from "./auth";
|
import { /* loadChat, */ /* logout, */ showLogin, showRegister } from "./auth";
|
||||||
|
|
||||||
const login = document.getElementById("login-link")!;
|
const login = document.getElementById("login-link")!;
|
||||||
const register = document.getElementById("register-link")!;
|
const register = document.getElementById("register-link")!;
|
||||||
const chat = document.getElementById("chat-link")!;
|
// const chat = document.getElementById("chat-link")!;
|
||||||
const logoutLink = document.getElementById("logout-link")!;
|
// const logoutLink = document.getElementById("logout-link")!;
|
||||||
|
|
||||||
login.addEventListener("click", showLogin);
|
login.addEventListener("click", showLogin);
|
||||||
register.addEventListener("click", showRegister);
|
register.addEventListener("click", showRegister);
|
||||||
chat.addEventListener("click", loadChat);
|
// chat.addEventListener("click", loadChat);
|
||||||
logoutLink.addEventListener("click", logout);
|
// logoutLink.addEventListener("click", logout);
|
||||||
|
|||||||
+6
-144
@@ -1,146 +1,8 @@
|
|||||||
import './css/style.scss';
|
import './css/style.scss';
|
||||||
import { showLogin, getAuthHeaders, authToken, currentUser } from './auth';
|
import "mdui/mdui.css";
|
||||||
import { API_BASE_URL, API_FULL_BASE_URL, PRODUCT_NAME } from './config';
|
|
||||||
import type { Message, Messages, WebSocketMessage } from './types';
|
|
||||||
import "./links";
|
import "./links";
|
||||||
|
import "./material";
|
||||||
const websocket = new WebSocket(`ws://${API_FULL_BASE_URL}/chat/ws`);
|
import "./chat";
|
||||||
|
import "./leftpanel";
|
||||||
|
import "./init";
|
||||||
// Функция для форматирования времени
|
|
||||||
function formatTime(dateString: string) {
|
|
||||||
const date = new Date(dateString);
|
|
||||||
let hours = date.getHours();
|
|
||||||
let minutes = date.getMinutes();
|
|
||||||
const hoursString = hours < 10 ? '0' + hours : hours;
|
|
||||||
const minutesString = minutes < 10 ? '0' + minutes : minutes;
|
|
||||||
return hoursString + ':' + minutesString;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавление нового сообщения в чат
|
|
||||||
export function addMessage(message: Message, isAuthor: boolean) {
|
|
||||||
const messagesContainer = document.querySelector('.chat-messages') as HTMLElement;
|
|
||||||
const messageDiv = document.createElement('div');
|
|
||||||
messageDiv.classList.add("message");
|
|
||||||
if (isAuthor) {
|
|
||||||
messageDiv.classList.add("sent");
|
|
||||||
} else {
|
|
||||||
messageDiv.classList.add("received");
|
|
||||||
}
|
|
||||||
messageDiv.dataset.id = `${message.id}`;
|
|
||||||
|
|
||||||
const messageInner = document.createElement('div');
|
|
||||||
messageInner.classList.add('message-inner');
|
|
||||||
|
|
||||||
if (!isAuthor) {
|
|
||||||
const usernameDiv = document.createElement('div');
|
|
||||||
usernameDiv.classList.add('message-username');
|
|
||||||
usernameDiv.textContent = message.username;
|
|
||||||
messageInner.appendChild(usernameDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
const contentDiv = document.createElement('div');
|
|
||||||
contentDiv.textContent = message.content;
|
|
||||||
messageInner.appendChild(contentDiv);
|
|
||||||
|
|
||||||
const timeDiv = document.createElement('div');
|
|
||||||
timeDiv.classList.add('message-time');
|
|
||||||
timeDiv.textContent = formatTime(message.timestamp);
|
|
||||||
|
|
||||||
if (isAuthor && message.is_read) {
|
|
||||||
const checkIcon = document.createElement('span');
|
|
||||||
checkIcon.classList.add("material-symbols", "outlined");
|
|
||||||
timeDiv.appendChild(checkIcon);
|
|
||||||
}
|
|
||||||
|
|
||||||
messageInner.appendChild(timeDiv);
|
|
||||||
messageDiv.appendChild(messageInner);
|
|
||||||
messagesContainer.appendChild(messageDiv);
|
|
||||||
|
|
||||||
// Прокрутка к новому сообщению
|
|
||||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загрузка сообщений
|
|
||||||
export function loadMessages() {
|
|
||||||
fetch(`${API_BASE_URL}/get_messages`, {
|
|
||||||
headers: getAuthHeaders()
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data: Messages) => {
|
|
||||||
if (data.messages && data.messages.length > 0) {
|
|
||||||
const messagesContainer = document.querySelector('.chat-messages') as HTMLElement;
|
|
||||||
|
|
||||||
const lastMessage = messagesContainer.lastElementChild as HTMLElement
|
|
||||||
let lastMessageId: number = 0
|
|
||||||
if (lastMessage) {
|
|
||||||
lastMessageId = Number(lastMessage.dataset.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем только новые сообщения
|
|
||||||
data.messages.forEach(msg => {
|
|
||||||
if (msg.id > lastMessageId) {
|
|
||||||
addMessage(msg, msg.username == currentUser!.username);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка сообщения
|
|
||||||
export function sendMessage() {
|
|
||||||
const input = document.querySelector('.message-input') as HTMLInputElement;
|
|
||||||
const message = input.value.trim();
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
const payload: WebSocketMessage = {
|
|
||||||
data: {
|
|
||||||
content: message
|
|
||||||
},
|
|
||||||
credentials: {
|
|
||||||
scheme: "Bearer",
|
|
||||||
credentials: authToken!
|
|
||||||
},
|
|
||||||
type: "sendMessage"
|
|
||||||
}
|
|
||||||
|
|
||||||
let callback: ((e: MessageEvent) => void) | null = null
|
|
||||||
callback = (e) => {
|
|
||||||
websocket.removeEventListener("message", callback!);
|
|
||||||
const response: WebSocketMessage = JSON.parse(e.data)
|
|
||||||
console.log(response)
|
|
||||||
if (!response.error) {
|
|
||||||
input.value = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
websocket.addEventListener("message", callback);
|
|
||||||
|
|
||||||
websocket.send(JSON.stringify(payload));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// --------------
|
|
||||||
// Initialization
|
|
||||||
// --------------
|
|
||||||
|
|
||||||
websocket.addEventListener("message", (e) => {
|
|
||||||
const message: WebSocketMessage = JSON.parse(e.data);
|
|
||||||
switch (message.type) {
|
|
||||||
case "newMessage": {
|
|
||||||
const newMessage: Message = message.data;
|
|
||||||
addMessage(newMessage, newMessage.username == currentUser!.username);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
showLogin();
|
|
||||||
|
|
||||||
document.getElementById('message-form')!.addEventListener('submit', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
sendMessage();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById("productname")!.textContent = PRODUCT_NAME;
|
|
||||||
document.title = PRODUCT_NAME;
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import 'mdui/components/tabs';
|
||||||
|
import 'mdui/components/tab';
|
||||||
|
import 'mdui/components/tab-panel';
|
||||||
|
import 'mdui/components/list';
|
||||||
|
import 'mdui/components/list-item';
|
||||||
|
import 'mdui/components/bottom-app-bar';
|
||||||
|
import 'mdui/components/button-icon';
|
||||||
|
import 'mdui/components/fab';
|
||||||
|
import 'mdui/components/dialog';
|
||||||
|
import 'mdui/components/button';
|
||||||
|
import 'mdui/components/text-field';
|
||||||
|
|
||||||
|
import { setColorScheme } from 'mdui/functions/setColorScheme.js';
|
||||||
|
|
||||||
|
setColorScheme("#91cef4");
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export function formatTime(dateString: string) {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
let hours = date.getHours();
|
||||||
|
let minutes = date.getMinutes();
|
||||||
|
const hoursString = hours < 10 ? '0' + hours : hours;
|
||||||
|
const minutesString = minutes < 10 ? '0' + minutes : minutes;
|
||||||
|
return hoursString + ':' + minutesString;
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { currentUser } from "./auth";
|
||||||
|
import { addMessage } from "./chat";
|
||||||
|
import { API_FULL_BASE_URL } from "./config";
|
||||||
|
import type { WebSocketMessage, Message } from "./types";
|
||||||
|
|
||||||
|
|
||||||
|
export const websocket = new WebSocket(`ws://${API_FULL_BASE_URL}/chat/ws`);
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
// Initialization
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
websocket.addEventListener("message", (e) => {
|
||||||
|
const message: WebSocketMessage = JSON.parse(e.data);
|
||||||
|
switch (message.type) {
|
||||||
|
case "newMessage": {
|
||||||
|
const newMessage: Message = message.data;
|
||||||
|
addMessage(newMessage, newMessage.username == currentUser!.username);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
+7
-6
@@ -7,24 +7,25 @@
|
|||||||
"backend:run": "cd backend && ../.venv/bin/fastapi dev --port 8300 main.py",
|
"backend:run": "cd backend && ../.venv/bin/fastapi dev --port 8300 main.py",
|
||||||
"backend:dependencies": "python3 -m venv .venv && ./.venv/bin/pip3 install -r backend/requirements.txt",
|
"backend:dependencies": "python3 -m venv .venv && ./.venv/bin/pip3 install -r backend/requirements.txt",
|
||||||
"backend:reinstall": "rm -rf .venv && npm run backend:dependencies",
|
"backend:reinstall": "rm -rf .venv && npm run backend:dependencies",
|
||||||
|
|
||||||
"frontend:dev": "vite frontend",
|
"frontend:dev": "vite frontend",
|
||||||
"frontend:build": "tsc --project frontend && vite build frontend",
|
"frontend:build": "tsc --project frontend && vite build frontend",
|
||||||
"frontend:preview": "vite preview frontend",
|
"frontend:preview": "vite preview frontend",
|
||||||
"frontend:dependencies": "npm install",
|
"frontend:dependencies": "npm install",
|
||||||
|
|
||||||
"dev": "concurrently 'npm run frontend:dev' 'npm run backend:run'",
|
"dev": "concurrently 'npm run frontend:dev' 'npm run backend:run'",
|
||||||
"build": "npm run frontend:build",
|
"build": "npm run frontend:build",
|
||||||
"install": "npm run backend:dependencies"
|
"install": "npm run backend:dependencies"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
"concurrently": "^9.2.0",
|
"concurrently": "^9.2.0",
|
||||||
"sass-embedded": "^1.90.0",
|
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
|
"sass-embedded": "^1.90.0",
|
||||||
|
"terser": "^5.43.1",
|
||||||
"typescript": "~5.9.2",
|
"typescript": "~5.9.2",
|
||||||
"vite": "^7.1.2",
|
"vite": "^7.1.2",
|
||||||
"vite-plugin-html": "^3.2.2",
|
"vite-plugin-html": "^3.2.2"
|
||||||
"terser": "^5.43.1",
|
},
|
||||||
"autoprefixer": "^10.4.21"
|
"dependencies": {
|
||||||
|
"mdui": "^2.1.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user