Merge branch 'iipbs/common/main' into iipbs/sn/ii
This commit is contained in:
@@ -117,6 +117,11 @@ function Gis({
|
||||
onCheckedLayersChange,
|
||||
searchBar: SearchBar,
|
||||
waterDepth: WaterDepth,
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => {},
|
||||
targetLayerTypes = [],
|
||||
orderedLayers = [],
|
||||
checkInterval = 3000,
|
||||
height: height = "h-[calc(100vh-10rem)]",
|
||||
}) {
|
||||
const mapRef = useRef();
|
||||
@@ -305,12 +310,19 @@ function Gis({
|
||||
onFeatureSelect(dummyFeature);
|
||||
}}
|
||||
imageVisible={imgVisble}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying}
|
||||
targetLayerTypes={targetLayerTypes}
|
||||
orderedLayers={orderedLayers}
|
||||
checkInterval={checkInterval}
|
||||
/>
|
||||
|
||||
{typeof WaterDepth === "function" && (
|
||||
<div className={`absolute bottom-4 z-40 ${tabVisible ? 'left-[260px]' : 'left-4'}`}>
|
||||
<WaterDepth
|
||||
onClose={() => setIsOpen(false)}
|
||||
isPlaying={isPlaying}
|
||||
setIsPlaying={setIsPlaying}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -5,18 +5,36 @@ import {
|
||||
DndProvider
|
||||
} from "@minoru/react-dnd-treeview";
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
|
||||
import { useGisContext } from './Gis';
|
||||
import GisApp from './lib/gis.core';
|
||||
|
||||
export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange, onLayerIconClick,imageVisible=false }) {
|
||||
export default React.memo(function GisTree({
|
||||
checkedNodes = [],
|
||||
setCheckedNodes = () => { },
|
||||
visible,
|
||||
setVisible,
|
||||
onCheckedChange,
|
||||
onLayerIconClick,
|
||||
imageVisible = false,
|
||||
isPlaying = false,
|
||||
setIsPlaying = () => {},
|
||||
targetLayerTypes = [],
|
||||
orderedLayers = [],
|
||||
checkInterval = 3000,
|
||||
}) {
|
||||
const { gisCore, treeData } = useGisContext();
|
||||
const [checked, setChecked] = useState([]);
|
||||
const [expanded, setExpanded] = useState([]);
|
||||
const [nodes, setNodes] = useState([]);
|
||||
const [activeTab, setActiveTab] = useState('layers');
|
||||
|
||||
const currentAnimationIndexRef = useRef(0);
|
||||
const targetNodesRef = useRef([]); // 대상 노드들을 저장할 ref
|
||||
const isPausedRef = useRef(false); // 일시정지 상태를 저장할 ref
|
||||
const timeoutIdRef = useRef(null); // setTimeout ID를 저장할 ref
|
||||
|
||||
useEffect(() => {
|
||||
if (treeData && treeData.length > 0) {
|
||||
const nodeData = setNodeValues(treeData);
|
||||
@@ -191,8 +209,163 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
|
||||
if (!checked.includes(nodeId)) alert('시설을 선택해주세요')
|
||||
else onLayerIconClick(nodeNm)
|
||||
}
|
||||
|
||||
// 특정 노드를 체크/언체크하는 함수
|
||||
const checkSpecificNode = useCallback((nodeId, shouldCheck = true) => {
|
||||
setChecked((prevChecked) => {
|
||||
let newChecked = [...prevChecked];
|
||||
const isCurrentlyChecked = prevChecked.includes(nodeId);
|
||||
|
||||
if (shouldCheck && !isCurrentlyChecked) {
|
||||
// 체크하기
|
||||
const descendants = getDescendants(nodeId, nodes);
|
||||
newChecked = Array.from(new Set([...newChecked, nodeId, ...descendants]));
|
||||
const ancestors = getAncestors(nodeId, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (isEveryChildChecked(p, nodes, newChecked)) {
|
||||
newChecked.push(p);
|
||||
}
|
||||
});
|
||||
} else if (!shouldCheck && isCurrentlyChecked) {
|
||||
// 언체크하기
|
||||
const descendants = getDescendants(nodeId, nodes);
|
||||
newChecked = newChecked.filter((cid) => cid !== nodeId && !descendants.includes(cid));
|
||||
const ancestors = getAncestors(nodeId, nodes);
|
||||
ancestors.forEach((p) => {
|
||||
if (newChecked.includes(p)) {
|
||||
newChecked = newChecked.filter((cid) => cid !== p);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 지도에 레이어 표시/숨김 적용
|
||||
const allIds = nodes.map((n) => n.id);
|
||||
const unchecked = allIds.filter((nid) => !newChecked.includes(nid));
|
||||
gisCore.setLayerOnMap(newChecked, true);
|
||||
gisCore.setLayerOnMap(unchecked, false);
|
||||
|
||||
// 상위로 변경 사항 전달
|
||||
const checkedNodesData = nodes.filter(node => newChecked.includes(node.id));
|
||||
onCheckedChange?.(newChecked, checkedNodesData);
|
||||
|
||||
return newChecked;
|
||||
});
|
||||
}, [nodes]);
|
||||
|
||||
// 침수위험지도 실행 함수
|
||||
const handleAutoCheckAnimation = useCallback((isEnabled, layerTypes = [], interval = 3000) => {
|
||||
// 기존 타임아웃 정리
|
||||
if (timeoutIdRef.current) {
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
timeoutIdRef.current = null;
|
||||
}
|
||||
|
||||
if (!isEnabled || layerTypes.length === 0) {
|
||||
// 애니메이션 비활성화 시 상태 초기화
|
||||
isPausedRef.current = false;
|
||||
targetNodesRef.current = [];
|
||||
currentAnimationIndexRef.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// 대상 노드들을 찾아서 특정 순서로 정렬 후 배열로 반환
|
||||
const getTargetNodesInOrder = () => {
|
||||
const orderedNodes = [];
|
||||
orderedLayers.forEach(lyrSn => {
|
||||
const node = nodes.find(n => n.id === lyrSn && layerTypes.includes(n.data?.lyrTyNm));
|
||||
if (node) {
|
||||
orderedNodes.push(node);
|
||||
}
|
||||
});
|
||||
|
||||
return orderedNodes;
|
||||
};
|
||||
|
||||
const targetNodes = getTargetNodesInOrder();
|
||||
|
||||
if (targetNodes.length === 0) {
|
||||
console.warn('대상 레이어를 찾을 수 없습니다:', layerTypes);
|
||||
return;
|
||||
}
|
||||
|
||||
// 처음 시작하는 경우에만 초기화
|
||||
if (targetNodesRef.current.length === 0) {
|
||||
targetNodesRef.current = targetNodes;
|
||||
currentAnimationIndexRef.current = 0;
|
||||
|
||||
console.log('자동 체크 대상 노드들 (순서대로):', targetNodes.map(n => ({ id: n.id, name: n.text, type: n.data?.lyrTyNm })));
|
||||
|
||||
// 모든 대상 레이어를 먼저 언체크
|
||||
targetNodes.forEach(node => {
|
||||
checkSpecificNode(node.id, false);
|
||||
});
|
||||
}
|
||||
|
||||
// 애니메이션 재개/시작
|
||||
isPausedRef.current = false;
|
||||
|
||||
const processNextLayer = () => {
|
||||
// 일시정지 상태이거나 애니메이션이 비활성화되면 중단
|
||||
if (isPausedRef.current || !isPlaying) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentTargetNodes = targetNodesRef.current;
|
||||
const currentIndex = currentAnimationIndexRef.current;
|
||||
|
||||
if (currentIndex < currentTargetNodes.length) {
|
||||
checkSpecificNode(currentTargetNodes[currentIndex].id, true);
|
||||
if (currentIndex > 0) {
|
||||
checkSpecificNode(currentTargetNodes[currentIndex - 1].id, false);
|
||||
}
|
||||
|
||||
currentAnimationIndexRef.current++;
|
||||
|
||||
// 다음 레이어 처리를 위한 타이머 설정
|
||||
timeoutIdRef.current = setTimeout(processNextLayer, interval);
|
||||
} else {
|
||||
// 애니메이션 완료 - 상태 초기화 및 완료 신호 전송
|
||||
console.log('애니메이션 완료');
|
||||
isPausedRef.current = false;
|
||||
targetNodesRef.current = [];
|
||||
currentAnimationIndexRef.current = 0;
|
||||
timeoutIdRef.current = null;
|
||||
setIsPlaying?.(false);
|
||||
}
|
||||
};
|
||||
|
||||
processNextLayer();
|
||||
|
||||
}, [nodes, checkSpecificNode, setIsPlaying, isPlaying]);
|
||||
|
||||
// 애니메이션 일시정지 함수
|
||||
const pauseAnimation = useCallback(() => {
|
||||
if (timeoutIdRef.current) {
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
timeoutIdRef.current = null;
|
||||
}
|
||||
isPausedRef.current = true;
|
||||
}, []);
|
||||
|
||||
// 외부에서 전달받은 props로 자동 체크 제어
|
||||
useEffect(() => {
|
||||
if (isPlaying) handleAutoCheckAnimation(true, targetLayerTypes, checkInterval); // 재생 시작/재개
|
||||
else pauseAnimation(); // 일시정지
|
||||
}, [isPlaying, handleAutoCheckAnimation, targetLayerTypes, checkInterval, pauseAnimation]);
|
||||
|
||||
// 컴포넌트 언마운트 시 정리
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (timeoutIdRef.current) {
|
||||
clearTimeout(timeoutIdRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
if (!treeData || treeData.length === 0) return null;
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user