diff options
-rw-r--r-- | src/css/dumbymap.css | 15 | ||||
-rw-r--r-- | src/dumbyUtils.mjs | 46 | ||||
-rw-r--r-- | src/dumbymap.mjs | 67 | ||||
-rw-r--r-- | src/editor.mjs | 16 |
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 | */ |
305 | export const addAnchorByPoint = ({ | 307 | export 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 @@ | |||
2 | import { markdown2HTML, generateMaps } from './dumbymap' | 2 | import { markdown2HTML, generateMaps } from './dumbymap' |
3 | import { defaultAliases, parseConfigsFromYaml } from 'mapclay' | 3 | import { defaultAliases, parseConfigsFromYaml } from 'mapclay' |
4 | import * as menuItem from './MenuItem' | 4 | import * as menuItem from './MenuItem' |
5 | import { addAnchorByPoint } from './dumbyUtils.mjs' | 5 | import { addMarkerByPoint } from './dumbyUtils.mjs' |
6 | import { shiftByWindow } from './utils.mjs' | 6 | import { shiftByWindow } from './utils.mjs' |
7 | import * as tutorial from './tutorial' | 7 | import * 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 | }) |