+ return entries.map(([objKey, val]) => (
+
- {key}:
+ {objKey}:
{' '}
{renderValueContent(val)}
@@ -188,12 +189,12 @@ function NestedPropertyRow({ entity, mode, propertyName, expectedProperties = []
// Render any remaining properties (excluding null values)
Object.entries(data)
- .filter(([key]) => !processedKeys.has(key))
- .filter(([key, value]) => value != null)
- .forEach(([key, value]) => {
+ .filter(([objKey]) => !processedKeys.has(objKey))
+ .filter(([, value]) => value != null)
+ .forEach(([objKey, value]) => {
renderedProperties.push(
-
- {key}:
+
+ {objKey}:
{renderValue(value)}
);
@@ -224,4 +225,10 @@ function NestedPropertyRow({ entity, mode, propertyName, expectedProperties = []
);
}
+NestedPropertyRow.propTypes = {
+ entity: PropTypes.object.isRequired,
+ propertyName: PropTypes.string.isRequired,
+ expectedProperties: PropTypes.array,
+};
+
export default NestedPropertyRow;
diff --git a/site/src/inspector_panel/SourcesRow.jsx b/site/src/inspector_panel/SourcesRow.jsx
index 1503262c..430a47ab 100644
--- a/site/src/inspector_panel/SourcesRow.jsx
+++ b/site/src/inspector_panel/SourcesRow.jsx
@@ -1,3 +1,4 @@
+import PropTypes from "prop-types";
import InfoToolTip from "./InfoToolTip";
import "./SourcesRow.css";
@@ -92,4 +93,10 @@ function SourcesRow({ entity, mode, tips }) {
);
}
+SourcesRow.propTypes = {
+ entity: PropTypes.object.isRequired,
+ mode: PropTypes.string.isRequired,
+ tips: PropTypes.object.isRequired,
+};
+
export default SourcesRow;
diff --git a/site/src/inspector_panel/TableRow.jsx b/site/src/inspector_panel/TableRow.jsx
index d96dccee..c8f9f3e1 100644
--- a/site/src/inspector_panel/TableRow.jsx
+++ b/site/src/inspector_panel/TableRow.jsx
@@ -1,7 +1,7 @@
import PropTypes from "prop-types";
import "./TableRow.css";
-function TableRow({ mode, table_key, entity, indented = false }) {
+function TableRow({ table_key, entity, indented = false }) {
// Function to check if a value is a URL
const isURL = (value) => {
if (!value || typeof value !== 'string') return false;
@@ -68,13 +68,13 @@ function TableRow({ mode, table_key, entity, indented = false }) {
// Handle objects
if (typeof value === 'object') {
- const entries = Object.entries(value).filter(([key, val]) => val != null && val !== "null");
+ const entries = Object.entries(value).filter(([, val]) => val != null && val !== "null");
if (entries.length === 0) return '{}';
- return entries.map(([key, val]) => (
-
+ return entries.map(([objKey, val]) => (
+
- {key}:
+ {objKey}:
{' '}
{renderValueContent(val)}
@@ -159,7 +159,6 @@ function TableRow({ mode, table_key, entity, indented = false }) {
}
TableRow.propTypes = {
- mode: PropTypes.string,
table_key: PropTypes.string.isRequired,
entity: PropTypes.object.isRequired,
indented: PropTypes.bool,
diff --git a/site/src/inspector_panel/ThemePanel.jsx b/site/src/inspector_panel/ThemePanel.jsx
index b7435e24..2ed97f3a 100644
--- a/site/src/inspector_panel/ThemePanel.jsx
+++ b/site/src/inspector_panel/ThemePanel.jsx
@@ -1,10 +1,10 @@
+import PropTypes from "prop-types";
import TableRow from "./TableRow";
import "./ThemePanel.css";
import IndentIcon from "../icons/icon-indent.svg?react";
import InfoToolTip from "./InfoToolTip";
import PushPinIcon from "@mui/icons-material/PushPin";
import PushPinOutlinedIcon from "@mui/icons-material/PushPinOutlined";
-import ThemeIcon from "./ThemeIcon";
import SourcesRow from "./SourcesRow.jsx";
import NestedPropertyRow from "./NestedPropertyRow.jsx";
@@ -18,6 +18,7 @@ const sharedProperties = [
"categories",
"subtype",
"class",
+ "subclass",
"version",
];
@@ -121,13 +122,11 @@ function ThemePanel({ mode, entity, tips, activeThemes, setActiveThemes }) {
)}
@@ -152,7 +151,7 @@ function ThemePanel({ mode, entity, tips, activeThemes, setActiveThemes }) {
.filter((key) => !sharedProperties.includes(key))
.filter((key) => entity[key] != null && entity[key] !== "null")
.map((key) => (
-
+
))}
@@ -160,4 +159,12 @@ function ThemePanel({ mode, entity, tips, activeThemes, setActiveThemes }) {
);
}
+ThemePanel.propTypes = {
+ mode: PropTypes.string.isRequired,
+ entity: PropTypes.object.isRequired,
+ tips: PropTypes.object.isRequired,
+ activeThemes: PropTypes.array.isRequired,
+ setActiveThemes: PropTypes.func.isRequired,
+};
+
export default ThemePanel;
diff --git a/site/src/inspector_panel/utils/PropertyOrderer.js b/site/src/inspector_panel/utils/PropertyOrderer.js
index 0c90cedc..2349708d 100644
--- a/site/src/inspector_panel/utils/PropertyOrderer.js
+++ b/site/src/inspector_panel/utils/PropertyOrderer.js
@@ -18,7 +18,7 @@ export const createOrderedKeys = (entity) => {
processedKeys.add(key);
// If this is "class" and "subclass" exists, add subclass right after
- if (key === "class" && entity.hasOwnProperty("subclass")) {
+ if (key === "class" && Object.prototype.hasOwnProperty.call(entity, "subclass")) {
orderedKeys.push({ key: "subclass", indented: true });
processedKeys.add("subclass");
}
diff --git a/site/src/nav/DownloadButton.jsx b/site/src/nav/DownloadButton.jsx
index 5d131f5c..cc612c6f 100644
--- a/site/src/nav/DownloadButton.jsx
+++ b/site/src/nav/DownloadButton.jsx
@@ -28,7 +28,7 @@ function DownloadButton({ mode, zoom, setZoom, visibleTypes}) {
myMap.getBounds();
setZoom(myMap.getZoom());
}
- }, [myMap]);
+ }, [myMap, setZoom]);
const handleDownloadClick = async () => {
@@ -219,4 +219,11 @@ function DownloadButton({ mode, zoom, setZoom, visibleTypes}) {
);
}
+DownloadButton.propTypes = {
+ mode: PropTypes.string.isRequired,
+ zoom: PropTypes.number.isRequired,
+ setZoom: PropTypes.func.isRequired,
+ visibleTypes: PropTypes.array.isRequired,
+};
+
export default DownloadButton;
diff --git a/site/src/nav/Header.jsx b/site/src/nav/Header.jsx
index 872afed9..2a324a17 100644
--- a/site/src/nav/Header.jsx
+++ b/site/src/nav/Header.jsx
@@ -30,6 +30,9 @@ export default function Header({ zoom, mode, setMode, setZoom, visibleTypes}) {
}
Header.propTypes = {
+ zoom: PropTypes.number.isRequired,
mode: PropTypes.string.isRequired,
setMode: PropTypes.func.isRequired,
+ setZoom: PropTypes.func.isRequired,
+ visibleTypes: PropTypes.array.isRequired,
};
diff --git a/site/src/navigator/Navigator.jsx b/site/src/navigator/Navigator.jsx
index e3626ac4..99101b13 100644
--- a/site/src/navigator/Navigator.jsx
+++ b/site/src/navigator/Navigator.jsx
@@ -1,25 +1,12 @@
+import PropTypes from "prop-types";
import "./Navigator.css";
import CloseIcon from "@mui/icons-material/Close";
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
import FlightTakeoffIcon from "@mui/icons-material/FlightTakeoff";
import { useMap } from "react-map-gl/maplibre";
import { tours } from "./NavigatorConfig";
-import { useState, useEffect } from "react";
-export function useNavigatorState(initialOpen = false) {
- const [navigatorOpen, setNavigatorOpen] = useState(() => {
- const stored = localStorage.getItem("navigatorOpen");
- return stored !== null ? JSON.parse(stored) : !initialOpen;
- });
-
- useEffect(() => {
- localStorage.setItem("navigatorOpen", JSON.stringify(navigatorOpen));
- }, [navigatorOpen]);
-
- return [navigatorOpen, setNavigatorOpen];
-}
-
-function Navigator({ open, setOpen, map, setVisibleTypes, setActiveThemes }) {
+function Navigator({ open, setOpen, setVisibleTypes, setActiveThemes }) {
const { myMap } = useMap();
const handleTourSelect = (tourId) => {
@@ -44,7 +31,7 @@ function Navigator({ open, setOpen, map, setVisibleTypes, setActiveThemes }) {
- We've picked a few spots around the world you might be interested
+ We've picked a few spots around the world you might be interested
in seeing.
@@ -75,4 +62,11 @@ function Navigator({ open, setOpen, map, setVisibleTypes, setActiveThemes }) {
);
}
+Navigator.propTypes = {
+ open: PropTypes.bool.isRequired,
+ setOpen: PropTypes.func.isRequired,
+ setVisibleTypes: PropTypes.func.isRequired,
+ setActiveThemes: PropTypes.func.isRequired,
+};
+
export default Navigator;
diff --git a/site/src/navigator/useNavigatorState.js b/site/src/navigator/useNavigatorState.js
new file mode 100644
index 00000000..724855fd
--- /dev/null
+++ b/site/src/navigator/useNavigatorState.js
@@ -0,0 +1,14 @@
+import { useState, useEffect } from "react";
+
+export function useNavigatorState(initialOpen = false) {
+ const [navigatorOpen, setNavigatorOpen] = useState(() => {
+ const stored = localStorage.getItem("navigatorOpen");
+ return stored !== null ? JSON.parse(stored) : !initialOpen;
+ });
+
+ useEffect(() => {
+ localStorage.setItem("navigatorOpen", JSON.stringify(navigatorOpen));
+ }, [navigatorOpen]);
+
+ return [navigatorOpen, setNavigatorOpen];
+}
diff --git a/site/vite.config.js b/site/vite.config.js
index 2c88fda6..31d38ea7 100644
--- a/site/vite.config.js
+++ b/site/vite.config.js
@@ -1,3 +1,4 @@
+/* eslint-env node */
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import svgr from 'vite-plugin-svgr'