diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 202d425a..00000000 --- a/.babelrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "presets": [ - "@babel/preset-env", - "@babel/preset-react", - "@babel/preset-typescript" - ] -} diff --git a/.gitignore b/.gitignore index 6ea0477a..5f914c5a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,13 @@ docker-compose.yaml !.yarn/sdks !.yarn/versions -.vite \ No newline at end of file +# IntelliJ +.idea/workspace.xml +.idea/tasks.xml + + +# For ignoring static files +*.png +*.jpg + +.vite diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..b58b603f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..3e22f4b7 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..03d9549e --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 00000000..d23208fb --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..f589ca37 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 00000000..60c07b5a --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/Run_with_Chrome_Debugger.xml b/.idea/runConfigurations/Run_with_Chrome_Debugger.xml new file mode 100644 index 00000000..036621be --- /dev/null +++ b/.idea/runConfigurations/Run_with_Chrome_Debugger.xml @@ -0,0 +1,15 @@ + + + + + + `) .join("\n"); + const { title, description } = buildPageMeta(pageContext); + const documentHtml = escapeInject` @@ -75,7 +75,7 @@ async function render(pageContext: PageContextServer) { /> ${dangerouslySkipEscape(scriptTags)} ${dangerouslySkipEscape(envScript)} - + ${title} diff --git a/src/pages/columns/+config.ts b/src/pages/columns/+config.ts new file mode 100644 index 00000000..e24ef27b --- /dev/null +++ b/src/pages/columns/+config.ts @@ -0,0 +1,3 @@ +export default { + title: "Columns", +}; diff --git a/src/pages/columns/@column/+Page.ts b/src/pages/columns/@column/+Page.ts index 2c93f790..0a8f83d9 100644 --- a/src/pages/columns/@column/+Page.ts +++ b/src/pages/columns/@column/+Page.ts @@ -1,6 +1,6 @@ -import ColumnPage from "~/pages/columns/@column/column-inspector"; +import ColumnPage from "./column-inspector"; import h from "@macrostrat/hyper"; -export function Page({ columnInfo, linkPrefix }) { - return h(ColumnPage, { columnInfo, linkPrefix }); +export function Page(props) { + return h(ColumnPage, props); } diff --git a/src/pages/columns/@column/+config.ts b/src/pages/columns/@column/+config.ts index bbb0ee14..ebdb29d8 100644 --- a/src/pages/columns/@column/+config.ts +++ b/src/pages/columns/@column/+config.ts @@ -11,4 +11,5 @@ export default { }, }, }, + //Page: "import:./column-inspector:ColumnPage", }; diff --git a/src/pages/columns/@column/+onBeforeRender.ts b/src/pages/columns/@column/+onBeforeRender.ts index b5a429cc..1af9801b 100644 --- a/src/pages/columns/@column/+onBeforeRender.ts +++ b/src/pages/columns/@column/+onBeforeRender.ts @@ -1 +1,117 @@ -export { onBeforeRender } from "~/pages/projects/@project/columns/@column/+onBeforeRender"; +import { apiV2Prefix } from "@macrostrat-web/settings"; +import { preprocessUnits } from "@macrostrat/column-views/src/helpers"; +import fetch from "node-fetch"; + +import { ColumnSummary } from "~/pages/map/map-interface/app-state/handlers/columns"; +import { fetchAPIData } from "~/pages/columns/utils"; + +export async function onBeforeRender(pageContext) { + // `.page.server.js` files always run in Node.js; we could use SQL/ORM queries here. + const col_id = pageContext.routeParams.column; + + // In cases where we are in a project context, we need to fetch the project data + const project_id = pageContext.routeParams.project ?? 1; + + // https://v2.macrostrat.org/api/v2/columns?col_id=3&response=long + + const linkPrefix = project_id == null ? "/" : `/projects/${project_id}/`; + + /** This is a hack to make sure that all requisite data is on the table. */ + const responses = await Promise.all([ + project_id == null + ? Promise.resolve(null) + : getAndUnwrap(apiV2Prefix + `/defs/projects?project_id=${project_id}`), + getData( + "columns", + { col_id, project_id, format: "geojson" }, + (res) => res?.features + ), + getData( + `units`, + { + project_id, + col_id, + }, + (res) => res + ), + ]); + + const [projectData, columns, unitsLong]: [any, any, any] = responses; + + const col = columns?.[0]; + + const columnInfo: ColumnSummary = { + ...col.properties, + geometry: col.geometry, + units: preprocessUnits(unitsLong), + }; + + return { + pageContext: { + exports: { + ...pageContext.exports, + title: columnInfo.col_name, + }, + pageProps: { + columnInfo, + linkPrefix, + project: projectData?.[0], + }, + }, + }; +} + +async function getAndUnwrap(url: string): Promise { + const res = await fetch(url); + const res1 = await res.json(); + return res1.success.data; +} + +async function getData( + entity: string, + args: { col_id: number; project_id: number }, + unwrapResponse: (res: any) => any[] +) { + /** Fetch column data without knowing if it is an 'in process' column, a priori. This + * gets around the current limitation that there's no way to request any column data + * without knowing the status_code. + */ + let res = await getAndUnwrap( + assembleURL(entity, { ...args, status_code: "active" }) + ); + let data = unwrapResponse(res); + + if (data.length > 0) { + return data; + } + + let res2 = await getAndUnwrap( + assembleURL(entity, { ...args, status_code: "in process" }) + ); + return unwrapResponse(res2); +} + +function assembleURL( + entity, + { + col_id, + project_id = 1, + status_code = "active", + ...rest + }: { + col_id: number; + project_id: number; + status_code?: "active" | "in process"; + [key: string]: string | number; + } +) { + const base = apiV2Prefix + "/" + entity; + let params = new URLSearchParams({ + col_id: col_id.toString(), + project_id: project_id.toString(), + response: "long", + status_code, + ...rest, + }); + return `${base}?${params}`; +} diff --git a/src/pages/columns/@column/column-inspector/column-inspector.module.styl b/src/pages/columns/@column/column-inspector/column-inspector.module.styl index 9eea998b..5d579361 100644 --- a/src/pages/columns/@column/column-inspector/column-inspector.module.styl +++ b/src/pages/columns/@column/column-inspector/column-inspector.module.styl @@ -1,6 +1,4 @@ .column-ui - * - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif --column-background-color: var(--background-color) --column-stroke-color: var(--text-color) @@ -26,8 +24,14 @@ padding 0 .column-header - margin-left: 2em - margin-right: 2em + margin: 1em 4em + h1 + font-size: 1.5em + .subtitle + font-weight: normal + + nav + position: sticky .column-nav position sticky diff --git a/src/pages/columns/@column/column-inspector/index.ts b/src/pages/columns/@column/column-inspector/index.ts index b9af7601..fc46aab9 100644 --- a/src/pages/columns/@column/column-inspector/index.ts +++ b/src/pages/columns/@column/column-inspector/index.ts @@ -14,13 +14,13 @@ import { PatternProvider } from "~/_providers"; import styles from "./column-inspector.module.styl"; import ModalUnitPanel from "./modal-panel"; import { BasePage } from "~/layouts"; -import { PageHeader } from "~/components"; import { navigate } from "vike/client/router"; +import { PageBreadcrumbs } from "~/renderer"; const h = hyperStyled(styles); -function ColumnPage({ columnInfo, linkPrefix = "/" }) { +function ColumnPage({ columnInfo, linkPrefix = "/", project }) { const { units, geometry } = columnInfo; const selectedUnit = useUnitSelection(units); @@ -30,57 +30,75 @@ function ColumnPage({ columnInfo, linkPrefix = "/" }) { const zoom = 7; return h(BasePage, [ - h(PageHeader, { title: columnInfo.col_name, className: "column-header" }), - h("div.column-ui", [ - h("div.left-column", [ - h("div.column-view", [ - h("p.column-details", [ - h("span.column-id", ["#", columnInfo.col_id]), - ", ", - h("span.project", ["project ", columnInfo.project_id]), - ", ", - h( - "a", - { - href: `/map/loc/${lon}/${lat}/column#z=${zoom}&show=columns,geology`, + h("div.main", [ + h("div.column-header", [ + h("nav", [h(PageBreadcrumbs, { showLogo: true })]), + h("h1.page-title", [ + h("span.col-name", columnInfo.col_name), + h.if(columnInfo.col_group != null)("span.subtitle", [ + h("span.separator", " – "), + h("span.col-group", `${columnInfo.col_group}`), + ]), + ]), + ]), + //h(PageHeader, { title: columnInfo.col_name, className: "column-header" }), + h("div.column-ui", [ + h("div.left-column", [ + h("div.column-view", [ + h("p.column-details", [ + h("span.column-id", ["#", columnInfo.col_id]), + ", ", + h("span.project", ["project ", columnInfo.project_id]), + ", ", + h( + "a", + { + href: `/map/loc/${lon}/${lat}/column#z=${zoom}&show=columns,geology`, + }, + "show in map" + ), + ".", + ]), + h(Column, { + data: units, + unconformityLabels: true, + columnWidth: 250, + width: 500, + unitComponentProps: { + nColumns: 3, }, - "show in map" - ), - ".", + }), ]), - h(Column, { - data: units, - unconformityLabels: true, - columnWidth: 250, - width: 500, - unitComponentProps: { - nColumns: 3, + ]), + h("div.right-column", [ + h(ColumnNavigatorMap, { + className: "column-map", + format: "geojson_bare", + showInProcessColumns: true, + currentColumn: { + geometry, + type: "Feature", + properties: { + col_id: columnInfo.col_id, + col_name: columnInfo.col_name, + project_id: columnInfo.project_id, + }, }, + style: { + display: selectedUnit == null ? "block" : "none", + }, + setCurrentColumn(newColumn) { + const { col_id } = newColumn.properties; + navigate(linkPrefix + `columns/${col_id}`, { + overwriteLastHistoryEntry: true, + }); + }, + margin: 10, + project_id: columnInfo.project_id, }), + h(ModalUnitPanel, { unitData: units }), ]), ]), - h("div.right-column", [ - h.if(selectedUnit == null)(ColumnNavigatorMap, { - className: "column-map", - format: "geojson_bare", - currentColumn: { - geometry, - type: "Feature", - properties: { - col_id: columnInfo.col_id, - col_name: columnInfo.col_name, - project_id: columnInfo.project_id, - }, - }, - setCurrentColumn(newColumn) { - const { col_id } = newColumn.properties; - navigate(linkPrefix + `columns/${col_id}`); - }, - margin: 10, - project_id: columnInfo.project_id, - }), - h(ModalUnitPanel, { unitData: units }), - ]), ]), ]); } diff --git a/src/pages/columns/utils.ts b/src/pages/columns/utils.ts index 8b735344..10a21126 100644 --- a/src/pages/columns/utils.ts +++ b/src/pages/columns/utils.ts @@ -6,6 +6,7 @@ export async function fetchAPIData(apiURL: string, params: any) { if (params != null) { url.search = new URLSearchParams(params).toString(); } + console.log(url.toString()); const res = await fetch(url.toString()); const res1 = await res.json(); return res1?.success?.data; diff --git a/src/pages/correlation/+Page.ts b/src/pages/correlation/+Page.ts new file mode 100644 index 00000000..1941fe11 --- /dev/null +++ b/src/pages/correlation/+Page.ts @@ -0,0 +1,340 @@ +import { MapView } from "@macrostrat/map-interface"; +import { + MapboxMapProvider, + useMapClickHandler, + useMapEaseTo, + useMapStyleOperator, +} from "@macrostrat/mapbox-react"; +import { LngLatBounds } from "mapbox-gl"; +import { FullscreenPage } from "~/layouts"; +import h from "./main.module.sass"; +import { compose, C } from "@macrostrat/hyper"; +import { baseMapURL, mapboxAccessToken } from "@macrostrat-web/settings"; +import { PageBreadcrumbs } from "~/renderer"; +import { Feature, FeatureCollection, LineString, Point } from "geojson"; +import { useEffect, useMemo } from "react"; +import { setGeoJSON } from "@macrostrat/mapbox-utils"; +import { useCorrelationDiagramStore } from "./state"; +import { PatternProvider } from "~/_providers"; + +import { buildCrossSectionLayers } from "~/_utils/map-layers"; +import { Button, OverlaysProvider } from "@blueprintjs/core"; +import classNames from "classnames"; +import { CorrelationChart } from "./correlation-chart"; +import { DarkModeProvider } from "@macrostrat/ui-components"; +import { ErrorBoundary } from "@macrostrat/ui-components"; +import { columnGeoJSONRecordToColumnIdentifier } from "./state"; +import { + UnitSelectionProvider, + useSelectedUnit, +} from "@macrostrat/column-views"; +import { UnitDescription } from "~/pages/correlation/column"; + +export function Page() { + const startup = useCorrelationDiagramStore((state) => state.startup); + useEffect(() => { + startup(); + }, []); + + const expanded = useCorrelationDiagramStore((state) => state.mapExpanded); + + return h(PageWrapper, [ + h("header", [h(PageBreadcrumbs)]), + h( + "div.diagram-container", + { className: expanded ? "map-expanded" : "map-inset" }, + [ + h("div.main-area", [ + h(CorrelationDiagramWrapper), + h("div.overlay-safe-area"), + ]), + h("div.assistant", [h(InsetMap), h(UnitDetailsPanel)]), + ] + ), + ]); +} + +const PageWrapper = compose( + FullscreenPage, + DarkModeProvider, + PatternProvider, + UnitSelectionManager, + ({ children }) => h("div.main-panel", children) +); + +function UnitSelectionManager({ children }) { + const selectedUnit = useCorrelationDiagramStore( + (state) => state.selectedUnit + ); + const setSelectedUnit = useCorrelationDiagramStore( + (state) => state.setSelectedUnit + ); + + return h( + UnitSelectionProvider, + { + unit: selectedUnit, + setUnit: setSelectedUnit, + }, + children + ); +} + +function UnitDetailsPanel() { + const selectedUnit = useSelectedUnit(); + const expanded = useCorrelationDiagramStore((state) => state.mapExpanded); + const setSelectedUnit = useCorrelationDiagramStore( + (state) => state.setSelectedUnit + ); + + if (selectedUnit == null || !expanded) { + return null; + } + + return h("div.unit-details-panel", [ + h(UnitDescription, { + unit: selectedUnit, + onClose: () => setSelectedUnit(null), + }), + ]); +} + +function CorrelationDiagramWrapper() { + const chartData = useCorrelationDiagramStore((state) => state.chartData); + + return h("div.correlation-diagram", [ + h( + ErrorBoundary, + h(OverlaysProvider, [h(CorrelationChart, { data: chartData })]) + ), + ]); +} + +function InsetMap() { + const focusedLine = useCorrelationDiagramStore((state) => state.focusedLine); + const columns = useCorrelationDiagramStore((state) => state.columns); + const expanded = useCorrelationDiagramStore((state) => state.mapExpanded); + + const padding = expanded ? 100 : 20; + + return h( + "div.column-selection-map", + { className: classNames({ expanded }) }, + [ + h(MapboxMapProvider, [ + h(MapExpandedButton), + h( + MapView, + { + style: baseMapURL, + accessToken: mapboxAccessToken, + }, + [ + h(MapClickHandler), + h(SectionLine, { focusedLine, padding }), + h(ColumnsLayer, { columns }), + h(SelectedColumnsLayer), + ] + ), + ]), + ] + ); +} + +function MapExpandedButton() { + const toggleMapExpanded = useCorrelationDiagramStore( + (state) => state.toggleMapExpanded + ); + const mapExpanded = useCorrelationDiagramStore((state) => state.mapExpanded); + + const icon = mapExpanded ? "collapse-all" : "expand-all"; + + return h(Button, { + className: "map-expanded-button", + icon, + onClick: toggleMapExpanded, + }); +} + +function MapClickHandler() { + const onClickMap = useCorrelationDiagramStore((state) => state.onClickMap); + + useMapClickHandler( + (e) => { + onClickMap(e, { type: "Point", coordinates: e.lngLat.toArray() }); + }, + [onClickMap] + ); + + return null; +} + +function SelectedColumnsLayer({ columns, focusedLine }) { + const focusedColumns = useCorrelationDiagramStore( + (state) => state.focusedColumns + ); + useMapStyleOperator( + (map) => { + let features = focusedColumns; + + const data: FeatureCollection = { + type: "FeatureCollection", + features, + }; + + const columnCentroidLine: Feature = { + type: "Feature", + geometry: { + type: "LineString", + coordinates: features.map( + (col) => col.properties.centroid.geometry.coordinates + ), + }, + properties: {}, + }; + + setGeoJSON(map, "selected-columns", data); + setGeoJSON(map, "selected-column-centroids", { + type: "FeatureCollection", + features: [columnCentroidLine], + }); + }, + [focusedColumns] + ); + return null; +} + +function ColumnsLayer({ columns, enabled = true }) { + useMapStyleOperator( + (map) => { + if (columns == null) { + return; + } + const data: FeatureCollection = { + type: "FeatureCollection", + features: columns, + }; + const sourceID = "columns"; + setGeoJSON(map, sourceID, data); + + const columnLayers = buildColumnLayers(sourceID); + for (let layer of columnLayers) { + if (map.getSource(layer.source) == null) { + continue; + } + if (map.getLayer(layer.id) == null) { + map.addLayer(layer); + } + } + }, + [columns, enabled] + ); + return null; +} + +function buildColumnLayers(sourceID: string) { + return [ + { + id: "selected-columns-fill", + type: "fill", + source: "selected-columns", + paint: { + "fill-color": "rgba(255, 0, 0, 0.1)", + }, + }, + { + id: "selected-column-centroids-line", + type: "line", + source: "selected-column-centroids", + paint: { + "line-color": "rgba(255, 0, 0, 0.8)", + "line-width": 2, + "line-dasharray": [2, 2], + }, + }, + { + id: "selected-column-centroids-points", + type: "circle", + source: "selected-column-centroids", + paint: { + "circle-radius": 4, + "circle-color": "rgba(255, 0, 0, 0.8)", + }, + }, + { + id: "columns-fill", + type: "fill", + source: sourceID, + paint: { + "fill-color": "rgba(0, 0, 0, 0.1)", + }, + }, + { + id: "columns-line", + type: "line", + source: sourceID, + paint: { + "line-color": "rgba(0, 0, 0, 0.5)", + "line-width": 1, + }, + }, + ]; +} + +function SectionLine({ focusedLine }: { focusedLine: LineString }) { + // Setup focused line + useMapStyleOperator( + (map) => { + if (focusedLine == null) { + return; + } + const data: FeatureCollection = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + geometry: focusedLine, + properties: { id: "focusedLine" }, + }, + ], + }; + + setGeoJSON(map, "crossSectionLine", data); + setGeoJSON(map, "crossSectionEndpoints", { + type: "FeatureCollection", + features: focusedLine.coordinates.map((coord) => ({ + type: "Feature", + geometry: { type: "Point", coordinates: coord }, + properties: {}, + })), + }); + + // Add layers + const layers = buildCrossSectionLayers(); + for (let layer of layers) { + if (map.getSource(layer.source) == null) { + continue; + } + if (map.getLayer(layer.id) == null) { + map.addLayer(layer); + } + } + }, + [focusedLine] + ); + + const bounds = useMemo(() => { + if (focusedLine == null || focusedLine?.coordinates.length < 2) { + return null; + } + let bounds = new LngLatBounds(); + for (let coord of focusedLine.coordinates) { + bounds.extend(coord); + } + return bounds; + }, [focusedLine]); + + useMapEaseTo({ bounds, padding: 50, trackResize: true }); + + return null; +} diff --git a/src/pages/correlation/+config.ts b/src/pages/correlation/+config.ts new file mode 100644 index 00000000..6a127bb9 --- /dev/null +++ b/src/pages/correlation/+config.ts @@ -0,0 +1,14 @@ +export default { + meta: { + Page: { + /* Ideally we'd make it so only the column inset map was rendered client-side, + but this will work for now. + */ + env: { + client: true, + server: false, + }, + }, + }, + title: "Correlation diagram", +}; diff --git a/src/pages/correlation/column.module.scss b/src/pages/correlation/column.module.scss new file mode 100644 index 00000000..6809fa73 --- /dev/null +++ b/src/pages/correlation/column.module.scss @@ -0,0 +1,109 @@ +.column-container :global { + --background-color: var(--column-background-color, #fff); + --stroke-color: var(--column-stroke-color, #000); +} +.column-container :global .column { + display: flex; + flex-direction: row; +} +.column-container :global .column .age-axis-label { + writing-mode: vertical-lr; + transform: rotate(180deg); + font-size: 12px; + text-align: center; + position: sticky; + top: 0; + max-height: 100vh; +} +.column-container :global .column .tick text { + text-anchor: middle; + font-weight: 400 !important; + transform: rotate(-90deg) translate(8px, -12px); +} +.column-container :global rect.unit { + stroke: var(--stroke-color); + stroke-width: 1px; +} +.column-container :global g.lithology-column use.frame { + ///stroke: var(--stroke-color); + //stroke-width: 1.5px !important; + pointer-events: none; +} +.column-container :global .unit-label { + text-align: center; +} +.column-container :global .col-note-label { + font-size: 10px; + margin-top: 3px; + padding: 2px; + padding-left: 3px; + line-height: 9px; + border-left: none !important; +} +.column-container :global g.height-range { + display: none; +} +.column-container :global .note-inner { + border: none; +} +.column-container :global .frame rect { + fill: transparent; +} +.column-container :global .labeled-unit .unit-overlay { + font-size: 10px; + font-style: italic; + display: flex; + align-items: center; + justify-content: center; +} +.column-container :global .labeled-unit .unit-overlay .unit-label { + display: block; + text-shadow: 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color), 0px 0px 4px var(--background-color); +} +.column-container :global .default-buttons { + width: 100%; + margin: 0 -10px; +} + +.column-container :global div.section :global(.timescale) { + transform: translateY(0.5); +} +.column-container :global .section .divisions .unit { + cursor: pointer; +} +.column-container :global rect.selection-overlay { + fill: rgba(255,0,0,0.5); +} + +.unconformity { + height: 0px; + font-size: 10px; + font-style: italic; + text-align: center; + margin-left: 0; +} +.unconformity-text { + transform: translateY(-0.5em); + width: var(--column-width); + color: var(--stroke-color); +} +.column-container.dark-mode :global { + color: var(--text-color); +} +.column-container.dark-mode :global .timescale { + color: #000; +} +.column-container.dark-mode :global g.inner pattern image { + filter: invert(80%); +} + +.column-container :global div.timescale { + width: calc(4.5em + 4px); + --timescale-level-size: 1.5em; +} + +.section { + position: relative; + display: flex; + flex-direction: row; +} diff --git a/src/pages/correlation/column.ts b/src/pages/correlation/column.ts new file mode 100644 index 00000000..9ea61f64 --- /dev/null +++ b/src/pages/correlation/column.ts @@ -0,0 +1,329 @@ +import { + ColumnLayoutContext, + ColumnProvider, + ColumnSVG, + useColumn, +} from "@macrostrat/column-components"; +import { Timescale, TimescaleOrientation } from "@macrostrat/timescale"; +import { JSONView, useDarkMode } from "@macrostrat/ui-components"; +import classNames from "classnames"; +import { useContext, useEffect, useMemo, useRef } from "react"; +import { AgeAxis, useSelectedUnit } from "@macrostrat/column-views"; +import h from "./column.module.scss"; +import { SectionRenderData } from "./types"; +import { + CompositeUnitsColumn, + TrackedLabeledUnit, +} from "@macrostrat/column-views"; +import styles from "./main.module.sass"; + +import { ColumnAxisType } from "@macrostrat/column-components"; + +import { UnitLong } from "@macrostrat/api-types"; +import { + LegendJSONView, + LegendPanelHeader, + UnitDetailsPopover, +} from "~/components/unit-details"; +import { useCorrelationDiagramStore } from "~/pages/correlation/state"; + +export function MacrostratColumnProvider(props) { + // A column provider specialized the Macrostrat API + return h(ColumnProvider, { axisType: ColumnAxisType.AGE, ...props }); +} + +interface ISectionProps { + data: SectionRenderData; + unitComponent?: React.FunctionComponent; + unitComponentProps?: any; + showLabels?: boolean; + width?: number; + columnWidth?: number; + targetUnitHeight?: number; +} + +function Section(props: ISectionProps) { + // Section with "squishy" timescale + const { + data, + unitComponent, + showLabels = true, + width = 300, + columnWidth = 150, + unitComponentProps, + } = props; + + const expanded = useCorrelationDiagramStore((s) => s.mapExpanded); + + const { units, bestPixelScale: pixelScale, t_age, b_age } = data; + const range = [b_age, t_age]; + + const dAge = range[0] - range[1]; + + const height = dAge * pixelScale; + + /** Ensure that we can arrange units into the maximum number + * of columns defined by unitComponentProps, but that we don't + * use more than necessary. + */ + const _unitComponentProps = useMemo(() => { + return { + ...unitComponentProps, + nColumns: Math.min( + Math.max(...units.map((d) => d.column)) + 1, + unitComponentProps?.nColumns ?? 2 + ), + }; + }, [units, unitComponentProps]); + + return h( + MacrostratColumnProvider, + { + divisions: units, + range, + pixelsPerMeter: pixelScale, // Actually pixels per myr + }, + [ + h( + ColumnSVG, + { + innerWidth: showLabels ? width : columnWidth, + paddingRight: 1, + paddingLeft: 1, + paddingV: 10, + innerHeight: height, + }, + h(CompositeUnitsColumn, { + width: columnWidth, + columnWidth, + gutterWidth: 5, + showLabels: false, + unitComponent, + unitComponentProps: _unitComponentProps, + clipToFrame: false, + }) + ), + h.if(!expanded)(SelectedUnitPopover, { width }), + ] + ); +} + +export function UnitComponent({ division, nColumns = 2, ...rest }) { + const { width } = useContext(ColumnLayoutContext); + + return h(TrackedLabeledUnit, { + division, + ...rest, + width: division.overlappingUnits.length > 0 ? width / nColumns : width, + x: (division.column * width) / nColumns, + }); +} + +function Unconformity({ upperUnits = [], lowerUnits = [], style }) { + if (upperUnits.length == 0 || lowerUnits.length == 0) { + return null; + } + + const ageGap = lowerUnits[0].t_age - upperUnits[upperUnits.length - 1].b_age; + + return h( + "div.unconformity", + { style }, + h("div.unconformity-text", `${ageGap.toFixed(1)} Ma`) + ); +} + +interface IColumnProps { + data: SectionRenderData[]; + unitComponent?: React.FunctionComponent; + unitComponentProps?: any; + showLabels?: boolean; + width?: number; + columnWidth?: number; + targetUnitHeight?: number; + unconformityLabels: boolean; + className?: string; +} + +export function Column(props: IColumnProps) { + const { + data, + unitComponent = UnitComponent, + unconformityLabels = false, + showLabels = true, + width = 300, + columnWidth = 150, + className: baseClassName, + ...rest + } = props; + + const darkMode = useDarkMode(); + + const sectionGroups = [[0, data]]; + + const className = classNames(baseClassName, { + "dark-mode": darkMode?.isEnabled ?? false, + }); + + return h( + "div.column-container", + { className }, + h("div.column", [ + h( + "div.main-column", + data.map((sectionData, i) => { + const lastGroup = sectionGroups[i - 1]?.[1]; + return h([ + h.if(unconformityLabels)(Unconformity, { + upperUnits: lastGroup, + lowerUnits: data, + style: { width: showLabels ? columnWidth : width }, + }), + h(`div.section.section-${i}`, [ + h(Section, { + data: sectionData, + unitComponent, + showLabels, + width, + columnWidth, + ...rest, + }), + ]), + ]); + }) + ), + ]) + ); +} + +interface TimescaleColumnProps { + packages: SectionRenderData[]; + className?: string; +} + +export function TimescaleColumn(props: TimescaleColumnProps) { + const { className: baseClassName, packages } = props; + + const darkMode = useDarkMode(); + + const className = classNames(baseClassName, { + "dark-mode": darkMode?.isEnabled ?? false, + }); + + return h( + "div.column-container", + { className }, + h("div.column", [ + h("div.age-axis-label", "Age (Ma)"), + h( + "div.main-column", + packages.map((data, i) => { + const range = [data.b_age, data.t_age]; + const pixelScale = data.bestPixelScale; + + return h([ + h(`div.section.section-${i}`, [ + h(TimescaleSection, { + range, + pixelScale, + }), + ]), + ]); + }) + ), + ]) + ); +} + +function TimescaleSection(props: { + range: [number, number]; + pixelScale: number; +}) { + // Section with "squishy" timescale + const { range, pixelScale } = props; + + const dAge = range[0] - range[1]; + const height = dAge * pixelScale; + + return h( + MacrostratColumnProvider, + { + divisions: [], + range, + pixelsPerMeter: pixelScale, // Actually pixels per myr + }, + [ + h(AgeAxis, { + width: 20, + padding: 0, + paddingV: 10, + showLabel: false, + }), + h("div.timescale-container", { style: { marginTop: `10px` } }, [ + h(Timescale, { + orientation: TimescaleOrientation.VERTICAL, + length: height, + levels: [2, 4], + absoluteAgeScale: true, + showAgeAxis: false, + ageRange: range, + }), + ]), + ] + ); +} + +export function UnitDescription({ + unit, + onClose, +}: { + unit: UnitLong; + onClose?: () => void; +}) { + const { unit_name, unit_id, strat_name_long } = unit; + const name = strat_name_long ?? unit_name; + return h("div.unit-description", [ + h(LegendPanelHeader, { title: name, id: unit_id, onClose }), + h(LegendJSONView, { data: unit }), + ]); +} + +function SelectedUnitPopover({ width }: { width: number }) { + const { scale, divisions } = useColumn(); + + const item0 = useSelectedUnit(); + const item = divisions.find((d) => d.unit_id == item0?.unit_id); + + const scrollParentRef = useRef(null); + useEffect(() => { + scrollParentRef.current = document.getElementsByClassName( + styles["overlay-safe-area"] + )[0]; + }, []); + + if (item == null) { + return null; + } + + const { t_age, b_age } = item0; + const range = [b_age, t_age]; + + const content = h(UnitDescription, { unit: item }); + + const top = scale(range[1]) + 10; + const bottom = scale(range[0]) + 10; + + return h( + UnitDetailsPopover, + { + style: { + top, + left: 0, + width, + height: bottom - top, + }, + boundary: scrollParentRef.current, + }, + content + ); +} diff --git a/src/columns/column-data.ts b/src/pages/correlation/columns/column-data.ts similarity index 100% rename from src/columns/column-data.ts rename to src/pages/correlation/columns/column-data.ts diff --git a/src/columns/column-map.ts b/src/pages/correlation/columns/column-map.ts similarity index 100% rename from src/columns/column-map.ts rename to src/pages/correlation/columns/column-map.ts diff --git a/src/columns/column-renderer-demo.ts b/src/pages/correlation/columns/column-renderer-demo.ts similarity index 100% rename from src/columns/column-renderer-demo.ts rename to src/pages/correlation/columns/column-renderer-demo.ts diff --git a/src/columns/index.tsx b/src/pages/correlation/columns/index.tsx similarity index 100% rename from src/columns/index.tsx rename to src/pages/correlation/columns/index.tsx diff --git a/src/columns/legend.ts b/src/pages/correlation/columns/legend.ts similarity index 100% rename from src/columns/legend.ts rename to src/pages/correlation/columns/legend.ts diff --git a/src/columns/main.styl b/src/pages/correlation/columns/main.styl similarity index 100% rename from src/columns/main.styl rename to src/pages/correlation/columns/main.styl diff --git a/src/columns/selection-panel.ts b/src/pages/correlation/columns/selection-panel.ts similarity index 100% rename from src/columns/selection-panel.ts rename to src/pages/correlation/columns/selection-panel.ts diff --git a/src/columns/single-column.ts b/src/pages/correlation/columns/single-column.ts similarity index 100% rename from src/columns/single-column.ts rename to src/pages/correlation/columns/single-column.ts diff --git a/src/pages/correlation/correlation-chart.ts b/src/pages/correlation/correlation-chart.ts new file mode 100644 index 00000000..8f5cd488 --- /dev/null +++ b/src/pages/correlation/correlation-chart.ts @@ -0,0 +1,276 @@ +/** Correlation chart */ +import { + preprocessUnits, + useUnitSelectionDispatch, +} from "@macrostrat/column-views"; +import { runColumnQuery } from "~/pages/map/map-interface/app-state/handlers/fetch"; +import { Column, TimescaleColumn } from "./column"; +import { UnitLong } from "@macrostrat/api-types"; +import { GapBoundPackage, SectionRenderData, AgeComparable } from "./types"; + +import h from "./main.module.sass"; +import { useCorrelationDiagramStore } from "~/pages/correlation/state"; + +export interface ColumnIdentifier { + col_id: number; + col_name: string; + project_id: number; +} + +export interface CorrelationChartData { + t_age: number; + b_age: number; + columnData: SectionRenderData[][]; // Units for each column +} + +export async function buildCorrelationChartData( + columns: ColumnIdentifier[], + ageMode: AgeScaleMode = AgeScaleMode.Broken +): Promise { + const promises = columns.map((col) => fetchUnitsForColumn(col.col_id)); + return Promise.all(promises).then((data) => buildColumnData(data, ageMode)); +} + +export function CorrelationChart({ data }: { data: CorrelationChartData }) { + const chartData = data; + + if (chartData == null || chartData.columnData.length == 0) { + return null; + } + + const firstColumn = chartData.columnData[0]; + + return h(ChartArea, [ + h(TimescaleColumnExt, { + key: "timescale", + packages: firstColumn, + }), + h( + chartData.columnData.map((packages, i) => + h(SingleColumn, { + packages, + key: i, + }) + ) + ), + ]); +} + +function ChartArea({ children }) { + const setSelectedUnit = useCorrelationDiagramStore( + (state) => state.setSelectedUnit + ); + + return h( + "div.correlation-chart-inner", + { + onClick() { + setSelectedUnit(null); + }, + }, + children + ); +} + +function TimescaleColumnExt({ packages }: { packages: SectionRenderData[] }) { + return h("div.column", [ + h(TimescaleColumn, { + showLabels: false, + unconformityLabels: true, + width: 100, + columnWidth: 100, + packages, + }), + ]); +} + +function SingleColumn({ + packages, +}: { + column: ColumnIdentifier; + packages: SectionRenderData[]; +}) { + return h("div.column", [ + h(Column, { + data: packages, + showLabels: false, + unconformityLabels: true, + width: 100, + columnWidth: 100, + }), + ]); +} + +type ColumnData = { + units: UnitLong[]; + columnID: number; +}; + +async function fetchUnitsForColumn(col_id: number): Promise { + const res = await runColumnQuery({ col_id }, null); + return { columnID: col_id, units: preprocessUnits(res) }; +} + +const targetUnitHeight = 20; + +export enum AgeScaleMode { + Continuous = "continuous", + Broken = "broken", +} + +function buildColumnData( + columns: ColumnData[], + ageMode: AgeScaleMode = AgeScaleMode.Continuous +): CorrelationChartData { + // Create a single gap-bound package for each column + const units = columns.map((d) => d.units); + const [b_age, t_age] = findEncompassingScaleBounds(units.flat()); + + if (ageMode == AgeScaleMode.Continuous) { + const dAge = b_age - t_age; + const maxNUnits = Math.max(...units.map((d) => d.length)); + const targetHeight = targetUnitHeight * maxNUnits; + const pixelScale = Math.ceil(targetHeight / dAge); + + const columnData: SectionRenderData[][] = columns.map((d) => { + return [ + { + b_age, + t_age, + bestPixelScale: pixelScale, + ...d, + }, + ]; + }); + + return { columnData, b_age, t_age }; + } + + let pkgs: GapBoundPackage[] = []; + for (const column of columns) { + pkgs.push(...findGapBoundPackages(column)); + } + pkgs = mergeOverlappingGapBoundPackages(pkgs); + pkgs.sort((a, b) => a.b_age - b.b_age); + + // Get the best pixel scale for each gap-bound package + const pixelScales = pkgs.map(findBestPixelScale); + + const columnData = columns.map((d) => { + return pkgs.map((pkg, i): SectionRenderData => { + const { t_age, b_age } = pkg; + let units = pkg.unitIndex.get(d.columnID) ?? []; + + units.sort((a, b) => a.b_age - b.b_age); + + return { + t_age, + b_age, + columnID: d.columnID, + bestPixelScale: pixelScales[i], + units, + }; + }); + }); + + return { columnData, b_age, t_age }; +} + +function findBestPixelScale(pkg: GapBoundPackage) { + const dAge = pkg.b_age - pkg.t_age; + const maxNUnits = Math.max( + ...Array.from(pkg.unitIndex.values()).map((d) => d.length) + ); + const targetHeight = targetUnitHeight * maxNUnits; + return targetHeight / dAge; +} + +function mergeOverlappingGapBoundPackages( + packages: GapBoundPackage[] +): GapBoundPackage[] { + // Sort by t_age + let remainingPackages: GapBoundPackage[] = packages; + let newPackages: GapBoundPackage[] = []; + while (remainingPackages.length > 0) { + const pkg = remainingPackages.pop(); + const overlapping = findOverlapping(pkg, remainingPackages); + newPackages.push(mergePackages(pkg, ...overlapping)); + remainingPackages = remainingPackages.filter( + (d) => !overlapping.includes(d) + ); + } + if (newPackages.length < packages.length) { + return mergeOverlappingGapBoundPackages(newPackages); + } else { + return newPackages; + } + // Chunk by divisions where t_age is less than the next b_age +} + +function findEncompassingScaleBounds(units: AgeComparable[]) { + const b_age = Math.max(...units.map((d) => d.b_age)); + const t_age = Math.min(...units.map((d) => d.t_age)); + return [b_age, t_age]; +} + +function mergePackages(...packages: GapBoundPackage[]): GapBoundPackage { + return packages.reduce( + (a: GapBoundPackage, b: GapBoundPackage) => { + a.b_age = Math.max(a.b_age, b.b_age); + a.t_age = Math.min(a.t_age, b.t_age); + for (let [columnID, units] of b.unitIndex) { + if (a.unitIndex.has(columnID)) { + a.unitIndex.set(columnID, a.unitIndex.get(columnID).concat(units)); + } else { + a.unitIndex.set(columnID, units); + } + } + return a; + }, + { + b_age: -Infinity, + t_age: Infinity, + unitIndex: new Map(), + } + ); +} + +function findGapBoundPackages(columnData: ColumnData): GapBoundPackage[] { + /** Find chunks of units overlapping in time, separated by unconformities */ + const { units, columnID } = columnData; + let packages: GapBoundPackage[] = []; + for (let unit of columnData.units) { + const newPackage: GapBoundPackage = { + b_age: unit.b_age, + t_age: unit.t_age, + unitIndex: new Map([[columnID, [unit]]]), + }; + + const overlappingPackages: GapBoundPackage[] = findOverlapping( + newPackage, + packages + ); + + // If the unit overlaps with some packages, remove them. + packages = packages.filter((d) => !overlappingPackages.includes(d)); + + // Merge the overlapping packages with the new package + if (overlappingPackages.length > 0) { + packages.push(mergePackages(newPackage, ...overlappingPackages)); + } else { + packages.push(newPackage); + } + } + return packages; +} + +function findOverlapping( + a: AgeComparable, + collection: T[] +): T[] { + return collection.filter((d) => ageOverlaps(a, d)); +} + +function ageOverlaps(a: AgeComparable, b: AgeComparable) { + return a.t_age <= b.b_age && a.b_age >= b.t_age; +} diff --git a/src/pages/correlation/hash-string.ts b/src/pages/correlation/hash-string.ts new file mode 100644 index 00000000..bbd79231 --- /dev/null +++ b/src/pages/correlation/hash-string.ts @@ -0,0 +1,92 @@ +import { LineString } from "geojson"; +import { getHashString, setHashString } from "@macrostrat/ui-components"; +import type { CorrelationState } from "./state"; + +interface CorrelationHashParams { + section?: LineString | null; + unit?: number; +} + +export function getCorrelationHashParams(): CorrelationHashParams { + if (typeof window === "undefined") { + return null; + } + + const hash = new URLSearchParams(window.location.hash.slice(1)); + const _section = hash.get("section"); + const _unit = hash.get("unit"); + + let section = parseSectionFromHash(_section); + let unit: number = null; + + if (_unit != null) { + unit = parseNumber(_unit); + } + + return { + section, + unit, + }; +} + +function parseSectionFromHash(section: any): LineString | null { + /* Section should be specified as space-separated coordinates */ + if (section == null || typeof section !== "string") { + return null; + } + + console.log(section); + + try { + let coords = section.split(" ").map(parseCoordinates); + return { + type: "LineString", + coordinates: coords, + }; + } catch (e) { + console.warn(e); + return null; + } +} + +function parseCoordinates(s: string): [number, number] { + let [x, y] = s.split(",").map(parseNumber); + if (x == null || y == null || isNaN(x) || isNaN(y)) { + throw new Error("Invalid coordinate string"); + } + if (x > 180 || x < -180 || y > 90 || y < -90) { + throw new Error("Invalid coordinate value"); + } + return [x, y]; +} + +function parseNumber(s: string): number { + let s1 = s; + // For some reason, we sometimes get en-dashes in the hash string + if (s1[0] == "−") { + s1 = "-" + s1.slice(1); + } + return Number(s1); +} + +export function setHashStringForCorrelation(state: CorrelationHashParams) { + const { section, unit } = state; + if (section == null) { + return; + } + if (section.coordinates.length < 2) { + return; + } + let _unit = unit; + if (unit == null) { + _unit = undefined; + } + + let hash = { + section: section.coordinates + .map((coord) => coord.map((d) => d.toFixed(2)).join(",")) + .join(" "), + unit: _unit, + }; + setHashString(hash); +} diff --git a/src/pages/correlation/main.module.sass b/src/pages/correlation/main.module.sass new file mode 100644 index 00000000..35226eab --- /dev/null +++ b/src/pages/correlation/main.module.sass @@ -0,0 +1,103 @@ +$map-size: 40em +$map-size-inset: 12em + +.diagram-container + flex: 1 + position: relative + min-height: 0 + display: flex + flex-direction: row + gap: 1em + overflow: hidden + &.map-inset + .column-selection-map + position: absolute + top: 1em + right: 2em + height: $map-size-inset + width: $map-size-inset + z-index: 1000 + + &.map-expanded + .assistant + width: 40em + position: relative + +.column-selection-map, .unit-details-panel, .correlation-diagram + box-shadow: 0 0 0.3em 0 rgba(0, 0, 0, 0.5) + border-radius: 6px + +.column-selection-map + overflow: hidden + :global(.main-view) + height: 100% + flex: 1 + min-height: $map-size-inset + max-height: $map-size + width: $map-size + position: relative + top: 0 + right: 0 + +.unit-details-panel + overflow: scroll + background: var(--panel-secondary-background-color) + flex: 2 + padding: 1em + max-width: 100% + +.main-area + position: relative + flex: 1 + min-height: 0 + min-width: 0 + + +.correlation-diagram + overflow: scroll + min-height: 0 + height: 100% + width: 100% + padding: 1em + background-color: var(--panel-secondary-background-color) + box-shadow: 0 0 0.3em 0 var(--card-shadow-color) + --column-background-color: var(--background-color) + --column-stroke-color: var(--text-color) + + +.map-expanded-button + position: absolute + top: 0.5em + right: 0.5em + z-index: 100 + +.main-panel + position: relative + height: 100% + display: flex + flex-direction: column + + +.correlation-chart-inner + display: flex + flex-direction: row + +.overlay-safe-area + position: absolute + top: 1em + left: 1em + right: 1em + bottom: 1em + pointer-events: none + +.map-inset + .overlay-safe-area + margin-right: $map-size-inset + 1em + + .column-selection-map + z-index: 1000 + +.assistant + display: flex + flex-direction: column + gap: 1em diff --git a/src/pages/correlation/state.ts b/src/pages/correlation/state.ts new file mode 100644 index 00000000..639f9b69 --- /dev/null +++ b/src/pages/correlation/state.ts @@ -0,0 +1,246 @@ +import { LineString, Point } from "geojson"; +import { create } from "zustand"; +import { ColumnGeoJSONRecord } from "~/pages/map/map-interface/app-state/handlers/columns"; +// Turf intersection +import { fetchAllColumns } from "~/pages/map/map-interface/app-state/handlers/fetch"; +import { + getCorrelationHashParams, + setHashStringForCorrelation, +} from "./hash-string"; +// Turf intersection +import { lineIntersect } from "@turf/line-intersect"; +import { distance } from "@turf/distance"; +import { nearestPointOnLine } from "@turf/nearest-point-on-line"; +import { centroid } from "@turf/centroid"; +import { + ColumnIdentifier, + CorrelationChartData, + buildCorrelationChartData, + AgeScaleMode, +} from "./correlation-chart"; +import { UnitLong } from "@macrostrat/api-types"; +import { LocalStorage } from "@macrostrat/ui-components"; +import { SectionRenderData } from "./types"; +import mapboxgl from "mapbox-gl"; + +export interface CorrelationState { + focusedLine: LineString | null; + columns: ColumnGeoJSONRecord[]; + focusedColumns: FocusedColumnGeoJSONRecord[]; + chartData: CorrelationChartData | null; + mapExpanded: boolean; + selectedUnit: UnitLong | null; + onClickMap: (event: mapboxgl.MapMouseEvent, point: Point) => void; + toggleMapExpanded: () => void; + startup: () => Promise; + setSelectedUnit: (unit: UnitLong) => void; +} + +interface CorrelationLocalStorageState { + mapExpanded: boolean; +} + +const localStorage = new LocalStorage( + "macrostrat.correlation.settings" +); + +/** Store management with Zustand. + * This is a newer and somewhat more subtle approach than the Redux store + * used in the mapping application. + * */ +export const useCorrelationDiagramStore = create( + (set, get) => { + const { mapExpanded } = localStorage.get() ?? { + mapExpanded: false, + }; + + const { section, unit } = getCorrelationHashParams(); + + return { + focusedLine: section, + columns: [], + focusedColumns: [], + mapExpanded, + selectedUnit: null, + setSelectedUnit(selectedUnit: UnitLong | null) { + set({ selectedUnit }); + + const { focusedLine } = get(); + + setHashStringForCorrelation({ + section: focusedLine, + unit: selectedUnit?.unit_id ?? null, + }); + }, + toggleMapExpanded: () => + set((state) => { + const partial = { mapExpanded: !state.mapExpanded }; + localStorage.set(partial); + return partial; + }), + onClickMap(event: mapboxgl.MapMouseEvent, point: Point) { + const state = get(); + + console.log(event, point); + // Check if shift key is pressed + const shiftKeyPressed = event.originalEvent.shiftKey; + + if ( + state.focusedLine == null || + (state.focusedLine.coordinates.length >= 2 && !shiftKeyPressed) + ) { + return set({ + focusedLine: { + type: "LineString", + coordinates: [point.coordinates], + }, + focusedColumns: [], + chartData: null, + selectedUnit: null, + }); + } + const focusedLine: LineString = { + type: "LineString", + coordinates: [...state.focusedLine.coordinates, point.coordinates], + }; + + const columns = buildCorrelationColumns(state.columns, focusedLine); + + set({ + focusedLine, + focusedColumns: columns, + selectedUnit: null, + }); + + buildCorrelationChartData( + columns.map(columnGeoJSONRecordToColumnIdentifier), + AgeScaleMode.Broken + ).then((data) => set({ chartData: data })); + + setHashStringForCorrelation({ + section: focusedLine, + unit: null, + }); + }, + async startup() { + const columns = await fetchAllColumns(); + set({ columns }); + + const { focusedLine } = get(); + if (focusedLine == null) { + return; + } + + const focusedColumns = buildCorrelationColumns(columns, focusedLine); + set({ focusedColumns }); + + const chartData = await buildCorrelationChartData( + focusedColumns.map(columnGeoJSONRecordToColumnIdentifier), + AgeScaleMode.Broken + ); + + // Actually set the selected unit from the hash string once column data has been downloaded + if (unit != null) { + const selectedUnit = findMatchingUnit(chartData.columnData, unit); + set({ selectedUnit }); + } + + set({ chartData }); + }, + }; + } +); + +function findMatchingUnit( + columns: SectionRenderData[][], + unit_id: number +): UnitLong { + for (const column of columns) { + for (const section of column) { + for (const unit of section.units) { + if (unit.unit_id == unit_id) { + return unit; + } + } + } + } + return null; +} + +function buildCorrelationColumns( + columns: ColumnGeoJSONRecord[], + line: LineString +): FocusedColumnGeoJSONRecord[] { + let features = []; + if (columns == null && line == null) { + return []; + } + return orderColumnsByDistance( + computeIntersectingColumns(columns, line), + line + ); +} + +function computeIntersectingColumns( + columns: ColumnGeoJSONRecord[], + line: LineString +): ColumnGeoJSONRecord[] { + if (columns == null || line == null) { + return []; + } + + return columns.filter((col) => { + const poly = col.geometry; + const intersection = lineIntersect(line, poly); + return intersection.features.length > 0; + }); +} + +interface FocusedColumnGeoJSONRecord extends ColumnGeoJSONRecord { + properties: { + centroid: Point; + nearestPointOnLine: Point; + distanceAlongLine: number; + } & ColumnGeoJSONRecord["properties"]; +} + +function orderColumnsByDistance( + columns: ColumnGeoJSONRecord[], + line: LineString +): FocusedColumnGeoJSONRecord[] { + const centroids = columns.map((col) => centroid(col.geometry)); + const projectedPoints = centroids.map((point) => + nearestPointOnLine(line, point) + ); + const distances = projectedPoints.map((point) => + distance(point.geometry.coordinates, line.coordinates[0]) + ); + + let newColumns = columns.map((col, i) => { + return { + ...col, + properties: { + ...col.properties, + centroid: centroids[i], + nearestPointOnLine: projectedPoints[i], + distanceAlongLine: distances[i], + }, + }; + }); + + return sorted(newColumns, (d) => d.properties.distanceAlongLine); +} + +function sorted(data, accessor: (d) => number) { + return data.sort((a, b) => accessor(a) - accessor(b)); +} + +export function columnGeoJSONRecordToColumnIdentifier( + col: ColumnGeoJSONRecord +): ColumnIdentifier { + return { + col_id: col.properties.col_id, + col_name: col.properties.col_name, + project_id: col.properties.project_id, + }; +} diff --git a/src/pages/correlation/types.ts b/src/pages/correlation/types.ts new file mode 100644 index 00000000..28408879 --- /dev/null +++ b/src/pages/correlation/types.ts @@ -0,0 +1,25 @@ +import { UnitLong } from "@macrostrat/api-types"; + +export interface AgeComparable { + b_age: number; + t_age: number; +} + +export interface CorrelatedGapBoundPackage extends AgeComparable { + units: UnitLong[][]; + bestPixelScale: number; +} + +type ColumnID = number; + +export type ColumnUnitIndex = Map; + +export interface GapBoundPackage extends AgeComparable { + unitIndex: ColumnUnitIndex; +} + +export interface SectionRenderData extends AgeComparable { + columnID: ColumnID; + units: UnitLong[]; + bestPixelScale: number; +} diff --git a/src/pages/dev/+Page.mdx b/src/pages/dev/+Page.mdx index 8f1299b7..4a40f32a 100644 --- a/src/pages/dev/+Page.mdx +++ b/src/pages/dev/+Page.mdx @@ -12,4 +12,6 @@ import { PageBreadcrumbs } from "~/renderer"; - [Map filter](/dev/filtering) - [Concept apps](/dev/concepts) +- [Built with Macrostrat](/dev/apps) +- [Test Site](/dev/test-site/main-page) - [Documentation](/dev/docs) diff --git a/src/pages/dev/+config.ts b/src/pages/dev/+config.ts new file mode 100644 index 00000000..eb856777 --- /dev/null +++ b/src/pages/dev/+config.ts @@ -0,0 +1,4 @@ +export default { + // Forces global style separation from other pages by reloading + clientRouting: false, + }; \ No newline at end of file diff --git a/src/pages/dev/globe/+config.ts b/src/pages/dev/globe/+config.ts index 2821332f..2f7658ef 100644 --- a/src/pages/dev/globe/+config.ts +++ b/src/pages/dev/globe/+config.ts @@ -8,8 +8,7 @@ export default { }, }, clientRouting: false, - documentProps: { - scripts: ["/cesium/Cesium.js"], - title: "Macrostrat – Globe", - }, + scripts: ["/cesium/Cesium.js"], + title: "Globe", + description: "Macrostrat Globe", }; diff --git a/src/pages/dev/lexicon/+Page.mdx b/src/pages/dev/lexicon/+Page.mdx new file mode 100644 index 00000000..bfbde1c5 --- /dev/null +++ b/src/pages/dev/lexicon/+Page.mdx @@ -0,0 +1,58 @@ +import { PageHeader } from "~/components"; +import { PageBreadcrumbs } from "~/renderer"; +import "./main.styl"; +import { Image } from "./index"; +import { MacrostratIcon } from "~/components"; + +
+ +[//]: # "Nav Bar" + +\ +\ + +[//]: # "Lithologies" +
+
+ +[//]: # "Stratigraphic Names" +
+
+ + +[//]: # "Footer" + + +
diff --git a/src/pages/dev/lexicon/index.ts b/src/pages/dev/lexicon/index.ts new file mode 100644 index 00000000..e3670446 --- /dev/null +++ b/src/pages/dev/lexicon/index.ts @@ -0,0 +1,6 @@ +import h from "@macrostrat/hyper"; + +export function Image({ src, className, width, height }) { + const srcWithAddedPrefix = "https://storage.macrostrat.org/assets/web/main-page/" + src; + return h("img", {src: srcWithAddedPrefix, className, width, height}) +} \ No newline at end of file diff --git a/src/pages/dev/lexicon/main.styl b/src/pages/dev/lexicon/main.styl new file mode 100644 index 00000000..2e546b4f --- /dev/null +++ b/src/pages/dev/lexicon/main.styl @@ -0,0 +1,152 @@ +.htnl, .body, .main + background-color: white + color: black + +a:hover + text-decoration: none +.nav + position: fixed + width: 100% + z-index: 10000 + top: 0 + + ul + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #015eab; + + li + float: left; + font-size: 15px + + li a + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none + +.big + color: #E0E1E6 + text-align: left + font-size: 75px + font-family: "Maven Pro", sans-serif + margin: 0 + margin-left: 20% + margin-top: 30px + +.line + border-bottom: 3px solid #E0E1E6; + width: 60% + margin: 20px 20% + + +.people + color: white + +.left + float: left + width: 30% + margin-left: 20% + +.right + float: left + width: 30% + margin-right: 20% + +.person-info + height: 30vh + width: 90% + position: relative + margin: 5% + +.text + text-align: right + position:absolute + padding: 5px 20px + z-index: 100 + background-color: rgba(0, 0, 0, 0.2) + bottom:0 + right: 0 + + a + color: white + + a:hover + color: white + +.back-img + position:absolute + z-index: 0 + height: 30vh + object-fit: cover + width: 100% + +n + font-size: 25px + font-weight: 200px + +t + font-weight: 400px + +e + font-size: 14px + font-weight: 200px + +.footer + width: 100% + background-color: #015EAB + margin-top: 280vh + +.footer-container + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 0 auto; + width: 60% + +.col-sm-4 + color: white + width: 33.333% + color: #E0E1E6; + font-weight: 200; + text-align: center; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; + font-weight: bold + +.nav-logo + padding-top: 5px + +.funding-logo + padding: 10px + +.footer-nav + list-style-type: none + padding-right: 40px + + li + padding: 5px 0 + + a + color: white + + a:hover + text-decoration: none + +.git_logo + vertical-align: sub + +.f1-text + a + color: white + +#who-made-it + padding-top: 25px + +.subtitle + margin-left: 20% + color: #E0E1E6; \ No newline at end of file diff --git a/src/pages/dev/main-page/sum.js b/src/pages/dev/main-page/sum.js new file mode 100644 index 00000000..118bc983 --- /dev/null +++ b/src/pages/dev/main-page/sum.js @@ -0,0 +1,3 @@ +export function sum(a, b) { + return a + b; + } \ No newline at end of file diff --git a/src/pages/dev/main-page/sum.test.js b/src/pages/dev/main-page/sum.test.js new file mode 100644 index 00000000..421674c8 --- /dev/null +++ b/src/pages/dev/main-page/sum.test.js @@ -0,0 +1,7 @@ +// sum.test.js +import { expect, test } from 'vitest' +import { sum } from './sum' + +test('file works', () => { + expect(sum(1, 2)).toBe(3) +}) \ No newline at end of file diff --git a/src/pages/dev/paleo/+Page.ts b/src/pages/dev/paleo/+Page.ts index b12363f9..db350a09 100644 --- a/src/pages/dev/paleo/+Page.ts +++ b/src/pages/dev/paleo/+Page.ts @@ -1,14 +1,219 @@ +import { HTMLSelect, NonIdealState, Spinner, Switch } from "@blueprintjs/core"; +import { burwellTileDomain, mapboxAccessToken } from "@macrostrat-web/settings"; import h from "@macrostrat/hyper"; -import { ClientOnly } from "~/renderer/client-only"; +import { + FeaturePanel, + FeatureSelectionHandler, + FloatingNavbar, + LocationPanel, + MapAreaContainer, + MapLoadingButton, + MapMarker, + MapView, + PanelCard, + TileInfo, +} from "@macrostrat/map-interface"; +import { + DarkModeButton, + Spacer, + useAPIResult, + useStoredState, +} from "@macrostrat/ui-components"; +import mapboxgl from "mapbox-gl"; +import { useCallback, useState } from "react"; +import { TimescalePanel } from "./timescale"; +import { usePaleogeographyState } from "./state"; +import { usePaleogeographyStyle } from "./map-style"; -const PaleoMap = () => import("./paleo-map"); +// Import other components export function Page() { + const mapboxToken = mapboxAccessToken; + mapboxgl.accessToken = mapboxToken; + + const [isOpen, setOpen] = useState(false); + + const [state, setState] = useStoredState("macrostrat:dev-map-page", { + showTileExtent: false, + xRay: false, + }); + const { showTileExtent, xRay } = state; + + const [paleoState, dispatch] = usePaleogeographyState(); + + const { + age, + mapPosition, + allModels: models, + activeModel, + initialized, + modelName, + } = paleoState; + + const model_id = activeModel?.id; + + const style = usePaleogeographyStyle({ + age, + model_id, + xRay, + }); + + const model = activeModel; + + const [inspectPosition, setInspectPosition] = + useState(null); + + const [data, setData] = useState(null); + + const onSelectPosition = useCallback((position: mapboxgl.LngLat) => { + setInspectPosition(position); + }, []); + + const onMapMoved = useCallback( + (pos) => dispatch({ type: "set-map-position", mapPosition: pos }), + [] + ); + + if (!initialized) { + return h(Spinner); + } + + let detailElement = null; + if (inspectPosition != null) { + detailElement = h( + LocationPanel, + { + onClose() { + setInspectPosition(null); + }, + position: inspectPosition, + }, + [ + h(TileInfo, { + feature: data?.[0] ?? null, + showExtent: showTileExtent, + setShowExtent() { + setState({ ...state, showTileExtent: !showTileExtent }); + }, + }), + h(FeaturePanel, { + features: data, + focusedSource: "plates", + focusedSourceTitle: "Paleogeography", + }), + ] + ); + } + + let mapView = h( + MapView, + { + style, + mapPosition, + projection: { name: "globe" }, + enableTerrain: false, + mapboxToken, + onMapMoved, + }, + [ + h(FeatureSelectionHandler, { + selectedLocation: inspectPosition, + setFeatures: setData, + }), + h(MapMarker, { + position: inspectPosition, + setPosition: onSelectPosition, + }), + ] + ); + if (activeModel == null) { + const title = + modelName == null + ? "No model selected" + : h(["Model ", h("code", modelName), " not found"]); + + mapView = h(NonIdealState, { + title, + description: "Please select a valid model from the control panel", + }); + } + return h( - ClientOnly, + MapAreaContainer, { - component: PaleoMap, + navbar: h(FloatingNavbar, [ + h("h2", "Paleogeography"), + h(Spacer), + h(MapLoadingButton, { + active: isOpen, + onClick() { + setOpen(!isOpen); + }, + }), + ]), + contextPanel: h(PanelCard, [ + h(PlateModelControls, { + models, + activeModel: model_id, + setModel(modelID) { + dispatch({ type: "set-model", modelID }); + }, + age, + }), + h(Switch, { + checked: xRay, + label: "X-ray mode", + onChange() { + setState({ ...state, xRay: !xRay }); + }, + }), + h(DarkModeButton), + ]), + detailPanel: detailElement, + contextPanelOpen: isOpen, + bottomPanel: h(TimescalePanel, { + age, + setAge(age) { + dispatch({ type: "set-age", age }); + }, + ageRange: ageRangeForModel(activeModel), + }), }, - null + mapView ); } + +function ageRangeForModel(model) { + if (model == null) return [3500, 0]; + const { max_age, min_age } = model; + return [max_age ?? 3500, min_age ?? 0]; +} + +function PlateModelControls({ models, activeModel, age, setModel }) { + return h("div.controls", [ + h("h3", [h("span", "Age:"), " ", h("span.age", age), " ", h("span", "Ma")]), + h(PlateModelSelector, { models, activeModel, setModel }), + ]); +} + +function PlateModelSelector({ models, activeModel, setModel }) { + if (models == null) return null; + + const onChange = (evt) => { + const { value } = evt.target; + setModel(value); + }; + + return h(HTMLSelect, { + value: activeModel, + onChange, + options: models + .filter((d) => { + return d.id != 5; + }) + .map((d) => ({ + label: d.name, + value: d.id, + })), + }); +} diff --git a/src/pages/dev/paleo/+config.ts b/src/pages/dev/paleo/+config.ts index 2199da7a..ac56ce59 100644 --- a/src/pages/dev/paleo/+config.ts +++ b/src/pages/dev/paleo/+config.ts @@ -1,10 +1,10 @@ export default { - route: "/map/dev*", meta: { Page: { env: { client: true, - } - } - } + server: false, + }, + }, + }, }; diff --git a/src/pages/dev/paleo/map-style.ts b/src/pages/dev/paleo/map-style.ts new file mode 100644 index 00000000..6cec46a6 --- /dev/null +++ b/src/pages/dev/paleo/map-style.ts @@ -0,0 +1,126 @@ +import { burwellTileDomain, mapboxAccessToken } from "@macrostrat-web/settings"; +import { buildMacrostratStyle } from "@macrostrat/mapbox-styles"; +import { buildInspectorStyle } from "@macrostrat/map-interface"; +import { useDarkMode } from "@macrostrat/ui-components"; +import mapboxgl from "mapbox-gl"; +import { useEffect, useState } from "react"; + +export function usePaleogeographyStyle({ age, model_id, xRay = false }) { + const dark = useDarkMode(); + const isEnabled = dark?.isEnabled; + const baseStyle: mapboxgl.Style = isEnabled ? darkStyle : lightStyle; + const [style, setStyle] = useState(baseStyle); + + useEffect(() => { + let _overlayStyle: mapboxgl.Style = buildMacrostratStyle({ + tileserverDomain: burwellTileDomain, + }) as mapboxgl.Style; + + if (model_id != null && age != null) { + _overlayStyle = replaceSourcesForTileset(_overlayStyle, model_id, age); + } + + buildInspectorStyle(baseStyle, _overlayStyle, { + mapboxToken: mapboxAccessToken, + inDarkMode: isEnabled, + xRay, + }).then(setStyle); + }, [baseStyle, xRay, age, model_id]); + + return style; +} + +const baseTilesetURL = + burwellTileDomain + "/carto-slim-rotated/{z}/{x}/{y}?model_id=3&t_step=0"; + +const common = { + version: 8, + sources: { + burwell: { + type: "vector", + tiles: [baseTilesetURL], + tileSize: 512, + }, + }, + glyphs: "mapbox://fonts/mapbox/{fontstack}/{range}.pbf", + sprite: "", //mapbox://sprites/mapbox/light-v10", + fog: { + range: [10, 20], + color: "#000000", + "high-color": "hsl(207, 23%, 5%)", + "space-color": "hsl(207, 23%, 10%)", + "horizon-blend": 0.1, + "star-intensity": 0, + }, +}; + +const darkStyle = { + name: "PaleoDark", + ...common, + layers: [ + { + id: "background", + type: "background", + paint: { + "background-color": "hsl(207, 18%, 21%)", + }, + }, + { + id: "plates", + type: "fill", + source: "burwell", + "source-layer": "plates", + paint: { + //"fill-color": "color", + "fill-color": "hsl(207, 18%, 30%)", + "fill-opacity": 0.8, + }, + }, + ], +} as mapboxgl.Style; + +const lightStyle = { + name: "PaleoLight", + ...common, + layers: [ + { + id: "background", + type: "background", + paint: { + "background-color": "hsl(185, 9%, 81%)", + }, + }, + { + id: "plates", + type: "fill", + source: "burwell", + "source-layer": "plates", + paint: { + //"fill-color": "color", + "fill-color": "hsl(55, 11%, 96%)", + "fill-opacity": 0.8, + }, + }, + ], +} as mapboxgl.Style; + +export function replaceSourcesForTileset( + style: mapboxgl.Style, + model_id: number, + age = 0 +): mapboxgl.Style { + const tilesetURL = + burwellTileDomain + + `/carto-slim-rotated/{z}/{x}/{y}?model_id=${model_id}&t_step=${age}`; + + return { + ...style, + sources: { + ...style.sources, + burwell: { + type: "vector", + tiles: [tilesetURL], + }, + }, + }; +} diff --git a/src/pages/dev/paleo/paleo-map.ts b/src/pages/dev/paleo/paleo-map.ts deleted file mode 100644 index 79a41c33..00000000 --- a/src/pages/dev/paleo/paleo-map.ts +++ /dev/null @@ -1,453 +0,0 @@ -import { HTMLSelect, Spinner, Switch } from "@blueprintjs/core"; -import { burwellTileDomain, mapboxAccessToken } from "@macrostrat-web/settings"; -import h from "@macrostrat/hyper"; -import { - FeaturePanel, - FeatureSelectionHandler, - FloatingNavbar, - LocationPanel, - MapAreaContainer, - MapLoadingButton, - MapMarker, - MapView, - PanelCard, - TileInfo, - applyMapPositionToHash, - buildInspectorStyle, - getMapPositionForHash, -} from "@macrostrat/map-interface"; -import { buildMacrostratStyle } from "@macrostrat/mapbox-styles"; -import { MapPosition } from "@macrostrat/mapbox-utils"; -import { - DarkModeButton, - Spacer, - getHashString, - setHashString, - useAPIResult, - useDarkMode, - useStoredState, -} from "@macrostrat/ui-components"; -import mapboxgl from "mapbox-gl"; -import { useCallback, useEffect, useMemo, useReducer, useState } from "react"; -import { MacrostratVectorTileset } from "~/pages/map/dev/map-layers"; -import { TimescalePanel } from "./timescale"; - -import "~/styles/global.styl"; - -// Import other components - -type PaleogeographyState = { - model_id: number; - age: number; - mapPosition: MapPosition; - initialized: boolean; -}; - -type PaleogeographyAction = - | { type: "set-model"; model_id: number } - | { type: "set-age"; age: number } - | { type: "set-map-position"; mapPosition: MapPosition } - | { type: "initialize"; state: PaleogeographyState }; - -function usePaleogeographyState( - defaultState: PaleogeographyState -): [PaleogeographyState, (s: PaleogeographyAction) => void] { - /** Use state synced with hash string for paleogeography layer */ - const defaultModelID = defaultState.model_id; - const defaultAge = defaultState.age; - - const [state, dispatch] = useReducer( - (state: PaleogeographyState, action: PaleogeographyAction) => { - switch (action.type) { - case "set-model": - return { - ...state, - model_id: action.model_id, - age: state.age ?? defaultAge, - }; - case "set-age": - return { ...state, age: action.age }; - case "set-map-position": - return { ...state, mapPosition: action.mapPosition }; - case "initialize": - return { ...action.state, initialized: true }; - } - }, - { model_id: null, age: null, mapPosition: null, initialized: false } - ); - - const { model_id, age, mapPosition } = state; - - useEffect(() => { - if (model_id == null || age == null || mapPosition == null) return; - let args: any = { model_id, age }; - applyMapPositionToHash(args, mapPosition); - setHashString(args, { sort: false, arrayFormat: "comma" }); - }, [model_id, age, mapPosition]); - - useEffect(() => { - const hashData = getHashString(window.location.hash) ?? {}; - const { model_id, age, ...rest } = hashData; - const mapPosition = getMapPositionForHash( - rest, - defaultState.mapPosition.camera - ); - - if (model_id == null || age == null) return; - if (Array.isArray(model_id)) return; - if (Array.isArray(age)) return; - dispatch({ - type: "initialize", - state: { - model_id: parseInt(model_id) ?? defaultModelID, - age: parseInt(age) ?? defaultAge, - mapPosition, - initialized: true, - }, - }); - }, []); - - return [state, dispatch]; -} - -const baseTilesetURL = - burwellTileDomain + "/carto-slim-rotated/{z}/{x}/{y}?model_id=6&t_step=0"; - -const common = { - version: 8, - sources: { - burwell: { - type: "vector", - tiles: [baseTilesetURL], - tileSize: 512, - }, - }, - glyphs: "mapbox://fonts/mapbox/{fontstack}/{range}.pbf", - sprite: "mapbox://sprites/mapbox/light-v10", -}; - -const darkStyle = { - name: "PaleoLight", - ...common, - layers: [ - { - id: "background", - type: "background", - paint: { - "background-color": "hsl(185, 2%, 10%)", - }, - }, - { - id: "plates", - type: "fill", - source: "burwell", - "source-layer": "plates", - paint: { - //"fill-color": "color", - "fill-color": "hsl(55, 1%, 20%)", - "fill-opacity": 0.8, - }, - }, - ], -}; -const lightStyle = { - name: "PaleoLight", - ...common, - layers: [ - { - id: "background", - type: "background", - paint: { - "background-color": "hsl(185, 9%, 81%)", - }, - }, - { - id: "plates", - type: "fill", - source: "burwell", - "source-layer": "plates", - paint: { - //"fill-color": "color", - "fill-color": "hsl(55, 11%, 96%)", - "fill-opacity": 0.8, - }, - }, - ], -}; - -export default function PaleoMap({ - tileset = MacrostratVectorTileset.CartoSlim, - overlayStyle = _macrostratStyle, - children, -}: { - headerElement?: React.ReactElement; - title?: string; - tileset?: MacrostratVectorTileset; - overlayStyle?: mapboxgl.Style; - children?: React.ReactNode; -}) { - // A stripped-down page for map development - - /* We apply a custom style to the panel container when we are interacting - with the search bar, so that we can block map interactions until search - bar focus is lost. - We also apply a custom style when the infodrawer is open so we can hide - the search bar on mobile platforms - */ - const title = "Paleogeography"; - const dark = useDarkMode(); - const isEnabled = dark?.isEnabled; - const mapboxToken = mapboxAccessToken; - mapboxgl.accessToken = mapboxToken; - - const style = isEnabled ? darkStyle : lightStyle; - - const [isOpen, setOpen] = useState(false); - - const [state, setState] = useStoredState("macrostrat:dev-map-page", { - showTileExtent: false, - xRay: false, - }); - const { showTileExtent, xRay } = state; - - const [actualStyle, setActualStyle] = useState(style); - const [paleoState, dispatch] = usePaleogeographyState({ - model_id: null, - age: 0, - initialized: false, - mapPosition: { - camera: { - lng: -40, - lat: 45, - altitude: 5000000, - }, - }, - }); - - const { age, model_id, mapPosition } = paleoState; - const plateModelId = model_id; - - const models: { id: string; max_age: number; min_age: number }[] = - useAPIResult(burwellTileDomain + "/carto/rotation-models"); - - useEffect(() => { - if (models == null) return; - if (plateModelId == null) { - dispatch({ type: "set-model", model_id: parseInt(models[0].id) }); - } - }, [models]); - - const model = models?.find((d) => d.id == plateModelId); - - // useEffect(() => { - // console.log("effect", model, age); - // if (model == null) return; - // const { max_age = 4000, min_age = 0 } = model; - // console.log("effect", model, age, max_age, min_age); - // if (age > max_age) { - // dispatch({ type: "set-age", age: max_age }); - // } else if (age < min_age) { - // dispatch({ type: "set-age", age: min_age }); - // } - // }, [model, age]); - - // Manage location hash - - const _overlayStyle = useMemo(() => { - if (plateModelId == null || age == null) return overlayStyle; - return replaceSourcesForTileset(overlayStyle, plateModelId, age); - }, [tileset, overlayStyle, plateModelId, age]) as mapboxgl.Style; - - useEffect(() => { - buildInspectorStyle(style, _overlayStyle, { - mapboxToken, - inDarkMode: isEnabled, - xRay, - }).then(setActualStyle); - }, [style, xRay, mapboxToken, isEnabled, _overlayStyle]); - - const [inspectPosition, setInspectPosition] = - useState(null); - - const [data, setData] = useState(null); - - const onSelectPosition = useCallback((position: mapboxgl.LngLat) => { - setInspectPosition(position); - }, []); - - const onMapMoved = useCallback( - (pos) => dispatch({ type: "set-map-position", mapPosition: pos }), - [] - ); - - if (age == null || model_id == null) { - return h(Spinner); - } - - let detailElement = null; - if (inspectPosition != null) { - detailElement = h( - LocationPanel, - { - onClose() { - setInspectPosition(null); - }, - position: inspectPosition, - }, - [ - h(TileInfo, { - feature: data?.[0] ?? null, - showExtent: showTileExtent, - setShowExtent() { - setState({ ...state, showTileExtent: !showTileExtent }); - }, - }), - h(FeaturePanel, { - features: data, - focusedSource: "plates", - focusedSourceTitle: "Paleogeography", - }), - ] - ); - } - - return h( - MapAreaContainer, - { - navbar: h(FloatingNavbar, [ - h("h2", title), - h(Spacer), - h(MapLoadingButton, { - active: isOpen, - onClick: () => setOpen(!isOpen), - }), - ]), - contextPanel: h(PanelCard, [ - h(PlateModelControls, { - models, - activeModel: plateModelId, - setModel(model_id) { - dispatch({ type: "set-model", model_id }); - }, - age, - }), - h(Switch, { - checked: xRay, - label: "X-ray mode", - onChange() { - setState({ ...state, xRay: !xRay }); - }, - }), - h(DarkModeButton), - children, - ]), - detailPanel: detailElement, - contextPanelOpen: isOpen, - bottomPanel: h(TimescalePanel, { - age, - setAge(age) { - dispatch({ type: "set-age", age }); - }, - ageRange: ageRangeForModel(model), - }), - }, - h( - MapView, - { - style: actualStyle, - mapPosition, - projection: { name: "globe" }, - enableTerrain: false, - mapboxToken, - onMapMoved, - }, - [ - h(FeatureSelectionHandler, { - selectedLocation: inspectPosition, - setFeatures: setData, - }), - h(MapMarker, { - position: inspectPosition, - setPosition: onSelectPosition, - }), - ] - ) - ); -} - -function ageRangeForModel(model) { - if (model == null) return [3500, 0]; - const { max_age, min_age } = model; - return [max_age ?? 3500, min_age ?? 0]; -} - -function PlateModelControls({ models, activeModel, age, setModel }) { - return h("div.controls", [ - h("h3", [h("span", "Age:"), " ", h("span.age", age), " ", h("span", "Ma")]), - h(PlateModelSelector, { models, activeModel, setModel }), - ]); -} - -function PlateModelSelector({ models, activeModel, setModel }) { - if (models == null) return null; - - const onChange = (evt) => { - const { value } = evt.target; - setModel(value); - }; - - return h(HTMLSelect, { - value: activeModel, - onChange, - options: models - .filter((d) => { - return d.id != 5; - }) - .map((d) => ({ - label: d.name, - value: d.id, - })), - }); -} - -export function replaceSourcesForTileset( - style: mapboxgl.Style, - model_id: number = 6, - age = 0 -) { - const tilesetURL = - burwellTileDomain + - `/carto-slim-rotated/{z}/{x}/{y}?model_id=${model_id}&t_step=${age}`; - - return { - ...style, - sources: { - ...style.sources, - burwell: { - type: "vector", - tiles: [tilesetURL], - tileSize: 512, - }, - }, - }; -} - -const _macrostratStyle = buildMacrostratStyle({ - tileserverDomain: burwellTileDomain, -}) as mapboxgl.Style; - -function isStateValid(state) { - if (state == null) { - return false; - } - if (typeof state != "object") { - return false; - } - // Must have several specific boolean keys - for (let k of ["showLineSymbols", "xRay", "showTileExtent", "bypassCache"]) { - if (typeof state[k] != "boolean") { - return false; - } - } - return true; -} diff --git a/src/pages/dev/paleo/state.ts b/src/pages/dev/paleo/state.ts new file mode 100644 index 00000000..37957d6a --- /dev/null +++ b/src/pages/dev/paleo/state.ts @@ -0,0 +1,167 @@ +import { + applyMapPositionToHash, + getMapPositionForHash, +} from "@macrostrat/map-interface"; +import { MapPosition } from "@macrostrat/mapbox-utils"; +import { getHashString, setHashString } from "@macrostrat/ui-components"; +import { useCallback, useEffect, useReducer } from "react"; +import { burwellTileDomain } from "@macrostrat-web/settings"; + +// Import other components + +type MapState = { + modelName: string; + age: number; + mapPosition: MapPosition; +}; + +type PaleogeographyState = MapState & { + initialized: boolean; + allModels: ModelInfo[]; + activeModel: ModelInfo | null; +}; + +type ModelInfo = { id: number; max_age: number; min_age: number; name: string }; + +type PaleogeographySyncAction = + | { type: "set-model"; modelID: number } + | { type: "set-age"; age: number } + | { type: "set-map-position"; mapPosition: MapPosition } + | { type: "set-initial-state"; state: PaleogeographyState }; + +type PaleogeographyAction = PaleogeographySyncAction | { type: "initialize" }; + +function paleogeographyReducer( + state: PaleogeographyState = defaultState, + action: PaleogeographySyncAction +): PaleogeographyState { + switch (action.type) { + case "set-model": + const activeModel = state.allModels.find((d) => d.id == action.modelID); + return updateHashString({ + ...state, + activeModel, + modelName: activeModel?.name ?? state.modelName, + age: normalizeAge(state.age, activeModel), + }); + case "set-age": + // Round to nearest 5 Ma + return updateHashString({ + ...state, + age: normalizeAge(action.age, state.activeModel), + }); + case "set-map-position": + return updateHashString({ ...state, mapPosition: action.mapPosition }); + case "set-initial-state": + return { ...state, ...action.state, initialized: true }; + } +} + +async function transformAction( + action: PaleogeographyAction +): Promise { + switch (action.type) { + case "initialize": + const hashData = getHashString(window.location.hash); + + const { model, age, ...rest } = hashData ?? {}; + const mapPosition = getMapPositionForHash( + rest, + defaultState.mapPosition.camera + ); + + const modelsURL = burwellTileDomain + "/carto/rotation-models"; + const models: ModelInfo[] = await fetch(modelsURL).then((res) => + res.json() + ); + + let modelName: string | null = null; + if (Array.isArray(model)) { + modelName = model[0]; + } else if (model != null) { + modelName = model; + } else { + modelName = "Seton2012"; + } + + let baseAge: number | null = null; + if (Array.isArray(age)) { + baseAge = parseInt(age[0]); + } else if (age != null) { + baseAge = parseInt(age); + } + + console.log(age, baseAge); + + const activeModel = models.find((d) => d.name == modelName); + + return { + type: "set-initial-state", + state: { + modelName, + age: normalizeAge(baseAge, activeModel), + mapPosition, + allModels: models, + activeModel, + initialized: true, + }, + }; + } + return action; +} + +function normalizeAge(age: number | null, model: ModelInfo | null): number { + if (model == null) return age; + let age1 = age ?? 0; + age1 = Math.max(model.min_age, Math.min(model.max_age, age)); + // Round age to nearest 5 Ma + return Math.round(age1 / 5) * 5; +} + +const defaultState: PaleogeographyState = { + modelName: "Seton2012", + age: 0, + mapPosition: { + camera: { + lng: -40, + lat: 45, + altitude: 5000000, + }, + }, + initialized: false, + allModels: [], + activeModel: null, +}; + +export function usePaleogeographyState(): [ + PaleogeographyState, + (s: PaleogeographyAction) => void +] { + /** Use state synced with hash string for paleogeography layer */ + + const [state, dispatch] = useReducer(paleogeographyReducer, defaultState); + + // convert to zustand or something... + const actionRunner = useCallback( + (action: PaleogeographyAction) => { + transformAction(action).then((a) => dispatch(a)); + }, + [dispatch] + ); + + useEffect(() => { + actionRunner({ + type: "initialize", + }); + }, []); + + return [state, actionRunner]; +} + +function updateHashString(state: PaleogeographyState): PaleogeographyState { + const { modelName, age, mapPosition } = state; + let args: any = { model: modelName, age }; + applyMapPositionToHash(args, mapPosition); + setHashString(args, { sort: false, arrayFormat: "comma" }); + return state; +} diff --git a/src/pages/dev/test-site/about/+Page.mdx b/src/pages/dev/test-site/about/+Page.mdx new file mode 100644 index 00000000..a00f9e88 --- /dev/null +++ b/src/pages/dev/test-site/about/+Page.mdx @@ -0,0 +1,148 @@ +import { PageHeader } from "~/components"; +import { LinkCard } from "~/components/cards"; +import { Image, Navbar, Footer } from "../index"; +import "./main.styl"; +import "../main.styl"; +import { MacrostratIcon } from "~/components"; + +[//]: # "Nav Bar" + + +
+ +[//]: # "About" +
+

+ About +
+

+ +
+
+ Summary +
+
+ Macrostrat is a platform for the aggregation and distribution of geological data relevant to the spatial and temporal distribution of sedimentary, igneous, and metamorphic rocks as well as data extracted from them. It is linked to the xDD (formly GeoDeepDive) digital library and machine reading system, and it aims to become a community resource for the addition, editing, and distribution of new stratigraphic, lithological, environmental, and economic data. Interactive applications built upon Macrostrat are designed for educational and research purposes. +
+
+ +
+
+ License +
+
+ All data are provided under a Creative Commons Attribution 4.0 International license (CC-BY-4.0). +
+
+ +
+
+ Citation +
+
+ In presentations: Acknowledge Macrostrat by name. You may also include any of the Macrostrat logos accessible on this webpage. + \ + \ + In publications: Acknowledge Macrostrat as the source of any information or data. In publications, you may cite our most recent infrastructure paper, Peters et al. (2018). In addition, you should also include citations to the original references associated with the data set that was used. These references are accessible from the API. If you would like your paper listed in the official publications, please contact us and we will provide a citation and link. +
+
+ +
+
+ Collaboration +
+
+ Our small team has worked hard to compile, format, and make data available via Macrostrat. We strongly encourage and welcome active collaborations, both scientific and geoinformatic. All data are provided freely on under a CC-BY-4.0 license. +
+
+ +
+
+ Funding +
+
+ Major Macrostrat data infrastructure development was supported by the US National Science Foundation (EAR-1150082, ICER-1440312), with ongoing support for data acquisition supported by NSF EAR-1948843 and ICER-1928323. Continuous and ongoing support has also been provided by the UW-Madison Department of Geoscience. If you use Macrostrat and like what we do, please consider helping out with a donation. Every contribute helps us to maintain infrastructure and keep improving. +
+
+
+ +[//]: # "API" +
+
API
+
All data contained in the Macrostrat database are freely available via our Application Programming Interface (API), which provides a convinient way to retrieve data for analysis or application creation. For more information head over to the API root to explore available routes.
+
+ + + +[//]: # "Apps" + + +
+ +[//]: # "Footer" +
diff --git a/src/pages/dev/test-site/about/main.styl b/src/pages/dev/test-site/about/main.styl new file mode 100644 index 00000000..f0d36dff --- /dev/null +++ b/src/pages/dev/test-site/about/main.styl @@ -0,0 +1,124 @@ +.html, .body, #apps + color: black + background-color: white + +#body + background-color: white + padding: 0 20% + +#apps + padding: 75px 0 + +a:hover + text-decoration: none + +.app-header + margin: 0 + padding: 0 + border: solid grey + +.app-box + height: 15vh + display: flex + width: 60% + margin: 0 20% + position: relative + color: black + +.app-background-text + margin: auto + padding-right: 20px + margin-right: 0 + text-align: right + max-width: 55% + font-size: 72px + +.blurb + font-size: 15px + +.app-img + height: 80% + margin: auto + margin-left: 6% + +.big-apps + font-size: 72px + font-family: "Maven Pro", sans-serif + text-align: center + background-color: #4BABBf + width: 60% + margin: 0 20% + color: white + + +.big + font-size: 72px + font-family: "Maven Pro", sans-serif; + text-align: center + +#api + position: relative + height: 30vh + background-color: white + +#api-circle-text + display: table-cell; + vertical-align: middle; + color: #6bbe98; + font-size: 72px + font-weight: 500 + +.api-circle + float: left + margin-left: 15% + margin-right: 50px + background-color: #fff; + height: 200px; + width: 200px; + border-radius: 50%; + text-align: center; + display: table; + border: 11px solid #6bbe98; + color: #4BABBf; + +#api-text + padding-top: 50px + margin-right: 15% + color: #4bab7f; + font-size: 20px; + line-height: 28px; + font-weight: 400; + + +#about + padding: 50px 0 + +.about-row + margin: 4% + font-size: 18px; + line-height: 28px; + font-weight: 200; + +.about-body-subtitle + float: left + font-size: 22px; + font-weight: 200; + +.about-body + margin-left: 30% + +#about-title + text-align: left + color: #E0E1E6 + position: relative + padding-bottom: 0 + +.line + width: 100%; + border-bottom: 1px solid #E0E1E6; + position: absolute; + +.rock-border + width: 100% + margin: 0 + padding: 0 diff --git a/src/pages/dev/test-site/donate/+Page.mdx b/src/pages/dev/test-site/donate/+Page.mdx new file mode 100644 index 00000000..83e71dbb --- /dev/null +++ b/src/pages/dev/test-site/donate/+Page.mdx @@ -0,0 +1,27 @@ +import { PageHeader } from "~/components"; +import { PageBreadcrumbs } from "~/renderer"; +import { Image, Navbar, Footer } from "../index"; +import "./main.styl"; +import "../main.styl"; +import { MacrostratIcon } from "~/components"; + +[//]: # "Nav Bar" + + +[//]: # "Donate" + + +[//]: # "Footer" +
\ No newline at end of file diff --git a/src/pages/dev/test-site/donate/main.styl b/src/pages/dev/test-site/donate/main.styl new file mode 100644 index 00000000..f2d090bf --- /dev/null +++ b/src/pages/dev/test-site/donate/main.styl @@ -0,0 +1,76 @@ +.htnl, .body, .main + background-color: white + color: black + +a:hover + text-decoration: none + +.big + font-size: 72px + font-family: "Maven Pro", sans-serif; + text-align: center + +p + margin-bottom: 10px; + margin-top: 0; + +.donate-container + height: 80vh + +.title + margin: 0 + font-size: 72px + font-family: "Maven Pro", sans-serif; + padding: 20vh 0 + text-align: center + +.donate-title + a + color:white + a:hover + text-decoration: none + + color: white + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + font-family: "Maven Pro", sans-serif; + padding: 0 + +.donate-left + float: left + width: 50% + height: 80vh + display: flex; + justify-content: center; + align-items: center; + +.donate-right + float: right + width: 50% + height: 80vh + display: flex; + justify-content: center; + align-items: center; + +.donate-info + background: rgba(255,255,255,0.6); + padding: 25px + color: black + width: 80% + font-size: 18px + + a + color: black + font-weight: bold + text-decoration: none + +.donate-img + position:absolute + z-index:5 + height: 80vh + width: 100% + object-fit: cover + +.text + position:absolute + z-index: 100 + padding: 0 20px \ No newline at end of file diff --git a/src/pages/dev/test-site/index.ts b/src/pages/dev/test-site/index.ts new file mode 100644 index 00000000..29a075f3 --- /dev/null +++ b/src/pages/dev/test-site/index.ts @@ -0,0 +1,49 @@ +import h from "@macrostrat/hyper"; +import { MacrostratIcon } from "~/components"; + +export function Image({ src, className, width, height }) { + const srcWithAddedPrefix = "https://storage.macrostrat.org/assets/web/main-page/" + src; + return h("img", {src: srcWithAddedPrefix, className, width, height}) +} + +export function Navbar() { + return h("div", {className: "nav"}, [ + h("ul", [ + h("li", h("a", {href: "/dev/test-site/main-page"}, h(MacrostratIcon))), + h("li", h("a", {href: "/dev/test-site/about"}, "About")), + h("li", h("a", {href: "/dev/test-site/publications"}, "Publications")), + h("li", h("a", {href: "/dev/test-site/people"}, "People")), + h("li", h("a", {href: "/dev/test-site/donate"}, "Donate")) + ]) + ]); +} + +export function Footer() { + return h("div", {className: "footer"}, [ + h("div", {className: "footer-container"}, [ + h("div", {className: "col-sm-4"}, [ + h(Image, {className: "logo_white", src: "logo_white.png", width: "100px"}), + h("p", {className: "f1-text"}, [ + "Produced by the ", + h("a", {href: "http://strata.geology.wisc.edu", target: "_blank"}, "UW Macrostrat Lab"), + h("a", {href: "https://github.com/UW-Macrostrat", target: "_blank"}, h(Image, {className: "git_logo", src: "git-logo.png", width: "18px"})), + ]) + ]), + h("div", {className: "col-sm-4"}, [ + h("ul", {className: "footer-nav"}, [ + h("li", h("a", {href: "/dev/test-site/about"}, "About")), + h("li", h("a", {href: "/dev/test-site/publications"}, "Publications")), + h("li", h("a", {href: "/dev/test-site/people"}, "People")), + h("li", h("a", {href: "/dev/test-site/donate"}, "Donate")) + ]) + ]), + h("div", {className: "col-sm-4"}, [ + h(Image, {className: "funding-logo", src: "nsf.png", width: "100px"}), + h("div", {className: "funding-line"}, "Current support:"), + h("div", {className: "funding-line"}, "EAR-1948843"), + h("div", {className: "funding-line"}, "ICER-1928323"), + h("div", {className: "funding-line"}, "UW-Madison Dept. Geoscience") + ]) + ]) + ]); +} \ No newline at end of file diff --git a/src/pages/dev/test-site/main-page/+Page.mdx b/src/pages/dev/test-site/main-page/+Page.mdx new file mode 100644 index 00000000..fe1fad95 --- /dev/null +++ b/src/pages/dev/test-site/main-page/+Page.mdx @@ -0,0 +1,154 @@ +import { PageHeader } from "~/components"; +import { LinkCard } from "~/components/cards"; +import { Image, Navbar, Footer } from "../index"; +import "./main.styl"; +import "../main.styl"; +import { MacrostratIcon } from "~/components"; + +
+ +[//]: # "Nav Bar" + + +[//]: # "Start Page" +
+ +
+ +

Macrostrat

v2

+
+
+
+ 1,400 + Regional Rock Columns +
+
+ 33,903 + Rock Units +
+
+ 2,500,000 + Geologic Map Polygons +
+
+ 51,212 + Stratigraphic Names +
+
+

A platform for geological data exploration, integration, and analysis

+
+ Search + Geologic Map +
+
+ + Go mobile +
+
+
+
+
+ + +[//]: # "Locations" +
+
+ +
+

North America

+
243 packages. 798 units. 897 collections.
+
+ +
+

Carribean

+
243 packages. 798 units. 897 collections.
+
+
+
+
+

New Zealand

+
828 packages. 2,168 units. 328 collections.
+
+ +
+

Deep Sea

+
388 packages. 7,124 units. 0 collections.
+
+ +
+
+ +[//]: # "Map Interface" +
+ +

+

With over 225 maps from data providers around the world across every scale, Macrostrat is the world's largest homogenized geologic map database. Our data processing pipeline links geologic map polygons to Macrostrat column polygons, external stratigraphic name lexicons, and geochronological intervals, enabling the enhancement of the original map data and allowing for direct links into xDD (formly GeoDeepDive).
+ \ +
Are you affiliated with a state or national geologic survey? Get in touch with us - we'd love to collaborate and help publicize your maps!
+ \ +
Get started by exploring the map or taking a look at which maps are currently a part of Macrostrat.
+

+
+ +[//]: # "Maps" +
+ +

+ The spatial footprint of rocks on the Earth's surface +

+
+ +[//]: # "Columns" +
+ +

+ Stratigraphic and geological columns showing the organization of rocks in time +

+
+ +[//]: # "Geologic Lexicon" +
+ +

+ Geologic units and data dictionaries +

+
+ +[//]: # "Projects" +
+ +

+ Projects for specific regions or geological problems +

+
+ + +[//]: # "Donate" + + +[//]: # "Footer" +
+ +
\ No newline at end of file diff --git a/src/pages/dev/test-site/main-page/main.styl b/src/pages/dev/test-site/main-page/main.styl new file mode 100644 index 00000000..1620dea6 --- /dev/null +++ b/src/pages/dev/test-site/main-page/main.styl @@ -0,0 +1,456 @@ +html, body + color: #E0E1E6 + background-color: white + margin: 0 + +#total + background-color: white + +a + color: white + +a:hover + text-decoration: none + +.nohighlight + color: white + +.start + width: 100% + display: flex; + justify-content: center; + align-items: center; + height: 95vh + color: white + + li + float: left + list-style-type: none + width: 33% + + ul + display: inline-block + width: 100% + +.text + position:absolute + z-index: 100 + padding: 0 20px + display: grid + margin: auto + +.cover-image + position:absolute + z-index:5 + left:0px + top:0px + height: 100vh + width: 100% + object-fit: cover + +.sea-image + position:absolute + z-index: 6 + height: 100vh + width: 30% + object-fit: cover + +.parent + display: inline-flex + width: 100% + +.big + font-size: 72px + font-family: "Maven Pro", sans-serif; + text-align: center + +.big-text + font-family: Helvetica, sans-serif + font-weight: 900 + font-size: 20px + text-shadow: black 1px 0 10px + +.top-stat, .top-stat-label + display: grid + margin: auto + color: white + +.top-stat + font-size: 35px + font-family: Helvetica, sans-serif + +.stats + display: grid + grid-template-columns: 25% 25% 25% 25% + margin-bottom: 3% + text-shadow: black 1px 0 10px; + + .stat + display: grid + margin: auto + + a + color: white + +.rockd + background-color: green + +.btn + background-color: #363434; + border: none; + color: white; + padding: 13px 10px; + margin: 5% 15% + text-align: center; + text-decoration: none; + border-radius: 12px; + +.btn:hover + background-color: #545151 + +.buttons1 + font-size: 20px + padding: 0px + display: grid + grid-template-columns: 33.33% 33.33% 33.33% + + a + color: white + text-decoration: none + + a:hover + color: white + +.rockd-png + width: 22px + padding-right: 4px + float: left + padding-top: 5px + +.country_container + height: 100vh + +.country + height: 100% + display: flex; + justify-content: center; + align-items: center; + text-align: center; + color: white + +.img + width: 50% + object-fit: cover + +.country-image + height: 50vh + width: 50% + +.rock-border + width: 100% + margin: 0 + padding: 0 + +.geologic-maps + height: 100vh + background-color: white + color: black + margin: 0 + +.map-info + padding: 0 10vh + margin: 0 + +.title + margin: 0 + font-size: 72px + font-family: "Maven Pro", sans-serif; + padding: 20vh 0 + text-align: center + +.header + margin: 0 20% + text-shadow: black 1px 0 10px + +#main-title + display: inline-block + +.version + display: inline-block + margin-left: 20px + font-size: 40px + +#about + height: 100vh + background-color: white + color: black + margin: 0 + +#apps + height: 100vh + color: black + background-color: white + +#api + height: 100vh + +.donate-container + height: 80vh + +.donate-title + a + color:white + a:hover + text-decoration: none + + color: white + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); + font-family: "Maven Pro", sans-serif; + padding: 0 + +.donate-left + float: left + width: 50% + height: 80vh + display: flex; + justify-content: center; + align-items: center; + +.donate-right + float: right + width: 50% + height: 80vh + display: flex; + justify-content: center; + align-items: center; + +.donate-info + background: rgba(255,255,255,0.6); + padding: 25px + color: black + width: 80% + font-size: 18px + + a + color: black + font-weight: bold + text-decoration: none + +.donate-img + position:absolute + z-index:5 + height: 80vh + width: 100% + object-fit: cover + +.app-header + margin: 0 + padding: 0 + +.app-box + height: 15vh + display: flex + width: 60% + margin: 0 20% + position: relative + color: black + +.app-background-text + margin: auto + padding-right: 20px + margin-right: 0 + text-align: right + max-width: 55% + font-size: 72px + +.blurb + font-size: 15px + +.app-img + height: 80% + margin: auto + margin-left: 6% + + +#api + position: relative + height: 30vh + background-color: white + +#api-circle-text + display: table-cell; + vertical-align: middle; + color: #6bbe98; + font-size: 72px + font-weight: 500 + +.api-circle + float: left + margin-left: 15% + margin-right: 50px + background-color: #fff; + height: 200px; + width: 200px; + border-radius: 50%; + text-align: center; + display: table; + border: 11px solid #6bbe98; + color: #4BABBf; + +#api-text + padding-top: 50px + margin-right: 15% + color: #4bab7f; + font-size: 20px; + line-height: 28px; + font-weight: 400; + +#publications + margin: 100px 10% + +#pub-title + text-align: left + color: #E0E1E6 + position: relative + padding-bottom: 0 + margin-bottom: 0 + +.pub-list + padding-top: 10px + margin-left: 20px + counter-reset: pub-counter 70 + color: black + + li + counter-increment: pub-counter -1 + + li::marker + font-size: 35px; + color: #babdc8; + content: counter(pub-counter) " " + counter-increment: pub-counter + +#about + margin: 0 10% + +.about-row + margin: 4% + font-size: 18px; + line-height: 28px; + font-weight: 200; + +.about-body-subtitle + float: left + font-size: 22px; + font-weight: 200; + +.about-body + margin-left: 30% + +#about-title + text-align: left + color: #E0E1E6 + position: relative + padding-bottom: 0 + +.line + width: 100%; + border-bottom: 1px solid #E0E1E6; + position: absolute; + +.geology-img + position:absolute + z-index: 0 + height: 100vh + width: 100% + object-fit: cover + +.map-info + position:absolute + z-index: 1 + color: white + font-size: 20px + margin: 40vh 10% + padding: 20px + background-color: rgba(0,0,0,.4) + +.map-title + position:absolute + z-index: 1 + color: white + width: 50% + margin: 0 25% + +.pub-line + width: 80%; + border-bottom: 1px solid #E0E1E6; + position: absolute; + +.text-donate + position:absolute + z-index: 100 + padding: 0 20px + + +.location + display: grid; + grid-template-columns: 25% 25% 25% 25%; + grid-template-rows: 40vh; + +.t1 + margin-left: 10% + +.t2 + margin-right: 10% + +.location-img + width: 100% + height: 40vh + display: flex; + margin: auto + object-fit: cover + +.location-text + color: black + display: grid; + grid-template-rows: 50% 50%; + + h1 + font-size: 45px + font-weight: 600 + display: grid + margin: auto + margin-top: 33% + +.caption + font-size: 15px + font-weight: 600 + display: grid + margin: auto + +.temp-class + display: grid + grid-template-columns: 40% 60% + grid-template-rows: 30vh + + + .link-title + display: grid + margin: auto + margin-left: 20% + font-size: 35px + + a + color: black + + p + display: grid + margin: auto + margin-left: 10% + margin-right: 10% + color: black + font-size: 16px + + + + \ No newline at end of file diff --git a/src/pages/dev/test-site/main.styl b/src/pages/dev/test-site/main.styl new file mode 100644 index 00000000..67dc178e --- /dev/null +++ b/src/pages/dev/test-site/main.styl @@ -0,0 +1,78 @@ +.nav + position: fixed + width: 100% + z-index: 10000 + + ul + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: #015eab; + + li + float: left; + font-size: 15px + + li a + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none + + +.footer + width: 100% + background-color: #015EAB + +.footer-container + display: flex; + flex-direction: row; + justify-content: space-between; + margin: 0 auto; + width: 60% + +.col-sm-4 + color: white + width: 33.333% + color: #E0E1E6; + font-weight: 200; + text-align: center; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; + font-weight: bold + padding-bottom: 18px + +.nav-logo + padding-top: 5px + +.funding-logo + padding: 10px + +.footer-nav + list-style-type: none + padding-right: 40px + + li + padding: 5px 0 + + a + color: white + + a:hover + text-decoration: none + +.git_logo + vertical-align: sub + +.f1-text + a + color: white + +#who-made-it + padding-top: 25px + +.logo_white + padding-top: 20px \ No newline at end of file diff --git a/src/pages/dev/test-site/people/+Page.mdx b/src/pages/dev/test-site/people/+Page.mdx new file mode 100644 index 00000000..d7625a5f --- /dev/null +++ b/src/pages/dev/test-site/people/+Page.mdx @@ -0,0 +1,161 @@ +import { PageHeader } from "~/components"; +import { PageBreadcrumbs } from "~/renderer"; +import { Image, Navbar, Footer } from "../index"; +import "./main.styl"; +import "../main.styl"; +import { MacrostratIcon } from "~/components"; + +
+ +[//]: # "Nav Bar" + +[//]: # "People" +

People

+

major contributors to the project

+
+ +
+
+
+ +
+ Shanan Peters\ + Professor, Database Developer \ + peters -at geology.wisc.edu +
+
+ +
+ +
+ Daven Quinn \ + Research Scientist, Developer \ + daven.quinn -at wisc.edu +
+
+ +
+ +
+ Evgeny Mazko \ + Graduate Student \ + mazko -at wisc.edu +
+
+ +
+ +
+ Michael McClennen \ + Senior Programmer Analyst \ + mmcclenn -at- geology.wisc.edu +
+
+ +
+ +
+ Casey Idzikowski \ + Research Specialist, Developer (former) +
+
+ +
+ +
+ Daniel Segessenmen \ + Graduate Student (former) +
+
+ +
+ +
+ Shan Ye \ + Graduate Student (former) +
+
+ +
+ +
+ Ben Linzmeier \ + Postdoctoral Scholar (former) +
+
+
+ +
+
+ +
+ Afiqah Rafi \ + Undergrad Student (former) +
+
+ +
+ +
+ Sharon McMullen \ + Researcher (former) +
+
+ +
+ +
+ Andrew Zaffos \ + Data Mobilization and Research Scientist \ + azaffos -at- email.arizona.edu +
+
+ +
+ +
+ Jon Husson \ + Postdoctoral Researcher (former) \ + jhusson - at - uvic.ca +
+
+ +
+ +
+ Erika Ito \ + Research Intern (former) +
+
+ +
+ +
+ Noel Heim \ + Researcher (former) +
+
+ +
+ +
+ John Czaplewski \ + Next-level Developer (former) +
+
+ +
+ +
+ Puneet Kishor \ + Generally Ignored \ + punkish at eidesis.org +
+
+
+
+ +[//]: # "Footer" +
+ +
diff --git a/src/pages/dev/test-site/people/main.styl b/src/pages/dev/test-site/people/main.styl new file mode 100644 index 00000000..b83cc223 --- /dev/null +++ b/src/pages/dev/test-site/people/main.styl @@ -0,0 +1,80 @@ +.htnl, .body, .main + background-color: white + color: black + +a:hover + text-decoration: none + +.big + color: #E0E1E6 + text-align: left + font-size: 75px + font-family: "Maven Pro", sans-serif + margin: 0 + margin-left: 20% + padding-top: 100px + +.line + border-bottom: 1px solid #E0E1E6; + width: 60% + margin: 0 20% + + +.people + color: white + +.left + float: left + width: 30% + margin-left: 20% + +.right + float: left + width: 30% + margin-right: 20% + +.person-info + height: 30vh + width: 90% + position: relative + margin: 5% + +.text + text-align: right + position:absolute + padding: 5px 20px + z-index: 100 + background-color: rgba(0, 0, 0, 0.2) + bottom:0 + right: 0 + + a + color: white + + a:hover + color: white + +.back-img + position:absolute + z-index: 0 + height: 30vh + object-fit: cover + width: 100% + +n + font-size: 25px + font-weight: 200px + +t + font-weight: 400px + +e + font-size: 14px + font-weight: 200px + +.footer + margin-top: 280vh + +.subtitle + margin-left: 20% + color: #E0E1E6; \ No newline at end of file diff --git a/src/pages/dev/test-site/publications/+Page.mdx b/src/pages/dev/test-site/publications/+Page.mdx new file mode 100644 index 00000000..f5bcab55 --- /dev/null +++ b/src/pages/dev/test-site/publications/+Page.mdx @@ -0,0 +1,100 @@ +import { PageHeader } from "~/components"; +import { PageBreadcrumbs } from "~/renderer"; +import { Image, Navbar, Footer } from "../index"; +import "./main.styl"; +import "../main.styl"; +import { MacrostratIcon } from "~/components"; + +
+ +[//]: # "Nav Bar" + + +[//]: # "Publications" +
+

Publications

+

literature utilizing Macrostrat

+
+ + [//]: # "NB: adding pubs to list requires updating css .pub-list counter" +
    +
  1. Gazdewich, S., T. Hauck, J. Husson. 2024. Authigenic carbonate burial within the Late Devonian western Canada sedimentary bsain and its impact on the global carbon cycle. Geochemistry, Geophysics, Geosystems 10.1029/2023GC011376. [link]
  2. +
  3. Segessenman, D.C. and S.E. Peters. 2024. Transgression-regression cycles drive correlations in Ediacaran-Cambrian rock and fossil records. Paleobiology 10.1017/pab.2023.31. [link]
  4. +
  5. Quinn, D.P., C.R. Idzikowski, S.E. Peters. 2023. Building a multi-scale, collaborative, and time-integrated digital crust: The next stage of the Macrostrat data system. Geoscience Data Journal 10.1002/gdj3.189. [link]
  6. +
  7. Tasistro-Hart, A.R. and F.A. Macdonald. 2023. Phanerozoic flooding of North America and the Great Unconformity. Proceedings of the National Academy of Sciences 120(37):e2309084120. [link]
  8. +
  9. Husson, J.M. and L.A. Coogan. 2023. River chemistry reveals a large decrease in dolomite abundance across the Phanerozoic. Geochemical Perspective Letters 26:1-6. [link]
  10. +
  11. Walton, C.R., J. Hao, F. Huang, F.E. Jenner, H. Williams, A.L. Zerkle, A. Lipp, R.M. Hazen, S.E. Peters, O. Shorttle. 2023. Evolution of the crustal phosphorus reservoir. Science Advances 9(18):eade6923. [link]
  12. +
  13. Balseiro, D. and M.G. Powell. 2023. Relative oversampling of carbonate rocks in the North American marine fossil record. Paleobiology [link]
  14. +
  15. Ye, S., S.E. Peters. 2023. Bedrock geological map predictions for Phanerozoic fossil occurrences. Paleobiology 49(3):394-413. [link]
  16. +
  17. Wang, J., Tarhan, L.G., Jacobson, A.D. et al. 2023. The evolution of the marine carbonate factory. Nature https://doi.org/10.1038/s41586-022-05654-5 [link]
  18. +
  19. Capel, E., C. Monnet, C.J. Cleal, J. Xue, T. Servais, B. Cascales-Miñana. 2023. The effect of geological biases on our perception of early land plant radiation. Palaeontology 66:e12644 [link]
  20. +
  21. Sessa, J.A., A.J. Fraass, LJ. LeVay, K.M. Jamson, S.E. Peters. 2023. The Extending Ocean Drilling Pursuits (eODP) Project: Synthesizing Scientific Ocean Drilling Data. Geochemistry, Geophysics, Geosystems [link]
  22. +
  23. Segessenman, D.C. and S.E. Peters. 2023. Macrostratigraphy of the Ediacaran system in North America. In "Laurentia: Turning Points in the Evolution of a Continent." S.J. Whitmeyer, M.L. Williams, D.A. Kellett, B. Tikoff, eds. GSA Memoir. [link]
  24. +
  25. Boulila, S., S.E. Peters, R.D. Müller, B.U. Haq, N.Hara. 2023. Earth’s interior dynamics drive marine fossil diversity cycles of tens of millions of years. Proceedings of the National Academy of Sciences e2221149120 [link]
  26. +
  27. Peters, S.E., D. Quinn, J.M. Husson, R.R. Gaines. 2022. Macrostratigraphy: insights into cyclic and secular evolution of the Earth-life system. Ann. Rev. Earth & Planet. Sci. 50:419-449 [link]
  28. +
  29. Emmings, J.F., S.W. Poulton, J. Walsh, K.A. Leeming, I. Ross, S.E. Peters. 2022. Pyrite mega-analysis reveals modes of anoxia through geologic time. Science Advances 8(11). [link]
  30. +
  31. Chen, G., Q. Cheng, S.E. Peters, C.J. Spencer, M. Zhao. 2022. Feedback between surface and deep processes: insight from time series analysis of sedimentary record. Earth and Planet. Sci. Letters. [link]
  32. +
  33. Peters, S.E. et al. 2021. Igneous rock area and age in continental crust. Geology. doi:10.1130/G49037.1. [link]
  34. +
  35. Loughney, K.M., C. Badgley, A. Bahadori, W.E. Hold, and E.T. Rasbury. 2021. Tectonic influence on Cenozoic mammal richness and sedimentation history of the Basin and Range, western North America. Science Advances 7(45):p.eabh4470. doi:10.1126/sciadv.abh4470
  36. +
  37. Key, M.M. Jr., P.N.W. Jackson, C.M. Reid. 2021. Trepostome bryozoans buck the trend and ignore calcite-aragonite seas. Palaeobiodiversity and Palaeoenvironments. doi:10.1007/s12549-021-00507-x. [link]
  38. +
  39. Lipp, A.G. et al. 2021. The composition and weathering of the continents over geologic time. Geochemical Perspectives Letters. doi:10.7185/geochemlet.2109. [link]
  40. +
  41. Barnes, B.D., J.M. Husson, S.E. Peters. 2020. Authigenic carbonate burial in the Late Devonian–Early Mississippian Bakken Formation (Williston Basin, USA). Sedimentology. doi:10.1111/sed.12695. [link]
  42. +
  43. Close, R.A. et al. 2020. The spatial structure of Phanerozoic marine animal diversity. Science doi:10.1126/science.aay8309. [link]
  44. +
  45. Balseiro, D. and Powell, M.G. 2019. Carbonate collapse and the Late Paleozoic Ice Age marine biodiversity crisis. Geology doi:10.1130/G46858.1. [link]
  46. +
  47. Keller, C.B., J.M. Husson, R.N. Mitchell, W.F. Bottke, T.M. Gernon, P. Boehnke, E.A. Bell, N.L. Swanson-Hysell, S.E. Peters. 2019. Neoproterozoic glacial origin of the Great Unconformity. Proc. Nat. Acad. of Sci. USA. 116(4):1136-1145. doi:10.1073/pnas.1804350116 [link]
  48. +
  49. Keating-Bitonti, C.R., and S.E. Peters. 2019. Influence of increasing carbonate saturation in Atlantic bottom water during the late Miocene. Palaeogeography, Palaeoclimatology, Palaeoecology 518:134-142. doi:10.1016/j.palaeo.2019.01.006[link]
  50. +
  51. Cohen, P.A., R. Lockwood, S.E. Peters. 2018. Integrating Macrostrat and Rockd into undergraduate Earth Science Teaching. Elements of Paleontology. doi:10.1017/9781108681445 [link]
  52. +
  53. Isson, T.T., and N.J. Planavsky. 2018. Reverse weathering as a long-term stabilizer of marine pH and planetary climate. Nature 560:571-475. doi:10.1038/s41586-018-0408-4 [link]
  54. +
  55. Husson, J.M. and S.E. Peters. 2018. Nature of the sedimentary rock record and its implications for Earth system evolution. Emerging Topics in Life Sciences. doi:10.1042/ETLS20170152 [link]
  56. +
  57. Peters, S.E., J.M. Husson. 2018. We need a global comprehensive stratigraphic database: here’s a start. The Sedimentary Record 16(1). doi:10.2110/sedred.2018.1 [link]
  58. +
  59. Peters, S.E., J.M. Husson, J. Czaplewski. 2018. Macrostrat: a platform for geological data integration and deep-time Earth crust research. Geochemistry, Geophysics, Geosystems. [link] \ + Preprint available on EarthArXiv 27,Jan18. doi:10.17605/OSF.IO/YNAXW [link]
  60. +
  61. Schachat, S.R., C.C. Labandeira, M.R. Saltzman, B.D. Cramer, J.L. Payne, C.K. Boyce. 2018. Phanerozoic pO2 and the early evolution of terrestrial animals. Proc. Roy. Soc. B.[link]
  62. +
  63. Zaffos, A., S. Finnegan, S.E. Peters. 2017. Plate tectonic regulation of global marine animal diversity. Proc. Nat. Acad. of Sci. USA. [link]
  64. +
  65. Peters, S.E., J.M. Husson. J. Wilcots. 2017. Rise and fall of stromatolites in shallow marine environments. Geology. [link]
  66. +
  67. Peters, S.E., J.M. Husson. 2017. Sediment cycling on continental and oceanic crust. Geology 45:323-326. [link]
  68. +
  69. Husson, J.M., S.E. Peters. 2017. Atmospheric oxygenation driven by unsteady growth of the continental sedimentary reservoir. Earth and Planetary Science Letters. 460:68-75. [link]
  70. +
  71. Schott, R. 2017. Rockd: Geology at your fingertips in a mobile world. Bulletin of the Eastern Section of the National Association of Geoscience Teachers 67(2):1-4. [link]
  72. +
  73. Chan, M.A., S.E. Peters, B. Tikoff. 2016. The future of field geology, open data sharing, and cybertechnology in Earth science. The Sedimentary Record 14:4-10. [link]
  74. +
  75. Nelsen, M.P., B.A. DiMichele, S.E. Peters, C.K. Boyce. 2016. Delayed fungal evolution did not cause the Paleozoic peak in coal production. Proc. Nat. Acad. of Sci. USA. [link]
  76. +
  77. Heavens, N.G. 2015. Injecting climate modeling into deep time studies: ideas for nearly every project. The Sedimentary Record 13:(4)4-10. [link]
  78. +
  79. Carroll, A.R. 2015. Geofuels: energy and the Earth. Cambridge University Press. [link]
  80. +
  81. Thomson, T.J. and M.L. Droser. 2015. Swimming reptiles make their mark in the Early Triassic: delayed ecologic recovery increased the preservation potential of vertebrate swim tracks. Geology 43:215-218. [link]
  82. +
  83. Fraass, A.J., D.C. Kelly, S.E. Peters. 2015. Macroevolutionary history of the planktic foraminifera. Annual Review of Earth and Planetary Sciences 43:5.1-5.28. [link]
  84. +
  85. Fan, Y., S. Richard, R.S. Bristol, S.E. Peters, et al.. 2015. DigitalCrust: A 4D data system of material properties for transforming research on crustal fluid flow. Geofluids 15:372-379. [link]
  86. +
  87. Peters, S.E., D.C. Kelly, and A. Fraass. 2013. Oceanographic controls on the diversity and extinction of planktonic foraminifera. Nature. 493:398-401.[link].
  88. +
  89. Benson, R.B.J., P.D. Mannion, R.J. Butler, P. Upchurch, A. Goswami, and S.E. Evans. 2012. Cretaceous tetrapod fossil record sampling and faunal turnover: implications for biogeography and the rise of modern clades. Palaeogeography, Palaeoclimatology, Palaeoecology. [link].
  90. +
  91. Rook, D.L., N.A. Heim, and J. Marcot. 2012.Contrasting patterns and connections of rock and biotic diversity in the marine and non-marine fossil records of North America. Palaeogeography, Palaeoclimatology, Palaeoecology. 372:123-129. [link]
  92. +
  93. Halevy, I, S.E. Peters, and W.W. Fischer. 2012. Sulfate burial constraints on the Phanerozoic sulfur cycle. Science 337:331-334. doi:10.1126/science.1220224.[link].
  94. +
  95. Peters, S.E. and R.R. Gaines. 2012. Formation of the ‘Great Unconformity’ as a trigger for the Cambrian explosion. Nature 484:363-366. doi:10.1038/nature10969. [link].
  96. +
  97. Finnegan, S., N.A. Heim, S.E. Peters and W.W. Fischer. 2012. Climate change and the selective signature of the late Ordovician mass extinction. PNAS doi:10.1073/pnas.1117039109. [link].
  98. +
  99. Hannisdal, B. and S.E. Peters. 2011. Phanerozoic Earth system evolution and marine biodiversity. Science 334:1121-1124. [link].
  100. +
  101. Butler, R.J. et al. 2011. Sea level, dinosaur diversity and sampling biases: investigating the ‘common cause’ hypothesis in the terrestrial realm. Proc. Roy. Soc. London B 278:1165-1170. [link].
  102. +
  103. Melott, A.L. and R.K. Bambach 2011. A ubquitous ~62-Myr periodic fluctuation superimposed on general trends in fossil biodiversity II. Evolutionary dynamics associated with period fluctuation in marine diversity. Paleobiology 37:369-382. [link].
  104. +
  105. Heim, N.A. and S.E. Peters. 2011. Regional environmental breadth predicts geographic range and longevity in fossil marine genera. PLoS One 6:(5) e18946; doi:10.1371/journal.pone.0018946 [PDF].
  106. +
  107. Peters, S.E. and N.A. Heim. 2011. Macrostratigraphy and macroevolution in marine environments: testing the common-cause hypothesis. In, Smith, A.B., and A. McGowan, eds. Comparing the rock and fossil records: implications for biodiversity. + Special Publication of the Geological Society of London 358:95-104. doi: 10.1144/SP358.7. [link]
  108. +
  109. Peters, S.E. and N.A. Heim. 2011. The stratigraphic distribution of marine fossils in North America. Geology 39:259-262; doi: 10.1130/G31442.1. [PDF]
  110. +
  111. Finnegan, S., S.E. Peters, and W.W. Fischer. 2011. Late Ordovician-Early Silurian selective extinction patterns in Laurentia and their relationship to climate change. In J.C. Gutiérrez-Marco, I. Rábano, and D. Garcia-Bellido, eds. Ordovician of the World. Cuadernos del Museo Geominera 14: 155-159.
  112. +
  113. Meyers, S.R. and S.E. Peters. 2011. A 56 million year rhythm in North American sedimentation during the Phanerozoic. EPSL doi:10.1016/j.epsl.2010.12.044. [PDF]
  114. +
  115. Heim, N.A. and S.E. Peters. 2011. Covariation in macrostratigraphic and macroevolutionary patterns in the marine record of North America. GSA Bulletin 123:620-630. + [PDF]
  116. +
  117. Peters, S.E. and N.A. Heim. 2010. The geological completeness of paleontological sampling in North America. Paleobiology 36:61-79. [PDF].
  118. +
  119. Marx, F.G. 2009. Marine mammals through time: when less is more in studying palaeodiversity. Proceedings of the Royal Society of London B 138:183-196. [link]
  120. +
  121. McGowan, A.J., and A. Smith. 2008. Are global Phanerozoic marine diversity curves truly global? A study of the relationship between regional rock records and global Phanerozoic marine diversity. Paleobiology 34:80-103. [link]
  122. +
  123. Mayhew, P.J., G.B. Jenkins, and T.G. Benton. 2008. Long-term association between global temperature and biodiversity, origination and extinction in the fossil record. Proceedings of the Royal Society of London B 275:47-53. [link]
  124. +
  125. Peters, S.E. 2008. Environmental determinants of extinction selectivity in the fossil record. Nature 454:626-629. + [PDF] [supplement]
  126. +
  127. Peters, S.E. 2008. Macrostratigraphy and its promise for paleobiology. Pp. 205-232 In P.H. Kelley and R.K. Bambach, eds. From evolution to geobiology: research questions driving paleontology at the start of a new century. The Paleontological Society Papers, Vol. 14. 9.[PDF]
  128. +
  129. Peters, S.E. and W.I. Ausich. 2008. A sampling-standardized macroevolutionary history for Ordovician-Early Silurian crinoids. Paleobiology 34:104-116. [PDF]
  130. +
  131. Smith, A.B. 2007. Marine diversity through the Phanerozoic: problems and prospects. Journal of the Geological Society, London 164:731-745.[link]
  132. +
  133. Peters, S.E. 2007. The problem with the Paleozoic. Paleobiology 33:165-181.[PDF]
  134. +
  135. Peters, S.E. 2006. Macrostratigraphy of North America. Journal of Geology 114:391-412.[PDF]
  136. +
  137. Peters, S.E. 2005. Geologic constraints on the macroevolutionary history of marine animals. Proceedings of the National Academy of Sciences U.S.A. 102:12326-12331.[PDF]
  138. +
+
+ +[//]: # "Footer" +
+ +
\ No newline at end of file diff --git a/src/pages/dev/test-site/publications/main.styl b/src/pages/dev/test-site/publications/main.styl new file mode 100644 index 00000000..7f2d8942 --- /dev/null +++ b/src/pages/dev/test-site/publications/main.styl @@ -0,0 +1,52 @@ +.htnl, .body, .main + background-color: white + color: black + +#container + background-color: white + +a:hover + text-decoration: none + +.big + font-size: 72px + font-family: "Maven Pro", sans-serif; + text-align: center + +p + margin-bottom: 10px; + margin-top: 0; + color: #E0E1E6 + +.pub-line + width: 60%; + border-bottom: 1px solid #E0E1E6; + position: absolute; + +#publications + padding: 50px 20% + +#pub-title + text-align: left + color: #E0E1E6 + position: relative + padding-bottom: 0 + margin-bottom: 0 + +.pub-list + padding-top: 10px + margin-left: 20px + counter-reset: pub-counter 70 + color: black + + li + counter-increment: pub-counter -1 + + li::marker + font-size: 35px; + color: #babdc8; + content: counter(pub-counter) " " + counter-increment: pub-counter + +.blurb + font-size: 15px \ No newline at end of file diff --git a/src/pages/map/+config.ts b/src/pages/map/+config.ts index 0bc22b69..1e6dc4e7 100644 --- a/src/pages/map/+config.ts +++ b/src/pages/map/+config.ts @@ -12,4 +12,5 @@ export default { }, }, }, + title: "Map", }; diff --git a/src/pages/map/map-interface/app-state/handlers/fetch.ts b/src/pages/map/map-interface/app-state/handlers/fetch.ts index c97d4f2a..14e7288a 100644 --- a/src/pages/map/map-interface/app-state/handlers/fetch.ts +++ b/src/pages/map/map-interface/app-state/handlers/fetch.ts @@ -1,8 +1,8 @@ import { SETTINGS, apiV2Prefix } from "@macrostrat-web/settings"; import axios from "axios"; import { joinURL } from "~/pages/map/map-interface/utils"; -import { ColumnGeoJSONRecord } from "../reducers"; -import { UPDATE_FILTERED_COLUMNS } from "../reducers/filtered-columns"; +import { ColumnGeoJSONRecord } from "./columns"; +import { UPDATE_COLUMN_FILTERS } from "../reducers/core/types"; import { XDDSnippet } from "~/types"; export const base = apiV2Prefix; @@ -54,7 +54,7 @@ function buildColumnQueryParams(filters) { export async function fetchFilteredColumns( providedFilters -): Promise { +): Promise { let queryString = buildColumnQueryParams(providedFilters); let url = `${base}/columns`; if (Object.keys(queryString).length === 0) { diff --git a/src/pages/map/map-interface/app-state/handlers/filters.ts b/src/pages/map/map-interface/app-state/handlers/filters.ts index 6b659fb0..d6b76d26 100644 --- a/src/pages/map/map-interface/app-state/handlers/filters.ts +++ b/src/pages/map/map-interface/app-state/handlers/filters.ts @@ -22,7 +22,7 @@ export async function runFilter(filter: Filter): Promise { return { category: "lithology", id: filter.name ?? filter.id, - name: filter.name ?? filter.id, + name: filter.name ?? filter.id.toString(), type: filter.type, legend_ids: [], }; @@ -169,11 +169,13 @@ export const fetchIntervalFilter = async ( type LithologyClassFilter = { type: FilterType.LithologyClasses; name: string; + id: number; }; type LithologyTypeFilter = { type: FilterType.LithologyTypes; name: string; + id: number; }; type LithologyFilter = { @@ -285,7 +287,8 @@ async function fetchAllLithTypes( return { category: "lithology", id, - name: id, + // TODO: revisit name/id differences + name: id.toString(), type, legend_ids, }; diff --git a/src/pages/map/map-interface/app-state/handlers/index.ts b/src/pages/map/map-interface/app-state/handlers/index.ts index 061214e4..95844f9a 100644 --- a/src/pages/map/map-interface/app-state/handlers/index.ts +++ b/src/pages/map/map-interface/app-state/handlers/index.ts @@ -1,7 +1,10 @@ -import { push } from "@lagunovsky/redux-react-router"; -import { mapPagePrefix, routerBasename } from "@macrostrat-web/settings"; import axios from "axios"; -import { AppAction, AppState } from "../reducers"; +import { + AppAction, + AppState, + MenuPage, + setInfoMarkerPosition, +} from "../reducers"; import { base, fetchAllColumns, @@ -13,85 +16,73 @@ import { } from "./fetch"; import { runFilter } from "./filters"; -import { formatCoordForZoomLevel } from "@macrostrat/mapbox-utils"; import { LineString } from "geojson"; -import { matchPath } from "react-router"; import { currentPageForPathName, isDetailPanelRoute } from "../nav-hooks"; -import { MenuPage, setInfoMarkerPosition } from "../reducers"; import { MapLayer } from "../reducers/core"; import { getInitialStateFromHash } from "../reducers/hash-string"; -import { ColumnGeoJSONRecord, findColumnsForLocation } from "./columns"; - -function routeForActivePage(page: MenuPage) { - let newPathname = routerBasename; - if (page != null) { - newPathname += "/" + page; - } - return newPathname; -} +import { + ColumnGeoJSONRecord, + ColumnSummary, + ColumnProperties, + findColumnsForLocation, +} from "./columns"; -async function actionRunner( +export default async function actionRunner( state: AppState, action: AppAction, dispatch = null ): Promise { const coreState = state.core; + switch (action.type) { case "get-initial-map-state": { - const { pathname } = state.router.location; - let s1 = setInfoMarkerPosition(state); - let coreState = s1.core; - - const activePage = currentPageForPathName(pathname); - // Harvest as much information as possible from the hash string - let [coreState1, filters] = getInitialStateFromHash( - coreState, - state.router.location.hash - ); - // If we are on the column route, the column layer must be enabled - const colMatch = matchPath( - mapPagePrefix + "/loc/:lng/:lat/column", - pathname - ); - if (colMatch != null) { - coreState1.mapLayers.add(MapLayer.COLUMNS); - } + // const colMatch = matchPath( + // mapPagePrefix + "/loc/:lng/:lat/column", + // pathname + // ); + // if (colMatch != null) { + // coreState1.mapLayers.add(MapLayer.COLUMNS); + // } // Fill out the remainder with defaults // We always get all columns on initial load, which might be - // a bit unnecessary - let allColumns: ColumnGeoJSONRecord[] | null = await fetchAllColumns(); + // a bit unnecessary and slow. + //let allColumns: ColumnGeoJSONRecord[] | null = await fetchAllColumns(); - dispatch({ - type: "replace-state", - state: { - ...state, - core: { - ...coreState1, - allColumns, - initialLoadComplete: true, + fetchAllColumns().then((res) => { + runAsyncAction( + state, + { + type: "set-all-columns", + columns: res, }, - menu: { activePage }, - }, + dispatch + ); }); + if (state.core.infoMarkerPosition != null) { + runAsyncAction( + state, + { + type: "map-query", + z: state.core.mapPosition.target?.zoom ?? 7, + ...state.core.infoMarkerPosition, + map_id: null, + columns: null, + }, + dispatch + ); + } // Apply all filters in parallel - const newFilters = await Promise.all( - filters.map((f) => { + const filters = await Promise.all( + state.core.filtersInfo.map((f) => { return runFilter(f); }) ); - await dispatch({ type: "set-filters", filters: newFilters }); - - // Then reload the map by faking a layer change event. - // There is probably a better way to do this. - return { - type: "map-layers-changed", - mapLayers: coreState1.mapLayers, - }; + return { type: "initial-load-complete", filters }; } case "map-layers-changed": { const { mapLayers } = action; @@ -102,6 +93,20 @@ async function actionRunner( return null; } } + case "set-all-columns": + if (state.core.infoMarkerPosition != null) { + fetchColumnInfo( + { + lng: state.core.infoMarkerPosition.lng, + lat: state.core.infoMarkerPosition.lat, + columns: [], + }, + action.columns, + state.core.columnInfo, + dispatch + ); + } + return action; case "toggle-menu": { // Push the menu onto the history stack let activePage = state.menu.activePage; @@ -125,56 +130,17 @@ async function actionRunner( dispatch ); } - case "set-menu-page": { - const { pathname, hash } = state.router.location; - if (!isDetailPanelRoute(pathname)) { - const newPathname = routeForActivePage(action.page); - await dispatch(push({ pathname: newPathname, hash })); - } - return { type: "set-menu-page", page: action.page }; - } - case "close-infodrawer": - // If we are showing a cross-section, we need to go there - await dispatch( - push({ - pathname: - state.core.crossSectionLine == null - ? routeForActivePage(state.menu.activePage) - : buildCrossSectionPath(state.core.crossSectionLine), - hash: state.router.location.hash, - }) - ); - return action; case "toggle-cross-section": { - let line: GeoJSON.LineString | null = null; + let line: LineString | null = null; if (state.core.crossSectionLine == null) { line = { type: "LineString", coordinates: [] }; } - const action = { + const action: AppAction = { type: "update-cross-section", line, }; return actionRunner(state, action, dispatch); } - case "update-cross-section": - if (state.core.crossSectionLine != null) { - // Return to the base route - let nextPathname = ""; - const pos = state.core.infoMarkerPosition; - if (pos != null) { - const z = state.core.mapPosition.target?.zoom ?? 7; - nextPathname = buildLocationPath(pos.lng, pos.lat, z); - } else { - nextPathname = routeForActivePage(state.menu.activePage); - } - await dispatch( - push({ - pathname: nextPathname, - hash: state.router.location.hash, - }) - ); - } - return action; case "fetch-search-query": const { term } = action; let CancelToken = axios.CancelToken; @@ -218,38 +184,7 @@ async function actionRunner( return { type: "add-filter", filter: await runFilter(action.filter) }; case "get-filtered-columns": return await fetchFilteredColumns(coreState.filters); - case "set-cross-section-line": { - const { line } = action; - - if (state.core.infoMarkerPosition == null) { - // If we are showing a marker, that route takes precedence - const pathname = buildCrossSectionPath(line); - await dispatch(push({ pathname, hash: location.hash })); - } - - return { type: "did-set-cross-section-line", line }; - } - case "map-query": { - const { lng, lat, z } = action; - // Check if matches column detail route - const { pathname } = state.router.location; - - let newPath = buildLocationPath(lng, lat, Number(z)); - if ( - pathname.startsWith(mapPagePrefix + "/loc") && - pathname.endsWith("/column") - ) { - // If so, we want to append columns to the end of the URL - newPath += "/column"; - } - - return push({ - pathname: newPath, - hash: location.hash, - }); - //return { ...action, type: "run-map-query" }; - } - case "run-map-query": + case "map-query": const { lng, lat, z, map_id } = action; // Get column data from the map action if it is provided. // This saves us from having to filter the columns more inefficiently @@ -266,58 +201,19 @@ async function actionRunner( lat, cancelToken: sourceMapQuery, }); - let mapData = await runMapQuery( - lng, - lat, - z, - map_id, - sourceMapQuery.token - ); - let { columns } = action; - // If no columns are provided, try to find them from the active column dataset - if ( - (columns == null || columns.length == 0) && - state.core.allColumns != null - ) { - columns = findColumnsForLocation(state.core.allColumns, { - lng, - lat, - }).map((c) => c.properties); - } - const firstColumn = columns?.[0]; - const { columnInfo } = state.core; - if (firstColumn != null && columnInfo?.col_id != firstColumn.col_id) { - // Get the column units if we don't have them already - actionRunner( - state, - { type: "get-column-units", column: firstColumn }, - dispatch - ).then(dispatch); - } else if (firstColumn == null && columnInfo != null) { - // Clear the column info if we don't have any columns - dispatch({ type: "clear-column-info", data: null, column: null }); - } - - coreState.infoMarkerPosition = { lng, lat }; - return { - type: "received-map-query", - data: mapData, - }; - case "get-column-units": - let CancelTokenGetColumn = axios.CancelToken; - let sourceGetColumn = CancelTokenGetColumn.source(); - dispatch({ type: "start-column-query", cancelToken: sourceGetColumn }); + // Run a bunch of async queries in ~parallel + runMapQuery(lng, lat, z, map_id, sourceMapQuery.token).then((res) => { + dispatch({ type: "received-map-query", data: res }); + }); - let columnData = await runColumnQuery( - action.column, - sourceGetColumn.token + fetchColumnInfo( + { lng, lat, columns: action.columns }, + state.core.allColumns, + state.core.columnInfo, + dispatch ); - return { - type: "received-column-query", - data: columnData, - column: action.column, - }; + return; case "get-pbdb": let collection_nos = action.collection_nos; dispatch({ type: "start-pdbd-query" }); @@ -330,18 +226,56 @@ async function actionRunner( } } -function buildCrossSectionPath(line: LineString) { - const pts = line.coordinates - .map((p) => `${p[0].toFixed(4)},${p[1].toFixed(4)}`) - .join("/"); - - return mapPagePrefix + "/cross-section/" + pts; +async function runAsyncAction( + state: AppState, + action: AppAction, + dispatch: any +) { + const res = await actionRunner(state, action, dispatch); + if (res != null) dispatch(res); } -function buildLocationPath(lng: number, lat: number, z: number) { - const ln = formatCoordForZoomLevel(lng, Number(z)); - const lt = formatCoordForZoomLevel(lat, Number(z)); - return mapPagePrefix + `/loc/${ln}/${lt}`; +async function getColumnUnits(column: ColumnProperties, dispatch: any) { + let CancelTokenGetColumn = axios.CancelToken; + let sourceGetColumn = CancelTokenGetColumn.source(); + dispatch({ type: "start-column-query", cancelToken: sourceGetColumn }); + + let columnData = await runColumnQuery(column, sourceGetColumn.token); + dispatch({ + type: "received-column-query", + data: columnData, + column: column, + }); } -export default actionRunner; +type ColumnFetchParams = { + lng: number; + lat: number; + columns: ColumnProperties[]; +}; + +function fetchColumnInfo( + params: ColumnFetchParams, + allColumns: ColumnGeoJSONRecord[] | null, + currentColumn: ColumnSummary | null, + dispatch: any +): AppAction | void { + const { lng, lat, columns } = params; + let providedColumns = columns ?? []; + + if (providedColumns.length == 0) { + // We could also just fire off a query using a lat/lon here + providedColumns = findColumnsForLocation(allColumns ?? [], { + lng, + lat, + }).map((c) => c.properties); + } + const nextColumn = providedColumns?.[0]; + if (nextColumn != null && currentColumn?.col_id != nextColumn.col_id) { + // Get the column units if we don't have them already + getColumnUnits(nextColumn, dispatch); + } else if (nextColumn == null && currentColumn != null) { + // Clear the column info if we don't have any columns + dispatch({ type: "clear-column-info" }); + } +} diff --git a/src/pages/map/map-interface/app-state/handlers/pathname.ts b/src/pages/map/map-interface/app-state/handlers/pathname.ts new file mode 100644 index 00000000..192de812 --- /dev/null +++ b/src/pages/map/map-interface/app-state/handlers/pathname.ts @@ -0,0 +1,64 @@ +import { + AppState, + AppAction, + MenuPage, +} from "~/pages/map/map-interface/app-state"; +import { push, UpdateLocationAction } from "@lagunovsky/redux-react-router"; +import { LineString } from "geojson"; +import { mapPagePrefix, routerBasename } from "@macrostrat-web/settings"; +import { formatCoordForZoomLevel } from "@macrostrat/mapbox-utils"; + +export function pathNameAction( + state: AppState +): UpdateLocationAction<"push"> | null { + /** Set the pathname based on the current state. Only one of a location, cross-section line, + * or active page can be selected at a time. + * The following priority is applied: + * 1. If a location is selected, show that location + * 2. If a cross-section line is selected, set the cross-section path + * 3. If an active page is selected, show that page + */ + + const pos = state.core.infoMarkerPosition; + let nextPathname: string = state.router.location.pathname; + if (pos != null) { + const z = state.core.mapPosition.target?.zoom ?? 7; + nextPathname = buildLocationPath(pos.lng, pos.lat, z); + // TODO: we could probably assign column page based on a flag in the state + if (state.router.location.pathname.endsWith("/column")) { + nextPathname += "/column"; + } + } else if (state.core.crossSectionLine != null) { + nextPathname = buildCrossSectionPath(state.core.crossSectionLine); + } else if (state.menu.activePage != null) { + nextPathname = routeForActivePage(state.menu.activePage); + } else { + nextPathname = routerBasename; + } + if (nextPathname == state.router.location.pathname) { + return null; + } + return push({ pathname: nextPathname, hash: state.router.location.hash }); +} + +function buildCrossSectionPath(line: LineString) { + const pts = line.coordinates + .map((p) => `${p[0].toFixed(4)},${p[1].toFixed(4)}`) + .join("/"); + + return mapPagePrefix + "/cross-section/" + pts; +} + +export function buildLocationPath(lng: number, lat: number, z: number) { + const ln = formatCoordForZoomLevel(lng, Number(z)); + const lt = formatCoordForZoomLevel(lat, Number(z)); + return mapPagePrefix + `/loc/${ln}/${lt}`; +} + +function routeForActivePage(page: MenuPage) { + let newPathname = routerBasename; + if (page != null) { + newPathname += "/" + page; + } + return newPathname; +} diff --git a/src/pages/map/map-interface/app-state/hooks.ts b/src/pages/map/map-interface/app-state/hooks.ts index 86656376..639b53ae 100644 --- a/src/pages/map/map-interface/app-state/hooks.ts +++ b/src/pages/map/map-interface/app-state/hooks.ts @@ -13,9 +13,13 @@ function useAppActions(): (action: AppAction) => Promise { const store = useStore(); return async (action) => { const appState = store.getState(); - const newAction = await actionRunner(appState, action, dispatch); - if (newAction == undefined || newAction == null) return; - dispatch(newAction as AppAction); + try { + const newAction = await actionRunner(appState, action, dispatch); + if (newAction == undefined || newAction == null) return; + dispatch(newAction as AppAction); + } catch (err) { + console.error(err); + } }; } diff --git a/src/pages/map/map-interface/app-state/reducers/core/index.ts b/src/pages/map/map-interface/app-state/reducers/core/index.ts index 3789d692..e32ca62e 100644 --- a/src/pages/map/map-interface/app-state/reducers/core/index.ts +++ b/src/pages/map/map-interface/app-state/reducers/core/index.ts @@ -60,6 +60,7 @@ const defaultState: CoreState = { mapUse3D: false, filtersOpen: false, filters: [], + filtersInfo: [], filteredColumns: {}, data: [], showExperimentsPanel: false, @@ -80,6 +81,8 @@ export function coreReducer( action: CoreAction ): CoreState { switch (action.type) { + case "initial-load-complete": + return { ...state, initialLoadComplete: true, filters: action.filters }; case "map-loading": if (state.mapIsLoading) return state; return { ...state, mapIsLoading: true }; @@ -144,12 +147,6 @@ export function coreReducer( ...coreReducer(state, { type: "stop-searching" }), filters: buildFilters(state.filters, [action.filter]), }; - case "set-filters": - /* Set multiple filters at once, usually on app load. */ - return { - ...state, - filters: buildFilters(state.filters, action.filters), - }; case "remove-filter": return { ...state, @@ -253,10 +250,6 @@ export function coreReducer( return { ...state, allColumns: action.columns }; case "received-column-query": - // summarize units - if (state.allColumns == null || state.allColumns.length == 0) { - return state; - } return { ...state, fetchingColumnInfo: false, diff --git a/src/pages/map/map-interface/app-state/reducers/core/types.ts b/src/pages/map/map-interface/app-state/reducers/core/types.ts index 5fa7375c..2b9b2282 100644 --- a/src/pages/map/map-interface/app-state/reducers/core/types.ts +++ b/src/pages/map/map-interface/app-state/reducers/core/types.ts @@ -22,7 +22,7 @@ type ASYNC_ADD_FILTER = { type: "async-add-filter"; filter: any }; type GET_FILTERED_COLUMNS = { type: "get-filtered-columns" }; type FETCH_XDD = { type: "fetch-xdd" }; type MAP_QUERY = { - type: "map-query" | "run-map-query"; + type: "map-query"; z: string | number; map_id: any; columns: ColumnProperties[] | null | undefined; @@ -41,7 +41,7 @@ type CLOSE_INFODRAWER = { type: "close-infodrawer" }; type TOGGLE_FILTERS = { type: "toggle-filters" }; type REMOVE_FILTER = { type: "remove-filter"; filter: any }; -type UPDATE_COLUMN_FILTERS = { +export type UPDATE_COLUMN_FILTERS = { type: "update-column-filters"; columns: ColumnGeoJSONRecord[]; }; @@ -158,6 +158,11 @@ type SetFocusedMapSource = { source_id: number | null; }; +type InitialLoadComplete = { + type: "initial-load-complete"; + filters: FilterData[]; +}; + export type CoreAction = | MAP_LAYERS_CHANGED | CLEAR_FILTERS @@ -198,7 +203,6 @@ export type CoreAction = | MapAction | ToggleHighResolutionTerrain | AddFilter - | SetFilters | SetTimeCursor | SetPlateModel | StopSearching @@ -210,7 +214,8 @@ export type CoreAction = | ToggleCrossSection | SetCrossSectionLine | SetFocusedMapSource - | ClearColumnInfo; + | ClearColumnInfo + | InitialLoadComplete; interface AsyncRequestState { // Events and tokens for xhr @@ -265,6 +270,7 @@ export interface CoreState extends MapState, AsyncRequestState { mapCenter: MapCenterInfo; mapUse3D: boolean; filtersOpen: boolean; + filtersInfo: Filter[]; filters: FilterData[]; filteredColumns: ColumnGeoJSONRecord[] | null; showExperimentsPanel: boolean; diff --git a/src/pages/map/map-interface/app-state/reducers/index.ts b/src/pages/map/map-interface/app-state/reducers/index.ts index f3e6a9f9..a4d79dea 100644 --- a/src/pages/map/map-interface/app-state/reducers/index.ts +++ b/src/pages/map/map-interface/app-state/reducers/index.ts @@ -6,10 +6,15 @@ import { mapPagePrefix } from "@macrostrat-web/settings"; import { createBrowserHistory } from "history"; import { matchPath } from "react-router"; import { performanceReducer } from "../../performance/core"; -import { contextPanelIsInitiallyOpen } from "../nav-hooks"; +import { + contextPanelIsInitiallyOpen, + currentPageForPathName, +} from "../nav-hooks"; import { CoreAction, coreReducer } from "./core"; -import { hashStringReducer } from "./hash-string"; +import { getInitialStateFromHash, hashStringReducer } from "./hash-string"; import { AppAction, AppState, MenuAction, MenuState } from "./types"; +import { pathNameAction } from "../handlers/pathname"; + export const browserHistory = createBrowserHistory(); const routerReducer = createRouterReducer(browserHistory); @@ -30,6 +35,7 @@ const defaultState: AppState = { core: coreReducer(undefined, { type: "init" }), router: routerReducer(undefined, { type: "init" }), menu: menuReducer(undefined, { type: "init" }), + nextRouterAction: null, }; function mainReducer( @@ -40,14 +46,38 @@ function mainReducer( * Then, for actions that don't need to affect multiple sections of * state, we pass thm to individual reducers. */ + switch (action.type) { + case "@@INIT": { + const route = state.router.location; + const { pathname } = route; + const isOpen = contextPanelIsInitiallyOpen(pathname); + const activePage = currentPageForPathName(pathname); + const s1 = setInfoMarkerPosition(state, pathname); + const [coreState, filters] = getInitialStateFromHash( + s1.core, + s1.router.location.hash + ); + + return { + ...s1, + core: { + ...s1.core, + ...coreState, + filtersInfo: filters, + menuOpen: isOpen, + contextPanelOpen: isOpen, + }, + menu: { activePage }, + }; + } case "@@router/ON_LOCATION_CHANGED": { - const { pathname } = action.payload.location; + const newRoute = action.payload.location; + const { pathname } = newRoute; const isOpen = contextPanelIsInitiallyOpen(pathname); - let s1 = setInfoMarkerPosition(state); + const s1 = setInfoMarkerPosition(state, pathname); - const newRoute = action.payload.location; let newAction = action; if (newRoute.hash == "") { newAction = { @@ -56,7 +86,7 @@ function mainReducer( ...action.payload, location: { ...action.payload.location, - hash: state.router.location.hash, + hash: s1.router.location.hash, }, }, }; @@ -65,7 +95,7 @@ function mainReducer( return { ...s1, core: { ...s1.core, menuOpen: isOpen, contextPanelOpen: isOpen }, - router: routerReducer(state.router, newAction), + router: routerReducer(s1.router, newAction), }; } case "set-menu-page": @@ -74,36 +104,63 @@ function mainReducer( core: { ...state.core, inputFocus: false }, menu: menuReducer(state.menu, action), }; - case "replace-state": - return action.state; default: return { router: routerReducer(state.router, action as RouterActions), core: coreReducer(state.core, action as CoreAction), menu: menuReducer(state.menu, action as MenuAction), performance: performanceReducer(state.performance, action), + nextRouterAction: state.nextRouterAction, }; } } -const appReducer = (state: AppState, action: AppAction) => { +export default function appReducer(state: AppState, action: AppAction) { // This might not be the right way to do hash management, but it // centralizes the logic in one place. - return hashStringReducer(mainReducer(state, action), action); -}; + return applyNextPath( + hashStringReducer(mainReducer(state, action), action), + action + ); +} + +const pathChangingActions: AppAction["type"][] = [ + "set-menu-page", + "update-cross-section", + "update-state", + "start-map-query", + "close-infodrawer", +]; + +function applyNextPath(state: AppState, action: AppAction): AppState { + if (!pathChangingActions.includes(action.type)) return state; + + const nextRouterAction = pathNameAction(state); + if (nextRouterAction == null) return state; + return { + ...state, + nextRouterAction, + }; +} -export function setInfoMarkerPosition(state: AppState): AppState { +export function setInfoMarkerPosition( + state: AppState, + pathname: string | null = null +): AppState { // Check if we are viewing a specific location const loc = matchPath( - mapPagePrefix + "/loc/:lng/:lat", - state.router.location.pathname + mapPagePrefix + "/loc/:lng/:lat/*", + pathname ?? state.router.location.pathname ); + + let s1 = state; + if (loc != null) { const { lng, lat } = loc.params; return { - ...state, + ...s1, core: { - ...state.core, + ...s1.core, infoMarkerPosition: { lng: Number(lng), lat: Number(lat) }, infoDrawerOpen: true, }, @@ -113,7 +170,7 @@ export function setInfoMarkerPosition(state: AppState): AppState { // Check if we're viewing a cross-section const crossSection = matchPath( mapPagePrefix + "/cross-section/:loc1/:loc2", - state.router.location.pathname + pathname ?? state.router.location.pathname ); if (crossSection != null) { const { loc1, loc2 } = crossSection.params; @@ -121,9 +178,9 @@ export function setInfoMarkerPosition(state: AppState): AppState { const [lng2, lat2] = loc2.split(",").map(Number); if (lng1 != null && lat1 != null && lng2 != null && lat2 != null) { return { - ...state, + ...s1, core: { - ...state.core, + ...s1.core, crossSectionLine: { type: "LineString", coordinates: [ @@ -140,74 +197,7 @@ export function setInfoMarkerPosition(state: AppState): AppState { return state; } -export default appReducer; export * from "./core"; export * from "./hash-string"; export * from "./map"; export * from "./types"; - -/* -function overallReducer(state: AppState, action: Action): AppState { - let pos: MapPosition; - if (action.type === "got-initial-map-state") { - pos = action.data.mapPosition; - } else if (action.type == "map-moved") { - pos = action.data; - } - - if (pos) { - // You can access both app and globe states here - const params = flyToParams(translateCameraPosition(pos)); - //console.log("Set globe position", destination); - return { - ...state, - core: { - ...state.core, - mapPosition: pos, - }, - globe: { - ...state.globe, - flyToProps: { ...params, duration: 0, once: true }, - }, - }; - } - - if (action.type == "map-loading" && !state.core.mapIsLoading) { - return appReducer(state, { - type: "reset-performance-counter", - name: "map-loading", - }); - } - if (action.type == "map-idle" && state.core.mapIsLoading) { - return appReducer(state, { type: "reset-performance-counter" }); - } - - switch (action.type) { - case "@@router/ON_LOCATION_CHANGED": - const isOpen = action.payload.location.pathname != "/"; - return { - ...state, - core: { ...state.core, menuOpen: isOpen, contextPanelOpen: isOpen }, - }; - case "got-initial-map-state": - case "map-moved": - return { - ...state, - core: { - ...state.core, - mapPosition: action.data, - }, - }; - default: - return state; - } -} - -const appReducer = reduceReducers(overallReducer, reducers); - -export type Action = CoreAction | MapAction | GlobeAction | RouterActions; - -export default appReducer; -export * from "./core"; -export * from "./map"; -*/ diff --git a/src/pages/map/map-interface/app-state/reducers/types.ts b/src/pages/map/map-interface/app-state/reducers/types.ts index e535329c..4e977be1 100644 --- a/src/pages/map/map-interface/app-state/reducers/types.ts +++ b/src/pages/map/map-interface/app-state/reducers/types.ts @@ -24,14 +24,8 @@ export type AppState = { core: CoreState; router: ReduxRouterState; menu: MenuState; + nextRouterAction: RouterActions | null; }; -type OverallActions = { type: "replace-state"; state: AppState }; - -export type AppAction = - | CoreAction - | MapAction - | RouterActions - | MenuAction - | OverallActions; +export type AppAction = CoreAction | MapAction | RouterActions | MenuAction; export * from "./types"; diff --git a/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts b/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts index b63f9945..f7cd676f 100644 --- a/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts +++ b/src/pages/map/map-interface/components/info-drawer/macrostrat-linked.ts @@ -281,6 +281,7 @@ function LithTypes(props) { [ lith_types.map((lithClass, i) => { return h(LithologyTag, { + key: lithClass.name, data: { ...lithClass, }, diff --git a/src/pages/map/map-interface/index.ts b/src/pages/map/map-interface/index.ts index 5c36deec..6a4d510e 100644 --- a/src/pages/map/map-interface/index.ts +++ b/src/pages/map/map-interface/index.ts @@ -1,6 +1,7 @@ import { ReduxRouter } from "@lagunovsky/redux-react-router"; import h from "@macrostrat/hyper"; import { Route, Routes } from "react-router-dom"; +import { useEffect } from "react"; import "~/styles/global.styl"; import "./searchbar.styl"; @@ -9,7 +10,13 @@ import "./ui-components.styl"; import { createRouterMiddleware } from "@lagunovsky/redux-react-router"; import { Provider } from "react-redux"; import { applyMiddleware, compose, createStore } from "redux"; -import reducerStack, { AppAction, AppState, browserHistory } from "./app-state"; +import reducerStack, { + AppAction, + AppState, + browserHistory, + useAppState, + useAppActions, +} from "./app-state"; /** Redux is used only for the main map applicaton. This heavy state-management approach is * essentially a legacy approach, and we are moving away from this in favor of more lightweight @@ -19,6 +26,7 @@ import reducerStack, { AppAction, AppState, browserHistory } from "./app-state"; const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const routerMiddleware = createRouterMiddleware(browserHistory); + // Create the data store let store = createStore( reducerStack, @@ -34,11 +42,23 @@ export default function MapApp({ routerBasename }) { h( ReduxRouter, { basename: routerBasename, store, history: browserHistory }, - [h(Routes, [h(Route, { path: "*", element: h(MapPage) })])] + [h(Routes, [h(Route, { path: "*", element: h(MapPage) })]), h(RouterSync)] ) ); } +function RouterSync() { + /** This is a temporary solution to sync the store with the history object. */ + const nextRouterAction = useAppState((state) => state.nextRouterAction); + const runAction = useAppActions(); + useEffect(() => { + if (nextRouterAction != null) { + runAction(nextRouterAction); + } + }, [nextRouterAction]); + return null; +} + // Extend the window type to include the Redux DevTools types declare global { interface Window { diff --git a/src/pages/map/map-interface/map-page/index.ts b/src/pages/map/map-interface/map-page/index.ts index f7a18c3c..cb11397e 100644 --- a/src/pages/map/map-interface/map-page/index.ts +++ b/src/pages/map/map-interface/map-page/index.ts @@ -3,11 +3,10 @@ import { Suspense, useCallback, useEffect, useRef } from "react"; import { Spinner } from "@blueprintjs/core"; import loadable from "@loadable/component"; import { mapPagePrefix } from "@macrostrat-web/settings"; -import hyper from "@macrostrat/hyper"; import { MapAreaContainer } from "@macrostrat/map-interface"; import classNames from "classnames"; import { useSelector } from "react-redux"; -import { Route, Routes, useParams } from "react-router-dom"; +import { Route, Routes } from "react-router-dom"; import { useTransition } from "transition-hook"; import { useAppActions, @@ -16,16 +15,14 @@ import { useContextPanelOpen, } from "../app-state"; import Searchbar from "../components/navbar"; -import styles from "./main.module.styl"; import MapContainer from "./map-view"; import { MenuPage } from "./menu"; +import h from "./main.module.styl"; const ElevationChart = loadable(() => import("../components/elevation-chart")); const InfoDrawer = loadable(() => import("../components/info-drawer")); const Menu = loadable(() => import("./menu")); -const h = hyper.styled(styles); - function MapView(props) { return h( Suspense, @@ -34,12 +31,13 @@ function MapView(props) { ); } -export const MapPage = ({ +function MapPage({ baseRoute = "/", menuPage = null, }: { + baseRoute?: string; menuPage?: MenuPage; -}) => { +}) { const runAction = useAppActions(); const inputFocus = useAppState((s) => s.core.inputFocus); const infoDrawerOpen = useAppState((s) => s.core.infoDrawerOpen); @@ -92,7 +90,7 @@ export const MapPage = ({ }, [h("div.context-underlay", { onClick: onMouseDown }), h(MapView)] ); -}; +} function MapPageRoutes() { return h(Routes, [ @@ -118,40 +116,13 @@ function InfoDrawerHolder() { h(Routes, [ h(Route, { path: mapPagePrefix + "/loc/:lng/:lat/*", - element: h(InfoDrawerLocationGrabber), + element: h.if(detailPanelTrans.shouldMount)(InfoDrawer, { + className: "detail-panel", + }), }), ]), - h.if(detailPanelTrans.shouldMount)(InfoDrawer, { - className: "detail-panel", - }), + //h(InfoDrawerLocationGrabber), ]); } -function InfoDrawerLocationGrabber() { - // We could probably do this in the reducer... - const { lat, lng } = useParams(); - const z = Math.round( - useAppState((s) => s.core.mapPosition.target?.zoom) ?? 7 - ); - const runAction = useAppActions(); - - // Todo: this is a pretty janky way to do state management - useEffect(() => { - if (lat && lng) { - runAction({ - type: "run-map-query", - lat: Number(lat), - lng: Number(lng), - z, - // Focused column or map unit from active layers. - // This is a bit anachronistic, since we want to be - // able to show columns that aren't necessarily shown on the map - columns: [], - map_id: null, - }); - } - }, [lat, lng]); - return null; -} - export default MapPageRoutes; diff --git a/src/pages/map/map-interface/map-page/map-styles/overlay.ts b/src/pages/map/map-interface/map-page/map-styles/overlay.ts index 41fbdbbc..88e4b96e 100644 --- a/src/pages/map/map-interface/map-page/map-styles/overlay.ts +++ b/src/pages/map/map-interface/map-page/map-styles/overlay.ts @@ -1,3 +1,5 @@ +import { buildCrossSectionLayers } from "~/_utils/map-layers"; + /** Add extra types we use in this style... */ interface SourceExt extends mapboxgl.Source { cluster?: boolean; @@ -89,23 +91,6 @@ export function buildOverlayLayers(): mapboxgl.Layer[] { "--panel-rule-color" ); - const crossSectionPointPaint = { - "circle-radius": { - stops: [ - [0, 3], - [12, 5], - ], - }, - "circle-color": centerColor, - "circle-stroke-width": { - stops: [ - [0, 2], - [12, 4], - ], - }, - "circle-stroke-color": ruleColor, - }; - return [ { id: "column_fill", @@ -167,36 +152,8 @@ export function buildOverlayLayers(): mapboxgl.Layer[] { visibility: "none", }, }, - { - id: "crossSectionLine", - type: "line", - source: "crossSectionLine", - paint: { - "line-width": { - stops: [ - [0, 1], - [12, 3], - ], - }, - "line-color": ruleColor, - "line-opacity": 1, - }, - }, - { - id: "crossSectionEndpoint", - type: "circle", - source: "crossSectionEndpoints", - paint: crossSectionPointPaint, - }, - { - id: "elevationMarker", - type: "circle", - source: "elevationMarker", - paint: { - ...crossSectionPointPaint, - "circle-color": "#4bc0c0", - }, - }, + ...buildCrossSectionLayers(), + // { // "id": "pbdbCollections", // "type": "fill", diff --git a/src/pages/map/map-interface/map-page/map-view/cross-section.ts b/src/pages/map/map-interface/map-page/map-view/cross-section.ts index f82210dc..05df828e 100644 --- a/src/pages/map/map-interface/map-page/map-view/cross-section.ts +++ b/src/pages/map/map-interface/map-page/map-view/cross-section.ts @@ -15,7 +15,7 @@ export function CrossSectionLine() { return null; } -function useCrossSectionLine(crossSectionLine) { +export function useCrossSectionLine(crossSectionLine) { const mapRef = useMapRef(); const previousLine = useRef(null); const { isStyleLoaded } = useMapStatus(); diff --git a/src/pages/map/map-interface/map-page/settings-panel.module.styl b/src/pages/map/map-interface/map-page/settings-panel.module.styl index 8e6e08f2..1b3c756b 100644 --- a/src/pages/map/map-interface/map-page/settings-panel.module.styl +++ b/src/pages/map/map-interface/map-page/settings-panel.module.styl @@ -16,11 +16,22 @@ font-size: 0.9em line-height: 1em +.dark-mode-controls + display: flex + gap: 0.5em + align-items: center + &>:first-child + flex-grow: 1 + .settings .auto-button font-size: 12px font-style: italic + &>* + width: 100% + margin: 0.1em 0 0.2em + :global .bp5-control display: flex @@ -34,8 +45,6 @@ position: relative .bp5-button, .bp5-button-group - width: 100% - margin: 0.1em 0 0.2em justify-content: start align-items: center //box-shadow: 0 0 0px 1px var(--card-shadow-color) @@ -52,6 +61,9 @@ .callout-panel border-radius: 3px overflow: hidden + .callout-header + :global(.bp5-button) + width: 100% &.expanded .callout-header :global(.bp5-button) @@ -78,3 +90,6 @@ gap: 0.5em &>* flex: 1 + + + diff --git a/src/pages/map/map-interface/map-page/settings-panel.ts b/src/pages/map/map-interface/map-page/settings-panel.ts index 1940b692..57b60bc7 100644 --- a/src/pages/map/map-interface/map-page/settings-panel.ts +++ b/src/pages/map/map-interface/map-page/settings-panel.ts @@ -4,6 +4,7 @@ import { AnchorButton, Button, + ButtonGroup, Callout, Collapse, Icon, @@ -12,7 +13,6 @@ import { Switch, Tag, } from "@blueprintjs/core"; -import hyper from "@macrostrat/hyper"; import { applyMapPositionToHash } from "@macrostrat/map-interface"; import { DarkModeButton, @@ -27,9 +27,7 @@ import { useAppState, } from "~/pages/map/map-interface/app-state"; -import styles from "./settings-panel.module.styl"; - -const h = hyper.styled(styles); +import h from "./settings-panel.module.styl"; const ExperimentsPanel = (props) => { const dispatch = useAppActions(); @@ -198,33 +196,30 @@ function ThemeButton() { const update = darkModeUpdater(); const icon = darkMode.isAutoset ? "tick" : "desktop"; - const autoButton = h( - Button, - { - minimal: true, - active: darkMode.isAutoset, - rightIcon: h(Icon, { icon, size: 12 }), - intent: darkMode.isAutoset ? "success" : "primary", - className: "auto-button sub-button", - small: true, - onClick(evt) { - if (darkMode.isAutoset) return; - evt.stopPropagation(); - update(null); - }, - }, - - "auto" - ); - const darkModeText = darkMode.isEnabled ? "Turn on the lights" : "Turn off the lights"; return h("div.dark-mode-controls", [ + h(DarkModeButton, { minimal: true, active: false, allowReset: true }, [ + h("span.text", darkModeText), + ]), h( - DarkModeButton, - { minimal: true, active: false, allowReset: true, rightIcon: autoButton }, - [h("span.text", darkModeText)] + Button, + { + minimal: true, + active: darkMode.isAutoset, + rightIcon: h(Icon, { icon, size: 12 }), + intent: darkMode.isAutoset ? "success" : "primary", + className: "auto-button sub-button", + small: true, + onClick(evt) { + if (darkMode.isAutoset) return; + evt.stopPropagation(); + update(null); + }, + }, + + "auto" ), ]); } diff --git a/src/pages/maps/+onBeforeRender.ts b/src/pages/maps/+onBeforeRender.ts index dee537ad..7d6d3ec1 100644 --- a/src/pages/maps/+onBeforeRender.ts +++ b/src/pages/maps/+onBeforeRender.ts @@ -6,7 +6,7 @@ export async function onBeforeRender(pageContext) { const res = await postgrest .from("sources_metadata") .select("*") - .order("source_id", { ascending: true }) + .order("source_id", { ascending: true }); const pageProps = { sources: res.data }; return { diff --git a/src/pages/maps/@id/+Page.ts b/src/pages/maps/@id/+Page.ts index 8a64dfe9..d969b594 100644 --- a/src/pages/maps/@id/+Page.ts +++ b/src/pages/maps/@id/+Page.ts @@ -349,13 +349,20 @@ function MapLegendPanel(params) { h("div.flex.row", [ h("h3", "Legend"), h("div.spacer"), - h( - DevLink, - // Not sure why we have to fully construct the URL here, vs. constructing a relative route. - // Probably lack of a trailing slash in the main page? - { href: `/maps/${params.source_id}/legend` }, - "Legend table" - ), + h("div.dev-links", [ + h( + DevLink, + // Not sure why we have to fully construct the URL here, vs. constructing a relative route. + // Probably lack of a trailing slash in the main page? + { href: `/maps/${params.source_id}/legend` }, + "Legend table" + ), + h( + DevLink, + { href: `/maps/${params.source_id}/correlation` }, + "Correlation of units" + ), + ]), ]), h(MapLegendData, params), ]) diff --git a/src/pages/maps/@id/correlation/+Page.ts b/src/pages/maps/@id/correlation/+Page.ts new file mode 100644 index 00000000..cf38d17a --- /dev/null +++ b/src/pages/maps/@id/correlation/+Page.ts @@ -0,0 +1,419 @@ +import { + Popover, + Spinner, + SegmentedControl, + FormGroup, + Button, +} from "@blueprintjs/core"; +import { FullscreenPage } from "~/layouts"; +import h from "./main.module.sass"; +import { PageBreadcrumbs } from "~/renderer"; +import { useLegendData, MapInfo } from "../utils"; +import { useElementSize, useInDarkMode } from "@macrostrat/ui-components"; +import { useMemo, useRef } from "react"; +import { Group } from "@visx/group"; +import { scaleBand, scaleLinear, scaleLog } from "@visx/scale"; +import { AxisLeft } from "@visx/axis"; +import { Timescale, TimescaleOrientation } from "@macrostrat/timescale"; +import { ForeignObject } from "@macrostrat/column-components"; +import { useState, useEffect } from "react"; +import { Bar } from "@visx/shape"; +import { CorrelationItem, AgeRange, AgeDisplayMode } from "./types"; +import { + buildCorrelationChartData, + mergeAgeRanges, + getBoundingAgeRange, + getBestAgeRange, +} from "./prepare-data"; +import { LegendItemInformation } from "./legend-item"; +import { UnitDetailsPopover } from "~/components/unit-details"; + +export function Page({ map }) { + const ref = useRef(null); + const size = useElementSize(ref); + const legendData = useLegendData(map); + + const [ageMode, setAgeMode] = useState(AgeDisplayMode.MapLegend); + const [ageScale, setAgeScale] = useState("linear"); + + const correlationChartData = useMemo(() => { + return buildCorrelationChartData(legendData, ageMode); + }, [legendData, ageMode]); + + const [selectedItem, setSelectedLegendID] = + useSelectedLegendID(correlationChartData); + + const settings = h("div.settings", [ + h("h3", "Settings"), + //h(AgeScaleSelector, { scale: ageScale, setScale: setAgeScale }), + h(AgeDisplayModeSelector, { + displayMode: ageMode, + setDisplayMode: setAgeMode, + }), + ]); + + return h(FullscreenPage, [ + h("div.page-inner", [ + h("div.flex.row", [ + h(PageBreadcrumbs), + h("div.spacer"), + h( + Popover, + { + content: settings, + usePortal: true, + rootBoundary: ref.current, + onOpening() { + setSelectedLegendID(null); + }, + }, + h(Button, { icon: "cog", minimal: true }) + ), + ]), + h("div.vis-container", { ref }, [ + h.if(legendData != null)(CorrelationChart, { + map, + ...size, + data: correlationChartData, + selectedItem, + setSelectedLegendID, + ageMode, + ageScale, + }), + ]), + ]), + ]); +} + +const verticalMargin = 60; + +export type BarsProps = { + width: number; + height: number; + map: MapInfo; + data: CorrelationItem[]; + ageMode: AgeDisplayMode; + ageScale: AgeScale; + selectedItem: CorrelationItem | null; + setSelectedLegendID: (a: number) => void; +}; + +type AgeScale = "linear" | "log"; + +function CorrelationChart({ + width, + height, + data, + ageMode = AgeDisplayMode.MapLegend, + ageScale = "linear", + selectedItem, + setSelectedLegendID, +}: BarsProps) { + // bounds + const xMax = width; + const yMax = height - verticalMargin; + + const domain = useMemo( + () => mergeAgeRanges(data.map((d) => getBoundingAgeRange(d, ageMode))), + [data, ageMode] + ); + + const xMin = 100; + + // scales, memoize for performance + const xScale = useMemo( + () => + scaleBand({ + range: [xMin, xMax], + round: false, + domain: data.map((d) => `${d.id}`), + padding: 0.2, + }), + [xMax, data] + ); + const yScale = useMemo(() => { + if (ageScale === "log") { + return scaleLog({ + range: [yMax, 0], + round: true, + domain: domain, + nice: true, + base: 10, + }); + } + + return scaleLinear({ + range: [yMax, 0], + round: false, + domain, + }); + }, [domain, yMax, ageScale]); + + if (data == null) { + return h(Spinner); + } + + if (width < 10) return null; + + return h("div.vis-frame", [ + h( + "svg.vis-area", + { + width, + height, + onClick() { + setSelectedLegendID(null); + }, + }, + [ + h(Group, { top: verticalMargin / 2, key: "main-plot" }, [ + h(AgeAxis, { + scale: yScale, + width: 40, + }), + h(ForeignObject, { width: 60, height, x: 40 }, [ + h(Timescale, { + orientation: TimescaleOrientation.VERTICAL, + length: yMax, + // Bug in timescale component, the age range appears to be changed + // if we pass it in statically. + ageRange: domain, + absoluteAgeScale: true, + levels: [2, 3], + }), + ]), + h( + Group, + data.map((d, i) => { + const ageRange = getBestAgeRange(d, ageMode); + + const yMin = yScale(ageRange[1]); + const yMax = yScale(ageRange[0]); + + const barWidth = xScale.bandwidth(); + const barHeight = yMax - yMin; + const barX = xScale(`${d.id}`); + const barY = yMin; + const main = h(Bar, { + key: d.id, + x: barX, + y: barY, + width: barWidth, + height: barHeight, + fill: d.color, + onClick(event) { + setSelectedLegendID(d.id); + event.stopPropagation(); + }, + }); + if ( + ageMode !== AgeDisplayMode.Both || + d.macrostratAgeRange == null + ) { + return main; + } + + // We need to render the un-corrected age range as well + const yMin1 = yScale(d.ageRange[1]); + const yMax1 = yScale(d.ageRange[0]); + return h(Group, { key: d.id }, [ + h(Bar, { + x: barX, + y: yMin1, + width: barWidth, + height: yMax1 - yMin1, + fill: d.color, + opacity: 0.3, + }), + main, + ]); + }) + ), + h( + ForeignObject, + { + width, + height: height - verticalMargin, + className: "popover-container", + }, + [ + h(SelectedLegendItemPopover, { + item: selectedItem, + ageMode, + xScale, + yScale, + }), + ] + ), + ]), + ] + ), + ]); +} + +function SelectedLegendItemPopover({ + item, + xScale, + ageMode, + yScale, +}: { + item: CorrelationItem | null; + xScale: any; + yScale: any; + ageMode: AgeDisplayMode; +}) { + if (item == null) { + return null; + } + + const range = getBoundingAgeRange(item, ageMode); + + const { details, id } = item; + + const content = h(LegendItemInformation, { legendItem: details }); + + const xv = xScale(`${id}`); + const top = yScale(range[1]); + const bottom = yScale(range[0]); + + return h( + UnitDetailsPopover, + { + style: { top, left: xv, width: xScale.bandwidth(), height: bottom - top }, + }, + content + ); +} + +function AgeAxis({ scale, width }) { + const darkMode = useInDarkMode(); + + const axisColor = darkMode ? "#ccc" : "#222"; + + return h(AxisLeft, { + scale, + left: width, + stroke: axisColor, + tickStroke: axisColor, + labelProps: { + fill: axisColor, + }, + tickLabelProps: () => { + return { + fill: axisColor, + textAnchor: "end", + verticalAnchor: "middle", + dx: "-0.25em", + fontSize: 12, + }; + }, + }); +} + +function useSelectedLegendID( + legendItems: CorrelationItem[] +): [CorrelationItem | null, (a: number) => void] { + /** Hook to manage the selected legend item, including handling of arrow-key navigation */ + + const [selectedLegendID, setSelectedLegendID] = useState(null); + + // Add arrow key navigation and escape key to close popover + const handleKeyDown = (e) => { + if (selectedLegendID == null) { + return; + } + const idx = legendItems.findIndex((d) => d.id === selectedLegendID); + if (idx == null) { + return; + } + if (e.key === "ArrowDown" || e.key === "ArrowRight") { + setSelectedLegendID(legendItems[idx + 1].id); + } else if (e.key === "ArrowUp" || e.key === "ArrowLeft") { + setSelectedLegendID(legendItems[idx - 1].id); + } else if (e.key === "Escape") { + setSelectedLegendID(null); + } + }; + + useEffect(() => { + // Get the focused legend_id from the query string if set + const urlParams = new URLSearchParams(window.location.search); + const legendID = urlParams.get("legend_id"); + if (legendID != null) { + setSelectedLegendID(parseInt(legendID)); + } + }, []); + + // Add event listener + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + if (selectedLegendID == null) { + urlParams.delete("legend_id"); + } else { + urlParams.set("legend_id", `${selectedLegendID}`); + } + let qString = urlParams.toString(); + if (qString.length > 0) { + qString = "?" + qString; + } + + const newUrl = `${window.location.pathname}${qString}`; + window.history.replaceState(null, "", newUrl); + + // Set query string to selected legend item + if (selectedLegendID == null) { + return; + } + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [selectedLegendID]); + + const selectedItem = useMemo( + () => legendItems.find((d) => d.id === selectedLegendID), + [legendItems, selectedLegendID] + ); + + return [selectedItem, setSelectedLegendID]; +} + +function AgeDisplayModeSelector({ + displayMode, + setDisplayMode, +}: { + displayMode: AgeDisplayMode; + setDisplayMode: (a: AgeDisplayMode) => void; +}) { + return h(FormGroup, { label: "Age source" }, [ + h(SegmentedControl, { + options: [ + { label: "Map legend", value: AgeDisplayMode.MapLegend }, + { label: "Macrostrat", value: AgeDisplayMode.Macrostrat }, + { label: "Both", value: AgeDisplayMode.Both }, + ], + small: true, + value: displayMode, + onValueChange: setDisplayMode, + }), + ]); +} + +function AgeScaleSelector({ + scale, + setScale, +}: { + scale: AgeScale; + setScale: (a: AgeScale) => void; +}) { + return h(FormGroup, { label: "Age scale" }, [ + h(SegmentedControl, { + options: [ + { label: "Linear", value: "linear" }, + { label: "Log", value: "log" }, + ], + small: true, + value: scale, + onValueChange: setScale, + }), + ]); +} diff --git a/src/pages/maps/@id/correlation/+config.ts b/src/pages/maps/@id/correlation/+config.ts new file mode 100644 index 00000000..ac56ce59 --- /dev/null +++ b/src/pages/maps/@id/correlation/+config.ts @@ -0,0 +1,10 @@ +export default { + meta: { + Page: { + env: { + client: true, + server: false, + }, + }, + }, +}; diff --git a/src/pages/maps/@id/correlation/+onBeforeRender.ts b/src/pages/maps/@id/correlation/+onBeforeRender.ts new file mode 100644 index 00000000..1d1295f7 --- /dev/null +++ b/src/pages/maps/@id/correlation/+onBeforeRender.ts @@ -0,0 +1,27 @@ +import { postgrestPrefix } from "@macrostrat-web/settings"; +import { PageContextServer } from "vike/types"; +import { PostgrestClient } from "@supabase/postgrest-js"; + +const client = new PostgrestClient(postgrestPrefix); + +export async function onBeforeRender(pageContext: PageContextServer) { + const { id } = pageContext.routeParams; + const res: any = await client + .from("sources") + .select("source_id,slug,name") + .eq("source_id", id); + + const map = res?.data?.[0]; + + return { + pageContext: { + pageProps: { + map, + }, + documentProps: { + // The page's + title: map.name + "– Legend", + }, + }, + }; +} diff --git a/src/pages/maps/@id/correlation/legend-item.ts b/src/pages/maps/@id/correlation/legend-item.ts new file mode 100644 index 00000000..b97c2996 --- /dev/null +++ b/src/pages/maps/@id/correlation/legend-item.ts @@ -0,0 +1,44 @@ +import { LegendItem } from "./types"; +import h from "./main.module.sass"; +import { + DataField, + IntervalField, + LithologyList, + LegendPanelHeader, +} from "~/components/unit-details"; +import { Button } from "@blueprintjs/core"; + +export function LegendItemInformation({ + legendItem, +}: { + legendItem: LegendItem; +}) { + return h([ + h(LegendPanelHeader, { title: legendItem.name, id: legendItem.legend_id }), + h("div.data", [ + h(DataField, { + label: "Stratigraphic names", + value: legendItem.strat_name, + }), + h(DataField, { label: "Age", value: legendItem.age }), + h(DataField, { + label: "Description", + value: legendItem.descrip, + inline: false, + }), + h(DataField, { label: "Comments", value: legendItem.comments }), + h(DataField, { label: "Lithology", value: legendItem.lith }), + h(LithologyList, { lithologies: legendItem.liths }), + h(IntervalField, { + intervals: [legendItem.b_interval, legendItem.t_interval], + }), + h(DataField, { + label: "Best age", + value: `${legendItem.best_age_bottom} - ${legendItem.best_age_top}`, + unit: "Ma", + }), + h(DataField, { label: "Unit IDs", value: legendItem.unit_ids }), + h(DataField, { label: "Concept IDs", value: legendItem.concept_ids }), + ]), + ]); +} diff --git a/src/pages/maps/@id/correlation/main.module.sass b/src/pages/maps/@id/correlation/main.module.sass new file mode 100644 index 00000000..1fe9b0bd --- /dev/null +++ b/src/pages/maps/@id/correlation/main.module.sass @@ -0,0 +1,51 @@ +div.correlation-chart + position: relative + +.bar + position: absolute + +.popover-container + pointer-events: none + position: relative + + + +// Ideally this wouldn't be necessary +.page-inner + position: relative + margin: 2em auto 3em + display: flex + flex-direction: column + height: 100% + + +.vis-container + flex: 1 + overflow: hidden + +.data-field + margin: 0.2em 0 0.4em + &.inline + display: flex + flex-direction: row + align-items: baseline + gap: 1em + justify-content: space-between + .data-container + text-align: right + .label + font-size: 0.9em + font-weight: bold + display: block + color: var(--secondary-color) + .value-container + font-size: 0.9em + display: inline-block + break-inside: avoid + .unit + color: var(--secondary-color) + +.settings + padding: 1em + >h3:first-child + margin-top: 0 diff --git a/src/pages/maps/@id/correlation/prepare-data.ts b/src/pages/maps/@id/correlation/prepare-data.ts new file mode 100644 index 00000000..839c508a --- /dev/null +++ b/src/pages/maps/@id/correlation/prepare-data.ts @@ -0,0 +1,89 @@ +import { LegendItem, CorrelationItem, AgeDisplayMode } from "./types"; +import { IntervalShort } from "~/components/unit-details"; +import { mergeAgeRanges, AgeRange } from "@macrostrat-web/utility-functions"; + +export { mergeAgeRanges }; + +export function buildCorrelationChartData( + legendData: LegendItem[], + ageMode: AgeDisplayMode +): CorrelationItem[] { + /** Build the data for a correlation chart */ + if (legendData == null) { + return []; + } + + let data1 = legendData + .map((d, i) => { + let ageRanges: AgeRange[] = []; + + if (d.b_interval != null) { + ageRanges.push(getAgeRangeForInterval(d.b_interval)); + } + if (d.t_interval != null) { + ageRanges.push(getAgeRangeForInterval(d.t_interval)); + } + + if (ageRanges.length === 0) { + return null; + } + + let macrostratAgeRange: AgeRange | null = null; + if (d.best_age_bottom != null && d.best_age_top != null) { + macrostratAgeRange = [d.best_age_bottom, d.best_age_top]; + } + + return { + details: d, + id: d.legend_id, + ageRange: mergeAgeRanges(ageRanges), + macrostratAgeRange, + frequency: i, + color: d.color, + }; + }) + .filter((d) => d != null) as CorrelationItem[]; + + return data1.sort((a, b) => + intervalComparison(getBestAgeRange(a, ageMode), getBestAgeRange(b, ageMode)) + ); +} + +function midpointAge(range: [number, number]) { + return (range[0] + range[1]) / 2; +} + +function getAgeRangeForInterval(interval: IntervalShort): AgeRange | null { + /** Get the age range for an interval, building up an index as we go */ + return [interval.b_age, interval.t_age]; +} + +function intervalComparison(a: AgeRange, b: AgeRange) { + // If age range fully overlaps with another, put the wider one first + return midpointAge(b) - midpointAge(a); +} + +export function getBoundingAgeRange( + item: CorrelationItem, + ageMode: AgeDisplayMode +) { + const bestAge = getBestAgeRange(item, ageMode); + if (ageMode == AgeDisplayMode.Macrostrat) { + return bestAge; + } + if (bestAge == item.ageRange) { + return item.ageRange; + } else { + return mergeAgeRanges([item.ageRange, bestAge]); + } +} + +export function getBestAgeRange( + item: CorrelationItem, + ageMode: AgeDisplayMode +) { + if (ageMode == AgeDisplayMode.MapLegend || item.macrostratAgeRange == null) { + return item.ageRange; + } + return item.macrostratAgeRange; +} diff --git a/src/pages/maps/@id/correlation/types.ts b/src/pages/maps/@id/correlation/types.ts new file mode 100644 index 00000000..de5af63e --- /dev/null +++ b/src/pages/maps/@id/correlation/types.ts @@ -0,0 +1,34 @@ +import type { IntervalShort } from "~/components/unit-details"; +import type { AgeRange } from "@macrostrat-web/utility-functions"; + +export type CorrelationItem = { + color: string; + ageRange: AgeRange; + macrostratAgeRange: AgeRange | null; + details: LegendItem; + id: number; +}; + +export interface LegendItem { + legend_id: number; + name: string; + strat_name: string; + age: string; + lith: string; + descrip: string; + comments: string; + liths: string; + b_interval: IntervalShort; + t_interval: IntervalShort; + best_age_bottom?: number; + best_age_top?: number; + unit_ids: string; + concept_ids: string; + color: string; +} + +export enum AgeDisplayMode { + MapLegend, + Macrostrat, + Both, +} diff --git a/src/pages/maps/@id/legend/+Page.ts b/src/pages/maps/@id/legend/+Page.ts index 78554dd2..d5eb366b 100644 --- a/src/pages/maps/@id/legend/+Page.ts +++ b/src/pages/maps/@id/legend/+Page.ts @@ -1,10 +1,8 @@ import { HotkeysProvider, Spinner, Tag } from "@blueprintjs/core"; import DataSheet from "@macrostrat/data-sheet2"; -import { useState } from "react"; import { FullscreenPage } from "~/layouts"; import hyper from "@macrostrat/hyper"; import styles from "./main.module.sass"; -import { useAsyncEffect } from "@macrostrat/ui-components"; import { ColorCell } from "@macrostrat/data-sheet2"; import { PageBreadcrumbs } from "~/renderer"; import { @@ -13,26 +11,12 @@ import { lithologyRenderer, ExpandedLithologies, } from "~/components/legend-table"; - -import { postgrest } from "~/providers"; +import { useLegendData } from "../utils"; const h = hyper.styled(styles); export function Page({ map }) { - const slug = map.slug; - - const [data, setData] = useState(null); - - useAsyncEffect(async () => { - const res = await postgrest - .from("legend") - .select( - "legend_id, name, strat_name, age, lith, descrip, comments, liths, b_interval, t_interval, best_age_bottom, best_age_top, unit_ids, concept_ids" - ) - .eq("source_id", map.source_id) - .order("legend_id", { ascending: true }); - setData(res.data); - }, [map.source_id]); + const data = useLegendData(map); if (data == null) { return h(Spinner); diff --git a/src/pages/maps/@id/legend/+onBeforeRender.ts b/src/pages/maps/@id/legend/+onBeforeRender.ts index 9eae1d5f..92306474 100644 --- a/src/pages/maps/@id/legend/+onBeforeRender.ts +++ b/src/pages/maps/@id/legend/+onBeforeRender.ts @@ -19,10 +19,7 @@ export async function onBeforeRender(pageContext: PageContextServer) { pageProps: { map, }, - documentProps: { - // The page's <title> - title: map.name + "– Legend", - }, + title: map.name + "– Legend", }, }; } diff --git a/src/pages/maps/@id/main.module.sass b/src/pages/maps/@id/main.module.sass index 47fb7a50..80c989b0 100644 --- a/src/pages/maps/@id/main.module.sass +++ b/src/pages/maps/@id/main.module.sass @@ -31,4 +31,11 @@ body width: 1em height: 1em display: inline-block - margin-right: 0.5em \ No newline at end of file + margin-right: 0.5em + +.dev-links + display: block + text-align: right + &>* + display: block + margin: 1em \ No newline at end of file diff --git a/src/pages/maps/@id/utils.ts b/src/pages/maps/@id/utils.ts new file mode 100644 index 00000000..28f37ed5 --- /dev/null +++ b/src/pages/maps/@id/utils.ts @@ -0,0 +1,24 @@ +import { useState } from "react"; +import { useAsyncEffect } from "@macrostrat/ui-components"; +import { postgrest } from "~/providers"; + +export type MapInfo = { + source_id: number; + slug?: string; +}; + +export function useLegendData(map: MapInfo): any[] | null { + /** Hook to return legend data from PostgREST */ + const [data, setData] = useState(null); + useAsyncEffect(async () => { + const res = await postgrest + .from("legend") + .select( + "legend_id, name, strat_name, age, lith, descrip, comments, liths, b_interval, t_interval, best_age_bottom, best_age_top, unit_ids, concept_ids, color" + ) + .eq("source_id", map.source_id) + .order("legend_id", { ascending: true }); + setData(res.data); + }, [map.source_id]); + return data; +} diff --git a/src/pages/projects/@project/columns/@column/+onBeforeRender.ts b/src/pages/projects/@project/columns/@column/+onBeforeRender.ts index 7cd05052..7e06a7bb 100644 --- a/src/pages/projects/@project/columns/@column/+onBeforeRender.ts +++ b/src/pages/projects/@project/columns/@column/+onBeforeRender.ts @@ -1,54 +1 @@ -import { apiV2Prefix } from "@macrostrat-web/settings"; -import { preprocessUnits } from "@macrostrat/column-views/src/helpers"; -import fetch from "node-fetch"; -import { ColumnSummary } from "~/pages/map/map-interface/app-state/handlers/columns"; -import { fetchAPIData } from "~/pages/columns/utils"; - -async function getAndUnwrap<T>(url: string): Promise<T> { - const res = await fetch(url); - const res1 = await res.json(); - return res1.success.data; -} - -export async function onBeforeRender(pageContext) { - // `.page.server.js` files always run in Node.js; we could use SQL/ORM queries here. - const col_id = pageContext.routeParams.column; - const project_id = pageContext.routeParams.project; - - // https://v2.macrostrat.org/api/v2/columns?col_id=3&response=long - - const baseRoute = project_id == null ? "/columns" : `/defs/columns`; - const linkPrefix = project_id == null ? "/" : `/projects/${project_id}/`; - - const responses = await Promise.all([ - getAndUnwrap( - apiV2Prefix + - baseRoute + - "?format=geojson&response=long&in_process=true&col_id=" + - col_id - ), - fetchAPIData(`/units`, { - response: "long", - col_id, - }), - ]); - - const [column, unitsLong]: [any, any] = responses; - - const col = column?.features[0]; - - const columnInfo: ColumnSummary = { - ...col.properties, - geometry: col.geometry, - units: preprocessUnits(unitsLong), - }; - - return { - pageContext: { - pageProps: { - columnInfo, - linkPrefix, - }, - }, - }; -} +export { onBeforeRender } from "~/pages/columns/@column/+onBeforeRender"; diff --git a/src/pages/sift/+config.ts b/src/pages/sift/+config.ts index cb2beb42..0acd0dec 100644 --- a/src/pages/sift/+config.ts +++ b/src/pages/sift/+config.ts @@ -1,7 +1,6 @@ export default { clientRouting: false, supportsDarkMode: false, - isolateStyles: true, meta: { Page: { /* Sift must be rendered as a single-page app, because that is its design. diff --git a/src/renderer/+onRenderClient.ts b/src/renderer/+onRenderClient.ts deleted file mode 100644 index 296f45fc..00000000 --- a/src/renderer/+onRenderClient.ts +++ /dev/null @@ -1,58 +0,0 @@ -export { render as onRenderClient }; - -import { FocusStyleManager } from "@blueprintjs/core"; -import h from "@macrostrat/hyper"; -import ReactDOM from "react-dom/client"; -import { PageShell } from "./page-shell"; -import type { PageContextClient } from "./types"; - -let root: ReactDOM.Root; - -// This render() hook only supports SSR, see https://vike.dev/render-modes for how to modify render() to support SPA -async function render(pageContext: PageContextClient) { - const { Page, pageProps } = pageContext; - if (!Page) - throw new Error( - "Client-side render() hook expects pageContext.Page to be defined" - ); - - FocusStyleManager.onlyShowFocusOnTabs(); - - const page = h(PageShell, { pageContext }, h(Page, pageProps)); - - const container = document.getElementById("app-container")!; - - // TODO: we might be able to switch to vike-react's internal renderer - if (container.innerHTML !== "" && pageContext.isHydration) { - // First render (hydration) - root = ReactDOM.hydrateRoot(container, page); - } else { - if (!root) { - // First render (not hydration) - root = ReactDOM.createRoot(container); - } else { - // Client-side navigation - //const title = getHeadSetting("title", pageContext) || "Macrostrat"; - //const lang = getHeadSetting("lang", pageContext) || "en"; - // const favicon = getHeadSetting('favicon', pageContext) - // // We skip if the value is undefined because we shouldn't remove values set in HTML (by the Head setting). - // // - This also means that previous values will leak: upon client-side navigation, the title set by the previous page won't be removed if the next page doesn't override it. But that's okay because usually pages always have a favicon and title, which means that previous values are always overriden. Also, as a workaround, the user can set the value to `null` to ensure that previous values are overriden. - //if (title !== undefined) document.title = title; - //if (lang !== undefined) document.documentElement.lang = lang; - //if (favicon !== undefined) setFavicon(favicon) - } - - root.render(page); - } -} - -/* To enable Client-side Routing: -export const clientRouting = true -// !! WARNING !! Before doing so, read https://vike.dev/clientRouting */ - -// function getHeadSetting(key: string, pageContext: PageContextClient) { -// return ( -// pageContext.documentProps?.[key] ?? -// pageContext.exports?.documentProps?.[key] -// ); -// } diff --git a/src/renderer/breadcrumbs.module.sass b/src/renderer/breadcrumbs.module.sass new file mode 100644 index 00000000..0d1d5f7d --- /dev/null +++ b/src/renderer/breadcrumbs.module.sass @@ -0,0 +1,5 @@ + +.breadcrumbs-root + display: inline-flex + align-items: center + gap: 0.5em \ No newline at end of file diff --git a/src/renderer/breadcrumbs.ts b/src/renderer/breadcrumbs.ts index 9d96e455..2f5a5575 100644 --- a/src/renderer/breadcrumbs.ts +++ b/src/renderer/breadcrumbs.ts @@ -1,17 +1,28 @@ -import h from "@macrostrat/hyper"; +import hyper from "@macrostrat/hyper"; import { usePageContext } from "./page-context"; import { Breadcrumbs } from "@blueprintjs/core"; import type { PageContext } from "./types"; import React from "react"; +import { MacrostratIcon } from "~/components"; +import styles from "./breadcrumbs.module.sass"; +const h = hyper.styled(styles); -export function PageBreadcrumbs() { +export function PageBreadcrumbs({ showLogo = false }) { const ctx = usePageContext(); + let items = buildBreadcrumbs(ctx.urlPathname, sitemap, ctx); + if (showLogo) { + items[0].text = h("span.breadcrumbs-root", [ + h(MacrostratIcon, { size: 16 }), + "Macrostrat", + ]); + } + return h(Breadcrumbs, { - items: buildBreadcrumns(ctx.urlPathname, sitemap, ctx), + items, }); } -function buildBreadcrumns( +function buildBreadcrumbs( currentPath: string, routes: Routes, ctx: PageContext @@ -51,9 +62,12 @@ function buildBreadcrumns( text = h("code", text); } + let disabled = child?.disabled ?? false; + items.push({ text, href: route == currentPath ? undefined : route, + disabled, }); children = child?.children; } @@ -64,16 +78,33 @@ interface Item { text: string | React.ReactNode; href?: string; current?: boolean; + disabled?: boolean; } interface Routes { slug?: string; name: string | ((urlPart: string, ctx: PageContext) => React.ReactNode); param?: string; + disabled?: boolean; children?: Routes[]; } -const sitemap: Routes = { +const columnsSubtree = { + slug: "columns", + name: "Columns", + children: [ + { + param: "@column", + name(urlPart, ctx) { + return h("span.column-name", [ + ctx.pageProps?.columnInfo?.col_name ?? urlPart, + ]); + }, + }, + ], +}; + +export const sitemap: Routes = { slug: "", name: "Macrostrat", children: [ @@ -156,5 +187,19 @@ const sitemap: Routes = { }, ], }, + columnsSubtree, + { + slug: "projects", + name: "Projects", + children: [ + { + param: "@project", + name(urlPart, ctx) { + return ctx.pageProps?.project?.project ?? urlPart; + }, + children: [{ ...columnsSubtree, disabled: true }], + }, + ], + }, ], }; diff --git a/src/renderer/types.ts b/src/renderer/types.ts index ef579c11..807d0a85 100644 --- a/src/renderer/types.ts +++ b/src/renderer/types.ts @@ -1,13 +1,12 @@ export type { PageContext, PageContextClient, PageContextServer, PageProps }; -import { PageContextBuiltInServerInternal } from "vike/dist/esm/shared/types"; import type { /* // When using Client Routing https://vike.dev/clientRouting PageContextBuiltInClientWithClientRouting as PageContextBuiltInClient /*/ // When using Server Routing - PageContextBuiltInClientWithServerRouting as PageContextBuiltInClient, + PageContextServer as PageContextBuiltInServer, PageContextClient as PageContextClientBase, PageContextServer as PageContextServerBase, } from "vike/types"; @@ -32,7 +31,6 @@ export type PageContextCustom = { user?: User; macrostratLogoFlavor?: string; mdxContent?: string; - title?: string; config: PageContextBuiltInServerInternal["config"] & { clientRouting?: boolean; supportsDarkMode?: boolean; @@ -40,6 +38,8 @@ export type PageContextCustom = { hydrationCanBeAborted?: boolean; }; exports: { + title?: string; + description?: string; pageStyle?: PageStyle; supportsDarkMode?: boolean; documentProps?: DocumentProps; diff --git a/vite.config.ts b/vite.config.ts index 5e0f4866..c77cb056 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -3,7 +3,7 @@ import mdx from "@mdx-js/rollup"; import react from "@vitejs/plugin-react"; import path from "path"; import ssr from "vike/plugin"; -import { UserConfig } from "vite"; +import { UserConfig, Plugin } from "vite"; import cesium from "vite-plugin-cesium"; import pkg from "./package.json"; @@ -36,8 +36,31 @@ const cesiumBuildPath = path.resolve(cesiumRoot, "Cesium"); // Check if we are building for server context +const cssModuleMatcher = /\.module\.(css|scss|sass|styl)$/; + +function hyperStyles(): Plugin { + return { + name: "hyper-styles", + enforce: "post", + // Post-process the output to add the hyperStyled import + transform(code, id) { + const code1 = code.replace("export default", "const styles ="); + if (cssModuleMatcher.test(id)) { + //const code2 = code1 + "\nexport default styles\n"; + const code3 = `import hyper from "@macrostrat/hyper"; + ${code1} + let h = hyper.styled(styles); + // Keep backwards compatibility with the existing default style object. + Object.assign(h, styles); + export default h;`; + //console.log(code3, id); + return code3; + } + }, + }; +} + const config: UserConfig = { - cacheDir: ".vite", root: path.resolve("./src"), resolve: { conditions: ["typescript"], @@ -66,6 +89,7 @@ const config: UserConfig = { cesiumBuildPath, cesiumBuildRootPath: cesiumRoot, }), + hyperStyles(), ], envDir: path.resolve(__dirname), build: { diff --git a/yarn.lock b/yarn.lock index 37ae2d6c..aba245fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,6 +22,13 @@ __metadata: languageName: node linkType: hard +"@antfu/utils@npm:^0.7.7": + version: 0.7.8 + resolution: "@antfu/utils@npm:0.7.8" + checksum: 10/9efffb78a9683add047b0e2be96a059c3a29baee1565a2d127500bc433def25a0375c1e84c9479cbb3f1dcec797b2c1f0a24e5d85c830ea64f9a8a6a6ca5b9c3 + languageName: node + linkType: hard + "@arcanis/slice-ansi@npm:^1.1.1": version: 1.1.1 resolution: "@arcanis/slice-ansi@npm:1.1.1" @@ -4373,13 +4380,20 @@ __metadata: languageName: node linkType: hard -"@brillout/json-serializer@npm:^0.5.1, @brillout/json-serializer@npm:^0.5.8": +"@brillout/json-serializer@npm:^0.5.1": version: 0.5.8 resolution: "@brillout/json-serializer@npm:0.5.8" checksum: 10/db70bb1e6fdb9711eb4901162a391a2ff83aec37914172a619b700f2e09a042f71eed383b9748432c799b751ed119131e464acba62422a2189d0a6e7aca42f74 languageName: node linkType: hard +"@brillout/json-serializer@npm:^0.5.10": + version: 0.5.10 + resolution: "@brillout/json-serializer@npm:0.5.10" + checksum: 10/6e245c62891fd4f1b5a9945b23344b929b94b309f60d5b4bcfff7750a8c657e85713132ef73bdf6e42b10b6ab254a520c58cf8bdc7d32c76663851274ff71ee9 + languageName: node + linkType: hard + "@brillout/picocolors@npm:^1.0.10": version: 1.0.10 resolution: "@brillout/picocolors@npm:1.0.10" @@ -4387,6 +4401,13 @@ __metadata: languageName: node linkType: hard +"@brillout/picocolors@npm:^1.0.11": + version: 1.0.13 + resolution: "@brillout/picocolors@npm:1.0.13" + checksum: 10/337fd4d9799b7cd55225a7405061c3aca1c6b357bb9f02996bf054713344dd1044cf7fb56e80044029d08a3c3169cb3e7094269bfcacc00b6530b408c39c6a69 + languageName: node + linkType: hard + "@brillout/require-shim@npm:^0.1.2": version: 0.1.2 resolution: "@brillout/require-shim@npm:0.1.2" @@ -4394,12 +4415,12 @@ __metadata: languageName: node linkType: hard -"@brillout/vite-plugin-server-entry@npm:^0.4.3": - version: 0.4.3 - resolution: "@brillout/vite-plugin-server-entry@npm:0.4.3" +"@brillout/vite-plugin-server-entry@npm:^0.4.5": + version: 0.4.6 + resolution: "@brillout/vite-plugin-server-entry@npm:0.4.6" dependencies: "@brillout/import": "npm:^0.2.3" - checksum: 10/b62333147a011e3f5c48f81631e292e579148ec0f4612ad252c0fa652bca04c3a841411a539297aabc7f4173af33618db72c51d2548b17407c78f2115ef6583f + checksum: 10/3db277830cdcc37c9bf59c21a231cd65dc10fb4acfee917923c375c3af1c90acb18b156f6d7f9cba0d7767267182b9180b494e131f99d6d6a1b0778f3e459154 languageName: node linkType: hard @@ -5536,6 +5557,46 @@ __metadata: languageName: node linkType: hard +"@hattip/core@npm:0.0.45, @hattip/core@npm:^0.0.45": + version: 0.0.45 + resolution: "@hattip/core@npm:0.0.45" + checksum: 10/b733b63862ec9d84b1a13781b198bf8ec4817ac425f440e96b883e9042115b21aeb133376005690de272cf513f17d65d7b8b186f1a83e397a1b7184ccacd72fd + languageName: node + linkType: hard + +"@hattip/headers@npm:0.0.45": + version: 0.0.45 + resolution: "@hattip/headers@npm:0.0.45" + dependencies: + "@hattip/core": "npm:0.0.45" + checksum: 10/940eca5ee23b8238bc218aa815e4130734567eb9b3abcfea573744169e955fb65b7338730e3d25571ba0a74334e6103aacbeb4cb2cd17fb7498977a30828e972 + languageName: node + linkType: hard + +"@hattip/polyfills@npm:^0.0.45": + version: 0.0.45 + resolution: "@hattip/polyfills@npm:0.0.45" + dependencies: + "@hattip/core": "npm:0.0.45" + "@whatwg-node/fetch": "npm:^0.9.17" + node-fetch-native: "npm:^1.6.4" + checksum: 10/b7520093aafe6981dc563340cdf71381cd08d1562a178a50e4b780ee7b1e992fc31ebd139699f1c5c5ee72e8e75c2d49a2b1a7bc7cab643896114eea33c78b6c + languageName: node + linkType: hard + +"@hattip/walk@npm:^0.0.45": + version: 0.0.45 + resolution: "@hattip/walk@npm:0.0.45" + dependencies: + "@hattip/headers": "npm:0.0.45" + cac: "npm:^6.7.14" + mime-types: "npm:^2.1.35" + bin: + hattip-walk: cli.js + checksum: 10/aa430e2d2e2ff8692ba75c2c32952a8b3a700b6ae9e03eee4d4d19f1b8b9ef5ab011b853960278c883fe2cd163960b449aee1bf6a5f46f5fbfcdc81c3de491c5 + languageName: node + linkType: hard + "@iarna/cli@npm:^2.1.0": version: 2.1.0 resolution: "@iarna/cli@npm:2.1.0" @@ -5719,6 +5780,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": "npm:^0.27.8" + checksum: 10/910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93 + languageName: node + linkType: hard + "@jest/source-map@npm:^27.5.1": version: 27.5.1 resolution: "@jest/source-map@npm:27.5.1" @@ -5918,6 +5988,13 @@ __metadata: languageName: node linkType: hard +"@kamilkisiela/fast-url-parser@npm:^1.1.4": + version: 1.1.4 + resolution: "@kamilkisiela/fast-url-parser@npm:1.1.4" + checksum: 10/5b79438235a81817b02b96ddc581c996961cec5b40c7d6ebabd01ac6af8d4a35a43b9b263144af25386cef92c054c3ef6b1723b09eb0d8cf7b4053781a474c5f + languageName: node + linkType: hard + "@lagunovsky/redux-react-router@npm:^3.2.0": version: 3.2.0 resolution: "@lagunovsky/redux-react-router@npm:3.2.0" @@ -6154,6 +6231,12 @@ __metadata: languageName: unknown linkType: soft +"@macrostrat-web/utility-functions@workspace:*, @macrostrat-web/utility-functions@workspace:packages/utility-functions": + version: 0.0.0-use.local + resolution: "@macrostrat-web/utility-functions@workspace:packages/utility-functions" + languageName: unknown + linkType: soft + "@macrostrat/api-types@workspace:deps/web-components/packages/api-types": version: 0.0.0-use.local resolution: "@macrostrat/api-types@workspace:deps/web-components/packages/api-types" @@ -6769,6 +6852,7 @@ __metadata: "@macrostrat-web/settings": "workspace:*" "@macrostrat-web/sift": "workspace:*" "@macrostrat-web/text-toolchain": "workspace:*" + "@macrostrat-web/utility-functions": "workspace:*" "@macrostrat/api-utils": "workspace:*" "@macrostrat/api-views": "workspace:*" "@macrostrat/auth-components": "workspace:*" @@ -6795,16 +6879,22 @@ __metadata: "@turf/bbox": "npm:^6.5.0" "@turf/boolean-contains": "npm:^6.5.0" "@turf/buffer": "npm:^6.5.0" - "@turf/centroid": "npm:^6.5.0" + "@turf/centroid": "npm:^7.0.0" + "@turf/distance": "npm:^7.0.0" + "@turf/line-intersect": "npm:^7.0.0" + "@turf/nearest-point-on-line": "npm:^7.0.0" "@types/compression": "npm:^1.7.2" + "@types/esprima": "npm:^4" "@types/express": "npm:^4.17.17" "@types/geojson": "npm:^7946.0.10" "@types/node": "npm:^20.4.10" "@types/react": "npm:^18.2.20" "@types/react-dom": "npm:^18.2.7" "@types/react-redux": "npm:^7.1.7" + "@types/underscore": "npm:^1" "@typescript-eslint/eslint-plugin": "npm:^6.3.0" "@typescript-eslint/parser": "npm:^6.3.0" + "@universal-middleware/express": "npm:^0.0.2" "@visx/axis": "npm:^2.14.0" "@visx/scale": "npm:^2.2.2" "@vitejs/plugin-react": "npm:^4.0.4" @@ -6824,6 +6914,7 @@ __metadata: d3-shape: "npm:^3.2.0" esbuild: "npm:^0.20.0" esbuild-register: "npm:^3.5.0" + esprima: "npm:^4.0.1" express: "npm:^4.18.2" hex-to-css-filter: "npm:^5.4.0" history: "npm:^5.3.0" @@ -6849,15 +6940,19 @@ __metadata: swagger-ui-react: "npm:^5.12.3" topojson-client: "npm:^3.0.0" transition-hook: "npm:^1.5.2" + tsx: "npm:^4.11.2" typescript: "npm:^5.1.6" + underscore: "npm:^1.13.6" use-debounce: "npm:^9.0.4" use-react-router-breadcrumbs: "npm:^3.2.1" use-resize-observer: "npm:^9.1.0" - vike: "npm:^0.4.159" - vike-react: "npm:^0.4.2" - vite: "npm:^5.0.12" + vike: "npm:^0.4.172" + vike-react: "npm:^0.4.11" + vite: "npm:^5.2.11" vite-plugin-cesium: "npm:^1.2.22" + vite-plugin-inspect: "npm:^0.8.4" vite-plugin-rewrite-all: "npm:^1.0.1" + vitest: "npm:^1.6.0" zustand: "npm:^4.5.1" languageName: unknown linkType: soft @@ -8872,7 +8967,7 @@ __metadata: languageName: node linkType: hard -"@rollup/pluginutils@npm:^5.0.2": +"@rollup/pluginutils@npm:^5.0.2, @rollup/pluginutils@npm:^5.1.0": version: 5.1.0 resolution: "@rollup/pluginutils@npm:5.1.0" dependencies: @@ -9182,6 +9277,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 10/297f95ff77c82c54de8c9907f186076e715ff2621c5222ba50b8d40a170661c0c5242c763cba2a4791f0f91cb1d8ffa53ea1d7294570cf8cd4694c0e383e484d + languageName: node + linkType: hard + "@sindresorhus/is@npm:^4.0.0": version: 4.6.0 resolution: "@sindresorhus/is@npm:4.6.0" @@ -10712,6 +10814,17 @@ __metadata: languageName: node linkType: hard +"@turf/bearing@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/bearing@npm:7.0.0" + dependencies: + "@turf/helpers": "npm:^7.0.0" + "@turf/invariant": "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10/afd0328ee6c3e6359c1f2d5d6df535e9d1697dfbeff9f33d96e0f2ba1bca2242b543565e8bb98b3a8ea5c44fce9826214e5fff098717287792bfbb102b00badc + languageName: node + linkType: hard + "@turf/boolean-contains@npm:^6.5.0": version: 6.5.0 resolution: "@turf/boolean-contains@npm:6.5.0" @@ -10780,6 +10893,17 @@ __metadata: languageName: node linkType: hard +"@turf/centroid@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/centroid@npm:7.0.0" + dependencies: + "@turf/helpers": "npm:^7.0.0" + "@turf/meta": "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10/2375762376dc6378616513799f969daa2deaf596281e1dc14c65cd0daf7e7a415dc35eb9d4786f10741e8dcb02b0cd1cf83c728a406a32c256246e66eb4d532a + languageName: node + linkType: hard + "@turf/clone@npm:^6.5.0": version: 6.5.0 resolution: "@turf/clone@npm:6.5.0" @@ -10789,6 +10913,17 @@ __metadata: languageName: node linkType: hard +"@turf/destination@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/destination@npm:7.0.0" + dependencies: + "@turf/helpers": "npm:^7.0.0" + "@turf/invariant": "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10/8e14baefa5a82975b9e329a1d5bfa15e588d4ad4a49554e33cf6da08dd682db803485e7278a8c432c7ee6684ff4a3ca91354f924de102bea2074b0392f612ce3 + languageName: node + linkType: hard + "@turf/distance@npm:6.3.0": version: 6.3.0 resolution: "@turf/distance@npm:6.3.0" @@ -10799,6 +10934,17 @@ __metadata: languageName: node linkType: hard +"@turf/distance@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/distance@npm:7.0.0" + dependencies: + "@turf/helpers": "npm:^7.0.0" + "@turf/invariant": "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10/e43f316efa42d3570b3e3c9cfe5f7bbc341c776560b5ae131f17b91bd59a5dc13f775ca4dece912db68b7d2f0baad4fa1f3ec89102d95ad33783cb3036c578ed + languageName: node + linkType: hard + "@turf/helpers@npm:^6.3.0, @turf/helpers@npm:^6.5.0": version: 6.5.0 resolution: "@turf/helpers@npm:6.5.0" @@ -10806,6 +10952,16 @@ __metadata: languageName: node linkType: hard +"@turf/helpers@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/helpers@npm:7.0.0" + dependencies: + deep-equal: "npm:^2.2.3" + tslib: "npm:^2.6.2" + checksum: 10/b356ba499634ff8e13af595c842e2c35efbfe365801848f4395577c402f3336a4e6fb0273dbb9c4dcb447636f29df304eaa68899dbf9368cff970170fe1ce39a + languageName: node + linkType: hard + "@turf/invariant@npm:^6.3.0, @turf/invariant@npm:^6.5.0": version: 6.5.0 resolution: "@turf/invariant@npm:6.5.0" @@ -10815,6 +10971,27 @@ __metadata: languageName: node linkType: hard +"@turf/invariant@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/invariant@npm:7.0.0" + dependencies: + "@turf/helpers": "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10/5361c27ab080104e08b44c5b49d69c890f9bcb7713512ae78a01c0dd1f5dac1936704c64bfb45c3a05cc8e5fb3ca0648aa92050c80d22c04a373cf3b7eb94683 + languageName: node + linkType: hard + +"@turf/line-intersect@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/line-intersect@npm:7.0.0" + dependencies: + "@turf/helpers": "npm:^7.0.0" + sweepline-intersections: "npm:^1.5.0" + tslib: "npm:^2.6.2" + checksum: 10/fda3f75f79ba59da1bc974ead94d7a657fce34c2d22d48a1d1685359bccc092e00913b1eb203f95b1a3e51dfc71694b8b0aba0b3b5d24c568dd8ff5bab9dec1c + languageName: node + linkType: hard + "@turf/meta@npm:^6.5.0": version: 6.5.0 resolution: "@turf/meta@npm:6.5.0" @@ -10824,6 +11001,31 @@ __metadata: languageName: node linkType: hard +"@turf/meta@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/meta@npm:7.0.0" + dependencies: + "@turf/helpers": "npm:^7.0.0" + checksum: 10/31dbb971f265ed7d33b64934a2ccd1bcd0b949fc303fe11871b786dc92a860815425d6146b7455edfb457c5b51dc54e5490eb34408ccf2239203e9eb1a581770 + languageName: node + linkType: hard + +"@turf/nearest-point-on-line@npm:^7.0.0": + version: 7.0.0 + resolution: "@turf/nearest-point-on-line@npm:7.0.0" + dependencies: + "@turf/bearing": "npm:^7.0.0" + "@turf/destination": "npm:^7.0.0" + "@turf/distance": "npm:^7.0.0" + "@turf/helpers": "npm:^7.0.0" + "@turf/invariant": "npm:^7.0.0" + "@turf/line-intersect": "npm:^7.0.0" + "@turf/meta": "npm:^7.0.0" + tslib: "npm:^2.6.2" + checksum: 10/729db8018bb469dec78f06a6a2cd443528f2357bc9122918d939e4cf2406fe314da08a49232e2418bd7e45da709a4037239616d5dce12fb960d9f67463730881 + languageName: node + linkType: hard + "@turf/projection@npm:^6.5.0": version: 6.5.0 resolution: "@turf/projection@npm:6.5.0" @@ -11221,6 +11423,15 @@ __metadata: languageName: node linkType: hard +"@types/esprima@npm:^4": + version: 4.0.6 + resolution: "@types/esprima@npm:4.0.6" + dependencies: + "@types/estree": "npm:*" + checksum: 10/36c5ca24a6506eb5742e8c66c0372918a859535022a5605f1e898b66e4941b91384f43c6aeb5c8ca1ed984af469dfabd22e784e833175bb1ab25af40787466ba + languageName: node + linkType: hard + "@types/estree-jsx@npm:^1.0.0": version: 1.0.0 resolution: "@types/estree-jsx@npm:1.0.0" @@ -11294,7 +11505,7 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:^4, @types/express@npm:^4.17.13, @types/express@npm:^4.7.0": +"@types/express@npm:^4, @types/express@npm:^4.7.0": version: 4.17.21 resolution: "@types/express@npm:4.17.21" dependencies: @@ -11306,6 +11517,18 @@ __metadata: languageName: node linkType: hard +"@types/express@npm:^4.17.13": + version: 4.17.18 + resolution: "@types/express@npm:4.17.18" + dependencies: + "@types/body-parser": "npm:*" + "@types/express-serve-static-core": "npm:^4.17.33" + "@types/qs": "npm:*" + "@types/serve-static": "npm:*" + checksum: 10/b344988a35d3cae7b29e984f010ac9124de5e61fe2104c0fc541db6ba8fc4433c69d505537429d157400572c258b47afca4bd668b58de101ae879c868b81bcb1 + languageName: node + linkType: hard + "@types/find-cache-dir@npm:^3.2.1": version: 3.2.1 resolution: "@types/find-cache-dir@npm:3.2.1" @@ -11911,6 +12134,13 @@ __metadata: languageName: node linkType: hard +"@types/underscore@npm:^1": + version: 1.11.15 + resolution: "@types/underscore@npm:1.11.15" + checksum: 10/5a9c262566551f61744e066d33578c0fad1139c2d5314ed77a8f867487a6d7dd18616f32df4c605070fbd569b942c6d2c083be9f9ae649f41a562b52812b06be + languageName: node + linkType: hard + "@types/unist@npm:*, @types/unist@npm:^3.0.0": version: 3.0.2 resolution: "@types/unist@npm:3.0.2" @@ -12102,6 +12332,17 @@ __metadata: languageName: node linkType: hard +"@universal-middleware/express@npm:^0.0.2": + version: 0.0.2 + resolution: "@universal-middleware/express@npm:0.0.2" + dependencies: + "@hattip/core": "npm:^0.0.45" + "@hattip/polyfills": "npm:^0.0.45" + "@hattip/walk": "npm:^0.0.45" + checksum: 10/8caa883047fc206594149c23dd54eb4c0f8b84d222bef7b6ea66d73545bc5354b359cbb07163db90bd75a62ec756a37411221acdbcdb9e798b861d9e08ec12e0 + languageName: node + linkType: hard + "@use-gesture/core@npm:10.3.1": version: 10.3.1 resolution: "@use-gesture/core@npm:10.3.1" @@ -12336,6 +12577,60 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/expect@npm:1.6.0" + dependencies: + "@vitest/spy": "npm:1.6.0" + "@vitest/utils": "npm:1.6.0" + chai: "npm:^4.3.10" + checksum: 10/e82304a12e22b98c1ccea81e8f33c838561deb878588eac463164cc4f8fc0c401ace3a9e6758d9e3a6bcc01313e845e8478aaefb7548eaded04b8de12c1928f6 + languageName: node + linkType: hard + +"@vitest/runner@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/runner@npm:1.6.0" + dependencies: + "@vitest/utils": "npm:1.6.0" + p-limit: "npm:^5.0.0" + pathe: "npm:^1.1.1" + checksum: 10/d83a608be36dace77f91a9d15ab7753f9c5923281188a8d9cb5ccec770df9cc9ba80e5e1e3465328c7605977be0f0708610855abf5f4af037a4ede5f51a83e47 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/snapshot@npm:1.6.0" + dependencies: + magic-string: "npm:^0.30.5" + pathe: "npm:^1.1.1" + pretty-format: "npm:^29.7.0" + checksum: 10/0bfc26a48b45814604ff0f7276d73a047b79f3618e0b620ff54ea2de548e9603a9770963ba6ebb19f7ea1ed51001cbca58d74aa0271651d4f8e88c6233885eba + languageName: node + linkType: hard + +"@vitest/spy@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/spy@npm:1.6.0" + dependencies: + tinyspy: "npm:^2.2.0" + checksum: 10/1c9698272a58aa47708bb8a1672d655fcec3285b02067cc3f70bfe76f4eda7a756eb379f8c945ccbe61677f5189aeb5ba93c2737a9d7db2de8c4e7bbdffcd372 + languageName: node + linkType: hard + +"@vitest/utils@npm:1.6.0": + version: 1.6.0 + resolution: "@vitest/utils@npm:1.6.0" + dependencies: + diff-sequences: "npm:^29.6.3" + estree-walker: "npm:^3.0.3" + loupe: "npm:^2.3.7" + pretty-format: "npm:^29.7.0" + checksum: 10/5c5d7295ac13fcea1da039232bcc7c3fc6f070070fe12ba2ad152456af6e216e48a3ae169016cfcd5055706a00dc567b8f62e4a9b1914f069f52b8f0a3c25e60 + languageName: node + linkType: hard + "@vx/axis@npm:0.0.198": version: 0.0.198 resolution: "@vx/axis@npm:0.0.198" @@ -12628,6 +12923,36 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/events@npm:^0.1.0": + version: 0.1.1 + resolution: "@whatwg-node/events@npm:0.1.1" + checksum: 10/3a356ca23522190201e27446cfd7ebf1cf96815ddb9d1ba5da0a00bbe6c1d28b4094862104411101fbedd47c758b25fe3683033f6a3e80933029efd664c33567 + languageName: node + linkType: hard + +"@whatwg-node/fetch@npm:^0.9.17": + version: 0.9.18 + resolution: "@whatwg-node/fetch@npm:0.9.18" + dependencies: + "@whatwg-node/node-fetch": "npm:^0.5.7" + urlpattern-polyfill: "npm:^10.0.0" + checksum: 10/3ef78969991a44ea99c4f88929c1472b65f066c9d2734992d0de77d11089eec5c3477503cd9513201516056496e28c9fb172635f69867417a0c68e88f4409c96 + languageName: node + linkType: hard + +"@whatwg-node/node-fetch@npm:^0.5.7": + version: 0.5.11 + resolution: "@whatwg-node/node-fetch@npm:0.5.11" + dependencies: + "@kamilkisiela/fast-url-parser": "npm:^1.1.4" + "@whatwg-node/events": "npm:^0.1.0" + busboy: "npm:^1.6.0" + fast-querystring: "npm:^1.1.1" + tslib: "npm:^2.3.1" + checksum: 10/0ddbe236ce38b7859ad4b3c2097560585473d6d70afd5e72d88a97daa89176af7b888c95b6fc93a0bd6a471a42b7fb32ef8dac4d1558fbd6d835b25887329a91 + languageName: node + linkType: hard + "@xtuc/ieee754@npm:^1.2.0": version: 1.2.0 resolution: "@xtuc/ieee754@npm:1.2.0" @@ -12902,6 +13227,15 @@ __metadata: languageName: node linkType: hard +"acorn-walk@npm:^8.3.2": + version: 8.3.3 + resolution: "acorn-walk@npm:8.3.3" + dependencies: + acorn: "npm:^8.11.0" + checksum: 10/59701dcb7070679622ba8e9c7f37577b4935565747ca0fd7c1c3ad30b3f1b1b008276282664e323b5495eb49f77fa12d3816fd06dc68e18f90fbebe759f71450 + languageName: node + linkType: hard + "acorn@npm:^7.1.1, acorn@npm:^7.4.1": version: 7.4.1 resolution: "acorn@npm:7.4.1" @@ -12920,6 +13254,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.11.0, acorn@npm:^8.11.3": + version: 8.12.0 + resolution: "acorn@npm:8.12.0" + bin: + acorn: bin/acorn + checksum: 10/550cc5033184eb98f7fbe2e9ddadd0f47f065734cc682f25db7a244f52314eb816801b64dec7174effd978045bd1754892731a90b1102b0ede9d17a15cfde138 + languageName: node + linkType: hard + "acorn@npm:^8.11.2, acorn@npm:^8.7.1": version: 8.11.3 resolution: "acorn@npm:8.11.3" @@ -13423,6 +13766,13 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^1.1.0": + version: 1.1.0 + resolution: "assertion-error@npm:1.1.0" + checksum: 10/fd9429d3a3d4fd61782eb3962ae76b6d08aa7383123fca0596020013b3ebd6647891a85b05ce821c47d1471ed1271f00b0545cf6a4326cf2fc91efcc3b0fbecf + languageName: node + linkType: hard + "ast-types@npm:^0.16.1": version: 0.16.1 resolution: "ast-types@npm:0.16.1" @@ -13558,6 +13908,15 @@ __metadata: languageName: node linkType: hard +"available-typed-arrays@npm:^1.0.7": + version: 1.0.7 + resolution: "available-typed-arrays@npm:1.0.7" + dependencies: + possible-typed-array-names: "npm:^1.0.0" + checksum: 10/6c9da3a66caddd83c875010a1ca8ef11eac02ba15fb592dc9418b2b5e7b77b645fa7729380a92d9835c2f05f2ca1b6251f39b993e0feb3f1517c74fa1af02cab + languageName: node + linkType: hard + "aws-sign2@npm:~0.7.0": version: 0.7.0 resolution: "aws-sign2@npm:0.7.0" @@ -14312,6 +14671,24 @@ __metadata: languageName: node linkType: hard +"bundle-name@npm:^4.1.0": + version: 4.1.0 + resolution: "bundle-name@npm:4.1.0" + dependencies: + run-applescript: "npm:^7.0.0" + checksum: 10/1d966c8d2dbf4d9d394e53b724ac756c2414c45c01340b37743621f59cc565a435024b394ddcb62b9b335d1c9a31f4640eb648c3fec7f97ee74dc0694c9beb6c + languageName: node + linkType: hard + +"busboy@npm:^1.6.0": + version: 1.6.0 + resolution: "busboy@npm:1.6.0" + dependencies: + streamsearch: "npm:^1.1.0" + checksum: 10/bee10fa10ea58e7e3e7489ffe4bda6eacd540a17de9f9cd21cc37e297b2dd9fe52b2715a5841afaec82900750d810d01d7edb4b2d456427f449b92b417579763 + languageName: node + linkType: hard + "byline@npm:^5.0.0": version: 5.0.0 resolution: "byline@npm:5.0.0" @@ -14340,7 +14717,7 @@ __metadata: languageName: node linkType: hard -"cac@npm:^6.0.0": +"cac@npm:^6.0.0, cac@npm:^6.7.14": version: 6.7.14 resolution: "cac@npm:6.7.14" checksum: 10/002769a0fbfc51c062acd2a59df465a2a947916b02ac50b56c69ec6c018ee99ac3e7f4dd7366334ea847f1ecacf4defaa61bcd2ac283db50156ce1f1d8c8ad42 @@ -14614,6 +14991,21 @@ __metadata: languageName: node linkType: hard +"chai@npm:^4.3.10": + version: 4.4.1 + resolution: "chai@npm:4.4.1" + dependencies: + assertion-error: "npm:^1.1.0" + check-error: "npm:^1.0.3" + deep-eql: "npm:^4.1.3" + get-func-name: "npm:^2.0.2" + loupe: "npm:^2.3.6" + pathval: "npm:^1.1.1" + type-detect: "npm:^4.0.8" + checksum: 10/c6d7aba913a67529c68dbec3673f94eb9c586c5474cc5142bd0b587c9c9ec9e5fbaa937e038ecaa6475aea31433752d5fabdd033b9248bde6ae53befcde774ae + languageName: node + linkType: hard + "chalk@npm:2.3.1": version: 2.3.1 resolution: "chalk@npm:2.3.1" @@ -14763,6 +15155,15 @@ __metadata: languageName: node linkType: hard +"check-error@npm:^1.0.3": + version: 1.0.3 + resolution: "check-error@npm:1.0.3" + dependencies: + get-func-name: "npm:^2.0.2" + checksum: 10/e2131025cf059b21080f4813e55b3c480419256914601750b0fee3bd9b2b8315b531e551ef12560419b8b6d92a3636511322752b1ce905703239e7cc451b6399 + languageName: node + linkType: hard + "chokidar@npm:>=3.0.0 <4.0.0, chokidar@npm:^3.5.3": version: 3.5.3 resolution: "chokidar@npm:3.5.3" @@ -15352,6 +15753,13 @@ __metadata: languageName: node linkType: hard +"confbox@npm:^0.1.7": + version: 0.1.7 + resolution: "confbox@npm:0.1.7" + checksum: 10/3086687b9a2a70d44d4b40a2d376536fe7e1baec4a2a34261b21b8a836026b419cbf89ded6054216631823e7d63c415dad4b4d53591d6edbb202bb9820dfa6fa + languageName: node + linkType: hard + "config-chain@npm:^1.1.13": version: 1.1.13 resolution: "config-chain@npm:1.1.13" @@ -15671,14 +16079,7 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^3.19.0": - version: 3.37.1 - resolution: "core-js@npm:3.37.1" - checksum: 10/25d6bd15fcc6ffd2a0ec0be57a78ff3358b3e1fdffdb6800fc93dcfdb3854037aee41f3d101aed8c37905d107daf98218b3e7ee95cec383710d2a66a5d9e541b - languageName: node - linkType: hard - -"core-js@npm:^3.6.4": +"core-js@npm:^3.19.0, core-js@npm:^3.6.4": version: 3.32.2 resolution: "core-js@npm:3.32.2" checksum: 10/7df03093f9d9a9157f98c55ce0facb6af60b78ccb5c3b4a30623b00570742688b5cb4d0ff92900cd08c0f853ff1e085831825249369c17966fecac61bd1fd5fc @@ -16970,6 +17371,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^4.1.3": + version: 4.1.4 + resolution: "deep-eql@npm:4.1.4" + dependencies: + type-detect: "npm:^4.0.0" + checksum: 10/f04f4d581f044a824a6322fe4f68fbee4d6780e93fc710cd9852cbc82bfc7010df00f0e05894b848abbe14dc3a25acac44f424e181ae64d12f2ab9d0a875a5ef + languageName: node + linkType: hard + "deep-equal@npm:^2.0.5": version: 2.2.2 resolution: "deep-equal@npm:2.2.2" @@ -16996,6 +17406,32 @@ __metadata: languageName: node linkType: hard +"deep-equal@npm:^2.2.3": + version: 2.2.3 + resolution: "deep-equal@npm:2.2.3" + dependencies: + array-buffer-byte-length: "npm:^1.0.0" + call-bind: "npm:^1.0.5" + es-get-iterator: "npm:^1.1.3" + get-intrinsic: "npm:^1.2.2" + is-arguments: "npm:^1.1.1" + is-array-buffer: "npm:^3.0.2" + is-date-object: "npm:^1.0.5" + is-regex: "npm:^1.1.4" + is-shared-array-buffer: "npm:^1.0.2" + isarray: "npm:^2.0.5" + object-is: "npm:^1.1.5" + object-keys: "npm:^1.1.1" + object.assign: "npm:^4.1.4" + regexp.prototype.flags: "npm:^1.5.1" + side-channel: "npm:^1.0.4" + which-boxed-primitive: "npm:^1.0.2" + which-collection: "npm:^1.0.1" + which-typed-array: "npm:^1.1.13" + checksum: 10/1ce49d0b71d0f14d8ef991a742665eccd488dfc9b3cada069d4d7a86291e591c92d2589c832811dea182b4015736b210acaaebce6184be356c1060d176f5a05f + languageName: node + linkType: hard + "deep-extend@npm:0.6.0, deep-extend@npm:^0.6.0": version: 0.6.0 resolution: "deep-extend@npm:0.6.0" @@ -17027,6 +17463,23 @@ __metadata: languageName: node linkType: hard +"default-browser-id@npm:^5.0.0": + version: 5.0.0 + resolution: "default-browser-id@npm:5.0.0" + checksum: 10/185bfaecec2c75fa423544af722a3469b20704c8d1942794a86e4364fe7d9e8e9f63241a5b769d61c8151993bc65833a5b959026fa1ccea343b3db0a33aa6deb + languageName: node + linkType: hard + +"default-browser@npm:^5.2.1": + version: 5.2.1 + resolution: "default-browser@npm:5.2.1" + dependencies: + bundle-name: "npm:^4.1.0" + default-browser-id: "npm:^5.0.0" + checksum: 10/afab7eff7b7f5f7a94d9114d1ec67273d3fbc539edf8c0f80019879d53aa71e867303c6f6d7cffeb10a6f3cfb59d4f963dba3f9c96830b4540cc7339a1bf9840 + languageName: node + linkType: hard + "default-gateway@npm:^6.0.3": version: 6.0.3 resolution: "default-gateway@npm:6.0.3" @@ -17081,6 +17534,13 @@ __metadata: languageName: node linkType: hard +"define-lazy-prop@npm:^3.0.0": + version: 3.0.0 + resolution: "define-lazy-prop@npm:3.0.0" + checksum: 10/f28421cf9ee86eecaf5f3b8fe875f13d7009c2625e97645bfff7a2a49aca678270b86c39f9c32939e5ca7ab96b551377ed4139558c795e076774287ad3af1aa4 + languageName: node + linkType: hard + "define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0": version: 1.2.1 resolution: "define-properties@npm:1.2.1" @@ -17277,6 +17737,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10/179daf9d2f9af5c57ad66d97cb902a538bcf8ed64963fa7aa0c329b3de3665ce2eb6ffdc2f69f29d445fa4af2517e5e55e5b6e00c00a9ae4f43645f97f7078cb + languageName: node + linkType: hard + "diff@npm:^4.0.1": version: 4.0.2 resolution: "diff@npm:4.0.2" @@ -17860,6 +18327,13 @@ __metadata: languageName: node linkType: hard +"error-stack-parser-es@npm:^0.1.1": + version: 0.1.4 + resolution: "error-stack-parser-es@npm:0.1.4" + checksum: 10/5f3364a997489f5ce3f7d814775a32a6b98cacd366b6802c05501e2c4347b7edb3a6869f30b6026398890060e9e67d4865a801f1eee84eb8e4535bd6d423327d + languageName: node + linkType: hard + "es-abstract@npm:^1.17.2, es-abstract@npm:^1.22.1": version: 1.22.2 resolution: "es-abstract@npm:1.22.2" @@ -18024,7 +18498,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0, esbuild@npm:^0.20.1": +"esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0, esbuild@npm:^0.20.1, esbuild@npm:~0.20.2": version: 0.20.2 resolution: "esbuild@npm:0.20.2" dependencies: @@ -18549,7 +19023,7 @@ __metadata: languageName: node linkType: hard -"estree-walker@npm:^3.0.0": +"estree-walker@npm:^3.0.0, estree-walker@npm:^3.0.3": version: 3.0.3 resolution: "estree-walker@npm:3.0.3" dependencies: @@ -18868,6 +19342,13 @@ __metadata: languageName: node linkType: hard +"fast-decode-uri-component@npm:^1.0.1": + version: 1.0.1 + resolution: "fast-decode-uri-component@npm:1.0.1" + checksum: 10/4b6ed26974414f688be4a15eab6afa997bad4a7c8605cb1deb928b28514817b4523a1af0fa06621c6cbfedb7e5615144c2c3e7512860e3a333a31a28d537dca7 + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -18875,7 +19356,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.0.0, fast-glob@npm:^3.2.11, fast-glob@npm:^3.3.0": +"fast-glob@npm:^3.0.0": version: 3.3.2 resolution: "fast-glob@npm:3.3.2" dependencies: @@ -18888,7 +19369,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9": +"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.2, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0": version: 3.3.1 resolution: "fast-glob@npm:3.3.1" dependencies: @@ -18922,6 +19403,15 @@ __metadata: languageName: node linkType: hard +"fast-querystring@npm:^1.1.1": + version: 1.1.2 + resolution: "fast-querystring@npm:1.1.2" + dependencies: + fast-decode-uri-component: "npm:^1.0.1" + checksum: 10/981da9b914f2b639dc915bdfa4f34ab028b967d428f02fbd293d99258593fde69c48eea73dfa03ced088268e0a8045c642e8debcd9b4821ebd125e130a0430c7 + languageName: node + linkType: hard + "fastclick@npm:^1.0.6": version: 1.0.6 resolution: "fastclick@npm:1.0.6" @@ -19420,7 +19910,7 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.1.0": +"fs-extra@npm:^11.1.0, fs-extra@npm:^11.2.0": version: 11.2.0 resolution: "fs-extra@npm:11.2.0" dependencies: @@ -19701,6 +20191,13 @@ __metadata: languageName: node linkType: hard +"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2": + version: 2.0.2 + resolution: "get-func-name@npm:2.0.2" + checksum: 10/3f62f4c23647de9d46e6f76d2b3eafe58933a9b3830c60669e4180d6c601ce1b4aa310ba8366143f55e52b139f992087a9f0647274e8745621fa2af7e0acf13b + languageName: node + linkType: hard + "get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.0, get-intrinsic@npm:^1.2.1": version: 1.2.1 resolution: "get-intrinsic@npm:1.2.1" @@ -19713,7 +20210,7 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.2.4": +"get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4": version: 1.2.4 resolution: "get-intrinsic@npm:1.2.4" dependencies: @@ -19843,6 +20340,15 @@ __metadata: languageName: node linkType: hard +"get-tsconfig@npm:^4.7.5": + version: 4.7.5 + resolution: "get-tsconfig@npm:4.7.5" + dependencies: + resolve-pkg-maps: "npm:^1.0.0" + checksum: 10/de7de5e4978354e8e6d9985baf40ea32f908a13560f793bc989930c229cc8d5c3f7b6b2896d8e43eb1a9b4e9e30018ef4b506752fd2a4b4d0dfee4af6841b119 + languageName: node + linkType: hard + "getpass@npm:^0.1.1": version: 0.1.7 resolution: "getpass@npm:0.1.7" @@ -20401,6 +20907,15 @@ __metadata: languageName: node linkType: hard +"has-tostringtag@npm:^1.0.2": + version: 1.0.2 + resolution: "has-tostringtag@npm:1.0.2" + dependencies: + has-symbols: "npm:^1.0.3" + checksum: 10/c74c5f5ceee3c8a5b8bc37719840dc3749f5b0306d818974141dda2471a1a2ca6c8e46b9d6ac222c5345df7a901c9b6f350b1e6d62763fec877e26609a401bfe + languageName: node + linkType: hard + "has-unicode@npm:^2.0.0, has-unicode@npm:^2.0.1, has-unicode@npm:~2.0.1": version: 2.0.1 resolution: "has-unicode@npm:2.0.1" @@ -21676,6 +22191,15 @@ __metadata: languageName: node linkType: hard +"is-docker@npm:^3.0.0": + version: 3.0.0 + resolution: "is-docker@npm:3.0.0" + bin: + is-docker: cli.js + checksum: 10/b698118f04feb7eaf3338922bd79cba064ea54a1c3db6ec8c0c8d8ee7613e7e5854d802d3ef646812a8a3ace81182a085dfa0a71cc68b06f3fa794b9783b3c90 + languageName: node + linkType: hard + "is-extendable@npm:^0.1.0": version: 0.1.1 resolution: "is-extendable@npm:0.1.1" @@ -21766,6 +22290,17 @@ __metadata: languageName: node linkType: hard +"is-inside-container@npm:^1.0.0": + version: 1.0.0 + resolution: "is-inside-container@npm:1.0.0" + dependencies: + is-docker: "npm:^3.0.0" + bin: + is-inside-container: cli.js + checksum: 10/c50b75a2ab66ab3e8b92b3bc534e1ea72ca25766832c0623ac22d134116a98bcf012197d1caabe1d1c4bd5f84363d4aa5c36bb4b585fbcaf57be172cd10a1a03 + languageName: node + linkType: hard + "is-installed-globally@npm:^0.1.0": version: 0.1.0 resolution: "is-installed-globally@npm:0.1.0" @@ -22103,6 +22638,15 @@ __metadata: languageName: node linkType: hard +"is-wsl@npm:^3.1.0": + version: 3.1.0 + resolution: "is-wsl@npm:3.1.0" + dependencies: + is-inside-container: "npm:^1.0.0" + checksum: 10/f9734c81f2f9cf9877c5db8356bfe1ff61680f1f4c1011e91278a9c0564b395ae796addb4bf33956871041476ec82c3e5260ed57b22ac91794d4ae70a1d2f0a9 + languageName: node + linkType: hard + "isarray@npm:0.0.1": version: 0.0.1 resolution: "isarray@npm:0.0.1" @@ -22821,6 +23365,13 @@ __metadata: languageName: node linkType: hard +"js-tokens@npm:^9.0.0": + version: 9.0.0 + resolution: "js-tokens@npm:9.0.0" + checksum: 10/65e7a55a1a18d61f1cf94bfd7704da870b74337fa08d4c58118e69a8b10225b5ad887ff3ae595d720301b0924811a9b0594c679621a85ecbac6e3aac8533c53b + languageName: node + linkType: hard + "js-yaml@npm:=4.1.0, js-yaml@npm:^4.1.0": version: 4.1.0 resolution: "js-yaml@npm:4.1.0" @@ -23640,6 +24191,16 @@ __metadata: languageName: node linkType: hard +"local-pkg@npm:^0.5.0": + version: 0.5.0 + resolution: "local-pkg@npm:0.5.0" + dependencies: + mlly: "npm:^1.4.2" + pkg-types: "npm:^1.0.3" + checksum: 10/20f4caba50dc6fb00ffcc1a78bc94b5acb33995e0aadf4d4edcdeab257e891aa08f50afddf02f3240b2c3d02432bc2078f2a916a280ed716b64753a3d250db70 + languageName: node + linkType: hard + "locate-path@npm:^2.0.0": version: 2.0.0 resolution: "locate-path@npm:2.0.0" @@ -24007,6 +24568,15 @@ __metadata: languageName: node linkType: hard +"loupe@npm:^2.3.6, loupe@npm:^2.3.7": + version: 2.3.7 + resolution: "loupe@npm:2.3.7" + dependencies: + get-func-name: "npm:^2.0.1" + checksum: 10/635c8f0914c2ce7ecfe4e239fbaf0ce1d2c00e4246fafcc4ed000bfdb1b8f89d05db1a220054175cca631ebf3894872a26fffba0124477fcb562f78762848fb1 + languageName: node + linkType: hard + "lower-case@npm:^2.0.2": version: 2.0.2 resolution: "lower-case@npm:2.0.2" @@ -24143,6 +24713,15 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.5": + version: 0.30.10 + resolution: "magic-string@npm:0.30.10" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.4.15" + checksum: 10/9f8bf6363a14c98a9d9f32ef833b194702a5c98fb931b05ac511b76f0b06fd30ed92beda6ca3261d2d52d21e39e891ef1136fbd032023f6cbb02d0b7d5767201 + languageName: node + linkType: hard + "make-dir@npm:^1.0.0": version: 1.3.0 resolution: "make-dir@npm:1.3.0" @@ -25183,7 +25762,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.0.1, mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": +"mime-types@npm:^2.0.1, mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:^2.1.35, mime-types@npm:~2.1.17, mime-types@npm:~2.1.19, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -25514,6 +26093,18 @@ __metadata: languageName: node linkType: hard +"mlly@npm:^1.4.2, mlly@npm:^1.7.0": + version: 1.7.1 + resolution: "mlly@npm:1.7.1" + dependencies: + acorn: "npm:^8.11.3" + pathe: "npm:^1.1.2" + pkg-types: "npm:^1.1.1" + ufo: "npm:^1.5.3" + checksum: 10/c1ef3989e95fb6c6c27a238330897b01f46507020501f45a681f2cae453f982e38dcb0e45aa65f672ea7280945d4a729d266f17a8acb187956f312b0cafddf61 + languageName: node + linkType: hard + "modify-values@npm:^1.0.0": version: 1.0.1 resolution: "modify-values@npm:1.0.1" @@ -25856,6 +26447,13 @@ __metadata: languageName: node linkType: hard +"node-fetch-native@npm:^1.6.4": + version: 1.6.4 + resolution: "node-fetch-native@npm:1.6.4" + checksum: 10/39c4c6d0c2a4bed1444943e1647ad0d79eb6638cf159bc37dffeafd22cffcf6a998e006aa1f3dd1d9d2258db7d78dee96b44bee4ba0bbaf0440ed348794f2543 + languageName: node + linkType: hard + "node-fetch-npm@npm:^2.0.2": version: 2.0.4 resolution: "node-fetch-npm@npm:2.0.4" @@ -26639,6 +27237,18 @@ __metadata: languageName: node linkType: hard +"open@npm:^10.1.0": + version: 10.1.0 + resolution: "open@npm:10.1.0" + dependencies: + default-browser: "npm:^5.2.1" + define-lazy-prop: "npm:^3.0.0" + is-inside-container: "npm:^1.0.0" + is-wsl: "npm:^3.1.0" + checksum: 10/a9c4105243a1b3c5312bf2aeb678f78d31f00618b5100088ee01eed2769963ea1f2dd464ac8d93cef51bba2d911e1a9c0c34a753ec7b91d6b22795903ea6647a + languageName: node + linkType: hard + "open@npm:^7.4.2": version: 7.4.2 resolution: "open@npm:7.4.2" @@ -26814,6 +27424,15 @@ __metadata: languageName: node linkType: hard +"p-limit@npm:^5.0.0": + version: 5.0.0 + resolution: "p-limit@npm:5.0.0" + dependencies: + yocto-queue: "npm:^1.0.0" + checksum: 10/87bf5837dee6942f0dbeff318436179931d9a97848d1b07dbd86140a477a5d2e6b90d9701b210b4e21fe7beaea2979dfde366e4f576fa644a59bd4d6a6371da7 + languageName: node + linkType: hard + "p-locate@npm:^2.0.0": version: 2.0.0 resolution: "p-locate@npm:2.0.0" @@ -27282,6 +27901,13 @@ __metadata: languageName: node linkType: hard +"pathval@npm:^1.1.1": + version: 1.1.1 + resolution: "pathval@npm:1.1.1" + checksum: 10/b50a4751068aa3a5428f5a0b480deecedc6f537666a3630a0c2ae2d5e7c0f4bf0ee77b48404441ec1220bef0c91625e6030b3d3cf5a32ab0d9764018d1d9dbb6 + languageName: node + linkType: hard + "pbf@npm:^3.2.1": version: 3.2.1 resolution: "pbf@npm:3.2.1" @@ -27305,6 +27931,13 @@ __metadata: languageName: node linkType: hard +"perfect-debounce@npm:^1.0.0": + version: 1.0.0 + resolution: "perfect-debounce@npm:1.0.0" + checksum: 10/220343acf52976947958fef3599849471605316e924fe19c633ae2772576298e9d38f02cefa8db46f06607505ce7b232cbb35c9bfd477bd0329bd0a2ce37c594 + languageName: node + linkType: hard + "performance-now@npm:^0.2.0": version: 0.2.0 resolution: "performance-now@npm:0.2.0" @@ -27425,6 +28058,17 @@ __metadata: languageName: node linkType: hard +"pkg-types@npm:^1.0.3, pkg-types@npm:^1.1.1": + version: 1.1.1 + resolution: "pkg-types@npm:1.1.1" + dependencies: + confbox: "npm:^0.1.7" + mlly: "npm:^1.7.0" + pathe: "npm:^1.1.2" + checksum: 10/225eaf7c0339027e176dd0d34a6d9a1384c21e0aab295e57dfbef1f1b7fc132f008671da7e67553e352b80b17ba38c531c720c914061d277410eef1bdd9d9608 + languageName: node + linkType: hard + "pngjs@npm:^3.3.3": version: 3.4.0 resolution: "pngjs@npm:3.4.0" @@ -27441,6 +28085,13 @@ __metadata: languageName: node linkType: hard +"possible-typed-array-names@npm:^1.0.0": + version: 1.0.0 + resolution: "possible-typed-array-names@npm:1.0.0" + checksum: 10/8ed3e96dfeea1c5880c1f4c9cb707e5fb26e8be22f14f82ef92df20fd2004e635c62ba47fbe8f2bb63bfd80dac1474be2fb39798da8c2feba2815435d1f749af + languageName: node + linkType: hard + "postcss-calc@npm:^7.0.1": version: 7.0.5 resolution: "postcss-calc@npm:7.0.5" @@ -28468,6 +29119,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10/dea96bc83c83cd91b2bfc55757b6b2747edcaac45b568e46de29deee80742f17bc76fe8898135a70d904f4928eafd8bb693cd1da4896e8bdd3c5e82cadf1d2bb + languageName: node + linkType: hard + "pretty-hrtime@npm:^1.0.3": version: 1.0.3 resolution: "pretty-hrtime@npm:1.0.3" @@ -29468,6 +30130,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.0.0": + version: 18.3.1 + resolution: "react-is@npm:18.3.1" + checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 + languageName: node + linkType: hard + "react-json-tree@npm:^0.15.0": version: 0.15.2 resolution: "react-json-tree@npm:0.15.2" @@ -29803,17 +30472,18 @@ __metadata: languageName: node linkType: hard -"react-streaming@npm:^0.3.19": - version: 0.3.19 - resolution: "react-streaming@npm:0.3.19" +"react-streaming@npm:^0.3.27": + version: 0.3.28 + resolution: "react-streaming@npm:0.3.28" dependencies: "@brillout/import": "npm:^0.2.3" "@brillout/json-serializer": "npm:^0.5.1" + "@brillout/picocolors": "npm:^1.0.11" isbot-fast: "npm:1.2.0" peerDependencies: react: ">=18" react-dom: ">=18" - checksum: 10/5c96dc355db925cf90830a001239a7e9788f01b53e5ad9dc1b054f52bab3f820f10422e3d395678f89f1f4b83eed3ec2b5577a7d857e134bc7cdd9c7d3cab680 + checksum: 10/700f41b753425c2de894875b8c0e00fc685b7caa914d90ef3f7c39aa07fe269afe8e24c406cc57f170e7cc279e011b891220472b6922692b4a0ddd9ab757fa40 languageName: node linkType: hard @@ -30715,6 +31385,13 @@ __metadata: languageName: node linkType: hard +"resolve-pkg-maps@npm:^1.0.0": + version: 1.0.0 + resolution: "resolve-pkg-maps@npm:1.0.0" + checksum: 10/0763150adf303040c304009231314d1e84c6e5ebfa2d82b7d94e96a6e82bacd1dcc0b58ae257315f3c8adb89a91d8d0f12928241cba2df1680fbe6f60bf99b0e + languageName: node + linkType: hard + "resolve-protobuf-schema@npm:^2.1.0": version: 2.1.0 resolution: "resolve-protobuf-schema@npm:2.1.0" @@ -30740,7 +31417,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.5, resolve@npm:^1.22.1, resolve@npm:^1.22.8, resolve@npm:^1.9.0": +"resolve@npm:^1.1.5, resolve@npm:^1.22.1, resolve@npm:^1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" dependencies: @@ -30753,7 +31430,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.16.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0": +"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.12.0, resolve@npm:^1.14.2, resolve@npm:^1.16.1, resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.9.0": version: 1.22.6 resolution: "resolve@npm:1.22.6" dependencies: @@ -30775,7 +31452,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.5#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.9.0#optional!builtin<compat/resolve>": +"resolve@patch:resolve@npm%3A^1.1.5#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.22.8#optional!builtin<compat/resolve>": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin<compat/resolve>::version=1.22.8&hash=c3c19d" dependencies: @@ -30788,7 +31465,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.10.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.12.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.16.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>": +"resolve@patch:resolve@npm%3A^1.1.6#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.10.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.12.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.14.2#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.16.1#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.17.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.19.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.20.0#optional!builtin<compat/resolve>, resolve@patch:resolve@npm%3A^1.9.0#optional!builtin<compat/resolve>": version: 1.22.6 resolution: "resolve@patch:resolve@npm%3A1.22.6#optional!builtin<compat/resolve>::version=1.22.6&hash=c3c19d" dependencies: @@ -31252,6 +31929,13 @@ __metadata: languageName: node linkType: hard +"run-applescript@npm:^7.0.0": + version: 7.0.0 + resolution: "run-applescript@npm:7.0.0" + checksum: 10/b02462454d8b182ad4117e5d4626e9e6782eb2072925c9fac582170b0627ae3c1ea92ee9b2df7daf84b5e9ffe14eb1cf5fb70bc44b15c8a0bfcdb47987e2410c + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -31826,6 +32510,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10/e93ff66c6531a079af8fb217240df01f980155b5dc408d2d7bebc398dd284e383eb318153bf8acd4db3c4fe799aa5b9a641e38b0ba3b1975700b1c89547ea4e7 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -31888,7 +32579,7 @@ __metadata: languageName: node linkType: hard -"sirv@npm:^2.0.0": +"sirv@npm:^2.0.0, sirv@npm:^2.0.4": version: 2.0.4 resolution: "sirv@npm:2.0.4" dependencies: @@ -32301,6 +32992,13 @@ __metadata: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10/2d4dc4e64e2db796de4a3c856d5943daccdfa3dd092e452a1ce059c81e9a9c29e0b9badba91b43ef0d5ff5c04ee62feb3bcc559a804e16faf447bac2d883aa99 + languageName: node + linkType: hard + "stampit@npm:^4.3.2": version: 4.3.2 resolution: "stampit@npm:4.3.2" @@ -32353,6 +33051,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.5.0": + version: 3.7.0 + resolution: "std-env@npm:3.7.0" + checksum: 10/6ee0cca1add3fd84656b0002cfbc5bfa20340389d9ba4720569840f1caa34bce74322aef4c93f046391583e50649d0cf81a5f8fe1d411e50b659571690a45f12 + languageName: node + linkType: hard + "stop-iteration-iterator@npm:^1.0.0": version: 1.0.0 resolution: "stop-iteration-iterator@npm:1.0.0" @@ -32434,6 +33139,13 @@ __metadata: languageName: node linkType: hard +"streamsearch@npm:^1.1.0": + version: 1.1.0 + resolution: "streamsearch@npm:1.1.0" + checksum: 10/612c2b2a7dbcc859f74597112f80a42cbe4d448d03da790d5b7b39673c1197dd3789e91cd67210353e58857395d32c1e955a9041c4e6d5bae723436b3ed9ed14 + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -32750,6 +33462,15 @@ __metadata: languageName: node linkType: hard +"strip-literal@npm:^2.0.0": + version: 2.1.0 + resolution: "strip-literal@npm:2.1.0" + dependencies: + js-tokens: "npm:^9.0.0" + checksum: 10/21c813aa1e669944e7e2318c8c927939fb90b0c52f53f57282bfc3dd6e19d53f70004f1f1693e33e5e790ad5ef102b0fce2b243808229d1ce07ae71f326c0e82 + languageName: node + linkType: hard + "style-inject@npm:^0.3.0": version: 0.3.0 resolution: "style-inject@npm:0.3.0" @@ -32863,6 +33584,13 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:*": + version: 9.4.0 + resolution: "supports-color@npm:9.4.0" + checksum: 10/cb8ff8daeaf1db642156f69a9aa545b6c01dd9c4def4f90a49f46cbf24be0c245d392fcf37acd119cd1819b99dad2cc9b7e3260813f64bcfd7f5b18b5a1eefb8 + languageName: node + linkType: hard + "supports-color@npm:^2.0.0": version: 2.0.0 resolution: "supports-color@npm:2.0.0" @@ -33041,6 +33769,15 @@ __metadata: languageName: node linkType: hard +"sweepline-intersections@npm:^1.5.0": + version: 1.5.0 + resolution: "sweepline-intersections@npm:1.5.0" + dependencies: + tinyqueue: "npm:^2.0.0" + checksum: 10/2782fec085d0a4c90874096d4b18e51d1210cbb9f5834f6122e49441cc0711a0adfda08260da4e03816a43c88293a672e63fab96a06115f2d854fefdeb532ead + languageName: node + linkType: hard + "symbol-tree@npm:^3.2.4": version: 3.2.4 resolution: "symbol-tree@npm:3.2.4" @@ -33379,6 +34116,13 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.5.1": + version: 2.8.0 + resolution: "tinybench@npm:2.8.0" + checksum: 10/9731d070bedee6d44f3bb565862c284776e6adfd70d81a051a5c79b77479408509b448ad8d467d538d18bc0ae857b3ead8168d7e98d7f1355f8a0b01aa2f163b + languageName: node + linkType: hard + "tinycolor2@npm:^1.4.1": version: 1.6.0 resolution: "tinycolor2@npm:1.6.0" @@ -33393,13 +34137,27 @@ __metadata: languageName: node linkType: hard -"tinyqueue@npm:^2.0.3": +"tinypool@npm:^0.8.3": + version: 0.8.4 + resolution: "tinypool@npm:0.8.4" + checksum: 10/7365944c2532f240111443e7012be31a634faf1a02db08a91db3aa07361c26a374d0be00a0f2ea052c4bee39c107ba67f1f814c108d9d51dfc725c559c1a9c03 + languageName: node + linkType: hard + +"tinyqueue@npm:^2.0.0, tinyqueue@npm:^2.0.3": version: 2.0.3 resolution: "tinyqueue@npm:2.0.3" checksum: 10/b676fdf2050bc9b2b76fe3faa433f3122c623e3f2c32d2a6921487b1e8fb541a85af79ea0eca3618e261b293280db78b02e1f835321783b8c078c507b739017c languageName: node linkType: hard +"tinyspy@npm:^2.2.0": + version: 2.2.1 + resolution: "tinyspy@npm:2.2.1" + checksum: 10/170d6232e87f9044f537b50b406a38fbfd6f79a261cd12b92879947bd340939a833a678632ce4f5c4a6feab4477e9c21cd43faac3b90b68b77dd0536c4149736 + languageName: node + linkType: hard + "tmp@npm:^0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -33721,6 +34479,29 @@ __metadata: languageName: node linkType: hard +"tslib@npm:^2.6.2": + version: 2.6.3 + resolution: "tslib@npm:2.6.3" + checksum: 10/52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c + languageName: node + linkType: hard + +"tsx@npm:^4.11.2": + version: 4.11.2 + resolution: "tsx@npm:4.11.2" + dependencies: + esbuild: "npm:~0.20.2" + fsevents: "npm:~2.3.3" + get-tsconfig: "npm:^4.7.5" + dependenciesMeta: + fsevents: + optional: true + bin: + tsx: dist/cli.mjs + checksum: 10/2026b1f007ed7a2b45363ff260211351bdfa3cdb2c71975ed4e4a274918f5a3a6343b4e4ecd558a8eaebe707d242f8d5fdc7b60537272279dc59f2812d86dbdb + languageName: node + linkType: hard + "tunnel-agent@npm:^0.6.0": version: 0.6.0 resolution: "tunnel-agent@npm:0.6.0" @@ -33846,7 +34627,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:4.0.8": +"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.8": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 10/5179e3b8ebc51fce1b13efb75fdea4595484433f9683bbc2dca6d99789dba4e602ab7922d2656f2ce8383987467f7770131d4a7f06a26287db0615d2f4c4ce7d @@ -34008,7 +34789,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^4.2.3||^5, typescript@npm:^5": +"typescript@npm:^4.2.3||^5": version: 5.4.5 resolution: "typescript@npm:5.4.5" bin: @@ -34018,7 +34799,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:^5.0.0, typescript@npm:^5.1.6": +"typescript@npm:^5, typescript@npm:^5.0.0, typescript@npm:^5.1.6": version: 5.2.2 resolution: "typescript@npm:5.2.2" bin: @@ -34038,7 +34819,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^4.2.3||^5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5#optional!builtin<compat/typescript>": +"typescript@patch:typescript@npm%3A^4.2.3||^5#optional!builtin<compat/typescript>": version: 5.4.5 resolution: "typescript@patch:typescript@npm%3A5.4.5#optional!builtin<compat/typescript>::version=5.4.5&hash=5adc0c" bin: @@ -34048,7 +34829,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A^5.0.0#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.1.6#optional!builtin<compat/typescript>": +"typescript@patch:typescript@npm%3A^5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.0#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.1.6#optional!builtin<compat/typescript>": version: 5.2.2 resolution: "typescript@patch:typescript@npm%3A5.2.2#optional!builtin<compat/typescript>::version=5.2.2&hash=f3b441" bin: @@ -34079,6 +34860,13 @@ __metadata: languageName: node linkType: hard +"ufo@npm:^1.5.3": + version: 1.5.3 + resolution: "ufo@npm:1.5.3" + checksum: 10/2b30dddd873c643efecdb58cfe457183cd4d95937ccdacca6942c697b87a2c578232c25a5149fda85436696bf0fdbc213bf2b220874712bc3e58c0fb00a2c950 + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.17.4 resolution: "uglify-js@npm:3.17.4" @@ -34574,6 +35362,13 @@ __metadata: languageName: node linkType: hard +"urlpattern-polyfill@npm:^10.0.0": + version: 10.0.0 + resolution: "urlpattern-polyfill@npm:10.0.0" + checksum: 10/346819dbe718e929988298d02a988b8ddfa601d08daaa7e69b1148eab699c86c0f0f933d68d8c8cf913166fe64156ed28904e673200d18ef7e9ed6b58cea3fc7 + languageName: node + linkType: hard + "use-async-effect@npm:^2.2.1": version: 2.2.7 resolution: "use-async-effect@npm:2.2.7" @@ -34918,29 +35713,29 @@ __metadata: languageName: node linkType: hard -"vike-react@npm:^0.4.2": - version: 0.4.2 - resolution: "vike-react@npm:0.4.2" +"vike-react@npm:^0.4.11": + version: 0.4.11 + resolution: "vike-react@npm:0.4.11" dependencies: - react-streaming: "npm:^0.3.19" + react-streaming: "npm:^0.3.27" peerDependencies: react: 18.x.x react-dom: 18.x.x - vike: ^0.4.159 + vike: ^0.4.160 vite: ^4.3.8 || ^5.0.10 - checksum: 10/488e74b2cce2ddb38031fa172386d8d0795048271c432861fa5a45d04e673ef6bafa9047be971256cc70bf969e9ac961a44a45b3454148ccef140f90727a2e03 + checksum: 10/38f08f58f884264aa15b8f63df815d374da3a9307ad7a55579de258bdf7657567214eb8b2ccdf11ac24f4e48b88feaa05d5122941b4a0f6ce8cbdeb9c15df7f6 languageName: node linkType: hard -"vike@npm:^0.4.159": - version: 0.4.159 - resolution: "vike@npm:0.4.159" +"vike@npm:^0.4.172": + version: 0.4.172 + resolution: "vike@npm:0.4.172" dependencies: "@brillout/import": "npm:^0.2.3" - "@brillout/json-serializer": "npm:^0.5.8" + "@brillout/json-serializer": "npm:^0.5.10" "@brillout/picocolors": "npm:^1.0.10" "@brillout/require-shim": "npm:^0.1.2" - "@brillout/vite-plugin-server-entry": "npm:^0.4.3" + "@brillout/vite-plugin-server-entry": "npm:^0.4.5" acorn: "npm:^8.0.0" cac: "npm:^6.0.0" es-module-lexer: "npm:^1.0.0" @@ -34950,13 +35745,28 @@ __metadata: source-map-support: "npm:^0.5.0" peerDependencies: react-streaming: ">=0.3.5" - vite: ">=3.1.0" + vite: ">=4.4.0" peerDependenciesMeta: react-streaming: optional: true bin: vike: node/cli/bin-entry.js - checksum: 10/8daf1447e04d50ec7f6eae55cda0016c84da0b0d55814cef0ed6925e9d1a0d1f56e755c4d9777ac0d2f7202b38c36b47b92315d65e3b33277c7619d36c3f47c7 + checksum: 10/5a1adb43af3ed953318b14ada19b50ad65d674de474b49dcf1e2ae6d7ed6c23ac5a095c82dfab4bd40c42e629c9943ecd6e53d1e933f99ff2c26a11a8a5f3ab5 + languageName: node + linkType: hard + +"vite-node@npm:1.6.0": + version: 1.6.0 + resolution: "vite-node@npm:1.6.0" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.3.4" + pathe: "npm:^1.1.1" + picocolors: "npm:^1.0.0" + vite: "npm:^5.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10/40230598c3c285cf65f407ac50b1c7753ab2dfa960de76ec1a95a0ce0ff963919d065c29ba538d9fb2fba3e0703a051d49d1ad6486001ba2f90616cc706ddc3d languageName: node linkType: hard @@ -34974,6 +35784,28 @@ __metadata: languageName: node linkType: hard +"vite-plugin-inspect@npm:^0.8.4": + version: 0.8.4 + resolution: "vite-plugin-inspect@npm:0.8.4" + dependencies: + "@antfu/utils": "npm:^0.7.7" + "@rollup/pluginutils": "npm:^5.1.0" + debug: "npm:^4.3.4" + error-stack-parser-es: "npm:^0.1.1" + fs-extra: "npm:^11.2.0" + open: "npm:^10.1.0" + perfect-debounce: "npm:^1.0.0" + picocolors: "npm:^1.0.0" + sirv: "npm:^2.0.4" + peerDependencies: + vite: ^3.1.0 || ^4.0.0 || ^5.0.0-0 + peerDependenciesMeta: + "@nuxt/kit": + optional: true + checksum: 10/7b8997bef0f39ba4a726b47c546aeb1ecd16d5f039e6ca5d5a1b24963197ce894aee6024b456c158d22c0cf37780e38b69e164d4cc5bc6e0d6e2442c809fb9a0 + languageName: node + linkType: hard + "vite-plugin-rewrite-all@npm:^1.0.1": version: 1.0.1 resolution: "vite-plugin-rewrite-all@npm:1.0.1" @@ -35105,6 +35937,96 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.2.11": + version: 5.2.12 + resolution: "vite@npm:5.2.12" + dependencies: + esbuild: "npm:^0.20.1" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.4.38" + rollup: "npm:^4.13.0" + peerDependencies: + "@types/node": ^18.0.0 || >=20.0.0 + less: "*" + lightningcss: ^1.21.0 + sass: "*" + stylus: "*" + sugarss: "*" + terser: ^5.4.0 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + bin: + vite: bin/vite.js + checksum: 10/c27d3efff93016e8171b6a362f605ad5f78e24086292987097ad4a7382ae78d9e0659065976a13bf7b51ba0f593d675579010692097ef36d8a5cc965f3efec4c + languageName: node + linkType: hard + +"vitest@npm:^1.6.0": + version: 1.6.0 + resolution: "vitest@npm:1.6.0" + dependencies: + "@vitest/expect": "npm:1.6.0" + "@vitest/runner": "npm:1.6.0" + "@vitest/snapshot": "npm:1.6.0" + "@vitest/spy": "npm:1.6.0" + "@vitest/utils": "npm:1.6.0" + acorn-walk: "npm:^8.3.2" + chai: "npm:^4.3.10" + debug: "npm:^4.3.4" + execa: "npm:^8.0.1" + local-pkg: "npm:^0.5.0" + magic-string: "npm:^0.30.5" + pathe: "npm:^1.1.1" + picocolors: "npm:^1.0.0" + std-env: "npm:^3.5.0" + strip-literal: "npm:^2.0.0" + tinybench: "npm:^2.5.1" + tinypool: "npm:^0.8.3" + vite: "npm:^5.0.0" + vite-node: "npm:1.6.0" + why-is-node-running: "npm:^2.2.2" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/node": ^18.0.0 || >=20.0.0 + "@vitest/browser": 1.6.0 + "@vitest/ui": 1.6.0 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10/ad921a723ac9438636d37111f0b2ea5afd0ba4a7813fb75382b9f75574e10d533cf950573ebb9332a595ce197cb83593737a6b55a3b6e6eb00bddbcd0920a03e + languageName: node + linkType: hard + "vlq@npm:^0.2.2": version: 0.2.3 resolution: "vlq@npm:0.2.3" @@ -35554,6 +36476,19 @@ __metadata: languageName: node linkType: hard +"which-typed-array@npm:^1.1.13": + version: 1.1.15 + resolution: "which-typed-array@npm:1.1.15" + dependencies: + available-typed-arrays: "npm:^1.0.7" + call-bind: "npm:^1.0.7" + for-each: "npm:^0.3.3" + gopd: "npm:^1.0.1" + has-tostringtag: "npm:^1.0.2" + checksum: 10/c3b6a99beadc971baa53c3ee5b749f2b9bdfa3b3b9a70650dd8511a48b61d877288b498d424712e9991d16019633086bd8b5923369460d93463c5825fa36c448 + languageName: node + linkType: hard + "which@npm:^1.2.9, which@npm:^1.3.0, which@npm:^1.3.1": version: 1.3.1 resolution: "which@npm:1.3.1" @@ -35576,6 +36511,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.2.2": + version: 2.2.2 + resolution: "why-is-node-running@npm:2.2.2" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10/f3582e0337f4b25537d492b1d40f00b978ce04b1d1eeea8f310bfa8aae8a7d11d118d672e2f0760c164ce3753a620a70aa29ff3620e340197624940cf9c08615 + languageName: node + linkType: hard + "wide-align@npm:^1.1.0, wide-align@npm:^1.1.5": version: 1.1.5 resolution: "wide-align@npm:1.1.5" @@ -36018,6 +36965,13 @@ __metadata: languageName: node linkType: hard +"yocto-queue@npm:^1.0.0": + version: 1.0.0 + resolution: "yocto-queue@npm:1.0.0" + checksum: 10/2cac84540f65c64ccc1683c267edce396b26b1e931aa429660aefac8fbe0188167b7aee815a3c22fa59a28a58d898d1a2b1825048f834d8d629f4c2a5d443801 + languageName: node + linkType: hard + "zenscroll@npm:^4.0.2": version: 4.0.2 resolution: "zenscroll@npm:4.0.2"