aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/dumbyUtils.mjs153
-rw-r--r--src/dumbymap.mjs93
-rw-r--r--src/utils.mjs38
3 files changed, 161 insertions, 123 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 @@
1import LeaderLine from 'leader-line' 1import LeaderLine from 'leader-line'
2import { 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 */
8export function focusNextMap (reverse = false) { 9export 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 */
23export function focusNextBlock (reverse = false) { 24export 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 */
58export function focusDelay () { 59export 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 */
67export function switchToNextLayout (reverse = false) { 68export 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 */
83export function removeBlockFocus () { 84export 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 */
94const 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 */
118const 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 */
93export const createGeoLink = (link, callback = null) => { 137export 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 */
217const 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 */
229const 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 */
240const isAnchorVisible = anchor => {
241 const mapContainer = anchor.closest('.mapclay')
242 return insideWindow(anchor) && insideParent(anchor, mapContainer)
243}
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index edc1521..bb98cea 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -152,87 +152,10 @@ export const generateMaps = (container, { delay, mapCallback }) => {
152 utils.createDocLink 152 utils.createDocLink
153 ) 153 )
154 154
155 // Get anchors with "geo:" scheme 155 // Add GeoLinks
156 htmlHolder.anchors = [] 156 container.querySelectorAll(geoLinkSelector)
157 const geoLinkCallback = link => { 157 .forEach(utils.createGeoLink)
158 link.onmouseover = () => addLeaderLines(link)
159 link.onmouseout = () => removeLeaderLines(link)
160 link.onclick = event => {
161 event.preventDefault()
162 htmlHolder.anchors
163 .filter(isAnchorPointedBy(link))
164 .forEach(updateMapByMarker(link.xy))
165 // TODO Just hide leader line and show it again
166 removeLeaderLines(link)
167 }
168 }
169 const geoLinks = Array.from(
170 container.querySelectorAll(geoLinkSelector)
171 ).filter(l => utils.createGeoLink(l, geoLinkCallback))
172
173 const isAnchorPointedBy = link => anchor => {
174 const mapContainer = anchor.closest('.mapclay')
175 const isTarget = !link.targets || link.targets.includes(mapContainer.id)
176 return anchor.title === link.url.searchParams.get('text') && isTarget
177 }
178
179 const isAnchorVisible = anchor => {
180 const mapContainer = anchor.closest('.mapclay')
181 return insideWindow(anchor) && insideParent(anchor, mapContainer)
182 }
183
184 const drawLeaderLine = link => anchor => {
185 const line = new LeaderLine({
186 start: link,
187 end: anchor,
188 hide: true,
189 middleLabel: link.url.searchParams.get('text'),
190 path: 'magnet'
191 })
192 line.show('draw', { duration: 300 })
193 return line
194 }
195
196 const addLeaderLines = link => {
197 link.lines = htmlHolder.anchors
198 .filter(isAnchorPointedBy(link))
199 .filter(isAnchorVisible)
200 .map(drawLeaderLine(link))
201 }
202 158
203 const removeLeaderLines = link => {
204 if (!link.lines) return
205 link.lines.forEach(line => line.remove())
206 link.lines = []
207 }
208
209 const updateMapByMarker = xy => marker => {
210 const renderer = marker.closest('.mapclay')?.renderer
211 renderer.updateCamera({ center: xy }, true)
212 }
213
214 const insideWindow = element => {
215 const rect = element.getBoundingClientRect()
216 return (
217 rect.left > 0 &&
218 rect.right < window.innerWidth + rect.width &&
219 rect.top > 0 &&
220 rect.bottom < window.innerHeight + rect.height
221 )
222 }
223
224 const insideParent = (childElement, parentElement) => {
225 const childRect = childElement.getBoundingClientRect()
226 const parentRect = parentElement.getBoundingClientRect()
227 const offset = 20
228
229 return (
230 childRect.left > parentRect.left + offset &&
231 childRect.right < parentRect.right - offset &&
232 childRect.top > parentRect.top + offset &&
233 childRect.bottom < parentRect.bottom - offset
234 )
235 }
236 // }}} 159 // }}}
237 // CSS observer {{{ 160 // CSS observer {{{
238 // Focus Map {{{ 161 // Focus Map {{{
@@ -378,16 +301,6 @@ export const generateMaps = (container, { delay, mapCallback }) => {
378 301
379 // Execute callback from caller 302 // Execute callback from caller
380 mapCallback?.call(this, mapElement) 303 mapCallback?.call(this, mapElement)
381 const markers = geoLinks
382 .filter(link => !link.targets || link.targets.includes(mapElement.id))
383 .map(link => ({ xy: link.xy, title: link.url.searchParams.get('text') }))
384
385 // FIXME Here may cause error
386 // Add markers with Geolinks
387 renderer.addMarkers(markers)
388 mapElement
389 .querySelectorAll('.marker')
390 .forEach(marker => htmlHolder.anchors.push(marker))
391 } 304 }
392 305
393 // Set unique ID for map container 306 // Set unique ID for map container
diff --git a/src/utils.mjs b/src/utils.mjs
index ffd8978..4eedf82 100644
--- a/src/utils.mjs
+++ b/src/utils.mjs
@@ -72,10 +72,10 @@ export const animateRectTransition = (element, rect, options = {}) => {
72 * @param {Number} delay milliseconds 72 * @param {Number} delay milliseconds
73 * @returns {Any} return value of function call, or null if throttled 73 * @returns {Any} return value of function call, or null if throttled
74 */ 74 */
75export function throttle (func, delay) { 75export function throttle(func, delay) {
76 let timerFlag = null 76 let timerFlag = null
77 77
78 return function (...args) { 78 return function(...args) {
79 const context = this 79 const context = this
80 if (timerFlag !== null) return null 80 if (timerFlag !== null) return null
81 81
@@ -99,3 +99,37 @@ export const shiftByWindow = element => {
99 const offsetY = window.innerHeight - rect.top - rect.height 99 const offsetY = window.innerHeight - rect.top - rect.height
100 element.style.transform = `translate(${offsetX < 0 ? offsetX : 0}px, ${offsetY < 0 ? offsetY : 0}px)` 100 element.style.transform = `translate(${offsetX < 0 ? offsetX : 0}px, ${offsetY < 0 ? offsetY : 0}px)`
101} 101}
102
103/**
104 * insideWindow. check DOMRect is inside window
105 *
106 * @param {HTMLElement} element
107 */
108export const insideWindow = element => {
109 const rect = element.getBoundingClientRect()
110 return (
111 rect.left > 0 &&
112 rect.right < window.innerWidth + rect.width &&
113 rect.top > 0 &&
114 rect.bottom < window.innerHeight + rect.height
115 )
116}
117
118/**
119 * insideParent. check children element is inside DOMRect of parent element
120 *
121 * @param {HTMLElement} childElement
122 * @param {HTMLElement} parentElement
123 */
124export const insideParent = (childElement, parentElement) => {
125 const childRect = childElement.getBoundingClientRect()
126 const parentRect = parentElement.getBoundingClientRect()
127 const offset = 20
128
129 return (
130 childRect.left > parentRect.left + offset &&
131 childRect.right < parentRect.right - offset &&
132 childRect.top > parentRect.top + offset &&
133 childRect.bottom < parentRect.bottom - offset
134 )
135}