fix: resolve context menu positioning issue and integrate it into table rows
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
Some checks failed
Build MIPS Binary / build (push) Has been cancelled
This commit is contained in:
@@ -171,11 +171,8 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
<For each=move || filtered_hashes.get() key=|hash| hash.clone() children={
|
<For each=move || filtered_hashes.get() key=|hash| hash.clone() children={
|
||||||
let on_action = on_action.clone();
|
let on_action = on_action.clone();
|
||||||
move |hash| {
|
move |hash| {
|
||||||
let h = hash.clone();
|
|
||||||
view! {
|
view! {
|
||||||
<TorrentContextMenu torrent_hash=h on_action=on_action.clone()>
|
<TorrentRow hash=hash.clone() on_action=on_action.clone() />
|
||||||
<TorrentRow hash=hash.clone() />
|
|
||||||
</TorrentContextMenu>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} />
|
} />
|
||||||
@@ -191,12 +188,9 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
<For each=move || filtered_hashes.get() key=|hash| hash.clone() children={
|
<For each=move || filtered_hashes.get() key=|hash| hash.clone() children={
|
||||||
let on_action = on_action.clone();
|
let on_action = on_action.clone();
|
||||||
move |hash| {
|
move |hash| {
|
||||||
let h = hash.clone();
|
|
||||||
view! {
|
view! {
|
||||||
<div class="pb-3">
|
<div class="pb-3">
|
||||||
<TorrentContextMenu torrent_hash=h on_action=on_action.clone()>
|
<TorrentCard hash=hash.clone() on_action=on_action.clone() />
|
||||||
<TorrentCard hash=hash.clone() />
|
|
||||||
</TorrentContextMenu>
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,6 +204,7 @@ pub fn TorrentTable() -> impl IntoView {
|
|||||||
#[component]
|
#[component]
|
||||||
fn TorrentRow(
|
fn TorrentRow(
|
||||||
hash: String,
|
hash: String,
|
||||||
|
on_action: Callback<(String, String)>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
|
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
|
||||||
let h = hash.clone();
|
let h = hash.clone();
|
||||||
@@ -220,6 +215,7 @@ fn TorrentRow(
|
|||||||
view! {
|
view! {
|
||||||
<Show when=move || torrent.get().is_some() fallback=|| ()>
|
<Show when=move || torrent.get().is_some() fallback=|| ()>
|
||||||
{
|
{
|
||||||
|
let on_action = on_action.clone();
|
||||||
move || {
|
move || {
|
||||||
let t = torrent.get().unwrap();
|
let t = torrent.get().unwrap();
|
||||||
let t_name = t.name.clone();
|
let t_name = t.name.clone();
|
||||||
@@ -232,53 +228,57 @@ fn TorrentRow(
|
|||||||
|
|
||||||
let t_name_for_title = t_name.clone();
|
let t_name_for_title = t_name.clone();
|
||||||
let t_name_for_content = t_name.clone();
|
let t_name_for_content = t_name.clone();
|
||||||
|
let h_for_menu = stored_hash.get_value();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<TableRow
|
<TorrentContextMenu torrent_hash=h_for_menu on_action=on_action.clone()>
|
||||||
class="cursor-pointer h-12"
|
<TableRow
|
||||||
attr:data-state=move || if is_selected.get() { "selected" } else { "" }
|
class="cursor-pointer h-12"
|
||||||
on:click=move |_| store.selected_torrent.set(Some(stored_hash.get_value()))
|
attr:data-state=move || if is_selected.get() { "selected" } else { "" }
|
||||||
>
|
on:click=move |_| store.selected_torrent.set(Some(stored_hash.get_value()))
|
||||||
<TableCell class="font-medium truncate max-w-[200px] lg:max-w-md px-2 py-0 h-12 flex items-center border-0" attr:title=t_name_for_title>
|
>
|
||||||
{t_name_for_content}
|
<TableCell class="font-medium truncate max-w-[200px] lg:max-w-md px-2 py-0 h-12 flex items-center border-0" attr:title=t_name_for_title>
|
||||||
</TableCell>
|
{t_name_for_content}
|
||||||
<TableCell class="font-mono text-xs text-muted-foreground px-2">
|
</TableCell>
|
||||||
{format_bytes(t.size)}
|
<TableCell class="font-mono text-xs text-muted-foreground px-2">
|
||||||
</TableCell>
|
{format_bytes(t.size)}
|
||||||
<TableCell class="px-2">
|
</TableCell>
|
||||||
<div class="flex items-center gap-2">
|
<TableCell class="px-2">
|
||||||
<div class="h-1.5 w-full bg-secondary rounded-full overflow-hidden">
|
<div class="flex items-center gap-2">
|
||||||
<div class="h-full bg-primary transition-all duration-500" style=format!("width: {}%", t.percent_complete)></div>
|
<div class="h-1.5 w-full bg-secondary rounded-full overflow-hidden">
|
||||||
|
<div class="h-full bg-primary transition-all duration-500" style=format!("width: {}%", t.percent_complete)></div>
|
||||||
|
</div>
|
||||||
|
<span class="text-[10px] text-muted-foreground w-10 text-right">{format!("{:.1}%", t.percent_complete)}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="text-[10px] text-muted-foreground w-10 text-right">{format!("{:.1}%", t.percent_complete)}</span>
|
</TableCell>
|
||||||
</div>
|
<TableCell class={format!("px-2 text-xs font-semibold {}", status_color)}>
|
||||||
</TableCell>
|
{format!("{:?}", t.status)}
|
||||||
<TableCell class={format!("px-2 text-xs font-semibold {}", status_color)}>
|
</TableCell>
|
||||||
{format!("{:?}", t.status)}
|
<TableCell class="text-right font-mono text-xs text-green-600 dark:text-green-500 px-2 whitespace-nowrap">
|
||||||
</TableCell>
|
{format_speed(t.down_rate)}
|
||||||
<TableCell class="text-right font-mono text-xs text-green-600 dark:text-green-500 px-2 whitespace-nowrap">
|
</TableCell>
|
||||||
{format_speed(t.down_rate)}
|
<TableCell class="text-right font-mono text-xs text-blue-600 dark:text-blue-500 px-2 whitespace-nowrap">
|
||||||
</TableCell>
|
{format_speed(t.up_rate)}
|
||||||
<TableCell class="text-right font-mono text-xs text-blue-600 dark:text-blue-500 px-2 whitespace-nowrap">
|
</TableCell>
|
||||||
{format_speed(t.up_rate)}
|
<TableCell class="text-right font-mono text-xs text-muted-foreground px-2 whitespace-nowrap">
|
||||||
</TableCell>
|
{format_duration(t.eta)}
|
||||||
<TableCell class="text-right font-mono text-xs text-muted-foreground px-2 whitespace-nowrap">
|
</TableCell>
|
||||||
{format_duration(t.eta)}
|
<TableCell class="text-right font-mono text-xs text-muted-foreground px-2 whitespace-nowrap">
|
||||||
</TableCell>
|
{format_date(t.added_date)}
|
||||||
<TableCell class="text-right font-mono text-xs text-muted-foreground px-2 whitespace-nowrap">
|
</TableCell>
|
||||||
{format_date(t.added_date)}
|
</TableRow>
|
||||||
</TableCell>
|
</TorrentContextMenu>
|
||||||
</TableRow>
|
|
||||||
}.into_any()
|
}.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</Show>
|
</Show>
|
||||||
}
|
}.into_any()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn TorrentCard(
|
fn TorrentCard(
|
||||||
hash: String,
|
hash: String,
|
||||||
|
on_action: Callback<(String, String)>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
|
let store = use_context::<crate::store::TorrentStore>().expect("store not provided");
|
||||||
let h = hash.clone();
|
let h = hash.clone();
|
||||||
@@ -289,53 +289,57 @@ fn TorrentCard(
|
|||||||
view! {
|
view! {
|
||||||
<Show when=move || torrent.get().is_some() fallback=|| ()>
|
<Show when=move || torrent.get().is_some() fallback=|| ()>
|
||||||
{
|
{
|
||||||
|
let on_action = on_action.clone();
|
||||||
move || {
|
move || {
|
||||||
let t = torrent.get().unwrap();
|
let t = torrent.get().unwrap();
|
||||||
let t_name = t.name.clone();
|
let t_name = t.name.clone();
|
||||||
let status_badge_class = match t.status { shared::TorrentStatus::Seeding => "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 border-green-200 dark:border-green-800", shared::TorrentStatus::Downloading => "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 border-blue-200 dark:border-blue-800", shared::TorrentStatus::Paused => "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800", shared::TorrentStatus::Error => "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 border-red-200 dark:border-red-800", _ => "bg-muted text-muted-foreground" };
|
let status_badge_class = match t.status { shared::TorrentStatus::Seeding => "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400 border-green-200 dark:border-green-800", shared::TorrentStatus::Downloading => "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 border-blue-200 dark:border-blue-800", shared::TorrentStatus::Paused => "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400 border-yellow-200 dark:border-yellow-800", shared::TorrentStatus::Error => "bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 border-red-200 dark:border-red-800", _ => "bg-muted text-muted-foreground" };
|
||||||
|
let h_for_menu = stored_hash.get_value();
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div
|
<TorrentContextMenu torrent_hash=h_for_menu on_action=on_action.clone()>
|
||||||
class=move || {
|
<div
|
||||||
let selected = store.selected_torrent.get();
|
class=move || {
|
||||||
let is_selected = selected.as_deref() == Some(stored_hash.get_value().as_str());
|
let selected = store.selected_torrent.get();
|
||||||
if is_selected {
|
let is_selected = selected.as_deref() == Some(stored_hash.get_value().as_str());
|
||||||
"ring-2 ring-primary rounded-lg transition-all"
|
if is_selected {
|
||||||
} else {
|
"ring-2 ring-primary rounded-lg transition-all"
|
||||||
"transition-all"
|
} else {
|
||||||
|
"transition-all"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
on:click=move |_| store.selected_torrent.set(Some(stored_hash.get_value()))
|
||||||
on:click=move |_| store.selected_torrent.set(Some(stored_hash.get_value()))
|
>
|
||||||
>
|
<Card class="h-full select-none cursor-pointer hover:border-primary transition-colors">
|
||||||
<Card class="h-full select-none cursor-pointer hover:border-primary transition-colors">
|
<CardHeader class="p-3 pb-0">
|
||||||
<CardHeader class="p-3 pb-0">
|
<div class="flex justify-between items-start gap-2">
|
||||||
<div class="flex justify-between items-start gap-2">
|
<CardTitle class="text-sm font-medium leading-tight line-clamp-2">{t_name.clone()}</CardTitle>
|
||||||
<CardTitle class="text-sm font-medium leading-tight line-clamp-2">{t_name.clone()}</CardTitle>
|
<div class={format!("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 {}", status_badge_class)}>{format!("{:?}", t.status)}</div>
|
||||||
<div class={format!("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 {}", status_badge_class)}>{format!("{:?}", t.status)}</div>
|
|
||||||
</div>
|
|
||||||
</CardHeader>
|
|
||||||
<CardBody class="p-3 pt-2 gap-3 flex flex-col">
|
|
||||||
<div class="flex flex-col gap-1">
|
|
||||||
<div class="flex justify-between text-[10px] text-muted-foreground">
|
|
||||||
<span>{format_bytes(t.size)}</span>
|
|
||||||
<span>{format!("{:.1}%", t.percent_complete)}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="h-1.5 w-full bg-secondary rounded-full overflow-hidden">
|
</CardHeader>
|
||||||
<div class="h-full bg-primary transition-all duration-500" style=format!("width: {}%", t.percent_complete)></div>
|
<CardBody class="p-3 pt-2 gap-3 flex flex-col">
|
||||||
|
<div class="flex flex-col gap-1">
|
||||||
|
<div class="flex justify-between text-[10px] text-muted-foreground">
|
||||||
|
<span>{format_bytes(t.size)}</span>
|
||||||
|
<span>{format!("{:.1}%", t.percent_complete)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="h-1.5 w-full bg-secondary rounded-full overflow-hidden">
|
||||||
|
<div class="h-full bg-primary transition-all duration-500" style=format!("width: {}%", t.percent_complete)></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="grid grid-cols-4 gap-2 text-[10px] font-mono text-muted-foreground pt-1 border-t border-border/50">
|
||||||
<div class="grid grid-cols-4 gap-2 text-[10px] font-mono text-muted-foreground pt-1 border-t border-border/50">
|
<div class="flex flex-col text-blue-600 dark:text-blue-500"><span>"DL"</span><span>{format_speed(t.down_rate)}</span></div>
|
||||||
<div class="flex flex-col text-blue-600 dark:text-blue-500"><span>"DL"</span><span>{format_speed(t.down_rate)}</span></div>
|
<div class="flex flex-col text-green-600 dark:text-green-500"><span>"UP"</span><span>{format_speed(t.up_rate)}</span></div>
|
||||||
<div class="flex flex-col text-green-600 dark:text-green-500"><span>"UP"</span><span>{format_speed(t.up_rate)}</span></div>
|
<div class="flex flex-col"><span>"ETA"</span><span>{format_duration(t.eta)}</span></div>
|
||||||
<div class="flex flex-col"><span>"ETA"</span><span>{format_duration(t.eta)}</span></div>
|
<div class="flex flex-col text-right"><span>"DATE"</span><span>{format_date(t.added_date)}</span></div>
|
||||||
<div class="flex flex-col text-right"><span>"DATE"</span><span>{format_date(t.added_date)}</span></div>
|
</div>
|
||||||
</div>
|
</CardBody>
|
||||||
</CardBody>
|
</Card>
|
||||||
</Card>
|
</div>
|
||||||
</div>
|
</TorrentContextMenu>
|
||||||
}
|
}.into_any()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</Show>
|
</Show>
|
||||||
}
|
}.into_any()
|
||||||
}
|
}
|
||||||
@@ -202,14 +202,14 @@ pub fn ContextMenuTrigger(
|
|||||||
#[prop(optional)] on_open: Option<Callback<()>>,
|
#[prop(optional)] on_open: Option<Callback<()>>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let ctx = expect_context::<ContextMenuContext>();
|
let ctx = expect_context::<ContextMenuContext>();
|
||||||
let trigger_class = tw_merge!("contents", class);
|
let trigger_class = tw_merge!("block w-full h-full", class);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div
|
<div
|
||||||
class=trigger_class
|
class=trigger_class
|
||||||
data-name="ContextMenuTrigger"
|
data-name="ContextMenuTrigger"
|
||||||
data-context-trigger=ctx.target_id
|
data-context-trigger=ctx.target_id
|
||||||
on:contextmenu=move |_| {
|
on:contextmenu=move |e: web_sys::MouseEvent| {
|
||||||
if let Some(cb) = on_open {
|
if let Some(cb) = on_open {
|
||||||
cb.run(());
|
cb.run(());
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ pub fn ContextMenuContent(
|
|||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let ctx = expect_context::<ContextMenuContext>();
|
let ctx = expect_context::<ContextMenuContext>();
|
||||||
|
|
||||||
let base_classes = "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md transition-all duration-200 data-[state=closed]:opacity-0 data-[state=closed]:scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100";
|
let base_classes = "fixed z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md transition-all duration-200 data-[state=closed]:opacity-0 data-[state=closed]:scale-95 data-[state=open]:opacity-100 data-[state=open]:scale-100";
|
||||||
|
|
||||||
let class = tw_merge!(base_classes, class);
|
let class = tw_merge!(base_classes, class);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user