Chat misc fixes

This commit is contained in:
Kalle
2026-06-11 21:27:22 +03:00
parent 6e987d506f
commit 544fe4f66c
4 changed files with 87 additions and 8 deletions

View File

@@ -2,7 +2,7 @@
display: flex;
flex-direction: column;
height: 100%;
max-height: var(--visual-viewport-height);
max-height: var(--visual-viewport-height, 100dvh);
overflow: hidden;
}

View File

@@ -26,6 +26,8 @@ import { useChatContext } from "~/features/chat/useChatContext";
import { FriendMenu } from "~/features/friends/components/FriendMenu";
import { useDateTimeFormat } from "~/hooks/intl/useDateTimeFormat";
import { useHydrated } from "~/hooks/useHydrated";
import { useLayoutSize } from "~/hooks/useMainContentWidth";
import { useVisualViewportHeight } from "~/hooks/useVisualViewportHeight";
import type { RootLoaderData } from "~/root";
import type { Breadcrumb, SendouRouteHandle } from "~/utils/remix.server";
import {
@@ -213,9 +215,16 @@ export function Layout({
const [sideNavModalOpen, setSideNavModalOpen] = React.useState(false);
const [chatSidebarModalOpen, setChatSidebarModalOpen] = React.useState(false);
const layoutSize = useLayoutSize();
useVisualViewportHeight();
const chatSidebarOpen = chatContext?.chatOpen ?? false;
const setChatSidebarOpen = chatContext?.setChatOpen ?? (() => {});
const setChatSidebarModalOpenAndSync = (open: boolean) => {
setChatSidebarModalOpen(open);
setChatSidebarOpen(open);
};
const { t } = useTranslation(["front", "common"]);
const { formatRelativeDate } = useRelativeDayFormat();
const isHydrated = useHydrated();
@@ -393,7 +402,7 @@ export function Layout({
className={styles.chatSidebarModalOverlay}
isDismissable
isOpen={chatSidebarModalOpen}
onOpenChange={setChatSidebarModalOpen}
onOpenChange={setChatSidebarModalOpenAndSync}
>
<Modal className={styles.chatSidebarModal}>
<Dialog
@@ -424,7 +433,7 @@ export function Layout({
}
onChatModalToggle={
data?.user
? () => setChatSidebarModalOpen((prev) => !prev)
? () => setChatSidebarModalOpenAndSync(!chatSidebarModalOpen)
: undefined
}
chatUnreadCount={chatContext?.totalUnreadCount}
@@ -440,7 +449,7 @@ export function Layout({
{children}
<Footer />
</div>
{chatSidebarOpen ? (
{chatSidebarOpen && layoutSize === "desktop" ? (
<div
className={clsx(
styles.chatSidebar,

View File

@@ -479,13 +479,14 @@ function ChatProviderInner({
const setChatOpen = React.useCallback(
(open: boolean) => {
_setChatOpen(open);
if (open && activeRoom) {
markAsRead(activeRoom);
}
if (!open) return;
if (open && rooms.length === 1 && !activeRoom) {
if (activeRoom) {
markAsRead(activeRoom);
} else if (rooms.length === 1) {
requestHistory(rooms[0].chatCode);
setActiveRoom(rooms[0].chatCode);
markAsRead(rooms[0].chatCode);
}
},
[activeRoom, markAsRead, requestHistory, rooms.length, rooms[0]?.chatCode],
@@ -501,9 +502,11 @@ function ChatProviderInner({
rooms,
userId,
isLoading,
readyState,
activeRoom,
setActiveRoom,
setChatOpen,
markAsRead,
subscribe,
unsubscribe,
setRooms,
@@ -568,9 +571,11 @@ function useChatRouteSync({
rooms,
userId,
isLoading,
readyState,
activeRoom,
setActiveRoom,
setChatOpen,
markAsRead,
subscribe,
unsubscribe,
setRooms,
@@ -581,9 +586,11 @@ function useChatRouteSync({
rooms: RoomInfo[];
userId: number;
isLoading: boolean;
readyState: "CONNECTING" | "CONNECTED" | "CLOSED";
activeRoom: string | null;
setActiveRoom: (chatCode: string | null) => void;
setChatOpen: (open: boolean) => void;
markAsRead: (chatCode: string) => void;
subscribe: (chatCode: string) => void;
unsubscribe: (chatCode: string) => void;
setRooms: React.Dispatch<React.SetStateAction<RoomInfo[]>>;
@@ -604,6 +611,31 @@ function useChatRouteSync({
const subscribedRoomRef = React.useRef<string[]>([]);
const previousRouteChatCodeRef = React.useRef<string[]>([]);
const previousPathnameRef = React.useRef<string | null>(null);
const hasConnectedRef = React.useRef(false);
// On reconnect the server sends a fresh initial rooms payload that drops
// rooms we joined via SUBSCRIBE as a non-participant, and the previous
// socket's subscriptions died with it. Clear the subscription tracking so
// the route sync effect below re-subscribes once the new payload arrives,
// and refresh history for the open room to fill any gap from the downtime.
const onReconnect = React.useEffectEvent(() => {
logger.debug("WS reconnected, re-acquiring room subscriptions and history");
subscribedRoomRef.current = [];
if (activeRoom) {
requestHistory(activeRoom);
}
});
React.useEffect(() => {
if (readyState !== "CONNECTED") return;
if (!hasConnectedRef.current) {
hasConnectedRef.current = true;
return;
}
onReconnect();
}, [readyState]);
React.useEffect(() => {
if (isLoading) return;
@@ -657,6 +689,7 @@ function useChatRouteSync({
}
if (layoutSize === "desktop") {
setChatOpen(true);
markAsRead(chatCodes[0]);
}
}
} else {
@@ -677,6 +710,7 @@ function useChatRouteSync({
}
if (layoutSize === "desktop") {
setChatOpen(true);
markAsRead(matchedRoom.chatCode);
}
}
}
@@ -690,6 +724,7 @@ function useChatRouteSync({
activeRoom,
setActiveRoom,
setChatOpen,
markAsRead,
layoutSize,
subscribe,
unsubscribe,

View File

@@ -0,0 +1,35 @@
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
const CSS_VARIABLE = "--visual-viewport-height";
/**
* Keeps the `--visual-viewport-height` CSS variable in sync with the visual
* viewport height on the document root. CSS has no native way to read the
* visual (as opposed to layout) viewport, so elements that must stay above the
* mobile on-screen keyboard rely on this variable to clamp their height as the
* keyboard opens and closes.
*/
export function useVisualViewportHeight() {
useIsomorphicLayoutEffect(() => {
const viewport = window.visualViewport;
if (!viewport) return;
const update = () => {
document.documentElement.style.setProperty(
CSS_VARIABLE,
`${viewport.height}px`,
);
};
update();
viewport.addEventListener("resize", update);
viewport.addEventListener("scroll", update);
return () => {
viewport.removeEventListener("resize", update);
viewport.removeEventListener("scroll", update);
document.documentElement.style.removeProperty(CSS_VARIABLE);
};
}, []);
}