fix: improve mobile touch interactions by disabling default browser gestures and updating sidebar resizing logic
Build and Release / release (push) Successful in 37s
Build and Release / release (push) Successful in 37s
This commit is contained in:
@@ -219,11 +219,13 @@ export default function ReplayPreviewLayerPanel({
|
|||||||
borderRadius: 20,
|
borderRadius: 20,
|
||||||
padding: "14px 10px",
|
padding: "14px 10px",
|
||||||
width: 58,
|
width: 58,
|
||||||
|
boxSizing: "border-box",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
boxShadow: "0 20px 48px rgba(2, 6, 23, 0.45)",
|
boxShadow: "0 20px 48px rgba(2, 6, 23, 0.45)",
|
||||||
backdropFilter: "blur(12px)",
|
backdropFilter: "blur(12px)",
|
||||||
maxHeight: "100%",
|
maxHeight: "100%",
|
||||||
overflow: "hidden",
|
overflowX: "hidden",
|
||||||
|
overflowY: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderStyles()}
|
{renderStyles()}
|
||||||
@@ -233,7 +235,9 @@ export default function ReplayPreviewLayerPanel({
|
|||||||
style={{
|
style={{
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
overflowY: "auto",
|
overflowY: "auto",
|
||||||
|
overflowX: "hidden",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
boxSizing: "border-box",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
|||||||
@@ -242,6 +242,21 @@ export default function PublicPreviewClientPage({
|
|||||||
localStorage.setItem("timeline-year", String(timelineYear));
|
localStorage.setItem("timeline-year", String(timelineYear));
|
||||||
}
|
}
|
||||||
}, [timelineYear]);
|
}, [timelineYear]);
|
||||||
|
|
||||||
|
// Prevent global browser zoom on multi-touch pinch gestures for this entire route
|
||||||
|
useEffect(() => {
|
||||||
|
const preventZoom = (e: TouchEvent) => {
|
||||||
|
if (e.touches.length > 1) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener("touchmove", preventZoom, { passive: false });
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener("touchmove", preventZoom);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loadInteractiveMap) return;
|
if (!loadInteractiveMap) return;
|
||||||
const timeoutId = window.setTimeout(() => {
|
const timeoutId = window.setTimeout(() => {
|
||||||
@@ -408,12 +423,8 @@ export default function PublicPreviewClientPage({
|
|||||||
if (isLargeScreen) {
|
if (isLargeScreen) {
|
||||||
return "min(392px, calc(100vw - 120px))";
|
return "min(392px, calc(100vw - 120px))";
|
||||||
}
|
}
|
||||||
if (isLayerPanelVisible) {
|
return "min(280px, calc(100vw - 86px))";
|
||||||
return `calc(100vw - 104px)`;
|
}, [isLargeScreen]);
|
||||||
} else {
|
|
||||||
return `calc(100vw - 36px)`;
|
|
||||||
}
|
|
||||||
}, [isLargeScreen, isLayerPanelVisible]);
|
|
||||||
|
|
||||||
const searchBarWrapperStyle = useMemo(() => {
|
const searchBarWrapperStyle = useMemo(() => {
|
||||||
if (isLargeScreen) {
|
if (isLargeScreen) {
|
||||||
|
|||||||
@@ -227,8 +227,14 @@ export default function TimelineBar({
|
|||||||
onMouseDown={() => startChangingYear(-1)}
|
onMouseDown={() => startChangingYear(-1)}
|
||||||
onMouseUp={stopChangingYear}
|
onMouseUp={stopChangingYear}
|
||||||
onMouseLeave={stopChangingYear}
|
onMouseLeave={stopChangingYear}
|
||||||
onTouchStart={() => startChangingYear(-1)}
|
onTouchStart={(e) => {
|
||||||
onTouchEnd={stopChangingYear}
|
e.preventDefault();
|
||||||
|
startChangingYear(-1);
|
||||||
|
}}
|
||||||
|
onTouchEnd={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
stopChangingYear();
|
||||||
|
}}
|
||||||
disabled={effectiveDisabled}
|
disabled={effectiveDisabled}
|
||||||
className={styles.adjustBtn}
|
className={styles.adjustBtn}
|
||||||
style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }}
|
style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }}
|
||||||
@@ -264,8 +270,14 @@ export default function TimelineBar({
|
|||||||
onMouseDown={() => startChangingYear(1)}
|
onMouseDown={() => startChangingYear(1)}
|
||||||
onMouseUp={stopChangingYear}
|
onMouseUp={stopChangingYear}
|
||||||
onMouseLeave={stopChangingYear}
|
onMouseLeave={stopChangingYear}
|
||||||
onTouchStart={() => startChangingYear(1)}
|
onTouchStart={(e) => {
|
||||||
onTouchEnd={stopChangingYear}
|
e.preventDefault();
|
||||||
|
startChangingYear(1);
|
||||||
|
}}
|
||||||
|
onTouchEnd={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
stopChangingYear();
|
||||||
|
}}
|
||||||
disabled={effectiveDisabled}
|
disabled={effectiveDisabled}
|
||||||
className={styles.adjustBtn}
|
className={styles.adjustBtn}
|
||||||
style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }}
|
style={{ width: 32, height: 32, borderRadius: 8, fontSize: 16 }}
|
||||||
@@ -397,8 +409,14 @@ export default function TimelineBar({
|
|||||||
onMouseDown={() => startChangingYear(-1)}
|
onMouseDown={() => startChangingYear(-1)}
|
||||||
onMouseUp={stopChangingYear}
|
onMouseUp={stopChangingYear}
|
||||||
onMouseLeave={stopChangingYear}
|
onMouseLeave={stopChangingYear}
|
||||||
onTouchStart={() => startChangingYear(-1)}
|
onTouchStart={(e) => {
|
||||||
onTouchEnd={stopChangingYear}
|
e.preventDefault();
|
||||||
|
startChangingYear(-1);
|
||||||
|
}}
|
||||||
|
onTouchEnd={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
stopChangingYear();
|
||||||
|
}}
|
||||||
disabled={effectiveDisabled}
|
disabled={effectiveDisabled}
|
||||||
className={styles.adjustBtn}
|
className={styles.adjustBtn}
|
||||||
title="Giảm 1 năm"
|
title="Giảm 1 năm"
|
||||||
@@ -411,8 +429,14 @@ export default function TimelineBar({
|
|||||||
onMouseDown={() => startChangingYear(1)}
|
onMouseDown={() => startChangingYear(1)}
|
||||||
onMouseUp={stopChangingYear}
|
onMouseUp={stopChangingYear}
|
||||||
onMouseLeave={stopChangingYear}
|
onMouseLeave={stopChangingYear}
|
||||||
onTouchStart={() => startChangingYear(1)}
|
onTouchStart={(e) => {
|
||||||
onTouchEnd={stopChangingYear}
|
e.preventDefault();
|
||||||
|
startChangingYear(1);
|
||||||
|
}}
|
||||||
|
onTouchEnd={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
stopChangingYear();
|
||||||
|
}}
|
||||||
disabled={effectiveDisabled}
|
disabled={effectiveDisabled}
|
||||||
className={styles.adjustBtn}
|
className={styles.adjustBtn}
|
||||||
title="Tăng 1 năm"
|
title="Tăng 1 năm"
|
||||||
@@ -714,6 +738,8 @@ function CanvasTimelineRuler({
|
|||||||
}
|
}
|
||||||
}, [year, drawYear]);
|
}, [year, drawYear]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleWheel = (e: React.WheelEvent) => {
|
const handleWheel = (e: React.WheelEvent) => {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -840,6 +866,7 @@ function CanvasTimelineRuler({
|
|||||||
border: "1px solid rgba(255, 255, 255, 0.08)",
|
border: "1px solid rgba(255, 255, 255, 0.08)",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
cursor: disabled ? "not-allowed" : "ew-resize",
|
cursor: disabled ? "not-allowed" : "ew-resize",
|
||||||
|
touchAction: "none",
|
||||||
}}
|
}}
|
||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
>
|
>
|
||||||
@@ -849,6 +876,7 @@ function CanvasTimelineRuler({
|
|||||||
display: "block",
|
display: "block",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
touchAction: "none",
|
||||||
}}
|
}}
|
||||||
onPointerDown={handlePointerDown}
|
onPointerDown={handlePointerDown}
|
||||||
onPointerMove={handlePointerMove}
|
onPointerMove={handlePointerMove}
|
||||||
|
|||||||
@@ -205,47 +205,7 @@ function PublicWikiSidebar({
|
|||||||
window.addEventListener("pointerup", onUp);
|
window.addEventListener("pointerup", onUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHeightPointerDown = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const startY = event.clientY;
|
|
||||||
const startHeight = sidebarHeight || 400;
|
|
||||||
|
|
||||||
// Tạo đường ghost ảo nằm ngang chỉ vị trí kéo chiều cao
|
|
||||||
const ghost = document.createElement("div");
|
|
||||||
ghost.style.position = "fixed";
|
|
||||||
ghost.style.left = "0";
|
|
||||||
ghost.style.right = "0";
|
|
||||||
ghost.style.height = "4px";
|
|
||||||
ghost.style.backgroundColor = "#38bdf8";
|
|
||||||
ghost.style.boxShadow = "0 0 12px rgba(56, 189, 248, 0.8)";
|
|
||||||
ghost.style.zIndex = "99999";
|
|
||||||
ghost.style.cursor = "row-resize";
|
|
||||||
ghost.style.pointerEvents = "none";
|
|
||||||
|
|
||||||
const startScreenY = window.innerHeight - startHeight;
|
|
||||||
ghost.style.top = `${startScreenY}px`;
|
|
||||||
document.body.appendChild(ghost);
|
|
||||||
|
|
||||||
const onMove = (e: PointerEvent) => {
|
|
||||||
ghost.style.top = `${e.clientY}px`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onUp = (e: PointerEvent) => {
|
|
||||||
window.removeEventListener("pointermove", onMove);
|
|
||||||
window.removeEventListener("pointerup", onUp);
|
|
||||||
if (ghost.parentNode) {
|
|
||||||
ghost.parentNode.removeChild(ghost);
|
|
||||||
}
|
|
||||||
const deltaY = startY - e.clientY;
|
|
||||||
const nextHeight = Math.max(200, Math.min(window.innerHeight * 0.9, startHeight + deltaY));
|
|
||||||
if (onSidebarHeightChange) {
|
|
||||||
onSidebarHeightChange(nextHeight);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("pointermove", onMove);
|
|
||||||
window.addEventListener("pointerup", onUp);
|
|
||||||
};
|
|
||||||
|
|
||||||
const [activeHeadingId, setActiveHeadingId] = useState<string | null>(null);
|
const [activeHeadingId, setActiveHeadingId] = useState<string | null>(null);
|
||||||
const processedWiki = useMemo(() => {
|
const processedWiki = useMemo(() => {
|
||||||
@@ -327,6 +287,27 @@ function PublicWikiSidebar({
|
|||||||
return () => root.removeEventListener("click", handleClick);
|
return () => root.removeEventListener("click", handleClick);
|
||||||
}, [onWikiLinkRequest, renderHtml]);
|
}, [onWikiLinkRequest, renderHtml]);
|
||||||
|
|
||||||
|
const isExpanded = useMemo(() => {
|
||||||
|
if (typeof window === "undefined") return false;
|
||||||
|
const fullHeight = Math.round(window.innerHeight * 0.70);
|
||||||
|
return (sidebarHeight || 400) >= fullHeight;
|
||||||
|
}, [sidebarHeight]);
|
||||||
|
|
||||||
|
const handleHeightToggle = () => {
|
||||||
|
if (typeof window === "undefined") return;
|
||||||
|
const halfHeight = Math.round(window.innerHeight * 0.45);
|
||||||
|
const fullHeight = Math.round(window.innerHeight * 0.85);
|
||||||
|
const currentHeight = sidebarHeight || 400;
|
||||||
|
|
||||||
|
const nextHeight = Math.abs(currentHeight - halfHeight) < Math.abs(currentHeight - fullHeight)
|
||||||
|
? fullHeight
|
||||||
|
: halfHeight;
|
||||||
|
|
||||||
|
if (onSidebarHeightChange) {
|
||||||
|
onSidebarHeightChange(nextHeight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const [isMobileOrTablet, setIsMobileOrTablet] = useState(false);
|
const [isMobileOrTablet, setIsMobileOrTablet] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkDevice = () => setIsMobileOrTablet(window.innerWidth < 1024);
|
const checkDevice = () => setIsMobileOrTablet(window.innerWidth < 1024);
|
||||||
@@ -359,17 +340,18 @@ function PublicWikiSidebar({
|
|||||||
{/* Grab Handle for bottom sheet on mobile */}
|
{/* Grab Handle for bottom sheet on mobile */}
|
||||||
{isMobileOrTablet ? (
|
{isMobileOrTablet ? (
|
||||||
<div
|
<div
|
||||||
onPointerDown={handleHeightPointerDown}
|
onClick={handleHeightToggle}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: 24,
|
height: 28,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
cursor: "row-resize",
|
cursor: "pointer",
|
||||||
zIndex: 60,
|
zIndex: 60,
|
||||||
userSelect: "none",
|
userSelect: "none",
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
gap: 8,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -380,6 +362,22 @@ function PublicWikiSidebar({
|
|||||||
backgroundColor: "rgba(255, 255, 255, 0.3)",
|
backgroundColor: "rgba(255, 255, 255, 0.3)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<svg
|
||||||
|
width="12"
|
||||||
|
height="12"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="rgba(255, 255, 255, 0.5)"
|
||||||
|
strokeWidth="3"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
style={{
|
||||||
|
transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
|
||||||
|
transition: "transform 0.3s ease",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<polyline points="18 15 12 9 6 15" />
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user