Detailed changes
@@ -5142,6 +5142,15 @@ dependencies = [
"winapi 0.3.9",
]
+[[package]]
+name = "time"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0a10c9a9fb3a5dce8c2239ed670f1a2569fcf42da035f5face1b19860d52b0"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "time-macros"
version = "0.1.1"
@@ -5832,6 +5841,7 @@ dependencies = [
"smol",
"surf",
"tempdir",
+ "time 0.3.2",
"tiny_http",
"toml 0.5.8",
"tree-sitter",
@@ -48,6 +48,7 @@ smallvec = { version = "1.6", features = ["union"] }
smol = "1.2.5"
surf = "2.2"
tempdir = { version = "0.3.7", optional = true }
+time = { version = "0.3", features = ["local-offset"] }
tiny_http = "0.8"
toml = "0.5"
tree-sitter = "0.19.5"
@@ -22,8 +22,14 @@ color = "$text.2"
[workspace.active_sidebar_icon]
color = "$text.0"
+[chat_panel]
+padding = { top = 10.0, bottom = 10.0, left = 10.0, right = 10.0 }
+
[chat_panel.message]
body = "$text.0"
+sender.margin.right = 10.0
+sender.text = { color = "#ff0000", weight = "bold", italic = true }
+timestamp.text = "$text.2"
[selector]
background = "$surface.2"
@@ -14,6 +14,7 @@ use std::{
ops::Range,
sync::Arc,
};
+use time::OffsetDateTime;
use zrpc::{
proto::{self, ChannelMessageSent},
TypedEnvelope,
@@ -47,6 +48,7 @@ pub struct Channel {
pub struct ChannelMessage {
pub id: u64,
pub body: String,
+ pub timestamp: OffsetDateTime,
pub sender: Arc<User>,
}
@@ -261,14 +263,17 @@ impl Channel {
this.insert_message(
ChannelMessage {
id: response.message_id,
+ timestamp: OffsetDateTime::from_unix_timestamp(
+ response.timestamp as i64,
+ )?,
body,
sender,
},
cx,
);
}
- });
- Ok(())
+ Ok(())
+ })
}
.log_err()
})
@@ -363,6 +368,7 @@ impl ChannelMessage {
Ok(ChannelMessage {
id: message.id,
body: message.body,
+ timestamp: OffsetDateTime::from_unix_timestamp(message.timestamp as i64)?,
sender,
})
}
@@ -9,6 +9,7 @@ use gpui::{
Subscription, View, ViewContext, ViewHandle,
};
use postage::watch;
+use time::{OffsetDateTime, UtcOffset};
pub struct ChatPanel {
channel_list: ModelHandle<ChannelList>,
@@ -75,12 +76,13 @@ impl ChatPanel {
fn set_active_channel(&mut self, channel: ModelHandle<Channel>, cx: &mut ViewContext<Self>) {
if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) {
let subscription = cx.subscribe(&channel, Self::channel_did_change);
+ let now = OffsetDateTime::now_utc();
self.messages = ListState::new(
channel
.read(cx)
.messages()
.cursor::<(), ()>()
- .map(|m| self.render_message(m))
+ .map(|m| self.render_message(m, now))
.collect(),
Orientation::Bottom,
);
@@ -99,12 +101,13 @@ impl ChatPanel {
old_range,
new_count,
} => {
+ let now = OffsetDateTime::now_utc();
self.messages.splice(
old_range.clone(),
channel
.read(cx)
.messages_in_range(old_range.start..(old_range.start + new_count))
- .map(|message| self.render_message(message)),
+ .map(|message| self.render_message(message, now)),
);
}
}
@@ -115,15 +118,50 @@ impl ChatPanel {
Expanded::new(1., List::new(self.messages.clone()).boxed()).boxed()
}
- fn render_message(&self, message: &ChannelMessage) -> ElementBox {
+ fn render_message(&self, message: &ChannelMessage, now: OffsetDateTime) -> ElementBox {
let settings = self.settings.borrow();
- Text::new(
- message.body.clone(),
- settings.ui_font_family,
- settings.ui_font_size,
- )
- .with_style(&settings.theme.chat_panel.message.body)
- .boxed()
+ let theme = &settings.theme.chat_panel.message;
+ Flex::column()
+ .with_child(
+ Flex::row()
+ .with_child(
+ Container::new(
+ Label::new(
+ message.sender.github_login.clone(),
+ settings.ui_font_family,
+ settings.ui_font_size,
+ )
+ .with_style(&theme.sender.label)
+ .boxed(),
+ )
+ .with_style(&theme.sender.container)
+ .boxed(),
+ )
+ .with_child(
+ Container::new(
+ Label::new(
+ format_timestamp(message.timestamp, now),
+ settings.ui_font_family,
+ settings.ui_font_size,
+ )
+ .with_style(&theme.timestamp.label)
+ .boxed(),
+ )
+ .with_style(&theme.timestamp.container)
+ .boxed(),
+ )
+ .boxed(),
+ )
+ .with_child(
+ Text::new(
+ message.body.clone(),
+ settings.ui_font_family,
+ settings.ui_font_size,
+ )
+ .with_style(&theme.body)
+ .boxed(),
+ )
+ .boxed()
}
fn render_input_box(&self) -> ElementBox {
@@ -157,9 +195,36 @@ impl View for ChatPanel {
}
fn render(&self, _: &RenderContext<Self>) -> ElementBox {
- Flex::column()
- .with_child(self.render_active_channel_messages())
- .with_child(self.render_input_box())
- .boxed()
+ let theme = &self.settings.borrow().theme;
+ Container::new(
+ Flex::column()
+ .with_child(self.render_active_channel_messages())
+ .with_child(self.render_input_box())
+ .boxed(),
+ )
+ .with_style(&theme.chat_panel.container)
+ .boxed()
+ }
+}
+
+fn format_timestamp(mut timestamp: OffsetDateTime, mut now: OffsetDateTime) -> String {
+ let local_offset = UtcOffset::current_local_offset().unwrap_or(UtcOffset::UTC);
+ timestamp = timestamp.to_offset(local_offset);
+ now = now.to_offset(local_offset);
+
+ let today = now.date();
+ let date = timestamp.date();
+ let mut hour = timestamp.hour();
+ let mut part = "am";
+ if hour > 12 {
+ hour -= 12;
+ part = "pm";
+ }
+ if date == today {
+ format!("{}:{}{}", hour, timestamp.minute(), part)
+ } else if date.next_day() == Some(today) {
+ format!("yesterday at {}:{}{}", hour, timestamp.minute(), part)
+ } else {
+ format!("{}/{}/{}", date.month(), date.day(), date.year())
}
}
@@ -55,12 +55,16 @@ pub struct SidebarIcon {
#[derive(Debug, Default, Deserialize)]
pub struct ChatPanel {
+ #[serde(flatten)]
+ pub container: ContainerStyle,
pub message: ChatMessage,
}
#[derive(Debug, Default, Deserialize)]
pub struct ChatMessage {
pub body: TextStyle,
+ pub sender: ContainedLabel,
+ pub timestamp: ContainedLabel,
}
#[derive(Debug, Default, Deserialize)]
@@ -70,12 +74,12 @@ pub struct Selector {
#[serde(flatten)]
pub label: LabelStyle,
- pub item: SelectorItem,
- pub active_item: SelectorItem,
+ pub item: ContainedLabel,
+ pub active_item: ContainedLabel,
}
#[derive(Debug, Default, Deserialize)]
-pub struct SelectorItem {
+pub struct ContainedLabel {
#[serde(flatten)]
pub container: ContainerStyle,
#[serde(flatten)]
@@ -99,6 +99,7 @@ impl ThemeSelector {
Ok(theme) => {
cx.notify_all();
action.0.settings_tx.lock().borrow_mut().theme = theme;
+ log::info!("reloaded theme {}", current_theme_name);
}
Err(error) => {
log::error!("failed to load theme {}: {:?}", current_theme_name, error)
@@ -958,7 +958,7 @@ impl View for Workspace {
if let Some(panel) = self.left_sidebar.active_item() {
content.add_child(
ConstrainedBox::new(ChildView::new(panel.id()).boxed())
- .with_width(200.0)
+ .with_width(300.0)
.named("left panel"),
);
}
@@ -966,7 +966,7 @@ impl View for Workspace {
if let Some(panel) = self.right_sidebar.active_item() {
content.add_child(
ConstrainedBox::new(ChildView::new(panel.id()).boxed())
- .with_width(200.0)
+ .with_width(300.0)
.named("right panel"),
);
}