diff options
author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-13 21:30:43 +0800 |
---|---|---|
committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-14 16:57:34 +0800 |
commit | 2c9ffa25df5913953553e70426f19cf47832bb45 (patch) | |
tree | 10c54538c64dc4fcb3654a658e1fd913b9802cc5 | |
parent | 9071bd030a0564ce387134d5844e777cb535e28f (diff) |
feat: migrate drag feature to dumbymap
-rw-r--r-- | src/css/dumbymap.css | 10 | ||||
-rw-r--r-- | src/dumbymap.mjs | 81 | ||||
-rw-r--r-- | src/editor.mjs | 96 |
3 files changed, 98 insertions, 89 deletions
diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css index 7ddb21a..1eb607b 100644 --- a/src/css/dumbymap.css +++ b/src/css/dumbymap.css | |||
@@ -764,6 +764,16 @@ root { | |||
764 | .dumby-block > :not(:has(.mapclay[data-render="fulfilled"])) { | 764 | .dumby-block > :not(:has(.mapclay[data-render="fulfilled"])) { |
765 | opacity: 0.3; | 765 | opacity: 0.3; |
766 | } | 766 | } |
767 | |||
768 | * { | ||
769 | cursor: not-allowed !important; | ||
770 | } | ||
771 | |||
772 | .mapclay[data-render="fulfilled"] { | ||
773 | &, & canvas { | ||
774 | cursor: crosshair !important; | ||
775 | } | ||
776 | } | ||
767 | } | 777 | } |
768 | 778 | ||
769 | .Dumby[data-layout='sticky'] { | 779 | .Dumby[data-layout='sticky'] { |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 4cfc484..81a65ca 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -11,6 +11,7 @@ import * as menuItem from './MenuItem' | |||
11 | import PlainModal from 'plain-modal' | 11 | import PlainModal from 'plain-modal' |
12 | import proj4 from 'proj4' | 12 | import proj4 from 'proj4' |
13 | import { register, fromEPSGCode } from 'ol/proj/proj4' | 13 | import { register, fromEPSGCode } from 'ol/proj/proj4' |
14 | import LeaderLine from 'leader-line' | ||
14 | 15 | ||
15 | /** CSS Selector for main components */ | 16 | /** CSS Selector for main components */ |
16 | const mapBlockSelector = 'pre:has(.language-map)' | 17 | const mapBlockSelector = 'pre:has(.language-map)' |
@@ -587,5 +588,85 @@ export const generateMaps = (container, { | |||
587 | document.removeEventListener('click', actionOutsideMenu), | 588 | document.removeEventListener('click', actionOutsideMenu), |
588 | ) | 589 | ) |
589 | 590 | ||
591 | /** Drag/Drop on map for new GeoLink */ | ||
592 | container.onmousedown = (e) => { | ||
593 | // Check should start drag event for GeoLink | ||
594 | const selection = document.getSelection() | ||
595 | if (e.which !== 1 || selection.type !== 'Range') return | ||
596 | |||
597 | const range = selection.getRangeAt(0) | ||
598 | const originContent = range.cloneContents() | ||
599 | const rect = range.getBoundingClientRect() | ||
600 | const mouseInRange = e.x < rect.right && e.x > rect.left && e.y < rect.bottom && e.y > rect.top | ||
601 | if (!mouseInRange) return | ||
602 | |||
603 | const geoLink = document.createElement('a') | ||
604 | geoLink.textContent = range.toString() | ||
605 | geoLink.classList.add('with-leader-line', 'geolink', 'drag') | ||
606 | range.deleteContents() | ||
607 | range.insertNode(geoLink) | ||
608 | |||
609 | const lineEnd = document.createElement('div') | ||
610 | lineEnd.className = 'arrow-head' | ||
611 | const offsetRect = container.getBoundingClientRect() | ||
612 | lineEnd.style.cssText = ` | ||
613 | position: absolute; | ||
614 | padding: 5px; | ||
615 | left: ${e.clientX}px; | ||
616 | top: ${e.clientY}px; | ||
617 | transform: translate(-${offsetRect.left + 5}px, -${offsetRect.top + 5}px);` | ||
618 | container.appendChild(lineEnd) | ||
619 | |||
620 | const line = new LeaderLine({ | ||
621 | start: geoLink, | ||
622 | end: lineEnd, | ||
623 | path: 'magnet', | ||
624 | }) | ||
625 | |||
626 | function onMouseMove (event) { | ||
627 | lineEnd.style.left = event.clientX + 'px' | ||
628 | lineEnd.style.top = event.clientY + 'px' | ||
629 | line.position() | ||
630 | |||
631 | // TODO Scroll dumbymap.htmlHolder when cursor is at upper/lower side | ||
632 | } | ||
633 | |||
634 | container.classList.add('dragging-geolink') | ||
635 | container.onmousemove = onMouseMove | ||
636 | container.onmouseup = function (e) { | ||
637 | container.classList.remove('dragging-geolink') | ||
638 | container.onmousemove = null | ||
639 | container.onmouseup = null | ||
640 | geoLink.classList.remove('drag') | ||
641 | line?.remove() | ||
642 | lineEnd.remove() | ||
643 | const resumeContent = () => { | ||
644 | range.deleteContents() | ||
645 | range.insertNode(originContent) | ||
646 | } | ||
647 | |||
648 | const map = document.elementFromPoint(e.clientX, e.clientY) | ||
649 | .closest('.mapclay[data-render="fulfilled"]') | ||
650 | if (!map) { | ||
651 | resumeContent('map/selection') | ||
652 | return | ||
653 | } | ||
654 | |||
655 | const refLink = utils.addAnchorByPoint({ | ||
656 | defaultName: geoLink.textContent, | ||
657 | point: e, | ||
658 | map, | ||
659 | }) | ||
660 | if (!refLink) { | ||
661 | resumeContent('reflink') | ||
662 | return | ||
663 | } | ||
664 | |||
665 | geoLink.href = refLink.link | ||
666 | utils.createGeoLink(geoLink) | ||
667 | } | ||
668 | } | ||
669 | container.ondragstart = () => false | ||
670 | |||
590 | return Object.seal(dumbymap) | 671 | return Object.seal(dumbymap) |
591 | } | 672 | } |
diff --git a/src/editor.mjs b/src/editor.mjs index a8e3e0d..df6e3bf 100644 --- a/src/editor.mjs +++ b/src/editor.mjs | |||
@@ -4,7 +4,6 @@ import { defaultAliases, parseConfigsFromYaml } from 'mapclay' | |||
4 | import * as menuItem from './MenuItem' | 4 | import * as menuItem from './MenuItem' |
5 | import { addAnchorByPoint, createGeoLink } from './dumbyUtils.mjs' | 5 | import { addAnchorByPoint, createGeoLink } from './dumbyUtils.mjs' |
6 | import { shiftByWindow } from './utils.mjs' | 6 | import { shiftByWindow } from './utils.mjs' |
7 | import LeaderLine from 'leader-line' | ||
8 | import * as tutorial from './tutorial' | 7 | import * as tutorial from './tutorial' |
9 | 8 | ||
10 | /** | 9 | /** |
@@ -27,9 +26,11 @@ const context = document.querySelector('[data-mode]') | |||
27 | const textArea = document.querySelector('.editor textarea') | 26 | const textArea = document.querySelector('.editor textarea') |
28 | const dumbyContainer = document.querySelector('.DumbyMap') | 27 | const dumbyContainer = document.querySelector('.DumbyMap') |
29 | dumbyContainer.dataset.scrollLine = '' | 28 | dumbyContainer.dataset.scrollLine = '' |
30 | /** Watch: Layout of DumbyMap */ | 29 | /** Watch: DumbyMap */ |
31 | new window.MutationObserver(mutaions => { | 30 | new window.MutationObserver(mutations => { |
32 | const mutation = mutaions.at(-1) | 31 | const mutation = mutations.at(-1) |
32 | |||
33 | /** Handle layout change */ | ||
33 | const layout = dumbyContainer.dataset.layout | 34 | const layout = dumbyContainer.dataset.layout |
34 | if (layout !== 'normal' || mutation.oldValue === 'normal') { | 35 | if (layout !== 'normal' || mutation.oldValue === 'normal') { |
35 | context.dataset.mode = '' | 36 | context.dataset.mode = '' |
@@ -38,6 +39,8 @@ new window.MutationObserver(mutaions => { | |||
38 | attributes: true, | 39 | attributes: true, |
39 | attributeFilter: ['data-layout'], | 40 | attributeFilter: ['data-layout'], |
40 | attributeOldValue: true, | 41 | attributeOldValue: true, |
42 | childList: true, | ||
43 | subtree: true, | ||
41 | }) | 44 | }) |
42 | let dumbymap | 45 | let dumbymap |
43 | 46 | ||
@@ -1070,88 +1073,3 @@ document.addEventListener('selectionchange', () => { | |||
1070 | cm.scrollIntoView(focus) | 1073 | cm.scrollIntoView(focus) |
1071 | } | 1074 | } |
1072 | }) | 1075 | }) |
1073 | |||
1074 | /** Drag/Drop on map for new reference style link */ | ||
1075 | dumbyContainer.onmousedown = (e) => { | ||
1076 | // Check should start drag event for GeoLink | ||
1077 | if (e.which !== 1) return | ||
1078 | const selection = document.getSelection() | ||
1079 | if (cm.getSelection() === '' || selection.type !== 'Range') return | ||
1080 | const range = selection.getRangeAt(0) | ||
1081 | const rect = range.getBoundingClientRect() | ||
1082 | const mouseInRange = e.x < rect.right && e.x > rect.left && e.y < rect.bottom && e.y > rect.top | ||
1083 | if (!mouseInRange) return | ||
1084 | |||
1085 | const geoLink = document.createElement('a') | ||
1086 | geoLink.textContent = range.toString() | ||
1087 | geoLink.classList.add('with-leader-line', 'geolink', 'drag') | ||
1088 | const originContent = range.cloneContents() | ||
1089 | range.deleteContents() | ||
1090 | range.insertNode(geoLink) | ||
1091 | |||
1092 | const lineEnd = document.createElement('div') | ||
1093 | lineEnd.style.cssText = `position: absolute; left: ${e.clientX}px; top: ${e.clientY}px;` | ||
1094 | document.body.appendChild(lineEnd) | ||
1095 | |||
1096 | const line = new LeaderLine({ | ||
1097 | start: geoLink, | ||
1098 | end: lineEnd, | ||
1099 | path: 'magnet', | ||
1100 | }) | ||
1101 | |||
1102 | function onMouseMove (event) { | ||
1103 | lineEnd.style.left = event.clientX + 'px' | ||
1104 | lineEnd.style.top = event.clientY + 'px' | ||
1105 | line.position() | ||
1106 | |||
1107 | // TODO Scroll dumbymap.htmlHolder when cursor is at upper/lower side | ||
1108 | } | ||
1109 | |||
1110 | context.classList.add('dragging-geolink') | ||
1111 | dumbyContainer.onmousemove = onMouseMove | ||
1112 | dumbymap.utils.renderedMaps().forEach(map => { map.style.cursor = 'crosshair' }) | ||
1113 | dumbyContainer.onmouseup = function (e) { | ||
1114 | context.classList.remove('dragging-geolink') | ||
1115 | dumbyContainer.onmousemove = null | ||
1116 | dumbyContainer.onmouseup = null | ||
1117 | line?.remove() | ||
1118 | lineEnd.remove() | ||
1119 | dumbymap.utils.renderedMaps().forEach(map => map.style.removeProperty('cursor')) | ||
1120 | const resumeContent = () => { | ||
1121 | range.deleteContents() | ||
1122 | range.insertNode(originContent) | ||
1123 | } | ||
1124 | |||
1125 | const map = document.elementFromPoint(e.clientX, e.clientY) | ||
1126 | .closest('.mapclay[data-render="fulfilled"]') | ||
1127 | const selection = cm.getSelection() | ||
1128 | if (!map || !selection) { | ||
1129 | resumeContent('map/selection') | ||
1130 | return | ||
1131 | } | ||
1132 | |||
1133 | const refLink = addAnchorByPoint({ | ||
1134 | defaultName: geoLink.textContent, | ||
1135 | point: e, | ||
1136 | map, | ||
1137 | validateAnchorName, | ||
1138 | }) | ||
1139 | if (!refLink) { | ||
1140 | resumeContent('reflink') | ||
1141 | return | ||
1142 | } | ||
1143 | |||
1144 | const scrollTop = dumbymap.htmlHolder.scrollTop | ||
1145 | geoLink.href = refLink.link | ||
1146 | createGeoLink(geoLink) | ||
1147 | appendRefLink(cm, refLink) | ||
1148 | if (selection === refLink.ref) { | ||
1149 | cm.replaceSelection(`[${selection}]`) | ||
1150 | } else { | ||
1151 | cm.replaceSelection(`[${selection}][${refLink.ref}]`) | ||
1152 | } | ||
1153 | dumbymap.htmlHolder.scrollBy(0, scrollTop) | ||
1154 | } | ||
1155 | } | ||
1156 | |||
1157 | dumbyContainer.ondragstart = () => false | ||