From d2aa7e7db7b14fb2e80df8e50b270fbbd4131a46 Mon Sep 17 00:00:00 2001 From: euzu Date: Wed, 3 Dec 2025 18:07:40 +0100 Subject: [PATCH] Filter Input and Filter editor started --- frontend/public/config.json | 2 +- frontend/scss/app/_component.scss | 1 + frontend/scss/app/components/_filter.scss | 1 + .../scss/app/components/_filter_input.scss | 27 +++++++ frontend/scss/app/components/_input.scss | 2 +- .../app/components/filter/filter_editor.rs | 0 .../src/app/components/filter/filter_input.rs | 80 +++++++++++++++++++ frontend/src/app/components/filter/mod.rs | 5 ++ frontend/src/app/components/mod.rs | 7 +- .../source_editor/output_m3u_form.rs | 13 ++- .../source_editor/output_strm_form.rs | 11 ++- .../source_editor/output_xtream_form.rs | 11 ++- .../components/source_editor/target_form.rs | 11 ++- frontend/src/model/web_config.rs | 5 +- 14 files changed, 161 insertions(+), 15 deletions(-) create mode 100644 frontend/scss/app/components/_filter_input.scss create mode 100644 frontend/src/app/components/filter/filter_editor.rs create mode 100644 frontend/src/app/components/filter/filter_input.rs create mode 100644 frontend/src/app/components/filter/mod.rs diff --git a/frontend/public/config.json b/frontend/public/config.json index 2a055c157..e7bfcc794 100644 --- a/frontend/public/config.json +++ b/frontend/public/config.json @@ -7,7 +7,7 @@ "authUrl": "/auth/" }, "discord": "https://discord.gg/gkzCmWw9Tf", - "documentation": "https://github.com/euzu/tuliprox/blob/master/README.md", + "documentation": "https://euzu.github.io/tuliprox-docs/", "wsUrl" : "/ws", "protocolVersion": 1 } \ No newline at end of file diff --git a/frontend/scss/app/_component.scss b/frontend/scss/app/_component.scss index 7367da42a..3ad18a9e5 100644 --- a/frontend/scss/app/_component.scss +++ b/frontend/scss/app/_component.scss @@ -81,3 +81,4 @@ @forward "components/source_editor/input_form"; @forward "components/cluster_flags"; @forward "components/titled_card"; +@forward "components/filter_input"; diff --git a/frontend/scss/app/components/_filter.scss b/frontend/scss/app/components/_filter.scss index 729b0976e..3b0b6da03 100644 --- a/frontend/scss/app/components/_filter.scss +++ b/frontend/scss/app/components/_filter.scss @@ -61,6 +61,7 @@ .bracket-3 { color: var(--code-bracket-3-color); } } .tp__filter__inline { + overflow: hidden; .tp__filter__code { padding: 0; margin-block: 0; diff --git a/frontend/scss/app/components/_filter_input.scss b/frontend/scss/app/components/_filter_input.scss new file mode 100644 index 000000000..716b623e2 --- /dev/null +++ b/frontend/scss/app/components/_filter_input.scss @@ -0,0 +1,27 @@ +.tp__filter-input { + display: flex; + cursor: pointer; + fill: var(--modest-text-color); + + .tp__input-wrapper { + height: 2.5rem; + padding: 0 var(--padding-mini); + justify-content: space-between; + } + + &:hover { + fill: var(--text-color); + cursor: pointer; + filter: brightness(1.2); + } + + &__preview { + flex: 1; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-height: 2rem; + pointer-events: none; + } + +} \ No newline at end of file diff --git a/frontend/scss/app/components/_input.scss b/frontend/scss/app/components/_input.scss index 799cba9ed..d2d96a021 100644 --- a/frontend/scss/app/components/_input.scss +++ b/frontend/scss/app/components/_input.scss @@ -3,7 +3,7 @@ flex-flow: column; overflow: hidden; box-sizing: border-box; - gap: 6px; + gap: var(--gap-small); .tp__input-wrapper { display: flex; flex-flow: row nowrap; diff --git a/frontend/src/app/components/filter/filter_editor.rs b/frontend/src/app/components/filter/filter_editor.rs new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/app/components/filter/filter_input.rs b/frontend/src/app/components/filter/filter_input.rs new file mode 100644 index 000000000..39f92193c --- /dev/null +++ b/frontend/src/app/components/filter/filter_input.rs @@ -0,0 +1,80 @@ +use crate::app::components::{AppIcon, FilterView}; +use crate::services::DialogService; +use yew::platform::spawn_local; +use yew::prelude::*; +use shared::foundation::filter::get_filter; +use crate::app::{ConfigContext}; + +#[derive(Properties, Clone, PartialEq, Debug)] +pub struct FilterInputProps { + #[prop_or_default] + pub icon: String, + #[prop_or_default] + pub filter: Option, + #[prop_or_default] + pub on_change: Callback>, +} + +#[function_component] +pub fn FilterInput(props: &FilterInputProps) -> Html { + let config_ctx = use_context::().expect("Config context not found"); + let dialog = use_context::().expect("Dialog service not found"); + + let filter_state = use_state(|| None); + let parsed_filter_state = use_state(|| None); + let templates_state = use_state(|| None); + + { + let templates = templates_state.clone(); + let cfg_templates = config_ctx.config.as_ref().and_then(|c| c.sources.templates.clone()); + use_effect_with(cfg_templates, move |templ| { + templates.set(templ.clone()); + }); + } + + { + let filter = filter_state.clone(); + let parsed_filter = parsed_filter_state.clone(); + let templates = templates_state.clone(); + use_effect_with(props.filter.clone(), move |flt| { + filter.set(flt.clone()); + let parsed = if let Some(new_fltr) = flt.as_ref() { + get_filter(new_fltr, (*templates).as_ref()).ok() + } else { + None + }; + parsed_filter.set(parsed); + }); + } + + let handle_click = { + let dialog = dialog.clone(); + let current_filter = filter_state.clone(); + Callback::from(move |e: MouseEvent| { + e.prevent_default(); + let dlg = dialog.clone(); + spawn_local(async move { + //let filter_view = html!{}; + //let _result = dlg.content(filter_view, None).await; + }); + }) + }; + + html! { +
+
+ + { + match (*parsed_filter_state).as_ref() { + None => html! {}, + Some(preview) => html! { + + } + } + } + + +
+
+ } +} \ No newline at end of file diff --git a/frontend/src/app/components/filter/mod.rs b/frontend/src/app/components/filter/mod.rs new file mode 100644 index 000000000..dceb737ba --- /dev/null +++ b/frontend/src/app/components/filter/mod.rs @@ -0,0 +1,5 @@ +mod filter_editor; +mod filter_input; + +pub use filter_editor::*; +pub use filter_input::*; diff --git a/frontend/src/app/components/mod.rs b/frontend/src/app/components/mod.rs index 29b37babf..7920a8648 100644 --- a/frontend/src/app/components/mod.rs +++ b/frontend/src/app/components/mod.rs @@ -50,9 +50,12 @@ mod floating_background; mod source_editor; mod cluster_flags_input; mod title_card; +mod filter; // pub use self::input::*; // pub use self::menu_item::*; // pub use self::popup_menu::*; +//pub use self::number_input::*; +//pub use self::date_input::*; pub(crate) use self::cluster_flags_input::*; pub(crate) use self::toggle_switch::*; @@ -94,6 +97,4 @@ pub(crate) use self::loading_screen::*; pub(crate) use self::websocket_status::*; pub(crate) use self::source_editor::*; pub(crate) use self::title_card::*; - -//pub use self::number_input::*; -//pub use self::date_input::*; +pub(crate) use self::filter::*; diff --git a/frontend/src/app/components/source_editor/output_m3u_form.rs b/frontend/src/app/components/source_editor/output_m3u_form.rs index a86b0ac58..c27ad2e5f 100644 --- a/frontend/src/app/components/source_editor/output_m3u_form.rs +++ b/frontend/src/app/components/source_editor/output_m3u_form.rs @@ -1,6 +1,6 @@ use crate::app::components::config::HasFormData; -use crate::app::components::{BlockId, BlockInstance, Card, EditMode, SourceEditorContext, TextButton}; -use crate::{edit_field_bool, edit_field_text_option, generate_form_reducer}; +use crate::app::components::{BlockId, BlockInstance, Card, EditMode, FilterInput, SourceEditorContext, TextButton}; +use crate::{config_field_child, edit_field_bool, edit_field_text_option, generate_form_reducer}; use shared::model::{M3uTargetOutputDto, TargetOutputDto}; use std::rc::Rc; use yew::{function_component, html, use_context, use_effect_with, use_reducer, Callback, Html, Properties, UseReducerHandle}; @@ -54,12 +54,19 @@ pub fn M3uTargetOutputView(props: &M3uTargetOutputViewProps) -> Html { } let render_output = || { + let output_form_state_1 = output_form_state.clone(); html! { { edit_field_text_option!(output_form_state, translate.t(LABEL_FILENAME), filename, M3uTargetOutputFormAction::Filename) } { edit_field_bool!(output_form_state, translate.t(LABEL_INCLUDE_TYPE_IN_URL), include_type_in_url, M3uTargetOutputFormAction::IncludeTypeInUrl) } { edit_field_bool!(output_form_state, translate.t(LABEL_MASK_REDIRECT_URL), mask_redirect_url, M3uTargetOutputFormAction::MaskRedirectUrl) } - { edit_field_text_option!(output_form_state, translate.t(LABEL_FILTER), filter, M3uTargetOutputFormAction::Filter) } + { config_field_child!(translate.t(LABEL_FILTER), { + html! { + + } + })} } }; diff --git a/frontend/src/app/components/source_editor/output_strm_form.rs b/frontend/src/app/components/source_editor/output_strm_form.rs index 258078322..17f7d0c4d 100644 --- a/frontend/src/app/components/source_editor/output_strm_form.rs +++ b/frontend/src/app/components/source_editor/output_strm_form.rs @@ -1,6 +1,6 @@ use crate::app::components::config::HasFormData; use crate::app::components::select::Select; -use crate::app::components::{BlockId, BlockInstance, Card, DropDownOption, DropDownSelection, EditMode, Panel, SourceEditorContext, TextButton}; +use crate::app::components::{BlockId, BlockInstance, Card, DropDownOption, DropDownSelection, EditMode, FilterInput, Panel, SourceEditorContext, TextButton}; use crate::{config_field_child, edit_field_bool, edit_field_list_option, edit_field_text, edit_field_text_option, generate_form_reducer}; use shared::model::{StrmExportStyle, StrmTargetOutputDto, TargetOutputDto}; use std::fmt::Display; @@ -106,6 +106,7 @@ pub fn StrmTargetOutputView(props: &StrmTargetOutputViewProps) -> Html { let render_main = || { let output_form_state_1 = output_form_state.clone(); + let output_form_state_2 = output_form_state.clone(); html! { { edit_field_text!(output_form_state, translate.t(LABEL_DIRECTORY), directory, StrmTargetOutputFormAction::Directory) } @@ -134,7 +135,13 @@ pub fn StrmTargetOutputView(props: &StrmTargetOutputViewProps) -> Html { /> } })} - { edit_field_text_option!(output_form_state, translate.t(LABEL_FILTER), filter, StrmTargetOutputFormAction::Filter) } + { config_field_child!(translate.t(LABEL_FILTER), { + html! { + + } + })} } }; diff --git a/frontend/src/app/components/source_editor/output_xtream_form.rs b/frontend/src/app/components/source_editor/output_xtream_form.rs index a60e697df..9ff661416 100644 --- a/frontend/src/app/components/source_editor/output_xtream_form.rs +++ b/frontend/src/app/components/source_editor/output_xtream_form.rs @@ -1,5 +1,5 @@ use crate::app::components::config::HasFormData; -use crate::app::components::{BlockId, BlockInstance, Card, EditMode, IconButton, Panel, SourceEditorContext, TextButton, TraktListItemForm, TitledCard}; +use crate::app::components::{BlockId, BlockInstance, Card, EditMode, IconButton, Panel, SourceEditorContext, TextButton, TraktListItemForm, TitledCard, FilterInput}; use crate::{config_field_child, edit_field_bool, edit_field_number_u16, edit_field_text, edit_field_text_option, generate_form_reducer}; use shared::model::{TargetOutputDto, TraktApiConfigDto, TraktConfigDto, TraktContentType, TraktListConfigDto, XtreamTargetOutputDto}; use std::fmt::Display; @@ -165,6 +165,7 @@ pub fn XtreamTargetOutputView(props: &XtreamTargetOutputViewProps) -> Html { }; let render_output = || { + let output_form_state_1 = output_form_state.clone(); html! { @@ -186,7 +187,13 @@ pub fn XtreamTargetOutputView(props: &XtreamTargetOutputViewProps) -> Html { { edit_field_number_u16!(output_form_state, translate.t(LABEL_SERIES), resolve_series_delay, XtreamTargetOutputFormAction::ResolveSeriesDelay) } - { edit_field_text_option!(output_form_state, translate.t(LABEL_FILTER), filter, XtreamTargetOutputFormAction::Filter) } + { config_field_child!(translate.t(LABEL_FILTER), { + html! { + + } + })} } }; diff --git a/frontend/src/app/components/source_editor/target_form.rs b/frontend/src/app/components/source_editor/target_form.rs index af03f6465..2bd679a7d 100644 --- a/frontend/src/app/components/source_editor/target_form.rs +++ b/frontend/src/app/components/source_editor/target_form.rs @@ -1,6 +1,6 @@ use crate::app::components::config::HasFormData; use crate::app::components::select::Select; -use crate::app::components::{BlockId, BlockInstance, Card, ClusterFlagsInput, DropDownOption, DropDownSelection, EditMode, Panel, SourceEditorContext, TextButton}; +use crate::app::components::{BlockId, BlockInstance, Card, ClusterFlagsInput, DropDownOption, DropDownSelection, EditMode, FilterInput, Panel, SourceEditorContext, TextButton}; use crate::{config_field_child, edit_field_bool, edit_field_list_option, edit_field_text, generate_form_reducer}; use shared::model::{ClusterFlags, ConfigTargetDto, ConfigTargetOptions, ProcessingOrder}; use std::fmt::Display; @@ -160,6 +160,7 @@ pub fn ConfigTargetView(props: &ConfigTargetViewProps) -> Html { let render_target = || { let target_form_state_1 = target_form_state.clone(); + let target_form_state_2 = target_form_state.clone(); html! {
@@ -167,7 +168,13 @@ pub fn ConfigTargetView(props: &ConfigTargetViewProps) -> Html { { edit_field_bool!(target_form_state, translate.t(LABEL_USE_MEMORY_CACHE), use_memory_cache, ConfigTargetFormAction::UseMemoryCache) }
{ edit_field_text!(target_form_state, translate.t(LABEL_NAME), name, ConfigTargetFormAction::Name) } - { edit_field_text!(target_form_state, translate.t(LABEL_FILTER), filter, ConfigTargetFormAction::Filter) } + { config_field_child!(translate.t(LABEL_FILTER), { + html! { + | { + target_form_state_2.dispatch(ConfigTargetFormAction::Filter(new_filter.unwrap_or_else(String::new))); + })} /> + } + })} { config_field_child!(translate.t(LABEL_PROCESSING_ORDER), { html! { diff --git a/frontend/src/model/web_config.rs b/frontend/src/model/web_config.rs index 0eb6476fc..db519e842 100644 --- a/frontend/src/model/web_config.rs +++ b/frontend/src/model/web_config.rs @@ -1,6 +1,9 @@ use serde::{Deserialize, Serialize}; +pub fn default_documentation_url() -> String { String::from("https://euzu.github.io/tuliprox-docs/") } + + #[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Debug, Default)] pub struct ApiConfig { #[serde(alias = "apiUrl")] @@ -24,7 +27,7 @@ pub struct WebConfig { pub discord: String, #[serde(default)] pub github: String, - #[serde(default)] + #[serde(default = "default_documentation_url")] pub documentation: String, #[serde(alias = "wsUrl")] pub ws_url: String,