diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
deleted file mode 100644
index 1cb64bbd..00000000
--- a/.github/workflows/build.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
-
-name: Build
-
-on: pull_request
-
-jobs:
- build:
- name: Build
- runs-on: ubuntu-latest
-
- strategy:
- matrix:
- node-version: [20]
-
- steps:
- - uses: actions/checkout@v4
- - name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v4
- with:
- node-version: ${{ matrix.node-version }}
- - run: cd site; npm install
-
- - name: Setup vars 📋
- id: vars
- run: |
- echo identifier=$(git rev-parse --short ${{ github.sha }})-20.x >> $GITHUB_OUTPUT
- echo distdir=aria-site/aria$(git rev-parse --short ${{ github.sha }})-20.x-dist >> $GITHUB_OUTPUT
-
- - run: cd site; npm run build
- env:
- BASE_ARIA_URL: ${{ steps.vars.outputs.identifier }}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index d2499de2..d1167434 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -1,11 +1,10 @@
-# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+# This workflow will do a clean install of node dependencies and deploy to AWS.
name: Deploy
on:
push:
- branches: [ main , manifest_driven_downloads]
+ branches: [ main ]
pull_request:
branches: [ main ]
@@ -37,7 +36,7 @@ jobs:
run: cd site; npm install
- name: Build 🏃
run: cd site; npm run build
- env:
+ env:
BASE_ARIA_URL: ${{ steps.vars.outputs.ariabaseurl }}
- name: Upload artifacts 📤
uses: actions/upload-artifact@v4
@@ -50,6 +49,9 @@ jobs:
name: Deploy
runs-on: ubuntu-latest
needs: build
+ environment:
+ name: preview
+ url: https://d13285jxgcxetl.cloudfront.net/aria/${{ steps.vars.outputs.distdir }}/index.html
steps:
- name: Checkout 📥
@@ -94,4 +96,4 @@ jobs:
- name: Deployment complete! 🚀
run: |
- echo "Your build is at: https://d13285jxgcxetl.cloudfront.net/aria/${{ steps.vars.outputs.distdir }}/index.html"
\ No newline at end of file
+ echo "Your build is at: https://d13285jxgcxetl.cloudfront.net/aria/${{ steps.vars.outputs.distdir }}/index.html" >> $GITHUB_STEP_SUMMARY
diff --git a/site/src/DownloadCatalog.js b/site/src/DownloadCatalog.js
index 997a293c..e90cee26 100644
--- a/site/src/DownloadCatalog.js
+++ b/site/src/DownloadCatalog.js
@@ -1,5 +1,5 @@
// URL for the remote manifest file
-const STAC_URL = 'https://labs.overturemaps.org/stac/catalog.json'
+const STAC_URL = 'https://stac.overturemaps.org/catalog.json'
// Cache the manifest to avoid repeated fetches
let cachedManifest = null;
@@ -24,7 +24,7 @@ async function fetchManifest() {
})
.then(stacData => {
const latest = stacData.latest;
- return fetch(`https://labs.overturemaps.org/stac/${latest}/manifest.geojson`);
+ return fetch(`https://stac.overturemaps.org/${latest}/manifest.geojson`);
})
.then(response => {
if (!response.ok) {
diff --git a/site/src/Map.jsx b/site/src/Map.jsx
index 5e21486f..b86073e8 100644
--- a/site/src/Map.jsx
+++ b/site/src/Map.jsx
@@ -20,8 +20,11 @@ import { layers } from "./Layers";
import ThemeTypeLayer from "./ThemeTypeLayer";
import FeaturePopup from "./FeatureSelector";
+// Fetch the latest Overture release from Overture STAC
+const LATEST_RELEASE = await fetch('https://stac.overturemaps.org/catalog.json').then(r => r.json()).then(r => r.latest.split('.')[0])
+
const PMTILES_URL =
- "pmtiles://https://d3c1b7bog2u1nn.cloudfront.net/2025-07-23/";
+ "pmtiles://https://d3c1b7bog2u1nn.cloudfront.net/" + LATEST_RELEASE + "/";
const INITIAL_VIEW_STATE = {
latitude: 38.90678,
diff --git a/site/src/inspector_panel/InspectorPanel.css b/site/src/inspector_panel/InspectorPanel.css
index 00f8a3b3..1cd10972 100644
--- a/site/src/inspector_panel/InspectorPanel.css
+++ b/site/src/inspector_panel/InspectorPanel.css
@@ -32,7 +32,7 @@
overflow-y: scroll;
overflow-x: auto;
max-height: 250px;
- table-layout: fixed;
+ table-layout: auto;
}
table th {
diff --git a/site/src/inspector_panel/InspectorPanel.jsx b/site/src/inspector_panel/InspectorPanel.jsx
index ce2e0279..a671731e 100644
--- a/site/src/inspector_panel/InspectorPanel.jsx
+++ b/site/src/inspector_panel/InspectorPanel.jsx
@@ -32,6 +32,19 @@ function InspectorPanel({
const theme = entity["theme"];
+ // Determine the panel title - use name if available, otherwise default
+ let panelTitle = "Inspector Panel";
+ if (entity["names"]) {
+ try {
+ const names = JSON.parse(entity["names"]);
+ if (names["primary"]) {
+ panelTitle = names["primary"];
+ }
+ } catch (e) {
+ // If parsing fails, keep default title
+ }
+ }
+
let inspectorPanel =
-
Inspector Panel
+
{panelTitle}
)}
+ {source.between && (
+
+ between:
+ {source.between}
+
+ )}
{index < sources.length - 1 && (
)}
diff --git a/site/src/inspector_panel/TableRow.css b/site/src/inspector_panel/TableRow.css
index b4734f37..19822aeb 100644
--- a/site/src/inspector_panel/TableRow.css
+++ b/site/src/inspector_panel/TableRow.css
@@ -1,65 +1,76 @@
.inspector-panel {
+ table {
+ table-layout: auto !important;
+ width: 100% !important;
+ display: block;
+ }
+
+ tbody {
+ display: block;
+ }
+
+ tr {
+ display: flex;
+ border-top: none;
+ border-bottom: 1px solid var(--border-color-light);
+ width: 100%;
+ }
+
td {
- padding: 5px;
- vertical-align: top;
+ padding: 2px 0;
+ padding-right: 8px;
border-left: 0;
border-right: 0;
word-break: break-all;
+ font-size: 13px;
+ display: block;
}
td:first-child {
text-align: left;
- width: 40%;
- word-break: normal;
- border: none;
- }
-
- td:last-child {
- overflow: hidden;
- width: 60%;
+ word-break: keep-all;
+ white-space: nowrap;
border: none;
+ padding-right: 6px;
+ font-size: 11px;
+ font-weight: 500;
+ color: #64748b;
+ flex-shrink: 0;
+ width: auto;
}
- tr {
- border-top: none;
- border-bottom: var(--ifm-table-border-width) solid
- var(--ifm-table-border-color);
- }
-
- td.expanded {
- max-height: unset;
- white-space: normal;
+ td:first-child strong {
+ font-weight: bold;
+ color: var(--ifm-font-color-base);
+ font-size: 13px;
}
- td.collapsed {
- max-height: 26px;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ td:last-child {
+ border: none;
+ word-break: break-word;
+ flex: 1;
+ min-width: 0;
}
-}
-button.expand {
- border: none;
- border-radius: 50%;
- padding: 0;
- height: 26px;
- width: 26px;
- justify-content: center;
- background-color: unset;
}
-button.expand:focus {
- outline: none;
+/* Nested content styling */
+.nested-content {
+ margin-left: 8px;
+ border-left: 1px solid #e2e8f0;
+ padding-left: 6px;
+ margin-top: 2px;
}
-div.first-child {
- display: flex;
- justify-content: space-between;
- padding-right: 10px;
+.nested-item {
+ margin: 1px 0;
+ line-height: 1.3;
+ font-size: 12px;
}
-svg.ec-icon {
- padding-top: 1px;
- fill: var(--ifm-color-secondary-darkest);
+.nested-key {
+ color: #64748b;
+ font-size: 11px;
+ font-weight: 500;
+ margin-right: 3px;
}
diff --git a/site/src/inspector_panel/TableRow.jsx b/site/src/inspector_panel/TableRow.jsx
index aaf4081d..d96dccee 100644
--- a/site/src/inspector_panel/TableRow.jsx
+++ b/site/src/inspector_panel/TableRow.jsx
@@ -1,49 +1,167 @@
import PropTypes from "prop-types";
-import { useState } from "react";
import "./TableRow.css";
-import AddIcon from "@mui/icons-material/Add";
-import RemoveIcon from "@mui/icons-material/Remove";
-import InfoToolTip from "./InfoToolTip";
-function TableRow({ mode, table_key, entity }) {
- const [expanded, setExpanded] = useState(false);
+function TableRow({ mode, table_key, entity, indented = false }) {
+ // Function to check if a value is a URL
+ const isURL = (value) => {
+ if (!value || typeof value !== 'string') return false;
+ try {
+ const url = new URL(value);
+ return url.protocol === 'http:' || url.protocol === 'https:';
+ } catch {
+ return false;
+ }
+ };
+
+ // Function to render a single URL as a clickable link
+ const renderURL = (url) => (
+
+ {url}
+
+ );
+
+ // Function to render value content without nested-content wrapper (for recursive calls)
+ const renderValueContent = (value) => {
+ // Handle null/undefined
+ if (value == null) return 'null';
+
+ // Handle arrays first - render inline for simple arrays, items for complex ones
+ if (Array.isArray(value)) {
+ if (value.length === 0) return '[]';
+
+ // Check if all items are URLs
+ const allURLs = value.every(item => isURL(item));
+
+ // Check if all items are simple (strings, numbers, booleans)
+ const allSimple = value.every(item =>
+ typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean'
+ );
+
+ if (allURLs) {
+ // Render URL arrays as items
+ return value.map((item, index) => (
+
+ {renderURL(item)}
+
+ ));
+ } else if (allSimple && value.length <= 3) {
+ // Render simple arrays inline
+ return `[${value.join(', ')}]`;
+ }
+
+ // Render complex or long arrays as items
+ return value.map((item, index) => (
+
+ {renderValueContent(item)}
+
+ ));
+ }
+
+ // Handle objects
+ if (typeof value === 'object') {
+ const entries = Object.entries(value).filter(([key, val]) => val != null && val !== "null");
+ if (entries.length === 0) return '{}';
+
+ return entries.map(([key, val]) => (
+
+
+ {key}:
+ {' '}
+ {renderValueContent(val)}
+
+ ));
+ }
- const handleExpand = () => {
- setExpanded(!expanded);
+ // Now handle strings - convert to string for further processing
+ const stringValue = value.toString();
+
+ // Try to parse as JSON if it looks like an array or object
+ if ((stringValue.startsWith('[') && stringValue.endsWith(']')) ||
+ (stringValue.startsWith('{') && stringValue.endsWith('}'))) {
+ try {
+ const parsed = JSON.parse(stringValue);
+ return renderValueContent(parsed);
+ } catch {
+ // If parsing fails, fall through to regular string handling
+ }
+ }
+
+ // Check if it's a URL
+ if (isURL(stringValue)) {
+ return renderURL(stringValue);
+ }
+
+ return stringValue;
+ };
+
+ // Function to render value with nested-content wrapper (for top-level calls)
+ const renderValue = (value) => {
+ // Handle simple values directly
+ if (value == null ||
+ typeof value === 'string' ||
+ typeof value === 'number' ||
+ typeof value === 'boolean') {
+ return renderValueContent(value);
+ }
+
+ // Handle arrays - check if simple first
+ if (Array.isArray(value)) {
+ if (value.length === 0) return '[]';
+
+ const allSimple = value.every(item =>
+ typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean'
+ );
+
+ if (allSimple && value.length <= 3) {
+ return `[${value.join(', ')}]`;
+ }
+ }
+
+ // For complex values (objects, complex arrays), wrap in nested-content
+ const content = renderValueContent(value);
+
+ // If content is a string, return it directly
+ if (typeof content === 'string') {
+ return content;
+ }
+
+ // Otherwise wrap in nested-content
+ return (
+
+ {content}
+
+ );
};
return (
-
-
- {table_key}
-
-
+ |
+ {table_key}:
|
{entity[table_key] != null ? (
-
- {entity[table_key].toString()}{" "}
+ |
+ {renderValue(entity[table_key])}
|
) : (
- None Found |
+ None Found |
)}
);
}
TableRow.propTypes = {
- entity: PropTypes.object,
+ mode: PropTypes.string,
+ table_key: PropTypes.string.isRequired,
+ entity: PropTypes.object.isRequired,
+ indented: PropTypes.bool,
};
export default TableRow;
diff --git a/site/src/inspector_panel/ThemePanel.jsx b/site/src/inspector_panel/ThemePanel.jsx
index cf60a1dc..b7435e24 100644
--- a/site/src/inspector_panel/ThemePanel.jsx
+++ b/site/src/inspector_panel/ThemePanel.jsx
@@ -6,6 +6,7 @@ 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";
const sharedProperties = [
"theme",
@@ -13,28 +14,24 @@ const sharedProperties = [
"update_time",
"id",
"sources",
+ "names",
+ "categories",
"subtype",
+ "class",
"version",
];
function ThemePanel({ mode, entity, tips, activeThemes, setActiveThemes }) {
return (
- {entity["names"] ? (
-
-
- {JSON.parse(entity["names"])["primary"]}
-
-
- ) : (
- <>>
- )}
{entity["id"] ? (
-
id:
-
{
- navigator.clipboard.writeText(entity["id"]);
- }}>{entity["id"]}
+
+ id:
+ {
+ navigator.clipboard.writeText(entity["id"]);
+ }}>{entity["id"]}
+
@@ -99,6 +96,42 @@ function ThemePanel({ mode, entity, tips, activeThemes, setActiveThemes }) {
) : (
<>>
)}
+ {entity["class"] ? (
+
+
+
class:
+ {entity["class"]}
+ {entity["subclass"] ? (
+
+ subclass:
+ {entity["subclass"]}
+
+ ) : (
+ <>>
+ )}
+
+
+
+ ) : (
+ <>>
+ )}
+
+
+
{entity["sources"] ? (
) : (
@@ -117,6 +150,7 @@ function ThemePanel({ mode, entity, tips, activeThemes, setActiveThemes }) {
{Object.keys(entity)
.filter((key) => !key.startsWith("@"))
.filter((key) => !sharedProperties.includes(key))
+ .filter((key) => entity[key] != null && entity[key] !== "null")
.map((key) => (
))}
diff --git a/site/src/main.jsx b/site/src/main.jsx
index 3fc910fc..fe0129b1 100644
--- a/site/src/main.jsx
+++ b/site/src/main.jsx
@@ -3,14 +3,9 @@ import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import 'infima/dist/css/default/default.css';
-import initWasm from "@geoarrow/geoarrow-wasm/esm/index.js";
-import wasmUrl from "@geoarrow/geoarrow-wasm/esm/index_bg.wasm?url"
-
-//TODO: Make this async and parallelize with the startup of the map component, rather than blocking in.
-await initWasm(wasmUrl);
ReactDOM.createRoot(document.getElementById('root')).render(
,
-)
\ No newline at end of file
+)
diff --git a/site/src/nav/DownloadButton.jsx b/site/src/nav/DownloadButton.jsx
index 11320d9a..5d131f5c 100644
--- a/site/src/nav/DownloadButton.jsx
+++ b/site/src/nav/DownloadButton.jsx
@@ -12,6 +12,8 @@ import RefreshIcon from "../icons/icon-refresh.svg?react";
import "./DownloadButton.css";
import Floater from "react-floater";
import CloseIcon from "@mui/icons-material/Close";
+import initWasm from "@geoarrow/geoarrow-wasm/esm/index.js";
+import wasmUrl from "@geoarrow/geoarrow-wasm/esm/index_bg.wasm?url"
const ZOOM_BOUND = 15;
@@ -29,6 +31,11 @@ function DownloadButton({ mode, zoom, setZoom, visibleTypes}) {
}, [myMap]);
const handleDownloadClick = async () => {
+
+ //TODO: Make this async and parallelize with the startup of the map component, rather than blocking in.
+ await initWasm(wasmUrl);
+
+
setLoading(true);
try {
//Get current map dimensions and convert to bbox