1// @flow 2 3import DOM from '../../util/dom'; 4import {bindAll} from '../../util/util'; 5import config from '../../util/config'; 6 7import type Map from '../map'; 8 9type Options = { 10 compact?: boolean, 11 customAttribution?: string | Array<string> 12}; 13 14/** 15 * An `AttributionControl` control presents the map's [attribution information](https://www.mapbox.com/help/attribution/). 16 * 17 * @implements {IControl} 18 * @param {Object} [options] 19 * @param {boolean} [options.compact] If `true` force a compact attribution that shows the full attribution on mouse hover, or if `false` force the full attribution control. The default is a responsive attribution that collapses when the map is less than 640 pixels wide. 20 * @param {string | Array<string>} [options.customAttribution] String or strings to show in addition to any other attributions. 21 * @example 22 * var map = new mapboxgl.Map({attributionControl: false}) 23 * .addControl(new mapboxgl.AttributionControl({ 24 * compact: true 25 * })); 26 */ 27class AttributionControl { 28 options: Options; 29 _map: Map; 30 _container: HTMLElement; 31 _innerContainer: HTMLElement; 32 _editLink: ?HTMLAnchorElement; 33 _attribHTML: string; 34 styleId: string; 35 styleOwner: string; 36 37 constructor(options: Options = {}) { 38 this.options = options; 39 40 bindAll([ 41 '_updateEditLink', 42 '_updateData', 43 '_updateCompact' 44 ], this); 45 } 46 47 getDefaultPosition() { 48 return 'bottom-right'; 49 } 50 51 onAdd(map: Map) { 52 const compact = this.options && this.options.compact; 53 54 this._map = map; 55 this._container = DOM.create('div', 'mapboxgl-ctrl mapboxgl-ctrl-attrib'); 56 this._innerContainer = DOM.create('div', 'mapboxgl-ctrl-attrib-inner', this._container); 57 58 if (compact) { 59 this._container.classList.add('mapboxgl-compact'); 60 } 61 62 this._updateAttributions(); 63 this._updateEditLink(); 64 65 this._map.on('styledata', this._updateData); 66 this._map.on('sourcedata', this._updateData); 67 this._map.on('moveend', this._updateEditLink); 68 69 if (compact === undefined) { 70 this._map.on('resize', this._updateCompact); 71 this._updateCompact(); 72 } 73 74 return this._container; 75 } 76 77 onRemove() { 78 DOM.remove(this._container); 79 80 this._map.off('styledata', this._updateData); 81 this._map.off('sourcedata', this._updateData); 82 this._map.off('moveend', this._updateEditLink); 83 this._map.off('resize', this._updateCompact); 84 85 this._map = (undefined: any); 86 } 87 88 _updateEditLink() { 89 let editLink = this._editLink; 90 if (!editLink) { 91 editLink = this._editLink = (this._container.querySelector('.mapbox-improve-map'): any); 92 } 93 94 const params = [ 95 {key: "owner", value: this.styleOwner}, 96 {key: "id", value: this.styleId}, 97 {key: "access_token", value: this._map._requestManager._customAccessToken || config.ACCESS_TOKEN} 98 ]; 99 100 if (editLink) { 101 const paramString = params.reduce((acc, next, i) => { 102 if (next.value) { 103 acc += `${next.key}=${next.value}${i < params.length - 1 ? '&' : ''}`; 104 } 105 return acc; 106 }, `?`); 107 editLink.href = `${config.FEEDBACK_URL}/${paramString}${this._map._hash ? this._map._hash.getHashString(true) : ''}`; 108 editLink.rel = "noopener nofollow"; 109 } 110 } 111 112 _updateData(e: any) { 113 if (e && (e.sourceDataType === 'metadata' || e.dataType === 'style')) { 114 this._updateAttributions(); 115 this._updateEditLink(); 116 } 117 } 118 119 _updateAttributions() { 120 if (!this._map.style) return; 121 let attributions: Array<string> = []; 122 if (this.options.customAttribution) { 123 if (Array.isArray(this.options.customAttribution)) { 124 attributions = attributions.concat( 125 this.options.customAttribution.map(attribution => { 126 if (typeof attribution !== 'string') return ''; 127 return attribution; 128 }) 129 ); 130 } else if (typeof this.options.customAttribution === 'string') { 131 attributions.push(this.options.customAttribution); 132 } 133 } 134 135 if (this._map.style.stylesheet) { 136 const stylesheet: any = this._map.style.stylesheet; 137 this.styleOwner = stylesheet.owner; 138 this.styleId = stylesheet.id; 139 } 140 141 const sourceCaches = this._map.style.sourceCaches; 142 for (const id in sourceCaches) { 143 const sourceCache = sourceCaches[id]; 144 if (sourceCache.used) { 145 const source = sourceCache.getSource(); 146 if (source.attribution && attributions.indexOf(source.attribution) < 0) { 147 attributions.push(source.attribution); 148 } 149 } 150 } 151 152 // remove any entries that are substrings of another entry. 153 // first sort by length so that substrings come first 154 attributions.sort((a, b) => a.length - b.length); 155 attributions = attributions.filter((attrib, i) => { 156 for (let j = i + 1; j < attributions.length; j++) { 157 if (attributions[j].indexOf(attrib) >= 0) { return false; } 158 } 159 return true; 160 }); 161 162 // check if attribution string is different to minimize DOM changes
163 const attribHTML = attributions.join(' | '); 164 if (attribHTML === this._attribHTML) return; 165 166 this._attribHTML = attribHTML; 167 168 if (attributions.length) { 169 this._innerContainer.innerHTML = attribHTML; 170 this._container.classList.remove('mapboxgl-attrib-empty'); 171 } else { 172 this._container.classList.add('mapboxgl-attrib-empty'); 173 } 174 // remove old DOM node from _editLink 175 this._editLink = null; 176 } 177 178 _updateCompact() { 179 if (this._map.getCanvasContainer().offsetWidth <= 640) { 180 this._container.classList.add('mapboxgl-compact'); 181 } else { 182 this._container.classList.remove('mapboxgl-compact'); 183 } 184 } 185 186} 187 188export default AttributionControl;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.