Merge branch 'feature/layout'

This commit is contained in:
Lukas Martinelli 2016-12-16 14:53:52 +01:00
commit 20788a0234
14 changed files with 573 additions and 507 deletions

View file

@ -5,11 +5,10 @@ root = true
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
# Matches multiple files with brace expansion notation # Matches multiple files with brace expansion notation
# Set default charset # Set default charset
[*.{js,jsx,html,sass}] [*.{js,jsx,html,sass}]
charset = utf-8 charset = utf-8
indent_style = tab indent_style = space
indent_size = 2 indent_size = 2
trim_trailing_whitespace = true trim_trailing_whitespace = true

View file

@ -15,6 +15,7 @@ before_install:
install: install:
- npm install - npm install
script: script:
- mkdir public
- npm run build - npm run build
- npm run lint - npm run lint
- npm run test - npm run test

View file

@ -8,7 +8,7 @@
"build": "webpack --config webpack.production.config.js --progress --profile --colors", "build": "webpack --config webpack.production.config.js --progress --profile --colors",
"test": "karma start --single-run", "test": "karma start --single-run",
"test-watch": "karma start", "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}" "lint": "eslint --ext js --ext jsx {src,test}"
}, },
"repository": { "repository": {
@ -32,7 +32,8 @@
"react-height": "^2.1.1", "react-height": "^2.1.1",
"react-icons": "^2.2.1", "react-icons": "^2.2.1",
"react-motion": "^0.4.4", "react-motion": "^0.4.4",
"rebass": "^0.3.1" "rebass": "^0.3.1",
"request": "^2.79.0"
}, },
"babel": { "babel": {
"presets": [ "presets": [

41
src/apistore.js Normal file
View file

@ -0,0 +1,41 @@
import request from 'request'
import style from './style.js'
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) => {
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
}
}

View file

@ -10,6 +10,7 @@ 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, StyleStore } from './stylestore.js' import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.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'
@ -23,15 +24,21 @@ export default class App extends React.Component {
constructor(props) { constructor(props) {
super(props) super(props)
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 = new StyleStore()
}
this.styleStore.latestStyle(mapStyle => this.onStyleUpload(mapStyle))
})
this.settingsStore = new SettingsStore() this.settingsStore = new SettingsStore()
this.state = { this.state = {
accessToken: this.settingsStore.accessToken, accessToken: this.settingsStore.accessToken,
workContext: "layers", workContext: "layers",
currentStyle: this.styleStore.latestStyle(), currentStyle: style.emptyStyle
}
if(this.state.currentStyle.get('layers').size === 0) {
loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle))
} }
} }
@ -62,6 +69,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())
this.setState({ currentStyle: snapshotStyle }) this.setState({ currentStyle: snapshotStyle })
console.log('Save')
this.styleStore.save(snapshotStyle) this.styleStore.save(snapshotStyle)
} }
@ -70,19 +78,18 @@ export default class App extends React.Component {
} }
onOpenSettings() { onOpenSettings() {
this.setState({ workContext: "settings" }) //TODO: open settings modal
//this.setState({ workContext: "settings" })
} }
onOpenAbout() { onOpenAbout() {
this.setState({ workContext: "about" }) //TODO: open about modal
} //this.setState({ workContext: "about" })
onOpenLayers() {
this.setState({ workContext: "layers", })
} }
onOpenSources() { onOpenSources() {
this.setState({ workContext: "sources", }) //TODO: open sources modal
//this.setState({ workContext: "sources", })
} }
onAccessTokenChanged(newToken) { onAccessTokenChanged(newToken) {
@ -99,7 +106,6 @@ export default class App extends React.Component {
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)}
onOpenSources={this.onOpenSources.bind(this)} onOpenSources={this.onOpenSources.bind(this)}
/> />
<WorkspaceDrawer <WorkspaceDrawer

View file

@ -44,19 +44,12 @@ export class LayerList extends React.Component {
}).toIndexedSeq() }).toIndexedSeq()
return <div> return <div>
<Toolbar style={{marginRight: 20}}>
<NavItem>
<Heading>Layers</Heading>
</NavItem>
<Space auto x={1} />
</Toolbar>
<div className={scrollbars.darkScrollbar} style={{ <div className={scrollbars.darkScrollbar} style={{
overflowY: "scroll", overflowY: "scroll",
bottom:0, bottom:0,
left:0, left:0,
right:0, right:0,
top:40, top:1,
position: "absolute", position: "absolute",
}}> }}>
{layerPanels} {layerPanels}

View file

@ -12,14 +12,15 @@ export class Map extends React.Component {
} }
componentWillReceiveProps(nextProps) { 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 // 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? // 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 || hasTokenChanged) {
this.state.map.setStyle(style.toJSON(nextProps.mapStyle)) this.state.map.setStyle(style.toJSON(nextProps.mapStyle))
return return
} }

View file

@ -1,12 +1,12 @@
.darkScrollbar::-webkit-scrollbar { .darkScrollbar::-webkit-scrollbar {
background-color: #313131; background-color: #26282e;
width: 10px; width: 10px;
} }
.darkScrollbar::-webkit-scrollbar-thumb { .darkScrollbar::-webkit-scrollbar-thumb {
border-radius: 6px; border-radius: 6px;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3); -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
background-color: #555; background-color: #40444e;
padding-left: 2px; padding-left: 2px;
padding-right: 2px; padding-right: 2px;
} }

View file

@ -25,6 +25,13 @@ function fromJSON(jsonStyle) {
})) }))
} }
// 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))
@ -81,4 +88,5 @@ export default {
fromJSON, fromJSON,
diffStyles, diffStyles,
ensureMetadataExists, ensureMetadataExists,
emptyStyle,
} }

View file

@ -9,13 +9,6 @@ const storageKeys = {
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) {
@ -27,13 +20,13 @@ export function loadDefaultStyle(cb) {
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()
@ -91,6 +84,10 @@ export class StyleStore {
this.mapStyles = loadStoredStyles() this.mapStyles = loadStoredStyles()
} }
supported(cb) {
cb(window.localStorage !== undefined)
}
// Delete entire style history // Delete entire style history
purge() { purge() {
for (let i = 0; i < window.localStorage.length; i++) { for (let i = 0; i < window.localStorage.length; i++) {
@ -102,13 +99,13 @@ export class StyleStore {
} }
// Find the last edited style // Find the last edited style
latestStyle() { latestStyle(cb) {
if(this.mapStyles.length === 0) return emptyStyle if(this.mapStyles.length === 0) return cb(style.emptyStyle)
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 style.fromJSON(styleItem) if(styleItem) return cb(style.fromJSON(styleItem))
return emptyStyle cb(style.emptyStyle)
} }
// Save current style replacing previous version // Save current style replacing previous version

View file

@ -11,10 +11,10 @@ export const fullHeight = {
} }
const baseColors = { const baseColors = {
black: '#242424', black: '#1c1f24',
gray: '#313131', gray: '#26282e',
midgray: '#778', midgray: '#36383e',
lowgray: '#dcdcdc', lowgray: '#8e8e8e',
white: '#fff', white: '#fff',
blue: '#00d9f7', blue: '#00d9f7',
green: '#B4C7AD', green: '#B4C7AD',

View file

@ -12,16 +12,26 @@ import Fixed from 'rebass/dist/Fixed'
import MdFileDownload from 'react-icons/lib/md/file-download' import MdFileDownload from 'react-icons/lib/md/file-download'
import MdFileUpload from 'react-icons/lib/md/file-upload' 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 MdSettings from 'react-icons/lib/md/settings'
import MdInfo from 'react-icons/lib/md/info' import MdInfo from 'react-icons/lib/md/info'
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 MdStyle from 'react-icons/lib/md/style'
import MdMap from 'react-icons/lib/md/map' 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 style from './style.js'
import { fullHeight } from './theme.js' import { fullHeight } from './theme.js'
import theme from './theme.js'; import theme from './theme.js';
const InlineBlock = props => <div style={{display: "inline-block", ...props.style}}>
{props.children}
</div>
export class Toolbar extends React.Component { export class Toolbar extends React.Component {
static propTypes = { static propTypes = {
// A new style has been uploaded // A new style has been uploaded
@ -36,8 +46,6 @@ export class Toolbar extends React.Component {
onOpenAbout: React.PropTypes.func, onOpenAbout: React.PropTypes.func,
// Open sources drawer // Open sources drawer
onOpenSources: React.PropTypes.func, onOpenSources: React.PropTypes.func,
// Open layers drawer
onOpenLayers: React.PropTypes.func,
// Whether a style is available for download or saving // Whether a style is available for download or saving
// A style with no layers should not be available // A style with no layers should not be available
styleAvailable: React.PropTypes.bool, styleAvailable: React.PropTypes.bool,
@ -57,77 +65,85 @@ export class Toolbar extends React.Component {
saveButton() { saveButton() {
if(this.props.styleAvailable) { if(this.props.styleAvailable) {
return <Block> return <InlineBlock>
<Button onClick={this.props.onStyleSave} big={true}> <Button onClick={this.props.onStyleSave} big={true}>
<Tooltip inverted rounded title="Save style">
<MdSave /> <MdSave />
</Tooltip> Save
</Button> </Button>
</Block> </InlineBlock>
} }
return null return null
} }
downloadButton() { downloadButton() {
if(this.props.styleAvailable) { if(this.props.styleAvailable) {
return <Block> return <InlineBlock>
<Button onClick={this.props.onStyleDownload} big={true}> <Button onClick={this.props.onStyleDownload} big={true}>
<Tooltip inverted rounded title="Download style">
<MdFileDownload /> <MdFileDownload />
</Tooltip> Download
</Button> </Button>
</Block> </InlineBlock>
} }
return null return null
} }
render() { render() {
return <Container style={{
...fullHeight, return <div style={{
position: "fixed",
height: 50,
width: '100%',
zIndex: 100, zIndex: 100,
left: 0, left: 0,
top: 0, top: 0,
backgroundColor: theme.colors.black } backgroundColor: theme.colors.black
}> }}>
<Block> <InlineBlock>
<Button style={{width: 300, textAlign: 'left'}}>
<img src="https://github.com/maputnik/editor/raw/master/media/maputnik.png" alt="Maputnik" style={{width: 40, height: 40, paddingRight: 5, verticalAlign: 'middle'}}/>
<span style={{fontSize: 20 }}>Maputnik</span>
</Button>
</InlineBlock>
<InlineBlock>
<FileReaderInput onChange={this.onUpload.bind(this)}> <FileReaderInput onChange={this.onUpload.bind(this)}>
<Button big={true} theme={this.props.styleAvailable ? "default" : "success"}> <Button big={true} theme={this.props.styleAvailable ? "default" : "success"}>
<Tooltip inverted rounded title="Upload style"> <MdOpenInBrowser />
<MdFileUpload /> Open
</Tooltip>
</Button> </Button>
</FileReaderInput> </FileReaderInput>
</Block> </InlineBlock>
{this.downloadButton()} {this.downloadButton()}
{this.saveButton()} {this.saveButton()}
<Block> <InlineBlock>
<Button big={true} onClick={this.props.onOpenLayers}>
<Tooltip inverted rounded title="Layers">
<MdLayers />
</Tooltip>
</Button>
</Block>
<Block>
<Button big={true} onClick={this.props.onOpenSources}>
<Tooltip inverted rounded title="Sources">
<MdMap />
</Tooltip>
</Button>
</Block>
<Block>
<Button big={true} onClick={this.props.onOpenSettings}> <Button big={true} onClick={this.props.onOpenSettings}>
<Tooltip inverted rounded title="Settings"> <MdLayers />
<MdSettings /> Tilesets
</Tooltip>
</Button> </Button>
</Block> </InlineBlock>
<Block> <InlineBlock>
<Button big={true} onClick={this.props.onOpenSettings}>
<MdFontDownload />
Fonts
</Button>
</InlineBlock>
<InlineBlock>
<Button big={true} onClick={this.props.onOpenSettings}>
<MdInsertEmoticon/>
Icons
</Button>
</InlineBlock>
<InlineBlock>
<Button big={true} onClick={this.props.onOpenSettings}>
<MdFindInPage />
Inspect
</Button>
</InlineBlock>
<InlineBlock>
<Button big={true} onClick={this.props.onOpenAbout}> <Button big={true} onClick={this.props.onOpenAbout}>
<Tooltip inverted rounded title="About"> <MdHelpOutline />
<MdInfo /> Help
</Tooltip>
</Button> </Button>
</Block> </InlineBlock>
</Container> </div>
} }
} }

View file

@ -60,8 +60,9 @@ export class WorkspaceDrawer extends React.Component {
return <div style={{ return <div style={{
...fullHeight, ...fullHeight,
top: 50,
left: 0,
zIndex: 100, zIndex: 100,
left: 60,
width: 300, width: 300,
overflow: "hidden", overflow: "hidden",
backgroundColor: colors.gray} backgroundColor: colors.gray}

View file

@ -62,7 +62,9 @@ module.exports = {
}] }]
}, },
node: { node: {
fs: "empty" fs: "empty",
net: 'empty',
tls: 'empty'
}, },
devServer: { devServer: {
contentBase: "./public", contentBase: "./public",