gui: add server add and edit modals
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 12V17" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 12V17" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4 7H20" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M6 10V18C6 19.6569 7.34315 21 9 21H15C16.6569 21 18 19.6569 18 18V10" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M9 5C9 3.89543 9.89543 3 11 3H13C14.1046 3 15 3.89543 15 5V7H9V5Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 862 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21.2799 6.40005L11.7399 15.94C10.7899 16.89 7.96987 17.33 7.33987 16.7C6.70987 16.07 7.13987 13.25 8.08987 12.3L17.6399 2.75002C17.8754 2.49308 18.1605 2.28654 18.4781 2.14284C18.7956 1.99914 19.139 1.92124 19.4875 1.9139C19.8359 1.90657 20.1823 1.96991 20.5056 2.10012C20.8289 2.23033 21.1225 2.42473 21.3686 2.67153C21.6147 2.91833 21.8083 3.21243 21.9376 3.53609C22.0669 3.85976 22.1294 4.20626 22.1211 4.55471C22.1128 4.90316 22.0339 5.24635 21.8894 5.5635C21.7448 5.88065 21.5375 6.16524 21.2799 6.40005V6.40005Z" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M11 4H6C4.93913 4 3.92178 4.42142 3.17163 5.17157C2.42149 5.92172 2 6.93913 2 8V18C2 19.0609 2.42149 20.0783 3.17163 20.8284C3.92178 21.5786 4.93913 22 6 22H17C19.21 22 20 20.2 20 18V13" stroke="#000000" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -538,6 +538,173 @@ a:visited {
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Add server — dashed outline style to distinguish from real cards */
|
||||||
|
.add-server-btn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.85rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 2px dashed rgba(255, 255, 255, 0.2);
|
||||||
|
background: transparent;
|
||||||
|
color: rgba(255, 255, 255, 0.45);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s, color 0.15s;
|
||||||
|
width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-server-btn:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.4);
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.0);
|
||||||
|
z-index: 999;
|
||||||
|
animation: backdrop-fade-in 150ms ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-container {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
/* Make this solid or nearly solid instead of see-through */
|
||||||
|
background: #141414;
|
||||||
|
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
|
||||||
|
|
||||||
|
padding: 1.25rem 1.5rem 1.4rem;
|
||||||
|
width: 500px;
|
||||||
|
max-width: 90vw;
|
||||||
|
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.9rem;
|
||||||
|
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
animation: modal-pop-in 160ms ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal h2 {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form layout */
|
||||||
|
|
||||||
|
.modal-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-field label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-field input {
|
||||||
|
padding: 0.55rem 0.6rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-field input::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.45);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-field input:focus {
|
||||||
|
border-color: rgba(255, 255, 255, 0.55);
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions row */
|
||||||
|
|
||||||
|
.modal-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Secondary button (Cancel) */
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
padding: 0.5rem 0.9rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
background: rgba(255, 255, 255, 0.07);
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s, border-color 0.15s, transform 0.1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-color: rgba(255, 255, 255, 0.35);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn:active {
|
||||||
|
transform: translateY(0) scale(0.97);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Primary button (Save) */
|
||||||
|
|
||||||
|
.modal-btn--primary {
|
||||||
|
background: rgba(67, 156, 255, 0.85);
|
||||||
|
border-color: rgba(67, 156, 255, 1);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn--primary:hover {
|
||||||
|
background: rgba(92, 174, 255, 0.95);
|
||||||
|
border-color: rgba(135, 196, 255, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delete button (danger) */
|
||||||
|
|
||||||
|
.modal-btn--danger {
|
||||||
|
background: rgba(220, 60, 60, 0.85);
|
||||||
|
border-color: rgba(220, 60, 60, 1);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn--danger:hover {
|
||||||
|
background: rgba(240, 80, 80, 0.95);
|
||||||
|
border-color: rgba(255, 120, 120, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-actions__spacer {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* Override mode username row */
|
/* Override mode username row */
|
||||||
|
|
||||||
.override-username-row {
|
.override-username-row {
|
||||||
@@ -592,3 +759,21 @@ a:visited {
|
|||||||
min-width: 60px;
|
min-width: 60px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Keyframes */
|
||||||
|
|
||||||
|
@keyframes backdrop-fade-in {
|
||||||
|
from { background: rgba(0, 0, 0, 0.0); }
|
||||||
|
to { background: rgba(0, 0, 0, 0.4); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes modal-pop-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+297
-1
@@ -497,7 +497,9 @@ pub fn ServerView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn ServerCard(
|
fn ServerCard(
|
||||||
|
idx: usize,
|
||||||
server: ServerEntry,
|
server: ServerEntry,
|
||||||
|
editing_index: Signal<Option<usize>>,
|
||||||
overrides: Resource<ProxyOverrides>,
|
overrides: Resource<ProxyOverrides>,
|
||||||
) -> Element {
|
) -> Element {
|
||||||
let user_config = use_context::<ConfigSystem>();
|
let user_config = use_context::<ConfigSystem>();
|
||||||
@@ -523,6 +525,14 @@ fn ServerCard(
|
|||||||
address: server.address.clone(),
|
address: server.address.clone(),
|
||||||
port: server.port,
|
port: server.port,
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
class: "server-card__action",
|
||||||
|
onclick: move |_| editing_index.set(Some(idx)),
|
||||||
|
img {
|
||||||
|
src: asset!("assets/edit-3-svgrepo-com.svg"),
|
||||||
|
alt: "Edit",
|
||||||
|
}
|
||||||
|
}
|
||||||
button {
|
button {
|
||||||
class: "server-card__action server-card__action--connect",
|
class: "server-card__action server-card__action--connect",
|
||||||
onclick: {
|
onclick: {
|
||||||
@@ -643,13 +653,16 @@ fn OverrideLoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
||||||
let user_config = use_context::<ConfigSystem>();
|
let user_config = use_context::<ConfigSystem>();
|
||||||
|
let net: Coroutine<Command> = use_coroutine_handle();
|
||||||
let state = use_context::<SharedState>();
|
let state = use_context::<SharedState>();
|
||||||
|
|
||||||
let servers = use_signal(|| {
|
let mut servers = use_signal(|| {
|
||||||
user_config
|
user_config
|
||||||
.config_get::<Vec<ServerEntry>>("servers")
|
.config_get::<Vec<ServerEntry>>("servers")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
});
|
});
|
||||||
|
let mut show_add_modal = use_signal(|| false);
|
||||||
|
let mut editing_index = use_signal(|| None::<usize>);
|
||||||
|
|
||||||
let is_override_mode = overrides
|
let is_override_mode = overrides
|
||||||
.read()
|
.read()
|
||||||
@@ -677,7 +690,9 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
for (idx, server) in servers.read().iter().enumerate() {
|
for (idx, server) in servers.read().iter().enumerate() {
|
||||||
ServerCard {
|
ServerCard {
|
||||||
key: "{idx}",
|
key: "{idx}",
|
||||||
|
idx,
|
||||||
server: server.clone(),
|
server: server.clone(),
|
||||||
|
editing_index,
|
||||||
overrides,
|
overrides,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -695,6 +710,65 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
),
|
),
|
||||||
_ => rsx!(),
|
_ => rsx!(),
|
||||||
}
|
}
|
||||||
|
button {
|
||||||
|
class: "add-server-btn",
|
||||||
|
onclick: move |_| show_add_modal.set(true),
|
||||||
|
"+ Add Server"
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerModals { servers, show_add_modal, editing_index }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn ServerModals(
|
||||||
|
servers: Signal<Vec<ServerEntry>>,
|
||||||
|
show_add_modal: Signal<bool>,
|
||||||
|
editing_index: Signal<Option<usize>>,
|
||||||
|
) -> Element {
|
||||||
|
let user_config = use_context::<ConfigSystem>();
|
||||||
|
|
||||||
|
rsx!(
|
||||||
|
if *show_add_modal.read() {
|
||||||
|
{
|
||||||
|
let user_config = user_config.clone();
|
||||||
|
let mut servers = servers;
|
||||||
|
let mut show_add_modal = show_add_modal;
|
||||||
|
rsx!(AddServerModal {
|
||||||
|
on_save: move |entry: ServerEntry| {
|
||||||
|
servers.write().push(entry);
|
||||||
|
user_config.config_set("servers", &*servers.read());
|
||||||
|
show_add_modal.set(false);
|
||||||
|
},
|
||||||
|
on_cancel: move |_| show_add_modal.set(false),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(idx) = *editing_index.read() {
|
||||||
|
if let Some(entry) = servers.read().get(idx).cloned() {
|
||||||
|
{
|
||||||
|
let user_config_save = user_config.clone();
|
||||||
|
let user_config_del = user_config.clone();
|
||||||
|
let mut servers = servers;
|
||||||
|
let mut editing_index = editing_index;
|
||||||
|
rsx!(EditServerModal {
|
||||||
|
entry,
|
||||||
|
on_save: move |updated: ServerEntry| {
|
||||||
|
servers.write()[idx] = updated;
|
||||||
|
user_config_save.config_set("servers", &*servers.read());
|
||||||
|
editing_index.set(None);
|
||||||
|
},
|
||||||
|
on_delete: move |_| {
|
||||||
|
servers.write().remove(idx);
|
||||||
|
user_config_del.config_set("servers", &*servers.read());
|
||||||
|
editing_index.set(None);
|
||||||
|
},
|
||||||
|
on_cancel: move |_| editing_index.set(None),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -741,6 +815,228 @@ fn ServerPingInfo(address: String, port: u16) -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn AddServerModal(on_save: EventHandler<ServerEntry>, on_cancel: EventHandler<()>) -> Element {
|
||||||
|
let user_config = use_context::<ConfigSystem>();
|
||||||
|
let mut name = use_signal(|| String::new());
|
||||||
|
let mut address = use_signal(|| String::new());
|
||||||
|
let mut port = use_signal(|| "64738".to_string());
|
||||||
|
let mut username = use_signal(|| {
|
||||||
|
user_config
|
||||||
|
.config_get::<String>("username")
|
||||||
|
.unwrap_or_default()
|
||||||
|
});
|
||||||
|
let mut password = use_signal(|| String::new());
|
||||||
|
|
||||||
|
let do_save = move |_| {
|
||||||
|
let port_num: u16 = port.read().parse().unwrap_or(64738);
|
||||||
|
on_save.call(ServerEntry {
|
||||||
|
name: name.read().clone(),
|
||||||
|
address: address.read().clone(),
|
||||||
|
port: port_num,
|
||||||
|
username: username.read().clone(),
|
||||||
|
password: if password.read().is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(password.read().clone())
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "modal-backdrop",
|
||||||
|
onclick: move |_| on_cancel.call(()),
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-container",
|
||||||
|
onclick: move |evt| evt.stop_propagation(),
|
||||||
|
div {
|
||||||
|
class: "modal",
|
||||||
|
h2 { "Add Server" }
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Name" }
|
||||||
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
placeholder: "My Mumble Server",
|
||||||
|
value: "{name.read()}",
|
||||||
|
oninput: move |evt| name.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Address" }
|
||||||
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
placeholder: "mumble.example.com",
|
||||||
|
value: "{address.read()}",
|
||||||
|
oninput: move |evt| address.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Port" }
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
placeholder: "64738",
|
||||||
|
value: "{port.read()}",
|
||||||
|
oninput: move |evt| port.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Username" }
|
||||||
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
placeholder: "Nickname",
|
||||||
|
value: "{username.read()}",
|
||||||
|
oninput: move |evt| username.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Password (optional)" }
|
||||||
|
input {
|
||||||
|
r#type: "password",
|
||||||
|
placeholder: "Password",
|
||||||
|
value: "{password.read()}",
|
||||||
|
oninput: move |evt| password.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-actions",
|
||||||
|
button {
|
||||||
|
class: "modal-btn",
|
||||||
|
onclick: move |_| on_cancel.call(()),
|
||||||
|
"Cancel"
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
class: "modal-btn modal-btn--primary",
|
||||||
|
disabled: address.read().is_empty() || username.read().is_empty(),
|
||||||
|
onclick: do_save,
|
||||||
|
"Save"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn EditServerModal(
|
||||||
|
entry: ServerEntry,
|
||||||
|
on_save: EventHandler<ServerEntry>,
|
||||||
|
on_delete: EventHandler<()>,
|
||||||
|
on_cancel: EventHandler<()>,
|
||||||
|
) -> Element {
|
||||||
|
let mut name = use_signal(|| entry.name.clone());
|
||||||
|
let mut address = use_signal(|| entry.address.clone());
|
||||||
|
let mut port = use_signal(|| entry.port.to_string());
|
||||||
|
let mut username = use_signal(|| entry.username.clone());
|
||||||
|
let mut password = use_signal(|| entry.password.clone().unwrap_or_default());
|
||||||
|
|
||||||
|
let do_save = move |_| {
|
||||||
|
let port_num: u16 = port.read().parse().unwrap_or(64738);
|
||||||
|
on_save.call(ServerEntry {
|
||||||
|
name: name.read().clone(),
|
||||||
|
address: address.read().clone(),
|
||||||
|
port: port_num,
|
||||||
|
username: username.read().clone(),
|
||||||
|
password: if password.read().is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(password.read().clone())
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
class: "modal-backdrop",
|
||||||
|
onclick: move |_| on_cancel.call(()),
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-container",
|
||||||
|
onclick: move |evt| evt.stop_propagation(),
|
||||||
|
div {
|
||||||
|
class: "modal",
|
||||||
|
h2 { "Edit Server" }
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Name" }
|
||||||
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
placeholder: "My Mumble Server",
|
||||||
|
value: "{name.read()}",
|
||||||
|
oninput: move |evt| name.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Address" }
|
||||||
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
placeholder: "mumble.example.com",
|
||||||
|
value: "{address.read()}",
|
||||||
|
oninput: move |evt| address.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Port" }
|
||||||
|
input {
|
||||||
|
r#type: "number",
|
||||||
|
placeholder: "64738",
|
||||||
|
value: "{port.read()}",
|
||||||
|
oninput: move |evt| port.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Username" }
|
||||||
|
input {
|
||||||
|
r#type: "text",
|
||||||
|
placeholder: "Nickname",
|
||||||
|
value: "{username.read()}",
|
||||||
|
oninput: move |evt| username.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-field",
|
||||||
|
label { "Password (optional)" }
|
||||||
|
input {
|
||||||
|
r#type: "password",
|
||||||
|
placeholder: "Password",
|
||||||
|
value: "{password.read()}",
|
||||||
|
oninput: move |evt| password.set(evt.value().clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
|
class: "modal-actions",
|
||||||
|
button {
|
||||||
|
class: "modal-btn modal-btn--danger",
|
||||||
|
onclick: move |_| on_delete.call(()),
|
||||||
|
"Delete"
|
||||||
|
}
|
||||||
|
span { class: "modal-actions__spacer" }
|
||||||
|
button {
|
||||||
|
class: "modal-btn",
|
||||||
|
onclick: move |_| on_cancel.call(()),
|
||||||
|
"Cancel"
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
class: "modal-btn modal-btn--primary",
|
||||||
|
disabled: address.read().is_empty() || username.read().is_empty(),
|
||||||
|
onclick: do_save,
|
||||||
|
"Save"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn app() -> Element {
|
pub fn app() -> Element {
|
||||||
static STYLE: Asset = asset!("/assets/main.scss");
|
static STYLE: Asset = asset!("/assets/main.scss");
|
||||||
|
|||||||
Reference in New Issue
Block a user