Eliminate GPUI View, ViewContext, and WindowContext types (#22632)
There's still a bit more work to do on this, but this PR is compiling (with warnings) after eliminating the key types. When the tasks below are complete, this will be the new narrative for GPUI: - `Entity<T>` - This replaces `View<T>`/`Model<T>`. It represents a unit of state, and if `T` implements `Render`, then `Entity<T>` implements `Element`. - `&mut App` This replaces `AppContext` and represents the app. - `&mut Context<T>` This replaces `ModelContext` and derefs to `App`. It is provided by the framework when updating an entity. - `&mut Window` Broken out of `&mut WindowContext` which no longer exists. Every method that once took `&mut WindowContext` now takes `&mut Window, &mut App` and every method that took `&mut ViewContext<T>` now takes `&mut Window, &mut Context<T>` Not pictured here are the two other failed attempts. It's been quite a month! Tasks: - [x] Remove `View`, `ViewContext`, `WindowContext` and thread through `Window` - [x] [@cole-miller @mikayla-maki] Redraw window when entities change - [x] [@cole-miller @mikayla-maki] Get examples and Zed running - [x] [@cole-miller @mikayla-maki] Fix Zed rendering - [x] [@mikayla-maki] Fix todo! macros and comments - [x] Fix a bug where the editor would not be redrawn because of view caching - [x] remove publicness window.notify() and replace with `AppContext::notify` - [x] remove `observe_new_window_models`, replace with `observe_new_models` with an optional window - [x] Fix a bug where the project panel would not be redrawn because of the wrong refresh() call being used - [x] Fix the tests - [x] Fix warnings by eliminating `Window` params or using `_` - [x] Fix conflicts - [x] Simplify generic code where possible - [x] Rename types - [ ] Update docs ### issues post merge - [x] Issues switching between normal and insert mode - [x] Assistant re-rendering failure - [x] Vim test failures - [x] Mac build issue Release Notes: - N/A --------- Co-authored-by: Antonio Scandurra <me@as-cii.com> Co-authored-by: Cole Miller <cole@zed.dev> Co-authored-by: Mikayla <mikayla@zed.dev> Co-authored-by: Joseph <joseph@zed.dev> Co-authored-by: max <max@zed.dev> Co-authored-by: Michael Sloan <michael@zed.dev> Co-authored-by: Mikayla Maki <mikaylamaki@Mikaylas-MacBook-Pro.local> Co-authored-by: Mikayla <mikayla.c.maki@gmail.com> Co-authored-by: joão <joao@zed.dev>
This commit is contained in:
parent
21b4a0d50e
commit
6fca1d2b0b
648 changed files with 36248 additions and 28208 deletions
|
@ -11,9 +11,8 @@ use editor::{
|
|||
EditorEvent,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AnyView, AppContext, ClipboardItem, Entity as _, EventEmitter, FocusableView, Model,
|
||||
Pixels, Point, Render, Subscription, Task, View, ViewContext, VisualContext as _, WeakView,
|
||||
WindowContext,
|
||||
actions, AnyView, App, ClipboardItem, Context, Entity, EventEmitter, Focusable, Pixels, Point,
|
||||
Render, Subscription, Task, VisualContext as _, WeakEntity, Window,
|
||||
};
|
||||
use project::Project;
|
||||
use rpc::proto::ChannelVisibility;
|
||||
|
@ -33,16 +32,16 @@ use workspace::{
|
|||
|
||||
actions!(collab, [CopyLink]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
pub fn init(cx: &mut App) {
|
||||
workspace::FollowableViewRegistry::register::<ChannelView>(cx)
|
||||
}
|
||||
|
||||
pub struct ChannelView {
|
||||
pub editor: View<Editor>,
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_buffer: Model<ChannelBuffer>,
|
||||
pub editor: Entity<Editor>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
project: Entity<Project>,
|
||||
channel_store: Entity<ChannelStore>,
|
||||
channel_buffer: Entity<ChannelBuffer>,
|
||||
remote_id: Option<ViewId>,
|
||||
_editor_event_subscription: Subscription,
|
||||
_reparse_subscription: Option<Subscription>,
|
||||
|
@ -52,20 +51,22 @@ impl ChannelView {
|
|||
pub fn open(
|
||||
channel_id: ChannelId,
|
||||
link_position: Option<String>,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
workspace: Entity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let pane = workspace.read(cx).active_pane().clone();
|
||||
let channel_view = Self::open_in_pane(
|
||||
channel_id,
|
||||
link_position,
|
||||
pane.clone(),
|
||||
workspace.clone(),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|mut cx| async move {
|
||||
window.spawn(cx, |mut cx| async move {
|
||||
let channel_view = channel_view.await?;
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
pane.update_in(&mut cx, |pane, window, cx| {
|
||||
telemetry::event!(
|
||||
"Channel Notes Opened",
|
||||
channel_id,
|
||||
|
@ -74,7 +75,7 @@ impl ChannelView {
|
|||
.room()
|
||||
.map(|r| r.read(cx).id())
|
||||
);
|
||||
pane.add_item(Box::new(channel_view.clone()), true, true, None, cx);
|
||||
pane.add_item(Box::new(channel_view.clone()), true, true, None, window, cx);
|
||||
})?;
|
||||
anyhow::Ok(channel_view)
|
||||
})
|
||||
|
@ -83,15 +84,16 @@ impl ChannelView {
|
|||
pub fn open_in_pane(
|
||||
channel_id: ChannelId,
|
||||
link_position: Option<String>,
|
||||
pane: View<Pane>,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
let channel_view = Self::load(channel_id, workspace, cx);
|
||||
cx.spawn(|mut cx| async move {
|
||||
pane: Entity<Pane>,
|
||||
workspace: Entity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let channel_view = Self::load(channel_id, workspace, window, cx);
|
||||
window.spawn(cx, |mut cx| async move {
|
||||
let channel_view = channel_view.await?;
|
||||
|
||||
pane.update(&mut cx, |pane, cx| {
|
||||
pane.update_in(&mut cx, |pane, window, cx| {
|
||||
let buffer_id = channel_view.read(cx).channel_buffer.read(cx).remote_id(cx);
|
||||
|
||||
let existing_view = pane
|
||||
|
@ -104,7 +106,12 @@ impl ChannelView {
|
|||
{
|
||||
if let Some(link_position) = link_position {
|
||||
existing_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
channel_view.focus_position_from_link(
|
||||
link_position,
|
||||
true,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
}
|
||||
return existing_view;
|
||||
|
@ -115,15 +122,27 @@ impl ChannelView {
|
|||
// replace that.
|
||||
if let Some(existing_item) = existing_view {
|
||||
if let Some(ix) = pane.index_for_item(&existing_item) {
|
||||
pane.close_item_by_id(existing_item.entity_id(), SaveIntent::Skip, cx)
|
||||
.detach();
|
||||
pane.add_item(Box::new(channel_view.clone()), true, true, Some(ix), cx);
|
||||
pane.close_item_by_id(
|
||||
existing_item.entity_id(),
|
||||
SaveIntent::Skip,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.detach();
|
||||
pane.add_item(
|
||||
Box::new(channel_view.clone()),
|
||||
true,
|
||||
true,
|
||||
Some(ix),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(link_position) = link_position {
|
||||
channel_view.update(cx, |channel_view, cx| {
|
||||
channel_view.focus_position_from_link(link_position, true, cx)
|
||||
channel_view.focus_position_from_link(link_position, true, window, cx)
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -134,9 +153,10 @@ impl ChannelView {
|
|||
|
||||
pub fn load(
|
||||
channel_id: ChannelId,
|
||||
workspace: View<Workspace>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
workspace: Entity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
let weak_workspace = workspace.downgrade();
|
||||
let workspace = workspace.read(cx);
|
||||
let project = workspace.project().to_owned();
|
||||
|
@ -146,7 +166,7 @@ impl ChannelView {
|
|||
let channel_buffer =
|
||||
channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
window.spawn(cx, |mut cx| async move {
|
||||
let channel_buffer = channel_buffer.await?;
|
||||
let markdown = markdown.await.log_err();
|
||||
|
||||
|
@ -160,9 +180,15 @@ impl ChannelView {
|
|||
})
|
||||
})?;
|
||||
|
||||
cx.new_view(|cx| {
|
||||
let mut this =
|
||||
Self::new(project, weak_workspace, channel_store, channel_buffer, cx);
|
||||
cx.new_window_model(|window, cx| {
|
||||
let mut this = Self::new(
|
||||
project,
|
||||
weak_workspace,
|
||||
channel_store,
|
||||
channel_buffer,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
this.acknowledge_buffer_version(cx);
|
||||
this
|
||||
})
|
||||
|
@ -170,25 +196,28 @@ impl ChannelView {
|
|||
}
|
||||
|
||||
pub fn new(
|
||||
project: Model<Project>,
|
||||
workspace: WeakView<Workspace>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_buffer: Model<ChannelBuffer>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
project: Entity<Project>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
channel_store: Entity<ChannelStore>,
|
||||
channel_buffer: Entity<ChannelBuffer>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let buffer = channel_buffer.read(cx).buffer();
|
||||
let this = cx.view().downgrade();
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, None, cx);
|
||||
let this = cx.model().downgrade();
|
||||
let editor = cx.new(|cx| {
|
||||
let mut editor = Editor::for_buffer(buffer, None, window, cx);
|
||||
editor.set_collaboration_hub(Box::new(ChannelBufferCollaborationHub(
|
||||
channel_buffer.clone(),
|
||||
)));
|
||||
editor.set_custom_context_menu(move |_, position, cx| {
|
||||
editor.set_custom_context_menu(move |_, position, window, cx| {
|
||||
let this = this.clone();
|
||||
Some(ui::ContextMenu::build(cx, move |menu, _| {
|
||||
menu.entry("Copy link to section", None, move |cx| {
|
||||
this.update(cx, |this, cx| this.copy_link_for_position(position, cx))
|
||||
.ok();
|
||||
Some(ui::ContextMenu::build(window, cx, move |menu, _, _| {
|
||||
menu.entry("Copy link to section", None, move |window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.copy_link_for_position(position, window, cx)
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
}))
|
||||
});
|
||||
|
@ -197,7 +226,7 @@ impl ChannelView {
|
|||
let _editor_event_subscription =
|
||||
cx.subscribe(&editor, |_, _, e: &EditorEvent, cx| cx.emit(e.clone()));
|
||||
|
||||
cx.subscribe(&channel_buffer, Self::handle_channel_buffer_event)
|
||||
cx.subscribe_in(&channel_buffer, window, Self::handle_channel_buffer_event)
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
|
@ -216,10 +245,13 @@ impl ChannelView {
|
|||
&mut self,
|
||||
position: String,
|
||||
first_attempt: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let position = Channel::slug(&position).to_lowercase();
|
||||
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
let snapshot = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||
|
||||
if let Some(outline) = snapshot.buffer_snapshot.outline(None) {
|
||||
if let Some(item) = outline
|
||||
|
@ -228,7 +260,7 @@ impl ChannelView {
|
|||
.find(|item| &Channel::slug(&item.text).to_lowercase() == &position)
|
||||
{
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), cx, |s| {
|
||||
editor.change_selections(Some(Autoscroll::focused()), window, cx, |s| {
|
||||
s.replace_cursors_with(|map| vec![item.range.start.to_display_point(map)])
|
||||
})
|
||||
});
|
||||
|
@ -239,12 +271,13 @@ impl ChannelView {
|
|||
if !first_attempt {
|
||||
return;
|
||||
}
|
||||
self._reparse_subscription = Some(cx.subscribe(
|
||||
self._reparse_subscription = Some(cx.subscribe_in(
|
||||
&self.editor,
|
||||
move |this, _, e: &EditorEvent, cx| {
|
||||
window,
|
||||
move |this, _, e: &EditorEvent, window, cx| {
|
||||
match e {
|
||||
EditorEvent::Reparsed(_) => {
|
||||
this.focus_position_from_link(position.clone(), false, cx);
|
||||
this.focus_position_from_link(position.clone(), false, window, cx);
|
||||
this._reparse_subscription.take();
|
||||
}
|
||||
EditorEvent::Edited { .. } | EditorEvent::SelectionsChanged { local: true } => {
|
||||
|
@ -256,15 +289,22 @@ impl ChannelView {
|
|||
));
|
||||
}
|
||||
|
||||
fn copy_link(&mut self, _: &CopyLink, cx: &mut ViewContext<Self>) {
|
||||
fn copy_link(&mut self, _: &CopyLink, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let position = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.selections.newest_display(cx).start);
|
||||
self.copy_link_for_position(position, cx)
|
||||
self.copy_link_for_position(position, window, cx)
|
||||
}
|
||||
|
||||
fn copy_link_for_position(&self, position: DisplayPoint, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
|
||||
fn copy_link_for_position(
|
||||
&self,
|
||||
position: DisplayPoint,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let snapshot = self
|
||||
.editor
|
||||
.update(cx, |editor, cx| editor.snapshot(window, cx));
|
||||
|
||||
let mut closest_heading = None;
|
||||
|
||||
|
@ -298,15 +338,16 @@ impl ChannelView {
|
|||
.ok();
|
||||
}
|
||||
|
||||
pub fn channel(&self, cx: &AppContext) -> Option<Arc<Channel>> {
|
||||
pub fn channel(&self, cx: &App) -> Option<Arc<Channel>> {
|
||||
self.channel_buffer.read(cx).channel(cx)
|
||||
}
|
||||
|
||||
fn handle_channel_buffer_event(
|
||||
&mut self,
|
||||
_: Model<ChannelBuffer>,
|
||||
_: &Entity<ChannelBuffer>,
|
||||
event: &ChannelBufferEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
ChannelBufferEvent::Disconnected => self.editor.update(cx, |editor, cx| {
|
||||
|
@ -320,7 +361,7 @@ impl ChannelView {
|
|||
});
|
||||
}
|
||||
ChannelBufferEvent::BufferEdited => {
|
||||
if self.editor.read(cx).is_focused(cx) {
|
||||
if self.editor.read(cx).is_focused(window) {
|
||||
self.acknowledge_buffer_version(cx);
|
||||
} else {
|
||||
self.channel_store.update(cx, |store, cx| {
|
||||
|
@ -338,7 +379,7 @@ impl ChannelView {
|
|||
}
|
||||
}
|
||||
|
||||
fn acknowledge_buffer_version(&mut self, cx: &mut ViewContext<ChannelView>) {
|
||||
fn acknowledge_buffer_version(&mut self, cx: &mut Context<ChannelView>) {
|
||||
self.channel_store.update(cx, |store, cx| {
|
||||
let channel_buffer = self.channel_buffer.read(cx);
|
||||
store.acknowledge_notes_version(
|
||||
|
@ -357,7 +398,7 @@ impl ChannelView {
|
|||
impl EventEmitter<EditorEvent> for ChannelView {}
|
||||
|
||||
impl Render for ChannelView {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.size_full()
|
||||
.on_action(cx.listener(Self::copy_link))
|
||||
|
@ -365,8 +406,8 @@ impl Render for ChannelView {
|
|||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ChannelView {
|
||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||
impl Focusable for ChannelView {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
self.editor.read(cx).focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
@ -377,8 +418,8 @@ impl Item for ChannelView {
|
|||
fn act_as_type<'a>(
|
||||
&'a self,
|
||||
type_id: TypeId,
|
||||
self_handle: &'a View<Self>,
|
||||
_: &'a AppContext,
|
||||
self_handle: &'a Entity<Self>,
|
||||
_: &'a App,
|
||||
) -> Option<AnyView> {
|
||||
if type_id == TypeId::of::<Self>() {
|
||||
Some(self_handle.to_any())
|
||||
|
@ -389,7 +430,7 @@ impl Item for ChannelView {
|
|||
}
|
||||
}
|
||||
|
||||
fn tab_icon(&self, cx: &WindowContext) -> Option<Icon> {
|
||||
fn tab_icon(&self, _: &Window, cx: &App) -> Option<Icon> {
|
||||
let channel = self.channel(cx)?;
|
||||
let icon = match channel.visibility {
|
||||
ChannelVisibility::Public => IconName::Public,
|
||||
|
@ -399,7 +440,7 @@ impl Item for ChannelView {
|
|||
Some(Icon::new(icon))
|
||||
}
|
||||
|
||||
fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> gpui::AnyElement {
|
||||
fn tab_content(&self, params: TabContentParams, _: &Window, cx: &App) -> gpui::AnyElement {
|
||||
let (channel_name, status) = if let Some(channel) = self.channel(cx) {
|
||||
let status = match (
|
||||
self.channel_buffer.read(cx).buffer().read(cx).read_only(),
|
||||
|
@ -439,38 +480,52 @@ impl Item for ChannelView {
|
|||
fn clone_on_split(
|
||||
&self,
|
||||
_: Option<WorkspaceId>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Option<View<Self>> {
|
||||
Some(cx.new_view(|cx| {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Entity<Self>> {
|
||||
Some(cx.new(|cx| {
|
||||
Self::new(
|
||||
self.project.clone(),
|
||||
self.workspace.clone(),
|
||||
self.channel_store.clone(),
|
||||
self.channel_buffer.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn is_singleton(&self, _cx: &AppContext) -> bool {
|
||||
fn is_singleton(&self, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn navigate(&mut self, data: Box<dyn Any>, cx: &mut ViewContext<Self>) -> bool {
|
||||
fn navigate(
|
||||
&mut self,
|
||||
data: Box<dyn Any>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| editor.navigate(data, cx))
|
||||
.update(cx, |editor, cx| editor.navigate(data, window, cx))
|
||||
}
|
||||
|
||||
fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, Item::deactivated)
|
||||
}
|
||||
|
||||
fn set_nav_history(&mut self, history: ItemNavHistory, cx: &mut ViewContext<Self>) {
|
||||
fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.editor
|
||||
.update(cx, |editor, cx| Item::set_nav_history(editor, history, cx))
|
||||
.update(cx, |item, cx| item.deactivated(window, cx))
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
fn set_nav_history(
|
||||
&mut self,
|
||||
history: ItemNavHistory,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
Item::set_nav_history(editor, history, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn as_searchable(&self, _: &Entity<Self>) -> Option<Box<dyn SearchableItemHandle>> {
|
||||
Some(Box::new(self.editor.clone()))
|
||||
}
|
||||
|
||||
|
@ -478,7 +533,7 @@ impl Item for ChannelView {
|
|||
true
|
||||
}
|
||||
|
||||
fn pixel_position_of_cursor(&self, cx: &AppContext) -> Option<Point<Pixels>> {
|
||||
fn pixel_position_of_cursor(&self, cx: &App) -> Option<Point<Pixels>> {
|
||||
self.editor.read(cx).pixel_position_of_cursor(cx)
|
||||
}
|
||||
|
||||
|
@ -492,7 +547,7 @@ impl FollowableItem for ChannelView {
|
|||
self.remote_id
|
||||
}
|
||||
|
||||
fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
|
||||
fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
|
||||
let channel_buffer = self.channel_buffer.read(cx);
|
||||
if !channel_buffer.is_connected() {
|
||||
return None;
|
||||
|
@ -502,7 +557,7 @@ impl FollowableItem for ChannelView {
|
|||
proto::view::ChannelView {
|
||||
channel_id: channel_buffer.channel_id.0,
|
||||
editor: if let Some(proto::view::Variant::Editor(proto)) =
|
||||
self.editor.read(cx).to_state_proto(cx)
|
||||
self.editor.read(cx).to_state_proto(window, cx)
|
||||
{
|
||||
Some(proto)
|
||||
} else {
|
||||
|
@ -513,11 +568,12 @@ impl FollowableItem for ChannelView {
|
|||
}
|
||||
|
||||
fn from_state_proto(
|
||||
workspace: View<workspace::Workspace>,
|
||||
workspace: Entity<workspace::Workspace>,
|
||||
remote_id: workspace::ViewId,
|
||||
state: &mut Option<proto::view::Variant>,
|
||||
cx: &mut WindowContext,
|
||||
) -> Option<gpui::Task<anyhow::Result<View<Self>>>> {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<gpui::Task<anyhow::Result<Entity<Self>>>> {
|
||||
let Some(proto::view::Variant::ChannelView(_)) = state else {
|
||||
return None;
|
||||
};
|
||||
|
@ -525,12 +581,12 @@ impl FollowableItem for ChannelView {
|
|||
unreachable!()
|
||||
};
|
||||
|
||||
let open = ChannelView::load(ChannelId(state.channel_id), workspace, cx);
|
||||
let open = ChannelView::load(ChannelId(state.channel_id), workspace, window, cx);
|
||||
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
Some(window.spawn(cx, |mut cx| async move {
|
||||
let this = open.await?;
|
||||
|
||||
let task = this.update(&mut cx, |this, cx| {
|
||||
let task = this.update_in(&mut cx, |this, window, cx| {
|
||||
this.remote_id = Some(remote_id);
|
||||
|
||||
if let Some(state) = state.editor {
|
||||
|
@ -545,6 +601,7 @@ impl FollowableItem for ChannelView {
|
|||
scroll_y: state.scroll_y,
|
||||
..Default::default()
|
||||
}),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
|
@ -565,31 +622,38 @@ impl FollowableItem for ChannelView {
|
|||
&self,
|
||||
event: &EditorEvent,
|
||||
update: &mut Option<proto::update_view::Variant>,
|
||||
cx: &WindowContext,
|
||||
window: &Window,
|
||||
cx: &App,
|
||||
) -> bool {
|
||||
self.editor
|
||||
.read(cx)
|
||||
.add_event_to_update_proto(event, update, cx)
|
||||
.add_event_to_update_proto(event, update, window, cx)
|
||||
}
|
||||
|
||||
fn apply_update_proto(
|
||||
&mut self,
|
||||
project: &Model<Project>,
|
||||
project: &Entity<Project>,
|
||||
message: proto::update_view::Variant,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> gpui::Task<anyhow::Result<()>> {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.apply_update_proto(project, message, cx)
|
||||
editor.apply_update_proto(project, message, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn set_leader_peer_id(&mut self, leader_peer_id: Option<PeerId>, cx: &mut ViewContext<Self>) {
|
||||
fn set_leader_peer_id(
|
||||
&mut self,
|
||||
leader_peer_id: Option<PeerId>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.set_leader_peer_id(leader_peer_id, cx)
|
||||
editor.set_leader_peer_id(leader_peer_id, window, cx)
|
||||
})
|
||||
}
|
||||
|
||||
fn is_project_item(&self, _cx: &WindowContext) -> bool {
|
||||
fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
|
@ -597,7 +661,7 @@ impl FollowableItem for ChannelView {
|
|||
Editor::to_follow_event(event)
|
||||
}
|
||||
|
||||
fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<Dedup> {
|
||||
fn dedup(&self, existing: &Self, _: &Window, cx: &App) -> Option<Dedup> {
|
||||
let existing = existing.channel_buffer.read(cx);
|
||||
if self.channel_buffer.read(cx).channel_id == existing.channel_id {
|
||||
if existing.is_connected() {
|
||||
|
@ -611,21 +675,18 @@ impl FollowableItem for ChannelView {
|
|||
}
|
||||
}
|
||||
|
||||
struct ChannelBufferCollaborationHub(Model<ChannelBuffer>);
|
||||
struct ChannelBufferCollaborationHub(Entity<ChannelBuffer>);
|
||||
|
||||
impl CollaborationHub for ChannelBufferCollaborationHub {
|
||||
fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap<PeerId, Collaborator> {
|
||||
fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
|
||||
self.0.read(cx).collaborators()
|
||||
}
|
||||
|
||||
fn user_participant_indices<'a>(
|
||||
&self,
|
||||
cx: &'a AppContext,
|
||||
) -> &'a HashMap<u64, ParticipantIndex> {
|
||||
fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
|
||||
self.0.read(cx).user_store().read(cx).participant_indices()
|
||||
}
|
||||
|
||||
fn user_names(&self, cx: &AppContext) -> HashMap<u64, SharedString> {
|
||||
fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
|
||||
let user_ids = self.collaborators(cx).values().map(|c| c.user_id);
|
||||
self.0
|
||||
.read(cx)
|
||||
|
|
|
@ -7,10 +7,10 @@ use collections::HashMap;
|
|||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{actions, Editor};
|
||||
use gpui::{
|
||||
actions, div, list, prelude::*, px, Action, AppContext, AsyncWindowContext, ClipboardItem,
|
||||
CursorStyle, DismissEvent, ElementId, EventEmitter, FocusHandle, FocusableView, FontWeight,
|
||||
HighlightStyle, ListOffset, ListScrollEvent, ListState, Model, Render, Stateful, Subscription,
|
||||
Task, View, ViewContext, VisualContext, WeakView,
|
||||
actions, div, list, prelude::*, px, Action, App, AsyncWindowContext, ClipboardItem, Context,
|
||||
CursorStyle, DismissEvent, ElementId, Entity, EventEmitter, FocusHandle, Focusable, FontWeight,
|
||||
HighlightStyle, ListOffset, ListScrollEvent, ListState, Render, Stateful, Subscription, Task,
|
||||
WeakEntity, Window,
|
||||
};
|
||||
use language::LanguageRegistry;
|
||||
use menu::Confirm;
|
||||
|
@ -36,10 +36,10 @@ mod message_editor;
|
|||
const MESSAGE_LOADING_THRESHOLD: usize = 50;
|
||||
const CHAT_PANEL_KEY: &str = "ChatPanel";
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<ChatPanel>(cx);
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
|
||||
workspace.toggle_panel_focus::<ChatPanel>(window, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
@ -47,11 +47,11 @@ pub fn init(cx: &mut AppContext) {
|
|||
|
||||
pub struct ChatPanel {
|
||||
client: Arc<Client>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_store: Entity<ChannelStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
message_list: ListState,
|
||||
active_chat: Option<(Model<ChannelChat>, Subscription)>,
|
||||
message_editor: View<MessageEditor>,
|
||||
active_chat: Option<(Entity<ChannelChat>, Subscription)>,
|
||||
message_editor: Entity<MessageEditor>,
|
||||
local_timezone: UtcOffset,
|
||||
fs: Arc<dyn Fs>,
|
||||
width: Option<Pixels>,
|
||||
|
@ -74,37 +74,46 @@ struct SerializedChatPanel {
|
|||
actions!(chat_panel, [ToggleFocus]);
|
||||
|
||||
impl ChatPanel {
|
||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
pub fn new(
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<Self> {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let client = workspace.app_state().client.clone();
|
||||
let channel_store = ChannelStore::global(cx);
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let languages = workspace.app_state().languages.clone();
|
||||
|
||||
let input_editor = cx.new_view(|cx| {
|
||||
let input_editor = cx.new(|cx| {
|
||||
MessageEditor::new(
|
||||
languages.clone(),
|
||||
user_store.clone(),
|
||||
None,
|
||||
cx.new_view(|cx| Editor::auto_height(4, cx)),
|
||||
cx.new(|cx| Editor::auto_height(4, window, cx)),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
let view = cx.view().downgrade();
|
||||
let message_list =
|
||||
ListState::new(0, gpui::ListAlignment::Bottom, px(1000.), move |ix, cx| {
|
||||
if let Some(view) = view.upgrade() {
|
||||
view.update(cx, |view, cx| {
|
||||
view.render_message(ix, cx).into_any_element()
|
||||
cx.new(|cx| {
|
||||
let model = cx.model().downgrade();
|
||||
let message_list = ListState::new(
|
||||
0,
|
||||
gpui::ListAlignment::Bottom,
|
||||
px(1000.),
|
||||
move |ix, window, cx| {
|
||||
if let Some(model) = model.upgrade() {
|
||||
model.update(cx, |this: &mut Self, cx| {
|
||||
this.render_message(ix, window, cx).into_any_element()
|
||||
})
|
||||
} else {
|
||||
div().into_any()
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, cx| {
|
||||
message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, _, cx| {
|
||||
if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
|
||||
this.load_more_messages(cx);
|
||||
}
|
||||
|
@ -172,7 +181,7 @@ impl ChatPanel {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn channel_id(&self, cx: &AppContext) -> Option<ChannelId> {
|
||||
pub fn channel_id(&self, cx: &App) -> Option<ChannelId> {
|
||||
self.active_chat
|
||||
.as_ref()
|
||||
.map(|(chat, _)| chat.read(cx).channel_id)
|
||||
|
@ -182,14 +191,14 @@ impl ChatPanel {
|
|||
self.is_scrolled_to_bottom
|
||||
}
|
||||
|
||||
pub fn active_chat(&self) -> Option<Model<ChannelChat>> {
|
||||
pub fn active_chat(&self) -> Option<Entity<ChannelChat>> {
|
||||
self.active_chat.as_ref().map(|(chat, _)| chat.clone())
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let serialized_panel = if let Some(panel) = cx
|
||||
.background_executor()
|
||||
|
@ -203,8 +212,8 @@ impl ChatPanel {
|
|||
None
|
||||
};
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let panel = Self::new(workspace, cx);
|
||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
let panel = Self::new(workspace, window, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width.map(|r| r.round());
|
||||
|
@ -216,7 +225,7 @@ impl ChatPanel {
|
|||
})
|
||||
}
|
||||
|
||||
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn serialize(&mut self, cx: &mut Context<Self>) {
|
||||
let width = self.width;
|
||||
self.pending_serialization = cx.background_executor().spawn(
|
||||
async move {
|
||||
|
@ -232,7 +241,7 @@ impl ChatPanel {
|
|||
);
|
||||
}
|
||||
|
||||
fn set_active_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
|
||||
fn set_active_chat(&mut self, chat: Entity<ChannelChat>, cx: &mut Context<Self>) {
|
||||
if self.active_chat.as_ref().map(|e| &e.0) != Some(&chat) {
|
||||
self.markdown_data.clear();
|
||||
self.message_list.reset(chat.read(cx).message_count());
|
||||
|
@ -249,9 +258,9 @@ impl ChatPanel {
|
|||
|
||||
fn channel_did_change(
|
||||
&mut self,
|
||||
_: Model<ChannelChat>,
|
||||
_: Entity<ChannelChat>,
|
||||
event: &ChannelChatEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
ChannelChatEvent::MessagesUpdated {
|
||||
|
@ -284,7 +293,7 @@ impl ChatPanel {
|
|||
cx.notify();
|
||||
}
|
||||
|
||||
fn acknowledge_last_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn acknowledge_last_message(&mut self, cx: &mut Context<Self>) {
|
||||
if self.active && self.is_scrolled_to_bottom {
|
||||
if let Some((chat, _)) = &self.active_chat {
|
||||
if let Some(channel_id) = self.channel_id(cx) {
|
||||
|
@ -305,7 +314,7 @@ impl ChatPanel {
|
|||
&mut self,
|
||||
message_id: Option<ChannelMessageId>,
|
||||
reply_to_message: &Option<ChannelMessage>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let reply_to_message = match reply_to_message {
|
||||
None => {
|
||||
|
@ -369,8 +378,8 @@ impl ChatPanel {
|
|||
),
|
||||
)
|
||||
.cursor(CursorStyle::PointingHand)
|
||||
.tooltip(|cx| Tooltip::text("Go to message", cx))
|
||||
.on_click(cx.listener(move |chat_panel, _, cx| {
|
||||
.tooltip(Tooltip::text("Go to message"))
|
||||
.on_click(cx.listener(move |chat_panel, _, _, cx| {
|
||||
if let Some(channel_id) = current_channel_id {
|
||||
chat_panel
|
||||
.select_channel(channel_id, reply_to_message_id.into(), cx)
|
||||
|
@ -380,7 +389,12 @@ impl ChatPanel {
|
|||
)
|
||||
}
|
||||
|
||||
fn render_message(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render_message(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let active_chat = &self.active_chat.as_ref().unwrap().0;
|
||||
let (message, is_continuation_from_previous, is_admin) =
|
||||
active_chat.update(cx, |active_chat, cx| {
|
||||
|
@ -530,7 +544,7 @@ impl ChatPanel {
|
|||
.w_full()
|
||||
.text_ui_sm(cx)
|
||||
.id(element_id)
|
||||
.child(text.element("body".into(), cx)),
|
||||
.child(text.element("body".into(), window, cx)),
|
||||
)
|
||||
.when(self.has_open_menu(message_id), |el| {
|
||||
el.bg(cx.theme().colors().element_selected)
|
||||
|
@ -560,7 +574,7 @@ impl ChatPanel {
|
|||
},
|
||||
)
|
||||
.child(
|
||||
self.render_popover_buttons(cx, message_id, can_delete_message, can_edit_message)
|
||||
self.render_popover_buttons(message_id, can_delete_message, can_edit_message, cx)
|
||||
.mt_neg_2p5(),
|
||||
)
|
||||
}
|
||||
|
@ -572,7 +586,7 @@ impl ChatPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_popover_button(&self, cx: &ViewContext<Self>, child: Stateful<Div>) -> Div {
|
||||
fn render_popover_button(&self, cx: &mut Context<Self>, child: Stateful<Div>) -> Div {
|
||||
div()
|
||||
.w_6()
|
||||
.bg(cx.theme().colors().element_background)
|
||||
|
@ -582,10 +596,10 @@ impl ChatPanel {
|
|||
|
||||
fn render_popover_buttons(
|
||||
&self,
|
||||
cx: &ViewContext<Self>,
|
||||
message_id: Option<u64>,
|
||||
can_delete_message: bool,
|
||||
can_edit_message: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Div {
|
||||
h_flex()
|
||||
.absolute()
|
||||
|
@ -606,16 +620,16 @@ impl ChatPanel {
|
|||
.id("reply")
|
||||
.child(
|
||||
IconButton::new(("reply", message_id), IconName::ReplyArrowRight)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.cancel_edit_message(cx);
|
||||
|
||||
this.message_editor.update(cx, |editor, cx| {
|
||||
editor.set_reply_to_message_id(message_id);
|
||||
editor.focus_handle(cx).focus(cx);
|
||||
window.focus(&editor.focus_handle(cx));
|
||||
})
|
||||
})),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::text("Reply", cx)),
|
||||
.tooltip(Tooltip::text("Reply")),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
@ -628,7 +642,7 @@ impl ChatPanel {
|
|||
.id("edit")
|
||||
.child(
|
||||
IconButton::new(("edit", message_id), IconName::Pencil)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
this.message_editor.update(cx, |editor, cx| {
|
||||
editor.clear_reply_to_message_id();
|
||||
|
||||
|
@ -655,18 +669,18 @@ impl ChatPanel {
|
|||
});
|
||||
|
||||
editor.set_edit_message_id(message_id);
|
||||
editor.focus_handle(cx).focus(cx);
|
||||
editor.focus_handle(cx).focus(window);
|
||||
}
|
||||
})
|
||||
})),
|
||||
)
|
||||
.tooltip(|cx| Tooltip::text("Edit", cx)),
|
||||
.tooltip(Tooltip::text("Edit")),
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
.when_some(message_id, |el, message_id| {
|
||||
let this = cx.view().clone();
|
||||
let this = cx.model().clone();
|
||||
|
||||
el.child(
|
||||
self.render_popover_button(
|
||||
|
@ -678,34 +692,36 @@ impl ChatPanel {
|
|||
("trigger", message_id),
|
||||
IconName::Ellipsis,
|
||||
))
|
||||
.menu(move |cx| {
|
||||
.menu(move |window, cx| {
|
||||
Some(Self::render_message_menu(
|
||||
&this,
|
||||
message_id,
|
||||
can_delete_message,
|
||||
window,
|
||||
cx,
|
||||
))
|
||||
}),
|
||||
)
|
||||
.id("more")
|
||||
.tooltip(|cx| Tooltip::text("More", cx)),
|
||||
.tooltip(Tooltip::text("More")),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_message_menu(
|
||||
this: &View<Self>,
|
||||
this: &Entity<Self>,
|
||||
message_id: u64,
|
||||
can_delete_message: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> View<ContextMenu> {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Entity<ContextMenu> {
|
||||
let menu = {
|
||||
ContextMenu::build(cx, move |menu, cx| {
|
||||
ContextMenu::build(window, cx, move |menu, window, _| {
|
||||
menu.entry(
|
||||
"Copy message text",
|
||||
None,
|
||||
cx.handler_for(this, move |this, cx| {
|
||||
window.handler_for(this, move |this, _, cx| {
|
||||
if let Some(message) = this.active_chat().and_then(|active_chat| {
|
||||
active_chat.read(cx).find_loaded_message(message_id)
|
||||
}) {
|
||||
|
@ -718,15 +734,21 @@ impl ChatPanel {
|
|||
menu.entry(
|
||||
"Delete message",
|
||||
None,
|
||||
cx.handler_for(this, move |this, cx| this.remove_message(message_id, cx)),
|
||||
window.handler_for(this, move |this, _, cx| {
|
||||
this.remove_message(message_id, cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
};
|
||||
this.update(cx, |this, cx| {
|
||||
let subscription = cx.subscribe(&menu, |this: &mut Self, _, _: &DismissEvent, _| {
|
||||
this.open_context_menu = None;
|
||||
});
|
||||
let subscription = cx.subscribe_in(
|
||||
&menu,
|
||||
window,
|
||||
|this: &mut Self, _, _: &DismissEvent, _, _| {
|
||||
this.open_context_menu = None;
|
||||
},
|
||||
);
|
||||
this.open_context_menu = Some((message_id, subscription));
|
||||
});
|
||||
menu
|
||||
|
@ -737,7 +759,7 @@ impl ChatPanel {
|
|||
current_user_id: u64,
|
||||
message: &channel::ChannelMessage,
|
||||
local_timezone: UtcOffset,
|
||||
cx: &AppContext,
|
||||
cx: &App,
|
||||
) -> RichText {
|
||||
let mentions = message
|
||||
.mentions
|
||||
|
@ -777,19 +799,19 @@ impl ChatPanel {
|
|||
);
|
||||
|
||||
rich_text.custom_ranges.push(range);
|
||||
rich_text.set_tooltip_builder_for_custom_ranges(move |_, _, cx| {
|
||||
Some(Tooltip::text(edit_timestamp_text.clone(), cx))
|
||||
rich_text.set_tooltip_builder_for_custom_ranges(move |_, _, _, cx| {
|
||||
Some(Tooltip::simple(edit_timestamp_text.clone(), cx))
|
||||
})
|
||||
}
|
||||
}
|
||||
rich_text
|
||||
}
|
||||
|
||||
fn send(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
|
||||
fn send(&mut self, _: &Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some((chat, _)) = self.active_chat.as_ref() {
|
||||
let message = self
|
||||
.message_editor
|
||||
.update(cx, |editor, cx| editor.take_message(cx));
|
||||
.update(cx, |editor, cx| editor.take_message(window, cx));
|
||||
|
||||
if let Some(id) = self.message_editor.read(cx).edit_message_id() {
|
||||
self.message_editor.update(cx, |editor, _| {
|
||||
|
@ -811,13 +833,13 @@ impl ChatPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn remove_message(&mut self, id: u64, cx: &mut ViewContext<Self>) {
|
||||
fn remove_message(&mut self, id: u64, cx: &mut Context<Self>) {
|
||||
if let Some((chat, _)) = self.active_chat.as_ref() {
|
||||
chat.update(cx, |chat, cx| chat.remove_message(id, cx).detach())
|
||||
}
|
||||
}
|
||||
|
||||
fn load_more_messages(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn load_more_messages(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some((chat, _)) = self.active_chat.as_ref() {
|
||||
chat.update(cx, |channel, cx| {
|
||||
if let Some(task) = channel.load_more_messages(cx) {
|
||||
|
@ -831,7 +853,7 @@ impl ChatPanel {
|
|||
&mut self,
|
||||
selected_channel_id: ChannelId,
|
||||
scroll_to_message_id: Option<u64>,
|
||||
cx: &mut ViewContext<ChatPanel>,
|
||||
cx: &mut Context<ChatPanel>,
|
||||
) -> Task<Result<()>> {
|
||||
let open_chat = self
|
||||
.active_chat
|
||||
|
@ -857,20 +879,18 @@ impl ChatPanel {
|
|||
|
||||
if let Some(message_id) = scroll_to_message_id {
|
||||
if let Some(item_ix) =
|
||||
ChannelChat::load_history_since_message(chat.clone(), message_id, (*cx).clone())
|
||||
ChannelChat::load_history_since_message(chat.clone(), message_id, cx.clone())
|
||||
.await
|
||||
{
|
||||
this.update(&mut cx, |this, cx| {
|
||||
if let Some(highlight_message_id) = highlight_message_id {
|
||||
let task = cx.spawn({
|
||||
|this, mut cx| async move {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.highlighted_message.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
let task = cx.spawn(|this, mut cx| async move {
|
||||
cx.background_executor().timer(Duration::from_secs(2)).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.highlighted_message.take();
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
});
|
||||
|
||||
this.highlighted_message = Some((highlight_message_id, task));
|
||||
|
@ -891,12 +911,12 @@ impl ChatPanel {
|
|||
})
|
||||
}
|
||||
|
||||
fn close_reply_preview(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn close_reply_preview(&mut self, cx: &mut Context<Self>) {
|
||||
self.message_editor
|
||||
.update(cx, |editor, _| editor.clear_reply_to_message_id());
|
||||
}
|
||||
|
||||
fn cancel_edit_message(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn cancel_edit_message(&mut self, cx: &mut Context<Self>) {
|
||||
self.message_editor.update(cx, |editor, cx| {
|
||||
// only clear the editor input if we were editing a message
|
||||
if editor.edit_message_id().is_none() {
|
||||
|
@ -919,7 +939,7 @@ impl ChatPanel {
|
|||
}
|
||||
|
||||
impl Render for ChatPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let channel_id = self
|
||||
.active_chat
|
||||
.as_ref()
|
||||
|
@ -971,11 +991,12 @@ impl Render for ChatPanel {
|
|||
.full_width()
|
||||
.key_binding(KeyBinding::for_action(
|
||||
&collab_panel::ToggleFocus,
|
||||
cx,
|
||||
window,
|
||||
))
|
||||
.on_click(|_, cx| {
|
||||
cx.dispatch_action(
|
||||
.on_click(|_, window, cx| {
|
||||
window.dispatch_action(
|
||||
collab_panel::ToggleFocus.boxed_clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
),
|
||||
|
@ -999,8 +1020,8 @@ impl Render for ChatPanel {
|
|||
.child(
|
||||
IconButton::new("cancel-edit-message", IconName::Close)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Cancel edit message", cx))
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
.tooltip(Tooltip::text("Cancel edit message"))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.cancel_edit_message(cx);
|
||||
})),
|
||||
),
|
||||
|
@ -1045,7 +1066,7 @@ impl Render for ChatPanel {
|
|||
)
|
||||
.when_some(channel_id, |this, channel_id| {
|
||||
this.cursor_pointer().on_click(cx.listener(
|
||||
move |chat_panel, _, cx| {
|
||||
move |chat_panel, _, _, cx| {
|
||||
chat_panel
|
||||
.select_channel(
|
||||
channel_id,
|
||||
|
@ -1061,8 +1082,8 @@ impl Render for ChatPanel {
|
|||
.child(
|
||||
IconButton::new("close-reply-preview", IconName::Close)
|
||||
.shape(ui::IconButtonShape::Square)
|
||||
.tooltip(|cx| Tooltip::text("Close reply", cx))
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
.tooltip(Tooltip::text("Close reply"))
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
this.close_reply_preview(cx);
|
||||
})),
|
||||
),
|
||||
|
@ -1073,7 +1094,7 @@ impl Render for ChatPanel {
|
|||
Some(
|
||||
h_flex()
|
||||
.p_2()
|
||||
.on_action(cx.listener(|this, _: &actions::Cancel, cx| {
|
||||
.on_action(cx.listener(|this, _: &actions::Cancel, _, cx| {
|
||||
this.cancel_edit_message(cx);
|
||||
this.close_reply_preview(cx);
|
||||
}))
|
||||
|
@ -1085,8 +1106,8 @@ impl Render for ChatPanel {
|
|||
}
|
||||
}
|
||||
|
||||
impl FocusableView for ChatPanel {
|
||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||
impl Focusable for ChatPanel {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
if self.active_chat.is_some() {
|
||||
self.message_editor.read(cx).focus_handle(cx)
|
||||
} else {
|
||||
|
@ -1096,7 +1117,7 @@ impl FocusableView for ChatPanel {
|
|||
}
|
||||
|
||||
impl Panel for ChatPanel {
|
||||
fn position(&self, cx: &WindowContext) -> DockPosition {
|
||||
fn position(&self, _: &Window, cx: &App) -> DockPosition {
|
||||
ChatPanelSettings::get_global(cx).dock
|
||||
}
|
||||
|
||||
|
@ -1104,7 +1125,7 @@ impl Panel for ChatPanel {
|
|||
matches!(position, DockPosition::Left | DockPosition::Right)
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
|
||||
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
|
||||
settings::update_settings_file::<ChatPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
|
@ -1112,18 +1133,18 @@ impl Panel for ChatPanel {
|
|||
);
|
||||
}
|
||||
|
||||
fn size(&self, cx: &WindowContext) -> Pixels {
|
||||
fn size(&self, _: &Window, cx: &App) -> Pixels {
|
||||
self.width
|
||||
.unwrap_or_else(|| ChatPanelSettings::get_global(cx).default_width)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
fn set_size(&mut self, size: Option<Pixels>, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.width = size;
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||
fn set_active(&mut self, active: bool, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active = active;
|
||||
if active {
|
||||
self.acknowledge_last_message(cx);
|
||||
|
@ -1134,7 +1155,7 @@ impl Panel for ChatPanel {
|
|||
"ChatPanel"
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<ui::IconName> {
|
||||
fn icon(&self, _window: &Window, cx: &App) -> Option<ui::IconName> {
|
||||
let show_icon = match ChatPanelSettings::get_global(cx).button {
|
||||
ChatPanelButton::Never => false,
|
||||
ChatPanelButton::Always => true,
|
||||
|
@ -1151,7 +1172,7 @@ impl Panel for ChatPanel {
|
|||
show_icon.then(|| ui::IconName::MessageBubbles)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
fn icon_tooltip(&self, _: &Window, _: &App) -> Option<&'static str> {
|
||||
Some("Chat Panel")
|
||||
}
|
||||
|
||||
|
@ -1159,7 +1180,7 @@ impl Panel for ChatPanel {
|
|||
Box::new(ToggleFocus)
|
||||
}
|
||||
|
||||
fn starts_open(&self, cx: &WindowContext) -> bool {
|
||||
fn starts_open(&self, _: &Window, cx: &App) -> bool {
|
||||
ActiveCall::global(cx)
|
||||
.read(cx)
|
||||
.room()
|
||||
|
@ -1183,7 +1204,7 @@ mod tests {
|
|||
use util::test::marked_text_ranges;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_render_markdown_with_mentions(cx: &mut AppContext) {
|
||||
fn test_render_markdown_with_mentions(cx: &mut App) {
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let (body, ranges) = marked_text_ranges("*hi*, «@abc», let's **call** «@fgh»", false);
|
||||
let message = channel::ChannelMessage {
|
||||
|
@ -1240,7 +1261,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_render_markdown_with_auto_detect_links(cx: &mut AppContext) {
|
||||
fn test_render_markdown_with_auto_detect_links(cx: &mut App) {
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let message = channel::ChannelMessage {
|
||||
id: ChannelMessageId::Saved(0),
|
||||
|
@ -1289,7 +1310,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_render_markdown_with_auto_detect_links_and_additional_formatting(cx: &mut AppContext) {
|
||||
fn test_render_markdown_with_auto_detect_links_and_additional_formatting(cx: &mut App) {
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
|
||||
let message = channel::ChannelMessage {
|
||||
id: ChannelMessageId::Saved(0),
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use anyhow::{Context, Result};
|
||||
use anyhow::{Context as _, Result};
|
||||
use channel::{ChannelChat, ChannelStore, MessageParams};
|
||||
use client::{UserId, UserStore};
|
||||
use collections::HashSet;
|
||||
use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle};
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
AsyncWindowContext, FocusableView, FontStyle, FontWeight, HighlightStyle, IntoElement, Model,
|
||||
Render, Task, TextStyle, View, ViewContext, WeakView,
|
||||
AsyncAppContext, AsyncWindowContext, Context, Entity, Focusable, FontStyle, FontWeight,
|
||||
HighlightStyle, IntoElement, Render, Task, TextStyle, WeakEntity, Window,
|
||||
};
|
||||
use language::{
|
||||
language_settings::SoftWrap, Anchor, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry,
|
||||
|
@ -42,24 +42,25 @@ static MENTIONS_SEARCH: LazyLock<SearchQuery> = LazyLock::new(|| {
|
|||
});
|
||||
|
||||
pub struct MessageEditor {
|
||||
pub editor: View<Editor>,
|
||||
user_store: Model<UserStore>,
|
||||
channel_chat: Option<Model<ChannelChat>>,
|
||||
pub editor: Entity<Editor>,
|
||||
user_store: Entity<UserStore>,
|
||||
channel_chat: Option<Entity<ChannelChat>>,
|
||||
mentions: Vec<UserId>,
|
||||
mentions_task: Option<Task<()>>,
|
||||
reply_to_message_id: Option<u64>,
|
||||
edit_message_id: Option<u64>,
|
||||
}
|
||||
|
||||
struct MessageEditorCompletionProvider(WeakView<MessageEditor>);
|
||||
struct MessageEditorCompletionProvider(WeakEntity<MessageEditor>);
|
||||
|
||||
impl CompletionProvider for MessageEditorCompletionProvider {
|
||||
fn completions(
|
||||
&self,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer: &Entity<Buffer>,
|
||||
buffer_position: language::Anchor,
|
||||
_: editor::CompletionContext,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Editor>,
|
||||
) -> Task<anyhow::Result<Vec<Completion>>> {
|
||||
let Some(handle) = self.0.upgrade() else {
|
||||
return Task::ready(Ok(Vec::new()));
|
||||
|
@ -71,21 +72,21 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
|||
|
||||
fn resolve_completions(
|
||||
&self,
|
||||
_buffer: Model<Buffer>,
|
||||
_buffer: Entity<Buffer>,
|
||||
_completion_indices: Vec<usize>,
|
||||
_completions: Rc<RefCell<Box<[Completion]>>>,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
_cx: &mut Context<Editor>,
|
||||
) -> Task<anyhow::Result<bool>> {
|
||||
Task::ready(Ok(false))
|
||||
}
|
||||
|
||||
fn is_completion_trigger(
|
||||
&self,
|
||||
_buffer: &Model<Buffer>,
|
||||
_buffer: &Entity<Buffer>,
|
||||
_position: language::Anchor,
|
||||
text: &str,
|
||||
_trigger_in_words: bool,
|
||||
_cx: &mut ViewContext<Editor>,
|
||||
_cx: &mut Context<Editor>,
|
||||
) -> bool {
|
||||
text == "@"
|
||||
}
|
||||
|
@ -94,12 +95,13 @@ impl CompletionProvider for MessageEditorCompletionProvider {
|
|||
impl MessageEditor {
|
||||
pub fn new(
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
user_store: Model<UserStore>,
|
||||
channel_chat: Option<Model<ChannelChat>>,
|
||||
editor: View<Editor>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
user_store: Entity<UserStore>,
|
||||
channel_chat: Option<Entity<ChannelChat>>,
|
||||
editor: Entity<Editor>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let this = cx.view().downgrade();
|
||||
let this = cx.model().downgrade();
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
|
||||
editor.set_use_autoclose(false);
|
||||
|
@ -121,9 +123,10 @@ impl MessageEditor {
|
|||
.as_singleton()
|
||||
.expect("message editor must be singleton");
|
||||
|
||||
cx.subscribe(&buffer, Self::on_buffer_event).detach();
|
||||
cx.observe_global::<settings::SettingsStore>(|view, cx| {
|
||||
view.editor.update(cx, |editor, cx| {
|
||||
cx.subscribe_in(&buffer, window, Self::on_buffer_event)
|
||||
.detach();
|
||||
cx.observe_global::<settings::SettingsStore>(|this, cx| {
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
editor.set_auto_replace_emoji_shortcode(
|
||||
MessageEditorSettings::get_global(cx)
|
||||
.auto_replace_emoji_shortcode
|
||||
|
@ -134,7 +137,7 @@ impl MessageEditor {
|
|||
.detach();
|
||||
|
||||
let markdown = language_registry.language_for_name("Markdown");
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
cx.spawn_in(window, |_, mut cx| async move {
|
||||
let markdown = markdown.await.context("failed to load Markdown language")?;
|
||||
buffer.update(&mut cx, |buffer, cx| {
|
||||
buffer.set_language(Some(markdown), cx)
|
||||
|
@ -177,7 +180,7 @@ impl MessageEditor {
|
|||
self.edit_message_id = None;
|
||||
}
|
||||
|
||||
pub fn set_channel_chat(&mut self, chat: Model<ChannelChat>, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_channel_chat(&mut self, chat: Entity<ChannelChat>, cx: &mut Context<Self>) {
|
||||
let channel_id = chat.read(cx).channel_id;
|
||||
self.channel_chat = Some(chat);
|
||||
let channel_name = ChannelStore::global(cx)
|
||||
|
@ -193,7 +196,7 @@ impl MessageEditor {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn take_message(&mut self, cx: &mut ViewContext<Self>) -> MessageParams {
|
||||
pub fn take_message(&mut self, window: &mut Window, cx: &mut Context<Self>) -> MessageParams {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let highlights = editor.text_highlights::<Self>(cx);
|
||||
let text = editor.text(cx);
|
||||
|
@ -208,7 +211,7 @@ impl MessageEditor {
|
|||
Vec::new()
|
||||
};
|
||||
|
||||
editor.clear(cx);
|
||||
editor.clear(window, cx);
|
||||
self.mentions.clear();
|
||||
let reply_to_message_id = std::mem::take(&mut self.reply_to_message_id);
|
||||
|
||||
|
@ -222,13 +225,14 @@ impl MessageEditor {
|
|||
|
||||
fn on_buffer_event(
|
||||
&mut self,
|
||||
buffer: Model<Buffer>,
|
||||
buffer: &Entity<Buffer>,
|
||||
event: &language::BufferEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let language::BufferEvent::Reparsed | language::BufferEvent::Edited = event {
|
||||
let buffer = buffer.read(cx).snapshot();
|
||||
self.mentions_task = Some(cx.spawn(|this, cx| async move {
|
||||
self.mentions_task = Some(cx.spawn_in(window, |this, cx| async move {
|
||||
cx.background_executor()
|
||||
.timer(MENTIONS_DEBOUNCE_INTERVAL)
|
||||
.await;
|
||||
|
@ -239,9 +243,9 @@ impl MessageEditor {
|
|||
|
||||
fn completions(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer: &Entity<Buffer>,
|
||||
end_anchor: Anchor,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Vec<Completion>>> {
|
||||
if let Some((start_anchor, query, candidates)) =
|
||||
self.collect_mention_candidates(buffer, end_anchor, cx)
|
||||
|
@ -281,7 +285,7 @@ impl MessageEditor {
|
|||
}
|
||||
|
||||
async fn resolve_completions_for_candidates(
|
||||
cx: &AsyncWindowContext,
|
||||
cx: &AsyncAppContext,
|
||||
query: &str,
|
||||
candidates: &[StringMatchCandidate],
|
||||
range: Range<Anchor>,
|
||||
|
@ -336,9 +340,9 @@ impl MessageEditor {
|
|||
|
||||
fn collect_mention_candidates(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer: &Entity<Buffer>,
|
||||
end_anchor: Anchor,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<(Anchor, String, Vec<StringMatchCandidate>)> {
|
||||
let end_offset = end_anchor.to_offset(buffer.read(cx));
|
||||
|
||||
|
@ -385,9 +389,9 @@ impl MessageEditor {
|
|||
|
||||
fn collect_emoji_candidates(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
buffer: &Entity<Buffer>,
|
||||
end_anchor: Anchor,
|
||||
cx: &mut ViewContext<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<(Anchor, String, &'static [StringMatchCandidate])> {
|
||||
static EMOJI_FUZZY_MATCH_CANDIDATES: LazyLock<Vec<StringMatchCandidate>> =
|
||||
LazyLock::new(|| {
|
||||
|
@ -445,7 +449,7 @@ impl MessageEditor {
|
|||
}
|
||||
|
||||
async fn find_mentions(
|
||||
this: WeakView<MessageEditor>,
|
||||
this: WeakEntity<MessageEditor>,
|
||||
buffer: BufferSnapshot,
|
||||
mut cx: AsyncWindowContext,
|
||||
) {
|
||||
|
@ -499,13 +503,13 @@ impl MessageEditor {
|
|||
.ok();
|
||||
}
|
||||
|
||||
pub(crate) fn focus_handle(&self, cx: &gpui::AppContext) -> gpui::FocusHandle {
|
||||
pub(crate) fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle {
|
||||
self.editor.read(cx).focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for MessageEditor {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let settings = ThemeSettings::get_global(cx);
|
||||
let text_style = TextStyle {
|
||||
color: if self.editor.read(cx).read_only(cx) {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -5,9 +5,8 @@ use client::{
|
|||
};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, anchored, deferred, div, AppContext, ClipboardItem, DismissEvent, EventEmitter,
|
||||
FocusableView, Model, ParentElement, Render, Styled, Subscription, Task, View, ViewContext,
|
||||
VisualContext, WeakView,
|
||||
actions, anchored, deferred, div, App, ClipboardItem, Context, DismissEvent, Entity,
|
||||
EventEmitter, Focusable, ParentElement, Render, Styled, Subscription, Task, WeakEntity, Window,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
|
@ -26,22 +25,23 @@ actions!(
|
|||
);
|
||||
|
||||
pub struct ChannelModal {
|
||||
picker: View<Picker<ChannelModalDelegate>>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
picker: Entity<Picker<ChannelModalDelegate>>,
|
||||
channel_store: Entity<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
}
|
||||
|
||||
impl ChannelModal {
|
||||
pub fn new(
|
||||
user_store: Model<UserStore>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
user_store: Entity<UserStore>,
|
||||
channel_store: Entity<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
mode: Mode,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
cx.observe(&channel_store, |_, _, cx| cx.notify()).detach();
|
||||
let channel_modal = cx.view().downgrade();
|
||||
let picker = cx.new_view(|cx| {
|
||||
let channel_modal = cx.model().downgrade();
|
||||
let picker = cx.new(|cx| {
|
||||
Picker::uniform_list(
|
||||
ChannelModalDelegate {
|
||||
channel_modal,
|
||||
|
@ -57,6 +57,7 @@ impl ChannelModal {
|
|||
has_all_members: false,
|
||||
mode,
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.modal(false)
|
||||
|
@ -69,27 +70,32 @@ impl ChannelModal {
|
|||
}
|
||||
}
|
||||
|
||||
fn toggle_mode(&mut self, _: &ToggleMode, cx: &mut ViewContext<Self>) {
|
||||
fn toggle_mode(&mut self, _: &ToggleMode, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let mode = match self.picker.read(cx).delegate.mode {
|
||||
Mode::ManageMembers => Mode::InviteMembers,
|
||||
Mode::InviteMembers => Mode::ManageMembers,
|
||||
};
|
||||
self.set_mode(mode, cx);
|
||||
self.set_mode(mode, window, cx);
|
||||
}
|
||||
|
||||
fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext<Self>) {
|
||||
fn set_mode(&mut self, mode: Mode, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.picker.update(cx, |picker, cx| {
|
||||
let delegate = &mut picker.delegate;
|
||||
delegate.mode = mode;
|
||||
delegate.selected_index = 0;
|
||||
picker.set_query("", cx);
|
||||
picker.update_matches(picker.query(cx), cx);
|
||||
picker.set_query("", window, cx);
|
||||
picker.update_matches(picker.query(cx), window, cx);
|
||||
cx.notify()
|
||||
});
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn set_channel_visibility(&mut self, selection: &ToggleState, cx: &mut ViewContext<Self>) {
|
||||
fn set_channel_visibility(
|
||||
&mut self,
|
||||
selection: &ToggleState,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.channel_store.update(cx, |channel_store, cx| {
|
||||
channel_store
|
||||
.set_channel_visibility(
|
||||
|
@ -105,7 +111,7 @@ impl ChannelModal {
|
|||
});
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
@ -113,14 +119,14 @@ impl ChannelModal {
|
|||
impl EventEmitter<DismissEvent> for ChannelModal {}
|
||||
impl ModalView for ChannelModal {}
|
||||
|
||||
impl FocusableView for ChannelModal {
|
||||
fn focus_handle(&self, cx: &AppContext) -> gpui::FocusHandle {
|
||||
impl Focusable for ChannelModal {
|
||||
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ChannelModal {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let Some(channel) = channel_store.channel_for_id(self.channel_id) else {
|
||||
return div();
|
||||
|
@ -169,7 +175,7 @@ impl Render for ChannelModal {
|
|||
Some(
|
||||
Button::new("copy-link", "Copy Link")
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
.on_click(cx.listener(move |this, _, _, cx| {
|
||||
if let Some(channel) = this
|
||||
.channel_store
|
||||
.read(cx)
|
||||
|
@ -197,8 +203,8 @@ impl Render for ChannelModal {
|
|||
this.border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(Label::new("Manage Members"))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.set_mode(Mode::ManageMembers, cx);
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.set_mode(Mode::ManageMembers, window, cx);
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
|
@ -212,8 +218,8 @@ impl Render for ChannelModal {
|
|||
this.border_color(cx.theme().colors().border)
|
||||
})
|
||||
.child(Label::new("Invite Members"))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.set_mode(Mode::InviteMembers, cx);
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.set_mode(Mode::InviteMembers, window, cx);
|
||||
})),
|
||||
),
|
||||
),
|
||||
|
@ -229,24 +235,24 @@ pub enum Mode {
|
|||
}
|
||||
|
||||
pub struct ChannelModalDelegate {
|
||||
channel_modal: WeakView<ChannelModal>,
|
||||
channel_modal: WeakEntity<ChannelModal>,
|
||||
matching_users: Vec<Arc<User>>,
|
||||
matching_member_indices: Vec<usize>,
|
||||
user_store: Model<UserStore>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
user_store: Entity<UserStore>,
|
||||
channel_store: Entity<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
selected_index: usize,
|
||||
mode: Mode,
|
||||
match_candidates: Vec<StringMatchCandidate>,
|
||||
members: Vec<ChannelMembership>,
|
||||
has_all_members: bool,
|
||||
context_menu: Option<(View<ContextMenu>, Subscription)>,
|
||||
context_menu: Option<(Entity<ContextMenu>, Subscription)>,
|
||||
}
|
||||
|
||||
impl PickerDelegate for ChannelModalDelegate {
|
||||
type ListItem = ListItem;
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
|
@ -261,11 +267,21 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
_: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
match self.mode {
|
||||
Mode::ManageMembers => {
|
||||
if self.has_all_members {
|
||||
|
@ -284,7 +300,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||
cx.background_executor().clone(),
|
||||
));
|
||||
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
cx.spawn_in(window, |picker, mut cx| async move {
|
||||
picker
|
||||
.update(&mut cx, |picker, cx| {
|
||||
let delegate = &mut picker.delegate;
|
||||
|
@ -300,7 +316,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||
let search_members = self.channel_store.update(cx, |store, cx| {
|
||||
store.fuzzy_search_members(self.channel_id, query.clone(), 100, cx)
|
||||
});
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
cx.spawn_in(window, |picker, mut cx| async move {
|
||||
async {
|
||||
let members = search_members.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
|
@ -322,7 +338,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||
let search_users = self
|
||||
.user_store
|
||||
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
cx.spawn_in(window, |picker, mut cx| async move {
|
||||
async {
|
||||
let users = search_users.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
|
@ -338,26 +354,26 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _: bool, window: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
if let Some(selected_user) = self.user_at_index(self.selected_index) {
|
||||
if Some(selected_user.id) == self.user_store.read(cx).current_user().map(|user| user.id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
match self.mode {
|
||||
Mode::ManageMembers => self.show_context_menu(self.selected_index, cx),
|
||||
Mode::ManageMembers => self.show_context_menu(self.selected_index, window, cx),
|
||||
Mode::InviteMembers => match self.member_status(selected_user.id, cx) {
|
||||
Some(proto::channel_member::Kind::Invitee) => {
|
||||
self.remove_member(selected_user.id, cx);
|
||||
self.remove_member(selected_user.id, window, cx);
|
||||
}
|
||||
Some(proto::channel_member::Kind::Member) => {}
|
||||
None => self.invite_member(selected_user, cx),
|
||||
None => self.invite_member(selected_user, window, cx),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
if self.context_menu.is_none() {
|
||||
self.channel_modal
|
||||
.update(cx, |_, cx| {
|
||||
|
@ -371,7 +387,8 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let user = self.user_at_index(ix)?;
|
||||
let membership = self.member_at_index(ix);
|
||||
|
@ -434,11 +451,7 @@ impl PickerDelegate for ChannelModalDelegate {
|
|||
}
|
||||
|
||||
impl ChannelModalDelegate {
|
||||
fn member_status(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
cx: &AppContext,
|
||||
) -> Option<proto::channel_member::Kind> {
|
||||
fn member_status(&self, user_id: UserId, cx: &App) -> Option<proto::channel_member::Kind> {
|
||||
self.members
|
||||
.iter()
|
||||
.find_map(|membership| (membership.user.id == user_id).then_some(membership.kind))
|
||||
|
@ -470,33 +483,39 @@ impl ChannelModalDelegate {
|
|||
&mut self,
|
||||
user_id: UserId,
|
||||
new_role: ChannelRole,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<()> {
|
||||
let update = self.channel_store.update(cx, |store, cx| {
|
||||
store.set_member_role(self.channel_id, user_id, new_role, cx)
|
||||
});
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
cx.spawn_in(window, |picker, mut cx| async move {
|
||||
update.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
picker.update_in(&mut cx, |picker, window, cx| {
|
||||
let this = &mut picker.delegate;
|
||||
if let Some(member) = this.members.iter_mut().find(|m| m.user.id == user_id) {
|
||||
member.role = new_role;
|
||||
}
|
||||
cx.focus_self();
|
||||
cx.focus_self(window);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach_and_prompt_err("Failed to update role", cx, |_, _| None);
|
||||
.detach_and_prompt_err("Failed to update role", window, cx, |_, _, _| None);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn remove_member(&mut self, user_id: UserId, cx: &mut ViewContext<Picker<Self>>) -> Option<()> {
|
||||
fn remove_member(
|
||||
&mut self,
|
||||
user_id: UserId,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<()> {
|
||||
let update = self.channel_store.update(cx, |store, cx| {
|
||||
store.remove_member(self.channel_id, user_id, cx)
|
||||
});
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
cx.spawn_in(window, |picker, mut cx| async move {
|
||||
update.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
picker.update_in(&mut cx, |picker, window, cx| {
|
||||
let this = &mut picker.delegate;
|
||||
if let Some(ix) = this.members.iter_mut().position(|m| m.user.id == user_id) {
|
||||
this.members.remove(ix);
|
||||
|
@ -514,20 +533,25 @@ impl ChannelModalDelegate {
|
|||
.selected_index
|
||||
.min(this.matching_member_indices.len().saturating_sub(1));
|
||||
|
||||
picker.focus(cx);
|
||||
picker.focus(window, cx);
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach_and_prompt_err("Failed to remove member", cx, |_, _| None);
|
||||
.detach_and_prompt_err("Failed to remove member", window, cx, |_, _, _| None);
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn invite_member(&mut self, user: Arc<User>, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn invite_member(
|
||||
&mut self,
|
||||
user: Arc<User>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
let invite_member = self.channel_store.update(cx, |store, cx| {
|
||||
store.invite_member(self.channel_id, user.id, ChannelRole::Member, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
invite_member.await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
|
@ -544,25 +568,30 @@ impl ChannelModalDelegate {
|
|||
cx.notify();
|
||||
})
|
||||
})
|
||||
.detach_and_prompt_err("Failed to invite member", cx, |_, _| None);
|
||||
.detach_and_prompt_err("Failed to invite member", window, cx, |_, _, _| None);
|
||||
}
|
||||
|
||||
fn show_context_menu(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn show_context_menu(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
let Some(membership) = self.member_at_index(ix) else {
|
||||
return;
|
||||
};
|
||||
let user_id = membership.user.id;
|
||||
let picker = cx.view().clone();
|
||||
let context_menu = ContextMenu::build(cx, |mut menu, _cx| {
|
||||
let picker = cx.model().clone();
|
||||
let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
|
||||
let role = membership.role;
|
||||
|
||||
if role == ChannelRole::Admin || role == ChannelRole::Member {
|
||||
let picker = picker.clone();
|
||||
menu = menu.entry("Demote to Guest", None, move |cx| {
|
||||
menu = menu.entry("Demote to Guest", None, move |window, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.set_user_role(user_id, ChannelRole::Guest, cx);
|
||||
.set_user_role(user_id, ChannelRole::Guest, window, cx);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -575,22 +604,22 @@ impl ChannelModalDelegate {
|
|||
"Demote to Member"
|
||||
};
|
||||
|
||||
menu = menu.entry(label, None, move |cx| {
|
||||
menu = menu.entry(label, None, move |window, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.set_user_role(user_id, ChannelRole::Member, cx);
|
||||
.set_user_role(user_id, ChannelRole::Member, window, cx);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
if role == ChannelRole::Member || role == ChannelRole::Guest {
|
||||
let picker = picker.clone();
|
||||
menu = menu.entry("Promote to Admin", None, move |cx| {
|
||||
menu = menu.entry("Promote to Admin", None, move |window, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker
|
||||
.delegate
|
||||
.set_user_role(user_id, ChannelRole::Admin, cx);
|
||||
.set_user_role(user_id, ChannelRole::Admin, window, cx);
|
||||
})
|
||||
});
|
||||
};
|
||||
|
@ -598,20 +627,24 @@ impl ChannelModalDelegate {
|
|||
menu = menu.separator();
|
||||
menu = menu.entry("Remove from Channel", None, {
|
||||
let picker = picker.clone();
|
||||
move |cx| {
|
||||
move |window, cx| {
|
||||
picker.update(cx, |picker, cx| {
|
||||
picker.delegate.remove_member(user_id, cx);
|
||||
picker.delegate.remove_member(user_id, window, cx);
|
||||
})
|
||||
}
|
||||
});
|
||||
menu
|
||||
});
|
||||
cx.focus_view(&context_menu);
|
||||
let subscription = cx.subscribe(&context_menu, |picker, _, _: &DismissEvent, cx| {
|
||||
picker.delegate.context_menu = None;
|
||||
picker.focus(cx);
|
||||
cx.notify();
|
||||
});
|
||||
window.focus(&context_menu.focus_handle(cx));
|
||||
let subscription = cx.subscribe_in(
|
||||
&context_menu,
|
||||
window,
|
||||
|picker, _, _: &DismissEvent, window, cx| {
|
||||
picker.delegate.context_menu = None;
|
||||
picker.focus(window, cx);
|
||||
cx.notify();
|
||||
},
|
||||
);
|
||||
self.context_menu = Some((context_menu, subscription));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use client::{ContactRequestStatus, User, UserStore};
|
||||
use gpui::{
|
||||
AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, ParentElement as _,
|
||||
Render, Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||
App, Context, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, ParentElement as _,
|
||||
Render, Styled, Task, WeakEntity, Window,
|
||||
};
|
||||
use picker::{Picker, PickerDelegate};
|
||||
use std::sync::Arc;
|
||||
|
@ -10,31 +10,31 @@ use util::{ResultExt as _, TryFutureExt};
|
|||
use workspace::ModalView;
|
||||
|
||||
pub struct ContactFinder {
|
||||
picker: View<Picker<ContactFinderDelegate>>,
|
||||
picker: Entity<Picker<ContactFinderDelegate>>,
|
||||
}
|
||||
|
||||
impl ContactFinder {
|
||||
pub fn new(user_store: Model<UserStore>, cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(user_store: Entity<UserStore>, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let delegate = ContactFinderDelegate {
|
||||
parent: cx.view().downgrade(),
|
||||
parent: cx.model().downgrade(),
|
||||
user_store,
|
||||
potential_contacts: Arc::from([]),
|
||||
selected_index: 0,
|
||||
};
|
||||
let picker = cx.new_view(|cx| Picker::uniform_list(delegate, cx).modal(false));
|
||||
let picker = cx.new(|cx| Picker::uniform_list(delegate, window, cx).modal(false));
|
||||
|
||||
Self { picker }
|
||||
}
|
||||
|
||||
pub fn set_query(&mut self, query: String, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_query(&mut self, query: String, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.picker.update(cx, |picker, cx| {
|
||||
picker.set_query(query, cx);
|
||||
picker.set_query(query, window, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for ContactFinder {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.elevation_3(cx)
|
||||
.child(
|
||||
|
@ -53,17 +53,17 @@ impl Render for ContactFinder {
|
|||
}
|
||||
|
||||
pub struct ContactFinderDelegate {
|
||||
parent: WeakView<ContactFinder>,
|
||||
parent: WeakEntity<ContactFinder>,
|
||||
potential_contacts: Arc<[Arc<User>]>,
|
||||
user_store: Model<UserStore>,
|
||||
user_store: Entity<UserStore>,
|
||||
selected_index: usize,
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for ContactFinder {}
|
||||
impl ModalView for ContactFinder {}
|
||||
|
||||
impl FocusableView for ContactFinder {
|
||||
fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for ContactFinder {
|
||||
fn focus_handle(&self, cx: &App) -> FocusHandle {
|
||||
self.picker.focus_handle(cx)
|
||||
}
|
||||
}
|
||||
|
@ -79,20 +79,30 @@ impl PickerDelegate for ContactFinderDelegate {
|
|||
self.selected_index
|
||||
}
|
||||
|
||||
fn set_selected_index(&mut self, ix: usize, _: &mut ViewContext<Picker<Self>>) {
|
||||
fn set_selected_index(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
_window: &mut Window,
|
||||
_: &mut Context<Picker<Self>>,
|
||||
) {
|
||||
self.selected_index = ix;
|
||||
}
|
||||
|
||||
fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
|
||||
fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> {
|
||||
"Search collaborator by username...".into()
|
||||
}
|
||||
|
||||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
|
||||
fn update_matches(
|
||||
&mut self,
|
||||
query: String,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Task<()> {
|
||||
let search_users = self
|
||||
.user_store
|
||||
.update(cx, |store, cx| store.fuzzy_search_users(query, cx));
|
||||
|
||||
cx.spawn(|picker, mut cx| async move {
|
||||
cx.spawn_in(window, |picker, mut cx| async move {
|
||||
async {
|
||||
let potential_contacts = search_users.await?;
|
||||
picker.update(&mut cx, |picker, cx| {
|
||||
|
@ -106,7 +116,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
|||
})
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: bool, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn confirm(&mut self, _: bool, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
if let Some(user) = self.potential_contacts.get(self.selected_index) {
|
||||
let user_store = self.user_store.read(cx);
|
||||
match user_store.contact_request_status(user) {
|
||||
|
@ -125,7 +135,7 @@ impl PickerDelegate for ContactFinderDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
fn dismissed(&mut self, cx: &mut ViewContext<Picker<Self>>) {
|
||||
fn dismissed(&mut self, _: &mut Window, cx: &mut Context<Picker<Self>>) {
|
||||
self.parent
|
||||
.update(cx, |_, cx| cx.emit(DismissEvent))
|
||||
.log_err();
|
||||
|
@ -135,7 +145,8 @@ impl PickerDelegate for ContactFinderDelegate {
|
|||
&self,
|
||||
ix: usize,
|
||||
selected: bool,
|
||||
cx: &mut ViewContext<Picker<Self>>,
|
||||
_: &mut Window,
|
||||
cx: &mut Context<Picker<Self>>,
|
||||
) -> Option<Self::ListItem> {
|
||||
let user = &self.potential_contacts[ix];
|
||||
let request_status = self.user_store.read(cx).contact_request_status(user);
|
||||
|
|
|
@ -9,7 +9,7 @@ use std::{rc::Rc, sync::Arc};
|
|||
|
||||
pub use collab_panel::CollabPanel;
|
||||
use gpui::{
|
||||
point, AppContext, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
|
||||
point, App, Pixels, PlatformDisplay, Size, WindowBackgroundAppearance, WindowBounds,
|
||||
WindowDecorations, WindowKind, WindowOptions,
|
||||
};
|
||||
use panel_settings::MessageEditorSettings;
|
||||
|
@ -21,7 +21,7 @@ use settings::Settings;
|
|||
use ui::px;
|
||||
use workspace::AppState;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
|
||||
CollaborationPanelSettings::register(cx);
|
||||
ChatPanelSettings::register(cx);
|
||||
NotificationPanelSettings::register(cx);
|
||||
|
@ -38,7 +38,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
fn notification_window_options(
|
||||
screen: Rc<dyn PlatformDisplay>,
|
||||
size: Size<Pixels>,
|
||||
cx: &AppContext,
|
||||
cx: &App,
|
||||
) -> WindowOptions {
|
||||
let notification_margin_width = px(16.);
|
||||
let notification_margin_height = px(-48.);
|
||||
|
|
|
@ -6,11 +6,10 @@ use collections::HashMap;
|
|||
use db::kvp::KEY_VALUE_STORE;
|
||||
use futures::StreamExt;
|
||||
use gpui::{
|
||||
actions, div, img, list, px, AnyElement, AppContext, AsyncWindowContext, CursorStyle,
|
||||
DismissEvent, Element, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
||||
IntoElement, ListAlignment, ListScrollEvent, ListState, Model, ParentElement, Render,
|
||||
StatefulInteractiveElement, Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||
WindowContext,
|
||||
actions, div, img, list, px, AnyElement, App, AsyncWindowContext, Context, CursorStyle,
|
||||
DismissEvent, Element, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement,
|
||||
IntoElement, ListAlignment, ListScrollEvent, ListState, ParentElement, Render,
|
||||
StatefulInteractiveElement, Styled, Task, WeakEntity, Window,
|
||||
};
|
||||
use notifications::{NotificationEntry, NotificationEvent, NotificationStore};
|
||||
use project::Fs;
|
||||
|
@ -36,16 +35,16 @@ const NOTIFICATION_PANEL_KEY: &str = "NotificationPanel";
|
|||
|
||||
pub struct NotificationPanel {
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
channel_store: Model<ChannelStore>,
|
||||
notification_store: Model<NotificationStore>,
|
||||
user_store: Entity<UserStore>,
|
||||
channel_store: Entity<ChannelStore>,
|
||||
notification_store: Entity<NotificationStore>,
|
||||
fs: Arc<dyn Fs>,
|
||||
width: Option<Pixels>,
|
||||
active: bool,
|
||||
notification_list: ListState,
|
||||
pending_serialization: Task<Option<()>>,
|
||||
subscriptions: Vec<gpui::Subscription>,
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
current_notification_toast: Option<(u64, Task<()>)>,
|
||||
local_timezone: UtcOffset,
|
||||
focus_handle: FocusHandle,
|
||||
|
@ -75,28 +74,32 @@ pub struct NotificationPresenter {
|
|||
|
||||
actions!(notification_panel, [ToggleFocus]);
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
cx.observe_new_views(|workspace: &mut Workspace, _| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, cx| {
|
||||
workspace.toggle_panel_focus::<NotificationPanel>(cx);
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.observe_new(|workspace: &mut Workspace, _, _| {
|
||||
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
|
||||
workspace.toggle_panel_focus::<NotificationPanel>(window, cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
impl NotificationPanel {
|
||||
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
|
||||
pub fn new(
|
||||
workspace: &mut Workspace,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Entity<Self> {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
let client = workspace.app_state().client.clone();
|
||||
let user_store = workspace.app_state().user_store.clone();
|
||||
let workspace_handle = workspace.weak_handle();
|
||||
|
||||
cx.new_view(|cx: &mut ViewContext<Self>| {
|
||||
cx.new(|cx| {
|
||||
let mut status = client.status();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
while (status.next().await).is_some() {
|
||||
if this
|
||||
.update(&mut cx, |_, cx| {
|
||||
.update(&mut cx, |_: &mut Self, cx| {
|
||||
cx.notify();
|
||||
})
|
||||
.is_err()
|
||||
|
@ -107,17 +110,18 @@ impl NotificationPanel {
|
|||
})
|
||||
.detach();
|
||||
|
||||
let view = cx.view().downgrade();
|
||||
let model = cx.model().downgrade();
|
||||
let notification_list =
|
||||
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, cx| {
|
||||
view.upgrade()
|
||||
.and_then(|view| {
|
||||
view.update(cx, |this, cx| this.render_notification(ix, cx))
|
||||
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, window, cx| {
|
||||
model
|
||||
.upgrade()
|
||||
.and_then(|model| {
|
||||
model.update(cx, |this, cx| this.render_notification(ix, window, cx))
|
||||
})
|
||||
.unwrap_or_else(|| div().into_any())
|
||||
});
|
||||
notification_list.set_scroll_handler(cx.listener(
|
||||
|this, event: &ListScrollEvent, cx| {
|
||||
|this, event: &ListScrollEvent, _, cx| {
|
||||
if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
|
||||
if let Some(task) = this
|
||||
.notification_store
|
||||
|
@ -149,27 +153,34 @@ impl NotificationPanel {
|
|||
unseen_notifications: Vec::new(),
|
||||
};
|
||||
|
||||
let mut old_dock_position = this.position(cx);
|
||||
let mut old_dock_position = this.position(window, cx);
|
||||
this.subscriptions.extend([
|
||||
cx.observe(&this.notification_store, |_, _, cx| cx.notify()),
|
||||
cx.subscribe(&this.notification_store, Self::on_notification_event),
|
||||
cx.observe_global::<SettingsStore>(move |this: &mut Self, cx| {
|
||||
let new_dock_position = this.position(cx);
|
||||
if new_dock_position != old_dock_position {
|
||||
old_dock_position = new_dock_position;
|
||||
cx.emit(Event::DockPositionChanged);
|
||||
}
|
||||
cx.notify();
|
||||
}),
|
||||
cx.subscribe_in(
|
||||
&this.notification_store,
|
||||
window,
|
||||
Self::on_notification_event,
|
||||
),
|
||||
cx.observe_global_in::<SettingsStore>(
|
||||
window,
|
||||
move |this: &mut Self, window, cx| {
|
||||
let new_dock_position = this.position(window, cx);
|
||||
if new_dock_position != old_dock_position {
|
||||
old_dock_position = new_dock_position;
|
||||
cx.emit(Event::DockPositionChanged);
|
||||
}
|
||||
cx.notify();
|
||||
},
|
||||
),
|
||||
]);
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load(
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
cx: AsyncWindowContext,
|
||||
) -> Task<Result<View<Self>>> {
|
||||
) -> Task<Result<Entity<Self>>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let serialized_panel = if let Some(panel) = cx
|
||||
.background_executor()
|
||||
|
@ -183,8 +194,8 @@ impl NotificationPanel {
|
|||
None
|
||||
};
|
||||
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let panel = Self::new(workspace, cx);
|
||||
workspace.update_in(&mut cx, |workspace, window, cx| {
|
||||
let panel = Self::new(workspace, window, cx);
|
||||
if let Some(serialized_panel) = serialized_panel {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel.width = serialized_panel.width.map(|w| w.round());
|
||||
|
@ -196,7 +207,7 @@ impl NotificationPanel {
|
|||
})
|
||||
}
|
||||
|
||||
fn serialize(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn serialize(&mut self, cx: &mut Context<Self>) {
|
||||
let width = self.width;
|
||||
self.pending_serialization = cx.background_executor().spawn(
|
||||
async move {
|
||||
|
@ -212,7 +223,12 @@ impl NotificationPanel {
|
|||
);
|
||||
}
|
||||
|
||||
fn render_notification(&mut self, ix: usize, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
fn render_notification(
|
||||
&mut self,
|
||||
ix: usize,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<AnyElement> {
|
||||
let entry = self.notification_store.read(cx).notification_at(ix)?;
|
||||
let notification_id = entry.id;
|
||||
let now = OffsetDateTime::now_utc();
|
||||
|
@ -229,7 +245,7 @@ impl NotificationPanel {
|
|||
let notification = entry.notification.clone();
|
||||
|
||||
if self.active && !entry.is_read {
|
||||
self.did_render_notification(notification_id, ¬ification, cx);
|
||||
self.did_render_notification(notification_id, ¬ification, window, cx);
|
||||
}
|
||||
|
||||
let relative_timestamp = time_format::format_localized_timestamp(
|
||||
|
@ -259,8 +275,8 @@ impl NotificationPanel {
|
|||
.when(can_navigate, |el| {
|
||||
el.cursor(CursorStyle::PointingHand).on_click({
|
||||
let notification = notification.clone();
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.did_click_notification(¬ification, cx)
|
||||
cx.listener(move |this, _, window, cx| {
|
||||
this.did_click_notification(¬ification, window, cx)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -288,8 +304,8 @@ impl NotificationPanel {
|
|||
.rounded_md()
|
||||
})
|
||||
.child(Label::new(relative_timestamp).color(Color::Muted))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(absolute_timestamp.clone(), cx)
|
||||
.tooltip(move |_, cx| {
|
||||
Tooltip::simple(absolute_timestamp.clone(), cx)
|
||||
}),
|
||||
)
|
||||
.children(if let Some(is_accepted) = response {
|
||||
|
@ -307,9 +323,9 @@ impl NotificationPanel {
|
|||
.justify_end()
|
||||
.child(Button::new("decline", "Decline").on_click({
|
||||
let notification = notification.clone();
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| {
|
||||
view.update(cx, |this, cx| {
|
||||
let model = cx.model().clone();
|
||||
move |_, _, cx| {
|
||||
model.update(cx, |this, cx| {
|
||||
this.respond_to_notification(
|
||||
notification.clone(),
|
||||
false,
|
||||
|
@ -320,9 +336,9 @@ impl NotificationPanel {
|
|||
}))
|
||||
.child(Button::new("accept", "Accept").on_click({
|
||||
let notification = notification.clone();
|
||||
let view = cx.view().clone();
|
||||
move |_, cx| {
|
||||
view.update(cx, |this, cx| {
|
||||
let model = cx.model().clone();
|
||||
move |_, _, cx| {
|
||||
model.update(cx, |this, cx| {
|
||||
this.respond_to_notification(
|
||||
notification.clone(),
|
||||
true,
|
||||
|
@ -344,7 +360,7 @@ impl NotificationPanel {
|
|||
fn present_notification(
|
||||
&self,
|
||||
entry: &NotificationEntry,
|
||||
cx: &AppContext,
|
||||
cx: &App,
|
||||
) -> Option<NotificationPresenter> {
|
||||
let user_store = self.user_store.read(cx);
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
|
@ -415,7 +431,8 @@ impl NotificationPanel {
|
|||
&mut self,
|
||||
notification_id: u64,
|
||||
notification: &Notification,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let should_mark_as_read = match notification {
|
||||
Notification::ContactRequestAccepted { .. } => true,
|
||||
|
@ -429,7 +446,7 @@ impl NotificationPanel {
|
|||
.entry(notification_id)
|
||||
.or_insert_with(|| {
|
||||
let client = self.client.clone();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
cx.background_executor().timer(MARK_AS_READ_DELAY).await;
|
||||
client
|
||||
.request(proto::MarkNotificationRead { notification_id })
|
||||
|
@ -443,7 +460,12 @@ impl NotificationPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn did_click_notification(&mut self, notification: &Notification, cx: &mut ViewContext<Self>) {
|
||||
fn did_click_notification(
|
||||
&mut self,
|
||||
notification: &Notification,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if let Notification::ChannelMessageMention {
|
||||
message_id,
|
||||
channel_id,
|
||||
|
@ -451,9 +473,9 @@ impl NotificationPanel {
|
|||
} = notification.clone()
|
||||
{
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
cx.window_context().defer(move |cx| {
|
||||
window.defer(cx, move |window, cx| {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
|
||||
if let Some(panel) = workspace.focus_panel::<ChatPanel>(window, cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
panel
|
||||
.select_channel(ChannelId(channel_id), Some(message_id), cx)
|
||||
|
@ -466,7 +488,7 @@ impl NotificationPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_showing_notification(&self, notification: &Notification, cx: &ViewContext<Self>) -> bool {
|
||||
fn is_showing_notification(&self, notification: &Notification, cx: &mut Context<Self>) -> bool {
|
||||
if !self.active {
|
||||
return false;
|
||||
}
|
||||
|
@ -490,16 +512,17 @@ impl NotificationPanel {
|
|||
|
||||
fn on_notification_event(
|
||||
&mut self,
|
||||
_: Model<NotificationStore>,
|
||||
_: &Entity<NotificationStore>,
|
||||
event: &NotificationEvent,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
NotificationEvent::NewNotification { entry } => {
|
||||
if !self.is_showing_notification(&entry.notification, cx) {
|
||||
self.unseen_notifications.push(entry.clone());
|
||||
}
|
||||
self.add_toast(entry, cx);
|
||||
self.add_toast(entry, window, cx);
|
||||
}
|
||||
NotificationEvent::NotificationRemoved { entry }
|
||||
| NotificationEvent::NotificationRead { entry } => {
|
||||
|
@ -516,7 +539,12 @@ impl NotificationPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn add_toast(&mut self, entry: &NotificationEntry, cx: &mut ViewContext<Self>) {
|
||||
fn add_toast(
|
||||
&mut self,
|
||||
entry: &NotificationEntry,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.is_showing_notification(&entry.notification, cx) {
|
||||
return;
|
||||
}
|
||||
|
@ -529,7 +557,7 @@ impl NotificationPanel {
|
|||
let notification_id = entry.id;
|
||||
self.current_notification_toast = Some((
|
||||
notification_id,
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
cx.spawn_in(window, |this, mut cx| async move {
|
||||
cx.background_executor().timer(TOAST_DURATION).await;
|
||||
this.update(&mut cx, |this, cx| this.remove_toast(notification_id, cx))
|
||||
.ok();
|
||||
|
@ -542,8 +570,8 @@ impl NotificationPanel {
|
|||
|
||||
workspace.dismiss_notification(&id, cx);
|
||||
workspace.show_notification(id, cx, |cx| {
|
||||
let workspace = cx.view().downgrade();
|
||||
cx.new_view(|_| NotificationToast {
|
||||
let workspace = cx.model().downgrade();
|
||||
cx.new(|_| NotificationToast {
|
||||
notification_id,
|
||||
actor,
|
||||
text,
|
||||
|
@ -554,7 +582,7 @@ impl NotificationPanel {
|
|||
.ok();
|
||||
}
|
||||
|
||||
fn remove_toast(&mut self, notification_id: u64, cx: &mut ViewContext<Self>) {
|
||||
fn remove_toast(&mut self, notification_id: u64, cx: &mut Context<Self>) {
|
||||
if let Some((current_id, _)) = &self.current_notification_toast {
|
||||
if *current_id == notification_id {
|
||||
self.current_notification_toast.take();
|
||||
|
@ -572,7 +600,8 @@ impl NotificationPanel {
|
|||
&mut self,
|
||||
notification: Notification,
|
||||
response: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.notification_store.update(cx, |store, cx| {
|
||||
store.respond_to_notification(notification, response, cx);
|
||||
|
@ -581,7 +610,7 @@ impl NotificationPanel {
|
|||
}
|
||||
|
||||
impl Render for NotificationPanel {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
v_flex()
|
||||
.size_full()
|
||||
.child(
|
||||
|
@ -611,15 +640,16 @@ impl Render for NotificationPanel {
|
|||
.full_width()
|
||||
.on_click({
|
||||
let client = self.client.clone();
|
||||
move |_, cx| {
|
||||
move |_, window, cx| {
|
||||
let client = client.clone();
|
||||
cx.spawn(move |cx| async move {
|
||||
client
|
||||
.authenticate_and_connect(true, &cx)
|
||||
.log_err()
|
||||
.await;
|
||||
})
|
||||
.detach()
|
||||
window
|
||||
.spawn(cx, move |cx| async move {
|
||||
client
|
||||
.authenticate_and_connect(true, &cx)
|
||||
.log_err()
|
||||
.await;
|
||||
})
|
||||
.detach()
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
@ -648,8 +678,8 @@ impl Render for NotificationPanel {
|
|||
}
|
||||
}
|
||||
|
||||
impl FocusableView for NotificationPanel {
|
||||
fn focus_handle(&self, _: &AppContext) -> FocusHandle {
|
||||
impl Focusable for NotificationPanel {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
@ -662,7 +692,7 @@ impl Panel for NotificationPanel {
|
|||
"NotificationPanel"
|
||||
}
|
||||
|
||||
fn position(&self, cx: &WindowContext) -> DockPosition {
|
||||
fn position(&self, _: &Window, cx: &App) -> DockPosition {
|
||||
NotificationPanelSettings::get_global(cx).dock
|
||||
}
|
||||
|
||||
|
@ -670,7 +700,7 @@ impl Panel for NotificationPanel {
|
|||
matches!(position, DockPosition::Left | DockPosition::Right)
|
||||
}
|
||||
|
||||
fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
|
||||
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
|
||||
settings::update_settings_file::<NotificationPanelSettings>(
|
||||
self.fs.clone(),
|
||||
cx,
|
||||
|
@ -678,18 +708,18 @@ impl Panel for NotificationPanel {
|
|||
);
|
||||
}
|
||||
|
||||
fn size(&self, cx: &WindowContext) -> Pixels {
|
||||
fn size(&self, _: &Window, cx: &App) -> Pixels {
|
||||
self.width
|
||||
.unwrap_or_else(|| NotificationPanelSettings::get_global(cx).default_width)
|
||||
}
|
||||
|
||||
fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
|
||||
fn set_size(&mut self, size: Option<Pixels>, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.width = size;
|
||||
self.serialize(cx);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
|
||||
fn set_active(&mut self, active: bool, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.active = active;
|
||||
|
||||
if self.active {
|
||||
|
@ -702,7 +732,7 @@ impl Panel for NotificationPanel {
|
|||
}
|
||||
}
|
||||
|
||||
fn icon(&self, cx: &WindowContext) -> Option<IconName> {
|
||||
fn icon(&self, _: &Window, cx: &App) -> Option<IconName> {
|
||||
let show_button = NotificationPanelSettings::get_global(cx).button;
|
||||
if !show_button {
|
||||
return None;
|
||||
|
@ -715,11 +745,11 @@ impl Panel for NotificationPanel {
|
|||
Some(IconName::BellDot)
|
||||
}
|
||||
|
||||
fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
|
||||
fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
|
||||
Some("Notification Panel")
|
||||
}
|
||||
|
||||
fn icon_label(&self, cx: &WindowContext) -> Option<String> {
|
||||
fn icon_label(&self, _window: &Window, cx: &App) -> Option<String> {
|
||||
let count = self.notification_store.read(cx).unread_notification_count();
|
||||
if count == 0 {
|
||||
None
|
||||
|
@ -741,21 +771,25 @@ pub struct NotificationToast {
|
|||
notification_id: u64,
|
||||
actor: Option<Arc<User>>,
|
||||
text: String,
|
||||
workspace: WeakView<Workspace>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
}
|
||||
|
||||
impl NotificationToast {
|
||||
fn focus_notification_panel(&self, cx: &mut ViewContext<Self>) {
|
||||
fn focus_notification_panel(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let workspace = self.workspace.clone();
|
||||
let notification_id = self.notification_id;
|
||||
cx.window_context().defer(move |cx| {
|
||||
window.defer(cx, move |window, cx| {
|
||||
workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
if let Some(panel) = workspace.focus_panel::<NotificationPanel>(cx) {
|
||||
if let Some(panel) = workspace.focus_panel::<NotificationPanel>(window, cx) {
|
||||
panel.update(cx, |panel, cx| {
|
||||
let store = panel.notification_store.read(cx);
|
||||
if let Some(entry) = store.notification_for_id(notification_id) {
|
||||
panel.did_click_notification(&entry.clone().notification, cx);
|
||||
panel.did_click_notification(
|
||||
&entry.clone().notification,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -766,7 +800,7 @@ impl NotificationToast {
|
|||
}
|
||||
|
||||
impl Render for NotificationToast {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let user = self.actor.clone();
|
||||
|
||||
h_flex()
|
||||
|
@ -778,10 +812,10 @@ impl Render for NotificationToast {
|
|||
.child(Label::new(self.text.clone()))
|
||||
.child(
|
||||
IconButton::new("close", IconName::Close)
|
||||
.on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
|
||||
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
|
||||
)
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.focus_notification_panel(cx);
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.focus_notification_panel(window, cx);
|
||||
cx.emit(DismissEvent);
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -5,14 +5,14 @@ pub mod project_shared_notification;
|
|||
#[cfg(feature = "stories")]
|
||||
mod stories;
|
||||
|
||||
use gpui::AppContext;
|
||||
use gpui::App;
|
||||
use std::sync::Arc;
|
||||
use workspace::AppState;
|
||||
|
||||
#[cfg(feature = "stories")]
|
||||
pub use stories::*;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
|
||||
incoming_call_notification::init(app_state, cx);
|
||||
project_shared_notification::init(app_state, cx);
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ impl ParentElement for CollabNotification {
|
|||
}
|
||||
|
||||
impl RenderOnce for CollabNotification {
|
||||
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
h_flex()
|
||||
.text_ui(cx)
|
||||
.justify_between()
|
||||
|
|
|
@ -2,14 +2,14 @@ use crate::notification_window_options;
|
|||
use crate::notifications::collab_notification::CollabNotification;
|
||||
use call::{ActiveCall, IncomingCall};
|
||||
use futures::StreamExt;
|
||||
use gpui::{prelude::*, AppContext, WindowHandle};
|
||||
use gpui::{prelude::*, App, WindowHandle};
|
||||
|
||||
use std::sync::{Arc, Weak};
|
||||
use ui::{prelude::*, Button, Label};
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
|
||||
let app_state = Arc::downgrade(app_state);
|
||||
let mut incoming_call = ActiveCall::global(cx).read(cx).incoming();
|
||||
cx.spawn(|mut cx| async move {
|
||||
|
@ -17,8 +17,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
while let Some(incoming_call) = incoming_call.next().await {
|
||||
for window in notification_windows.drain(..) {
|
||||
window
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.remove_window();
|
||||
.update(&mut cx, |_, window, _| {
|
||||
window.remove_window();
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
@ -36,8 +36,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
.log_err()
|
||||
{
|
||||
let window = cx
|
||||
.open_window(options, |cx| {
|
||||
cx.new_view(|_| {
|
||||
.open_window(options, |_, cx| {
|
||||
cx.new(|_| {
|
||||
IncomingCallNotification::new(
|
||||
incoming_call.clone(),
|
||||
app_state.clone(),
|
||||
|
@ -67,14 +67,14 @@ impl IncomingCallNotificationState {
|
|||
Self { call, app_state }
|
||||
}
|
||||
|
||||
fn respond(&self, accept: bool, cx: &mut AppContext) {
|
||||
fn respond(&self, accept: bool, cx: &mut App) {
|
||||
let active_call = ActiveCall::global(cx);
|
||||
if accept {
|
||||
let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
|
||||
let caller_user_id = self.call.calling_user.id;
|
||||
let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
|
||||
let app_state = self.app_state.clone();
|
||||
let cx: &mut AppContext = cx;
|
||||
let cx: &mut App = cx;
|
||||
cx.spawn(|cx| async move {
|
||||
join.await?;
|
||||
if let Some(project_id) = initial_project_id {
|
||||
|
@ -111,19 +111,19 @@ impl IncomingCallNotification {
|
|||
}
|
||||
|
||||
impl Render for IncomingCallNotification {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(window, cx);
|
||||
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
self.state.call.calling_user.avatar_uri.clone(),
|
||||
Button::new("accept", "Accept").on_click({
|
||||
let state = self.state.clone();
|
||||
move |_, cx| state.respond(true, cx)
|
||||
move |_, _, cx| state.respond(true, cx)
|
||||
}),
|
||||
Button::new("decline", "Decline").on_click({
|
||||
let state = self.state.clone();
|
||||
move |_, cx| state.respond(false, cx)
|
||||
move |_, _, cx| state.respond(false, cx)
|
||||
}),
|
||||
)
|
||||
.child(v_flex().overflow_hidden().child(Label::new(format!(
|
||||
|
|
|
@ -3,14 +3,14 @@ use crate::notifications::collab_notification::CollabNotification;
|
|||
use call::{room, ActiveCall};
|
||||
use client::User;
|
||||
use collections::HashMap;
|
||||
use gpui::{AppContext, Size};
|
||||
use gpui::{App, Size};
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use ui::{prelude::*, Button, Label};
|
||||
use util::ResultExt;
|
||||
use workspace::AppState;
|
||||
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
||||
pub fn init(app_state: &Arc<AppState>, cx: &mut App) {
|
||||
let app_state = Arc::downgrade(app_state);
|
||||
let active_call = ActiveCall::global(cx);
|
||||
let mut notification_windows = HashMap::default();
|
||||
|
@ -28,8 +28,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
for screen in cx.displays() {
|
||||
let options = notification_window_options(screen, window_size, cx);
|
||||
let Some(window) = cx
|
||||
.open_window(options, |cx| {
|
||||
cx.new_view(|_| {
|
||||
.open_window(options, |_, cx| {
|
||||
cx.new(|_| {
|
||||
ProjectSharedNotification::new(
|
||||
owner.clone(),
|
||||
*project_id,
|
||||
|
@ -55,8 +55,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
if let Some(windows) = notification_windows.remove(project_id) {
|
||||
for window in windows {
|
||||
window
|
||||
.update(cx, |_, cx| {
|
||||
cx.remove_window();
|
||||
.update(cx, |_, window, _| {
|
||||
window.remove_window();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
@ -67,8 +67,8 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
|
|||
for (_, windows) in notification_windows.drain() {
|
||||
for window in windows {
|
||||
window
|
||||
.update(cx, |_, cx| {
|
||||
cx.remove_window();
|
||||
.update(cx, |_, window, _| {
|
||||
window.remove_window();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
|
@ -101,14 +101,14 @@ impl ProjectSharedNotification {
|
|||
}
|
||||
}
|
||||
|
||||
fn join(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn join(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(app_state) = self.app_state.upgrade() {
|
||||
workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn dismiss(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn dismiss(&mut self, cx: &mut Context<Self>) {
|
||||
if let Some(active_room) =
|
||||
ActiveCall::global(cx).read_with(cx, |call, _| call.room().cloned())
|
||||
{
|
||||
|
@ -122,18 +122,20 @@ impl ProjectSharedNotification {
|
|||
}
|
||||
|
||||
impl Render for ProjectSharedNotification {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(cx);
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let ui_font = theme::setup_ui_font(window, cx);
|
||||
|
||||
div().size_full().font(ui_font).child(
|
||||
CollabNotification::new(
|
||||
self.owner.avatar_uri.clone(),
|
||||
Button::new("open", "Open").on_click(cx.listener(move |this, _event, cx| {
|
||||
Button::new("open", "Open").on_click(cx.listener(move |this, _event, _, cx| {
|
||||
this.join(cx);
|
||||
})),
|
||||
Button::new("dismiss", "Dismiss").on_click(cx.listener(move |this, _event, cx| {
|
||||
this.dismiss(cx);
|
||||
})),
|
||||
Button::new("dismiss", "Dismiss").on_click(cx.listener(
|
||||
move |this, _event, _, cx| {
|
||||
this.dismiss(cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(Label::new(self.owner.github_login.clone()))
|
||||
.child(Label::new(format!(
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::notifications::collab_notification::CollabNotification;
|
|||
pub struct CollabNotificationStory;
|
||||
|
||||
impl Render for CollabNotificationStory {
|
||||
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let window_container = |width, height| div().w(px(width)).h(px(height));
|
||||
|
||||
Story::container()
|
||||
|
|
|
@ -126,7 +126,7 @@ impl Settings for CollaborationPanelSettings {
|
|||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::AppContext,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ impl Settings for ChatPanelSettings {
|
|||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::AppContext,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ impl Settings for NotificationPanelSettings {
|
|||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::AppContext,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ impl Settings for MessageEditorSettings {
|
|||
|
||||
fn load(
|
||||
sources: SettingsSources<Self::FileContent>,
|
||||
_: &mut gpui::AppContext,
|
||||
_: &mut gpui::App,
|
||||
) -> anyhow::Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue