Compare commits

...

28 Commits

Author SHA1 Message Date
Lukas Martinelli
5c286f8d96 Remove static fontstacks.json 2017-01-25 13:47:37 +01:00
Lukas Martinelli
404b53587f Special fontstacks.json handling for Tileserver GL 2017-01-25 13:46:46 +01:00
Lukas Martinelli
e5fbe3b74a Collapse layer groups by default #66 2017-01-25 13:34:10 +01:00
Lukas Martinelli
3f262885ca Highlight selected layer more #62 2017-01-25 13:23:54 +01:00
Lukas Martinelli
c837179f71 Clean up layer.scss 2017-01-25 13:23:29 +01:00
Lukas Martinelli
9a947658e2 Improve default property styling #92 2017-01-25 12:54:33 +01:00
Lukas Martinelli
2458d4b637 Show inspect tooltip only on click in map #90 2017-01-22 21:16:11 +01:00
Lukas Martinelli
e4850805fb Fix default tileset of OpenMapTiles #88 2017-01-18 13:06:24 +01:00
Lukas Martinelli
3a15a3bb06 Show type of feature in popup 2017-01-18 10:03:15 +01:00
Lukas Martinelli
75ca1fa930 Deal with no metadata in style 2017-01-16 20:07:21 +01:00
Lukas Martinelli
377840ca24 Fix lint issues in _modal.scss 2017-01-16 16:34:55 +01:00
Lukas Martinelli
48e9589b58 Merge pull request #86 from klokantech/gist-preview
Gist preview & access token
2017-01-16 15:48:12 +01:00
Lukas Martinelli
11e9cef834 Improve styles and text 2017-01-16 15:43:52 +01:00
jirik
7e3aa09d3e Proview & Access Token logic when saving to Gist 2017-01-16 15:13:19 +01:00
jirik
e3b7e002b4 Hypertext links are white instead of blue 2017-01-16 15:13:11 +01:00
jirik
3b7fb7ae75 Fix checkbox not showing status 2017-01-16 15:13:02 +01:00
jirik
fab004cdfe StringInput fires change if state and props values do not match
Now it is also possible to call onChange listener if new value is empty string
2017-01-16 15:12:03 +01:00
Lukas Martinelli
07523c00f0 Point styles to master not gh-pages 2017-01-16 11:08:18 +01:00
Lukas Martinelli
c15ac14f88 Bump version to v1.0.1 2017-01-16 10:14:59 +01:00
Lukas Martinelli
8f6006c19f Less opacity for default values #73 2017-01-15 17:10:38 +01:00
Lukas Martinelli
16bedcf5b1 Add minzoom and maxzoom block #77 2017-01-15 13:46:55 +01:00
Lukas Martinelli
05349d8ffe Convert filter value to number if possible #63 2017-01-15 13:39:40 +01:00
Lukas Martinelli
a1e1895651 Deal specially with has operator #84 2017-01-15 10:42:59 +01:00
Lukas Martinelli
a111599850 Save chang event on XYZ editor #85 2017-01-15 10:36:57 +01:00
Lukas Martinelli
121a95cee8 Move my key message up 2017-01-14 15:09:27 +01:00
Lukas Martinelli
decd1f3ea2 Add tilezen style 2017-01-14 14:45:04 +01:00
Lukas Martinelli
c632718324 Remove id from empty style to generate one 2017-01-14 14:41:13 +01:00
Lukas Martinelli
9509b59696 Add open Mapbox styles to gallery 2017-01-14 14:00:32 +01:00
29 changed files with 319 additions and 91 deletions

View File

@@ -8,6 +8,8 @@ targeted at developers and map designers.
- :link: Design your maps online at **http://maputnik.com/editor/** (all in local storage) - :link: Design your maps online at **http://maputnik.com/editor/** (all in local storage)
- :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development - :link: Use the [Maputnik CLI](https://github.com/maputnik/editor/wiki/Maputnik-CLI) for local style development
Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independance is an OSS map designer.
## Documentation ## Documentation
The documentation can be found in the [Wiki](https://github.com/maputnik/editor/wiki). You are welcome to collaborate! The documentation can be found in the [Wiki](https://github.com/maputnik/editor/wiki). You are welcome to collaborate!
@@ -17,8 +19,6 @@ The documentation can be found in the [Wiki](https://github.com/maputnik/editor/
[![Design Map from Scratch](https://j.gifs.com/g5XMgl.gif)](https://youtu.be/XoDh0gEnBQo) [![Design Map from Scratch](https://j.gifs.com/g5XMgl.gif)](https://youtu.be/XoDh0gEnBQo)
Mapbox has built one of the best and most amazing OSS ecosystems. A key component to ensure its longevity and independance is an OSS map designer.
## Develop ## Develop
Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/). Maputnik is written in ES6 and is using [React](https://github.com/facebook/react) and [Mapbox GL JS](https://www.mapbox.com/mapbox-gl-js/api/).

View File

@@ -1,6 +1,6 @@
{ {
"name": "maputnik", "name": "maputnik",
"version": "1.0.0", "version": "1.0.1",
"description": "A MapboxGL visual style editor", "description": "A MapboxGL visual style editor",
"main": "''", "main": "''",
"scripts": { "scripts": {
@@ -30,12 +30,11 @@
"lodash.isequal": "^4.4.0", "lodash.isequal": "^4.4.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.31.0", "mapbox-gl": "^0.31.0",
"mapbox-gl-inspect": "^1.2.0", "mapbox-gl-inspect": "^1.2.1",
"mapbox-gl-style-spec": "^8.11.0", "mapbox-gl-style-spec": "^8.11.0",
"mousetrap": "^1.6.0", "mousetrap": "^1.6.0",
"ol-mapbox-style": "1.0.1", "ol-mapbox-style": "1.0.1",
"openlayers": "^3.19.1", "openlayers": "^3.19.1",
"randomcolor": "^0.4.4",
"react": "^15.4.0", "react": "^15.4.0",
"react-addons-pure-render-mixin": "^15.4.0", "react-addons-pure-render-mixin": "^15.4.0",
"react-autocomplete": "^1.4.0", "react-autocomplete": "^1.4.0",

View File

@@ -91,6 +91,7 @@ export default class Toolbar extends React.Component {
/> />
<ExportModal <ExportModal
mapStyle={this.props.mapStyle} mapStyle={this.props.mapStyle}
onStyleChanged={this.props.onStyleChanged}
isOpen={this.state.isOpen.export} isOpen={this.state.isOpen.export}
onOpenToggle={this.toggleModal.bind(this, 'export')} onOpenToggle={this.toggleModal.bind(this, 'export')}
/> />

View File

@@ -132,7 +132,6 @@ export default class ZoomSpecProperty extends React.Component {
if(this.props.fieldSpec['zoom-function']) { if(this.props.fieldSpec['zoom-function']) {
zoomBtn = <MakeZoomFunctionButton onClick={this.makeZoomFunction.bind(this)} /> zoomBtn = <MakeZoomFunctionButton onClick={this.makeZoomFunction.bind(this)} />
} }
return <InputBlock return <InputBlock
doc={this.props.fieldSpec.doc} doc={this.props.fieldSpec.doc}
label={labelFromFieldName(this.props.fieldName)} label={labelFromFieldName(this.props.fieldName)}
@@ -143,11 +142,10 @@ export default class ZoomSpecProperty extends React.Component {
} }
render() { render() {
if(isZoomField(this.props.value)) { const propClass = this.props.fieldSpec.default === this.props.value ? "maputnik-default-property" : "maputnik-modified-property"
return this.renderZoomProperty(); return <div className={propClass}>
} else { {isZoomField(this.props.value) ? this.renderZoomProperty() : this.renderProperty()}
return this.renderProperty(); </div>
}
} }
} }

View File

@@ -5,6 +5,12 @@ import StringInput from '../inputs/StringInput'
import AutocompleteInput from '../inputs/AutocompleteInput' import AutocompleteInput from '../inputs/AutocompleteInput'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
function tryParseInt(v) {
if (v === '') return v
if (isNaN(v)) return v
return parseFloat(v)
}
class SingleFilterEditor extends React.Component { class SingleFilterEditor extends React.Component {
static propTypes = { static propTypes = {
filter: React.PropTypes.array.isRequired, filter: React.PropTypes.array.isRequired,
@@ -17,7 +23,12 @@ class SingleFilterEditor extends React.Component {
} }
onFilterPartChanged(filterOp, propertyName, filterArgs) { onFilterPartChanged(filterOp, propertyName, filterArgs) {
const newFilter = [filterOp, propertyName, ...filterArgs] let newFilter = [filterOp, propertyName, ...filterArgs.map(tryParseInt)]
if(filterOp === 'has' || filterOp === '!has') {
newFilter = [filterOp, propertyName]
} else if(filterArgs.length === 0) {
newFilter = [filterOp, propertyName, '']
}
this.props.onChange(newFilter) this.props.onChange(newFilter)
} }
@@ -42,12 +53,14 @@ class SingleFilterEditor extends React.Component {
options={otherFilterOps} options={otherFilterOps}
/> />
</div> </div>
{filterArgs.length > 0 &&
<div className="maputnik-filter-editor-args"> <div className="maputnik-filter-editor-args">
<StringInput <StringInput
value={filterArgs.join(',')} value={filterArgs.join(',')}
onChange={ v=> this.onFilterPartChanged(filterOp, propertyName, v.split(','))} onChange={ v=> this.onFilterPartChanged(filterOp, propertyName, v.split(','))}
/> />
</div> </div>
}
</div> </div>
} }

View File

@@ -17,7 +17,9 @@ class CheckboxInput extends React.Component {
checked={this.props.value} checked={this.props.value}
/> />
<div className="maputnik-checkbox-box"> <div className="maputnik-checkbox-box">
<svg className="maputnik-checkbox-icon" viewBox='0 0 32 32'> <svg style={{
display: this.props.value ? 'inline' : 'none'
}} className="maputnik-checkbox-icon" viewBox='0 0 32 32'>
<path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' /> <path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' />
</svg> </svg>
</div> </div>

View File

@@ -1,9 +1,6 @@
import React from 'react' import React from 'react'
import AutocompleteInput from './AutocompleteInput' import AutocompleteInput from './AutocompleteInput'
//TODO: Query available font stack dynamically
import fontStacks from '../../config/fontstacks.json'
class FontInput extends React.Component { class FontInput extends React.Component {
static propTypes = { static propTypes = {
value: React.PropTypes.array.isRequired, value: React.PropTypes.array.isRequired,

View File

@@ -27,7 +27,7 @@ class StringInput extends React.Component {
placeholder={this.props.default} placeholder={this.props.default}
onChange={e => this.setState({ value: e.target.value })} onChange={e => this.setState({ value: e.target.value })}
onBlur={() => { onBlur={() => {
if(this.state.value) this.props.onChange(this.state.value) if(this.state.value!==this.props.value) this.props.onChange(this.state.value)
}} }}
/> />
} }

View File

@@ -6,6 +6,8 @@ import PropertyGroup from '../fields/PropertyGroup'
import LayerEditorGroup from './LayerEditorGroup' import LayerEditorGroup from './LayerEditorGroup'
import LayerTypeBlock from './LayerTypeBlock' import LayerTypeBlock from './LayerTypeBlock'
import LayerIdBlock from './LayerIdBlock' import LayerIdBlock from './LayerIdBlock'
import MinZoomBlock from './MinZoomBlock'
import MaxZoomBlock from './MaxZoomBlock'
import LayerSourceBlock from './LayerSourceBlock' import LayerSourceBlock from './LayerSourceBlock'
import LayerSourceLayerBlock from './LayerSourceLayerBlock' import LayerSourceLayerBlock from './LayerSourceLayerBlock'
@@ -130,6 +132,14 @@ export default class LayerEditor extends React.Component {
onChange={v => this.changeProperty(null, 'source-layer', v)} onChange={v => this.changeProperty(null, 'source-layer', v)}
/> />
} }
<MinZoomBlock
value={this.props.layer.minzoom}
onChange={v => this.changeProperty(null, 'minzoom', v)}
/>
<MaxZoomBlock
value={this.props.layer.maxzoom}
onChange={v => this.changeProperty(null, 'maxzoom', v)}
/>
</div> </div>
case 'filter': return <div> case 'filter': return <div>
<div className="maputnik-filter-editor-wrapper"> <div className="maputnik-filter-editor-wrapper">

View File

@@ -115,7 +115,7 @@ class LayerListContainer extends React.Component {
if(lookupKey in this.state.collapsedGroups) { if(lookupKey in this.state.collapsedGroups) {
newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey] newGroups[lookupKey] = !this.state.collapsedGroups[lookupKey]
} else { } else {
newGroups[lookupKey] = true newGroups[lookupKey] = false
} }
this.setState({ this.setState({
collapsedGroups: newGroups collapsedGroups: newGroups
@@ -124,7 +124,7 @@ class LayerListContainer extends React.Component {
isCollapsed(groupPrefix, idx) { isCollapsed(groupPrefix, idx) {
const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')] const collapsed = this.state.collapsedGroups[[groupPrefix, idx].join('-')]
return collapsed === undefined ? false : collapsed return collapsed === undefined ? true : collapsed
} }
render() { render() {
@@ -147,7 +147,7 @@ class LayerListContainer extends React.Component {
const groupIdx = findClosestCommonPrefix(this.props.layers, idx) const groupIdx = findClosestCommonPrefix(this.props.layers, idx)
const listItem = <LayerListItem const listItem = <LayerListItem
className={classnames({ className={classnames({
'maputnik-layer-list-item-collapsed': this.isCollapsed(groupPrefix, groupIdx), 'maputnik-layer-list-item-collapsed': layers.length > 1 && this.isCollapsed(groupPrefix, groupIdx),
'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1 'maputnik-layer-list-item-group-last': idxInGroup == layers.length - 1 && layers.length > 1
})} })}
index={idx} index={idx}

View File

@@ -0,0 +1,26 @@
import React from 'react'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput'
class MaxZoomBlock extends React.Component {
static propTypes = {
value: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired,
}
render() {
return <InputBlock label={"Max Zoom"} doc={GlSpec.layer.maxzoom.doc}>
<NumberInput
value={this.props.value}
onChange={this.props.onChange}
min={GlSpec.layer.maxzoom.minimum}
max={GlSpec.layer.maxzoom.maximum}
default={GlSpec.layer.maxzoom.maximum}
/>
</InputBlock>
}
}
export default MaxZoomBlock

View File

@@ -0,0 +1,26 @@
import React from 'react'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
import InputBlock from '../inputs/InputBlock'
import NumberInput from '../inputs/NumberInput'
class MinZoomBlock extends React.Component {
static propTypes = {
value: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired,
}
render() {
return <InputBlock label={"Min Zoom"} doc={GlSpec.layer.minzoom.doc}>
<NumberInput
value={this.props.value}
onChange={this.props.onChange}
min={GlSpec.layer.minzoom.minimum}
max={GlSpec.layer.minzoom.maximum}
default={GlSpec.layer.minzoom.minimum}
/>
</InputBlock>
}
}
export default MinZoomBlock

View File

@@ -23,6 +23,9 @@ function renderProperties(feature) {
function renderFeature(feature) { function renderFeature(feature) {
return <div key={feature.id}> return <div key={feature.id}>
<div className="maputnik-popup-layer-id">{feature.layer['source-layer']}</div> <div className="maputnik-popup-layer-id">{feature.layer['source-layer']}</div>
<InputBlock key={"property-type"} label={"$type"}>
<StringInput value={feature.geometry.type} style={{backgroundColor: 'transparent'}} />
</InputBlock>
{renderProperties(feature)} {renderProperties(feature)}
</div> </div>
} }

View File

@@ -7,6 +7,8 @@ import FeaturePropertyPopup from './FeaturePropertyPopup'
import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color' import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color'
import style from '../../libs/style.js' import style from '../../libs/style.js'
import tokens from '../../config/tokens.json' import tokens from '../../config/tokens.json'
import colors from 'mapbox-gl-inspect/lib/colors'
import Color from 'color'
import { colorHighlightedLayer } from '../../libs/highlight' import { colorHighlightedLayer } from '../../libs/highlight'
import 'mapbox-gl/dist/mapbox-gl.css' import 'mapbox-gl/dist/mapbox-gl.css'
import '../../mapboxgl.css' import '../../mapboxgl.css'
@@ -112,11 +114,15 @@ export default class MapboxGlMap extends React.Component {
const inspect = new MapboxInspect({ const inspect = new MapboxInspect({
popup: new MapboxGl.Popup({ popup: new MapboxGl.Popup({
closeButton: false,
closeOnClick: false closeOnClick: false
}), }),
showMapPopup: true, showMapPopup: true,
showMapPopupOnHover: false,
showInspectMapPopupOnHover: true,
showInspectButton: false, showInspectButton: false,
assignLayerColor: (layerId, alpha) => {
return Color(colors.brightColor(layerId, alpha)).desaturate(0.5).string()
},
buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer), buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer),
renderPopup: features => { renderPopup: features => {
if(this.props.inspectModeEnabled) { if(this.props.inspectModeEnabled) {

View File

@@ -5,9 +5,11 @@ import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button' import Button from '../Button'
import Modal from './Modal' import Modal from './Modal'
import MdFileDownload from 'react-icons/lib/md/file-download' import MdFileDownload from 'react-icons/lib/md/file-download'
import style from '../../libs/style.js'
import formatStyle from 'mapbox-gl-style-spec/lib/format' import formatStyle from 'mapbox-gl-style-spec/lib/format'
import GitHub from 'github-api' import GitHub from 'github-api'
@@ -15,18 +17,35 @@ import GitHub from 'github-api'
class Gist extends React.Component { class Gist extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
} }
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {} this.state = {
preview: false,
saving: false,
latestGist: null,
}
}
componentWillReceiveProps(nextProps) {
this.setState({
...this.state,
preview: !!(nextProps.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']
})
} }
onSave() { onSave() {
this.setState({ this.setState({
...this.state,
saving: true saving: true
}); });
const mapStyleStr = formatStyle(this.props.mapStyle); const preview = this.state.preview && (this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token'];
const mapStyleStr = preview ?
formatStyle(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
formatStyle(stripAccessTokens(this.props.mapStyle));
const styleTitle = this.props.mapStyle.name || 'Style'; const styleTitle = this.props.mapStyle.name || 'Style';
const htmlStr = ` const htmlStr = `
<!DOCTYPE html> <!DOCTYPE html>
@@ -56,49 +75,100 @@ class Gist extends React.Component {
</body> </body>
</html> </html>
` `
const files = {
"style.json": {
content: mapStyleStr
}
}
if(preview) {
files["index.html"] = {
content: htmlStr
}
}
const gh = new GitHub(); const gh = new GitHub();
let gist = gh.getGist(); // not a gist yet let gist = gh.getGist(); // not a gist yet
gist.create({ gist.create({
public: true, public: true,
description: styleTitle + 'Preview', description: styleTitle,
files: { files: files
"style.json": {
content: mapStyleStr
},
"index.html": {
content: htmlStr
}
}
}).then(function({data}) { }).then(function({data}) {
return gist.read(); return gist.read();
}).then(function({data}) { }).then(function({data}) {
this.setState({ this.setState({
latestGist: data ...this.state,
latestGist: data,
saving: false,
}); });
}.bind(this)); }.bind(this));
} }
onPreviewChange(value) {
this.setState({
...this.state,
preview: value
})
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
renderPreviewLink() {
const gist = this.state.latestGist;
const user = gist.user || 'anonymous';
const preview = !!gist.files['index.html'];
if(preview) {
return <span><a target="_blank" href={"https://bl.ocks.org/"+user+"/"+gist.id}>Preview</a>,{' '}</span>
}
return null;
}
renderLatestGist() { renderLatestGist() {
const gist = this.state.latestGist; const gist = this.state.latestGist;
const saving = this.state.saving; const saving = this.state.saving;
if(gist) { if(saving) {
return <p>Saving...</p>
} else if(gist) {
const user = gist.user || 'anonymous'; const user = gist.user || 'anonymous';
return <p> return <p>
Latest saved gist:{' '} Latest saved gist:{' '}
<a target="_blank" href={"https://bl.ocks.org/"+user+"/"+gist.id}>Preview</a>,{' '} {this.renderPreviewLink(this)}
<a target="_blank" href={"https://gist.github.com/"+user+"/"+gist.id}>Source</a> <a target="_blank" href={"https://gist.github.com/"+user+"/"+gist.id}>Source</a>
</p> </p>
} else if(saving) {
return <p>Saving...</p>
} }
} }
render() { render() {
return <div> return <div className="maputnik-export-gist">
<Button onClick={this.onSave.bind(this)}> <Button onClick={this.onSave.bind(this)}>
<MdFileDownload /> <MdFileDownload />
Save to Gist (anonymous) Save to Gist (anonymous)
</Button> </Button>
{' '}
<CheckboxInput
value={this.state.preview}
name='gist-style-preview'
onChange={this.onPreviewChange.bind(this)}
/>
<span> Include preview</span>
{this.state.preview ?
<div>
<InputBlock
label={"OpenMapTiles Access Token: "}>
<StringInput
value={(this.props.mapStyle.metadata || {})['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}/>
</InputBlock>
<a target="_blank" href="https://openmaptiles.com/hosting/">Get your free access token</a>
</div>
: null}
{this.renderLatestGist()} {this.renderLatestGist()}
</div> </div>
} }
@@ -117,6 +187,7 @@ function stripAccessTokens(mapStyle) {
class ExportModal extends React.Component { class ExportModal extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
isOpen: React.PropTypes.bool.isRequired, isOpen: React.PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, onOpenToggle: React.PropTypes.func.isRequired,
} }
@@ -150,7 +221,7 @@ class ExportModal extends React.Component {
<div className="maputnik-modal-section"> <div className="maputnik-modal-section">
<h4>Save style</h4> <h4>Save style</h4>
<Gist mapStyle={this.props.mapStyle} /> <Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
</div> </div>
</Modal> </Modal>
} }

View File

@@ -101,7 +101,9 @@ class OpenModal extends React.Component {
<p> <p>
Open one of the publicly available styles to start from. Open one of the publicly available styles to start from.
</p> </p>
<div className="maputnik-style-gallery-container">
{styleOptions} {styleOptions}
</div>
</section> </section>
</Modal> </Modal>
} }

View File

@@ -29,6 +29,15 @@ class TileURLSourceEditor extends React.Component {
onChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired,
} }
changeTileUrl(idx, value) {
const tiles = this.props.source.tiles.slice(0)
tiles[idx] = value
this.props.onChange({
...this.props.source,
tiles: tiles
})
}
renderTileUrls() { renderTileUrls() {
const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th'] const prefix = ['1st', '2nd', '3rd', '4th', '5th', '6th', '7th']
const tiles = this.props.source.tiles || [] const tiles = this.props.source.tiles || []
@@ -36,6 +45,7 @@ class TileURLSourceEditor extends React.Component {
return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={GlSpec.source_tile.tiles.doc}> return <InputBlock key={tileIndex} label={prefix[tileIndex] + " Tile URL"} doc={GlSpec.source_tile.tiles.doc}>
<StringInput <StringInput
value={tileUrl} value={tileUrl}
onChange={this.changeTileUrl.bind(this, tileIndex)}
/> />
</InputBlock> </InputBlock>
}) })

View File

@@ -10,6 +10,5 @@
"sources": { }, "sources": { },
"glyphs": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf", "glyphs": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"sprites": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf", "sprites": "https://demo.tileserver.org/fonts/{fontstack}/{range}.pbf",
"layers": [], "layers": []
"id": "empty-style"
} }

View File

@@ -1,35 +0,0 @@
[
"Metropolis Black Italic",
"Metropolis Black",
"Metropolis Bold Italic",
"Metropolis Bold",
"Metropolis Extra Bold Italic",
"Metropolis Extra Bold",
"Metropolis Extra Light Italic",
"Metropolis Extra Light",
"Metropolis Light Italic",
"Metropolis Light",
"Metropolis Medium Italic",
"Metropolis Medium",
"Metropolis Regular Italic",
"Metropolis Regular",
"Metropolis Semi Bold Italic",
"Metropolis Semi Bold",
"Metropolis Thin Italic",
"Metropolis Thin",
"Open Sans Bold Italic",
"Open Sans Bold",
"Open Sans Extra Bold Italic",
"Open Sans Extra Bold",
"Open Sans Italic",
"Open Sans Light Italic",
"Open Sans Light",
"Open Sans Regular",
"Open Sans Semibold Italic",
"Open Sans Semibold",
"Klokantech Noto Sans Bold",
"Klokantech Noto Sans CJK Bold",
"Klokantech Noto Sans CJK Regular",
"Klokantech Noto Sans Italic",
"Klokantech Noto Sans Regular"
]

View File

@@ -2,26 +2,26 @@
{ {
"id": "klokantech-basic", "id": "klokantech-basic",
"title": "Klokantech Basic", "title": "Klokantech Basic",
"url": "https://rawgit.com/openmaptiles/klokantech-basic-gl-style/gh-pages/style-cdn.json", "url": "https://rawgit.com/openmaptiles/klokantech-basic-gl-style/master/style.json",
"thumbnail": "https://camo.githubusercontent.com/08dcb3dd384c6083b02e6692c939d68c4114eb33/687474703a2f2f64656d6f2e74696c657365727665722e6f72672f7374796c65732f6b6c6f6b616e746563682d62617369632f7374617469632f382e3631393138342c34372e3333363230332c31302e30372f363030783430304032782e706e67" "thumbnail": "http://maputnik.com/thumbnails/klokantech-basic.png"
}, },
{ {
"id": "dark-matter", "id": "dark-matter",
"title": "Dark Matter", "title": "Dark Matter",
"url": "https://rawgit.com/openmaptiles/dark-matter-gl-style/gh-pages/style-cdn.json", "url": "https://rawgit.com/openmaptiles/dark-matter-gl-style/master/style.json",
"thumbnail": "https://camo.githubusercontent.com/258db708523e523782addeecdcc8697368a24df9/687474703a2f2f64656d6f2e74696c657365727665722e6f72672f7374796c65732f6461726b2d6d61747465722f7374617469632f382e3534303538372c34372e3337303535352c31352e30382f363030783430304032782e706e67" "thumbnail": "http://maputnik.com/thumbnails/dark-matter.png"
}, },
{ {
"id": "positron", "id": "positron",
"title": "Positron", "title": "Positron",
"url": "https://rawgit.com/openmaptiles/positron-gl-style/gh-pages/style-cdn.json", "url": "https://rawgit.com/openmaptiles/positron-gl-style/master/style.json",
"thumbnail": "https://camo.githubusercontent.com/56df86562b6c36b7cc44ee6e8b91eb4d8e593b66/687474703a2f2f64656d6f2e74696c657365727665722e6f72672f7374796c65732f706f736974726f6e2f7374617469632f31302e3938373235382c34362e3435333135302c342e30322f363030783430304032782e706e67" "thumbnail": "http://maputnik.com/thumbnails/positron.png"
}, },
{ {
"id": "osm-bright", "id": "osm-bright",
"title": "OSM Bright", "title": "OSM Bright",
"url": "https://rawgit.com/openmaptiles/osm-bright-gl-style/gh-pages/style-cdn.json", "url": "https://rawgit.com/openmaptiles/osm-bright-gl-style/master/style.json",
"thumbnail": "https://camo.githubusercontent.com/0fdf9922c6b632f903e47b3dfbcfb65e62b25046/687474703a2f2f64656d6f2e74696c657365727665722e6f72672f7374796c65732f6f736d2d6272696768742f7374617469632f382e3234333936372c34362e3931363331352c372e32312f363030783430304032782e706e67" "thumbnail": "http://maputnik.com/thumbnails/osm-bright.png"
}, },
{ {
"id": "osm-liberty", "id": "osm-liberty",
@@ -34,5 +34,29 @@
"title": "Empty Style", "title": "Empty Style",
"url": "https://rawgit.com/maputnik/editor/master/src/config/empty-style.json", "url": "https://rawgit.com/maputnik/editor/master/src/config/empty-style.json",
"thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII=" "thumbnail": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAECAQAAAAHDYbIAAAAEUlEQVR42mP8/58BDhiJ4wAA974H/U5Xe1oAAAAASUVORK5CYII="
},
{
"id": "mapbox-satellite",
"title": "Mapbox Satellite",
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/satellite-v9.json",
"thumbnail": "http://maputnik.com/thumbnails/mapbox-satellite.png"
},
{
"id": "mapbox-bright",
"title": "Mapbox Bright",
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/bright-v9.json",
"thumbnail": "http://maputnik.com/thumbnails/mapbox-bright.png"
},
{
"id": "mapbox-basic",
"title": "Mapbox Basic",
"url": "https://rawgit.com/mapbox/mapbox-gl-styles/master/styles/basic-v9.json",
"thumbnail": "http://maputnik.com/thumbnails/mapbox-basic.png"
},
{
"id": "tilezen",
"title": "Tilezen",
"url": "https://rawgit.com/lukasmartinelli/tilezen-gl-style/master/style.json",
"thumbnail": "http://maputnik.com/thumbnails/tilezen.png"
} }
] ]

View File

@@ -6,7 +6,7 @@
}, },
"openmaptiles": { "openmaptiles": {
"type": "vector", "type": "vector",
"url": "https://free.tilehosting.com/data/v3.json?key=25ItXg7aI5wurYDtttD", "url": "https://free.tilehosting.com/data/v3.json?key={key}",
"title": "OpenMapTiles" "title": "OpenMapTiles"
}, },
"tilezen": { "tilezen": {

View File

@@ -1,6 +1,3 @@
import randomColor from 'randomcolor'
import Color from 'color'
import stylegen from 'mapbox-gl-inspect/lib/stylegen' import stylegen from 'mapbox-gl-inspect/lib/stylegen'
import colors from 'mapbox-gl-inspect/lib/colors' import colors from 'mapbox-gl-inspect/lib/colors'
@@ -8,6 +5,12 @@ export function colorHighlightedLayer(layer) {
if(!layer || layer.type === 'background' || layer.type === 'raster') return null if(!layer || layer.type === 'background' || layer.type === 'raster') return null
function changeLayer(l) { function changeLayer(l) {
if(l.type === 'circle') {
l.paint['circle-radius'] = 3
} else if(l.type === 'line') {
l.paint['line-width'] = 2
}
if(layer.filter) { if(layer.filter) {
l.filter = layer.filter l.filter = layer.filter
} else { } else {
@@ -17,7 +20,8 @@ export function colorHighlightedLayer(layer) {
return l return l
} }
const color = colors.brightColor(layer.id, 1) const sourceLayerId = layer['source-layer'] || ''
const color = colors.brightColor(sourceLayerId, 1)
const layers = [] const layers = []
if(layer.type === "fill" || layer.type === 'fill-extrusion') { if(layer.type === "fill" || layer.type === 'fill-extrusion') {

View File

@@ -21,7 +21,12 @@ function loadJSON(url, defaultValue, cb) {
export function downloadGlyphsMetadata(urlTemplate, cb) { export function downloadGlyphsMetadata(urlTemplate, cb) {
if(!urlTemplate) return cb([]) if(!urlTemplate) return cb([])
const url = urlTemplate.replace('{fontstack}/{range}.pbf', 'fontstacks.json')
// Special handling because Tileserver GL serves the fontstacks metadata differently
// https://github.com/klokantech/tileserver-gl/pull/104
let url = urlTemplate.replace('/fonts/{fontstack}/{range}.pbf', '/fontstacks.json')
url = url.replace('{fontstack}/{range}.pbf', 'fontstacks.json')
loadJSON(url, [], cb) loadJSON(url, [], cb)
} }

View File

@@ -72,3 +72,7 @@ label:hover {
clear: both; clear: both;
} }
} }
a {
color: white;
}

5
src/styles/_export.scss Normal file
View File

@@ -0,0 +1,5 @@
.maputnik-export-gist {
label.maputnik-checkbox-wrapper {
display: inline-block;
}
}

View File

@@ -129,3 +129,38 @@
background-color: $color-gray; background-color: $color-gray;
} }
} }
// PROPERTY
.maputnik-default-property {
.maputnik-input-block-label {
color: darken($color-lowgray, 25%);
}
.maputnik-string,
.maputnik-number,
.maputnik-color,
.maputnik-select,
.maputnik-checkbox-wrapper {
background-color: darken($color-gray, 2%);
color: darken($color-lowgray, 25%);
}
.maputnik-make-zoom-function svg {
opacity: 0.4;
}
.maputnik-multibutton .maputnik-button {
background-color: darken($color-midgray, 10%);
color: darken($color-lowgray, 25%);
&:hover {
background-color: lighten($color-midgray, 12);
color: $color-white;
}
}
.maputnik-multibutton .maputnik-button-selected {
background-color: darken($color-midgray, 2%);
color: $color-lowgray;
}
}

View File

@@ -65,6 +65,11 @@
@extend .maputnik-big-button; @extend .maputnik-big-button;
} }
.maputnik-style-gallery-container {
max-height: 400px;
overflow-y: scroll;
}
.maputnik-public-style { .maputnik-public-style {
vertical-align: top; vertical-align: top;
margin-top: 10px; margin-top: 10px;
@@ -176,3 +181,21 @@
margin-right: $margin-3; margin-right: $margin-3;
float: right; float: right;
} }
//EXPORT MODAL
.maputnik-export-gist {
font-size: $font-size-6;
.maputnik-input-block {
margin-left: 0;
margin-right: 0;
label {
vertical-align: middle;
}
}
span {
color: $color-lowgray;
}
}

View File

@@ -27,6 +27,7 @@ $toolbar-height: 40px;
@import 'picker'; @import 'picker';
@import 'toolbar'; @import 'toolbar';
@import 'modal'; @import 'modal';
@import 'export';
@import 'layout'; @import 'layout';
@import 'layer'; @import 'layer';
@import 'input'; @import 'input';

View File

@@ -14,7 +14,6 @@ module.exports = {
'mapbox-gl/dist/mapbox-gl.js', 'mapbox-gl/dist/mapbox-gl.js',
//TODO: Build failure because cannot resolve migrations file //TODO: Build failure because cannot resolve migrations file
//"mapbox-gl-style-spec", //"mapbox-gl-style-spec",
"randomcolor",
"lodash.clonedeep", "lodash.clonedeep",
"lodash.throttle", "lodash.throttle",
'color', 'color',