| 1 | import 'katex/dist/katex.min.css' |
| 2 | |
| 3 | import { Sigma } from 'lucide-react' |
| 4 | import { useMemo } from 'react' |
| 5 | import { Checkbox } from '../../ui/Checkbox' |
| 6 | import { InspectorSection } from './InspectorSection' |
| 7 | import { renderFormulaToHtml } from './formulaEditUtils' |
| 8 | import type { ElementEditorProps } from './types' |
| 9 | import { useT } from '@renderer/i18n' |
| 10 | |
| 11 | const FORMULA_FIELDS = ['formulaLatex', 'formulaHtml', 'formulaDisplayMode'] as const |
| 12 | |
| 13 | export function FormulaInspector({ draft, onDraftChange }: ElementEditorProps): React.JSX.Element { |
| 14 | const t = useT() |
| 15 | const rendered = useMemo( |
| 16 | () => renderFormulaToHtml(draft.formulaLatex, draft.formulaDisplayMode), |
| 17 | [draft.formulaDisplayMode, draft.formulaLatex] |
| 18 | ) |
| 19 | |
| 20 | const updateFormula = (latex: string, displayMode: boolean, commit = false): void => { |
| 21 | const nextRendered = renderFormulaToHtml(latex, displayMode) |
| 22 | const nextDraft = { |
| 23 | ...draft, |
| 24 | formulaLatex: latex, |
| 25 | formulaDisplayMode: displayMode, |
| 26 | formulaHtml: nextRendered.html |
| 27 | } |
| 28 | onDraftChange( |
| 29 | nextDraft, |
| 30 | commit && nextRendered.html ? { commit: true, fields: [...FORMULA_FIELDS] } : undefined |
| 31 | ) |
| 32 | } |
| 33 | |
| 34 | return ( |
| 35 | <InspectorSection |
| 36 | title={t('sessionDetail.formulaContent')} |
| 37 | icon={<Sigma className="h-3.5 w-3.5 text-[#7a875f]" />} |
| 38 | > |
| 39 | <textarea |
| 40 | value={draft.formulaLatex} |
| 41 | onChange={(event) => updateFormula(event.target.value, draft.formulaDisplayMode)} |
| 42 | onBlur={() => updateFormula(draft.formulaLatex, draft.formulaDisplayMode, true)} |
| 43 | onKeyDown={(event) => { |
| 44 | if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { |
| 45 | event.currentTarget.blur() |
| 46 | } |
| 47 | }} |
| 48 | className="min-h-[92px] w-full resize-y rounded-[9px] border border-[#d9cfbd]/72 bg-[#fffaf1]/92 px-2.5 py-2 font-mono text-[11px] leading-5 text-[#34402c] outline-none transition-colors placeholder:text-[#b1a58f] focus:border-[#9daf8a]" |
| 49 | placeholder="x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}" |
| 50 | spellCheck={false} |
| 51 | /> |
| 52 | |
| 53 | <label className="mt-2 flex items-center gap-2 text-[11px] text-[#6f664f]"> |
| 54 | <Checkbox |
| 55 | checked={draft.formulaDisplayMode} |
| 56 | onCheckedChange={(checked) => |
| 57 | updateFormula(draft.formulaLatex, checked === true, true) |
| 58 | } |
| 59 | /> |
| 60 | <span>{t('sessionDetail.formulaDisplayMode')}</span> |
| 61 | </label> |
| 62 | |
| 63 | <div className="mt-3 rounded-[9px] border border-[#e1d6c4]/70 bg-white/64 px-2.5 py-2"> |
| 64 | <div className="mb-1.5 text-[10px] font-semibold uppercase tracking-[0.12em] text-[#8a806b]"> |
| 65 | {t('sessionDetail.formulaPreview')} |
| 66 | </div> |
| 67 | {rendered.html ? ( |
| 68 | <div |
| 69 | className="overflow-x-auto text-[#0a0a0b]" |
| 70 | dangerouslySetInnerHTML={{ __html: rendered.html }} |
| 71 | /> |
| 72 | ) : ( |
| 73 | <p className="text-[11px] leading-5 text-[#a0977e]"> |
| 74 | {rendered.error || t('sessionDetail.formulaPreviewEmpty')} |
| 75 | </p> |
| 76 | )} |
| 77 | </div> |
| 78 | </InspectorSection> |
| 79 | ) |
| 80 | } |
| 81 |