Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 46 additions & 46 deletions src/components/app.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { AbsoluteCenter, Box, Center, FileUpload } from "@chakra-ui/react";
import { useDuckDb } from "duckdb-wasm-kit";
import { type ReactNode, useEffect } from "react";
import { lazy, Suspense, type ReactNode } from "react";
import { ErrorBoundary } from "react-error-boundary";
import { useStacGeoparquet } from "../contexts/stac-geoparquet";
import { useStore } from "../store";
import { warmStacWasm } from "../utils/stac-wasm";
import { uploadFile } from "../utils/upload";
import Map from "./map";
import Overlay from "./overlay";
import type { ExtraLayerProps } from "./stac-map";
import { ErrorBoundaryAlert } from "./ui/error-alert";

const StacGeoparquetFeature =
import.meta.env.VITE_STAC_GEOPARQUET === "false"
? null
: lazy(() => import("./stac-geoparquet"));

function MapFallback({ error }: { error: unknown }) {
return (
<AbsoluteCenter h="100%" w="100%">
Expand All @@ -28,61 +32,57 @@ function OverlayFallback({ error }: { error: unknown }) {
);
}

function AppContents({ extraLayers }: { extraLayers?: ExtraLayerProps[] }) {
const setUploadedFile = useStore((state) => state.setUploadedFile);
const parquetCtx = useStacGeoparquet();
return (
<Box h={"100%"} w={"100%"}>
<FileUpload.Root
unstyled={true}
onFileAccept={(details) => {
void uploadFile({
file: details.files[0],
setUploadedFile,
registerParquet: parquetCtx?.registerParquet,
});
}}
h={"100%"}
w={"100%"}
>
<FileUpload.HiddenInput />
<FileUpload.Dropzone disableClick={true} h={"100%"} w={"100%"}>
<ErrorBoundary FallbackComponent={MapFallback}>
<Map extraLayers={extraLayers} />
</ErrorBoundary>
</FileUpload.Dropzone>
</FileUpload.Root>
</Box>
);
}

export default function App({
footer,
extraLayers,
}: {
footer?: ReactNode;
extraLayers?: ExtraLayerProps[];
}) {
const setUploadedFile = useStore((state) => state.setUploadedFile);
const setConnection = useStore((state) => state.setConnection);
const { db } = useDuckDb();

useEffect(() => {
if (db) {
(async () => {
const connection = await db.connect();
await connection.query("LOAD spatial;");
await connection.query("LOAD icu;");
await connection.query("LOAD httpfs;");
setConnection(connection);
})();
}
}, [db, setConnection]);

useEffect(() => {
warmStacWasm();
}, []);

return (
const inner = (
<>
<Box h={"100%"} w={"100%"}>
<FileUpload.Root
unstyled={true}
onFileAccept={(details) => {
uploadFile({
file: details.files[0],
setUploadedFile,
db,
});
}}
disabled={!db}
h={"100%"}
w={"100%"}
>
<FileUpload.HiddenInput />
<FileUpload.Dropzone disableClick={true} h={"100%"} w={"100%"}>
<ErrorBoundary FallbackComponent={MapFallback}>
<Map extraLayers={extraLayers} />
</ErrorBoundary>
</FileUpload.Dropzone>
</FileUpload.Root>
</Box>
<AppContents extraLayers={extraLayers} />
<ErrorBoundary FallbackComponent={OverlayFallback}>
<Overlay />
</ErrorBoundary>
{footer}
</>
);

if (StacGeoparquetFeature) {
return (
<Suspense fallback={inner}>
<StacGeoparquetFeature>{inner}</StacGeoparquetFeature>
</Suspense>
);
}
return inner;
}
13 changes: 11 additions & 2 deletions src/components/examples.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import { Badge, Menu, Portal, Span } from "@chakra-ui/react";
import { type ReactNode } from "react";
import { useMemo, type ReactNode } from "react";
import { useExamples } from "../contexts/examples";
import { useStacGeoparquet } from "../contexts/stac-geoparquet";
import { useStore } from "../store";

export function Examples({ children }: { children: ReactNode }) {
const setHref = useStore((store) => store.setHref);
const examples = useExamples();
const parquetCtx = useStacGeoparquet();
const visibleExamples = useMemo(
() =>
parquetCtx
? examples
: examples.filter((example) => example.badge !== "stac-geoparquet"),
[examples, parquetCtx]
);
return (
<Menu.Root onSelect={(details) => setHref(details.value)}>
<Menu.Trigger asChild>{children}</Menu.Trigger>
<Portal>
<Menu.Positioner>
<Menu.Content>
{examples.map(({ title, badge, href }, index) => (
{visibleExamples.map(({ title, badge, href }, index) => (
<Menu.Item key={"example-" + index} value={href}>
{title}
<Span flex={1}></Span>
Expand Down
16 changes: 10 additions & 6 deletions src/components/href-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ import {
Input,
InputGroup,
} from "@chakra-ui/react";
import { useDuckDb } from "duckdb-wasm-kit";
import { useState } from "react";
import { LuUpload } from "react-icons/lu";
import { useStacGeoparquet } from "../contexts/stac-geoparquet";
import { useStore } from "../store";
import { uploadFile } from "../utils/upload";

export default function HrefInput() {
const href = useStore((store) => store.href);
const setHref = useStore((state) => state.setHref);
const setUploadedFile = useStore((store) => store.setUploadedFile);
const { db } = useDuckDb();
const parquetCtx = useStacGeoparquet();
const [input, setInput] = useState(href || "");
const [lastHref, setLastHref] = useState(href);

Expand All @@ -24,6 +24,10 @@ export default function HrefInput() {
setInput(href || "");
}

const placeholder = parquetCtx
? "Enter a url to a STAC API, JSON, or GeoParquet"
: "Enter a url to a STAC API or JSON";

return (
<Box
as={"form"}
Expand All @@ -37,16 +41,16 @@ export default function HrefInput() {
endElement={
<FileUpload.Root
onFileAccept={(details) =>
uploadFile({
void uploadFile({
file: details.files[0],
setUploadedFile,
db,
registerParquet: parquetCtx?.registerParquet,
})
}
>
<FileUpload.HiddenInput />
<FileUpload.Trigger asChild>
<IconButton variant={"plain"} size={"sm"} disabled={!db}>
<IconButton variant={"plain"} size={"sm"}>
<LuUpload />
</IconButton>
</FileUpload.Trigger>
Expand All @@ -55,7 +59,7 @@ export default function HrefInput() {
>
<Input
bg={"bg.muted/90"}
placeholder="Enter a url to a STAC API, JSON, or GeoParquet"
placeholder={placeholder}
value={input}
onChange={(e) => setInput(e.target.value)}
/>
Expand Down
27 changes: 3 additions & 24 deletions src/components/items.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type BBox2D, useStore } from "@/store";
import { getItemsDatetimeExtent, itemMatchesFilter } from "@/utils/datetime";
import { fitBoundsToBbox } from "@/utils/map";
import { fetchStacValue, getSelfHref } from "@/utils/stac";
import { loadStacWasm } from "@/utils/stac-wasm";
import { useStacGeoparquet } from "../contexts/stac-geoparquet";
import {
Button,
ButtonGroup,
Expand All @@ -11,7 +11,6 @@ import {
DownloadTrigger,
Input,
InputGroup,
Spinner,
Stack,
} from "@chakra-ui/react";
import { GeoJsonLayer } from "@deck.gl/layers";
Expand All @@ -30,7 +29,7 @@ export function Items({ items }: { items: StacItem[] }) {
const [filterByMapBbox, setFilterByMapBbox] = useState(true);
const [filterText, setFilterText] = useState("");
const [hovered, setHovered] = useState<StacItem>();
const [isExporting, setIsExporting] = useState(false);
const parquetCtx = useStacGeoparquet();
const setHref = useStore((store) => store.setHref);
const setLayer = useStore((store) => store.setLayer);
const fillColor = useStore((store) => store.fillColor);
Expand Down Expand Up @@ -159,27 +158,7 @@ export function Items({ items }: { items: StacItem[] }) {
<LuDownload /> JSON
</Button>
</DownloadTrigger>
<DownloadTrigger
fileName="items.parquet"
mimeType="application/vnd.apache.parquet"
data={async () => {
try {
setIsExporting(true);
const stacWasm = await loadStacWasm();
return new Blob([
stacWasm.stacJsonToParquet(items) as BlobPart,
]);
} finally {
setIsExporting(false);
}
}}
asChild
>
<Button disabled={isExporting}>
{isExporting ? <Spinner size="xs" /> : <LuDownload />}{" "}
stac-geoparquet
</Button>
</DownloadTrigger>
{parquetCtx && <parquetCtx.ParquetExportButton items={items} />}
</ButtonGroup>
<EntityList
items={filteredItems}
Expand Down
Loading
Loading