logicaffeine_web/ui/components/
formula_editor.rs1use dioxus::prelude::*;
18use super::katex::KatexSpan;
19
20const FORMULA_EDITOR_STYLE: &str = r#"
21.formula-editor {
22 display: flex;
23 flex-direction: column;
24 height: 100%;
25 background: #0f1419;
26}
27
28.formula-editor-input {
29 flex: 1;
30 display: flex;
31 flex-direction: column;
32 padding: 16px;
33 border-bottom: 1px solid rgba(255, 255, 255, 0.08);
34}
35
36.formula-editor-label {
37 font-size: 12px;
38 font-weight: 600;
39 color: rgba(255, 255, 255, 0.5);
40 margin-bottom: 8px;
41 text-transform: uppercase;
42 letter-spacing: 0.5px;
43}
44
45.formula-textarea {
46 flex: 1;
47 width: 100%;
48 min-height: 120px;
49 background: rgba(255, 255, 255, 0.04);
50 border: 1px solid rgba(255, 255, 255, 0.1);
51 border-radius: 8px;
52 padding: 14px;
53 font-size: 15px;
54 font-family: 'SF Mono', 'Fira Code', monospace;
55 color: rgba(255, 255, 255, 0.9);
56 resize: none;
57 outline: none;
58 line-height: 1.6;
59}
60
61.formula-textarea:focus {
62 border-color: rgba(102, 126, 234, 0.5);
63 box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
64}
65
66.formula-textarea::placeholder {
67 color: rgba(255, 255, 255, 0.3);
68}
69
70.formula-preview {
71 padding: 24px;
72 display: flex;
73 align-items: center;
74 justify-content: center;
75 min-height: 150px;
76 background: rgba(255, 255, 255, 0.02);
77}
78
79.formula-preview-empty {
80 color: rgba(255, 255, 255, 0.3);
81 font-size: 14px;
82 text-align: center;
83}
84
85.formula-preview .katex-display {
86 font-size: 1.8em;
87}
88
89.formula-error {
90 color: #e06c75;
91 padding: 12px 16px;
92 background: rgba(224, 108, 117, 0.1);
93 border-radius: 6px;
94 font-size: 13px;
95 margin: 16px;
96}
97
98/* Quick insert bar */
99.formula-quick-insert {
100 display: flex;
101 gap: 4px;
102 padding: 8px 16px;
103 border-top: 1px solid rgba(255, 255, 255, 0.06);
104 background: rgba(255, 255, 255, 0.02);
105 flex-wrap: wrap;
106}
107
108.quick-insert-btn {
109 padding: 6px 10px;
110 background: rgba(255, 255, 255, 0.04);
111 border: 1px solid rgba(255, 255, 255, 0.08);
112 border-radius: 4px;
113 color: rgba(255, 255, 255, 0.7);
114 font-size: 14px;
115 cursor: pointer;
116 transition: all 0.1s ease;
117}
118
119.quick-insert-btn:hover {
120 background: rgba(102, 126, 234, 0.15);
121 border-color: rgba(102, 126, 234, 0.3);
122 color: #667eea;
123}
124
125/* Mobile */
126@media (max-width: 768px) {
127 .formula-textarea {
128 font-size: 16px; /* Prevent iOS zoom */
129 min-height: 100px;
130 }
131
132 .formula-preview .katex-display {
133 font-size: 1.5em;
134 }
135}
136"#;
137
138const QUICK_INSERTS: &[(&str, &str)] = &[
140 ("\u{2200}", "\\forall "),
141 ("\u{2203}", "\\exists "),
142 ("\u{2192}", "\\to "),
143 ("\u{2227}", "\\land "),
144 ("\u{2228}", "\\lor "),
145 ("\u{00AC}", "\\neg "),
146 ("frac", "\\frac{}{}"),
147 ("sum", "\\sum_{i=0}^{n}"),
148 ("sqrt", "\\sqrt{}"),
149];
150
151#[component]
152pub fn FormulaEditor(
153 latex: String,
154 on_change: EventHandler<String>,
155 on_insert: Option<EventHandler<String>>,
156) -> Element {
157 let has_content = !latex.trim().is_empty();
159
160 rsx! {
161 style { "{FORMULA_EDITOR_STYLE}" }
162
163 div { class: "formula-editor",
164 div { class: "formula-editor-input",
166 label { class: "formula-editor-label", "LaTeX Input" }
167 textarea {
168 class: "formula-textarea",
169 placeholder: "Enter LaTeX formula... (e.g., \\forall x \\in A: P(x))",
170 value: "{latex}",
171 oninput: move |e| on_change.call(e.value()),
172 spellcheck: "false",
173 }
174 }
175
176 div { class: "formula-quick-insert",
178 for (label, insert) in QUICK_INSERTS.iter() {
179 button {
180 class: "quick-insert-btn",
181 key: "{insert}",
182 onclick: {
183 let insert = insert.to_string();
184 let handler = on_insert.clone();
185 move |_| {
186 if let Some(ref h) = handler {
187 h.call(insert.clone());
188 }
189 }
190 },
191 "{label}"
192 }
193 }
194 }
195
196 div { class: "formula-preview",
198 if has_content {
199 KatexSpan { latex: latex.clone(), display: true }
200 } else {
201 div { class: "formula-preview-empty",
202 "Preview will appear here"
203 }
204 }
205 }
206 }
207 }
208}
209
210#[component]
212pub fn LatexPreview(latex: String) -> Element {
213 let has_content = !latex.trim().is_empty();
214
215 rsx! {
216 div { class: "formula-preview",
217 style: "height: 100%; background: #12161c;",
218 if has_content {
219 KatexSpan { latex: latex, display: true }
220 } else {
221 div { class: "formula-preview-empty",
222 "Enter a formula to see the preview"
223 }
224 }
225 }
226 }
227}