Skip to content

Commit

Permalink
Domain request headers and bug fixes (#63)
Browse files Browse the repository at this point in the history
* added customizable domain headers to stylesheet resources
* fixed localStorage existing data bug - closes #62
* updated README
  • Loading branch information
justenPalmer authored and ARolek committed Apr 30, 2019
1 parent 9b5ced6 commit 86a41e3
Show file tree
Hide file tree
Showing 18 changed files with 448 additions and 169 deletions.
37 changes: 28 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,42 @@
# fresco
An open source vector map style editor utilizing Mapbox GL.

[Try it](https://fresco.netlify.com/)
Fresco is an open source [Mapbox Vector Tile Style](https://docs.mapbox.com/mapbox-gl-js/style-spec) editor that allows cartographers to craft stylesheets for use with [Mapbox GL](https://docs.mapbox.com/mapbox-gl-js/api/) maps. Unlike other style editors, Fresco does not attempt to hide the complexity of Mapbox GL Styles but rather exposes an interactive JSON code editor to allow for maximum control and flexibility. This allows the user to implement more complex styles concepts like data driven styles with [expressions](https://docs.mapbox.com/help/tutorials/mapbox-gl-js-expressions/). When using Fresco, it may be helpful to have the [Mapbox Style Spec](https://docs.mapbox.com/mapbox-gl-js/style-spec/) available as a reference.

Styles created and modified with Fresco are saved to the browser's local storage and are auto-saved on changes.

Give it a try: [https://fresco.gospatial.org/](https://fresco.gospatial.org/)

![map editing screen shot](/docs/img/osm-screenshot.png)

## Features

- Style editor for Mapbox-gl styles
- Styles stored in localStorage (in the browser)
- JSON editor
- Layer property editor
- MapboxGL expression support
- Interactive JSON code editor.
- Style editor for Mapbox GL styles.
- Mapbox GL layer style expression editor.
- Auto save on changes.
- Styles persisted to local storage (in the browser).
- Mapbox GL style error parser. Displays the error at the error location in the style.
- Integrated Mapbox GL style spec attributes (info on style fields).
- Custom domain header configurations. Useful for domains which require `Authorization` headers.

## Usage

## Installation
Fresco may be used in the browser by visiting [https://fresco.gospatial.org/](https://fresco.gospatial.org/) or by downloading a pre compiled binary from the [releases](https://github.com/go-spatial/fresco/releases) page.

## Running from source

Fresco is built on top of React. To run Freco from source use the following steps:

1. Download the latest version of [Node.js](https://nodejs.org/en/download/)
2. Clone this repository to your computer
3. Navigate to this repo on your computer
4. Run `npm install`
5. To startup, run `npm start` - Fresco should open in a browser window

![map editing screenshot](/docs/img/osm-screenshot.png)
## Contributing

Contributions are welcome! Fork the repo and send in a PR with any bug fixes or features.

## Looking for a vector tile server?

If you're looking to create vector tiles that can be styled with Fresco, check out [tegola](https://github.com/go-spatial/tegola)!
4 changes: 2 additions & 2 deletions src/middleware/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {applyMiddleware} from 'redux';
import logger from 'redux-logger';
//import logger from 'redux-logger';
import thunk from 'redux-thunk';

import error from './error';

export default applyMiddleware(logger,error,thunk);
export default applyMiddleware(/*logger,*/error,thunk);
12 changes: 10 additions & 2 deletions src/model/Msource.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Store from '../Store';
import SourceReader from '../utility/SourceReader';
import MaterialColor from '../utility/MaterialColor';
import Url from '../utility/Url';

import styleSpec from '../vendor/style-spec/style-spec';

import Mstyle from './Mstyle';

export default {

add:function(source,key,makeLayers){
add:function(source, key, makeLayers, headers){
return new Promise((resolve,reject)=>{
if (!source.url) throw new Error('no source.url');
if (!source.type) throw new Error('no source.type');
Expand All @@ -22,7 +23,7 @@ export default {
//set key on source
key = key || source.url;

SourceReader.load(source.url).then((sourceJson)=>{
SourceReader.load(source.url, {headers:headers.toJS()}).then((sourceJson)=>{

Store.dispatch({
type:'SOURCE_ADD',
Expand All @@ -35,6 +36,13 @@ export default {
payload:sourceJson
});

const domain = Url.getDomain(source.url)
Store.dispatch({
type:'STYLE_STORE_SETIN',
key:['domains', domain],
payload:headers
});

if (makeLayers){
const sourceLayers = sourceJson.vector_layers;
if (sourceLayers) this.setupInitialLayers(key, sourceLayers);
Expand Down
69 changes: 67 additions & 2 deletions src/model/Mstyle.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Store from '../Store';
import MapboxError from '../utility/MapboxError';
import LocalStorage from '../utility/LocalStorage';
import Uid from '../utility/Uid';
import Url from '../utility/Url';

const Mstyle = {

Expand Down Expand Up @@ -104,6 +105,46 @@ const Mstyle = {
get:function(){
return Store.getState().style;
},
getDomains:function(){
const style = this.get()

let domains = [];
const glyphs = style.getIn(['rec','glyphs'])
const glyphsDomain = Url.getDomain(glyphs)
if (glyphsDomain) domains.push(glyphsDomain)

const sprites = style.getIn(['rec','sprites'])
const spritesDomain = Url.getDomain(sprites)
if (spritesDomain && !domains.includes(spritesDomain)) domains.push(spritesDomain)

// loop through sources and get all domains (from tiles too)
const sources = style.getIn(['rec','sources'])
sources.forEach((source, sourceKey)=>{
const url = source.get('url');
if (url){
const domain = Url.getDomain(url)
if (domain && !domains.includes(domain)) domains.push(domain)
const sourceJson = Store.getState().style.getIn(['rec','_store','sourceJson',url])

let tiles;
if (source.has('tiles')){
tiles = source.get('tiles')
} else if (sourceJson && sourceJson.has('tiles')){
tiles = sourceJson.get('tiles')
}

tiles && tiles.forEach((tile)=>{
const domain = Url.getDomain(tile)
if (domain && !domains.includes(domain)) domains.push(domain)
})
}
})

return domains
},
getStore:function(){
return this.get().getIn(['rec','_store'])
},
getJS:function(){
return this.get().toJS();
},
Expand All @@ -115,8 +156,6 @@ const Mstyle = {
getJSforMapbox:function(){

// apply interactive transformations to style



return this.get().get('rec').delete('_store').toJS();
},
Expand All @@ -128,6 +167,12 @@ const Mstyle = {

if (all._config) delete all._config;

// remove all non-styles from all
for (let i in all){
if (!all[i] || !all[i].id || !all[i].layers) // not a mapbox style
delete all[i]
}

Store.dispatch({
type:'STYLES_DEFINE',
payload:all
Expand Down Expand Up @@ -220,6 +265,26 @@ const Mstyle = {
});
},

setDomains:function(domains){
//console.log('setJSON source:',source);
return new Promise((resolve,reject)=>{
Store.dispatch({
type:'STYLE_STORE_SETIN',
key:['domains'],
payload:domains
});

Mstyle.save();

Store.dispatch({
type:'SOURCE_RELOAD',
payload:{}
});

return resolve();
});
},

validate:function(){
return new Promise((resolve,reject)=>{
const errors = validateStyle(this.getJSforMapbox());
Expand Down
2 changes: 0 additions & 2 deletions src/model/MstyleSource.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export default {
},NameFromURL.get(sourceUrl),true).then((source)=>{
//console.log('added source:',source);
const json = Msource.getJson(source.url);
console.log('added source json:',json);
const center = [
json.getIn(['center',0]),
json.getIn(['center',1])
Expand All @@ -47,7 +46,6 @@ export default {
},

setStyleSourceJSON:function(style){
console.log('style',style);
if (!style.has('sources') || style.get('sources').size < 1) return;

style.get('sources').keySeq().map((key)=>{
Expand Down
2 changes: 0 additions & 2 deletions src/utility/LocalStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ export default {
}
} catch(e){
console.error('localstorage json parse error:',e);
window.localStorage.clear();
}
console.log('items:',items);
return items;
},

Expand Down
7 changes: 4 additions & 3 deletions src/utility/SourceReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import axios from 'axios';

export default {

load:(sourceUrl)=>{
return new Promise((resolve,reject)=>{
axios.get(sourceUrl)
load:(sourceUrl, {headers})=>{
return new Promise((resolve, reject)=>{
console.log('headers:',headers)
axios.get(sourceUrl, {headers})
.then(function (response) {
//if (!response.data.maps) return reject('no maps defined on source');
return resolve(response.data);
Expand Down
10 changes: 10 additions & 0 deletions src/utility/Url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const getDomain = (url)=>{
if (!url) return null
var matches = url.match(/^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i);
var domain = matches && matches[1];
return domain || null
}

module.exports = {
getDomain
}
1 change: 0 additions & 1 deletion src/view/Vfield/VfieldJSON.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ export default class VfieldJSON extends React.Component {
const val = this.cm.getValue();
const json = this.strToJson(val);
if (json) return handle.change(json);
console.log('perform lint!');
this.cm.performLint();
},
focus(){
Expand Down
17 changes: 17 additions & 0 deletions src/view/Vmap/Vmapbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Mconfig from '../../model/Mconfig';
import Msource from '../../model/Msource';
import Mstyle from '../../model/Mstyle';
import Mlayer from '../../model/Mlayer';
import Url from '../../utility/Url';
import VmapboxControls from './VmapboxControls';
import VmapboxInspector from './VmapboxInspector';

Expand Down Expand Up @@ -157,6 +158,21 @@ class Vmapbox extends React.Component {
}

transformRequest = (url, resourceType)=>{

const store = Mstyle.getStore()
const domains = store.getIn(['domains'])

const domain = Url.getDomain(url)

if (domains && domains.has(domain)){
const headers = domains.getIn([domain]).toJS()
return {
url: url,
headers
}
}

/*
if (resourceType === 'Source') {
const sources = Msource.get(); // get all sources
Expand Down Expand Up @@ -217,6 +233,7 @@ class Vmapbox extends React.Component {
headers: settings.get('headers').toJS()
}
}
*/
}
};

Expand Down
9 changes: 4 additions & 5 deletions src/view/Vproperty/VpropertyMetadata/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ export default class VpropertyMetadata extends React.Component {
super(props);
const {handle, property} = this.props;

//console.log('handle:',handle);

// convert value into array of arrays [[k,v],[k,v]]
let valAry = [];
property.value && property.value.keySeq().toArray().forEach((key)=>{
Expand All @@ -43,7 +41,8 @@ export default class VpropertyMetadata extends React.Component {

this.handle = {
add:()=>{
this.setState({valAry:this.state.valAry.push(List([]))});
const valAry = this.state.valAry.push(List([]))
this.setState({valAry});
}
};

Expand Down Expand Up @@ -127,7 +126,7 @@ export default class VpropertyMetadata extends React.Component {
else if (value === false) value = 'false';

rows.push(
<div key={key} className="row">
<div key={key} className="row mb-2">
<div className="col-sm-6 pr-1">
<Vfield key={keyName} field={{
type:'string',
Expand All @@ -154,7 +153,7 @@ export default class VpropertyMetadata extends React.Component {
<div className="mt-2 p-2 func-border position-relative">
{rows}

<div className="mt-2">
<div className="">
<div onClick={this.handle.add} className="btn btn-xs btn-light">
<i className="material-icons md-14">add</i>
</div>
Expand Down
Loading

0 comments on commit 86a41e3

Please sign in to comment.