diff --git a/packages/lithology-hierarchy/src/index.ts b/packages/lithology-hierarchy/src/index.ts index 2aa1d5950..7bda860b7 100644 --- a/packages/lithology-hierarchy/src/index.ts +++ b/packages/lithology-hierarchy/src/index.ts @@ -4,8 +4,9 @@ import { apiV2Prefix } from "@macrostrat-web/settings"; import { Spinner } from "@blueprintjs/core"; import { useAPIResult, ErrorCallout } from "@macrostrat/ui-components"; import { useState } from "react"; -import { nestLiths, Lith } from "./nest-data"; +import { nestLiths, nestItems, Lith } from "./nest-data"; import Hierarchy from "./simple-hierarchy"; +import LexHierarchyInner from "./lex-hierarchy"; const h = hyper.styled(styles); @@ -35,3 +36,13 @@ export default function MacrostratLithologyHierarchy({ width, height }) { ]), ]); } + +export function LexHierarchy({ width, height, data, href = null, onClick = () => {} }: { width: string | number; height: string | number; data: Lith[]; href?: string | null; onClick?: () => void }) { + const nestedData = nestItems(data); + + return h("div.flex.row", [ + h("div.example-container", [ + h(LexHierarchyInner, { width, height, data: nestedData, href, onClick }), + ]), + ]); +} diff --git a/packages/lithology-hierarchy/src/lex-hierarchy.ts b/packages/lithology-hierarchy/src/lex-hierarchy.ts new file mode 100644 index 000000000..ea187238f --- /dev/null +++ b/packages/lithology-hierarchy/src/lex-hierarchy.ts @@ -0,0 +1,75 @@ +import hyper from "@macrostrat/hyper"; +import styles from "./main.module.sass"; +import { TreeNodeData } from "./nest-data"; +import { LithologyTag } from "@macrostrat/data-components"; +import React, {useMemo} from "react" + +const h = hyper.styled(styles); + +export default function LexHierarchyInner({ data, href, onClick }: { data: TreeNodeData; href: string; onClick: () => void }) { + return h(Tree, { data, level: 0, href, onClick }); +} + +const Tree = React.memo(function Tree({ + data, + level = 0, + href, + onClick, +}: { + data: TreeNodeData; + level: number; + href: string; + onClick: () => void; +}) { + const headerEl = "h" + (level + 2); + + const [subTrees, nodes] = useMemo(() => divideChildren(data), [data]); + + return h("div.tree", { className: `tree-level-${level}` }, [ + h("div.main-tree", [ + h.if(data.children != null)(headerEl, capitalize(data.name)), + h.if(nodes.length > 0)( + "div.nodes", + nodes.map((d) => + h("div.node", { key: d.name }, [ + h(LithologyTag, { + data: d.lith ?? d, + href, + onClick, + }), + ]) + ) + ), + ]), + subTrees.map((d) => + h(Tree, { + key: d.name, + data: d, + level: level + 1, + href, + onClick, + }) + ), + ]); +}); + + +function capitalize(s: string) { + return s.charAt(0).toUpperCase() + s.slice(1); +} + +function divideChildren(data: TreeNodeData) { + /** Divide children into terminal and non-terminal nodes */ + const terminal = []; + const nonTerminal = []; + const { children = [] } = data; + for (const child of children) { + const len = child.children?.length ?? 0; + if (len == 0) { + terminal.push(child); + } else { + nonTerminal.push(child); + } + } + return [nonTerminal, terminal]; +} diff --git a/packages/lithology-hierarchy/src/main.module.sass b/packages/lithology-hierarchy/src/main.module.sass index 4349c1bc0..63fea7174 100644 --- a/packages/lithology-hierarchy/src/main.module.sass +++ b/packages/lithology-hierarchy/src/main.module.sass @@ -37,4 +37,9 @@ li display: inline-block margin-right: 0.5em - line-height: 2em \ No newline at end of file + line-height: 2em + +.nodes + display: flex + gap: .5em + flex-wrap: wrap \ No newline at end of file diff --git a/packages/lithology-hierarchy/src/nest-data.ts b/packages/lithology-hierarchy/src/nest-data.ts index dc41b0f1c..164d523f9 100644 --- a/packages/lithology-hierarchy/src/nest-data.ts +++ b/packages/lithology-hierarchy/src/nest-data.ts @@ -115,3 +115,88 @@ function convert(data: TreeNodeMap): TreeNodeData { children: Array.from(data.children.values()).map(convert), }; } + + +export function nestItems(liths: Lith[]): TreeNodeData { + const root: TreeNodeMap = { name: "Rocks", children: new Map() }; + // Ensure that empty strings are treated as null + for (let lith of liths) { + for (const key of ["type", "group", "class"]) { + if (lith[key] === "") lith[key] = null; + } + } + + for (let lith of liths) { + if (lith.class == null || lith.type == null) + console.error(lith, "Class and type should never be null"); + if (lith.class == null) console.log(lith.name, "Class is null"); + if (lith.type == null) console.log(lith.name, "Type is null"); + + // Create a class if it doesn't exist + if (lith.class != null) { + if (!root.children.has(lith.class)) { + root.children.set(lith.class, { + name: lith.class, + lith, + children: new Map(), + }); + } + } else { + if (!root.children.has(lith.name)) { + root.children.set(lith.name, { name: lith.name, lith }); + } + } + + // Add the type to the class + if (lith.class != null && lith.type != null) { + const parent = root.children.get(lith.class); + if (!parent.children.has(lith.type)) { + parent.children.set(lith.type, { + name: lith.type, + children: new Map(), + lith, + }); + } + } + + // Add the group to the type + if (lith.class != null && lith.type != null) { + if (lith.group != null) { + const parent = root.children.get(lith.class); + const grandparent = parent.children.get(lith.type); + if (!grandparent.children.has(lith.group)) { + grandparent.children.set(lith.group, { + name: lith.group, + children: new Map(), + }); + } + } else { + const parent = root.children.get(lith.class); + const grandparent = parent.children.get(lith.type); + if (!grandparent.children.has(lith.name)) { + grandparent.children.set(lith.name, { name: lith.name, lith }); + } + } + } + + // Add the lithology to the group + if ( + lith.class != null && + lith.type != null && + lith.group != null && + lith.name != null + ) { + const parent = root.children.get(lith.class); + const grandparent = parent.children.get(lith.type); + const greatgrandparent = grandparent.children.get(lith.group); + greatgrandparent.children.set(lith.name, { + name: lith.name, + lith, + children: new Map(), + }); + } + } + + // Export to TreeNode format + return convert(root); +} \ No newline at end of file diff --git a/pages/lex/+Page.client.ts b/pages/lex/+Page.client.ts index 10d9a3e1a..845568c3f 100644 --- a/pages/lex/+Page.client.ts +++ b/pages/lex/+Page.client.ts @@ -123,6 +123,11 @@ export function Page() { { href: "/lex/fossils", title: "Fossils" }, "Fossil taxonomic occurrences from the Paleobiology Database linked to Macrostrat units" ), + h( + LinkCard, + { href: "/lex/measurements", title: "Measurements" }, + "Measurement names and descriptions" + ), h("p", [ h("strong", h("a", { href: "/sift" }, "Sift")), ", Macrostrat's legacy lexicon app, is still available for use as it is gradually brought into this new framework.", diff --git a/pages/lex/economics/+Page.ts b/pages/lex/economics/+Page.client.ts similarity index 50% rename from pages/lex/economics/+Page.ts rename to pages/lex/economics/+Page.client.ts index 52d7ced24..8965d905b 100644 --- a/pages/lex/economics/+Page.ts +++ b/pages/lex/economics/+Page.client.ts @@ -4,7 +4,8 @@ import { useState } from "react"; import { ContentPage } from "~/layouts"; import { useData } from "vike-react/useData"; import { SearchBar } from "~/components/general"; -import { LithologyTag } from "~/components/lex/tag"; +import { LexHierarchy } from "@macrostrat-web/lithology-hierarchy"; +import { navigate } from "vike/client/router"; export function Page() { const { res } = useData(); @@ -27,8 +28,6 @@ export function LexListPage({ res, title, route, id }) { ); }); - const grouped = groupByClassThenType(filtered); - return h(ContentPage, { className: "econ-list-page" }, [ h(StickyHeader, [ h(PageBreadcrumbs, { title }), @@ -37,46 +36,6 @@ export function LexListPage({ res, title, route, id }) { onChange: handleChange, }), ]), - h( - "div.econ-list", - Object.entries(grouped).map(([className, types]) => - h("div.econ-class-group", [ - h("h2", UpperCase(className)), - ...Object.entries(types).map(([type, group]) => - h("div.econ-group", [ - h("h3", UpperCase(type)), - h( - "div.econ-items", - group.map((d) => h(LithologyTag, { data: d, href: `/lex/${route}/${d[id]}` })) - ), - ]) - ), - ]) - ) - ), + h(LexHierarchy, { data: filtered, onClick: (e, item) => navigate(`/lex/${route}/${item[id]}`) }), ]); -} - -function groupByClassThenType(items) { - return items.reduce((acc, item) => { - const { class: className, type } = item; - - if (!type || type.trim() === "") { - return acc; - } - - if (!acc[className]) { - acc[className] = {}; - } - if (!acc[className][type]) { - acc[className][type] = []; - } - - acc[className][type].push(item); - return acc; - }, {}); -} - -function UpperCase(str) { - return str.charAt(0).toUpperCase() + str.slice(1); -} +} \ No newline at end of file diff --git a/pages/lex/environments/+Page.ts b/pages/lex/environments/+Page.client.ts similarity index 80% rename from pages/lex/environments/+Page.ts rename to pages/lex/environments/+Page.client.ts index 1e6c442ba..e7d16bf94 100644 --- a/pages/lex/environments/+Page.ts +++ b/pages/lex/environments/+Page.client.ts @@ -1,6 +1,6 @@ import h from "@macrostrat/hyper"; import { useData } from "vike-react/useData"; -import { LexListPage } from "../economics/+Page"; +import { LexListPage } from "../economics/+Page.client"; export function Page() { const { res } = useData(); diff --git a/pages/lex/measurements/+Page.client.ts b/pages/lex/measurements/+Page.client.ts new file mode 100644 index 000000000..315b85365 --- /dev/null +++ b/pages/lex/measurements/+Page.client.ts @@ -0,0 +1,324 @@ +import h from "./main.module.sass"; + +import { + MapAreaContainer, + MapView, + buildInspectorStyle +} from "@macrostrat/map-interface"; +import { mapboxAccessToken } from "@macrostrat-web/settings"; +import { useEffect, useState } from "react"; +import { useDarkMode, FlexRow } from "@macrostrat/ui-components"; +import { FullscreenPage } from "~/layouts"; +import { MultiSelect } from "@blueprintjs/select" +import { MenuItem, Switch, Divider, Icon } from "@blueprintjs/core"; +import { tileserverDomain } from "@macrostrat-web/settings"; +import { fetchAPIData, fetchPGData } from "~/_utils"; +import { Measurement } from "./measurement"; +import { usePageContext } from "vike-react/usePageContext"; +import { Loading } from "~/components"; + +export function Page() { + const [types, setTypes] = useState([]); + + useEffect(() => { + fetchAPIData("/defs/measurements", { all: true}) + .then(data => setTypes(data)) + .catch(err => console.error("Error fetching data:", err)); + }, []); + return h(FullscreenPage, h(Map, {types})) +} + +function Map({types}) { + const id = usePageContext().urlOriginal.split("=")[1]; + const [clustered, setClustered] = useState(true); + const [selectedMeasurement, setSelectedMeasurement] = useState(null); + + const [selectedTypes, setSelectedTypes] = useState([]); + + useEffect(() => { + if (types.length > 0 && id != null) { + const matching = types.filter((d) => d.measure_id === parseInt(id)); + setSelectedTypes(matching); + } + }, [types, id]); + + + const style = useMapStyle({ selectedTypes, clustered }); + + if(style == null) return null; + + const mapPosition = { + camera: { + lat: 39, + lng: -98, + altitude: 6000000, + }, + }; + + const handleClick = (map, e) => { + const cluster = map.queryRenderedFeatures(e.point, { + layers: ['clusters'] + }); + + if(cluster.length > 0) { + const zoom = cluster[0].properties.expansion_zoom ?? 12; + + map.flyTo({ + center: cluster[0].geometry.coordinates, + zoom: zoom + 2, + speed: 10, + curve: .5, + }); + } + + const features = map.queryRenderedFeatures(e.point, { + layers: ['unclustered-point'] + }); + + if (features.length > 0) { + const properties = features[0].properties; + setSelectedMeasurement(properties.id); + } + }; + + return types.length == 0 ? h(Loading) : h( + "div.map-container", + [ + // The Map Area Container + h( + MapAreaContainer, + { + className: 'map-area-container', + contextPanel: h(Panel, { selectedTypes, setSelectedTypes, clustered, setClustered, selectedMeasurement, setSelectedMeasurement, types }), + key: selectedTypes.join(",") + clustered, + }, + [ + h(MapView, { + style, + mapboxToken: mapboxAccessToken, + mapPosition, + onMapLoaded: (map) => { + map.on('click', (e) => handleClick(map, e)); + } + }), + ] + ), + ] + ); +} + +function useMapStyle({selectedTypes, clustered}) { + const dark = useDarkMode(); + const isEnabled = dark?.isEnabled; + + const baseStyle = isEnabled + ? "mapbox://styles/mapbox/dark-v10" + : "mapbox://styles/mapbox/light-v10"; + + const [actualStyle, setActualStyle] = useState(null); + + const ids = selectedTypes.map((t) => t.measure_id) + + const baseURL = tileserverDomain + "/measurements/tile/{z}/{x}/{y}" + const params = "cluster=" + clustered + (ids.length > 0 ? "&measurement_id=" + ids.join(",") : ""); + + const url = baseURL + "?" + params; + + const baseColor = "#868aa2"; + const endColor = "#212435"; + + const clusteredLayers = [ + { + id: "clusters", + type: "circle", + source: "measurements", + "source-layer": "default", + filter: ['>', ['get', 'n'], 1], + paint: { + "circle-radius": [ + 'step', + ['get', 'n'], + 7, 50, + 9, 100, + 11, 150, + 13, 200, + 15, + ], + "circle-color": [ + 'step', + ['get', 'n'], + "#7b7fa0", 50, + '#636b8d', 100, + '#4a546e', 150, + '#353b49', 200, + endColor + ], + "circle-stroke-color": [ + 'step', + ['get', 'n'], + "#8b8eab", 50, + '#7a7e96', 100, + '#5d5f7c', 150, + '#484b63', + ], + "circle-stroke-width": 3, + "circle-stroke-opacity": 1, + }, + }, + { + id: 'cluster-count', + type: 'symbol', + source: 'measurements', + "source-layer": "default", + filter: ['has', 'n'], + layout: { + 'text-field': ['get', 'n'], + 'text-size': 10, + 'text-allow-overlap': true, + 'text-ignore-placement': true, + }, + paint: { + "text-color": "#fff" + }, + }, + { + id: 'unclustered-point', + type: 'circle', + source: 'measurements', + "source-layer": "default", + filter: ['<=', ['get', 'n'], 1], + paint: { + 'circle-color': baseColor, + 'circle-radius': 4, + 'circle-stroke-width': 1, + 'circle-stroke-color': '#fff' + } + }, + ]; + + const unclusteredLayers = [ + { + id: 'points', + type: 'circle', + source: 'measurements', + "source-layer": "default", + paint: { + 'circle-color': "#373ec4", + 'circle-radius': 4, + } + }, + ]; + + const overlayStyle = { + sources: { + measurements: { + type: "vector", + tiles: [ url ], + } + }, + layers: clustered ? clusteredLayers : unclusteredLayers, + } + + // Auto select sample type + useEffect(() => { + buildInspectorStyle(baseStyle, overlayStyle, { + mapboxToken: mapboxAccessToken, + inDarkMode: isEnabled, + }).then((s) => { + setActualStyle(s); + }); + }, [isEnabled, clustered, selectedTypes]); + + return actualStyle; +} + +function Panel({selectedTypes, setSelectedTypes, clustered, setClustered, selectedMeasurement, setSelectedMeasurement, types}) { + const isItemSelected = (item) => + selectedTypes.some((selected) => selected.measure_id === item.measure_id); + + const handleItemSelect = (item) => { + if (!isItemSelected(item)) { + setSelectedTypes([...selectedTypes, item]); + } + }; + + const handleItemDelete = (itemToDelete) => { + const next = selectedTypes.filter( + (item) => item.measure_id !== itemToDelete.measure_id + ); + setSelectedTypes(next); + }; + + const itemPredicate = (query, item) => + item.name.toLowerCase().includes(query.toLowerCase()); + + const itemRenderer = (item, { handleClick, modifiers }) => { + if (!modifiers.matchesPredicate) return null; + + const { measure_id, name, type } = item; + + return h(MenuItem, { + key: measure_id, + text: h('div.type', [ + h('p', name), + h(FlexRow, { alignItems: "center", gap: ".25em"}, [ + h('div.text', item.class), + h(Icon, {icon: "chevron-right", size: 12}), + h('div.text', type), + ]), + ]), + onClick: handleClick, + active: modifiers.active, + shouldDismissPopover: false, + }); + }; + + const items = types.filter((f) => !isItemSelected(f)); + + return h('div.panel', [ + h.if(!selectedMeasurement)('div.filter', [ + h("h3", "Filter Measurements"), + h(Divider), + h('div.filter-select', [ + h(MultiSelect, { + items, + itemRenderer, + itemPredicate, + selectedItems: selectedTypes, + onItemSelect: handleItemSelect, + onRemove: handleItemDelete, + tagRenderer: (item) => item.name, + tagInputProps: { tagKey: "measure_id" }, + popoverProps: { minimal: true }, + fill: true, + }), + h('a.view-filters', { href: '/lex/measurements/filters' }, "View Filters"), + h( + Switch, + { + checked: clustered, + label: "Clustered", + onChange: () => setClustered(!clustered), + } + ), + ]), + ]), + h.if(selectedMeasurement)(SelectedMeasurement, { selectedMeasurement, setSelectedMeasurement }), + ]); +} + +function SelectedMeasurement({ selectedMeasurement, setSelectedMeasurement }) { + const [data, setData] = useState(null); + + useEffect(() => { + fetchPGData("/measurements_with_type", { id: "eq." + selectedMeasurement }) + .then(data => setData(data[0])) + .catch(err => console.error("Error fetching data:", err)); + }, [selectedMeasurement]); + + if (selectedMeasurement == null || data == null) { + return null; + } + + return h(Measurement, { data, setSelectedMeasurement }); +} \ No newline at end of file diff --git a/pages/lex/measurements/@id/+Page.client.ts b/pages/lex/measurements/@id/+Page.client.ts new file mode 100644 index 000000000..179d8ef2c --- /dev/null +++ b/pages/lex/measurements/@id/+Page.client.ts @@ -0,0 +1,21 @@ +import { useData } from "vike-react/useData"; +import h from "./main.module.sass"; +import { LexItemPage } from "~/components/lex"; +import { usePageContext } from "vike-react/usePageContext"; +import { Measurement } from "../measurement"; + +export function Page() { + const { resData } = useData(); + + const id = usePageContext().urlParsed.pathname.split("/")[3]; + + const children = [h(Measurement, { data: resData })]; + + return LexItemPage({ + children, + id, + resData, + siftLink: "measurements", + header: h("div.strat-header", [h("h1.strat-title", resData?.sample_name)]), + }); +} \ No newline at end of file diff --git a/pages/lex/measurements/@id/+data.ts b/pages/lex/measurements/@id/+data.ts new file mode 100644 index 000000000..d22e7d2c2 --- /dev/null +++ b/pages/lex/measurements/@id/+data.ts @@ -0,0 +1,13 @@ +import { pbdbDomain } from "@macrostrat-web/settings"; +import { fetchAPIData, fetchAPIRefs, fetchPGData } from "~/_utils"; + +export async function data(pageContext) { + const id = parseInt(pageContext.urlParsed.pathname.split("/")[3]); + + // Await all API calls + const [resData] = await Promise.all([ + fetchPGData("/measurements_with_type", { id: "eq." + id }), + ]); + + return { resData: resData[0] }; +} diff --git a/pages/lex/measurements/@id/main.module.sass b/pages/lex/measurements/@id/main.module.sass new file mode 100644 index 000000000..4c3d0be57 --- /dev/null +++ b/pages/lex/measurements/@id/main.module.sass @@ -0,0 +1,9 @@ +.strat-tag + padding: .2em .5em + border-radius: .2em + background-color: var(--tertiary-background) + font-size: 1.6em + +.strat-header + display: flex + gap: .5em \ No newline at end of file diff --git a/pages/lex/measurements/filters/+Page.client.ts b/pages/lex/measurements/filters/+Page.client.ts new file mode 100644 index 000000000..cb0ec7f2d --- /dev/null +++ b/pages/lex/measurements/filters/+Page.client.ts @@ -0,0 +1,36 @@ +import h from "./main.module.sass" +import { navigate } from "vike/client/router"; +import { LexHierarchy } from "@macrostrat-web/lithology-hierarchy"; +import { useAPIResult, ErrorCallout } from "@macrostrat/ui-components"; +import { useState } from "react"; +import { apiV2Prefix } from "@macrostrat-web/settings"; +import { Spinner } from "@blueprintjs/core"; +import { Footer, PageBreadcrumbs } from "~/components"; + + +export function Page() { + const [error, setError] = useState(null); + const res = useAPIResult( + `${apiV2Prefix}/defs/measurements`, + { + all: true, + }, + { onError: setError } + ); + + if (error != null) { + return h(ErrorCallout, { error }); + } + if (res == null) { + return h(Spinner); + } + const data = res.success.data; + + return h('div.page', [ + h(PageBreadcrumbs, { title: "Filters" }), + h('div.hierarchy', [ + h(LexHierarchy, { data, onClick: (e, item) => navigate(`/lex/measurements?id=${item.measure_id}`) }), + ]), + h(Footer) + ]); +} diff --git a/pages/lex/measurements/filters/main.module.sass b/pages/lex/measurements/filters/main.module.sass new file mode 100644 index 000000000..a3e36dd57 --- /dev/null +++ b/pages/lex/measurements/filters/main.module.sass @@ -0,0 +1,2 @@ +.hierarchy + height: 1000px \ No newline at end of file diff --git a/pages/lex/measurements/main.module.sass b/pages/lex/measurements/main.module.sass new file mode 100644 index 000000000..79881e9ea --- /dev/null +++ b/pages/lex/measurements/main.module.sass @@ -0,0 +1,24 @@ +.panel + background-color: var(--background-color) + padding: 1em 1em .5em 1em + + h3 + margin: 0 + + .selected-measurement + .ref + display: flex + justify-content: center + align-items: center + + .close-btn + cursor: pointer + + .filter-select + display: flex + flex-direction: column + gap: 1em + + .type + .text + margin: 0 \ No newline at end of file diff --git a/pages/lex/measurements/measurement.ts b/pages/lex/measurements/measurement.ts new file mode 100644 index 000000000..bc6b997d4 --- /dev/null +++ b/pages/lex/measurements/measurement.ts @@ -0,0 +1,52 @@ +import h from "./main.module.sass"; + +import { LithologyTag } from "@macrostrat/data-components"; +import { Divider, Icon } from "@blueprintjs/core"; +import { DataField } from "~/components/unit-details"; +import { FlexRow } from "@macrostrat/ui-components"; + + +export function Measurement({data, setSelectedMeasurement}) { + const { sample_name, sample_geo_unit, sample_lith, lith_id, lith_color, int_name, int_id, int_color, sample_description, ref, type, id } = data; + + // Lithology tag component + let lithProps = { + data: { name: sample_lith, color: lith_color } + }; + + if (lith_id !== 0) { + lithProps.href = '/lex/lithologies/' + lith_id + } + + // Interval tag component + let ageProps = { + data: { name: int_name, color: int_color } + }; + + if (int_id !== 0) { + ageProps.href = '/lex/intervals/' + int_id; + } + + let topRows = null; + + if (setSelectedMeasurement) { + topRows = [ + h(FlexRow, { justifyContent: 'space-between' }, [ + h("h3", "Selected Measurement"), + h(Icon, { icon: "cross", className: 'close-btn', onClick: () => setSelectedMeasurement(null) }), + ]), + h(Divider), + h(DataField, { label: "Name", value: h('a.ref', { href: '/lex/measurements/' + id, target: "_blank" }, sample_name) }), + ]; + } + + return h("div.selected-measurement", [ + topRows, + h(DataField, { label: "Type", value: type }), + h(DataField, { label: "Geological Unit", value: sample_geo_unit }), + h.if(sample_lith)(DataField, { label: "Lithology", value: h(LithologyTag, lithProps) }), + h.if(int_id)(DataField, { label: "Age", value: h(LithologyTag, ageProps) }), + h(DataField, { label: "Description", value: sample_description }), + h.if(ref.includes("http"))('a.ref', { href: ref, target: "_blank" }, "View Reference"), + ]); +} \ No newline at end of file diff --git a/pages/lex/minerals/+Page.ts b/pages/lex/minerals/+Page.client.ts similarity index 100% rename from pages/lex/minerals/+Page.ts rename to pages/lex/minerals/+Page.client.ts diff --git a/src/components/lex/index.ts b/src/components/lex/index.ts index bb4ee03fe..e91883bf3 100644 --- a/src/components/lex/index.ts +++ b/src/components/lex/index.ts @@ -72,7 +72,7 @@ export function LexItemPage(props: LexItemPageProps) { } function LexItemPageInner(props: LexItemPageProps) { - const { children, siftLink, id, resData, refs, header } = props; + const { children, siftLink, id, resData, refs = [], header } = props; const { name, strat_name_long } = resData; diff --git a/src/components/navigation/PageBreadcrumbs.ts b/src/components/navigation/PageBreadcrumbs.ts index 6b127d005..d2d45f709 100644 --- a/src/components/navigation/PageBreadcrumbs.ts +++ b/src/components/navigation/PageBreadcrumbs.ts @@ -168,6 +168,10 @@ export const sitemap: Routes = { }, ], }, + { + slug: "measurements", + name: "Measurements", + }, { slug: "integrations", name: "Integrations", diff --git a/src/types/lex.ts b/src/types/lex.ts index d3759c9df..785ff8bac 100644 --- a/src/types/lex.ts +++ b/src/types/lex.ts @@ -66,7 +66,7 @@ export interface LexItemPageProps { siftLink: string; id: number; resData: ResData; - refs: string[]; + refs?: string[]; header?: any; }