aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/css/dumbymap.css15
-rw-r--r--src/dumbyUtils.mjs46
-rw-r--r--src/dumbymap.mjs67
-rw-r--r--src/editor.mjs16
4 files changed, 74 insertions, 70 deletions
diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css
index 6c581fa..dc1f59b 100644
--- a/src/css/dumbymap.css
+++ b/src/css/dumbymap.css
@@ -391,6 +391,10 @@ root {
391 cursor: crosshair !important; 391 cursor: crosshair !important;
392 } 392 }
393 } 393 }
394
395 .point-by-arrow {
396 display: block;
397 }
394 } 398 }
395 399
396 400
@@ -811,3 +815,14 @@ root {
811 background: #E4E4E7; 815 background: #E4E4E7;
812 } 816 }
813} 817}
818
819.point-by-arrow {
820 display: none;
821 padding: 5px;
822
823 position: absolute;
824
825 transform: translate(-50%, -50%);
826 cursor: crosshair;
827 pointer-events: none;
828}
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs
index 7b06840..be0da43 100644
--- a/src/dumbyUtils.mjs
+++ b/src/dumbyUtils.mjs
@@ -190,11 +190,14 @@ export const createGeoLink = (link) => {
190 if (link.dataset.valid === 'false') return 190 if (link.dataset.valid === 'false') return
191 191
192 removeLeaderLines(link) 192 removeLeaderLines(link)
193 getMarkersFromMaps(link) 193 getMarkersFromMaps(link).forEach(marker => {
194 .forEach(updateMapCameraByMarker([ 194 const map = marker.closest('.mapclay')
195 map.scrollIntoView({ behavior: 'smooth' })
196 updateMapCameraByMarker([
195 Number(link.dataset.lon), 197 Number(link.dataset.lon),
196 Number(link.dataset.lat), 198 Number(link.dataset.lat),
197 ])) 199 ])(marker)
200 })
198 } 201 }
199 202
200 // Use middle click to remove markers 203 // Use middle click to remove markers
@@ -296,43 +299,24 @@ const isAnchorVisible = anchor => {
296} 299}
297 300
298/** 301/**
299 * addAnchorByPoint. 302 * addMarkerByPoint.
300 * 303 *
301 * @param {point} options.point - object has {x, y} for window coordinates 304 * @param {Number[]} options.point - page XY
302 * @param {HTMLElement} options.map 305 * @param {HTMLElement} options.map
303 * @param {Function} options.validateAnchorName - validate anchor name is OK to use
304 */ 306 */
305export const addAnchorByPoint = ({ 307export const addMarkerByPoint = ({ point, map }) => {
306 defaultName,
307 point,
308 map,
309 validateAnchorName = () => true,
310}) => {
311 const rect = map.getBoundingClientRect() 308 const rect = map.getBoundingClientRect()
312 const [x, y] = map.renderer 309 const [lon, lat] = map.renderer
313 .unproject([point.x - rect.left, point.y - rect.top]) 310 .unproject([point[0] - rect.left, point[1] - rect.top])
314 .map(coord => parseFloat(coord.toFixed(6))) 311 .map(value => parseFloat(value.toFixed(6)))
315
316 let prompt
317 let anchorName
318
319 do {
320 prompt = prompt ? 'Anchor name exists' : 'Name this anchor'
321 anchorName = window.prompt(prompt, defaultName ?? '')
322 }
323 while (anchorName !== null && !validateAnchorName(anchorName))
324 if (anchorName === null) return
325
326 const desc = window.prompt('Description', anchorName) ?? anchorName
327 312
328 const link = `geo:${y},${x}?xy=${x},${y}&id=${map.id}&type=circle`
329 const marker = map.renderer.addMarker({ 313 const marker = map.renderer.addMarker({
330 xy: [x, y], 314 xy: [lon, lat],
331 type: 'circle', 315 type: 'circle',
332 }) 316 })
333 marker.dataset.xy = `${x},${y}` 317 marker.dataset.xy = `${lon},${lat}`
334 318
335 return { ref: anchorName, link, title: desc } 319 return marker
336} 320}
337 321
338/** 322/**
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index 4329bbf..a35a2d6 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -563,84 +563,79 @@ export const generateMaps = (container, {
563 ) 563 )
564 564
565 /** Drag/Drop on map for new GeoLink */ 565 /** Drag/Drop on map for new GeoLink */
566 const pointByArrow = document.createElement('div')
567 pointByArrow.className = 'point-by-arrow'
568 container.appendChild(pointByArrow)
569 container.ondragstart = () => false
566 container.onmousedown = (e) => { 570 container.onmousedown = (e) => {
567 // Check should start drag event for GeoLink 571 // Check should start drag event for GeoLink
568 const selection = document.getSelection() 572 const selection = document.getSelection()
569 if (e.which !== 1 || selection.type !== 'Range') return 573 if (e.which !== 1 || selection.type !== 'Range') return
570 574
575 // Check if click is inside selection
571 const range = selection.getRangeAt(0) 576 const range = selection.getRangeAt(0)
572 const originContent = range.cloneContents()
573 const rect = range.getBoundingClientRect() 577 const rect = range.getBoundingClientRect()
574 const mouseInRange = e.x < rect.right && e.x > rect.left && e.y < rect.bottom && e.y > rect.top 578 const mouseInRange = e.clientX < rect.right && e.clientX > rect.left && e.clientY < rect.bottom && e.clientY > rect.top
575 if (!mouseInRange) return 579 if (!mouseInRange) return
576 580
581 // link placeholder when dragging
582 container.classList.add('dragging-geolink')
577 const geoLink = document.createElement('a') 583 const geoLink = document.createElement('a')
578 geoLink.textContent = range.toString() 584 geoLink.textContent = range.toString()
579 geoLink.classList.add('with-leader-line', 'geolink', 'drag') 585 geoLink.classList.add('with-leader-line', 'geolink', 'drag')
586
587 // Replace current content with link
588 const originContent = range.cloneContents()
589 const resumeContent = () => {
590 range.deleteContents()
591 range.insertNode(originContent)
592 }
580 range.deleteContents() 593 range.deleteContents()
581 range.insertNode(geoLink) 594 range.insertNode(geoLink)
582 595
583 const lineEnd = document.createElement('div') 596 // Add leader-line
584 lineEnd.className = 'arrow-head'
585 const offsetRect = container.getBoundingClientRect()
586 lineEnd.style.cssText = `
587 position: absolute;
588 padding: 5px;
589 left: ${e.clientX}px;
590 top: ${e.clientY}px;
591 transform: translate(-${offsetRect.left + 5}px, -${offsetRect.top + 5}px);`
592 container.appendChild(lineEnd)
593
594 const line = new LeaderLine({ 597 const line = new LeaderLine({
595 start: geoLink, 598 start: geoLink,
596 end: lineEnd, 599 end: pointByArrow,
597 path: 'magnet', 600 path: 'magnet',
598 }) 601 })
599 602
600 function onMouseMove (event) { 603 // Update leader-line with mouse move
601 lineEnd.style.left = event.clientX + 'px' 604 container.onmousemove = (event) => {
602 lineEnd.style.top = event.clientY + 'px' 605 const rect = container.getBoundingClientRect()
606 pointByArrow.style.left = `${event.clientX - rect.left}px`
607 pointByArrow.style.top = `${event.clientY - rect.top}px`
603 line.position() 608 line.position()
604 609
605 // TODO Scroll dumbymap.htmlHolder when cursor is at upper/lower side 610 // TODO Scroll dumbymap.htmlHolder when cursor is at upper/lower side
606 } 611 }
612 container.onmousemove(e)
607 613
608 container.classList.add('dragging-geolink') 614 // Handler for dragend
609 container.onmousemove = onMouseMove 615 container.onmouseup = (e) => {
610 container.onmouseup = function (e) {
611 container.classList.remove('dragging-geolink') 616 container.classList.remove('dragging-geolink')
612 container.onmousemove = null 617 container.onmousemove = null
613 container.onmouseup = null 618 container.onmouseup = null
614 geoLink.classList.remove('drag') 619 geoLink.classList.remove('drag')
615 line?.remove() 620 line.remove()
616 lineEnd.remove()
617 const resumeContent = () => {
618 range.deleteContents()
619 range.insertNode(originContent)
620 }
621 621
622 const map = document.elementFromPoint(e.clientX, e.clientY) 622 const map = document.elementFromPoint(e.clientX, e.clientY)
623 .closest('.mapclay[data-render="fulfilled"]') 623 .closest('.mapclay[data-render="fulfilled"]')
624 if (!map) { 624 if (!map) {
625 resumeContent('map/selection') 625 resumeContent()
626 return 626 return
627 } 627 }
628 628
629 const refLink = utils.addAnchorByPoint({ 629 const marker = utils.addMarkerByPoint({ point: [e.clientX, e.clientY], map })
630 defaultName: geoLink.textContent, 630 if (!marker) {
631 point: e, 631 resumeContent()
632 map,
633 })
634 if (!refLink) {
635 resumeContent('reflink')
636 return 632 return
637 } 633 }
638 634
639 geoLink.href = refLink.link 635 geoLink.href = `geo:${marker.dataset.xy.split(',').reverse()}`
640 utils.createGeoLink(geoLink) 636 utils.createGeoLink(geoLink)
641 } 637 }
642 } 638 }
643 container.ondragstart = () => false
644 639
645 return Object.seal(dumbymap) 640 return Object.seal(dumbymap)
646} 641}
diff --git a/src/editor.mjs b/src/editor.mjs
index e2d3d6d..c258c3d 100644
--- a/src/editor.mjs
+++ b/src/editor.mjs
@@ -2,7 +2,7 @@
2import { markdown2HTML, generateMaps } from './dumbymap' 2import { markdown2HTML, generateMaps } from './dumbymap'
3import { defaultAliases, parseConfigsFromYaml } from 'mapclay' 3import { defaultAliases, parseConfigsFromYaml } from 'mapclay'
4import * as menuItem from './MenuItem' 4import * as menuItem from './MenuItem'
5import { addAnchorByPoint } from './dumbyUtils.mjs' 5import { addMarkerByPoint } from './dumbyUtils.mjs'
6import { shiftByWindow } from './utils.mjs' 6import { shiftByWindow } from './utils.mjs'
7import * as tutorial from './tutorial' 7import * as tutorial from './tutorial'
8 8
@@ -463,8 +463,18 @@ const menuForEditor = (event, menu) => {
463 if (map) { 463 if (map) {
464 const item = new menuItem.Item({ 464 const item = new menuItem.Item({
465 text: 'Add Anchor', 465 text: 'Add Anchor',
466 onclick: (event) => { 466 onclick: () => {
467 const refLink = addAnchorByPoint({ point: event, map, validateAnchorName }) 467 let anchorName
468 do {
469 anchorName = window.prompt(anchorName ? 'Name exists' : 'Name of Anchor')
470 } while (refLinks.find(ref => ref === anchorName))
471 if (anchorName === null) return
472
473 const marker = addMarkerByPoint({ point: [event.clientX, event.clientY], map })
474 const refLink = {
475 ref: anchorName,
476 link: `geo:${marker.dataset.xy.split(',').reverse()}`,
477 }
468 appendRefLink(cm, refLink) 478 appendRefLink(cm, refLink)
469 }, 479 },
470 }) 480 })