Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 19x 19x 1x 1x 1x 11x 11x 1x 1x 1x 10x 10x 1x 1x 13x 13x 11x 13x 13x 13x 1x 1x 13x 13x 13x 13x 13x 13x 13x 13x 13x 7x 6x 6x 6x 6x 6x 6x 6x 13x 13x 2x 11x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 13x 1x 1x 1x 42x 42x 42x 17x 13x 13x 13x 13x 13x 13x 12x 12x 12x 13x 13x 13x 13x 42x 42x 42x | import { NIST_STRUCTURE_URL } from '@/constants/hazards';
import { lookupOshaChemical, type OshaChemical } from '@/helpers/oshaChemicalDb';
import {
getCidByName,
getCidsByCas,
getCompoundDescription,
getCompoundProperties,
getGhsClassification,
getSolubility,
getSynonymsByCid,
pubchemCompoundUrl,
pubchemStructureImageUrl,
type PubChemDescription,
type PubChemGhs,
type PubChemProperties,
} from '@/helpers/pubchem';
import { resolveEnglishChemical } from '@/helpers/wikidata';
import { isCAS } from '@/utils/typeGuards/common';
import { useEffect, useState } from 'react';
/**
* Everything known about one searched chemical, merged from OSHA OBIS and PubChem. Each source
* is optional; a lookup that fails simply leaves its fields undefined.
* @category Hooks
* @group Chemical Info
*/
export interface ChemicalInfo {
/** The term that was looked up. */
term: string;
/** OSHA OBIS record, when the chemical is in that dataset. */
osha?: OshaChemical;
/** PubChem compound id, when resolved. */
cid?: PubChemCID;
/** PubChem compound page. */
pubchemUrl?: string;
/** Wikipedia article (or search) for the chemical. */
wikipediaUrl: string;
/** NIST WebBook page, when a CAS number is known. */
nistUrl?: string;
properties?: PubChemProperties;
synonyms: string[];
description?: PubChemDescription;
ghs?: PubChemGhs;
solubility?: string[];
/** CAS number, from the query, OBIS, or PubChem synonyms. */
cas?: string;
/** Structure image URLs to try in order (NIST, then PubChem). */
imageUrls: string[];
}
/**
* Builds a Wikipedia search URL for a chemical name. Wikipedia jumps straight to the article on
* an exact title or redirect match, and otherwise shows search results.
* @category Hooks
* @group Chemical Info
* @param name - A chemical name.
* @returns The Wikipedia URL.
* @example
* ```ts
* wikipediaUrl('sodium hydroxide');
* // "https://en.wikipedia.org/wiki/Special:Search?search=sodium%20hydroxide"
* ```
* @source
*/
export function wikipediaUrl(name: string): string {
return `https://en.wikipedia.org/wiki/Special:Search?search=${encodeURIComponent(name)}`;
}
/**
* Builds the NIST WebBook compound-page URL for a CAS number.
* @category Hooks
* @group Chemical Info
* @param cas - A CAS registry number such as `67-64-1`.
* @returns The WebBook page URL.
* @example
* ```ts
* nistWebbookUrl('67-64-1');
* // "https://webbook.nist.gov/cgi/cbook.cgi?ID=C67641"
* ```
* @source
*/
export function nistWebbookUrl(cas: string): string {
return `https://webbook.nist.gov/cgi/cbook.cgi?ID=C${cas.replace(/-/g, '')}`;
}
/**
* Builds the NIST WebBook structure-image URL for a CAS number.
* @category Hooks
* @group Chemical Info
* @param cas - A CAS registry number such as `12024-21-4`.
* @returns The image URL (CAS hyphens removed, per NIST's `C<digits>` id format).
* @example
* ```ts
* nistStructureImageUrl('12024-21-4');
* // "https://webbook.nist.gov/cgi/cbook.cgi?Struct=C12024214&Type=Color"
* ```
* @source
*/
export function nistStructureImageUrl(cas: string): string {
return `${NIST_STRUCTURE_URL}?Struct=C${cas.replace(/-/g, '')}&Type=Color`;
}
/**
* Resolves a PubChem CID from a CAS number or name, falling back to the OBIS CAS.
* @param term - The searched term.
* @param osha - The OBIS record, if any.
* @returns The CID, or undefined.
* @source
*/
async function resolveCid(term: string, osha?: OshaChemical): Promise<PubChemCID | undefined> {
if (isCAS(term)) return (await getCidsByCas(term))?.[0];
const byName = await getCidByName(term);
if (byName !== undefined || osha?.cas === undefined || !isCAS(osha.cas)) return byName;
return (await getCidsByCas(osha.cas))?.[0];
}
/**
* Gathers and merges chemical data for a term from OSHA OBIS and PubChem. A non-English name is
* first resolved to its English name and CAS number through Wikidata.
* @param term - A chemical name or CAS number.
* @returns The merged info; always resolves.
* @source
*/
async function fetchChemicalInfo(term: string): Promise<ChemicalInfo> {
// A name in another language (e.g. Dutch "zoutzuur") matches nothing in OBIS or PubChem, so
// look the term up by its CAS number (or English name) instead; the dialog still shows `term`.
const english = isCAS(term) ? undefined : await resolveEnglishChemical(term);
const lookupTerm = english?.cas ?? english?.name ?? term;
const osha = await lookupOshaChemical(lookupTerm);
const cid = await resolveCid(lookupTerm, osha);
const [properties, synonyms, description, ghs, solubility] =
cid === undefined
? [undefined, undefined, undefined, undefined, undefined]
: await Promise.all([
getCompoundProperties(cid),
getSynonymsByCid(cid),
getCompoundDescription(cid),
getGhsClassification(cid),
getSolubility(cid),
]);
const allSynonyms = synonyms ?? [];
const cas = isCAS(lookupTerm)
? lookupTerm
: (osha?.cas ?? allSynonyms.find((name) => isCAS(name)));
const imageUrls = [
...(cas === undefined ? [] : [nistStructureImageUrl(cas)]),
...(cid === undefined ? [] : [pubchemStructureImageUrl(cid)]),
];
return {
term,
osha,
cid,
nistUrl: cas === undefined ? undefined : nistWebbookUrl(cas),
wikipediaUrl: wikipediaUrl(properties?.title ?? osha?.name ?? english?.name ?? term),
pubchemUrl: cid === undefined ? undefined : pubchemCompoundUrl(cid),
properties,
synonyms: allSynonyms,
description,
ghs,
solubility,
cas,
imageUrls,
};
}
/**
* Loads merged chemical details for a term while the dialog is open.
* @category Hooks
* @group Chemical Info
* @param term - The term to look up, or undefined when no dialog is open.
* @returns The info (once loaded) and a loading flag.
* @example
* ```tsx
* const { info, loading } = useChemicalInfo('67-64-1');
* ```
* @source
*/
export function useChemicalInfo(term: string | undefined): {
info?: ChemicalInfo;
loading: boolean;
} {
const [info, setInfo] = useState<ChemicalInfo | undefined>();
const [loading, setLoading] = useState(false);
useEffect(() => {
if (term === undefined) return;
let cancelled = false;
setInfo(undefined);
setLoading(true);
const load = async () => {
const result = await fetchChemicalInfo(term);
if (cancelled) return;
setInfo(result);
setLoading(false);
};
void load();
return () => {
cancelled = true;
};
}, [term]);
return { info, loading };
}
|