import { h, s } from 'lib/h' import * as Color from 'lib/color' import * as ContextMenu from 'lib/contextMenu' import * as MarkerForm from 'markerForm' import * as Icons from 'lib/icons' import * as Modal from 'lib/modal' import * as State from 'state' const L = window.L interface CreateParams { id: State.Index, pos: L.Pos, color: string, icon: string, name: string, radius: number, addToMap: (layer: L.Layer | L.FeatureGroup) => void, removeFromMap: (layer: L.Layer | L.FeatureGroup) => void, } export function add({ id, pos, color, icon, name, radius, addToMap, removeFromMap }: CreateParams) { const marker = L.marker(pos, { draggable: true, autoPan: true, icon: divIcon({ icon, color, name }), }) const circle = radius !== 0 ? L.circle(pos, { radius, color, fillColor: color }) : undefined const layer = circle !== undefined ? L.featureGroup([ marker, circle ]) : L.featureGroup([ marker ]) const onUpdate = () => Modal.show(MarkerForm.view({ onValidate: (color: string, icon: string, name: string, radius: number) => { removeFromMap(layer) add({ id, pos, color, icon, name, radius, addToMap, removeFromMap }) State.update(id, { pos, color, icon, name, radius }) Modal.hide() }, onCancel: () => Modal.hide(), color, icon, name, radius, })) marker.addEventListener('contextmenu', e => { ContextMenu.show( e.originalEvent, [ { label: 'Modify', action: onUpdate, } , { label: 'Remove', action: () => { removeFromMap(layer) State.remove(id) } } ] ) }) marker.addEventListener('drag', e => { circle && circle.setLatLng(marker.getLatLng()) }) marker.addEventListener('dragend', e => { const pos = marker.getLatLng() removeFromMap(layer) add({ id, pos, color, icon, name, radius, addToMap, removeFromMap }) State.update(id, { pos, color, icon, name, radius }) }) marker.addEventListener('dblclick', onUpdate) addToMap(layer) } interface CreateIconParams { icon: string, color: string, name: string, } function divIcon({ icon, color, name }: CreateIconParams): L.Icon { const c = Color.parse(color) const crBlack = Color.contrastRatio({ red: 0, green: 0, blue: 0 }, c) const crWhite = Color.contrastRatio({ red: 255, green: 255, blue: 255 }, c) const textCol = crBlack > crWhite ? 'black' : 'white' const width = 10 const height = 15 const stroke = 'black' const strokeWidth = 0.6 // Triangle const t = [ { x: width * 0.15, y: 7.46 }, { x: width / 2, y: height }, { x: width * 0.85, y: 7.46 } ] return L.divIcon( { className: '' , popupAnchor: [ 0, -34 ] , html: h('div', { className: 'g-Marker' }, s('svg', { viewBox: `0 0 ${width} ${height}`, class: 'g-Marker__Base' }, s('circle', { cx: width / 2, cy: width / 2, r: (width - 2 * strokeWidth) / 2, stroke, 'stroke-width': strokeWidth, fill: color } ), s('polygon', { points: `${t[0].x},${t[0].y} ${t[1].x},${t[1].y} ${t[2].x},${t[2].y}`, fill: color } ), s('line', { x1: t[0].x, y1: t[0].y, x2: t[1].x, y2: t[1].y, stroke, 'stroke-width': strokeWidth } ), s('line', { x1: t[1].x, y1: t[1].y, x2: t[2].x, y2: t[2].y, stroke, 'stroke-width': strokeWidth } ), ), Icons.get( icon, { class: 'g-Marker__Icon' , style: `fill: ${textCol}; stroke: ${textCol}` } ), h('div', { className: 'g-Marker__Title', style: `color: black; text-shadow: ${textShadow('white', 1, 1)}` }, name ) ) } ) } function textShadow(color: string, w: number, blurr: number): string { return [[-w, -w], [-w, 0], [-w, w], [0, -w], [0, w], [w, -w], [w, 0], [w, w]] .map(xs => `${color} ${xs[0]}px ${xs[1]}px ${blurr}px`) .join(', ') }