- add EventEmmitter for directly binding Events to component
- add NotificationToast for messages - add leaflet map component and zoom control component - change focus:ring to focus:ring-2 inside BaseButton - `@tailwindcss/line-clamp` plugin is now included by default...remove it from tailwind.config.js - npm updates
This commit is contained in:
parent
080c21126b
commit
cd66f318b6
22 changed files with 1063 additions and 292 deletions
|
@ -80,7 +80,7 @@ const componentClass = computed(() => {
|
|||
'whitespace-nowrap',
|
||||
'focus:outline-none',
|
||||
'transition-colors',
|
||||
'focus:ring',
|
||||
'focus:ring-2',
|
||||
'duration-150',
|
||||
'border',
|
||||
props.roundedFull ? 'rounded-full' : 'rounded',
|
||||
|
|
|
@ -28,6 +28,10 @@ const props = defineProps({
|
|||
type: [String, Number, Boolean],
|
||||
default: null,
|
||||
},
|
||||
deleteId: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'cancel', 'confirm']);
|
||||
|
@ -37,9 +41,15 @@ const value = computed({
|
|||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
// mode = cancel or confirm
|
||||
const confirmCancel = (mode) => {
|
||||
value.value = false;
|
||||
emit(mode);
|
||||
value.value = false;//close
|
||||
if (props.deleteId){
|
||||
emit(mode, props.deleteId);
|
||||
} else {
|
||||
emit(mode);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const confirm = () => confirmCancel('confirm');
|
||||
|
|
153
resources/js/Components/Map/EventEmitter.ts
Normal file
153
resources/js/Components/Map/EventEmitter.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import {PartialObserver, Subject, Subscription} from 'rxjs';
|
||||
|
||||
/**
|
||||
* Use in components with the `@Output` directive to emit custom events
|
||||
* synchronously or asynchronously, and register handlers for those events
|
||||
* by subscribing to an instance.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* Extends
|
||||
* [RxJS `Subject`](https://rxjs.dev/api/index/class/Subject)
|
||||
* for Angular by adding the `emit()` method.
|
||||
*
|
||||
* In the following example, a component defines two output properties
|
||||
* that create event emitters. When the title is clicked, the emitter
|
||||
* emits an open or close event to toggle the current visibility state.
|
||||
*
|
||||
* ```html
|
||||
* @Component({
|
||||
* selector: 'zippy',
|
||||
* template: `
|
||||
* <div class="zippy">
|
||||
* <div (click)="toggle()">Toggle</div>
|
||||
* <div [hidden]="!visible">
|
||||
* <ng-content></ng-content>
|
||||
* </div>
|
||||
* </div>`})
|
||||
* export class Zippy {
|
||||
* visible: boolean = true;
|
||||
* @Output() open: EventEmitter<any> = new EventEmitter();
|
||||
* @Output() close: EventEmitter<any> = new EventEmitter();
|
||||
*
|
||||
* toggle() {
|
||||
* this.visible = !this.visible;
|
||||
* if (this.visible) {
|
||||
* this.open.emit(null);
|
||||
* } else {
|
||||
* this.close.emit(null);
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Access the event object with the `$event` argument passed to the output event
|
||||
* handler:
|
||||
*
|
||||
* ```html
|
||||
* <zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>
|
||||
* ```
|
||||
*
|
||||
* @see [Observables in Angular](guide/observables-in-angular)
|
||||
* @publicApi
|
||||
*/
|
||||
export interface EventEmitter<T> extends Subject<T> {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
__isAsync: boolean;
|
||||
|
||||
/**
|
||||
* Creates an instance of this class that can
|
||||
* deliver events synchronously or asynchronously.
|
||||
*
|
||||
* @param [isAsync=false] When true, deliver events asynchronously.
|
||||
*
|
||||
*/
|
||||
new(isAsync?: boolean): EventEmitter<T>;
|
||||
|
||||
/**
|
||||
* Emits an event containing a given value.
|
||||
* @param value The value to emit.
|
||||
*/
|
||||
emit(value?: T): void;
|
||||
|
||||
/**
|
||||
* Registers handlers for events emitted by this instance.
|
||||
* @param next When supplied, a custom handler for emitted events.
|
||||
* @param error When supplied, a custom handler for an error notification from this emitter.
|
||||
* @param complete When supplied, a custom handler for a completion notification from this
|
||||
* emitter.
|
||||
*/
|
||||
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void):
|
||||
Subscription;
|
||||
/**
|
||||
* Registers handlers for events emitted by this instance.
|
||||
* @param observerOrNext When supplied, a custom handler for emitted events, or an observer
|
||||
* object.
|
||||
* @param error When supplied, a custom handler for an error notification from this emitter.
|
||||
* @param complete When supplied, a custom handler for a completion notification from this
|
||||
* emitter.
|
||||
*/
|
||||
subscribe(observerOrNext?: any, error?: any, complete?: any): Subscription;
|
||||
}
|
||||
|
||||
class EventEmitter_ extends Subject<any> {
|
||||
__isAsync: boolean; // tslint:disable-line
|
||||
|
||||
constructor(isAsync: boolean = false) {
|
||||
super();
|
||||
this.__isAsync = isAsync;
|
||||
}
|
||||
|
||||
emit(value?: any) {
|
||||
super.next(value);
|
||||
}
|
||||
|
||||
override subscribe(observerOrNext?: any, error?: any, complete?: any): Subscription {
|
||||
let nextFn = observerOrNext;
|
||||
let errorFn = error || (() => null);
|
||||
let completeFn = complete;
|
||||
|
||||
if (observerOrNext && typeof observerOrNext === 'object') {
|
||||
const observer = observerOrNext as PartialObserver<unknown>;
|
||||
nextFn = observer.next?.bind(observer);
|
||||
errorFn = observer.error?.bind(observer);
|
||||
completeFn = observer.complete?.bind(observer);
|
||||
}
|
||||
|
||||
if (this.__isAsync) {
|
||||
errorFn = _wrapInTimeout(errorFn);
|
||||
|
||||
if (nextFn) {
|
||||
nextFn = _wrapInTimeout(nextFn);
|
||||
}
|
||||
|
||||
if (completeFn) {
|
||||
completeFn = _wrapInTimeout(completeFn);
|
||||
}
|
||||
}
|
||||
|
||||
const sink = super.subscribe({next: nextFn, error: errorFn, complete: completeFn});
|
||||
|
||||
if (observerOrNext instanceof Subscription) {
|
||||
observerOrNext.add(sink);
|
||||
}
|
||||
|
||||
return sink;
|
||||
}
|
||||
}
|
||||
|
||||
function _wrapInTimeout(fn: (value: unknown) => any) {
|
||||
return (value: unknown) => {
|
||||
setTimeout(fn, undefined, value);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @publicApi
|
||||
*/
|
||||
export const EventEmitter: {
|
||||
new (isAsync?: boolean): EventEmitter<any>; new<T>(isAsync?: boolean): EventEmitter<T>;
|
||||
readonly prototype: EventEmitter<any>;
|
||||
} = EventEmitter_ as any;
|
14
resources/js/Components/Map/MapOptions.ts
Normal file
14
resources/js/Components/Map/MapOptions.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Layer } from 'leaflet';
|
||||
|
||||
// https://github.com/52North/helgoland-toolbox/blob/develop/libs/map/src/lib/base/map-options.ts
|
||||
|
||||
export interface LayerOptions {
|
||||
label: string;
|
||||
visible: boolean;
|
||||
layer: Layer;
|
||||
}
|
||||
|
||||
// export type LayerMap = Map<string, LayerOptions>;
|
||||
|
||||
export class LayerMap extends Map<string, LayerOptions> {
|
||||
}
|
134
resources/js/Components/Map/map.component.vue
Normal file
134
resources/js/Components/Map/map.component.vue
Normal file
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<div style="position: relative">
|
||||
<!-- <Map className="h-36" :center="state.center" :zoom="state.zoom"> // map component content </Map> -->
|
||||
<div :id="mapId" class="map-container mapDesktop rounded">
|
||||
<ZoomControlComponent :mapId="mapId"></ZoomControlComponent>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { EventEmitter } from './EventEmitter';
|
||||
import { Component, Vue, Prop } from 'vue-facing-decorator';
|
||||
// import type { Coverage } from '@/Dataset';
|
||||
import { Map, Control, MapOptions, LatLngBoundsExpression, tileLayer, latLng, latLngBounds, FeatureGroup } from 'leaflet';
|
||||
import { LayerOptions, LayerMap } from './MapOptions';
|
||||
import { MapService } from '@/Stores/map';
|
||||
import ZoomControlComponent from './zoom.component.vue';
|
||||
|
||||
const DEFAULT_BASE_LAYER_NAME = 'BaseLayer';
|
||||
// const DEFAULT_BASE_LAYER_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
const DEFAULT_BASE_LAYER_ATTRIBUTION = '© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors';
|
||||
|
||||
@Component({
|
||||
name: 'MapComponent',
|
||||
components: {
|
||||
ZoomControlComponent,
|
||||
},
|
||||
})
|
||||
export default class MapComponent extends Vue {
|
||||
/**
|
||||
* A map with the given ID is created inside this component.
|
||||
* This ID can be used the get the map instance over the map cache service.
|
||||
*/
|
||||
@Prop()
|
||||
public mapId: string;
|
||||
|
||||
/**
|
||||
* The corresponding leaflet map options (see: https://leafletjs.com/reference-1.3.4.html#map-option)
|
||||
*/
|
||||
@Prop()
|
||||
public mapOptions: MapOptions;
|
||||
|
||||
// markerService: MarkerService
|
||||
/**
|
||||
* Bounds for the map
|
||||
*/
|
||||
@Prop({ default: null })
|
||||
public fitBounds: LatLngBoundsExpression;
|
||||
|
||||
/**
|
||||
* Describes the the zoom control options (see: https://leafletjs.com/reference-1.3.4.html#control-zoom)
|
||||
*/
|
||||
@Prop()
|
||||
public zoomControlOptions: Control.ZoomOptions;
|
||||
|
||||
@Prop()
|
||||
public baseMaps: LayerMap;
|
||||
|
||||
mapService = MapService();
|
||||
|
||||
/**
|
||||
* Informs when initialization is done with map id.
|
||||
*/
|
||||
public onMapInitializedEvent: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
protected map!: Map;
|
||||
protected drawnItems!: FeatureGroup<any>;
|
||||
|
||||
// @Prop({ type: Object })
|
||||
// geolocation: Coverage;
|
||||
|
||||
mounted(): void {
|
||||
this.initMap();
|
||||
}
|
||||
|
||||
public deleteTest(): void {
|
||||
this.onMapInitializedEvent.emit(this.mapId);
|
||||
}
|
||||
|
||||
// @Emit(this.onMapInitializedEvent)
|
||||
protected initMap(): void {
|
||||
let map: Map = (this.map = new Map(this.mapId, this.mapOptions));
|
||||
this.mapService.setMap(this.mapId, map);
|
||||
map.scrollWheelZoom.disable();
|
||||
|
||||
// return this.mapId;
|
||||
// this.$emit("onMapInitializedEvent", this.mapId);
|
||||
this.onMapInitializedEvent.emit(this.mapId);
|
||||
this.addBaseMap();
|
||||
|
||||
// if (this.fitBounds) {
|
||||
// this.map.fitBounds(this.fitBounds);
|
||||
// }
|
||||
const southWest = latLng(46.5, 9.9);
|
||||
const northEast = latLng(48.9, 16.9);
|
||||
const bounds = latLngBounds(southWest, northEast);
|
||||
map.fitBounds(bounds);
|
||||
|
||||
// Initialise the FeatureGroup to store editable layers
|
||||
let drawnItems = (this.drawnItems = new FeatureGroup());
|
||||
map.addLayer(drawnItems);
|
||||
}
|
||||
|
||||
private addBaseMap(layerOptions?: LayerOptions): void {
|
||||
if (this.map) {
|
||||
if (!this.baseMaps || this.baseMaps.size === 0) {
|
||||
// let bmapgrau = tileLayer('https://{s}.wien.gv.at/basemap/bmapgrau/normal/google3857/{z}/{y}/{x}.png', {
|
||||
// subdomains: ['maps', 'maps1', 'maps2', 'maps3', 'maps4'],
|
||||
// attribution: 'Datenquelle: <a href="http://www.basemap.at/">basemap.at</a>',
|
||||
// });
|
||||
let osmGgray = tileLayer.wms('https://ows.terrestris.de/osm-gray/service', {
|
||||
format: 'image/png',
|
||||
attribution: DEFAULT_BASE_LAYER_ATTRIBUTION,
|
||||
layers: 'OSM-WMS',
|
||||
});
|
||||
layerOptions = {
|
||||
label: DEFAULT_BASE_LAYER_NAME,
|
||||
visible: true,
|
||||
layer: osmGgray,
|
||||
};
|
||||
this.map.attributionControl.setPrefix(false);
|
||||
layerOptions.layer.addTo(this.map);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.leaflet-container {
|
||||
height: 600px; /* <-- map height */
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
106
resources/js/Components/Map/zoom.component.vue
Normal file
106
resources/js/Components/Map/zoom.component.vue
Normal file
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="gba-control-zoom btn-group-vertical">
|
||||
<!-- <button ref="inputPlus" type="button" class="button is-light is-small" :click.prevent="zoomIn">
|
||||
<fa-icon [icon]="faPlus"></fa-icon>
|
||||
</button> -->
|
||||
<!-- <BaseButton ref="inputPlus" :icon="mdiPlus" color="white" rounded small @click.prevent="zoomIn" /> -->
|
||||
<button
|
||||
ref="inputPlus"
|
||||
class="inline-flex cursor-pointer justify-center items-center whitespace-nowrap focus:outline-none transition-colors duration-150 border rounded ring-blue-700 bg-teal-50 text-black border-teal-50 hover:bg-gray-200 text-sm p-1"
|
||||
type="button"
|
||||
@click.prevent="zoomIn"
|
||||
>
|
||||
<BaseIcon v-if="mdiPlus" :path="mdiPlus" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
ref="inputMinus"
|
||||
class="inline-flex cursor-pointer justify-center items-center whitespace-nowrap focus:outline-none transition-colors duration-150 border rounded ring-blue-700 bg-teal-50 text-black border-teal-50 hover:bg-gray-200 text-sm p-1"
|
||||
type="button"
|
||||
@click.prevent="zoomOut"
|
||||
>
|
||||
<BaseIcon v-if="mdiMinus" :path="mdiMinus" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue, Prop, Ref } from 'vue-facing-decorator';
|
||||
import { MapService } from '@/Stores/map';
|
||||
|
||||
import BaseIcon from '@/Components/BaseIcon.vue';
|
||||
import { mdiPlus, mdiMinus } from '@mdi/js';
|
||||
|
||||
@Component({
|
||||
name: 'zoom-control',
|
||||
components: {
|
||||
BaseIcon
|
||||
},
|
||||
})
|
||||
export default class ZoomControlComponent extends Vue {
|
||||
mdiPlus = mdiPlus;
|
||||
mdiMinus = mdiMinus;
|
||||
|
||||
/**
|
||||
* Connect map id.
|
||||
*/
|
||||
@Prop() public mapId: string;
|
||||
|
||||
@Ref('inputPlus') private _inputPlus: HTMLElement;
|
||||
@Ref('inputMinus') private _inpuMinus: HTMLElement;
|
||||
|
||||
mapService = MapService();
|
||||
|
||||
public zoomIn() {
|
||||
let map = this.mapService.getMap(this.mapId);
|
||||
map && map.zoomIn();
|
||||
}
|
||||
|
||||
public zoomOut() {
|
||||
let map = this.mapService.getMap(this.mapId);
|
||||
map && map.zoomOut();
|
||||
}
|
||||
|
||||
public updateDisabled() {
|
||||
let map = this.mapService.getMap(this.mapId);
|
||||
// let className = 'leaflet-disabled';
|
||||
|
||||
// this._inputPlus.nativeElement.disabled = false;
|
||||
this._inputPlus.setAttribute('aria-disabled', 'false');
|
||||
|
||||
// this._inpuMinus.nativeElement.disabled = false;
|
||||
this._inpuMinus.setAttribute('aria-disabled', 'false');
|
||||
|
||||
if (map.getZoom() === map.getMinZoom()) {
|
||||
// this._inpuMinus.nativeElement.disabled = true;
|
||||
this._inpuMinus.setAttribute('aria-disabled', 'true');
|
||||
}
|
||||
if (map.getZoom() === map.getMaxZoom()) {
|
||||
// this._inputPlus.nativeElement.disabled = true;
|
||||
this._inputPlus.setAttribute('aria-disabled', 'true');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="css">
|
||||
.gba-control-zoom {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
z-index: 500;
|
||||
}
|
||||
|
||||
.btn-group-vertical button {
|
||||
display: block;
|
||||
|
||||
margin-left: 0;
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
</style>
|
|
@ -34,7 +34,7 @@ import { Notification, NotificationGroup } from '@/notiwind';
|
|||
<div class="px-4 py-2 -mx-3">
|
||||
<div class="mx-3">
|
||||
<span class="font-semibold text-blue-500">{{ notification.title }}</span>
|
||||
<p class="text-sm text-gray-600">T{{ notification.text }}</p>
|
||||
<p class="text-sm text-gray-600">{{ notification.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
editor.link_modal.header
Reference in a new issue