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, VectorSourceSpecification} from "maplibre-gl"; import { type WithTranslation, withTranslation } from "react-i18next"; import { Modal } from "./Modal"; import { InputButton } from "../InputButton"; import { FieldString } from "../FieldString"; import { FieldSelect } from "../FieldSelect"; import { ModalSourcesTypeEditor, type EditorMode } from "./ModalSourcesTypeEditor"; import { generateId } from "../../libs/style"; import { deleteSource, addSource, changeSource } from "../../libs/source"; import publicSources from "../../config/tilesets.json"; import { type OnStyleChangedCallback, type StyleSpecificationWithId } from "../../libs/definitions"; 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"; if(source.url && source.url.startsWith("pmtiles://")) return "pmtiles_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: generateId(), source: this.defaultSource("tilejson_vector"), }; } defaultSource(mode: EditorMode): SourceSpecification { const source = (this.state || {}).source || {}; const {protocol} = window.location; switch(mode) { case "pmtiles_vector": return { type: "vector", url: `${protocol}//localhost:3000/file.pmtiles` }; 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}.png`], 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}.png`], 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: StyleSpecificationWithId isOpen: boolean onOpenToggle(): void onStyleChanged: OnStyleChangedCallback } & 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} />
; } } export const ModalSources = withTranslation()(ModalSourcesInternal);