1#![recursion_limit = "512"]
2#![allow(clippy::must_use_candidate)]
3
4#[cfg(any(
5 all(feature = "ssr", feature = "hydrate", not(feature = "docs-only")),
6 not(any(feature = "ssr", feature = "hydrate")),
7))]
8compile_error!("exactly one of the features \"ssr\" or \"hydrate\" must be enabled");
9
10pub mod query;
11mod settings;
12
13pub mod ws {
14 pub use flams_flodown::math::MathSocket;
15 #[cfg(feature = "ssr")]
16 pub use flams_flodown::math::TeXSocket;
17 pub use flams_router_base::ws::*;
18 pub use flams_router_buildqueue_components::QueueSocket;
19 pub use flams_router_logging::LogSocket;
20}
21
22pub mod server_fns {
23 pub mod content {
24 pub use flams_router_content::server_fns::*;
25 }
26 pub mod backend {
27 pub use flams_router_backend::server_fns::*;
28 }
29 pub mod buildqueue {
30 pub use flams_router_buildqueue_base::server_fns::*;
31 }
32 pub mod git {
33 pub use flams_router_git_base::server_fns::*;
34 }
35 pub mod login {
36 pub use flams_router_login::server_fns::*;
37 }
38 pub mod search {
39 pub use flams_router_search::{search_query, search_symbols};
40 }
41 pub use super::query::query_api as query;
42 pub use super::settings::{get_settings as settings, reload};
43}
44
45pub use flams_router_base::LoginState;
46use flams_router_base::maybe_lazy;
47use flams_web_utils::components::{Layout, LayoutHeader, LayoutSider};
48use ftml_component_utils::{Divider, Grid, GridItem};
49use ftml_components::config::AllowSubterms;
50use ftml_dom::FtmlViews;
51use leptos::prelude::*;
52use leptos_meta::{Title, provide_meta_context};
53use leptos_router::{
54 components::{Outlet, ParentRoute, Redirect, Route, Router, Routes},
55 path,
56};
57
58#[component]
59pub fn Main() -> AnyView {
60 provide_meta_context();
61 let (_, set_is_routing) = signal(false);
62 view! {
63 <Title text="𝖥𝖫∀𝖬∫"/>
64 <Router set_is_routing>{
65 view!{<Routes fallback=|| NotFound()>
67 <ParentRoutepath=() view=Top>
68 <ParentRoute path=path!("/dashboard") view=Dashboard>
69 <ParentRoute path=path!("mathhub") view={|| main_page(Page::MathHub)}>
70 <Route path=path!("") view={maybe_lazy!(flams_router_backend::components::ArchivesTop)}/>
71 </ParentRoute>
72 <ParentRoute path=path!("log") view={|| main_page(Page::Log)}>
74 <Route path=path!("") view={maybe_lazy!(flams_router_logging::Logger)}/>
75 </ParentRoute>
76 <ParentRoute path=path!("queue") view={|| main_page(Page::Queue)}>
77 <Route path=path!("") view={maybe_lazy!(flams_router_buildqueue_components::QueuesTop)}/>
78 </ParentRoute>
79 <ParentRoute path=path!("settings") view={|| main_page(Page::Settings)}>
80 <Route path=path!("") view={maybe_lazy!(settings::Settings)}/>
81 </ParentRoute>
82 <ParentRoute path=path!("query") view={|| main_page(Page::Query)}>
83 <Route path=path!("") view={maybe_lazy!(query::Query)}/>
84 </ParentRoute>
85 <ParentRoute path=path!("archives") view={|| main_page(Page::MyArchives)}>
86 <Route path=path!("") view={maybe_lazy!(flams_router_git_components::Archives)}/>
87 </ParentRoute>
88 <ParentRoute path=path!("users") view={|| main_page(Page::Users)}>
89 <Route path=path!("") view={maybe_lazy!(flams_router_login::components::Users)}/>
90 </ParentRoute>
91 <ParentRoute path=path!("search") view={|| main_page(Page::Search)}>
92 <Route path=path!("") view={maybe_lazy!(flams_router_search::components::SearchTop)}/>
93 </ParentRoute>
94 <ParentRoute path=path!("flodown") view={|| main_page(Page::FloDown)}>
95 <Route path=path!("") view={maybe_lazy!(flams_flodown::FloDownEditor)}/>
96 </ParentRoute>
97 <ParentRoute path=path!("") view={|| main_page(Page::Home)}>
98 <Route path=path!("") view={maybe_lazy!(flams_router_backend::index_components::Index)}/>
99 </ParentRoute>
100 <ParentRoute path=path!("*any") view={|| main_page(Page::NotFound)}>
101 <Route path=path!("") view=||view!(<NotFound/>)/>
102 </ParentRoute>
103 </ParentRoute>
104 <ParentRoute path=path!("/vscode") view={maybe_lazy!(flams_router_vscode::VSCWrap)}><Route path=path!("search") view={maybe_lazy!(flams_router_search::vscode::VSCSearch)}/>
106 <Route path=path!("proofs") view={maybe_lazy!(flams_router_vscode::checks::Checks)}/>
107 </ParentRoute>
108 <Route path=path!("/document") view={maybe_lazy!(flams_router_content::components::TopDocRouter)}/>
109 <Route path=path!("/") view={maybe_lazy!(flams_router_content::components::UriTopRouter)}/>
110 </ParentRoute>
111 </Routes>}
112 }</Router>
113 }.into_any()
114}
115
116#[component(transparent)]
117fn Top() -> AnyView {
118 use flams_router_login::components::LoginProvider;
119 provide_context(AllowSubterms(true));
120 view!(<LoginProvider><leptos_router::components::Outlet/></LoginProvider>).into_any()
121}
122
123#[derive(Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
124enum Page {
125 Home,
126 MathHub,
127 Log,
129 NotFound,
130 Queue,
131 Settings,
132 Login,
133 Query,
134 Search,
135 FloDown,
136 MyArchives,
137 Users,
138}
139impl Page {
140 pub const fn key(self) -> &'static str {
141 match self {
142 Self::Home => "home",
143 Self::MathHub => "mathhub",
144 Self::Log => "log",
146 Self::Login => "login",
147 Self::Queue => "queue",
148 Self::Settings => "settings",
149 Self::Query => "query",
150 Self::MyArchives => "archives",
151 Self::Search => "search",
152 Self::FloDown => "flodown",
153 Self::Users => "users",
154 Self::NotFound => "notfound",
155 }
156 }
157}
158impl std::fmt::Display for Page {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.write_str(self.key())
161 }
162}
163
164#[component(transparent)]
165pub fn Dashboard() -> impl IntoView {
166 view! {
167 <Outlet/>
168 }
169}
170
171fn main_page(page: Page) -> AnyView {
172 ftml_dom::global_setup(move || flams_router_content::Views::top(move || {
177 view! {
178 <div style="width:100vw;height:100vh;"><Layout>
179 <LayoutHeader style="text-align:center;" slot>
181 <div style="width:100%">
182 <Grid cols=3>
183 <GridItem>""</GridItem>
184 <GridItem>
185 <svg xmlns="http://www.w3.org/2000/svg" width="120px" height="60px" viewBox="0 -805.5 2248.7 1111" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" style="color:var(--colorBrandForeground1)"><defs><path id="MJX-5-TEX-SS-1D5A5" d="M86 0V691H526V611H358L190 612V384H485V308H190V0H86Z"></path><path id="MJX-5-TEX-SS-1D5AB" d="M87 0V694H191V79L297 80H451L499 81V0H87Z"></path><path id="MJX-5-TEX-N-2200" d="M0 673Q0 684 7 689T20 694Q32 694 38 680T82 567L126 451H430L473 566Q483 593 494 622T512 668T519 685Q524 694 538 694Q556 692 556 674Q556 670 426 329T293 -15Q288 -22 278 -22T263 -15Q260 -11 131 328T0 673ZM414 410Q414 411 278 411T142 410L278 55L414 410Z"></path><path id="MJX-5-TEX-SS-1D5AC" d="M92 0V694H228L233 680Q236 675 284 547T382 275T436 106Q446 149 497 292T594 558L640 680L645 694H782V0H689V305L688 606Q688 577 500 78L479 23H392L364 96Q364 97 342 156T296 280T246 418T203 544T186 609V588Q185 568 185 517T185 427T185 305V0H92Z"></path><path id="MJX-5-TEX-SO-222B" d="M113 -244Q113 -246 119 -251T139 -263T167 -269Q186 -269 199 -260Q220 -247 232 -218T251 -133T262 -15T276 155T297 367Q300 390 305 438T314 512T325 580T340 647T361 703T390 751T428 784T479 804Q481 804 488 804T501 805Q552 802 581 769T610 695Q610 669 594 657T561 645Q542 645 527 658T512 694Q512 705 516 714T526 729T538 737T548 742L552 743Q552 745 545 751T525 762T498 768Q475 768 460 756T434 716T418 652T407 559T398 444T387 300T369 133Q349 -38 337 -102T303 -207Q256 -306 169 -306Q119 -306 87 -272T55 -196Q55 -170 71 -158T104 -146Q123 -146 138 -159T153 -195Q153 -206 149 -215T139 -230T127 -238T117 -242L113 -244Z"></path></defs><g stroke="currentcolor" fill="currentcolor" stroke-width="0" transform="scale(1,-1)"><g data-mml-node="math"><g data-mml-node="mstyle"><g data-mml-node="TeXAtom" data-mjx-texclass="ORD"><g data-mml-node="mi"><use data-c="1D5A5" xlink:href="#MJX-5-TEX-SS-1D5A5"></use></g></g><g data-mml-node="mspace" transform="translate(569,0)"></g><g data-mml-node="TeXAtom" data-mjx-texclass="ORD" transform="translate(469,0)"><g data-mml-node="mi"><use data-c="1D5AB" xlink:href="#MJX-5-TEX-SS-1D5AB"></use></g></g><g data-mml-node="mspace" transform="translate(1011,0)"></g><g data-mml-node="mpadded" transform="translate(651,0)"><g transform="translate(0,23)"><g data-mml-node="mi"><use data-c="2200" xlink:href="#MJX-5-TEX-N-2200"></use></g></g></g><g data-mml-node="mspace" transform="translate(1207,0)"></g><g data-mml-node="TeXAtom" data-mjx-texclass="ORD" transform="translate(1097,0)"><g data-mml-node="mi"><use data-c="1D5AC" xlink:href="#MJX-5-TEX-SS-1D5AC"></use></g></g><g data-mml-node="mspace" transform="translate(1972,0)"></g><g data-mml-node="mo" transform="translate(1638.7,0) translate(0 0.5)"><use data-c="222B" xlink:href="#MJX-5-TEX-SO-222B"></use></g></g></g></g></svg>
186 </GridItem>
190 <GridItem>
191 <div style="width:calc(100% - 20px);text-align:right;padding:10px">
192 {user_field().into_any()}
193 </div>
194 </GridItem>
195 </Grid>
196 <Divider/>
197 </div>
198 </LayoutHeader>
199 <LayoutSider class="flams-menu" slot>
201 {side_menu(page)}
202 </LayoutSider>
203 <Outlet/></Layout></div>
211 }
212 })).into_any()
213}
214
215#[component]
238fn NotFound() -> AnyView {
239 #[cfg(feature = "ssr")]
240 {
241 let resp = expect_context::<leptos_axum::ResponseOptions>();
242 resp.set_status(axum::http::StatusCode::NOT_FOUND);
243 }
244
245 view! {
246 <h3>"Not Found"</h3>
247 }
248 .into_any()
249}
250
251fn side_menu(page: Page) -> AnyView {
252 use ftml_component_utils::{NavDrawer, NavItem};
253 view! {
254 <NavDrawer selected_value=page.to_string() class="flams-menu-inner">
255 <NavItem value="home" href="/">"Home"</NavItem>
256 <NavItem value="mathhub" href="/dashboard/mathhub">"MathHub"</NavItem>
257 <NavItem value="query" href="/dashboard/query">"Queries"</NavItem>
258 <NavItem value="search" href="/dashboard/search">"Search Content"</NavItem>
259 {move || {let s = LoginState::get(); match s {
260 LoginState::NoAccounts => view!{
261 <NavItem value="log" href="/dashboard/log">"Logs"</NavItem>
262 <NavItem value="settings" href="/dashboard/settings">"Settings"</NavItem>
263 <NavItem value="queue" href="/dashboard/queue">"Queue"</NavItem>
264 <NavItem value="flodown" href="/dashboard/flodown">"FloDown"</NavItem>
265 }.into_any(),
266 LoginState::Admin => view!{
267 <NavItem value="log" href="/dashboard/log">"Logs"</NavItem>
268 <NavItem value="settings" href="/dashboard/settings">"Settings"</NavItem>
269 <NavItem value="queue" href="/dashboard/queue">"Queue"</NavItem>
270 <NavItem value="flodown" href="/dashboard/flodown">"FloDown"</NavItem>
271 <NavItem value="users" href="/dashboard/users">"Manage Users"</NavItem>
272 }.into_any(),
273 LoginState::User{is_admin:true,..} => view!{
274 <NavItem value="log" href="/dashboard/log">"Logs"</NavItem>
275 <NavItem value="settings" href="/dashboard/settings">"Settings"</NavItem>
276 <NavItem value="queue" href="/dashboard/queue">"Queue"</NavItem>
277 <NavItem value="archives" href="/dashboard/archives">"My Archives"</NavItem>
278 <NavItem value="flodown" href="/dashboard/flodown">"FloDown"</NavItem>
279 }.into_any(),
280 LoginState::User{..} => view!{
281 <NavItem value="queue" href="/dashboard/queue">"Queue"</NavItem>
282 <NavItem value="archives" href="/dashboard/archives">"My Archives"</NavItem>
283 <NavItem value="flodown" href="/dashboard/flodown">"FloDown"</NavItem>
284 }.into_any(),
285 LoginState::None | LoginState::Loading => ().into_any()
286 }}}
287 </NavDrawer>
288 }
289 .into_any()
290}
291
292fn user_field() -> AnyView {
293 use ftml_component_utils::{
294 Menu, MenuItem, MenuPosition, MenuTrigger, MenuTriggerType, Spinner, theming::Theme,
295 };
296
297 view! {<div class="flams-user-menu-trigger">{
299 let theme = expect_context::<RwSignal<Theme>>();
300 let on_select = move |key: &'static str| match key {
301 "theme" => {
302 theme.update(|v| {
303 if v.name == "dark" {
304 *v = Theme::light();
305 } else {
306 *v = Theme::dark();
307 }
308 });
309 }
310 _ => unreachable!(),
311 };
312 let src = Memo::new(|_| match LoginState::get() {
313 LoginState::User { avatar, .. } => Some(avatar),
314 LoginState::Admin => Some("/admin.png".to_string()),
315 _ => None,
316 });
317 let icon = Memo::new(move |_| if theme.with(|v| v.name == "dark")
318 {icondata_bi::BiSunRegular} else {icondata_bi::BiMoonSolid}
319 );
320 let text = Memo::new(move |_| if theme.with(|v| v.name == "dark")
321 {"Light Mode"} else {"Dark Mode"}
322 );
323 view!{
324 <Menu on_select trigger_type=MenuTriggerType::Hover position=MenuPosition::Bottom>
325 <MenuTrigger slot>
326 <ftml_component_utils::Avatar src />
327 </MenuTrigger>
328 <MenuItem value="theme" icon=icon>{text}</MenuItem>
330 <Divider/>
331 {move || match LoginState::get() {
332 LoginState::None => login_form().into_any(),
333 LoginState::NoAccounts => view!(<span>"Admin"</span>).into_any(),
334 LoginState::Admin => logout_form("admin".to_string()).into_any(),
335 LoginState::User{name,..} => logout_form(name).into_any(),
336 LoginState::Loading => view!(<Spinner small=true/>).into_any()
337 }}
338 </Menu>
339 }
340 }</div>
341 }
343 .into_any()
344}
345
346fn logout_form(user: String) -> AnyView {
347 use ftml_component_utils::Button;
348 let login = expect_context::<RwSignal<LoginState>>();
349 let action = Action::new(move |_| {
350 login.set(LoginState::None);
351 flams_router_login::server_fns::logout()
352 });
353 view!(<span>{user}" "<Button on_click=move |_| {action.dispatch(());}>Logout</Button></span>)
354 .into_any()
355}
356
357fn login_form() -> AnyView {
358 use ftml_component_utils::{Button, Input, InputType};
359 let login = expect_context();
360 let action = Action::new(move |pwd: &String| do_login(pwd.clone(), login));
361 let value = RwSignal::<String>::new(String::new());
362 view! {
363 <Button on_click=move |_| {action.dispatch(value.get_untracked());}>Login</Button>
364 <Input placeholder="admin pwd" value input_type=InputType::Password/>
365 }
366 .into_any()
367}
368
369#[allow(unused_variables)]
370async fn do_login(pw: String, login: RwSignal<LoginState>) {
371 let pwd = if pw.is_empty() { None } else { Some(pw) };
372 match flams_router_login::server_fns::login(pwd).await {
373 Ok(Some(u @ (LoginState::Admin | LoginState::User { .. }))) => login.set(u),
374 Ok(_) => (),
375 Err(e) => {
376 #[cfg(feature = "hydrate")]
377 flams_web_utils::components::display_error(std::borrow::Cow::Owned(format!(
378 "Error: {e}"
379 )));
380 }
381 }
382 let _ = view!(<Redirect path="/dashboard"/>);
383}