import React from 'react' import {MdAddCircleOutline, MdDelete} from 'react-icons/md' import latest from '@maplibre/maplibre-gl-style-spec/dist/latest.json' import type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, StyleSpecification, VectorSourceSpecification} from 'maplibre-gl' import { WithTranslation, withTranslation } from 'react-i18next'; import Modal from './Modal' import InputButton from './InputButton' import FieldString from './FieldString' import FieldSelect from './FieldSelect' import ModalSourcesTypeEditor, { EditorMode } from './ModalSourcesTypeEditor' import style from '../libs/style' import { deleteSource, addSource, changeSource } from '../libs/source' import publicSources from '../config/tilesets.json' type PublicSourceProps = { id: string type: string title: string onSelect(...args: unknown[]): unknown }; class PublicSource extends React.Component { render() { return
this.props.onSelect(this.props.id)} >

{this.props.title}

#{this.props.id}

} } function editorMode(source: SourceSpecification) { if(source.type === 'raster') { if(source.tiles) return 'tile_raster' return 'tilejson_raster' } if(source.type === 'raster-dem') { if(source.tiles) return 'tilexyz_raster-dem' return 'tilejson_raster-dem' } if(source.type === 'vector') { if(source.tiles) return 'tile_vector' return 'tilejson_vector' } if(source.type === 'geojson') { if (typeof(source.data) === "string") { return 'geojson_url'; } else { return 'geojson_json'; } } if(source.type === 'image') { return 'image'; } if(source.type === 'video') { return 'video'; } return null } type ActiveModalSourcesTypeEditorProps = { sourceId: string source: SourceSpecification onDelete(...args: unknown[]): unknown onChange(...args: unknown[]): unknown } & WithTranslation; class ActiveModalSourcesTypeEditor extends React.Component { render() { const t = this.props.t; return
#{this.props.sourceId} this.props.onDelete(this.props.sourceId)} style={{backgroundColor: 'transparent'}} >
} } type AddSourceProps = { onAdd(...args: unknown[]): unknown } & WithTranslation; type AddSourceState = { mode: EditorMode sourceId: string source: SourceSpecification }; class AddSource extends React.Component { constructor(props: AddSourceProps) { super(props) this.state = { mode: 'tilejson_vector', sourceId: style.generateId(), source: this.defaultSource('tilejson_vector'), } } defaultSource(mode: EditorMode): SourceSpecification { const source = (this.state || {}).source || {} const {protocol} = window.location; switch(mode) { case 'geojson_url': return { type: 'geojson', data: `${protocol}//localhost:3000/geojson.json` } case 'geojson_json': return { type: 'geojson', cluster: (source as GeoJSONSourceSpecification).cluster || false, data: '' } case 'tilejson_vector': return { type: 'vector', url: (source as VectorSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json` } case 'tile_vector': return { type: 'vector', tiles: (source as VectorSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`], minzoom: (source as VectorSourceSpecification).minzoom || 0, maxzoom: (source as VectorSourceSpecification).maxzoom || 14, scheme: (source as VectorSourceSpecification).scheme || 'xyz' } case 'tilejson_raster': return { type: 'raster', url: (source as RasterSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json` } case 'tile_raster': return { type: 'raster', tiles: (source as RasterSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`], minzoom: (source as RasterSourceSpecification).minzoom || 0, maxzoom: (source as RasterSourceSpecification).maxzoom || 14, scheme: (source as RasterSourceSpecification).scheme || 'xyz', tileSize: (source as RasterSourceSpecification).tileSize || 512, } case 'tilejson_raster-dem': return { type: 'raster-dem', url: (source as RasterDEMSourceSpecification).url || `${protocol}//localhost:3000/tilejson.json` } case 'tilexyz_raster-dem': return { type: 'raster-dem', tiles: (source as RasterDEMSourceSpecification).tiles || [`${protocol}//localhost:3000/{x}/{y}/{z}.pbf`], minzoom: (source as RasterDEMSourceSpecification).minzoom || 0, maxzoom: (source as RasterDEMSourceSpecification).maxzoom || 14, tileSize: (source as RasterDEMSourceSpecification).tileSize || 512 } case 'image': return { type: 'image', url: `${protocol}//localhost:3000/image.png`, coordinates: [ [0,0], [0,0], [0,0], [0,0], ], } case 'video': return { type: 'video', urls: [ `${protocol}//localhost:3000/movie.mp4` ], coordinates: [ [0,0], [0,0], [0,0], [0,0], ], } default: return {} as any } } onAdd = () => { const {source, sourceId} = this.state; this.props.onAdd(sourceId, source); } onChangeSource = (source: SourceSpecification) => { this.setState({source}); } render() { const t = this.props.t; // Kind of a hack because the type changes, however maputnik has 1..n // options per type, for example // // - 'geojson' - 'GeoJSON (URL)' and 'GeoJSON (JSON)' // - 'raster' - 'Raster (TileJSON URL)' and 'Raster (XYZ URL)' // // So we just ignore the values entirely as they are self explanatory const sourceTypeFieldSpec = { doc: latest.source_vector.type.doc }; return
this.setState({ sourceId: v})} data-wd-key="modal:sources.add.source_id" /> this.setState({mode: mode as EditorMode, source: this.defaultSource(mode as EditorMode)})} value={this.state.mode as string} data-wd-key="modal:sources.add.source_type" /> {t("Add Source")}
} } type ModalSourcesInternalProps = { mapStyle: StyleSpecification isOpen: boolean onOpenToggle(...args: unknown[]): unknown onStyleChanged(...args: unknown[]): unknown } & WithTranslation; class ModalSourcesInternal extends React.Component { stripTitle(source: SourceSpecification & {title?: string}): SourceSpecification { const strippedSource = {...source} delete strippedSource['title'] return strippedSource } render() { const {t, mapStyle} = this.props; const i18nProps = {t, i18n: this.props.i18n, tReady: this.props.tReady}; const activeSources = Object.keys(mapStyle.sources).map(sourceId => { const source = mapStyle.sources[sourceId] return this.props.onStyleChanged(changeSource(mapStyle, sourceId, src))} onDelete={() => this.props.onStyleChanged(deleteSource(mapStyle, sourceId))} {...i18nProps} /> }) const tilesetOptions = Object.keys(publicSources).filter((sourceId: string) => !(sourceId in mapStyle.sources)).map((sourceId: string) => { const source = publicSources[sourceId as keyof typeof publicSources] as SourceSpecification & {title: string}; return this.props.onStyleChanged(addSource(mapStyle, sourceId, this.stripTitle(source)))} /> }) return

{t("Active Sources")}

{activeSources}

{t("Choose Public Source")}

{t("Add one of the publicly available sources to your style.")}

{tilesetOptions}

{t("Add New Source")}

{t("Add a new source to your style. You can only choose the source type and id at creation time!")}

this.props.onStyleChanged(addSource(mapStyle, sourceId, source))} {...i18nProps} />
} } const ModalSources = withTranslation()(ModalSourcesInternal); export default ModalSources;