logicaffeine_web/ui/components/
repl_output.rs1use dioxus::prelude::*;
12use crate::ui::state::ReplLine;
13
14const REPL_OUTPUT_STYLE: &str = r#"
15.repl-output {
16 display: flex;
17 flex-direction: column;
18 height: 100%;
19 min-height: 0;
20 background: #0f1419;
21 font-family: 'SF Mono', 'Fira Code', monospace;
22}
23
24.repl-output-header {
25 padding: 12px 16px;
26 border-bottom: 1px solid rgba(255, 255, 255, 0.08);
27 background: rgba(255, 255, 255, 0.02);
28 display: flex;
29 justify-content: space-between;
30 align-items: center;
31}
32
33.repl-output-title {
34 font-size: 14px;
35 font-weight: 600;
36 color: rgba(255, 255, 255, 0.9);
37}
38
39.repl-clear-btn {
40 padding: 4px 10px;
41 border: 1px solid rgba(255, 255, 255, 0.1);
42 background: transparent;
43 color: rgba(255, 255, 255, 0.5);
44 font-size: 12px;
45 border-radius: 4px;
46 cursor: pointer;
47 transition: all 0.15s ease;
48}
49
50.repl-clear-btn:hover {
51 border-color: rgba(255, 255, 255, 0.2);
52 color: rgba(255, 255, 255, 0.8);
53}
54
55.repl-history {
56 flex: 1;
57 min-height: 0;
58 overflow: auto;
59 padding: 12px;
60}
61
62.repl-line {
63 margin-bottom: 16px;
64}
65
66.repl-input-line {
67 display: flex;
68 gap: 8px;
69 color: rgba(255, 255, 255, 0.6);
70 font-size: 13px;
71 line-height: 1.5;
72}
73
74.repl-prompt {
75 color: #667eea;
76 user-select: none;
77 flex-shrink: 0;
78}
79
80.repl-input-text {
81 color: rgba(255, 255, 255, 0.9);
82 white-space: pre-wrap;
83 word-break: break-word;
84}
85
86.repl-output-line {
87 padding-left: 24px;
88 margin-top: 4px;
89 font-size: 13px;
90 line-height: 1.5;
91 white-space: pre-wrap;
92 word-break: break-word;
93}
94
95.repl-output-line.success {
96 color: #4ade80;
97}
98
99.repl-output-line.error {
100 color: #e06c75;
101}
102
103.repl-empty {
104 display: flex;
105 flex-direction: column;
106 align-items: center;
107 justify-content: center;
108 height: 100%;
109 color: rgba(255, 255, 255, 0.4);
110 text-align: center;
111 padding: 20px;
112}
113
114.repl-empty .hint {
115 font-size: 13px;
116 margin-top: 8px;
117}
118
119.repl-empty .commands {
120 margin-top: 16px;
121 font-size: 12px;
122 color: rgba(255, 255, 255, 0.3);
123}
124
125.repl-empty .commands code {
126 background: rgba(255, 255, 255, 0.05);
127 padding: 2px 6px;
128 border-radius: 4px;
129 margin: 0 2px;
130}
131"#;
132
133#[component]
134pub fn ReplOutput(
135 lines: Vec<ReplLine>,
136 on_clear: EventHandler<()>,
137) -> Element {
138 rsx! {
139 style { "{REPL_OUTPUT_STYLE}" }
140
141 div { class: "repl-output",
142 div { class: "repl-output-header",
144 span { class: "repl-output-title", "Output" }
145 if !lines.is_empty() {
146 button {
147 class: "repl-clear-btn",
148 onclick: move |_| on_clear.call(()),
149 "Clear"
150 }
151 }
152 }
153
154 div { class: "repl-history",
156 if lines.is_empty() {
157 div { class: "repl-empty",
158 div { "No output yet" }
159 div { class: "hint", "Execute code to see results here" }
160 div { class: "commands",
161 "Commands: "
162 code { "Definition" }
163 code { "Check" }
164 code { "Eval" }
165 code { "Inductive" }
166 }
167 }
168 } else {
169 for (i, line) in lines.iter().enumerate() {
170 div { key: "{i}", class: "repl-line",
171 div { class: "repl-input-line",
173 span { class: "repl-prompt", ">" }
174 span { class: "repl-input-text", "{line.input}" }
175 }
176
177 match &line.output {
179 Ok(output) if !output.is_empty() => rsx! {
180 div { class: "repl-output-line success", "{output}" }
181 },
182 Err(error) => rsx! {
183 div { class: "repl-output-line error", "{error}" }
184 },
185 _ => rsx! {}
186 }
187 }
188 }
189 }
190 }
191 }
192 }
193}