Merge pull request #86 from klokantech/gist-preview

Gist preview & access token
This commit is contained in:
Lukas Martinelli 2017-01-16 15:48:12 +01:00 committed by GitHub
commit 48e9589b58
8 changed files with 120 additions and 20 deletions

View file

@ -91,6 +91,7 @@ export default class Toolbar extends React.Component {
/> />
<ExportModal <ExportModal
mapStyle={this.props.mapStyle} mapStyle={this.props.mapStyle}
onStyleChanged={this.props.onStyleChanged}
isOpen={this.state.isOpen.export} isOpen={this.state.isOpen.export}
onOpenToggle={this.toggleModal.bind(this, 'export')} onOpenToggle={this.toggleModal.bind(this, 'export')}
/> />

View file

@ -17,7 +17,9 @@ class CheckboxInput extends React.Component {
checked={this.props.value} checked={this.props.value}
/> />
<div className="maputnik-checkbox-box"> <div className="maputnik-checkbox-box">
<svg className="maputnik-checkbox-icon" viewBox='0 0 32 32'> <svg style={{
display: this.props.value ? 'inline' : 'none'
}} className="maputnik-checkbox-icon" viewBox='0 0 32 32'>
<path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' /> <path d='M1 14 L5 10 L13 18 L27 4 L31 8 L13 26 z' />
</svg> </svg>
</div> </div>

View file

@ -27,7 +27,7 @@ class StringInput extends React.Component {
placeholder={this.props.default} placeholder={this.props.default}
onChange={e => this.setState({ value: e.target.value })} onChange={e => this.setState({ value: e.target.value })}
onBlur={() => { onBlur={() => {
if(this.state.value) this.props.onChange(this.state.value) if(this.state.value!==this.props.value) this.props.onChange(this.state.value)
}} }}
/> />
} }

View file

@ -5,9 +5,11 @@ import GlSpec from 'mapbox-gl-style-spec/reference/latest.js'
import InputBlock from '../inputs/InputBlock' import InputBlock from '../inputs/InputBlock'
import StringInput from '../inputs/StringInput' import StringInput from '../inputs/StringInput'
import SelectInput from '../inputs/SelectInput' import SelectInput from '../inputs/SelectInput'
import CheckboxInput from '../inputs/CheckboxInput'
import Button from '../Button' import Button from '../Button'
import Modal from './Modal' import Modal from './Modal'
import MdFileDownload from 'react-icons/lib/md/file-download' import MdFileDownload from 'react-icons/lib/md/file-download'
import style from '../../libs/style.js'
import formatStyle from 'mapbox-gl-style-spec/lib/format' import formatStyle from 'mapbox-gl-style-spec/lib/format'
import GitHub from 'github-api' import GitHub from 'github-api'
@ -15,18 +17,35 @@ import GitHub from 'github-api'
class Gist extends React.Component { class Gist extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
} }
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {} this.state = {
preview: false,
saving: false,
latestGist: null,
}
}
componentWillReceiveProps(nextProps) {
this.setState({
...this.state,
preview: !!nextProps.mapStyle.metadata['maputnik:openmaptiles_access_token']
})
} }
onSave() { onSave() {
this.setState({ this.setState({
...this.state,
saving: true saving: true
}); });
const mapStyleStr = formatStyle(this.props.mapStyle); const preview = this.state.preview && this.props.mapStyle.metadata['maputnik:openmaptiles_access_token'];
const mapStyleStr = preview ?
formatStyle(stripAccessTokens(style.replaceAccessToken(this.props.mapStyle))) :
formatStyle(stripAccessTokens(this.props.mapStyle));
const styleTitle = this.props.mapStyle.name || 'Style'; const styleTitle = this.props.mapStyle.name || 'Style';
const htmlStr = ` const htmlStr = `
<!DOCTYPE html> <!DOCTYPE html>
@ -56,49 +75,100 @@ class Gist extends React.Component {
</body> </body>
</html> </html>
` `
const files = {
"style.json": {
content: mapStyleStr
}
}
if(preview) {
files["index.html"] = {
content: htmlStr
}
}
const gh = new GitHub(); const gh = new GitHub();
let gist = gh.getGist(); // not a gist yet let gist = gh.getGist(); // not a gist yet
gist.create({ gist.create({
public: true, public: true,
description: styleTitle + 'Preview', description: styleTitle,
files: { files: files
"style.json": {
content: mapStyleStr
},
"index.html": {
content: htmlStr
}
}
}).then(function({data}) { }).then(function({data}) {
return gist.read(); return gist.read();
}).then(function({data}) { }).then(function({data}) {
this.setState({ this.setState({
latestGist: data ...this.state,
latestGist: data,
saving: false,
}); });
}.bind(this)); }.bind(this));
} }
onPreviewChange(value) {
this.setState({
...this.state,
preview: value
})
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
renderPreviewLink() {
const gist = this.state.latestGist;
const user = gist.user || 'anonymous';
const preview = !!gist.files['index.html'];
if(preview) {
return <span><a target="_blank" href={"https://bl.ocks.org/"+user+"/"+gist.id}>Preview</a>,{' '}</span>
}
return null;
}
renderLatestGist() { renderLatestGist() {
const gist = this.state.latestGist; const gist = this.state.latestGist;
const saving = this.state.saving; const saving = this.state.saving;
if(gist) { if(saving) {
return <p>Saving...</p>
} else if(gist) {
const user = gist.user || 'anonymous'; const user = gist.user || 'anonymous';
return <p> return <p>
Latest saved gist:{' '} Latest saved gist:{' '}
<a target="_blank" href={"https://bl.ocks.org/"+user+"/"+gist.id}>Preview</a>,{' '} {this.renderPreviewLink(this)}
<a target="_blank" href={"https://gist.github.com/"+user+"/"+gist.id}>Source</a> <a target="_blank" href={"https://gist.github.com/"+user+"/"+gist.id}>Source</a>
</p> </p>
} else if(saving) {
return <p>Saving...</p>
} }
} }
render() { render() {
return <div> return <div className="maputnik-export-gist">
<Button onClick={this.onSave.bind(this)}> <Button onClick={this.onSave.bind(this)}>
<MdFileDownload /> <MdFileDownload />
Save to Gist (anonymous) Save to Gist (anonymous)
</Button> </Button>
{' '}
<CheckboxInput
value={this.state.preview}
name='gist-style-preview'
onChange={this.onPreviewChange.bind(this)}
/>
<span> Include preview</span>
{this.state.preview ?
<div>
<InputBlock
label={"OpenMapTiles Access Token: "}>
<StringInput
value={this.props.mapStyle.metadata['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}/>
</InputBlock>
<a target="_blank" href="https://openmaptiles.com/hosting/">Get your free access token</a>
</div>
: null}
{this.renderLatestGist()} {this.renderLatestGist()}
</div> </div>
} }
@ -117,6 +187,7 @@ function stripAccessTokens(mapStyle) {
class ExportModal extends React.Component { class ExportModal extends React.Component {
static propTypes = { static propTypes = {
mapStyle: React.PropTypes.object.isRequired, mapStyle: React.PropTypes.object.isRequired,
onStyleChanged: React.PropTypes.func.isRequired,
isOpen: React.PropTypes.bool.isRequired, isOpen: React.PropTypes.bool.isRequired,
onOpenToggle: React.PropTypes.func.isRequired, onOpenToggle: React.PropTypes.func.isRequired,
} }
@ -150,7 +221,7 @@ class ExportModal extends React.Component {
<div className="maputnik-modal-section"> <div className="maputnik-modal-section">
<h4>Save style</h4> <h4>Save style</h4>
<Gist mapStyle={this.props.mapStyle} /> <Gist mapStyle={this.props.mapStyle} onStyleChanged={this.props.onStyleChanged}/>
</div> </div>
</Modal> </Modal>
} }

View file

@ -72,3 +72,7 @@ label:hover {
clear: both; clear: both;
} }
} }
a {
color: white;
}

5
src/styles/_export.scss Normal file
View file

@ -0,0 +1,5 @@
.maputnik-export-gist {
label.maputnik-checkbox-wrapper {
display: inline-block;
}
}

View file

@ -181,3 +181,19 @@
margin-right: $margin-3; margin-right: $margin-3;
float: right; float: right;
} }
//EXPORT MODAL
.maputnik-export-gist {
.maputnik-input-block {
margin-left: 0;
margin-right: 0;
label {
vertical-align: middle;
}
}
font-size: $font-size-6;
span {
color: $color-lowgray;
}
}

View file

@ -27,6 +27,7 @@ $toolbar-height: 40px;
@import 'picker'; @import 'picker';
@import 'toolbar'; @import 'toolbar';
@import 'modal'; @import 'modal';
@import 'export';
@import 'layout'; @import 'layout';
@import 'layer'; @import 'layer';
@import 'input'; @import 'input';