From e5c6af3fadf4a0da9843889dc34454287c3d8c62 Mon Sep 17 00:00:00 2001 From: Lukas Martinelli Date: Fri, 16 Dec 2016 14:49:25 +0100 Subject: [PATCH] Fall back to local style store And convert tabs to spaces in process --- src/apistore.js | 13 ++-- src/app.jsx | 186 +++++++++++++++++++++++----------------------- src/style.js | 96 +++++++++++++----------- src/stylestore.js | 151 ++++++++++++++++++------------------- 4 files changed, 225 insertions(+), 221 deletions(-) diff --git a/src/apistore.js b/src/apistore.js index 21502a2..6b7c372 100644 --- a/src/apistore.js +++ b/src/apistore.js @@ -1,14 +1,13 @@ import request from 'request' import style from './style.js' -// Empty style is always used if no style could be restored or fetched -export const emptyStyle = style.ensureMetadataExists(style.fromJSON({ - version: 8, - sources: {}, - layers: [], -})) - export class ApiStyleStore { + supported(cb) { + request('http://localhost:8000/styles', (error, response, body) => { + cb(error === undefined) + }) + } + latestStyle(cb) { if(this.latestStyleId) { request('http://localhost:8000/styles/' + this.latestStyleId, (error, response, body) => { diff --git a/src/app.jsx b/src/app.jsx index d77235a..9192e4f 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -9,120 +9,120 @@ import Fixed from 'rebass/dist/Fixed' import { Map } from './map.jsx' import {Toolbar} from './toolbar.jsx' import style from './style.js' -import { loadDefaultStyle, SettingsStore } from './stylestore.js' -import { emptyStyle, ApiStyleStore } from './apistore.js' +import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.js' +import { ApiStyleStore } from './apistore.js' import { WorkspaceDrawer } from './workspace.jsx' import theme from './theme.js' import './index.scss' export default class App extends React.Component { - static childContextTypes = { - rebass: React.PropTypes.object, - reactIconBase: React.PropTypes.object - } + static childContextTypes = { + rebass: React.PropTypes.object, + reactIconBase: React.PropTypes.object + } - constructor(props) { - super(props) - this.styleStore = new ApiStyleStore() - this.settingsStore = new SettingsStore() - this.state = { - accessToken: this.settingsStore.accessToken, - workContext: "layers", - currentStyle: emptyStyle - } + constructor(props) { + super(props) - this.styleStore.latestStyle(mapStyle => { - this.onStyleUpload(mapStyle) + this.styleStore = new ApiStyleStore() + this.styleStore.supported(isSupported => { + if(!isSupported) { + console.log('Falling back to local storage for storing styles') + this.styleStore = new StyleStore() + } + this.styleStore.latestStyle(mapStyle => this.onStyleUpload(mapStyle)) }) - /* - if(this.state.currentStyle.get('layers').size === 0) { - loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle)) - } - */ - } - onReset() { - this.styleStore.purge() - loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle)) - } + this.settingsStore = new SettingsStore() + this.state = { + accessToken: this.settingsStore.accessToken, + workContext: "layers", + currentStyle: style.emptyStyle + } + } - getChildContext() { - return { - rebass: theme, - reactIconBase: { size: 20 } - } - } + onReset() { + this.styleStore.purge() + loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle)) + } - onStyleDownload() { - const mapStyle = style.toJSON(this.state.currentStyle) - const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"}); - saveAs(blob, mapStyle.id + ".json"); - this.onStyleSave() - } + getChildContext() { + return { + rebass: theme, + reactIconBase: { size: 20 } + } + } - onStyleUpload(newStyle) { - const savedStyle = this.styleStore.save(newStyle) - this.setState({ currentStyle: savedStyle }) - } + onStyleDownload() { + const mapStyle = style.toJSON(this.state.currentStyle) + const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"}); + saveAs(blob, mapStyle.id + ".json"); + this.onStyleSave() + } - onStyleSave() { - const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON()) - this.setState({ currentStyle: snapshotStyle }) + onStyleUpload(newStyle) { + const savedStyle = this.styleStore.save(newStyle) + this.setState({ currentStyle: savedStyle }) + } + + onStyleSave() { + const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON()) + this.setState({ currentStyle: snapshotStyle }) console.log('Save') - this.styleStore.save(snapshotStyle) - } + this.styleStore.save(snapshotStyle) + } - onStyleChanged(newStyle) { - this.setState({ currentStyle: newStyle }) - } + onStyleChanged(newStyle) { + this.setState({ currentStyle: newStyle }) + } - onOpenSettings() { - this.setState({ workContext: "settings" }) - } + onOpenSettings() { + this.setState({ workContext: "settings" }) + } - onOpenAbout() { - this.setState({ workContext: "about" }) - } + onOpenAbout() { + this.setState({ workContext: "about" }) + } - onOpenLayers() { - this.setState({ workContext: "layers", }) - } + onOpenLayers() { + this.setState({ workContext: "layers", }) + } - onOpenSources() { - this.setState({ workContext: "sources", }) - } + onOpenSources() { + this.setState({ workContext: "sources", }) + } - onAccessTokenChanged(newToken) { - this.settingsStore.accessToken = newToken - this.setState({ accessToken: newToken }) - } + onAccessTokenChanged(newToken) { + this.settingsStore.accessToken = newToken + this.setState({ accessToken: newToken }) + } - render() { - return
- 0} - onStyleSave={this.onStyleSave.bind(this)} - onStyleUpload={this.onStyleUpload.bind(this)} - onStyleDownload={this.onStyleDownload.bind(this)} - onOpenSettings={this.onOpenSettings.bind(this)} - onOpenAbout={this.onOpenAbout.bind(this)} - onOpenLayers={this.onOpenLayers.bind(this)} - onOpenSources={this.onOpenSources.bind(this)} - /> - - -
- } + render() { + return
+ 0} + onStyleSave={this.onStyleSave.bind(this)} + onStyleUpload={this.onStyleUpload.bind(this)} + onStyleDownload={this.onStyleDownload.bind(this)} + onOpenSettings={this.onOpenSettings.bind(this)} + onOpenAbout={this.onOpenAbout.bind(this)} + onOpenLayers={this.onOpenLayers.bind(this)} + onOpenSources={this.onOpenSources.bind(this)} + /> + + +
+ } } diff --git a/src/style.js b/src/style.js index d5a869e..ab9e213 100644 --- a/src/style.js +++ b/src/style.js @@ -9,34 +9,41 @@ import randomColor from 'randomcolor' // 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 (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] - } - })) + 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 +const emptyStyle = ensureMetadataExists(fromJSON({ + version: 8, + sources: {}, + layers: [], +})) + function ensureHasId(style) { - if(style.has('id')) return style - return style.set('id', Math.random().toString(36).substr(2, 9)) + if(style.has('id')) return style + return style.set('id', Math.random().toString(36).substr(2, 9)) } function ensureHasTimestamp(style) { - if(style.has('id')) return style - return style.set('created', new Date().toJSON()) + if(style.has('id')) return style + return style.set('created', new Date().toJSON()) } function ensureMetadataExists(style) { - return ensureHasId(ensureHasTimestamp(style)) + return ensureHasId(ensureHasTimestamp(style)) } // Compare style with other style and return changes @@ -44,41 +51,42 @@ function ensureMetadataExists(style) { // Should be able to improve performance since we can only compare // by reference function diffStyles(before, after) { - return diffJSONStyles(toJSON(before), toJSON(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 + 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) { - layer.metadata = {} - } - if(!"maputnik:color" in layer.metadata) { - layer.metadata["maputnik:color"] = randomColor() - } - return layer - }) + return layers.map(layer => { + if(!layer.metadata) { + layer.metadata = {} + } + if(!"maputnik:color" in layer.metadata) { + layer.metadata["maputnik:color"] = randomColor() + } + return layer + }) } export default { - colorizeLayers, - toJSON, - fromJSON, - diffStyles, - ensureMetadataExists, + colorizeLayers, + toJSON, + fromJSON, + diffStyles, + ensureMetadataExists, + emptyStyle, } diff --git a/src/stylestore.js b/src/stylestore.js index 85db8b4..9927ada 100644 --- a/src/stylestore.js +++ b/src/stylestore.js @@ -5,117 +5,114 @@ import style from './style.js' const storagePrefix = "maputnik" const stylePrefix = 'style' const storageKeys = { - latest: [storagePrefix, 'latest_style'].join(':'), - accessToken: [storagePrefix, 'access_token'].join(':') + latest: [storagePrefix, 'latest_style'].join(':'), + accessToken: [storagePrefix, 'access_token'].join(':') } -// Empty style is always used if no style could be restored or fetched -const emptyStyle = style.ensureMetadataExists(style.fromJSON({ - version: 8, - sources: {}, - layers: [], -})) - const defaultStyleUrl = "https://raw.githubusercontent.com/osm2vectortiles/mapbox-gl-styles/master/styles/basic-v9-cdn.json" // Fetch a default style via URL and return it or a fallback style via callback export function loadDefaultStyle(cb) { - console.log('Load default style') - var request = new XMLHttpRequest() - request.open('GET', defaultStyleUrl, true) + console.log('Load default style') + var request = new XMLHttpRequest() + request.open('GET', defaultStyleUrl, true) - request.onload = () => { - if (request.status >= 200 && request.status < 400) { - cb(style.ensureMetadataExists(style.fromJSON(request.responseText))) - } else { - cb(emptyStyle) - } - } + request.onload = () => { + if (request.status >= 200 && request.status < 400) { + cb(style.ensureMetadataExists(style.fromJSON(request.responseText))) + } else { + cb(style.emptyStyle) + } + } - request.onerror = function() { - console.log('Could not fetch default style') - cb(emptyStyle) - } + request.onerror = function() { + console.log('Could not fetch default style') + cb(style.emptyStyle) + } - request.send() + request.send() } // Return style ids and dates of all styles stored in local storage function loadStoredStyles() { - const styles = [] - for (let i = 0; i < window.localStorage.length; i++) { - const key = window.localStorage.key(i) - if(isStyleKey(key)) { - styles.push(fromKey(key)) - } - } - return styles + const styles = [] + for (let i = 0; i < window.localStorage.length; i++) { + const key = window.localStorage.key(i) + if(isStyleKey(key)) { + styles.push(fromKey(key)) + } + } + return styles } function isStyleKey(key) { - const parts = key.split(":") - return parts.length == 3 && parts[0] === storagePrefix && parts[1] === stylePrefix + const parts = key.split(":") + return parts.length == 3 && parts[0] === storagePrefix && parts[1] === stylePrefix } // Load style id from key function fromKey(key) { - if(!isStyleKey(key)) { - throw "Key is not a valid style key" - } + if(!isStyleKey(key)) { + throw "Key is not a valid style key" + } - const parts = key.split(":") - const styleId = parts[2] - return styleId + const parts = key.split(":") + const styleId = parts[2] + return styleId } // Calculate key that identifies the style with a version function styleKey(styleId) { - return [storagePrefix, stylePrefix, styleId].join(":") + return [storagePrefix, stylePrefix, styleId].join(":") } // Store style independent settings export class SettingsStore { - get accessToken() { - const token = window.localStorage.getItem(storageKeys.accessToken) - return token ? token : "" - } - set accessToken(val) { - window.localStorage.setItem(storageKeys.accessToken, val) - } + get accessToken() { + const token = window.localStorage.getItem(storageKeys.accessToken) + return token ? token : "" + } + set accessToken(val) { + window.localStorage.setItem(storageKeys.accessToken, val) + } } // Manages many possible styles that are stored in the local storage export class StyleStore { - // Tile store will load all items from local storage and - // assume they do not change will working on it - constructor() { - this.mapStyles = loadStoredStyles() - } + // Tile store will load all items from local storage and + // assume they do not change will working on it + constructor() { + this.mapStyles = loadStoredStyles() + } - // Delete entire style history - purge() { - for (let i = 0; i < window.localStorage.length; i++) { - const key = window.localStorage.key(i) - if(key.startsWith(storagePrefix)) { - window.localStorage.removeItem(key) - } - } - } + supported(cb) { + cb(window.localStorage !== undefined) + } - // Find the last edited style - latestStyle() { - if(this.mapStyles.length === 0) return emptyStyle - const styleId = window.localStorage.getItem(storageKeys.latest) - const styleItem = window.localStorage.getItem(styleKey(styleId)) + // Delete entire style history + purge() { + for (let i = 0; i < window.localStorage.length; i++) { + const key = window.localStorage.key(i) + if(key.startsWith(storagePrefix)) { + window.localStorage.removeItem(key) + } + } + } - if(styleItem) return style.fromJSON(styleItem) - return emptyStyle - } + // Find the last edited style + latestStyle(cb) { + if(this.mapStyles.length === 0) return cb(style.emptyStyle) + const styleId = window.localStorage.getItem(storageKeys.latest) + const styleItem = window.localStorage.getItem(styleKey(styleId)) - // Save current style replacing previous version - save(mapStyle) { - const key = styleKey(mapStyle.get('id')) - window.localStorage.setItem(key, JSON.stringify(style.toJSON(mapStyle))) - window.localStorage.setItem(storageKeys.latest, mapStyle.get('id')) - return mapStyle - } + if(styleItem) return cb(style.fromJSON(styleItem)) + cb(style.emptyStyle) + } + + // Save current style replacing previous version + save(mapStyle) { + const key = styleKey(mapStyle.get('id')) + window.localStorage.setItem(key, JSON.stringify(style.toJSON(mapStyle))) + window.localStorage.setItem(storageKeys.latest, mapStyle.get('id')) + return mapStyle + } }