Files
DefVideo/client/src/components/overlay/RouteInfoOverlay.tsx
b23042 819065a8f5 UI 수정
기획안 반영 및 보완
2026-06-19 14:40:47 +09:00

95 lines
3.5 KiB
TypeScript

import { useCallback, useRef, useState } from 'react';
import { useGeoStore } from '../../store/geoStore';
import { usePlayerStore } from '../../store/playerStore';
import styles from './RouteInfo.module.css';
/** 정적 에셋 경로 (Vite base 반영). */
const bgUrl = `${import.meta.env.BASE_URL}assets/title-panel-bg@2x.png`;
/** 원본 디자인 무대 가로폭(px). 배너는 이 기준으로 만들어졌다. */
const STAGE_WIDTH = 1920;
/** 초 → "M분 S초". */
function formatDuration(sec?: number | null): string {
if (sec == null || !isFinite(sec) || sec <= 0) return '';
const m = Math.floor(sec / 60);
const s = Math.round(sec % 60);
return s > 0 ? `${m}${s}` : `${m}`;
}
/** "158k700" → 158700 (m). 매칭 실패 시 -1. */
function stationKm(title: string): number {
const m = title.match(/(\d+)[Kk](\d+)/);
return m ? parseInt(m[1], 10) * 1000 + parseInt(m[2], 10) : -1;
}
/**
* 영상 좌상단 노선 정보 배너 — videoplayer 의 RouteInfo 디자인 이식.
* 값은 모두 선택한 폴더에서 가져온다(하드코딩 없음).
* - 연장(lengthKm): route.json 우선 → 측점 CSV 구간(min~max km) 계산 폴백.
* - 소요(durationSec): route.json 우선 → 실제 영상 길이 폴백.
* - 방향/노선명: CSV에 없는 정보 → route.json(routeInfo).
* 표출할 값이 하나도 없으면 렌더하지 않는다.
*/
export default function RouteInfoOverlay() {
const routeInfo = useGeoStore((s) => s.routeMeta?.routeInfo);
const stations = useGeoStore((s) => s.stations);
const videoDuration = usePlayerStore((s) => s.duration);
// 원본처럼 영상 폭/1920 비율로 배너를 스케일 (부모=영상 영역 폭 관측).
const [scale, setScale] = useState(1);
const roRef = useRef<ResizeObserver | null>(null);
const setPanelRef = useCallback((el: HTMLDivElement | null) => {
roRef.current?.disconnect();
const parent = el?.parentElement;
if (!parent) return;
const update = () => setScale(parent.clientWidth / STAGE_WIDTH);
update();
const ro = new ResizeObserver(update);
ro.observe(parent);
roRef.current = ro;
}, []);
const direction = routeInfo?.direction;
const name = routeInfo?.name;
// 연장: route.json 우선 → 측점 구간 계산 폴백
let lengthKm = routeInfo?.lengthKm ?? null;
if (lengthKm == null && stations.length) {
const kms = stations.map((s) => stationKm(s.title)).filter((k) => k >= 0);
if (kms.length >= 2) {
lengthKm = Math.round((Math.max(...kms) - Math.min(...kms)) / 10) / 100; // m→km, 소수2
}
}
// 소요시간: route.json 우선 → 실제 영상 길이 폴백
const dur = formatDuration(routeInfo?.durationSec ?? videoDuration);
if (!direction && !name && lengthKm == null && !dur) return null;
return (
<div
ref={setPanelRef}
className={styles.panel}
style={{ transform: `scale(${scale})`, transformOrigin: 'top left' }}
>
<img className={styles.bg} src={bgUrl} alt="" />
{direction && <p className={styles.direction}>{direction}</p>}
{name && <p className={styles.routeName}>{name}</p>}
{lengthKm != null && (
<>
<p className={styles.lengthLabel}></p>
<p className={styles.lengthValue}>{lengthKm}</p>
<p className={styles.lengthUnit}>km</p>
</>
)}
{dur && (
<>
<p className={styles.durationValue}>{dur}</p>
<p className={styles.durationLabel}></p>
</>
)}
</div>
);
}