remote projects per user (#10594)
Release Notes: - Made remote projects per-user instead of per-channel. If you'd like to be part of the remote development alpha, please email hi@zed.dev. --------- Co-authored-by: Bennet Bo Fenner <53836821+bennetbo@users.noreply.github.com> Co-authored-by: Bennet <bennetbo@gmx.de> Co-authored-by: Nate Butler <1714999+iamnbutler@users.noreply.github.com> Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
parent
8ae4c3277f
commit
e0c83a1d32
56 changed files with 2807 additions and 1625 deletions
|
@ -39,7 +39,6 @@ db.workspace = true
|
|||
editor.workspace = true
|
||||
emojis.workspace = true
|
||||
extensions_ui.workspace = true
|
||||
feature_flags.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
|
|
|
@ -305,10 +305,6 @@ impl ChannelView {
|
|||
});
|
||||
}
|
||||
ChannelBufferEvent::BufferEdited => {
|
||||
// Emit the edited event on the editor context so that other views can update it's state (e.g. markdown preview)
|
||||
self.editor.update(cx, |_, cx| {
|
||||
cx.emit(EditorEvent::Edited);
|
||||
});
|
||||
if self.editor.read(cx).is_focused(cx) {
|
||||
self.acknowledge_buffer_version(cx);
|
||||
} else {
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
mod channel_modal;
|
||||
mod contact_finder;
|
||||
mod dev_server_modal;
|
||||
|
||||
use self::channel_modal::ChannelModal;
|
||||
use self::dev_server_modal::DevServerModal;
|
||||
use crate::{
|
||||
channel_view::ChannelView, chat_panel::ChatPanel, face_pile::FacePile,
|
||||
CollaborationPanelSettings,
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use channel::{Channel, ChannelEvent, ChannelStore, RemoteProject};
|
||||
use channel::{Channel, ChannelEvent, ChannelStore};
|
||||
use client::{ChannelId, Client, Contact, ProjectId, User, UserStore};
|
||||
use contact_finder::ContactFinder;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
use feature_flags::{self, FeatureFlagAppExt};
|
||||
use fuzzy::{match_strings, StringMatchCandidate};
|
||||
use gpui::{
|
||||
actions, anchored, canvas, deferred, div, fill, list, point, prelude::*, px, AnyElement,
|
||||
|
@ -27,7 +24,7 @@ use gpui::{
|
|||
use menu::{Cancel, Confirm, SecondaryConfirm, SelectNext, SelectPrev};
|
||||
use project::{Fs, Project};
|
||||
use rpc::{
|
||||
proto::{self, ChannelVisibility, DevServerStatus, PeerId},
|
||||
proto::{self, ChannelVisibility, PeerId},
|
||||
ErrorCode, ErrorExt,
|
||||
};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
@ -191,7 +188,6 @@ enum ListEntry {
|
|||
id: ProjectId,
|
||||
name: SharedString,
|
||||
},
|
||||
RemoteProject(channel::RemoteProject),
|
||||
Contact {
|
||||
contact: Arc<Contact>,
|
||||
calling: bool,
|
||||
|
@ -282,23 +278,10 @@ impl CollabPanel {
|
|||
.push(cx.observe(&this.user_store, |this, _, cx| {
|
||||
this.update_entries(true, cx)
|
||||
}));
|
||||
let mut has_opened = false;
|
||||
this.subscriptions.push(cx.observe(
|
||||
&this.channel_store,
|
||||
move |this, channel_store, cx| {
|
||||
if !has_opened {
|
||||
if !channel_store
|
||||
.read(cx)
|
||||
.dev_servers_for_id(ChannelId(1))
|
||||
.is_empty()
|
||||
{
|
||||
this.manage_remote_projects(ChannelId(1), cx);
|
||||
has_opened = true;
|
||||
}
|
||||
}
|
||||
this.subscriptions
|
||||
.push(cx.observe(&this.channel_store, move |this, _, cx| {
|
||||
this.update_entries(true, cx)
|
||||
},
|
||||
));
|
||||
}));
|
||||
this.subscriptions
|
||||
.push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx)));
|
||||
this.subscriptions.push(cx.subscribe(
|
||||
|
@ -586,7 +569,6 @@ impl CollabPanel {
|
|||
}
|
||||
|
||||
let hosted_projects = channel_store.projects_for_id(channel.id);
|
||||
let remote_projects = channel_store.remote_projects_for_id(channel.id);
|
||||
let has_children = channel_store
|
||||
.channel_at_index(mat.candidate_id + 1)
|
||||
.map_or(false, |next_channel| {
|
||||
|
@ -624,12 +606,6 @@ impl CollabPanel {
|
|||
for (name, id) in hosted_projects {
|
||||
self.entries.push(ListEntry::HostedProject { id, name });
|
||||
}
|
||||
|
||||
if cx.has_flag::<feature_flags::Remoting>() {
|
||||
for remote_project in remote_projects {
|
||||
self.entries.push(ListEntry::RemoteProject(remote_project));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1089,59 +1065,6 @@ impl CollabPanel {
|
|||
.tooltip(move |cx| Tooltip::text("Open Project", cx))
|
||||
}
|
||||
|
||||
fn render_remote_project(
|
||||
&self,
|
||||
remote_project: &RemoteProject,
|
||||
is_selected: bool,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let id = remote_project.id;
|
||||
let name = remote_project.name.clone();
|
||||
let maybe_project_id = remote_project.project_id;
|
||||
|
||||
let dev_server = self
|
||||
.channel_store
|
||||
.read(cx)
|
||||
.find_dev_server_by_id(remote_project.dev_server_id);
|
||||
|
||||
let tooltip_text = SharedString::from(match dev_server {
|
||||
Some(dev_server) => format!("Open Remote Project ({})", dev_server.name),
|
||||
None => "Open Remote Project".to_string(),
|
||||
});
|
||||
|
||||
let dev_server_is_online = dev_server.map(|s| s.status) == Some(DevServerStatus::Online);
|
||||
|
||||
let dev_server_text_color = if dev_server_is_online {
|
||||
Color::Default
|
||||
} else {
|
||||
Color::Disabled
|
||||
};
|
||||
|
||||
ListItem::new(ElementId::NamedInteger(
|
||||
"remote-project".into(),
|
||||
id.0 as usize,
|
||||
))
|
||||
.indent_level(2)
|
||||
.indent_step_size(px(20.))
|
||||
.selected(is_selected)
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
//TODO display error message if dev server is offline
|
||||
if dev_server_is_online {
|
||||
if let Some(project_id) = maybe_project_id {
|
||||
this.join_remote_project(project_id, cx);
|
||||
}
|
||||
}
|
||||
}))
|
||||
.start_slot(
|
||||
h_flex()
|
||||
.relative()
|
||||
.gap_1()
|
||||
.child(IconButton::new(0, IconName::FileTree).icon_color(dev_server_text_color)),
|
||||
)
|
||||
.child(Label::new(name.clone()).color(dev_server_text_color))
|
||||
.tooltip(move |cx| Tooltip::text(tooltip_text.clone(), cx))
|
||||
}
|
||||
|
||||
fn has_subchannels(&self, ix: usize) -> bool {
|
||||
self.entries.get(ix).map_or(false, |entry| {
|
||||
if let ListEntry::Channel { has_children, .. } = entry {
|
||||
|
@ -1343,24 +1266,11 @@ impl CollabPanel {
|
|||
}
|
||||
|
||||
if self.channel_store.read(cx).is_root_channel(channel_id) {
|
||||
context_menu = context_menu
|
||||
.separator()
|
||||
.entry(
|
||||
"Manage Members",
|
||||
None,
|
||||
cx.handler_for(&this, move |this, cx| {
|
||||
this.manage_members(channel_id, cx)
|
||||
}),
|
||||
)
|
||||
.when(cx.has_flag::<feature_flags::Remoting>(), |context_menu| {
|
||||
context_menu.entry(
|
||||
"Manage Remote Projects",
|
||||
None,
|
||||
cx.handler_for(&this, move |this, cx| {
|
||||
this.manage_remote_projects(channel_id, cx)
|
||||
}),
|
||||
)
|
||||
})
|
||||
context_menu = context_menu.separator().entry(
|
||||
"Manage Members",
|
||||
None,
|
||||
cx.handler_for(&this, move |this, cx| this.manage_members(channel_id, cx)),
|
||||
)
|
||||
} else {
|
||||
context_menu = context_menu.entry(
|
||||
"Move this channel",
|
||||
|
@ -1624,12 +1534,6 @@ impl CollabPanel {
|
|||
} => {
|
||||
// todo()
|
||||
}
|
||||
ListEntry::RemoteProject(project) => {
|
||||
if let Some(project_id) = project.project_id {
|
||||
self.join_remote_project(project_id, cx)
|
||||
}
|
||||
}
|
||||
|
||||
ListEntry::OutgoingRequest(_) => {}
|
||||
ListEntry::ChannelEditor { .. } => {}
|
||||
}
|
||||
|
@ -1801,18 +1705,6 @@ impl CollabPanel {
|
|||
self.show_channel_modal(channel_id, channel_modal::Mode::ManageMembers, cx);
|
||||
}
|
||||
|
||||
fn manage_remote_projects(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||
let channel_store = self.channel_store.clone();
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
workspace.toggle_modal(cx, |cx| {
|
||||
DevServerModal::new(channel_store.clone(), channel_id, cx)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn remove_selected_channel(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
|
||||
if let Some(channel) = self.selected_channel() {
|
||||
self.remove_channel(channel.id, cx)
|
||||
|
@ -2113,18 +2005,6 @@ impl CollabPanel {
|
|||
.detach_and_prompt_err("Failed to join channel", cx, |_, _| None)
|
||||
}
|
||||
|
||||
fn join_remote_project(&mut self, project_id: ProjectId, cx: &mut ViewContext<Self>) {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let app_state = workspace.read(cx).app_state().clone();
|
||||
workspace::join_remote_project(project_id, app_state, cx).detach_and_prompt_err(
|
||||
"Failed to join project",
|
||||
cx,
|
||||
|_, _| None,
|
||||
)
|
||||
}
|
||||
|
||||
fn join_channel_chat(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
|
@ -2260,9 +2140,6 @@ impl CollabPanel {
|
|||
ListEntry::HostedProject { id, name } => self
|
||||
.render_channel_project(*id, name, is_selected, cx)
|
||||
.into_any_element(),
|
||||
ListEntry::RemoteProject(remote_project) => self
|
||||
.render_remote_project(remote_project, is_selected, cx)
|
||||
.into_any_element(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3005,11 +2882,6 @@ impl PartialEq for ListEntry {
|
|||
return id == other_id;
|
||||
}
|
||||
}
|
||||
ListEntry::RemoteProject(project) => {
|
||||
if let ListEntry::RemoteProject(other) = other {
|
||||
return project.id == other.id;
|
||||
}
|
||||
}
|
||||
ListEntry::ChannelNotes { channel_id } => {
|
||||
if let ListEntry::ChannelNotes {
|
||||
channel_id: other_id,
|
||||
|
|
|
@ -1,622 +0,0 @@
|
|||
use channel::{ChannelStore, DevServer, RemoteProject};
|
||||
use client::{ChannelId, DevServerId, RemoteProjectId};
|
||||
use editor::Editor;
|
||||
use gpui::{
|
||||
AppContext, ClipboardItem, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
|
||||
ScrollHandle, Task, View, ViewContext,
|
||||
};
|
||||
use rpc::proto::{self, CreateDevServerResponse, DevServerStatus};
|
||||
use ui::{prelude::*, Indicator, List, ListHeader, ModalContent, ModalHeader, Tooltip};
|
||||
use util::ResultExt;
|
||||
use workspace::ModalView;
|
||||
|
||||
pub struct DevServerModal {
|
||||
mode: Mode,
|
||||
focus_handle: FocusHandle,
|
||||
scroll_handle: ScrollHandle,
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
remote_project_name_editor: View<Editor>,
|
||||
remote_project_path_editor: View<Editor>,
|
||||
dev_server_name_editor: View<Editor>,
|
||||
_subscriptions: [gpui::Subscription; 2],
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CreateDevServer {
|
||||
creating: Option<Task<()>>,
|
||||
dev_server: Option<CreateDevServerResponse>,
|
||||
}
|
||||
|
||||
struct CreateRemoteProject {
|
||||
dev_server_id: DevServerId,
|
||||
creating: Option<Task<()>>,
|
||||
remote_project: Option<proto::RemoteProject>,
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
Default,
|
||||
CreateRemoteProject(CreateRemoteProject),
|
||||
CreateDevServer(CreateDevServer),
|
||||
}
|
||||
|
||||
impl DevServerModal {
|
||||
pub fn new(
|
||||
channel_store: Model<ChannelStore>,
|
||||
channel_id: ChannelId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let name_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||
let path_editor = cx.new_view(|cx| Editor::single_line(cx));
|
||||
let dev_server_name_editor = cx.new_view(|cx| {
|
||||
let mut editor = Editor::single_line(cx);
|
||||
editor.set_placeholder_text("Dev server name", cx);
|
||||
editor
|
||||
});
|
||||
|
||||
let focus_handle = cx.focus_handle();
|
||||
|
||||
let subscriptions = [
|
||||
cx.observe(&channel_store, |_, _, cx| {
|
||||
cx.notify();
|
||||
}),
|
||||
cx.on_focus_out(&focus_handle, |_, _cx| { /* cx.emit(DismissEvent) */ }),
|
||||
];
|
||||
|
||||
Self {
|
||||
mode: Mode::Default,
|
||||
focus_handle,
|
||||
scroll_handle: ScrollHandle::new(),
|
||||
channel_store,
|
||||
channel_id,
|
||||
remote_project_name_editor: name_editor,
|
||||
remote_project_path_editor: path_editor,
|
||||
dev_server_name_editor,
|
||||
_subscriptions: subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_remote_project(
|
||||
&mut self,
|
||||
dev_server_id: DevServerId,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let channel_id = self.channel_id;
|
||||
let name = self
|
||||
.remote_project_name_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
let path = self
|
||||
.remote_project_path_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if name == "" {
|
||||
return;
|
||||
}
|
||||
if path == "" {
|
||||
return;
|
||||
}
|
||||
|
||||
let create = self.channel_store.update(cx, |store, cx| {
|
||||
store.create_remote_project(channel_id, dev_server_id, name, path, cx)
|
||||
});
|
||||
|
||||
let task = cx.spawn(|this, mut cx| async move {
|
||||
let result = create.await;
|
||||
if let Err(e) = &result {
|
||||
cx.prompt(
|
||||
gpui::PromptLevel::Critical,
|
||||
"Failed to create project",
|
||||
Some(&format!("{:?}. Please try again.", e)),
|
||||
&["Ok"],
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
}
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.mode = Mode::CreateRemoteProject(CreateRemoteProject {
|
||||
dev_server_id,
|
||||
creating: None,
|
||||
remote_project: result.ok().and_then(|r| r.remote_project),
|
||||
});
|
||||
})
|
||||
.log_err();
|
||||
});
|
||||
|
||||
self.mode = Mode::CreateRemoteProject(CreateRemoteProject {
|
||||
dev_server_id,
|
||||
creating: Some(task),
|
||||
remote_project: None,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn create_dev_server(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let name = self
|
||||
.dev_server_name_editor
|
||||
.read(cx)
|
||||
.text(cx)
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
if name == "" {
|
||||
return;
|
||||
}
|
||||
|
||||
let dev_server = self.channel_store.update(cx, |store, cx| {
|
||||
store.create_dev_server(self.channel_id, name.clone(), cx)
|
||||
});
|
||||
|
||||
let task = cx.spawn(|this, mut cx| async move {
|
||||
match dev_server.await {
|
||||
Ok(dev_server) => {
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: None,
|
||||
dev_server: Some(dev_server),
|
||||
});
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
Err(e) => {
|
||||
cx.prompt(
|
||||
gpui::PromptLevel::Critical,
|
||||
"Failed to create server",
|
||||
Some(&format!("{:?}. Please try again.", e)),
|
||||
&["Ok"],
|
||||
)
|
||||
.await
|
||||
.log_err();
|
||||
this.update(&mut cx, |this, _| {
|
||||
this.mode = Mode::CreateDevServer(Default::default());
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
self.mode = Mode::CreateDevServer(CreateDevServer {
|
||||
creating: Some(task),
|
||||
dev_server: None,
|
||||
});
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn cancel(&mut self, _: &menu::Cancel, cx: &mut ViewContext<Self>) {
|
||||
match self.mode {
|
||||
Mode::Default => cx.emit(DismissEvent),
|
||||
Mode::CreateRemoteProject(_) | Mode::CreateDevServer(_) => {
|
||||
self.mode = Mode::Default;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_dev_server(
|
||||
&mut self,
|
||||
dev_server: &DevServer,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let dev_server_id = dev_server.id;
|
||||
let status = dev_server.status;
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.child(
|
||||
h_flex()
|
||||
.group("dev-server")
|
||||
.justify_between()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.id(("status", dev_server.id.0))
|
||||
.relative()
|
||||
.child(Icon::new(IconName::Server).size(IconSize::Small))
|
||||
.child(
|
||||
div().absolute().bottom_0().left(rems_from_px(8.0)).child(
|
||||
Indicator::dot().color(match status {
|
||||
DevServerStatus::Online => Color::Created,
|
||||
DevServerStatus::Offline => Color::Deleted,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
match status {
|
||||
DevServerStatus::Online => "Online",
|
||||
DevServerStatus::Offline => "Offline",
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(dev_server.name.clone())
|
||||
.child(
|
||||
h_flex()
|
||||
.visible_on_hover("dev-server")
|
||||
.gap_1()
|
||||
.child(
|
||||
IconButton::new("edit-dev-server", IconName::Pencil)
|
||||
.disabled(true) //TODO implement this on the collab side
|
||||
.tooltip(|cx| {
|
||||
Tooltip::text("Coming Soon - Edit dev server", cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
IconButton::new("remove-dev-server", IconName::Trash)
|
||||
.disabled(true) //TODO implement this on the collab side
|
||||
.tooltip(|cx| {
|
||||
Tooltip::text("Coming Soon - Remove dev server", cx)
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex().gap_1().child(
|
||||
IconButton::new("add-remote-project", IconName::Plus)
|
||||
.tooltip(|cx| Tooltip::text("Add a remote project", cx))
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
this.mode = Mode::CreateRemoteProject(CreateRemoteProject {
|
||||
dev_server_id,
|
||||
creating: None,
|
||||
remote_project: None,
|
||||
});
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.w_full()
|
||||
.bg(cx.theme().colors().title_bar_background)
|
||||
.border()
|
||||
.border_color(cx.theme().colors().border_variant)
|
||||
.rounded_md()
|
||||
.my_1()
|
||||
.py_0p5()
|
||||
.px_3()
|
||||
.child(
|
||||
List::new().empty_message("No projects.").children(
|
||||
channel_store
|
||||
.remote_projects_for_id(dev_server.channel_id)
|
||||
.iter()
|
||||
.filter_map(|remote_project| {
|
||||
if remote_project.dev_server_id == dev_server.id {
|
||||
Some(self.render_remote_project(remote_project, cx))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
// .child(div().ml_8().child(
|
||||
// Button::new(("add-project", dev_server_id.0), "Add Project").on_click(cx.listener(
|
||||
// move |this, _, cx| {
|
||||
// this.mode = Mode::CreateRemoteProject(CreateRemoteProject {
|
||||
// dev_server_id,
|
||||
// creating: None,
|
||||
// remote_project: None,
|
||||
// });
|
||||
// cx.notify();
|
||||
// },
|
||||
// )),
|
||||
// ))
|
||||
}
|
||||
|
||||
fn render_remote_project(
|
||||
&mut self,
|
||||
project: &RemoteProject,
|
||||
_: &mut ViewContext<Self>,
|
||||
) -> impl IntoElement {
|
||||
h_flex()
|
||||
.gap_2()
|
||||
.child(Icon::new(IconName::FileTree))
|
||||
.child(Label::new(project.name.clone()))
|
||||
.child(Label::new(format!("({})", project.path.clone())).color(Color::Muted))
|
||||
}
|
||||
|
||||
fn render_create_dev_server(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let Mode::CreateDevServer(CreateDevServer {
|
||||
creating,
|
||||
dev_server,
|
||||
}) = &self.mode
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
self.dev_server_name_editor.update(cx, |editor, _| {
|
||||
editor.set_read_only(creating.is_some() || dev_server.is_some())
|
||||
});
|
||||
v_flex()
|
||||
.px_1()
|
||||
.pt_0p5()
|
||||
.gap_px()
|
||||
.child(
|
||||
v_flex().py_0p5().px_1().child(
|
||||
h_flex()
|
||||
.px_1()
|
||||
.py_0p5()
|
||||
.child(
|
||||
IconButton::new("back", IconName::ArrowLeft)
|
||||
.style(ButtonStyle::Transparent)
|
||||
.on_click(cx.listener(|this, _: &gpui::ClickEvent, cx| {
|
||||
this.mode = Mode::Default;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(Headline::new("Register dev server")),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.ml_5()
|
||||
.gap_2()
|
||||
.child("Name")
|
||||
.child(self.dev_server_name_editor.clone())
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Confirm, cx| this.create_dev_server(cx)),
|
||||
)
|
||||
.when(creating.is_none() && dev_server.is_none(), |div| {
|
||||
div.child(
|
||||
Button::new("create-dev-server", "Create").on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
this.create_dev_server(cx);
|
||||
},
|
||||
)),
|
||||
)
|
||||
})
|
||||
.when(creating.is_some() && dev_server.is_none(), |div| {
|
||||
div.child(Button::new("create-dev-server", "Creating...").disabled(true))
|
||||
}),
|
||||
)
|
||||
.when_some(dev_server.clone(), |div, dev_server| {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let status = channel_store
|
||||
.find_dev_server_by_id(DevServerId(dev_server.dev_server_id))
|
||||
.map(|server| server.status)
|
||||
.unwrap_or(DevServerStatus::Offline);
|
||||
let instructions = SharedString::from(format!(
|
||||
"zed --dev-server-token {}",
|
||||
dev_server.access_token
|
||||
));
|
||||
div.child(
|
||||
v_flex()
|
||||
.ml_8()
|
||||
.gap_2()
|
||||
.child(Label::new(format!(
|
||||
"Please log into `{}` and run:",
|
||||
dev_server.name
|
||||
)))
|
||||
.child(instructions.clone())
|
||||
.child(
|
||||
IconButton::new("copy-access-token", IconName::Copy)
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
cx.write_to_clipboard(ClipboardItem::new(
|
||||
instructions.to_string(),
|
||||
))
|
||||
}))
|
||||
.icon_size(IconSize::Small)
|
||||
.tooltip(|cx| Tooltip::text("Copy access token", cx)),
|
||||
)
|
||||
.when(status == DevServerStatus::Offline, |this| {
|
||||
this.child(Label::new("Waiting for connection..."))
|
||||
})
|
||||
.when(status == DevServerStatus::Online, |this| {
|
||||
this.child(Label::new("Connection established! 🎊")).child(
|
||||
Button::new("done", "Done").on_click(cx.listener(|this, _, cx| {
|
||||
this.mode = Mode::Default;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_default(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let dev_servers = channel_store.dev_servers_for_id(self.channel_id);
|
||||
// let dev_servers = Vec::new();
|
||||
|
||||
v_flex()
|
||||
.id("scroll-container")
|
||||
.h_full()
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.scroll_handle)
|
||||
.px_1()
|
||||
.pt_0p5()
|
||||
.gap_px()
|
||||
.child(
|
||||
ModalHeader::new("Manage Remote Project")
|
||||
.child(Headline::new("Remote Projects").size(HeadlineSize::Small)),
|
||||
)
|
||||
.child(
|
||||
ModalContent::new().child(
|
||||
List::new()
|
||||
.empty_message("No dev servers registered.")
|
||||
.header(Some(
|
||||
ListHeader::new("Dev Servers").end_slot(
|
||||
Button::new("register-dev-server-button", "New Server")
|
||||
.icon(IconName::Plus)
|
||||
.icon_position(IconPosition::Start)
|
||||
.tooltip(|cx| Tooltip::text("Register a new dev server", cx))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
this.mode = Mode::CreateDevServer(Default::default());
|
||||
this.dev_server_name_editor
|
||||
.read(cx)
|
||||
.focus_handle(cx)
|
||||
.focus(cx);
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
))
|
||||
.children(dev_servers.iter().map(|dev_server| {
|
||||
self.render_dev_server(dev_server, cx).into_any_element()
|
||||
})),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_create_project(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let Mode::CreateRemoteProject(CreateRemoteProject {
|
||||
dev_server_id,
|
||||
creating,
|
||||
remote_project,
|
||||
}) = &self.mode
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let (dev_server_name, dev_server_status) = channel_store
|
||||
.find_dev_server_by_id(*dev_server_id)
|
||||
.map(|server| (server.name.clone(), server.status))
|
||||
.unwrap_or((SharedString::from(""), DevServerStatus::Offline));
|
||||
v_flex()
|
||||
.px_1()
|
||||
.pt_0p5()
|
||||
.gap_px()
|
||||
.child(
|
||||
ModalHeader::new("Manage Remote Project")
|
||||
.child(Headline::new("Manage Remote Projects")),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.py_0p5()
|
||||
.px_1()
|
||||
.child(div().px_1().py_0p5().child(
|
||||
IconButton::new("back", IconName::ArrowLeft).on_click(cx.listener(
|
||||
|this, _, cx| {
|
||||
this.mode = Mode::Default;
|
||||
cx.notify()
|
||||
},
|
||||
)),
|
||||
))
|
||||
.child("Add Project..."),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.ml_5()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.id(("status", dev_server_id.0))
|
||||
.relative()
|
||||
.child(Icon::new(IconName::Server))
|
||||
.child(div().absolute().bottom_0().left(rems_from_px(12.0)).child(
|
||||
Indicator::dot().color(match dev_server_status {
|
||||
DevServerStatus::Online => Color::Created,
|
||||
DevServerStatus::Offline => Color::Deleted,
|
||||
}),
|
||||
))
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
match dev_server_status {
|
||||
DevServerStatus::Online => "Online",
|
||||
DevServerStatus::Offline => "Offline",
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.child(dev_server_name.clone()),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.ml_5()
|
||||
.gap_2()
|
||||
.child("Name")
|
||||
.child(self.remote_project_name_editor.clone())
|
||||
.on_action(cx.listener(|this, _: &menu::Confirm, cx| {
|
||||
cx.focus_view(&this.remote_project_path_editor)
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.ml_5()
|
||||
.gap_2()
|
||||
.child("Path")
|
||||
.child(self.remote_project_path_editor.clone())
|
||||
.on_action(
|
||||
cx.listener(|this, _: &menu::Confirm, cx| this.create_dev_server(cx)),
|
||||
)
|
||||
.when(creating.is_none() && remote_project.is_none(), |div| {
|
||||
div.child(Button::new("create-remote-server", "Create").on_click({
|
||||
let dev_server_id = *dev_server_id;
|
||||
cx.listener(move |this, _, cx| {
|
||||
this.create_remote_project(dev_server_id, cx)
|
||||
})
|
||||
}))
|
||||
})
|
||||
.when(creating.is_some(), |div| {
|
||||
div.child(Button::new("create-dev-server", "Creating...").disabled(true))
|
||||
}),
|
||||
)
|
||||
.when_some(remote_project.clone(), |div, remote_project| {
|
||||
let channel_store = self.channel_store.read(cx);
|
||||
let status = channel_store
|
||||
.find_remote_project_by_id(RemoteProjectId(remote_project.id))
|
||||
.map(|project| {
|
||||
if project.project_id.is_some() {
|
||||
DevServerStatus::Online
|
||||
} else {
|
||||
DevServerStatus::Offline
|
||||
}
|
||||
})
|
||||
.unwrap_or(DevServerStatus::Offline);
|
||||
div.child(
|
||||
v_flex()
|
||||
.ml_5()
|
||||
.ml_8()
|
||||
.gap_2()
|
||||
.when(status == DevServerStatus::Offline, |this| {
|
||||
this.child(Label::new("Waiting for project..."))
|
||||
})
|
||||
.when(status == DevServerStatus::Online, |this| {
|
||||
this.child(Label::new("Project online! 🎊")).child(
|
||||
Button::new("done", "Done").on_click(cx.listener(|this, _, cx| {
|
||||
this.mode = Mode::Default;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
}),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl ModalView for DevServerModal {}
|
||||
|
||||
impl FocusableView for DevServerModal {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<DismissEvent> for DevServerModal {}
|
||||
|
||||
impl Render for DevServerModal {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
div()
|
||||
.track_focus(&self.focus_handle)
|
||||
.elevation_3(cx)
|
||||
.key_context("DevServerModal")
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.pb_4()
|
||||
.w(rems(34.))
|
||||
.min_h(rems(20.))
|
||||
.max_h(rems(40.))
|
||||
.child(match &self.mode {
|
||||
Mode::Default => self.render_default(cx).into_any_element(),
|
||||
Mode::CreateRemoteProject(_) => self.render_create_project(cx).into_any_element(),
|
||||
Mode::CreateDevServer(_) => self.render_create_dev_server(cx).into_any_element(),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -171,44 +171,48 @@ impl Render for CollabTitlebarItem {
|
|||
let room = room.read(cx);
|
||||
let project = self.project.read(cx);
|
||||
let is_local = project.is_local();
|
||||
let is_shared = is_local && project.is_shared();
|
||||
let is_remote_project = project.remote_project_id().is_some();
|
||||
let is_shared = (is_local || is_remote_project) && project.is_shared();
|
||||
let is_muted = room.is_muted();
|
||||
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||
let is_screen_sharing = room.is_screen_sharing();
|
||||
let can_use_microphone = room.can_use_microphone();
|
||||
let can_share_projects = room.can_share_projects();
|
||||
|
||||
this.when(is_local && can_share_projects, |this| {
|
||||
this.child(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
if is_shared { "Unshare" } else { "Share" },
|
||||
)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if is_shared {
|
||||
"Stop sharing project with call participants"
|
||||
} else {
|
||||
"Share project with call participants"
|
||||
},
|
||||
cx,
|
||||
this.when(
|
||||
(is_local || is_remote_project) && can_share_projects,
|
||||
|this| {
|
||||
this.child(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
if is_shared { "Unshare" } else { "Share" },
|
||||
)
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(is_shared)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
if is_shared {
|
||||
this.unshare_project(&Default::default(), cx);
|
||||
} else {
|
||||
this.share_project(&Default::default(), cx);
|
||||
}
|
||||
},
|
||||
)),
|
||||
)
|
||||
})
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::text(
|
||||
if is_shared {
|
||||
"Stop sharing project with call participants"
|
||||
} else {
|
||||
"Share project with call participants"
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.style(ButtonStyle::Subtle)
|
||||
.selected_style(ButtonStyle::Tinted(TintColor::Accent))
|
||||
.selected(is_shared)
|
||||
.label_size(LabelSize::Small)
|
||||
.on_click(cx.listener(
|
||||
move |this, _, cx| {
|
||||
if is_shared {
|
||||
this.unshare_project(&Default::default(), cx);
|
||||
} else {
|
||||
this.share_project(&Default::default(), cx);
|
||||
}
|
||||
},
|
||||
)),
|
||||
)
|
||||
},
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.child(
|
||||
|
@ -406,7 +410,7 @@ impl CollabTitlebarItem {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl Element {
|
||||
pub fn render_project_name(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let name = {
|
||||
let mut names = self.project.read(cx).visible_worktrees(cx).map(|worktree| {
|
||||
let worktree = worktree.read(cx);
|
||||
|
@ -423,15 +427,26 @@ impl CollabTitlebarItem {
|
|||
};
|
||||
|
||||
let workspace = self.workspace.clone();
|
||||
popover_menu("project_name_trigger")
|
||||
.trigger(
|
||||
Button::new("project_name_trigger", name)
|
||||
.when(!is_project_selected, |b| b.color(Color::Muted))
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.tooltip(move |cx| Tooltip::text("Recent Projects", cx)),
|
||||
)
|
||||
.menu(move |cx| Some(Self::render_project_popover(workspace.clone(), cx)))
|
||||
Button::new("project_name_trigger", name)
|
||||
.when(!is_project_selected, |b| b.color(Color::Muted))
|
||||
.style(ButtonStyle::Subtle)
|
||||
.label_size(LabelSize::Small)
|
||||
.tooltip(move |cx| {
|
||||
Tooltip::for_action(
|
||||
"Recent Projects",
|
||||
&recent_projects::OpenRecent {
|
||||
create_new_window: false,
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.on_click(cx.listener(move |_, _, cx| {
|
||||
if let Some(workspace) = workspace.upgrade() {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
RecentProjects::open(workspace, false, cx);
|
||||
})
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn render_project_branch(&self, cx: &mut ViewContext<Self>) -> Option<impl Element> {
|
||||
|
@ -607,17 +622,6 @@ impl CollabTitlebarItem {
|
|||
Some(view)
|
||||
}
|
||||
|
||||
pub fn render_project_popover(
|
||||
workspace: WeakView<Workspace>,
|
||||
cx: &mut WindowContext<'_>,
|
||||
) -> View<RecentProjects> {
|
||||
let view = RecentProjects::open_popover(workspace, cx);
|
||||
|
||||
let focus_handle = view.focus_handle(cx);
|
||||
cx.focus(&focus_handle);
|
||||
view
|
||||
}
|
||||
|
||||
fn render_connection_status(
|
||||
&self,
|
||||
status: &client::Status,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue