From 575dacdb2ce9dd7bf37707ebf5d5cf880a5430a3 Mon Sep 17 00:00:00 2001 From: lukasmartinelli Date: Sat, 10 Sep 2016 21:29:18 +0200 Subject: [PATCH] Reorganize JSON to immutable conversion --- src/app.jsx | 7 +++--- src/map.jsx | 16 +++++------- src/style.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++ src/stylestore.js | 53 +++++++++------------------------------ src/toolbar.jsx | 3 ++- 5 files changed, 86 insertions(+), 56 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 7a97d4e..9401c90 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -39,10 +39,10 @@ export default class App extends React.Component { } 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"}); saveAs(blob, mapStyle.id + ".json"); - this.onStyleSave(mapStyle) + this.onStyleSave() } onStyleUpload(newStyle) { @@ -52,8 +52,7 @@ export default class App extends React.Component { onStyleSave() { const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON()) - const savedStyle = this.styleStore.save(snapshotStyle) - this.setState({ currentStyle: savedStyle }) + this.setState({ currentStyle: snapshotStyle }) } onStyleChanged(newStyle) { diff --git a/src/map.jsx b/src/map.jsx index fa471a9..1f2197e 100644 --- a/src/map.jsx +++ b/src/map.jsx @@ -1,8 +1,7 @@ import React from 'react' import MapboxGl from 'mapbox-gl'; -import diffStyles from 'mapbox-gl-style-spec/lib/diff' import { fullHeight } from './theme.js' -import { styleToJS } from './stylestore.js' +import style from './style.js' import Immutable from 'immutable' 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 // 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') if(mapIdChanged || tokenChanged) { - this.state.map.setStyle(styleToJS(nextProps.mapStyle)) + this.state.map.setStyle(style.toJSON(nextProps.mapStyle)) return } // TODO: If there is no map yet we need to apply the changes later? - // How to deal with that? if(this.state.map) { - //TODO: Write own diff algo that operates on immutable collections - // 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 => { + style.diffStyles(this.props.mapStyle, nextProps.mapStyle).forEach(change => { this.state.map[change.command].apply(this.state.map, change.args); }); } } shouldComponentUpdate(nextProps, nextState) { + //TODO: If we enable this React mixin for immutable comparison we can remove this? return nextProps.mapStyle !== this.props.mapStyle } @@ -45,7 +41,7 @@ export class Map extends React.Component { const map = new MapboxGl.Map({ container: this.container, - style: styleToJS(this.props.mapStyle), + style: style.toJSON(this.props.mapStyle), }); map.on("style.load", (...args) => { diff --git a/src/style.js b/src/style.js index 9440fba..13d1abe 100644 --- a/src/style.js +++ b/src/style.js @@ -1,6 +1,62 @@ 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' +// 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) { return layers.map(layer => { if(!layer.metadata) { @@ -12,3 +68,10 @@ export function colorizeLayers(layers) { return layer }) } + +export default { + colorizeLayers, + toJSON, + fromJSON, + diffStyles, +} diff --git a/src/stylestore.js b/src/stylestore.js index 25204cd..7101ae3 100644 --- a/src/stylestore.js +++ b/src/stylestore.js @@ -1,5 +1,6 @@ import { colorizeLayers } from './style.js' import Immutable from 'immutable' +import style from './style.js' const storagePrefix = "mapolo" const storageKeys = { @@ -8,47 +9,32 @@ const storageKeys = { } // Empty style is always used if no style could be restored or fetched -const emptyStyle = ensureOptionalStyleProps(makeStyleImmutable({ +const emptyStyle = style.fromJSON({ version: 8, sources: {}, layers: [], -})) +}) 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 export function loadDefaultStyle(cb) { - var request = new XMLHttpRequest(); - request.open('GET', defaultStyleUrl, true); + var request = new XMLHttpRequest() + request.open('GET', defaultStyleUrl, true) request.onload = () => { if (request.status >= 200 && request.status < 400) { - cb(makeStyleImmutable(JSON.parse(request.responseText))) + cb(style.fromJSON(request.responseText)) } else { cb(emptyStyle) } - }; + } request.onerror = function() { console.log('Could not fetch default style') cb(emptyStyle) - }; + } - request.send(); + request.send() } // Return style ids and dates of all styles stored in local storage @@ -84,17 +70,6 @@ function styleKey(styleId) { 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 export class SettingsStore { get accessToken() { @@ -120,18 +95,14 @@ export class StyleStore { const styleId = window.localStorage.getItem(storageKeys.latest) const styleItem = window.localStorage.getItem(styleKey(styleId)) - if(styleItem) return makeStyleImmutable(JSON.parse(styleItem)) - return memptyStyle + if(styleItem) return style.fromJSON(styleItem) + return emptyStyle } // Save current style replacing previous version save(mapStyle) { - if(!(mapStyle instanceof Immutable.Map)) { - mapStyle = makeStyleImmutable(mapStyle) - } - mapStyle = ensureOptionalStyleProps(mapStyle) 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')) return mapStyle } diff --git a/src/toolbar.jsx b/src/toolbar.jsx index fb0e9e2..11297b9 100644 --- a/src/toolbar.jsx +++ b/src/toolbar.jsx @@ -10,6 +10,7 @@ import MdSettings from 'react-icons/lib/md/settings' import MdLayers from 'react-icons/lib/md/layers' import MdSave from 'react-icons/lib/md/save' +import { GlStyle } from './style.js' import { fullHeight } from './theme.js' import theme from './theme.js'; @@ -36,7 +37,7 @@ export class Toolbar extends React.Component { reader.readAsText(file, "UTF-8"); reader.onload = e => { const style = JSON.parse(e.target.result); - this.props.onStyleUpload(style); + this.props.onStyleUpload(GlStyle.fromJSON(style)); } reader.onerror = e => console.log(e.target); }