Factor out parts of the frontend into separate components
This commit is contained in:
parent
a25e143b55
commit
3078591e2c
173
Cargo.lock
generated
173
Cargo.lock
generated
@ -96,12 +96,6 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "anyhow"
|
|
||||||
version = "1.0.89"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anymap2"
|
name = "anymap2"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
@ -178,17 +172,6 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum-client-ip"
|
|
||||||
version = "0.6.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9eefda7e2b27e1bda4d6fa8a06b50803b8793769045918bc37ad062d48a6efac"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"forwarded-header-value",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@ -230,16 +213,6 @@ dependencies = [
|
|||||||
"urlencoding",
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "axum_static"
|
|
||||||
version = "1.7.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3d134d2aeabd596bc82e44748f999c9b0e535a7b756cb36c62df0de6d2445d6e"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"tower-http 0.5.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.74"
|
version = "0.3.74"
|
||||||
@ -536,12 +509,6 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "either"
|
|
||||||
version = "1.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -573,16 +540,6 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "forwarded-header-value"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9"
|
|
||||||
dependencies = [
|
|
||||||
"nonempty",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "frontend"
|
name = "frontend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -592,17 +549,13 @@ dependencies = [
|
|||||||
"common",
|
"common",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"gloo 0.11.0",
|
"gloo 0.11.0",
|
||||||
"gloo-console 0.3.0",
|
|
||||||
"gloo-net 0.6.0",
|
|
||||||
"gloo-timers 0.3.0",
|
|
||||||
"itertools",
|
|
||||||
"log",
|
"log",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-logger",
|
"wasm-logger",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"yew",
|
"yew",
|
||||||
|
"yew-hooks",
|
||||||
"yew-router",
|
"yew-router",
|
||||||
"yew-websocket",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -904,26 +857,6 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gloo-net"
|
|
||||||
version = "0.2.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9902a044653b26b99f7e3693a42f171312d9be8b26b5697bd1e43ad1f8a35e10"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"gloo-utils 0.1.7",
|
|
||||||
"js-sys",
|
|
||||||
"pin-project",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-net"
|
name = "gloo-net"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -987,27 +920,6 @@ dependencies = [
|
|||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gloo-net"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
"gloo-utils 0.2.0",
|
|
||||||
"http 1.1.0",
|
|
||||||
"js-sys",
|
|
||||||
"pin-project",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gloo-render"
|
name = "gloo-render"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@ -1377,15 +1289,6 @@ version = "1.70.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.13.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@ -1488,12 +1391,6 @@ dependencies = [
|
|||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nonempty"
|
|
||||||
version = "0.7.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nu-ansi-term"
|
name = "nu-ansi-term"
|
||||||
version = "0.46.0"
|
version = "0.46.0"
|
||||||
@ -1871,11 +1768,8 @@ dependencies = [
|
|||||||
name = "server"
|
name = "server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
|
||||||
"axum",
|
"axum",
|
||||||
"axum-client-ip",
|
|
||||||
"axum-login",
|
"axum-login",
|
||||||
"axum_static",
|
|
||||||
"ciborium",
|
"ciborium",
|
||||||
"clap",
|
"clap",
|
||||||
"common",
|
"common",
|
||||||
@ -1885,7 +1779,7 @@ dependencies = [
|
|||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower",
|
"tower",
|
||||||
"tower-http 0.6.1",
|
"tower-http",
|
||||||
"tower-sessions",
|
"tower-sessions",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
@ -2183,31 +2077,6 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tower-http"
|
|
||||||
version = "0.5.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
"bytes",
|
|
||||||
"futures-util",
|
|
||||||
"http 1.1.0",
|
|
||||||
"http-body",
|
|
||||||
"http-body-util",
|
|
||||||
"http-range-header",
|
|
||||||
"httpdate",
|
|
||||||
"mime",
|
|
||||||
"mime_guess",
|
|
||||||
"percent-encoding",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
"tower-layer",
|
|
||||||
"tower-service",
|
|
||||||
"tracing",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-http"
|
name = "tower-http"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
@ -2670,6 +2539,22 @@ dependencies = [
|
|||||||
"yew-macro",
|
"yew-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yew-hooks"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cbe7d30ef9d9afb9be38b1b310c42d59963c6d1c950f0e0435b78b346b4b24bf"
|
||||||
|
dependencies = [
|
||||||
|
"gloo 0.11.0",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"yew",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yew-macro"
|
name = "yew-macro"
|
||||||
version = "0.21.0"
|
version = "0.21.0"
|
||||||
@ -2715,28 +2600,6 @@ dependencies = [
|
|||||||
"syn 2.0.79",
|
"syn 2.0.79",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "yew-websocket"
|
|
||||||
version = "1.21.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6d75b4aa6d3a7644d649b80413f6095281c3897aeda85b5519ed12fd3de5b013"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"futures",
|
|
||||||
"gloo 0.8.1",
|
|
||||||
"gloo-console 0.2.3",
|
|
||||||
"gloo-net 0.2.6",
|
|
||||||
"js-sys",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"serde_json",
|
|
||||||
"thiserror",
|
|
||||||
"wasm-bindgen",
|
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
|
||||||
"yew",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.35"
|
version = "0.7.35"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ChatMessage {
|
pub struct ChatMessage {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub time: DateTime<Local>,
|
pub time: DateTime<Local>,
|
||||||
|
@ -9,14 +9,10 @@ ciborium = "0.2.2"
|
|||||||
common = { version = "0.1.0", path = "../common" }
|
common = { version = "0.1.0", path = "../common" }
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
gloo = "0.11.0"
|
gloo = "0.11.0"
|
||||||
gloo-console = "0.3.0"
|
|
||||||
gloo-net = "0.6.0"
|
|
||||||
gloo-timers = "0.3.0"
|
|
||||||
itertools = "0.13.0"
|
|
||||||
log = "0.4.22"
|
log = "0.4.22"
|
||||||
wasm-bindgen = "0.2.93"
|
wasm-bindgen = "0.2.93"
|
||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
web-sys = { version = "0.3.70", features = ["Navigator", "WebSocket", "EventListener"] }
|
web-sys = { version = "0.3.70", features = ["Navigator", "WebSocket", "EventListener"] }
|
||||||
yew = { version = "0.21.0", features = ["csr"] }
|
yew = { version = "0.21.0", features = ["csr"] }
|
||||||
|
yew-hooks = "0.3.2"
|
||||||
yew-router = "0.18.0"
|
yew-router = "0.18.0"
|
||||||
yew-websocket = "1.21.0"
|
|
||||||
|
11
frontend/src/components/alert.rs
Normal file
11
frontend/src/components/alert.rs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct Props {
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Alert(props: &Props) -> Html {
|
||||||
|
html! { <div class="alert alert-warning" role="alert">{ &props.message }</div> }
|
||||||
|
}
|
19
frontend/src/components/message.rs
Normal file
19
frontend/src/components/message.rs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
use common::ChatMessage;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Properties, PartialEq)]
|
||||||
|
pub struct Props {
|
||||||
|
pub message: ChatMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Message(props: &Props) -> Html {
|
||||||
|
html! {
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="ms-1 me-2">{ props.message.message.clone() }</div>
|
||||||
|
<div class="me-2 text-nowrap">
|
||||||
|
{ props.message.time.format("%I:%M:%S %p").to_string() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
8
frontend/src/components/mod.rs
Normal file
8
frontend/src/components/mod.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
mod alert;
|
||||||
|
pub use alert::Alert;
|
||||||
|
|
||||||
|
mod message;
|
||||||
|
pub use message::Message;
|
||||||
|
|
||||||
|
mod nav;
|
||||||
|
pub use nav::Nav;
|
31
frontend/src/components/nav.rs
Normal file
31
frontend/src/components/nav.rs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Nav() -> Html {
|
||||||
|
html! {
|
||||||
|
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">{ "Chat" }</a>
|
||||||
|
<div class="nav-item dropdown navbar-nav">
|
||||||
|
<a
|
||||||
|
class="nav-link dropdown-toggle"
|
||||||
|
href="#"
|
||||||
|
role="button"
|
||||||
|
data-bs-toggle="dropdown"
|
||||||
|
aria-expanded="false"
|
||||||
|
>
|
||||||
|
{ "PJHT" }
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">{ "Options" }</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a class="dropdown-item" href="#">{ "Logout" }</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
use std::{sync::Arc, time::Duration};
|
pub mod components;
|
||||||
|
use components::{Alert, Message, Nav};
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use common::ChatMessage;
|
use common::ChatMessage;
|
||||||
use gloo::{events::EventListener, net::http::Request};
|
use gloo::{events::EventListener, net::http::Request, console::log, timers::future::sleep};
|
||||||
use gloo_console::log;
|
|
||||||
use gloo_timers::future::sleep;
|
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::{js_sys::Uint8Array, CloseEvent, Event, MessageEvent, WebSocket};
|
use web_sys::{js_sys::Uint8Array, CloseEvent, Event, MessageEvent, WebSocket};
|
||||||
use yew::{platform::spawn_local, prelude::*};
|
use yew::{platform::spawn_local, prelude::*};
|
||||||
@ -188,14 +189,10 @@ impl Component for Home {
|
|||||||
<Nav />
|
<Nav />
|
||||||
<div class="container-fluid d-flex flex-column flex-grow-1 mt-3">
|
<div class="container-fluid d-flex flex-column flex-grow-1 mt-3">
|
||||||
if self.ws_state != WsState::Open {
|
if self.ws_state != WsState::Open {
|
||||||
<div class="alert alert-warning" role="alert">
|
<Alert message="Connection to backend lost, trying to reconnect"/>
|
||||||
{ "Connection to backend lost, trying to reconnect" }
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
if !self.authenticated {
|
if !self.authenticated {
|
||||||
<div class="alert alert-warning" role="alert">
|
<Alert message="Authenticating to backend"/>
|
||||||
{ "Authenticating to backend" }
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
<div
|
<div
|
||||||
ref={self.message_container_ref.clone()}
|
ref={self.message_container_ref.clone()}
|
||||||
@ -203,10 +200,8 @@ impl Component for Home {
|
|||||||
style="flex-basis: 0"
|
style="flex-basis: 0"
|
||||||
>
|
>
|
||||||
{ self.messages.iter().rev().map(|msg| html!{
|
{ self.messages.iter().rev().map(|msg| html!{
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<Message message={msg.clone()}/>
|
||||||
<div class="ms-1 me-2">{msg.message.clone()}</div>
|
}
|
||||||
<div class="me-2 text-nowrap">{msg.time.format("%I:%M:%S %p").to_string()}</div>
|
|
||||||
</div>}
|
|
||||||
).collect::<Vec<_>>() }
|
).collect::<Vec<_>>() }
|
||||||
</div>
|
</div>
|
||||||
<div class="bottom-0 mb-3 mt-3">
|
<div class="bottom-0 mb-3 mt-3">
|
||||||
@ -304,37 +299,7 @@ impl Component for Home {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[function_component]
|
|
||||||
fn Nav() -> Html {
|
|
||||||
html! {
|
|
||||||
<nav class="navbar navbar-expand-lg bg-body-tertiary">
|
|
||||||
<div class="container-fluid">
|
|
||||||
<a class="navbar-brand" href="#">{ "Chat" }</a>
|
|
||||||
<div class="nav-item dropdown navbar-nav">
|
|
||||||
<a
|
|
||||||
class="nav-link dropdown-toggle"
|
|
||||||
href="#"
|
|
||||||
role="button"
|
|
||||||
data-bs-toggle="dropdown"
|
|
||||||
aria-expanded="false"
|
|
||||||
>
|
|
||||||
{ "PJHT" }
|
|
||||||
</a>
|
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="#">{ "Options" }</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a class="dropdown-item" href="#">{ "Logout" }</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
fn App() -> Html {
|
fn App() -> Html {
|
||||||
html! {
|
html! {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user