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

20
app/biome.json Normal file
View File

@@ -0,0 +1,20 @@
{
"javascript": {
"formatter": {
"trailingComma": "es5"
}
},
"formatter": {
"indentStyle": "space"
},
"linter": {
"rules": {
"style": {
"useNamingConvention": {}
},
"nursery": {
"noUnusedImports": {}
}
}
}
}

156
app/package-lock.json generated
View File

@@ -28,6 +28,7 @@
"react-use": "^17.4.0" "react-use": "^17.4.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.5.3",
"@maplibre/maplibre-gl-style-spec": "^19.3.1", "@maplibre/maplibre-gl-style-spec": "^19.3.1",
"@types/d3-path": "^3.0.0", "@types/d3-path": "^3.0.0",
"@types/d3-scale-chromatic": "^3.0.0", "@types/d3-scale-chromatic": "^3.0.0",
@@ -448,6 +449,161 @@
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@biomejs/biome": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.5.3.tgz",
"integrity": "sha512-yvZCa/g3akwTaAQ7PCwPWDCkZs3Qa5ONg/fgOUT9e6wAWsPftCjLQFPXBeGxPK30yZSSpgEmRCfpGTmVbUjGgg==",
"dev": true,
"hasInstallScript": true,
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.*"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "1.5.3",
"@biomejs/cli-darwin-x64": "1.5.3",
"@biomejs/cli-linux-arm64": "1.5.3",
"@biomejs/cli-linux-arm64-musl": "1.5.3",
"@biomejs/cli-linux-x64": "1.5.3",
"@biomejs/cli-linux-x64-musl": "1.5.3",
"@biomejs/cli-win32-arm64": "1.5.3",
"@biomejs/cli-win32-x64": "1.5.3"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.5.3.tgz",
"integrity": "sha512-ImU7mh1HghEDyqNmxEZBoMPr8SxekkZuYcs+gynKlNW+TALQs7swkERiBLkG9NR0K1B3/2uVzlvYowXrmlW8hw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.5.3.tgz",
"integrity": "sha512-vCdASqYnlpq/swErH7FD6nrFz0czFtK4k/iLgj0/+VmZVjineFPgevOb+Sr9vz0tk0GfdQO60bSpI74zU8M9Dw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.5.3.tgz",
"integrity": "sha512-cupBQv0sNF1OKqBfx7EDWMSsKwRrBUZfjXawT4s6hKV6ALq7p0QzWlxr/sDmbKMLOaLQtw2Qgu/77N9rm+f9Rg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.5.3.tgz",
"integrity": "sha512-DYuMizUYUBYfS0IHGjDrOP1RGipqWfMGEvNEJ398zdtmCKLXaUvTimiox5dvx4X15mBK5M2m8wgWUgOP1giUpQ==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.5.3.tgz",
"integrity": "sha512-YQrSArQvcv4FYsk7Q91Yv4uuu5F8hJyORVcv3zsjCLGkjIjx2RhjYLpTL733SNL7v33GmOlZY0eFR1ko38tuUw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.5.3.tgz",
"integrity": "sha512-UUHiAnlDqr2Y/LpvshBFhUYMWkl2/Jn+bi3U6jKuav0qWbbBKU/ByHgR4+NBxpKBYoCtWxhnmatfH1bpPIuZMw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.5.3.tgz",
"integrity": "sha512-HxatYH7vf/kX9nrD+pDYuV2GI9GV8EFo6cfKkahAecTuZLPxryHx1WEfJthp5eNsE0+09STGkKIKjirP0ufaZA==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.5.3.tgz",
"integrity": "sha512-fMvbSouZEASU7mZH8SIJSANDm5OqsjgtVXlbUqxwed6BP7uuHRSs396Aqwh2+VoW8fwTpp6ybIUoC9FrzB0kyA==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.*"
}
},
"node_modules/@emotion/babel-plugin": { "node_modules/@emotion/babel-plugin": {
"version": "11.10.5", "version": "11.10.5",
"resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.5.tgz",

View File

@@ -30,6 +30,7 @@
"react-use": "^17.4.0" "react-use": "^17.4.0"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.5.3",
"@maplibre/maplibre-gl-style-spec": "^19.3.1", "@maplibre/maplibre-gl-style-spec": "^19.3.1",
"@types/d3-path": "^3.0.0", "@types/d3-path": "^3.0.0",
"@types/d3-scale-chromatic": "^3.0.0", "@types/d3-scale-chromatic": "^3.0.0",

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

View File

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

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"; import { useState } from "react";
import { PMTiles } from "../../js/index"; import { PMTiles } from "../../js/index";
import { styled } from "./stitches.config"; import { styled } from "./stitches.config";
@@ -6,7 +6,7 @@ import Inspector from "./Inspector";
import MaplibreMap from "./MaplibreMap"; import MaplibreMap from "./MaplibreMap";
import Metadata from "./Metadata"; 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"; import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
const StyledToolbar = styled(ToolbarPrimitive.Root, { const StyledToolbar = styled(ToolbarPrimitive.Root, {
@@ -28,7 +28,7 @@ const itemStyles = {
fontSize: "$2", fontSize: "$2",
alignItems: "center", alignItems: "center",
"&:hover": { backgroundColor: "$hover", color: "$white" }, "&: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( const StyledLink = styled(
@@ -71,9 +71,9 @@ const ToolbarToggleGroup = StyledToggleGroup;
const ToolbarToggleItem = StyledToggleItem; const ToolbarToggleItem = StyledToggleItem;
function Loader(props: { file: PMTiles; mapHashPassed: boolean }) { 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") { if (tab === "maplibre") {
view = ( view = (
<MaplibreMap file={props.file} mapHashPassed={props.mapHashPassed} /> <MaplibreMap file={props.file} mapHashPassed={props.mapHashPassed} />

View File

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

View File

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

View File

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