aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-10-13 21:30:43 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-10-14 16:57:34 +0800
commit2c9ffa25df5913953553e70426f19cf47832bb45 (patch)
tree10c54538c64dc4fcb3654a658e1fd913b9802cc5
parent9071bd030a0564ce387134d5844e777cb535e28f (diff)
feat: migrate drag feature to dumbymap
-rw-r--r--src/css/dumbymap.css10
-rw-r--r--src/dumbymap.mjs81
-rw-r--r--src/editor.mjs96
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'
11import PlainModal from 'plain-modal' 11import PlainModal from 'plain-modal'
12import proj4 from 'proj4' 12import proj4 from 'proj4'
13import { register, fromEPSGCode } from 'ol/proj/proj4' 13import { register, fromEPSGCode } from 'ol/proj/proj4'
14import LeaderLine from 'leader-line'
14 15
15/** CSS Selector for main components */ 16/** CSS Selector for main components */
16const mapBlockSelector = 'pre:has(.language-map)' 17const 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'
4import * as menuItem from './MenuItem' 4import * as menuItem from './MenuItem'
5import { addAnchorByPoint, createGeoLink } from './dumbyUtils.mjs' 5import { addAnchorByPoint, createGeoLink } from './dumbyUtils.mjs'
6import { shiftByWindow } from './utils.mjs' 6import { shiftByWindow } from './utils.mjs'
7import LeaderLine from 'leader-line'
8import * as tutorial from './tutorial' 7import * as tutorial from './tutorial'
9 8
10/** 9/**
@@ -27,9 +26,11 @@ const context = document.querySelector('[data-mode]')
27const textArea = document.querySelector('.editor textarea') 26const textArea = document.querySelector('.editor textarea')
28const dumbyContainer = document.querySelector('.DumbyMap') 27const dumbyContainer = document.querySelector('.DumbyMap')
29dumbyContainer.dataset.scrollLine = '' 28dumbyContainer.dataset.scrollLine = ''
30/** Watch: Layout of DumbyMap */ 29/** Watch: DumbyMap */
31new window.MutationObserver(mutaions => { 30new 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})
42let dumbymap 45let 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 */
1075dumbyContainer.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
1157dumbyContainer.ondragstart = () => false