@trailstash/ultra
Version:
A web based tool for making MapLibre GL maps with data from sources such as Overpass, GeoJSON, GPX, KML, TCX, etc
260 lines (246 loc) • 6.66 kB
JavaScript
import equal from "deep-equal";
import pick from "lodash.pick";
import { compressToEncodedURIComponent } from "lz-string";
import { h } from "../lib/dom.js";
import { setBaseStyle } from "../lib/style.js";
import { setSetting, parseSettings } from "../lib/settings.js";
import {
getAutoRunFromQueryParams,
getOptionsFromQueryParams,
getQueryFromQueryParams,
toQueryParams,
} from "../lib/queryParams.js";
import { localStorage } from "../lib/localStorage.js";
import { UltraMap } from "./ultra-map.js";
import { HelpModal } from "./help-modal.js";
import { StylePicker } from "./style-picker.js";
import { alertOnError } from "../lib/error.js";
const style = new CSSStyleSheet();
style.replaceSync(`
:host {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
}
main {
display: flex;
flex-direction: row;
flex-grow: 1;
}
code-editor {
width: 45%;
border: none;
border-right: 1px solid #8f8f9d;
resize: horizontal;
overflow: auto;
}
ultra-map {
flex-grow: 1;
height: 100%;
}
(max-width: 900px) {
main {
flex-direction: column;
}
code-editor {
width: auto;
height: 45%;
resize: vertical;
}
}
code-editor.fs {
display: none;
}
help-modal {
float: right;
}
.fs help-modal,
.fs run-button {
z-index: 1;
display: block;
position: fixed;
top: 4px;
left: 4px;
}
:host(:fullscreen) code-editor, :host(:fullscreen) nav-bar {
display: none;
}
run-button, help-modal {
margin-right: 4px;
}
`);
export class UltraApp extends HTMLElement {
query = "";
settings = {};
help;
runButton = true;
static MAP_INIT_SETTINGS = [
"loadSettingsFromQueryParams",
"options",
"controls",
"mapStyle",
"queryProviders",
"persistState",
];
static props = ["settings", "query", "help"];
constructor() {
super();
// Bind methods
this.onClickRun = this.onClickRun.bind(this);
this.onClickAbort = this.onClickAbort.bind(this);
this.resetMap = this.resetMap.bind(this);
this.onMoveEnd = this.onMoveEnd.bind(this);
}
async connectedCallback() {
// Get initial query and center&zoom
try {
this.query =
(await Promise.resolve(getQueryFromQueryParams()).catch(
alertOnError,
)) || this.query;
} catch (e) {
alert(e.message);
}
this.settings = {
...this.settings,
loadSettingsFromQueryParams: false,
};
const querySettings = parseSettings(this.query);
delete querySettings.query;
if (querySettings.title) {
this.settings.title = querySettings.title;
document.title = this.settings.title;
}
if (querySettings.icon) {
this.settings.icon = querySettings.icon;
document.querySelector("[rel=icon]").href = this.settings.icon;
}
if (querySettings.controls) {
this.settings.controls = querySettings.controls;
}
if (querySettings.options) {
this.settings.options = querySettings.options;
}
if (querySettings.help) {
this.help = querySettings.help;
}
if (typeof querySettings.runButton !== "undefined") {
this.runButton = !!querySettings.runButton;
}
// Create & populate Shadow DOM
const shadow = this.attachShadow({ mode: "open" });
shadow.adoptedStyleSheets.push(style);
shadow.appendChild(
h(
"nav-bar",
{ mode: "app", title: this.settings.title, icon: this.settings.icon },
h(
"span",
{ slot: "buttons" },
...(this.runButton
? [
h("run-button", {
colors: {
run: "white",
abort: "white",
text: "black",
},
text: {
run: "Reload",
abort: "Cancel",
},
icons: {
run: "rotate",
abort: "xmark",
},
}),
]
: []),
...(this.help
? [
h("help-modal", {
help: this.help,
minimal: true,
}),
]
: []),
),
),
);
shadow.appendChild(
h(
"main",
{},
h("ultra-map", pick(this.settings, UltraApp.MAP_INIT_SETTINGS)),
),
);
this.refs = {
navBar: shadow.querySelector("nav-bar"),
ultraMap: shadow.querySelector("ultra-map"),
runButton: shadow.querySelector("run-button"),
stylePicker: shadow.querySelector("style-picker"),
};
// Setup Event Listeners
this.refs.runButton?.addEventListener("run", this.onClickRun);
this.refs.runButton?.addEventListener("abort", this.onClickAbort);
this.refs.ultraMap.map.on("moveend", this.onMoveEnd);
this.refs.ultraMap.map.once("idle", this.onMoveEnd);
this.onClickRun();
}
async onClickRun() {
if (this.refs.runButton) this.refs.runButton.loading = true; // Loading button state
this.resetMap(); // clear settings from previous run
try {
// basic query/option parsing
const settings = {
...this.settings,
...parseSettings(this.query),
};
Object.assign(this.refs.ultraMap, {
...pick(settings, UltraMap.RUNTIME_SETTINGS),
mapStyle: setBaseStyle(
settings.mapStyle,
this.settings.mapStyle || UltraMap.defaults.mapStyle,
),
});
this.controller = new AbortController();
const { data, mapStyle } = await this.refs.ultraMap.run(this.controller);
delete this.controller;
if (this.refs.runButton) this.refs.runButton.loading = false;
} catch (err) {
this.resetMap();
this.refs.ultraMap.run();
if (this.refs.runButton) this.refs.runButton.loading = false;
if (err.name != "AbortError") {
alert(err);
throw err;
}
}
}
onClickAbort() {
if (this.controller) {
this.controller.abort();
delete this.controller;
}
this.resetMap();
this.refs.ultraMap.run();
if (this.refs.runButton) this.refs.runButton.loading = false;
}
resetMap() {
Object.assign(
this.refs.ultraMap,
pick(
{
...UltraMap.defaults,
mapStyle: this.settings.mapStyle || UltraMap.defaults.mapStyle,
},
UltraMap.RUNTIME_SETTINGS,
),
);
}
onMoveEnd() {
const zoom = this.refs.ultraMap.zoom;
const center = this.refs.ultraMap.center.toArray();
}
}