Skip to content

Commit 1f8d5f6

Browse files
add settings button and modal
1 parent 5098f93 commit 1f8d5f6

File tree

15 files changed

+1215
-970
lines changed

15 files changed

+1215
-970
lines changed

index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import dotenv from "dotenv";
22
dotenv.config();
33

4-
import { getSession } from "./src/server/session";
54
import { handleRequest } from "./src/server/routes";
6-
import { createWebSocketHandler } from "./src/server/websocket";
5+
import { getSession } from "./src/server/session";
76
import { setAllUsersOffline } from "./src/server/status";
7+
import { createWebSocketHandler } from "./src/server/websocket";
88

99
const port = process.env.PORT || 5177;
1010

public/css/input.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
@plugin '@tailwindcss/typography';
44

5-
@variant dark (@media (prefers-color-scheme: dark));
5+
@variant dark (&:where(.dark, .dark *));
66

77
/*
88
The default border color has changed to `currentColor` in Tailwind CSS v4,

public/css/tailwind.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/images/settings.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,53 @@
11
export class AudioManager {
2-
constructor(settingsManager) {
3-
this.settingsManager = settingsManager;
4-
this.notificationSound = new Audio('/public/sounds/notification.mp3');
5-
this.notificationSound.volume = 1; // Full volume
6-
7-
// Handle loading errors
8-
this.notificationSound.addEventListener('error', (e) => {
9-
console.error('Error loading notification sound:', e.error);
10-
});
2+
constructor(settingsManager) {
3+
this.settingsManager = settingsManager;
4+
this.notificationSound = new Audio("/public/sounds/notification.mp3");
5+
this.notificationSound.volume = 1; // Full volume
116

12-
// Preload the audio
13-
this.notificationSound.load();
14-
}
7+
// Handle loading errors
8+
this.notificationSound.addEventListener("error", (e) => {
9+
console.error("Error loading notification sound:", e.error);
10+
});
1511

16-
shouldPlaySound() {
17-
const soundSetting = this.settingsManager.getSetting('sound');
12+
// Preload the audio
13+
this.notificationSound.load();
14+
}
1815

19-
if (soundSetting === 'never') {
20-
return false;
21-
}
16+
shouldPlaySound() {
17+
const soundSetting = this.settingsManager.getSetting("sound");
2218

23-
if (soundSetting === 'always') {
24-
return true;
25-
}
19+
if (soundSetting === "never") {
20+
return false;
21+
}
2622

27-
return soundSetting === 'unfocused' && !document.hasFocus();
23+
if (soundSetting === "always") {
24+
return true;
2825
}
2926

30-
playMessageNotification() {
31-
if (!this.shouldPlaySound()) {
32-
return;
33-
}
34-
35-
try {
36-
this.notificationSound.currentTime = 0; // Reset audio to start
37-
const playPromise = this.notificationSound.play();
38-
39-
if (playPromise !== undefined) {
40-
playPromise.catch(error => {
41-
console.log('Error playing notification sound:', error);
42-
});
43-
}
44-
} catch (error) {
45-
console.error('Error playing notification sound:', error);
46-
}
27+
return soundSetting === "unfocused" && !document.hasFocus();
28+
}
29+
30+
playMessageNotification() {
31+
if (!this.shouldPlaySound()) {
32+
return;
4733
}
4834

49-
setVolume(volume) {
50-
// volume should be between 0 and 1
51-
this.notificationSound.volume = Math.max(0, Math.min(1, volume));
35+
try {
36+
this.notificationSound.currentTime = 0; // Reset audio to start
37+
const playPromise = this.notificationSound.play();
38+
39+
if (playPromise !== undefined) {
40+
playPromise.catch((error) => {
41+
console.log("Error playing notification sound:", error);
42+
});
43+
}
44+
} catch (error) {
45+
console.error("Error playing notification sound:", error);
5246
}
47+
}
48+
49+
setVolume(volume) {
50+
// volume should be between 0 and 1
51+
this.notificationSound.volume = Math.max(0, Math.min(1, volume));
52+
}
5353
}

public/pages/index/modules/chat.js

Lines changed: 132 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,153 @@
11
export class ChatManager {
2-
constructor(websocketManager, uiManager, audioManager, settingsManager, notificationManager, userManager) {
3-
this.websocketManager = websocketManager;
4-
this.uiManager = uiManager;
5-
this.audioManager = audioManager;
6-
this.settingsManager = settingsManager;
7-
this.notificationManager = notificationManager;
8-
this.userManager = userManager;
9-
this.typingTimeout = null;
10-
this.isTyping = false;
11-
12-
this.setupEventListeners();
2+
constructor(
3+
websocketManager,
4+
uiManager,
5+
audioManager,
6+
settingsManager,
7+
notificationManager,
8+
userManager,
9+
) {
10+
this.websocketManager = websocketManager;
11+
this.uiManager = uiManager;
12+
this.audioManager = audioManager;
13+
this.settingsManager = settingsManager;
14+
this.notificationManager = notificationManager;
15+
this.userManager = userManager;
16+
this.typingTimeout = null;
17+
this.isTyping = false;
18+
19+
this.setupEventListeners();
20+
}
21+
22+
setupEventListeners() {
23+
// Handle form submission
24+
this.uiManager.messageForm.addEventListener("submit", (e) =>
25+
this.handleSubmit(e),
26+
);
27+
28+
// Handle keydown for Enter key
29+
this.uiManager.messageInput.addEventListener("keydown", (e) =>
30+
this.handleKeydown(e),
31+
);
32+
33+
// Handle typing status
34+
this.uiManager.messageInput.addEventListener("input", () =>
35+
this.handleTyping(),
36+
);
37+
38+
// Handle reconnect button
39+
this.uiManager.reconnectButton.addEventListener("click", () => {
40+
this.websocketManager.manualReconnect();
41+
});
42+
}
43+
44+
handleSubmit(e) {
45+
e.preventDefault();
46+
47+
// Prevent sending if not connected
48+
if (!this.websocketManager.isConnectedStatus()) {
49+
return;
1350
}
1451

15-
setupEventListeners() {
16-
// Handle form submission
17-
this.uiManager.messageForm.addEventListener("submit", (e) => this.handleSubmit(e));
52+
const content = this.uiManager.getMessageContent();
53+
if (content) {
54+
this.uiManager.showSending();
1855

19-
// Handle keydown for Enter key
20-
this.uiManager.messageInput.addEventListener("keydown", (e) => this.handleKeydown(e));
56+
const success = this.websocketManager.send({
57+
type: "message",
58+
content,
59+
});
2160

22-
// Handle typing status
23-
this.uiManager.messageInput.addEventListener("input", () => this.handleTyping());
61+
if (success) {
62+
this.uiManager.clearMessageInput();
63+
}
2464

25-
// Handle reconnect button
26-
this.uiManager.reconnectButton.addEventListener("click", () => {
27-
this.websocketManager.manualReconnect();
28-
});
65+
this.uiManager.hideSending();
2966
}
67+
}
3068

31-
handleSubmit(e) {
69+
handleKeydown(e) {
70+
if (e.key === "Enter" && !e.shiftKey) {
71+
// Prevent sending if not connected
72+
if (!this.websocketManager.isConnectedStatus()) {
3273
e.preventDefault();
74+
return;
75+
}
3376

34-
// Prevent sending if not connected
35-
if (!this.websocketManager.isConnectedStatus()) {
36-
return;
37-
}
38-
39-
const content = this.uiManager.getMessageContent();
40-
if (content) {
41-
this.uiManager.showSending();
42-
43-
const success = this.websocketManager.send({
44-
type: "message",
45-
content,
46-
});
47-
48-
if (success) {
49-
this.uiManager.clearMessageInput();
50-
}
51-
52-
this.uiManager.hideSending();
53-
}
77+
const content = this.uiManager.getMessageContent();
78+
if (content) {
79+
e.preventDefault();
80+
this.uiManager.messageForm.dispatchEvent(new Event("submit"));
81+
}
5482
}
83+
}
5584

56-
handleKeydown(e) {
57-
if (e.key === "Enter" && !e.shiftKey) {
58-
// Prevent sending if not connected
59-
if (!this.websocketManager.isConnectedStatus()) {
60-
e.preventDefault();
61-
return;
62-
}
63-
64-
const content = this.uiManager.getMessageContent();
65-
if (content) {
66-
e.preventDefault();
67-
this.uiManager.messageForm.dispatchEvent(new Event("submit"));
68-
}
69-
}
85+
handleTyping() {
86+
if (!this.isTyping) {
87+
this.isTyping = true;
88+
this.sendTypingStatus(true);
7089
}
7190

72-
handleTyping() {
73-
if (!this.isTyping) {
74-
this.isTyping = true;
75-
this.sendTypingStatus(true);
76-
}
77-
78-
// Clear previous timeout
79-
if (this.typingTimeout) {
80-
clearTimeout(this.typingTimeout);
81-
}
82-
83-
// Set new timeout
84-
this.typingTimeout = setTimeout(() => {
85-
this.isTyping = false;
86-
this.sendTypingStatus(false);
87-
}, 1000);
91+
// Clear previous timeout
92+
if (this.typingTimeout) {
93+
clearTimeout(this.typingTimeout);
8894
}
8995

90-
sendTypingStatus(isTyping) {
91-
this.websocketManager.send({
92-
type: "typing",
93-
isTyping: isTyping
94-
});
96+
// Set new timeout
97+
this.typingTimeout = setTimeout(() => {
98+
this.isTyping = false;
99+
this.sendTypingStatus(false);
100+
}, 1000);
101+
}
102+
103+
sendTypingStatus(isTyping) {
104+
this.websocketManager.send({
105+
type: "typing",
106+
isTyping: isTyping,
107+
});
108+
}
109+
110+
async handleInitialLoad() {
111+
try {
112+
const response = await fetch("/messages");
113+
if (!response.ok) {
114+
throw new Error("Failed to load messages");
115+
}
116+
const messages = await response.json();
117+
messages.reverse();
118+
119+
messages.forEach((msg) => {
120+
this.uiManager.appendMessage(msg);
121+
});
122+
this.uiManager.updateEmptyState();
123+
} catch (error) {
124+
console.error("Error loading messages:", error);
125+
} finally {
126+
this.uiManager.hideLoadingState();
127+
requestAnimationFrame(() => {
128+
this.uiManager.scrollToBottom(false);
129+
});
95130
}
96-
97-
async handleInitialLoad() {
98-
try {
99-
const response = await fetch("/messages");
100-
if (!response.ok) {
101-
throw new Error("Failed to load messages");
102-
}
103-
const messages = await response.json();
104-
messages.reverse();
105-
106-
messages.forEach((msg) => {
107-
this.uiManager.appendMessage(msg);
108-
});
109-
this.uiManager.updateEmptyState();
110-
} catch (error) {
111-
console.error("Error loading messages:", error);
112-
} finally {
113-
this.uiManager.hideLoadingState();
114-
requestAnimationFrame(() => {
115-
this.uiManager.scrollToBottom(false);
116-
});
131+
}
132+
133+
handleWebSocketMessage(event) {
134+
const data = JSON.parse(event.data);
135+
136+
switch (data.type) {
137+
case "message":
138+
this.uiManager.appendMessage(data);
139+
this.audioManager.playMessageNotification();
140+
if (!this.userManager.isCurrentUser(data.username)) {
141+
this.notificationManager.notify("New Message", {
142+
body: `${data.username}: ${data.content}`,
143+
tag: "chat-message",
144+
});
117145
}
118-
}
146+
break;
119147

120-
handleWebSocketMessage(event) {
121-
const data = JSON.parse(event.data);
122-
123-
switch (data.type) {
124-
case "message":
125-
this.uiManager.appendMessage(data);
126-
this.audioManager.playMessageNotification();
127-
if (!this.userManager.isCurrentUser(data.username)) {
128-
this.notificationManager.notify('New Message', {
129-
body: `${data.username}: ${data.content}`,
130-
tag: 'chat-message'
131-
});
132-
}
133-
break;
134-
135-
case "typing":
136-
this.uiManager.updateTypingIndicator(data.message);
137-
break;
138-
}
148+
case "typing":
149+
this.uiManager.updateTypingIndicator(data.message);
150+
break;
139151
}
152+
}
140153
}

0 commit comments

Comments
 (0)