mirror of
https://github.com/maputnik/editor.git
synced 2026-09-15 08:47:26 +00:00
599240033a
## Launch Checklist <!-- Thanks for the PR! Feel free to add or remove items from the checklist. --> When localStorage is full you start getting a "QuotaExceededError". RIght now Maputnik does not handle this situation gracefully, it just fails loading the style with a non-descriptive error message. This PR purges localStorage and tries again when this particular error happens. It still does not show a descriptive error message. If you try to load a style that is larger than what localStorage can handle, it will still fail with a non-descriptive error message. Increased the size of `example-style.json` so that it causes a QuotaExceededError when running the regression test (try it before and after this PR). - [x] Briefly describe the changes in this PR. - [x] Link to related issues. N/A - [x] Include before/after visuals or gifs if this PR includes visual changes. N/A - [x] Write tests for all new functionality. - [x] Add an entry to `CHANGELOG.md` under the `## main` section. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
119 lines
3.2 KiB
TypeScript
119 lines
3.2 KiB
TypeScript
import style from './style'
|
|
import {loadStyleUrl} from './urlopen'
|
|
import publicSources from '../config/styles.json'
|
|
import type {StyleSpecification} from 'maplibre-gl'
|
|
|
|
const storagePrefix = "maputnik"
|
|
const stylePrefix = 'style'
|
|
const storageKeys = {
|
|
latest: [storagePrefix, 'latest_style'].join(':'),
|
|
accessToken: [storagePrefix, 'access_token'].join(':')
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
// Return style ids and dates of all styles stored in local storage
|
|
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!))
|
|
}
|
|
}
|
|
return styles
|
|
}
|
|
|
|
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: string) {
|
|
if(!isStyleKey(key)) {
|
|
throw "Key is not a valid style key"
|
|
}
|
|
|
|
const parts = key.split(":")
|
|
const styleId = parts[2]
|
|
return styleId
|
|
}
|
|
|
|
// Calculate key that identifies the style with a version
|
|
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();
|
|
}
|
|
|
|
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) as string;
|
|
if(key.startsWith(storagePrefix)) {
|
|
window.localStorage.removeItem(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find the last edited style
|
|
latestStyle(cb: (...args: any[]) => void) {
|
|
if(this.mapStyles.length === 0) return loadDefaultStyle(cb)
|
|
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)
|
|
}
|
|
|
|
// Save current style replacing previous version
|
|
save(mapStyle: StyleSpecification & { id: string }) {
|
|
mapStyle = style.ensureStyleValidity(mapStyle)
|
|
const key = styleKey(mapStyle.id)
|
|
|
|
const saveFn = () => {
|
|
window.localStorage.setItem(key, JSON.stringify(mapStyle))
|
|
window.localStorage.setItem(storageKeys.latest, mapStyle.id)
|
|
}
|
|
|
|
try {
|
|
saveFn()
|
|
} catch (e) {
|
|
// Handle quota exceeded error
|
|
if (e instanceof DOMException && (
|
|
e.code === 22 || // Firefox
|
|
e.code === 1014 || // Firefox
|
|
e.name === 'QuotaExceededError' ||
|
|
e.name === 'NS_ERROR_DOM_QUOTA_REACHED'
|
|
)) {
|
|
this.purge()
|
|
saveFn() // Retry after clearing
|
|
} else {
|
|
throw e
|
|
}
|
|
}
|
|
return mapStyle
|
|
}
|
|
}
|