diff options
author | 2025-05-15 09:09:37 +0100 | |
---|---|---|
committer | 2025-05-15 09:09:37 +0100 | |
commit | 62aaa8cb8583d9189358a6c15ca69257b342c4ea (patch) | |
tree | ab63c80e8d38df4e4ef3c3b8a41bccde82d4ffdd /src | |
parent | cf6e64da8dfb78dd103727066fa494d79912fe2a (diff) | |
download | macaw-web-62aaa8cb8583d9189358a6c15ca69257b342c4ea.tar.gz macaw-web-62aaa8cb8583d9189358a6c15ca69257b342c4ea.tar.bz2 macaw-web-62aaa8cb8583d9189358a6c15ca69257b342c4ea.zip |
feat: dock hover and pinning focus
Diffstat (limited to 'src')
-rw-r--r-- | src/lib.rs | 124 |
1 files changed, 111 insertions, 13 deletions
@@ -621,6 +621,15 @@ fn Macaw( let user_presences = Store::new(UserPresences::new()); provide_context(user_presences); + let client_user = LocalResource::new(move || { + async move { + let client = use_context::<Client>().expect("client not in context"); + let user = client.get_user((*client.jid).clone()).await.unwrap(); + MacawUser::got_user(user) + } + }); + provide_context(client_user); + // TODO: get cached contacts on login before getting the updated contacts OnceResource::new(async move { @@ -717,16 +726,22 @@ pub enum SidebarOpen { Chats, } -pub fn toggle_open(state: &mut Option<SidebarOpen>, open: SidebarOpen) { +/// 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 + *state = None; + false } else { - *state = Some(open) + *state = Some(open); + true } } - None => *state = Some(open), + None => { + *state = Some(open); + true + }, } } @@ -736,19 +751,41 @@ pub fn Sidebar() -> impl IntoView { let (open, set_open) = signal(None::<SidebarOpen>); // for what is just in the hovered state (not clicked to be pinned open yet necessarily) let (hovered, set_hovered) = signal(None::<SidebarOpen>); + let (just_closed, set_just_closed) = signal(false); view! { - <div class="sidebar"> + <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::Chats) on:click=move |_| { - set_open.update(|state| toggle_open(state, SidebarOpen::Roster)) + <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> <img src="/assets/caw.png" /> </div> - <div class="chats-tab dock-item" class:focused=move || *open.read() == Some(SidebarOpen::Chats) class:hovering=move || *hovered.read() == Some(SidebarOpen::Chats) on:click=move |_| { - set_open.update(|state| toggle_open(state, SidebarOpen::Chats)) + <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" /> @@ -757,15 +794,45 @@ pub fn Sidebar() -> impl IntoView { <div class="pins"> </div> <div class="personal"> + <PersonalStatus /> </div> </div> + {move || if let Some(hovered) = *hovered.read() { + if Some(hovered) != *open.read() { + if !just_closed.get() { + match hovered { + SidebarOpen::Roster => view! { + <div class="sidebar-drawer sidebar-hovering-drawer"> + <RosterList /> + </div> + }.into_any(), + SidebarOpen::Chats => view! { + <div class="sidebar-drawer sidebar-hovering-drawer"> + <ChatsList /> + </div> + }.into_any(), + } + } else { + + view! {}.into_any() + } + } else { + view! {}.into_any() + } + } else { + view! {}.into_any() + }} {move || if let Some(opened) = *open.read() { match opened { SidebarOpen::Roster => view! { - <RosterList /> + <div class="sidebar-drawer"> + <RosterList /> + </div> }.into_any(), SidebarOpen::Chats => view! { - <ChatsList /> + <div class="sidebar-drawer"> + <ChatsList /> + </div> }.into_any(), } } else { @@ -776,6 +843,26 @@ pub fn Sidebar() -> impl IntoView { } #[component] +pub fn PersonalStatus() -> impl IntoView { + let user: LocalResource<MacawUser> = use_context().expect("no local user in context"); + + let (open, set_open) = signal(false); + move || if let Some(user) = user.get() { + let user: Store<User> = <ArcStore<filamento::user::User> as Clone>::clone(&(*user.user)).into(); + view! { + <div class="dock-item" class:focused=move || *open.read() on:click=move |_| { + set_open.update(|state| *state = !*state) + }> + <AvatarWithPresence user=user /> + <div class="dock-pill"></div> + </div> + }.into_any() + } else { + view! {}.into_any() + } +} + +#[component] pub fn OpenChatsPanelView() -> impl IntoView { let open_chats: Store<OpenChatsPanel> = use_context().expect("no open chats panel in context"); @@ -1605,12 +1692,23 @@ impl DerefMut for MacawMessage { } } +#[derive(Clone)] struct MacawUser { - user: StateListener<JID, Store<User>>, + user: StateListener<JID, ArcStore<User>>, +} + +impl MacawUser { + fn got_user(user: User) -> Self { + + let user_state_store: StateStore<JID, ArcStore<User>> = + use_context().expect("no user state store"); + let user = user_state_store.store(user.jid.clone(), ArcStore::new(user)); + Self { user } + } } impl Deref for MacawUser { - type Target = StateListener<JID, Store<User>>; + type Target = StateListener<JID, ArcStore<User>>; fn deref(&self) -> &Self::Target { &self.user |