mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2025-01-14 17:23:26 +01:00
Merge branch 'feature/layout'
This commit is contained in:
commit
20788a0234
14 changed files with 573 additions and 507 deletions
|
@ -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
|
||||
|
|
|
@ -15,6 +15,7 @@ before_install:
|
|||
install:
|
||||
- npm install
|
||||
script:
|
||||
- mkdir public
|
||||
- npm run build
|
||||
- npm run lint
|
||||
- npm run test
|
||||
|
|
|
@ -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": {
|
||||
|
@ -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": [
|
||||
|
|
41
src/apistore.js
Normal file
41
src/apistore.js
Normal 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
|
||||
}
|
||||
}
|
30
src/app.jsx
30
src/app.jsx
|
@ -10,6 +10,7 @@ import { Map } from './map.jsx'
|
|||
import {Toolbar} from './toolbar.jsx'
|
||||
import style from './style.js'
|
||||
import { loadDefaultStyle, SettingsStore, StyleStore } from './stylestore.js'
|
||||
import { ApiStyleStore } from './apistore.js'
|
||||
import { WorkspaceDrawer } from './workspace.jsx'
|
||||
|
||||
import theme from './theme.js'
|
||||
|
@ -23,15 +24,21 @@ export default class App extends React.Component {
|
|||
|
||||
constructor(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.latestStyle(mapStyle => this.onStyleUpload(mapStyle))
|
||||
})
|
||||
|
||||
this.settingsStore = new SettingsStore()
|
||||
this.state = {
|
||||
accessToken: this.settingsStore.accessToken,
|
||||
workContext: "layers",
|
||||
currentStyle: this.styleStore.latestStyle(),
|
||||
}
|
||||
if(this.state.currentStyle.get('layers').size === 0) {
|
||||
loadDefaultStyle(mapStyle => this.onStyleUpload(mapStyle))
|
||||
currentStyle: style.emptyStyle
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -70,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) {
|
||||
|
@ -99,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)}
|
||||
/>
|
||||
<WorkspaceDrawer
|
||||
|
|
|
@ -44,19 +44,12 @@ export class LayerList extends React.Component {
|
|||
}).toIndexedSeq()
|
||||
|
||||
return <div>
|
||||
<Toolbar style={{marginRight: 20}}>
|
||||
<NavItem>
|
||||
<Heading>Layers</Heading>
|
||||
</NavItem>
|
||||
<Space auto x={1} />
|
||||
</Toolbar>
|
||||
|
||||
<div className={scrollbars.darkScrollbar} style={{
|
||||
overflowY: "scroll",
|
||||
bottom:0,
|
||||
left:0,
|
||||
right:0,
|
||||
top:40,
|
||||
top:1,
|
||||
position: "absolute",
|
||||
}}>
|
||||
{layerPanels}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
.darkScrollbar::-webkit-scrollbar {
|
||||
background-color: #313131;
|
||||
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;
|
||||
background-color: #40444e;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
if(style.has('id')) return style
|
||||
return style.set('id', Math.random().toString(36).substr(2, 9))
|
||||
|
@ -81,4 +88,5 @@ export default {
|
|||
fromJSON,
|
||||
diffStyles,
|
||||
ensureMetadataExists,
|
||||
emptyStyle,
|
||||
}
|
||||
|
|
|
@ -9,13 +9,6 @@ const storageKeys = {
|
|||
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) {
|
||||
|
@ -27,13 +20,13 @@ export function loadDefaultStyle(cb) {
|
|||
if (request.status >= 200 && request.status < 400) {
|
||||
cb(style.ensureMetadataExists(style.fromJSON(request.responseText)))
|
||||
} else {
|
||||
cb(emptyStyle)
|
||||
cb(style.emptyStyle)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = function() {
|
||||
console.log('Could not fetch default style')
|
||||
cb(emptyStyle)
|
||||
cb(style.emptyStyle)
|
||||
}
|
||||
|
||||
request.send()
|
||||
|
@ -91,6 +84,10 @@ export class StyleStore {
|
|||
this.mapStyles = loadStoredStyles()
|
||||
}
|
||||
|
||||
supported(cb) {
|
||||
cb(window.localStorage !== undefined)
|
||||
}
|
||||
|
||||
// Delete entire style history
|
||||
purge() {
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
|
@ -102,13 +99,13 @@ export class StyleStore {
|
|||
}
|
||||
|
||||
// Find the last edited style
|
||||
latestStyle() {
|
||||
if(this.mapStyles.length === 0) 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))
|
||||
|
||||
if(styleItem) return style.fromJSON(styleItem)
|
||||
return emptyStyle
|
||||
if(styleItem) return cb(style.fromJSON(styleItem))
|
||||
cb(style.emptyStyle)
|
||||
}
|
||||
|
||||
// Save current style replacing previous version
|
||||
|
|
|
@ -11,10 +11,10 @@ export const fullHeight = {
|
|||
}
|
||||
|
||||
const baseColors = {
|
||||
black: '#242424',
|
||||
gray: '#313131',
|
||||
midgray: '#778',
|
||||
lowgray: '#dcdcdc',
|
||||
black: '#1c1f24',
|
||||
gray: '#26282e',
|
||||
midgray: '#36383e',
|
||||
lowgray: '#8e8e8e',
|
||||
white: '#fff',
|
||||
blue: '#00d9f7',
|
||||
green: '#B4C7AD',
|
||||
|
|
104
src/toolbar.jsx
104
src/toolbar.jsx
|
@ -12,16 +12,26 @@ 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 => <div style={{display: "inline-block", ...props.style}}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
export class Toolbar extends React.Component {
|
||||
static propTypes = {
|
||||
// A new style has been uploaded
|
||||
|
@ -36,8 +46,6 @@ export class Toolbar extends React.Component {
|
|||
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,
|
||||
|
@ -57,77 +65,85 @@ export class Toolbar extends React.Component {
|
|||
|
||||
saveButton() {
|
||||
if(this.props.styleAvailable) {
|
||||
return <Block>
|
||||
return <InlineBlock>
|
||||
<Button onClick={this.props.onStyleSave} big={true}>
|
||||
<Tooltip inverted rounded title="Save style">
|
||||
<MdSave />
|
||||
</Tooltip>
|
||||
Save
|
||||
</Button>
|
||||
</Block>
|
||||
</InlineBlock>
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
downloadButton() {
|
||||
if(this.props.styleAvailable) {
|
||||
return <Block>
|
||||
return <InlineBlock>
|
||||
<Button onClick={this.props.onStyleDownload} big={true}>
|
||||
<Tooltip inverted rounded title="Download style">
|
||||
<MdFileDownload />
|
||||
</Tooltip>
|
||||
Download
|
||||
</Button>
|
||||
</Block>
|
||||
</InlineBlock>
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
render() {
|
||||
return <Container style={{
|
||||
...fullHeight,
|
||||
|
||||
return <div style={{
|
||||
position: "fixed",
|
||||
height: 50,
|
||||
width: '100%',
|
||||
zIndex: 100,
|
||||
left: 0,
|
||||
top: 0,
|
||||
backgroundColor: theme.colors.black }
|
||||
}>
|
||||
<Block>
|
||||
backgroundColor: theme.colors.black
|
||||
}}>
|
||||
<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)}>
|
||||
<Button big={true} theme={this.props.styleAvailable ? "default" : "success"}>
|
||||
<Tooltip inverted rounded title="Upload style">
|
||||
<MdFileUpload />
|
||||
</Tooltip>
|
||||
<MdOpenInBrowser />
|
||||
Open
|
||||
</Button>
|
||||
</FileReaderInput>
|
||||
</Block>
|
||||
</InlineBlock>
|
||||
{this.downloadButton()}
|
||||
{this.saveButton()}
|
||||
<Block>
|
||||
<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>
|
||||
<InlineBlock>
|
||||
<Button big={true} onClick={this.props.onOpenSettings}>
|
||||
<Tooltip inverted rounded title="Settings">
|
||||
<MdSettings />
|
||||
</Tooltip>
|
||||
<MdLayers />
|
||||
Tilesets
|
||||
</Button>
|
||||
</Block>
|
||||
<Block>
|
||||
</InlineBlock>
|
||||
<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}>
|
||||
<Tooltip inverted rounded title="About">
|
||||
<MdInfo />
|
||||
</Tooltip>
|
||||
<MdHelpOutline />
|
||||
Help
|
||||
</Button>
|
||||
</Block>
|
||||
</Container>
|
||||
</InlineBlock>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,8 +60,9 @@ export class WorkspaceDrawer extends React.Component {
|
|||
|
||||
return <div style={{
|
||||
...fullHeight,
|
||||
top: 50,
|
||||
left: 0,
|
||||
zIndex: 100,
|
||||
left: 60,
|
||||
width: 300,
|
||||
overflow: "hidden",
|
||||
backgroundColor: colors.gray}
|
||||
|
|
|
@ -62,7 +62,9 @@ module.exports = {
|
|||
}]
|
||||
},
|
||||
node: {
|
||||
fs: "empty"
|
||||
fs: "empty",
|
||||
net: 'empty',
|
||||
tls: 'empty'
|
||||
},
|
||||
devServer: {
|
||||
contentBase: "./public",
|
||||
|
|
Loading…
Reference in a new issue