From 2db80db95a74af454e6f2b8548c56dfae6c93ea6 Mon Sep 17 00:00:00 2001 From: Lukas Martinelli Date: Sat, 3 Dec 2016 17:03:39 +0100 Subject: [PATCH 1/6] Spaces not tabs --- .editorconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 2b6d1dc..99557a1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,11 +5,10 @@ root = true end_of_line = lf insert_final_newline = true - # Matches multiple files with brace expansion notation # Set default charset [*.{js,jsx,html,sass}] charset = utf-8 -indent_style = tab +indent_style = space indent_size = 2 trim_trailing_whitespace = true From b5cfecec8101686f99019f449196f04cc33d2eca Mon Sep 17 00:00:00 2001 From: Lukas Martinelli Date: Sat, 3 Dec 2016 23:28:43 +0100 Subject: [PATCH 2/6] Load and save style from style api --- package.json | 3 ++- src/apistore.js | 42 ++++++++++++++++++++++++++++++++++++++++++ src/app.jsx | 14 +++++++++++--- src/map.jsx | 5 +++-- webpack.config.js | 4 +++- 5 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 src/apistore.js diff --git a/package.json b/package.json index e2d6f73..b18398f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "react-height": "^2.1.1", "react-icons": "^2.2.1", "react-motion": "^0.4.4", - "rebass": "^0.3.1" + "rebass": "^0.3.1", + "request": "^2.79.0" }, "babel": { "presets": [ diff --git a/src/apistore.js b/src/apistore.js new file mode 100644 index 0000000..21502a2 --- /dev/null +++ b/src/apistore.js @@ -0,0 +1,42 @@ +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 { + latestStyle(cb) { + if(this.latestStyleId) { + request('http://localhost:8000/styles/' + this.latestStyleId, (error, response, body) => { + cb(JSON.parse(body)) + }) + } else { + request('http://localhost:8000/styles', (error, response, body) => { + if (!error && response.statusCode == 200) { + const styleIds = JSON.parse(body); + this.latestStyleId = styleIds[0]; + request('http://localhost:8000/styles/' + this.latestStyleId, (error, response, body) => { + cb(style.fromJSON(JSON.parse(body))) + }) + } + }) + } + } + + // Save current style replacing previous version + save(mapStyle) { + const id = mapStyle.get('id') + request.put({ + url: 'http://localhost:8000/styles/' + id, + json: true, + body: style.toJSON(mapStyle) + }, (error, response, body) => { + console.log('Saved style'); + }) + return mapStyle + } +} diff --git a/src/app.jsx b/src/app.jsx index 85a9d09..d77235a 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -9,7 +9,8 @@ import Fixed from 'rebass/dist/Fixed' import { Map } from './map.jsx' import {Toolbar} from './toolbar.jsx' import style from './style.js' -import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.js' +import { loadDefaultStyle, SettingsStore } from './stylestore.js' +import { emptyStyle, ApiStyleStore } from './apistore.js' import { WorkspaceDrawer } from './workspace.jsx' import theme from './theme.js' @@ -23,16 +24,22 @@ export default class App extends React.Component { constructor(props) { super(props) - this.styleStore = new StyleStore() + this.styleStore = new ApiStyleStore() this.settingsStore = new SettingsStore() this.state = { accessToken: this.settingsStore.accessToken, workContext: "layers", - currentStyle: this.styleStore.latestStyle(), + currentStyle: emptyStyle } + + this.styleStore.latestStyle(mapStyle => { + this.onStyleUpload(mapStyle) + }) + /* if(this.state.currentStyle.get('layers').size === 0) { loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle)) } + */ } onReset() { @@ -62,6 +69,7 @@ export default class App extends React.Component { onStyleSave() { const snapshotStyle = this.state.currentStyle.set('modified', new Date().toJSON()) this.setState({ currentStyle: snapshotStyle }) + console.log('Save') this.styleStore.save(snapshotStyle) } diff --git a/src/map.jsx b/src/map.jsx index df3969e..e6634c0 100644 --- a/src/map.jsx +++ b/src/map.jsx @@ -12,14 +12,15 @@ export class Map extends React.Component { } componentWillReceiveProps(nextProps) { - const tokenChanged = nextProps.accessToken !== MapboxGl.accessToken + const hasTokenChanged = nextProps.accessToken !== MapboxGl.accessToken + MapboxGl.accessToken = nextProps.accessToken // 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) { + if(mapIdChanged || hasTokenChanged) { this.state.map.setStyle(style.toJSON(nextProps.mapStyle)) return } diff --git a/webpack.config.js b/webpack.config.js index 1159966..97d14a4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -62,7 +62,9 @@ module.exports = { }] }, node: { - fs: "empty" + fs: "empty", + net: 'empty', + tls: 'empty' }, devServer: { contentBase: "./public", From 02a7ccf831238c0cbd4290ece0afa3aee72d8188 Mon Sep 17 00:00:00 2001 From: Lukas Martinelli Date: Sun, 4 Dec 2016 17:03:36 +0100 Subject: [PATCH 3/6] Modify layout --- package.json | 2 +- src/layers/list.jsx | 85 ++++++++--------- src/scrollbars.scss | 14 +-- src/theme.js | 166 ++++++++++++++++---------------- src/toolbar.jsx | 224 ++++++++++++++++++++++++-------------------- src/workspace.jsx | 107 ++++++++++----------- 6 files changed, 305 insertions(+), 293 deletions(-) diff --git a/package.json b/package.json index b18398f..3cee983 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "webpack --config webpack.production.config.js --progress --profile --colors", "test": "karma start --single-run", "test-watch": "karma start", - "start": "webpack-dev-server --progress --profile --colors", + "start": "webpack-dev-server --progress --profile --colors --watch-poll", "lint": "eslint --ext js --ext jsx {src,test}" }, "repository": { diff --git a/src/layers/list.jsx b/src/layers/list.jsx index 1adb458..ccf152d 100644 --- a/src/layers/list.jsx +++ b/src/layers/list.jsx @@ -12,55 +12,48 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; // List of collapsible layer editors export class LayerList extends React.Component { - static propTypes = { - layers: React.PropTypes.instanceOf(Immutable.OrderedMap), - onLayersChanged: React.PropTypes.func.isRequired - } + static propTypes = { + layers: React.PropTypes.instanceOf(Immutable.OrderedMap), + onLayersChanged: React.PropTypes.func.isRequired + } - constructor(props) { - super(props) - this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); - } + constructor(props) { + super(props) + this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this); + } - onLayerDestroyed(deletedLayer) { - const remainingLayers = this.props.layers.delete(deletedLayer.get('id')) - this.props.onLayersChanged(remainingLayers) - } + onLayerDestroyed(deletedLayer) { + const remainingLayers = this.props.layers.delete(deletedLayer.get('id')) + this.props.onLayersChanged(remainingLayers) + } - onLayerChanged(layer) { - const changedLayers = this.props.layers.set(layer.get('id'), layer) - this.props.onLayersChanged(changedLayers) - } + onLayerChanged(layer) { + const changedLayers = this.props.layers.set(layer.get('id'), layer) + this.props.onLayersChanged(changedLayers) + } - render() { - var layerPanels = [] - layerPanels = this.props.layers.map(layer => { - return - }).toIndexedSeq() + render() { + var layerPanels = [] + layerPanels = this.props.layers.map(layer => { + return + }).toIndexedSeq() - return
- - - Layers - - - - -
- {layerPanels} -
-
- } + return
+
+ {layerPanels} +
+
+ } } diff --git a/src/scrollbars.scss b/src/scrollbars.scss index 5c4ab23..e7d44eb 100644 --- a/src/scrollbars.scss +++ b/src/scrollbars.scss @@ -1,12 +1,12 @@ .darkScrollbar::-webkit-scrollbar { - background-color: #313131; - width: 10px; + background-color: #26282e; + width: 10px; } .darkScrollbar::-webkit-scrollbar-thumb { - border-radius: 6px; - -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); - background-color: #555; - padding-left: 2px; - padding-right: 2px; + border-radius: 6px; + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); + background-color: #40444e; + padding-left: 2px; + padding-right: 2px; } diff --git a/src/theme.js b/src/theme.js index cc8f5fa..7f5083f 100644 --- a/src/theme.js +++ b/src/theme.js @@ -1,112 +1,112 @@ const caps = { - textTransform: 'uppercase', - letterSpacing: '.2em' + textTransform: 'uppercase', + letterSpacing: '.2em' } export const fullHeight = { - position: "fixed", - top: 0, - bottom: 0, - height: "100%", + position: "fixed", + top: 0, + bottom: 0, + height: "100%", } const baseColors = { - black: '#242424', - gray: '#313131', - midgray: '#778', - lowgray: '#dcdcdc', - white: '#fff', - blue: '#00d9f7', - green: '#B4C7AD', - orange: '#fb3', - red: '#f04', + black: '#1c1f24', + gray: '#26282e', + midgray: '#36383e', + lowgray: '#8e8e8e', + white: '#fff', + blue: '#00d9f7', + green: '#B4C7AD', + orange: '#fb3', + red: '#f04', } const themeColors = { - primary: baseColors.gray, - secondary: baseColors.midgray, - default: baseColors.gray, - info: baseColors.blue, - success: baseColors.green, - warning: baseColors.orange, - error: baseColors.red + primary: baseColors.gray, + secondary: baseColors.midgray, + default: baseColors.gray, + info: baseColors.blue, + success: baseColors.green, + warning: baseColors.orange, + error: baseColors.red } export const colors = { - ...baseColors, - ...themeColors + ...baseColors, + ...themeColors } export const inputBase = { - display: 'block', - border: '1px solid rgb(36, 36, 36)', - height: 30, - width: '100%', - paddingLeft: 5, - paddingRight: 5, - backgroundColor: colors.gray, + display: 'block', + border: '1px solid rgb(36, 36, 36)', + height: 30, + width: '100%', + paddingLeft: 5, + paddingRight: 5, + backgroundColor: colors.gray, } const scale = [3, 5, 10, 30, 40] const fontSizes = [28, 24, 20, 16, 14, 12, 10] const border = { - borderColor: colors.black, - borderRadius: 0, + borderColor: colors.black, + borderRadius: 0, } const dark = { - name: 'Dark', - color: colors.white, - fontFamily: 'Roboto, sans-serif', - scale, - fontSizes, - colors, - inverted: colors.midGray, - ...border, + name: 'Dark', + color: colors.white, + fontFamily: 'Roboto, sans-serif', + scale, + fontSizes, + colors, + inverted: colors.midGray, + ...border, - Block: { - backgroundColor: colors.gray, - ...border, - borderLeft: 0, - borderRight: 0, - marginBottom: 0, - paddingBottom: 0, - }, - PanelHeader: { - marginRight: -10, - marginBottom: 0, - fontSize: fontSizes[5], - fontWeight: 400, - color: colors.white, - }, - Button: { - color: '#00d9f7', - }, - Menu: { - color: '#00d9f7', - backgroundColor: '#000' - }, - Message: { - color: '#111', - opacity: 15/16 - }, - Header: { - fontWeight: 400, - }, - ButtonCircle : { - }, - Toolbar: { - fontWeight: 400, - minHeight: scale[3] - }, - Label: { - fontWeight: 300, - }, - Input: { - fontWeight: 300, - fontSize: fontSizes[5], - }, + Block: { + backgroundColor: colors.gray, + ...border, + borderLeft: 0, + borderRight: 0, + marginBottom: 0, + paddingBottom: 0, + }, + PanelHeader: { + marginRight: -10, + marginBottom: 0, + fontSize: fontSizes[5], + fontWeight: 400, + color: colors.white, + }, + Button: { + color: '#00d9f7', + }, + Menu: { + color: '#00d9f7', + backgroundColor: '#000' + }, + Message: { + color: '#111', + opacity: 15/16 + }, + Header: { + fontWeight: 400, + }, + ButtonCircle : { + }, + Toolbar: { + fontWeight: 400, + minHeight: scale[3] + }, + Label: { + fontWeight: 300, + }, + Input: { + fontWeight: 300, + fontSize: fontSizes[5], + }, } export default dark diff --git a/src/toolbar.jsx b/src/toolbar.jsx index 918fbd8..89b543b 100644 --- a/src/toolbar.jsx +++ b/src/toolbar.jsx @@ -12,122 +12,140 @@ import Fixed from 'rebass/dist/Fixed' import MdFileDownload from 'react-icons/lib/md/file-download' import MdFileUpload from 'react-icons/lib/md/file-upload' +import MdOpenInBrowser from 'react-icons/lib/md/open-in-browser' import MdSettings from 'react-icons/lib/md/settings' import MdInfo from 'react-icons/lib/md/info' import MdLayers from 'react-icons/lib/md/layers' import MdSave from 'react-icons/lib/md/save' +import MdStyle from 'react-icons/lib/md/style' import MdMap from 'react-icons/lib/md/map' +import MdInsertEmoticon from 'react-icons/lib/md/insert-emoticon' +import MdFontDownload from 'react-icons/lib/md/font-download' +import MdHelpOutline from 'react-icons/lib/md/help-outline' +import MdFindInPage from 'react-icons/lib/md/find-in-page' import style from './style.js' import { fullHeight } from './theme.js' import theme from './theme.js'; +const InlineBlock = props =>
+ {props.children} +
+ export class Toolbar extends React.Component { - static propTypes = { - // A new style has been uploaded - onStyleUpload: React.PropTypes.func.isRequired, - // Current style is requested for download - onStyleDownload: React.PropTypes.func.isRequired, - // Style is explicitely saved to local cache - onStyleSave: React.PropTypes.func, - // Open settings drawer - onOpenSettings: React.PropTypes.func, - // Open about page - onOpenAbout: React.PropTypes.func, - // Open sources drawer - onOpenSources: React.PropTypes.func, - // Open layers drawer - onOpenLayers: React.PropTypes.func, - // Whether a style is available for download or saving - // A style with no layers should not be available - styleAvailable: React.PropTypes.bool, - } + static propTypes = { + // A new style has been uploaded + onStyleUpload: React.PropTypes.func.isRequired, + // Current style is requested for download + onStyleDownload: React.PropTypes.func.isRequired, + // Style is explicitely saved to local cache + onStyleSave: React.PropTypes.func, + // Open settings drawer + onOpenSettings: React.PropTypes.func, + // Open about page + onOpenAbout: React.PropTypes.func, + // Open sources drawer + onOpenSources: React.PropTypes.func, + // Open layers drawer + onOpenLayers: React.PropTypes.func, + // Whether a style is available for download or saving + // A style with no layers should not be available + styleAvailable: React.PropTypes.bool, + } - onUpload(_, files) { - const [e, file] = files[0]; - const reader = new FileReader(); - reader.readAsText(file, "UTF-8"); - reader.onload = e => { - let mapStyle = style.fromJSON(JSON.parse(e.target.result)) - mapStyle = style.ensureMetadataExists(mapStyle) - this.props.onStyleUpload(mapStyle); - } - reader.onerror = e => console.log(e.target); - } + onUpload(_, files) { + const [e, file] = files[0]; + const reader = new FileReader(); + reader.readAsText(file, "UTF-8"); + reader.onload = e => { + let mapStyle = style.fromJSON(JSON.parse(e.target.result)) + mapStyle = style.ensureMetadataExists(mapStyle) + this.props.onStyleUpload(mapStyle); + } + reader.onerror = e => console.log(e.target); + } - saveButton() { - if(this.props.styleAvailable) { - return - - - } - return null - } + saveButton() { + if(this.props.styleAvailable) { + return + + + } + return null + } - downloadButton() { - if(this.props.styleAvailable) { - return - - - } - return null - } + downloadButton() { + if(this.props.styleAvailable) { + return + + + } + return null + } - render() { - return - - - - - - {this.downloadButton()} - {this.saveButton()} - - - - - - - - - - - - - - } + render() { + + return
+ + + + + + + + + {this.downloadButton()} + {this.saveButton()} + + + + + + + + + + + + + + + +
+ } } diff --git a/src/workspace.jsx b/src/workspace.jsx index 69f1ca7..d222414 100644 --- a/src/workspace.jsx +++ b/src/workspace.jsx @@ -8,65 +8,66 @@ import { colors, fullHeight } from './theme.js' /** The workspace drawer contains the editor components depending on the edit * context chosen in the toolbar. It holds the state of the layers.*/ export class WorkspaceDrawer extends React.Component { - static propTypes = { - mapStyle: React.PropTypes.object.isRequired, - onStyleChanged: React.PropTypes.func.isRequired, - workContext: React.PropTypes.oneOf(['layers', 'settings', 'sources']).isRequired, - accessToken: React.PropTypes.string, - onAccessTokenChanged: React.PropTypes.func, - onReset: React.PropTypes.func, - } + static propTypes = { + mapStyle: React.PropTypes.object.isRequired, + onStyleChanged: React.PropTypes.func.isRequired, + workContext: React.PropTypes.oneOf(['layers', 'settings', 'sources']).isRequired, + accessToken: React.PropTypes.string, + onAccessTokenChanged: React.PropTypes.func, + onReset: React.PropTypes.func, + } - onLayersChanged(changedLayers) { - const changedStyle = this.props.mapStyle.set('layers', changedLayers) - this.props.onStyleChanged(changedStyle) - } + onLayersChanged(changedLayers) { + const changedStyle = this.props.mapStyle.set('layers', changedLayers) + this.props.onStyleChanged(changedStyle) + } - onSourcesChanged(changedSources) { - const changedStyle = this.props.mapStyle.set('sources', changedSources) - this.props.onStyleChanged(changedStyle) - } + onSourcesChanged(changedSources) { + const changedStyle = this.props.mapStyle.set('sources', changedSources) + this.props.onStyleChanged(changedStyle) + } - render() { - let workspaceContent = null + render() { + let workspaceContent = null - if(this.props.workContext === "sources") { - workspaceContent = - } + if(this.props.workContext === "sources") { + workspaceContent = + } - if(this.props.workContext === "layers") { - workspaceContent = - } + if(this.props.workContext === "layers") { + workspaceContent = + } - if(this.props.workContext === "settings") { - workspaceContent = - } + if(this.props.workContext === "settings") { + workspaceContent = + } - if(this.props.workContext === "about") { - workspaceContent = - } + if(this.props.workContext === "about") { + workspaceContent = + } - return
- {workspaceContent} -
- } + return
+ {workspaceContent} +
+ } } From f7fabd1bc85f3adcfdf8ac95b1d1c435d166e292 Mon Sep 17 00:00:00 2001 From: lukasmartinelli Date: Fri, 25 Nov 2016 13:40:56 +0100 Subject: [PATCH 4/6] Ensure public directory exists --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index b48a6cf..510eea6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ before_install: install: - npm install script: + - mkdir public - npm run build - npm run lint - npm run test From e5c6af3fadf4a0da9843889dc34454287c3d8c62 Mon Sep 17 00:00:00 2001 From: Lukas Martinelli Date: Fri, 16 Dec 2016 14:49:25 +0100 Subject: [PATCH 5/6] 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 + } } From d599b7fd5bced7ec20c6f60b7a321c16207ddb2d Mon Sep 17 00:00:00 2001 From: Lukas Martinelli Date: Fri, 16 Dec 2016 14:52:44 +0100 Subject: [PATCH 6/6] Always keep layers drawer open --- src/app.jsx | 14 ++++++-------- src/toolbar.jsx | 2 -- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/app.jsx b/src/app.jsx index 9192e4f..fe63388 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -78,19 +78,18 @@ export default class App extends React.Component { } onOpenSettings() { - this.setState({ workContext: "settings" }) + //TODO: open settings modal + //this.setState({ workContext: "settings" }) } onOpenAbout() { - this.setState({ workContext: "about" }) - } - - onOpenLayers() { - this.setState({ workContext: "layers", }) + //TODO: open about modal + //this.setState({ workContext: "about" }) } onOpenSources() { - this.setState({ workContext: "sources", }) + //TODO: open sources modal + //this.setState({ workContext: "sources", }) } onAccessTokenChanged(newToken) { @@ -107,7 +106,6 @@ export default class App extends React.Component { 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)} />