diff options
| author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-15 00:10:39 +0800 |
|---|---|---|
| committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-15 15:05:55 +0800 |
| commit | c39dcc9b5a9055fec9a58ce833bd9535ba19b086 (patch) | |
| tree | 6be9cb28e7c4f1fc6a0a453a8e1fc00a2cf35b5e | |
| parent | 7dc541019225eeb7d4d9ff678b94d0c54cbf9e5c (diff) | |
feat: patch 2c9ffa2
* addAnchorByPoint -> addMarkerByPoint
just return newly created marker
* Add pointByArrow as general element in container
| -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 | }) |