diff --git a/package.json b/package.json index 9fa2f548..10c334fc 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@macrostrat/api-types": "^1.1.3", "@macrostrat/color-utils": "^1.1.2", "@macrostrat/column-components": "^1.3.1", - "@macrostrat/column-views": "^2.2.1", + "@macrostrat/column-views": "^2.2.2", "@macrostrat/data-components": "^0.2.2", "@macrostrat/data-sheet": "^2.2.1", "@macrostrat/feedback-components": "^1.1.10", @@ -122,6 +122,7 @@ "immutability-helper": "^3.1.1", "jose": "^5.1.2", "jotai": "^2.15.1", + "jotai-location": "^0.6.2", "mapbox-gl": "^3.13.0", "new-github-issue-url": "^1.0.0", "part-regex": "^0.1.2", diff --git a/pages/columns/@column/column-inspector/index.module.sass b/pages/columns/@column/column-inspector/index.module.sass index 6f081ef4..0afaa06a 100644 --- a/pages/columns/@column/column-inspector/index.module.sass +++ b/pages/columns/@column/column-inspector/index.module.sass @@ -27,11 +27,41 @@ top: 0 display: flex flex-direction: column - padding: 2em 0 + padding: 2em 1em + overflow-y: scroll & > * border-radius: 4px + h3 + color: var(--secondary-color) + +.column-settings-panel + :global + .bp5-form-group, .bp5-control-group + .bp5-label + color: var(--text-color) + flex-grow: 1 + +.facet-control + :global(.bp5-html-select).unset select + color: var(--secondary-color) + font-style: italic + + :global(.bp5-label) .facet-label :global(.bp5-popover-target) + margin-left: 0.2em + display: inline + +.range-control + :global + .bp5-form-group, .bp5-control-group + flex: 1 + justify-content: space-between + + .bp5-control-group > * + flex-grow: unset + flex-shrink: unset + .right-column-boxes display: flex flex-direction: column @@ -60,8 +90,7 @@ top: 2.2em h3 - color: #444 - margin-bottom: 0.3em + color: var(--secondary-color) .default-buttons width: 100% diff --git a/pages/columns/@column/column-inspector/index.ts b/pages/columns/@column/column-inspector/index.ts index 5b67f62e..fd0f2189 100644 --- a/pages/columns/@column/column-inspector/index.ts +++ b/pages/columns/@column/column-inspector/index.ts @@ -2,6 +2,7 @@ import { ColoredUnitComponent, Column, DetritalColumn, + ExtUnit, FossilDataType, HybridScaleType, Identifier, @@ -11,7 +12,7 @@ import { ReferencesField, } from "@macrostrat/column-views"; import { hyperStyled } from "@macrostrat/hyper"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { ReactNode, useCallback, useEffect, useMemo, useRef } from "react"; import { apiV2Prefix } from "@macrostrat-web/settings"; import { PatternProvider } from "~/_providers"; import styles from "./index.module.sass"; @@ -20,13 +21,159 @@ import { navigate } from "vike/client/router"; import { SGPMeasurementsColumn, StableIsotopesColumn } from "./facets"; import { ModalUnitPanel } from "./modal-panel"; -import { PageBreadcrumbs } from "~/components"; +import { AlphaTag, BetaTag, PageBreadcrumbs } from "~/components"; import { onDemand } from "~/_utils"; -import { ErrorBoundary } from "@macrostrat/ui-components"; -import { DataField } from "@macrostrat/data-components"; +import { CollapsePanel, ErrorBoundary } from "@macrostrat/ui-components"; +import { DataField, Parenthetical } from "@macrostrat/data-components"; import { ColumnAxisType } from "@macrostrat/column-components"; -import { atom, useAtom, useAtomValue } from "jotai"; -import { Button, FormGroup, HTMLSelect } from "@blueprintjs/core"; +import { atom, useAtom, useAtomValue, useSetAtom, WritableAtom } from "jotai"; +import { + Button, + Collapse, + ControlGroup, + FormGroup, + HTMLSelect, + NumericInput, +} from "@blueprintjs/core"; +import { useHydrateAtoms } from "jotai/utils"; + +interface ColumnHashState { + unit?: number; + t_age?: number; + b_age?: number; + t_pos?: number; + b_pos?: number; + axis?: string; + facet?: string; + scale?: number; +} + +function validateInt(value: string | null): number | undefined { + if (value == null) return undefined; + const id = parseInt(value); + if (isNaN(id)) return undefined; + return id; +} + +function validateNumber(value: string | null): number | undefined { + if (value == null) return undefined; + const num = parseFloat(value); + if (isNaN(num)) return undefined; + return num; +} + +function validateValues( + value: string | null, + options: string[] +): T | undefined { + if (value == null) return undefined; + if (options.includes(value)) { + return value as T; + } + return undefined; +} + +function validateAxis(value: string | null): ColumnAxisType | undefined { + const validAxes = Object.values(ColumnAxisType); + return validateValues(value, validAxes); +} + +const facets = [ + { label: "None", value: "none" }, + { label: "Carbon/oxygen isotopes", value: "stable-isotopes" }, + { label: "SGP", value: "sgp-samples" }, + { label: "Fossils (taxa)", value: "fossil-taxa" }, + { label: "Fossils (collections)", value: "fossil-collections" }, + { label: "Detrital zircons", value: "detrital-zircons" }, +]; + +const validFacets = facets.map((d) => d.value).filter((d) => d != "none"); + +function getStateFromHash(): ColumnHashState { + const hash = document.location.hash.substring(1); + const params = new URLSearchParams(hash); + const state: ColumnHashState = {}; + + state.facet = validateValues( + params.get("facet"), + validFacets as string[] + ); + state.axis = validateAxis(params.get("axis")); + state.unit = validateInt(params.get("unit")); + for (const key of ["t_age", "b_age", "t_pos", "b_pos", "scale"]) { + state[key] = validateNumber(params.get(key)); + } + + return state; +} + +function setHashFromState(state: ColumnHashState) { + if (window == null) return; + let params = new URLSearchParams(); + for (const key in state) { + const value = state[key]; + if (value == null) { + params.delete(key); + } else { + params.set(key, value.toString()); + } + } + let newHash = params.toString(); + let newURL = document.location.pathname; + if (newHash !== "") { + newURL += `#${newHash}`; + } + newURL += document.location.search; + + if (newHash !== document.location.hash) { + history.replaceState(null, document.title, newURL); + } +} + +const hashStateAtom = atom(getStateFromHash()); + +function atomWithHashParam(key: keyof ColumnHashState) { + return atom( + (get) => { + const hashState = get(hashStateAtom); + return hashState[key] as T; + }, + (get, set, newValue: number | null) => { + set(hashStateAtom, (prev) => { + console.log("Updating hash state", key, newValue); + return { ...prev, [key]: newValue }; + }); + } + ); +} + +const selectedUnitIDAtom = atomWithHashParam("unit"); +const selectedUnitAtom = atom((get) => { + const units = get(unitsAtom); + const selectedUnitID = get(selectedUnitIDAtom); + if (selectedUnitID == null) return null; + return units.find((d) => d.unit_id == selectedUnitID) ?? null; +}); + +const validateSelectedUnitIDAtom = atom(null, (get, set) => { + const units = get(unitsAtom); + const selectedUnitID = get(selectedUnitIDAtom); + const unitIDs = units.map((d) => d.unit_id); + if (!unitIDs.includes(selectedUnitID)) { + // Clear invalid selection + set(selectedUnitIDAtom, null); + } +}); + +const axisTypeAtom = atomWithHashParam("axis"); +const facetAtom = atomWithHashParam("facet"); + +const t_ageAtom = atomWithHashParam("t_age"); +const b_ageAtom = atomWithHashParam("b_age"); +const t_posAtom = atomWithHashParam("t_pos"); +const b_posAtom = atomWithHashParam("b_pos"); + +const pixelScaleAtom = atomWithHashParam("scale"); const ColumnMap = onDemand(() => import("./map").then((mod) => mod.ColumnMap)); @@ -40,9 +187,6 @@ export function ColumnPage(props) { ); } -const heightAxisTypeAtom = atom(); -const facetAtom = atom(); - function inferHeightAxisType(axisType: ColumnAxisType, units): ColumnAxisType { if (axisType !== ColumnAxisType.HEIGHT && axisType !== ColumnAxisType.DEPTH) { return axisType; @@ -62,20 +206,68 @@ function inferHeightAxisType(axisType: ColumnAxisType, units): ColumnAxisType { return null; } +interface ColumnInfo { + col_id: number; + col_type: "section" | "column"; + units: ExtUnit[]; +} + +const columnInfoAtom = atom(); + +const columnTypeAtom = atom<"section" | "column">((get) => { + return get(columnInfoAtom).col_type; +}); + +const unitsAtom = atom((get) => { + return get(columnInfoAtom).units; +}); + +const defaultAxisTypeAtom = atom((get) => { + const columnType = get(columnTypeAtom); + const isSection = columnType === "section"; + return isSection ? ColumnAxisType.HEIGHT : ColumnAxisType.AGE; +}); + +const inferredAxisTypeAtom = atom((get) => { + /** Column axis type, inferred from column type if not set by user */ + return get(axisTypeAtom) ?? get(defaultAxisTypeAtom); +}); + +const heightAxisTypeAtom = atom((get) => { + const inferredAxisType = get(inferredAxisTypeAtom); + const units = get(unitsAtom); + return inferHeightAxisType(inferredAxisType, units); +}); + +function useUpdateAtoms(atomsWithValues: [WritableAtom, any][]) { + useHydrateAtoms(atomsWithValues); + const setAtoms = atomsWithValues.map(([atom]) => useSetAtom(atom)); + useEffect( + () => { + atomsWithValues.forEach(([atom, value], i) => { + setAtoms[i](value); + }); + }, + atomsWithValues.map(([, value]) => value) + ); +} + function ColumnPageInner({ columnInfo, linkPrefix = "/", projectID }) { - const { units } = columnInfo; + const { units, col_id } = columnInfo; - const userSetAxisType = useAtomValue(heightAxisTypeAtom); const isSection = columnInfo.col_type == "section"; - console.log(userSetAxisType); + useUpdateAtoms([[columnInfoAtom, columnInfo]]); + + const validateSelectedUnitID = useSetAtom(validateSelectedUnitIDAtom); - const defaultAxisType = isSection - ? ColumnAxisType.HEIGHT - : ColumnAxisType.AGE; + const pixelScale = useAtomValue(pixelScaleAtom); + + useEffect(() => { + validateSelectedUnitID(); + }, [col_id]); - let axisType = userSetAxisType ?? defaultAxisType; - axisType = inferHeightAxisType(axisType, units); + let axisType = useAtomValue(heightAxisTypeAtom); let facetType = useAtomValue(facetAtom); const facetElement = useMemo(() => { @@ -86,13 +278,11 @@ function ColumnPageInner({ columnInfo, linkPrefix = "/", projectID }) { let hybridScale = null; let maxInternalColumns = undefined; - let unconformityLabels = true; let showTimescale = true; if (isSection) { maxInternalColumns = 1; if (axisType !== ColumnAxisType.AGE) { - unconformityLabels = false; showTimescale = false; } } else if ( @@ -107,35 +297,33 @@ function ColumnPageInner({ columnInfo, linkPrefix = "/", projectID }) { if (axisType == ColumnAxisType.ORDINAL) { // For ordinal columns, use the fancier "equidistant-surfaces" scale + // We should be able to use height-based scale for height-based columns, but that isn't supported yet axisType = ColumnAxisType.AGE; hybridScale = { type: HybridScaleType.EquidistantSurfaces }; maxInternalColumns = undefined; } - const [selectedUnitID, setSelectedUnitID] = useState( - getInitialSelectedUnitID - ); + const { t_age, b_age, t_pos, b_pos } = useAtomValue(hashStateAtom); + + const [selectedUnitID, setSelectedUnitID] = + useAtom(selectedUnitIDAtom); + + const selectedUnit = useAtomValue(selectedUnitAtom); - const selectedUnit = useMemo(() => { - if (selectedUnitID == null) return null; - return units.find((d) => d.unit_id == selectedUnitID); - }, [selectedUnitID]); + const hashParams = useAtomValue(hashStateAtom); useEffect(() => { - setHashString(selectedUnitID); - }, [selectedUnitID]); - - const onSelectColumn = useCallback( - (col_id: number) => { - // do nothing - // We could probably find a more elegant way to do this - navigate(`${linkPrefix}columns/${col_id}${window.location.hash}`, { - overwriteLastHistoryEntry: true, - }); - }, - [setSelectedUnitID] - ); + setHashFromState(hashParams); + }, [hashParams]); + + const onSelectColumn = useCallback((col_id: number) => { + // do nothing + // We could probably find a more elegant way to do this + navigate(`${linkPrefix}columns/${col_id}${window.location.hash}`, { + overwriteLastHistoryEntry: true, + }); + }, []); let assistantContent = h(ColumnGlobalModal, { data: columnInfo }); @@ -173,9 +361,14 @@ function ColumnPageInner({ columnInfo, linkPrefix = "/", projectID }) { h( Column, { + /** TODO: we ideally would not have to force a re-render like this. + * It is very expensive given the complexity of the column view. + * However, not doing this results in artifacts (particularly with + * label rendering) when columns are switched. + */ units, unitComponent: ColoredUnitComponent, - unconformityLabels, + unconformityLabels: "minimal", collapseSmallUnconformities: true, showTimescale, axisType, @@ -186,6 +379,11 @@ function ColumnPageInner({ columnInfo, linkPrefix = "/", projectID }) { maxInternalColumns, showLabelColumn, hybridScale, + pixelScale, + t_age: t_age ?? 0, + b_age: b_age ?? 4500, + t_pos, + b_pos, }, children ), @@ -269,92 +467,196 @@ function facetElements(facet: string | null, columnID: number) { } function ColumnSettingsPanel() { - return h("div.column-settings-panel", [h(AxisTypeControl), h(FacetControl)]); + const axisType = useAtomValue(heightAxisTypeAtom); + const isHeightAxis = + axisType === ColumnAxisType.HEIGHT || axisType === ColumnAxisType.DEPTH; + + let unit = "pixels/Myr"; + if (isHeightAxis) { + unit = "pixels/m"; + } else if (axisType === ColumnAxisType.ORDINAL) { + unit = "pixels/surface"; + } + + let heightAxisLabel = "Height"; + if (axisType === ColumnAxisType.DEPTH) { + heightAxisLabel = "Depth"; + } + + return h("div.column-settings-panel", [ + h("h3", "Settings"), + h(AxisTypeControl), + h(FacetControl), + h(RangeControl, { + label: "Age range", + unit: "Ma", + topAtom: t_ageAtom, + bottomAtom: b_ageAtom, + }), + h.if(isHeightAxis)(RangeControl, { + label: heightAxisLabel + " range", + unit: "m", + topAtom: t_posAtom, + bottomAtom: b_posAtom, + }), + h(NumericAtomControl, { + label: h("span", [ + "Fixed scale", + " ", + h(Parenthetical, { className: "unit" }, unit), + ]), + atom: pixelScaleAtom, + }), + ]); +} + +function ClearButton({ value, setValue, disabled = null }) { + return h(Button, { + minimal: true, + small: true, + icon: "cross", + disabled: disabled ?? value == null, + onClick: () => setValue(null), + }); +} + +function AtomClearButton({ atom, disabled }) { + const [value, setValue] = useAtom(atom); + return h(ClearButton, { value, setValue, disabled }); +} + +function NumericAtomControl({ + label, + atom, + placeholder, +}: { + label: string; + atom: typeof t_ageAtom; + placeholder?: string; +}) { + const [value, setValue] = useAtom(atom); + return h( + FormGroup, + { label, inline: true }, + h(ControlGroup, { fill: false }, [ + h(AtomNumericInput, { + atom, + placeholder, + }), + h(AtomClearButton, { atom }), + ]) + ); +} + +function RangeControl({ + label, + unit, + topAtom, + bottomAtom, + disabled, +}: { + label: string; + topAtom: WritableAtom; + bottomAtom: WritableAtom; + disabled?: boolean; + placeholder?: string; + unit?: string; +}) { + const bothSetAtom = useRef( + atom( + (get) => { + return get(topAtom) ?? get(bottomAtom); + }, + (get, set) => { + set(bottomAtom, null); + set(topAtom, null); + } + ) + ); + let labelEl: ReactNode = label; + if (unit != null) { + labelEl = h("span", [ + label, + " ", + h(Parenthetical, { className: "unit" }, unit), + ]); + } + + return h( + FormGroup, + { label: labelEl, inline: false, className: "range-control", disabled }, + h(ControlGroup, { fill: true }, [ + h(AtomNumericInput, { + atom: bottomAtom, + placeholder: "Bottom", + }), + h(AtomNumericInput, { + atom: topAtom, + placeholder: "Top", + }), + h(AtomClearButton, { + atom: bothSetAtom.current, + }), + ]) + ); +} + +function AtomNumericInput({ atom, ...rest }) { + const [value, setValue] = useAtom(atom); + return h(NumericInput, { + value: value ?? "", + onValueChange: setValue, + ...rest, + }); } function AxisTypeControl() { const optionsValues = Object.entries(ageAxisOptions).map(([k, v]) => { return { label: k, value: v }; }); - const [axisType, setAxisType] = useAtom(heightAxisTypeAtom); + const axisType = useAtomValue(inferredAxisTypeAtom); + const defaultAxisType = useAtomValue(defaultAxisTypeAtom); + const setAxisType = useSetAtom(axisTypeAtom); return h( FormGroup, { label: "Axis type", inline: true }, - h([ + h(ControlGroup, { fill: true }, [ h(HTMLSelect, { options: optionsValues, value: axisType, onChange: (evt) => { const value = evt.target.value as ColumnAxisType; // set axis type - console.log("Selected axis type:", value); setAxisType(value); }, }), - h(Button, { - minimal: true, - small: true, - icon: "cross", - disabled: axisType == null, - onClick: () => setAxisType(null), + h(ClearButton, { + value: axisType, + setValue: setAxisType, + disabled: axisType === defaultAxisType, }), ]) ); } -const facets = [ - { label: "None", value: null }, - { label: "Carbon/oxygen isotopes", value: "stable-isotopes" }, - { label: "SGP", value: "sgp-samples" }, - { label: "Fossils (taxa)", value: "fossil-taxa" }, - { label: "Fossils (collections)", value: "fossil-collections" }, - { label: "Detrital zircons", value: "detrital-zircons" }, -]; - function FacetControl() { const [facet, setFacet] = useAtom(facetAtom); return h("div.facet-control", [ h( FormGroup, - { label: "Facet", inline: true }, - h(HTMLSelect, { - options: facets, - value: facet, - onChange: (evt) => { - const value = evt.target.value; - setFacet(value); - }, - }) + { label: h("span.facet-label", ["Facet ", h(AlphaTag, {content: "This feature is in early development"})]), inline: true }, + h(ControlGroup, { fill: true }, [ + h(HTMLSelect, { + options: facets, + value: facet ?? "none", + className: facet == null ? "unset" : null, + onChange: (evt) => { + const value = evt.target.value; + setFacet(value === "none" ? null : value); + }, + }), + h(ClearButton, { value: facet, setValue: setFacet }), + ]) ), ]); } - -function getHashParams() { - // Harvest selected unit ID from hash string - const currentHash = document.location.hash.substring(1); - return new URLSearchParams(currentHash); -} - -function getInitialSelectedUnitID() { - // Harvest selected unit ID from hash string - const params = getHashParams(); - const unit_id = params.get("unit"); - // If no unit_id, return null - if (unit_id == null) return null; - const id = parseInt(unit_id); - if (isNaN(id)) return null; - return id; -} - -function setHashString(selectedUnitID: number) { - const params = getHashParams(); - params.delete("unit"); - if (selectedUnitID != null) { - params.set("unit", selectedUnitID.toString()); - } - console.log(selectedUnitID, params); - const newHash = params.toString(); - if (newHash !== document.location.hash) { - document.location.hash = newHash; - } -} diff --git a/pages/maps/main.module.sass b/pages/maps/main.module.sass index f8cd27ff..10f49529 100644 --- a/pages/maps/main.module.sass +++ b/pages/maps/main.module.sass @@ -35,10 +35,6 @@ display: inline-block font-size: 0.8em -h1, -h3 - margin: 0 - .maps-list padding: 0 display: flex @@ -189,4 +185,4 @@ h3 .top-row display: flex - gap: .5em \ No newline at end of file + gap: .5em diff --git a/src/components/general/index.ts b/src/components/general/index.ts index fbee82c9..4c809eba 100644 --- a/src/components/general/index.ts +++ b/src/components/general/index.ts @@ -172,25 +172,43 @@ export function IDTag({ id }) { return h("div.id-tag", "ID: #" + id); } -export function BetaTag({ content = "This page is in beta and may be incomplete."} ) { +export function BetaTag({ + content = "This page is in beta and may be incomplete.", +}) { + let _content = content; + if (typeof content === "string") { + _content = h("div.tag-content", content); + } + return h( Popover, { - content: h("div.tag-content", content), - interactionKind: "hover" + content: _content, + interactionKind: "hover", + inline: true, }, - [h(Tag, { intent: "warning" }, "Beta")] + h(Tag, { intent: "warning" }, "Beta") ); } -export function AlphaTag({ content = "This page is in alpha and highly experimental." }) { +export function AlphaTag({ + content = "This page is in alpha and highly experimental.", +}: { + content?: React.ReactNode; +}) { + let _content = content; + if (typeof content === "string") { + _content = h("div.tag-content", content); + } + return h( Popover, { - content: h("div.tag-content", content), - interactionKind: "hover" + content: _content, + interactionKind: "hover", + minimal: true, }, - [h(Tag, { intent: "danger" }, "Alpha")] + h(Tag, { intent: "danger" }, "Alpha") ); } diff --git a/src/components/general/layout.module.sass b/src/components/general/layout.module.sass index 0da34645..09448fe3 100644 --- a/src/components/general/layout.module.sass +++ b/src/components/general/layout.module.sass @@ -189,6 +189,7 @@ a.macrostrat-logo-link font-weight: 400 font-family: "Maven Pro", sans-serif margin: 0 + * font-family: "Publico Text", serif @@ -205,4 +206,8 @@ a.macrostrat-logo-link height: 16px .tag-content - padding: .5em \ No newline at end of file + padding: .25em + font-size: 0.9em + font-style: italic + color: var(--secondary-color) + background-color: var(--secondary-background) diff --git a/src/components/lex/main.module.sass b/src/components/lex/main.module.sass index d198cf76..0f5f1ad5 100644 --- a/src/components/lex/main.module.sass +++ b/src/components/lex/main.module.sass @@ -1,223 +1,216 @@ .int-names + display: flex + gap: 5px + align-items: flex-end + + .strat-concept-title display: flex - gap: 5px + gap: 10px align-items: flex-end - .strat-concept-title - display: flex - gap: 10px - align-items: flex-end - - .strat-concept-name - font-size: 2em - - - .int-name - padding: 2px 6px - margin: .2em 0 - border-radius: 0.2em - font-size: 2em - display: flex - width: fit-content - - .int-abbrev - display: flex - gap: 5px - align-items: center - - p - margin: 0 - - .int-abbrev-item - padding: 2px 6px - margin: .2em 0 - border-radius: 0.2em - font-size: 1em + .strat-concept-name + font-size: 2em -.page-header + + .int-name + padding: 2px 6px + margin: .2em 0 + border-radius: 0.2em + font-size: 2em display: flex - justify-content: space-between + width: fit-content -ul, h3 - margin: 0 - padding: 0 - list-style-type: none + .int-abbrev + display: flex + gap: 5px + align-items: center + + p + margin: 0 + + .int-abbrev-item + padding: 2px 6px + margin: .2em 0 + border-radius: 0.2em + font-size: 1em + +.page-header + display: flex + justify-content: space-between .ref-list - margin-top: 1em + margin-top: 1em .table - background-color: var(--secondary-color) - color: var(--text-emphasized-color) - display: grid - grid-template-columns: 20% 80% + background-color: var(--secondary-color) + color: var(--text-emphasized-color) + display: grid + grid-template-columns: 20% 80% - p - margin: 0 - - .table-content - padding: 2em - display: flex - flex-direction: column - align-items: center - justify-content: space-between - gap: .5em + p + margin: 0 -.int-page + .table-content + padding: 2em display: flex flex-direction: column - gap: 1em + align-items: center + justify-content: space-between + gap: .5em + +.int-page + display: flex + flex-direction: column + gap: 1em .map-area-container - width: 100% !important + width: 100% !important .int-header - display: flex - justify-content: space-between + display: flex + justify-content: space-between - .sift-link - text-align: right + .sift-link + text-align: right .prevalent-taxa-container + display: flex + justify-content: space-between + + .link display: flex - justify-content: space-between + gap: 5px + justify-content: center - .link - display: flex - gap: 5px - justify-content: center - - .taxa-header - display: flex - flex-direction: column - align-items: center - justify-content: center - - .taxa - display: flex - flex-direction: column - align-items: center - gap: 10px + .taxa-header + display: flex + flex-direction: column + align-items: center + justify-content: center + + .taxa + display: flex + flex-direction: column + align-items: center + gap: 10px .divider - width: 100% + width: 100% .legend-item - display: flex - gap: 5px - align-items: center + display: flex + gap: 5px + align-items: center - a - color: var(--text-emphasized-color) !important + a + color: var(--text-emphasized-color) !important - p - margin: 0 + p + margin: 0 .box - height: 15px - width: 15px - border-radius: 3px + height: 15px + width: 15px + border-radius: 3px .charts - display: flex - justify-content: center + display: flex + justify-content: center - .chart - width: 33% + .chart + width: 33% - h3 - text-align: center + h3 + text-align: center .area - display: flex - align-items: center - justify-content: center + display: flex + align-items: center + justify-content: center .page-container - height: 100% + height: 100% .chart-container - g - cursor: pointer - .chart-title - fill: var(--text-emphasized-color) + g + cursor: pointer + + .chart-title + fill: var(--text-emphasized-color) .range - color: var(--text-emphasized-color) !important + color: var(--text-emphasized-color) !important .img-dark-mode - filter: invert(1) + filter: invert(1) .concept-info - display: flex - flex-direction: column - gap: .5em - - h3 - font-weight: bold - font-size: 1.8em + display: flex + flex-direction: column + gap: .5em - .concept-name - display: flex - gap: 5px - align-items: flex-end + h3 + font-weight: bold + font-size: 1.8em - .title - font-size: 1.2em + .concept-name + display: flex + gap: 5px + align-items: flex-end .title - font-weight: bold - color: var(--text-emphasized-color) + font-size: 1.2em + + .title + font-weight: bold + color: var(--text-emphasized-color) .concept-item - display: flex - justify-content: space-between + display: flex + justify-content: space-between .concept-header - display: flex - gap: .5em + display: flex + gap: .5em .units-list, .fossils-list, .maps-list - display: flex - flex-direction: column + display: flex + flex-direction: column .load-more-btn - border: none - width: 100px - cursor: pointer - background-color: var(--accent-secondary-color) - margin-top: 10px + border: none + width: 100px + cursor: pointer + background-color: var(--accent-secondary-color) + margin-top: 10px - &:hover - background-color: var(--accent-secondary-hover-color) + &:hover + background-color: var(--accent-secondary-hover-color) -.load-more-wrapper +.load-more-wrapper display: flex justify-content: center .match-name - padding: .2em .5em - border-radius: .2em - color: white + padding: .2em .5em + border-radius: .2em + color: white .concept-head-container - display: flex - gap: .5em - align-items: center - - .head - margin: 0 + display: flex + gap: .5em + align-items: center -h4 + .head margin: 0 -.text - margin: 0 .25em 0 0 +.text + margin: 0 .25em 0 0 .group - margin: 0 - padding: .5em + margin: 0 + padding: .5em .via - color: var(--text-color) - font-size: 15px \ No newline at end of file + color: var(--text-color) + font-size: 15px diff --git a/yarn.lock b/yarn.lock index d9680b0c..e9ea7220 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3196,9 +3196,9 @@ __metadata: languageName: node linkType: hard -"@macrostrat/column-views@npm:^2.2.1": - version: 2.2.1 - resolution: "@macrostrat/column-views@npm:2.2.1" +"@macrostrat/column-views@npm:^2.2.2": + version: 2.2.2 + resolution: "@macrostrat/column-views@npm:2.2.2" dependencies: "@macrostrat/api-types": "npm:^1.1.3" "@macrostrat/color-utils": "npm:^1.1.2" @@ -3244,7 +3244,7 @@ __metadata: peerDependencies: "@blueprintjs/core": ^5.0.0 react: ^16.13.1||^17||^18||^19 - checksum: 10/cbc3154e5624e3d28573512407d75e235351e6a983ab6de8e662cdcbc264adaf0f3e27c2a098e0908711998771ba96b6d3c3f5385b55d312b43819615d4550e9 + checksum: 10/14610501bedbf3e7277a82f479ec2d4b84c4581d96e2c0c7ca978077c0bce0d0ecbc2245fc950ff788d78addebd94f9df60c0312b535b47f8968d3e3e63f7b35 languageName: node linkType: hard @@ -3789,7 +3789,7 @@ __metadata: "@macrostrat/api-types": "npm:^1.1.3" "@macrostrat/color-utils": "npm:^1.1.2" "@macrostrat/column-components": "npm:^1.3.1" - "@macrostrat/column-views": "npm:^2.2.1" + "@macrostrat/column-views": "npm:^2.2.2" "@macrostrat/data-components": "npm:^0.2.2" "@macrostrat/data-sheet": "npm:^2.2.1" "@macrostrat/feedback-components": "npm:^1.1.10" @@ -3855,6 +3855,7 @@ __metadata: immutability-helper: "npm:^3.1.1" jose: "npm:^5.1.2" jotai: "npm:^2.15.1" + jotai-location: "npm:^0.6.2" mapbox-gl: "npm:^3.13.0" new-github-issue-url: "npm:^1.0.0" nodemon: "npm:^3.1.11" @@ -13306,6 +13307,15 @@ __metadata: languageName: node linkType: hard +"jotai-location@npm:^0.6.2": + version: 0.6.2 + resolution: "jotai-location@npm:0.6.2" + peerDependencies: + jotai: ">=1.11.0" + checksum: 10/2bc81d3e2a0bf8007c69074e0d9b68c55480f24280c9263d1a4638559a2e9ec46d2de9f04f60d5f41a31b5f27a91a149e954e0ac8f6ca31fd375a7f25c9d5ba0 + languageName: node + linkType: hard + "jotai-scope@npm:^0.9.7": version: 0.9.7 resolution: "jotai-scope@npm:0.9.7"