mirror of
https://github.com/maputnik/editor.git
synced 2026-06-09 08:47:26 +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:
@@ -1,48 +0,0 @@
|
||||
interface DebugStore {
|
||||
[namespace: string]: {
|
||||
[key: string]: any
|
||||
}
|
||||
}
|
||||
|
||||
const debugStore: DebugStore = {};
|
||||
|
||||
function enabled() {
|
||||
const qs = new URL(window.location.href).searchParams;
|
||||
const debugQs = qs.get("debug");
|
||||
if(debugQs) {
|
||||
return !!debugQs.match(/^(|1|true)$/);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function genErr() {
|
||||
return new Error("Debug not enabled, enable by appending '?debug' to your query string");
|
||||
}
|
||||
|
||||
function set(namespace: keyof DebugStore, key: string, value: any) {
|
||||
if(!enabled()) {
|
||||
throw genErr();
|
||||
}
|
||||
debugStore[namespace] = debugStore[namespace] || {};
|
||||
debugStore[namespace][key] = value;
|
||||
}
|
||||
|
||||
function get(namespace: keyof DebugStore, key: string) {
|
||||
if(!enabled()) {
|
||||
throw genErr();
|
||||
}
|
||||
if(Object.prototype.hasOwnProperty.call(debugStore, namespace)) {
|
||||
return debugStore[namespace][key];
|
||||
}
|
||||
}
|
||||
|
||||
const mod = {
|
||||
enabled,
|
||||
get,
|
||||
set
|
||||
};
|
||||
|
||||
(window as any).debug = mod;
|
||||
export default mod;
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
import type { StyleSpecification } from "maplibre-gl";
|
||||
|
||||
export type StyleSpecificationWithId = StyleSpecification & {id: string};
|
||||
|
||||
export type OnStyleChangedOpts = {
|
||||
save?: boolean;
|
||||
addRevision?: boolean;
|
||||
initialLoad?: boolean;
|
||||
}
|
||||
|
||||
export type OnStyleChangedCallback = (newStyle: StyleSpecificationWithId, opts: OnStyleChangedOpts={}) => void;
|
||||
|
||||
export interface IStyleStore {
|
||||
getLatestStyle(): Promise<StyleSpecificationWithId>;
|
||||
save(mapStyle: StyleSpecificationWithId): StyleSpecificationWithId;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {StyleSpecification} from "maplibre-gl";
|
||||
import { StyleSpecificationWithId } from "./definitions";
|
||||
|
||||
export class RevisionStore {
|
||||
revisions: (StyleSpecification & {id: string})[];
|
||||
revisions: StyleSpecificationWithId[];
|
||||
currentIdx: number;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export class RevisionStore {
|
||||
return this.revisions[this.currentIdx]
|
||||
}
|
||||
|
||||
addRevision(revision: StyleSpecification & {id: string}) {
|
||||
addRevision(revision: StyleSpecificationWithId) {
|
||||
// clear any "redo" revisions once a change is made
|
||||
// and ensure current index is at end of list
|
||||
this.revisions = this.revisions.slice(0, this.currentIdx + 1);
|
||||
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
import type {StyleSpecification, SourceSpecification} from "maplibre-gl";
|
||||
import type {SourceSpecification} from "maplibre-gl";
|
||||
import type {StyleSpecificationWithId} from "./definitions";
|
||||
|
||||
export function deleteSource(mapStyle: StyleSpecification, sourceId: string) {
|
||||
export function deleteSource(mapStyle: StyleSpecificationWithId, sourceId: string) {
|
||||
const remainingSources = { ...mapStyle.sources}
|
||||
delete remainingSources[sourceId]
|
||||
return {
|
||||
@@ -10,11 +11,11 @@ export function deleteSource(mapStyle: StyleSpecification, sourceId: string) {
|
||||
}
|
||||
|
||||
|
||||
export function addSource(mapStyle: StyleSpecification, sourceId: string, source: SourceSpecification) {
|
||||
export function addSource(mapStyle: StyleSpecificationWithId, sourceId: string, source: SourceSpecification) {
|
||||
return changeSource(mapStyle, sourceId, source)
|
||||
}
|
||||
|
||||
export function changeSource(mapStyle: StyleSpecification, sourceId: string, source: SourceSpecification) {
|
||||
export function changeSource(mapStyle: StyleSpecificationWithId, sourceId: string, source: SourceSpecification) {
|
||||
const changedSources = {
|
||||
...mapStyle.sources,
|
||||
[sourceId]: source
|
||||
|
||||
@@ -1,46 +1,38 @@
|
||||
import style from './style.js'
|
||||
import style from '../style'
|
||||
import {format} from '@maplibre/maplibre-gl-style-spec'
|
||||
import type {StyleSpecification} from 'maplibre-gl'
|
||||
import ReconnectingWebSocket from 'reconnecting-websocket'
|
||||
import type {IStyleStore, OnStyleChangedCallback, StyleSpecificationWithId} from '../definitions'
|
||||
|
||||
export type ApiStyleStoreOptions = {
|
||||
port: string | null
|
||||
host: string | null
|
||||
onLocalStyleChange?: (style: any) => void
|
||||
onLocalStyleChange?: OnStyleChangedCallback
|
||||
}
|
||||
|
||||
export class ApiStyleStore {
|
||||
export class ApiStyleStore implements IStyleStore {
|
||||
|
||||
localUrl: string;
|
||||
websocketUrl: string;
|
||||
latestStyleId: string | undefined = undefined;
|
||||
onLocalStyleChange: (style: any) => void;
|
||||
onLocalStyleChange: OnStyleChangedCallback;
|
||||
|
||||
constructor(opts: ApiStyleStoreOptions) {
|
||||
this.onLocalStyleChange = opts.onLocalStyleChange || (() => {})
|
||||
const port = opts.port || '8000'
|
||||
const host = opts.host || 'localhost'
|
||||
const port = window.location.port
|
||||
const host = 'localhost'
|
||||
this.localUrl = `http://${host}:${port}`
|
||||
this.websocketUrl = `ws://${host}:${port}/ws`
|
||||
this.init = this.init.bind(this)
|
||||
}
|
||||
|
||||
init(cb: (...args: any[]) => void) {
|
||||
fetch(this.localUrl + '/styles', {
|
||||
mode: 'cors',
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json();
|
||||
})
|
||||
.then((body) => {
|
||||
const styleIds = body;
|
||||
this.latestStyleId = styleIds[0]
|
||||
this.notifyLocalChanges()
|
||||
cb(null)
|
||||
})
|
||||
.catch(() => {
|
||||
cb(new Error('Can not connect to style API'))
|
||||
})
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
const response = await fetch(this.localUrl + '/styles', {mode: 'cors'});
|
||||
const body = await response.json();
|
||||
const styleIds = body;
|
||||
this.latestStyleId = styleIds[0]
|
||||
this.notifyLocalChanges();
|
||||
} catch {
|
||||
throw new Error('Can not connect to style API');
|
||||
}
|
||||
}
|
||||
|
||||
notifyLocalChanges() {
|
||||
@@ -59,24 +51,20 @@ export class ApiStyleStore {
|
||||
}
|
||||
}
|
||||
|
||||
latestStyle(cb: (...args: any[]) => void) {
|
||||
async getLatestStyle(): Promise<StyleSpecificationWithId> {
|
||||
if(this.latestStyleId) {
|
||||
fetch(this.localUrl + '/styles/' + this.latestStyleId, {
|
||||
const response = await fetch(this.localUrl + '/styles/' + this.latestStyleId, {
|
||||
mode: 'cors',
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
cb(style.ensureStyleValidity(body))
|
||||
})
|
||||
});
|
||||
const body = await response.json();
|
||||
return style.ensureStyleValidity(body);
|
||||
} else {
|
||||
throw new Error('No latest style available. You need to init the api backend first.')
|
||||
}
|
||||
}
|
||||
|
||||
// Save current style replacing previous version
|
||||
save(mapStyle: StyleSpecification & { id: string }) {
|
||||
save(mapStyle: StyleSpecificationWithId) {
|
||||
const styleJSON = format(
|
||||
style.stripAccessTokens(
|
||||
style.replaceAccessTokens(mapStyle)
|
||||
@@ -0,0 +1,29 @@
|
||||
/// <reference types="vite/client" />
|
||||
import { IStyleStore, OnStyleChangedCallback } from "../definitions";
|
||||
import { getStyleUrlFromAddressbarAndRemoveItIfNeeded, loadStyleUrl } from "../urlopen";
|
||||
import { ApiStyleStore } from "./apistore";
|
||||
import { StyleStore } from "./stylestore";
|
||||
|
||||
export async function createStyleStore(onStyleChanged: OnStyleChangedCallback): Promise<IStyleStore> {
|
||||
const styleUrl = getStyleUrlFromAddressbarAndRemoveItIfNeeded();
|
||||
const useStyleUrl = styleUrl && window.confirm("Load style from URL: " + styleUrl + " and discard current changes?");
|
||||
let styleStore: IStyleStore;
|
||||
if (import.meta.env.MODE === 'desktop' && !useStyleUrl) {
|
||||
const apiStyleStore = new ApiStyleStore({
|
||||
onLocalStyleChange: mapStyle => onStyleChanged(mapStyle, {save: false}),
|
||||
});
|
||||
try {
|
||||
await apiStyleStore.init();
|
||||
styleStore = apiStyleStore;
|
||||
} catch {
|
||||
styleStore = new StyleStore();
|
||||
}
|
||||
} else {
|
||||
styleStore = new StyleStore();
|
||||
}
|
||||
const styleToLoad = useStyleUrl ? await loadStyleUrl(styleUrl) : await styleStore.getLatestStyle();
|
||||
onStyleChanged(styleToLoad, {initialLoad: true, save: false});
|
||||
return styleStore;
|
||||
}
|
||||
|
||||
export type { IStyleStore };
|
||||
@@ -1,7 +1,7 @@
|
||||
import style from './style'
|
||||
import {loadStyleUrl} from './urlopen'
|
||||
import publicSources from '../config/styles.json'
|
||||
import type {StyleSpecification} from 'maplibre-gl'
|
||||
import style from '../style'
|
||||
import {loadStyleUrl} from '../urlopen'
|
||||
import publicSources from '../../config/styles.json'
|
||||
import type {IStyleStore, StyleSpecificationWithId} from '../definitions'
|
||||
|
||||
const storagePrefix = "maputnik"
|
||||
const stylePrefix = 'style'
|
||||
@@ -13,8 +13,8 @@ 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: (...args: any[]) => void) {
|
||||
loadStyleUrl(defaultStyleUrl, cb)
|
||||
export function loadDefaultStyle(): Promise<StyleSpecificationWithId> {
|
||||
return loadStyleUrl(defaultStyleUrl);
|
||||
}
|
||||
|
||||
// Return style ids and dates of all styles stored in local storage
|
||||
@@ -51,7 +51,7 @@ function styleKey(styleId: string) {
|
||||
}
|
||||
|
||||
// Manages many possible styles that are stored in the local storage
|
||||
export class StyleStore {
|
||||
export class StyleStore implements IStyleStore {
|
||||
/**
|
||||
* List of style ids
|
||||
*/
|
||||
@@ -63,10 +63,6 @@ export class StyleStore {
|
||||
this.mapStyles = loadStoredStyles();
|
||||
}
|
||||
|
||||
init(cb: (...args: any[]) => void) {
|
||||
cb(null)
|
||||
}
|
||||
|
||||
// Delete entire style history
|
||||
purge() {
|
||||
for (let i = 0; i < window.localStorage.length; i++) {
|
||||
@@ -78,17 +74,21 @@ export class StyleStore {
|
||||
}
|
||||
|
||||
// Find the last edited style
|
||||
latestStyle(cb: (...args: any[]) => void) {
|
||||
if(this.mapStyles.length === 0) return loadDefaultStyle(cb)
|
||||
async getLatestStyle(): Promise<StyleSpecificationWithId> {
|
||||
if(this.mapStyles.length === 0) {
|
||||
return loadDefaultStyle();
|
||||
}
|
||||
const styleId = window.localStorage.getItem(storageKeys.latest) as string;
|
||||
const styleItem = window.localStorage.getItem(styleKey(styleId))
|
||||
|
||||
if(styleItem) return cb(JSON.parse(styleItem))
|
||||
loadDefaultStyle(cb)
|
||||
if (styleItem) {
|
||||
return JSON.parse(styleItem) as StyleSpecificationWithId;
|
||||
}
|
||||
return loadDefaultStyle();
|
||||
}
|
||||
|
||||
// Save current style replacing previous version
|
||||
save(mapStyle: StyleSpecification & { id: string }) {
|
||||
save(mapStyle: StyleSpecificationWithId) {
|
||||
mapStyle = style.ensureStyleValidity(mapStyle)
|
||||
const key = styleKey(mapStyle.id)
|
||||
|
||||
+6
-6
@@ -1,6 +1,7 @@
|
||||
import {derefLayers} from '@maplibre/maplibre-gl-style-spec'
|
||||
import type {StyleSpecification, LayerSpecification} from 'maplibre-gl'
|
||||
import tokens from '../config/tokens.json'
|
||||
import type {StyleSpecificationWithId} from './definitions'
|
||||
|
||||
// Empty style is always used if no style could be restored or fetched
|
||||
const emptyStyle = ensureStyleValidity({
|
||||
@@ -13,15 +14,14 @@ function generateId() {
|
||||
return Math.random().toString(36).substring(2, 9)
|
||||
}
|
||||
|
||||
function ensureHasId(style: StyleSpecification & { id?: string }): StyleSpecification & { id: string } {
|
||||
function ensureHasId(style: StyleSpecification & { id?: string }): StyleSpecificationWithId {
|
||||
if(!('id' in style) || !style.id) {
|
||||
style.id = generateId();
|
||||
return style as StyleSpecification & { id: string };
|
||||
}
|
||||
return style as StyleSpecification & { id: string };
|
||||
return style as StyleSpecificationWithId;
|
||||
}
|
||||
|
||||
function ensureHasNoInteractive(style: StyleSpecification & {id: string}) {
|
||||
function ensureHasNoInteractive(style: StyleSpecificationWithId) {
|
||||
const changedLayers = style.layers.map(layer => {
|
||||
const changedLayer: LayerSpecification & { interactive?: any } = { ...layer }
|
||||
delete changedLayer.interactive
|
||||
@@ -34,14 +34,14 @@ function ensureHasNoInteractive(style: StyleSpecification & {id: string}) {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureHasNoRefs(style: StyleSpecification & {id: string}) {
|
||||
function ensureHasNoRefs(style: StyleSpecificationWithId) {
|
||||
return {
|
||||
...style,
|
||||
layers: derefLayers(style.layers)
|
||||
}
|
||||
}
|
||||
|
||||
function ensureStyleValidity(style: StyleSpecification): StyleSpecification & { id: string } {
|
||||
function ensureStyleValidity(style: StyleSpecification): StyleSpecificationWithId {
|
||||
return ensureHasNoInteractive(ensureHasNoRefs(ensureHasId(style)))
|
||||
}
|
||||
|
||||
|
||||
+20
-23
@@ -1,30 +1,27 @@
|
||||
import style from './style'
|
||||
import { StyleSpecificationWithId } from './definitions';
|
||||
|
||||
export function initialStyleUrl() {
|
||||
export function getStyleUrlFromAddressbarAndRemoveItIfNeeded(): string | null {
|
||||
const initialUrl = new URL(window.location.href);
|
||||
return initialUrl.searchParams.get('style');
|
||||
const styleUrl = initialUrl.searchParams.get('style');
|
||||
if (styleUrl) {
|
||||
initialUrl.searchParams.delete('style');
|
||||
window.history.replaceState({}, document.title, initialUrl.toString())
|
||||
}
|
||||
return styleUrl;
|
||||
}
|
||||
|
||||
export function loadStyleUrl(styleUrl: string, cb: (...args: any[]) => void) {
|
||||
export async function loadStyleUrl(styleUrl: string): Promise<StyleSpecificationWithId> {
|
||||
console.log('Loading style', styleUrl)
|
||||
fetch(styleUrl, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then(function(response) {
|
||||
return response.json();
|
||||
})
|
||||
.then(function(body) {
|
||||
cb(style.ensureStyleValidity(body))
|
||||
})
|
||||
.catch(function() {
|
||||
console.warn('Could not fetch default style', styleUrl)
|
||||
cb(style.emptyStyle)
|
||||
})
|
||||
}
|
||||
|
||||
export function removeStyleQuerystring() {
|
||||
const initialUrl = new URL(window.location.href);
|
||||
initialUrl.searchParams.delete('style');
|
||||
window.history.replaceState({}, document.title, initialUrl.toString())
|
||||
try {
|
||||
const response = await fetch(styleUrl, {
|
||||
mode: 'cors',
|
||||
credentials: "same-origin"
|
||||
});
|
||||
const body = await response.json();
|
||||
return style.ensureStyleValidity(body);
|
||||
} catch {
|
||||
console.warn('Could not fetch default style: ' + styleUrl)
|
||||
return style.emptyStyle
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user