mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 04:05:54 -05:00
Chat misc fixes
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
35
app/hooks/useVisualViewportHeight.ts
Normal file
35
app/hooks/useVisualViewportHeight.ts
Normal 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);
|
||||
};
|
||||
}, []);
|
||||
}
|
||||
Reference in New Issue
Block a user