mirror of
https://github.com/euzu/tuliprox.git
synced 2026-10-04 06:52:26 +02:00
Merge pull request #330 from euzu/featue/playlist_explorer_copy_url
Playlist explorer copy url
This commit is contained in:
@@ -241,7 +241,9 @@
|
||||
"NORMALIZE_REGEX": "Normalize Regex",
|
||||
"NAME_PREFIX": "Name Prefix",
|
||||
"NAME_PREFIX_SEPARATOR":"Name Prefix Separator",
|
||||
"STRIP": "Strip"
|
||||
"STRIP": "Strip",
|
||||
"COPY_LINK_TULIPROX": "Copy Virtual Id",
|
||||
"COPY_LINK_PROVIDER": "Copy Provider Url"
|
||||
},
|
||||
"TABLE": {
|
||||
"EMPTY": "",
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
],
|
||||
"path": "M 14.09194,7.6882684e-4 C 13.973764,9.5800672e-5 13.856738,1.8569152e-4 13.740338,0.00128559 10.134203,0.03533121 7.3722672,0.9073449 5.3120566,2.2058124 2.6199728,3.9025243 1.1166249,6.3549771 0.44196532,8.8193928 4.4918101,7.9934421 7.5738185,8.392102 10.393515,9.4901562 V 3.0538267 c 0,-1.0502871 0.717633,-1.3329492 1.494308,-0.8443969 l 5.939123,3.7362233 C 17.883676,5.8672459 17.940027,5.7886156 17.993602,5.7100075 18.773785,4.4409406 19.517515,2.4795189 20.024457,0.67721647 17.837484,0.22159145 15.864573,0.01086418 14.09194,7.6882684e-4 Z M 12.283121,4.6015429 c -0.352571,-0.00358 -0.610222,0.2693437 -0.610222,0.817525 V 18.544943 c 0,0.716278 0.702783,1.00662 1.134576,0.772566 L 23.366199,12.42229 c 0.321466,-0.209944 0.293977,-0.733308 0,-1.002527 L 12.807475,4.7777605 C 12.623215,4.6618552 12.443381,4.6031688 12.283121,4.6015429 Z M 4.6820187,10.631177 c -1.3670233,0.0065 -2.8875164,0.207303 -4.66075611,0.63924 -0.22262,3.841376 1.29470801,7.532859 4.53424051,9.96688 3.3072703,2.484918 8.4898149,3.647573 15.5090939,1.986968 -0.453335,-1.879096 -1.379185,-3.757389 -2.654289,-5.468942 l -5.522485,3.606515 c -0.568764,0.308297 -1.494308,-0.07453 -1.494308,-1.018031 V 12.036783 C 8.6043863,11.2147 6.6723813,10.706689 4.6820187,10.631177 Z"
|
||||
},
|
||||
{
|
||||
"keys": [
|
||||
"ContentCopy"
|
||||
],
|
||||
"path": "M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
|
||||
},
|
||||
{
|
||||
"keys": [
|
||||
"DeleteSweep"
|
||||
@@ -469,5 +463,9 @@
|
||||
{
|
||||
"keys": ["HdHomerunConfig"],
|
||||
"path": "m 16,4.2 c 1.5,0 3,0.6 4.2,1.7 L 21,5.1 C 19.6,3.7 17.8,3 16,3 14.2,3 12.4,3.7 11,5.1 l 0.8,0.8 C 13,4.8 14.5,4.2 16,4.2 m -3.3,2.5 0.8,0.8 c 0.7,-0.7 1.6,-1 2.5,-1 0.9,0 1.8,0.3 2.5,1 L 19.3,6.7 C 18.4,5.8 17.2,5.3 16,5.3 14.8,5.3 13.6,5.8 12.7,6.7 M 19,13 H 17 V 9 h -2 v 4 H 5 c -1.1,0 -2,0.9 -2,2 v 4 c 0,1.1 0.9,2 2,2 h 14 c 1.1,0 2,-0.9 2,-2 v -4 c 0,-1.1 -0.9,-2 -2,-2 m 0,6 H 5 V 15 H 19 Z M 6,16 h 2 v 2 H 6 Z"
|
||||
},
|
||||
{
|
||||
"keys": ["Clipboard"],
|
||||
"path": "M19 2h-4.18C14.4.84 13.3 0 12 0S9.6.84 9.18 2H5c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2m-7 0c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1m7 18H5V4h2v3h10V4h2z"
|
||||
}
|
||||
]
|
||||
@@ -123,4 +123,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__channel {
|
||||
padding-left: var(--padding-micro);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -110,7 +110,7 @@ pub fn EpgConfigView(props: &EpgConfigViewProps) -> Html {
|
||||
};
|
||||
|
||||
let render_empty = || html! {
|
||||
<div class="tp__messaging-config-view__body tp__config-view-page__body">
|
||||
<div class="tp__epg-config-view__body tp__config-view-page__body">
|
||||
{render_smart_match(None)}
|
||||
{render_sources(None)}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,51 @@
|
||||
use std::fmt::Display;
|
||||
use std::rc::Rc;
|
||||
use std::str::FromStr;
|
||||
use wasm_bindgen::JsCast;
|
||||
use yew::platform::spawn_local;
|
||||
use crate::app::context::PlaylistExplorerContext;
|
||||
use yew::prelude::*;
|
||||
use shared::model::{CommonPlaylistItem, SearchRequest, UiPlaylistGroup, XtreamCluster};
|
||||
use crate::app::components::{IconButton, NoContent, Search};
|
||||
use yew_hooks::use_clipboard;
|
||||
use yew_i18n::use_translation;
|
||||
use shared::create_tuliprox_error_result;
|
||||
use shared::error::{TuliproxError, TuliproxErrorKind};
|
||||
use shared::model::{CommonPlaylistItem, PlaylistRequestType, SearchRequest, UiPlaylistGroup, XtreamCluster};
|
||||
use crate::app::components::{AppIcon, IconButton, NoContent, Search};
|
||||
use crate::app::components::menu_item::MenuItem;
|
||||
use crate::app::components::popup_menu::PopupMenu;
|
||||
use crate::hooks::use_service_context;
|
||||
use crate::html_if;
|
||||
use crate::model::{BusyStatus, EventMessage};
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
enum ExplorerAction {
|
||||
CopyLinkTuliprox,
|
||||
CopyLinkProvider,
|
||||
}
|
||||
|
||||
impl Display for ExplorerAction {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", match self {
|
||||
Self::CopyLinkTuliprox => "copy_link_tuliprox",
|
||||
Self::CopyLinkProvider => "copy_link_provider",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ExplorerAction {
|
||||
type Err = TuliproxError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, TuliproxError> {
|
||||
if s.eq("copy_link_tuliprox") {
|
||||
Ok(Self::CopyLinkTuliprox)
|
||||
} else if s.eq("copy_link_provider") {
|
||||
Ok(Self::CopyLinkProvider)
|
||||
} else {
|
||||
create_tuliprox_error_result!(TuliproxErrorKind::Info, "Unknown InputType: {}", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ExplorerLevel {
|
||||
Categories,
|
||||
Group(Rc<UiPlaylistGroup>),
|
||||
@@ -16,18 +54,81 @@ enum ExplorerLevel {
|
||||
#[function_component]
|
||||
pub fn PlaylistExplorer() -> Html {
|
||||
let context = use_context::<PlaylistExplorerContext>().expect("PlaylistExplorer context not found");
|
||||
let translate = use_translation();
|
||||
let service_ctx = use_service_context();
|
||||
let current_item = use_state(|| ExplorerLevel::Categories);
|
||||
let playlist = use_state(|| (*context.playlist).clone());
|
||||
let selected_channel = use_state(|| None::<Rc<CommonPlaylistItem>>);
|
||||
let popup_anchor_ref = use_state(|| None::<web_sys::Element>);
|
||||
let popup_is_open = use_state(|| false);
|
||||
let clipboard = use_clipboard();
|
||||
|
||||
let handle_popup_close = {
|
||||
let set_is_open = popup_is_open.clone();
|
||||
Callback::from(move |()| {
|
||||
set_is_open.set(false);
|
||||
})
|
||||
};
|
||||
|
||||
let handle_popup_onclick = {
|
||||
let set_selected_channel = selected_channel.clone();
|
||||
let set_anchor_ref = popup_anchor_ref.clone();
|
||||
let set_is_open = popup_is_open.clone();
|
||||
Callback::from(move |(dto, event): (Rc<CommonPlaylistItem>, MouseEvent)| {
|
||||
if let Some(target) = event.target_dyn_into::<web_sys::Element>() {
|
||||
set_selected_channel.set(Some(dto.clone()));
|
||||
set_anchor_ref.set(Some(target));
|
||||
set_is_open.set(true);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
{
|
||||
let set_playlist = playlist.clone();
|
||||
let set_current_item = current_item.clone();
|
||||
let set_selected_channel = selected_channel.clone();
|
||||
let set_popup_is_open = popup_is_open.clone();
|
||||
let set_anchor_ref = popup_anchor_ref.clone();
|
||||
use_effect_with(context.playlist.clone(), move |new_playlist| {
|
||||
set_current_item.set(ExplorerLevel::Categories);
|
||||
set_playlist.set((**new_playlist).clone());
|
||||
// Reset popup state and selection when the underlying data changes
|
||||
set_selected_channel.set(None);
|
||||
set_popup_is_open.set(false);
|
||||
set_anchor_ref.set(None);
|
||||
|| {}
|
||||
});
|
||||
}
|
||||
|
||||
let copy_to_clipboard ={
|
||||
let clipboard = clipboard.clone();
|
||||
move |text: String| {
|
||||
clipboard.write_text(text);
|
||||
}
|
||||
};
|
||||
|
||||
let handle_menu_click = {
|
||||
let popup_is_open_state = popup_is_open.clone();
|
||||
let selected_channel = selected_channel.clone();
|
||||
Callback::from(move |(name, _): (String, _)| {
|
||||
if let Ok(action) = ExplorerAction::from_str(&name) {
|
||||
match action {
|
||||
ExplorerAction::CopyLinkTuliprox => {
|
||||
if let Some(dto) = &*selected_channel {
|
||||
copy_to_clipboard(dto.virtual_id.to_string());
|
||||
}
|
||||
}
|
||||
ExplorerAction::CopyLinkProvider => {
|
||||
if let Some(dto) = &*selected_channel {
|
||||
copy_to_clipboard(dto.url.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
popup_is_open_state.set(false);
|
||||
})
|
||||
};
|
||||
|
||||
let handle_back_click = {
|
||||
let current_item = current_item.clone();
|
||||
Callback::from(move |_| {
|
||||
@@ -151,10 +252,17 @@ pub fn PlaylistExplorer() -> Html {
|
||||
<div class="tp__playlist-explorer__group">
|
||||
<div class="tp__playlist-explorer__group-list">
|
||||
{
|
||||
group.channels.iter().map(|c| {
|
||||
group.channels.iter().map(|chan| {
|
||||
let chan_clone = chan.clone();
|
||||
let popup_onclick = handle_popup_onclick.clone();
|
||||
html! {
|
||||
<span class="tp__playlist-explorer__item">
|
||||
{render_channel_logo(c)} {c.title.clone()}
|
||||
<span class="tp__playlist-explorer__item tp__playlist-explorer__channel">
|
||||
<button class="tp__icon-button"
|
||||
onclick={Callback::from(move |event: MouseEvent| popup_onclick.emit((chan_clone.clone(), event)))}>
|
||||
<AppIcon name="Popup"></AppIcon>
|
||||
</button>
|
||||
{render_channel_logo(chan)}
|
||||
{chan.title.clone()}
|
||||
</span>
|
||||
}
|
||||
}).collect::<Html>()
|
||||
@@ -182,6 +290,14 @@ pub fn PlaylistExplorer() -> Html {
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
<PopupMenu is_open={*popup_is_open} anchor_ref={(*popup_anchor_ref).clone()} on_close={handle_popup_close}>
|
||||
{ html_if!((*context.playlist_request_type).as_ref() == Some(&PlaylistRequestType::Target), {
|
||||
<MenuItem icon="Clipboard" name={ExplorerAction::CopyLinkTuliprox.to_string()} label={translate.t("LABEL.COPY_LINK_TULIPROX")} onclick={&handle_menu_click}></MenuItem>
|
||||
})
|
||||
}
|
||||
<MenuItem icon="Clipboard" name={ExplorerAction::CopyLinkProvider.to_string()} label={translate.t("LABEL.COPY_LINK_PROVIDER")} onclick={&handle_menu_click}></MenuItem>
|
||||
</PopupMenu>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::app::context::PlaylistExplorerContext;
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use yew_i18n::use_translation;
|
||||
use shared::model::{UiPlaylistCategories};
|
||||
use shared::model::{PlaylistRequestType, UiPlaylistCategories};
|
||||
use crate::app::components::playlist::playlist_explorer::PlaylistExplorer;
|
||||
|
||||
#[function_component]
|
||||
@@ -12,6 +12,7 @@ pub fn PlaylistExplorerView() -> Html {
|
||||
let breadcrumbs = use_state(|| Rc::new(vec![translate.t("LABEL.PLAYLISTS"), translate.t("LABEL.LIST")]));
|
||||
let active_page = use_state(|| PlaylistExplorerPage::SourceSelector);
|
||||
let playlist = use_state(|| None::<Rc<UiPlaylistCategories>>);
|
||||
let playlist_req_type = use_state(|| None::<PlaylistRequestType>);
|
||||
|
||||
let handle_breadcrumb_select = {
|
||||
let view_visible = active_page.clone();
|
||||
@@ -38,6 +39,7 @@ pub fn PlaylistExplorerView() -> Html {
|
||||
let context = PlaylistExplorerContext {
|
||||
active_page: active_page.clone(),
|
||||
playlist: playlist.clone(),
|
||||
playlist_request_type: playlist_req_type.clone(),
|
||||
};
|
||||
|
||||
html! {
|
||||
|
||||
@@ -45,9 +45,12 @@ pub fn PlaylistSourceSelector() -> Html {
|
||||
set_loading.set(true);
|
||||
services.event.broadcast(EventMessage::Busy(BusyStatus::Show));
|
||||
let set_loading = set_loading.clone();
|
||||
let req_type = request.rtype;
|
||||
let req = request;
|
||||
spawn_local(async move {
|
||||
let playlist = services.playlist.get_playlist_categories(&request).await;
|
||||
let playlist = services.playlist.get_playlist_categories(&req).await;
|
||||
playlist_explorer_ctx_clone.playlist.set(playlist);
|
||||
playlist_explorer_ctx_clone.playlist_request_type.set(Some(req_type));
|
||||
set_loading.set(false);
|
||||
services.event.broadcast(EventMessage::Busy(BusyStatus::Hide));
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
use regex::Regex;
|
||||
use yew::UseStateHandle;
|
||||
use shared::model::{AppConfigDto, ConfigTargetDto, ProxyUserCredentialsDto, SearchRequest, StatusCheck, UiPlaylistCategories};
|
||||
use shared::model::{AppConfigDto, ConfigTargetDto, PlaylistRequestType, ProxyUserCredentialsDto, SearchRequest, StatusCheck, UiPlaylistCategories};
|
||||
use crate::app::components::{InputRow, PlaylistEditorPage, PlaylistExplorerPage, UserlistPage};
|
||||
|
||||
type SingleSource = (Vec<Rc<InputRow>>, Vec<Rc<ConfigTargetDto>>);
|
||||
@@ -20,6 +20,7 @@ pub struct PlaylistEditorContext {
|
||||
pub struct PlaylistExplorerContext {
|
||||
pub active_page: UseStateHandle<PlaylistExplorerPage>,
|
||||
pub playlist: UseStateHandle<Option<Rc<UiPlaylistCategories>>>,
|
||||
pub playlist_request_type: UseStateHandle<Option<PlaylistRequestType>>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user