pull/1755/head
BasilBP 2025-09-29 16:09:50 +05:30
rodzic 00606435b4
commit a8a2b5a4a0
3 zmienionych plików z 707 dodań i 583 usunięć

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 1.5 KiB

Wyświetl plik

@ -1,5 +1,5 @@
:root {
--panel-color: #ffffff;
--panel-color: #f0f0f0; /* Light grey panel background from your last input */
--header-color: #6c757d;
--text-color: #333;
--border-color: #dee2e6;
@ -7,57 +7,20 @@
--button-hover-bg: #d4dae0;
}
/* --- GcpInterface.css Layout Styles --- */
.gcp-window {
display: flex;
flex-direction: column;
height: 100vh;
width: 100%;
height: 100%;
}
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
color: var(--text-color);
font-weight: bold;
}
.top-bar .title {
font-size: 1.2em;
display: flex;
align-items: center;
}
.top-bar {
font-size: 1.5em;
}
.top-bar-left {
display: flex;
align-items: center;
}
.top-bar-right {
display: flex;
align-items: center;
gap: 12px;
}
.export-button {
background-color: var(--panel-color);
color: var(--text-color);
border: 1px solid var(--border-color);
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
}
.export-button:hover {
background-color: var(--button-bg);
}
/* Hide old unused elements */
.top-bar { display: none; }
.gcp-list-section .no-points { display: none; }
.gcp-list-section ul { padding: 0; margin: 0; }
.gcp-list-section ul { list-style-type: none; }
.main-content {
@ -66,74 +29,216 @@
overflow: hidden;
}
.main-content.ui-match-content {
flex-grow: 1;
overflow: hidden;
background-color: var(--panel-color);
border-radius: 8px;
}
.left-panel {
flex: 1 1 40%;
max-width: 40%;
padding: 15px;
border-right: 1px solid var(--border-color);
.left-panel.ui-match-left-panel {
flex: 0 0 350px;
max-width: 350px;
padding: 20px;
border-right: none;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.right-panel {
flex: 1 1 60%;
max-width: 60%;
.right-panel.ui-match-right-panel {
flex: 1;
max-width: none;
padding: 10px;
background: #e0f0ff;
}
/* --- Panel Headers (Linked Points & How to use) --- */
.left-panel h4 {
color: #888;
font-size: 0.9em;
/* Resetting the default h4 styles for a cleaner look */
color: var(--text-color);
font-size: 1.1em;
font-weight: 500;
margin-top: 0;
border-bottom: 1px solid var(--border-color);
margin-bottom: 5px;
padding-bottom: 0;
}
.gcp-list-section {
margin-bottom: 20px;
}
.directions-section {
margin-bottom: 20px;
}
.ui-match-header {
cursor: pointer;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-color); /* Grey line below the header */
padding-bottom: 8px;
}
.gcp-list-section .no-points {
color: #999;
font-style: italic;
}
.gcp-list-section ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.directions-section .collapsible {
cursor: pointer;
user-select: none;
.ui-match-header span {
font-size: 0.8em;
margin-left: 5px;
color: #666;
}
.directions-section ol {
font-size: 0.9em;
color: #555;
padding-left: 20px;
margin-top: 5px;
}
.file-controls {
/* --- Linked Points Mock UI (Blue Tiles - New Design) --- */
.linked-points-mock-ui {
display: flex;
flex-direction: column;
gap: 10px;
margin: 20px 0;
gap: 12px;
padding: 10px 0;
}
.file-controls button {
padding: 10px;
border: 1px dashed var(--border-color);
background-color: var(--button-bg);
cursor: pointer;
text-align: center;
.mock-link-row-ui {
display: flex;
align-items: center;
justify-content: space-between;
}
.mock-link-block {
height: 20px;
width: 90px;
background-color: #4a90e2; /* Blue color for the blocks */
border-radius: 4px;
}
.file-controls button:hover {
background-color: var(--button-hover-bg);
border-color: #aaa;
.mock-link-line {
flex-grow: 1;
height: 2px;
background-color: #ccc; /* Grey line connecting the blocks */
margin: 0 8px;
}
/* GcpInterface.css */
/* GcpInterface.css */
/* --- Linked Points List (Actual Data) --- */
.linked-points-list {
list-style-type: none;
padding: 0;
margin: 10px 0;
max-height: 200px;
overflow-y: auto;
border-radius: 4px;
background-color: #f0f0f0;
}
.linked-points-list li {
/* Set up for the two containers and the delete button */
display: flex;
align-items: center;
padding: 6px 12px;
border-bottom: 1px solid var(--border-color);
/* NEW: Use gap for spacing and make it a relative container for the line */
gap: 20px;
position: relative;
}
.linked-points-list li:last-child {
border-bottom: none;
}
/* Styles for the blue containers (blocks) */
.linked-points-list li > .link-image-name,
.linked-points-list li > .link-gcp-id {
background-color: #4a90e2; /* Matching the blue from the mockup */
color: white;
border-radius: 4px;
padding: 6px 10px;
text-align: center;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: 500;
line-height: 1;
/* FIX: Enforce equal width */
flex: 1 1 0; /* flex-grow: 1, flex-shrink: 1, flex-basis: 0 ensures equal width */
min-width: 0;
}
/* Remove margin and pseudo-element from .link-image-name to enable equal width */
.linked-points-list li > .link-image-name {
/* margin-right: 20px; - REMOVED */
/* position: relative; - REMOVED */
}
/* Creating the connecting line using a pseudo-element on the LI itself */
.linked-points-list li::before {
content: '';
position: absolute;
top: 50%;
/* FIX: Position the line in the center of the 20px gap:
Start position is half the LI width (50%), minus half the gap (10px),
and minus half the line width (5px). */
left: calc(50% - 15px);
width: 10px;
height: 2px;
background-color: #6c757d; /* Grey line color */
transform: translateY(-50%);
z-index: 2;
}
/* Push delete button to the right */
.delete-link-btn {
background: none;
border: none;
color: #dc3545;
cursor: pointer;
font-size: 1.2em;
font-weight: bold;
padding: 0 5px;
margin-left: 10px;
flex-shrink: 0; /* Prevents button from shrinking */
}
.delete-link-btn:hover {
color: #a71d2a;
}
/* --- File Controls (Choose/Load Buttons) --- */
.file-controls.ui-match-controls {
display: flex;
flex-direction: row;
gap: 15px;
margin: 10px 0 20px 0;
}
.file-controls .ui-match-button {
flex: 1;
padding: 12px 10px;
border: none;
border-radius: 4px;
font-weight: bold;
color: white;
background-color: #4a90e2; /* Blue color */
transition: background-color 0.2s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.file-controls .ui-match-button:hover {
background-color: #357bd1;
}
/* --- Image Grid (Real Images) --- */
.image-grid {
display: grid;
grid-template-columns: repeat(3, minmax(100px, 1fr));
@ -144,6 +249,8 @@
position: relative;
border: 1px solid var(--border-color);
padding: 4px;
cursor: pointer;
background-color: #fff; /* White background for image tile border */
}
.thumbnail img {
@ -152,6 +259,8 @@
display: block;
}
/* Removed styles for .thumbnail.mock-thumbnail (grey tiles) */
.thumbnail .image-name {
display: none;
position: absolute;
@ -208,56 +317,43 @@
z-index: 3;
}
/* --- Map Panel and Export Button --- */
.right-panel {
flex-grow: 1;
}
.map-container {
.map-container.ui-match-map-container {
width: 100%;
height: 100%;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
position: relative;
}
.export-button:disabled {
background-color: #e9ecef;
color: #adb5bd;
.export-button-overlay {
position: absolute;
bottom: 20px;
right: 20px;
z-index: 1000;
}
.export-button.ui-match-export-button {
padding: 10px 20px;
border: none;
border-radius: 4px;
font-weight: bold;
color: white;
background-color: #4a90e2;
transition: background-color 0.2s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
text-transform: uppercase;
}
.export-button.ui-match-export-button:hover:not(:disabled) {
background-color: #357bd1;
}
.export-button.ui-match-export-button:disabled {
background-color: #a0c4ec;
color: #fff;
cursor: not-allowed;
border-color: #dee2e6;
}
.linked-points-list {
list-style-type: none;
padding: 0;
margin: 10px 0;
max-height: 200px;
overflow-y: auto;
border: 1px solid var(--border-color);
border-radius: 4px;
}
.linked-points-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
font-size: 0.85em;
border-bottom: 1px solid var(--border-color);
}
.linked-points-list li:last-child {
border-bottom: none;
}
.delete-link-btn {
background: none;
border: none;
color: #dc3545;
cursor: pointer;
font-size: 1.2em;
font-weight: bold;
padding: 0 5px;
}
.delete-link-btn:hover {
color: #a71d2a;
}

Wyświetl plik

@ -6,7 +6,7 @@ import proj4 from 'proj4';
import './GcpInterface.css';
import ImageViewer from "./ImageViewer";
import { useNavigate } from "react-router-dom";
import Login from './Login';
import Login from './Login'; // Assuming this is needed for the original logic
const MapBoundsUpdater = ({ bounds }) => {
const map = useMap();
@ -36,7 +36,8 @@ function distance2D(lat1, lon1, lat2, lon2) {
function GcpInterface() {
const [gcpPoints, setGcpPoints] = useState([]);
const [images, setImages] = useState([]);
const [showDirections, setShowDirections] = useState(false);
// State is renamed to match the UI title
const [showHowToUse, setShowHowToUse] = useState(false);
const [selectedGcpPoint, setSelectedGcpPoint] = useState(null);
const [selectedImage, setSelectedImage] = useState(null);
const [selectedIndex, setSelectedIndex] = useState(null);
@ -47,6 +48,8 @@ function GcpInterface() {
const imageInputRef = useRef(null);
const navigate = useNavigate();
// --- Handlers (unchanged logic) ---
const handleRemoveImage = (e, imageUrlToRemove) => {
e.stopPropagation();
setImages(currentImages => currentImages.filter(img => img.url !== imageUrlToRemove));
@ -222,26 +225,15 @@ function GcpInterface() {
return <Login />;
}
// --- Component JSX (updated structure and content) ---
return (
<div className="gcp-window">
<div className="top-bar">
<div className="top-bar-left">
<div className="title">
Ground Control Point Interface
</div>
</div>
<div className="top-bar-right">
<button
className="export-button"
onClick={handleExport}
disabled={gcpLinks.length === 0}
>
EXPORT FILE
</button>
</div>
</div>
<div className="main-content">
<div className="left-panel">
<div className="gcp-window ui-match-window">
{/* The Top Bar is removed to match the UI which assumes a parent sidebar. */}
<div className="main-content ui-match-content">
{/* Left Panel: Controls and Image Thumbnails */}
<div className="left-panel ui-match-left-panel">
{selectedImage ? (
<ImageViewer
image={selectedImage}
@ -254,14 +246,38 @@ function GcpInterface() {
) : (
<>
<div className="gcp-list-section">
<h4>LINKED POINTS ({gcpLinks.length})</h4>
{/* New Collapsible Header for Linked Points */}
<h4 onClick={() => {}} className="collapsible ui-match-header linked-points-header">
Linked Points <span></span>
</h4>
{/* Mock UI/Empty state for Linked Points - Matches the blue tile design */}
{gcpLinks.length === 0 ? (
<p className="no-points">No links created yet...</p>
<div className="linked-points-mock-ui">
<div className="mock-link-row-ui">
<div className="mock-link-block left"></div>
<div className="mock-link-line"></div>
<div className="mock-link-block right"></div>
</div>
<div className="mock-link-row-ui">
<div className="mock-link-block left"></div>
<div className="mock-link-line"></div>
<div className="mock-link-block right"></div>
</div>
<div className="mock-link-row-ui">
<div className="mock-link-block left"></div>
<div className="mock-link-line"></div>
<div className="mock-link-block right"></div>
</div>
</div>
) : (
// Actual linked points list
<ul className="linked-points-list">
{gcpLinks.map((link) => (
<li key={link.id}>
<span> {link.image.name} {link.gcp.id}</span>
<span className="link-image-name">{link.image.name}</span>
{/* Separator span REMOVED. The line will be created in the gap below. */}
<span className="link-gcp-id">{link.gcp.id}</span>
<button
className="delete-link-btn"
onClick={() => setGcpLinks(links => links.filter(l => l.id !== link.id))}
@ -273,21 +289,15 @@ function GcpInterface() {
</ul>
)}
</div>
<div className="gcp-list-section">
<h4>GROUND CONTROL POINTS</h4>
{gcpPoints.length === 0 ? (
<p className="no-points">No points...</p>
) : (
<ul>
{gcpPoints.map((p, i) => <li key={i}>{p.id}</li>)}
</ul>
)}
</div>
{/* The GCP Points section is removed as requested by the user. */}
<div className="directions-section">
<h4 onClick={() => setShowDirections(!showDirections)} className="collapsible">
{showDirections ? '▼' : '►'} DIRECTIONS
{/* Renamed to match the UI image */}
<h4 onClick={() => setShowHowToUse(!showHowToUse)} className="collapsible ui-match-header">
How to use <span>{showHowToUse ? '▲' : '▼'}</span>
</h4>
{showDirections && (
{showHowToUse && (
<>
<div style={{ marginBottom: "8px" }}>
Connect at least 5 high-contrast objects in 3 or more photos to their corresponding locations on the map.
@ -302,17 +312,7 @@ function GcpInterface() {
</>
)}
</div>
<div className="file-controls">
<input
type="file"
accept=".txt"
ref={gcpInputRef}
onChange={handleGcpFileChange}
style={{ display: 'none' }}
/>
<button onClick={() => gcpInputRef.current.click()}>
Load existing Control Point File
</button>
<div className="file-controls ui-match-controls">
<input
type="file"
accept="image/jpeg, image/png"
@ -321,11 +321,22 @@ function GcpInterface() {
onChange={handleImageChange}
style={{ display: 'none' }}
/>
<button onClick={() => imageInputRef.current.click()}>
Choose images
<button className="ui-match-button" onClick={() => imageInputRef.current.click()}>
Choose Images
</button>
<input
type="file"
accept=".txt"
ref={gcpInputRef}
onChange={handleGcpFileChange}
style={{ display: 'none' }}
/>
<button className="ui-match-button" onClick={() => gcpInputRef.current.click()}>
Load GCP
</button>
</div>
<div className="image-grid">
{/* Only map real images. Mock tiles are removed as requested. */}
{displayedImages.map((image, i) => {
const linkCount = imageLinkCounts[image.url] || 0;
return (
@ -344,8 +355,14 @@ function GcpInterface() {
</>
)}
</div>
<div className="right-panel">
<MapContainer center={[20, 0]} zoom={2} className="map-container" style={{cursor: pendingPoint ? 'crosshair' : 'auto'}}>
{/* Right Panel: Map */}
<div className="right-panel ui-match-right-panel">
<MapContainer
center={[20, 0]}
zoom={2}
className="map-container ui-match-map-container"
style={{cursor: pendingPoint ? 'crosshair' : 'auto'}}
>
<MapClickHandler onMapClick={handleMapClick} />
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
@ -363,6 +380,17 @@ function GcpInterface() {
</Marker>
))}
<MapBoundsUpdater bounds={mapBounds} />
{/* Export Button overlay, matching UI image placement */}
<div className="export-button-overlay">
<button
className="export-button ui-match-export-button"
onClick={handleExport}
disabled={gcpLinks.length === 0}
>
Export File
</button>
</div>
</MapContainer>
</div>
</div>