collab_ui.rs

  1pub mod channel_view;
  2pub mod chat_panel;
  3pub mod collab_panel;
  4mod collab_titlebar_item;
  5mod face_pile;
  6pub mod notification_panel;
  7pub mod notifications;
  8mod panel_settings;
  9mod sharing_status_indicator;
 10
 11use call::{report_call_event_for_room, ActiveCall, Room};
 12use feature_flags::{ChannelsAlpha, FeatureFlagAppExt};
 13use gpui::{
 14    actions,
 15    elements::{Empty, Image},
 16    geometry::{
 17        rect::RectF,
 18        vector::{vec2f, Vector2F},
 19    },
 20    platform::{Screen, WindowBounds, WindowKind, WindowOptions},
 21    AnyElement, AppContext, Element, ImageData, Task,
 22};
 23use std::{rc::Rc, sync::Arc};
 24use theme::Theme;
 25use time::{OffsetDateTime, UtcOffset};
 26use util::ResultExt;
 27use workspace::AppState;
 28
 29pub use collab_titlebar_item::CollabTitlebarItem;
 30pub use panel_settings::{
 31    ChatPanelSettings, CollaborationPanelSettings, NotificationPanelSettings,
 32};
 33
 34actions!(
 35    collab,
 36    [ToggleScreenSharing, ToggleMute, ToggleDeafen, LeaveCall]
 37);
 38
 39pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
 40    settings::register::<CollaborationPanelSettings>(cx);
 41    settings::register::<ChatPanelSettings>(cx);
 42    settings::register::<NotificationPanelSettings>(cx);
 43
 44    vcs_menu::init(cx);
 45    collab_titlebar_item::init(cx);
 46    collab_panel::init(cx);
 47    chat_panel::init(cx);
 48    notifications::init(&app_state, cx);
 49    sharing_status_indicator::init(cx);
 50
 51    cx.add_global_action(toggle_screen_sharing);
 52    cx.add_global_action(toggle_mute);
 53    cx.add_global_action(toggle_deafen);
 54}
 55
 56pub fn toggle_screen_sharing(_: &ToggleScreenSharing, cx: &mut AppContext) {
 57    let call = ActiveCall::global(cx).read(cx);
 58    if let Some(room) = call.room().cloned() {
 59        let client = call.client();
 60        let toggle_screen_sharing = room.update(cx, |room, cx| {
 61            if room.is_screen_sharing() {
 62                report_call_event_for_room(
 63                    "disable screen share",
 64                    room.id(),
 65                    room.channel_id(),
 66                    &client,
 67                    cx,
 68                );
 69                Task::ready(room.unshare_screen(cx))
 70            } else {
 71                report_call_event_for_room(
 72                    "enable screen share",
 73                    room.id(),
 74                    room.channel_id(),
 75                    &client,
 76                    cx,
 77                );
 78                room.share_screen(cx)
 79            }
 80        });
 81        toggle_screen_sharing.detach_and_log_err(cx);
 82    }
 83}
 84
 85pub fn toggle_mute(_: &ToggleMute, cx: &mut AppContext) {
 86    let call = ActiveCall::global(cx).read(cx);
 87    if let Some(room) = call.room().cloned() {
 88        let client = call.client();
 89        room.update(cx, |room, cx| {
 90            let operation = if room.is_muted(cx) {
 91                "enable microphone"
 92            } else {
 93                "disable microphone"
 94            };
 95            report_call_event_for_room(operation, room.id(), room.channel_id(), &client, cx);
 96
 97            room.toggle_mute(cx)
 98        })
 99        .map(|task| task.detach_and_log_err(cx))
100        .log_err();
101    }
102}
103
104pub fn toggle_deafen(_: &ToggleDeafen, cx: &mut AppContext) {
105    if let Some(room) = ActiveCall::global(cx).read(cx).room().cloned() {
106        room.update(cx, Room::toggle_deafen)
107            .map(|task| task.detach_and_log_err(cx))
108            .log_err();
109    }
110}
111
112fn notification_window_options(
113    screen: Rc<dyn Screen>,
114    window_size: Vector2F,
115) -> WindowOptions<'static> {
116    const NOTIFICATION_PADDING: f32 = 16.;
117
118    let screen_bounds = screen.content_bounds();
119    WindowOptions {
120        bounds: WindowBounds::Fixed(RectF::new(
121            screen_bounds.upper_right()
122                + vec2f(
123                    -NOTIFICATION_PADDING - window_size.x(),
124                    NOTIFICATION_PADDING,
125                ),
126            window_size,
127        )),
128        titlebar: None,
129        center: false,
130        focus: false,
131        show: true,
132        kind: WindowKind::PopUp,
133        is_movable: false,
134        screen: Some(screen),
135    }
136}
137
138fn render_avatar<T: 'static>(avatar: Option<Arc<ImageData>>, theme: &Arc<Theme>) -> AnyElement<T> {
139    let avatar_style = theme.chat_panel.avatar;
140    avatar
141        .map(|avatar| {
142            Image::from_data(avatar)
143                .with_style(avatar_style.image)
144                .aligned()
145                .contained()
146                .with_corner_radius(avatar_style.outer_corner_radius)
147                .constrained()
148                .with_width(avatar_style.outer_width)
149                .with_height(avatar_style.outer_width)
150                .into_any()
151        })
152        .unwrap_or_else(|| {
153            Empty::new()
154                .constrained()
155                .with_width(avatar_style.outer_width)
156                .into_any()
157        })
158        .contained()
159        .with_style(theme.chat_panel.avatar_container)
160        .into_any()
161}
162
163fn format_timestamp(
164    mut timestamp: OffsetDateTime,
165    mut now: OffsetDateTime,
166    local_timezone: UtcOffset,
167) -> String {
168    timestamp = timestamp.to_offset(local_timezone);
169    now = now.to_offset(local_timezone);
170
171    let today = now.date();
172    let date = timestamp.date();
173    let mut hour = timestamp.hour();
174    let mut part = "am";
175    if hour > 12 {
176        hour -= 12;
177        part = "pm";
178    }
179    if date == today {
180        format!("{:02}:{:02}{}", hour, timestamp.minute(), part)
181    } else if date.next_day() == Some(today) {
182        format!("yesterday at {:02}:{:02}{}", hour, timestamp.minute(), part)
183    } else {
184        format!("{:02}/{}/{}", date.month() as u32, date.day(), date.year())
185    }
186}
187
188fn is_channels_feature_enabled(cx: &gpui::WindowContext<'_>) -> bool {
189    cx.is_staff() || cx.has_flag::<ChannelsAlpha>()
190}