Filter Input and Filter editor started

This commit is contained in:
euzu
2025-12-03 18:07:40 +01:00
parent 9c5c79dea5
commit d2aa7e7db7
14 changed files with 161 additions and 15 deletions
+1 -1
View File
@@ -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
}
+1
View File
@@ -81,3 +81,4 @@
@forward "components/source_editor/input_form";
@forward "components/cluster_flags";
@forward "components/titled_card";
@forward "components/filter_input";
@@ -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;
@@ -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;
}
}
+1 -1
View File
@@ -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;
@@ -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<String>,
#[prop_or_default]
pub on_change: Callback<Option<String>>,
}
#[function_component]
pub fn FilterInput(props: &FilterInputProps) -> Html {
let config_ctx = use_context::<ConfigContext>().expect("Config context not found");
let dialog = use_context::<DialogService>().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!{<FilterView pretty={true} filter={current_filter} />};
//let _result = dlg.content(filter_view, None).await;
});
})
};
html! {
<div class={"tp__filter-input tp__input"} onclick={handle_click}>
<div class={"tp__input-wrapper"}>
<span class="tp__filter-input__preview">
{
match (*parsed_filter_state).as_ref() {
None => html! {},
Some(preview) => html! {
<FilterView inline={true} filter={preview.clone()} />
}
}
}
</span>
<AppIcon name={if props.icon.is_empty() { "Edit".to_owned() } else { props.icon.clone()} } />
</div>
</div>
}
}
@@ -0,0 +1,5 @@
mod filter_editor;
mod filter_input;
pub use filter_editor::*;
pub use filter_input::*;
+4 -3
View File
@@ -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::*;
@@ -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! {
<Card class="tp__config-view__card">
{ 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! {
<FilterInput filter={output_form_state_1.form.filter.clone()} on_change={Callback::from(move |new_filter| {
output_form_state_1.dispatch(M3uTargetOutputFormAction::Filter(new_filter));
})} />
}
})}
</Card>
}
};
@@ -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! {
<Card class="tp__config-view__card">
{ 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! {
<FilterInput filter={output_form_state_2.form.filter.clone()} on_change={Callback::from(move |new_filter| {
output_form_state_2.dispatch(StrmTargetOutputFormAction::Filter(new_filter));
})} />
}
})}
</Card>
}
};
@@ -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! {
<Card class="tp__config-view__card">
<TitledCard title={translate.t(LABEL_SKIP_DIRECT_SOURCE)}>
@@ -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) }
</div>
</TitledCard>
{ edit_field_text_option!(output_form_state, translate.t(LABEL_FILTER), filter, XtreamTargetOutputFormAction::Filter) }
{ config_field_child!(translate.t(LABEL_FILTER), {
html! {
<FilterInput filter={output_form_state_1.form.filter.clone()} on_change={Callback::from(move |new_filter| {
output_form_state_1.dispatch(XtreamTargetOutputFormAction::Filter(new_filter));
})} />
}
})}
</Card>
}
};
@@ -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! {
<Card class="tp__config-view__card">
<div class="tp__config-view__cols-2">
@@ -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) }
</div>
{ 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! {
<FilterInput filter={target_form_state_2.form.filter.clone()} on_change={Callback::from(move |new_filter: Option<String>| {
target_form_state_2.dispatch(ConfigTargetFormAction::Filter(new_filter.unwrap_or_else(String::new)));
})} />
}
})}
{ config_field_child!(translate.t(LABEL_PROCESSING_ORDER), {
html! {
+4 -1
View File
@@ -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,