diff options
author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-04 21:17:13 +0800 |
---|---|---|
committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-04 21:17:14 +0800 |
commit | 9a0fdb914d50c32f062de58704b40ea1ceb23203 (patch) | |
tree | 32c429bcdefede5a19694d914c5bb9aa588b1ce9 /src/dumbyUtils.mjs | |
parent | cb0056b995c7e458d11ef363cb5ef452b214e735 (diff) |
refactor: add GeoLinks
* In current implementation, marks are generated in the first time link
is hovered. This prevent complexity since rendering maps is async.
Diffstat (limited to 'src/dumbyUtils.mjs')
-rw-r--r-- | src/dumbyUtils.mjs | 153 |
1 files changed, 122 insertions, 31 deletions
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index 5182a8f..c7b45f4 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs | |||
@@ -1,11 +1,12 @@ | |||
1 | import LeaderLine from 'leader-line' | 1 | import LeaderLine from 'leader-line' |
2 | import { insideWindow, insideParent } from './utils' | ||
2 | 3 | ||
3 | /** | 4 | /** |
4 | * focusNextMap. | 5 | * focusNextMap. |
5 | * | 6 | * |
6 | * @param {Boolean} reverse -- focus previous map | 7 | * @param {Boolean} reverse -- focus previous map |
7 | */ | 8 | */ |
8 | export function focusNextMap (reverse = false) { | 9 | export function focusNextMap(reverse = false) { |
9 | const renderedList = this.utils.renderedMaps() | 10 | const renderedList = this.utils.renderedMaps() |
10 | const index = renderedList.findIndex(e => e.classList.contains('focus')) | 11 | const index = renderedList.findIndex(e => e.classList.contains('focus')) |
11 | const nextIndex = (index + (reverse ? -1 : 1)) % renderedList.length | 12 | const nextIndex = (index + (reverse ? -1 : 1)) % renderedList.length |
@@ -20,7 +21,7 @@ export function focusNextMap (reverse = false) { | |||
20 | * | 21 | * |
21 | * @param {Boolean} reverse -- focus previous block | 22 | * @param {Boolean} reverse -- focus previous block |
22 | */ | 23 | */ |
23 | export function focusNextBlock (reverse = false) { | 24 | export function focusNextBlock(reverse = false) { |
24 | const blocks = this.blocks.filter(b => | 25 | const blocks = this.blocks.filter(b => |
25 | b.checkVisibility({ | 26 | b.checkVisibility({ |
26 | contentVisibilityAuto: true, | 27 | contentVisibilityAuto: true, |
@@ -55,7 +56,7 @@ export const scrollToBlock = block => { | |||
55 | /** | 56 | /** |
56 | * focusDelay. Delay of throttle, value changes by cases | 57 | * focusDelay. Delay of throttle, value changes by cases |
57 | */ | 58 | */ |
58 | export function focusDelay () { | 59 | export function focusDelay() { |
59 | return window.window.getComputedStyle(this.showcase).display === 'none' ? 50 : 300 | 60 | return window.window.getComputedStyle(this.showcase).display === 'none' ? 50 : 300 |
60 | } | 61 | } |
61 | 62 | ||
@@ -64,7 +65,7 @@ export function focusDelay () { | |||
64 | * | 65 | * |
65 | * @param {Boolean} reverse -- Switch to previous one | 66 | * @param {Boolean} reverse -- Switch to previous one |
66 | */ | 67 | */ |
67 | export function switchToNextLayout (reverse = false) { | 68 | export function switchToNextLayout(reverse = false) { |
68 | const layouts = this.layouts | 69 | const layouts = this.layouts |
69 | const currentLayoutName = this.container.getAttribute('data-layout') | 70 | const currentLayoutName = this.container.getAttribute('data-layout') |
70 | const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName) | 71 | const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName) |
@@ -80,27 +81,68 @@ export function switchToNextLayout (reverse = false) { | |||
80 | /** | 81 | /** |
81 | * removeBlockFocus. | 82 | * removeBlockFocus. |
82 | */ | 83 | */ |
83 | export function removeBlockFocus () { | 84 | export function removeBlockFocus() { |
84 | this.blocks.forEach(b => b.classList.remove('focus')) | 85 | this.blocks.forEach(b => b.classList.remove('focus')) |
85 | } | 86 | } |
86 | 87 | ||
87 | /** | 88 | /** |
89 | * getMarkersFromMaps. Get marker elements by GeoLink | ||
90 | * | ||
91 | * @param {HTMLAnchorElement} link | ||
92 | * @return {HTMLElement[]} markers | ||
93 | */ | ||
94 | const getMarkersFromMaps = link => { | ||
95 | const maps = Array.from( | ||
96 | link.closest('.Dumby') | ||
97 | .querySelectorAll('.mapclay[data-render="fulfilled"]') | ||
98 | ) | ||
99 | return maps | ||
100 | .filter(map => link.targets ? link.targets.includes(map.id) : true) | ||
101 | .map(map => { | ||
102 | const renderer = map.renderer | ||
103 | |||
104 | return map.querySelector(`.marker[title="${link.title}"]`) ?? | ||
105 | renderer.addMarker({ | ||
106 | xy: link.xy, | ||
107 | title: link.title | ||
108 | }) | ||
109 | }) | ||
110 | } | ||
111 | |||
112 | /** | ||
113 | * addLeaderLine, from link element to target element | ||
114 | * | ||
115 | * @param {HTMLAnchorElement} link | ||
116 | * @param {Element} target | ||
117 | */ | ||
118 | const addLeaderLine = (link, target) => { | ||
119 | const line = new LeaderLine({ | ||
120 | start: link, | ||
121 | end: target, | ||
122 | hide: true, | ||
123 | middleLabel: link.url.searchParams.get('text'), | ||
124 | path: 'magnet' | ||
125 | }) | ||
126 | line.show('draw', { duration: 300 }) | ||
127 | |||
128 | return line | ||
129 | } | ||
130 | |||
131 | /** | ||
88 | * Create geolinks, which points to map by geo schema and id | 132 | * Create geolinks, which points to map by geo schema and id |
89 | * | 133 | * |
90 | * @param {HTMLElement} Elements contains anchor elements for doclinks | 134 | * @param {HTMLElement} Elements contains anchor elements for doclinks |
91 | * @returns {Boolean} ture is link is created, false if coordinates are invalid | 135 | * @returns {Boolean} ture is link is created, false if coordinates are invalid |
92 | */ | 136 | */ |
93 | export const createGeoLink = (link, callback = null) => { | 137 | export const createGeoLink = (link) => { |
94 | const url = new URL(link.href) | 138 | const url = new URL(link.href) |
95 | const xyInParams = url.searchParams.get('xy') | 139 | const xyInParams = url.searchParams.get('xy')?.split(',')?.map(Number) |
96 | const xy = xyInParams | 140 | const xy = xyInParams ?? url?.href |
97 | ? xyInParams.split(',')?.map(Number) | 141 | ?.match(/^geo:([0-9.,]+)/) |
98 | : url?.href | 142 | ?.at(1) |
99 | ?.match(/^geo:([0-9.,]+)/) | 143 | ?.split(',') |
100 | ?.at(1) | 144 | ?.reverse() |
101 | ?.split(',') | 145 | ?.map(Number) |
102 | ?.reverse() | ||
103 | ?.map(Number) | ||
104 | 146 | ||
105 | if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false | 147 | if (!xy || isNaN(xy[0]) || isNaN(xy[1])) return false |
106 | 148 | ||
@@ -110,10 +152,23 @@ export const createGeoLink = (link, callback = null) => { | |||
110 | link.classList.add('with-leader-line', 'geolink') | 152 | link.classList.add('with-leader-line', 'geolink') |
111 | link.targets = link.url.searchParams.get('id')?.split(',') ?? null | 153 | link.targets = link.url.searchParams.get('id')?.split(',') ?? null |
112 | 154 | ||
113 | // LeaderLine | ||
114 | link.lines = [] | 155 | link.lines = [] |
115 | callback?.call(this, link) | ||
116 | 156 | ||
157 | // LeaderLine | ||
158 | link.onmouseover = () => { | ||
159 | const anchors = getMarkersFromMaps(link) | ||
160 | anchors | ||
161 | .filter(isAnchorVisible) | ||
162 | .forEach(anchor => { | ||
163 | const line = addLeaderLine(link, anchor) | ||
164 | link.lines.push(line) | ||
165 | }) | ||
166 | } | ||
167 | link.onmouseout = () => removeLeaderLines(link) | ||
168 | link.onclick = (event) => { | ||
169 | event.preventDefault() | ||
170 | getMarkersFromMaps(link).forEach(updateMapCameraByMarker(link.xy)) | ||
171 | } | ||
117 | return true | 172 | return true |
118 | } | 173 | } |
119 | 174 | ||
@@ -129,24 +184,60 @@ export const createDocLink = link => { | |||
129 | link.onmouseover = () => { | 184 | link.onmouseover = () => { |
130 | const label = decodeURIComponent(link.href.split('#')[1]) | 185 | const label = decodeURIComponent(link.href.split('#')[1]) |
131 | const selector = link.title.split('=>')[1] ?? '#' + label | 186 | const selector = link.title.split('=>')[1] ?? '#' + label |
132 | const target = document.querySelector(selector) | 187 | const targets = document.querySelectorAll(selector) |
133 | if (!target?.checkVisibility()) return | 188 | |
134 | 189 | targets.forEach(target => { | |
135 | const line = new LeaderLine({ | 190 | if (!target?.checkVisibility()) return |
136 | start: link, | 191 | |
137 | end: target, | 192 | const line = new LeaderLine({ |
138 | middleLabel: LeaderLine.pathLabel({ | 193 | start: link, |
139 | text: label, | 194 | end: target, |
140 | fontWeight: 'bold' | 195 | middleLabel: LeaderLine.pathLabel({ |
141 | }), | 196 | text: label, |
142 | hide: true, | 197 | fontWeight: 'bold' |
143 | path: 'magnet' | 198 | }), |
199 | hide: true, | ||
200 | path: 'magnet' | ||
201 | }) | ||
202 | link.lines.push(line) | ||
203 | line.show('draw', { duration: 300 }) | ||
144 | }) | 204 | }) |
145 | link.lines.push(line) | ||
146 | line.show('draw', { duration: 300 }) | ||
147 | } | 205 | } |
148 | link.onmouseout = () => { | 206 | link.onmouseout = () => { |
149 | link.lines.forEach(line => line.remove()) | 207 | link.lines.forEach(line => line.remove()) |
150 | link.lines.length = 0 | 208 | link.lines.length = 0 |
151 | } | 209 | } |
152 | } | 210 | } |
211 | |||
212 | /** | ||
213 | * removeLeaderLines. clean lines start from link | ||
214 | * | ||
215 | * @param {HTMLAnchorElement} link | ||
216 | */ | ||
217 | const removeLeaderLines = link => { | ||
218 | if (!link.lines) return | ||
219 | link.lines.forEach(line => line.remove()) | ||
220 | link.lines = [] | ||
221 | } | ||
222 | |||
223 | /** | ||
224 | * updateMapByMarker. get function for updating map camera by marker | ||
225 | * | ||
226 | * @param {Number[]} xy | ||
227 | * @return {Function} function | ||
228 | */ | ||
229 | const updateMapCameraByMarker = xy => marker => { | ||
230 | console.log('update') | ||
231 | const renderer = marker.closest('.mapclay')?.renderer | ||
232 | renderer.updateCamera({ center: xy }, true) | ||
233 | } | ||
234 | |||
235 | /** | ||
236 | * isAnchorVisible. check anchor(marker) is visible for current map camera | ||
237 | * | ||
238 | * @param {Element} anchor | ||
239 | */ | ||
240 | const isAnchorVisible = anchor => { | ||
241 | const mapContainer = anchor.closest('.mapclay') | ||
242 | return insideWindow(anchor) && insideParent(anchor, mapContainer) | ||
243 | } | ||