mirror of
https://github.com/euzu/tuliprox.git
synced 2026-09-29 20:42:31 +02:00
webui
This commit is contained in:
@@ -147,7 +147,6 @@ impl ConfigInputAliasDto {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct ConfigInputDto {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::rc::Rc;
|
||||
use crate::error::{TuliproxError, TuliproxErrorKind};
|
||||
use crate::{create_tuliprox_error_result, handle_tuliprox_error_result_list, info_err};
|
||||
use crate::foundation::filter::{get_filter, Filter};
|
||||
@@ -86,10 +87,10 @@ pub struct HdHomeRunTargetOutputDto {
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||
#[serde(deny_unknown_fields, tag = "type", rename_all = "lowercase")]
|
||||
pub enum TargetOutputDto {
|
||||
Xtream(XtreamTargetOutputDto),
|
||||
M3u(M3uTargetOutputDto),
|
||||
Strm(StrmTargetOutputDto),
|
||||
HdHomeRun(HdHomeRunTargetOutputDto),
|
||||
Xtream(Rc<XtreamTargetOutputDto>),
|
||||
M3u(Rc<M3uTargetOutputDto>),
|
||||
Strm(Rc<StrmTargetOutputDto>),
|
||||
HdHomeRun(Rc<HdHomeRunTargetOutputDto>),
|
||||
}
|
||||
|
||||
impl TargetOutputDto {
|
||||
|
||||
@@ -14,4 +14,9 @@ $media-size-mobile: 600px;
|
||||
|
||||
.tp__pulse {
|
||||
animation: tp__pulse-bg 500ms ease-out;
|
||||
}
|
||||
|
||||
.tp__error-text {
|
||||
color: var(--attention-color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
@@ -18,3 +18,4 @@
|
||||
@forward "dashboard_view";
|
||||
@forward "playlist_view";
|
||||
@forward "playlist_assistant";
|
||||
@forward "reveal_content";
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
gap: 6px;
|
||||
.input-wrapper {
|
||||
.tp__input-wrapper {
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--input-border-color);
|
||||
|
||||
@@ -60,10 +60,5 @@
|
||||
gap: 20px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: var(--attention-color);
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
.tp__reveal_content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
fill: var(--modest-text-color);
|
||||
&:hover {
|
||||
fill: var(--text-color);
|
||||
}
|
||||
.svg-icon {
|
||||
height: 2rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,8 +39,8 @@
|
||||
--text-button-primary-hover-background-color: #2f68be;
|
||||
--text-button-primary-hover-color: #ffffff;
|
||||
|
||||
--toggle-switch-on-background-color: var(--primary-color);
|
||||
--toggle-switch-off-background-color: #888888;
|
||||
--toggle-switch-on-background-color: #36c55a;
|
||||
--toggle-switch-off-background-color: #b3b3b6;
|
||||
--toggle-switch-toggle-color: #ffffff;
|
||||
|
||||
--table-header-background-color: #242427;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
$toggle-switch-width: 3.2rem;
|
||||
$toggle-switch-width: 2.8rem;
|
||||
$toggle-switch-height: 1.6rem;
|
||||
$toggle-switch-toggle-padding: 3px;
|
||||
$toggle-switch-toggle-width: calc($toggle-switch-height - $toggle-switch-toggle-padding * 2);
|
||||
@@ -9,6 +9,11 @@ $toggle-switch-toggle-end-pos: calc($toggle-switch-width - ($toggle-switch-toggl
|
||||
display: inline-block;
|
||||
width: $toggle-switch-width;
|
||||
height: $toggle-switch-height;
|
||||
cursor: pointer;
|
||||
&__readonly {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input {
|
||||
opacity: 0; width: 0; height: 0
|
||||
}
|
||||
@@ -20,7 +25,6 @@ $toggle-switch-toggle-end-pos: calc($toggle-switch-width - ($toggle-switch-toggl
|
||||
}
|
||||
&__track {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background-color: var(--toggle-switch-off-background-color);
|
||||
border-radius: $toggle-switch-toggle-width;
|
||||
@@ -37,6 +41,7 @@ $toggle-switch-toggle-end-pos: calc($toggle-switch-width - ($toggle-switch-toggl
|
||||
border-radius: 50%;
|
||||
transition: 0.3s;
|
||||
transform: translateX(0);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
&__active {
|
||||
|
||||
|
||||
@@ -77,8 +77,8 @@ pub fn Login() -> Html {
|
||||
<Input label={translation.t("LABEL.USERNAME")} input_ref={username_ref} input_type="text" name="username" autocomplete={true} autofocus={true}/>
|
||||
<Input label={translation.t("LABEL.PASSWORD")} input_ref={password_ref} input_type="password" name="password" autocomplete={true} onkeydown={handle_key_down}/>
|
||||
<div class="tp__login-view__form-action">
|
||||
<TextButton style="tp__primary" name="login" title={ translation.t("LABEL.LOGIN")} onclick={handle_login}></TextButton>
|
||||
<span class={if *auth_success { "tp__hidden" } else { "error-text" }}>{ "Failed to login" }</span>
|
||||
<TextButton style="primary" name="login" title={ translation.t("LABEL.LOGIN")} onclick={handle_login}></TextButton>
|
||||
<span class={if *auth_success { "tp__hidden" } else { "tp__error-text" }}>{ "Failed to login" }</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -18,6 +18,7 @@ mod breadcrumbs;
|
||||
mod playlist;
|
||||
mod table;
|
||||
mod toggle_switch;
|
||||
mod reveal_content;
|
||||
|
||||
pub use self::svg_icon::*;
|
||||
// pub use self::input::*;
|
||||
|
||||
@@ -10,7 +10,7 @@ pub struct PanelProps {
|
||||
#[function_component]
|
||||
pub fn Panel(props: &PanelProps) -> Html {
|
||||
html! {
|
||||
<div class={classes!("panel", if props.value == props.active {""} else {"tp__hidden"} )}>
|
||||
<div class={classes!("tp__panel", if props.value == props.active {""} else {"tp__hidden"} )}>
|
||||
{ for props.children.iter() }
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@ mod create;
|
||||
mod context;
|
||||
mod page;
|
||||
mod target_table;
|
||||
mod target;
|
||||
|
||||
pub use self::assistant::*;
|
||||
pub use self::list::*;
|
||||
pub use self::create::*;
|
||||
pub use self::context::*;
|
||||
pub use self::page::*;
|
||||
pub use self::target_table::*;
|
||||
pub use self::target_table::*;
|
||||
pub use self::target::*;
|
||||
@@ -0,0 +1,10 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
#[function_component]
|
||||
pub fn HdHomerunOutput() -> Html {
|
||||
|
||||
html! {
|
||||
<div class="tp__hdhomerun_output">
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
#[function_component]
|
||||
pub fn M3uOutput() -> Html {
|
||||
|
||||
html! {
|
||||
<div class="tp__m3u_output">
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
mod xtream_output;
|
||||
mod m3u_output;
|
||||
mod strm_output;
|
||||
mod hdhomerun_output;
|
||||
mod target_output;
|
||||
|
||||
pub use self::xtream_output::*;
|
||||
pub use self::m3u_output::*;
|
||||
pub use self::strm_output::*;
|
||||
pub use self::hdhomerun_output::*;
|
||||
pub use self::target_output::*;
|
||||
@@ -0,0 +1,10 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
#[function_component]
|
||||
pub fn StrmOutput() -> Html {
|
||||
|
||||
html! {
|
||||
<div class="tp__strm_output">
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use shared::model::{ConfigTargetDto, TargetOutputDto};
|
||||
use crate::app::components::{HdHomerunOutput, M3uOutput, StrmOutput, XtreamOutput};
|
||||
|
||||
#[derive(Properties, Clone, PartialEq, Debug)]
|
||||
pub struct TargetOutputProps {
|
||||
pub target: Rc<ConfigTargetDto>,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn TargetOutput(props: &TargetOutputProps) -> Html {
|
||||
html! {
|
||||
<div class="tp__target_output">
|
||||
{
|
||||
props.target.output.iter().map(|output| {
|
||||
match output {
|
||||
TargetOutputDto::Xtream(xc) => html! { <XtreamOutput output={xc} /> },
|
||||
TargetOutputDto::M3u(m3u) => html! { <M3uOutput /> },
|
||||
TargetOutputDto::Strm(strm) => html! { <StrmOutput /> },
|
||||
TargetOutputDto::HdHomeRun(hdhr) => html! { <HdHomerunOutput /> },
|
||||
}
|
||||
}).collect::<Html>()
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use shared::model::XtreamTargetOutputDto;
|
||||
|
||||
#[derive(Properties, PartialEq, Clone)]
|
||||
pub struct XtreamOutputProps {
|
||||
pub output: XtreamTargetOutputDto,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn XtreamOutput(props: &XtreamOutputProps) -> Html {
|
||||
|
||||
html! {
|
||||
<div class="tp__xtream_output">
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
<div class="tp__tp__target_output_row">
|
||||
<label>{translate.t("LABEL.SKIP_DIRECT_SOURCE")}</label>
|
||||
<TagList>
|
||||
<Tag style={if xc.skip_live_direct_source { "on" } else { "off "}} label={translate.t("LABEL.LIVE")}/>
|
||||
<Tag style={if xc.skip_vod_direct_source { "on" } else { "off "}} label={translate.t("LABEL.VOD")}/>
|
||||
<Tag style={if xc.skip_series_direct_source { "on" } else { "off "}} label={translate.t("LABEL.SERIES")}/>
|
||||
</TagList>
|
||||
</div>
|
||||
<div class="tp__tp__target_output_row"><label>{translate.t("LABEL.RESOVE")}</label></div>
|
||||
|
||||
#[serde(default = "default_as_true")]
|
||||
pub skip_live_direct_source: bool,
|
||||
#[serde(default = "default_as_true")]
|
||||
pub skip_video_direct_source: bool,
|
||||
#[serde(default = "default_as_true")]
|
||||
pub skip_series_direct_source: bool,
|
||||
#[serde(default)]
|
||||
pub resolve_series: bool,
|
||||
#[serde(default = "default_resolve_delay_secs")]
|
||||
pub resolve_series_delay: u16,
|
||||
#[serde(default)]
|
||||
pub resolve_vod: bool,
|
||||
#[serde(default = "default_resolve_delay_secs")]
|
||||
pub resolve_vod_delay: u16,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub trakt: Option<TraktConfigDto>,
|
||||
<span>{"xc"}</span>
|
||||
},
|
||||
*/
|
||||
@@ -5,7 +5,8 @@ use yew::prelude::*;
|
||||
use yew::suspense::use_future;
|
||||
use yew_i18n::use_translation;
|
||||
use shared::model::{ConfigTargetDto};
|
||||
use crate::app::components::{PlaylistContext, Table, TableDefinition, ToggleSwitch};
|
||||
use crate::app::components::{PlaylistContext, Table, TableDefinition, TargetOutput, ToggleSwitch};
|
||||
use crate::app::components::reveal_content::RevealContent;
|
||||
use crate::hooks::use_service_context;
|
||||
|
||||
const HEADERS: [&str; 11] = [
|
||||
@@ -47,12 +48,12 @@ pub fn TargetTable() -> Html {
|
||||
move |(_row, col, dto): (usize, usize, Rc<ConfigTargetDto>)| {
|
||||
match col {
|
||||
0 => html! { &dto.id.to_string() },
|
||||
1 => html! { <ToggleSwitch value={&dto.enabled} /> },
|
||||
1 => html! { <ToggleSwitch readonly={false} value={&dto.enabled} /> },
|
||||
2 => html! { &dto.name.to_string() },
|
||||
3 => html! { "options" },
|
||||
4 => html! { dto.sort.as_ref().map_or_else(String::new, |s| format!("{s:?}")) },
|
||||
3 => html! { <RevealContent>{"Hello"}</RevealContent> },
|
||||
4 => html! { <RevealContent>{dto.sort.as_ref().map_or_else(String::new, |s| format!("{s:?}"))}</RevealContent> },
|
||||
5 => html! { &dto.filter.clone() },
|
||||
6 => html! { "output" },
|
||||
6 => html! { <TargetOutput target={Rc::clone(&dto)} /> },
|
||||
7 => html! { "rename" },
|
||||
8 => html! { dto.mapping.as_ref().map_or_else(String::new, |m| format!("{m:?}")) },
|
||||
9 => html! { &dto.processing_order.to_string() },
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use yew::prelude::*;
|
||||
use crate::app::components::AppIcon;
|
||||
|
||||
#[derive(Properties, Clone, PartialEq, Debug)]
|
||||
pub struct RevealContentProps {
|
||||
#[prop_or_default]
|
||||
pub icon: String,
|
||||
pub children: Children,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn RevealContent(props: &RevealContentProps) -> Html {
|
||||
html! {
|
||||
<div class={"tp__reveal_content"}>
|
||||
<AppIcon name={if props.icon.is_empty() {"Visibility".to_string()} else {props.icon.to_string()} } />
|
||||
// { for props.children.iter() }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
use log::info;
|
||||
use yew::prelude::*;
|
||||
|
||||
#[derive(Properties, Clone, PartialEq, Debug)]
|
||||
pub struct ToggleSwitchProps {
|
||||
#[prop_or_default]
|
||||
pub value: bool,
|
||||
#[prop_or_default]
|
||||
pub readonly: bool,
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
@@ -12,11 +15,18 @@ pub fn ToggleSwitch(props: &ToggleSwitchProps) -> Html {
|
||||
|
||||
let onclick = {
|
||||
let toggled = toggled.clone();
|
||||
Callback::from(move |_| toggled.set(!*toggled))
|
||||
let readonly = props.readonly;
|
||||
Callback::from(move |e: MouseEvent| {
|
||||
if readonly {
|
||||
e.prevent_default();
|
||||
return;
|
||||
}
|
||||
toggled.set(!*toggled)
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<label class="tp__toggle-switch">
|
||||
<label class={classes!("tp__toggle-switch", if props.readonly { "tp__toggle-switch__readonly" } else {""})}>
|
||||
<input type="checkbox"
|
||||
checked={*toggled}
|
||||
onclick={onclick}/>
|
||||
|
||||
Reference in New Issue
Block a user