Skip to content

Commit 3c9ac4e

Browse files
committed
chat split movable
1 parent 08aacc1 commit 3c9ac4e

File tree

6 files changed

+114
-23
lines changed

6 files changed

+114
-23
lines changed

src/main/client/src/component/Chat.jsx

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {
55
useContext,
66
useRef,
77
} from "react"
8+
import {
9+
twJoin,
10+
} from "tailwind-merge"
811
import {
912
useAuthStore,
1013
} from "../store.js"
@@ -23,7 +26,7 @@ export const Chat = ({chatId}) => {
2326
let auth = useAuthStore(state => state.auth)
2427

2528
useEffect(() => {
26-
let sub1 = stompClient.subscribe("/topic/users/Lobby", (message) => {
29+
let sub1 = stompClient.subscribe("/topic/users/" + chatId, (message) => {
2730
let r = JSON.parse(message.body)
2831
setUsers(r.users)
2932
})
@@ -47,6 +50,7 @@ export const Chat = ({chatId}) => {
4750
},
4851
})
4952
setMessages(chat.messages || [])
53+
setUsers(chat.users || [])
5054
})
5155
return () => {
5256
sub1.unsubscribe()
@@ -84,20 +88,19 @@ export const Chat = ({chatId}) => {
8488
}), [stompClient, chatId])
8589

8690
return <>
87-
<div
88-
className="grow border border-gray-500 bg-gray-900 rounded-lg flex flex-col overflow-y-hidden">
89-
<div className="px-1 flex-none h-14 overflow-y-scroll">
90-
{users.map(user => (
91-
<p key={user}>{user}</p>
92-
))}
93-
</div>
94-
<div className="w-full flex-none h-[2px] bg-gray-500" />
95-
<div ref={messageRef} className="px-1 overflow-y-scroll">
91+
<SplitPane
92+
messageRef={messageRef}
93+
topElement={<>
94+
{users.map(user => (
95+
<p key={user}>{user}</p>
96+
))}
97+
</>}
98+
bottomElement={<>
9699
{messages.map(message => (
97100
<p key={message.n}>{message.user + ": " + message.message}</p>
98101
))}
99-
</div>
100-
</div>
102+
</>}
103+
/>
101104
<form className="flex-none mb-2" onSubmit={onSendMessage}>
102105
<input
103106
className="w-full rounded-lg p-2 border border-gray-500 bg-stone-800 text-stone-100"
@@ -118,3 +121,78 @@ function isLastChildVisible(container) {
118121
let containerBottom = containerTop + container.clientHeight
119122
return lastChildTop < containerBottom
120123
}
124+
125+
function SplitPane({messageRef, topElement, bottomElement}) {
126+
let [dragging, setDragging] = useState(false)
127+
let [splitPos, setSplitPos] = useState(60)
128+
let [ghostPos, setGhostPos] = useState(splitPos)
129+
let draggingRef = useRef()
130+
let ghostPosRef = useRef()
131+
let containerRef = useRef()
132+
draggingRef.current = dragging
133+
ghostPosRef.current = ghostPos
134+
useEffect(() => {
135+
let mousemove = (e) => {
136+
if (!draggingRef.current) {
137+
return
138+
}
139+
let pos = e.clientY
140+
let rect = containerRef.current.getBoundingClientRect()
141+
setGhostPos(pos - rect.top)
142+
}
143+
let mouseup = (e) => {
144+
if (!draggingRef.current) {
145+
return
146+
}
147+
let pos = e.clientY
148+
let rect = containerRef.current.getBoundingClientRect()
149+
setGhostPos(pos - rect.top)
150+
setSplitPos(pos - rect.top)
151+
setDragging(false)
152+
}
153+
window.document.addEventListener("mousemove", mousemove)
154+
window.document.addEventListener("mouseup", mouseup)
155+
return () => {
156+
window.document.removeEventListener("mousemove", mousemove)
157+
window.document.removeEventListener("mouseup", mouseup)
158+
}
159+
}, [draggingRef, setGhostPos, setDragging])
160+
let onMouseDown = useCallback((e) => {
161+
e.preventDefault()
162+
let pos = e.clientY
163+
let rect = containerRef.current.getBoundingClientRect()
164+
setGhostPos(pos - rect.top)
165+
setDragging(true)
166+
}, [setDragging])
167+
return (
168+
<div
169+
ref={(ref) => {
170+
containerRef.current = ref
171+
}}
172+
className={twJoin(
173+
"grow border border-gray-500 bg-gray-900 rounded-lg flex flex-col overflow-y-hidden relative",
174+
dragging && "cursor-row-resize",
175+
)}>
176+
<div
177+
style={{height: splitPos + "px"}}
178+
className="px-1 flex-none overflow-y-scroll">
179+
{topElement}
180+
</div>
181+
<div
182+
onMouseDown={onMouseDown}
183+
className={twJoin(
184+
"w-full flex-none h-[3px] cursor-row-resize",
185+
!dragging && "bg-gray-500",
186+
dragging && "bg-transparent",
187+
)} />
188+
{dragging && (
189+
<div
190+
style={{top: ghostPos + "px"}}
191+
className="w-full absolute h-[3px] bg-gray-500 z-20" />
192+
)}
193+
<div ref={messageRef} className="px-1 overflow-y-scroll">
194+
{bottomElement}
195+
</div>
196+
</div>
197+
)
198+
}

src/main/client/src/component/SideBar.jsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
useRef,
33
useEffect,
44
useState,
5+
useCallback,
56
} from "react"
67
import {
78
useLayoutStore,
@@ -43,15 +44,16 @@ export const SideBar = ({page, children}) => {
4344
window.document.removeEventListener("mouseup", mouseup)
4445
}
4546
}, [vw, page, draggingRef, setDragging, setSidebarWidth, sanitizeSidebarWidth])
47+
let onMouseDown = useCallback((e) => {
48+
e.preventDefault()
49+
setDragging(true)
50+
}, [setDragging])
4651
return (
4752
<div
4853
style={{width: sidebarWidth + "px"}}
4954
className="fixed top-0 right-0 h-full bg-slate-800">
5055
<div
51-
onMouseDown={(e) => {
52-
e.preventDefault()
53-
setDragging(true)
54-
}}
56+
onMouseDown={onMouseDown}
5557
style={{right: sidebarWidth + "px"}}
5658
className="fixed top-0 w-[3px] h-full bg-slate-700 z-10 cursor-col-resize" />
5759
{dragging && (

src/main/client/src/feature/game/Game.jsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,9 +300,7 @@ export const Game = () => {
300300
width={context.width} height={context.width}>
301301
</canvas>
302302
</div>
303-
<div
304-
style={{right: (sidebarWidth + 12) + "px"}}
305-
className="absolute bottom-4">
303+
<div className="absolute left-2 top-2">
306304
<button onClick={onMuteClick}>
307305
<IconContext.Provider value={{
308306
size: "1.5em",

src/main/java/com/bernd/ChatController.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,7 @@ public Chat getChat(@PathVariable String id) {
4040
@MessageMapping("/chat/send/")
4141
public ResponseEntity<?> sendChat(ChatRequest chatRequest, Principal principal) {
4242
String user = Auth.getPrincipal(principal);
43-
Chat chat = chats.map().computeIfAbsent(chatRequest.id(),
44-
id -> new Chat(id, new AtomicInteger(0), new ArrayList<>(), new TreeSet<>()));
43+
Chat chat = chats.get(chatRequest.id());
4544
ChatMessage message = new ChatMessage(chat.counter().getAndIncrement(), chatRequest.message(), user);
4645
chat.messages().add(message);
4746
if (chat.users().add(user)) {

src/main/java/com/bernd/Chats.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
package com.bernd;
22

33
import com.bernd.model.Chat;
4+
import java.util.ArrayList;
45
import java.util.LinkedHashMap;
56
import java.util.Map;
7+
import java.util.TreeSet;
8+
import java.util.concurrent.atomic.AtomicInteger;
69
import org.springframework.stereotype.Component;
710

811
@Component
912
public class Chats {
1013
private final Map<String, Chat> map = new LinkedHashMap<>();
1114

1215
Chat get(String id) {
13-
return map.get(id);
16+
return map.computeIfAbsent(id,
17+
_id -> new Chat(id, new AtomicInteger(0), new ArrayList<>(), new TreeSet<>()));
1418
}
1519

1620
Chat put(Chat chat) {

src/main/java/com/bernd/GameController.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
import com.bernd.game.MoveList;
55
import com.bernd.model.AcceptRequest;
66
import com.bernd.model.ActiveGame;
7+
import com.bernd.model.Chat;
8+
import com.bernd.model.ChatMessage;
79
import com.bernd.model.Game;
810
import com.bernd.model.Move;
911
import com.bernd.model.OpenGame;
12+
import com.bernd.model.UsersMessage;
1013
import com.bernd.model.ViewGame;
14+
import com.bernd.util.Auth;
1115
import com.bernd.util.RandomString;
1216
import java.security.Principal;
1317
import org.springframework.http.HttpStatus;
@@ -33,18 +37,20 @@ public class GameController {
3337
private final Games games;
3438
private final OpenGames openGames;
3539
private final ActiveGames activeGames;
40+
private final Chats chats;
3641

3742
GameController(
3843
MessageSendingOperations<String> operations,
3944
Users users,
4045
Games games,
4146
OpenGames openGames,
42-
ActiveGames activeGames) {
47+
ActiveGames activeGames, Chats chats) {
4348
this.operations = operations;
4449
this.users = users;
4550
this.games = games;
4651
this.openGames = openGames;
4752
this.activeGames = activeGames;
53+
this.chats = chats;
4854
}
4955

5056
@ResponseBody
@@ -55,6 +61,10 @@ public ViewGame getGame(@PathVariable String id, Principal p) {
5561
if (game == null) {
5662
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "no such game");
5763
}
64+
Chat chat = chats.get(id);
65+
if (chat.users().add(Auth.getPrincipal(p))) {
66+
operations.convertAndSend("/topic/users/" + chat.id(), new UsersMessage(chat.users()));
67+
}
5868
return game.toView();
5969
}
6070

0 commit comments

Comments
 (0)