Refactor crop button

pull/1636/head
Piero Toffanin 2025-03-24 14:45:44 -04:00
rodzic ac03ad071a
commit 48eec415e0
5 zmienionych plików z 326 dodań i 245 usunięć

Wyświetl plik

@ -0,0 +1,278 @@
import React from 'react';
import ReactDOM from 'ReactDOM';
import L from 'leaflet';
import '../css/CropButton.scss';
import PropTypes from 'prop-types';
import { _ } from '../classes/gettext';
const Colors = {
fill: '#fff',
stroke: '#1a1a1a'
};
class CropButton extends React.Component {
static defaultProps = {
group: null,
title: _("Crop"),
onPolygonCreated: () => {},
onPolygonChange: () => {}
};
static propTypes = {
map: PropTypes.object.isRequired,
group: PropTypes.object,
title: PropTypes.string,
onPolygonCreated: PropTypes.func,
onPolygonChange: PropTypes.func
};
constructor(props){
super(props);
this.state = {
cropping: false
}
this.map = props.map;
this.group = props.group;
if (!this.group){
this.group = L.layerGroup();
this.group.addTo(this.map);
}
}
toggleCrop = () => {
const { cropping } = this.state;
let crop = !cropping;
if (!crop) {
if (this.captureMarker) {
this.captureMarker.off('click', this.handleMarkerClick);
this.captureMarker.off('dblclick', this.handleMarkerDblClick);
this.captureMarker.off('mousemove', this.handleMarkerMove);
this.captureMarker.off('contextmenu', this.handleMarkerContextMenu);
this.map.off('move', this.onMapMove);
this.map.off('resize', this.onMapResize);
this.group.removeLayer(this.captureMarker);
this.captureMarker = null;
}
if (this.acceptMarker) {
this.group.removeLayer(this.acceptMarker);
this.acceptMarker = null;
}
if (this.measureBoundary) {
this.group.removeLayer(this.measureBoundary);
this.measureBoundary = null;
}
if (this.measureArea) {
this.group.removeLayer(this.measureArea);
this.measureArea = null;
}
}else{
if (!this.captureMarker) {
this.captureMarker = L.marker(this.map.getCenter(), {
clickable: true,
zIndexOffset: 10001
}).setIcon(L.divIcon({
iconSize: this.map.getSize().multiplyBy(2),
className: "crop-button-marker-layer"
})).addTo(this.group);
this.captureMarker.on('click', this.handleMarkerClick);
this.captureMarker.on('dblclick', this.handleMarkerDblClick);
this.captureMarker.on('mousemove', this.handleMarkerMove);
this.captureMarker.on('contextmenu', this.handleMarkerContextMenu);
this.map.on('move', this.onMapMove);
this.map.on('resize', this.onMapResize);
}
this.deletePolygon();
// Reset latlngs
this.latlngs = [];
}
this.setState({cropping: !cropping});
}
handleMarkerClick = e => {
L.DomEvent.stop(e);
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
this.uniqueLatLonPush(latlng);
if (this.latlngs.length >= 1) {
if (!this.measureBoundary) {
this.measureBoundary = L.polyline(this.latlngs.concat(latlng), {
clickable: false,
color: Colors.stroke,
weight: 2,
opacity: 0.9,
fill: false,
}).addTo(this.group);
} else {
this.measureBoundary.setLatLngs(this.latlngs.concat(latlng));
}
}
if (this.latlngs.length >= 2) {
if (!this.measureArea) {
this.measureArea = L.polygon(this.latlngs.concat(latlng), {
clickable: false,
stroke: false,
fillColor: Colors.fill,
fillOpacity: 0.2,
}).addTo(this.group);
} else {
this.measureArea.setLatLngs(this.latlngs.concat(latlng));
}
}
if (this.latlngs.length >= 3) {
if (this.acceptMarker) {
this.group.removeLayer(this.acceptMarker);
this.acceptMarker = null;
}
const onAccept = e => {
L.DomEvent.stop(e);
this.confirmPolygon();
return false;
};
let acceptLatlng = this.latlngs[0];
this.acceptMarker = L.marker(acceptLatlng, {
icon: L.icon({
iconUrl: `/static/app/img/accept.png`,
iconSize: [20, 20],
iconAnchor: [10, 10],
className: "crop-button-accept-button",
}),
zIndexOffset: 99999
}).addTo(this.group)
.on("click", onAccept)
.on("contextmenu", onAccept);
}
}
deletePolygon = () => {
if (this.polygon){
this.group.removeLayer(this.polygon);
this.polygon = null;
this.props.onPolygonChange();
}
}
confirmPolygon = () => {
if (this.latlngs.length >= 3){
this.polygon = L.polygon(this.latlngs, {
clickable: true,
weight: 3,
opacity: 0.9,
color: "#ffa716",
fillColor: "#ffa716",
fillOpacity: 0.2
}).addTo(this.group);
this.props.onPolygonCreated(this.polygon);
this.props.onPolygonChange();
}
this.toggleCrop();
}
getCropPolygon = () => {
if (!this.polygon) return null;
return this.polygon.toGeoJSON(14);
}
uniqueLatLonPush = latlng => {
if (this.latlngs.length === 0) this.latlngs.push(latlng);
else{
const last = this.latlngs[this.latlngs.length - 1];
if (last.lat !== latlng.lat && last.lng !== latlng.lng) this.latlngs.push(latlng);
}
};
handleMarkerDblClick = e => {
if (this.latlngs.length >= 2){
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
this.uniqueLatLonPush(latlng);
this.confirmPolygon();
}
}
handleMarkerMove = e => {
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
let lls = this.latlngs.concat(latlng);
lls.push(lls[0]);
if (this.measureBoundary) {
this.measureBoundary.setLatLngs(lls);
}
if (this.measureArea) {
this.measureArea.setLatLngs(lls);
}
}
handleMarkerContextMenu = e => {
if (this.latlngs.length >= 2){
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
this.uniqueLatLonPush(latlng);
this.confirmPolygon();
}
return false;
}
onMapMove = () => {
if (this.captureMarker) this.captureMarker.setLatLng(this.map.getCenter());
};
onMapResize = () => {
if (this.captureMarker) this.captureMarker.setIcon(L.divIcon({
iconSize: this._map.getSize().multiplyBy(2)
}));
}
render() {
return (<div>
<a href="javascript:void(0);"
onClick={this.toggleCrop}
title={this.props.title}
className={"leaflet-control-crop-button leaflet-bar-part theme-secondary " + (this.state.cropping ? "selected" : "")}><i className="fa fa-crop-alt"></i></a>
</div>);
}
}
export default L.Control.extend({
_btn: null,
options: {
position: 'topright'
},
onAdd: function (map) {
var container = L.DomUtil.create('div', 'crop-button leaflet-control-crop leaflet-bar leaflet-control');
L.DomEvent.disableClickPropagation(container);
ReactDOM.render(<CropButton ref={(domNode) => this._btn = domNode} map={map}
group={this.options.group}
title={this.options.title}
onPolygonCreated={this.options.onPolygonCreated}
onPolygonChange={this.options.onPolygonChange} />, container);
return container;
},
deletePolygon: function(){
return this._btn.deletePolygon();
},
getCropPolygon: function(){
return this._btn.getCropPolygon();
}
});

Wyświetl plik

@ -13,14 +13,10 @@ import Standby from './Standby';
import exifr from '../vendor/exifr';
import '../vendor/leaflet/leaflet-markers-canvas';
import { _, interpolate } from '../classes/gettext';
import CropButton from './CropButton';
import 'leaflet-fullscreen/dist/Leaflet.fullscreen';
import 'leaflet-fullscreen/dist/leaflet.fullscreen.css';
const Colors = {
fill: '#fff',
stroke: '#1a1a1a'
};
class MapPreview extends React.Component {
static defaultProps = {
getFiles: null,
@ -39,8 +35,7 @@ class MapPreview extends React.Component {
this.state = {
showLoading: true,
error: "",
cropping: false
error: ""
};
this.basemaps = {};
@ -147,6 +142,15 @@ _('Example:'),
});
}
this.cropButton = new CropButton({
position:'bottomleft',
title: _("Set Reconstruction Area (optional)"),
group: this.group,
onPolygonCreated: this.onPolygonCreated,
onPolygonChange: this.props.onPolygonChange
});
this.map.addControl(this.cropButton);
this.map.fitBounds([
[13.772919746115805,
45.664640939831735],
@ -157,6 +161,21 @@ _('Example:'),
this.loadNewFiles();
}
onPolygonCreated = (polygon) => {
const popupContainer = L.DomUtil.create('div');
popupContainer.className = "crop-button-delete";
const deleteLink = L.DomUtil.create('a');
deleteLink.href = "javascript:void(0)";
deleteLink.innerHTML = `<i class="fa fa-trash"></i> ${_("Delete")}`;
deleteLink.onclick = (e) => {
L.DomEvent.stop(e);
this.cropButton.deletePolygon();
};
popupContainer.appendChild(deleteLink);
polygon.bindPopup(popupContainer);
}
sampled = (arr, N) => {
// Return a uniformly sampled array with max N elements
if (arr.length <= N) return arr;
@ -316,214 +335,7 @@ _('Example:'),
}
getCropPolygon = () => {
if (!this.polygon) return null;
return this.polygon.toGeoJSON(14);
}
toggleCrop = () => {
const { cropping } = this.state;
let crop = !cropping;
if (!crop) {
if (this.captureMarker) {
this.captureMarker.off('click', this.handleMarkerClick);
this.captureMarker.off('dblclick', this.handleMarkerDblClick);
this.captureMarker.off('mousemove', this.handleMarkerMove);
this.captureMarker.off('contextmenu', this.handleMarkerContextMenu);
this.map.off('move', this.onMapMove);
this.map.off('resize', this.onMapResize);
this.group.removeLayer(this.captureMarker);
this.captureMarker = null;
}
if (this.acceptMarker) {
this.group.removeLayer(this.acceptMarker);
this.acceptMarker = null;
}
if (this.measureBoundary) {
this.group.removeLayer(this.measureBoundary);
this.measureBoundary = null;
}
if (this.measureArea) {
this.group.removeLayer(this.measureArea);
this.measureArea = null;
}
this.cropButton.blur();
}
else{
if (!this.captureMarker) {
this.captureMarker = L.marker(this.map.getCenter(), {
clickable: true,
zIndexOffset: 10001
}).setIcon(L.divIcon({
iconSize: this.map.getSize().multiplyBy(2),
className: "map-preview-marker-layer"
})).addTo(this.group);
this.captureMarker.on('click', this.handleMarkerClick);
this.captureMarker.on('dblclick', this.handleMarkerDblClick);
this.captureMarker.on('mousemove', this.handleMarkerMove);
this.captureMarker.on('contextmenu', this.handleMarkerContextMenu);
this.map.on('move', this.onMapMove);
this.map.on('resize', this.onMapResize);
}
if (this.polygon){
this.group.removeLayer(this.polygon);
this.polygon = null;
this.props.onPolygonChange();
}
// Reset latlngs
this.latlngs = [];
}
this.setState({cropping: !cropping});
}
handleMarkerClick = e => {
L.DomEvent.stop(e);
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
this.uniqueLatLonPush(latlng);
if (this.latlngs.length >= 1) {
if (!this.measureBoundary) {
this.measureBoundary = L.polyline(this.latlngs.concat(latlng), {
clickable: false,
color: Colors.stroke,
weight: 2,
opacity: 0.9,
fill: false,
}).addTo(this.group);
} else {
this.measureBoundary.setLatLngs(this.latlngs.concat(latlng));
}
}
if (this.latlngs.length >= 2) {
if (!this.measureArea) {
this.measureArea = L.polygon(this.latlngs.concat(latlng), {
clickable: false,
stroke: false,
fillColor: Colors.fill,
fillOpacity: 0.2,
}).addTo(this.group);
} else {
this.measureArea.setLatLngs(this.latlngs.concat(latlng));
}
}
if (this.latlngs.length >= 3) {
if (this.acceptMarker) {
this.group.removeLayer(this.acceptMarker);
this.acceptMarker = null;
}
const onAccept = e => {
L.DomEvent.stop(e);
this.confirmPolygon();
return false;
};
let acceptLatlng = this.latlngs[0];
this.acceptMarker = L.marker(acceptLatlng, {
icon: L.icon({
iconUrl: `/static/app/img/accept.png`,
iconSize: [20, 20],
iconAnchor: [10, 10],
className: "map-preview-accept-button",
}),
zIndexOffset: 99999
}).addTo(this.group)
.on("click", onAccept)
.on("contextmenu", onAccept);
}
};
confirmPolygon = () => {
if (this.latlngs.length >= 3){
const popupContainer = L.DomUtil.create('div');
popupContainer.className = "map-preview-delete";
const deleteLink = L.DomUtil.create('a');
deleteLink.href = "javascript:void(0)";
deleteLink.innerHTML = `<i class="fa fa-trash"></i> ${_("Delete")}`;
deleteLink.onclick = (e) => {
L.DomEvent.stop(e);
if (this.polygon){
this.group.removeLayer(this.polygon);
this.polygon = null;
this.props.onPolygonChange();
}
};
popupContainer.appendChild(deleteLink);
this.polygon = L.polygon(this.latlngs, {
clickable: true,
weight: 3,
opacity: 0.9,
color: "#ffa716",
fillColor: "#ffa716",
fillOpacity: 0.2
}).bindPopup(popupContainer).addTo(this.group);
this.props.onPolygonChange();
}
this.toggleCrop();
}
uniqueLatLonPush = latlng => {
if (this.latlngs.length === 0) this.latlngs.push(latlng);
else{
const last = this.latlngs[this.latlngs.length - 1];
if (last.lat !== latlng.lat && last.lng !== latlng.lng) this.latlngs.push(latlng);
}
};
handleMarkerDblClick = e => {
if (this.latlngs.length >= 2){
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
this.uniqueLatLonPush(latlng);
this.confirmPolygon();
}
}
handleMarkerMove = e => {
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
let lls = this.latlngs.concat(latlng);
lls.push(lls[0]);
if (this.measureBoundary) {
this.measureBoundary.setLatLngs(lls);
}
if (this.measureArea) {
this.measureArea.setLatLngs(lls);
}
}
handleMarkerContextMenu = e => {
if (this.latlngs.length >= 2){
const latlng = this.map.mouseEventToLatLng(e.originalEvent);
this.uniqueLatLonPush(latlng);
this.confirmPolygon();
}
return false;
}
onMapMove = () => {
if (this.captureMarker) this.captureMarker.setLatLng(this.map.getCenter());
};
onMapResize = () => {
if (this.captureMarker) this.captureMarker.setIcon(L.divIcon({
iconSize: this._map.getSize().multiplyBy(2)
}));
return this.cropButton.getCropPolygon();
}
setAlignmentPolygon = (task) => {
@ -632,14 +444,6 @@ _('Example:'),
</ul>
</div> : ""}
{this.state.error === "" ?
<div className="crop-control">
<button ref={(domNode) => {this.cropButton = domNode; }} type="button" onClick={this.toggleCrop} className={"btn btn-sm " + (this.state.cropping ? "btn-default" : "btn-secondary")} title={_("Set Reconstruction Area (optional)")}>
<i className="fa fa-crop-alt"></i>
</button>
</div>
: ""}
<div
style={{height: "100%"}}
ref={(domNode) => (this.container = domNode)}

Wyświetl plik

@ -0,0 +1,15 @@
.crop-control:active, .crop-control:focus{
outline: none;
}
.crop-button-marker-layer{
&:hover{
cursor: crosshair;
}
}
.crop-button-accept-button{
&:hover{
cursor: pointer;
}
}

Wyświetl plik

@ -20,16 +20,6 @@
}
}
.crop-control{
position: absolute;
top: 50px;
left: 8px;
z-index: 999;
.btn:active, .btn:focus{
outline: none;
}
}
.leaflet-control-layers-expanded{
.leaflet-control-layers-base{
overflow: hidden;
@ -38,18 +28,7 @@
overflow: hidden;
}
.map-preview-marker-layer{
&:hover{
cursor: crosshair;
}
}
.map-preview-accept-button{
&:hover{
cursor: pointer;
}
}
.map-preview-delete{
.crop-button-delete{
min-width: 70px;
}

Wyświetl plik

@ -293,6 +293,11 @@ pre.prettyprint:focus,
border-color: {% scalebyiv theme_secondary 0.90 %} !important;
}
.leaflet-bar a.selected{
background-color: {{ theme_button_default }} !important;
color: {{ theme_secondary }} !important;
}
.leaflet-popup-content-wrapper{
background-color: {{ theme_secondary }} !important;
color: {{ theme_primary }} !important;