MediaWiki:Common.js: Difference between revisions
No edit summary |
No edit summary |
||
| Line 183: | Line 183: | ||
------------------------------------------------------------ | ------------------------------------------------------------ | ||
Paste this whole block at the bottom of MediaWiki:Common.js | Paste this whole block at the bottom of MediaWiki:Common.js | ||
Supports clickable pins, text labels, | Supports clickable pins, text labels, per-marker zoom ranges, | ||
and category layers with an on/off legend. | |||
============================================================ */ | ============================================================ */ | ||
( function () { | ( function () { | ||
| Line 229: | Line 229: | ||
} catch ( e2 ) { /* fall through */ } | } catch ( e2 ) { /* fall through */ } | ||
} | } | ||
return []; | return { pins: [], categories: {} }; | ||
} | } | ||
function normalize( data ) { | function normalize( data ) { | ||
if ( Array.isArray( data ) ) { | |||
return { pins: data, categories: {} }; | |||
} | |||
if ( data && Array.isArray( data.pins ) ) { | |||
return { pins: data.pins, categories: data.categories || {} }; | |||
} | |||
return { pins: [], categories: {} }; | |||
}; | |||
} | } | ||
function fetchMarkers( src ) { | function fetchMarkers( src ) { | ||
if ( !src ) { | if ( !src ) { | ||
return Promise.resolve( [] ); | return Promise.resolve( { pins: [], categories: {} } ); | ||
} | } | ||
if ( /^https?:\/\//.test( src ) ) { | if ( /^https?:\/\//.test( src ) ) { | ||
| Line 274: | Line 265: | ||
'<circle cx="12" cy="11.2" r="4.2" fill="#fff" fill-opacity=".92"/></svg>'; | '<circle cx="12" cy="11.2" r="4.2" fill="#fff" fill-opacity=".92"/></svg>'; | ||
function | // A marker's colour: its own "color" wins, else its category's colour. | ||
function markerColor( p, categories ) { | |||
var cat = ( categories && p.category ) ? categories[ p.category ] : null; | |||
return p.color || ( cat && cat.color ); | |||
} | |||
var pin = document.createElement( p.page ? 'a' : 'span' ); | function buildPin( p, categories ) { | ||
var color = markerColor( p, categories ); | |||
var pin = document.createElement( p.page ? 'a' : 'span' ); | |||
pin.className = 'imap-pin'; | pin.className = 'imap-pin'; | ||
if ( p.page ) { | if ( p.page ) { | ||
pin.href = articleUrl( p.page ); | pin.href = articleUrl( p.page ); | ||
} | } | ||
if ( color ) { | if ( color ) { | ||
pin.style.setProperty( '--pin-color', color ); | |||
} | } | ||
pin.setAttribute( 'aria-label', p.title || p.page || 'map location' ); | pin.setAttribute( 'aria-label', p.title || p.page || 'map location' ); | ||
pin.innerHTML = PIN_SVG; | pin.innerHTML = PIN_SVG; | ||
| Line 297: | Line 292: | ||
} | } | ||
function buildLabel( p ) { | function buildLabel( p, categories ) { | ||
var color = markerColor( p, categories ); | |||
var el = document.createElement( p.page ? 'a' : 'span' ); | var el = document.createElement( p.page ? 'a' : 'span' ); | ||
el.className = 'imap-label'; | el.className = 'imap-label'; | ||
| Line 304: | Line 300: | ||
el.href = articleUrl( p.page ); | el.href = articleUrl( p.page ); | ||
} | } | ||
if ( | if ( color ) { | ||
el.style.color = | el.style.color = color; | ||
} | } | ||
if ( p.size ) { | if ( p.size ) { | ||
| Line 313: | Line 309: | ||
} | } | ||
// Add every marker; return a list we can show / hide by zoom. | // Add every marker; return a list we can show / hide by zoom + category. | ||
function addMarkers( viewer, markers, categories ) { | function addMarkers( viewer, markers, categories ) { | ||
var ti = viewer.world.getItemAt( 0 ); | var ti = viewer.world.getItemAt( 0 ); | ||
var tracked = []; | var tracked = []; | ||
| Line 324: | Line 320: | ||
return; | return; | ||
} | } | ||
var built = ( p.kind === 'label' ) ? buildLabel( p ) : buildPin( p, categories ); | var built = ( p.kind === 'label' ) ? buildLabel( p, categories ) : buildPin( p, categories ); | ||
viewer.addOverlay( { | viewer.addOverlay( { | ||
element: built.element, | element: built.element, | ||
| Line 332: | Line 328: | ||
tracked.push( { | tracked.push( { | ||
el: built.element, | el: built.element, | ||
cat: p.category || null, | |||
min: typeof p.minZoom === 'number' ? p.minZoom : 0, | min: typeof p.minZoom === 'number' ? p.minZoom : 0, | ||
max: typeof p.maxZoom === 'number' ? p.maxZoom : Infinity, | max: typeof p.maxZoom === 'number' ? p.maxZoom : Infinity, | ||
| Line 346: | Line 343: | ||
} | } | ||
function makeVisibilityUpdater( viewer, tracked ) { | // A marker is visible when its category is enabled AND the current zoom | ||
// is inside its range. `enabled` is shared with the legend checkboxes. | |||
function makeVisibilityUpdater( viewer, tracked, enabled ) { | |||
return function () { | return function () { | ||
if ( !tracked.length ) { | if ( !tracked.length ) { | ||
| Line 353: | Line 352: | ||
var z = relativeZoom( viewer ); | var z = relativeZoom( viewer ); | ||
tracked.forEach( function ( t ) { | tracked.forEach( function ( t ) { | ||
var visible = z >= t.min && z <= t.max; | var catOn = !t.cat || enabled[ t.cat ] !== false; | ||
var visible = catOn && z >= t.min && z <= t.max; | |||
if ( visible !== t.shown ) { | if ( visible !== t.shown ) { | ||
t.shown = visible; | t.shown = visible; | ||
| Line 360: | Line 360: | ||
} ); | } ); | ||
}; | }; | ||
} | |||
// Build the on/off legend from the categories actually used by pins. | |||
// Populates `enabled` with each category's starting state and wires the | |||
// checkboxes to re-run `onChange`. | |||
function buildLegend( el, pins, categories, enabled, onChange ) { | |||
var used = {}; | |||
pins.forEach( function ( p ) { | |||
if ( p.category ) { | |||
used[ p.category ] = true; | |||
} | |||
} ); | |||
// Config order first, then any used categories not in the config. | |||
var keys = []; | |||
Object.keys( categories ).forEach( function ( k ) { | |||
if ( used[ k ] ) { | |||
keys.push( k ); | |||
} | |||
} ); | |||
Object.keys( used ).forEach( function ( k ) { | |||
if ( keys.indexOf( k ) === -1 ) { | |||
keys.push( k ); | |||
} | |||
} ); | |||
// Starting state: on, unless the category sets "default": false. | |||
keys.forEach( function ( k ) { | |||
var cfg = categories[ k ] || {}; | |||
enabled[ k ] = ( cfg.default !== false ); | |||
} ); | |||
if ( !keys.length ) { | |||
return; | |||
} | |||
var panel = document.createElement( 'div' ); | |||
panel.className = 'imap-legend'; | |||
var title = document.createElement( 'div' ); | |||
title.className = 'imap-legend-title'; | |||
title.textContent = 'Layers'; | |||
panel.appendChild( title ); | |||
keys.forEach( function ( k ) { | |||
var cfg = categories[ k ] || {}; | |||
var row = document.createElement( 'label' ); | |||
var cb = document.createElement( 'input' ); | |||
cb.type = 'checkbox'; | |||
cb.checked = enabled[ k ]; | |||
cb.addEventListener( 'change', function () { | |||
enabled[ k ] = cb.checked; | |||
onChange(); | |||
} ); | |||
var swatch = document.createElement( 'span' ); | |||
swatch.className = 'imap-legend-swatch'; | |||
swatch.style.background = ( cfg.color || '#2f6fed' ); | |||
var text = document.createElement( 'span' ); | |||
text.textContent = cfg.label || k; | |||
row.appendChild( cb ); | |||
row.appendChild( swatch ); | |||
row.appendChild( text ); | |||
panel.appendChild( row ); | |||
} ); | |||
el.appendChild( panel ); | |||
} | } | ||
| Line 432: | Line 502: | ||
enableCoordHelper( viewer, el ); | enableCoordHelper( viewer, el ); | ||
} | } | ||
fetchMarkers( markersSrc ).then( function ( markerData ) { | fetchMarkers( markersSrc ).then( function ( markerData ) { | ||
var pins = markerData.pins || []; | |||
var update = makeVisibilityUpdater( viewer, tracked ); | var categories = markerData.categories || {}; | ||
var enabled = {}; | |||
var tracked = addMarkers( viewer, pins, categories ); | |||
var update = makeVisibilityUpdater( viewer, tracked, enabled ); | |||
buildLegend( el, pins, categories, enabled, update ); // sets defaults in `enabled` | |||
update(); // set the correct state for the starting zoom | update(); // set the correct state for the starting zoom | ||
viewer.addHandler( 'animation', update ); | viewer.addHandler( 'animation', update ); | ||