diff --git a/frontend/public/assets/i18n/en.json b/frontend/public/assets/i18n/en.json
index 181ded960..e2817dca1 100644
--- a/frontend/public/assets/i18n/en.json
+++ b/frontend/public/assets/i18n/en.json
@@ -189,7 +189,7 @@
"RENAME_SETTINGS": "Rename Settings",
"PATTERN": "Pattern",
"NEW_NAME": "New Name",
- "SKIP": "Skip",
+ "SKIP": "Skip",
"LIVE_STREAM_USE_PREFIX": "Live use prefix",
"LIVE_STREAM_WITHOUT_EXTENSION": "Live w/o ext.",
"ALIAS": "Alias",
@@ -201,12 +201,12 @@
"COUNTER": "Counter",
"MAPPING_PATH": "Mapping dir",
"USER_CONFIG_DIR": "User Config dir",
- "USER_ACCESS_CONTROL": "User Access Control",
+ "USER_ACCESS_CONTROL": "User Access Control",
"SLEEP_TIMER_MINS": "Sleep Timer (mins)",
- "CONNECT_TIMEOUT_SECS": "Connect Timeout (secs)",
+ "CONNECT_TIMEOUT_SECS": "Connect Timeout (secs)",
"CONFIG_HOT_RELOAD": "Config Hot Reload",
- "CUSTOM_STREAM_RESPONSE_PATH": "Custom Stream Response dir",
- "REVERSE_PROXY" : "Reverse Proxy",
+ "CUSTOM_STREAM_RESPONSE_PATH": "Custom Stream Response dir",
+ "REVERSE_PROXY": "Reverse Proxy",
"WEB_SEARCH": "Web Search",
"USER_UI_ENABLED": "User-UI Enabled",
"RESOURCE_REWRITE_DISABLED": "Resource Rewrite Disabled",
@@ -220,13 +220,13 @@
"THROTTLE_KBPS": "Throttle kbps",
"FRIENDLY_NAME": "Friendly Name",
"MANUFACTURER": "Manufacturer",
- "MODEL_NAME": "Model Name",
+ "MODEL_NAME": "Model Name",
"MODEL_NUMBER": "Model Number",
- "FIRMWARE_NAME": "Firmware Name",
+ "FIRMWARE_NAME": "Firmware Name",
"FIRMWARE_VERSION": "Firmware Version",
- "DEVICE_TYPE": "Device Type",
+ "DEVICE_TYPE": "Device Type",
"DEVICE_UDN": "Device UDN",
- "TUNER_COUNT": "Tuner Count",
+ "TUNER_COUNT": "Tuner Count",
"SOURCE": "Source",
"PRIORITY": "Priority",
"LOGO_OVERRIDE": "logo Override",
@@ -238,12 +238,12 @@
"BEST_MATCH_THRESHOLD": "Best Match Threshold",
"NORMALIZE_REGEX": "Normalize Regex",
"NAME_PREFIX": "Name Prefix",
- "NAME_PREFIX_SEPARATOR":"Name Prefix Separator",
+ "NAME_PREFIX_SEPARATOR": "Name Prefix Separator",
"STRIP": "Strip",
"COPY_LINK_TULIPROX_VIRTUAL_ID": "Copy Virtual Id",
"COPY_LINK_TULIPROX_WEBPLAYER_URL": "Copy Webplayer Url",
"COPY_LINK_PROVIDER_URL": "Copy Provider Url",
- "CONTENT_SECURITY_POLICY" : "Content Security Policy",
+ "CONTENT_SECURITY_POLICY": "Content Security Policy",
"CUSTOM_ATTRIBUTES": "Custom Attributes",
"EMPTY": "",
"OPTIONS": "Options",
@@ -260,12 +260,18 @@
"ALIASES": "Aliases",
"MAX_CONNECTIONS": "Max Connections",
"METHOD": "Method",
- "EPG_TIMESHIFT": "Epg Timeshift",
- "CREATED_AT": "Created at",
- "EXP_DATE": "Exp. date",
- "UI_ENABLED": "Ui",
+ "EPG_TIMESHIFT": "Epg Timeshift",
+ "CREATED_AT": "Created at",
+ "EXP_DATE": "Exp. date",
+ "UI_ENABLED": "Ui",
"COMMENT": "Comment",
- "STAGED": "Staged"
+ "STAGED": "Staged",
+ "USER_STATUS_ACTIVE": "Active",
+ "USER_STATUS_EXPIRED": "Expired",
+ "USER_STATUS_BANNED": "Banned",
+ "USER_STATUS_TRIAL": "Trial",
+ "USER_STATUS_DISABLED": "Disabled",
+ "USER_STATUS_PENDING": "Pending"
},
"TITLE": {
"USER_BOUQUET_EDITOR": "User group editor"
@@ -276,7 +282,7 @@
"INVALID_FILETYPE": "Invalid filetype",
"TARGET_NOT_EXISTS": "Target does not exist",
"CONFIRM_DELETE": "You really want to delete?",
- "CONFIG_CHANGED": "Configuration on server changed.",
+ "CONFIG_CHANGED": "Configuration on server changed.",
"DOWNLOAD": {
"SUCCESS": "Successfully downloaded",
"FAIL": "Failed to download!",
diff --git a/frontend/scss/app/_component.scss b/frontend/scss/app/_component.scss
index b69addab8..f673ac791 100644
--- a/frontend/scss/app/_component.scss
+++ b/frontend/scss/app/_component.scss
@@ -66,3 +66,4 @@
@forward "components/config/config_view";
@forward "components/tabset";
@forward "components/form";
+@forward "components/select";
\ No newline at end of file
diff --git a/frontend/scss/app/components/_select.scss b/frontend/scss/app/components/_select.scss
new file mode 100644
index 000000000..5a87fd0f6
--- /dev/null
+++ b/frontend/scss/app/components/_select.scss
@@ -0,0 +1,45 @@
+.tp__select {
+ display: flex;
+ flex-flow: column;
+ overflow: hidden;
+ box-sizing: border-box;
+ gap: 6px;
+
+ &-wrapper {
+ fill: var(--menu-icon-color);
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ overflow: hidden;
+ box-sizing: border-box;
+ padding-left: var(--padding-mini);
+ border: 1px solid var(--input-border-color);
+ border-radius: var(--border-radius);
+ background-color: var(--input-background-color);
+ &:focus-within {
+ border-color: var(--input-focus-border-color);
+ }
+
+ .svg-icon {
+ height: 2rem;
+ width: 2rem;
+ }
+
+ .tp__icon-button {
+ .svg-icon {
+ height: 1.5rem;
+ width: 1.5rem;
+ }
+ }
+ }
+ label {
+ padding-left: 2px;
+ color: var(--text-color);
+ }
+
+ &__selected {
+ display: flex;
+ flex-flow: row nowrap;
+ flex: 1 1 auto;
+ }
+}
diff --git a/frontend/src/app/components/config/macros.rs b/frontend/src/app/components/config/macros.rs
index c016adc67..c21a921fc 100644
--- a/frontend/src/app/components/config/macros.rs
+++ b/frontend/src/app/components/config/macros.rs
@@ -105,33 +105,6 @@ macro_rules! config_field_empty {
};
}
-#[macro_export]
-macro_rules! edit_field_text {
- ($instance:expr, $label:expr, $field:ident) => {
- $crate::edit_field_text!(@inner $instance, $label, $field, false)
- };
- ($instance:expr, $label:expr, $field:ident, $hidden:expr) => {
- $crate::edit_field_text!(@inner $instance, $label, $field, $hidden)
- };
- (@inner $instance:expr, $label:expr, $field:ident, $hidden:expr) => {{
- let instance = $instance.clone();
- html! {
-
- <$crate::app::components::input::Input
- label={$label}
- hidden={$hidden}
- name={stringify!($field)}
- autocomplete={true}
- value={instance.borrow().$field.clone()}
- ontext={Callback::from(move |value: String| {
- instance.borrow_mut().$field = value;
- })}
- />
-
- }
- }};
-}
-
#[macro_export]
macro_rules! edit_field_text_option {
($instance:expr, $label:expr, $field:ident) => {
@@ -163,6 +136,33 @@ macro_rules! edit_field_text_option {
}};
}
+#[macro_export]
+macro_rules! edit_field_text {
+ ($instance:expr, $label:expr, $field:ident) => {
+ $crate::edit_field_text!(@inner $instance, $label, $field, false)
+ };
+ ($instance:expr, $label:expr, $field:ident, $hidden:expr) => {
+ $crate::edit_field_text!(@inner $instance, $label, $field, $hidden)
+ };
+ (@inner $instance:expr, $label:expr, $field:ident, $hidden:expr) => {{
+ let instance = $instance.clone();
+ html! {
+
+ <$crate::app::components::input::Input
+ label={$label}
+ hidden={$hidden}
+ name={stringify!($field)}
+ autocomplete={true}
+ value={instance.borrow().$field.clone()}
+ ontext={Callback::from(move |value: String| {
+ instance.borrow_mut().$field = value;
+ })}
+ />
+
+ }
+ }};
+}
+
//
#[macro_export]
diff --git a/frontend/src/app/components/drop_down_icon_button.rs b/frontend/src/app/components/drop_down_icon_button.rs
index 002e58a2c..21140eb32 100644
--- a/frontend/src/app/components/drop_down_icon_button.rs
+++ b/frontend/src/app/components/drop_down_icon_button.rs
@@ -10,9 +10,9 @@ use crate::html_if;
#[derive(Clone, PartialEq, Debug)]
pub struct DropDownOption {
- id: String,
- label: String,
- selected: bool,
+ pub(crate) id: String,
+ pub(crate) label: String,
+ pub(crate) selected: bool,
}
impl DropDownOption {
@@ -31,7 +31,6 @@ pub struct DropDownIconButtonProps {
pub options: Rc>,
#[prop_or_default]
pub multi_select: bool,
-
}
#[function_component]
diff --git a/frontend/src/app/components/mod.rs b/frontend/src/app/components/mod.rs
index c8f3c5a95..395d9eeb4 100644
--- a/frontend/src/app/components/mod.rs
+++ b/frontend/src/app/components/mod.rs
@@ -35,6 +35,7 @@ mod accordion_panel;
mod csv_table;
mod config;
mod tabset;
+mod select;
// pub use self::input::*;
// pub use self::menu_item::*;
// pub use self::popup_menu::*;
diff --git a/frontend/src/app/components/select.rs b/frontend/src/app/components/select.rs
new file mode 100644
index 000000000..34a6b0ab7
--- /dev/null
+++ b/frontend/src/app/components/select.rs
@@ -0,0 +1,69 @@
+use std::rc::Rc;
+use log::warn;
+use yew::prelude::*;
+use crate::app::components::{DropDownIconButton, DropDownOption, Tag, TagList};
+
+#[derive(Properties, Clone, PartialEq, Debug)]
+pub struct SelectProps {
+ pub name: String,
+ #[prop_or_default]
+ pub icon: Option,
+ pub onselect: Callback<(String, Vec)>,
+ #[prop_or_default]
+ pub class: String,
+ pub options: Rc>,
+ #[prop_or_default]
+ pub multi_select: bool,
+}
+
+fn create_tag_from_str(s: &str) -> Rc {
+ Rc::new(Tag { label: s.to_owned(), class: Some("active".to_string()) })
+}
+
+fn create_tag_from_option(o: &DropDownOption) -> Rc {
+ create_tag_from_str(o.label.as_str())
+}
+
+#[function_component]
+pub fn Select(props: &SelectProps) -> Html {
+
+ // TODO render for selections !!!
+
+ let selected_options = use_state(|| props.options.as_ref().iter().filter(|o| o.selected)
+ .map(create_tag_from_option)
+ .collect::>>());
+ {
+ let set_selected_options = selected_options.clone();
+ use_effect_with(props.options.clone(), move |options| {
+ let selections = options.as_ref().iter().filter(|o| o.selected)
+ .map(create_tag_from_option)
+ .collect::>>();
+ set_selected_options.set(selections);
+ });
+ }
+
+ let handle_options_click = {
+ let onselect = props.onselect.clone();
+ let set_selections = selected_options.clone();
+ Callback::from(move |(name, selections): (String, Vec)| {
+ set_selections.set(selections.iter().map(|s|s.as_str()).map(create_tag_from_str).collect::>>());
+ onselect.emit((name, selections));
+ })
+ };
+
+ html! {
+
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/tag_list.rs b/frontend/src/app/components/tag_list.rs
index 8a3f73dbe..0fa3e7761 100644
--- a/frontend/src/app/components/tag_list.rs
+++ b/frontend/src/app/components/tag_list.rs
@@ -32,6 +32,14 @@ pub fn tag_list(props: &TagListProps) -> Html {
let tag_state = use_state(|| tags.clone());
let new_tag = use_state(String::default);
+ {
+ let tag_state = tag_state.clone();
+ use_effect_with(tags.clone(), move |tags| {
+ tag_state.set(tags.clone());
+ || ()
+ });
+ }
+
let on_remove = {
let tag_state = tag_state.clone();
let on_change = on_change.clone();
diff --git a/frontend/src/app/components/userlist/proxy_user_credentials_form.rs b/frontend/src/app/components/userlist/proxy_user_credentials_form.rs
index 1baf13af0..674695016 100644
--- a/frontend/src/app/components/userlist/proxy_user_credentials_form.rs
+++ b/frontend/src/app/components/userlist/proxy_user_credentials_form.rs
@@ -3,10 +3,11 @@ use std::rc::Rc;
use log::warn;
use yew::prelude::*;
use yew_i18n::use_translation;
-use shared::model::ProxyUserCredentialsDto;
+use shared::model::{ProxyUserCredentialsDto, ProxyUserStatus};
use crate::app::TargetUser;
use crate::{edit_field_bool, edit_field_text, edit_field_text_option};
-use crate::app::components::TextButton;
+use crate::app::components::select::Select;
+use crate::app::components::{convert_bool_to_chip_style, Chip, DropDownOption, TextButton};
#[derive(Properties, PartialEq, Clone)]
pub struct ProxyUserCredentialsFormProps {
@@ -16,18 +17,52 @@ pub struct ProxyUserCredentialsFormProps {
#[function_component]
pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html {
let translate = use_translation();
- let form_state = use_memo(props.user.clone(), |user| Rc::new(RefCell::new(user.as_ref().map_or_else(|| ProxyUserCredentialsDto::default(), |usr| usr.credentials.as_ref().clone()))));
+ let form_state = use_memo(props.user.clone(),
+ |user| RefCell::new(user.as_ref()
+ .map_or_else(|| ProxyUserCredentialsDto::default(), |usr| usr.credentials.as_ref().clone())));
+
+ let proxy_user_status = use_memo(props.user.clone(), |user|
+ vec![
+ ProxyUserStatus::Active,
+ ProxyUserStatus::Expired,
+ ProxyUserStatus::Banned,
+ ProxyUserStatus::Trial,
+ ProxyUserStatus::Disabled,
+ ProxyUserStatus::Pending,
+ ].iter().map(|s| DropDownOption {
+ id: s.to_string(),
+ label: format!("LABEL.USER_STATUS_{}", s.to_string().to_uppercase()),
+ selected: user.as_ref().is_some_and(|user| user.credentials.status.as_ref() == Some(s)),
+ }).collect::>()
+ );
let handle_save_user = {
- let user = form_state.clone();
+ let user = form_state.clone();
Callback::from(move |_| {
warn!("{:?}", user.borrow());
})
};
+ let user_active = props.user.as_ref().is_some_and(|u| u.credentials.is_active());
+
html! {
+
+
+
+
+
+
{ edit_field_text!(*form_state, translate.t("LABEL.USERNAME"), username) }
{ edit_field_text!(*form_state, translate.t("LABEL.PASSWORD"), password, true) }
{ edit_field_bool!(*form_state, translate.t("LABEL.USER_UI_ENABLED"), ui_enabled) }
@@ -40,7 +75,6 @@ pub fn ProxyUserCredentialsForm(props: &ProxyUserCredentialsFormProps) -> Html {
// pub created_at: Option
,
// pub exp_date: Option,
// pub max_connections: u32,
- // pub status: Option,
{ edit_field_text_option!(*form_state, translate.t("LABEL.COMMENT"), comment) }