/
DrawingManager.tsx
323 lines (301 loc) · 9.32 KB
/
DrawingManager.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
import React, {
ReactNode,
Ref,
useImperativeHandle,
useLayoutEffect,
useMemo,
} from "react"
import { useMap } from "../hooks/useMap"
export const DrawingManagerContext =
React.createContext<kakao.maps.drawing.DrawingManager>(
undefined as unknown as kakao.maps.drawing.DrawingManager,
)
export type DrawingManagerProps<
T extends kakao.maps.drawing.OverlayType = kakao.maps.drawing.OverlayType,
> = Partial<kakao.maps.drawing.OverlayOptions> &
Pick<kakao.maps.drawing.OverlayOptions, `${T}Options`> & {
/**
* 마우스 오버 시 가이드 툴팁 표시 여부. ‘draw’, ‘drag’, ‘edit’ 3가지를 지정할 수 있다 (기본값: 모두 표시 안함)
* 예를들어 [‘draw’]로 설정하면 객체를 그릴때 가이드 툴팁이 표시된다
*/
guideTooltip?: Array<"draw" | "drag" | "edit">
/**
* 사용할 그리기 요소 지정한다 (기본값: 모든 그리기 요소)
*/
drawingMode?: Array<T | `${T}`>
/**
* 그리기 요소를 선택하면 발생한다.
*/
onSelect?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
event: kakao.maps.drawing.MouseEvent,
) => void
/**
* 그리기를 시작하면 발생한다.
*/
onDrawstart?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
event: kakao.maps.drawing.MouseEvent,
) => void
/**
* 그리기 시작 후, 마우스를 이동하면 발생한다.
*/
onDraw?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
event: kakao.maps.drawing.MouseEvent,
) => void
/**
* 그리기를 시작하면 발생한다.
*/
onDrawend?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
event: kakao.maps.drawing.MouseEvent,
) => void
/**
* 다음 단계 그리기를 하면 발생한다. (Polyline, Polygon, Arrow 한정)
*/
onDrawnext?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
event: kakao.maps.drawing.MouseEvent,
) => void
/**
* 그리기를 취소하면 발생한다.
*/
onCancle?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
event: kakao.maps.drawing.MouseEvent,
) => void
/**
* 그리기 요소를 삭제하면 발생한다.
*/
onRemove?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
event: kakao.maps.drawing.MouseEvent,
) => void
/**
* 그리기 요소들의 상태가 변경되면 발생한다.
* 각 요소의 생성/수정/이동/삭제 동작과 undo 또는 redo 함수 호출이 이에 해당한다.
*/
onStateChanged?: (
drawingManager: kakao.maps.drawing.DrawingManager<T>,
) => void
/**
* 객체 생성후 해당 이벤트가 발생합니다.
*/
onCreate?: (drawingManager: kakao.maps.drawing.DrawingManager<T>) => void
/**
* Toolbox에 대해서 추가할 때 사용합니다.
*/
children?: ReactNode
}
function useDrawingManagerEvent<T extends kakao.maps.drawing.OverlayType>(
target: kakao.maps.drawing.DrawingManager<T> | undefined,
type:
| "drawstart"
| "draw"
| "drawend"
| "drawnext"
| "select"
| "cancel"
| "remove"
| "state_changed",
callback: // eslint-disable-next-line @typescript-eslint/no-explicit-any
| ((target: kakao.maps.drawing.DrawingManager<T>, ...args: any[]) => void)
| undefined,
) {
useLayoutEffect(() => {
if (!target || !callback) return
const wrapCallback = (...args: unknown[]) => {
if (AbortSignal === undefined) return callback(target)
else return callback(target, ...args)
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
target.addListener(type, wrapCallback)
}, [callback, target, type])
}
/**
* 그리기 관리자 객체를 생성하는 컴포넌트 입니다.
*
* 초기 생성자에 필요한 Props는 최초 렌더링에만 반영을 하고 이후에는 값이 변경되더라도 적용되지 않습니다.
*
* > `on~` 시리즈를 제외한 props는 초기 렌더링 이후 작동 안함
*
* DrawingManager는 이전 Map, Marker, CustomOverlay와 달리 Props를 통해 제어를 하는 것이 아닌 직접 DrawingManager 객체를 이용하여 제어를 해야 합니다.
*
* 이를 위해 ref 객체를 통해 `DrawingManager` 객체를 접근 할 수 있으며, 이를 활용하여 여러 이벤트 처리 및 제어가 가능합니다.
*
* TypeScript 사용자를 위한 `Generic`이 적용되어 있으므로, `ref` 객체에 대한 typing 및 `drawingMode`에 대해 확실하게 정의해야 합니다.
*
* ```tsx
* const Main = () => {
* const managerRef = useRef<kakao.maps.drawing.DrawingManager<
* kakao.maps.drawing.OverlayType.POLYLINE
* >>(null);
*
* function selectOverlay(type: string) {
* const manager = managerRef.current;
* manager.cancel();
* manager.select(kakao.maps.drawing.OverlayType.POLYLINE);
* }
*
* return (
* <>
* <Map
* center={{
* // 지도의 중심좌표
* lat: 33.450701,
* lng: 126.570667,
* }}
* style={{
* width: "100%",
* height: "450px",
* }}
* level={3} // 지도의 확대 레벨
* >
* <DrawingManager
* ref={managerRef}
* drawingMode={[
* kakao.maps.drawing.OverlayType.POLYLINE,
* ]}
* guideTooltip={['draw', 'drag', 'edit']}
* polylineOptions={{
* draggable: true,
* removable: true,
* editable: true
* }}
* />
* </Map>
* <button onClick={(e) => {
* selectOverlay('POLYLINE')
* }}>선</button>
* </>
* )
* }
* ```
*
* > JavaScript 코드 버전
*
* ```jsx live
* function() {
* const Main = () => {
* const managerRef = useRef(null);
*
* function selectOverlay() {
* const manager = managerRef.current;
* manager.cancel();
* manager.select(kakao.maps.drawing.OverlayType.POLYLINE);
* }
*
* return (
* <>
* <Map
* center={{
* // 지도의 중심좌표
* lat: 33.450701,
* lng: 126.570667,
* }}
* style={{
* width: "100%",
* height: "450px",
* }}
* level={3} // 지도의 확대 레벨
* >
* <DrawingManager
* ref={managerRef}
* drawingMode={[
* kakao.maps.drawing.OverlayType.POLYLINE,
* ]}
* guideTooltip={['draw', 'drag', 'edit']}
* polylineOptions={{
* draggable: true,
* removable: true,
* editable: true
* }}
* />
* </Map>
* <button onClick={selectOverlay}>선</button>
* </>
* )
* }
* return (<Main />)
* }
* ```
*/
export const DrawingManager = React.forwardRef(function DrawingManager<
T extends kakao.maps.drawing.OverlayType,
>(
{
arrowOptions,
circleOptions,
ellipseOptions,
markerOptions,
polygonOptions,
polylineOptions,
rectangleOptions,
drawingMode,
guideTooltip,
onSelect,
onDrawstart,
onDraw,
onDrawend,
onDrawnext,
onCancle,
onRemove,
onStateChanged,
onCreate,
children,
}: DrawingManagerProps<T>,
ref: Ref<kakao.maps.drawing.DrawingManager<T>>,
) {
const map = useMap("Toolbox")
const drawingManager = useMemo(
() => {
if (!window.kakao.maps.drawing) {
console.warn(
"drawing 라이브러리를 별도 로드 해야 사용 가능합니다. `https://apis.map.kakao.com/web/guide/#loadlibrary`",
)
return
}
return new kakao.maps.drawing.DrawingManager<T>({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
map,
drawingMode,
guideTooltip,
arrowOptions,
circleOptions,
ellipseOptions,
markerOptions,
polygonOptions,
polylineOptions,
rectangleOptions,
})
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
)
useImperativeHandle(ref, () => drawingManager!, [drawingManager])
useLayoutEffect(() => {
drawingManager && onCreate && onCreate(drawingManager)
}, [drawingManager, onCreate])
useDrawingManagerEvent(drawingManager, "select", onSelect)
useDrawingManagerEvent(drawingManager, "drawstart", onDrawstart)
useDrawingManagerEvent(drawingManager, "draw", onDraw)
useDrawingManagerEvent(drawingManager, "drawend", onDrawend)
useDrawingManagerEvent(drawingManager, "drawnext", onDrawnext)
useDrawingManagerEvent(drawingManager, "cancel", onCancle)
useDrawingManagerEvent(drawingManager, "remove", onRemove)
useDrawingManagerEvent(drawingManager, "state_changed", onStateChanged)
if (!drawingManager) return null
return (
<DrawingManagerContext.Provider value={drawingManager}>
{children}
</DrawingManagerContext.Provider>
)
}) as unknown as <
T extends kakao.maps.drawing.OverlayType = kakao.maps.drawing.OverlayType,
>(
_props: DrawingManagerProps<T> &
React.RefAttributes<kakao.maps.drawing.DrawingManager<T>>,
) => null