diff --git a/.idea/misc.xml b/.idea/misc.xml index cc6eae03c..86f49135a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/packages/api-types/CHANGELOG.md b/packages/api-types/CHANGELOG.md index 0c99b9079..ca73bc3d9 100644 --- a/packages/api-types/CHANGELOG.md +++ b/packages/api-types/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.1.2] - 2025-08-22 + +- Added types for `StratName` and `StratNameConcept` + ## [1.1.1] - 2025-06-25 - Add type for `Interval` diff --git a/packages/api-types/package.json b/packages/api-types/package.json index ea5373822..8152db890 100644 --- a/packages/api-types/package.json +++ b/packages/api-types/package.json @@ -1,6 +1,6 @@ { "name": "@macrostrat/api-types", - "version": "1.1.1", + "version": "1.1.2", "description": "Type definitions for Macrostrat's API", "main": "./src/index.d.ts", "types": "./src/index.d.ts", diff --git a/packages/api-types/src/units.d.ts b/packages/api-types/src/units.d.ts index 3b7458ed3..2c63f1d12 100644 --- a/packages/api-types/src/units.d.ts +++ b/packages/api-types/src/units.d.ts @@ -80,3 +80,45 @@ export interface UnitLongFull extends UnitLong { b_pos?: number | string; t_pos?: number | string; } + +export interface StratName { + strat_name: string; + strat_name_long: string; + rank: string; + strat_name_id: number; + concept_id: number; + bed: string; + bed_id: number; + mbr: string; + mbr_id: number; + fm: string; + fm_id: number; + subgp: string; + subgp_id: number; + gp: string; + gp_id: number; + sgp: string; + sgp_id: number; + b_age: string; + t_age: string; + b_period: string; + t_period: string; + c_interval: string; + t_units: number; + ref_id: number; +} + +export interface StratNameConcept { + concept_id: number; + name: string; + geologic_age: string; + int_id: string; + b_int_id: string; + t_int_id: string; + usage_notes: string; + other: string; + province: string; + refs: string; + url: string; + author: string; +} diff --git a/packages/column-views/CHANGELOG.md b/packages/column-views/CHANGELOG.md index 6dde6b3c9..3de30865c 100644 --- a/packages/column-views/CHANGELOG.md +++ b/packages/column-views/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.1.3] - 2025-08-22 + +- Added `UnitDetailsPanelWithNavigation` component +- Added `ColumnBasicInfo` component +- Improve styling of `UnitDetailsPanel` +- Add `ReferencesField` component for bibliographic info +- Add data fetchers for stratigraphic names + ## [2.1.2] - 2025-06-26 - UnitDetailsPanel strat name and interval now clickable diff --git a/packages/column-views/package.json b/packages/column-views/package.json index 31f4b3011..0e2b29fa5 100644 --- a/packages/column-views/package.json +++ b/packages/column-views/package.json @@ -1,6 +1,6 @@ { "name": "@macrostrat/column-views", - "version": "2.1.2", + "version": "2.1.3", "description": "Data views for Macrostrat stratigraphic columns", "type": "module", "source": "src/index.ts", diff --git a/packages/column-views/src/age-axis.ts b/packages/column-views/src/age-axis.ts index bea2995c7..cdbff37aa 100644 --- a/packages/column-views/src/age-axis.ts +++ b/packages/column-views/src/age-axis.ts @@ -29,12 +29,16 @@ const AgeAxisCore = ({ ticks, tickSpacing = 40, showDomain = false }) => { }; export function VerticalAxisLabel(props) { - const { label = "Age", unit = "Ma", className } = props; - return h("div.column-axis-label.age-axis-label", { className }, [ - label, - " ", - h.if(unit)(Parenthetical, { className: "age-axis-unit" }, unit), - ]); + const { label = "Age", unit = "Ma", className, height } = props; + return h( + "div.column-axis-label.age-axis-label", + { className, style: { height } }, + [ + label, + " ", + h.if(unit)(Parenthetical, { className: "age-axis-unit" }, unit), + ], + ); } export function CompositeAgeAxis() { @@ -81,6 +85,7 @@ export function CompositeAgeAxisCore(props: CompositeStratigraphicScaleInfo) { h(VerticalAxisLabel, { label: axisLabel, unit: axisUnit, + height: totalHeight, }), h( SVG, diff --git a/packages/column-views/src/column.module.sass b/packages/column-views/src/column.module.sass index 2213599ee..d03903a22 100644 --- a/packages/column-views/src/column.module.sass +++ b/packages/column-views/src/column.module.sass @@ -134,3 +134,9 @@ body:global(.dark-mode) .column-container --unit-font-style: normal +.column-title-row + display: flex + flex-direction: row + gap: 1em + justify-content: space-between + align-items: baseline diff --git a/packages/column-views/src/column.ts b/packages/column-views/src/column.ts index df2495213..f07628515 100644 --- a/packages/column-views/src/column.ts +++ b/packages/column-views/src/column.ts @@ -12,7 +12,11 @@ import { } from "./units"; import { ColumnHeightScaleOptions } from "./prepare-units/composite-scale"; -import { UnitSelectionPopover } from "./unit-details"; +import { + Identifier, + ReferencesField, + UnitSelectionPopover, +} from "./unit-details"; import { MacrostratColumnDataProvider, useCompositeScale, @@ -27,6 +31,7 @@ import { CompositeAgeAxis } from "./age-axis"; import { MergeSectionsMode, usePreparedColumnUnits } from "./prepare-units"; import { UnitLong } from "@macrostrat/api-types"; import { NonIdealState } from "@blueprintjs/core"; +import { DataField } from "@macrostrat/data-components"; const h = hyperStyled(styles); @@ -270,3 +275,20 @@ export function ColumnContainer(props: ColumnContainerProps) { ...rest, }); } + +export function ColumnBasicInfo({ data, showColumnID = true }) { + if (data == null) return null; + return h("div.column-info", [ + h("div.column-title-row", [ + h("h2", data.col_name), + h.if(showColumnID)("h4", h(Identifier, { id: data.col_id })), + ]), + h(DataField, { row: true, label: "Group", value: data.col_group }), + h(ReferencesField, { + refs: data.refs, + inline: false, + row: true, + className: "column-refs", + }), + ]); +} diff --git a/packages/column-views/src/data-provider/base.ts b/packages/column-views/src/data-provider/base.ts index fd61a5853..0b40812b5 100644 --- a/packages/column-views/src/data-provider/base.ts +++ b/packages/column-views/src/data-provider/base.ts @@ -8,6 +8,7 @@ import { ColumnGeoJSONRecordWithID, Environment, MacrostratRef, + StratName, } from "@macrostrat/api-types"; import { fetchAllColumns, @@ -15,6 +16,7 @@ import { fetchIntervals, fetchLithologies, fetchRefs, + fetchStratNames, } from "./fetch"; import { APIProvider } from "@macrostrat/ui-components"; import { ColumnProvider } from "@macrostrat/column-components"; @@ -55,6 +57,9 @@ interface MacrostratStore extends RefsSlice { projectID: number | null, inProcess: boolean, ): Promise; + // Strat names unify both "strat names" and "concepts" + stratNames: Map | null; + getStratNames(ids: number[] | null): Promise; } function createMacrostratStore( @@ -83,6 +88,7 @@ function createMacrostratStore( ...createEnvironmentsSlice(set, get), ...createColumnsSlice(set, get), ...createRefsSlice(set, get), + ...createStratNamesSlice(set, get), }; }); } @@ -229,6 +235,34 @@ function createIntervalsSlice(set, get) { }; } +function createStratNamesSlice(set, get) { + return { + stratNames: null, + async getStratNames(ids: number[] | null): Promise { + const { stratNames, fetch } = get(); + let nameMap = stratNames ?? new Map(); + let stratNamesAlreadyLoaded = []; + let stratNamesToLoad = []; + for (const id of ids) { + if (nameMap.has(id)) { + stratNamesAlreadyLoaded.push(nameMap.get(id)); + } else { + stratNamesToLoad.push(id); + } + } + if (stratNamesToLoad.length > 0) { + const data = await fetchStratNames(stratNamesToLoad, fetch); + if (data == null) return stratNamesAlreadyLoaded; + for (const d of data) { + nameMap.set(d.strat_name_id, d); + } + set({ stratNames: nameMap }); + } + return ids.map((id) => nameMap.get(id)); + }, + }; +} + function includesTimescale(intervals: Map, timescaleID: number) { if (intervals == null) return false; if (timescaleID == null) return true; @@ -251,12 +285,21 @@ export function useMacrostratStore(selector: MacrostratSelector | "api") { return useStore(ctx, selector); } +type DataTypeKey = + | "lithologies" + | "intervals" + | "columns" + | "environments" + | "refs" + | "strat_names"; + const dataTypeMapping = { lithologies: (store) => store.getLithologies, intervals: (store) => store.getIntervals, columns: (store) => store.getColumns, environments: (store) => store.getEnvironments, refs: (store) => store.getRefs, + strat_names: (store) => store.getStratNames, }; export function useMacrostratDefs(dataType: string): Map | null { @@ -299,14 +342,18 @@ export function useMacrostratColumns( }, [colData, inProcess]); } -export function useMacrostratData(dataType: string, ...args: any[]) { +export function useMacrostratData(dataType: DataTypeKey, ...args: any[]) { const selector = dataTypeMapping[dataType]; const operator = useMacrostratStore(selector); const [value, setValue] = useState(null); useEffect(() => { - operator(...args).then(setValue); + try { + operator(...args).then(setValue); + } catch (e) { + console.error(e); + } }, [operator, ...args]); return value; diff --git a/packages/column-views/src/data-provider/fetch.ts b/packages/column-views/src/data-provider/fetch.ts index 714a281f3..2919d6b84 100644 --- a/packages/column-views/src/data-provider/fetch.ts +++ b/packages/column-views/src/data-provider/fetch.ts @@ -2,6 +2,8 @@ import { ColumnGeoJSONRecord, ColumnGeoJSONRecordWithID, MacrostratRef, + StratName, + StratUnit, UnitLong, } from "@macrostrat/api-types"; import { @@ -201,6 +203,19 @@ export async function fetchRefs( return await unwrapResponse(res); } +export async function fetchStratNames( + names: number[], + fetch = defaultFetch, +): Promise { + let url = `/defs/strat_names`; + if (names.length == 0) { + return []; + } + url += "?strat_name_id=" + names.join(","); + const res = await fetch(url); + return await unwrapResponse(res); +} + export type ColumnData = { units: UnitLong[]; columnID: number; diff --git a/packages/column-views/src/unit-details/index.ts b/packages/column-views/src/unit-details/index.ts index 7edba452d..0801e549d 100644 --- a/packages/column-views/src/unit-details/index.ts +++ b/packages/column-views/src/unit-details/index.ts @@ -1,2 +1,3 @@ export * from "./panel"; export * from "./popover"; +export * from "./modal-panel"; diff --git a/packages/column-views/src/unit-details/modal-panel.ts b/packages/column-views/src/unit-details/modal-panel.ts new file mode 100644 index 000000000..6b5157377 --- /dev/null +++ b/packages/column-views/src/unit-details/modal-panel.ts @@ -0,0 +1,61 @@ +import { Button } from "@blueprintjs/core"; +import { UnitDetailsPanel } from "./panel"; +import { useKeyHandler } from "@macrostrat/ui-components"; +import h from "@macrostrat/hyper"; + +export function UnitDetailsPanelWithNavigation(props) { + const { unitData, className, selectedUnit, onSelectUnit, features, ...rest } = + props; + + const ix = unitData?.findIndex( + (unit) => unit.unit_id === selectedUnit?.unit_id, + ); + + const keyMap = { + 38: ix - 1, + 40: ix + 1, + }; + + useKeyHandler( + (event) => { + const nextIx = keyMap[event.keyCode]; + if (nextIx == null || nextIx < 0 || nextIx >= unitData.length) return; + onSelectUnit(unitData[nextIx].unit_id); + event.stopPropagation(); + }, + [unitData, ix], + ); + + if (selectedUnit == null) return null; + + const actions = h([ + h(Button, { + icon: "arrow-up", + disabled: ix === 0, + onClick() { + onSelectUnit(unitData[ix - 1]?.unit_id); + }, + }), + h(Button, { + icon: "arrow-down", + disabled: ix === unitData.length - 1, + onClick() { + onSelectUnit(unitData[ix + 1]?.unit_id); + }, + }), + ]); + + return h(UnitDetailsPanel, { + unit: selectedUnit, + onClose(event) { + onSelectUnit(null); + }, + className, + actions, + showLithologyProportions: true, + selectUnit: onSelectUnit, + columnUnits: unitData, + features, + ...rest, + }); +} diff --git a/packages/column-views/src/unit-details/panel.module.sass b/packages/column-views/src/unit-details/panel.module.sass index 15a0b2588..3ce404a58 100644 --- a/packages/column-views/src/unit-details/panel.module.sass +++ b/packages/column-views/src/unit-details/panel.module.sass @@ -1,39 +1,43 @@ .legend-panel-header display: flex justify-content: space-between - align-items: baseline - gap: 1em + align-items: center + gap: 0.5em border-bottom: 1px solid var(--panel-rule-color) position: sticky top: 0 background-color: var(--panel-secondary-background-color) padding: 0.5em 1em z-index: 50 + min-height: 45px + overflow: hidden .title-container - display: flex - flex-direction: row gap: 0.5em align-items: center - margin: -0.5em 0 + margin: 0 + flex-grow: 1 + z-index: 5 &:hover - .hidden-actions - display: flex - opacity: 0.5 + .hidden-actions-container + visibility: visible h1, h2, h3 - margin: 0.5em 0 - .hidden-actions - display: none + display: inline + margin: 0 + .hidden-actions-container + visibility: hidden button transform: translateY(0px) code font-size: 0.9em color: var(--secondary-color) + .unit-id:before + content: '#' + // Tertiary color is too light... + color: var(--secondary-color) + .spacer flex: 1 - button - line-height: 1.5 - transform: translateY(2px) .unit-details-panel display: flex @@ -46,11 +50,11 @@ position: relative margin: 1em -.proportion, .sep, .no-units +.proportion, .sep, .no-units, .strat-name-text color: var(--secondary-color) font-style: italic -.strat-name-id, .unit-id +.strat-name-id, .identifier font-family: var(--monospace-font) color: var(--secondary-color) @@ -81,4 +85,24 @@ margin: 0 0.3em .clickable - cursor: pointer \ No newline at end of file + cursor: pointer + +.hidden-actions-container + min-width: 0 + display: inline-block + //margin-right: -5em + width: 0 + position: relative + +.hidden-actions + padding-left: 1em + width: fit-content + display: flex + align-items: center + gap: 0.25em + background-color: var(--panel-secondary-background-color) + z-index: 50 + position: relative + bottom: 0 + button + color: var(--secondary-color) !important diff --git a/packages/column-views/src/unit-details/panel.ts b/packages/column-views/src/unit-details/panel.ts index 38affdb91..2988fa92d 100644 --- a/packages/column-views/src/unit-details/panel.ts +++ b/packages/column-views/src/unit-details/panel.ts @@ -21,6 +21,7 @@ import { UnitLongFull, Lithology, Interval, + StratUnit, } from "@macrostrat/api-types"; import { defaultNameFunction } from "../units/names"; import classNames from "classnames"; @@ -113,10 +114,15 @@ export function LegendPanelHeader({ return h("header.legend-panel-header", [ h("div.title-container", [ h.if(title != null)("h3", title), - h.if(hiddenActions != null)("div.hidden-actions", hiddenActions), + h.if(hiddenActions != null || id != null)( + "span.hidden-actions-container", + h("div.hidden-actions", [ + h.if(id != null)("code.unit-id", id), + h.if(hiddenActions != null)([hiddenActions]), + ]), + ), ]), h("div.spacer"), - h.if(id != null)("code", id), h.if(actions != null)(ButtonGroup, { minimal: true }, actions), h.if(onClose != null)(Button, { icon: "cross", @@ -159,7 +165,12 @@ function UnitDetailsContent({ features?: Set; onClickItem?: ( event: MouseEvent, - item: Lithology | Environment | UnitLong | Interval, + item: + | Lithology + | Environment + | UnitLong + | Interval + | { strat_name_id: number }, ) => void; getItemHref?: (item: Lithology | Environment | UnitLong) => string | null; }) { @@ -222,7 +233,7 @@ function UnitDetailsContent({ /** We are trying to move away from passing the "color" parameter in the API */ let colorSwatch: ReactNode = null; - if ("color" in unit) { + if ("color" in unit && features.has(UnitDetailsFeature.Color)) { const unit1 = unit as UnitLongFull; colorSwatch = h("div.color-swatch", { style: { backgroundColor: unit1.color }, @@ -250,20 +261,9 @@ function UnitDetailsContent({ onClickItem, getItemHref, }), - h.if(unit.strat_name_id != null)( - DataField, - { - label: "Stratigraphic name", - }, - h( - "span", - { - className: "strat-name-id" + (onClickItem ? " clickable" : ""), - onClick: (e) => onClickItem(e, { strat_name_id: unit.strat_name_id }), - }, - unit.strat_name_id, - ), - ), + h.if(unit.strat_name_id != null)(StratNameField, { + strat_name_id: unit.strat_name_id, + }), outcropField, h.if(features.has(UnitDetailsFeature.AdjacentUnits))([ h( @@ -278,12 +278,61 @@ function UnitDetailsContent({ ), ]), colorSwatch, + h(ReferencesField, { refs: unit.refs, inline: true }), + ]); +} + +export function ReferencesField({ refs, className = null, ...rest }) { + if (refs == null || refs.length === 0) { + return null; + } + return h( + DataField, + { + label: "Source", + className: classNames("refs-field", className), + ...rest, + }, + h(BibInfo, { refs }), + ); +} + +function StratNameField({ + strat_name_id, + onClickItem, +}: { + strat_name_id: number; + onClickItem?: (event: MouseEvent, item: { strat_name_id: number }) => void; +}) { + const stratNames = useMemo(() => [strat_name_id], [strat_name_id]); + const data = useMacrostratData("strat_names", stratNames); + const stratNameData = data?.[0]; + let inner: any = h(Identifier, { id: strat_name_id }); + const name = stratNameData?.strat_name_long; + if (name != null) { + inner = h("span.strat-name", name); + } + + const clickable = onClickItem != null; + + const className = classNames({ + clickable, + }); + + return h( + DataField, + { + label: "Stratigraphic name", + }, h( - DataField, - { label: "Source", inline: true }, - h(BibInfo, { refs: unit.refs }), + clickable ? "a" : "span", + { + className, + onClick: (e) => onClickItem(e, { strat_name_id }), + }, + inner, ), - ]); + ); } function getThickness(unit): [string, string] { @@ -331,12 +380,18 @@ function BibInfo({ refs }) { } if (refData.length == 1) { - return h(Citation, { data: refData[0], tag: "span" }); + return h(Citation, { + data: refData[0], + tag: "span", + key: refData[0].ref_id, + }); } return h( "ul.refs", - refData.map((data, i) => h(Citation, { data, tag: "li", key: i })), + refData.map((data, i) => + h(Citation, { data, tag: "li", key: data.ref_id }), + ), ); } @@ -474,6 +529,33 @@ function enhanceLithologies( }); } +export function Identifier({ + id, + onClick, + className, +}: { + id: number | string; + onClick?: (id: number | string) => void; + className?: string; +}) { + /** An item that displays a numeric identifier, optionally clickable */ + const tag = onClick != null ? "a" : "span"; + return h( + tag, + { + onClick() { + onClick?.(id); + }, + className: classNames( + "identifier", + { clickable: onClick != null }, + className, + ), + }, + id, + ); +} + function UnitIDList({ units, selectUnit }) { const u1 = units.filter((d) => d != 0); @@ -489,19 +571,15 @@ function UnitIDList({ units, selectUnit }) { return h( ItemList, { className: "unit-id-list" }, - u1.map((unit) => { - return h( - tag, - - { - className: "unit-id", - onClick() { - selectUnit?.(unit.id); - }, - key: unit.id, + u1.map((unitID) => { + return h(Identifier, { + className: "unit-id", + onClick() { + selectUnit?.(unitID); }, - unit, - ); + key: unitID, + id: unitID, + }); }), ); } diff --git a/packages/column-views/src/units/selection.ts b/packages/column-views/src/units/selection.ts index e08823739..e83da09b5 100644 --- a/packages/column-views/src/units/selection.ts +++ b/packages/column-views/src/units/selection.ts @@ -93,7 +93,6 @@ export function UnitSelectionProvider(props: { } }, onUnitSelected: (unit: T, target: HTMLElement, event: PointerEvent) => { - console.log("onUnitSelected", unit, target, event); const el = props.columnRef?.current; let overlayPosition = null; diff --git a/packages/column-views/stories/column-navigation.stories.ts b/packages/column-views/stories/column-navigation.stories.ts index 725cd5a1b..aa961bd02 100644 --- a/packages/column-views/stories/column-navigation.stories.ts +++ b/packages/column-views/stories/column-navigation.stories.ts @@ -2,13 +2,12 @@ import h from "@macrostrat/hyper"; import { Meta } from "@storybook/react-vite"; import "@macrostrat/style-system"; import { ColumnStoryUI } from "./column-ui"; -import { useArgs } from "storybook/preview-api"; import { MinimalUnit } from "../src/units/boxes"; import { BoundaryAgeModelOverlay, EnvironmentColoredUnitComponent, } from "../src"; -import { useCallback } from "react"; +import { useColumnSelection } from "./column-ui/utils"; const baseArgTypes = { columnID: { @@ -85,27 +84,6 @@ export default { }, } as Meta; -function useColumnSelection() { - const [{ columnID, selectedUnit }, updateArgs] = useArgs(); - const setColumn = (columnID) => { - updateArgs({ columnID }); - }; - - const setSelectedUnit = useCallback( - (selectedUnit) => { - updateArgs({ selectedUnit }); - }, - [updateArgs], - ); - - return { - columnID, - selectedUnit, - setColumn, - setSelectedUnit, - }; -} - function Template(args) { return h(ColumnStoryUI, { ...args, diff --git a/packages/column-views/stories/column-page.stories.module.sass b/packages/column-views/stories/column-page.stories.module.sass new file mode 100644 index 000000000..58bdc156b --- /dev/null +++ b/packages/column-views/stories/column-page.stories.module.sass @@ -0,0 +1,27 @@ +.column-ui + display: flex + flex-direction: row + justify-content: center + gap: 1em + +.column-container, .column-view + width: 600px + +.right-column + width: 400px + max-height: 100vh + position: sticky + top: 0 + padding: 2em 0 + gap: 1em + display: flex + flex-direction: column + &>* + border-radius: 6px + overflow: hidden + box-shadow: 0 3px 2px var(--card-shadow-color) + border: 1px solid var(--panel-rule-color) + +.column-selector-map + height: 350px + border-radius: 1em diff --git a/packages/column-views/stories/column-page.stories.ts b/packages/column-views/stories/column-page.stories.ts new file mode 100644 index 000000000..ca3794b8c --- /dev/null +++ b/packages/column-views/stories/column-page.stories.ts @@ -0,0 +1,116 @@ +import hyper from "@macrostrat/hyper"; +import { Meta } from "@storybook/react-vite"; +import "@macrostrat/style-system"; +import { + ColoredUnitComponent, + Column, + ColumnNavigationMap, + UnitDetailsPanelWithNavigation, + ReferencesField, + UnitDetailsFeature, + Identifier, + ColumnBasicInfo, +} from "../src"; +import { useColumnBasicInfo, useColumnUnits } from "./column-ui/utils"; +import styles from "./column-page.stories.module.sass"; +import { UnitLong } from "@macrostrat/api-types"; +import { useArgs } from "storybook/preview-api"; +import { DataField } from "@macrostrat/data-components"; +import { FlexRow } from "@macrostrat/ui-components"; + +export default { + title: "Column views/Column page", + component: ColumnStoryUI, + args: { + columnID: 494, + selectedUnitID: 15160, + }, +} as Meta; + +function Template(args) { + return h(ColumnStoryUI, { + ...args, + ...useColumnSelection(), + }); +} + +export const Primary = Template.bind({}); + +const h = hyper.styled(styles); + +const mapboxToken = import.meta.env.VITE_MAPBOX_API_TOKEN; + +function ColumnStoryUI({ + columnID, + setColumnID, + setSelectedUnitID, + selectedUnitID, + inProcess, + projectID, + ...rest +}) { + const units: UnitLong[] = (useColumnUnits(columnID, inProcess) as any) ?? []; + const info = useColumnBasicInfo(columnID, inProcess); + + // Sync props with internal state + const selectedUnit = units?.find((d) => d.unit_id === selectedUnitID) ?? null; + + return h("div.column-ui", [ + h("div.column-container", [ + h(ColumnBasicInfo, { data: info }), + h(Column, { + key: columnID, + units, + selectedUnit: selectedUnitID, + onUnitSelected: setSelectedUnitID, + unconformityLabels: true, + keyboardNavigation: true, + columnWidth: 300, + showUnitPopover: false, + width: 450, + unitComponent: ColoredUnitComponent, + collapseSmallUnconformities: false, + targetUnitHeight: 20, + ...rest, + }), + ]), + h("div.right-column", [ + h(ColumnNavigationMap, { + inProcess, + projectID, + accessToken: mapboxToken, + selectedColumn: columnID, + onSelectColumn: setColumnID, + className: "column-selector-map", + }), + h.if(selectedUnit != null)(UnitDetailsPanelWithNavigation, { + unitData: units, + className: "unit-details-panel", + selectedUnit, + onSelectUnit: setSelectedUnitID, + features: new Set([ + UnitDetailsFeature.DepthRange, + UnitDetailsFeature.JSONToggle, + ]), + }), + ]), + ]); +} + +function useColumnSelection() { + const [{ columnID, selectedUnitID }, updateArgs] = useArgs(); + const setColumnID = (columnID) => { + updateArgs({ columnID }); + }; + + const setSelectedUnitID = (selectedUnitID) => { + updateArgs({ selectedUnitID }); + }; + + return { + columnID, + selectedUnitID, + setColumnID, + setSelectedUnitID, + }; +} diff --git a/packages/column-views/stories/column-ui/story-ui.ts b/packages/column-views/stories/column-ui/story-ui.ts index 5db902663..f4a309ea9 100644 --- a/packages/column-views/stories/column-ui/story-ui.ts +++ b/packages/column-views/stories/column-ui/story-ui.ts @@ -8,6 +8,7 @@ import styles from "./story-ui.module.sass"; import { Spinner } from "@blueprintjs/core"; import { useColumnBasicInfo, useColumnUnits } from "./utils"; +import { UnitLong } from "@macrostrat/api-types"; const mapboxToken = import.meta.env.VITE_MAPBOX_API_TOKEN; @@ -53,7 +54,7 @@ function ColumnCore({ setSelectedUnit, ...rest }) { - const units = useColumnUnits(col_id, inProcess); + const units = useColumnUnits(col_id, inProcess) as any as UnitLong[]; const info = useColumnBasicInfo(col_id, inProcess); if (units == null || info == null) { diff --git a/packages/column-views/stories/column-ui/utils.ts b/packages/column-views/stories/column-ui/utils.ts index e3150a189..03b49058b 100644 --- a/packages/column-views/stories/column-ui/utils.ts +++ b/packages/column-views/stories/column-ui/utils.ts @@ -1,6 +1,7 @@ import { useAPIResult } from "@macrostrat/ui-components"; -import { useMemo } from "react"; +import { useMemo, useCallback } from "react"; import { BaseUnit } from "@macrostrat/api-types"; +import { useArgs } from "storybook/preview-api"; export function useColumnUnits(col_id, inProcess = false): BaseUnit[] | null { const status_code = inProcess ? "in process" : undefined; @@ -23,7 +24,28 @@ export function useColumnBasicInfo(col_id, inProcess = false) { "https://macrostrat.org/api/v2/columns", params, (res) => { - return res.success.data[0]; + return res.success?.data[0]; }, ); } + +export function useColumnSelection() { + const [{ columnID, selectedUnit }, updateArgs] = useArgs(); + const setColumn = (columnID) => { + updateArgs({ columnID }); + }; + + const setSelectedUnit = useCallback( + (selectedUnit) => { + updateArgs({ selectedUnit }); + }, + [updateArgs], + ); + + return { + columnID, + selectedUnit, + setColumn, + setSelectedUnit, + }; +} diff --git a/packages/column-views/stories/static-column.stories.ts b/packages/column-views/stories/static-column.stories.ts index f3c67f661..a48ec817b 100644 --- a/packages/column-views/stories/static-column.stories.ts +++ b/packages/column-views/stories/static-column.stories.ts @@ -1,6 +1,6 @@ import h from "@macrostrat/hyper"; -import { ComponentStory, ComponentMeta } from "@storybook/react-vite"; +import { StoryFn, Meta } from "@storybook/react-vite"; import { Column } from "../src"; import res from "./data/black-mesa-basin-490.json"; @@ -9,11 +9,10 @@ export default { title: "Column views/Static column", component: Column, description: "A column rendered using static units", -} as ComponentMeta; +} as Meta; // More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args -const Template: ComponentStory = (args) => - h(Column, { ...args }); +const Template: StoryFn = (args) => h(Column, { ...args }); export function BlackMesaBasin() { const units = res.success.data; diff --git a/packages/data-components/CHANGELOG.md b/packages/data-components/CHANGELOG.md index 3706057b7..c65a49ae7 100644 --- a/packages/data-components/CHANGELOG.md +++ b/packages/data-components/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## [0.2.1] - 2025-08-22 + +- Improvements to Rockd checkin component +- Added `row` style option to `DataField` + ## [0.2.0] - 2025-06-25 - onClick capability added to LithologyTag, IntervalTag, and EnvironmentList, diff --git a/packages/data-components/package.json b/packages/data-components/package.json index 36d7ec357..3f8ef0c6f 100644 --- a/packages/data-components/package.json +++ b/packages/data-components/package.json @@ -1,6 +1,6 @@ { "name": "@macrostrat/data-components", - "version": "0.2.0", + "version": "0.2.1", "description": "A library of React components tailored for Macrostrat data and endpoints", "type": "module", "source": "src/index.ts", diff --git a/packages/data-components/src/components/unit-details/base.module.sass b/packages/data-components/src/components/unit-details/base.module.sass index b1f23d8f6..772a3fb77 100644 --- a/packages/data-components/src/components/unit-details/base.module.sass +++ b/packages/data-components/src/components/unit-details/base.module.sass @@ -6,6 +6,12 @@ align-items: baseline justify-content: end gap: 0.5em + &.row + flex-flow: row nowrap + justify-content: space-between + .label + flex-grow: unset + .data-container text-align: right &.inline @@ -20,6 +26,8 @@ display: block color: var(--secondary-color) text-align: left + width: var(--data-field-label-width) + min-width: var(--data-field-label-min-width) .value-container text-align: right //font-size: 0.9em diff --git a/packages/data-components/src/components/unit-details/base.ts b/packages/data-components/src/components/unit-details/base.ts index d60ffe1f7..272057fd6 100644 --- a/packages/data-components/src/components/unit-details/base.ts +++ b/packages/data-components/src/components/unit-details/base.ts @@ -13,6 +13,7 @@ export function DataField({ value, inline = false, showIfEmpty = false, + row = false, className, children, unit, @@ -24,6 +25,7 @@ export function DataField({ className?: string; children?: any; unit?: string; + row?: boolean; }) { if (!showIfEmpty && (value == null || value === "") && children == null) { return null; @@ -31,7 +33,7 @@ export function DataField({ return h( "div.data-field", - { className: classNames(className, { inline, flex: !inline }) }, + { className: classNames(className, { inline, flex: !inline, row }) }, [ h("div.label", label), h.if(value != null)("div.value-container", h(Value, { value, unit })), diff --git a/packages/style-system/CHANGELOG.md b/packages/style-system/CHANGELOG.md index 90b3c1ccc..040ad37d2 100644 --- a/packages/style-system/CHANGELOG.md +++ b/packages/style-system/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.3] - 2025-08-22 + +- Adjusted box shadow color for dark mode + ## [0.2.2] - 2025-06-25 Update types and dependencies diff --git a/packages/style-system/package.json b/packages/style-system/package.json index 89418f235..7fc3141ac 100644 --- a/packages/style-system/package.json +++ b/packages/style-system/package.json @@ -1,6 +1,6 @@ { "name": "@macrostrat/style-system", - "version": "0.2.2", + "version": "0.2.3", "description": "Style system for Macrostrat", "main": "dist/style-system.css", "source": "src/index.ts", diff --git a/packages/style-system/src/colors.scss b/packages/style-system/src/colors.scss index 671e941fd..ca9b3e0ab 100644 --- a/packages/style-system/src/colors.scss +++ b/packages/style-system/src/colors.scss @@ -63,7 +63,7 @@ $dark-colors: ( // light gray 1, accent-border-color: #5f6b7c, // gray5, - card-shadow-color: color.adjust($gray1, $alpha: 0.2), + card-shadow-color: rgba(255, 255, 255, 0.1), ui-color-accent-text: rgb(252, 217, 250), ui-color-accent: color.adjust(rgb(143, 39, 138), $lightness: -50%), );