use std::collections::{HashMap, HashSet};
use jid::BareJID;
use leptos::prelude::*;
use reactive_stores::Store;
use crate::components::{
chats_list::ChatsList, personal_status::PersonalStatus, roster_list::RosterList,
};
#[derive(PartialEq, Eq, Clone, Copy, Hash)]
pub enum SidebarOpen {
Roster,
Chats,
}
#[derive(Store)]
pub struct Drawer {
open: SidebarOpen,
hovering: bool,
}
pub enum Open {
/// Currently on screen
Focused,
/// Open in background somewhere (e.g. in another chat tab)
Open,
/// Closed
Closed,
}
impl Open {
pub fn is_focused(&self) -> bool {
match self {
Open::Focused => true,
Open::Open => false,
Open::Closed => false,
}
}
pub fn is_open(&self) -> bool {
match self {
Open::Focused => true,
Open::Open => true,
Open::Closed => false,
}
}
}
/// returns whether the state was changed to open (true) or closed (false)
pub fn toggle_open(state: &mut Option<SidebarOpen>, open: SidebarOpen) -> bool {
match state {
Some(opened) => {
if *opened == open {
*state = None;
false
} else {
*state = Some(open);
true
}
}
None => {
*state = Some(open);
true
}
}
}
#[component]
pub fn Sidebar() -> impl IntoView {
let requests: ReadSignal<HashSet<BareJID>> =
use_context().expect("no pending subscriptions in context");
// for what has been clicked open (in the background)
let (open, set_open) = signal(None::<SidebarOpen>);
// for what is just in the hovered state (not clicked to be pinned open yet necessarily)
let open = Memo::new(move |_| open.get());
let (hovered, set_hovered) = signal(None::<SidebarOpen>);
let hovered = Memo::new(move |_| hovered.get());
let (just_closed, set_just_closed) = signal(false);
let just_closed = Memo::new(move |_| just_closed.get());
let pages = Memo::new(move |_| {
let mut pages = HashSet::new();
if let Some(hovered) = *hovered.read() {
pages.insert(hovered);
}
if let Some(opened) = *open.read() {
pages.insert(opened);
}
pages
});
view! {
<div
class="sidebar"
on:mouseleave=move |_| {
set_hovered.set(None);
set_just_closed.set(false);
}
>
<div class="dock panel">
<div class="shortcuts">
<div
class="roster-tab dock-item"
class:focused=move || *open.read() == Some(SidebarOpen::Roster)
class:hovering=move || *hovered.read() == Some(SidebarOpen::Roster)
on:mouseenter=move |_| {
set_just_closed.set(false);
set_hovered.set(Some(SidebarOpen::Roster))
}
on:click=move |_| {
set_open
.update(|state| {
if !toggle_open(state, SidebarOpen::Roster) {
set_just_closed.set(true);
}
})
}
>
<div class="dock-pill"></div>
<div class="dock-icon">
<div class="icon-with-badge">
<img src="/assets/caw.png" />
{move || {
let len = requests.read().len();
if len > 0 {
view! { <div class="badge">{len}</div> }.into_any()
} else {
view! {}.into_any()
}
}}
</div>
</div>
</div>
<div
class="chats-tab dock-item"
class:focused=move || *open.read() == Some(SidebarOpen::Chats)
class:hovering=move || *hovered.read() == Some(SidebarOpen::Chats)
on:mouseenter=move |_| {
set_just_closed.set(false);
set_hovered.set(Some(SidebarOpen::Chats))
}
on:click=move |_| {
set_open
.update(|state| {
if !toggle_open(state, SidebarOpen::Chats) {
set_just_closed.set(true);
}
})
}
>
<div class="dock-pill"></div>
<img src="/assets/bubble.png" />
</div>
</div>
<div class="pins"></div>
<div class="personal">
<PersonalStatus />
</div>
</div>
{move || {
if !just_closed.get() {
view! {
<For each=move || pages.get() key=|page| *page let(page)>
{move || match page {
SidebarOpen::Roster => {
view! {
<div
class:sidebar-drawer=true
class:sidebar-hovering-drawer=move || {
!(*open.read() == Some(SidebarOpen::Roster))
&& (*hovered.read() == Some(SidebarOpen::Roster))
}
>
<RosterList />
</div>
}
.into_any()
}
SidebarOpen::Chats => {
view! {
<div
class:sidebar-drawer=true
class:sidebar-hovering-drawer=move || {
!(*open.read() == Some(SidebarOpen::Chats))
&& (*hovered.read() == Some(SidebarOpen::Chats))
}
>
<ChatsList />
</div>
}
.into_any()
}
}}
</For>
}
.into_any()
} else {
view! {}.into_any()
}
}}
</div>
}
}