kopia lustrzana https://github.com/OpenDroneMap/WebODM
step1
rodzic
00606435b4
commit
a8a2b5a4a0
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 1.5 KiB |
|
@ -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;
|
||||
}
|
|
@ -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='© <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>
|
||||
|
|
Ładowanie…
Reference in New Issue