diff options
| author | Hsieh Chin Fan <pham@topo.tw> | 2024-09-28 17:42:09 +0800 |
|---|---|---|
| committer | Hsieh Chin Fan <pham@topo.tw> | 2024-09-28 17:45:29 +0800 |
| commit | de3cfc866bb6c79ab431325cc91510123bc69699 (patch) | |
| tree | 89ed6e7a5dd9ea4dfd06a55382e5686b55333cf4 | |
| parent | 2dae67f743daef2b00f89f2e4a9080d0cc91f90e (diff) | |
refactor: mapclay v0.8.2
* class name from 'map-container' -> 'mapclay'
* Just append rendered element when semantic HTML is generated from
markdown. Reuse logic is handed by mapclay
* Move logic about geolinks into afterMapRendered for each rendering
(afterEachMapLoaded -> afterMapRendered)
| -rw-r--r-- | package.json | 2 | ||||
| -rw-r--r-- | src/dumbyUtils.mjs | 6 | ||||
| -rw-r--r-- | src/dumbymap.mjs | 97 |
3 files changed, 53 insertions, 52 deletions
diff --git a/package.json b/package.json index 53defc4..e55d489 100644 --- a/package.json +++ b/package.json | |||
| @@ -50,7 +50,7 @@ | |||
| 50 | "dependencies": { | 50 | "dependencies": { |
| 51 | "easymde": "^2.18.0", | 51 | "easymde": "^2.18.0", |
| 52 | "leader-line": "^1.0.7", | 52 | "leader-line": "^1.0.7", |
| 53 | "mapclay": "^0.7.0", | 53 | "mapclay": "^0.8.2", |
| 54 | "markdown-it": "^14.1.0", | 54 | "markdown-it": "^14.1.0", |
| 55 | "markdown-it-anchor": "^9.2.0", | 55 | "markdown-it-anchor": "^9.2.0", |
| 56 | "markdown-it-footnote": "^4.0.0", | 56 | "markdown-it-footnote": "^4.0.0", |
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index fc9eab9..c878e48 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs | |||
| @@ -1,12 +1,10 @@ | |||
| 1 | export function focusNextMap(reverse = false) { | 1 | export function focusNextMap(reverse = false) { |
| 2 | const renderedList = this.renderMaps | 2 | const renderedList = Array.from(this.htmlHolder.querySelectorAll('[data-render=fulfilled]')) |
| 3 | .map(render => render.target) | ||
| 4 | .filter(ele => ele.getAttribute('data-state') === 'rendered') | ||
| 5 | const mapNum = renderedList.length | 3 | const mapNum = renderedList.length |
| 6 | if (mapNum === 0) return | 4 | if (mapNum === 0) return |
| 7 | 5 | ||
| 8 | // Get current focused map element | 6 | // Get current focused map element |
| 9 | const currentFocus = this.container.querySelector('.map-container.focus') | 7 | const currentFocus = this.container.querySelector('.mapclay.focus') |
| 10 | 8 | ||
| 11 | // Remove class name of focus for ALL candidates | 9 | // Remove class name of focus for ALL candidates |
| 12 | // This may trigger animation | 10 | // This may trigger animation |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 8c1b216..56375f7 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
| @@ -191,13 +191,13 @@ export const generateMaps = (container, {delay, mapCallback}) => { | |||
| 191 | .filter(l => createGeoLink(l, geoLinkCallback)) | 191 | .filter(l => createGeoLink(l, geoLinkCallback)) |
| 192 | 192 | ||
| 193 | const isAnchorPointedBy = (link) => (anchor) => { | 193 | const isAnchorPointedBy = (link) => (anchor) => { |
| 194 | const mapContainer = anchor.closest('.map-container') | 194 | const mapContainer = anchor.closest('.mapclay') |
| 195 | const isTarget = !link.targets || link.targets.includes(mapContainer.id) | 195 | const isTarget = !link.targets || link.targets.includes(mapContainer.id) |
| 196 | return anchor.title === link.url.pathname && isTarget | 196 | return anchor.title === link.url.pathname && isTarget |
| 197 | } | 197 | } |
| 198 | 198 | ||
| 199 | const isAnchorVisible = (anchor) => { | 199 | const isAnchorVisible = (anchor) => { |
| 200 | const mapContainer = anchor.closest('.map-container') | 200 | const mapContainer = anchor.closest('.mapclay') |
| 201 | return insideWindow(anchor) && insideParent(anchor, mapContainer) | 201 | return insideWindow(anchor) && insideParent(anchor, mapContainer) |
| 202 | } | 202 | } |
| 203 | 203 | ||
| @@ -227,7 +227,7 @@ export const generateMaps = (container, {delay, mapCallback}) => { | |||
| 227 | } | 227 | } |
| 228 | 228 | ||
| 229 | const updateMapByMarker = (xy) => (marker) => { | 229 | const updateMapByMarker = (xy) => (marker) => { |
| 230 | const renderer = marker.closest('.map-container')?.renderer | 230 | const renderer = marker.closest('.mapclay')?.renderer |
| 231 | renderer.updateCamera({ center: xy }, true) | 231 | renderer.updateCamera({ center: xy }, true) |
| 232 | } | 232 | } |
| 233 | 233 | ||
| @@ -275,7 +275,7 @@ export const generateMaps = (container, {delay, mapCallback}) => { | |||
| 275 | // Placeholder for map in Showcase, it should has the same DOMRect | 275 | // Placeholder for map in Showcase, it should has the same DOMRect |
| 276 | const placeholder = target.cloneNode(true) | 276 | const placeholder = target.cloneNode(true) |
| 277 | placeholder.removeAttribute('id') | 277 | placeholder.removeAttribute('id') |
| 278 | placeholder.classList.remove('map-container', 'focus') | 278 | placeholder.classList.remove('mapclay', 'focus') |
| 279 | target.parentElement.replaceChild(placeholder, target) | 279 | target.parentElement.replaceChild(placeholder, target) |
| 280 | 280 | ||
| 281 | // FIXME Maybe use @start-style for CSS | 281 | // FIXME Maybe use @start-style for CSS |
| @@ -345,8 +345,8 @@ export const generateMaps = (container, {delay, mapCallback}) => { | |||
| 345 | 345 | ||
| 346 | // Since layout change may show/hide showcase, the current focused map should do something | 346 | // Since layout change may show/hide showcase, the current focused map should do something |
| 347 | // Reset attribute triggers MutationObserver which is observing it | 347 | // Reset attribute triggers MutationObserver which is observing it |
| 348 | const focusMap = container.querySelector('.map-container.focus') | 348 | const focusMap = container.querySelector('.mapclay.focus') |
| 349 | ?? container.querySelector('.map-container') | 349 | ?? container.querySelector('.mapclay') |
| 350 | focusMap?.classList?.add('focus') | 350 | focusMap?.classList?.add('focus') |
| 351 | }); | 351 | }); |
| 352 | layoutObserver.observe(container, { | 352 | layoutObserver.observe(container, { |
| @@ -361,19 +361,32 @@ export const generateMaps = (container, {delay, mapCallback}) => { | |||
| 361 | //}}} | 361 | //}}} |
| 362 | // Render Maps {{{ | 362 | // Render Maps {{{ |
| 363 | 363 | ||
| 364 | const afterEachMapLoaded = (renderMap) => { | 364 | const afterMapRendered = (renderer) => { |
| 365 | const mapContainer = renderMap.target | 365 | const mapElement = renderer.target |
| 366 | mapCache[mapContainer.id] = renderMap | 366 | mapElement.setAttribute('tabindex', "-1") |
| 367 | mapContainer.setAttribute('tabindex', "-1") | 367 | if (mapElement.getAttribute('data-render') === 'fulfilled') { |
| 368 | mapContainer.setAttribute('data-state', "rendered") | 368 | mapCache[mapElement.id] = renderer |
| 369 | } | ||
| 370 | |||
| 371 | // Execute callback from caller | ||
| 372 | mapCallback?.call(this, mapElement) | ||
| 373 | const markers = geoLinks | ||
| 374 | .filter(link => !link.targets || link.targets.includes(mapElement.id)) | ||
| 375 | .map(link => ({ xy: link.xy, title: link.url.pathname })) | ||
| 369 | 376 | ||
| 377 | // Add markers with Geolinks | ||
| 378 | renderer.addMarkers(markers) | ||
| 379 | mapElement.querySelectorAll('.marker') | ||
| 380 | .forEach(marker => htmlHolder.anchors.push(marker)) | ||
| 381 | |||
| 382 | // Work with Mutation Observer | ||
| 370 | const observer = mapFocusObserver() | 383 | const observer = mapFocusObserver() |
| 371 | mapFocusObserver().observe(mapContainer, { | 384 | mapFocusObserver().observe(mapElement, { |
| 372 | attributes: true, | 385 | attributes: true, |
| 373 | attributeFilter: ["class"], | 386 | attributeFilter: ["class"], |
| 374 | attributeOldValue: true | 387 | attributeOldValue: true |
| 375 | }); | 388 | }); |
| 376 | onRemove(mapContainer, () => observer.disconnect()) | 389 | onRemove(mapElement, () => observer.disconnect()) |
| 377 | } | 390 | } |
| 378 | 391 | ||
| 379 | // Set unique ID for map container | 392 | // Set unique ID for map container |
| @@ -397,6 +410,7 @@ export const generateMaps = (container, {delay, mapCallback}) => { | |||
| 397 | const elementsWithMapConfig = Array.from(container.querySelectorAll('pre:has(.language-map)') ?? []) | 410 | const elementsWithMapConfig = Array.from(container.querySelectorAll('pre:has(.language-map)') ?? []) |
| 398 | // Add default aliases into each config | 411 | // Add default aliases into each config |
| 399 | const configConverter = (config => ({ | 412 | const configConverter = (config => ({ |
| 413 | use: config.use ?? 'Leaflet', | ||
| 400 | width: "100%", | 414 | width: "100%", |
| 401 | ...config, | 415 | ...config, |
| 402 | aliases: { | 416 | aliases: { |
| @@ -419,47 +433,36 @@ export const generateMaps = (container, {delay, mapCallback}) => { | |||
| 419 | configList = parseConfigsFromYaml(configText).map(assignMapId) | 433 | configList = parseConfigsFromYaml(configText).map(assignMapId) |
| 420 | } catch (_) { | 434 | } catch (_) { |
| 421 | console.warn('Fail to parse yaml config for element', target) | 435 | console.warn('Fail to parse yaml config for element', target) |
| 436 | return | ||
| 422 | } | 437 | } |
| 423 | 438 | ||
| 424 | // If map in cache has the same ID, and its config is the same, | 439 | // If map in cache has the same ID, just put it into target |
| 425 | // then don't render them again | 440 | // So user won't feel anything changes when editing markdown |
| 426 | configList.forEach(config => { | 441 | configList.forEach(config => { |
| 427 | const cache = mapCache[config.id] | 442 | const cache = mapCache[config.id] |
| 428 | if (cache && JSON.stringify(cache.config) === JSON.stringify(configConverter(config))) { | 443 | if (!cache) return; |
| 429 | target.appendChild(cache.target) | ||
| 430 | config.render = () => null | ||
| 431 | } | ||
| 432 | }) | ||
| 433 | 444 | ||
| 434 | // Render maps | 445 | target.appendChild(cache.target) |
| 435 | render(target, configList).forEach(renderMap => { | 446 | config.target = cache.target |
| 436 | renderMaps.push(renderMap) | ||
| 437 | renderMap.promise | ||
| 438 | .then(_ => afterEachMapLoaded(renderMap)) | ||
| 439 | .catch(err => console.error('Fail to render target element with ID:', renderMap.target.id, err)) | ||
| 440 | }) | 447 | }) |
| 441 | }) | ||
| 442 | 448 | ||
| 443 | Promise.allSettled(renderMaps.map(r => r.promise)) | 449 | // trivial: if map cache is applied, do not show yaml text |
| 444 | .then(() => { | 450 | if (target.querySelector('.mapclay')) { |
| 445 | console.info('Finish Rendering') | 451 | target.querySelectorAll(':scope > :not([data-render=fulfilled])') |
| 446 | 452 | .forEach(e => e.remove()) | |
| 447 | renderMaps | 453 | } |
| 448 | .map(r => r.target) | 454 | |
| 449 | .filter(target => target.getAttribute('data-state') === 'rendered') | 455 | // Render maps with delay |
| 450 | .forEach(ele => { | 456 | const timer = setTimeout(() => |
| 451 | mapCallback(ele) | 457 | render(target, configList).forEach(renderMap => { |
| 452 | const markers = geoLinks | 458 | renderMaps.push(renderMap) |
| 453 | .filter(link => !link.targets || link.targets.includes(ele.id)) | 459 | renderMap.then(afterMapRendered) |
| 454 | .map(link => ({ | 460 | }), |
| 455 | xy: link.xy, | 461 | delay ?? 1000 |
| 456 | title: link.url.pathname | 462 | ) |
| 457 | })) | 463 | onRemove(htmlHolder, () => { |
| 458 | ele?.renderer?.addMarkers(markers) | 464 | clearTimeout(timer) |
| 459 | }) | 465 | }) |
| 460 | |||
| 461 | htmlHolder.querySelectorAll('.marker') | ||
| 462 | .forEach(marker => htmlHolder.anchors.push(marker)) | ||
| 463 | }) | 466 | }) |
| 464 | 467 | ||
| 465 | //}}} | 468 | //}}} |