Merge pull request 'feat(map): 자유 시점 토글 추가 (모드별 독립 상태)' (#40) from feature/free-camera-toggle into develop
Reviewed-on: #40
This commit is contained in:
커밋
ebf25d9ee5
@ -411,6 +411,7 @@ export function DashboardPage() {
|
|||||||
onMapReady={handleMapReady}
|
onMapReady={handleMapReady}
|
||||||
alarmMmsiMap={alarmMmsiMap}
|
alarmMmsiMap={alarmMmsiMap}
|
||||||
onClickShipPhoto={handleOpenImageModal}
|
onClickShipPhoto={handleOpenImageModal}
|
||||||
|
freeCamera={state.freeCamera}
|
||||||
/>
|
/>
|
||||||
<GlobalTrackReplayPanel />
|
<GlobalTrackReplayPanel />
|
||||||
<WeatherPanel
|
<WeatherPanel
|
||||||
|
|||||||
@ -80,6 +80,7 @@ export function DashboardSidebar({
|
|||||||
typeEnabled, setTypeEnabled,
|
typeEnabled, setTypeEnabled,
|
||||||
overlays, setOverlays,
|
overlays, setOverlays,
|
||||||
projection, setProjection, isProjectionToggleDisabled,
|
projection, setProjection, isProjectionToggleDisabled,
|
||||||
|
freeCamera, toggleFreeCamera,
|
||||||
selectedMmsi, setSelectedMmsi,
|
selectedMmsi, setSelectedMmsi,
|
||||||
fleetRelationSortMode, setFleetRelationSortMode,
|
fleetRelationSortMode, setFleetRelationSortMode,
|
||||||
hoveredFleetOwnerKey, hoveredFleetMmsiSet,
|
hoveredFleetOwnerKey, hoveredFleetMmsiSet,
|
||||||
@ -158,14 +159,24 @@ export function DashboardSidebar({
|
|||||||
title="지도 표시 설정"
|
title="지도 표시 설정"
|
||||||
className="md:shrink-0"
|
className="md:shrink-0"
|
||||||
actions={
|
actions={
|
||||||
<ToggleButton
|
<div className="flex gap-1">
|
||||||
on={projection === 'globe'}
|
<ToggleButton
|
||||||
onClick={isProjectionToggleDisabled ? undefined : () => setProjection((p) => (p === 'globe' ? 'mercator' : 'globe'))}
|
on={freeCamera}
|
||||||
title={isProjectionToggleDisabled ? '3D 모드 준비 중...' : '3D 지구본 투영'}
|
onClick={toggleFreeCamera}
|
||||||
className={`px-2 py-0.5 text-[9px]${isProjectionToggleDisabled ? " opacity-40 cursor-not-allowed" : ""}`}
|
title="자유 시점 모드 (회전/틸트 허용)"
|
||||||
>
|
className="px-2 py-0.5 text-[9px]"
|
||||||
3D
|
>
|
||||||
</ToggleButton>
|
자유 시점
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton
|
||||||
|
on={projection === 'globe'}
|
||||||
|
onClick={isProjectionToggleDisabled ? undefined : () => setProjection((p) => (p === 'globe' ? 'mercator' : 'globe'))}
|
||||||
|
title={isProjectionToggleDisabled ? '3D 모드 준비 중...' : '3D 지구본 투영'}
|
||||||
|
className={`px-2 py-0.5 text-[9px]${isProjectionToggleDisabled ? " opacity-40 cursor-not-allowed" : ""}`}
|
||||||
|
>
|
||||||
|
3D
|
||||||
|
</ToggleButton>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MapToggles value={overlays} onToggle={(k) => setOverlays((prev) => ({ ...prev, [k]: !prev[k] }))} />
|
<MapToggles value={overlays} onToggle={(k) => setOverlays((prev) => ({ ...prev, [k]: !prev[k] }))} />
|
||||||
|
|||||||
@ -54,6 +54,15 @@ export function useDashboardState(uid: number | null) {
|
|||||||
});
|
});
|
||||||
const [mapView, setMapView] = usePersistedState<MapViewState | null>(uid, 'mapView', null);
|
const [mapView, setMapView] = usePersistedState<MapViewState | null>(uid, 'mapView', null);
|
||||||
|
|
||||||
|
// ── 자유 시점 (모드별 독립) ──
|
||||||
|
const [freeCameraMercator, setFreeCameraMercator] = usePersistedState(uid, 'freeCameraMercator', true);
|
||||||
|
const [freeCameraGlobe, setFreeCameraGlobe] = usePersistedState(uid, 'freeCameraGlobe', true);
|
||||||
|
const freeCamera = projection === 'globe' ? freeCameraGlobe : freeCameraMercator;
|
||||||
|
const toggleFreeCamera = useCallback(() => {
|
||||||
|
if (projection === 'globe') setFreeCameraGlobe((v) => !v);
|
||||||
|
else setFreeCameraMercator((v) => !v);
|
||||||
|
}, [projection, setFreeCameraGlobe, setFreeCameraMercator]);
|
||||||
|
|
||||||
// ── Sort & alarm filters (persisted) ──
|
// ── Sort & alarm filters (persisted) ──
|
||||||
const [fleetRelationSortMode, setFleetRelationSortMode] = usePersistedState<FleetRelationSortMode>(uid, 'sortMode', 'count');
|
const [fleetRelationSortMode, setFleetRelationSortMode] = usePersistedState<FleetRelationSortMode>(uid, 'sortMode', 'count');
|
||||||
const [alarmKindEnabled, setAlarmKindEnabled] = usePersistedState<Record<LegacyAlarmKind, boolean>>(
|
const [alarmKindEnabled, setAlarmKindEnabled] = usePersistedState<Record<LegacyAlarmKind, boolean>>(
|
||||||
@ -132,7 +141,7 @@ export function useDashboardState(uid: number | null) {
|
|||||||
baseMap, projection, setProjection,
|
baseMap, projection, setProjection,
|
||||||
mapStyleSettings, setMapStyleSettings,
|
mapStyleSettings, setMapStyleSettings,
|
||||||
overlays, setOverlays, settings, setSettings,
|
overlays, setOverlays, settings, setSettings,
|
||||||
mapView, setMapView,
|
mapView, setMapView, freeCamera, toggleFreeCamera,
|
||||||
fleetRelationSortMode, setFleetRelationSortMode,
|
fleetRelationSortMode, setFleetRelationSortMode,
|
||||||
alarmKindEnabled, setAlarmKindEnabled,
|
alarmKindEnabled, setAlarmKindEnabled,
|
||||||
fleetFocus, setFleetFocus,
|
fleetFocus, setFleetFocus,
|
||||||
|
|||||||
@ -83,6 +83,7 @@ export function Map3D({
|
|||||||
onMapReady,
|
onMapReady,
|
||||||
alarmMmsiMap,
|
alarmMmsiMap,
|
||||||
onClickShipPhoto,
|
onClickShipPhoto,
|
||||||
|
freeCamera = true,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
// ── Shared refs ──────────────────────────────────────────────────────
|
// ── Shared refs ──────────────────────────────────────────────────────
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
@ -549,6 +550,25 @@ export function Map3D({
|
|||||||
{ projection, ensureMercatorOverlay, onProjectionLoadingChange, pulseMapSync, setMapSyncEpoch },
|
{ projection, ensureMercatorOverlay, onProjectionLoadingChange, pulseMapSync, setMapSyncEpoch },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// freeCamera 토글에 따라 회전/틸트 제어 (모드별 독립 상태)
|
||||||
|
// mapSyncEpoch: 맵 비동기 생성 후 최초 적용을 위해 의존
|
||||||
|
useEffect(() => {
|
||||||
|
const map = mapRef.current;
|
||||||
|
if (!map) return;
|
||||||
|
try {
|
||||||
|
if (freeCamera) {
|
||||||
|
map.dragRotate.enable();
|
||||||
|
map.touchPitch.enable();
|
||||||
|
} else {
|
||||||
|
map.dragRotate.disable();
|
||||||
|
map.touchPitch.disable();
|
||||||
|
if (!projectionBusyRef.current && (map.getPitch() !== 0 || map.getBearing() !== 0)) {
|
||||||
|
map.easeTo({ pitch: 0, bearing: 0, duration: 300 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch { /* ignore */ }
|
||||||
|
}, [freeCamera, projection, mapSyncEpoch]);
|
||||||
|
|
||||||
useBaseMapToggle(
|
useBaseMapToggle(
|
||||||
mapRef,
|
mapRef,
|
||||||
{ baseMap, projection, showSeamark: settings.showSeamark, mapSyncEpoch, pulseMapSync },
|
{ baseMap, projection, showSeamark: settings.showSeamark, mapSyncEpoch, pulseMapSync },
|
||||||
|
|||||||
@ -81,12 +81,12 @@ export function useMapInit(
|
|||||||
style,
|
style,
|
||||||
center: iv?.center ?? [126.5, 34.2],
|
center: iv?.center ?? [126.5, 34.2],
|
||||||
zoom: iv?.zoom ?? 7,
|
zoom: iv?.zoom ?? 7,
|
||||||
pitch: iv?.pitch ?? 45,
|
pitch: 0,
|
||||||
bearing: iv?.bearing ?? 0,
|
bearing: 0,
|
||||||
maxPitch: 85,
|
maxPitch: 85,
|
||||||
dragRotate: true,
|
dragRotate: false,
|
||||||
pitchWithRotate: true,
|
pitchWithRotate: true,
|
||||||
touchPitch: true,
|
touchPitch: false,
|
||||||
scrollZoom: { around: 'center' },
|
scrollZoom: { around: 'center' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -238,6 +238,7 @@ export function useProjectionToggle(
|
|||||||
map.setRenderWorldCopies(next !== 'globe');
|
map.setRenderWorldCopies(next !== 'globe');
|
||||||
|
|
||||||
// Globe에서는 easeTo around 미지원 → scrollZoom 동작 전환
|
// Globe에서는 easeTo around 미지원 → scrollZoom 동작 전환
|
||||||
|
// dragRotate/touchPitch는 Map3D의 freeCamera effect에서 제어
|
||||||
try {
|
try {
|
||||||
map.scrollZoom.disable();
|
map.scrollZoom.disable();
|
||||||
if (next === 'globe') {
|
if (next === 'globe') {
|
||||||
|
|||||||
@ -73,6 +73,8 @@ export interface Map3DProps {
|
|||||||
alarmMmsiMap?: Map<number, LegacyAlarmKind>;
|
alarmMmsiMap?: Map<number, LegacyAlarmKind>;
|
||||||
/** 사진 있는 선박 클릭 시 콜백 (사진 표시자 or 선박 아이콘) */
|
/** 사진 있는 선박 클릭 시 콜백 (사진 표시자 or 선박 아이콘) */
|
||||||
onClickShipPhoto?: (mmsi: number) => void;
|
onClickShipPhoto?: (mmsi: number) => void;
|
||||||
|
/** 자유 시점 모드 (회전/틸트 허용) */
|
||||||
|
freeCamera?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DashSeg = {
|
export type DashSeg = {
|
||||||
|
|||||||
불러오는 중...
Reference in New Issue
Block a user