aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-10-25 23:46:30 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-10-26 00:31:21 +0800
commit2a2acc8e31aef538a8e68e6b53cacafb38841c26 (patch)
tree3e6101a4b7090b57342df9e31db4cb184b8e8116 /src
parent587ba9e22cdbc48f675e77ec6572520a0cd12a58 (diff)
refactor: patch 1dd8064, remove classes for GeoLink/DocLink
Seems link content script don't accecpt custom element access its own properties. This commit revert part of 1dd8064, generate GeoLink/DocLonk by appending features onto existing anchor element
Diffstat (limited to 'src')
-rw-r--r--src/Link.mjs366
-rw-r--r--src/MenuItem.mjs6
-rw-r--r--src/dumbyUtils.mjs2
-rw-r--r--src/dumbymap.mjs10
4 files changed, 188 insertions, 196 deletions
diff --git a/src/Link.mjs b/src/Link.mjs
index 9b58fb3..031b125 100644
--- a/src/Link.mjs
+++ b/src/Link.mjs
@@ -2,212 +2,204 @@ import LeaderLine from 'leader-line'
2import { insideWindow, insideParent } from './utils' 2import { insideWindow, insideParent } from './utils'
3import * as markers from './marker.mjs' 3import * as markers from './marker.mjs'
4 4
5/**
6 * GeoLink: anchor element with geo scheme and properties about maps
7 * @typedef {Object} GeoLink
8 * @extends HTMLAnchorElement
9 * @property {string[]} targets - ids of target map elements
10 * @property {LeaderLine[]} lines
11 * @property {Object} dataset
12 * @property {string} dataset.lon - longitude string of geo scheme
13 * @property {string} dataset.lat - latitude string of geo scheme
14 * @property {string} dataset.crs - short name of CRS in EPSG/ESRI format
15 */
16
17/**
18 * DocLink: anchor element which points to DOM node by filter
19 * @typedef {Object} DocLink
20 * @extends HTMLAnchorElement
21 * @property {LeaderLine[]} lines
22 */
23
5/** VAR: pattern for coodinates */ 24/** VAR: pattern for coodinates */
6export const coordPattern = /^geo:([-]?[0-9.]+),([-]?[0-9.]+)/ 25export const coordPattern = /^geo:([-]?[0-9.]+),([-]?[0-9.]+)/
7 26
8/** 27/**
9 * Class: GeoLink - link for maps 28 * GeoLink: append GeoLink features onto anchor element
10 * 29 * @param {HTMLAnchorElement} link
11 * @extends {window.HTMLAnchorElement} 30 * @return {GeoLink}
12 */ 31 */
13export class GeoLink extends window.HTMLAnchorElement { 32export const GeoLink = (link) => {
14 static replaceWith = (link) => 33 const url = new URL(link.href)
15 link.replaceWith(new GeoLink(link)) 34 const params = new URLSearchParams(link.search)
16 35 const xyInParams = params.get('xy')?.split(',')?.map(Number)
17 /** 36 const [lon, lat] = url.href
18 * Creates a new GeoLink instance 37 ?.match(coordPattern)
19 * 38 ?.slice(1)
20 * @param {HTMLAnchorElement} link 39 ?.reverse()
21 */ 40 ?.map(Number)
22 constructor (link) { 41 const xy = xyInParams ?? [lon, lat]
23 super() 42
24 this.innerHTML = link.innerHTML 43 if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false
25 this.href = link.href 44
26 45 // Geo information in link
27 const url = new URL(link.href) 46 link.dataset.lon = lon
28 const params = new URLSearchParams(link.search) 47 link.dataset.lat = lat
29 const xyInParams = params.get('xy')?.split(',')?.map(Number) 48 link.dataset.crs = params.get('crs')
30 const [lon, lat] = url.href 49 link.classList.add('with-leader-line', 'geolink')
31 ?.match(coordPattern) 50 link.classList.remove('not-geolink')
32 ?.slice(1) 51 // TODO refactor as data attribute
33 ?.reverse() 52 link.title = 'Left-Click to move Camera, Middle-Click to clean anchor'
34 ?.map(Number) 53 link.targets = params.get('id')?.split(',') ?? null
35 const xy = xyInParams ?? [lon, lat] 54 link.lines = []
36
37 if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false
38
39 // Geo information in link
40 this.dataset.lon = lon
41 this.dataset.lat = lat
42 this.dataset.crs = params.get('crs')
43 this.classList.add('with-leader-line', 'geolink')
44 this.classList.remove('not-geolink')
45 // TODO refactor as data attribute
46 this.targets = params.get('id')?.split(',') ?? null
47 this.title = 'Left-Click to move Camera, Middle-Click to clean anchor'
48 this.lines = []
49
50 // Hover link for LeaderLine
51 this.onmouseover = () => this.getMarkersFromMaps()
52 .filter(isAnchorVisible)
53 .forEach(anchor => {
54 const labelText = new URL(this).searchParams.get('text') ?? this.textContent
55 const line = new LeaderLine({
56 start: this,
57 end: anchor,
58 hide: true,
59 middleLabel: labelText,
60 path: 'magnet',
61 })
62 line.show('draw', { duration: 300 })
63
64 this.lines.push(line)
65 })
66 55
67 this.onmouseout = () => removeLeaderLines(this) 56 // Hover link for LeaderLine
68 57 link.onmouseover = () => getMarkersFromMaps(link)
69 // Click to move camera 58 .filter(isAnchorVisible)
70 this.onclick = (event) => { 59 .forEach(anchor => {
71 event.preventDefault() 60 const labelText = new URL(link).searchParams.get('text') ?? link.textContent
72 removeLeaderLines(this) 61 const line = new LeaderLine({
73 this.getMarkersFromMaps().forEach(marker => { 62 start: link,
74 const map = marker.closest('.mapclay') 63 end: anchor,
75 map.scrollIntoView({ behavior: 'smooth' }) 64 hide: true,
76 updateMapCameraByMarker([ 65 middleLabel: labelText,
77 Number(this.dataset.lon), 66 path: 'magnet',
78 Number(this.dataset.lat),
79 ])(marker)
80 }) 67 })
81 } 68 line.show('draw', { duration: 300 })
82 69
83 // Use middle click to remove markers 70 link.lines.push(line)
84 this.onauxclick = (e) => { 71 })
85 if (e.which !== 2) return 72
86 e.preventDefault() 73 link.onmouseout = () => removeLeaderLines(link)
87 removeLeaderLines(this) 74
88 this.getMarkersFromMaps() 75 // Click to move camera
89 .forEach(marker => marker.remove()) 76 link.onclick = (event) => {
90 } 77 event.preventDefault()
78 removeLeaderLines(link)
79 getMarkersFromMaps(link).forEach(marker => {
80 const map = marker.closest('.mapclay')
81 map.scrollIntoView({ behavior: 'smooth' })
82 updateMapCameraByMarker([
83 Number(link.dataset.lon),
84 Number(link.dataset.lat),
85 ])(marker)
86 })
91 } 87 }
92 88
93 /** 89 // Use middle click to remove markers
94 * getMarkersFromMaps. Get marker elements by GeoLink 90 link.onauxclick = (e) => {
95 * 91 if (e.which !== 2) return
96 * @param {HTMLAnchorElement} link 92 e.preventDefault()
97 * @return {HTMLElement[]} markers 93 removeLeaderLines(link)
98 */ 94 getMarkersFromMaps(link)
99 getMarkersFromMaps () { 95 .forEach(marker => marker.remove())
100 const params = new URLSearchParams(this.search)
101 const maps = Array.from(
102 this.closest('.Dumby')
103 .querySelectorAll('.mapclay[data-render="fulfilled"]'),
104 )
105 return maps
106 .filter(map => this.targets ? this.targets.includes(map.id) : true)
107 .map(map => {
108 const renderer = map.renderer
109 const lonLat = [Number(this.dataset.lon), Number(this.dataset.lat)]
110 const type = params.get('type') ?? 'pin'
111 const svg = markers[type]
112 const element = document.createElement('div')
113 element.style.cssText = `width: ${svg.size[0]}px; height: ${svg.size[1]}px;`
114 element.innerHTML = svg.html
115
116 const marker = map.querySelector(`.marker[data-xy="${lonLat}"]`) ??
117 renderer.addMarker({
118 xy: lonLat,
119 element,
120 type,
121 anchor: svg.anchor,
122 size: svg.size,
123 })
124 marker.dataset.xy = lonLat
125 marker.title = new URLSearchParams(this.search).get('xy') ?? lonLat
126 const crs = this.dataset.crs
127 if (crs && crs !== 'EPSG:4326') {
128 marker.title += '@' + this.dataset.crs
129 }
130
131 return marker
132 })
133 } 96 }
134} 97
135if (!window.customElements.get('dumby-geolink')) { 98 return link
136 window.customElements.define('dumby-geolink', GeoLink, { extends: 'a' })
137} 99}
138 100
139/** 101/**
140 * Class: DocLink - link for DOM 102 * GeoLink: getMarkersFromMaps. Get marker elements by GeoLink
141 * 103 *
142 * @extends {window.HTMLAnchorElement} 104 * @param {GeoLink} link
105 * @return {HTMLElement[]} markers
143 */ 106 */
144export class DocLink extends window.HTMLAnchorElement { 107export const getMarkersFromMaps = (link) => {
145 static replaceWith = (link) => 108 const params = new URLSearchParams(link.search)
146 link.replaceWith(new DocLink(link)) 109 const maps = Array.from(
147 110 link.closest('.Dumby')
148 /** 111 .querySelectorAll('.mapclay[data-render="fulfilled"]'),
149 * Creates a new DocLink instance 112 )
150 * 113 return maps
151 * @param {HTMLAnchorElement} link 114 .filter(map => link.targets ? link.targets.includes(map.id) : true)
152 */ 115 .map(map => {
153 constructor (link) { 116 const renderer = map.renderer
154 super() 117 const lonLat = [Number(link.dataset.lon), Number(link.dataset.lat)]
155 this.innerHTML = link.innerHTML 118 const type = params.get('type') ?? 'pin'
156 this.href = link.href 119 const svg = markers[type]
157 120 const element = document.createElement('div')
158 const label = decodeURIComponent(link.href.split('#')[1]) 121 element.style.cssText = `width: ${svg.size[0]}px; height: ${svg.size[1]}px;`
159 const selector = link.title.split('=>')[1] ?? (label ? '#' + label : null) 122 element.innerHTML = svg.html
160 if (!selector) return false 123
161 124 const marker = map.querySelector(`.marker[data-xy="${lonLat}"]`) ??
162 this.classList.add('with-leader-line', 'doclink') 125 renderer.addMarker({
163 this.lines = [] 126 xy: lonLat,
164 127 element,
165 this.onmouseover = () => { 128 // FIXME In conten script, leaflet cannot render marker with HTMLElement
166 const targets = document.querySelectorAll(selector) 129 // so pass html string here
167 130 html: svg.html,
168 targets.forEach(target => { 131 type,
169 if (!target?.checkVisibility()) return 132 anchor: svg.anchor,
170 133 size: svg.size,
171 // highlight selected target
172 target.dataset.style = target.style.cssText
173 const rect = target.getBoundingClientRect()
174 const isTiny = rect.width < 100 || rect.height < 100
175 if (isTiny) {
176 target.style.background = 'lightPink'
177 } else {
178 target.style.outline = 'lightPink 6px dashed'
179 }
180
181 // point to selected target
182 const line = new LeaderLine({
183 start: this,
184 end: target,
185 middleLabel: LeaderLine.pathLabel({
186 text: label,
187 fontWeight: 'bold',
188 }),
189 hide: true,
190 path: 'magnet',
191 }) 134 })
192 this.lines.push(line) 135 marker.dataset.xy = lonLat
193 line.show('draw', { duration: 300 }) 136 marker.title = new URLSearchParams(link.search).get('xy') ?? lonLat
194 }) 137 const crs = link.dataset.crs
195 } 138 if (crs && crs !== 'EPSG:4326') {
139 marker.title += '@' + link.dataset.crs
140 }
141
142 return marker
143 })
144}
145
146/**
147 * DocLink: append DocLink features onto anchor element
148 * @param {HTMLAnchorElement} link
149 * @return {DocLink}
150 */
151export const DocLink = (link) => {
152 const label = decodeURIComponent(link.href.split('#')[1])
153 const selector = link.title.split('=>')[1] ?? (label ? '#' + label : null)
154 if (!selector) return false
196 155
197 this.onmouseout = () => { 156 link.classList.add('with-leader-line', 'doclink')
198 removeLeaderLines(this) 157 link.lines = []
199 158
200 // resume targets from highlight 159 link.onmouseover = () => {
201 const targets = document.querySelectorAll(selector) 160 const targets = document.querySelectorAll(selector)
202 targets.forEach(target => { 161
203 target.style.cssText = target.dataset.style 162 targets.forEach(target => {
204 delete target.dataset.style 163 if (!target?.checkVisibility()) return
164
165 // highlight selected target
166 target.dataset.style = target.style.cssText
167 const rect = target.getBoundingClientRect()
168 const isTiny = rect.width < 100 || rect.height < 100
169 if (isTiny) {
170 target.style.background = 'lightPink'
171 } else {
172 target.style.outline = 'lightPink 6px dashed'
173 }
174
175 // point to selected target
176 const line = new LeaderLine({
177 start: link,
178 end: target,
179 middleLabel: LeaderLine.pathLabel({
180 text: label,
181 fontWeight: 'bold',
182 }),
183 hide: true,
184 path: 'magnet',
205 }) 185 })
206 } 186 link.lines.push(line)
187 line.show('draw', { duration: 300 })
188 })
207 } 189 }
208} 190
209if (!window.customElements.get('dumby-doclink')) { 191 link.onmouseout = () => {
210 window.customElements.define('dumby-doclink', DocLink, { extends: 'a' }) 192 removeLeaderLines(link)
193
194 // resume targets from highlight
195 const targets = document.querySelectorAll(selector)
196 targets.forEach(target => {
197 target.style.cssText = target.dataset.style
198 delete target.dataset.style
199 })
200 }
201
202 return link
211} 203}
212 204
213/** 205/**
@@ -234,7 +226,7 @@ const updateMapCameraByMarker = lonLat => marker => {
234/** 226/**
235 * removeLeaderLines. clean lines start from link 227 * removeLeaderLines. clean lines start from link
236 * 228 *
237 * @param {HTMLAnchorElement} link 229 * @param {GeoLink} link
238 */ 230 */
239export const removeLeaderLines = link => { 231export const removeLeaderLines = link => {
240 if (!link.lines) return 232 if (!link.lines) return
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs
index 7b1f41e..4769817 100644
--- a/src/MenuItem.mjs
+++ b/src/MenuItem.mjs
@@ -1,6 +1,6 @@
1import { shiftByWindow } from './utils.mjs' 1import { shiftByWindow } from './utils.mjs'
2/* eslint-disable no-unused-vars */ 2/* eslint-disable no-unused-vars */
3import { GeoLink, removeLeaderLines } from './Link.mjs' 3import { GeoLink, getMarkersFromMaps, removeLeaderLines } from './Link.mjs'
4/* eslint-enable */ 4/* eslint-enable */
5import * as markers from './marker.mjs' 5import * as markers from './marker.mjs'
6 6
@@ -447,9 +447,9 @@ export const setGeoLinkTypeItem = ({ link, type, ...others }) => {
447 params.set('type', type) 447 params.set('type', type)
448 link.search = params 448 link.search = params
449 removeLeaderLines(link) 449 removeLeaderLines(link)
450 link.getMarkersFromMaps() 450 getMarkersFromMaps(link)
451 .forEach(marker => marker.remove()) 451 .forEach(marker => marker.remove())
452 link.getMarkersFromMaps() 452 getMarkersFromMaps(link)
453 }, 453 },
454 }) 454 })
455} 455}
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs
index 0c05973..02cabca 100644
--- a/src/dumbyUtils.mjs
+++ b/src/dumbyUtils.mjs
@@ -238,6 +238,6 @@ export const addGeoLinkByDrag = (container, range, endOfLeaderLine) => {
238 } 238 }
239 239
240 link.href = `geo:${marker.dataset.xy.split(',').reverse()}` 240 link.href = `geo:${marker.dataset.xy.split(',').reverse()}`
241 GeoLink.replaceWith(link) 241 GeoLink(link)
242 } 242 }
243} 243}
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index 3200a05..f1e971b 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -6,7 +6,7 @@ import MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers'
6import * as mapclay from 'mapclay' 6import * as mapclay from 'mapclay'
7import { onRemove, animateRectTransition, throttle, debounce, shiftByWindow } from './utils' 7import { onRemove, animateRectTransition, throttle, debounce, shiftByWindow } from './utils'
8import { Layout, SideBySide, Overlay, Sticky } from './Layout' 8import { Layout, SideBySide, Overlay, Sticky } from './Layout'
9import { GeoLink, DocLink } from './Link.mjs' 9import { GeoLink, DocLink, getMarkersFromMaps } from './Link.mjs'
10import * as utils from './dumbyUtils' 10import * as utils from './dumbyUtils'
11import * as menuItem from './MenuItem' 11import * as menuItem from './MenuItem'
12import PlainModal from 'plain-modal' 12import PlainModal from 'plain-modal'
@@ -246,9 +246,9 @@ export const generateMaps = (container, {
246 246
247 // Add GeoLinks/DocLinks by pattern 247 // Add GeoLinks/DocLinks by pattern
248 target.querySelectorAll(geoLinkSelector) 248 target.querySelectorAll(geoLinkSelector)
249 .forEach(GeoLink.replaceWith) 249 .forEach(GeoLink)
250 target.querySelectorAll(docLinkSelector) 250 target.querySelectorAll(docLinkSelector)
251 .forEach(DocLink.replaceWith) 251 .forEach(DocLink)
252 252
253 // Add GeoLinks from text nodes 253 // Add GeoLinks from text nodes
254 // const addedNodes = Array.from(mutation.addedNodes) 254 // const addedNodes = Array.from(mutation.addedNodes)
@@ -319,7 +319,7 @@ export const generateMaps = (container, {
319 values.at(-1) 319 values.at(-1)
320 .map(utils.setGeoSchemeByCRS(crsString)) 320 .map(utils.setGeoSchemeByCRS(crsString))
321 .filter(link => link) 321 .filter(link => link)
322 .forEach(GeoLink.replaceWith) 322 .forEach(GeoLink)
323 }) 323 })
324 } 324 }
325 325
@@ -557,7 +557,7 @@ export const generateMaps = (container, {
557 menu.appendChild(new menuItem.Item({ 557 menu.appendChild(new menuItem.Item({
558 text: 'Delete', 558 text: 'Delete',
559 onclick: () => { 559 onclick: () => {
560 geoLink.getMarkersFromMaps() 560 getMarkersFromMaps(geoLink)
561 .forEach(m => m.remove()) 561 .forEach(m => m.remove())
562 geoLink.replaceWith( 562 geoLink.replaceWith(
563 document.createTextNode(geoLink.textContent), 563 document.createTextNode(geoLink.textContent),