Skip to content

Commit 60c7cee

Browse files
authored
Merge pull request #324 from UW-Macrostrat/maps-update
Maps update
2 parents 4bd3543 + 2648f2d commit 60c7cee

File tree

8 files changed

+435
-342
lines changed

8 files changed

+435
-342
lines changed

.yarnrc.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ pnpFallbackMode: all
1717

1818
pnpMode: loose
1919

20-
yarnPath: .yarn/releases/yarn-4.8.1.cjs
20+
yarnPath: .yarn/releases/yarn-4.8.1.cjs

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"@vitejs/plugin-react": "^4.0.4",
100100
"allotment": "^1.20.2",
101101
"axios": "^1.7.9",
102+
"cesium": "^1.130.1",
102103
"chroma-js": "^3.0.0",
103104
"classnames": "^2.2.6",
104105
"compression": "^1.7.4",

pages/maps/+Page.ts

Lines changed: 80 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,177 +1,98 @@
1-
import h from "./main.module.scss";
2-
import { Spinner, Switch, AnchorButton } from "@blueprintjs/core";
1+
import h from "./main.module.sass";
2+
import { Switch, AnchorButton, Icon } from "@blueprintjs/core";
33
import { ContentPage } from "~/layouts";
44
import {
5-
PageHeader,
65
DevLinkButton,
76
AssistantLinks,
87
LinkCard,
98
StickyHeader,
109
} from "~/components";
11-
import { useState, useRef, useEffect } from "react";
12-
import { useAPIResult } from "@macrostrat/ui-components";
10+
import { useState } from "react";
11+
import { PostgRESTInfiniteScrollView } from "@macrostrat/ui-components";
1312
import { apiDomain } from "@macrostrat-web/settings";
1413
import { IDTag, SearchBar } from "~/components/general";
1514
import { useData } from "vike-react/useData";
1615
import { PageBreadcrumbs } from "~/components";
1716

17+
const PAGE_SIZE = 20;
18+
1819
export function Page() {
1920
const { sources } = useData();
2021

21-
const [input, setInput] = useState("");
2222
const [activeOnly, setActiveOnly] = useState(true);
23-
const [recentOrder, setRecentOrder] = useState(false);
24-
25-
const startingID = sources[sources.length - 1].source_id;
26-
const [key, setKey] = useState({
27-
lastID: startingID,
28-
lastYear: 9999,
29-
});
30-
const [data, setData] = useState(sources);
31-
const pageSize = 20;
32-
33-
const result = useSourceData(
34-
key.lastID,
35-
input,
36-
pageSize,
37-
activeOnly,
38-
recentOrder,
39-
key.lastYear
40-
);
41-
42-
useEffect(() => {
43-
if (
44-
result &&
45-
data[data.length - 1]?.source_id !== result[result.length - 1]?.source_id
46-
) {
47-
setData((prevData) => {
48-
return [...prevData, ...result];
49-
});
50-
}
51-
}, [result]);
52-
53-
const resetData = () => {
54-
window.scrollTo(0, 0);
55-
setData([]);
56-
setKey({
57-
lastID: 0,
58-
lastYear: 9999,
59-
});
60-
};
61-
62-
const handleInputChange = (event) => {
63-
setInput(event.toLowerCase());
64-
resetData();
65-
};
66-
67-
const handleActiveChange = () => {
68-
setActiveOnly(!activeOnly);
69-
resetData();
70-
};
71-
72-
const handleRecentChange = () => {
73-
setRecentOrder(!recentOrder);
74-
resetData();
75-
};
76-
77-
return h("div.maps-page", [
78-
h(ContentPage, [
79-
h(StickyHeader, { className: "header-container" }, [
80-
h("div.header", [
81-
h(PageBreadcrumbs, {
82-
title: "Maps",
83-
}),
84-
h(SearchBar, {
85-
placeholder: "Filter by name...",
86-
onChange: handleInputChange,
87-
}),
88-
h(Switch, {
89-
label: "Active only",
90-
checked: activeOnly,
91-
onChange: handleActiveChange,
92-
}),
93-
h(Switch, {
94-
label: "Recent order",
95-
checked: recentOrder,
96-
onChange: handleRecentChange,
97-
}),
98-
]),
99-
h(AssistantLinks, { className: "assistant-links" }, [
100-
h(
101-
AnchorButton,
102-
{ icon: "flows", href: "/maps/ingestion" },
103-
"Ingestion system"
23+
const [recentOrder, setRecentOrder] = useState(true);
24+
25+
const key = "" + activeOnly + recentOrder; // should be able to remove on next release (4.3.7)
26+
27+
const initialItems = recentOrder && activeOnly ? sources : undefined;
28+
29+
return h("div.maps-list-page", [
30+
h(ContentPage, [
31+
h("div.flex-row", [
32+
h('div.main', [
33+
h(StickyHeader, { className: "header-container" }, [
34+
h("div.header", [
35+
h(PageBreadcrumbs, {
36+
title: "Maps",
37+
showLogo: true,
38+
}),
39+
h('div.search', [
40+
h('div.switches', [
41+
h(Switch, {
42+
label: "Active only",
43+
checked: activeOnly,
44+
onChange: () => {
45+
window.scrollTo(0, 0);
46+
setActiveOnly(!activeOnly);
47+
},
48+
}),
49+
h(Switch, {
50+
label: "Recent order",
51+
checked: recentOrder,
52+
onChange: () => {
53+
window.scrollTo(0, 0);
54+
setRecentOrder(!recentOrder)
55+
},
56+
}),
57+
]),
58+
]),
59+
]),
60+
]),
61+
h(PostgRESTInfiniteScrollView, {
62+
route: `${apiDomain}/api/pg/sources_metadata`,
63+
id_key: "source_id",
64+
order_key: recentOrder ? "ref_year" : undefined,
65+
filterable: true,
66+
extraParams: {
67+
is_finalized: activeOnly ? "eq.true" : undefined,
68+
},
69+
ascending: !recentOrder,
70+
limit: PAGE_SIZE,
71+
itemComponent: SourceItem,
72+
initialItems,
73+
key
74+
})
75+
]),
76+
h("div.sidebar",
77+
h("div.sidebar-content", [
78+
h(AssistantLinks, { className: "assistant-links" }, [
79+
h(
80+
AnchorButton,
81+
{ icon: "flows", href: "/maps/ingestion" },
82+
"Ingestion system"
83+
),
84+
h(AnchorButton, { icon: "map", href: "/map/sources" }, "Show on map"),
85+
h(DevLinkButton, { href: "/maps/legend" }, "Legend table"),
86+
]),
87+
])
10488
),
105-
h(AnchorButton, { icon: "map", href: "/map/sources" }, "Show on map"),
106-
h(DevLinkButton, { href: "/maps/legend" }, "Legend table"),
107-
]),
89+
])
10890
]),
109-
h(
110-
"div.strat-list",
111-
h(
112-
"div.strat-items",
113-
data.map((data) => h(SourceItem, { data }))
114-
)
115-
),
116-
LoadMoreTrigger({ data, setKey, pageSize, result }),
117-
]),
118-
]);
119-
}
120-
121-
function useSourceData(
122-
lastID,
123-
input,
124-
pageSize,
125-
activeOnly,
126-
recentOrder,
127-
lastYear
128-
) {
129-
const url = `${apiDomain}/api/pg/sources_metadata`;
130-
131-
const result = useAPIResult(url, {
132-
is_finalized: activeOnly ? "eq.true" : undefined,
133-
status_code: activeOnly ? "eq.active" : undefined,
134-
source_id: !recentOrder ? `gt.${lastID}` : undefined,
135-
or: recentOrder
136-
? `(ref_year.lt.${lastYear},and(ref_year.eq.${lastYear},source_id.gt.${lastID}))`
137-
: undefined,
138-
name: `ilike.%${input}%`,
139-
limit: pageSize,
140-
order: recentOrder ? "ref_year.desc,source_id.asc" : "source_id.asc",
141-
});
142-
return result;
143-
}
144-
145-
function LoadMoreTrigger({ data, setKey, pageSize, result }) {
146-
const ref = useRef(null);
147-
148-
useEffect(() => {
149-
if (!ref.current) return;
150-
151-
const observer = new IntersectionObserver(([entry]) => {
152-
if (entry.isIntersecting) {
153-
if (data.length > 0) {
154-
const id = data[data.length - 1].source_id;
155-
const year = data[data.length - 1].ref_year || 9999;
156-
157-
setKey({
158-
lastID: id,
159-
lastYear: year,
160-
});
161-
}
162-
}
163-
});
164-
165-
observer.observe(ref.current);
166-
167-
return () => observer.disconnect();
168-
}, [data]);
169-
170-
return h.if(result?.length == pageSize)("div.load-more", { ref }, h(Spinner));
91+
]);
17192
}
17293

17394
function SourceItem({ data }) {
174-
const { source_id, name, ref_title, url, scale, ref_year } = data;
95+
const { source_id, name, ref_title, url, scale, ref_year, ref_source } = data;
17596
const href = `/maps/${source_id}`;
17697

17798
return h(
@@ -185,7 +106,12 @@ function SourceItem({ data }) {
185106
},
186107
[
187108
h("div.content", [
188-
h("a", { href: url, target: "_blank" }, ref_title),
109+
h('div.source', [
110+
h("span", ref_source + ": " + ref_title + " (" + ref_year + ") "),
111+
h("a", { href: url, target: "_blank" },
112+
h(Icon, { icon: "link" }),
113+
),
114+
]),
189115
h("div.tags", [h(IDTag, { id: source_id })]),
190116
]),
191117
]

pages/maps/+data.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
1-
import { postgrest } from "~/_providers";
1+
import { apiDomain } from "@macrostrat-web/settings";
22

33
export async function data(pageContext) {
44
// `.page.server.js` files always run in Node.js; we could use SQL/ORM queries here.
55

6-
const res = await postgrest
7-
.from("sources_metadata")
8-
.select("*")
9-
.eq("is_finalized", true)
10-
.eq("status_code", "active")
11-
.order("source_id", { ascending: true })
12-
.limit(20);
6+
const params = new URLSearchParams({
7+
is_finalized: "eq.true",
8+
status_code: "eq.active",
9+
or: `(ref_year.lt.9999,and(ref_year.eq.9999,source_id.gt.0))`,
10+
limit: 20,
11+
order: "ref_year.desc,source_id.asc",
12+
}).toString()
1313

14-
return { sources: res.data };
14+
const res = await fetch(`${apiDomain}/api/pg/sources_metadata?${params}`)
15+
if (!res.ok) {
16+
throw new Error(`Failed to fetch data: ${res.statusText}`);
17+
}
18+
const data = await res.json();
19+
20+
return { sources: data };
1521
}

0 commit comments

Comments
 (0)