mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2025-01-14 17:33:30 +01:00
Merge branch 'feature/layout'
This commit is contained in:
commit
20788a0234
14 changed files with 573 additions and 507 deletions
|
@ -5,11 +5,10 @@ root = true
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
|
|
||||||
# Matches multiple files with brace expansion notation
|
# Matches multiple files with brace expansion notation
|
||||||
# Set default charset
|
# Set default charset
|
||||||
[*.{js,jsx,html,sass}]
|
[*.{js,jsx,html,sass}]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = tab
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
|
@ -15,6 +15,7 @@ before_install:
|
||||||
install:
|
install:
|
||||||
- npm install
|
- npm install
|
||||||
script:
|
script:
|
||||||
|
- mkdir public
|
||||||
- npm run build
|
- npm run build
|
||||||
- npm run lint
|
- npm run lint
|
||||||
- npm run test
|
- npm run test
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"build": "webpack --config webpack.production.config.js --progress --profile --colors",
|
"build": "webpack --config webpack.production.config.js --progress --profile --colors",
|
||||||
"test": "karma start --single-run",
|
"test": "karma start --single-run",
|
||||||
"test-watch": "karma start",
|
"test-watch": "karma start",
|
||||||
"start": "webpack-dev-server --progress --profile --colors",
|
"start": "webpack-dev-server --progress --profile --colors --watch-poll",
|
||||||
"lint": "eslint --ext js --ext jsx {src,test}"
|
"lint": "eslint --ext js --ext jsx {src,test}"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -32,7 +32,8 @@
|
||||||
"react-height": "^2.1.1",
|
"react-height": "^2.1.1",
|
||||||
"react-icons": "^2.2.1",
|
"react-icons": "^2.2.1",
|
||||||
"react-motion": "^0.4.4",
|
"react-motion": "^0.4.4",
|
||||||
"rebass": "^0.3.1"
|
"rebass": "^0.3.1",
|
||||||
|
"request": "^2.79.0"
|
||||||
},
|
},
|
||||||
"babel": {
|
"babel": {
|
||||||
"presets": [
|
"presets": [
|
||||||
|
|
41
src/apistore.js
Normal file
41
src/apistore.js
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import request from 'request'
|
||||||
|
import style from './style.js'
|
||||||
|
|
||||||
|
export class ApiStyleStore {
|
||||||
|
supported(cb) {
|
||||||
|
request('http://localhost:8000/styles', (error, response, body) => {
|
||||||
|
cb(error === undefined)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
latestStyle(cb) {
|
||||||
|
if(this.latestStyleId) {
|
||||||
|
request('http://localhost:8000/styles/' + this.latestStyleId, (error, response, body) => {
|
||||||
|
cb(JSON.parse(body))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
request('http://localhost:8000/styles', (error, response, body) => {
|
||||||
|
if (!error && response.statusCode == 200) {
|
||||||
|
const styleIds = JSON.parse(body);
|
||||||
|
this.latestStyleId = styleIds[0];
|
||||||
|
request('http://localhost:8000/styles/' + this.latestStyleId, (error, response, body) => {
|
||||||
|
cb(style.fromJSON(JSON.parse(body)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save current style replacing previous version
|
||||||
|
save(mapStyle) {
|
||||||
|
const id = mapStyle.get('id')
|
||||||
|
request.put({
|
||||||
|
url: 'http://localhost:8000/styles/' + id,
|
||||||
|
json: true,
|
||||||
|
body: style.toJSON(mapStyle)
|
||||||
|
}, (error, response, body) => {
|
||||||
|
console.log('Saved style');
|
||||||
|
})
|
||||||
|
return mapStyle
|
||||||
|
}
|
||||||
|
}
|
180
src/app.jsx
180
src/app.jsx
|
@ -10,111 +10,117 @@ import { Map } from './map.jsx'
|
||||||
import {Toolbar} from './toolbar.jsx'
|
import {Toolbar} from './toolbar.jsx'
|
||||||
import style from './style.js'
|
import style from './style.js'
|
||||||
import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.js'
|
import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.js'
|
||||||
|
import { ApiStyleStore } from './apistore.js'
|
||||||
import { WorkspaceDrawer } from './workspace.jsx'
|
import { WorkspaceDrawer } from './workspace.jsx'
|
||||||
|
|
||||||
import theme from './theme.js'
|
import theme from './theme.js'
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
|
|
||||||
export default class App extends React.Component {
|
export default class App extends React.Component {
|
||||||
static childContextTypes = {
|
static childContextTypes = {
|
||||||
rebass: React.PropTypes.object,
|
rebass: React.PropTypes.object,
|
||||||
reactIconBase: React.PropTypes.object
|
reactIconBase: React.PropTypes.object
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.styleStore = new StyleStore()
|
|
||||||
this.settingsStore = new SettingsStore()
|
|
||||||
this.state = {
|
|
||||||
accessToken: this.settingsStore.accessToken,
|
|
||||||
workContext: "layers",
|
|
||||||
currentStyle: this.styleStore.latestStyle(),
|
|
||||||
}
|
|
||||||
if(this.state.currentStyle.get('layers').size === 0) {
|
|
||||||
loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onReset() {
|
this.styleStore = new ApiStyleStore()
|
||||||
this.styleStore.purge()
|
this.styleStore.supported(isSupported => {
|
||||||
loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle))
|
if(!isSupported) {
|
||||||
}
|
console.log('Falling back to local storage for storing styles')
|
||||||
|
this.styleStore = new StyleStore()
|
||||||
|
}
|
||||||
|
this.styleStore.latestStyle(mapStyle => this.onStyleUpload(mapStyle))
|
||||||
|
})
|
||||||
|
|
||||||
getChildContext() {
|
this.settingsStore = new SettingsStore()
|
||||||
return {
|
this.state = {
|
||||||
rebass: theme,
|
accessToken: this.settingsStore.accessToken,
|
||||||
reactIconBase: { size: 20 }
|
workContext: "layers",
|
||||||
}
|
currentStyle: style.emptyStyle
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onStyleDownload() {
|
onReset() {
|
||||||
const mapStyle = style.toJSON(this.state.currentStyle)
|
this.styleStore.purge()
|
||||||
const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"});
|
loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle))
|
||||||
saveAs(blob, mapStyle.id + ".json");
|
}
|
||||||
this.onStyleSave()
|
|
||||||
}
|
|
||||||
|
|
||||||
onStyleUpload(newStyle) {
|
getChildContext() {
|
||||||
const savedStyle = this.styleStore.save(newStyle)
|
return {
|
||||||
this.setState({ currentStyle: savedStyle })
|
rebass: theme,
|
||||||
}
|
reactIconBase: { size: 20 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onStyleSave() {
|
onStyleDownload() {
|
||||||
const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON())
|
const mapStyle = style.toJSON(this.state.currentStyle)
|
||||||
this.setState({ currentStyle: snapshotStyle })
|
const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"});
|
||||||
this.styleStore.save(snapshotStyle)
|
saveAs(blob, mapStyle.id + ".json");
|
||||||
}
|
this.onStyleSave()
|
||||||
|
}
|
||||||
|
|
||||||
onStyleChanged(newStyle) {
|
onStyleUpload(newStyle) {
|
||||||
this.setState({ currentStyle: newStyle })
|
const savedStyle = this.styleStore.save(newStyle)
|
||||||
}
|
this.setState({ currentStyle: savedStyle })
|
||||||
|
}
|
||||||
|
|
||||||
onOpenSettings() {
|
onStyleSave() {
|
||||||
this.setState({ workContext: "settings" })
|
const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON())
|
||||||
}
|
this.setState({ currentStyle: snapshotStyle })
|
||||||
|
console.log('Save')
|
||||||
|
this.styleStore.save(snapshotStyle)
|
||||||
|
}
|
||||||
|
|
||||||
onOpenAbout() {
|
onStyleChanged(newStyle) {
|
||||||
this.setState({ workContext: "about" })
|
this.setState({ currentStyle: newStyle })
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpenLayers() {
|
onOpenSettings() {
|
||||||
this.setState({ workContext: "layers", })
|
//TODO: open settings modal
|
||||||
}
|
//this.setState({ workContext: "settings" })
|
||||||
|
}
|
||||||
|
|
||||||
onOpenSources() {
|
onOpenAbout() {
|
||||||
this.setState({ workContext: "sources", })
|
//TODO: open about modal
|
||||||
}
|
//this.setState({ workContext: "about" })
|
||||||
|
}
|
||||||
|
|
||||||
onAccessTokenChanged(newToken) {
|
onOpenSources() {
|
||||||
this.settingsStore.accessToken = newToken
|
//TODO: open sources modal
|
||||||
this.setState({ accessToken: newToken })
|
//this.setState({ workContext: "sources", })
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
onAccessTokenChanged(newToken) {
|
||||||
return <div style={{ fontFamily: theme.fontFamily, color: theme.color, fontWeight: 300 }}>
|
this.settingsStore.accessToken = newToken
|
||||||
<Toolbar
|
this.setState({ accessToken: newToken })
|
||||||
styleAvailable={this.state.currentStyle.get('layers').size > 0}
|
}
|
||||||
onStyleSave={this.onStyleSave.bind(this)}
|
|
||||||
onStyleUpload={this.onStyleUpload.bind(this)}
|
render() {
|
||||||
onStyleDownload={this.onStyleDownload.bind(this)}
|
return <div style={{ fontFamily: theme.fontFamily, color: theme.color, fontWeight: 300 }}>
|
||||||
onOpenSettings={this.onOpenSettings.bind(this)}
|
<Toolbar
|
||||||
onOpenAbout={this.onOpenAbout.bind(this)}
|
styleAvailable={this.state.currentStyle.get('layers').size > 0}
|
||||||
onOpenLayers={this.onOpenLayers.bind(this)}
|
onStyleSave={this.onStyleSave.bind(this)}
|
||||||
onOpenSources={this.onOpenSources.bind(this)}
|
onStyleUpload={this.onStyleUpload.bind(this)}
|
||||||
/>
|
onStyleDownload={this.onStyleDownload.bind(this)}
|
||||||
<WorkspaceDrawer
|
onOpenSettings={this.onOpenSettings.bind(this)}
|
||||||
onStyleChanged={this.onStyleChanged.bind(this)}
|
onOpenAbout={this.onOpenAbout.bind(this)}
|
||||||
onReset={this.onReset.bind(this)}
|
onOpenSources={this.onOpenSources.bind(this)}
|
||||||
workContext={this.state.workContext}
|
/>
|
||||||
mapStyle={this.state.currentStyle}
|
<WorkspaceDrawer
|
||||||
accessToken={this.state.accessToken}
|
onStyleChanged={this.onStyleChanged.bind(this)}
|
||||||
onAccessTokenChanged={this.onAccessTokenChanged.bind(this)}
|
onReset={this.onReset.bind(this)}
|
||||||
/>
|
workContext={this.state.workContext}
|
||||||
<Map
|
mapStyle={this.state.currentStyle}
|
||||||
mapStyle={this.state.currentStyle}
|
accessToken={this.state.accessToken}
|
||||||
accessToken={this.state.accessToken}
|
onAccessTokenChanged={this.onAccessTokenChanged.bind(this)}
|
||||||
/>
|
/>
|
||||||
</div>
|
<Map
|
||||||
}
|
mapStyle={this.state.currentStyle}
|
||||||
|
accessToken={this.state.accessToken}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,55 +12,48 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
|
||||||
|
|
||||||
// List of collapsible layer editors
|
// List of collapsible layer editors
|
||||||
export class LayerList extends React.Component {
|
export class LayerList extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
layers: React.PropTypes.instanceOf(Immutable.OrderedMap),
|
layers: React.PropTypes.instanceOf(Immutable.OrderedMap),
|
||||||
onLayersChanged: React.PropTypes.func.isRequired
|
onLayersChanged: React.PropTypes.func.isRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerDestroyed(deletedLayer) {
|
onLayerDestroyed(deletedLayer) {
|
||||||
const remainingLayers = this.props.layers.delete(deletedLayer.get('id'))
|
const remainingLayers = this.props.layers.delete(deletedLayer.get('id'))
|
||||||
this.props.onLayersChanged(remainingLayers)
|
this.props.onLayersChanged(remainingLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayerChanged(layer) {
|
onLayerChanged(layer) {
|
||||||
const changedLayers = this.props.layers.set(layer.get('id'), layer)
|
const changedLayers = this.props.layers.set(layer.get('id'), layer)
|
||||||
this.props.onLayersChanged(changedLayers)
|
this.props.onLayersChanged(changedLayers)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var layerPanels = []
|
var layerPanels = []
|
||||||
layerPanels = this.props.layers.map(layer => {
|
layerPanels = this.props.layers.map(layer => {
|
||||||
return <LayerEditor
|
return <LayerEditor
|
||||||
key={layer.get('id')}
|
key={layer.get('id')}
|
||||||
layer={layer}
|
layer={layer}
|
||||||
onLayerDestroyed={this.onLayerDestroyed.bind(this)}
|
onLayerDestroyed={this.onLayerDestroyed.bind(this)}
|
||||||
onLayerChanged={this.onLayerChanged.bind(this)}
|
onLayerChanged={this.onLayerChanged.bind(this)}
|
||||||
/>
|
/>
|
||||||
}).toIndexedSeq()
|
}).toIndexedSeq()
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<Toolbar style={{marginRight: 20}}>
|
<div className={scrollbars.darkScrollbar} style={{
|
||||||
<NavItem>
|
overflowY: "scroll",
|
||||||
<Heading>Layers</Heading>
|
bottom:0,
|
||||||
</NavItem>
|
left:0,
|
||||||
<Space auto x={1} />
|
right:0,
|
||||||
</Toolbar>
|
top:1,
|
||||||
|
position: "absolute",
|
||||||
<div className={scrollbars.darkScrollbar} style={{
|
}}>
|
||||||
overflowY: "scroll",
|
{layerPanels}
|
||||||
bottom:0,
|
</div>
|
||||||
left:0,
|
</div>
|
||||||
right:0,
|
}
|
||||||
top:40,
|
|
||||||
position: "absolute",
|
|
||||||
}}>
|
|
||||||
{layerPanels}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,14 +12,15 @@ export class Map extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
const tokenChanged = nextProps.accessToken !== MapboxGl.accessToken
|
const hasTokenChanged = nextProps.accessToken !== MapboxGl.accessToken
|
||||||
|
MapboxGl.accessToken = nextProps.accessToken
|
||||||
|
|
||||||
// If the id has changed a new style has been uplaoded and
|
// If the id has changed a new style has been uplaoded and
|
||||||
// it is safer to do a full new render
|
// it is safer to do a full new render
|
||||||
// TODO: might already be handled in diff algorithm?
|
// TODO: might already be handled in diff algorithm?
|
||||||
const mapIdChanged = this.props.mapStyle.get('id') !== nextProps.mapStyle.get('id')
|
const mapIdChanged = this.props.mapStyle.get('id') !== nextProps.mapStyle.get('id')
|
||||||
|
|
||||||
if(mapIdChanged || tokenChanged) {
|
if(mapIdChanged || hasTokenChanged) {
|
||||||
this.state.map.setStyle(style.toJSON(nextProps.mapStyle))
|
this.state.map.setStyle(style.toJSON(nextProps.mapStyle))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
.darkScrollbar::-webkit-scrollbar {
|
.darkScrollbar::-webkit-scrollbar {
|
||||||
background-color: #313131;
|
background-color: #26282e;
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.darkScrollbar::-webkit-scrollbar-thumb {
|
.darkScrollbar::-webkit-scrollbar-thumb {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
|
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
|
||||||
background-color: #555;
|
background-color: #40444e;
|
||||||
padding-left: 2px;
|
padding-left: 2px;
|
||||||
padding-right: 2px;
|
padding-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
96
src/style.js
96
src/style.js
|
@ -9,34 +9,41 @@ import randomColor from 'randomcolor'
|
||||||
// It also ensures that every style has an id and
|
// It also ensures that every style has an id and
|
||||||
// a created date for future reference
|
// a created date for future reference
|
||||||
function fromJSON(jsonStyle) {
|
function fromJSON(jsonStyle) {
|
||||||
if (typeof jsonStyle === 'string' || jsonStyle instanceof String) {
|
if (typeof jsonStyle === 'string' || jsonStyle instanceof String) {
|
||||||
jsonStyle = JSON.parse(jsonStyle)
|
jsonStyle = JSON.parse(jsonStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Immutable.Map(Object.keys(jsonStyle).map(key => {
|
return Immutable.Map(Object.keys(jsonStyle).map(key => {
|
||||||
const val = jsonStyle[key]
|
const val = jsonStyle[key]
|
||||||
if(key === "layers") {
|
if(key === "layers") {
|
||||||
return [key, Immutable.OrderedMap(val.map(l => [l.id, Immutable.fromJS(l)]))]
|
return [key, Immutable.OrderedMap(val.map(l => [l.id, Immutable.fromJS(l)]))]
|
||||||
} else if(key === "sources" || key === "metadata" || key === "transition") {
|
} else if(key === "sources" || key === "metadata" || key === "transition") {
|
||||||
return [key, Immutable.fromJS(val)]
|
return [key, Immutable.fromJS(val)]
|
||||||
} else {
|
} else {
|
||||||
return [key, val]
|
return [key, val]
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Empty style is always used if no style could be restored or fetched
|
||||||
|
const emptyStyle = ensureMetadataExists(fromJSON({
|
||||||
|
version: 8,
|
||||||
|
sources: {},
|
||||||
|
layers: [],
|
||||||
|
}))
|
||||||
|
|
||||||
function ensureHasId(style) {
|
function ensureHasId(style) {
|
||||||
if(style.has('id')) return style
|
if(style.has('id')) return style
|
||||||
return style.set('id', Math.random().toString(36).substr(2, 9))
|
return style.set('id', Math.random().toString(36).substr(2, 9))
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureHasTimestamp(style) {
|
function ensureHasTimestamp(style) {
|
||||||
if(style.has('id')) return style
|
if(style.has('id')) return style
|
||||||
return style.set('created', new Date().toJSON())
|
return style.set('created', new Date().toJSON())
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureMetadataExists(style) {
|
function ensureMetadataExists(style) {
|
||||||
return ensureHasId(ensureHasTimestamp(style))
|
return ensureHasId(ensureHasTimestamp(style))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare style with other style and return changes
|
// Compare style with other style and return changes
|
||||||
|
@ -44,41 +51,42 @@ function ensureMetadataExists(style) {
|
||||||
// Should be able to improve performance since we can only compare
|
// Should be able to improve performance since we can only compare
|
||||||
// by reference
|
// by reference
|
||||||
function diffStyles(before, after) {
|
function diffStyles(before, after) {
|
||||||
return diffJSONStyles(toJSON(before), toJSON(after))
|
return diffJSONStyles(toJSON(before), toJSON(after))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turns immutable style back into JSON with the original order of the
|
// Turns immutable style back into JSON with the original order of the
|
||||||
// layers preserved
|
// layers preserved
|
||||||
function toJSON(mapStyle) {
|
function toJSON(mapStyle) {
|
||||||
const jsonStyle = {}
|
const jsonStyle = {}
|
||||||
for(let [key, value] of mapStyle.entries()) {
|
for(let [key, value] of mapStyle.entries()) {
|
||||||
if(key === "layers") {
|
if(key === "layers") {
|
||||||
jsonStyle[key] = value.toIndexedSeq().toJS()
|
jsonStyle[key] = value.toIndexedSeq().toJS()
|
||||||
} else if(key === 'sources' || key === "metadata" || key === "transition") {
|
} else if(key === 'sources' || key === "metadata" || key === "transition") {
|
||||||
jsonStyle[key] = value.toJS()
|
jsonStyle[key] = value.toJS()
|
||||||
} else {
|
} else {
|
||||||
jsonStyle[key] = value
|
jsonStyle[key] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return jsonStyle
|
return jsonStyle
|
||||||
}
|
}
|
||||||
|
|
||||||
export function colorizeLayers(layers) {
|
export function colorizeLayers(layers) {
|
||||||
return layers.map(layer => {
|
return layers.map(layer => {
|
||||||
if(!layer.metadata) {
|
if(!layer.metadata) {
|
||||||
layer.metadata = {}
|
layer.metadata = {}
|
||||||
}
|
}
|
||||||
if(!"maputnik:color" in layer.metadata) {
|
if(!"maputnik:color" in layer.metadata) {
|
||||||
layer.metadata["maputnik:color"] = randomColor()
|
layer.metadata["maputnik:color"] = randomColor()
|
||||||
}
|
}
|
||||||
return layer
|
return layer
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
colorizeLayers,
|
colorizeLayers,
|
||||||
toJSON,
|
toJSON,
|
||||||
fromJSON,
|
fromJSON,
|
||||||
diffStyles,
|
diffStyles,
|
||||||
ensureMetadataExists,
|
ensureMetadataExists,
|
||||||
|
emptyStyle,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,117 +5,114 @@ import style from './style.js'
|
||||||
const storagePrefix = "maputnik"
|
const storagePrefix = "maputnik"
|
||||||
const stylePrefix = 'style'
|
const stylePrefix = 'style'
|
||||||
const storageKeys = {
|
const storageKeys = {
|
||||||
latest: [storagePrefix, 'latest_style'].join(':'),
|
latest: [storagePrefix, 'latest_style'].join(':'),
|
||||||
accessToken: [storagePrefix, 'access_token'].join(':')
|
accessToken: [storagePrefix, 'access_token'].join(':')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty style is always used if no style could be restored or fetched
|
|
||||||
const emptyStyle = style.ensureMetadataExists(style.fromJSON({
|
|
||||||
version: 8,
|
|
||||||
sources: {},
|
|
||||||
layers: [],
|
|
||||||
}))
|
|
||||||
|
|
||||||
const defaultStyleUrl = "https://raw.githubusercontent.com/osm2vectortiles/mapbox-gl-styles/master/styles/basic-v9-cdn.json"
|
const defaultStyleUrl = "https://raw.githubusercontent.com/osm2vectortiles/mapbox-gl-styles/master/styles/basic-v9-cdn.json"
|
||||||
// Fetch a default style via URL and return it or a fallback style via callback
|
// Fetch a default style via URL and return it or a fallback style via callback
|
||||||
export function loadDefaultStyle(cb) {
|
export function loadDefaultStyle(cb) {
|
||||||
console.log('Load default style')
|
console.log('Load default style')
|
||||||
var request = new XMLHttpRequest()
|
var request = new XMLHttpRequest()
|
||||||
request.open('GET', defaultStyleUrl, true)
|
request.open('GET', defaultStyleUrl, true)
|
||||||
|
|
||||||
request.onload = () => {
|
request.onload = () => {
|
||||||
if (request.status >= 200 && request.status < 400) {
|
if (request.status >= 200 && request.status < 400) {
|
||||||
cb(style.ensureMetadataExists(style.fromJSON(request.responseText)))
|
cb(style.ensureMetadataExists(style.fromJSON(request.responseText)))
|
||||||
} else {
|
} else {
|
||||||
cb(emptyStyle)
|
cb(style.emptyStyle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request.onerror = function() {
|
request.onerror = function() {
|
||||||
console.log('Could not fetch default style')
|
console.log('Could not fetch default style')
|
||||||
cb(emptyStyle)
|
cb(style.emptyStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.send()
|
request.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return style ids and dates of all styles stored in local storage
|
// Return style ids and dates of all styles stored in local storage
|
||||||
function loadStoredStyles() {
|
function loadStoredStyles() {
|
||||||
const styles = []
|
const styles = []
|
||||||
for (let i = 0; i < window.localStorage.length; i++) {
|
for (let i = 0; i < window.localStorage.length; i++) {
|
||||||
const key = window.localStorage.key(i)
|
const key = window.localStorage.key(i)
|
||||||
if(isStyleKey(key)) {
|
if(isStyleKey(key)) {
|
||||||
styles.push(fromKey(key))
|
styles.push(fromKey(key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return styles
|
return styles
|
||||||
}
|
}
|
||||||
|
|
||||||
function isStyleKey(key) {
|
function isStyleKey(key) {
|
||||||
const parts = key.split(":")
|
const parts = key.split(":")
|
||||||
return parts.length == 3 && parts[0] === storagePrefix && parts[1] === stylePrefix
|
return parts.length == 3 && parts[0] === storagePrefix && parts[1] === stylePrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load style id from key
|
// Load style id from key
|
||||||
function fromKey(key) {
|
function fromKey(key) {
|
||||||
if(!isStyleKey(key)) {
|
if(!isStyleKey(key)) {
|
||||||
throw "Key is not a valid style key"
|
throw "Key is not a valid style key"
|
||||||
}
|
}
|
||||||
|
|
||||||
const parts = key.split(":")
|
const parts = key.split(":")
|
||||||
const styleId = parts[2]
|
const styleId = parts[2]
|
||||||
return styleId
|
return styleId
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate key that identifies the style with a version
|
// Calculate key that identifies the style with a version
|
||||||
function styleKey(styleId) {
|
function styleKey(styleId) {
|
||||||
return [storagePrefix, stylePrefix, styleId].join(":")
|
return [storagePrefix, stylePrefix, styleId].join(":")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store style independent settings
|
// Store style independent settings
|
||||||
export class SettingsStore {
|
export class SettingsStore {
|
||||||
get accessToken() {
|
get accessToken() {
|
||||||
const token = window.localStorage.getItem(storageKeys.accessToken)
|
const token = window.localStorage.getItem(storageKeys.accessToken)
|
||||||
return token ? token : ""
|
return token ? token : ""
|
||||||
}
|
}
|
||||||
set accessToken(val) {
|
set accessToken(val) {
|
||||||
window.localStorage.setItem(storageKeys.accessToken, val)
|
window.localStorage.setItem(storageKeys.accessToken, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manages many possible styles that are stored in the local storage
|
// Manages many possible styles that are stored in the local storage
|
||||||
export class StyleStore {
|
export class StyleStore {
|
||||||
// Tile store will load all items from local storage and
|
// Tile store will load all items from local storage and
|
||||||
// assume they do not change will working on it
|
// assume they do not change will working on it
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mapStyles = loadStoredStyles()
|
this.mapStyles = loadStoredStyles()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete entire style history
|
supported(cb) {
|
||||||
purge() {
|
cb(window.localStorage !== undefined)
|
||||||
for (let i = 0; i < window.localStorage.length; i++) {
|
}
|
||||||
const key = window.localStorage.key(i)
|
|
||||||
if(key.startsWith(storagePrefix)) {
|
|
||||||
window.localStorage.removeItem(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the last edited style
|
// Delete entire style history
|
||||||
latestStyle() {
|
purge() {
|
||||||
if(this.mapStyles.length === 0) return emptyStyle
|
for (let i = 0; i < window.localStorage.length; i++) {
|
||||||
const styleId = window.localStorage.getItem(storageKeys.latest)
|
const key = window.localStorage.key(i)
|
||||||
const styleItem = window.localStorage.getItem(styleKey(styleId))
|
if(key.startsWith(storagePrefix)) {
|
||||||
|
window.localStorage.removeItem(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(styleItem) return style.fromJSON(styleItem)
|
// Find the last edited style
|
||||||
return emptyStyle
|
latestStyle(cb) {
|
||||||
}
|
if(this.mapStyles.length === 0) return cb(style.emptyStyle)
|
||||||
|
const styleId = window.localStorage.getItem(storageKeys.latest)
|
||||||
|
const styleItem = window.localStorage.getItem(styleKey(styleId))
|
||||||
|
|
||||||
// Save current style replacing previous version
|
if(styleItem) return cb(style.fromJSON(styleItem))
|
||||||
save(mapStyle) {
|
cb(style.emptyStyle)
|
||||||
const key = styleKey(mapStyle.get('id'))
|
}
|
||||||
window.localStorage.setItem(key, JSON.stringify(style.toJSON(mapStyle)))
|
|
||||||
window.localStorage.setItem(storageKeys.latest, mapStyle.get('id'))
|
// Save current style replacing previous version
|
||||||
return mapStyle
|
save(mapStyle) {
|
||||||
}
|
const key = styleKey(mapStyle.get('id'))
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(style.toJSON(mapStyle)))
|
||||||
|
window.localStorage.setItem(storageKeys.latest, mapStyle.get('id'))
|
||||||
|
return mapStyle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
166
src/theme.js
166
src/theme.js
|
@ -1,112 +1,112 @@
|
||||||
const caps = {
|
const caps = {
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
letterSpacing: '.2em'
|
letterSpacing: '.2em'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const fullHeight = {
|
export const fullHeight = {
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseColors = {
|
const baseColors = {
|
||||||
black: '#242424',
|
black: '#1c1f24',
|
||||||
gray: '#313131',
|
gray: '#26282e',
|
||||||
midgray: '#778',
|
midgray: '#36383e',
|
||||||
lowgray: '#dcdcdc',
|
lowgray: '#8e8e8e',
|
||||||
white: '#fff',
|
white: '#fff',
|
||||||
blue: '#00d9f7',
|
blue: '#00d9f7',
|
||||||
green: '#B4C7AD',
|
green: '#B4C7AD',
|
||||||
orange: '#fb3',
|
orange: '#fb3',
|
||||||
red: '#f04',
|
red: '#f04',
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeColors = {
|
const themeColors = {
|
||||||
primary: baseColors.gray,
|
primary: baseColors.gray,
|
||||||
secondary: baseColors.midgray,
|
secondary: baseColors.midgray,
|
||||||
default: baseColors.gray,
|
default: baseColors.gray,
|
||||||
info: baseColors.blue,
|
info: baseColors.blue,
|
||||||
success: baseColors.green,
|
success: baseColors.green,
|
||||||
warning: baseColors.orange,
|
warning: baseColors.orange,
|
||||||
error: baseColors.red
|
error: baseColors.red
|
||||||
}
|
}
|
||||||
|
|
||||||
export const colors = {
|
export const colors = {
|
||||||
...baseColors,
|
...baseColors,
|
||||||
...themeColors
|
...themeColors
|
||||||
}
|
}
|
||||||
|
|
||||||
export const inputBase = {
|
export const inputBase = {
|
||||||
display: 'block',
|
display: 'block',
|
||||||
border: '1px solid rgb(36, 36, 36)',
|
border: '1px solid rgb(36, 36, 36)',
|
||||||
height: 30,
|
height: 30,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
paddingLeft: 5,
|
paddingLeft: 5,
|
||||||
paddingRight: 5,
|
paddingRight: 5,
|
||||||
backgroundColor: colors.gray,
|
backgroundColor: colors.gray,
|
||||||
}
|
}
|
||||||
|
|
||||||
const scale = [3, 5, 10, 30, 40]
|
const scale = [3, 5, 10, 30, 40]
|
||||||
const fontSizes = [28, 24, 20, 16, 14, 12, 10]
|
const fontSizes = [28, 24, 20, 16, 14, 12, 10]
|
||||||
|
|
||||||
const border = {
|
const border = {
|
||||||
borderColor: colors.black,
|
borderColor: colors.black,
|
||||||
borderRadius: 0,
|
borderRadius: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
const dark = {
|
const dark = {
|
||||||
name: 'Dark',
|
name: 'Dark',
|
||||||
color: colors.white,
|
color: colors.white,
|
||||||
fontFamily: 'Roboto, sans-serif',
|
fontFamily: 'Roboto, sans-serif',
|
||||||
scale,
|
scale,
|
||||||
fontSizes,
|
fontSizes,
|
||||||
colors,
|
colors,
|
||||||
inverted: colors.midGray,
|
inverted: colors.midGray,
|
||||||
...border,
|
...border,
|
||||||
|
|
||||||
Block: {
|
Block: {
|
||||||
backgroundColor: colors.gray,
|
backgroundColor: colors.gray,
|
||||||
...border,
|
...border,
|
||||||
borderLeft: 0,
|
borderLeft: 0,
|
||||||
borderRight: 0,
|
borderRight: 0,
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
},
|
},
|
||||||
PanelHeader: {
|
PanelHeader: {
|
||||||
marginRight: -10,
|
marginRight: -10,
|
||||||
marginBottom: 0,
|
marginBottom: 0,
|
||||||
fontSize: fontSizes[5],
|
fontSize: fontSizes[5],
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: colors.white,
|
color: colors.white,
|
||||||
},
|
},
|
||||||
Button: {
|
Button: {
|
||||||
color: '#00d9f7',
|
color: '#00d9f7',
|
||||||
},
|
},
|
||||||
Menu: {
|
Menu: {
|
||||||
color: '#00d9f7',
|
color: '#00d9f7',
|
||||||
backgroundColor: '#000'
|
backgroundColor: '#000'
|
||||||
},
|
},
|
||||||
Message: {
|
Message: {
|
||||||
color: '#111',
|
color: '#111',
|
||||||
opacity: 15/16
|
opacity: 15/16
|
||||||
},
|
},
|
||||||
Header: {
|
Header: {
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
},
|
},
|
||||||
ButtonCircle : {
|
ButtonCircle : {
|
||||||
},
|
},
|
||||||
Toolbar: {
|
Toolbar: {
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
minHeight: scale[3]
|
minHeight: scale[3]
|
||||||
},
|
},
|
||||||
Label: {
|
Label: {
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
},
|
},
|
||||||
Input: {
|
Input: {
|
||||||
fontWeight: 300,
|
fontWeight: 300,
|
||||||
fontSize: fontSizes[5],
|
fontSize: fontSizes[5],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default dark
|
export default dark
|
||||||
|
|
222
src/toolbar.jsx
222
src/toolbar.jsx
|
@ -12,122 +12,138 @@ import Fixed from 'rebass/dist/Fixed'
|
||||||
|
|
||||||
import MdFileDownload from 'react-icons/lib/md/file-download'
|
import MdFileDownload from 'react-icons/lib/md/file-download'
|
||||||
import MdFileUpload from 'react-icons/lib/md/file-upload'
|
import MdFileUpload from 'react-icons/lib/md/file-upload'
|
||||||
|
import MdOpenInBrowser from 'react-icons/lib/md/open-in-browser'
|
||||||
import MdSettings from 'react-icons/lib/md/settings'
|
import MdSettings from 'react-icons/lib/md/settings'
|
||||||
import MdInfo from 'react-icons/lib/md/info'
|
import MdInfo from 'react-icons/lib/md/info'
|
||||||
import MdLayers from 'react-icons/lib/md/layers'
|
import MdLayers from 'react-icons/lib/md/layers'
|
||||||
import MdSave from 'react-icons/lib/md/save'
|
import MdSave from 'react-icons/lib/md/save'
|
||||||
|
import MdStyle from 'react-icons/lib/md/style'
|
||||||
import MdMap from 'react-icons/lib/md/map'
|
import MdMap from 'react-icons/lib/md/map'
|
||||||
|
import MdInsertEmoticon from 'react-icons/lib/md/insert-emoticon'
|
||||||
|
import MdFontDownload from 'react-icons/lib/md/font-download'
|
||||||
|
import MdHelpOutline from 'react-icons/lib/md/help-outline'
|
||||||
|
import MdFindInPage from 'react-icons/lib/md/find-in-page'
|
||||||
|
|
||||||
import style from './style.js'
|
import style from './style.js'
|
||||||
import { fullHeight } from './theme.js'
|
import { fullHeight } from './theme.js'
|
||||||
import theme from './theme.js';
|
import theme from './theme.js';
|
||||||
|
|
||||||
|
const InlineBlock = props => <div style={{display: "inline-block", ...props.style}}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
|
||||||
export class Toolbar extends React.Component {
|
export class Toolbar extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// A new style has been uploaded
|
// A new style has been uploaded
|
||||||
onStyleUpload: React.PropTypes.func.isRequired,
|
onStyleUpload: React.PropTypes.func.isRequired,
|
||||||
// Current style is requested for download
|
// Current style is requested for download
|
||||||
onStyleDownload: React.PropTypes.func.isRequired,
|
onStyleDownload: React.PropTypes.func.isRequired,
|
||||||
// Style is explicitely saved to local cache
|
// Style is explicitely saved to local cache
|
||||||
onStyleSave: React.PropTypes.func,
|
onStyleSave: React.PropTypes.func,
|
||||||
// Open settings drawer
|
// Open settings drawer
|
||||||
onOpenSettings: React.PropTypes.func,
|
onOpenSettings: React.PropTypes.func,
|
||||||
// Open about page
|
// Open about page
|
||||||
onOpenAbout: React.PropTypes.func,
|
onOpenAbout: React.PropTypes.func,
|
||||||
// Open sources drawer
|
// Open sources drawer
|
||||||
onOpenSources: React.PropTypes.func,
|
onOpenSources: React.PropTypes.func,
|
||||||
// Open layers drawer
|
// Whether a style is available for download or saving
|
||||||
onOpenLayers: React.PropTypes.func,
|
// A style with no layers should not be available
|
||||||
// Whether a style is available for download or saving
|
styleAvailable: React.PropTypes.bool,
|
||||||
// A style with no layers should not be available
|
}
|
||||||
styleAvailable: React.PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
onUpload(_, files) {
|
onUpload(_, files) {
|
||||||
const [e, file] = files[0];
|
const [e, file] = files[0];
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsText(file, "UTF-8");
|
reader.readAsText(file, "UTF-8");
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
let mapStyle = style.fromJSON(JSON.parse(e.target.result))
|
let mapStyle = style.fromJSON(JSON.parse(e.target.result))
|
||||||
mapStyle = style.ensureMetadataExists(mapStyle)
|
mapStyle = style.ensureMetadataExists(mapStyle)
|
||||||
this.props.onStyleUpload(mapStyle);
|
this.props.onStyleUpload(mapStyle);
|
||||||
}
|
}
|
||||||
reader.onerror = e => console.log(e.target);
|
reader.onerror = e => console.log(e.target);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveButton() {
|
saveButton() {
|
||||||
if(this.props.styleAvailable) {
|
if(this.props.styleAvailable) {
|
||||||
return <Block>
|
return <InlineBlock>
|
||||||
<Button onClick={this.props.onStyleSave} big={true}>
|
<Button onClick={this.props.onStyleSave} big={true}>
|
||||||
<Tooltip inverted rounded title="Save style">
|
<MdSave />
|
||||||
<MdSave />
|
Save
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Button>
|
</InlineBlock>
|
||||||
</Block>
|
}
|
||||||
}
|
return null
|
||||||
return null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
downloadButton() {
|
downloadButton() {
|
||||||
if(this.props.styleAvailable) {
|
if(this.props.styleAvailable) {
|
||||||
return <Block>
|
return <InlineBlock>
|
||||||
<Button onClick={this.props.onStyleDownload} big={true}>
|
<Button onClick={this.props.onStyleDownload} big={true}>
|
||||||
<Tooltip inverted rounded title="Download style">
|
<MdFileDownload />
|
||||||
<MdFileDownload />
|
Download
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Button>
|
</InlineBlock>
|
||||||
</Block>
|
}
|
||||||
}
|
return null
|
||||||
return null
|
}
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <Container style={{
|
|
||||||
...fullHeight,
|
return <div style={{
|
||||||
zIndex: 100,
|
position: "fixed",
|
||||||
left: 0,
|
height: 50,
|
||||||
top: 0,
|
width: '100%',
|
||||||
backgroundColor: theme.colors.black }
|
zIndex: 100,
|
||||||
}>
|
left: 0,
|
||||||
<Block>
|
top: 0,
|
||||||
<FileReaderInput onChange={this.onUpload.bind(this)}>
|
backgroundColor: theme.colors.black
|
||||||
<Button big={true} theme={this.props.styleAvailable ? "default" : "success"}>
|
}}>
|
||||||
<Tooltip inverted rounded title="Upload style">
|
<InlineBlock>
|
||||||
<MdFileUpload />
|
<Button style={{width: 300, textAlign: 'left'}}>
|
||||||
</Tooltip>
|
<img src="https://github.com/maputnik/editor/raw/master/media/maputnik.png" alt="Maputnik" style={{width: 40, height: 40, paddingRight: 5, verticalAlign: 'middle'}}/>
|
||||||
</Button>
|
<span style={{fontSize: 20 }}>Maputnik</span>
|
||||||
</FileReaderInput>
|
</Button>
|
||||||
</Block>
|
</InlineBlock>
|
||||||
{this.downloadButton()}
|
<InlineBlock>
|
||||||
{this.saveButton()}
|
<FileReaderInput onChange={this.onUpload.bind(this)}>
|
||||||
<Block>
|
<Button big={true} theme={this.props.styleAvailable ? "default" : "success"}>
|
||||||
<Button big={true} onClick={this.props.onOpenLayers}>
|
<MdOpenInBrowser />
|
||||||
<Tooltip inverted rounded title="Layers">
|
Open
|
||||||
<MdLayers />
|
</Button>
|
||||||
</Tooltip>
|
</FileReaderInput>
|
||||||
</Button>
|
</InlineBlock>
|
||||||
</Block>
|
{this.downloadButton()}
|
||||||
<Block>
|
{this.saveButton()}
|
||||||
<Button big={true} onClick={this.props.onOpenSources}>
|
<InlineBlock>
|
||||||
<Tooltip inverted rounded title="Sources">
|
<Button big={true} onClick={this.props.onOpenSettings}>
|
||||||
<MdMap />
|
<MdLayers />
|
||||||
</Tooltip>
|
Tilesets
|
||||||
</Button>
|
</Button>
|
||||||
</Block>
|
</InlineBlock>
|
||||||
<Block>
|
<InlineBlock>
|
||||||
<Button big={true} onClick={this.props.onOpenSettings}>
|
<Button big={true} onClick={this.props.onOpenSettings}>
|
||||||
<Tooltip inverted rounded title="Settings">
|
<MdFontDownload />
|
||||||
<MdSettings />
|
Fonts
|
||||||
</Tooltip>
|
</Button>
|
||||||
</Button>
|
</InlineBlock>
|
||||||
</Block>
|
<InlineBlock>
|
||||||
<Block>
|
<Button big={true} onClick={this.props.onOpenSettings}>
|
||||||
<Button big={true} onClick={this.props.onOpenAbout}>
|
<MdInsertEmoticon/>
|
||||||
<Tooltip inverted rounded title="About">
|
Icons
|
||||||
<MdInfo />
|
</Button>
|
||||||
</Tooltip>
|
</InlineBlock>
|
||||||
</Button>
|
<InlineBlock>
|
||||||
</Block>
|
<Button big={true} onClick={this.props.onOpenSettings}>
|
||||||
</Container>
|
<MdFindInPage />
|
||||||
}
|
Inspect
|
||||||
|
</Button>
|
||||||
|
</InlineBlock>
|
||||||
|
<InlineBlock>
|
||||||
|
<Button big={true} onClick={this.props.onOpenAbout}>
|
||||||
|
<MdHelpOutline />
|
||||||
|
Help
|
||||||
|
</Button>
|
||||||
|
</InlineBlock>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,65 +8,66 @@ import { colors, fullHeight } from './theme.js'
|
||||||
/** The workspace drawer contains the editor components depending on the edit
|
/** The workspace drawer contains the editor components depending on the edit
|
||||||
* context chosen in the toolbar. It holds the state of the layers.*/
|
* context chosen in the toolbar. It holds the state of the layers.*/
|
||||||
export class WorkspaceDrawer extends React.Component {
|
export class WorkspaceDrawer extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
mapStyle: React.PropTypes.object.isRequired,
|
mapStyle: React.PropTypes.object.isRequired,
|
||||||
onStyleChanged: React.PropTypes.func.isRequired,
|
onStyleChanged: React.PropTypes.func.isRequired,
|
||||||
workContext: React.PropTypes.oneOf(['layers', 'settings', 'sources']).isRequired,
|
workContext: React.PropTypes.oneOf(['layers', 'settings', 'sources']).isRequired,
|
||||||
accessToken: React.PropTypes.string,
|
accessToken: React.PropTypes.string,
|
||||||
onAccessTokenChanged: React.PropTypes.func,
|
onAccessTokenChanged: React.PropTypes.func,
|
||||||
onReset: React.PropTypes.func,
|
onReset: React.PropTypes.func,
|
||||||
}
|
}
|
||||||
|
|
||||||
onLayersChanged(changedLayers) {
|
onLayersChanged(changedLayers) {
|
||||||
const changedStyle = this.props.mapStyle.set('layers', changedLayers)
|
const changedStyle = this.props.mapStyle.set('layers', changedLayers)
|
||||||
this.props.onStyleChanged(changedStyle)
|
this.props.onStyleChanged(changedStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
onSourcesChanged(changedSources) {
|
onSourcesChanged(changedSources) {
|
||||||
const changedStyle = this.props.mapStyle.set('sources', changedSources)
|
const changedStyle = this.props.mapStyle.set('sources', changedSources)
|
||||||
this.props.onStyleChanged(changedStyle)
|
this.props.onStyleChanged(changedStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let workspaceContent = null
|
let workspaceContent = null
|
||||||
|
|
||||||
if(this.props.workContext === "sources") {
|
if(this.props.workContext === "sources") {
|
||||||
workspaceContent = <SourceList
|
workspaceContent = <SourceList
|
||||||
onSourcesChanged={this.onSourcesChanged.bind(this)}
|
onSourcesChanged={this.onSourcesChanged.bind(this)}
|
||||||
sources={this.props.mapStyle.get('sources')}
|
sources={this.props.mapStyle.get('sources')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.workContext === "layers") {
|
if(this.props.workContext === "layers") {
|
||||||
workspaceContent = <LayerList
|
workspaceContent = <LayerList
|
||||||
onLayersChanged={this.onLayersChanged.bind(this)}
|
onLayersChanged={this.onLayersChanged.bind(this)}
|
||||||
layers={this.props.mapStyle.get('layers')}
|
layers={this.props.mapStyle.get('layers')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.workContext === "settings") {
|
if(this.props.workContext === "settings") {
|
||||||
workspaceContent = <SettingsEditor
|
workspaceContent = <SettingsEditor
|
||||||
onReset={this.props.onReset}
|
onReset={this.props.onReset}
|
||||||
onStyleChanged={this.props.onStyleChanged}
|
onStyleChanged={this.props.onStyleChanged}
|
||||||
mapStyle={this.props.mapStyle}
|
mapStyle={this.props.mapStyle}
|
||||||
accessToken={this.props.accessToken}
|
accessToken={this.props.accessToken}
|
||||||
onAccessTokenChanged={this.props.onAccessTokenChanged}
|
onAccessTokenChanged={this.props.onAccessTokenChanged}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.props.workContext === "about") {
|
if(this.props.workContext === "about") {
|
||||||
workspaceContent = <About />
|
workspaceContent = <About />
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div style={{
|
return <div style={{
|
||||||
...fullHeight,
|
...fullHeight,
|
||||||
zIndex: 100,
|
top: 50,
|
||||||
left: 60,
|
left: 0,
|
||||||
width: 300,
|
zIndex: 100,
|
||||||
overflow: "hidden",
|
width: 300,
|
||||||
backgroundColor: colors.gray}
|
overflow: "hidden",
|
||||||
}>
|
backgroundColor: colors.gray}
|
||||||
{workspaceContent}
|
}>
|
||||||
</div>
|
{workspaceContent}
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,9 @@ module.exports = {
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
fs: "empty"
|
fs: "empty",
|
||||||
|
net: 'empty',
|
||||||
|
tls: 'empty'
|
||||||
},
|
},
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: "./public",
|
contentBase: "./public",
|
||||||
|
|
Loading…
Reference in a new issue