mirror of
https://github.com/maputnik/editor.git
synced 2025-12-26 08:00:01 +00:00
Move style and store initialization to mount method (#1351)
This is in order to reduce warnings in the console for React 19 usage. This removes the deprecated defaultProp and also move all the store initialization logic out of the App.tsx file, keeping it a lot more clean. It removes the `debug` flag from the supported urls along with the `localport` and `localhost`, which I'm not sure if and how they were ever used. The tests are using the `style` url, so I think it is covered in terms of tests. It also improves some typings along the project. It removes some callbacks from the code and moves to use promises. ## Launch Checklist - [x] Briefly describe the changes in this PR. - [x] Include before/after visuals or gifs if this PR includes visual changes. - [x] Write tests for all new functionality. - [ ] Add an entry to `CHANGELOG.md` under the `## main` section. Before: <img width="1263" height="439" alt="image" src="https://github.com/user-attachments/assets/1988c4f7-39de-4fd2-b6da-b4736abc0441" /> After: <img width="1263" height="203" alt="image" src="https://github.com/user-attachments/assets/28079e6d-9de7-40a1-9869-01a0876ca79f" /> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bart Louwers <bart.louwers@gmail.com>
This commit is contained in:
@@ -27,17 +27,15 @@ import ModalDebug from './ModalDebug'
|
||||
|
||||
import {downloadGlyphsMetadata, downloadSpriteMetadata} from '../libs/metadata'
|
||||
import style from '../libs/style'
|
||||
import { initialStyleUrl, loadStyleUrl, removeStyleQuerystring } from '../libs/urlopen'
|
||||
import { undoMessages, redoMessages } from '../libs/diffmessage'
|
||||
import { StyleStore } from '../libs/stylestore'
|
||||
import { ApiStyleStore } from '../libs/apistore'
|
||||
import { createStyleStore, type IStyleStore } from '../libs/store/style-store-factory'
|
||||
import { RevisionStore } from '../libs/revisions'
|
||||
import LayerWatcher from '../libs/layerwatcher'
|
||||
import tokens from '../config/tokens.json'
|
||||
import isEqual from 'lodash.isequal'
|
||||
import Debug from '../libs/debug'
|
||||
import { SortEnd } from 'react-sortable-hoc';
|
||||
import { MapOptions } from 'maplibre-gl';
|
||||
import { OnStyleChangedOpts, StyleSpecificationWithId } from '../libs/definitions'
|
||||
|
||||
// Buffer must be defined globally for @maplibre/maplibre-gl-style-spec validate() function to succeed.
|
||||
window.Buffer = buffer.Buffer;
|
||||
@@ -83,12 +81,6 @@ function updateRootSpec(spec: any, fieldName: string, newValues: any) {
|
||||
}
|
||||
}
|
||||
|
||||
type OnStyleChangedOpts = {
|
||||
save?: boolean
|
||||
addRevision?: boolean
|
||||
initialLoad?: boolean
|
||||
}
|
||||
|
||||
type MappedErrors = {
|
||||
message: string
|
||||
parsed?: {
|
||||
@@ -104,7 +96,7 @@ type MappedErrors = {
|
||||
type AppState = {
|
||||
errors: MappedErrors[],
|
||||
infos: string[],
|
||||
mapStyle: StyleSpecification & {id: string},
|
||||
mapStyle: StyleSpecificationWithId,
|
||||
dirtyMapStyle?: StyleSpecification,
|
||||
selectedLayerIndex: number,
|
||||
selectedLayerOriginalId?: string,
|
||||
@@ -140,25 +132,56 @@ type AppState = {
|
||||
|
||||
export default class App extends React.Component<any, AppState> {
|
||||
revisionStore: RevisionStore;
|
||||
styleStore: StyleStore | ApiStyleStore;
|
||||
styleStore: IStyleStore | null = null;
|
||||
layerWatcher: LayerWatcher;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props)
|
||||
|
||||
this.revisionStore = new RevisionStore()
|
||||
const params = new URLSearchParams(window.location.search.substring(1))
|
||||
let port = params.get("localport")
|
||||
if (port == null && (window.location.port !== "80" && window.location.port !== "443")) {
|
||||
port = window.location.port
|
||||
this.revisionStore = new RevisionStore();
|
||||
this.configureKeyboardShortcuts();
|
||||
|
||||
this.state = {
|
||||
errors: [],
|
||||
infos: [],
|
||||
mapStyle: style.emptyStyle,
|
||||
selectedLayerIndex: 0,
|
||||
sources: {},
|
||||
vectorLayers: {},
|
||||
mapState: "map",
|
||||
spec: latest,
|
||||
mapView: {
|
||||
zoom: 0,
|
||||
center: {
|
||||
lng: 0,
|
||||
lat: 0,
|
||||
},
|
||||
},
|
||||
isOpen: {
|
||||
settings: false,
|
||||
sources: false,
|
||||
open: false,
|
||||
shortcuts: false,
|
||||
export: false,
|
||||
debug: false,
|
||||
},
|
||||
maplibreGlDebugOptions: {
|
||||
showTileBoundaries: false,
|
||||
showCollisionBoxes: false,
|
||||
showOverdrawInspector: false,
|
||||
},
|
||||
openlayersDebugOptions: {
|
||||
debugToolbox: false,
|
||||
},
|
||||
fileHandle: null,
|
||||
}
|
||||
this.styleStore = new ApiStyleStore({
|
||||
onLocalStyleChange: mapStyle => this.onStyleChanged(mapStyle, {save: false}),
|
||||
port: port,
|
||||
host: params.get("localhost")
|
||||
|
||||
this.layerWatcher = new LayerWatcher({
|
||||
onVectorLayersChange: v => this.setState({ vectorLayers: v })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
configureKeyboardShortcuts = () => {
|
||||
const shortcuts = [
|
||||
{
|
||||
key: "?",
|
||||
@@ -228,74 +251,6 @@ export default class App extends React.Component<any, AppState> {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const styleUrl = initialStyleUrl()
|
||||
if(styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?")) {
|
||||
this.styleStore = new StyleStore()
|
||||
loadStyleUrl(styleUrl, mapStyle => this.onStyleChanged(mapStyle))
|
||||
removeStyleQuerystring()
|
||||
} else {
|
||||
if(styleUrl) {
|
||||
removeStyleQuerystring()
|
||||
}
|
||||
this.styleStore.init(err => {
|
||||
if(err) {
|
||||
console.log('Falling back to local storage for storing styles')
|
||||
this.styleStore = new StyleStore()
|
||||
}
|
||||
this.styleStore.latestStyle(mapStyle => this.onStyleChanged(mapStyle, {initialLoad: true}))
|
||||
|
||||
if(Debug.enabled()) {
|
||||
Debug.set("maputnik", "styleStore", this.styleStore);
|
||||
Debug.set("maputnik", "revisionStore", this.revisionStore);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if(Debug.enabled()) {
|
||||
Debug.set("maputnik", "revisionStore", this.revisionStore);
|
||||
Debug.set("maputnik", "styleStore", this.styleStore);
|
||||
}
|
||||
|
||||
this.state = {
|
||||
errors: [],
|
||||
infos: [],
|
||||
mapStyle: style.emptyStyle,
|
||||
selectedLayerIndex: 0,
|
||||
sources: {},
|
||||
vectorLayers: {},
|
||||
mapState: "map",
|
||||
spec: latest,
|
||||
mapView: {
|
||||
zoom: 0,
|
||||
center: {
|
||||
lng: 0,
|
||||
lat: 0,
|
||||
},
|
||||
},
|
||||
isOpen: {
|
||||
settings: false,
|
||||
sources: false,
|
||||
open: false,
|
||||
shortcuts: false,
|
||||
export: false,
|
||||
// TODO: Disabled for now, this should be opened on the Nth visit to the editor
|
||||
debug: false,
|
||||
},
|
||||
maplibreGlDebugOptions: {
|
||||
showTileBoundaries: false,
|
||||
showCollisionBoxes: false,
|
||||
showOverdrawInspector: false,
|
||||
},
|
||||
openlayersDebugOptions: {
|
||||
debugToolbox: false,
|
||||
},
|
||||
fileHandle: null,
|
||||
}
|
||||
|
||||
this.layerWatcher = new LayerWatcher({
|
||||
onVectorLayersChange: v => this.setState({ vectorLayers: v })
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyPress = (e: KeyboardEvent) => {
|
||||
@@ -321,7 +276,8 @@ export default class App extends React.Component<any, AppState> {
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
async componentDidMount() {
|
||||
this.styleStore = await createStyleStore((mapStyle, opts) => this.onStyleChanged(mapStyle, opts));
|
||||
window.addEventListener("keydown", this.handleKeyPress);
|
||||
}
|
||||
|
||||
@@ -329,8 +285,8 @@ export default class App extends React.Component<any, AppState> {
|
||||
window.removeEventListener("keydown", this.handleKeyPress);
|
||||
}
|
||||
|
||||
saveStyle(snapshotStyle: StyleSpecification & {id: string}) {
|
||||
this.styleStore.save(snapshotStyle)
|
||||
saveStyle(snapshotStyle: StyleSpecificationWithId) {
|
||||
this.styleStore?.save(snapshotStyle)
|
||||
}
|
||||
|
||||
updateFonts(urlTemplate: string) {
|
||||
@@ -371,7 +327,7 @@ export default class App extends React.Component<any, AppState> {
|
||||
this.onStyleChanged(changedStyle)
|
||||
}
|
||||
|
||||
onStyleChanged = (newStyle: StyleSpecification & {id: string}, opts: OnStyleChangedOpts={}) => {
|
||||
onStyleChanged = (newStyle: StyleSpecificationWithId, opts: OnStyleChangedOpts={}): void => {
|
||||
opts = {
|
||||
save: true,
|
||||
addRevision: true,
|
||||
@@ -507,7 +463,7 @@ export default class App extends React.Component<any, AppState> {
|
||||
this.revisionStore.addRevision(newStyle);
|
||||
}
|
||||
if (opts.save) {
|
||||
this.saveStyle(newStyle as StyleSpecification & {id: string});
|
||||
this.saveStyle(newStyle);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
@@ -620,7 +576,7 @@ export default class App extends React.Component<any, AppState> {
|
||||
}, this.setStateInUrl);
|
||||
}
|
||||
|
||||
setDefaultValues = (styleObj: StyleSpecification & {id: string}) => {
|
||||
setDefaultValues = (styleObj: StyleSpecificationWithId) => {
|
||||
const metadata: {[key: string]: string} = styleObj.metadata || {} as any
|
||||
if(metadata['maputnik:renderer'] === undefined) {
|
||||
const changedStyle = {
|
||||
@@ -636,7 +592,7 @@ export default class App extends React.Component<any, AppState> {
|
||||
}
|
||||
}
|
||||
|
||||
openStyle = (styleObj: StyleSpecification & {id: string}, fileHandle: FileSystemFileHandle | null) => {
|
||||
openStyle = (styleObj: StyleSpecificationWithId, fileHandle: FileSystemFileHandle | null) => {
|
||||
this.setState({fileHandle: fileHandle});
|
||||
styleObj = this.setDefaultValues(styleObj)
|
||||
this.onStyleChanged(styleObj)
|
||||
|
||||
@@ -16,6 +16,7 @@ import pkgJson from '../../package.json'
|
||||
import maputnikLogo from 'maputnik-design/logos/logo-color.svg?inline'
|
||||
import { withTranslation, WithTranslation } from 'react-i18next';
|
||||
import { supportedLanguages } from '../i18n';
|
||||
import type { OnStyleChangedCallback } from '../libs/definitions';
|
||||
|
||||
// This is required because of <https://stackoverflow.com/a/49846426>, there isn't another way to detect support that I'm aware of.
|
||||
const browser = detect();
|
||||
@@ -93,9 +94,9 @@ export type MapState = "map" | "inspect" | "filter-achromatopsia" | "filter-deut
|
||||
type AppToolbarInternalProps = {
|
||||
mapStyle: object
|
||||
inspectModeEnabled: boolean
|
||||
onStyleChanged(...args: unknown[]): unknown
|
||||
onStyleChanged: OnStyleChangedCallback
|
||||
// A new style has been uploaded
|
||||
onStyleOpen(...args: unknown[]): unknown
|
||||
onStyleOpen: OnStyleChangedCallback
|
||||
// A dict of source id's and the available source layers
|
||||
sources: object
|
||||
children?: React.ReactNode
|
||||
|
||||
@@ -14,32 +14,34 @@ type FieldTypeInternalProps = {
|
||||
disabled?: boolean
|
||||
} & WithTranslation;
|
||||
|
||||
const FieldTypeInternal: React.FC<FieldTypeInternalProps> = (props) => {
|
||||
const t = props.t;
|
||||
const FieldTypeInternal: React.FC<FieldTypeInternalProps> = ({
|
||||
t,
|
||||
value,
|
||||
wdKey,
|
||||
onChange,
|
||||
error,
|
||||
disabled = false
|
||||
}) => {
|
||||
const layerstypes: [string, string][] = Object.keys(v8.layer.type.values || {}).map(v => [v, startCase(v.replace(/-/g, ' '))]);
|
||||
return (
|
||||
<Block label={t('Type')} fieldSpec={v8.layer.type}
|
||||
data-wd-key={props.wdKey}
|
||||
error={props.error}
|
||||
data-wd-key={wdKey}
|
||||
error={error}
|
||||
>
|
||||
{props.disabled && (
|
||||
<InputString value={props.value} disabled={true} />
|
||||
{disabled && (
|
||||
<InputString value={value} disabled={true} />
|
||||
)}
|
||||
{!props.disabled && (
|
||||
{!disabled && (
|
||||
<InputSelect
|
||||
options={layerstypes}
|
||||
onChange={props.onChange}
|
||||
value={props.value}
|
||||
data-wd-key={props.wdKey + '.select'}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
data-wd-key={wdKey + '.select'}
|
||||
/>
|
||||
)}
|
||||
</Block>
|
||||
);
|
||||
};
|
||||
|
||||
FieldTypeInternal.defaultProps = {
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
const FieldType = withTranslation()(FieldTypeInternal);
|
||||
export default FieldType;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react'
|
||||
import {mdiTableRowPlusAfter} from '@mdi/js';
|
||||
import {isEqual} from 'lodash';
|
||||
import {ExpressionSpecification, LegacyFilterSpecification, StyleSpecification} from 'maplibre-gl'
|
||||
import {ExpressionSpecification, LegacyFilterSpecification} from 'maplibre-gl'
|
||||
import {latest, migrate, convertFilter} from '@maplibre/maplibre-gl-style-spec'
|
||||
import {mdiFunctionVariant} from '@mdi/js';
|
||||
|
||||
@@ -14,6 +14,7 @@ import InputButton from './InputButton'
|
||||
import Doc from './Doc'
|
||||
import ExpressionProperty from './_ExpressionProperty';
|
||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
||||
import type { StyleSpecificationWithId } from '../libs/definitions';
|
||||
|
||||
|
||||
function combiningFilter(props: FilterEditorInternalProps): LegacyFilterSpecification | ExpressionSpecification {
|
||||
@@ -39,7 +40,7 @@ function migrateFilter(filter: LegacyFilterSpecification | ExpressionSpecificati
|
||||
return (migrate(createStyleFromFilter(filter) as any).layers[0] as any).filter;
|
||||
}
|
||||
|
||||
function createStyleFromFilter(filter: LegacyFilterSpecification | ExpressionSpecification): StyleSpecification & {id: string} {
|
||||
function createStyleFromFilter(filter: LegacyFilterSpecification | ExpressionSpecification): StyleSpecificationWithId {
|
||||
return {
|
||||
"id": "tmp",
|
||||
"version": 8,
|
||||
|
||||
@@ -3,7 +3,6 @@ import Slugify from 'slugify'
|
||||
import {saveAs} from 'file-saver'
|
||||
import {version} from 'maplibre-gl/package.json'
|
||||
import {format} from '@maplibre/maplibre-gl-style-spec'
|
||||
import type {StyleSpecification} from 'maplibre-gl'
|
||||
import {MdMap, MdSave} from 'react-icons/md'
|
||||
import {WithTranslation, withTranslation} from 'react-i18next';
|
||||
|
||||
@@ -12,6 +11,7 @@ import InputButton from './InputButton'
|
||||
import Modal from './Modal'
|
||||
import style from '../libs/style'
|
||||
import fieldSpecAdditional from '../libs/field-spec-additional'
|
||||
import type {OnStyleChangedCallback, StyleSpecificationWithId} from '../libs/definitions'
|
||||
|
||||
|
||||
const MAPLIBRE_GL_VERSION = version;
|
||||
@@ -19,8 +19,8 @@ const showSaveFilePickerAvailable = typeof window.showSaveFilePicker === "functi
|
||||
|
||||
|
||||
type ModalExportInternalProps = {
|
||||
mapStyle: StyleSpecification & { id: string }
|
||||
onStyleChanged(...args: unknown[]): unknown
|
||||
mapStyle: StyleSpecificationWithId
|
||||
onStyleChanged: OnStyleChangedCallback
|
||||
isOpen: boolean
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
onSetFileHandle(fileHandle: FileSystemFileHandle | null): unknown
|
||||
|
||||
@@ -12,10 +12,11 @@ import FieldEnum from './FieldEnum'
|
||||
import FieldColor from './FieldColor'
|
||||
import Modal from './Modal'
|
||||
import fieldSpecAdditional from '../libs/field-spec-additional'
|
||||
import type {OnStyleChangedCallback, StyleSpecificationWithId} from '../libs/definitions';
|
||||
|
||||
type ModalSettingsInternalProps = {
|
||||
mapStyle: StyleSpecification
|
||||
onStyleChanged(...args: unknown[]): unknown
|
||||
mapStyle: StyleSpecificationWithId
|
||||
onStyleChanged: OnStyleChangedCallback
|
||||
onChangeMetadataProperty(...args: unknown[]): unknown
|
||||
isOpen: boolean
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
@@ -62,7 +63,7 @@ class ModalSettingsInternal extends React.Component<ModalSettingsInternalProps>
|
||||
changeTerrainProperty(property: keyof TerrainSpecification, value: any) {
|
||||
const terrain = {
|
||||
...this.props.mapStyle.terrain,
|
||||
}
|
||||
} as TerrainSpecification;
|
||||
|
||||
if (value === undefined) {
|
||||
delete terrain[property];
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
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 type {GeoJSONSourceSpecification, RasterDEMSourceSpecification, RasterSourceSpecification, SourceSpecification, VectorSourceSpecification} from 'maplibre-gl'
|
||||
import { WithTranslation, withTranslation } from 'react-i18next';
|
||||
|
||||
import Modal from './Modal'
|
||||
@@ -13,6 +13,7 @@ import ModalSourcesTypeEditor, { EditorMode } from './ModalSourcesTypeEditor'
|
||||
import style from '../libs/style'
|
||||
import { deleteSource, addSource, changeSource } from '../libs/source'
|
||||
import publicSources from '../config/tilesets.json'
|
||||
import { OnStyleChangedCallback, StyleSpecificationWithId } from '../libs/definitions';
|
||||
|
||||
|
||||
type PublicSourceProps = {
|
||||
@@ -270,10 +271,10 @@ class AddSource extends React.Component<AddSourceProps, AddSourceState> {
|
||||
}
|
||||
|
||||
type ModalSourcesInternalProps = {
|
||||
mapStyle: StyleSpecification
|
||||
mapStyle: StyleSpecificationWithId
|
||||
isOpen: boolean
|
||||
onOpenToggle(...args: unknown[]): unknown
|
||||
onStyleChanged(...args: unknown[]): unknown
|
||||
onStyleChanged: OnStyleChangedCallback
|
||||
} & WithTranslation;
|
||||
|
||||
class ModalSourcesInternal extends React.Component<ModalSourcesInternalProps> {
|
||||
|
||||
Reference in New Issue
Block a user