mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-26 16:52:25 +01:00
initialized empty playlist explorer view
This commit is contained in:
@@ -8,7 +8,7 @@ use axum::{
|
||||
response::IntoResponse,
|
||||
};
|
||||
use log::{error, info};
|
||||
use shared::model::{ProtocolHandler, ProtocolHandlerMemory, ProtocolMessage, WsCloseCode, PROTOCOL_VERSION};
|
||||
use shared::model::{ProtocolHandler, ProtocolHandlerMemory, ProtocolMessage, UserRole, WsCloseCode, PROTOCOL_VERSION};
|
||||
use std::sync::Arc;
|
||||
|
||||
// WebSocket upgrade handler
|
||||
@@ -114,11 +114,11 @@ async fn handle_protocol_message(
|
||||
Ok(ProtocolMessage::Auth(auth_token)) => {
|
||||
mem.token = None;
|
||||
if verify_auth_admin_token(&auth_token, secret_key) {
|
||||
mem.admin = true;
|
||||
mem.role = UserRole::Admin;
|
||||
mem.token = Some(auth_token);
|
||||
Some(ProtocolMessage::Authorized)
|
||||
} else if verify_auth_user_token(&auth_token, secret_key) {
|
||||
mem.admin = false;
|
||||
mem.role = UserRole::User;
|
||||
mem.token = Some(auth_token);
|
||||
Some(ProtocolMessage::Authorized)
|
||||
} else {
|
||||
@@ -127,7 +127,7 @@ async fn handle_protocol_message(
|
||||
},
|
||||
Ok(ProtocolMessage::StatusRequest(auth_token)) => {
|
||||
if !auth || verify_auth_admin_token(&auth_token, secret_key) {
|
||||
mem.admin = true;
|
||||
mem.role = UserRole::Admin;
|
||||
mem.token = Some(auth_token);
|
||||
let status = create_status_check(app_state).await;
|
||||
Some(ProtocolMessage::StatusResponse(status))
|
||||
@@ -192,7 +192,7 @@ async fn handle_event_message(socket: &mut WebSocket, event: EventMessage, handl
|
||||
match handler {
|
||||
ProtocolHandler::Version(_) => {},
|
||||
ProtocolHandler::Default(mem) => {
|
||||
if mem.admin {
|
||||
if mem.role.is_admin() {
|
||||
match event {
|
||||
EventMessage::ActiveUserChange(users, connections) => {
|
||||
let msg = ProtocolMessage::ActiveUserResponse(users, connections)
|
||||
|
||||
@@ -326,7 +326,7 @@ impl AppConfig {
|
||||
}
|
||||
|
||||
match utils::read_file_as_bytes(&PathBuf::from(&file_path)) {
|
||||
Ok(data) => Some(TransportStreamBuffer::new(data, )),
|
||||
Ok(data) => Some(TransportStreamBuffer::new(data)),
|
||||
Err(err) => {
|
||||
error!("Failed to load a resource file: {} {err}", file_path.display());
|
||||
None
|
||||
|
||||
@@ -122,7 +122,7 @@ macros::from_impl!(Config);
|
||||
impl From<&ConfigDto> for Config {
|
||||
fn from(dto: &ConfigDto) -> Self {
|
||||
Config {
|
||||
threads: 0,
|
||||
threads: dto.threads,
|
||||
api: ConfigApi::from(&dto.api),
|
||||
working_dir: dto.working_dir.to_string(),
|
||||
backup_dir: dto.backup_dir.clone(),
|
||||
|
||||
@@ -150,7 +150,7 @@ impl ConfigInput {
|
||||
let mut aliases: Vec<ConfigInputAlias> = batch_aliases.into_iter()
|
||||
.map(ConfigInputAlias::from)
|
||||
.collect();
|
||||
if let Some(mut first) = aliases.pop() {
|
||||
if let Some(mut first) = aliases.remove(0) {
|
||||
self.username = first.username.take();
|
||||
self.password = first.password.take();
|
||||
self.url = first.url.trim().to_string();
|
||||
@@ -222,9 +222,6 @@ pub fn get_batch_aliases(input_type: InputType, url: &str) -> Result<Option<(Pat
|
||||
if input_type == InputType::M3uBatch || input_type == InputType::XtreamBatch {
|
||||
return match utils::csv_read_inputs(input_type, url) {
|
||||
Ok((file_path, mut batch_aliases)) => {
|
||||
if !batch_aliases.is_empty() {
|
||||
batch_aliases.reverse();
|
||||
}
|
||||
Ok(Some((file_path, batch_aliases)))
|
||||
}
|
||||
Err(err) => {
|
||||
|
||||
@@ -14,7 +14,6 @@ macro_rules! apply_batch_aliases {
|
||||
($target:expr, $batch_aliases:expr) => {{
|
||||
if !$batch_aliases.is_empty() {
|
||||
if !$batch_aliases.is_empty() {
|
||||
$batch_aliases.reverse();
|
||||
if let Some(aliases) = $target.aliases.as_mut() {
|
||||
aliases.extend($batch_aliases);
|
||||
} else {
|
||||
|
||||
@@ -5,18 +5,27 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const PROTOCOL_VERSION: u8 = 1;
|
||||
|
||||
pub struct ProtocolHandlerMemory {
|
||||
pub token: Option<String>,
|
||||
pub admin: bool,
|
||||
#[derive(Default)]
|
||||
pub enum UserRole {
|
||||
#[default]
|
||||
Unauthorized,
|
||||
Admin,
|
||||
User,
|
||||
}
|
||||
|
||||
impl Default for ProtocolHandlerMemory {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
token: None,
|
||||
admin: false,
|
||||
}
|
||||
impl UserRole {
|
||||
pub fn is_admin(&self) -> bool {
|
||||
self.eq(&UserRole::Admin)
|
||||
}
|
||||
pub fn is_user(&self) -> bool {
|
||||
self.eq(&UserRole::User)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ProtocolHandlerMemory {
|
||||
pub token: Option<String>,
|
||||
pub role: UserRole,
|
||||
}
|
||||
|
||||
pub enum ProtocolHandler {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"tabTitle": "tuliprox",
|
||||
"appTitle": "tuliprox",
|
||||
"appLogo": "/assets/tuliprox-logo.svg",
|
||||
"api": {
|
||||
"apiUrl": "/api/v1/",
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
.icon-AppTitle {
|
||||
width: 100px !important;
|
||||
}
|
||||
&-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
@forward "components/dashboard/stats_view";
|
||||
@forward "components/list_view";
|
||||
@forward "components/userlist/userlist_view";
|
||||
@forward "components/playlist/playlist_view";
|
||||
@forward "components/playlist/playlist_editor_view";
|
||||
@forward "components/playlist/playlist_explorer_view";
|
||||
@forward "components/playlist/assistant/playlist_assistant";
|
||||
@forward "components/reveal_content";
|
||||
@forward "components/hide_content";
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
.tp__playlist-create,
|
||||
.tp__playlist-list {
|
||||
.tp__playlist-editor-create,
|
||||
.tp__playlist-editor-list {
|
||||
|
||||
&__body {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
.tp__playlist-editor-create,
|
||||
.tp__playlist-editor-list {
|
||||
|
||||
&__body {
|
||||
|
||||
> :nth-child(even) {
|
||||
border-left: 2px solid var(--output-xtream-color);
|
||||
}
|
||||
|
||||
> :nth-child(odd) {
|
||||
border-left: 2px solid var(--output-hdhomerun-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__source {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
border-right: 1px solid var(--border-color);
|
||||
box-sizing: border-box;
|
||||
border-top-right-radius: var(--border-radius);
|
||||
border-bottom-right-radius: var(--border-radius);
|
||||
padding: var(--padding-mini);
|
||||
gap: var(--gap-default);
|
||||
|
||||
.svg-icon {
|
||||
width: 1.2rem;
|
||||
min-width: 1.2rem;
|
||||
min-height: 1.2rem;
|
||||
}
|
||||
&-label,
|
||||
&-inputs > .tp__collapse-panel__header > .tp__collapse-panel__header-title {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
align-items: center;
|
||||
gap: var(--gap-default);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
&-label {
|
||||
color: var(--modest-text-color);
|
||||
fill: var(--modest-text-color);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,3 @@ body {
|
||||
max-width: 100%;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
use crate::app::components::{AppIcon, DashboardView, IconButton, InputRow, Panel, PlaylistEditorView, PlaylistExplorerView, Sidebar, StatsView, UserlistView};
|
||||
use crate::app::context::{ConfigContext, PlaylistContext, StatusContext};
|
||||
use crate::hooks::{use_server_status, use_service_context};
|
||||
use crate::model::ViewType;
|
||||
use crate::services::WsMessage;
|
||||
use shared::model::{AppConfigDto, StatusCheck};
|
||||
use std::future;
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use yew::suspense::use_future;
|
||||
use shared::model::{AppConfigDto, StatusCheck};
|
||||
use crate::app::components::{IconButton, Sidebar, DashboardView, PlaylistView, Panel, UserlistView, StatsView};
|
||||
use crate::app::context::{ConfigContext, StatusContext};
|
||||
use crate::model::ViewType;
|
||||
use crate::hooks::{use_server_status, use_service_context};
|
||||
use crate::services::WsMessage;
|
||||
|
||||
#[function_component]
|
||||
pub fn Home() -> Html {
|
||||
let services = use_service_context();
|
||||
let app_title = services.config.ui_config.app_title.as_ref().map_or("tuliprox", |v| v.as_str());
|
||||
let config = use_state(|| None::<Rc<AppConfigDto>>);
|
||||
let status = use_state(|| None::<Rc<StatusCheck>>);
|
||||
|
||||
@@ -65,6 +64,32 @@ pub fn Home() -> Html {
|
||||
});
|
||||
}
|
||||
|
||||
let sources = use_memo(config.clone(), |config_ctx| {
|
||||
if let Some(cfg) = config_ctx.as_ref() {
|
||||
let mut sources = vec![];
|
||||
for source in &cfg.sources.sources {
|
||||
let mut inputs = vec![];
|
||||
for input_cfg in &source.inputs {
|
||||
let input = Rc::new(input_cfg.clone());
|
||||
inputs.push(Rc::new(InputRow::Input(Rc::clone(&input))));
|
||||
if let Some(aliases) = input_cfg.aliases.as_ref() {
|
||||
for alias in aliases {
|
||||
inputs.push(Rc::new(InputRow::Alias(Rc::new(alias.clone()), Rc::clone(&input))));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut targets = vec![];
|
||||
for target in &source.targets {
|
||||
targets.push(Rc::new(target.clone()));
|
||||
}
|
||||
sources.push((inputs, targets));
|
||||
}
|
||||
Some(Rc::new(sources))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
// {
|
||||
// let services_ctx = services.clone();
|
||||
// let status_signal = status.clone();
|
||||
@@ -126,12 +151,15 @@ pub fn Home() -> Html {
|
||||
// }
|
||||
|
||||
let config_context = ConfigContext {
|
||||
config: (*config).clone(),
|
||||
config: (*config).clone(),
|
||||
};
|
||||
|
||||
let status_context = StatusContext {
|
||||
status: (*status).clone(),
|
||||
};
|
||||
let playlist_context = PlaylistContext {
|
||||
sources: sources.clone(),
|
||||
};
|
||||
|
||||
//<div class={"app-header__toolbar"}><select onchange={handle_language} defaultValue={i18next.language}>{services.config().getUiConfig().languages.map(l => <option key={l} value={l}>{l}</option>)}</select></div>
|
||||
// <div class={"app-header__toolbar"}><button data-tooltip={preferencesVisible ? "LABEL.PLAYLIST_BROWSER" : "LABEL.CONFIGURATION"} onClick={handlePreferences}>{getIconByName(preferencesVisible ? "Live" : "Config")}</button></div>
|
||||
@@ -139,12 +167,19 @@ pub fn Home() -> Html {
|
||||
html! {
|
||||
<ContextProvider<ConfigContext> context={config_context}>
|
||||
<ContextProvider<StatusContext> context={status_context}>
|
||||
<ContextProvider<PlaylistContext> context={playlist_context}>
|
||||
<div class="tp__app">
|
||||
<Sidebar onview={handle_view_change}/>
|
||||
|
||||
<div class="tp__app-main">
|
||||
<div class="tp__app-main__header tp__app-header">
|
||||
{app_title}
|
||||
{
|
||||
if let Some(ref title) = services.config.ui_config.app_title {
|
||||
html! { title.as_str() }
|
||||
} else {
|
||||
html! { <AppIcon name="AppTitle" /> }
|
||||
}
|
||||
}
|
||||
<div class={"tp__app-header-toolbar"}>
|
||||
// <select onchange={handle_language} defaultValue={i18next.language}>{
|
||||
// services.config().getUiConfig().languages.map(l => <option key={l} value={l}>{l}</option>)
|
||||
@@ -170,7 +205,10 @@ pub fn Home() -> Html {
|
||||
<StatsView/>
|
||||
</Panel>
|
||||
<Panel class="tp__full-width" value={ViewType::PlaylistEditor.to_string()} active={view_visible.to_string()}>
|
||||
<PlaylistView/>
|
||||
<PlaylistEditorView/>
|
||||
</Panel>
|
||||
<Panel class="tp__full-width" value={ViewType::PlaylistExplorer.to_string()} active={view_visible.to_string()}>
|
||||
<PlaylistExplorerView/>
|
||||
</Panel>
|
||||
<Panel class="tp__full-width" value={ViewType::Users.to_string()} active={view_visible.to_string()}>
|
||||
<UserlistView/>
|
||||
@@ -178,6 +216,7 @@ pub fn Home() -> Html {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ContextProvider<PlaylistContext>>
|
||||
</ContextProvider<StatusContext>>
|
||||
</ContextProvider<ConfigContext>>
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
use yew::prelude::*;
|
||||
use yew_i18n::use_translation;
|
||||
use crate::app::components::{PlaylistContext, PlaylistPage, TextButton};
|
||||
use crate::app::components::{PlaylistEditorContext, PlaylistEditorPage, TextButton};
|
||||
|
||||
#[function_component]
|
||||
pub fn PlaylistCreate() -> Html {
|
||||
let translate = use_translation();
|
||||
let playlist_ctx = use_context::<PlaylistContext>().expect("Playlist context not found");
|
||||
let playlist_editor_ctx = use_context::<PlaylistEditorContext>().expect("PlaylistEditor context not found");
|
||||
|
||||
let handle_back = {
|
||||
let playlist_ctx = playlist_ctx.clone();
|
||||
Callback::from(move |_| {
|
||||
playlist_ctx.active_page.set(PlaylistPage::List);
|
||||
playlist_editor_ctx.active_page.set(PlaylistEditorPage::List);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
use yew::prelude::*;
|
||||
use yew_i18n::use_translation;
|
||||
use crate::app::components::{AppIcon, CollapsePanel, InputTable, PlaylistContext, PlaylistPage, TargetTable, TextButton};
|
||||
use crate::app::components::{AppIcon, CollapsePanel, InputTable, PlaylistContext, PlaylistEditorPage, TargetTable, TextButton};
|
||||
use crate::app::context::PlaylistEditorContext;
|
||||
|
||||
#[function_component]
|
||||
pub fn PlaylistList() -> Html {
|
||||
let translate = use_translation();
|
||||
let playlist_ctx = use_context::<PlaylistContext>().expect("Playlist context not found");
|
||||
let playlist_editor_ctx = use_context::<PlaylistEditorContext>().expect("PlaylistEditor context not found");
|
||||
|
||||
let handle_create = {
|
||||
let playlist_ctx = playlist_ctx.clone();
|
||||
Callback::from(move |_| {
|
||||
playlist_ctx.active_page.set(PlaylistPage::Create);
|
||||
playlist_editor_ctx.active_page.set(PlaylistEditorPage::Create);
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
mod assistant;
|
||||
mod list;
|
||||
mod create;
|
||||
mod page;
|
||||
mod playlist_editor_page;
|
||||
mod playlist_explorer_page;
|
||||
mod target_table;
|
||||
mod target;
|
||||
mod playlist_view;
|
||||
mod playlist_editor_view;
|
||||
mod playlist_explorer_view;
|
||||
mod processing;
|
||||
mod mappings;
|
||||
mod filter_view;
|
||||
@@ -19,7 +21,8 @@ pub use self::assistant::*;
|
||||
pub use self::list::*;
|
||||
pub use self::create::*;
|
||||
pub use crate::app::context::*;
|
||||
pub use self::page::*;
|
||||
pub use self::playlist_editor_page::*;
|
||||
pub use self::playlist_explorer_page::*;
|
||||
pub use self::target_table::*;
|
||||
pub use self::target::*;
|
||||
pub use self::processing::*;
|
||||
@@ -27,7 +30,8 @@ pub use self::mappings::*;
|
||||
pub use self::filter_view::*;
|
||||
pub use self::input_table::*;
|
||||
pub use self::input::*;
|
||||
pub use self::playlist_view::*;
|
||||
pub use self::playlist_editor_view::*;
|
||||
pub use self::playlist_explorer_view::*;
|
||||
|
||||
pub fn make_tags(data: &[(bool, &str)], translate: &YewI18n) -> Vec<Rc<Tag>> {
|
||||
data.iter()
|
||||
|
||||
+5
-5
@@ -4,24 +4,24 @@ use shared::error::{TuliproxError,TuliproxErrorKind};
|
||||
use shared::info_err;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PlaylistPage {
|
||||
pub enum PlaylistEditorPage {
|
||||
List,
|
||||
Create,
|
||||
}
|
||||
|
||||
impl FromStr for PlaylistPage {
|
||||
impl FromStr for PlaylistEditorPage {
|
||||
type Err = TuliproxError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, TuliproxError> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"list" => Ok(PlaylistPage::List),
|
||||
"create" => Ok(PlaylistPage::Create),
|
||||
"list" => Ok(PlaylistEditorPage::List),
|
||||
"create" => Ok(PlaylistEditorPage::Create),
|
||||
_ => Err(info_err!(format!("Unknown page type: {s}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PlaylistPage {
|
||||
impl Display for PlaylistEditorPage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", match *self {
|
||||
Self::List => "list",
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::app::components::{Breadcrumbs, Panel, PlaylistCreate, PlaylistEditorPage, PlaylistList};
|
||||
use crate::app::context::PlaylistEditorContext;
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use yew_i18n::use_translation;
|
||||
|
||||
#[function_component]
|
||||
pub fn PlaylistEditorView() -> Html {
|
||||
let translate = use_translation();
|
||||
let breadcrumbs = use_state(|| Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.LIST")]));
|
||||
let active_page = use_state(|| PlaylistEditorPage::List);
|
||||
|
||||
let handle_breadcrumb_select = {
|
||||
let view_visible = active_page.clone();
|
||||
Callback::from(move |(_name, index)| {
|
||||
if index == 0 && *view_visible != PlaylistEditorPage::List {
|
||||
view_visible.set(PlaylistEditorPage::List);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
{
|
||||
let breadcrumbs = breadcrumbs.clone();
|
||||
let view_visible_dep = active_page.clone();
|
||||
let view_visible = active_page.clone();
|
||||
let translate = translate.clone();
|
||||
use_effect_with(view_visible_dep, move |_| {
|
||||
match *view_visible {
|
||||
PlaylistEditorPage::List => breadcrumbs.set(Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.LIST")])),
|
||||
PlaylistEditorPage::Create => breadcrumbs.set(Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.CREATE")])),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let context = PlaylistEditorContext {
|
||||
active_page: active_page.clone(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<ContextProvider<PlaylistEditorContext> context={context}>
|
||||
<div class="tp__playlist-editor-view tp__list-view">
|
||||
<Breadcrumbs items={&*breadcrumbs} onclick={ handle_breadcrumb_select }/>
|
||||
<div class="tp__playlist-editor-view__body tp__list-view__body">
|
||||
<Panel value={PlaylistEditorPage::List.to_string()} active={active_page.to_string()}>
|
||||
<PlaylistList />
|
||||
</Panel>
|
||||
<Panel value={PlaylistEditorPage::Create.to_string()} active={active_page.to_string()}>
|
||||
<PlaylistCreate />
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
</ContextProvider<PlaylistEditorContext>>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
use shared::error::{TuliproxError,TuliproxErrorKind};
|
||||
use shared::info_err;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum PlaylistExplorerPage {
|
||||
List,
|
||||
Create,
|
||||
}
|
||||
|
||||
impl FromStr for PlaylistExplorerPage {
|
||||
type Err = TuliproxError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, TuliproxError> {
|
||||
match s.to_lowercase().as_str() {
|
||||
"list" => Ok(PlaylistExplorerPage::List),
|
||||
"create" => Ok(PlaylistExplorerPage::Create),
|
||||
_ => Err(info_err!(format!("Unknown page type: {s}"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PlaylistExplorerPage {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "{}", match *self {
|
||||
Self::List => "list",
|
||||
Self::Create => "create",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::app::components::{Breadcrumbs, Panel, PlaylistExplorerPage};
|
||||
use crate::app::context::PlaylistExplorerContext;
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use yew_i18n::use_translation;
|
||||
|
||||
#[function_component]
|
||||
pub fn PlaylistExplorerView() -> Html {
|
||||
let translate = use_translation();
|
||||
let breadcrumbs = use_state(|| Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.LIST")]));
|
||||
let active_page = use_state(|| PlaylistExplorerPage::List);
|
||||
|
||||
let handle_breadcrumb_select = {
|
||||
let view_visible = active_page.clone();
|
||||
Callback::from(move |(_name, index)| {
|
||||
if index == 0 && *view_visible != PlaylistExplorerPage::List {
|
||||
view_visible.set(PlaylistExplorerPage::List);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
{
|
||||
let breadcrumbs = breadcrumbs.clone();
|
||||
let view_visible_dep = active_page.clone();
|
||||
let view_visible = active_page.clone();
|
||||
let translate = translate.clone();
|
||||
use_effect_with(view_visible_dep, move |_| {
|
||||
match *view_visible {
|
||||
PlaylistExplorerPage::List => breadcrumbs.set(Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.LIST")])),
|
||||
PlaylistExplorerPage::Create => breadcrumbs.set(Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.CREATE")])),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let context = PlaylistExplorerContext {
|
||||
active_page: active_page.clone(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<ContextProvider<PlaylistExplorerContext> context={context}>
|
||||
<div class="tp__playlist-explorer-view tp__list-view">
|
||||
<Breadcrumbs items={&*breadcrumbs} onclick={ handle_breadcrumb_select }/>
|
||||
<div class="tp__playlist-explorer-view__body tp__list-view__body">
|
||||
<Panel value={PlaylistExplorerPage::List.to_string()} active={active_page.to_string()}>
|
||||
{"List"}
|
||||
</Panel>
|
||||
<Panel value={PlaylistExplorerPage::Create.to_string()} active={active_page.to_string()}>
|
||||
{"Create"}
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
</ContextProvider<PlaylistExplorerContext>>
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
use crate::app::components::{Breadcrumbs, InputRow, Panel, PlaylistContext, PlaylistCreate, PlaylistList, PlaylistPage};
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use yew_i18n::use_translation;
|
||||
use crate::app::context::ConfigContext;
|
||||
|
||||
#[function_component]
|
||||
pub fn PlaylistView() -> Html {
|
||||
let translate = use_translation();
|
||||
let breadcrumbs = use_state(|| Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.LIST")]));
|
||||
let active_page = use_state(|| PlaylistPage::List);
|
||||
let config_ctx = use_context::<ConfigContext>().expect("Config context not found");
|
||||
|
||||
let handle_breadcrumb_select = {
|
||||
let view_visible = active_page.clone();
|
||||
Callback::from(move |(_name, index)| {
|
||||
if index == 0 && *view_visible != PlaylistPage::List {
|
||||
view_visible.set(PlaylistPage::List);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
{
|
||||
let breadcrumbs = breadcrumbs.clone();
|
||||
let view_visible_dep = active_page.clone();
|
||||
let view_visible = active_page.clone();
|
||||
let translate = translate.clone();
|
||||
use_effect_with(view_visible_dep, move |_| {
|
||||
match *view_visible {
|
||||
PlaylistPage::List => breadcrumbs.set(Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.LIST")])),
|
||||
PlaylistPage::Create => breadcrumbs.set(Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.CREATE")])),
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
let sources = use_memo(config_ctx.config.map(|c| c.sources.clone()), |cfg_sources_opt| {
|
||||
if let Some(cfg_sources) = cfg_sources_opt {
|
||||
let mut sources = vec![];
|
||||
for source in &cfg_sources.sources {
|
||||
let mut inputs = vec![];
|
||||
for input_cfg in &source.inputs {
|
||||
let input = Rc::new(input_cfg.clone());
|
||||
inputs.push(Rc::new(InputRow::Input(Rc::clone(&input))));
|
||||
if let Some(aliases) = input_cfg.aliases.as_ref() {
|
||||
for alias in aliases {
|
||||
inputs.push(Rc::new(InputRow::Alias(Rc::new(alias.clone()), Rc::clone(&input))));
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut targets = vec![];
|
||||
for target in &source.targets {
|
||||
targets.push(Rc::new(target.clone()));
|
||||
}
|
||||
sources.push((inputs, targets));
|
||||
}
|
||||
Some(Rc::new(sources))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let context = PlaylistContext {
|
||||
sources,
|
||||
active_page: active_page.clone(),
|
||||
};
|
||||
|
||||
html! {
|
||||
<ContextProvider<PlaylistContext> context={context}>
|
||||
<div class="tp__playlist-view tp__list-view">
|
||||
<Breadcrumbs items={&*breadcrumbs} onclick={ handle_breadcrumb_select }/>
|
||||
<div class="tp__playlist-view__body tp__list-view__body">
|
||||
<Panel value={PlaylistPage::List.to_string()} active={active_page.to_string()}>
|
||||
<PlaylistList />
|
||||
</Panel>
|
||||
<Panel value={PlaylistPage::Create.to_string()} active={active_page.to_string()}>
|
||||
<PlaylistCreate />
|
||||
</Panel>
|
||||
</div>
|
||||
</div>
|
||||
</ContextProvider<PlaylistContext>>
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ pub fn Sidebar(props: &SidebarProps) -> Html {
|
||||
<CollapsePanel title={translate.t("LABEL.PLAYLIST")}>
|
||||
<MenuItem icon="PlayArrowOutline" name={ViewType::PlaylistEditor.to_string()} label={translate.t("LABEL.PLAYLIST")}
|
||||
onclick={&handle_menu_click}></MenuItem>
|
||||
<MenuItem icon="Live" name={ViewType::PlaylistViewer.to_string()} label={translate.t("LABEL.PLAYLIST_VIEWER")}
|
||||
<MenuItem icon="Live" name={ViewType::PlaylistExplorer.to_string()} label={translate.t("LABEL.PLAYLIST_VIEWER")}
|
||||
onclick={&handle_menu_click}></MenuItem>
|
||||
</CollapsePanel>
|
||||
</div>
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
use std::rc::Rc;
|
||||
use yew::UseStateHandle;
|
||||
use shared::model::{AppConfigDto, ConfigTargetDto, ProxyUserCredentialsDto, StatusCheck};
|
||||
use crate::app::components::{InputRow, PlaylistPage, UserlistPage};
|
||||
use crate::app::components::{InputRow, PlaylistEditorPage, PlaylistExplorerPage, UserlistPage};
|
||||
|
||||
type SingleSource = (Vec<Rc<InputRow>>, Vec<Rc<ConfigTargetDto>>);
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct PlaylistContext {
|
||||
pub sources: Rc<Option<Rc<Vec<SingleSource>>>>,
|
||||
pub active_page: UseStateHandle<PlaylistPage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct PlaylistEditorContext {
|
||||
pub active_page: UseStateHandle<PlaylistEditorPage>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct PlaylistExplorerContext {
|
||||
pub active_page: UseStateHandle<PlaylistExplorerPage>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct TargetUser {
|
||||
pub target: String,
|
||||
|
||||
@@ -8,7 +8,7 @@ pub enum ViewType {
|
||||
Stats,
|
||||
Users,
|
||||
PlaylistEditor,
|
||||
PlaylistViewer
|
||||
PlaylistExplorer
|
||||
}
|
||||
|
||||
impl FromStr for ViewType {
|
||||
@@ -20,7 +20,7 @@ impl FromStr for ViewType {
|
||||
"stats" => Ok(ViewType::Stats),
|
||||
"users" => Ok(ViewType::Users),
|
||||
"playlist_editor" => Ok(ViewType::PlaylistEditor),
|
||||
"playlist_viewer" => Ok(ViewType::PlaylistEditor),
|
||||
"playlist_explorer" => Ok(ViewType::PlaylistExplorer),
|
||||
_ => Err(info_err!(format!("Unknown view type: {s}"))),
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ impl fmt::Display for ViewType {
|
||||
ViewType::Stats => "stats",
|
||||
ViewType::Users => "users",
|
||||
ViewType::PlaylistEditor => "playlist_editor",
|
||||
ViewType::PlaylistViewer => "playlist_viewer",
|
||||
ViewType::PlaylistExplorer => "playlist_explorer",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user