@trailstash/ultra
Version:
A web based tool for making MapLibre GL maps with data from sources such as Overpass, GeoJSON, GPX, KML, TCX, etc
283 lines (266 loc) • 6.96 kB
JavaScript
import { h, t } from "../lib/dom.js";
import { style as buttonCSS } from "./button.js";
import { normalizeCSS } from "../lib/normalize.js";
import { toQueryParams } from "../lib/queryParams.js";
import { htmlExport } from "../lib/htmlExport.js";
export class ExportModal extends HTMLElement {
#center;
#zoom;
#query;
#data;
#style;
constructor() {
super();
}
get center() {
return this.#center;
}
set center(value) {
this.#center = value;
this.update();
}
get zoom() {
return this.#zoom;
}
set zoom(value) {
this.#zoom = value;
this.update();
}
get query() {
return this.#query;
}
set query(value) {
this.#query = value;
this.update();
}
set data(data) {
this.#data = data;
this.update();
}
get data() {
return this.#data;
}
set style(style) {
this.#style = style;
this.update();
}
get style() {
return this.#style
? {
...this.#style,
glyphs:
this.#style && this.#style.glyphs
? new URL(this.#style.glyphs).searchParams.get("url")
: undefined,
}
: null;
}
get html() {
return htmlExport(this.style, "Ultra Export", "", {
zoom: this.zoom,
center: this.center,
});
}
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
const dataDownloadButton = h("button", {}, t("download"));
dataDownloadButton.addEventListener("click", () =>
this.download(JSON.stringify(this.data), "ultra.geojson"),
);
const styleDownloadButton = h("button", {}, t("download"));
styleDownloadButton.addEventListener("click", () => {
this.download(JSON.stringify(this.style), "style.json");
});
const htmlDownloadButton = h("button", {}, t("download"));
htmlDownloadButton.addEventListener("click", () => {
this.download(this.html, "ultra.html");
});
const queryDownloadButton = h("button", {}, t("download"));
queryDownloadButton.addEventListener("click", () => {
this.download(this.query, "query.ultra");
});
const dataCopyButton = h("button", {}, t("copy"));
dataCopyButton.addEventListener("click", () => {
navigator.clipboard.writeText(JSON.stringify(this.data));
});
const styleCopyButton = h("button", {}, t("copy"));
styleCopyButton.addEventListener("click", () => {
navigator.clipboard.writeText(JSON.stringify(this.style));
});
const htmlCopyButton = h("button", {}, t("copy"));
htmlCopyButton.addEventListener("click", () => {
navigator.clipboard.writeText(this.html);
});
const queryCopyButton = h("button", {}, t("copy"));
queryCopyButton.addEventListener("click", () => {
navigator.clipboard.writeText(this.query);
});
const div = h(
"div",
{ style: "", slot: "modal-content" },
h(
"style",
{},
`
div {
padding: 0 8px 8px;
max-width: 100%;
width: 460px;
}
details, .note {
font-size: 0.8em;
}
h3, h5 {
margin-bottom: 0;
}
input[type=text] {
width: 100%;
padding: 0;
margin: 0;
}
label {
display: block;
}
button {
cursor: pointer;
}
`,
),
h(
"div",
{ className: "data" },
h("h3", {}, "Data"),
h("p", {}, "Export the query result as GeoJSON"),
dataDownloadButton,
t(" "),
dataCopyButton,
),
h(
"div",
{ className: "map" },
h("h3", {}, "Map"),
h("fa-icon", { icon: "triangle-exclamation" }),
h(
"span",
{ className: "note" },
t(" Note: some styles' tiles have CORS restrictions"),
),
h(
"p",
{},
t("Export the map as a MapLibre style "),
h(
"details",
{},
h(
"summary",
{},
h("fa-icon", { icon: "triangle-exclamation" }),
t(" usage notes"),
),
t("Ultra's "),
h(
"a",
{
href: "https://overpass-ultra.us/docs/style/#png-sprites-via-https",
target: "_blank",
},
"sprite",
),
t(" support, "),
h(
"a",
{
href: "https://overpass-ultra.us/docs/style/#fallback-fontstack",
target: "_blank",
},
"Fallback glyphs",
),
t(", and interactive popups"),
t(" are run-time features and aren't included in the generated "),
h("code", {}, "style.json"),
t(". You may experience issues with missing icons or text."),
),
),
styleDownloadButton,
t(" "),
styleCopyButton,
/*
h(
"p",
{},
t("Export an interactive map"),
h(
"details",
{},
h(
"summary",
{},
h("fa-icon", { icon: "triangle-exclamation" }),
t(" usage notes"),
),
t(
"Ultra's interactive popups are not yet supported in exported HTML.",
),
),
),
htmlDownloadButton,
t(" "),
htmlCopyButton,
*/
),
/*
h(
"div",
{ className: "query" },
h("h3", {}, "Query"),
h("p", {}, "Export the Ultra query"),
queryDownloadButton,
t(" "),
queryCopyButton,
),
*/
);
const button = h(
"button-modal",
{ text: "Export", icon: "file-export" },
div,
);
this.refs = {
button,
div,
};
shadow.appendChild(button);
this.update();
}
update() {
if (this.data) {
this.refs.div.querySelector(".data").style.display = "block";
} else {
this.refs.div.querySelector(".data").style.display = "none";
}
if (this.style) {
this.refs.button.refs.button.disabled = false;
} else {
this.refs.button.refs.button.disabled = true;
}
}
download(data, filename) {
const blob = new Blob([data], { type: "octet/stream" });
const url = window.URL.createObjectURL(blob);
const link = h("a", { download: filename, href: url });
// this is necessary as link.click() does not work on the latest firefox
link.dispatchEvent(
new MouseEvent("click", {
bubbles: true,
cancelable: true,
view: window,
}),
);
setTimeout(() => {
// For Firefox it is necessary to delay revoking the ObjectURL
window.URL.revokeObjectURL(url);
link.remove();
}, 100);
}
}