Compare commits

...

2 Commits

Author SHA1 Message Date
Harel M 3bf0e510e6 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.
2023-12-21 00:07:53 +02:00
Harel M e8d07fa694 Initial migration to typescript (#845)
This migrates some utilities classes from javascript to typescript.
Nothing special about the migration, mainly added types and made sure
the code adheres to the strict mode.

I have picked some small files so git doesn't think they are the same
since they are "very different".
I hope that in later PRs, when migrating lager files this won't be an
issues.
2023-12-20 22:56:33 +02:00
30 changed files with 225 additions and 164 deletions
+26
View File
@@ -68,6 +68,8 @@
"@storybook/react-vite": "^7.6.5", "@storybook/react-vite": "^7.6.5",
"@storybook/theming": "^7.6.5", "@storybook/theming": "^7.6.5",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.9",
"@types/react": "^16.14.52", "@types/react": "^16.14.52",
"@types/react-dom": "^16.9.24", "@types/react-dom": "^16.9.24",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
@@ -4695,6 +4697,30 @@
"integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
"dev": true "dev": true
}, },
"node_modules/@types/lodash": {
"version": "4.14.202",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
"dev": true
},
"node_modules/@types/lodash.isequal": {
"version": "4.5.8",
"resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz",
"integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/lodash.throttle": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz",
"integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==",
"dev": true,
"dependencies": {
"@types/lodash": "*"
}
},
"node_modules/@types/mapbox__point-geometry": { "node_modules/@types/mapbox__point-geometry": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/mapbox__point-geometry/-/mapbox__point-geometry-0.1.4.tgz",
+2
View File
@@ -97,6 +97,8 @@
"@storybook/react-vite": "^7.6.5", "@storybook/react-vite": "^7.6.5",
"@storybook/theming": "^7.6.5", "@storybook/theming": "^7.6.5",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/lodash.isequal": "^4.5.8",
"@types/lodash.throttle": "^4.1.9",
"@types/react": "^16.14.52", "@types/react": "^16.14.52",
"@types/react-dom": "^16.9.24", "@types/react-dom": "^16.9.24",
"@types/uuid": "^9.0.7", "@types/uuid": "^9.0.7",
+2 -2
View File
@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Collapse as ReactCollapse } from 'react-collapse' import { Collapse as ReactCollapse } from 'react-collapse'
import accessibility from '../../libs/accessibility' import {reducedMotionEnabled} from '../../libs/accessibility'
export default class Collapse extends React.Component { export default class Collapse extends React.Component {
@@ -15,7 +15,7 @@ export default class Collapse extends React.Component {
} }
render() { render() {
if (accessibility.reducedMotionEnabled()) { if (reducedMotionEnabled()) {
return ( return (
<div style={{display: this.props.isActive ? "block" : "none"}}> <div style={{display: this.props.isActive ? "block" : "none"}}>
{this.props.children} {this.props.children}
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { combiningFilterOps } from '../libs/filterops.js' import {combiningFilterOps} from '../libs/filterops'
import {mdiTableRowPlusAfter} from '@mdi/js'; import {mdiTableRowPlusAfter} from '@mdi/js';
import {isEqual} from 'lodash'; import {isEqual} from 'lodash';
+1 -1
View File
@@ -9,7 +9,7 @@ import InputUrl from './InputUrl'
import {MdFileUpload} from 'react-icons/md' import {MdFileUpload} from 'react-icons/md'
import {MdAddCircleOutline} 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' import publicStyles from '../config/styles.json'
class PublicStyle extends React.Component { class PublicStyle extends React.Component {
+1 -1
View File
@@ -1,7 +1,7 @@
import React from 'react' import React from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { otherFilterOps } from '../libs/filterops.js' import {otherFilterOps} from '../libs/filterops'
import InputString from './InputString' import InputString from './InputString'
import InputAutocomplete from './InputAutocomplete' import InputAutocomplete from './InputAutocomplete'
import InputSelect from './InputSelect' import InputSelect from './InputSelect'
-12
View File
@@ -1,12 +0,0 @@
import throttle from 'lodash.throttle'
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
const reducedMotionEnabled = throttle(() => {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
}, 3000);
export default {
reducedMotionEnabled
}
+8
View File
@@ -0,0 +1,8 @@
import throttle from 'lodash.throttle'
export default {
// Throttle for 3 seconds so when a user enables it they don't have to refresh the page.
reducedMotionEnabled: throttle(() => {
return window.matchMedia("(prefers-reduced-motion: reduce)").matches
}, 3000)
}
+17 -6
View File
@@ -1,10 +1,21 @@
import style from './style.js' 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' import ReconnectingWebSocket from 'reconnecting-websocket'
export type ApiStyleStoreOptions = {
port?: string
host?: string
onLocalStyleChange?: (style: any) => void
}
export class ApiStyleStore { export class ApiStyleStore {
constructor(opts) { localUrl: string;
websocketUrl: string;
latestStyleId: string | undefined = undefined;
onLocalStyleChange: (style: any) => void;
constructor(opts: ApiStyleStoreOptions) {
this.onLocalStyleChange = opts.onLocalStyleChange || (() => {}) this.onLocalStyleChange = opts.onLocalStyleChange || (() => {})
const port = opts.port || '8000' const port = opts.port || '8000'
const host = opts.host || 'localhost' const host = opts.host || 'localhost'
@@ -13,7 +24,7 @@ export class ApiStyleStore {
this.init = this.init.bind(this) this.init = this.init.bind(this)
} }
init(cb) { init(cb: (...args: any[]) => void) {
fetch(this.localUrl + '/styles', { fetch(this.localUrl + '/styles', {
mode: 'cors', mode: 'cors',
}) })
@@ -26,7 +37,7 @@ export class ApiStyleStore {
this.notifyLocalChanges() this.notifyLocalChanges()
cb(null) cb(null)
}) })
.catch(function(e) { .catch(() => {
cb(new Error('Can not connect to style API')) 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) { if(this.latestStyleId) {
fetch(this.localUrl + '/styles/' + this.latestStyleId, { fetch(this.localUrl + '/styles/' + this.latestStyleId, {
mode: 'cors', mode: 'cors',
@@ -64,7 +75,7 @@ export class ApiStyleStore {
} }
// Save current style replacing previous version // Save current style replacing previous version
save(mapStyle) { save(mapStyle: StyleSpecification & { id: string }) {
const styleJSON = format( const styleJSON = format(
style.stripAccessTokens( style.stripAccessTokens(
style.replaceAccessTokens(mapStyle) style.replaceAccessTokens(mapStyle)
-13
View File
@@ -1,13 +0,0 @@
import {diff} from '@maplibre/maplibre-gl-style-spec'
export function diffMessages(beforeStyle, afterStyle) {
const changes = diff(beforeStyle, afterStyle)
return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' '))
}
export function undoMessages(beforeStyle, afterStyle) {
return diffMessages(beforeStyle, afterStyle).map(m => 'Undo ' + m)
}
export function redoMessages(beforeStyle, afterStyle) {
return diffMessages(beforeStyle, afterStyle).map(m => 'Redo ' + m)
}
+13
View File
@@ -0,0 +1,13 @@
import {StyleSpecification, diff} from '@maplibre/maplibre-gl-style-spec'
function diffMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
const changes = diff(beforeStyle, afterStyle)
return changes.map(cmd => cmd.command + ' ' + cmd.args.join(' '))
}
export function undoMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
return diffMessages(beforeStyle, afterStyle).map(m => 'Undo ' + m)
}
export function redoMessages(beforeStyle: StyleSpecification, afterStyle: StyleSpecification) {
return diffMessages(beforeStyle, afterStyle).map(m => 'Redo ' + m)
}
@@ -3,7 +3,7 @@
*/ */
let REF = 0; let REF = 0;
export default function(prefix="") { export default function generateUniqueId(prefix="") {
REF++; REF++;
return prefix+REF; return prefix+REF;
} }
-6
View File
@@ -1,6 +0,0 @@
import {latest} from '@maplibre/maplibre-gl-style-spec'
export const combiningFilterOps = ['all', 'any', 'none']
export const setFilterOps = ['in', '!in']
export const otherFilterOps = Object
.keys(latest.filter_operator.values)
.filter(op => combiningFilterOps.indexOf(op) < 0)
+7
View File
@@ -0,0 +1,7 @@
import {latest} from '@maplibre/maplibre-gl-style-spec'
export const combiningFilterOps = ['all', 'any', 'none'];
export const setFilterOps = ['in', '!in'];
export const otherFilterOps = Object
.keys(latest.filter_operator.values)
.filter(op => combiningFilterOps.indexOf(op) < 0);
@@ -1,17 +1,20 @@
// @ts-ignore
import stylegen from 'mapbox-gl-inspect/lib/stylegen' import stylegen from 'mapbox-gl-inspect/lib/stylegen'
// @ts-ignore
import colors from 'mapbox-gl-inspect/lib/colors' 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 if(!layer || layer.type === 'background' || layer.type === 'raster') return null
function changeLayer(l) { function changeLayer(l: LayerSpecification & {filter?: FilterSpecification}) {
if(l.type === 'circle') { if(l.type === 'circle') {
l.paint['circle-radius'] = 3 l.paint!['circle-radius'] = 3
} else if(l.type === 'line') { } 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 l.filter = layer.filter
} else { } else {
delete l['filter'] delete l['filter']
@@ -21,8 +24,7 @@ export function colorHighlightedLayer(layer) {
} }
const sourceLayerId = layer['source-layer'] || '' const sourceLayerId = layer['source-layer'] || ''
const color = colors.brightColor(sourceLayerId, 1) const color = colors.brightColor(sourceLayerId, 1);
const layers = []
if(layer.type === "fill" || layer.type === 'fill-extrusion') { if(layer.type === "fill" || layer.type === 'fill-extrusion') {
return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer['source-layer'])) return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer['source-layer']))
+11 -10
View File
@@ -1,17 +1,18 @@
import {latest} from '@maplibre/maplibre-gl-style-spec' import {latest} from '@maplibre/maplibre-gl-style-spec'
import { LayerSpecification } from 'maplibre-gl'
export function changeType(layer, newType) { export function changeType(layer: LayerSpecification, newType: string) {
const changedPaintProps = { ...layer.paint } const changedPaintProps: LayerSpecification["paint"] = { ...layer.paint }
Object.keys(changedPaintProps).forEach(propertyName => { Object.keys(changedPaintProps).forEach(propertyName => {
if(!(propertyName in latest['paint_' + newType])) { 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 => { Object.keys(changedLayoutProps).forEach(propertyName => {
if(!(propertyName in latest['layout_' + newType])) { 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 /** A {@property} in either the paint our layout {@group} has changed
* to a {@newValue}. * 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 // Remove the property if undefined
if(newValue === undefined) { if(newValue === undefined) {
if(group) { if(group) {
const newLayer = { const newLayer: any = {
...layer, ...layer,
// Change object so the diff works in ./src/components/map/MaplibreGlMap.jsx // Change object so the diff works in ./src/components/map/MaplibreGlMap.jsx
[group]: { [group]: {
...layer[group] ...layer[group] as any
} }
}; };
delete newLayer[group][property]; delete newLayer[group][property];
@@ -45,7 +46,7 @@ export function changeProperty(layer, group, property, newValue) {
} }
return newLayer; return newLayer;
} else { } else {
const newLayer = { const newLayer: any = {
...layer ...layer
}; };
delete newLayer[property]; delete newLayer[property];
@@ -57,7 +58,7 @@ export function changeProperty(layer, group, property, newValue) {
return { return {
...layer, ...layer,
[group]: { [group]: {
...layer[group], ...layer[group] as any,
[property]: newValue [property]: newValue
} }
} }
@@ -1,10 +1,22 @@
import throttle from 'lodash.throttle' import throttle from 'lodash.throttle'
import isEqual from 'lodash.isequal' 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 /** Listens to map events to build up a store of available vector
* layers contained in the tiles */ * layers contained in the tiles */
export default class LayerWatcher { 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.onSourcesChange = opts.onSourcesChange || (() => {})
this.onVectorLayersChange = opts.onVectorLayersChange || (() => {}) this.onVectorLayersChange = opts.onVectorLayersChange || (() => {})
@@ -17,13 +29,13 @@ export default class LayerWatcher {
this.throttledAnalyzeVectorLayerFields = throttle(this.analyzeVectorLayerFields, 5000) this.throttledAnalyzeVectorLayerFields = throttle(this.analyzeVectorLayerFields, 5000)
} }
analyzeMap(map) { analyzeMap(map: Map) {
const previousSources = { ...this._sources } const previousSources = { ...this._sources }
Object.keys(map.style.sourceCaches).forEach(sourceId => { Object.keys(map.style.sourceCaches).forEach(sourceId => {
//NOTE: This heavily depends on the internal API of Maplibre GL //NOTE: This heavily depends on the internal API of Maplibre GL
//so this breaks between Maplibre GL JS releases //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)) { if(!isEqual(previousSources, this._sources)) {
@@ -33,14 +45,14 @@ export default class LayerWatcher {
this.throttledAnalyzeVectorLayerFields(map) this.throttledAnalyzeVectorLayerFields(map)
} }
analyzeVectorLayerFields(map) { analyzeVectorLayerFields(map: Map) {
const previousVectorLayers = { ...this._vectorLayers } const previousVectorLayers = { ...this._vectorLayers }
Object.keys(this._sources).forEach(sourceId => { Object.keys(this._sources).forEach(sourceId => {
(this._sources[sourceId] || []).forEach(vectorLayerId => { (this._sources[sourceId] || []).forEach(vectorLayerId => {
const knownProperties = this._vectorLayers[vectorLayerId] || {} const knownProperties = this._vectorLayers[vectorLayerId] || {}
const params = { sourceLayer: 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 => { Object.keys(feature.properties).forEach(propertyName => {
const knownPropertyValues = knownProperties[propertyName] || {} const knownPropertyValues = knownProperties[propertyName] || {}
knownPropertyValues[feature.properties[propertyName]] = {} knownPropertyValues[feature.properties[propertyName]] = {}
@@ -1,3 +1,3 @@
import MapLibreGl from "maplibre-gl" 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', () => {});
@@ -1,6 +1,6 @@
import npmurl from 'url' import npmurl from 'url'
function loadJSON(url, defaultValue, cb) { function loadJSON(url: string, defaultValue: any, cb: (...args: any[]) => void) {
fetch(url, { fetch(url, {
mode: 'cors', mode: 'cors',
credentials: "same-origin" 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([]) if(!urlTemplate) return cb([])
// Special handling because Tileserver GL serves the fontstacks metadata differently // Special handling because Tileserver GL serves the fontstacks metadata differently
@@ -27,14 +27,14 @@ export function downloadGlyphsMetadata(urlTemplate, cb) {
if(urlObj.pathname === normPathPart) { if(urlObj.pathname === normPathPart) {
urlObj.pathname = '/fontstacks.json'; urlObj.pathname = '/fontstacks.json';
} else { } else {
urlObj.pathname = urlObj.pathname.replace(normPathPart, '.json'); urlObj.pathname = urlObj.pathname!.replace(normPathPart, '.json');
} }
let url = npmurl.format(urlObj); let url = npmurl.format(urlObj);
loadJSON(url, [], cb) loadJSON(url, [], cb)
} }
export function downloadSpriteMetadata(baseUrl, cb) { export function downloadSpriteMetadata(baseUrl: string, cb: (...args: any[]) => void) {
if(!baseUrl) return cb([]) if(!baseUrl) return cb([])
const url = baseUrl + '.json' const url = baseUrl + '.json'
loadJSON(url, {}, glyphs => cb(Object.keys(glyphs))) loadJSON(url, {}, glyphs => cb(Object.keys(glyphs)))
-12
View File
@@ -1,12 +0,0 @@
function asBool(queryObj, key) {
if(queryObj.hasOwnProperty(key)) {
return !queryObj[key].match(/^false|0$/);
}
else {
return false;
}
}
module.exports = {
asBool
}
@@ -1,4 +1,10 @@
import type {StyleSpecification} from "@maplibre/maplibre-gl-style-spec";
export class RevisionStore { export class RevisionStore {
revisions: StyleSpecification[];
currentIdx: number;
constructor(initialRevisions=[]) { constructor(initialRevisions=[]) {
this.revisions = initialRevisions this.revisions = initialRevisions
this.currentIdx = initialRevisions.length - 1 this.currentIdx = initialRevisions.length - 1
@@ -12,7 +18,7 @@ export class RevisionStore {
return this.revisions[this.currentIdx] return this.revisions[this.currentIdx]
} }
addRevision(revision) { addRevision(revision: StyleSpecification) {
//TODO: compare new revision style id with old ones //TODO: compare new revision style id with old ones
//and ensure that it is always the same id //and ensure that it is always the same id
this.revisions.push(revision) this.revisions.push(revision)
@@ -21,15 +27,15 @@ export class RevisionStore {
undo() { undo() {
if(this.currentIdx > 0) { if(this.currentIdx > 0) {
this.currentIdx-- this.currentIdx--;
} }
return this.current return this.current;
} }
redo() { redo() {
if(this.currentIdx < this.revisions.length - 1) { if(this.currentIdx < this.revisions.length - 1) {
this.currentIdx++ this.currentIdx++
} }
return this.current return this.current;
} }
} }
-14
View File
@@ -1,14 +0,0 @@
export default function(a, b) {
a = parseFloat(a);
b = parseFloat(b);
if(a < b) {
return -1
}
else if(a > b) {
return 1
}
else {
return 0;
}
}
+14
View File
@@ -0,0 +1,14 @@
export default function(num1: string | number, num2: string| number) {
const a = +num1;
const b = +num2;
if(a < b) {
return -1
}
else if(a > b) {
return 1
}
else {
return 0;
}
}
-25
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
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
}
}
+21 -19
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' import tokens from '../config/tokens.json'
// Empty style is always used if no style could be restored or fetched // Empty style is always used if no style could be restored or fetched
@@ -9,18 +9,20 @@ const emptyStyle = ensureStyleValidity({
}) })
function generateId() { function generateId() {
return Math.random().toString(36).substr(2, 9) return Math.random().toString(36).substring(2, 9)
} }
function ensureHasId(style) { function ensureHasId(style: StyleSpecification & { id?: string }): StyleSpecification & { id: string } {
if('id' in style) return style if(!('id' in style) || !style.id) {
style.id = generateId() style.id = generateId();
return style 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 changedLayers = style.layers.map(layer => {
const changedLayer = { ...layer } const changedLayer: LayerSpecification & { interactive?: any } = { ...layer }
delete changedLayer.interactive delete changedLayer.interactive
return changedLayer return changedLayer
}) })
@@ -31,18 +33,18 @@ function ensureHasNoInteractive(style) {
} }
} }
function ensureHasNoRefs(style) { function ensureHasNoRefs(style: StyleSpecification & {id: string}) {
return { return {
...style, ...style,
layers: derefLayers(style.layers) layers: derefLayers(style.layers)
} }
} }
function ensureStyleValidity(style) { function ensureStyleValidity(style: StyleSpecification): StyleSpecification & { id: string } {
return ensureHasNoInteractive(ensureHasNoRefs(ensureHasId(style))) return ensureHasNoInteractive(ensureHasNoRefs(ensureHasId(style)))
} }
function indexOfLayer(layers, layerId) { function indexOfLayer(layers: LayerSpecification[], layerId: string) {
for (let i = 0; i < layers.length; i++) { for (let i = 0; i < layers.length; i++) {
if(layers[i].id === layerId) { if(layers[i].id === layerId) {
return i return i
@@ -51,25 +53,25 @@ function indexOfLayer(layers, layerId) {
return null return null
} }
function getAccessToken(sourceName, mapStyle, opts) { function getAccessToken(sourceName: string, mapStyle: StyleSpecification, opts: {allowFallback?: boolean}) {
if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") { if(sourceName === "thunderforest_transport" || sourceName === "thunderforest_outdoors") {
sourceName = "thunderforest" sourceName = "thunderforest"
} }
const metadata = mapStyle.metadata || {} const metadata = mapStyle.metadata || {} as any;
let accessToken = metadata[`maputnik:${sourceName}_access_token`] let accessToken = metadata[`maputnik:${sourceName}_access_token`]
if(opts.allowFallback && !accessToken) { if(opts.allowFallback && !accessToken) {
accessToken = tokens[sourceName] accessToken = tokens[sourceName as keyof typeof tokens]
} }
return accessToken; return accessToken;
} }
function replaceSourceAccessToken(mapStyle, sourceName, opts={}) { function replaceSourceAccessToken(mapStyle: StyleSpecification, sourceName: string, opts={}) {
const source = mapStyle.sources[sourceName] const source = mapStyle.sources[sourceName]
if(!source) return mapStyle if(!source) return mapStyle
if(!source.hasOwnProperty("url")) return mapStyle if(!("url" in source) || !source.url) return mapStyle
const accessToken = getAccessToken(sourceName, mapStyle, opts) const accessToken = getAccessToken(sourceName, mapStyle, opts)
@@ -92,7 +94,7 @@ function replaceSourceAccessToken(mapStyle, sourceName, opts={}) {
return changedStyle return changedStyle
} }
function replaceAccessTokens(mapStyle, opts={}) { function replaceAccessTokens(mapStyle: StyleSpecification, opts={}) {
let changedStyle = mapStyle let changedStyle = mapStyle
Object.keys(mapStyle.sources).forEach((sourceName) => { Object.keys(mapStyle.sources).forEach((sourceName) => {
@@ -112,9 +114,9 @@ function replaceAccessTokens(mapStyle, opts={}) {
return changedStyle return changedStyle
} }
function stripAccessTokens(mapStyle) { function stripAccessTokens(mapStyle: StyleSpecification) {
const changedMetadata = { const changedMetadata = {
...mapStyle.metadata ...mapStyle.metadata as any
}; };
delete changedMetadata['maputnik:openmaptiles_access_token']; delete changedMetadata['maputnik:openmaptiles_access_token'];
return { return {
View File
@@ -1,6 +1,7 @@
import style from './style.js' import style from './style'
import { loadStyleUrl } from './urlopen' import {loadStyleUrl} from './urlopen'
import publicSources from '../config/styles.json' import publicSources from '../config/styles.json'
import { StyleSpecification } from '@maplibre/maplibre-gl-style-spec'
const storagePrefix = "maputnik" const storagePrefix = "maputnik"
const stylePrefix = 'style' const stylePrefix = 'style'
@@ -12,7 +13,7 @@ const storageKeys = {
const defaultStyleUrl = publicSources[0].url const defaultStyleUrl = publicSources[0].url
// Fetch a default style via URL and return it or a fallback style via callback // 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) loadStyleUrl(defaultStyleUrl, cb)
} }
@@ -21,20 +22,20 @@ function loadStoredStyles() {
const styles = [] const styles = []
for (let i = 0; i < window.localStorage.length; i++) { for (let i = 0; i < window.localStorage.length; i++) {
const key = window.localStorage.key(i) const key = window.localStorage.key(i)
if(isStyleKey(key)) { if(isStyleKey(key!)) {
styles.push(fromKey(key)) styles.push(fromKey(key!))
} }
} }
return styles return styles
} }
function isStyleKey(key) { function isStyleKey(key: string) {
const parts = key.split(":") const parts = key.split(":")
return parts.length === 3 && parts[0] === storagePrefix && parts[1] === stylePrefix return parts.length === 3 && parts[0] === storagePrefix && parts[1] === stylePrefix
} }
// Load style id from key // Load style id from key
function fromKey(key) { function fromKey(key: string) {
if(!isStyleKey(key)) { if(!isStyleKey(key)) {
throw "Key is not a valid style 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 // Calculate key that identifies the style with a version
function styleKey(styleId) { function styleKey(styleId: string) {
return [storagePrefix, stylePrefix, styleId].join(":") return [storagePrefix, stylePrefix, styleId].join(":")
} }
// Manages many possible styles that are stored in the local storage // Manages many possible styles that are stored in the local storage
export class StyleStore { export class StyleStore {
/**
* List of style ids
*/
mapStyles: string[];
// Tile store will load all items from local storage and // Tile store will load all items from local storage and
// assume they do not change will working on it // assume they do not change will working on it
constructor() { constructor() {
this.mapStyles = loadStoredStyles() this.mapStyles = loadStoredStyles();
} }
init(cb) { init(cb: (...args: any[]) => void) {
cb(null) cb(null)
} }
// Delete entire style history // Delete entire style history
purge() { purge() {
for (let i = 0; i < window.localStorage.length; i++) { 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)) { if(key.startsWith(storagePrefix)) {
window.localStorage.removeItem(key) window.localStorage.removeItem(key)
} }
@@ -72,9 +78,9 @@ export class StyleStore {
} }
// Find the last edited style // Find the last edited style
latestStyle(cb) { latestStyle(cb: (...args: any[]) => void) {
if(this.mapStyles.length === 0) return loadDefaultStyle(cb) 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)) const styleItem = window.localStorage.getItem(styleKey(styleId))
if(styleItem) return cb(JSON.parse(styleItem)) if(styleItem) return cb(JSON.parse(styleItem))
@@ -82,7 +88,7 @@ export class StyleStore {
} }
// Save current style replacing previous version // Save current style replacing previous version
save(mapStyle) { save(mapStyle: StyleSpecification & { id: string }) {
mapStyle = style.ensureStyleValidity(mapStyle) mapStyle = style.ensureStyleValidity(mapStyle)
const key = styleKey(mapStyle.id) const key = styleKey(mapStyle.id)
window.localStorage.setItem(key, JSON.stringify(mapStyle)) window.localStorage.setItem(key, JSON.stringify(mapStyle))
@@ -1,5 +1,11 @@
import {Map} from 'maplibre-gl';
export default class ZoomControl { export default class ZoomControl {
onAdd(map) { _map: Map| undefined = undefined;
_container: HTMLDivElement | undefined = undefined;
_textEl: HTMLSpanElement | null = null;
onAdd(map: Map) {
this._map = map; this._map = map;
this._container = document.createElement('div'); this._container = document.createElement('div');
this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom'; this._container.className = 'maplibregl-ctrl maplibregl-ctrl-group maplibregl-ctrl-zoom';
@@ -15,17 +21,17 @@ export default class ZoomControl {
} }
updateZoomLevel() { updateZoomLevel() {
this._textEl.innerHTML = this._map.getZoom().toFixed(2); this._textEl!.innerHTML = this._map!.getZoom().toFixed(2);
} }
addEventListeners (){ addEventListeners (){
this._map.on('render', this.updateZoomLevel.bind(this) ); this._map!.on('render', () => this.updateZoomLevel());
this._map.on('zoomIn', this.updateZoomLevel.bind(this) ); this._map!.on('zoomIn', () => this.updateZoomLevel());
this._map.on('zoomOut', this.updateZoomLevel.bind(this) ); this._map!.on('zoomOut', () => this.updateZoomLevel());
} }
onRemove() { onRemove() {
this._container.parentNode.removeChild(this._container); this._container!.parentNode!.removeChild(this._container!);
this._map = undefined; this._map = undefined;
} }
} }