mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-27 09:45:29 +01:00
Reorganize JSON to immutable conversion
This commit is contained in:
parent
ac3844f35c
commit
575dacdb2c
5 changed files with 86 additions and 56 deletions
|
@ -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) {
|
||||||
|
|
16
src/map.jsx
16
src/map.jsx
|
@ -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) => {
|
||||||
|
|
63
src/style.js
63
src/style.js
|
@ -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,
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue