Migrate all the non react components code to typescript (#847)

This completes the migration to typescript of all the non react
components code.
The only changes introduced besides types are the type checks using
`"something" in object` which narrows down types in typescript.
This commit is contained in:
Harel M
2023-12-21 00:07:53 +02:00
committed by GitHub
parent e8d07fa694
commit 3bf0e510e6
15 changed files with 149 additions and 96 deletions

View File

@@ -9,7 +9,7 @@ import InputUrl from './InputUrl'
import {MdFileUpload} from 'react-icons/md'
import {MdAddCircleOutline} from 'react-icons/md'
import style from '../libs/style.js'
import style from '../libs/style'
import publicStyles from '../config/styles.json'
class PublicStyle extends React.Component {

View File

@@ -1,10 +1,21 @@
import style from './style.js'
import {format} from '@maplibre/maplibre-gl-style-spec'
import {StyleSpecification, format} from '@maplibre/maplibre-gl-style-spec'
import ReconnectingWebSocket from 'reconnecting-websocket'
export type ApiStyleStoreOptions = {
port?: string
host?: string
onLocalStyleChange?: (style: any) => void
}
export class ApiStyleStore {
constructor(opts) {
localUrl: string;
websocketUrl: string;
latestStyleId: string | undefined = undefined;
onLocalStyleChange: (style: any) => void;
constructor(opts: ApiStyleStoreOptions) {
this.onLocalStyleChange = opts.onLocalStyleChange || (() => {})
const port = opts.port || '8000'
const host = opts.host || 'localhost'
@@ -13,7 +24,7 @@ export class ApiStyleStore {
this.init = this.init.bind(this)
}
init(cb) {
init(cb: (...args: any[]) => void) {
fetch(this.localUrl + '/styles', {
mode: 'cors',
})
@@ -26,7 +37,7 @@ export class ApiStyleStore {
this.notifyLocalChanges()
cb(null)
})
.catch(function(e) {
.catch(() => {
cb(new Error('Can not connect to style API'))
})
}
@@ -47,7 +58,7 @@ export class ApiStyleStore {
}
}
latestStyle(cb) {
latestStyle(cb: (...args: any[]) => void) {
if(this.latestStyleId) {
fetch(this.localUrl + '/styles/' + this.latestStyleId, {
mode: 'cors',
@@ -64,7 +75,7 @@ export class ApiStyleStore {
}
// Save current style replacing previous version
save(mapStyle) {
save(mapStyle: StyleSpecification & { id: string }) {
const styleJSON = format(
style.stripAccessTokens(
style.replaceAccessTokens(mapStyle)

View File

@@ -1,17 +1,20 @@
// @ts-ignore
import stylegen from 'mapbox-gl-inspect/lib/stylegen'
// @ts-ignore
import colors from 'mapbox-gl-inspect/lib/colors'
import {FilterSpecification,LayerSpecification } from '@maplibre/maplibre-gl-style-spec'
export function colorHighlightedLayer(layer) {
export function colorHighlightedLayer(layer: LayerSpecification) {
if(!layer || layer.type === 'background' || layer.type === 'raster') return null
function changeLayer(l) {
function changeLayer(l: LayerSpecification & {filter?: FilterSpecification}) {
if(l.type === 'circle') {
l.paint['circle-radius'] = 3
l.paint!['circle-radius'] = 3
} else if(l.type === 'line') {
l.paint['line-width'] = 2
l.paint!['line-width'] = 2
}
if(layer.filter) {
if("filter" in layer) {
l.filter = layer.filter
} else {
delete l['filter']
@@ -21,8 +24,7 @@ export function colorHighlightedLayer(layer) {
}
const sourceLayerId = layer['source-layer'] || ''
const color = colors.brightColor(sourceLayerId, 1)
const layers = []
const color = colors.brightColor(sourceLayerId, 1);
if(layer.type === "fill" || layer.type === 'fill-extrusion') {
return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer['source-layer']))

View File

@@ -1,17 +1,18 @@
import {latest} from '@maplibre/maplibre-gl-style-spec'
import { LayerSpecification } from 'maplibre-gl'
export function changeType(layer, newType) {
const changedPaintProps = { ...layer.paint }
export function changeType(layer: LayerSpecification, newType: string) {
const changedPaintProps: LayerSpecification["paint"] = { ...layer.paint }
Object.keys(changedPaintProps).forEach(propertyName => {
if(!(propertyName in latest['paint_' + newType])) {
delete changedPaintProps[propertyName]
delete changedPaintProps[propertyName as keyof LayerSpecification["paint"]]
}
})
const changedLayoutProps = { ...layer.layout }
const changedLayoutProps: LayerSpecification["layout"] = { ...layer.layout }
Object.keys(changedLayoutProps).forEach(propertyName => {
if(!(propertyName in latest['layout_' + newType])) {
delete changedLayoutProps[propertyName]
delete changedLayoutProps[propertyName as keyof LayerSpecification["layout"]]
}
})
@@ -26,15 +27,15 @@ export function changeType(layer, newType) {
/** A {@property} in either the paint our layout {@group} has changed
* to a {@newValue}.
*/
export function changeProperty(layer, group, property, newValue) {
export function changeProperty(layer: LayerSpecification, group: keyof LayerSpecification, property: string, newValue: any) {
// Remove the property if undefined
if(newValue === undefined) {
if(group) {
const newLayer = {
const newLayer: any = {
...layer,
// Change object so the diff works in ./src/components/map/MaplibreGlMap.jsx
[group]: {
...layer[group]
...layer[group] as any
}
};
delete newLayer[group][property];
@@ -45,7 +46,7 @@ export function changeProperty(layer, group, property, newValue) {
}
return newLayer;
} else {
const newLayer = {
const newLayer: any = {
...layer
};
delete newLayer[property];
@@ -57,7 +58,7 @@ export function changeProperty(layer, group, property, newValue) {
return {
...layer,
[group]: {
...layer[group],
...layer[group] as any,
[property]: newValue
}
}

View File

@@ -1,10 +1,22 @@
import throttle from 'lodash.throttle'
import isEqual from 'lodash.isequal'
import { Map } from 'maplibre-gl';
export type LayerWatcherOptions = {
onSourcesChange?: (sources: { [sourceId: string]: string[] }) => void;
onVectorLayersChange?: (vectorLayers: { [vectorLayerId: string]: { [propertyName: string]: { [propertyValue: string]: {} } } }) => void;
}
/** Listens to map events to build up a store of available vector
* layers contained in the tiles */
export default class LayerWatcher {
constructor(opts = {}) {
onSourcesChange: (sources: { [sourceId: string]: string[] }) => void;
onVectorLayersChange: (vectorLayers: { [vectorLayerId: string]: { [propertyName: string]: { [propertyValue: string]: {} } } }) => void;
throttledAnalyzeVectorLayerFields: (map: any) => void;
_sources: { [sourceId: string]: string[] };
_vectorLayers: { [vectorLayerId: string]: { [propertyName: string]: { [propertyValue: string]: {} } } };
constructor(opts: LayerWatcherOptions = {}) {
this.onSourcesChange = opts.onSourcesChange || (() => {})
this.onVectorLayersChange = opts.onVectorLayersChange || (() => {})
@@ -17,13 +29,13 @@ export default class LayerWatcher {
this.throttledAnalyzeVectorLayerFields = throttle(this.analyzeVectorLayerFields, 5000)
}
analyzeMap(map) {
analyzeMap(map: Map) {
const previousSources = { ...this._sources }
Object.keys(map.style.sourceCaches).forEach(sourceId => {
//NOTE: This heavily depends on the internal API of Maplibre GL
//so this breaks between Maplibre GL JS releases
this._sources[sourceId] = map.style.sourceCaches[sourceId]._source.vectorLayerIds
this._sources[sourceId] = map.style.sourceCaches[sourceId]._source.vectorLayerIds as string[];
})
if(!isEqual(previousSources, this._sources)) {
@@ -33,14 +45,14 @@ export default class LayerWatcher {
this.throttledAnalyzeVectorLayerFields(map)
}
analyzeVectorLayerFields(map) {
analyzeVectorLayerFields(map: Map) {
const previousVectorLayers = { ...this._vectorLayers }
Object.keys(this._sources).forEach(sourceId => {
(this._sources[sourceId] || []).forEach(vectorLayerId => {
const knownProperties = this._vectorLayers[vectorLayerId] || {}
const params = { sourceLayer: vectorLayerId }
map.querySourceFeatures(sourceId, params).forEach(feature => {
map.querySourceFeatures(sourceId, params as any).forEach(feature => {
Object.keys(feature.properties).forEach(propertyName => {
const knownPropertyValues = knownProperties[propertyName] || {}
knownPropertyValues[feature.properties[propertyName]] = {}

View File

@@ -1,3 +1,3 @@
import MapLibreGl from "maplibre-gl"
MapLibreGl.setRTLTextPlugin('https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js');
MapLibreGl.setRTLTextPlugin('https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.2.3/mapbox-gl-rtl-text.min.js', () => {});

View File

@@ -1,6 +1,6 @@
import npmurl from 'url'
function loadJSON(url, defaultValue, cb) {
function loadJSON(url: string, defaultValue: any, cb: (...args: any[]) => void) {
fetch(url, {
mode: 'cors',
credentials: "same-origin"
@@ -17,7 +17,7 @@ function loadJSON(url, defaultValue, cb) {
})
}
export function downloadGlyphsMetadata(urlTemplate, cb) {
export function downloadGlyphsMetadata(urlTemplate: string, cb: (...args: any[]) => void) {
if(!urlTemplate) return cb([])
// Special handling because Tileserver GL serves the fontstacks metadata differently
@@ -27,14 +27,14 @@ export function downloadGlyphsMetadata(urlTemplate, cb) {
if(urlObj.pathname === normPathPart) {
urlObj.pathname = '/fontstacks.json';
} else {
urlObj.pathname = urlObj.pathname.replace(normPathPart, '.json');
urlObj.pathname = urlObj.pathname!.replace(normPathPart, '.json');
}
let url = npmurl.format(urlObj);
loadJSON(url, [], cb)
}
export function downloadSpriteMetadata(baseUrl, cb) {
export function downloadSpriteMetadata(baseUrl: string, cb: (...args: any[]) => void) {
if(!baseUrl) return cb([])
const url = baseUrl + '.json'
loadJSON(url, {}, glyphs => cb(Object.keys(glyphs)))

View File

@@ -1,4 +1,10 @@
import type {StyleSpecification} from "@maplibre/maplibre-gl-style-spec";
export class RevisionStore {
revisions: StyleSpecification[];
currentIdx: number;
constructor(initialRevisions=[]) {
this.revisions = initialRevisions
this.currentIdx = initialRevisions.length - 1
@@ -12,7 +18,7 @@ export class RevisionStore {
return this.revisions[this.currentIdx]
}
addRevision(revision) {
addRevision(revision: StyleSpecification) {
//TODO: compare new revision style id with old ones
//and ensure that it is always the same id
this.revisions.push(revision)
@@ -21,15 +27,15 @@ export class RevisionStore {
undo() {
if(this.currentIdx > 0) {
this.currentIdx--
this.currentIdx--;
}
return this.current
return this.current;
}
redo() {
if(this.currentIdx < this.revisions.length - 1) {
this.currentIdx++
}
return this.current
return this.current;
}
}

View File

@@ -1,25 +0,0 @@
export function deleteSource(mapStyle, sourceId) {
const remainingSources = { ...mapStyle.sources}
delete remainingSources[sourceId]
return {
...mapStyle,
sources: remainingSources
}
}
export function addSource(mapStyle, sourceId, source) {
return changeSource(mapStyle, sourceId, source)
}
export function changeSource(mapStyle, sourceId, source) {
const changedSources = {
...mapStyle.sources,
[sourceId]: source
}
return {
...mapStyle,
sources: changedSources
}
}

27
src/libs/source.ts Normal file
View File

@@ -0,0 +1,27 @@
import type {StyleSpecification, SourceSpecification} from "@maplibre/maplibre-gl-style-spec";
export function deleteSource(mapStyle: StyleSpecification, sourceId: string) {
const remainingSources = { ...mapStyle.sources}
delete remainingSources[sourceId]
return {
...mapStyle,
sources: remainingSources
}
}
export function addSource(mapStyle: StyleSpecification, sourceId: string, source: SourceSpecification) {
return changeSource(mapStyle, sourceId, source)
}
export function changeSource(mapStyle: StyleSpecification, sourceId: string, source: SourceSpecification) {
const changedSources = {
...mapStyle.sources,
[sourceId]: source
}
return {
...mapStyle,
sources: changedSources
}
}

View File

@@ -1,4 +1,4 @@
import {derefLayers} from '@maplibre/maplibre-gl-style-spec'
import {derefLayers, StyleSpecification, LayerSpecification} from '@maplibre/maplibre-gl-style-spec'
import tokens from '../config/tokens.json'
// Empty style is always used if no style could be restored or fetched
@@ -9,18 +9,20 @@ const emptyStyle = ensureStyleValidity({
})
function generateId() {
return Math.random().toString(36).substr(2, 9)
return Math.random().toString(36).substring(2, 9)
}
function ensureHasId(style) {
if('id' in style) return style
style.id = generateId()
return style
function ensureHasId(style: StyleSpecification & { id?: string }): StyleSpecification & { id: string } {
if(!('id' in style) || !style.id) {
style.id = generateId();
return style as StyleSpecification & { id: string };
}
return style as StyleSpecification & { id: string };
}
function ensureHasNoInteractive(style) {
function ensureHasNoInteractive(style: StyleSpecification & {id: string}) {
const changedLayers = style.layers.map(layer => {
const changedLayer = { ...layer }
const changedLayer: LayerSpecification & { interactive?: any } = { ...layer }
delete changedLayer.interactive
return changedLayer
})
@@ -31,18 +33,18 @@ function ensureHasNoInteractive(style) {
}
}
function ensureHasNoRefs(style) {
function ensureHasNoRefs(style: StyleSpecification & {id: string}) {
return {
...style,
layers: derefLayers(style.layers)
}
}
function ensureStyleValidity(style) {
function ensureStyleValidity(style: StyleSpecification): StyleSpecification & { id: string } {
return ensureHasNoInteractive(ensureHasNoRefs(ensureHasId(style)))
}
function indexOfLayer(layers, layerId) {
function indexOfLayer(layers: LayerSpecification[], layerId: string) {
for (let i = 0; i < layers.length; i++) {
if(layers[i].id === layerId) {
return i
@@ -51,25 +53,25 @@ function indexOfLayer(layers, layerId) {
return null
}
function getAccessToken(sourceName, mapStyle, opts) {
function getAccessToken(sourceName: string, mapStyle: StyleSpecification, opts: {allowFallback?: boolean}) {
if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") {
sourceName = "thunderforest"
}
const metadata = mapStyle.metadata || {}
const metadata = mapStyle.metadata || {} as any;
let accessToken = metadata[`maputnik:${sourceName}_access_token`]
if(opts.allowFallback && !accessToken) {
accessToken = tokens[sourceName]
accessToken = tokens[sourceName as keyof typeof tokens]
}
return accessToken;
}
function replaceSourceAccessToken(mapStyle, sourceName, opts={}) {
function replaceSourceAccessToken(mapStyle: StyleSpecification, sourceName: string, opts={}) {
const source = mapStyle.sources[sourceName]
if(!source) return mapStyle
if(!source.hasOwnProperty("url")) return mapStyle
if(!("url" in source) || !source.url) return mapStyle
const accessToken = getAccessToken(sourceName, mapStyle, opts)
@@ -92,7 +94,7 @@ function replaceSourceAccessToken(mapStyle, sourceName, opts={}) {
return changedStyle
}
function replaceAccessTokens(mapStyle, opts={}) {
function replaceAccessTokens(mapStyle: StyleSpecification, opts={}) {
let changedStyle = mapStyle
Object.keys(mapStyle.sources).forEach((sourceName) => {
@@ -112,9 +114,9 @@ function replaceAccessTokens(mapStyle, opts={}) {
return changedStyle
}
function stripAccessTokens(mapStyle) {
function stripAccessTokens(mapStyle: StyleSpecification) {
const changedMetadata = {
...mapStyle.metadata
...mapStyle.metadata as any
};
delete changedMetadata['maputnik:openmaptiles_access_token'];
return {

View File

@@ -1,6 +1,7 @@
import style from './style.js'
import { loadStyleUrl } from './urlopen'
import style from './style'
import {loadStyleUrl} from './urlopen'
import publicSources from '../config/styles.json'
import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
const storagePrefix = "maputnik"
const stylePrefix = 'style'
@@ -12,7 +13,7 @@ const storageKeys = {
const defaultStyleUrl = publicSources[0].url
// Fetch a default style via URL and return it or a fallback style via callback
export function loadDefaultStyle(cb) {
export function loadDefaultStyle(cb: (...args: any[]) => void) {
loadStyleUrl(defaultStyleUrl, cb)
}
@@ -21,20 +22,20 @@ function loadStoredStyles() {
const styles = []
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i)
if(isStyleKey(key)) {
styles.push(fromKey(key))
if(isStyleKey(key!)) {
styles.push(fromKey(key!))
}
}
return styles
}
function isStyleKey(key) {
function isStyleKey(key: string) {
const parts = key.split(":")
return parts.length === 3 && parts[0] === storagePrefix && parts[1] === stylePrefix
}
// Load style id from key
function fromKey(key) {
function fromKey(key: string) {
if(!isStyleKey(key)) {
throw "Key is not a valid style key"
}
@@ -45,26 +46,31 @@ function fromKey(key) {
}
// Calculate key that identifies the style with a version
function styleKey(styleId) {
function styleKey(styleId: string) {
return [storagePrefix, stylePrefix, styleId].join(":")
}
// Manages many possible styles that are stored in the local storage
export class StyleStore {
/**
* List of style ids
*/
mapStyles: string[];
// Tile store will load all items from local storage and
// assume they do not change will working on it
constructor() {
this.mapStyles = loadStoredStyles()
this.mapStyles = loadStoredStyles();
}
init(cb) {
init(cb: (...args: any[]) => void) {
cb(null)
}
// Delete entire style history
purge() {
for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i)
const key = window.localStorage.key(i) as string;
if(key.startsWith(storagePrefix)) {
window.localStorage.removeItem(key)
}
@@ -72,9 +78,9 @@ export class StyleStore {
}
// Find the last edited style
latestStyle(cb) {
latestStyle(cb: (...args: any[]) => void) {
if(this.mapStyles.length === 0) return loadDefaultStyle(cb)
const styleId = window.localStorage.getItem(storageKeys.latest)
const styleId = window.localStorage.getItem(storageKeys.latest) as string;
const styleItem = window.localStorage.getItem(styleKey(styleId))
if(styleItem) return cb(JSON.parse(styleItem))
@@ -82,7 +88,7 @@ export class StyleStore {
}
// Save current style replacing previous version
save(mapStyle) {
save(mapStyle: StyleSpecification & { id: string }) {
mapStyle = style.ensureStyleValidity(mapStyle)
const key = styleKey(mapStyle.id)
window.localStorage.setItem(key, JSON.stringify(mapStyle))