@mint-ui/map 1.2.0-test.34 → 1.2.0-test.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/mint-map/core/advanced/shared/context.d.ts +19 -12
- package/dist/components/mint-map/core/advanced/shared/context.js +54 -75
- package/dist/components/mint-map/core/advanced/shared/helpers.d.ts +20 -0
- package/dist/components/mint-map/core/advanced/shared/helpers.js +40 -0
- package/dist/components/mint-map/core/advanced/shared/hooks.d.ts +74 -0
- package/dist/components/mint-map/core/advanced/shared/hooks.js +189 -0
- package/dist/components/mint-map/core/advanced/shared/index.d.ts +3 -0
- package/dist/components/mint-map/core/advanced/shared/performance.d.ts +12 -110
- package/dist/components/mint-map/core/advanced/shared/performance.js +56 -151
- package/dist/components/mint-map/core/advanced/shared/types.d.ts +18 -153
- package/dist/components/mint-map/core/advanced/shared/types.js +0 -1
- package/dist/components/mint-map/core/advanced/shared/utils.d.ts +36 -27
- package/dist/components/mint-map/core/advanced/shared/utils.js +58 -52
- package/dist/components/mint-map/core/advanced/shared/viewport.d.ts +42 -0
- package/dist/components/mint-map/core/advanced/shared/viewport.js +51 -0
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/WoongCanvasMarker.d.ts +22 -74
- package/dist/components/mint-map/core/advanced/woongCanvasMarker/WoongCanvasMarker.js +156 -617
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.d.ts +26 -76
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/WoongCanvasPolygon.js +152 -551
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/renderer.d.ts +67 -8
- package/dist/components/mint-map/core/advanced/woongCanvasPolygon/renderer.js +81 -20
- package/dist/index.es.js +917 -1575
- package/dist/index.js +11 -0
- package/dist/index.umd.js +923 -1573
- package/package.json +1 -1
|
@@ -14,15 +14,15 @@ var types = require('../shared/types.js');
|
|
|
14
14
|
var utils = require('../shared/utils.js');
|
|
15
15
|
var context = require('../shared/context.js');
|
|
16
16
|
var performance = require('../shared/performance.js');
|
|
17
|
+
var viewport = require('../shared/viewport.js');
|
|
18
|
+
var hooks = require('../shared/hooks.js');
|
|
19
|
+
var helpers = require('../shared/helpers.js');
|
|
17
20
|
|
|
18
21
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
19
22
|
|
|
20
23
|
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
21
24
|
var Konva__default = /*#__PURE__*/_interopDefaultLegacy(Konva);
|
|
22
25
|
|
|
23
|
-
// 메인 컴포넌트
|
|
24
|
-
// ============================================================================
|
|
25
|
-
|
|
26
26
|
var WoongCanvasMarker = function (props) {
|
|
27
27
|
var data = props.data,
|
|
28
28
|
onClick = props.onClick,
|
|
@@ -32,232 +32,88 @@ var WoongCanvasMarker = function (props) {
|
|
|
32
32
|
enableMultiSelect = _a === void 0 ? false : _a,
|
|
33
33
|
_b = props.topOnHover,
|
|
34
34
|
topOnHover = _b === void 0 ? false : _b,
|
|
35
|
-
_c = props.
|
|
36
|
-
|
|
37
|
-
_d = props.
|
|
38
|
-
|
|
39
|
-
_e = props.maxCacheSize,
|
|
40
|
-
maxCacheSize = _e === void 0 ? performance.DEFAULT_MAX_CACHE_SIZE : _e,
|
|
35
|
+
_c = props.cullingMargin,
|
|
36
|
+
cullingMargin = _c === void 0 ? performance.DEFAULT_CULLING_MARGIN : _c,
|
|
37
|
+
_d = props.maxCacheSize,
|
|
38
|
+
maxCacheSize = _d === void 0 ? performance.DEFAULT_MAX_CACHE_SIZE : _d,
|
|
41
39
|
externalSelectedItems = props.selectedItems,
|
|
42
40
|
externalSelectedItem = props.selectedItem,
|
|
43
|
-
|
|
44
|
-
disableInteraction =
|
|
41
|
+
_e = props.disableInteraction,
|
|
42
|
+
disableInteraction = _e === void 0 ? false : _e,
|
|
45
43
|
renderBase = props.renderBase,
|
|
46
|
-
renderAnimation = props.renderAnimation,
|
|
47
44
|
renderEvent = props.renderEvent,
|
|
48
|
-
options = tslib.__rest(props, ["data", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "
|
|
49
|
-
// Hooks & Context
|
|
50
|
-
// --------------------------------------------------------------------------
|
|
51
|
-
|
|
45
|
+
options = tslib.__rest(props, ["data", "onClick", "onMouseOver", "onMouseOut", "enableMultiSelect", "topOnHover", "cullingMargin", "maxCacheSize", "selectedItems", "selectedItem", "disableInteraction", "renderBase", "renderEvent"]);
|
|
52
46
|
|
|
53
47
|
var controller = MintMapProvider.useMintMapController();
|
|
54
48
|
var context$1 = context.useWoongCanvasContext();
|
|
55
|
-
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; //
|
|
56
|
-
// DOM Refs
|
|
57
|
-
// --------------------------------------------------------------------------
|
|
49
|
+
var currentZIndex = options.zIndex !== undefined ? options.zIndex : 0; // DOM Refs
|
|
58
50
|
|
|
59
51
|
var divRef = React.useRef(document.createElement('div'));
|
|
60
52
|
var divElement = divRef.current;
|
|
61
53
|
var containerRef = React.useRef(null);
|
|
62
|
-
var markerRef = React.useRef(); //
|
|
63
|
-
// Konva Refs
|
|
64
|
-
// --------------------------------------------------------------------------
|
|
54
|
+
var markerRef = React.useRef(); // Konva Refs
|
|
65
55
|
|
|
66
56
|
var stageRef = React.useRef(null);
|
|
67
57
|
var baseLayerRef = React.useRef(null);
|
|
68
|
-
var
|
|
69
|
-
var eventLayerRef = React.useRef(null); // --------------------------------------------------------------------------
|
|
70
|
-
// Data Refs - 선택 및 Hover 상태 관리
|
|
71
|
-
// --------------------------------------------------------------------------
|
|
72
|
-
|
|
73
|
-
/** data prop을 ref로 추적 (stale closure 방지, useEffect에서 동기화) */
|
|
74
|
-
|
|
75
|
-
var dataRef = React.useRef(data); // --------------------------------------------------------------------------
|
|
76
|
-
// State Refs - 선택 및 Hover 상태 관리
|
|
77
|
-
// --------------------------------------------------------------------------
|
|
78
|
-
|
|
79
|
-
/** 상호작용 비활성화 상태 (Ref로 관리하여 클로저 문제 해결) */
|
|
58
|
+
var eventLayerRef = React.useRef(null); // 상태 관리 Refs (React 리렌더링 최소화)
|
|
80
59
|
|
|
60
|
+
var dataRef = React.useRef(data);
|
|
81
61
|
var disableInteractionRef = React.useRef(disableInteraction);
|
|
82
|
-
/** 현재 Hover 중인 항목 */
|
|
83
|
-
|
|
84
62
|
var hoveredItemRef = React.useRef(null);
|
|
85
|
-
/** 외부에서 전달된 선택 항목 (Ref로 관리하여 클로저 문제 해결) */
|
|
86
|
-
|
|
87
63
|
var selectedItemRef = React.useRef(externalSelectedItem);
|
|
88
|
-
/**
|
|
89
|
-
* 선택된 항목의 ID Set
|
|
90
|
-
*
|
|
91
|
-
* 용도:
|
|
92
|
-
* 1. onClick 콜백에 전달 - onClick(data, selectedIdsRef.current)
|
|
93
|
-
* 2. 선택 여부 빠른 체크 - selectedIdsRef.current.has(id)
|
|
94
|
-
* 3. 메모리 효율 - ID만 저장 (작음)
|
|
95
|
-
*
|
|
96
|
-
* selectedItemsMapRef와 차이:
|
|
97
|
-
* - selectedIdsRef: ID만 저장 { "id1", "id2" }
|
|
98
|
-
* - selectedItemsMapRef: 전체 객체 저장 { id1: {...}, id2: {...} }
|
|
99
|
-
*
|
|
100
|
-
* 둘 다 필요: ID만 필요한 곳은 이것, 전체 데이터 필요한 곳은 Map
|
|
101
|
-
*/
|
|
102
|
-
|
|
103
64
|
var selectedIdsRef = React.useRef(new Set());
|
|
104
|
-
|
|
105
|
-
* 선택된 항목의 실제 데이터 Map (핵심 성능 최적화!)
|
|
106
|
-
*
|
|
107
|
-
* 목적: doRenderEvent에서 filter() 순회 제거
|
|
108
|
-
* - 이전: markersRef.current.filter() → O(전체 마커 수)
|
|
109
|
-
* - 현재: Map.values() → O(선택된 항목 수)
|
|
110
|
-
*
|
|
111
|
-
* 성능 개선: 10,000개 중 1개 선택 시
|
|
112
|
-
* - 이전: 10,000번 체크
|
|
113
|
-
* - 현재: 1번 접근 (10,000배 빠름!)
|
|
114
|
-
*/
|
|
115
|
-
|
|
116
|
-
var selectedItemsMapRef = React.useRef(new Map()); // --------------------------------------------------------------------------
|
|
117
|
-
// Drag Refs
|
|
118
|
-
// --------------------------------------------------------------------------
|
|
65
|
+
var selectedItemsMapRef = React.useRef(new Map()); // 드래그 상태 Refs
|
|
119
66
|
|
|
120
67
|
var draggingRef = React.useRef(false);
|
|
121
68
|
var prevCenterOffsetRef = React.useRef(null);
|
|
122
69
|
var accumTranslateRef = React.useRef({
|
|
123
70
|
x: 0,
|
|
124
71
|
y: 0
|
|
125
|
-
}); //
|
|
126
|
-
// Performance Refs (캐싱 & 최적화)
|
|
127
|
-
// --------------------------------------------------------------------------
|
|
128
|
-
|
|
129
|
-
/** 좌표 변환 결과 LRU 캐시 */
|
|
72
|
+
}); // 성능 최적화 Refs
|
|
130
73
|
|
|
131
74
|
var offsetCacheRef = React.useRef(new performance.LRUCache(maxCacheSize));
|
|
132
|
-
/** 공간 인덱스 (빠른 Hit Test) */
|
|
133
|
-
|
|
134
75
|
var spatialIndexRef = React.useRef(new performance.SpatialHashGrid(performance.SPATIAL_GRID_CELL_SIZE));
|
|
135
|
-
/** 바운딩 박스 캐시 (Viewport Culling 최적화) */
|
|
136
|
-
|
|
137
76
|
var boundingBoxCacheRef = React.useRef(new Map());
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
var viewportRef = React.useRef(null); // --------------------------------------------------------------------------
|
|
141
|
-
// 유틸리티 함수: 뷰포트 관리
|
|
142
|
-
// --------------------------------------------------------------------------
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* 현재 뷰포트 영역 계산
|
|
146
|
-
*/
|
|
77
|
+
var viewportRef = React.useRef(null); // 뷰포트 영역 계산 (Viewport Culling용)
|
|
147
78
|
|
|
148
79
|
var updateViewport = function () {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
viewportRef.current = {
|
|
152
|
-
minX: -cullingMargin,
|
|
153
|
-
maxX: stage.width() + cullingMargin,
|
|
154
|
-
minY: -cullingMargin,
|
|
155
|
-
maxY: stage.height() + cullingMargin
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
/**
|
|
159
|
-
* 아이템이 현재 뷰포트 안에 있는지 확인 (바운딩 박스 캐싱)
|
|
160
|
-
*/
|
|
80
|
+
viewport.updateViewport(stageRef.current, cullingMargin, viewportRef);
|
|
81
|
+
}; // 뷰포트 내부 여부 확인 (바운딩 박스 캐싱)
|
|
161
82
|
|
|
162
83
|
|
|
163
84
|
var isInViewport = function (item) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
var bbox = boundingBoxCacheRef.current.get(item.id);
|
|
168
|
-
|
|
169
|
-
if (!bbox) {
|
|
170
|
-
// 바운딩 박스 계산 (공통 함수 사용)
|
|
171
|
-
var computed = computeBoundingBox(item);
|
|
172
|
-
if (!computed) return false;
|
|
173
|
-
bbox = computed;
|
|
174
|
-
boundingBoxCacheRef.current.set(item.id, bbox);
|
|
175
|
-
} // 바운딩 박스와 viewport 교차 체크
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return !(bbox.maxX < viewport.minX || bbox.minX > viewport.maxX || bbox.maxY < viewport.minY || bbox.minY > viewport.maxY);
|
|
179
|
-
}; // --------------------------------------------------------------------------
|
|
180
|
-
// 유틸리티 함수: 좌표 변환 캐싱
|
|
181
|
-
// --------------------------------------------------------------------------
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 마커 좌표 변환 결과를 캐시하고 반환
|
|
185
|
-
*
|
|
186
|
-
* @param markerData 마커 데이터
|
|
187
|
-
* @returns 변환된 좌표 또는 null
|
|
188
|
-
*/
|
|
85
|
+
return viewport.isInViewport(item, viewportRef, boundingBoxCacheRef, computeBoundingBox);
|
|
86
|
+
}; // 마커 좌표 변환 (위경도 → 화면 좌표, LRU 캐시 사용)
|
|
189
87
|
|
|
190
88
|
|
|
191
89
|
var getOrComputeMarkerOffset = function (markerData) {
|
|
192
90
|
var cached = offsetCacheRef.current.get(markerData.id);
|
|
193
91
|
if (cached && !Array.isArray(cached)) return cached;
|
|
194
92
|
var result = utils.computeMarkerOffset(markerData, controller);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
offsetCacheRef.current.set(markerData.id, result);
|
|
198
|
-
}
|
|
199
|
-
|
|
93
|
+
if (!result) return null;
|
|
94
|
+
offsetCacheRef.current.set(markerData.id, result);
|
|
200
95
|
return result;
|
|
201
|
-
}; //
|
|
202
|
-
// 유틸리티 함수: 바운딩 박스 계산
|
|
203
|
-
// --------------------------------------------------------------------------
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* 마커의 바운딩 박스 계산
|
|
207
|
-
*
|
|
208
|
-
* 🎯 마커의 경우:
|
|
209
|
-
* - boxHeight: 본체만 (Hit Test 영역)
|
|
210
|
-
* - tailHeight: 꼬리 높이 (Viewport Culling용, 화면에 보이는 전체 영역)
|
|
211
|
-
*
|
|
212
|
-
* @param item 마커 데이터
|
|
213
|
-
* @returns 바운딩 박스 또는 null
|
|
214
|
-
*/
|
|
96
|
+
}; // 마커 바운딩 박스 계산 (Viewport Culling 및 Hit Test용)
|
|
215
97
|
|
|
216
98
|
|
|
217
99
|
var computeBoundingBox = function (item) {
|
|
218
|
-
// 마커: 중심점 기준 박스 크기 계산 (꼬리 포함)
|
|
219
100
|
var offset = getOrComputeMarkerOffset(item);
|
|
220
101
|
if (!offset) return null;
|
|
221
102
|
var boxWidth = item.boxWidth || 50;
|
|
222
103
|
var boxHeight = item.boxHeight || 28;
|
|
223
|
-
var tailHeight = item.tailHeight || 0;
|
|
224
|
-
|
|
104
|
+
var tailHeight = item.tailHeight || 0;
|
|
225
105
|
return {
|
|
226
106
|
minX: offset.x - boxWidth / 2,
|
|
227
107
|
minY: offset.y - boxHeight - tailHeight,
|
|
228
108
|
maxX: offset.x + boxWidth / 2,
|
|
229
109
|
maxY: offset.y
|
|
230
110
|
};
|
|
231
|
-
}; //
|
|
232
|
-
// 유틸리티 함수: 공간 인덱싱
|
|
233
|
-
// --------------------------------------------------------------------------
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* 공간 인덱스 빌드 (빠른 Hit Test를 위한 자료구조)
|
|
237
|
-
*/
|
|
111
|
+
}; // 공간 인덱스 빌드 (빠른 Hit Test용)
|
|
238
112
|
|
|
239
113
|
|
|
240
114
|
var buildSpatialIndex = function () {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
var currentData = dataRef.current;
|
|
244
|
-
|
|
245
|
-
for (var _i = 0, currentData_1 = currentData; _i < currentData_1.length; _i++) {
|
|
246
|
-
var item = currentData_1[_i]; // 바운딩 박스 계산 (공통 함수 사용)
|
|
247
|
-
|
|
248
|
-
var bbox = computeBoundingBox(item);
|
|
249
|
-
|
|
250
|
-
if (bbox) {
|
|
251
|
-
spatial.insert(item, bbox.minX, bbox.minY, bbox.maxX, bbox.maxY);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}; // --------------------------------------------------------------------------
|
|
255
|
-
// 렌더링 함수 결정 (dataType에 따라)
|
|
256
|
-
// --------------------------------------------------------------------------
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* 외부 렌더링 함수에 전달할 유틸리티 객체
|
|
260
|
-
*/
|
|
115
|
+
hooks.buildSpatialIndex(dataRef.current, spatialIndexRef.current, computeBoundingBox);
|
|
116
|
+
}; // 렌더링 유틸리티 객체
|
|
261
117
|
|
|
262
118
|
|
|
263
119
|
var renderUtils = {
|
|
@@ -265,48 +121,29 @@ var WoongCanvasMarker = function (props) {
|
|
|
265
121
|
return null;
|
|
266
122
|
},
|
|
267
123
|
getOrComputeMarkerOffset: getOrComputeMarkerOffset
|
|
268
|
-
};
|
|
269
|
-
/** Base Layer에서 사용할 빈 Set (재사용) */
|
|
270
|
-
|
|
271
|
-
React.useRef(new Set());
|
|
272
|
-
/**
|
|
273
|
-
* Base 레이어 렌더링 (뷰포트 컬링 적용, 선택된 마커 제외)
|
|
274
|
-
*
|
|
275
|
-
* 🔥 최적화:
|
|
276
|
-
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
277
|
-
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
278
|
-
* 3. 클로저로 최신 데이터 참조
|
|
279
|
-
*
|
|
280
|
-
* 🎯 topOnHover 지원:
|
|
281
|
-
* - renderEvent가 없을 때 Base Layer에서 hover 처리 (fallback)
|
|
282
|
-
* - renderEvent가 있으면 Event Layer에서 처리 (성능 최적화)
|
|
283
|
-
*/
|
|
124
|
+
}; // Base Layer 렌더링 (뷰포트 컬링 적용, 선택된 마커 제외)
|
|
284
125
|
|
|
285
126
|
var doRenderBase = function () {
|
|
286
127
|
var layer = baseLayerRef.current;
|
|
287
|
-
if (!layer) return;
|
|
288
|
-
|
|
128
|
+
if (!layer) return;
|
|
289
129
|
var shape = layer.findOne('.base-render-shape');
|
|
290
130
|
|
|
291
131
|
if (!shape) {
|
|
292
|
-
// 최초 생성 (한 번만 실행됨)
|
|
293
|
-
// sceneFunc도 여기서 한 번만 설정 (클로저로 최신 데이터 참조)
|
|
294
132
|
shape = new Konva__default["default"].Shape({
|
|
295
133
|
name: 'base-render-shape',
|
|
296
134
|
sceneFunc: function (context, shape) {
|
|
297
135
|
var ctx = context;
|
|
298
|
-
var hovered = hoveredItemRef.current; //
|
|
136
|
+
var hovered = hoveredItemRef.current; // 뷰포트 컬링: 화면에 보이는 항목만 필터링
|
|
299
137
|
|
|
300
|
-
var visibleItems =
|
|
138
|
+
var visibleItems = dataRef.current.filter(function (item) {
|
|
301
139
|
return isInViewport(item);
|
|
302
|
-
})
|
|
140
|
+
}); // topOnHover 옵션: hover된 항목을 나중에 그려서 최상위에 표시
|
|
303
141
|
|
|
304
142
|
if (topOnHover && !renderEvent && hovered) {
|
|
305
|
-
// hover된 항목 제외하고 렌더링
|
|
306
143
|
visibleItems = visibleItems.filter(function (item) {
|
|
307
144
|
return item.id !== hovered.id;
|
|
308
145
|
});
|
|
309
|
-
} // 일반
|
|
146
|
+
} // 일반 항목들 먼저 렌더링
|
|
310
147
|
|
|
311
148
|
|
|
312
149
|
renderBase({
|
|
@@ -315,12 +152,10 @@ var WoongCanvasMarker = function (props) {
|
|
|
315
152
|
selectedIds: selectedIdsRef.current,
|
|
316
153
|
hoveredItem: hovered,
|
|
317
154
|
utils: renderUtils
|
|
318
|
-
}); // hover된 항목을
|
|
155
|
+
}); // hover된 항목을 마지막에 렌더링하여 최상위에 표시
|
|
319
156
|
|
|
320
157
|
if (topOnHover && !renderEvent && hovered) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (isHoveredInViewport) {
|
|
158
|
+
if (isInViewport(hovered)) {
|
|
324
159
|
renderBase({
|
|
325
160
|
ctx: ctx,
|
|
326
161
|
items: [hovered],
|
|
@@ -336,61 +171,26 @@ var WoongCanvasMarker = function (props) {
|
|
|
336
171
|
hitStrokeWidth: 0
|
|
337
172
|
});
|
|
338
173
|
layer.add(shape);
|
|
339
|
-
}
|
|
340
|
-
|
|
174
|
+
}
|
|
341
175
|
|
|
342
176
|
layer.batchDraw();
|
|
343
|
-
};
|
|
344
|
-
/**
|
|
345
|
-
* Animation 레이어 렌더링 (선택된 마커 애니메이션)
|
|
346
|
-
*
|
|
347
|
-
* 🔥 최적화: sceneFunc 내부에서 최신 items 참조
|
|
348
|
-
* - 선택 변경 시에만 재생성
|
|
349
|
-
* - 지도 이동 시에는 기존 Animation 계속 실행
|
|
350
|
-
*/
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
var doRenderAnimation = function () {
|
|
354
|
-
if (!renderAnimation) return;
|
|
355
|
-
var layer = animationLayerRef.current;
|
|
356
|
-
if (!layer) return;
|
|
357
|
-
renderAnimation({
|
|
358
|
-
layer: layer,
|
|
359
|
-
selectedIds: selectedIdsRef.current,
|
|
360
|
-
items: dataRef.current,
|
|
361
|
-
utils: renderUtils
|
|
362
|
-
});
|
|
363
|
-
};
|
|
364
|
-
/**
|
|
365
|
-
* Event 레이어 렌더링 (hover + 선택 상태 표시)
|
|
366
|
-
*
|
|
367
|
-
* 🔥 최적화:
|
|
368
|
-
* 1. Shape 재사용으로 객체 생성/파괴 오버헤드 제거
|
|
369
|
-
* 2. sceneFunc 한 번만 설정 (함수 재생성 제거)
|
|
370
|
-
* 3. 클로저로 최신 데이터 참조
|
|
371
|
-
*/
|
|
177
|
+
}; // Event Layer 렌더링 (hover 효과 및 선택 상태 표시)
|
|
372
178
|
|
|
373
179
|
|
|
374
180
|
var doRenderEvent = function () {
|
|
375
181
|
var layer = eventLayerRef.current;
|
|
376
|
-
if (!layer) return;
|
|
377
|
-
if (!renderEvent) return; // 🔥 Shape 재사용: 이미 존재하면 재사용, 없으면 생성
|
|
378
|
-
|
|
182
|
+
if (!layer || !renderEvent) return;
|
|
379
183
|
var shape = layer.findOne('.event-render-shape');
|
|
380
184
|
|
|
381
185
|
if (!shape) {
|
|
382
|
-
// 최초 생성 (한 번만 실행됨)
|
|
383
|
-
// sceneFunc도 여기서 한 번만 설정 (클로저로 최신 데이터 참조)
|
|
384
186
|
shape = new Konva__default["default"].Shape({
|
|
385
187
|
name: 'event-render-shape',
|
|
386
188
|
sceneFunc: function (context, shape) {
|
|
387
|
-
var ctx = context;
|
|
388
|
-
|
|
389
|
-
var
|
|
390
|
-
var hovered = hoveredItemRef.current; // topOnHover가 true이면 hover된 항목을 최상단에 렌더링
|
|
189
|
+
var ctx = context;
|
|
190
|
+
var selectedItems = helpers.mapValuesToArray(selectedItemsMapRef.current);
|
|
191
|
+
var hovered = hoveredItemRef.current;
|
|
391
192
|
|
|
392
193
|
if (topOnHover && hovered) {
|
|
393
|
-
// 1. 먼저 일반 항목들 렌더링 (hover된 항목 제외)
|
|
394
194
|
renderEvent({
|
|
395
195
|
ctx: ctx,
|
|
396
196
|
hoveredItem: null,
|
|
@@ -399,13 +199,9 @@ var WoongCanvasMarker = function (props) {
|
|
|
399
199
|
return item.id !== hovered.id;
|
|
400
200
|
}),
|
|
401
201
|
selectedItem: selectedItemRef.current
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
var isHoveredInViewport = enableViewportCulling ? isInViewport(hovered) : true;
|
|
202
|
+
});
|
|
405
203
|
|
|
406
|
-
if (
|
|
407
|
-
// hover된 항목이 선택되어 있다면 hoverSelectedItems에 포함시켜서
|
|
408
|
-
// renderEvent에서 hover 스타일만 적용되도록 함
|
|
204
|
+
if (isInViewport(hovered)) {
|
|
409
205
|
var hoveredIsSelected = selectedItems.some(function (item) {
|
|
410
206
|
return item.id === hovered.id;
|
|
411
207
|
});
|
|
@@ -419,7 +215,6 @@ var WoongCanvasMarker = function (props) {
|
|
|
419
215
|
});
|
|
420
216
|
}
|
|
421
217
|
} else {
|
|
422
|
-
// topOnHover가 false이거나 hover된 항목이 없으면 일반 렌더링
|
|
423
218
|
renderEvent({
|
|
424
219
|
ctx: ctx,
|
|
425
220
|
hoveredItem: hovered,
|
|
@@ -434,137 +229,63 @@ var WoongCanvasMarker = function (props) {
|
|
|
434
229
|
hitStrokeWidth: 0
|
|
435
230
|
});
|
|
436
231
|
layer.add(shape);
|
|
437
|
-
}
|
|
438
|
-
|
|
232
|
+
}
|
|
439
233
|
|
|
440
234
|
layer.batchDraw();
|
|
441
|
-
};
|
|
442
|
-
/**
|
|
443
|
-
* 전체 즉시 렌더링 (IDLE 시 호출)
|
|
444
|
-
*/
|
|
235
|
+
}; // 전체 즉시 렌더링
|
|
445
236
|
|
|
446
237
|
|
|
447
238
|
var renderAllImmediate = function () {
|
|
448
239
|
updateViewport();
|
|
449
240
|
buildSpatialIndex();
|
|
450
241
|
doRenderBase();
|
|
451
|
-
doRenderAnimation();
|
|
452
242
|
doRenderEvent();
|
|
453
|
-
}; //
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
var markerOptions = tslib.__assign({
|
|
475
|
-
position: bounds.nw
|
|
476
|
-
}, options);
|
|
477
|
-
|
|
478
|
-
markerRef.current && controller.updateMarker(markerRef.current, markerOptions); // 4. transform 제거 전에 새 데이터로 즉시 렌더링 (겹침 방지)
|
|
479
|
-
|
|
480
|
-
if (containerRef.current) {
|
|
481
|
-
containerRef.current.style.transform = '';
|
|
482
|
-
containerRef.current.style.visibility = '';
|
|
483
|
-
} // 5. 새 위치에서 렌더링
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
renderAllImmediate();
|
|
487
|
-
};
|
|
488
|
-
/**
|
|
489
|
-
* 줌 시작 시 처리 (일시적으로 숨김)
|
|
490
|
-
*/
|
|
243
|
+
}; // 지도 이벤트 핸들러 생성
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
var _f = hooks.createMapEventHandlers({
|
|
247
|
+
controller: controller,
|
|
248
|
+
containerRef: containerRef,
|
|
249
|
+
markerRef: markerRef,
|
|
250
|
+
options: options,
|
|
251
|
+
prevCenterOffsetRef: prevCenterOffsetRef,
|
|
252
|
+
accumTranslateRef: accumTranslateRef,
|
|
253
|
+
offsetCacheRef: offsetCacheRef,
|
|
254
|
+
boundingBoxCacheRef: boundingBoxCacheRef,
|
|
255
|
+
renderAllImmediate: renderAllImmediate
|
|
256
|
+
}),
|
|
257
|
+
handleIdle = _f.handleIdle,
|
|
258
|
+
handleZoomStart = _f.handleZoomStart,
|
|
259
|
+
handleZoomEnd = _f.handleZoomEnd,
|
|
260
|
+
handleCenterChanged = _f.handleCenterChanged,
|
|
261
|
+
handleDragStartShared = _f.handleDragStart,
|
|
262
|
+
handleDragEndShared = _f.handleDragEnd;
|
|
491
263
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
};
|
|
498
|
-
/**
|
|
499
|
-
* 줌 종료 시 처리 (다시 표시)
|
|
500
|
-
*/
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
var handleZoomEnd = function () {
|
|
504
|
-
if (containerRef.current) {
|
|
505
|
-
containerRef.current.style.visibility = '';
|
|
506
|
-
}
|
|
264
|
+
var handleDragStart = function () {
|
|
265
|
+
handleDragStartShared();
|
|
266
|
+
draggingRef.current = true;
|
|
267
|
+
controller.setMapCursor('grabbing');
|
|
507
268
|
};
|
|
508
|
-
/**
|
|
509
|
-
* 지도 중심 변경 시 처리 (transform으로 이동 추적)
|
|
510
|
-
*/
|
|
511
|
-
|
|
512
269
|
|
|
513
|
-
var
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
if (!prev) {
|
|
519
|
-
prevCenterOffsetRef.current = {
|
|
520
|
-
x: curr.x,
|
|
521
|
-
y: curr.y
|
|
522
|
-
};
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
var dx = prev.x - curr.x;
|
|
527
|
-
var dy = prev.y - curr.y;
|
|
528
|
-
accumTranslateRef.current = {
|
|
529
|
-
x: accumTranslateRef.current.x + dx,
|
|
530
|
-
y: accumTranslateRef.current.y + dy
|
|
531
|
-
};
|
|
532
|
-
prevCenterOffsetRef.current = {
|
|
533
|
-
x: curr.x,
|
|
534
|
-
y: curr.y
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
if (containerRef.current) {
|
|
538
|
-
containerRef.current.style.transform = "translate(".concat(accumTranslateRef.current.x, "px, ").concat(accumTranslateRef.current.y, "px)");
|
|
539
|
-
}
|
|
540
|
-
}; // --------------------------------------------------------------------------
|
|
541
|
-
// Hit Test & 상태 관리
|
|
542
|
-
// --------------------------------------------------------------------------
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* 특정 좌표의 마커 데이터 찾기 (Spatial Index 사용)
|
|
546
|
-
*
|
|
547
|
-
* topOnHover가 true일 때:
|
|
548
|
-
* - 현재 hover된 항목을 최우선으로 체크
|
|
549
|
-
* - 시각적으로 최상단에 있는 항목이 hit test에서도 우선됨
|
|
550
|
-
*
|
|
551
|
-
* @param offset 검사할 좌표
|
|
552
|
-
* @returns 찾은 마커 데이터 또는 null
|
|
553
|
-
*/
|
|
270
|
+
var handleDragEnd = function () {
|
|
271
|
+
handleDragEndShared();
|
|
272
|
+
draggingRef.current = false;
|
|
273
|
+
controller.setMapCursor('grab');
|
|
274
|
+
}; // Hit Test: 특정 좌표의 마커 찾기
|
|
554
275
|
|
|
555
276
|
|
|
556
277
|
var findData = function (offset) {
|
|
557
|
-
// topOnHover
|
|
278
|
+
// topOnHover 옵션이 켜져 있으면 hover된 항목을 최우선으로 확인
|
|
558
279
|
if (topOnHover && hoveredItemRef.current) {
|
|
559
280
|
var hovered = hoveredItemRef.current;
|
|
560
281
|
|
|
561
282
|
if (utils.isPointInMarkerData(offset, hovered, getOrComputeMarkerOffset)) {
|
|
562
|
-
return hovered;
|
|
283
|
+
return hovered;
|
|
563
284
|
}
|
|
564
|
-
} //
|
|
285
|
+
} // 공간 인덱스에서 후보 항목 조회 (O(1) 수준의 빠른 조회)
|
|
565
286
|
|
|
566
287
|
|
|
567
|
-
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); //
|
|
288
|
+
var candidates = spatialIndexRef.current.queryPoint(offset.x, offset.y); // 역순 순회: 나중에 추가된 항목(최상위)이 먼저 선택되도록
|
|
568
289
|
|
|
569
290
|
for (var i = candidates.length - 1; i >= 0; i--) {
|
|
570
291
|
var item = candidates[i];
|
|
@@ -575,18 +296,7 @@ var WoongCanvasMarker = function (props) {
|
|
|
575
296
|
}
|
|
576
297
|
|
|
577
298
|
return null;
|
|
578
|
-
};
|
|
579
|
-
/**
|
|
580
|
-
* Hover 상태 설정 및 레이어 렌더링
|
|
581
|
-
*
|
|
582
|
-
* @param data hover된 마커/폴리곤 데이터 또는 null
|
|
583
|
-
*
|
|
584
|
-
* 최적화: RAF 제거하여 즉시 렌더링 (16ms 지연 제거)
|
|
585
|
-
*
|
|
586
|
-
* 🎯 topOnHover 지원:
|
|
587
|
-
* - renderEvent가 있으면: Event Layer에서만 처리 (성능 최적화)
|
|
588
|
-
* - renderEvent가 없고 topOnHover=true면: Base Layer에서 처리
|
|
589
|
-
*/
|
|
299
|
+
}; // Hover 상태 설정 및 렌더링
|
|
590
300
|
|
|
591
301
|
|
|
592
302
|
var setHovered = function (data) {
|
|
@@ -596,32 +306,18 @@ var WoongCanvasMarker = function (props) {
|
|
|
596
306
|
controller.setMapCursor('grabbing');
|
|
597
307
|
} else {
|
|
598
308
|
controller.setMapCursor(data ? 'pointer' : 'grab');
|
|
599
|
-
}
|
|
600
|
-
|
|
309
|
+
}
|
|
601
310
|
|
|
602
311
|
if (renderEvent) {
|
|
603
|
-
// renderEvent가 있으면 Event Layer에서만 처리 (성능 최적화)
|
|
604
312
|
doRenderEvent();
|
|
605
313
|
} else if (topOnHover) {
|
|
606
|
-
// renderEvent가 없고 topOnHover가 true면 Base Layer에서 처리
|
|
607
314
|
doRenderBase();
|
|
608
315
|
}
|
|
609
|
-
};
|
|
610
|
-
/**
|
|
611
|
-
* 클릭 처리 (단일/다중 선택)
|
|
612
|
-
*
|
|
613
|
-
* @param data 클릭된 마커/폴리곤 데이터
|
|
614
|
-
*
|
|
615
|
-
* 🔥 최적화: 단일 Shape 렌더링으로 Base Layer 재렌더링 속도 향상
|
|
616
|
-
* - sceneFunc에서 selectedIds를 체크하여 선택된 마커만 스킵
|
|
617
|
-
* - 객체 생성 오버헤드 제거로 1000개 이상도 부드럽게 처리
|
|
618
|
-
*/
|
|
316
|
+
}; // 클릭 처리: 선택 상태 업데이트
|
|
619
317
|
|
|
620
318
|
|
|
621
319
|
var handleLocalClick = function (data) {
|
|
622
|
-
// 1. 선택 상태 업데이트
|
|
623
320
|
if (enableMultiSelect) {
|
|
624
|
-
// 다중 선택: Set과 Map 동시 업데이트
|
|
625
321
|
var newSelected = new Set(selectedIdsRef.current);
|
|
626
322
|
|
|
627
323
|
if (newSelected.has(data.id)) {
|
|
@@ -634,7 +330,6 @@ var WoongCanvasMarker = function (props) {
|
|
|
634
330
|
|
|
635
331
|
selectedIdsRef.current = newSelected;
|
|
636
332
|
} else {
|
|
637
|
-
// 단일 선택: 토글
|
|
638
333
|
var newSelected = new Set();
|
|
639
334
|
|
|
640
335
|
if (!selectedIdsRef.current.has(data.id)) {
|
|
@@ -648,153 +343,80 @@ var WoongCanvasMarker = function (props) {
|
|
|
648
343
|
selectedIdsRef.current = newSelected;
|
|
649
344
|
}
|
|
650
345
|
|
|
651
|
-
|
|
652
|
-
// 2. Base Layer 재렌더링 (단일 Shape로 최적화되어 빠름)
|
|
653
|
-
doRenderBase(); // 3. Animation Layer 렌더링 (선택된 마커 애니메이션)
|
|
654
|
-
|
|
655
|
-
doRenderAnimation();
|
|
656
|
-
} // 4. Event Layer 렌더링 (hover 처리)
|
|
657
|
-
|
|
658
|
-
|
|
346
|
+
doRenderBase();
|
|
659
347
|
doRenderEvent();
|
|
660
|
-
}; //
|
|
661
|
-
// 이벤트 핸들러: UI 이벤트
|
|
662
|
-
// --------------------------------------------------------------------------
|
|
663
|
-
|
|
664
|
-
/**
|
|
665
|
-
* 클릭 이벤트 처리
|
|
666
|
-
*/
|
|
348
|
+
}; // 클릭 이벤트 핸들러
|
|
667
349
|
|
|
668
350
|
|
|
669
351
|
var handleClick = function (event) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
if (
|
|
673
|
-
|
|
674
|
-
if (
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
var data_1 = findData(clickedOffset);
|
|
679
|
-
|
|
680
|
-
if (data_1) {
|
|
681
|
-
handleLocalClick(data_1);
|
|
682
|
-
|
|
683
|
-
if (onClick) {
|
|
684
|
-
onClick(data_1, selectedIdsRef.current);
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
} catch (error) {
|
|
688
|
-
console.error('[WoongCanvasMarker] handleClick error:', error);
|
|
689
|
-
}
|
|
690
|
-
};
|
|
691
|
-
/**
|
|
692
|
-
* 마우스 이동 이벤트 처리 (hover 감지)
|
|
693
|
-
*/
|
|
352
|
+
if (disableInteractionRef.current) return;
|
|
353
|
+
var clickedOffset = helpers.validateEvent(event, context$1, controller);
|
|
354
|
+
if (!clickedOffset) return;
|
|
355
|
+
var data = findData(clickedOffset);
|
|
356
|
+
if (!data) return;
|
|
357
|
+
handleLocalClick(data);
|
|
358
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(data, selectedIdsRef.current);
|
|
359
|
+
}; // 마우스 이동 이벤트 핸들러 (hover 감지)
|
|
694
360
|
|
|
695
361
|
|
|
696
362
|
var handleMouseMove = function (event) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
if (prevHovered !== hoveredItem) {
|
|
709
|
-
setHovered(hoveredItem);
|
|
710
|
-
if (prevHovered && onMouseOut) onMouseOut(prevHovered);
|
|
711
|
-
if (hoveredItem && onMouseOver) onMouseOver(hoveredItem);
|
|
712
|
-
}
|
|
713
|
-
} catch (error) {
|
|
714
|
-
console.error('[WoongCanvasMarker] handleMouseMove error:', error);
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
/**
|
|
718
|
-
* 드래그 시작 처리 (커서를 grabbing으로 변경)
|
|
719
|
-
*/
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
var handleDragStart = function () {
|
|
723
|
-
draggingRef.current = true;
|
|
724
|
-
controller.setMapCursor('grabbing');
|
|
725
|
-
};
|
|
726
|
-
/**
|
|
727
|
-
* 드래그 종료 처리 (커서를 기본으로 복원)
|
|
728
|
-
*/
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
var handleDragEnd = function () {
|
|
732
|
-
draggingRef.current = false;
|
|
733
|
-
controller.setMapCursor('grab');
|
|
734
|
-
};
|
|
735
|
-
/**
|
|
736
|
-
* 마우스가 canvas를 벗어날 때 hover cleanup
|
|
737
|
-
*/
|
|
363
|
+
if (disableInteractionRef.current) return;
|
|
364
|
+
var mouseOffset = helpers.validateEvent(event, context$1, controller);
|
|
365
|
+
if (!mouseOffset) return;
|
|
366
|
+
var hoveredItem = findData(mouseOffset);
|
|
367
|
+
var prevHovered = hoveredItemRef.current;
|
|
368
|
+
if (prevHovered === hoveredItem) return;
|
|
369
|
+
setHovered(hoveredItem);
|
|
370
|
+
if (prevHovered) onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
371
|
+
if (hoveredItem) onMouseOver === null || onMouseOver === void 0 ? void 0 : onMouseOver(hoveredItem);
|
|
372
|
+
}; // 마우스가 맵 영역을 벗어날 때 hover 상태 초기화
|
|
738
373
|
|
|
739
374
|
|
|
740
375
|
var handleMouseLeave = function () {
|
|
741
|
-
if (disableInteractionRef.current) return;
|
|
742
|
-
|
|
376
|
+
if (disableInteractionRef.current) return;
|
|
743
377
|
var prevHovered = hoveredItemRef.current;
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (onMouseOut) {
|
|
751
|
-
onMouseOut(prevHovered);
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
}; // --------------------------------------------------------------------------
|
|
755
|
-
// Lifecycle: DOM 초기화
|
|
756
|
-
// --------------------------------------------------------------------------
|
|
378
|
+
if (!prevHovered) return;
|
|
379
|
+
hoveredItemRef.current = null;
|
|
380
|
+
controller.setMapCursor('grab');
|
|
381
|
+
doRenderEvent();
|
|
382
|
+
onMouseOut === null || onMouseOut === void 0 ? void 0 : onMouseOut(prevHovered);
|
|
383
|
+
}; // DOM 초기화
|
|
757
384
|
|
|
758
385
|
|
|
759
386
|
React.useEffect(function () {
|
|
760
387
|
divElement.style.width = 'fit-content';
|
|
761
388
|
return function () {
|
|
762
|
-
if (markerRef.current)
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
389
|
+
if (!markerRef.current) return;
|
|
390
|
+
controller.clearDrawable(markerRef.current);
|
|
391
|
+
markerRef.current = undefined;
|
|
766
392
|
};
|
|
767
|
-
}, []); //
|
|
768
|
-
// Lifecycle: 마커 생성/업데이트
|
|
769
|
-
// --------------------------------------------------------------------------
|
|
393
|
+
}, []); // 마커 생성/업데이트
|
|
770
394
|
|
|
771
395
|
React.useEffect(function () {
|
|
772
|
-
if (options)
|
|
773
|
-
|
|
396
|
+
if (!options) return;
|
|
397
|
+
var bounds = controller.getCurrBounds();
|
|
774
398
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
399
|
+
var markerOptions = tslib.__assign({
|
|
400
|
+
position: bounds.nw
|
|
401
|
+
}, options);
|
|
778
402
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
markerRef.current.element = divElement;
|
|
784
|
-
controller.createMarker(markerRef.current);
|
|
403
|
+
if (markerRef.current) {
|
|
404
|
+
controller.updateMarker(markerRef.current, markerOptions);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
785
407
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
408
|
+
markerRef.current = new MapDrawables.Marker(markerOptions);
|
|
409
|
+
markerRef.current.element = divElement;
|
|
410
|
+
controller.createMarker(markerRef.current);
|
|
789
411
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
412
|
+
if (divElement.parentElement) {
|
|
413
|
+
divElement.parentElement.style.pointerEvents = 'none';
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (options.zIndex !== undefined) {
|
|
417
|
+
controller.setMarkerZIndex(markerRef.current, options.zIndex);
|
|
794
418
|
}
|
|
795
|
-
}, [options]); //
|
|
796
|
-
// Lifecycle: Konva 초기화 및 이벤트 리스너 등록
|
|
797
|
-
// --------------------------------------------------------------------------
|
|
419
|
+
}, [options]); // Konva 초기화 및 이벤트 리스너 등록
|
|
798
420
|
|
|
799
421
|
React.useEffect(function () {
|
|
800
422
|
var mapDiv = controller.mapDivElement;
|
|
@@ -803,34 +425,21 @@ var WoongCanvasMarker = function (props) {
|
|
|
803
425
|
width: mapDiv.offsetWidth,
|
|
804
426
|
height: mapDiv.offsetHeight
|
|
805
427
|
});
|
|
806
|
-
stageRef.current = stage;
|
|
807
|
-
|
|
428
|
+
stageRef.current = stage;
|
|
808
429
|
var baseLayer = new Konva__default["default"].Layer({
|
|
809
|
-
listening: false // 이벤트 리스닝 비활성화로 성능 향상
|
|
810
|
-
|
|
811
|
-
});
|
|
812
|
-
var animationLayer = new Konva__default["default"].Layer({
|
|
813
430
|
listening: false
|
|
814
431
|
});
|
|
815
432
|
var eventLayer = new Konva__default["default"].Layer({
|
|
816
433
|
listening: false
|
|
817
434
|
});
|
|
818
435
|
baseLayerRef.current = baseLayer;
|
|
819
|
-
animationLayerRef.current = animationLayer;
|
|
820
436
|
eventLayerRef.current = eventLayer;
|
|
821
437
|
stage.add(baseLayer);
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
stage.add(animationLayer);
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
stage.add(eventLayer); // 초기 뷰포트 설정
|
|
828
|
-
|
|
829
|
-
updateViewport(); // ResizeObserver (맵 크기 변경 감지)
|
|
438
|
+
stage.add(eventLayer);
|
|
439
|
+
updateViewport(); // ResizeObserver: 맵 크기 변경 감지 (RAF로 debounce)
|
|
830
440
|
|
|
831
441
|
var resizeRafId = null;
|
|
832
442
|
var resizeObserver = new ResizeObserver(function () {
|
|
833
|
-
// RAF로 다음 프레임에 한 번만 실행 (debounce 효과)
|
|
834
443
|
if (resizeRafId !== null) {
|
|
835
444
|
cancelAnimationFrame(resizeRafId);
|
|
836
445
|
}
|
|
@@ -853,10 +462,9 @@ var WoongCanvasMarker = function (props) {
|
|
|
853
462
|
controller.addEventListener('CLICK', handleClick);
|
|
854
463
|
controller.addEventListener('MOUSEMOVE', handleMouseMove);
|
|
855
464
|
controller.addEventListener('DRAGSTART', handleDragStart);
|
|
856
|
-
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
857
|
-
|
|
465
|
+
controller.addEventListener('DRAGEND', handleDragEnd);
|
|
858
466
|
mapDiv.addEventListener('mouseleave', handleMouseLeave);
|
|
859
|
-
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
467
|
+
renderAllImmediate(); // Context 사용 시 컴포넌트 등록
|
|
860
468
|
|
|
861
469
|
var componentInstance = null;
|
|
862
470
|
|
|
@@ -877,22 +485,17 @@ var WoongCanvasMarker = function (props) {
|
|
|
877
485
|
},
|
|
878
486
|
isInteractionDisabled: function () {
|
|
879
487
|
return disableInteractionRef.current;
|
|
880
|
-
}
|
|
881
|
-
|
|
488
|
+
}
|
|
882
489
|
};
|
|
883
490
|
context$1.registerComponent(componentInstance);
|
|
884
|
-
}
|
|
885
|
-
|
|
491
|
+
}
|
|
886
492
|
|
|
887
493
|
return function () {
|
|
888
|
-
// RAF 정리
|
|
889
494
|
if (resizeRafId !== null) {
|
|
890
495
|
cancelAnimationFrame(resizeRafId);
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
resizeObserver.disconnect(); // 이벤트 리스너 정리
|
|
496
|
+
}
|
|
895
497
|
|
|
498
|
+
resizeObserver.disconnect();
|
|
896
499
|
controller.removeEventListener('IDLE', handleIdle);
|
|
897
500
|
controller.removeEventListener('ZOOMSTART', handleZoomStart);
|
|
898
501
|
controller.removeEventListener('ZOOM_CHANGED', handleZoomEnd);
|
|
@@ -901,69 +504,41 @@ var WoongCanvasMarker = function (props) {
|
|
|
901
504
|
controller.removeEventListener('MOUSEMOVE', handleMouseMove);
|
|
902
505
|
controller.removeEventListener('DRAGSTART', handleDragStart);
|
|
903
506
|
controller.removeEventListener('DRAGEND', handleDragEnd);
|
|
904
|
-
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
507
|
+
mapDiv.removeEventListener('mouseleave', handleMouseLeave);
|
|
905
508
|
|
|
906
509
|
if (context$1 && componentInstance) {
|
|
907
510
|
context$1.unregisterComponent(componentInstance);
|
|
908
|
-
}
|
|
909
|
-
|
|
511
|
+
}
|
|
910
512
|
|
|
911
513
|
baseLayer.destroyChildren();
|
|
912
|
-
animationLayer.destroyChildren();
|
|
913
514
|
eventLayer.destroyChildren();
|
|
914
|
-
stage.destroy();
|
|
915
|
-
|
|
515
|
+
stage.destroy();
|
|
916
516
|
offsetCacheRef.current.clear();
|
|
917
517
|
boundingBoxCacheRef.current.clear();
|
|
918
518
|
spatialIndexRef.current.clear();
|
|
919
519
|
};
|
|
920
|
-
}, []); //
|
|
921
|
-
// --------------------------------------------------------------------------
|
|
922
|
-
// Lifecycle: disableInteraction 동기화
|
|
923
|
-
// --------------------------------------------------------------------------
|
|
520
|
+
}, []); // disableInteraction 동기화
|
|
924
521
|
|
|
925
522
|
React.useEffect(function () {
|
|
926
523
|
disableInteractionRef.current = disableInteraction;
|
|
927
|
-
}, [disableInteraction]); //
|
|
928
|
-
// Lifecycle: 외부 selectedItems 동기화
|
|
929
|
-
// --------------------------------------------------------------------------
|
|
524
|
+
}, [disableInteraction]); // 외부 selectedItems 동기화
|
|
930
525
|
|
|
931
526
|
React.useEffect(function () {
|
|
932
|
-
if (!stageRef.current) return;
|
|
933
|
-
|
|
934
|
-
if (externalSelectedItems === undefined) return; // 외부에서 전달된 selectedItems로 동기화
|
|
935
|
-
|
|
936
|
-
var newSelectedIds = new Set();
|
|
937
|
-
var newSelectedItemsMap = new Map();
|
|
938
|
-
externalSelectedItems.forEach(function (item) {
|
|
939
|
-
newSelectedIds.add(item.id);
|
|
940
|
-
newSelectedItemsMap.set(item.id, item);
|
|
941
|
-
});
|
|
942
|
-
selectedIdsRef.current = newSelectedIds;
|
|
943
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 렌더링
|
|
944
|
-
|
|
527
|
+
if (!stageRef.current) return;
|
|
528
|
+
hooks.syncExternalSelectedItems(externalSelectedItems, selectedIdsRef, selectedItemsMapRef);
|
|
945
529
|
doRenderBase();
|
|
946
|
-
doRenderAnimation();
|
|
947
530
|
doRenderEvent();
|
|
948
|
-
}, [externalSelectedItems]); //
|
|
949
|
-
// --------------------------------------------------------------------------
|
|
950
|
-
// Lifecycle: 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
951
|
-
// --------------------------------------------------------------------------
|
|
531
|
+
}, [externalSelectedItems]); // 외부 selectedItem 변경 시 Event Layer 리렌더링
|
|
952
532
|
|
|
953
533
|
React.useEffect(function () {
|
|
954
|
-
if (!stageRef.current) return;
|
|
955
|
-
|
|
956
|
-
selectedItemRef.current = externalSelectedItem; // selectedItem이 변경되면 Event Layer만 다시 그림
|
|
957
|
-
|
|
534
|
+
if (!stageRef.current) return;
|
|
535
|
+
selectedItemRef.current = externalSelectedItem;
|
|
958
536
|
doRenderEvent();
|
|
959
|
-
}, [externalSelectedItem]); //
|
|
960
|
-
// Lifecycle: 데이터 변경 시 렌더링
|
|
961
|
-
// --------------------------------------------------------------------------
|
|
537
|
+
}, [externalSelectedItem]); // 데이터 변경 시 렌더링 (캐시 정리 및 선택 상태 동기화)
|
|
962
538
|
|
|
963
539
|
React.useEffect(function () {
|
|
964
|
-
if (!stageRef.current) return;
|
|
965
|
-
|
|
966
|
-
dataRef.current = data; // 데이터 변경 시 즉시 transform 제거 및 캐시 정리 (겹침 방지)
|
|
540
|
+
if (!stageRef.current) return;
|
|
541
|
+
dataRef.current = data;
|
|
967
542
|
|
|
968
543
|
if (containerRef.current) {
|
|
969
544
|
containerRef.current.style.transform = '';
|
|
@@ -973,46 +548,10 @@ var WoongCanvasMarker = function (props) {
|
|
|
973
548
|
accumTranslateRef.current = {
|
|
974
549
|
x: 0,
|
|
975
550
|
y: 0
|
|
976
|
-
};
|
|
977
|
-
|
|
551
|
+
};
|
|
978
552
|
offsetCacheRef.current.clear();
|
|
979
553
|
boundingBoxCacheRef.current.clear();
|
|
980
|
-
|
|
981
|
-
* 선택 상태 동기화 (최적화 버전)
|
|
982
|
-
*
|
|
983
|
-
* data가 변경되면 selectedItemsMapRef도 업데이트 필요
|
|
984
|
-
* (참조가 바뀌므로 기존 Map의 데이터는 stale 상태)
|
|
985
|
-
*
|
|
986
|
-
* 🔥 중요: 화면 밖 데이터도 선택 상태 유지!
|
|
987
|
-
* - 현재 data에 있으면 최신 데이터로 업데이트
|
|
988
|
-
* - 없으면 기존 selectedItemsMapRef의 데이터 유지
|
|
989
|
-
*
|
|
990
|
-
* 최적화: data를 Map으로 먼저 변환하여 find() 순회 제거
|
|
991
|
-
* - O(전체 데이터 수 + 선택된 개수) - 매우 효율적
|
|
992
|
-
*/
|
|
993
|
-
|
|
994
|
-
var dataMap = new Map(data.map(function (m) {
|
|
995
|
-
return [m.id, m];
|
|
996
|
-
}));
|
|
997
|
-
var newSelectedItemsMap = new Map();
|
|
998
|
-
selectedIdsRef.current.forEach(function (id) {
|
|
999
|
-
// 현재 data에 있으면 최신 데이터 사용
|
|
1000
|
-
var currentItem = dataMap.get(id);
|
|
1001
|
-
|
|
1002
|
-
if (currentItem) {
|
|
1003
|
-
newSelectedItemsMap.set(id, currentItem);
|
|
1004
|
-
} else {
|
|
1005
|
-
// 화면 밖이면 기존 데이터 유지
|
|
1006
|
-
var prevItem = selectedItemsMapRef.current.get(id);
|
|
1007
|
-
|
|
1008
|
-
if (prevItem) {
|
|
1009
|
-
newSelectedItemsMap.set(id, prevItem);
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
}); // selectedIdsRef는 그대로 유지 (화면 밖 항목도 선택 상태 유지)
|
|
1013
|
-
|
|
1014
|
-
selectedItemsMapRef.current = newSelectedItemsMap; // 즉시 렌더링
|
|
1015
|
-
|
|
554
|
+
selectedItemsMapRef.current = hooks.syncSelectedItems(data, selectedIdsRef.current, selectedItemsMapRef.current);
|
|
1016
555
|
renderAllImmediate();
|
|
1017
556
|
}, [data]);
|
|
1018
557
|
return reactDom.createPortal(React__default["default"].createElement("div", {
|