mirror of
https://github.com/maputnik/editor.git
synced 2025-12-08 07:10:00 +00:00
Adds lint to CI and fixes errors. I'm not sure I'm fully proud of all the solutions there. But there's no lint issues and the lint is being checked as part of the CI. --------- Co-authored-by: Yuri Astrakhan <yuriastrakhan@gmail.com>
263 lines
6.8 KiB
TypeScript
263 lines
6.8 KiB
TypeScript
import React, { FormEvent } from 'react'
|
||
import {MdFileUpload} from 'react-icons/md'
|
||
import {MdAddCircleOutline} from 'react-icons/md'
|
||
import FileReaderInput, { Result } from 'react-file-reader-input'
|
||
|
||
import ModalLoading from './ModalLoading'
|
||
import Modal from './Modal'
|
||
import InputButton from './InputButton'
|
||
import InputUrl from './InputUrl'
|
||
|
||
import style from '../libs/style'
|
||
import publicStyles from '../config/styles.json'
|
||
|
||
type PublicStyleProps = {
|
||
url: string
|
||
thumbnailUrl: string
|
||
title: string
|
||
onSelect(...args: unknown[]): unknown
|
||
};
|
||
|
||
class PublicStyle extends React.Component<PublicStyleProps> {
|
||
render() {
|
||
return <div className="maputnik-public-style">
|
||
<InputButton
|
||
className="maputnik-public-style-button"
|
||
aria-label={this.props.title}
|
||
onClick={() => this.props.onSelect(this.props.url)}
|
||
>
|
||
<div className="maputnik-public-style-header">
|
||
<div>{this.props.title}</div>
|
||
<span className="maputnik-space" />
|
||
<MdAddCircleOutline />
|
||
</div>
|
||
<div
|
||
className="maputnik-public-style-thumbnail"
|
||
style={{
|
||
backgroundImage: `url(${this.props.thumbnailUrl})`
|
||
}}
|
||
></div>
|
||
</InputButton>
|
||
</div>
|
||
}
|
||
}
|
||
|
||
type ModalOpenProps = {
|
||
isOpen: boolean
|
||
onOpenToggle(...args: unknown[]): unknown
|
||
onStyleOpen(...args: unknown[]): unknown
|
||
};
|
||
|
||
type ModalOpenState = {
|
||
styleUrl: string
|
||
error?: string | null
|
||
activeRequest?: any
|
||
activeRequestUrl?: string | null
|
||
};
|
||
|
||
export default class ModalOpen extends React.Component<ModalOpenProps, ModalOpenState> {
|
||
constructor(props: ModalOpenProps) {
|
||
super(props);
|
||
this.state = {
|
||
styleUrl: ""
|
||
};
|
||
}
|
||
|
||
clearError() {
|
||
this.setState({
|
||
error: null
|
||
})
|
||
}
|
||
|
||
onCancelActiveRequest(e: Event) {
|
||
// Else the click propagates to the underlying modal
|
||
if(e) e.stopPropagation();
|
||
|
||
if(this.state.activeRequest) {
|
||
this.state.activeRequest.abort();
|
||
this.setState({
|
||
activeRequest: null,
|
||
activeRequestUrl: null
|
||
});
|
||
}
|
||
}
|
||
|
||
onStyleSelect = (styleUrl: string) => {
|
||
this.clearError();
|
||
|
||
let canceled: boolean = false;
|
||
|
||
fetch(styleUrl, {
|
||
mode: 'cors',
|
||
credentials: "same-origin"
|
||
})
|
||
.then(function(response) {
|
||
return response.json();
|
||
})
|
||
.then((body) => {
|
||
if(canceled) {
|
||
return;
|
||
}
|
||
|
||
this.setState({
|
||
activeRequest: null,
|
||
activeRequestUrl: null
|
||
});
|
||
|
||
const mapStyle = style.ensureStyleValidity(body)
|
||
console.log('Loaded style ', mapStyle.id)
|
||
this.props.onStyleOpen(mapStyle)
|
||
this.onOpenToggle()
|
||
})
|
||
.catch((err) => {
|
||
this.setState({
|
||
error: `Failed to load: '${styleUrl}'`,
|
||
activeRequest: null,
|
||
activeRequestUrl: null
|
||
});
|
||
console.error(err);
|
||
console.warn('Could not open the style URL', styleUrl)
|
||
})
|
||
|
||
this.setState({
|
||
activeRequest: {
|
||
abort: function() {
|
||
canceled = true;
|
||
}
|
||
},
|
||
activeRequestUrl: styleUrl
|
||
})
|
||
}
|
||
|
||
onSubmitUrl = (e: FormEvent<HTMLFormElement>) => {
|
||
e.preventDefault();
|
||
this.onStyleSelect(this.state.styleUrl);
|
||
}
|
||
|
||
onUpload = (_: any, files: Result[]) => {
|
||
const [, file] = files[0];
|
||
const reader = new FileReader();
|
||
|
||
this.clearError();
|
||
|
||
reader.readAsText(file, "UTF-8");
|
||
reader.onload = e => {
|
||
let mapStyle;
|
||
try {
|
||
mapStyle = JSON.parse(e.target?.result as string)
|
||
}
|
||
catch(err) {
|
||
this.setState({
|
||
error: (err as Error).toString()
|
||
});
|
||
return;
|
||
}
|
||
mapStyle = style.ensureStyleValidity(mapStyle)
|
||
this.props.onStyleOpen(mapStyle);
|
||
this.onOpenToggle();
|
||
}
|
||
reader.onerror = e => console.log(e.target);
|
||
}
|
||
|
||
onOpenToggle() {
|
||
this.setState({
|
||
styleUrl: ""
|
||
});
|
||
this.clearError();
|
||
this.props.onOpenToggle();
|
||
}
|
||
|
||
onChangeUrl = (url: string) => {
|
||
this.setState({
|
||
styleUrl: url,
|
||
});
|
||
}
|
||
|
||
render() {
|
||
const styleOptions = publicStyles.map(style => {
|
||
return <PublicStyle
|
||
key={style.id}
|
||
url={style.url}
|
||
title={style.title}
|
||
thumbnailUrl={style.thumbnail}
|
||
onSelect={this.onStyleSelect}
|
||
/>
|
||
})
|
||
|
||
let errorElement;
|
||
if(this.state.error) {
|
||
errorElement = (
|
||
<div className="maputnik-modal-error">
|
||
{this.state.error}
|
||
<a href="#" onClick={() => this.clearError()} className="maputnik-modal-error-close">×</a>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div>
|
||
<Modal
|
||
data-wd-key="modal:open"
|
||
isOpen={this.props.isOpen}
|
||
onOpenToggle={() => this.onOpenToggle()}
|
||
title={'Open Style'}
|
||
>
|
||
{errorElement}
|
||
<section className="maputnik-modal-section">
|
||
<h1>Upload Style</h1>
|
||
<p>Upload a JSON style from your computer.</p>
|
||
<FileReaderInput onChange={this.onUpload} tabIndex={-1} aria-label="Style file">
|
||
<InputButton className="maputnik-upload-button"><MdFileUpload /> Upload</InputButton>
|
||
</FileReaderInput>
|
||
</section>
|
||
|
||
<section className="maputnik-modal-section">
|
||
<form onSubmit={this.onSubmitUrl}>
|
||
<h1>Load from URL</h1>
|
||
<p>
|
||
Load from a URL. Note that the URL must have <a href="https://enable-cors.org" target="_blank" rel="noopener noreferrer">CORS enabled</a>.
|
||
</p>
|
||
<InputUrl
|
||
aria-label="Style URL"
|
||
data-wd-key="modal:open.url.input"
|
||
type="text"
|
||
className="maputnik-input"
|
||
default="Enter URL..."
|
||
value={this.state.styleUrl}
|
||
onInput={this.onChangeUrl}
|
||
onChange={this.onChangeUrl}
|
||
/>
|
||
<div>
|
||
<InputButton
|
||
data-wd-key="modal:open.url.button"
|
||
type="submit"
|
||
className="maputnik-big-button"
|
||
disabled={this.state.styleUrl.length < 1}
|
||
>Load from URL</InputButton>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section className="maputnik-modal-section maputnik-modal-section--shrink">
|
||
<h1>Gallery Styles</h1>
|
||
<p>
|
||
Open one of the publicly available styles to start from.
|
||
</p>
|
||
<div className="maputnik-style-gallery-container">
|
||
{styleOptions}
|
||
</div>
|
||
</section>
|
||
</Modal>
|
||
|
||
<ModalLoading
|
||
isOpen={!!this.state.activeRequest}
|
||
title={'Loading style'}
|
||
onCancel={(e: Event) => this.onCancelActiveRequest(e)}
|
||
message={"Loading: "+this.state.activeRequestUrl}
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
}
|
||
|