applying linter fixes to viewer /app [#49] (#344)

Add biome linter and configuration to /app viewer.
This commit is contained in:
Brandon Liu
2024-02-01 15:59:34 +08:00
committed by GitHub
parent 61ed024d21
commit 7cc025c776
10 changed files with 328 additions and 148 deletions

View File

@@ -1,11 +1,11 @@
import React, { useState, useEffect } from "react";
import { styled, globalStyles } from "./stitches.config";
import { PMTiles } from "../../js/index";
import { GitHubLogoIcon } from "@radix-ui/react-icons";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { GitHubLogoIcon } from "@radix-ui/react-icons";
import React, { useState, useEffect } from "react";
import { PMTiles } from "../../js/index";
import { globalStyles, styled } from "./stitches.config";
import Start from "./Start";
import Loader from "./Loader";
import Start from "./Start";
const Header = styled("div", {
height: "$4",
@@ -57,15 +57,15 @@ const GIT_SHA = (import.meta.env.VITE_GIT_SHA || "").substr(0, 8);
function App() {
globalStyles();
let [errorDisplay, setErrorDisplay] = useState<string | undefined>();
let [file, setFile] = useState<PMTiles | undefined>();
let [mapHashPassed, setMapHashPassed] = useState<boolean>(false);
const [errorDisplay, setErrorDisplay] = useState<string | undefined>();
const [file, setFile] = useState<PMTiles | undefined>();
const [mapHashPassed, setMapHashPassed] = useState<boolean>(false);
// initial load
useEffect(() => {
const loadUrl = new URLSearchParams(location.search).get("url");
if (loadUrl) {
let initialValue = new PMTiles(loadUrl);
const initialValue = new PMTiles(loadUrl);
setFile(initialValue);
}
if (location.hash.includes("map")) {
@@ -84,7 +84,7 @@ function App() {
// maintaining URL state
useEffect(() => {
const url = new URL(window.location.href);
if (file && file.source.getKey().startsWith("http")) {
if (file?.source.getKey().startsWith("http")) {
url.searchParams.set("url", file.source.getKey());
history.pushState(null, "", url.toString());
} else {
@@ -93,7 +93,7 @@ function App() {
}
}, [file]);
let clear = (event: React.MouseEvent<HTMLAnchorElement>) => {
const clear = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
setFile(undefined);
};

View File

@@ -1,13 +1,12 @@
import { useState, useEffect, useRef, Dispatch, SetStateAction } from "react";
import { createPortal } from "react-dom";
import { PMTiles, Entry, tileIdToZxy, TileType, Header } from "../../js/index";
import { styled } from "./stitches.config";
import Protobuf from "pbf";
import { VectorTile, VectorTileFeature } from "@mapbox/vector-tile";
import { VectorTile } from "@mapbox/vector-tile";
import { path } from "d3-path";
import { schemeSet3 } from "d3-scale-chromatic";
import { useMeasure } from "react-use";
import Protobuf from "pbf";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
import { UncontrolledReactSVGPanZoom } from "react-svg-pan-zoom";
import { useMeasure } from "react-use";
import { Entry, Header, PMTiles, TileType, tileIdToZxy } from "../../js/index";
import { styled } from "./stitches.config";
const TableContainer = styled("div", {
height: "calc(100vh - $4 - $4)",
@@ -15,7 +14,7 @@ const TableContainer = styled("div", {
width: "calc(100%/3)",
});
const SVGContainer = styled("div", {
const SvgContainer = styled("div", {
width: "100%",
height: "calc(100vh - $4 - $4)",
});
@@ -45,7 +44,7 @@ const TileRow = (props: {
entry: Entry;
setSelectedEntry: (val: Entry | null) => void;
}) => {
let [z, x, y] = tileIdToZxy(props.entry.tileId);
const [z, x, y] = tileIdToZxy(props.entry.tileId);
return (
<TableRow
onClick={() => {
@@ -59,7 +58,7 @@ const TileRow = (props: {
<td>{props.entry.offset}</td>
<td>{props.entry.length}</td>
<td>
{props.entry.runLength == 0
{props.entry.runLength === 0
? "directory"
: `tile(${props.entry.runLength})`}
</td>
@@ -80,7 +79,7 @@ interface Feature {
properties: any;
}
let smartCompare = (a: Layer, b: Layer): number => {
const smartCompare = (a: Layer, b: Layer): number => {
if (a.name === "earth") return -4;
if (a.name === "water") return -3;
if (a.name === "natural") return -2;
@@ -93,7 +92,7 @@ const FeatureSvg = (props: {
feature: Feature;
setSelectedFeature: Dispatch<SetStateAction<Feature | null>>;
}) => {
let [highlighted, setHighlighted] = useState(false);
const [highlighted, setHighlighted] = useState(false);
let fill = "none";
let stroke = "";
@@ -103,15 +102,15 @@ const FeatureSvg = (props: {
stroke = highlighted ? "white" : "currentColor";
}
let mouseOver = () => {
const mouseOver = () => {
setHighlighted(true);
};
let mouseOut = () => {
const mouseOut = () => {
setHighlighted(false);
};
let mouseDown = () => {
const mouseDown = () => {
props.setSelectedFeature(props.feature);
};
@@ -125,7 +124,7 @@ const FeatureSvg = (props: {
onMouseOver={mouseOver}
onMouseOut={mouseOut}
onMouseDown={mouseDown}
></path>
/>
);
};
@@ -134,12 +133,12 @@ const LayerSvg = (props: {
color: string;
setSelectedFeature: Dispatch<SetStateAction<Feature | null>>;
}) => {
let elems = props.layer.features.map((f, i) => (
const elems = props.layer.features.map((f, i) => (
<FeatureSvg
key={i}
feature={f}
setSelectedFeature={props.setSelectedFeature}
></FeatureSvg>
/>
));
return <g color={props.color}>{elems}</g>;
};
@@ -153,8 +152,8 @@ const StyledFeatureProperties = styled("div", {
});
const FeatureProperties = (props: { feature: Feature }) => {
let tmp: [string, string][] = [];
for (var key in props.feature.properties) {
const tmp: [string, string][] = [];
for (const key in props.feature.properties) {
tmp.push([key, props.feature.properties[key]]);
}
@@ -180,44 +179,44 @@ const VectorPreview = (props: {
entry: Entry;
tileType: TileType;
}) => {
let [layers, setLayers] = useState<Layer[]>([]);
let [maxExtent, setMaxExtent] = useState<number>(0);
let [selectedFeature, setSelectedFeature] = useState<Feature | null>(null);
const Viewer = useRef<UncontrolledReactSVGPanZoom>(null);
const [layers, setLayers] = useState<Layer[]>([]);
const [maxExtent, setMaxExtent] = useState<number>(0);
const [selectedFeature, setSelectedFeature] = useState<Feature | null>(null);
const viewer = useRef<UncontrolledReactSVGPanZoom>(null);
const [ref, { width, height }] = useMeasure<HTMLDivElement>();
useEffect(() => {
Viewer.current!.zoomOnViewerCenter(0.1);
viewer.current?.zoomOnViewerCenter(0.1);
}, []);
useEffect(() => {
let fn = async (entry: Entry) => {
let [z, x, y] = tileIdToZxy(entry.tileId);
let resp = await props.file.getZxy(z, x, y);
const fn = async (entry: Entry) => {
const [z, x, y] = tileIdToZxy(entry.tileId);
const resp = await props.file.getZxy(z, x, y);
let tile = new VectorTile(new Protobuf(new Uint8Array(resp!.data)));
let newLayers = [];
let max_extent = 0;
for (let [name, layer] of Object.entries(tile.layers)) {
if (layer.extent > max_extent) {
max_extent = layer.extent;
const tile = new VectorTile(new Protobuf(new Uint8Array(resp!.data)));
const newLayers = [];
let maxExtent = 0;
for (const [name, layer] of Object.entries(tile.layers)) {
if (layer.extent > maxExtent) {
maxExtent = layer.extent;
}
let features: Feature[] = [];
for (var i = 0; i < layer.length; i++) {
let feature = layer.feature(i);
let p = path();
let geom = feature.loadGeometry();
const features: Feature[] = [];
for (let i = 0; i < layer.length; i++) {
const feature = layer.feature(i);
const p = path();
const geom = feature.loadGeometry();
if (feature.type === 1) {
for (let ring of geom) {
for (let pt of ring) {
for (const ring of geom) {
for (const pt of ring) {
p.arc(pt.x, pt.y, 20, 0, 2 * Math.PI);
}
}
} else {
for (let ring of geom) {
for (const ring of geom) {
p.moveTo(ring[0].x, ring[0].y);
for (var j = 1; j < ring.length; j++) {
for (let j = 1; j < ring.length; j++) {
p.lineTo(ring[j].x, ring[j].y);
}
if (feature.type === 3) {
@@ -235,7 +234,7 @@ const VectorPreview = (props: {
}
newLayers.push({ features: features, name: name });
}
setMaxExtent(max_extent);
setMaxExtent(maxExtent);
newLayers.sort(smartCompare);
setLayers(newLayers);
};
@@ -245,19 +244,19 @@ const VectorPreview = (props: {
}
}, [props.entry]);
let elems = layers.map((l, i) => (
const elems = layers.map((l, i) => (
<LayerSvg
key={i}
layer={l}
color={schemeSet3[i % 12]}
setSelectedFeature={setSelectedFeature}
></LayerSvg>
/>
));
return (
<SVGContainer ref={ref}>
<SvgContainer ref={ref}>
<UncontrolledReactSVGPanZoom
ref={Viewer}
ref={viewer}
width={width}
height={height}
detectAutoPan={false}
@@ -268,20 +267,20 @@ const VectorPreview = (props: {
<svg viewBox="0 0 4096 4096">{elems}</svg>
</UncontrolledReactSVGPanZoom>
{selectedFeature ? <FeatureProperties feature={selectedFeature} /> : null}
</SVGContainer>
</SvgContainer>
);
};
const RasterPreview = (props: { file: PMTiles; entry: Entry }) => {
let [imgSrc, setImageSrc] = useState<string>("");
const [imgSrc, setImageSrc] = useState<string>("");
useEffect(() => {
let fn = async (entry: Entry) => {
const fn = async (entry: Entry) => {
// TODO 0,0,0 is broken
let [z, x, y] = tileIdToZxy(entry.tileId);
let resp = await props.file.getZxy(z, x, y);
let blob = new Blob([resp!.data]);
var imageUrl = window.URL.createObjectURL(blob);
const [z, x, y] = tileIdToZxy(entry.tileId);
const resp = await props.file.getZxy(z, x, y);
const blob = new Blob([resp!.data]);
const imageUrl = window.URL.createObjectURL(blob);
setImageSrc(imageUrl);
};
@@ -290,12 +289,12 @@ const RasterPreview = (props: { file: PMTiles; entry: Entry }) => {
}
}, [props.entry]);
return <img src={imgSrc}></img>;
return <img src={imgSrc} alt="raster tile" />;
};
function getHashString(entry: Entry) {
const [z, x, y] = tileIdToZxy(entry.tileId);
let hash = `${z}/${x}/${y}`;
const hash = `${z}/${x}/${y}`;
const hashName = "inspector";
let found = false;
@@ -318,9 +317,9 @@ function getHashString(entry: Entry) {
}
function Inspector(props: { file: PMTiles }) {
let [entryRows, setEntryRows] = useState<Entry[]>([]);
let [selectedEntry, setSelectedEntryRaw] = useState<Entry | null>(null);
let [header, setHeader] = useState<Header | null>(null);
const [entryRows, setEntryRows] = useState<Entry[]>([]);
const [selectedEntry, setSelectedEntryRaw] = useState<Entry | null>(null);
const [header, setHeader] = useState<Header | null>(null);
function setSelectedEntry(val: Entry | null) {
if (val && val.runLength > 0) {
@@ -330,13 +329,13 @@ function Inspector(props: { file: PMTiles }) {
}
useEffect(() => {
let fn = async () => {
let header = await props.file.getHeader();
const fn = async () => {
const header = await props.file.getHeader();
setHeader(header);
if (header.specVersion < 3) {
setEntryRows([]);
} else if (selectedEntry !== null && selectedEntry.runLength === 0) {
let entries = await props.file.cache.getDirectory(
const entries = await props.file.cache.getDirectory(
props.file.source,
header.leafDirectoryOffset + selectedEntry.offset,
selectedEntry.length,
@@ -344,7 +343,7 @@ function Inspector(props: { file: PMTiles }) {
);
setEntryRows(entries);
} else if (selectedEntry === null) {
let entries = await props.file.cache.getDirectory(
const entries = await props.file.cache.getDirectory(
props.file.source,
header.rootDirectoryOffset,
header.rootDirectoryLength,
@@ -357,11 +356,11 @@ function Inspector(props: { file: PMTiles }) {
fn();
}, [props.file, selectedEntry]);
let rows = entryRows.map((e, i) => (
<TileRow key={i} entry={e} setSelectedEntry={setSelectedEntry}></TileRow>
const rows = entryRows.map((e, i) => (
<TileRow key={i} entry={e} setSelectedEntry={setSelectedEntry} />
));
let tilePreview = <div></div>;
let tilePreview = <div />;
if (selectedEntry && header?.tileType) {
if (selectedEntry.runLength === 0) {
// do nothing

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState } from "react";
import { PMTiles } from "../../js/index";
import { styled } from "./stitches.config";
@@ -6,7 +6,7 @@ import Inspector from "./Inspector";
import MaplibreMap from "./MaplibreMap";
import Metadata from "./Metadata";
import { MagnifyingGlassIcon, ImageIcon } from "@radix-ui/react-icons";
import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
const StyledToolbar = styled(ToolbarPrimitive.Root, {
@@ -28,7 +28,7 @@ const itemStyles = {
fontSize: "$2",
alignItems: "center",
"&:hover": { backgroundColor: "$hover", color: "$white" },
"&:focus": { position: "relative", boxShadow: `0 0 0 2px blue` },
"&:focus": { position: "relative", boxShadow: "0 0 0 2px blue" },
};
const StyledLink = styled(
@@ -71,9 +71,9 @@ const ToolbarToggleGroup = StyledToggleGroup;
const ToolbarToggleItem = StyledToggleItem;
function Loader(props: { file: PMTiles; mapHashPassed: boolean }) {
let [tab, setTab] = useState("maplibre");
const [tab, setTab] = useState("maplibre");
let view;
let view: any;
if (tab === "maplibre") {
view = (
<MaplibreMap file={props.file} mapHashPassed={props.mapHashPassed} />

View File

@@ -1,17 +1,17 @@
import React, { useState, useEffect, useRef } from "react";
import { renderToString } from "react-dom/server";
import { PMTiles, TileType } from "../../js/index";
import { Protocol } from "../../js/adapters";
import { styled } from "./stitches.config";
import {
LayerSpecification,
StyleSpecification,
} from "@maplibre/maplibre-gl-style-spec";
import { schemeSet3 } from "d3-scale-chromatic";
import maplibregl from "maplibre-gl";
import { MapGeoJSONFeature } from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";
import { schemeSet3 } from "d3-scale-chromatic";
import base_theme from "protomaps-themes-base";
import {
StyleSpecification,
LayerSpecification,
} from "@maplibre/maplibre-gl-style-spec";
import baseTheme from "protomaps-themes-base";
import React, { useState, useEffect, useRef } from "react";
import { renderToString } from "react-dom/server";
import { Protocol } from "../../js/adapters";
import { PMTiles, TileType } from "../../js/index";
import { styled } from "./stitches.config";
const INITIAL_ZOOM = 0;
const INITIAL_LNG = 0;
@@ -102,7 +102,7 @@ interface LayerVisibility {
visible: boolean;
}
interface PMTilesMetadata {
interface Metadata {
name?: string;
type?: string;
tilestats?: unknown;
@@ -187,12 +187,12 @@ const LayersVisibilityController = (props: {
};
const rasterStyle = async (file: PMTiles): Promise<StyleSpecification> => {
let header = await file.getHeader();
let metadata = (await file.getMetadata()) as PMTilesMetadata;
const header = await file.getHeader();
const metadata = (await file.getMetadata()) as Metadata;
let layers: LayerSpecification[] = [];
if (metadata.type !== "baselayer") {
layers = base_theme("basemap", "black");
layers = baseTheme("basemap", "black");
}
layers.push({
@@ -206,7 +206,7 @@ const rasterStyle = async (file: PMTiles): Promise<StyleSpecification> => {
sources: {
source: {
type: "raster",
tiles: ["pmtiles://" + file.source.getKey() + "/{z}/{x}/{y}"],
tiles: [`pmtiles://${file.source.getKey()}/{z}/{x}/{y}`],
minzoom: header.minZoom,
maxzoom: header.maxZoom,
},
@@ -228,25 +228,23 @@ const vectorStyle = async (
style: StyleSpecification;
layersVisibility: LayerVisibility[];
}> => {
let header = await file.getHeader();
let metadata = (await file.getMetadata()) as PMTilesMetadata;
const header = await file.getHeader();
const metadata = (await file.getMetadata()) as Metadata;
let layers: LayerSpecification[] = [];
let baseOpacity = 0.35;
if (metadata.type !== "baselayer") {
layers = base_theme("basemap", "black");
layers = baseTheme("basemap", "black");
baseOpacity = 0.9;
}
var tilestats: any;
var vector_layers: LayerSpecification[];
tilestats = metadata.tilestats;
vector_layers = metadata.vector_layers;
const tilestats = metadata.tilestats;
const vectorLayers = metadata.vector_layers;
if (vector_layers) {
for (let [i, layer] of vector_layers.entries()) {
if (vectorLayers) {
for (const [i, layer] of vectorLayers.entries()) {
layers.push({
id: layer.id + "_fill",
id: `${layer.id}_fill`,
type: "fill",
source: "source",
"source-layer": layer.id,
@@ -268,7 +266,7 @@ const vectorStyle = async (
filter: ["==", ["geometry-type"], "Polygon"],
});
layers.push({
id: layer.id + "_stroke",
id: `${layer.id}_stroke`,
type: "line",
source: "source",
"source-layer": layer.id,
@@ -284,7 +282,7 @@ const vectorStyle = async (
filter: ["==", ["geometry-type"], "LineString"],
});
layers.push({
id: layer.id + "_point",
id: `${layer.id}_point`,
type: "circle",
source: "source",
"source-layer": layer.id,
@@ -315,7 +313,7 @@ const vectorStyle = async (
sources: {
source: {
type: "vector",
tiles: ["pmtiles://" + file.source.getKey() + "/{z}/{x}/{y}"],
tiles: [`pmtiles://${file.source.getKey()}/{z}/{x}/{y}`],
minzoom: header.minZoom,
maxzoom: header.maxZoom,
bounds: bounds,
@@ -331,7 +329,7 @@ const vectorStyle = async (
glyphs: "https://cdn.protomaps.com/fonts/pbf/{fontstack}/{range}.pbf",
layers: layers,
},
layersVisibility: vector_layers.map((l: LayerSpecification) => ({
layersVisibility: vectorLayers.map((l: LayerSpecification) => ({
id: l.id,
visible: true,
})),
@@ -339,11 +337,13 @@ const vectorStyle = async (
};
function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
let mapContainerRef = useRef<HTMLDivElement>(null);
let [hamburgerOpen, setHamburgerOpen] = useState<boolean>(true);
let [showAttributes, setShowAttributes] = useState<boolean>(false);
let [showTileBoundaries, setShowTileBoundaries] = useState<boolean>(false);
let [layersVisibility, setLayersVisibility] = useState<LayerVisibility[]>([]);
const mapContainerRef = useRef<HTMLDivElement>(null);
const [hamburgerOpen, setHamburgerOpen] = useState<boolean>(true);
const [showAttributes, setShowAttributes] = useState<boolean>(false);
const [showTileBoundaries, setShowTileBoundaries] = useState<boolean>(false);
const [layersVisibility, setLayersVisibility] = useState<LayerVisibility[]>(
[]
);
const mapRef = useRef<maplibregl.Map | null>(null);
const hoveredFeaturesRef = useRef<Set<MapGeoJSONFeature>>(new Set());
@@ -383,7 +383,7 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
};
useEffect(() => {
let protocol = new Protocol();
const protocol = new Protocol();
maplibregl.addProtocol("pmtiles", protocol.tile);
protocol.add(props.file); // this is necessary for non-HTTP sources
@@ -423,7 +423,7 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
const { x, y } = e.point;
const r = 2; // radius around the point
var features = map.queryRenderedFeatures([
let features = map.queryRenderedFeatures([
[x - r, y - r],
[x + r, y + r],
]);
@@ -436,7 +436,9 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
hoveredFeatures.add(feature);
}
let content = renderToString(<FeaturesProperties features={features} />);
const content = renderToString(
<FeaturesProperties features={features} />
);
if (!features.length) {
popup.remove();
} else {
@@ -452,10 +454,10 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
}, []);
useEffect(() => {
let initStyle = async () => {
const initStyle = async () => {
if (mapRef.current) {
let map = mapRef.current;
let header = await props.file.getHeader();
const map = mapRef.current;
const header = await props.file.getHeader();
if (!props.mapHashPassed) {
// the map hash was not passed, so auto-detect the initial viewport based on metadata
map.fitBounds(
@@ -471,12 +473,12 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
if (
header.tileType === TileType.Png ||
header.tileType === TileType.Webp ||
header.tileType == TileType.Jpeg
header.tileType === TileType.Jpeg
) {
let style = await rasterStyle(props.file);
const style = await rasterStyle(props.file);
map.setStyle(style);
} else {
let { style, layersVisibility } = await vectorStyle(props.file);
const { style, layersVisibility } = await vectorStyle(props.file);
map.setStyle(style);
setLayersVisibility(layersVisibility);
}
@@ -488,7 +490,7 @@ function MaplibreMap(props: { file: PMTiles; mapHashPassed: boolean }) {
return (
<MapContainer ref={mapContainerRef}>
<div ref={mapContainerRef}></div>
<div ref={mapContainerRef} />
<Hamburger onClick={toggleHamburger}>menu</Hamburger>
{hamburgerOpen ? (
<Options>

View File

@@ -1,7 +1,7 @@
import { useState, useEffect } from "react";
import { PMTiles, Header } from "../../js/index";
import { styled } from "./stitches.config";
import { JsonViewer } from "@textea/json-viewer";
import { useEffect, useState } from "react";
import { Header, PMTiles } from "../../js/index";
import { styled } from "./stitches.config";
const Padded = styled("div", {
padding: "2rem",
@@ -13,11 +13,11 @@ const Heading = styled("div", {
});
function Metadata(props: { file: PMTiles }) {
let [metadata, setMetadata] = useState<any>();
let [header, setHeader] = useState<Header | null>(null);
const [metadata, setMetadata] = useState<unknown>();
const [header, setHeader] = useState<Header | null>(null);
useEffect(() => {
let pmtiles = props.file;
const pmtiles = props.file;
const fetchData = async () => {
setMetadata(await pmtiles.getMetadata());
setHeader(await pmtiles.getHeader());

View File

@@ -1,8 +1,7 @@
import { useState, Dispatch, SetStateAction, useCallback } from "react";
import maplibregl from "maplibre-gl";
import { PMTiles, FileSource } from "../../js/index";
import { styled } from "./stitches.config";
import { Dispatch, SetStateAction, useCallback, useState } from "react";
import { useDropzone } from "react-dropzone";
import { FileSource, PMTiles } from "../../js/index";
import { styled } from "./stitches.config";
import * as LabelPrimitive from "@radix-ui/react-label";
@@ -13,7 +12,7 @@ const Input = styled("input", {
justifyContent: "center",
fontSize: "$3",
fontFamily: "$sans",
"&:focus": { boxShadow: `0 0 0 1px black` },
"&:focus": { boxShadow: "0 0 0 1px black" },
width: "100%",
border: "1px solid $white",
padding: "$1",
@@ -115,8 +114,8 @@ function Start(props: {
onDrop,
});
let [remoteUrl, setRemoteUrl] = useState<string>("");
let [selectedExample, setSelectedExample] = useState<number | null>(1);
const [remoteUrl, setRemoteUrl] = useState<string>("");
const [selectedExample, setSelectedExample] = useState<number | null>(1);
const onRemoteUrlChangeHandler = (
event: React.ChangeEvent<HTMLInputElement>
@@ -142,7 +141,7 @@ function Start(props: {
id="remoteUrl"
placeholder="https://example.com/my_archive.pmtiles"
onChange={onRemoteUrlChangeHandler}
></Input>
/>
<Button color="gray" onClick={onSubmit} disabled={!remoteUrl.trim()}>
Load URL
</Button>

View File

@@ -1,9 +1,12 @@
import React from "react";
import ReactDOM from "react-dom/client";
import reactDom from "react-dom/client";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
const root = document.getElementById("root");
if (root) {
reactDom.createRoot(root).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
}