feat: add scroll to bottom button and auto-scroll on session load
This commit is contained in:
@@ -225,6 +225,33 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-to-bottom-btn {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 5rem;
|
||||||
|
right: 1rem;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(30, 30, 30, 0.9);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 5;
|
||||||
|
transition: all 0.2s;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-to-bottom-btn:hover {
|
||||||
|
background: rgba(50, 50, 50, 0.95);
|
||||||
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-container {
|
.messages-container {
|
||||||
@@ -629,4 +656,12 @@
|
|||||||
right: 0;
|
right: 0;
|
||||||
left: auto;
|
left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scroll-to-bottom-btn {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
bottom: 4.5rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-1
@@ -60,8 +60,10 @@ export default function GamePage() {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [streamingContent, setStreamingContent] = useState("");
|
const [streamingContent, setStreamingContent] = useState("");
|
||||||
const [showSessionMenu, setShowSessionMenu] = useState(false);
|
const [showSessionMenu, setShowSessionMenu] = useState(false);
|
||||||
|
const [showScrollButton, setShowScrollButton] = useState(false);
|
||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||||
|
const messagesContainerRef = useRef<HTMLDivElement>(null);
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -159,10 +161,25 @@ export default function GamePage() {
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}, [session?.messages]);
|
}, [session?.messages]);
|
||||||
|
|
||||||
|
// Scroll to bottom on session load
|
||||||
|
useEffect(() => {
|
||||||
|
if (session && !isInitialLoading) {
|
||||||
|
setTimeout(() => scrollToBottom(), 100);
|
||||||
|
}
|
||||||
|
}, [currentSessionId, isInitialLoading]);
|
||||||
|
|
||||||
const scrollToBottom = () => {
|
const scrollToBottom = () => {
|
||||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleScroll = () => {
|
||||||
|
const container = messagesContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = container;
|
||||||
|
const distanceFromBottom = scrollHeight - scrollTop - clientHeight;
|
||||||
|
setShowScrollButton(distanceFromBottom > 200);
|
||||||
|
};
|
||||||
|
|
||||||
const startStory = async (
|
const startStory = async (
|
||||||
storyData: Story,
|
storyData: Story,
|
||||||
sessionData: GameSession,
|
sessionData: GameSession,
|
||||||
@@ -528,7 +545,11 @@ export default function GamePage() {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="game-content">
|
<div className="game-content">
|
||||||
<div className="messages-container">
|
<div
|
||||||
|
className="messages-container"
|
||||||
|
ref={messagesContainerRef}
|
||||||
|
onScroll={handleScroll}
|
||||||
|
>
|
||||||
{session?.messages.map((message) => (
|
{session?.messages.map((message) => (
|
||||||
<div key={message.id} className={`message ${message.role}`}>
|
<div key={message.id} className={`message ${message.role}`}>
|
||||||
<div className="message-content">
|
<div className="message-content">
|
||||||
@@ -571,6 +592,12 @@ export default function GamePage() {
|
|||||||
<div ref={messagesEndRef} />
|
<div ref={messagesEndRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showScrollButton && (
|
||||||
|
<button className="scroll-to-bottom-btn" onClick={scrollToBottom}>
|
||||||
|
↓
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* RPG кнопки скрыты — раскомментировать при необходимости
|
{/* RPG кнопки скрыты — раскомментировать при необходимости
|
||||||
<div className="quick-actions">
|
<div className="quick-actions">
|
||||||
<button onClick={() => handleQuickAction("Осмотреться вокруг")}>
|
<button onClick={() => handleQuickAction("Осмотреться вокруг")}>
|
||||||
|
|||||||
Reference in New Issue
Block a user