Detailed changes
@@ -1254,6 +1254,31 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
+[[package]]
+name = "channels"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "client",
+ "collections",
+ "context_menu",
+ "db",
+ "editor",
+ "futures 0.3.28",
+ "gpui",
+ "log",
+ "menu",
+ "project",
+ "schemars",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "settings",
+ "theme",
+ "util",
+ "workspace",
+]
+
[[package]]
name = "chrono"
version = "0.4.26"
@@ -9857,6 +9882,7 @@ dependencies = [
"backtrace",
"breadcrumbs",
"call",
+ "channels",
"chrono",
"cli",
"client",
@@ -6,6 +6,7 @@ members = [
"crates/auto_update",
"crates/breadcrumbs",
"crates/call",
+ "crates/channels",
"crates/cli",
"crates/client",
"crates/clock",
@@ -122,6 +122,12 @@
// Amount of indentation for nested items.
"indent_size": 20
},
+ "channels_panel": {
+ // Where to dock channels panel. Can be 'left' or 'right'.
+ "dock": "left",
+ // Default width of the channels panel.
+ "default_width": 240
+ },
"assistant": {
// Where to dock the assistant. Can be 'left', 'right' or 'bottom'.
"dock": "right",
@@ -0,0 +1,38 @@
+[package]
+name = "channels"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[lib]
+path = "src/channels.rs"
+doctest = false
+
+[dependencies]
+collections = { path = "../collections" }
+context_menu = { path = "../context_menu" }
+client = { path = "../client" }
+db = { path = "../db" }
+editor = { path = "../editor" }
+gpui = { path = "../gpui" }
+project = { path = "../project" }
+theme = { path = "../theme" }
+settings = { path = "../settings" }
+workspace = { path = "../workspace" }
+menu = { path = "../menu" }
+util = { path = "../util" }
+
+log.workspace = true
+anyhow.workspace = true
+schemars.workspace = true
+serde_json.workspace = true
+serde.workspace = true
+serde_derive.workspace = true
+futures.workspace = true
+
+[dev-dependencies]
+client = { path = "../client", features = ["test-support"] }
+editor = { path = "../editor", features = ["test-support"] }
+gpui = { path = "../gpui", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }
+serde_json.workspace = true
@@ -0,0 +1,103 @@
+mod channels_panel;
+mod channels_panel_settings;
+
+pub use channels_panel::*;
+use gpui::{AppContext, Entity};
+
+use std::sync::Arc;
+
+use client::Client;
+
+pub fn init(client: Arc<Client>, cx: &mut AppContext) {
+ let channels = cx.add_model(|cx| Channels::new(client, cx));
+ cx.set_global(channels);
+ channels_panel::init(cx);
+}
+
+#[derive(Debug, Clone)]
+struct Channel {
+ id: u64,
+ name: String,
+ sub_channels: Vec<Channel>,
+ _room: Option<()>,
+}
+
+impl Channel {
+ fn new(id: u64, name: impl AsRef<str>, members: Vec<Channel>) -> Channel {
+ Channel {
+ name: name.as_ref().to_string(),
+ id,
+ sub_channels: members,
+ _room: None,
+ }
+ }
+
+ fn members(&self) -> &[Channel] {
+ &self.sub_channels
+ }
+
+ fn name(&self) -> &str {
+ &self.name
+ }
+}
+
+struct Channels {
+ channels: Vec<Channel>,
+}
+
+impl Channels {
+ fn channels(&self) -> Vec<Channel> {
+ self.channels.clone()
+ }
+}
+
+enum ChannelEvents {}
+
+impl Entity for Channels {
+ type Event = ChannelEvents;
+}
+
+impl Channels {
+ fn new(_client: Arc<Client>, _cx: &mut AppContext) -> Self {
+ //TODO: Subscribe to channel updates from the server
+ Channels {
+ channels: vec![Channel::new(
+ 0,
+ "Zed Industries",
+ vec![
+ Channel::new(1, "#general", Vec::new()),
+ Channel::new(2, "#admiral", Vec::new()),
+ Channel::new(3, "#livestreaming", vec![]),
+ Channel::new(4, "#crdb", Vec::new()),
+ Channel::new(5, "#crdb-1", Vec::new()),
+ Channel::new(6, "#crdb-2", Vec::new()),
+ Channel::new(7, "#crdb-3", vec![]),
+ Channel::new(8, "#crdb-4", Vec::new()),
+ Channel::new(9, "#crdb-1", Vec::new()),
+ Channel::new(10, "#crdb-1", Vec::new()),
+ Channel::new(11, "#crdb-1", Vec::new()),
+ Channel::new(12, "#crdb-1", vec![]),
+ Channel::new(13, "#crdb-1", Vec::new()),
+ Channel::new(14, "#crdb-1", Vec::new()),
+ Channel::new(15, "#crdb-1", Vec::new()),
+ Channel::new(16, "#crdb-1", Vec::new()),
+ Channel::new(17, "#crdb", vec![]),
+ ],
+ ),
+ Channel::new(
+ 18,
+ "CRDB Consulting",
+ vec![
+ Channel::new(19, "#crdb 😭", Vec::new()),
+ Channel::new(20, "#crdb 😌", Vec::new()),
+ Channel::new(21, "#crdb 🦀", vec![]),
+ Channel::new(22, "#crdb 😤", Vec::new()),
+ Channel::new(23, "#crdb 😤", Vec::new()),
+ Channel::new(24, "#crdb 😤", Vec::new()),
+ Channel::new(25, "#crdb 😤", vec![]),
+ Channel::new(26, "#crdb 😤", Vec::new()),
+ ],
+ )],
+ }
+ }
+}
@@ -0,0 +1,369 @@
+use std::sync::Arc;
+
+use crate::{
+ channels_panel_settings::{ChannelsPanelDockPosition, ChannelsPanelSettings},
+ Channel, Channels,
+};
+use anyhow::Result;
+use collections::HashMap;
+use context_menu::ContextMenu;
+use db::kvp::KEY_VALUE_STORE;
+use gpui::{
+ actions,
+ elements::{ChildView, Empty, Flex, Label, MouseEventHandler, ParentElement, Stack},
+ serde_json, AnyElement, AppContext, AsyncAppContext, Element, Entity, ModelHandle, Task, View,
+ ViewContext, ViewHandle, WeakViewHandle,
+};
+use project::Fs;
+use serde_derive::{Deserialize, Serialize};
+use settings::SettingsStore;
+use theme::ChannelTreeStyle;
+use util::{ResultExt, TryFutureExt};
+use workspace::{
+ dock::{DockPosition, Panel},
+ Workspace,
+};
+
+actions!(channels, [ToggleFocus]);
+
+const CHANNELS_PANEL_KEY: &'static str = "ChannelsPanel";
+
+pub fn init(cx: &mut AppContext) {
+ settings::register::<ChannelsPanelSettings>(cx);
+}
+
+pub struct ChannelsPanel {
+ width: Option<f32>,
+ fs: Arc<dyn Fs>,
+ has_focus: bool,
+ pending_serialization: Task<Option<()>>,
+ channels: ModelHandle<Channels>,
+ context_menu: ViewHandle<ContextMenu>,
+ collapsed_channels: HashMap<u64, bool>,
+}
+
+#[derive(Serialize, Deserialize)]
+struct SerializedChannelsPanel {
+ width: Option<f32>,
+ collapsed_channels: Option<HashMap<u64, bool>>,
+}
+
+#[derive(Debug)]
+pub enum Event {
+ DockPositionChanged,
+ Focus,
+}
+
+impl Entity for ChannelsPanel {
+ type Event = Event;
+}
+
+impl ChannelsPanel {
+ pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
+ cx.add_view(|cx| {
+ let view_id = cx.view_id();
+ let this = Self {
+ width: None,
+ has_focus: false,
+ fs: workspace.app_state().fs.clone(),
+ pending_serialization: Task::ready(None),
+ channels: cx.global::<ModelHandle<Channels>>().clone(),
+ context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
+ collapsed_channels: HashMap::default(),
+ };
+
+ // Update the dock position when the setting changes.
+ let mut old_dock_position = this.position(cx);
+ cx.observe_global::<SettingsStore, _>(move |this: &mut ChannelsPanel, 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);
+ }
+ })
+ .detach();
+
+ this
+ })
+ }
+
+ pub fn load(
+ workspace: WeakViewHandle<Workspace>,
+ cx: AsyncAppContext,
+ ) -> Task<Result<ViewHandle<Self>>> {
+ cx.spawn(|mut cx| async move {
+ let serialized_panel = if let Some(panel) = cx
+ .background()
+ .spawn(async move { KEY_VALUE_STORE.read_kvp(CHANNELS_PANEL_KEY) })
+ .await
+ .log_err()
+ .flatten()
+ {
+ Some(serde_json::from_str::<SerializedChannelsPanel>(&panel)?)
+ } else {
+ None
+ };
+
+ workspace.update(&mut cx, |workspace, cx| {
+ let panel = ChannelsPanel::new(workspace, cx);
+ if let Some(serialized_panel) = serialized_panel {
+ panel.update(cx, |panel, cx| {
+ panel.width = serialized_panel.width;
+ panel.collapsed_channels =
+ serialized_panel.collapsed_channels.unwrap_or_default();
+ cx.notify();
+ });
+ }
+ panel
+ })
+ })
+ }
+
+ fn serialize(&mut self, cx: &mut ViewContext<Self>) {
+ let width = self.width;
+ let collapsed_channels = self.collapsed_channels.clone();
+ self.pending_serialization = cx.background().spawn(
+ async move {
+ KEY_VALUE_STORE
+ .write_kvp(
+ CHANNELS_PANEL_KEY.into(),
+ serde_json::to_string(&SerializedChannelsPanel {
+ width,
+ collapsed_channels: Some(collapsed_channels),
+ })?,
+ )
+ .await?;
+ anyhow::Ok(())
+ }
+ .log_err(),
+ );
+ }
+
+ fn render_channel(
+ &mut self,
+ depth: usize,
+ channel: &Channel,
+ style: &ChannelTreeStyle,
+ root: bool,
+ cx: &mut ViewContext<Self>,
+ ) -> AnyElement<Self> {
+ let has_chilren = !channel.members().is_empty();
+
+ let sub_channel_details = has_chilren.then(|| {
+ let mut sub_channels = Flex::column();
+ let collapsed = self
+ .collapsed_channels
+ .get(&channel.id)
+ .copied()
+ .unwrap_or_default();
+ if !collapsed {
+ for sub_channel in channel.members() {
+ sub_channels = sub_channels.with_child(self.render_channel(
+ depth + 1,
+ sub_channel,
+ style,
+ false,
+ cx,
+ ));
+ }
+ }
+ (sub_channels, collapsed)
+ });
+
+ let channel_id = channel.id;
+
+ enum ChannelCollapser {}
+ Flex::row()
+ .with_child(
+ Empty::new()
+ .constrained()
+ .with_width(depth as f32 * style.channel_indent),
+ )
+ .with_child(
+ Flex::column()
+ .with_child(
+ Flex::row()
+ .with_child(
+ sub_channel_details
+ .as_ref()
+ .map(|(_, expanded)| {
+ MouseEventHandler::<ChannelCollapser, _>::new(
+ channel.id as usize,
+ cx,
+ |state, _cx| {
+ let icon =
+ style.channel_icon.style_for(!*expanded, state);
+ theme::ui::icon(icon)
+ },
+ )
+ .on_click(
+ gpui::platform::MouseButton::Left,
+ move |_, v, cx| {
+ let entry = v
+ .collapsed_channels
+ .entry(channel_id)
+ .or_default();
+ *entry = !*entry;
+ v.serialize(cx);
+ cx.notify();
+ },
+ )
+ .into_any()
+ })
+ .unwrap_or_else(|| {
+ Empty::new()
+ .constrained()
+ .with_width(style.channel_icon.default_style().width())
+ .into_any()
+ }),
+ )
+ .with_child(
+ Label::new(
+ channel.name().to_string(),
+ if root {
+ style.root_name.clone()
+ } else {
+ style.channel_name.clone()
+ },
+ )
+ .into_any(),
+ ),
+ )
+ .with_children(sub_channel_details.map(|(elements, _)| elements)),
+ )
+ .into_any()
+ }
+}
+
+impl View for ChannelsPanel {
+ fn ui_name() -> &'static str {
+ "ChannelsPanel"
+ }
+
+ fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext<Self>) {
+ if !self.has_focus {
+ self.has_focus = true;
+ cx.emit(Event::Focus);
+ }
+ }
+
+ fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
+ self.has_focus = false;
+ }
+
+ fn render(&mut self, cx: &mut gpui::ViewContext<'_, '_, Self>) -> gpui::AnyElement<Self> {
+ let theme = theme::current(cx).clone();
+
+ let mut channels_column = Flex::column();
+ for channel in self.channels.read(cx).channels() {
+ channels_column = channels_column.with_child(self.render_channel(
+ 0,
+ &channel,
+ &theme.channels_panel.channel_tree,
+ true,
+ cx,
+ ));
+ }
+
+ let spacing = theme.channels_panel.spacing;
+
+ enum ChannelsPanelScrollTag {}
+ Stack::new()
+ .with_child(
+ // Full panel column
+ Flex::column()
+ .with_spacing(spacing)
+ .with_child(
+ // Channels section column
+ Flex::column()
+ .with_child(
+ Flex::row().with_child(
+ Label::new(
+ "Active Channels",
+ theme.editor.invalid_information_diagnostic.message.clone(),
+ )
+ .into_any(),
+ ),
+ )
+ // Channels list column
+ .with_child(channels_column),
+ )
+ // TODO: Replace with spacing implementation
+ .with_child(Empty::new().constrained().with_height(spacing))
+ .with_child(
+ Flex::column().with_child(
+ Flex::row().with_child(
+ Label::new(
+ "Contacts",
+ theme.editor.invalid_information_diagnostic.message.clone(),
+ )
+ .into_any(),
+ ),
+ ),
+ )
+ .scrollable::<ChannelsPanelScrollTag>(0, None, cx)
+ .expanded(),
+ )
+ .with_child(ChildView::new(&self.context_menu, cx))
+ .into_any_named("channels panel")
+ .into_any()
+ }
+}
+
+impl Panel for ChannelsPanel {
+ fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
+ match settings::get::<ChannelsPanelSettings>(cx).dock {
+ ChannelsPanelDockPosition::Left => DockPosition::Left,
+ ChannelsPanelDockPosition::Right => DockPosition::Right,
+ }
+ }
+
+ fn position_is_valid(&self, position: DockPosition) -> bool {
+ matches!(position, DockPosition::Left | DockPosition::Right)
+ }
+
+ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
+ settings::update_settings_file::<ChannelsPanelSettings>(
+ self.fs.clone(),
+ cx,
+ move |settings| {
+ let dock = match position {
+ DockPosition::Left | DockPosition::Bottom => ChannelsPanelDockPosition::Left,
+ DockPosition::Right => ChannelsPanelDockPosition::Right,
+ };
+ settings.dock = Some(dock);
+ },
+ );
+ }
+
+ fn size(&self, cx: &gpui::WindowContext) -> f32 {
+ self.width
+ .unwrap_or_else(|| settings::get::<ChannelsPanelSettings>(cx).default_width)
+ }
+
+ fn set_size(&mut self, size: f32, cx: &mut ViewContext<Self>) {
+ self.width = Some(size);
+ self.serialize(cx);
+ cx.notify();
+ }
+
+ fn icon_path(&self) -> &'static str {
+ "icons/bolt_16.svg"
+ }
+
+ fn icon_tooltip(&self) -> (String, Option<Box<dyn gpui::Action>>) {
+ ("Channels Panel".to_string(), Some(Box::new(ToggleFocus)))
+ }
+
+ fn should_change_position_on_event(event: &Self::Event) -> bool {
+ matches!(event, Event::DockPositionChanged)
+ }
+
+ fn has_focus(&self, _cx: &gpui::WindowContext) -> bool {
+ self.has_focus
+ }
+
+ fn is_focus_event(event: &Self::Event) -> bool {
+ matches!(event, Event::Focus)
+ }
+}
@@ -0,0 +1,37 @@
+use anyhow;
+use schemars::JsonSchema;
+use serde_derive::{Deserialize, Serialize};
+use settings::Setting;
+
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ChannelsPanelDockPosition {
+ Left,
+ Right,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct ChannelsPanelSettings {
+ pub dock: ChannelsPanelDockPosition,
+ pub default_width: f32,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+pub struct ChannelsPanelSettingsContent {
+ pub dock: Option<ChannelsPanelDockPosition>,
+ pub default_width: Option<f32>,
+}
+
+impl Setting for ChannelsPanelSettings {
+ const KEY: Option<&'static str> = Some("channels_panel");
+
+ type FileContent = ChannelsPanelSettingsContent;
+
+ fn load(
+ default_value: &Self::FileContent,
+ user_values: &[&Self::FileContent],
+ _: &gpui::AppContext,
+ ) -> anyhow::Result<Self> {
+ Self::load_via_json_merge(default_value, user_values)
+ }
+}
@@ -22,6 +22,7 @@ pub struct Flex<V: View> {
children: Vec<AnyElement<V>>,
scroll_state: Option<(ElementStateHandle<Rc<ScrollState>>, usize)>,
child_alignment: f32,
+ spacing: f32,
}
impl<V: View> Flex<V> {
@@ -31,6 +32,7 @@ impl<V: View> Flex<V> {
children: Default::default(),
scroll_state: None,
child_alignment: -1.,
+ spacing: 0.,
}
}
@@ -42,6 +44,11 @@ impl<V: View> Flex<V> {
Self::new(Axis::Vertical)
}
+ pub fn with_spacing(mut self, spacing: f32) -> Self {
+ self.spacing = spacing;
+ self
+ }
+
/// Render children centered relative to the cross-axis of the parent flex.
///
/// If this is a flex row, children will be centered vertically. If this is a
@@ -1649,22 +1649,6 @@ impl workspace::dock::Panel for ProjectPanel {
cx.notify();
}
- fn should_zoom_in_on_event(_: &Self::Event) -> bool {
- false
- }
-
- fn should_zoom_out_on_event(_: &Self::Event) -> bool {
- false
- }
-
- fn is_zoomed(&self, _: &WindowContext) -> bool {
- false
- }
-
- fn set_zoomed(&mut self, _: bool, _: &mut ViewContext<Self>) {}
-
- fn set_active(&mut self, _: bool, _: &mut ViewContext<Self>) {}
-
fn icon_path(&self) -> &'static str {
"icons/folder_tree_16.svg"
}
@@ -1677,14 +1661,6 @@ impl workspace::dock::Panel for ProjectPanel {
matches!(event, Event::DockPositionChanged)
}
- fn should_activate_on_event(_: &Self::Event) -> bool {
- false
- }
-
- fn should_close_on_event(_: &Self::Event) -> bool {
- false
- }
-
fn has_focus(&self, _: &WindowContext) -> bool {
self.has_focus
}
@@ -49,6 +49,7 @@ pub struct Theme {
pub copilot: Copilot,
pub contact_finder: ContactFinder,
pub project_panel: ProjectPanel,
+ pub channels_panel: ChanelsPanelStyle,
pub command_palette: CommandPalette,
pub picker: Picker,
pub editor: Editor,
@@ -880,6 +881,16 @@ impl<T> Interactive<T> {
}
}
+impl<T> Toggleable<Interactive<T>> {
+ pub fn style_for(&self, active: bool, state: &mut MouseState) -> &T {
+ self.in_state(active).style_for(state)
+ }
+
+ pub fn default_style(&self) -> &T {
+ &self.inactive.default
+ }
+}
+
impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -1045,6 +1056,75 @@ pub struct AssistantStyle {
pub saved_conversation: SavedConversation,
}
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct Contained<T> {
+ container: ContainerStyle,
+ contained: T,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct FlexStyle {
+ // Between item spacing
+ item_spacing: f32,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct ChannelProjectStyle {
+ // TODO: Implement Contained Flex
+ // ContainerStyle + Spacing between elements
+ // Negative spacing overlaps elements instead of spacing them out
+ pub container: Contained<FlexStyle>,
+ pub host: ImageStyle,
+ pub title: ContainedText,
+ pub members: Contained<FlexStyle>,
+ pub member: ImageStyle
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct ChanneltemStyle {
+ pub icon: IconStyle,
+ pub title: TextStyle,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct ChannelListStyle {
+ pub section_title: ContainedText,
+ pub channel: Toggleable<Contained<ChanneltemStyle>>,
+ pub project: ChannelProjectStyle
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct ContactItemStyle {
+ pub container: Contained<FlexStyle>,
+ pub avatar: IconStyle,
+ pub name: TextStyle,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct ContactsListStyle {
+ pub section_title: ContainedText,
+ pub contact: ContactItemStyle,
+}
+
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct ChannelTreeStyle {
+ pub channel_indent: f32,
+ pub channel_name: TextStyle,
+ pub root_name: TextStyle,
+ pub channel_icon: Toggleable<Interactive<IconStyle>>,
+}
+
+#[derive(Clone, Deserialize, Default, JsonSchema)]
+pub struct ChanelsPanelStyle {
+ pub channel_tree: ChannelTreeStyle,
+ pub spacing: f32,
+ // TODO: Uncomment:
+ // pub container: ContainerStyle,
+ // pub channel_list: ChannelListStyle,
+ // pub contacts_list: ContactsListStyle
+}
+
#[derive(Clone, Deserialize, Default, JsonSchema)]
pub struct SavedConversation {
pub container: Interactive<ContainerStyle>,
@@ -107,6 +107,16 @@ pub struct IconStyle {
pub container: ContainerStyle,
}
+impl IconStyle {
+ pub fn width(&self) -> f32 {
+ self.icon.dimensions.width
+ + self.container.padding.left
+ + self.container.padding.right
+ + self.container.margin.left
+ + self.container.margin.right
+ }
+}
+
pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
svg(&style.icon).contained().with_style(style.container)
}
@@ -20,13 +20,27 @@ pub trait Panel: View {
None
}
fn should_change_position_on_event(_: &Self::Event) -> bool;
- fn should_zoom_in_on_event(_: &Self::Event) -> bool;
- fn should_zoom_out_on_event(_: &Self::Event) -> bool;
- fn is_zoomed(&self, cx: &WindowContext) -> bool;
- fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>);
- fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>);
- fn should_activate_on_event(_: &Self::Event) -> bool;
- fn should_close_on_event(_: &Self::Event) -> bool;
+ fn should_zoom_in_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn should_zoom_out_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn is_zoomed(&self, _cx: &WindowContext) -> bool {
+ false
+ }
+ fn set_zoomed(&mut self, _zoomed: bool, _cx: &mut ViewContext<Self>) {
+
+ }
+ fn set_active(&mut self, _active: bool, _cx: &mut ViewContext<Self>) {
+
+ }
+ fn should_activate_on_event(_: &Self::Event) -> bool {
+ false
+ }
+ fn should_close_on_event(_: &Self::Event) -> bool {
+ false
+ }
fn has_focus(&self, cx: &WindowContext) -> bool;
fn is_focus_event(_: &Self::Event) -> bool;
}
@@ -21,6 +21,7 @@ activity_indicator = { path = "../activity_indicator" }
auto_update = { path = "../auto_update" }
breadcrumbs = { path = "../breadcrumbs" }
call = { path = "../call" }
+channels = { path = "../channels" }
cli = { path = "../cli" }
collab_ui = { path = "../collab_ui" }
collections = { path = "../collections" }
@@ -155,6 +155,7 @@ fn main() {
outline::init(cx);
project_symbols::init(cx);
project_panel::init(Assets, cx);
+ channels::init(client.clone(), cx);
diagnostics::init(cx);
search::init(cx);
semantic_index::init(fs.clone(), http.clone(), languages.clone(), cx);
@@ -9,6 +9,7 @@ use ai::AssistantPanel;
use anyhow::Context;
use assets::Assets;
use breadcrumbs::Breadcrumbs;
+use channels::ChannelsPanel;
pub use client;
use collab_ui::{CollabTitlebarItem, ToggleContactsMenu};
use collections::VecDeque;
@@ -221,6 +222,11 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
workspace.toggle_panel_focus::<ProjectPanel>(cx);
},
);
+ cx.add_action(
+ |workspace: &mut Workspace, _: &channels::ToggleFocus, cx: &mut ViewContext<Workspace>| {
+ workspace.toggle_panel_focus::<channels::ChannelsPanel>(cx);
+ },
+ );
cx.add_action(
|workspace: &mut Workspace,
_: &terminal_panel::ToggleFocus,
@@ -339,9 +345,13 @@ pub fn initialize_workspace(
let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
- let (project_panel, terminal_panel, assistant_panel) =
- futures::try_join!(project_panel, terminal_panel, assistant_panel)?;
-
+ let channels_panel = ChannelsPanel::load(workspace_handle.clone(), cx.clone());
+ let (project_panel, terminal_panel, assistant_panel, channels_panel) = futures::try_join!(
+ project_panel,
+ terminal_panel,
+ assistant_panel,
+ channels_panel
+ )?;
workspace_handle.update(&mut cx, |workspace, cx| {
let project_panel_position = project_panel.position(cx);
workspace.add_panel_with_extra_event_handler(
@@ -359,6 +369,7 @@ pub fn initialize_workspace(
);
workspace.add_panel(terminal_panel, cx);
workspace.add_panel(assistant_panel, cx);
+ workspace.add_panel(channels_panel, cx);
if !was_deserialized
&& workspace
@@ -24,6 +24,7 @@ import { titlebar } from "./titlebar"
import editor from "./editor"
import feedback from "./feedback"
import { useTheme } from "../common"
+import channels_panel from "./channels_panel"
export default function app(): any {
const theme = useTheme()
@@ -46,6 +47,7 @@ export default function app(): any {
editor: editor(),
project_diagnostics: project_diagnostics(),
project_panel: project_panel(),
+ channels_panel: channels_panel(),
contacts_popover: contacts_popover(),
contact_finder: contact_finder(),
contact_list: contact_list(),
@@ -0,0 +1,68 @@
+// import { with_opacity } from "../theme/color"
+import {
+ // Border,
+ // TextStyle,
+ // background,
+ // border,
+ foreground,
+ text,
+} from "./components"
+import { interactive, toggleable } from "../element"
+// import merge from "ts-deepmerge"
+import { useTheme } from "../theme"
+export default function channels_panel(): any {
+ const theme = useTheme()
+
+ // const { is_light } = theme
+
+ return {
+ spacing: 10,
+ channel_tree: {
+ channel_indent: 10,
+ channel_name: text(theme.middle, "sans", "variant", { size: "md" }),
+ root_name: text(theme.middle, "sans", "variant", { size: "lg", weight: "bold" }),
+ channel_icon: (() => {
+ const base_icon = (asset: any, color: any) => {
+ return {
+ icon: {
+ color,
+ asset,
+ dimensions: {
+ width: 12,
+ height: 12,
+ }
+ },
+ container: {
+ corner_radius: 4,
+ padding: {
+ top: 4, bottom: 4, left: 4, right: 4
+ },
+ margin: {
+ right: 4,
+ },
+ }
+ }
+ }
+
+ return toggleable({
+ state: {
+ inactive: interactive({
+ state: {
+ default: base_icon("icons/chevron_right_8.svg", foreground(theme.middle, "variant")),
+ hovered: base_icon("icons/chevron_right_8.svg", foreground(theme.middle, "hovered")),
+ clicked: base_icon("icons/chevron_right_8.svg", foreground(theme.middle, "active")),
+ },
+ }),
+ active: interactive({
+ state: {
+ default: base_icon("icons/chevron_down_8.svg", foreground(theme.highest, "variant")),
+ hovered: base_icon("icons/chevron_down_8.svg", foreground(theme.highest, "hovered")),
+ clicked: base_icon("icons/chevron_down_8.svg", foreground(theme.highest, "active")),
+ },
+ }),
+ },
+ })
+ })(),
+ }
+ }
+}