Fall back to local style store

And convert tabs to spaces in process
This commit is contained in:
Lukas Martinelli 2016-12-16 14:49:25 +01:00
parent f7fabd1bc8
commit e5c6af3fad
4 changed files with 225 additions and 221 deletions

View file

@ -1,14 +1,13 @@
import request from 'request' import request from 'request'
import style from './style.js' 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 { export class ApiStyleStore {
supported(cb) {
request('http://localhost:8000/styles', (error, response, body) => {
cb(error === undefined)
})
}
latestStyle(cb) { latestStyle(cb) {
if(this.latestStyleId) { if(this.latestStyleId) {
request('http://localhost:8000/styles/' + this.latestStyleId, (error, response, body) => { request('http://localhost:8000/styles/' + this.latestStyleId, (error, response, body) => {

View file

@ -9,120 +9,120 @@ import Fixed from 'rebass/dist/Fixed'
import { Map } from './map.jsx' import { Map } from './map.jsx'
import {Toolbar} from './toolbar.jsx' import {Toolbar} from './toolbar.jsx'
import style from './style.js' import style from './style.js'
import { loadDefaultStyle, SettingsStore } from './stylestore.js' import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.js'
import { emptyStyle, ApiStyleStore } from './apistore.js' import { ApiStyleStore } from './apistore.js'
import { WorkspaceDrawer } from './workspace.jsx' import { WorkspaceDrawer } from './workspace.jsx'
import theme from './theme.js' import theme from './theme.js'
import './index.scss' import './index.scss'
export default class App extends React.Component { export default class App extends React.Component {
static childContextTypes = { static childContextTypes = {
rebass: React.PropTypes.object, rebass: React.PropTypes.object,
reactIconBase: React.PropTypes.object reactIconBase: React.PropTypes.object
} }
constructor(props) { constructor(props) {
super(props) super(props)
this.styleStore = new ApiStyleStore()
this.settingsStore = new SettingsStore()
this.state = {
accessToken: this.settingsStore.accessToken,
workContext: "layers",
currentStyle: emptyStyle
}
this.styleStore.latestStyle(mapStyle => { this.styleStore = new ApiStyleStore()
this.onStyleUpload(mapStyle) 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.settingsStore = new SettingsStore()
this.styleStore.purge() this.state = {
loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle)) accessToken: this.settingsStore.accessToken,
} workContext: "layers",
currentStyle: style.emptyStyle
}
}
getChildContext() { onReset() {
return { this.styleStore.purge()
rebass: theme, loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle))
reactIconBase: { size: 20 } }
}
}
onStyleDownload() { getChildContext() {
const mapStyle = style.toJSON(this.state.currentStyle) return {
const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"}); rebass: theme,
saveAs(blob, mapStyle.id + ".json"); reactIconBase: { size: 20 }
this.onStyleSave() }
} }
onStyleUpload(newStyle) { onStyleDownload() {
const savedStyle = this.styleStore.save(newStyle) const mapStyle = style.toJSON(this.state.currentStyle)
this.setState({ currentStyle: savedStyle }) const blob = new Blob([JSON.stringify(mapStyle, null, 4)], {type: "application/json;charset=utf-8"});
} saveAs(blob, mapStyle.id + ".json");
this.onStyleSave()
}
onStyleSave() { onStyleUpload(newStyle) {
const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON()) const savedStyle = this.styleStore.save(newStyle)
this.setState({ currentStyle: snapshotStyle }) this.setState({ currentStyle: savedStyle })
}
onStyleSave() {
const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON())
this.setState({ currentStyle: snapshotStyle })
console.log('Save') console.log('Save')
this.styleStore.save(snapshotStyle) this.styleStore.save(snapshotStyle)
} }
onStyleChanged(newStyle) { onStyleChanged(newStyle) {
this.setState({ currentStyle: newStyle }) this.setState({ currentStyle: newStyle })
} }
onOpenSettings() { onOpenSettings() {
this.setState({ workContext: "settings" }) this.setState({ workContext: "settings" })
} }
onOpenAbout() { onOpenAbout() {
this.setState({ workContext: "about" }) this.setState({ workContext: "about" })
} }
onOpenLayers() { onOpenLayers() {
this.setState({ workContext: "layers", }) this.setState({ workContext: "layers", })
} }
onOpenSources() { onOpenSources() {
this.setState({ workContext: "sources", }) this.setState({ workContext: "sources", })
} }
onAccessTokenChanged(newToken) { onAccessTokenChanged(newToken) {
this.settingsStore.accessToken = newToken this.settingsStore.accessToken = newToken
this.setState({ accessToken: newToken }) this.setState({ accessToken: newToken })
} }
render() { render() {
return <div style={{ fontFamily: theme.fontFamily, color: theme.color, fontWeight: 300 }}> return <div style={{ fontFamily: theme.fontFamily, color: theme.color, fontWeight: 300 }}>
<Toolbar <Toolbar
styleAvailable={this.state.currentStyle.get('layers').size > 0} styleAvailable={this.state.currentStyle.get('layers').size > 0}
onStyleSave={this.onStyleSave.bind(this)} onStyleSave={this.onStyleSave.bind(this)}
onStyleUpload={this.onStyleUpload.bind(this)} onStyleUpload={this.onStyleUpload.bind(this)}
onStyleDownload={this.onStyleDownload.bind(this)} onStyleDownload={this.onStyleDownload.bind(this)}
onOpenSettings={this.onOpenSettings.bind(this)} onOpenSettings={this.onOpenSettings.bind(this)}
onOpenAbout={this.onOpenAbout.bind(this)} onOpenAbout={this.onOpenAbout.bind(this)}
onOpenLayers={this.onOpenLayers.bind(this)} onOpenLayers={this.onOpenLayers.bind(this)}
onOpenSources={this.onOpenSources.bind(this)} onOpenSources={this.onOpenSources.bind(this)}
/> />
<WorkspaceDrawer <WorkspaceDrawer
onStyleChanged={this.onStyleChanged.bind(this)} onStyleChanged={this.onStyleChanged.bind(this)}
onReset={this.onReset.bind(this)} onReset={this.onReset.bind(this)}
workContext={this.state.workContext} workContext={this.state.workContext}
mapStyle={this.state.currentStyle} mapStyle={this.state.currentStyle}
accessToken={this.state.accessToken} accessToken={this.state.accessToken}
onAccessTokenChanged={this.onAccessTokenChanged.bind(this)} onAccessTokenChanged={this.onAccessTokenChanged.bind(this)}
/> />
<Map <Map
mapStyle={this.state.currentStyle} mapStyle={this.state.currentStyle}
accessToken={this.state.accessToken} accessToken={this.state.accessToken}
/> />
</div> </div>
} }
} }

View file

@ -9,34 +9,41 @@ import randomColor from 'randomcolor'
// It also ensures that every style has an id and // It also ensures that every style has an id and
// a created date for future reference // a created date for future reference
function fromJSON(jsonStyle) { function fromJSON(jsonStyle) {
if (typeof jsonStyle === 'string' || jsonStyle instanceof String) { if (typeof jsonStyle === 'string' || jsonStyle instanceof String) {
jsonStyle = JSON.parse(jsonStyle) jsonStyle = JSON.parse(jsonStyle)
} }
return Immutable.Map(Object.keys(jsonStyle).map(key => { return Immutable.Map(Object.keys(jsonStyle).map(key => {
const val = jsonStyle[key] const val = jsonStyle[key]
if(key === "layers") { if(key === "layers") {
return [key, Immutable.OrderedMap(val.map(l => [l.id, Immutable.fromJS(l)]))] return [key, Immutable.OrderedMap(val.map(l => [l.id, Immutable.fromJS(l)]))]
} else if(key === "sources" || key === "metadata" || key === "transition") { } else if(key === "sources" || key === "metadata" || key === "transition") {
return [key, Immutable.fromJS(val)] return [key, Immutable.fromJS(val)]
} else { } else {
return [key, val] 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) { function ensureHasId(style) {
if(style.has('id')) return style if(style.has('id')) return style
return style.set('id', Math.random().toString(36).substr(2, 9)) return style.set('id', Math.random().toString(36).substr(2, 9))
} }
function ensureHasTimestamp(style) { function ensureHasTimestamp(style) {
if(style.has('id')) return style if(style.has('id')) return style
return style.set('created', new Date().toJSON()) return style.set('created', new Date().toJSON())
} }
function ensureMetadataExists(style) { function ensureMetadataExists(style) {
return ensureHasId(ensureHasTimestamp(style)) return ensureHasId(ensureHasTimestamp(style))
} }
// Compare style with other style and return changes // 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 // Should be able to improve performance since we can only compare
// by reference // by reference
function diffStyles(before, after) { 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 // Turns immutable style back into JSON with the original order of the
// layers preserved // layers preserved
function toJSON(mapStyle) { function toJSON(mapStyle) {
const jsonStyle = {} const jsonStyle = {}
for(let [key, value] of mapStyle.entries()) { for(let [key, value] of mapStyle.entries()) {
if(key === "layers") { if(key === "layers") {
jsonStyle[key] = value.toIndexedSeq().toJS() jsonStyle[key] = value.toIndexedSeq().toJS()
} else if(key === 'sources' || key === "metadata" || key === "transition") { } else if(key === 'sources' || key === "metadata" || key === "transition") {
jsonStyle[key] = value.toJS() jsonStyle[key] = value.toJS()
} else { } else {
jsonStyle[key] = value jsonStyle[key] = value
} }
} }
return jsonStyle return jsonStyle
} }
export function colorizeLayers(layers) { export function colorizeLayers(layers) {
return layers.map(layer => { return layers.map(layer => {
if(!layer.metadata) { if(!layer.metadata) {
layer.metadata = {} layer.metadata = {}
} }
if(!"maputnik:color" in layer.metadata) { if(!"maputnik:color" in layer.metadata) {
layer.metadata["maputnik:color"] = randomColor() layer.metadata["maputnik:color"] = randomColor()
} }
return layer return layer
}) })
} }
export default { export default {
colorizeLayers, colorizeLayers,
toJSON, toJSON,
fromJSON, fromJSON,
diffStyles, diffStyles,
ensureMetadataExists, ensureMetadataExists,
emptyStyle,
} }

View file

@ -5,117 +5,114 @@ import style from './style.js'
const storagePrefix = "maputnik" const storagePrefix = "maputnik"
const stylePrefix = 'style' const stylePrefix = 'style'
const storageKeys = { const storageKeys = {
latest: [storagePrefix, 'latest_style'].join(':'), latest: [storagePrefix, 'latest_style'].join(':'),
accessToken: [storagePrefix, 'access_token'].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" 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 // Fetch a default style via URL and return it or a fallback style via callback
export function loadDefaultStyle(cb) { export function loadDefaultStyle(cb) {
console.log('Load default style') console.log('Load default style')
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(style.ensureMetadataExists(style.fromJSON(request.responseText))) cb(style.ensureMetadataExists(style.fromJSON(request.responseText)))
} else { } else {
cb(emptyStyle) cb(style.emptyStyle)
} }
} }
request.onerror = function() { request.onerror = function() {
console.log('Could not fetch default style') console.log('Could not fetch default style')
cb(emptyStyle) cb(style.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
function loadStoredStyles() { function loadStoredStyles() {
const styles = [] const styles = []
for (let i = 0; i < window.localStorage.length; i++) { for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i) const key = window.localStorage.key(i)
if(isStyleKey(key)) { if(isStyleKey(key)) {
styles.push(fromKey(key)) styles.push(fromKey(key))
} }
} }
return styles return styles
} }
function isStyleKey(key) { function isStyleKey(key) {
const parts = key.split(":") const parts = key.split(":")
return parts.length == 3 && parts[0] === storagePrefix && parts[1] === stylePrefix return parts.length == 3 && parts[0] === storagePrefix && parts[1] === stylePrefix
} }
// Load style id from key // Load style id from key
function fromKey(key) { function fromKey(key) {
if(!isStyleKey(key)) { if(!isStyleKey(key)) {
throw "Key is not a valid style key" throw "Key is not a valid style key"
} }
const parts = key.split(":") const parts = key.split(":")
const styleId = parts[2] const styleId = parts[2]
return styleId return styleId
} }
// Calculate key that identifies the style with a version // Calculate key that identifies the style with a version
function styleKey(styleId) { function styleKey(styleId) {
return [storagePrefix, stylePrefix, styleId].join(":") return [storagePrefix, stylePrefix, styleId].join(":")
} }
// Store style independent settings // Store style independent settings
export class SettingsStore { export class SettingsStore {
get accessToken() { get accessToken() {
const token = window.localStorage.getItem(storageKeys.accessToken) const token = window.localStorage.getItem(storageKeys.accessToken)
return token ? token : "" return token ? token : ""
} }
set accessToken(val) { set accessToken(val) {
window.localStorage.setItem(storageKeys.accessToken, val) window.localStorage.setItem(storageKeys.accessToken, val)
} }
} }
// Manages many possible styles that are stored in the local storage // Manages many possible styles that are stored in the local storage
export class StyleStore { export class StyleStore {
// Tile store will load all items from local storage and // Tile store will load all items from local storage and
// assume they do not change will working on it // assume they do not change will working on it
constructor() { constructor() {
this.mapStyles = loadStoredStyles() this.mapStyles = loadStoredStyles()
} }
// Delete entire style history supported(cb) {
purge() { cb(window.localStorage !== undefined)
for (let i = 0; i < window.localStorage.length; i++) { }
const key = window.localStorage.key(i)
if(key.startsWith(storagePrefix)) {
window.localStorage.removeItem(key)
}
}
}
// Find the last edited style // Delete entire style history
latestStyle() { purge() {
if(this.mapStyles.length === 0) return emptyStyle for (let i = 0; i < window.localStorage.length; i++) {
const styleId = window.localStorage.getItem(storageKeys.latest) const key = window.localStorage.key(i)
const styleItem = window.localStorage.getItem(styleKey(styleId)) if(key.startsWith(storagePrefix)) {
window.localStorage.removeItem(key)
}
}
}
if(styleItem) return style.fromJSON(styleItem) // Find the last edited style
return emptyStyle 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 if(styleItem) return cb(style.fromJSON(styleItem))
save(mapStyle) { cb(style.emptyStyle)
const key = styleKey(mapStyle.get('id')) }
window.localStorage.setItem(key, JSON.stringify(style.toJSON(mapStyle)))
window.localStorage.setItem(storageKeys.latest, mapStyle.get('id')) // Save current style replacing previous version
return mapStyle 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
}
} }