From e0a8b0a8e91bdcb62c21f5384d69a744d0c1cbd6 Mon Sep 17 00:00:00 2001 From: lukasmartinelli Date: Sat, 10 Sep 2016 13:42:23 +0200 Subject: [PATCH] App style is now single source of truth --- src/app.jsx | 15 +++-- src/layers.jsx | 62 ++++++++++--------- src/map.jsx | 4 +- src/stylestore.js | 151 +++++++++++++++++++++------------------------- src/workspace.jsx | 7 +-- 5 files changed, 117 insertions(+), 122 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 8519c3f8..c9424af5 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -20,9 +20,10 @@ export default class App extends React.Component { constructor(props) { super(props) + this.styleStore = new StyleStore() this.state = { - styleStore: new StyleStore(), workContext: "layers", + currentStyle: this.styleStore.latestStyle(), } } @@ -34,17 +35,19 @@ export default class App extends React.Component { } onStyleDownload() { - const mapStyle = JSON.stringify(this.state.styleStore.currentStyle, null, 4) + this.styleStore.save(newStyle) + const mapStyle = JSON.stringify(this.state.currentStyle.toJS(), null, 4) const blob = new Blob([mapStyle], {type: "application/json;charset=utf-8"}); saveAs(blob, mapStyle.id + ".json"); } onStyleUpload(newStyle) { - this.setState({ styleStore: new StyleStore(newStyle) }) + const savedStyle = this.styleStore.save(newStyle) + this.setState({ currentStyle: savedStyle }) } onStyleChanged(newStyle) { - this.setState({ styleStore: new StyleStore(newStyle) }) + this.setState({ currentStyle: newStyle }) } onOpenSettings() { @@ -66,10 +69,10 @@ export default class App extends React.Component {
- +
} diff --git a/src/layers.jsx b/src/layers.jsx index c526d867..db83c220 100644 --- a/src/layers.jsx +++ b/src/layers.jsx @@ -7,6 +7,7 @@ import Collapse from 'react-collapse' import theme from './theme.js' import scrollbars from './scrollbars.scss' import _ from 'lodash' +import Immutable from 'immutable' export class FillLayer extends React.Component { static propTypes = { @@ -24,14 +25,14 @@ export class FillLayer extends React.Component { } render() { - const paint = this.props.layer.paint + const paint = this.props.layer.get('paint') return
- - - - - - + + + + + +
} } @@ -51,10 +52,10 @@ export class BackgroundLayer extends React.Component { } render() { - const paint = this.props.layer.paint + const paint = this.props.layer.get('paint') return
- - + +
} } @@ -105,15 +106,13 @@ export class LayerPanel extends React.Component { } onPaintChanged(property, newValue) { - const layer = _.cloneDeep(this.props.layer) - layer.paint[property] = newValue; - this.props.onLayerChanged(layer) + const changedLayer = this.props.layer.setIn(['paint', property], newValue) + this.props.onLayerChanged(changedLayer) } onLayoutChanged(property, newValue) { - const layer = _.cloneDeep(this.props.layer) - layer.layout[property] = newValue; - this.props.onLayerChanged(layer) + const changedLayer = this.props.layer.setIn(['layout', property], newValue) + this.props.onLayerChanged(changedLayer) } toggleLayer() { @@ -168,11 +167,11 @@ export class LayerPanel extends React.Component { borderRight: 0, borderStyle: "solid", borderColor: theme.borderColor, - borderLeftColor: this.props.layer.metadata["mapolo:color"], + borderLeftColor: this.props.layer.getIn(['metadata', 'mapolo:color']) }}> - #{this.props.layer.id} + #{this.props.layer.get('id')} @@ -184,7 +183,7 @@ export class LayerPanel extends React.Component {
- {this.layerFromType(this.props.layer.type)} + {this.layerFromType(this.props.layer.get('type'))}
@@ -193,7 +192,7 @@ export class LayerPanel extends React.Component { export class LayerEditor extends React.Component { static propTypes = { - layers: React.PropTypes.array.isRequired, + layers: React.PropTypes.instanceOf(Immutable.List), onLayersChanged: React.PropTypes.func.isRequired } @@ -208,6 +207,7 @@ export class LayerEditor extends React.Component { for (let i = 0; i < this.props.layers.length; i++) { if(this.props.layers[i].id == deletedLayer.id) { deleteIdx = i + break } } @@ -219,25 +219,29 @@ export class LayerEditor extends React.Component { onLayerChanged(changedLayer) { //TODO: That's just horrible... let changeIdx = -1 - for (let i = 0; i < this.props.layers.length; i++) { - if(this.props.layers[i].id == changedLayer.id) { + for (let entry of this.props.layers.entries()) { + let [i, layer] = entry + if(layer.get('id') == changedLayer.get('id')) { changeIdx = i + break } } - const changedLayers = _.cloneDeep(this.props.layers) - changedLayers[changeIdx] = changedLayer + + const changedLayers = this.props.layers.set(changeIdx, changedLayer) this.props.onLayersChanged(changedLayers) } render() { - const layerPanels = this.props.layers.map(layer => { - return - }); + />) + } return
diff --git a/src/map.jsx b/src/map.jsx index defd1433..cec370a3 100644 --- a/src/map.jsx +++ b/src/map.jsx @@ -11,7 +11,7 @@ export class Map extends React.Component { componentWillReceiveProps(nextProps) { const map = this.state.map if(map) { - const changes = diffStyles(this.props.mapStyle, nextProps.mapStyle) + const changes = diffStyles(this.props.mapStyle.toJS(), nextProps.mapStyle.toJS()) changes.forEach(change => { map[change.command].apply(map, change.args); }); @@ -26,7 +26,7 @@ export class Map extends React.Component { MapboxGl.accessToken = "pk.eyJ1IjoibW9yZ2Vua2FmZmVlIiwiYSI6IjIzcmN0NlkifQ.0LRTNgCc-envt9d5MzR75w"; const map = new MapboxGl.Map({ container: this.container, - style: this.props.mapStyle, + style: this.props.mapStyle.toJS(), }); map.on("style.load", (...args) => { diff --git a/src/stylestore.js b/src/stylestore.js index 6f0821ae..bc7b00fd 100644 --- a/src/stylestore.js +++ b/src/stylestore.js @@ -1,4 +1,12 @@ import { colorizeLayers } from './style.js' +import Immutable from 'immutable' + +const storage = { + prefix: 'mapolo', + keys: { + latest: 'mapolo:latest_style' + } +} const emptyStyle = { version: 8, @@ -6,96 +14,77 @@ const emptyStyle = { layers: [] } + // Return style ids and dates of all styles stored in local storage +function loadStoredStyles() { + const styles = [] + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i) + if(isStyleKey(key)) { + styles.push(fromKey(key)) + } + } + return styles +} + +function isStyleKey(key) { + const parts = key.split(":") + return parts.length == 2 && parts[0] === storage.prefix +} + +// Load style id from key +function fromKey(key) { + if(!isStyleKey(key)) { + throw "Key is not a valid style key" + } + + const parts = key.split(":") + const styleId = parts[1] + return styleId +} + +// Calculate key that identifies the style with a version +function styleKey(styleId) { + return [storage.prefix, styleId].join(":") +} + +// Ensure a style has a unique id and a created date +function ensureOptionalStyleProps(mapStyle) { + if(!('id' in mapStyle)) { + mapStyle = mapStyle.set('id', Math.random().toString(36).substr(2, 9)) + } + if(!("created" in mapStyle)) { + mapStyle = mapStyle.set('created', new Date()) + } + return mapStyle +} + // Manages many possible styles that are stored in the local storage export class StyleStore { - // By default the style store will use the last edited style - // as current working style if no explicit style is set - constructor(mapStyle) { - if(mapStyle) { - this.load(mapStyle) - } else { - try { - const latestStyle = this.latestStyle() - console.log("Loading latest stlye " + latestStyle.id + " from " + latestStyle.modified) - this.load(latestStyle) - } catch(err) { - console.log(err) - this.load(emptyStyle) - } - } + // Tile store will load all items from local storage and + // assume they do not change will working on it + constructor() { + this.mapStyles = loadStoredStyles() } // Find the last edited style latestStyle() { - const styles = this.loadStoredStyles() - - if(styles.length == 0) { - throw "No existing style found" + if(this.mapStyles.length == 0) { + return Immutable.fromJS(emptyStyle) } - - let maxStyle = styles[0] - styles.forEach(s => { - if(s.date > maxStyle.date) { - maxStyle = s - } - }) - - return JSON.parse(window.localStorage.getItem(this.styleKey(maxStyle.styleId, maxStyle.date))) + const styleId = window.localStorage.getItem(storage.keys.latest) + const styleItem = window.localStorage.getItem(styleKey(styleId)) + return Immutable.fromJS(JSON.parse(styleItem)) } - // Return style ids and dates of all styles stored in local storage - loadStoredStyles() { - const styles = [] - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i) - if(this.isStyleKey(key)) { - styles.push(this.fromKey(key)) - } + // Save current style replacing previous version + save(mapStyle) { + if(!(mapStyle instanceof Immutable.Map)) { + mapStyle = Immutable.fromJS(mapStyle) } - return styles - } - - isStyleKey(key) { - const parts = key.split(":") - return parts.length >= 3 && parts[0] === "mapolo" - } - - // Load style from local storage by key - fromKey(key) { - if(!this.isStyleKey(key)) { - throw "Key is not a valid style key" - } - - const parts = key.split(":") - const styleId = parts[1] - const date = new Date(parts.slice(2).join(":")) - return {styleId, date} - } - - // Calculate key that identifies the style with a version - styleKey(styleId, modifiedDate) { - return ["mapolo", styleId, modifiedDate.toJSON()].join(":") - } - - // Take snapshot of current style and load it - backup(mapStyle) { - mapStyle.modified = new Date() - const key = this.styleKey(mapStyle.id, mapStyle.modified) - window.localStorage.setItem(key, JSON.stringify(mapStyle)) - } - - // Load a style from external into the store - // replacing the previous version - load(mapStyle) { - if(!("id" in mapStyle)) { - mapStyle.id = Math.random().toString(36).substr(2, 9) - } - if(!("created" in mapStyle)) { - mapStyle.created = new Date() - } - mapStyle.layers = colorizeLayers(mapStyle.layers) - - this.backup(mapStyle) - this.currentStyle = mapStyle + mapStyle = ensureOptionalStyleProps(mapStyle) + const key = styleKey(mapStyle.get('id')) + window.localStorage.setItem(key, JSON.stringify(mapStyle.toJS())) + window.localStorage.setItem(storage.keys.latest, mapStyle.get('id')) + return mapStyle } } diff --git a/src/workspace.jsx b/src/workspace.jsx index 32bd72c5..eb3683f8 100644 --- a/src/workspace.jsx +++ b/src/workspace.jsx @@ -12,9 +12,8 @@ export class WorkspaceDrawer extends React.Component { workContext: React.PropTypes.oneOf(['layers', 'settings']).isRequired, } - onLayersChanged(layers) { - const changedStyle = this.props.mapStyle - changedStyle.layers = layers + onLayersChanged(changedLayers) { + const changedStyle = this.props.mapStyle.set('layers', changedLayers) this.props.onStyleChanged(changedStyle) } @@ -24,7 +23,7 @@ export class WorkspaceDrawer extends React.Component { if(this.props.workContext === "layers") { workspaceContent = }