1
- import { Card , NonIdealState , Spinner } from '@blueprintjs/core'
1
+ import { Button , Card , NonIdealState , Spinner } from '@blueprintjs/core'
2
2
3
+ import clsx from 'clsx'
4
+ import { clamp } from 'lodash-es'
3
5
import { useCallback , useEffect , useState } from 'react'
4
6
import { createPortal } from 'react-dom'
5
7
import { Rnd , RndResizeCallback } from 'react-rnd'
8
+ import { useWindowSize } from 'react-use'
6
9
7
10
import { sendMessage , useMessage } from '../../../utils/messenger'
8
11
import { useLazyStorage } from '../../../utils/useLazyStorage'
@@ -17,6 +20,7 @@ import {
17
20
} from './connection'
18
21
19
22
interface FloatingMapConfig {
23
+ show : boolean
20
24
x : number
21
25
y : number
22
26
width : number
@@ -26,6 +30,8 @@ interface FloatingMapConfig {
26
30
const UID = 'floating-map'
27
31
const STORAGE_KEY = `copilot-${ UID } `
28
32
33
+ const HEADER_CLASS = 'floating-map-header'
34
+
29
35
const HEADER_HEIGHT = 16
30
36
const ASPECT_RATIO = 16 / 9
31
37
const MIN_HEIGHT = 150 + HEADER_HEIGHT
@@ -43,6 +49,7 @@ export function FloatingMap() {
43
49
const [ config , setConfig ] = useLazyStorage < FloatingMapConfig > (
44
50
STORAGE_KEY ,
45
51
{
52
+ show : true ,
46
53
x : 0 ,
47
54
y : window . innerHeight - DEFAULT_HEIGHT ,
48
55
width : DEFAULT_WIDTH ,
@@ -52,6 +59,16 @@ export function FloatingMap() {
52
59
( savedValue , defaultValue ) => ( { ...defaultValue , ...savedValue } ) ,
53
60
)
54
61
62
+ const { width : windowWidth , height : windowHeight } = useWindowSize ( )
63
+
64
+ useEffect ( ( ) => {
65
+ setConfig ( ( cfg ) => ( {
66
+ ...cfg ,
67
+ x : clamp ( cfg . x , 0 , windowWidth - cfg . width ) ,
68
+ y : clamp ( cfg . y , 0 , windowHeight - cfg . height ) ,
69
+ } ) )
70
+ } , [ windowWidth , windowHeight ] )
71
+
55
72
const [ iframeWindow , setIframeWindow ] = useState < Window | null | undefined > ( )
56
73
const [ mapStatus , setMapStatus ] = useState ( MapStatus . Loading )
57
74
@@ -137,55 +154,98 @@ export function FloatingMap() {
137
154
138
155
return createPortal (
139
156
< div className = "fixed z-30 inset-0 pointer-events-none" >
140
- < Rnd
141
- className = "pointer-events-auto"
142
- dragHandleClassName = "drag-handle"
143
- bounds = "window"
144
- minWidth = { MIN_WIDTH }
145
- minHeight = { MIN_HEIGHT }
146
- lockAspectRatio = { ASPECT_RATIO }
147
- lockAspectRatioExtraHeight = { HEADER_HEIGHT }
148
- default = { config }
149
- onDragStart = { onDragStartHandler }
150
- onDragStop = { onDragStopHandler }
151
- onResizeStart = { onResizeStartHandler }
152
- onResizeStop = { onResizeStopHandler }
153
- >
154
- < Card
155
- className = "h-full !p-0 flex flex-col overflow-hidden"
156
- elevation = { 3 }
157
+ { config . show ? (
158
+ < Rnd
159
+ className = "pointer-events-auto"
160
+ dragHandleClassName = { HEADER_CLASS }
161
+ bounds = "window"
162
+ minWidth = { MIN_WIDTH }
163
+ minHeight = { MIN_HEIGHT }
164
+ lockAspectRatio = { ASPECT_RATIO }
165
+ lockAspectRatioExtraHeight = { HEADER_HEIGHT }
166
+ default = { config }
167
+ position = { config }
168
+ onDragStart = { onDragStartHandler }
169
+ onDragStop = { onDragStopHandler }
170
+ onResizeStart = { onResizeStartHandler }
171
+ onResizeStop = { onResizeStopHandler }
157
172
>
158
- < div
159
- className = "drag-handle cursor-move bg-gray-200"
160
- style = { { height : HEADER_HEIGHT } }
161
- />
162
- { level ? (
163
- < div className = "relative flex-grow" >
164
- < iframe
165
- title = { UID }
166
- className = "w-full h-full"
167
- src = { getMapUrl ( level ) }
168
- onLoad = { ( e ) => {
169
- setIframeWindow ( ( e . target as HTMLIFrameElement ) . contentWindow )
170
- } }
171
- />
172
- { mapStatus === MapStatus . Loading && (
173
- < NonIdealState
174
- className = "absolute inset-0 bg-gray-900/50 [&_*]:!text-white"
175
- icon = {
176
- < Spinner className = "[&_.bp4-spinner-head]:stroke-current" />
177
- }
178
- description = { iframeWindow ? undefined : '等待地图连接...' }
173
+ < Card
174
+ className = "h-full !p-0 flex flex-col overflow-hidden"
175
+ elevation = { 3 }
176
+ >
177
+ < FloatingMapHeader config = { config } setConfig = { setConfig } />
178
+ { level ? (
179
+ < div className = "relative flex-grow" >
180
+ < iframe
181
+ title = { UID }
182
+ className = "w-full h-full"
183
+ src = { getMapUrl ( level ) }
184
+ onLoad = { ( e ) => {
185
+ setIframeWindow (
186
+ ( e . target as HTMLIFrameElement ) . contentWindow ,
187
+ )
188
+ } }
179
189
/>
180
- ) }
181
- </ div >
182
- ) : (
183
- < NonIdealState icon = "area-of-interest" title = "未选择关卡" />
184
- ) }
190
+ { mapStatus === MapStatus . Loading && (
191
+ < NonIdealState
192
+ className = "absolute inset-0 bg-gray-900/50 [&_*]:!text-white"
193
+ icon = {
194
+ < Spinner className = "[&_.bp4-spinner-head]:stroke-current" />
195
+ }
196
+ description = { iframeWindow ? undefined : '等待地图连接...' }
197
+ />
198
+ ) }
199
+ </ div >
200
+ ) : (
201
+ < NonIdealState icon = "area-of-interest" title = "未选择关卡" />
202
+ ) }
203
+ </ Card >
204
+ </ Rnd >
205
+ ) : (
206
+ < Card
207
+ className = "absolute !p-0 overflow-hidden pointer-events-auto"
208
+ elevation = { 2 }
209
+ style = { { left : 0 , bottom : 0 } }
210
+ >
211
+ < FloatingMapHeader config = { config } setConfig = { setConfig } />
185
212
</ Card >
186
- </ Rnd >
213
+ ) }
187
214
</ div > ,
188
215
189
216
document . body ,
190
217
)
191
218
}
219
+
220
+ function FloatingMapHeader ( {
221
+ className,
222
+ config,
223
+ setConfig,
224
+ } : {
225
+ className ?: string
226
+ config : FloatingMapConfig
227
+ setConfig : ( config : FloatingMapConfig ) => void
228
+ } ) {
229
+ return (
230
+ < div
231
+ className = { clsx (
232
+ className ,
233
+ HEADER_CLASS ,
234
+ 'flex items-center text-xs bg-gray-200' ,
235
+ config . show ? 'cursor-move' : 'cursor-default' ,
236
+ ) }
237
+ style = { { height : HEADER_HEIGHT } }
238
+ >
239
+ < Button
240
+ minimal
241
+ small = { ! config . show }
242
+ className = "min-h-0 !py-0"
243
+ title = { config . show ? '隐藏地图' : '显示地图' }
244
+ icon = { config . show ? 'caret-down' : 'caret-up' }
245
+ onClick = { ( ) => setConfig ( { ...config , show : ! config . show } ) }
246
+ >
247
+ { ! config . show && '地图' }
248
+ </ Button >
249
+ </ div >
250
+ )
251
+ }
0 commit comments