Deeplink error fix, updated chat ux
This commit is contained in:
parent
7811a66b5c
commit
60635d07c2
4 changed files with 63 additions and 7 deletions
|
|
@ -6,6 +6,7 @@ import {
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link";
|
import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link";
|
||||||
|
import { isTauri } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
type DeeplinkContextValue = {
|
type DeeplinkContextValue = {
|
||||||
deeplinks: readonly string[];
|
deeplinks: readonly string[];
|
||||||
|
|
@ -30,8 +31,11 @@ export default function DeeplinkProvider({
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [deeplinks, setDeeplinks] = useState<string[]>([]);
|
const [deeplinks, setDeeplinks] = useState<string[]>([]);
|
||||||
|
const isTauriEnv = isTauri();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isTauriEnv) return;
|
||||||
|
|
||||||
let mounted = true;
|
let mounted = true;
|
||||||
let unlisten: (() => void) | undefined;
|
let unlisten: (() => void) | undefined;
|
||||||
|
|
||||||
|
|
@ -53,7 +57,7 @@ export default function DeeplinkProvider({
|
||||||
mounted = false;
|
mounted = false;
|
||||||
unlisten?.();
|
unlisten?.();
|
||||||
};
|
};
|
||||||
}, []);
|
}, [isTauriEnv]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(deeplinks);
|
console.log(deeplinks);
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ export default function InputComponent({
|
||||||
<CardHeader className="relative p-0 flex flex-col">
|
<CardHeader className="relative p-0 flex flex-col">
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
className="pointer-events-none absolute left-0 top-0 w-full opacity-0"
|
className="pointer-events-none absolute left-0 top-0 w-full px-2.5 opacity-0"
|
||||||
style={{ visibility: "hidden" }}
|
style={{ visibility: "hidden" }}
|
||||||
>
|
>
|
||||||
<Message
|
<Message
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ function generateFixedLoadingSize(size: number, height: number) {
|
||||||
* @param props Parameter props.
|
* @param props Parameter props.
|
||||||
* @returns unknown.
|
* @returns unknown.
|
||||||
*/
|
*/
|
||||||
export default function Message(props: {
|
function MessageComponent(props: {
|
||||||
message: RawMessage & {
|
message: RawMessage & {
|
||||||
failed?: boolean;
|
failed?: boolean;
|
||||||
};
|
};
|
||||||
|
|
@ -143,3 +143,16 @@ export default function Message(props: {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default React.memo(MessageComponent, (prev, next) => {
|
||||||
|
return (
|
||||||
|
prev.notEncrypted === next.notEncrypted &&
|
||||||
|
prev.measureRef === next.measureRef &&
|
||||||
|
prev.message.timestamp === next.message.timestamp &&
|
||||||
|
prev.message.content === next.message.content &&
|
||||||
|
prev.message.height === next.message.height &&
|
||||||
|
prev.message.sent_by_self === next.message.sent_by_self &&
|
||||||
|
prev.message.message_state === next.message.message_state &&
|
||||||
|
prev.message.failed === next.message.failed
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,11 @@ import Message from "./components/message";
|
||||||
|
|
||||||
import { PAGE_SIZE } from "./values";
|
import { PAGE_SIZE } from "./values";
|
||||||
import { useLayoutEffect } from "react";
|
import { useLayoutEffect } from "react";
|
||||||
|
import { useIsMobile } from "@tensamin/ui/utils";
|
||||||
|
|
||||||
|
function getDistanceFromBottom(element: HTMLDivElement) {
|
||||||
|
return element.scrollHeight - (element.scrollTop + element.clientHeight);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the chat screen with virtualized history and live message updates.
|
* Renders the chat screen with virtualized history and live message updates.
|
||||||
|
|
@ -18,6 +23,8 @@ export default function Screen() {
|
||||||
|
|
||||||
const inputBoxRef = React.useRef<HTMLDivElement | null>(null);
|
const inputBoxRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
const scrollRef = React.useRef<HTMLDivElement | null>(null);
|
const scrollRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
const stickyBottomThreshold = isMobile ? 220 : 140;
|
||||||
|
|
||||||
const [hasScrolledToBottomInitially, setHasScrolledToBottomInitially] =
|
const [hasScrolledToBottomInitially, setHasScrolledToBottomInitially] =
|
||||||
React.useState(false);
|
React.useState(false);
|
||||||
|
|
@ -72,11 +79,22 @@ export default function Screen() {
|
||||||
return [...historicalMessages, ...liveWithoutDuplicates];
|
return [...historicalMessages, ...liveWithoutDuplicates];
|
||||||
}, [historicalMessages, liveMessagesSnapshot]);
|
}, [historicalMessages, liveMessagesSnapshot]);
|
||||||
|
|
||||||
|
const messagesRef = React.useRef(messages);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
messagesRef.current = messages;
|
||||||
|
}, [messages]);
|
||||||
|
|
||||||
|
const getItemKey = React.useCallback((index: number) => {
|
||||||
|
return messagesRef.current[index]?.timestamp ?? index;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const virtualizer = useVirtualizer({
|
const virtualizer = useVirtualizer({
|
||||||
count: messages.length,
|
count: messages.length,
|
||||||
getScrollElement: () => scrollRef.current,
|
getScrollElement: () => scrollRef.current,
|
||||||
|
getItemKey,
|
||||||
estimateSize: (index) => messages[index].height,
|
estimateSize: (index) => messages[index].height,
|
||||||
overscan: 6,
|
overscan: isMobile ? 10 : 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -125,11 +143,16 @@ export default function Screen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count > lastLiveMessageCount) {
|
if (count > lastLiveMessageCount) {
|
||||||
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
const shouldStickToBottom =
|
||||||
|
getDistanceFromBottom(scrollRef.current) <= stickyBottomThreshold;
|
||||||
|
|
||||||
|
if (shouldStickToBottom) {
|
||||||
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setLastLiveMessageCount(count);
|
setLastLiveMessageCount(count);
|
||||||
}, [lastLiveMessageCount, liveMessagesSnapshot.length]);
|
}, [lastLiveMessageCount, liveMessagesSnapshot.length, stickyBottomThreshold]);
|
||||||
|
|
||||||
React.useLayoutEffect(() => {
|
React.useLayoutEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
|
@ -163,6 +186,9 @@ export default function Screen() {
|
||||||
const host = scrollRef.current;
|
const host = scrollRef.current;
|
||||||
if (!host || !el || typeof window === "undefined") return;
|
if (!host || !el || typeof window === "undefined") return;
|
||||||
|
|
||||||
|
const shouldStickToBottom =
|
||||||
|
getDistanceFromBottom(host) <= stickyBottomThreshold;
|
||||||
|
|
||||||
const viewportHeight = Math.max(
|
const viewportHeight = Math.max(
|
||||||
window.innerHeight,
|
window.innerHeight,
|
||||||
window.visualViewport?.height ?? 0,
|
window.visualViewport?.height ?? 0,
|
||||||
|
|
@ -170,7 +196,17 @@ export default function Screen() {
|
||||||
const inputHeight = el.scrollHeight + 56;
|
const inputHeight = el.scrollHeight + 56;
|
||||||
|
|
||||||
host.style.maxHeight = `calc(${viewportHeight}px - ${inputHeight - 62}px - env(safe-area-inset-bottom) - env(safe-area-inset-top))`;
|
host.style.maxHeight = `calc(${viewportHeight}px - ${inputHeight - 62}px - env(safe-area-inset-bottom) - env(safe-area-inset-top))`;
|
||||||
}, []);
|
|
||||||
|
if (shouldStickToBottom) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (!scrollRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [stickyBottomThreshold]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
updateMaxHeight();
|
updateMaxHeight();
|
||||||
|
|
@ -221,6 +257,9 @@ export default function Screen() {
|
||||||
ref={scrollRef}
|
ref={scrollRef}
|
||||||
id="chat_container"
|
id="chat_container"
|
||||||
className="flex-1 overflow-y-auto px-2.5"
|
className="flex-1 overflow-y-auto px-2.5"
|
||||||
|
style={{
|
||||||
|
overflowAnchor: "none",
|
||||||
|
}}
|
||||||
onScroll={handleContainerScroll}
|
onScroll={handleContainerScroll}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue