mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
Fix image previews in replies (#225)
This commit is contained in:
parent
2ac71da9a6
commit
d3b717d1be
2 changed files with 89 additions and 103 deletions
|
@ -142,6 +142,35 @@ pub fn text_to_message(input: String) -> RoomMessageEventContent {
|
||||||
RoomMessageEventContent::new(msg)
|
RoomMessageEventContent::new(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Before the image is loaded, already display a placeholder frame of the image size.
|
||||||
|
fn placeholder_frame(
|
||||||
|
text: Option<&str>,
|
||||||
|
outer_width: usize,
|
||||||
|
image_preview_size: &ImagePreviewSize,
|
||||||
|
) -> Option<String> {
|
||||||
|
let ImagePreviewSize { width, height } = image_preview_size;
|
||||||
|
if outer_width < *width || (*width < 2 || *height < 2) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut placeholder = "\u{230c}".to_string();
|
||||||
|
placeholder.push_str(&" ".repeat(width - 2));
|
||||||
|
placeholder.push_str("\u{230d}\n");
|
||||||
|
if *height > 2 {
|
||||||
|
if let Some(text) = text {
|
||||||
|
if text.width() <= width - 2 {
|
||||||
|
placeholder.push(' ');
|
||||||
|
placeholder.push_str(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholder.push_str(&"\n".repeat(height - 2));
|
||||||
|
placeholder.push('\u{230e}');
|
||||||
|
placeholder.push_str(&" ".repeat(width - 2));
|
||||||
|
placeholder.push_str("\u{230f}\n");
|
||||||
|
Some(placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn millis_to_datetime(ms: UInt) -> DateTime<LocalTz> {
|
fn millis_to_datetime(ms: UInt) -> DateTime<LocalTz> {
|
||||||
let time = i64::from(ms) / 1000;
|
let time = i64::from(ms) / 1000;
|
||||||
|
@ -531,6 +560,16 @@ enum MessageColumns {
|
||||||
One,
|
One,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MessageColumns {
|
||||||
|
fn user_gutter_width(&self, settings: &ApplicationSettings) -> u16 {
|
||||||
|
if let MessageColumns::One = self {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
settings.tunables.user_gutter_width as u16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct MessageFormatter<'a> {
|
struct MessageFormatter<'a> {
|
||||||
settings: &'a ApplicationSettings,
|
settings: &'a ApplicationSettings,
|
||||||
|
|
||||||
|
@ -649,7 +688,7 @@ impl<'a> MessageFormatter<'a> {
|
||||||
let width = self.width();
|
let width = self.width();
|
||||||
let w = width.saturating_sub(2);
|
let w = width.saturating_sub(2);
|
||||||
let shortcodes = self.settings.tunables.message_shortcode_display;
|
let shortcodes = self.settings.tunables.message_shortcode_display;
|
||||||
let mut replied = msg.show_msg(w, style, true, shortcodes);
|
let (mut replied, _) = msg.show_msg(w, style, true, shortcodes);
|
||||||
let mut sender = msg.sender_span(info, self.settings);
|
let mut sender = msg.sender_span(info, self.settings);
|
||||||
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
||||||
let trailing = w.saturating_sub(sender_width + 1);
|
let trailing = w.saturating_sub(sender_width + 1);
|
||||||
|
@ -881,46 +920,17 @@ impl Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the image preview Protocol and x,y offset, based on get_render_format.
|
/// Render the message as a [Text] object for the terminal.
|
||||||
pub fn line_preview<'a>(
|
///
|
||||||
&'a self,
|
/// This will also get the image preview Protocol with an x/y offset.
|
||||||
prev: Option<&Message>,
|
pub fn show_with_preview<'a>(
|
||||||
vwctx: &ViewportContext<MessageCursor>,
|
|
||||||
info: &'a RoomInfo,
|
|
||||||
settings: &'a ApplicationSettings,
|
|
||||||
) -> Option<(&dyn Protocol, u16, u16)> {
|
|
||||||
let width = vwctx.get_width();
|
|
||||||
let user_gutter = settings.tunables.user_gutter_width;
|
|
||||||
// The x position where get_render_format would render the text.
|
|
||||||
let x = (if user_gutter + MIN_MSG_LEN <= width {
|
|
||||||
user_gutter
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}) as u16;
|
|
||||||
// See get_render_format; account for possible "date" line.
|
|
||||||
let date_y = match &prev {
|
|
||||||
Some(prev) if !prev.timestamp.same_day(&self.timestamp) => 1,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
if let ImageStatus::Loaded(backend) = &self.image_preview {
|
|
||||||
return Some((backend.as_ref(), x, date_y));
|
|
||||||
} else if let Some(reply) = self.reply_to().and_then(|e| info.get_event(&e)) {
|
|
||||||
if let ImageStatus::Loaded(backend) = &reply.image_preview {
|
|
||||||
// The reply should be offset a bit:
|
|
||||||
return Some((backend.as_ref(), x + 2, date_y + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show<'a>(
|
|
||||||
&'a self,
|
&'a self,
|
||||||
prev: Option<&Message>,
|
prev: Option<&Message>,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
vwctx: &ViewportContext<MessageCursor>,
|
vwctx: &ViewportContext<MessageCursor>,
|
||||||
info: &'a RoomInfo,
|
info: &'a RoomInfo,
|
||||||
settings: &'a ApplicationSettings,
|
settings: &'a ApplicationSettings,
|
||||||
) -> Text<'a> {
|
) -> (Text<'a>, Option<(&dyn Protocol, u16, u16)>) {
|
||||||
let width = vwctx.get_width();
|
let width = vwctx.get_width();
|
||||||
|
|
||||||
let style = self.get_render_style(selected, settings);
|
let style = self.get_render_style(selected, settings);
|
||||||
|
@ -939,12 +949,20 @@ impl Message {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now show the message contents, and the inlined reply if we couldn't find it above.
|
// Now show the message contents, and the inlined reply if we couldn't find it above.
|
||||||
let msg = self.show_msg(
|
let (msg, proto) = self.show_msg(
|
||||||
width,
|
width,
|
||||||
style,
|
style,
|
||||||
reply.is_some(),
|
reply.is_some(),
|
||||||
settings.tunables.message_shortcode_display,
|
settings.tunables.message_shortcode_display,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Given our text so far, determine the image offset.
|
||||||
|
let proto = proto.map(|p| {
|
||||||
|
let y_off = text.lines.len() as u16;
|
||||||
|
let x_off = fmt.cols.user_gutter_width(settings);
|
||||||
|
(p, x_off, y_off)
|
||||||
|
});
|
||||||
|
|
||||||
fmt.push_text(msg, style, &mut text);
|
fmt.push_text(msg, style, &mut text);
|
||||||
|
|
||||||
if text.lines.is_empty() {
|
if text.lines.is_empty() {
|
||||||
|
@ -961,18 +979,29 @@ impl Message {
|
||||||
fmt.push_thread_reply_count(thread.len(), &mut text);
|
fmt.push_thread_reply_count(thread.len(), &mut text);
|
||||||
}
|
}
|
||||||
|
|
||||||
text
|
(text, proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_msg(
|
pub fn show<'a>(
|
||||||
|
&'a self,
|
||||||
|
prev: Option<&Message>,
|
||||||
|
selected: bool,
|
||||||
|
vwctx: &ViewportContext<MessageCursor>,
|
||||||
|
info: &'a RoomInfo,
|
||||||
|
settings: &'a ApplicationSettings,
|
||||||
|
) -> Text<'a> {
|
||||||
|
self.show_with_preview(prev, selected, vwctx, info, settings).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_msg(
|
||||||
&self,
|
&self,
|
||||||
width: usize,
|
width: usize,
|
||||||
style: Style,
|
style: Style,
|
||||||
hide_reply: bool,
|
hide_reply: bool,
|
||||||
emoji_shortcodes: bool,
|
emoji_shortcodes: bool,
|
||||||
) -> Text {
|
) -> (Text, Option<&dyn Protocol>) {
|
||||||
if let Some(html) = &self.html {
|
if let Some(html) = &self.html {
|
||||||
html.to_text(width, style, hide_reply, emoji_shortcodes)
|
(html.to_text(width, style, hide_reply, emoji_shortcodes), None)
|
||||||
} else {
|
} else {
|
||||||
let mut msg = self.event.body();
|
let mut msg = self.event.body();
|
||||||
if emoji_shortcodes {
|
if emoji_shortcodes {
|
||||||
|
@ -983,20 +1012,24 @@ impl Message {
|
||||||
msg.to_mut().push_str(" \u{2705}");
|
msg.to_mut().push_str(" \u{2705}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(placeholder) = match &self.image_preview {
|
let mut proto = None;
|
||||||
|
let placeholder = match &self.image_preview {
|
||||||
ImageStatus::None => None,
|
ImageStatus::None => None,
|
||||||
ImageStatus::Downloading(image_preview_size) => {
|
ImageStatus::Downloading(image_preview_size) => {
|
||||||
Message::placeholder_frame(Some("Downloading..."), width, image_preview_size)
|
placeholder_frame(Some("Downloading..."), width, image_preview_size)
|
||||||
},
|
},
|
||||||
ImageStatus::Loaded(backend) => {
|
ImageStatus::Loaded(backend) => {
|
||||||
Message::placeholder_frame(None, width, &backend.rect().into())
|
proto = Some(backend.as_ref());
|
||||||
|
placeholder_frame(None, width, &backend.rect().into())
|
||||||
},
|
},
|
||||||
ImageStatus::Error(err) => Some(format!("[Image error: {err}]\n")),
|
ImageStatus::Error(err) => Some(format!("[Image error: {err}]\n")),
|
||||||
} {
|
};
|
||||||
|
|
||||||
|
if let Some(placeholder) = placeholder {
|
||||||
msg.to_mut().insert_str(0, &placeholder);
|
msg.to_mut().insert_str(0, &placeholder);
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapped_text(msg, width, style)
|
(wrapped_text(msg, width, style), proto)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,35 +1041,6 @@ impl Message {
|
||||||
settings.get_user_span(self.sender.as_ref(), info)
|
settings.get_user_span(self.sender.as_ref(), info)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Before the image is loaded, already display a placeholder frame of the image size.
|
|
||||||
fn placeholder_frame(
|
|
||||||
text: Option<&str>,
|
|
||||||
outer_width: usize,
|
|
||||||
image_preview_size: &ImagePreviewSize,
|
|
||||||
) -> Option<String> {
|
|
||||||
let ImagePreviewSize { width, height } = image_preview_size;
|
|
||||||
if outer_width < *width || (*width < 2 || *height < 2) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut placeholder = "\u{230c}".to_string();
|
|
||||||
placeholder.push_str(&" ".repeat(width - 2));
|
|
||||||
placeholder.push_str("\u{230d}\n");
|
|
||||||
if *height > 2 {
|
|
||||||
if let Some(text) = text {
|
|
||||||
if text.width() <= width - 2 {
|
|
||||||
placeholder.push(' ');
|
|
||||||
placeholder.push_str(text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
placeholder.push_str(&"\n".repeat(height - 2));
|
|
||||||
placeholder.push('\u{230e}');
|
|
||||||
placeholder.push_str(&" ".repeat(width - 2));
|
|
||||||
placeholder.push_str("\u{230f}\n");
|
|
||||||
Some(placeholder)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_sender<'a>(
|
fn show_sender<'a>(
|
||||||
&'a self,
|
&'a self,
|
||||||
prev: Option<&Message>,
|
prev: Option<&Message>,
|
||||||
|
@ -1287,7 +1291,7 @@ pub mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Message::placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 4 }),
|
placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 4 }),
|
||||||
pretty_frame_test(
|
pretty_frame_test(
|
||||||
r#"
|
r#"
|
||||||
⌌ ⌍
|
⌌ ⌍
|
||||||
|
@ -1298,22 +1302,13 @@ pub mod tests {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }), None);
|
||||||
Message::placeholder_frame(None, 2, &ImagePreviewSize { width: 4, height: 4 }),
|
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }), None);
|
||||||
None
|
|
||||||
);
|
assert_eq!(placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }), None);
|
||||||
assert_eq!(
|
|
||||||
Message::placeholder_frame(None, 4, &ImagePreviewSize { width: 1, height: 4 }),
|
|
||||||
None
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Message::placeholder_frame(None, 4, &ImagePreviewSize { width: 4, height: 1 }),
|
placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 4 }),
|
||||||
None
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Message::placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 4 }),
|
|
||||||
pretty_frame_test(
|
pretty_frame_test(
|
||||||
r#"
|
r#"
|
||||||
⌌ ⌍
|
⌌ ⌍
|
||||||
|
@ -1324,10 +1319,7 @@ pub mod tests {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Message::placeholder_frame(Some("idontfit"), 4, &ImagePreviewSize {
|
placeholder_frame(Some("idontfit"), 4, &ImagePreviewSize { width: 4, height: 4 }),
|
||||||
width: 4,
|
|
||||||
height: 4,
|
|
||||||
}),
|
|
||||||
pretty_frame_test(
|
pretty_frame_test(
|
||||||
r#"
|
r#"
|
||||||
⌌ ⌍
|
⌌ ⌍
|
||||||
|
@ -1338,7 +1330,7 @@ pub mod tests {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Message::placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 2 }),
|
placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 4, height: 2 }),
|
||||||
pretty_frame_test(
|
pretty_frame_test(
|
||||||
r#"
|
r#"
|
||||||
⌌ ⌍
|
⌌ ⌍
|
||||||
|
@ -1347,7 +1339,7 @@ pub mod tests {
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Message::placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 2, height: 3 }),
|
placeholder_frame(Some("OK"), 4, &ImagePreviewSize { width: 2, height: 3 }),
|
||||||
pretty_frame_test(
|
pretty_frame_test(
|
||||||
r#"
|
r#"
|
||||||
⌌⌍
|
⌌⌍
|
||||||
|
|
|
@ -1292,7 +1292,6 @@ impl<'a> StatefulWidget for Scrollback<'a> {
|
||||||
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
|
||||||
let info = self.store.application.rooms.get_or_default(state.room_id.clone());
|
let info = self.store.application.rooms.get_or_default(state.room_id.clone());
|
||||||
let settings = &self.store.application.settings;
|
let settings = &self.store.application.settings;
|
||||||
let picker = &self.store.application.picker;
|
|
||||||
let area = if state.cursor.timestamp.is_some() {
|
let area = if state.cursor.timestamp.is_some() {
|
||||||
render_jump_to_recent(area, buf, self.focused)
|
render_jump_to_recent(area, buf, self.focused)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1343,13 +1342,8 @@ impl<'a> StatefulWidget for Scrollback<'a> {
|
||||||
|
|
||||||
for (key, item) in thread.range(&corner_key..) {
|
for (key, item) in thread.range(&corner_key..) {
|
||||||
let sel = key == cursor_key;
|
let sel = key == cursor_key;
|
||||||
let txt = item.show(prev, foc && sel, &state.viewctx, info, settings);
|
let (txt, mut msg_preview) =
|
||||||
|
item.show_with_preview(prev, foc && sel, &state.viewctx, info, settings);
|
||||||
let mut msg_preview = if picker.is_some() {
|
|
||||||
item.line_preview(prev, &state.viewctx, info, settings)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let incomplete_ok = !full || !sel;
|
let incomplete_ok = !full || !sel;
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue