Reorganize JSON to immutable conversion

This commit is contained in:
lukasmartinelli 2016-09-10 21:29:18 +02:00
parent ac3844f35c
commit 575dacdb2c
5 changed files with 86 additions and 56 deletions

View file

@ -39,10 +39,10 @@ export default class App extends React.Component {
} }
onStyleDownload() { onStyleDownload() {
const mapStyle = JSON.stringify(this.state.currentStyle.toJS(), null, 4) const mapStyle = JSON.stringify(this.state.currentStyle.toJSON(), null, 4)
const blob = new Blob([mapStyle], {type: "application/json;charset=utf-8"}); const blob = new Blob([mapStyle], {type: "application/json;charset=utf-8"});
saveAs(blob, mapStyle.id + ".json"); saveAs(blob, mapStyle.id + ".json");
this.onStyleSave(mapStyle) this.onStyleSave()
} }
onStyleUpload(newStyle) { onStyleUpload(newStyle) {
@ -52,8 +52,7 @@ export default class App extends React.Component {
onStyleSave() { onStyleSave() {
const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON()) const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON())
const savedStyle = this.styleStore.save(snapshotStyle) this.setState({ currentStyle: snapshotStyle })
this.setState({ currentStyle: savedStyle })
} }
onStyleChanged(newStyle) { onStyleChanged(newStyle) {

View file

@ -1,8 +1,7 @@
import React from 'react' import React from 'react'
import MapboxGl from 'mapbox-gl'; import MapboxGl from 'mapbox-gl';
import diffStyles from 'mapbox-gl-style-spec/lib/diff'
import { fullHeight } from './theme.js' import { fullHeight } from './theme.js'
import { styleToJS } from './stylestore.js' import style from './style.js'
import Immutable from 'immutable' import Immutable from 'immutable'
export class Map extends React.Component { export class Map extends React.Component {
@ -16,27 +15,24 @@ export class Map extends React.Component {
// 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?
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 || tokenChanged) {
this.state.map.setStyle(styleToJS(nextProps.mapStyle)) this.state.map.setStyle(style.toJSON(nextProps.mapStyle))
return return
} }
// TODO: If there is no map yet we need to apply the changes later? // TODO: If there is no map yet we need to apply the changes later?
// How to deal with that?
if(this.state.map) { if(this.state.map) {
//TODO: Write own diff algo that operates on immutable collections style.diffStyles(this.props.mapStyle, nextProps.mapStyle).forEach(change => {
// Should be able to improve performance since we can only compare
// by reference
const changes = diffStyles(styleToJS(this.props.mapStyle), styleToJS(nextProps.mapStyle))
changes.forEach(change => {
this.state.map[change.command].apply(this.state.map, change.args); this.state.map[change.command].apply(this.state.map, change.args);
}); });
} }
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
//TODO: If we enable this React mixin for immutable comparison we can remove this?
return nextProps.mapStyle !== this.props.mapStyle return nextProps.mapStyle !== this.props.mapStyle
} }
@ -45,7 +41,7 @@ export class Map extends React.Component {
const map = new MapboxGl.Map({ const map = new MapboxGl.Map({
container: this.container, container: this.container,
style: styleToJS(this.props.mapStyle), style: style.toJSON(this.props.mapStyle),
}); });
map.on("style.load", (...args) => { map.on("style.load", (...args) => {

View file

@ -1,6 +1,62 @@
import React from 'react'; import React from 'react';
import Immutable from 'immutable'
import spec from 'mapbox-gl-style-spec/reference/latest.min.js'
import diffJSONStyles from 'mapbox-gl-style-spec/lib/diff'
import randomColor from 'randomcolor' import randomColor from 'randomcolor'
// 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)
}
if(!('id' in jsonStyle)) {
jsonStyle.id = Math.random().toString(36).substr(2, 9)
}
if(!('created' in jsonStyle)) {
jsonStyle.created = new Date().toJSON()
}
return new 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.Map(val)]
} else {
return [key, val]
}
}))
}
// Compare style with other style and return changes
//TODO: Write own diff algo that operates on immutable collections
// Should be able to improve performance since we can only compare
// by reference
function diffStyles(before, after) {
return diffJSONStyles(toJSON(before), toJSON(after))
}
// 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 function colorizeLayers(layers) { export function colorizeLayers(layers) {
return layers.map(layer => { return layers.map(layer => {
if(!layer.metadata) { if(!layer.metadata) {
@ -12,3 +68,10 @@ export function colorizeLayers(layers) {
return layer return layer
}) })
} }
export default {
colorizeLayers,
toJSON,
fromJSON,
diffStyles,
}

View file

@ -1,5 +1,6 @@
import { colorizeLayers } from './style.js' import { colorizeLayers } from './style.js'
import Immutable from 'immutable' import Immutable from 'immutable'
import style from './style.js'
const storagePrefix = "mapolo" const storagePrefix = "mapolo"
const storageKeys = { const storageKeys = {
@ -8,47 +9,32 @@ const storageKeys = {
} }
// 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 = ensureOptionalStyleProps(makeStyleImmutable({ const emptyStyle = style.fromJSON({
version: 8, version: 8,
sources: {}, sources: {},
layers: [], 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"
// TODO: Stop converting around so much.. we should make a module containing the immutable style stuff
export function styleToJS(mapStyle) {
const jsonStyle = mapStyle.toJS()
jsonStyle.layers = mapStyle.get('layers').toIndexedSeq().toJS()
return jsonStyle
}
function makeStyleImmutable(mapStyle) {
if(mapStyle instanceof Immutable.Map) return mapStyle
const style = Immutable.fromJS(mapStyle)
const orderdLayers = Immutable.OrderedMap(mapStyle.layers.map(l => [l.id, Immutable.fromJS(l)]))
return style.set('layers', orderdLayers)
}
// 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) {
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(makeStyleImmutable(JSON.parse(request.responseText))) cb(style.fromJSON(request.responseText))
} else { } else {
cb(emptyStyle) cb(emptyStyle)
} }
}; }
request.onerror = function() { request.onerror = function() {
console.log('Could not fetch default style') console.log('Could not fetch default style')
cb(emptyStyle) cb(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
@ -84,17 +70,6 @@ function styleKey(styleId) {
return [storagePrefix, styleId].join(":") return [storagePrefix, styleId].join(":")
} }
// Ensure a style has a unique id and a created date
function ensureOptionalStyleProps(mapStyle) {
if(!mapStyle.has('id')) {
mapStyle = mapStyle.set('id', Math.random().toString(36).substr(2, 9))
}
if(!mapStyle.has('created')) {
mapStyle = mapStyle.set('created', new Date())
}
return mapStyle
}
// Store style independent settings // Store style independent settings
export class SettingsStore { export class SettingsStore {
get accessToken() { get accessToken() {
@ -120,18 +95,14 @@ 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 makeStyleImmutable(JSON.parse(styleItem)) if(styleItem) return style.fromJSON(styleItem)
return memptyStyle return emptyStyle
} }
// Save current style replacing previous version // Save current style replacing previous version
save(mapStyle) { save(mapStyle) {
if(!(mapStyle instanceof Immutable.Map)) {
mapStyle = makeStyleImmutable(mapStyle)
}
mapStyle = ensureOptionalStyleProps(mapStyle)
const key = styleKey(mapStyle.get('id')) const key = styleKey(mapStyle.get('id'))
window.localStorage.setItem(key, JSON.stringify(styleToJS(mapStyle))) window.localStorage.setItem(key, JSON.stringify(style.toJSON(mapStyle)))
window.localStorage.setItem(storageKeys.latest, mapStyle.get('id')) window.localStorage.setItem(storageKeys.latest, mapStyle.get('id'))
return mapStyle return mapStyle
} }

View file

@ -10,6 +10,7 @@ import MdSettings from 'react-icons/lib/md/settings'
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 { GlStyle } from './style.js'
import { fullHeight } from './theme.js' import { fullHeight } from './theme.js'
import theme from './theme.js'; import theme from './theme.js';
@ -36,7 +37,7 @@ export class Toolbar extends React.Component {
reader.readAsText(file, "UTF-8"); reader.readAsText(file, "UTF-8");
reader.onload = e => { reader.onload = e => {
const style = JSON.parse(e.target.result); const style = JSON.parse(e.target.result);
this.props.onStyleUpload(style); this.props.onStyleUpload(GlStyle.fromJSON(style));
} }
reader.onerror = e => console.log(e.target); reader.onerror = e => console.log(e.target);
} }