Merge remote-tracking branch 'upstream/master' into fix/web-driver-tests-v7

Conflicts:
	config/webpack.production.config.js
	package-lock.json
	package.json
This commit is contained in:
orangemug 2018-03-06 07:22:26 +00:00
commit a3fa86f7ee
22 changed files with 5074 additions and 3603 deletions

View file

@ -35,7 +35,7 @@ exports.config = {
mochaOpts: {
ui: 'bdd',
// Because we don't know how long the initial build will take...
timeout: 2*60*1000
timeout: 4*60*1000
},
onPrepare: function (config, capabilities) {
var compiler = webpack(webpackConfig);

View file

@ -25,8 +25,7 @@ module.exports = {
},
module: {
noParse: [
/mapbox-gl\/dist\/mapbox-gl.js/,
/openlayers\/dist\/ol.js/
/mapbox-gl\/dist\/mapbox-gl.js/
],
loaders: loaders
},

View file

@ -4,9 +4,21 @@ module.exports = [
exclude: /(node_modules|bower_components|public)/,
loaders: ['react-hot-loader/webpack']
},
// HACK: This is a massive hack and reaches into the mapbox-gl private API.
// We have to include this for access to `normalizeSourceURL`. We should
// remove this ASAP, see <https://github.com/mapbox/mapbox-gl-js/issues/2416>
{
test: /.*node_modules[\/\\]mapbox-gl[\/\\]src[\/\\]util[\/\\].*\.js/,
loader: 'babel-loader',
query: {
presets: ['env', 'react', 'flow'],
plugins: ['transform-runtime', 'transform-decorators-legacy', 'transform-class-properties'],
}
},
{
test: /\.jsx?$/,
exclude: /(.*node_modules(?![\/\\]@mapbox[\/\\]mapbox-gl-style-spec)|bower_components|public)/,
// Note: These modules aren't ES5 therefore we much compile them.
exclude: /(.*node_modules(?![\/\\](@mapbox[\/\\]mapbox-gl-style-spec|ol|mapbox-to-ol-style))|bower_components|public)/,
loader: 'babel-loader',
query: {
presets: ['env', 'react'],

View file

@ -7,6 +7,7 @@ var WebpackCleanupPlugin = require('webpack-cleanup-plugin');
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
var CopyWebpackPlugin = require('copy-webpack-plugin');
var artifacts = require("../test/artifacts");
var UglifyJsPlugin = require('uglifyjs-webpack-plugin');
var OUTPATH = artifacts.pathSync("/build");
@ -43,8 +44,7 @@ module.exports = {
},
module: {
noParse: [
/mapbox-gl\/dist\/mapbox-gl.js/,
/openlayers\/dist\/ol.js/
/mapbox-gl\/dist\/mapbox-gl.js/
],
loaders
},
@ -62,12 +62,7 @@ module.exports = {
NODE_ENV: '"production"'
}
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
screw_ie8: true,
}
}),
new UglifyJsPlugin(),
new ExtractTextPlugin('[contenthash].css', {
allChunks: true
}),

8311
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "maputnik",
"version": "1.1.0-beta3",
"version": "1.1.0",
"description": "A MapboxGL visual style editor",
"main": "''",
"scripts": {
@ -10,7 +10,8 @@
"test-watch": "cross-env NODE_ENV=test wdio config/wdio.conf.js --watch",
"start": "webpack-dev-server --progress --profile --colors --config config/webpack.config.js",
"lint": "eslint --ext js --ext jsx {src,test}",
"lint-styles": "stylelint 'src/styles/*.scss'"
"lint-styles": "stylelint 'src/styles/*.scss'",
"nsp": "nsp check --reporter summary"
},
"repository": {
"type": "git",
@ -21,7 +22,7 @@
"homepage": "https://github.com/maputnik/editor#readme",
"dependencies": {
"@mapbox/mapbox-gl-rtl-text": "^0.1.1",
"@mapbox/mapbox-gl-style-spec": "^10.0.1",
"@mapbox/mapbox-gl-style-spec": "^11.1.1",
"classnames": "^2.2.5",
"codemirror": "^5.32.0",
"color": "^2.0.0",
@ -32,12 +33,12 @@
"lodash.clonedeep": "^4.5.0",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.43.0",
"mapbox-gl-inspect": "^1.2.6",
"mapbox-gl": "^0.44.1",
"mapbox-gl-inspect": "^1.3.0",
"maputnik-design": "github:maputnik/design",
"mousetrap": "^1.6.1",
"ol-mapbox-style": "^1.0.1",
"openlayers": "^4.4.2",
"ol-mapbox-style": "^2.10.1",
"ol": "^4.6.4",
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-addons-pure-render-mixin": "^15.6.2",
@ -97,6 +98,8 @@
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-flow": "^6.23.0",
"babel-preset-react": "^6.24.1",
"babel-register": "^6.26.0",
"babel-runtime": "^6.26.0",
@ -118,6 +121,7 @@
"mkdirp": "^0.5.1",
"mocha": "^4.0.1",
"node-sass": "^4.6.0",
"nsp": "^3.1.0",
"react-hot-loader": "^3.1.1",
"sass-loader": "^6.0.6",
"selenium-standalone": "^6.11.0",
@ -126,6 +130,7 @@
"stylelint-config-standard": "^15.0.1",
"transform-loader": "^0.2.4",
"uuid": "^3.1.0",
"uglifyjs-webpack-plugin": "^1.1.8",
"wdio-mocha-framework": "^0.5.11",
"wdio-phantomjs-service": "^0.2.2",
"wdio-selenium-standalone-service": "0.0.9",

View file

@ -22,6 +22,10 @@ import tokens from '../config/tokens.json'
import isEqual from 'lodash.isequal'
import Debug from '../libs/debug'
import MapboxGl from 'mapbox-gl'
import mapboxUtil from 'mapbox-gl/src/util/mapbox'
function updateRootSpec(spec, fieldName, newValues) {
return {
...spec,
@ -112,7 +116,9 @@ export default class App extends React.Component {
updateFonts(urlTemplate) {
const metadata = this.state.mapStyle.metadata || {}
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
downloadGlyphsMetadata(urlTemplate.replace('{key}', accessToken), fonts => {
let glyphUrl = (typeof urlTemplate === 'string')? urlTemplate.replace('{key}', accessToken): urlTemplate;
downloadGlyphsMetadata(glyphUrl, fonts => {
this.setState({ spec: updateRootSpec(this.state.spec, 'glyphs', fonts)})
})
}
@ -124,6 +130,10 @@ export default class App extends React.Component {
}
onStyleChanged(newStyle, save=true) {
const errors = styleSpec.validate(newStyle, styleSpec.latest)
if(errors.length === 0) {
if(newStyle.glyphs !== this.state.mapStyle.glyphs) {
this.updateFonts(newStyle.glyphs)
}
@ -131,8 +141,6 @@ export default class App extends React.Component {
this.updateIcons(newStyle.sprite)
}
const errors = styleSpec.validate(newStyle, styleSpec.latest)
if(errors.length === 0) {
this.revisionStore.addRevision(newStyle)
if(save) this.saveStyle(newStyle)
this.setState({
@ -215,13 +223,23 @@ export default class App extends React.Component {
layers: []
};
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector") {
const url = val.url;
if(!this.state.sources.hasOwnProperty(key) && val.type === "vector" && val.hasOwnProperty("url")) {
let url = val.url;
try {
url = mapboxUtil.normalizeSourceURL(url, MapboxGl.accessToken);
} catch(err) {
console.warn("Failed to normalizeSourceURL: ", err);
}
fetch(url)
.then((response) => {
return response.json();
})
.then((json) => {
if(!json.hasOwnProperty("vector_layers")) {
return;
}
// Create new objects before setState
const sources = Object.assign({}, this.state.sources);
@ -250,7 +268,7 @@ export default class App extends React.Component {
mapRenderer() {
const mapProps = {
mapStyle: style.replaceAccessToken(this.state.mapStyle),
mapStyle: style.replaceAccessToken(this.state.mapStyle, {allowFallback: true}),
onDataChange: (e) => {
this.layerWatcher.analyzeMap(e.map)
this.fetchSources();

View file

@ -22,6 +22,7 @@ import SettingsModal from './modals/SettingsModal'
import ExportModal from './modals/ExportModal'
import SourcesModal from './modals/SourcesModal'
import OpenModal from './modals/OpenModal'
import pkgJson from '../../package.json'
import style from '../libs/style'
@ -137,7 +138,9 @@ export default class Toolbar extends React.Component {
className="maputnik-toolbar-logo"
>
<img src={logoImage} alt="Maputnik" />
<h1>Maputnik</h1>
<h1>Maputnik
<span className="maputnik-toolbar-version">v{pkgJson.version}</span>
</h1>
</ToolbarLink>
<div className="maputnik-toolbar__actions">
<ToolbarAction wdKey="nav:open" onClick={this.toggleModal.bind(this, 'open')}>

View file

@ -18,6 +18,8 @@ class LayerIcon extends React.Component {
switch(this.props.type) {
case 'fill-extrusion': return <BackgroundIcon {...iconProps} />
case 'raster': return <FillIcon {...iconProps} />
case 'hillshade': return <FillIcon {...iconProps} />
case 'heatmap': return <FillIcon {...iconProps} />
case 'fill': return <FillIcon {...iconProps} />
case 'background': return <BackgroundIcon {...iconProps} />
case 'line': return <LineIcon {...iconProps} />

View file

@ -112,6 +112,11 @@ export default class LayerEditor extends React.Component {
comment = this.props.layer.metadata['maputnik:comment']
}
let sourceLayerIds;
if(this.props.sources.hasOwnProperty(this.props.layer.source)) {
sourceLayerIds = this.props.sources[this.props.layer.source].layers;
}
switch(type) {
case 'layer': return <div>
<LayerIdBlock
@ -129,8 +134,9 @@ export default class LayerEditor extends React.Component {
onChange={v => this.changeProperty(null, 'source', v)}
/>
}
{this.props.layer.type !== 'raster' && this.props.layer.type !== 'background' && <LayerSourceLayerBlock
sourceLayerIds={this.props.sources[this.props.layer.source].layers}
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
<LayerSourceLayerBlock
sourceLayerIds={sourceLayerIds}
value={this.props.layer['source-layer']}
onChange={v => this.changeProperty(null, 'source-layer', v)}
/>

View file

@ -25,6 +25,8 @@ class LayerTypeBlock extends React.Component {
['raster', 'Raster'],
['circle', 'Circle'],
['fill-extrusion', 'Fill Extrusion'],
['hillshade', 'Hillshade'],
['heatmap', 'Heatmap'],
]}
onChange={this.props.onChange}
value={this.props.value}

View file

@ -38,7 +38,7 @@ function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) {
const sources = {}
Object.keys(originalMapStyle.sources).forEach(sourceId => {
const source = originalMapStyle.sources[sourceId]
if(source.type !== 'raster') {
if(source.type !== 'raster' && source.type !== 'raster-dem') {
sources[sourceId] = source
}
})

View file

@ -3,66 +3,8 @@ import PropTypes from 'prop-types'
import style from '../../libs/style.js'
import isEqual from 'lodash.isequal'
import { loadJSON } from '../../libs/urlopen'
import 'openlayers/dist/ol.css'
import 'ol/ol.css'
function suitableVectorSource(mapStyle) {
const sources = Object.keys(mapStyle.sources)
.map(sourceId => {
return {
id: sourceId,
source: mapStyle.sources[sourceId]
}
})
.filter(({source}) => (source.type === 'vector' || source.type === 'geojson'))
return sources[0]
}
function toVectorLayer(source, tilegrid, cb) {
function newMVTLayer(tileUrl) {
const ol = require('openlayers')
return new ol.layer.VectorTile({
source: new ol.source.VectorTile({
format: new ol.format.MVT(),
tileGrid: tilegrid,
tilePixelRatio: 8,
url: tileUrl
})
})
}
function newGeoJSONLayer(sourceUrl) {
const ol = require('openlayers')
return new ol.layer.Vector({
source: new ol.source.Vector({
format: new ol.format.GeoJSON(),
url: sourceUrl
})
})
}
if (source.type === 'vector') {
if(!source.tiles) {
sourceFromTileJSON(source.url, tileSource => {
cb(newMVTLayer(tileSource.tiles[0]))
})
} else {
cb(newMVTLayer(source.tiles[0]))
}
} else if (source.type === 'geojson') {
cb(newGeoJSONLayer(source.data))
}
}
function sourceFromTileJSON(url, cb) {
loadJSON(url, null, tilejson => {
if(!tilejson) return
cb({
type: 'vector',
tiles: tilejson.tiles,
minzoom: tilejson.minzoom,
maxzoom: tilejson.maxzoom,
})
})
}
class OpenLayers3Map extends React.Component {
static propTypes = {
@ -79,49 +21,17 @@ class OpenLayers3Map extends React.Component {
constructor(props) {
super(props)
this.tilegrid = null
this.resolutions = null
this.layer = null
this.map = null
}
updateStyle(newMapStyle) {
const oldSource = suitableVectorSource(this.props.mapStyle)
const newSource = suitableVectorSource(newMapStyle)
const resolutions = this.resolutions
function setStyleFunc(map, layer) {
const olms = require('ol-mapbox-style')
const styleFunc = olms.getStyleFunction(newMapStyle, newSource.id, resolutions)
layer.setStyle(styleFunc)
//NOTE: We need to mark the source as changed in order
//to trigger a rerender
layer.getSource().changed()
map.render()
}
if(newSource) {
if(this.layer && !isEqual(oldSource, newSource)) {
this.map.removeLayer(this.layer)
this.layer = null
}
if(!this.layer) {
var self = this
toVectorLayer(newSource.source, this.tilegrid, vectorLayer => {
self.layer = vectorLayer
self.map.addLayer(self.layer)
setStyleFunc(self.map, self.layer)
})
} else {
setStyleFunc(this.map, this.layer)
}
}
const olms = require('ol-mapbox-style');
const styleFunc = olms.apply(this.map, newMapStyle)
}
componentWillReceiveProps(nextProps) {
require.ensure(["openlayers", "ol-mapbox-style"], () => {
if(!this.map || !this.resolutions) return
require.ensure(["ol", "ol-mapbox-style"], () => {
if(!this.map) return
this.updateStyle(nextProps.mapStyle)
})
}
@ -129,24 +39,22 @@ class OpenLayers3Map extends React.Component {
componentDidMount() {
//Load OpenLayers dynamically once we need it
//TODO: Make this more convenient
require.ensure(["openlayers", "ol-mapbox-style"], ()=> {
require.ensure(["ol", "ol/map", "ol/view", "ol/control/zoom", "ol-mapbox-style"], ()=> {
console.log('Loaded OpenLayers3 renderer')
const ol = require('openlayers')
const olms = require('ol-mapbox-style')
const olMap = require('ol/map').default
const olView = require('ol/view').default
const olZoom = require('ol/control/zoom').default
this.tilegrid = ol.tilegrid.createXYZ({tileSize: 512, maxZoom: 22})
this.resolutions = this.tilegrid.getResolutions()
const map = new ol.Map({
const map = new olMap({
target: this.container,
layers: [],
view: new ol.View({
view: new olView({
zoom: 2,
center: [52.5, -78.4]
})
})
map.addControl(new ol.control.Zoom())
map.addControl(new olZoom())
this.map = map
this.updateStyle(this.props.mapStyle)
})

View file

@ -142,7 +142,7 @@ class AddModal extends React.Component {
onChange={v => this.setState({ source: v })}
/>
}
{this.state.type !== 'background' && this.state.type !== 'raster' &&
{['background', 'raster', 'hillshade', 'heatmap'].indexOf(this.state.type) < 0 &&
<LayerSourceLayerBlock
isFixed={true}
sourceLayerIds={layers}

View file

@ -25,6 +25,7 @@ class Gist extends React.Component {
super(props);
this.state = {
preview: false,
public: false,
saving: false,
latestGist: null,
}
@ -42,7 +43,10 @@ class Gist extends React.Component {
...this.state,
saving: true
});
const preview = this.state.preview && (this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token'];
const preview = this.state.preview;
const mapboxToken = (this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token'];
const mapStyleStr = preview ?
styleSpec.format(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
@ -55,8 +59,8 @@ class Gist extends React.Component {
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>`+styleTitle+` Preview</title>
<link rel="stylesheet" type="text/css" href="https://api.mapbox.com/mapbox-gl-js/v0.43.0/mapbox-gl.css" />
<script src="https://api.mapbox.com/mapbox-gl-js/v0.43.0/mapbox-gl.js"></script>
<link rel="stylesheet" type="text/css" href="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.css" />
<script src="https://api.mapbox.com/mapbox-gl-js/v0.44.0/mapbox-gl.js"></script>
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
@ -65,6 +69,7 @@ class Gist extends React.Component {
<body>
<div id='map'></div>
<script>
mapboxgl.accessToken = '${mapboxToken}';
var map = new mapboxgl.Map({
container: 'map',
style: 'style.json',
@ -89,7 +94,7 @@ class Gist extends React.Component {
const gh = new GitHub();
let gist = gh.getGist(); // not a gist yet
gist.create({
public: true,
public: this.state.public,
description: styleTitle,
files: files
}).then(function({data}) {
@ -110,6 +115,13 @@ class Gist extends React.Component {
})
}
onPublicChange(value) {
this.setState({
...this.state,
public: value
})
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
@ -162,13 +174,22 @@ class Gist extends React.Component {
<MdFileDownload />
Save to Gist (anonymous)
</Button>
{' '}
<div className="maputnik-modal-sub-section">
<CheckboxInput
value={this.state.public}
name='gist-style-public'
onChange={this.onPublicChange.bind(this)}
/>
<span> Public gist</span>
</div>
<div className="maputnik-modal-sub-section">
<CheckboxInput
value={this.state.preview}
name='gist-style-preview'
onChange={this.onPreviewChange.bind(this)}
/>
<span> Include preview</span>
</div>
{this.state.preview ?
<div>
<InputBlock
@ -177,6 +198,12 @@ class Gist extends React.Component {
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}/>
</InputBlock>
<InputBlock
label={"Mapbox Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}/>
</InputBlock>
<a target="_blank" rel="noopener noreferrer" href="https://openmaptiles.com/hosting/">Get your free access token</a>
</div>
: null}

View file

@ -45,6 +45,10 @@ function editorMode(source) {
if(source.tiles) return 'tilexyz_raster'
return 'tilejson_raster'
}
if(source.type === 'raster-dem') {
if(source.tiles) return 'tilexyz_raster-dem'
return 'tilejson_raster-dem'
}
if(source.type === 'vector') {
if(source.tiles) return 'tilexyz_vector'
return 'tilejson_vector'
@ -127,6 +131,16 @@ class AddSource extends React.Component {
minzoom: source.minzoom || 0,
maxzoom: source.maxzoom || 14
}
case 'tilejson_raster-dem': return {
type: 'raster-dem',
url: source.url || 'http://localhost:3000/tilejson.json'
}
case 'tilexyz_raster-dem': return {
type: 'raster-dem',
tiles: source.tiles || ['http://localhost:3000/{x}/{y}/{z}.pbf'],
minzoom: source.minzoom || 0,
maxzoom: source.maxzoom || 14
}
default: return {}
}
}
@ -147,6 +161,8 @@ class AddSource extends React.Component {
['tilexyz_vector', 'Vector (XYZ URLs)'],
['tilejson_raster', 'Raster (TileJSON URL)'],
['tilexyz_raster', 'Raster (XYZ URL)'],
['tilejson_raster-dem', 'Raster DEM (TileJSON URL)'],
['tilexyz_raster-dem', 'Raster DEM (XYZ URLs)'],
]}
onChange={mode => this.setState({mode: mode, source: this.defaultSource(mode)})}
value={this.state.mode}

View file

@ -115,6 +115,8 @@ class SourceTypeEditor extends React.Component {
case 'tilexyz_vector': return <TileURLSourceEditor {...commonProps} />
case 'tilejson_raster': return <TileJSONSourceEditor {...commonProps} />
case 'tilexyz_raster': return <TileURLSourceEditor {...commonProps} />
case 'tilejson_raster-dem': return <TileJSONSourceEditor {...commonProps} />
case 'tilexyz_raster-dem': return <TileURLSourceEditor {...commonProps} />
default: return null
}
}

View file

@ -91,7 +91,8 @@
"circle-stroke-width",
"circle-pitch-scale",
"circle-translate",
"circle-translate-anchor"
"circle-translate-anchor",
"circle-pitch-alignment"
]
}
]
@ -147,7 +148,9 @@
"icon-rotate",
"icon-padding",
"icon-keep-upright",
"icon-offset"
"icon-offset",
"icon-anchor",
"icon-pitch-alignment"
]
},
{
@ -194,5 +197,35 @@
]
}
]
},
"hillshade": {
"groups": [
{
"title": "Paint properties",
"type": "properties",
"fields": [
"hillshade-illumination-direction",
"hillshade-illumination-anchor",
"hillshade-exaggeration",
"hillshade-shadow-color",
"hillshade-highlight-color",
"hillshade-accent-color"
]
}
]
},
"heatmap": {
"groups": [
{
"title": "Paint properties",
"type": "properties",
"fields": [
"heatmap-radius",
"heatmap-weight",
"heatmap-intensity",
"heatmap-opacity"
]
}
]
}
}

View file

@ -54,12 +54,23 @@ function indexOfLayer(layers, layerId) {
return null
}
function replaceAccessToken(mapStyle) {
function replaceAccessToken(mapStyle, opts={}) {
const omtSource = mapStyle.sources.openmaptiles
if(!omtSource) return mapStyle
if(!omtSource.hasOwnProperty("url")) return mapStyle
const metadata = mapStyle.metadata || {}
const accessToken = metadata['maputnik:openmaptiles_access_token'] || tokens.openmaptiles
let accessToken = metadata['maputnik:openmaptiles_access_token'];
if(opts.allowFallback && !accessToken) {
accessToken = tokens.openmaptiles;
}
if(!accessToken) {
// Early exit.
return mapStyle;
}
const changedSources = {
...mapStyle.sources,
openmaptiles: {

View file

@ -46,6 +46,7 @@
// BUTTON
.maputnik-button {
display: inline-block;
cursor: pointer;
background-color: $color-midgray;
color: $color-lowgray;

View file

@ -6,6 +6,7 @@
background-color: $color-black;
box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
z-index: 3;
position: relative;
}
.maputnik-modal-section {
@ -20,6 +21,10 @@
flex-shrink: 0;
}
.maputnik-modal-sub-section {
margin-top: $margin-1;
}
.maputnik-modal-section--shrink {
flex-shrink: 1;
}
@ -75,6 +80,7 @@
position: fixed;
align-items: center;
justify-content: center;
z-index: 9;
@include flex-row;
}

View file

@ -36,12 +36,24 @@
cursor: pointer;
color: $color-white;
text-decoration: none;
line-height: 20px;
h1 {
position: relative;
}
&:hover {
background-color: $color-midgray;
}
}
.maputnik-toolbar-version {
position: absolute;
font-size: 10px;
bottom: -2px;
margin-left: 4px;
}
.maputnik-toolbar-action {
@extend .maputnik-toolbar-link;
}
@ -56,7 +68,7 @@
}
.maputnik-toolbar-logo {
flex: 0 0 140px;
flex: 0 0 170px;
}
.maputnik-toolbar__inner {