Remove Immutable JS

This commit is contained in:
Lukas Martinelli 2016-12-20 16:08:49 +01:00
parent ed87425f01
commit 36c4032063
22 changed files with 100 additions and 199 deletions

View file

@ -21,7 +21,6 @@
"dependencies": { "dependencies": {
"color": "^1.0.3", "color": "^1.0.3",
"file-saver": "^1.3.2", "file-saver": "^1.3.2",
"immutable": "^3.8.1",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"lodash.topairs": "^4.3.0", "lodash.topairs": "^4.3.0",
"mapbox-gl": "mapbox/mapbox-gl-js#6c24b9621d2aa770eda67fb5638b4d78087b5624", "mapbox-gl": "mapbox/mapbox-gl-js#6c24b9621d2aa770eda67fb5638b4d78087b5624",

View file

@ -8,18 +8,8 @@ import Toolbar from 'rebass/dist/Toolbar'
import NavItem from 'rebass/dist/NavItem' import NavItem from 'rebass/dist/NavItem'
import Space from 'rebass/dist/Space' import Space from 'rebass/dist/Space'
import Immutable from 'immutable'
import PureRenderMixin from 'react-addons-pure-render-mixin';
/** About page with basic infos and links to github repo */ /** About page with basic infos and links to github repo */
export class About extends React.Component { export class About extends React.Component {
static propTypes = {}
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() { render() {
return <div> return <div>
<Toolbar style={{marginRight: 20}}> <Toolbar style={{marginRight: 20}}>

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import { saveAs } from 'file-saver' import { saveAs } from 'file-saver'
import Drawer from 'rebass/dist/Drawer' import Drawer from 'rebass/dist/Drawer'
@ -43,7 +42,7 @@ export default class App extends React.Component {
this.state = { this.state = {
accessToken: this.settingsStore.accessToken, accessToken: this.settingsStore.accessToken,
mapStyle: style.emptyStyle, mapStyle: style.emptyStyle,
selectedLayerId: null, selectedLayerIndex: 0,
} }
} }
@ -60,19 +59,20 @@ export default class App extends React.Component {
} }
onStyleDownload() { onStyleDownload() {
const mapStyle = style.toJSON(this.state.mapStyle) const mapStyle = this.state.mapStyle
const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"}); const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"});
saveAs(blob, mapStyle.id + ".json"); saveAs(blob, mapStyle.id + ".json");
this.onStyleSave() this.onStyleSave()
} }
onStyleUpload(newStyle) { onStyleUpload(newStyle) {
console.log('upload', newStyle)
const savedStyle = this.styleStore.save(newStyle) const savedStyle = this.styleStore.save(newStyle)
this.setState({ mapStyle: savedStyle }) this.setState({ mapStyle: savedStyle })
} }
onStyleSave() { onStyleSave() {
const snapshotStyle = this.state.mapStyle.set('modified', new Date().toJSON()) const snapshotStyle = this.state.mapStyle.modified = new Date().toJSON()
this.setState({ mapStyle: snapshotStyle }) this.setState({ mapStyle: snapshotStyle })
console.log('Save') console.log('Save')
this.styleStore.save(snapshotStyle) this.styleStore.save(snapshotStyle)
@ -88,20 +88,22 @@ export default class App extends React.Component {
} }
onLayersChanged(changedLayers) { onLayersChanged(changedLayers) {
const changedStyle = this.state.mapStyle.set('layers', changedLayers) const changedStyle = {
this.onStyleChanged(changedStyle) ...this.state.mapStyle,
layers: [changedLayers]
}
this.setState({ mapStyle: newStyle })
} }
onLayerChanged(layer) { onLayerChanged(layer) {
console.log('layer changed', layer) const changedStyle = {
const layers = this.state.mapStyle.get('layers') ...this.state.mapStyle,
const changedLayers = layers.set(layer.get('id'), layer) layers: {
this.onLayersChanged(changedLayers) ...this.state.mapStyle.layers,
} [layer.id]: layer
}
onLayerChanged(layer) { }
const changedStyle = this.state.mapStyle.setIn(['layers', layer.id], Immutable.fromJS(layer)) this.setState({ mapStyle: changedStyle })
this.onStyleChanged(changedStyle)
} }
mapRenderer() { mapRenderer() {
@ -112,7 +114,9 @@ export default class App extends React.Component {
this.layerWatcher.map = map this.layerWatcher.map = map
} }
} }
const renderer = this.state.mapStyle.getIn(['metadata', 'maputnik:renderer'], 'mbgljs')
const metadata = this.state.mapStyle.metadata || {}
const renderer = metadata['maputnik:renderer'] || 'mbgljs'
if(renderer === 'ol3') { if(renderer === 'ol3') {
return <OpenLayers3Map {...mapProps} /> return <OpenLayers3Map {...mapProps} />
} else { } else {
@ -121,11 +125,18 @@ export default class App extends React.Component {
} }
onLayerSelected(layerId) { onLayerSelected(layerId) {
this.setState({ selectedLayerId: layerId }) const layers = this.state.mapStyle.layers
for (let i = 0; i < layers.length; i++) {
if(layers[i].id === layerId) {
this.setState({ selectedLayerIndex: i })
break
}
}
} }
render() { render() {
const selectedLayer = this.state.mapStyle.getIn(['layers', this.state.selectedLayerId], null) const layers = this.state.mapStyle.layers || []
const selectedLayer = layers.length > 0 ? layers[this.state.selectedLayerIndex] : null
return <div style={{ fontFamily: theme.fontFamily, color: theme.color, fontWeight: 300 }}> return <div style={{ fontFamily: theme.fontFamily, color: theme.color, fontWeight: 300 }}>
<Toolbar <Toolbar
@ -149,7 +160,7 @@ export default class App extends React.Component {
<LayerList <LayerList
onLayersChanged={this.onLayersChanged.bind(this)} onLayersChanged={this.onLayersChanged.bind(this)}
onLayerSelected={this.onLayerSelected.bind(this)} onLayerSelected={this.onLayerSelected.bind(this)}
layers={this.state.mapStyle.get('layers')} layers={layers}
/> />
</div> </div>
<div style={{ <div style={{
@ -162,7 +173,7 @@ export default class App extends React.Component {
width: 300, width: 300,
backgroundColor: colors.gray} backgroundColor: colors.gray}
}> }>
{selectedLayer && <LayerEditor layer={selectedLayer.toJS()} onLayerChanged={this.onLayerChanged.bind(this)} sources={this.layerWatcher.sources} vectorLayers={this.layerWatcher.vectorLayers}/>} {selectedLayer && <LayerEditor layer={selectedLayer} onLayerChanged={this.onLayerChanged.bind(this)} sources={this.layerWatcher.sources} vectorLayers={this.layerWatcher.vectorLayers}/>}
</div> </div>
{this.mapRenderer()} {this.mapRenderer()}
</div> </div>

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import FileReaderInput from 'react-file-reader-input' import FileReaderInput from 'react-file-reader-input'
import Button from 'rebass/dist/Button' import Button from 'rebass/dist/Button'
@ -37,7 +36,7 @@ const InlineBlock = props => <div style={{display: "inline-block", ...props.styl
export default class Toolbar extends React.Component { export default class Toolbar extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.instanceOf(Immutable.Map).isRequired, mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired, onStyleChanged: React.PropTypes.func.isRequired,
// A new style has been uploaded // A new style has been uploaded
onStyleUpload: React.PropTypes.func.isRequired, onStyleUpload: React.PropTypes.func.isRequired,
@ -60,7 +59,7 @@ export default class Toolbar extends React.Component {
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 = JSON.parse(e.target.result)
mapStyle = style.ensureMetadataExists(mapStyle) mapStyle = style.ensureMetadataExists(mapStyle)
this.props.onStyleUpload(mapStyle); this.props.onStyleUpload(mapStyle);
} }
@ -68,7 +67,7 @@ export default class Toolbar extends React.Component {
} }
saveButton() { saveButton() {
if(this.props.mapStyle.get('layers').size > 0) { if(this.props.mapStyle.layers.length > 0) {
return <InlineBlock> return <InlineBlock>
<Button onClick={this.props.onStyleSave} big={true}> <Button onClick={this.props.onStyleSave} big={true}>
<MdSave /> <MdSave />

View file

@ -35,7 +35,6 @@ class ColorField extends React.Component {
} }
render() { render() {
console.log(Color(this.props.value))
const picker = <div style={{ const picker = <div style={{
position: 'absolute', position: 'absolute',
left: 163, left: 163,

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js' import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js'
import ZoomSpecField from './ZoomSpecField' import ZoomSpecField from './ZoomSpecField'
@ -26,7 +25,7 @@ function getGroupName(layerType, fieldName) {
export default class PropertyGroup extends React.Component { export default class PropertyGroup extends React.Component {
static propTypes = { static propTypes = {
layer: React.PropTypes.object.isRequired, layer: React.PropTypes.object.isRequired,
groupFields: React.PropTypes.instanceOf(Immutable.OrderedSet).isRequired, groupFields: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired,
} }
@ -50,7 +49,7 @@ export default class PropertyGroup extends React.Component {
value={fieldValue} value={fieldValue}
fieldSpec={fieldSpec} fieldSpec={fieldSpec}
/> />
}).toIndexedSeq() })
return <div style={{ return <div style={{
padding: margins[2], padding: margins[2],

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import color from 'color' import color from 'color'
import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js' import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js'
@ -12,10 +11,6 @@ import StringField from './StringField'
import input from '../../config/input.js' import input from '../../config/input.js'
import theme from '../../config/rebass.js' import theme from '../../config/rebass.js'
function isZoomField(value) {
return Immutable.Map.isMap(value)
}
function labelFromFieldName(fieldName) { function labelFromFieldName(fieldName) {
let label = fieldName.split('-').slice(1).join(' ') let label = fieldName.split('-').slice(1).join(' ')
if(label.length > 0) { if(label.length > 0) {

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import Color from 'color' import Color from 'color'
import NumberField from './NumberField' import NumberField from './NumberField'
@ -14,7 +13,7 @@ import colors from '../../config/colors.js'
import { margins } from '../../config/scales.js' import { margins } from '../../config/scales.js'
function isZoomField(value) { function isZoomField(value) {
return Immutable.Map.isMap(value) return typeof value === 'object' && value.stops
} }
const specFieldProps = { const specFieldProps = {
@ -46,9 +45,9 @@ export default class ZoomSpecField extends React.Component {
</label> </label>
if(isZoomField(this.props.value)) { if(isZoomField(this.props.value)) {
const zoomFields = this.props.value.get('stops').map(stop => { const zoomFields = this.props.value.stops.map(stop => {
const zoomLevel = stop.get(0) const zoomLevel = stop[0]
const value = stop.get(1) const value = stop[1]
return <div style={input.property} key={zoomLevel}> return <div style={input.property} key={zoomLevel}>
{label} {label}
@ -71,7 +70,7 @@ export default class ZoomSpecField extends React.Component {
max={22} max={22}
/> />
</div> </div>
}).toSeq() })
return <div style={{ return <div style={{
border: 1, border: 1,
borderStyle: 'solid', borderStyle: 'solid',

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js' import GlSpec from 'mapbox-gl-style-spec/reference/latest.min.js'
@ -75,11 +74,11 @@ class SingleFilterEditor extends React.Component {
static propTypes = { static propTypes = {
filter: React.PropTypes.array.isRequired, filter: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired,
properties: React.PropTypes.instanceOf(Immutable.Map), properties: React.PropTypes.object,
} }
static defaultProps = { static defaultProps = {
properties: Immutable.Map(), properties: {},
} }
onFilterPartChanged(filterOp, propertyName, filterArgs) { onFilterPartChanged(filterOp, propertyName, filterArgs) {
@ -103,9 +102,9 @@ class SingleFilterEditor extends React.Component {
value={propertyName} value={propertyName}
onChange={newPropertyName => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)} onChange={newPropertyName => this.onFilterPartChanged(filterOp, newPropertyName, filterArgs)}
> >
{this.props.properties.keySeq().map(propName => { {Object.keys(this.props.properties).map(propName => {
return <option key={propName} value={propName}>{propName}</option> return <option key={propName} value={propName}>{propName}</option>
}).toIndexedSeq()} })}
</select> </select>
<OperatorSelect <OperatorSelect
value={filterOp} value={filterOp}
@ -129,7 +128,7 @@ class SingleFilterEditor extends React.Component {
export default class CombiningFilterEditor extends React.Component { export default class CombiningFilterEditor extends React.Component {
static propTypes = { static propTypes = {
/** Properties of the vector layer and the available fields */ /** Properties of the vector layer and the available fields */
properties: React.PropTypes.instanceOf(Immutable.Map).isRequired, properties: React.PropTypes.object.isRequired,
filter: React.PropTypes.array.isRequired, filter: React.PropTypes.array.isRequired,
onChange: React.PropTypes.func.isRequired, onChange: React.PropTypes.func.isRequired,
} }

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import Toolbar from 'rebass/dist/Toolbar' import Toolbar from 'rebass/dist/Toolbar'
import NavItem from 'rebass/dist/NavItem' import NavItem from 'rebass/dist/NavItem'
@ -29,8 +28,8 @@ class UnsupportedLayer extends React.Component {
export default class LayerEditor extends React.Component { export default class LayerEditor extends React.Component {
static propTypes = { static propTypes = {
layer: React.PropTypes.object.isRequired, layer: React.PropTypes.object.isRequired,
sources: React.PropTypes.instanceOf(Immutable.Map), sources: React.PropTypes.object,
vectorLayers: React.PropTypes.instanceOf(Immutable.Map), vectorLayers: React.PropTypes.object,
onLayerChanged: React.PropTypes.func, onLayerChanged: React.PropTypes.func,
onLayerDestroyed: React.PropTypes.func, onLayerDestroyed: React.PropTypes.func,
} }
@ -94,7 +93,7 @@ export default class LayerEditor extends React.Component {
return <PropertyGroup return <PropertyGroup
key={this.props.group} key={this.props.group}
layer={this.props.layer} layer={this.props.layer}
groupFields={Immutable.OrderedSet(group.fields)} groupFields={group.fields}
onChange={this.onPropertyChange.bind(this)} onChange={this.onPropertyChange.bind(this)}
/> />
}) })
@ -119,19 +118,20 @@ export default class LayerEditor extends React.Component {
</NavItem> </NavItem>
</Toolbar> </Toolbar>
{propertyGroups} {propertyGroups}
{this.props.layer.type !== 'background' && <div>
<FilterEditor <FilterEditor
filter={this.props.layer.filter} filter={this.props.layer.filter}
properties={this.props.vectorLayers.get(this.props.layer['source-layer'])} properties={this.props.vectorLayers[this.props.layer['source-layer']]}
onChange={f => this.onFilterChange(Immutable.fromJS(f))} onChange={f => this.onFilterChange(f)}
/> />
{this.props.layer.type !== 'background' <SourceEditor
&& <SourceEditor source={this.props.layer.source}
source={this.props.layer.source} sourceLayer={this.props.layer['source-layer']}
sourceLayer={this.props.layer['source-layer']} sources={this.props.sources}
sources={this.props.sources} onSourceChange={console.log}
onSourceChange={console.log} onSourceLayerChange={console.log}
onSourceLayerChange={console.log} />
/>} </div>}
</div> </div>
} }
} }

View file

@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'; import PureRenderMixin from 'react-addons-pure-render-mixin';
import Immutable from 'immutable'
import Heading from 'rebass/dist/Heading' import Heading from 'rebass/dist/Heading'
import Toolbar from 'rebass/dist/Toolbar' import Toolbar from 'rebass/dist/Toolbar'
@ -15,7 +14,7 @@ import { margins } from '../../config/scales.js'
import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc'; import {SortableContainer, SortableHandle, arrayMove} from 'react-sortable-hoc';
const layerListPropTypes = { const layerListPropTypes = {
layers: React.PropTypes.instanceOf(Immutable.OrderedMap), layers: React.PropTypes.array.isRequired,
onLayersChanged: React.PropTypes.func.isRequired, onLayersChanged: React.PropTypes.func.isRequired,
onLayerSelected: React.PropTypes.func, onLayerSelected: React.PropTypes.func,
} }
@ -34,23 +33,18 @@ class LayerListContainer extends React.Component {
} }
onLayerDestroyed(deletedLayer) { onLayerDestroyed(deletedLayer) {
const remainingLayers = this.props.layers.delete(deletedLayer.get('id')) const remainingLayers = this.props.layers.delete(deletedLayer.id)
this.props.onLayersChanged(remainingLayers) this.props.onLayersChanged(remainingLayers)
} }
onLayerChanged(layer) {
const changedLayers = this.props.layers.set(layer.get('id'), layer)
this.props.onLayersChanged(changedLayers)
}
render() { render() {
const layerPanels = this.props.layers.toIndexedSeq().map((layer, index) => { const layerPanels = this.props.layers.map((layer, index) => {
const layerId = layer.get('id') const layerId = layer.id
return <LayerListItem return <LayerListItem
index={index} index={index}
key={layerId} key={layerId}
layerId={layerId} layerId={layerId}
layerType={layer.get('type')} layerType={layer.type}
onLayerSelected={this.props.onLayerSelected} onLayerSelected={this.props.onLayerSelected}
/> />
}) })
@ -73,13 +67,8 @@ export default class LayerList extends React.Component {
onSortEnd(move) { onSortEnd(move) {
const { oldIndex, newIndex } = move const { oldIndex, newIndex } = move
if(oldIndex === newIndex) return if(oldIndex === newIndex) return
let layers = this.props.layers.slice(0)
//TODO: Implement this more performant for immutable collections
// instead of converting back and forth
let layers = this.props.layers.toArray()
layers = arrayMove(layers, oldIndex, newIndex) layers = arrayMove(layers, oldIndex, newIndex)
layers = Immutable.OrderedMap(layers.map(l => [l.get('id'), l]))
this.props.onLayersChanged(layers) this.props.onLayersChanged(layers)
} }

View file

@ -1,7 +1,5 @@
import React from 'react' import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin'
import Radium from 'radium' import Radium from 'radium'
import Immutable from 'immutable'
import Color from 'color' import Color from 'color'
import Heading from 'rebass/dist/Heading' import Heading from 'rebass/dist/Heading'
@ -38,11 +36,6 @@ class LayerListItem extends React.Component {
onLayerSelected: React.PropTypes.func.isRequired, onLayerSelected: React.PropTypes.func.isRequired,
} }
constructor(props) {
super(props)
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() { render() {
return <li return <li
key={this.props.layerId} key={this.props.layerId}

View file

@ -1,6 +1,4 @@
import React from 'react' import React from 'react'
import PureRenderMixin from 'react-addons-pure-render-mixin';
import Immutable from 'immutable'
import PropertyGroup from '../fields/PropertyGroup' import PropertyGroup from '../fields/PropertyGroup'
import input from '../../config/input.js' import input from '../../config/input.js'
@ -16,23 +14,18 @@ export default class SourceEditor extends React.Component {
/** List of available sources in the style /** List of available sources in the style
* https://www.mapbox.com/mapbox-gl-style-spec/#root-sources */ * https://www.mapbox.com/mapbox-gl-style-spec/#root-sources */
sources: React.PropTypes.instanceOf(Immutable.Map).isRequired, sources: React.PropTypes.object.isRequired,
} }
constructor(props) {
super(props);
this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
}
render() { render() {
const options = this.props.sources.map((source, sourceId)=> { const options = Object.keys(this.props.sources).map(sourceId => {
return <option key={sourceId} value={sourceId}>{sourceId}</option> return <option key={sourceId} value={sourceId}>{sourceId}</option>
}).toIndexedSeq() })
const layerOptions = this.props.sources.get(this.props.source, Immutable.Set()).map(vectorLayerId => { const layerOptions = this.props.sources[this.props.source].map(vectorLayerId => {
const id = vectorLayerId const id = vectorLayerId
return <option key={id} value={id}>{id}</option> return <option key={id} value={id}>{id}</option>
}).toIndexedSeq() })
return <div> return <div>
<div style={input.property}> <div style={input.property}>

View file

@ -1,17 +1,11 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
export default class Map extends React.Component { export default class Map extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.instanceOf(Immutable.Map).isRequired, mapStyle: React.PropTypes.object.isRequired,
accessToken: React.PropTypes.string, accessToken: React.PropTypes.string,
} }
shouldComponentUpdate(nextProps, nextState) {
//TODO: If we enable this React mixin for immutable comparison we can remove this?
return nextProps.mapStyle !== this.props.mapStyle
}
render() { render() {
return <div return <div
ref={x => this.container = x} ref={x => this.container = x}

View file

@ -23,7 +23,7 @@ export default class MapboxGlMap extends Map {
//Mapbox GL now does diffing natively so we don't need to calculate //Mapbox GL now does diffing natively so we don't need to calculate
//the necessary operations ourselves! //the necessary operations ourselves!
this.state.map.setStyle(style.toJSON(nextProps.mapStyle), { diff: true}) this.state.map.setStyle(nextProps.mapStyle, { diff: true})
} }
componentDidMount() { componentDidMount() {
@ -31,7 +31,7 @@ export default class MapboxGlMap extends Map {
const map = new MapboxGl.Map({ const map = new MapboxGl.Map({
container: this.container, container: this.container,
style: style.toJSON(this.props.mapStyle), style: this.props.mapStyle,
}); });
map.on("style.load", (...args) => { map.on("style.load", (...args) => {

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import Select from 'rebass/dist/Select' import Select from 'rebass/dist/Select'
import Overlay from 'rebass/dist/Overlay' import Overlay from 'rebass/dist/Overlay'
@ -15,7 +14,7 @@ import Input from 'rebass/dist/Input'
class SettingsModal extends React.Component { class SettingsModal extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.instanceOf(Immutable.Map).isRequired, mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired, onStyleChanged: React.PropTypes.func.isRequired,
open: React.PropTypes.bool.isRequired, open: React.PropTypes.bool.isRequired,
toggle: React.PropTypes.func.isRequired, toggle: React.PropTypes.func.isRequired,
@ -47,31 +46,31 @@ class SettingsModal extends React.Component {
<Input <Input
name="name" name="name"
label="Name" label="Name"
value={this.props.mapStyle.get('name')} value={this.props.mapStyle.name}
onChange={this.onChange.bind(this, "name")} onChange={this.onChange.bind(this, "name")}
/> />
<Input <Input
name="owner" name="owner"
label="Owner" label="Owner"
value={this.props.mapStyle.get('owner')} value={this.props.mapStyle.owner}
onChange={this.onChange.bind(this, "owner")} onChange={this.onChange.bind(this, "owner")}
/> />
<Input <Input
name="sprite" name="sprite"
label="Sprite URL" label="Sprite URL"
value={this.props.mapStyle.get('sprite')} value={this.props.mapStyle.sprite}
onChange={this.onChange.bind(this, "sprite")} onChange={this.onChange.bind(this, "sprite")}
/> />
<Input <Input
name="glyphs" name="glyphs"
label="Glyphs URL" label="Glyphs URL"
value={this.props.mapStyle.get('glyphs')} value={this.props.mapStyle.glyphs}
onChange={this.onChange.bind(this, "glyphs")} onChange={this.onChange.bind(this, "glyphs")}
/> />
<Input <Input
name="glyphs" name="glyphs"
label="Glyphs URL" label="Glyphs URL"
value={this.props.mapStyle.get('glyphs')} value={this.props.mapStyle.glyphs}
onChange={this.onChange.bind(this, "glyphs")} onChange={this.onChange.bind(this, "glyphs")}
/> />
<Select <Select

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import Overlay from 'rebass/dist/Overlay' import Overlay from 'rebass/dist/Overlay'
import Panel from 'rebass/dist/Panel' import Panel from 'rebass/dist/Panel'
@ -19,8 +18,7 @@ import theme from '../../config/rebass'
class TilesetsModal extends React.Component { class TilesetsModal extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.instanceOf(Immutable.Map).isRequired, mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
open: React.PropTypes.bool.isRequired, open: React.PropTypes.bool.isRequired,
toggle: React.PropTypes.func.isRequired, toggle: React.PropTypes.func.isRequired,
} }
@ -29,11 +27,6 @@ class TilesetsModal extends React.Component {
super(props); super(props);
} }
onChange(property, e) {
const changedStyle = this.props.mapStyle.set(property, e.target.value)
this.props.onStyleChanged(changedStyle)
}
render() { render() {
const tilesetOptions = publicTilesets.map(tileset => { const tilesetOptions = publicTilesets.map(tileset => {
return <div key={tileset.id} style={{ return <div key={tileset.id} style={{

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import Input from 'rebass/dist/Input' import Input from 'rebass/dist/Input'
import Toolbar from 'rebass/dist/Toolbar' import Toolbar from 'rebass/dist/Toolbar'
@ -20,7 +19,7 @@ class UnsupportedSource extends React.Component {
class VectorSource extends React.Component { class VectorSource extends React.Component {
static propTypes = { static propTypes = {
source: React.PropTypes.instanceOf(Immutable.Map).isRequired, source: React.PropTypes.object.isRequired,
onSourceChanged: React.PropTypes.func.isRequired, onSourceChanged: React.PropTypes.func.isRequired,
} }
@ -45,7 +44,7 @@ class VectorSource extends React.Component {
export class SourceEditor extends React.Component { export class SourceEditor extends React.Component {
static propTypes = { static propTypes = {
sourceId: React.PropTypes.string.isRequired, sourceId: React.PropTypes.string.isRequired,
source: React.PropTypes.instanceOf(Immutable.Map).isRequired, source: React.PropTypes.object.isRequired,
onSourceChanged: React.PropTypes.func.isRequired, onSourceChanged: React.PropTypes.func.isRequired,
} }

View file

@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import Immutable from 'immutable'
import Heading from 'rebass/dist/Heading' import Heading from 'rebass/dist/Heading'
import Toolbar from 'rebass/dist/Toolbar' import Toolbar from 'rebass/dist/Toolbar'
@ -13,7 +12,7 @@ import PureRenderMixin from 'react-addons-pure-render-mixin';
// List of collapsible layer editors // List of collapsible layer editors
export class SourceList extends React.Component { export class SourceList extends React.Component {
static propTypes = { static propTypes = {
sources: React.PropTypes.instanceOf(Immutable.Map).isRequired, sources: React.PropTypes.object.isRequired,
onSourcesChanged: React.PropTypes.func.isRequired, onSourcesChanged: React.PropTypes.func.isRequired,
} }

View file

@ -1,6 +1,4 @@
import Immutable from 'immutable'
import throttle from 'lodash.throttle' import throttle from 'lodash.throttle'
import entries from 'lodash.topairs'
/** Listens to map events to build up a store of available vector /** Listens to map events to build up a store of available vector
* layers contained in the tiles */ * layers contained in the tiles */
@ -47,22 +45,15 @@ export default class LayerWatcher {
this._vectorLayers[vectorLayerId] = knownProperties this._vectorLayers[vectorLayerId] = knownProperties
}) })
}) })
console.log(this.vectorLayers)
console.log(this.vectorLayers.toJSON())
} }
/** Access all known sources and their vector tile ids */ /** Access all known sources and their vector tile ids */
get sources() { get sources() {
return Immutable.Map(Object.keys(this._sources).map(key => { return this._sources
return [key, Immutable.Set(this._sources[key])]
}))
} }
get vectorLayers() { get vectorLayers() {
return Immutable.Map(entries(this._vectorLayers).map(([key, layer]) => { return this._vectorLayers
return [key, Immutable.Map(entries(layer).map(([propId, values]) => {
return [propId, Immutable.Set(Object.keys(values))]
}))]
}))
} }
} }

View file

@ -1,68 +1,30 @@
import React from 'react'; import React from 'react';
import Immutable from 'immutable'
import spec from 'mapbox-gl-style-spec/reference/latest.min.js' import spec from 'mapbox-gl-style-spec/reference/latest.min.js'
// Standard JSON to Immutable conversion except layers
// are stored in an OrderedMap to make lookups id fast
// It also ensures that every style has an id and
// a created date for future reference
function fromJSON(jsonStyle) {
if (typeof jsonStyle === 'string' || jsonStyle instanceof String) {
jsonStyle = JSON.parse(jsonStyle)
}
return Immutable.Map(Object.keys(jsonStyle).map(key => {
const val = jsonStyle[key]
if(key === "layers") {
return [key, Immutable.OrderedMap(val.map(l => [l.id, Immutable.fromJS(l)]))]
} else if(key === "sources" || key === "metadata" || key === "transition") {
return [key, Immutable.fromJS(val)]
} else {
return [key, val]
}
}))
}
// Empty style is always used if no style could be restored or fetched // Empty style is always used if no style could be restored or fetched
const emptyStyle = ensureMetadataExists(fromJSON({ const emptyStyle = ensureMetadataExists({
version: 8, version: 8,
sources: {}, sources: {},
layers: [], layers: [],
})) })
function ensureHasId(style) { function ensureHasId(style) {
if(style.has('id')) return style if('id' in style) return style
return style.set('id', Math.random().toString(36).substr(2, 9)) style.id = Math.random().toString(36).substr(2, 9)
return style
} }
function ensureHasTimestamp(style) { function ensureHasTimestamp(style) {
if(style.has('id')) return style if('created' in style) return style
return style.set('created', new Date().toJSON()) style.created = new Date().toJSON()
return style
} }
function ensureMetadataExists(style) { function ensureMetadataExists(style) {
return ensureHasId(ensureHasTimestamp(style)) return ensureHasId(ensureHasTimestamp(style))
} }
// Turns immutable style back into JSON with the original order of the
// layers preserved
function toJSON(mapStyle) {
const jsonStyle = {}
for(let [key, value] of mapStyle.entries()) {
if(key === "layers") {
jsonStyle[key] = value.toIndexedSeq().toJS()
} else if(key === 'sources' || key === "metadata" || key === "transition") {
jsonStyle[key] = value.toJS()
} else {
jsonStyle[key] = value
}
}
return jsonStyle
}
export default { export default {
toJSON,
fromJSON,
ensureMetadataExists, ensureMetadataExists,
emptyStyle, emptyStyle,
} }

View file

@ -1,5 +1,4 @@
import { colorizeLayers } from './style.js' import { colorizeLayers } from './style.js'
import Immutable from 'immutable'
import style from './style.js' import style from './style.js'
const storagePrefix = "maputnik" const storagePrefix = "maputnik"
@ -18,7 +17,7 @@ export function loadDefaultStyle(cb) {
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(JSON.parse(request.responseText)))
} else { } else {
cb(style.emptyStyle) cb(style.emptyStyle)
} }
@ -104,15 +103,15 @@ export class StyleStore {
const styleId = window.localStorage.getItem(storageKeys.latest) const styleId = window.localStorage.getItem(storageKeys.latest)
const styleItem = window.localStorage.getItem(styleKey(styleId)) const styleItem = window.localStorage.getItem(styleKey(styleId))
if(styleItem) return cb(style.fromJSON(styleItem)) if(styleItem) return cb(JSON.parse(styleItem))
cb(style.emptyStyle) cb(style.emptyStyle)
} }
// Save current style replacing previous version // Save current style replacing previous version
save(mapStyle) { save(mapStyle) {
const key = styleKey(mapStyle.get('id')) const key = styleKey(mapStyle.id)
window.localStorage.setItem(key, JSON.stringify(style.toJSON(mapStyle))) window.localStorage.setItem(key, JSON.stringify(mapStyle))
window.localStorage.setItem(storageKeys.latest, mapStyle.get('id')) window.localStorage.setItem(storageKeys.latest, mapStyle.id)
return mapStyle return mapStyle
} }
} }