asdf98 commited on
Commit
9d34bb7
·
verified ·
1 Parent(s): 5d78fc5

fix: minimap pinned bottom-right with safe padding; no top-left overflow

Browse files
Files changed (1) hide show
  1. src/components/Minimap.tsx +84 -36
src/components/Minimap.tsx CHANGED
@@ -1,36 +1,84 @@
1
- import { useState } from 'react';
2
- import { Maximize2, Minimize2 } from 'lucide-react';
3
- import { useAppStore } from '../store';
4
-
5
- export const Minimap = () => {
6
- const { images, pan, zoom, setPan } = useAppStore();
7
- const [isCollapsed, setIsCollapsed] = useState(false);
8
- if (images.length === 0) return null;
9
-
10
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
11
- images.forEach(img => { minX = Math.min(minX, img.x); minY = Math.min(minY, img.y); maxX = Math.max(maxX, img.x + img.width); maxY = Math.max(maxY, img.y + img.height); });
12
- const pad = 1000; minX -= pad; minY -= pad; maxX += pad; maxY += pad;
13
- const width = maxX - minX, height = maxY - minY;
14
- const mapW = 150, mapH = 150;
15
- const scale = Math.min(mapW / width, mapH / height) * 0.9;
16
- const vpW = window.innerWidth / zoom, vpH = window.innerHeight / zoom, vpX = -pan.x / zoom, vpY = -pan.y / zoom;
17
-
18
- const handleClick = (e: React.PointerEvent<HTMLDivElement>) => {
19
- const rect = e.currentTarget.getBoundingClientRect();
20
- const offX = mapW / 2 - (minX + width / 2) * scale, offY = mapH / 2 - (minY + height / 2) * scale;
21
- const cx = (e.clientX - rect.left - offX) / scale, cy = (e.clientY - rect.top - offY) / scale;
22
- setPan({ x: -cx * zoom + window.innerWidth / 2, y: -cy * zoom + window.innerHeight / 2 });
23
- };
24
-
25
- if (isCollapsed) return <div className="absolute bottom-4 right-4 z-20"><button className="w-8 h-8 bg-[#1C1C1E]/80 border border-[#3A3A3E] rounded-lg shadow-xl backdrop-blur flex items-center justify-center text-gray-400 hover:text-white" onClick={() => setIsCollapsed(false)}><Maximize2 size={14} /></button></div>;
26
-
27
- return (
28
- <div className="absolute bottom-4 right-4 z-20 w-[150px] h-[150px] bg-[#1C1C1E]/80 border border-[#3A3A3E] rounded-lg shadow-xl overflow-hidden backdrop-blur opacity-40 hover:opacity-100 transition-opacity cursor-crosshair relative pointer-events-auto">
29
- <button className="absolute top-1 right-1 w-5 h-5 bg-black/50 hover:bg-black/80 rounded flex items-center justify-center text-white/70 hover:text-white z-30" onClick={e => { e.stopPropagation(); setIsCollapsed(true); }}><Minimize2 size={10} /></button>
30
- <div className="relative w-full h-full" style={{ transform: `translate(${mapW/2 - (minX + width/2)*scale}px, ${mapH/2 - (minY + height/2)*scale}px)` }} onPointerDown={handleClick}>
31
- {images.map(img => <div key={img.id} className="absolute bg-gray-500/50 rounded-[1px]" style={{ left: img.x*scale, top: img.y*scale, width: img.width*scale, height: img.height*scale }} />)}
32
- <div className="absolute border border-[#0A84FF]/80 bg-[#0A84FF]/10 pointer-events-none" style={{ left: vpX*scale, top: vpY*scale, width: vpW*scale, height: vpH*scale }} />
33
- </div>
34
- </div>
35
- );
36
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react';
2
+ import { Maximize2, Minimize2, Navigation } from 'lucide-react';
3
+ import { useAppStore } from '../store';
4
+
5
+ export const Minimap = () => {
6
+ const { images, pan, zoom, setPan, isBrowserOpen, isLibraryOpen, isSettingsOpen } = useAppStore();
7
+ const [isCollapsed, setIsCollapsed] = useState(false);
8
+ if (images.length === 0) return null;
9
+
10
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
11
+ images.forEach(img => {
12
+ minX = Math.min(minX, img.x);
13
+ minY = Math.min(minY, img.y);
14
+ maxX = Math.max(maxX, img.x + img.width);
15
+ maxY = Math.max(maxY, img.y + img.height);
16
+ });
17
+
18
+ const contentW = Math.max(1, maxX - minX);
19
+ const contentH = Math.max(1, maxY - minY);
20
+ const pad = Math.max(200, Math.max(contentW, contentH) * 0.15);
21
+ minX -= pad; minY -= pad; maxX += pad; maxY += pad;
22
+ const width = Math.max(1, maxX - minX);
23
+ const height = Math.max(1, maxY - minY);
24
+
25
+ const MAP_W = 160;
26
+ const MAP_H = 120;
27
+ const scale = Math.min(MAP_W / width, MAP_H / height);
28
+ const ox = (MAP_W - width * scale) / 2;
29
+ const oy = (MAP_H - height * scale) / 2;
30
+
31
+ const vpX = -pan.x / zoom;
32
+ const vpY = -pan.y / zoom;
33
+ const vpW = window.innerWidth / zoom;
34
+ const vpH = window.innerHeight / zoom;
35
+
36
+ const safeRight = isSettingsOpen ? 24 : isBrowserOpen ? 24 : 20;
37
+ const safeLeftPanel = isLibraryOpen ? 8 : 20;
38
+ const wrapperStyle: React.CSSProperties = {
39
+ position: 'fixed',
40
+ right: safeRight,
41
+ bottom: 20,
42
+ zIndex: 45,
43
+ maxWidth: 'calc(100vw - 40px)',
44
+ maxHeight: 'calc(100vh - 40px)',
45
+ };
46
+
47
+ const handleClick = (e: React.PointerEvent<HTMLDivElement>) => {
48
+ const rect = e.currentTarget.getBoundingClientRect();
49
+ const mx = e.clientX - rect.left;
50
+ const my = e.clientY - rect.top;
51
+ const worldX = (mx - ox) / scale + minX;
52
+ const worldY = (my - oy) / scale + minY;
53
+ setPan({ x: -worldX * zoom + window.innerWidth / 2, y: -worldY * zoom + window.innerHeight / 2 });
54
+ };
55
+
56
+ if (isCollapsed) {
57
+ return (
58
+ <div style={wrapperStyle}>
59
+ <button className="w-9 h-9 bg-[#1C1C1E]/90 border border-[#3A3A3E] rounded-xl shadow-2xl backdrop-blur flex items-center justify-center text-[#A0A0A0] hover:text-white hover:border-[#0A84FF]/60 transition-colors" onClick={() => setIsCollapsed(false)} title="Show navigator">
60
+ <Navigation size={15} />
61
+ </button>
62
+ </div>
63
+ );
64
+ }
65
+
66
+ return (
67
+ <div style={wrapperStyle} className="w-[160px] bg-[#1C1C1E]/92 border border-[#3A3A3E] rounded-xl shadow-2xl overflow-hidden backdrop-blur-md opacity-70 hover:opacity-100 transition-opacity pointer-events-auto">
68
+ <div className="h-7 px-2.5 flex items-center justify-between border-b border-[#3A3A3E]/70 bg-black/20">
69
+ <div className="flex items-center gap-1.5 text-[10px] font-semibold text-[#A0A0A0] uppercase tracking-wider">
70
+ <Navigation size={11} /> Navigator
71
+ </div>
72
+ <button className="w-5 h-5 hover:bg-white/10 rounded flex items-center justify-center text-white/60 hover:text-white" onClick={e => { e.stopPropagation(); setIsCollapsed(true); }} title="Collapse navigator">
73
+ <Minimize2 size={10} />
74
+ </button>
75
+ </div>
76
+ <div className="relative cursor-crosshair bg-[#111113]" style={{ width: MAP_W, height: MAP_H }} onPointerDown={handleClick}>
77
+ {images.map(img => (
78
+ <div key={img.id} className="absolute bg-[#8A8A8C]/55 rounded-[1px]" style={{ left: ox + (img.x - minX) * scale, top: oy + (img.y - minY) * scale, width: Math.max(2, img.width * scale), height: Math.max(2, img.height * scale) }} />
79
+ ))}
80
+ <div className="absolute border border-[#0A84FF] bg-[#0A84FF]/15 pointer-events-none rounded-[2px]" style={{ left: ox + (vpX - minX) * scale, top: oy + (vpY - minY) * scale, width: Math.max(8, vpW * scale), height: Math.max(8, vpH * scale) }} />
81
+ </div>
82
+ </div>
83
+ );
84
+ };