tethys.backend/resources/js/Components/Map/draw.component.vue
Arno Kaimbacher 4229001572
Some checks failed
build.yaml / Enhance Map Zoom Control and Improve Map Page Layout (push) Failing after 0s
Enhance Map Zoom Control and Improve Map Page Layout
- Refactored zoom control component for better accessibility and styling.
- Added hover effects and improved button states for zoom in/out buttons.
- Updated map page layout with enhanced dataset card design and responsive styles.
- Introduced empty state for no datasets found and improved results header.
- Added icons for dataset cards and improved author display.
2025-11-05 13:15:23 +01:00

559 lines
No EOL
13 KiB
Vue

<template>
<div class="draw-control-container">
<button
ref="drawButton"
class="draw-button"
:class="{ 'is-active': enabled }"
type="button"
@click.stop.prevent="toggleDraw"
:aria-label="enabled ? 'Stop drawing' : 'Start drawing'"
:aria-pressed="enabled"
>
<!-- Icon changes based on state -->
<!-- <BaseIcon
v-if="enabled"
:path="mdiClose"
:size="20"
/> -->
<BaseIcon
:path="mdiVectorRectangle"
:size="20"
/>
<!-- Status indicator -->
<!-- <span class="draw-status-badge" :class="{ 'is-active': enabled }">
{{ enabled ? 'Active' : 'Draw' }}
</span> -->
</button>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-facing-decorator';
import BaseIcon from '@/Components/BaseIcon.vue';
import { mdiVectorRectangle, mdiClose } from '@mdi/js';
import { MapService } from '@/Stores/map.service';
import { Map } from 'leaflet';
import { on, off, preventDefault } from 'leaflet/src/dom/DomEvent';
import { Rectangle } from 'leaflet';
import { LatLngBounds } from 'leaflet';
import { LatLng } from 'leaflet';
@Component({
name: 'draw-control',
components: {
BaseIcon,
},
})
export class DrawControlComponent extends Vue {
public TYPE = 'rectangle';
mdiVectorRectangle = mdiVectorRectangle;
mdiClose = mdiClose;
options = {
shapeOptions: {
stroke: true,
color: '#65DC21',
weight: 4,
opacity: 0.5,
fill: true,
fillColor: '#65DC21',
fillOpacity: 0.2,
clickable: true,
},
repeatMode: true,
showArea: true, //Whether to show the area in the tooltip
metric: true, // Whether to use the metric measurement system or imperial
};
@Prop() public mapId: string;
@Prop public southWest: LatLng;
@Prop public northEast: LatLng;
@Prop({
default: true,
})
public preserve: boolean;
mapService = MapService();
private _enabled: boolean;
private _map: Map;
private _isDrawing: boolean = false;
private _startLatLng: LatLng;
private _mapDraggable: boolean;
private _shape: Rectangle | undefined;
get enabled() {
return this._enabled;
}
enable() {
if (this._enabled) {
return this;
}
this._enabled = true;
this.addHooks();
this._map.control = this;
return this;
}
disable() {
if (!this._enabled) {
return this;
}
this._enabled = false;
this.removeHooks();
return this;
}
// enabled() {
// return !!this._enabled;
// }
private addHooks() {
this._map = this.mapService.getMap(this.mapId);
if (this._map) {
this._mapDraggable = this._map.dragging.enabled();
if (this._mapDraggable) {
this._map.dragging.disable();
}
this._map.getContainer().style.cursor = 'crosshair';
this._map
.on('mousedown', this._onMouseDown, this)
.on('mousemove', this._onMouseMove, this)
.on('touchstart', this._onMouseDown, this)
.on('touchmove', this._onMouseMove, this);
}
}
private removeHooks() {
if (this._map) {
if (this._mapDraggable) {
this._map.dragging.enable();
}
this._map.getContainer().style.cursor = '';
this._map
.off('mousedown', this._onMouseDown, this)
.off('mousemove', this._onMouseMove, this)
.off('touchstart', this._onMouseDown, this)
.off('touchmove', this._onMouseMove, this);
off(document, 'mouseup', this._onMouseUp, this);
off(document, 'touchend', this._onMouseUp, this);
if (this._shape && this.preserve == false) {
this._map.removeLayer(this._shape);
this._shape = undefined;
}
}
this._isDrawing = false;
}
// private _onMouseDown(e: LeafletMouseEvent) {
private _onMouseDown(e: any) {
this._isDrawing = true;
this._startLatLng = e.latlng;
on(document, 'mouseup', this._onMouseUp, this);
on(document, 'touchend', this._onMouseUp, this);
preventDefault(e.originalEvent);
}
// private _onMouseMove(e: LeafletMouseEvent) {
private _onMouseMove(e: any) {
var latlng = e.latlng;
if (this._isDrawing) {
this._drawShape(latlng);
}
}
private _onMouseUp() {
if (this._shape) {
this._fireCreatedEvent(this._shape);
}
this.disable();
if (this.options.repeatMode) {
this.enable();
}
}
private _fireCreatedEvent(shape: Rectangle) {
var rectangle = new Rectangle(shape.getBounds(), this.options.shapeOptions);
this._map.fire('Draw.Event.CREATED', { layer: rectangle, type: this.TYPE });
}
public removeShape() {
if (this._shape) {
this._map.removeLayer(this._shape);
this._shape = undefined;
}
}
public drawShape(southWest: LatLng, northEast: LatLng) {
if (!this._shape) {
const bounds = new LatLngBounds(southWest, northEast);
this._shape = new Rectangle(bounds, this.options.shapeOptions);
this._map = this.mapService.getMap(this.mapId);
this._shape.addTo(this._map);
} else {
this._shape.setBounds(new LatLngBounds(southWest, northEast));
}
}
private _drawShape(latlng: LatLng) {
if (!this._shape) {
const bounds = new LatLngBounds(this._startLatLng, latlng);
this._shape = new Rectangle(bounds, this.options.shapeOptions);
this._shape.addTo(this._map);
} else {
this._shape.setBounds(new LatLngBounds(this._startLatLng, latlng));
}
}
public toggleDraw() {
if (this._enabled == true) {
this.disable();
} else {
this.enable();
}
}
}
export default DrawControlComponent;
</script>
<style scoped>
.draw-control-container {
position: absolute;
left: 1rem;
top: 8rem;
z-index: 1000;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.draw-button {
display: flex;
align-items: center;
gap: 0;
padding: 0.625rem;
background: white;
border: 2px solid #e5e7eb;
border-radius: 0.75rem;
color: #374151;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
outline: none;
font-size: 0.875rem;
font-weight: 600;
position: relative;
overflow: visible;
width: 2.5rem;
height: 2.5rem;
justify-content: center;
}
.dark .draw-button {
background: #1f2937;
border-color: #374151;
color: #d1d5db;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3);
}
/* Inactive state hover */
.draw-button:not(.is-active):hover {
background: #f9fafb;
border-color: #65DC21;
color: #357C06;
transform: translateY(-2px);
/* box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); */
width: auto;
padding: 0.625rem 1rem;
gap: 0.5rem;
}
.dark .draw-button:not(.is-active):hover {
background: #111827;
border-color: #65DC21;
color: #65DC21;
}
/* Active state */
.draw-button.is-active {
background: linear-gradient(135deg, #65DC21 0%, #357C06 100%);
border-color: #357C06;
color: white;
box-shadow: 0 10px 15px -3px rgba(101, 220, 33, 0.4), 0 4px 6px -2px rgba(101, 220, 33, 0.2);
width: auto;
padding: 0.625rem 1rem;
gap: 0.5rem;
}
.dark .draw-button.is-active {
box-shadow: 0 10px 15px -3px rgba(101, 220, 33, 0.5), 0 4px 6px -2px rgba(101, 220, 33, 0.3);
}
/* Active state hover */
.draw-button.is-active:hover {
background: linear-gradient(135deg, #429E04 0%, #295B09 100%);
transform: translateY(-2px);
box-shadow: 0 20px 25px -5px rgba(101, 220, 33, 0.4), 0 10px 10px -5px rgba(101, 220, 33, 0.2);
}
/* Active state press */
.draw-button:active {
transform: translateY(0) scale(0.98);
}
/* Focus state */
.draw-button:focus-visible {
outline: 3px solid rgba(101, 220, 33, 0.5);
outline-offset: 2px;
}
/* Icon styling */
.draw-button :deep(svg) {
width: 1.25rem;
height: 1.25rem;
transition: transform 0.3s ease;
}
/* .draw-button.is-active :deep(svg) {
transform: rotate(90deg);
} */
/* Status badge */
.draw-status-badge {
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
transition: all 0.3s ease;
max-width: 0;
opacity: 0;
overflow: hidden;
white-space: nowrap;
}
/* Show badge on hover when inactive */
.draw-button:not(.is-active):hover .draw-status-badge {
max-width: 100px;
opacity: 1;
}
/* Show badge when active */
.draw-button.is-active .draw-status-badge {
max-width: 100px;
opacity: 1;
}
/* Pulse animation for active state */
.draw-button.is-active::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.3);
border-radius: 0.75rem;
transform: translate(-50%, -50%) scale(0);
animation: pulse 2s ease-out infinite;
pointer-events: none;
}
/* @keyframes pulse {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 1;
}
100% {
transform: translate(-50%, -50%) scale(1.5);
opacity: 0;
}
} */
/* Glow effect for active state */
.draw-button.is-active::after {
content: '';
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(135deg, #65DC21, #357C06);
border-radius: 0.75rem;
opacity: 0;
z-index: -1;
transition: opacity 0.3s ease;
filter: blur(8px);
}
.draw-button.is-active:hover::after {
opacity: 0.6;
}
/* Inactive state indicator */
.draw-button:not(.is-active) .draw-status-badge {
color: #6b7280;
}
.dark .draw-button:not(.is-active) .draw-status-badge {
color: #9ca3af;
}
.draw-button:not(.is-active):hover .draw-status-badge {
color: #357C06;
}
.dark .draw-button:not(.is-active):hover .draw-status-badge {
color: #65DC21;
}
/* Active state indicator */
.draw-button.is-active .draw-status-badge {
color: white;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
/* Tooltip on hover */
.draw-button:hover::after {
content: attr(aria-label);
position: absolute;
bottom: calc(100% + 0.5rem);
left: 50%;
transform: translateX(-50%);
background: #1f2937;
color: white;
padding: 0.375rem 0.75rem;
border-radius: 0.375rem;
font-size: 0.75rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
z-index: 1001;
animation: fadeInTooltip 0.2s ease 0.5s forwards;
}
/* @keyframes fadeInTooltip {
from {
opacity: 0;
transform: translateX(-50%) translateY(4px);
}
to {
opacity: 1;
transform: translateX(-50%) translateY(0);
}
} */
/* Ripple effect on click */
.draw-button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
.draw-button:active::before {
width: 300px;
height: 300px;
}
/* Responsive design */
@media (max-width: 768px) {
.draw-control-container {
right: 0.75rem;
top: 0.75rem;
}
.draw-button {
width: 2.25rem;
height: 2.25rem;
padding: 0.5rem;
font-size: 0.8125rem;
}
.draw-button:not(.is-active):hover,
.draw-button.is-active {
padding: 0.5rem 0.875rem;
}
.draw-button :deep(svg) {
width: 1.125rem;
height: 1.125rem;
}
.draw-status-badge {
font-size: 0.6875rem;
}
/* Hide tooltip on mobile */
.draw-button:hover::after {
display: none;
}
}
/* @media (max-width: 640px) {
.draw-control-container {
right: 0.5rem;
top: 0.5rem;
}
.draw-button {
width: 2rem;
height: 2rem;
padding: 0.5rem;
}
.draw-button:not(.is-active):hover,
.draw-button.is-active {
padding: 0.5rem 0.75rem;
}
.draw-button :deep(svg) {
width: 1rem;
height: 1rem;
}
} */
/* Accessibility: reduce motion */
@media (prefers-reduced-motion: reduce) {
.draw-button,
.draw-button :deep(svg),
.draw-status-badge {
transition: none;
}
.draw-button.is-active::before,
.draw-button.is-active::after {
animation: none;
}
}
</style>
<style>
/* Global styles for draw mode */
.leaflet-container.draw-mode-active {
cursor: crosshair !important;
}
.leaflet-container.draw-mode-active * {
cursor: crosshair !important;
}
</style>