/
45case
/
ptable
Обзор
Документация
Войти
/
45case
/
ptable
Код
Запросы
3
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
dev
src/entities/electrons/lib/formatElectronDisplay.ts
301 строка
8 KB
Olya
Страница Электроны + сайдбар с характеритстиками (Степень окисления, Конфигурация, Развёрнутая, Уровни энергии, ВЗМО)
24 июл 2026, 11:54
24 июл 2026, 11:54
3cf1d48
Код
Авторство
О чём код?
const SUPERSCRIPT_DIGITS: Record<string, string> = { "0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", "5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹", }; const SUBSHELL_COUNT = /(\d+)([spdf])(\d+)/gi; const ORBITAL_LETTERS = ["s", "p", "d", "f"] as const; function toSuperscript(value: string | number): string { return String(value).replace(/\d/g, (digit) => SUPERSCRIPT_DIGITS[digit] ?? digit); } /** * Electrons in the highest occupied atomic orbital (HOAO) subshell. * Ptable Configuration (`electronstring`): color by this count (linear electrons scale). */ export function getHoaoElectronCount(data: { electronConfiguration?: string | null; expandedConfiguration?: string | null; quantum?: { n: number; l: number; m: number } | null; }): number | null { const config = data.electronConfiguration ?? data.expandedConfiguration; if (!config?.trim()) { return null; } const matches = [...config.matchAll(SUBSHELL_COUNT)]; if (matches.length === 0) { return null; } if (data.quantum != null && data.quantum.l >= 0 && data.quantum.l < ORBITAL_LETTERS.length) { const label = `${data.quantum.n}${ORBITAL_LETTERS[data.quantum.l]}`; const found = matches.find( (match) => `${match[1]}${String(match[2]).toLowerCase()}` === label, ); if (found?.[3] != null) { return Number.parseInt(found[3], 10); } } const last = matches[matches.length - 1]; if (last?.[3] == null) { return null; } return Number.parseInt(last[3], 10); } /** `[Kr] 5s1 4d4` → `[Kr] 5s¹ 4d⁴` */ export function formatElectronConfiguration(config: string | null | undefined): string { if (!config?.trim()) { return "—"; } return config.replace(SUBSHELL_COUNT, (_, n, orbital, count) => { return `${n}${String(orbital).toLowerCase()}${toSuperscript(count)}`; }); } type ConfigSubshell = { n: number; orbital: "s" | "p" | "d" | "f"; count: number; }; function parseConfigSubshells(config: string): ConfigSubshell[] { return [...config.matchAll(SUBSHELL_COUNT)].flatMap((match) => { const orbital = String(match[2] ?? "").toLowerCase(); if (orbital !== "s" && orbital !== "p" && orbital !== "d" && orbital !== "f") { return []; } return [ { n: Number.parseInt(match[1] ?? "", 10), orbital, count: Number.parseInt(match[3] ?? "", 10), }, ]; }); } function formatSubshellToken(token: ConfigSubshell): string { return `${token.n}${token.orbital}${token.count}`; } function findLastSubshellIndex( tokens: readonly ConfigSubshell[], predicate: (token: ConfigSubshell) => boolean, ): number { for (let index = tokens.length - 1; index >= 0; index -= 1) { if (predicate(tokens[index]!)) { return index; } } return -1; } /** * Ptable card abridgement for Configuration (`electronstring` on the grid): * strip noble-gas core, then drop inner filled shells the same way ptable does. * * Examples: * - `[Kr] 5s1 4d4` → `5s1 4d4` * - `[Ar] 4s2 3d10 4p1` → `3d10 4p1` (Ga) * - `[Xe] 6s2 4f14 5d2` → `4f14 5d2` (Hf) * - `[Xe] 6s2 4f14 5d10 6p1` → `5d10 6p1` (Tl) */ export function abridgeElectronConfigurationForCard( config: string | null | undefined, ): string { if (!config?.trim()) { return ""; } const withoutCore = config.replace(/\[[^\]]+\]\s*/g, "").trim(); if (!withoutCore) { return ""; } const tokens = parseConfigSubshells(withoutCore); if (tokens.length === 0) { return ""; } const last = tokens[tokens.length - 1]!; if (last.orbital === "p") { const d10 = findLastSubshellIndex( tokens, (token) => token.orbital === "d" && token.count === 10, ); if (d10 >= 0) { return tokens.slice(d10).map(formatSubshellToken).join(" "); } const f14 = findLastSubshellIndex( tokens, (token) => token.orbital === "f" && token.count === 14, ); if (f14 >= 0) { return tokens.slice(f14).map(formatSubshellToken).join(" "); } return tokens.map(formatSubshellToken).join(" "); } if (last.orbital === "d") { const f14 = findLastSubshellIndex( tokens, (token) => token.orbital === "f" && token.count === 14, ); if (f14 >= 0) { return tokens.slice(f14).map(formatSubshellToken).join(" "); } if (tokens.some((token) => token.orbital === "f")) { return tokens .filter((token) => token.orbital !== "s") .map(formatSubshellToken) .join(" "); } } return tokens.map(formatSubshellToken).join(" "); } /** * Card footer for Configuration — abridged like ptable grid cells, with superscripts. * Aside keeps the full `[Kr] 5s¹ 4d⁴`; cards show e.g. `3d¹⁰ 4p¹` for Ga. */ export function formatElectronConfigurationForCard( config: string | null | undefined, ): string { const abridged = abridgeElectronConfigurationForCard(config); if (!abridged) { return ""; } return formatElectronConfiguration(abridged); } /** Ptable Expanded card footer: last two subshells only (`weight` case). */ export function formatExpandedConfigurationForCard( config: string | null | undefined, ): string { if (!config?.trim()) { return ""; } const abridged = config.trim().split(/\s+/).slice(-2).join(" "); const formatted = formatElectronConfiguration(abridged); return formatted === "—" ? "" : formatted; } /** `2 8 18 12 1` — полная строка оболочек (aside / Energy levels). */ export function formatElectronsPerShell(shells: number[] | null | undefined): string { if (!shells?.length) { return "—"; } return shells.join(" "); } /** * Card footer for Energy levels (ptable `electrons` / `weight`): * at most the last 3 shells; prefix "-" when truncated * (e.g. `2 8 18 32 18 8 2` → `- 18 8 2`). */ export function formatElectronsPerShellForCard( shells: number[] | null | undefined, ): string { if (!shells?.length) { return "—"; } const maxShown = 3; if (shells.length <= maxShown) { return shells.join(" "); } return `- ${shells.slice(-maxShown).join(" ")}`; } /** Aside HOAO: `𝑛=2, 𝑙=0, 𝑚=0` (ptable `quantum` / `aside`). */ export function formatQuantumNumbers( quantum: { n: number; l: number; m: number } | null | undefined, ): string { if (!quantum) { return "—"; } return `𝑛=${quantum.n}, 𝑙=${quantum.l}, 𝑚=${quantum.m}`; } /** * Card footer HOAO (ptable `quantum` / `weight`): no "=" — * e.g. `𝑛2 𝑙1 𝑚0`. */ export function formatQuantumNumbersForCard( quantum: { n: number; l: number; m: number } | null | undefined, ): string { if (!quantum) { return ""; } return `𝑛${quantum.n} 𝑙${quantum.l} 𝑚${quantum.m}`; } /** Multiline HOAO block like `#orbital_lmn` on ptable. */ export function formatQuantumNumbersBlock( quantum: { n: number; l: number; m: number } | null | undefined, ): string { if (!quantum) { return ""; } return `𝑛 = ${quantum.n}\n𝑙 = ${quantum.l}\n𝑚 = ${quantum.m}`; } /** Always 7 shell slots for CloseUp footer (empty → nbsp). */ export function getCloseUpShellSlots(shells: number[] | null | undefined): string[] { const slots = Array.from({ length: 7 }, () => "\u00A0"); if (!shells?.length) { return slots; } shells.slice(0, 7).forEach((count, index) => { slots[index] = String(count); }); return slots; } /** Panel: −1, +2, +3 (unicode minus, always signed except 0). */ export function formatOxidationValueSigned(value: number): string { if (value === 0) { return "0"; } if (value < 0) { return `−${Math.abs(value)}`; } return `+${value}`; } export function formatOxidationStatesSigned( states: ReadonlyArray<{ value: number; common: boolean }>, ): string { if (states.length === 0) { return "—"; } return states.map((state) => formatOxidationValueSigned(state.value)).join(", "); }