feat: add replay button to TimelineBar and implement responsive search bar layout with 100svh height support
Build and Release / release (push) Successful in 37s
Build and Release / release (push) Successful in 37s
This commit is contained in:
+1
-1
@@ -20,7 +20,7 @@ const srOnlyStyle: React.CSSProperties = {
|
|||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<div style={{ position: "relative", width: "100%", height: "100vh", overflow: "hidden", backgroundColor: "#0b1220" }}>
|
<div style={{ position: "relative", width: "100%", height: "100svh", overflow: "hidden", backgroundColor: "#0b1220" }}>
|
||||||
{/* Preload LCP image */}
|
{/* Preload LCP image */}
|
||||||
<link rel="preload" as="image" href="/images/map_placeholder.webp" fetchPriority="high" />
|
<link rel="preload" as="image" href="/images/map_placeholder.webp" fetchPriority="high" />
|
||||||
|
|
||||||
|
|||||||
@@ -153,8 +153,8 @@ export default function PreviewMapShell({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative min-h-screen overflow-hidden bg-gray-950 text-gray-100">
|
<div className="relative overflow-hidden bg-gray-950 text-gray-100" style={{ minHeight: "100svh", height: "100svh" }}>
|
||||||
<div className="relative min-h-screen">
|
<div className="relative" style={{ minHeight: "100svh", height: "100svh" }}>
|
||||||
<Map
|
<Map
|
||||||
ref={mapHandleRef}
|
ref={mapHandleRef}
|
||||||
mode="preview"
|
mode="preview"
|
||||||
@@ -175,6 +175,7 @@ export default function PreviewMapShell({
|
|||||||
onPlayPreviewReplay={onPlayPreviewReplay}
|
onPlayPreviewReplay={onPlayPreviewReplay}
|
||||||
onLoad={onLoad}
|
onLoad={onLoad}
|
||||||
showViewportControls={false}
|
showViewportControls={false}
|
||||||
|
height="100svh"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TimelineBar
|
<TimelineBar
|
||||||
@@ -188,6 +189,7 @@ export default function PreviewMapShell({
|
|||||||
filterEnabled={timelineFilterEnabled}
|
filterEnabled={timelineFilterEnabled}
|
||||||
onFilterEnabledChange={onTimelineFilterEnabledChange}
|
onFilterEnabledChange={onTimelineFilterEnabledChange}
|
||||||
style={timelineStyle}
|
style={timelineStyle}
|
||||||
|
onPlayReplay={onPlayPreviewReplay}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<style dangerouslySetInnerHTML={{ __html: `
|
<style dangerouslySetInnerHTML={{ __html: `
|
||||||
@@ -408,45 +410,7 @@ export default function PreviewMapShell({
|
|||||||
)}
|
)}
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
{onPlayPreviewReplay ? (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onPlayPreviewReplay}
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 10,
|
|
||||||
right: 18,
|
|
||||||
width: 46,
|
|
||||||
height: 46,
|
|
||||||
backgroundColor: "#1e293b",
|
|
||||||
border: "1px solid rgba(56, 189, 248, 0.3)",
|
|
||||||
borderRadius: "50%",
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#38bdf8",
|
|
||||||
transition: "all 0.2s ease",
|
|
||||||
boxShadow: "0 4px 12px rgba(56, 189, 248, 0.15)",
|
|
||||||
backdropFilter: "blur(8px)",
|
|
||||||
zIndex: 22,
|
|
||||||
}}
|
|
||||||
title="Play replay của geometry đang chọn"
|
|
||||||
aria-label="Play selected replay"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden="true"
|
|
||||||
style={{
|
|
||||||
width: 0,
|
|
||||||
height: 0,
|
|
||||||
borderTop: "6px solid transparent",
|
|
||||||
borderBottom: "6px solid transparent",
|
|
||||||
borderLeft: "9px solid currentColor",
|
|
||||||
marginLeft: "3px",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{overlay}
|
{overlay}
|
||||||
|
|
||||||
|
|||||||
@@ -404,6 +404,46 @@ export default function PublicPreviewClientPage({
|
|||||||
};
|
};
|
||||||
}, [isLayerPanelVisible, displayedActiveEntity, isLargeScreen, sidebarWidth, sidebarHeight]);
|
}, [isLayerPanelVisible, displayedActiveEntity, isLargeScreen, sidebarWidth, sidebarHeight]);
|
||||||
|
|
||||||
|
const searchBarWidth = useMemo(() => {
|
||||||
|
if (isLargeScreen) {
|
||||||
|
return "min(392px, calc(100vw - 120px))";
|
||||||
|
}
|
||||||
|
if (isLayerPanelVisible) {
|
||||||
|
return `calc(100vw - 104px)`;
|
||||||
|
} else {
|
||||||
|
return `calc(100vw - 36px)`;
|
||||||
|
}
|
||||||
|
}, [isLargeScreen, isLayerPanelVisible]);
|
||||||
|
|
||||||
|
const searchBarWrapperStyle = useMemo(() => {
|
||||||
|
if (isLargeScreen) {
|
||||||
|
return {
|
||||||
|
position: "absolute" as const,
|
||||||
|
top: 10,
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
right: "auto",
|
||||||
|
zIndex: 18,
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
pointerEvents: "auto" as const,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
position: "absolute" as const,
|
||||||
|
top: 10,
|
||||||
|
left: "auto",
|
||||||
|
right: 18,
|
||||||
|
transform: "none",
|
||||||
|
zIndex: 18,
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
alignItems: "flex-start",
|
||||||
|
pointerEvents: "auto" as const,
|
||||||
|
};
|
||||||
|
}, [isLargeScreen]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isBackgroundVisibilityReady && loadInteractiveMap && (
|
{isBackgroundVisibilityReady && loadInteractiveMap && (
|
||||||
@@ -470,19 +510,7 @@ export default function PublicPreviewClientPage({
|
|||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div
|
<div style={searchBarWrapperStyle}>
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: 10,
|
|
||||||
left: "50%",
|
|
||||||
transform: "translateX(-50%)",
|
|
||||||
zIndex: 18,
|
|
||||||
display: "flex",
|
|
||||||
gap: "10px",
|
|
||||||
alignItems: "flex-start",
|
|
||||||
pointerEvents: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PresentPlaceSearch
|
<PresentPlaceSearch
|
||||||
focusedPlace={focusedPresentPlace}
|
focusedPlace={focusedPresentPlace}
|
||||||
onFocusPlace={handleFocusPresentPlace}
|
onFocusPlace={handleFocusPresentPlace}
|
||||||
@@ -493,7 +521,7 @@ export default function PublicPreviewClientPage({
|
|||||||
top: 0,
|
top: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
transform: "none",
|
transform: "none",
|
||||||
width: "min(392px, calc(100vw - 120px))",
|
width: searchBarWidth,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Props = {
|
|||||||
filterEnabled?: boolean;
|
filterEnabled?: boolean;
|
||||||
onFilterEnabledChange?: (enabled: boolean) => void;
|
onFilterEnabledChange?: (enabled: boolean) => void;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
onPlayReplay?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function TimelineBar({
|
export default function TimelineBar({
|
||||||
@@ -28,6 +29,7 @@ export default function TimelineBar({
|
|||||||
filterEnabled,
|
filterEnabled,
|
||||||
onFilterEnabledChange,
|
onFilterEnabledChange,
|
||||||
style,
|
style,
|
||||||
|
onPlayReplay,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const lower = FIXED_TIMELINE_START_YEAR;
|
const lower = FIXED_TIMELINE_START_YEAR;
|
||||||
const upper = FIXED_TIMELINE_END_YEAR;
|
const upper = FIXED_TIMELINE_END_YEAR;
|
||||||
@@ -272,6 +274,40 @@ export default function TimelineBar({
|
|||||||
>
|
>
|
||||||
+
|
+
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{onPlayReplay ? (
|
||||||
|
<>
|
||||||
|
<div style={{ width: 1, height: 16, backgroundColor: "rgba(255, 255, 255, 0.15)" }} />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onPlayReplay}
|
||||||
|
style={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "#2563eb",
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.2)",
|
||||||
|
color: "white",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
title="Xem diễn biến lịch sử (Replay)"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<polygon points="5 3 19 12 5 21 5 3" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Ruler row below: full width */}
|
{/* Ruler row below: full width */}
|
||||||
@@ -336,6 +372,7 @@ export default function TimelineBar({
|
|||||||
maxYear={upper}
|
maxYear={upper}
|
||||||
disabled={effectiveDisabled}
|
disabled={effectiveDisabled}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={styles.numberWrapper}>
|
<div className={styles.numberWrapper}>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -385,6 +422,39 @@ export default function TimelineBar({
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{onPlayReplay ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={onPlayReplay}
|
||||||
|
style={{
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: "50%",
|
||||||
|
backgroundColor: "#2563eb",
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.2)",
|
||||||
|
color: "white",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.2s ease",
|
||||||
|
marginLeft: 8,
|
||||||
|
marginRight: 8,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
title="Xem diễn biến lịch sử (Replay)"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="14"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
>
|
||||||
|
<polygon points="5 3 19 12 5 21 5 3" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
{typeof timeRange === "number" && onTimeRangeChange ? (
|
{typeof timeRange === "number" && onTimeRangeChange ? (
|
||||||
<label
|
<label
|
||||||
title="time_range (0-30)"
|
title="time_range (0-30)"
|
||||||
|
|||||||
Reference in New Issue
Block a user