feat: add code block and update the open link dialogs with the code block (#924)
This commit is contained in:
parent
cfcf7a7669
commit
c43272683b
7 changed files with 169 additions and 87 deletions
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
default: minor
|
||||
---
|
||||
|
||||
# Code blocks are selectable, and the open link confirmation has a copy button
|
||||
|
|
@ -478,3 +478,29 @@ pub fn open_folder_button(folder: std::path::PathBuf) -> Button {
|
|||
})
|
||||
.child(Icon::new(IconType::Folder).size(16.))
|
||||
}
|
||||
|
||||
pub fn copy_button(text: String, copied_body: &'static str, dispatch: crate::Actions) -> Button {
|
||||
Button::new()
|
||||
.secondary()
|
||||
.on_press(move |_| {
|
||||
if let Err(err) = freya::text_edit::Clipboard::set(text.clone()) {
|
||||
tracing::warn!("clipboard copy failed: {err:?}");
|
||||
dispatch
|
||||
.notify("Copy failed")
|
||||
.body("Could not copy to the clipboard.")
|
||||
.error()
|
||||
.toast_only()
|
||||
.send();
|
||||
} else {
|
||||
dispatch
|
||||
.notify("Copied to clipboard")
|
||||
.body(copied_body)
|
||||
.info()
|
||||
.icon(IconType::ClipboardCheck)
|
||||
.toast_only()
|
||||
.send();
|
||||
}
|
||||
})
|
||||
.child(Icon::new(IconType::Copy01).size(14.))
|
||||
.text("Copy")
|
||||
}
|
||||
|
|
|
|||
107
packages/oneclient_app/src/components/code_block.rs
Normal file
107
packages/oneclient_app/src/components/code_block.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use freya::prelude::*;
|
||||
|
||||
use crate::components::ScrollArea;
|
||||
use crate::theme::{self, colors};
|
||||
use crate::ui::border_all_color;
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct CodeBlock {
|
||||
code: String,
|
||||
color: Color,
|
||||
background: Color,
|
||||
font_family: Cow<'static, str>,
|
||||
font_size: f32,
|
||||
max_lines: Option<usize>,
|
||||
height: Option<f32>,
|
||||
key: DiffKey,
|
||||
}
|
||||
|
||||
impl KeyExt for CodeBlock {
|
||||
fn write_key(&mut self) -> &mut DiffKey {
|
||||
&mut self.key
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeBlock {
|
||||
pub fn new(code: impl Into<String>) -> Self {
|
||||
Self {
|
||||
code: code.into(),
|
||||
color: colors::fg_primary(),
|
||||
background: colors::component_bg(),
|
||||
font_family: Cow::Borrowed(theme::MONO_FONT),
|
||||
font_size: 12.,
|
||||
max_lines: None,
|
||||
height: None,
|
||||
key: DiffKey::None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn color(mut self, color: Color) -> Self {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn background(mut self, background: Color) -> Self {
|
||||
self.background = background;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn font_family(mut self, font_family: Cow<'static, str>) -> Self {
|
||||
self.font_family = font_family;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn font_size(mut self, font_size: f32) -> Self {
|
||||
self.font_size = font_size;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_lines(mut self, max_lines: usize) -> Self {
|
||||
self.max_lines = Some(max_lines);
|
||||
self
|
||||
}
|
||||
|
||||
/// Fixes the block's height and scrolls the code inside it
|
||||
pub fn height(mut self, height: f32) -> Self {
|
||||
self.height = Some(height);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for CodeBlock {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let text = SelectableText::new()
|
||||
.span(self.code.clone())
|
||||
.font_family(self.font_family.clone())
|
||||
.font_size(self.font_size)
|
||||
.max_lines(self.max_lines)
|
||||
.width(Size::fill())
|
||||
.color(self.color);
|
||||
|
||||
let block = rect()
|
||||
.width(Size::fill())
|
||||
.padding(Gaps::new_all(12.))
|
||||
.corner_radius(CornerRadius::new_all(8.))
|
||||
.background(self.background)
|
||||
.border(border_all_color(1., colors::component_border()));
|
||||
|
||||
match self.height {
|
||||
Some(height) => block
|
||||
.height(Size::px(height))
|
||||
.overflow(Overflow::Clip)
|
||||
.child(
|
||||
ScrollArea::new()
|
||||
.width(Size::fill())
|
||||
.height(Size::fill())
|
||||
.child(text),
|
||||
),
|
||||
None => block.child(text),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_key(&self) -> DiffKey {
|
||||
self.key.clone().or(self.default_key())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
use freya::prelude::*;
|
||||
|
||||
use crate::components::{Button, Icon, IconType, OVERLAY_BASE_LEVEL, OverlayPopup};
|
||||
use crate::hooks::use_link_confirm;
|
||||
use crate::components::{
|
||||
Button, CodeBlock, Icon, IconType, OVERLAY_BASE_LEVEL, OverlayPopup, copy_button,
|
||||
};
|
||||
use crate::hooks::{use_dispatch, use_link_confirm};
|
||||
use crate::theme::colors;
|
||||
use crate::ui::border_all_color;
|
||||
|
||||
|
|
@ -13,6 +15,7 @@ pub struct ConfirmLinkOverlay;
|
|||
impl Component for ConfirmLinkOverlay {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let mut pending = use_link_confirm();
|
||||
let dispatch = use_dispatch();
|
||||
let Some(url) = pending.read().clone() else {
|
||||
return rect().into_element();
|
||||
};
|
||||
|
|
@ -73,20 +76,9 @@ impl Component for ConfirmLinkOverlay {
|
|||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
.padding(Gaps::new_all(10.))
|
||||
.corner_radius(CornerRadius::new_all(8.))
|
||||
.background(colors::component_bg())
|
||||
.border(border_all_color(1., colors::component_border()))
|
||||
.child(
|
||||
label()
|
||||
.text(url.clone())
|
||||
.font_size(12.)
|
||||
.max_lines(4)
|
||||
.width(Size::fill())
|
||||
.color(colors::code_info()),
|
||||
),
|
||||
CodeBlock::new(url.clone())
|
||||
.color(colors::code_info())
|
||||
.max_lines(4),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
|
|
@ -94,6 +86,11 @@ impl Component for ConfirmLinkOverlay {
|
|||
.width(Size::fill())
|
||||
.main_align(Alignment::End)
|
||||
.spacing(8.)
|
||||
.child(copy_button(
|
||||
url.clone(),
|
||||
"Link copied to your clipboard.",
|
||||
dispatch,
|
||||
))
|
||||
.child(
|
||||
Button::new()
|
||||
.secondary()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use freya::prelude::*;
|
|||
use super::MarkdownStyle;
|
||||
use super::image::MarkdownImage;
|
||||
use super::parse::{Block, Inline, List, TextSpan};
|
||||
use crate::components::CodeBlock;
|
||||
use crate::hooks::LinkConfirmState;
|
||||
|
||||
pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element {
|
||||
|
|
@ -19,19 +20,12 @@ pub fn render_block(block: &Block, key: usize, style: &MarkdownStyle) -> Element
|
|||
Block::Paragraph { content } => render_content(content, style.paragraph_size, style)
|
||||
.key(key)
|
||||
.into(),
|
||||
Block::Code { code } => rect()
|
||||
.key(key)
|
||||
.width(Size::fill())
|
||||
Block::Code { code } => CodeBlock::new(code.clone())
|
||||
.color(style.color_code)
|
||||
.background(style.background_code)
|
||||
.corner_radius(CornerRadius::new_all(6.))
|
||||
.padding(Gaps::new_all(12.))
|
||||
.child(
|
||||
label()
|
||||
.text(code.clone())
|
||||
.font_family(style.code_font_family.clone())
|
||||
.font_size(style.code_font_size)
|
||||
.color(style.color_code),
|
||||
)
|
||||
.font_family(style.code_font_family.clone())
|
||||
.font_size(style.code_font_size)
|
||||
.key(key)
|
||||
.into(),
|
||||
Block::List(list) => render_list(list, style).key(key).into(),
|
||||
Block::Image { url, alt } => rect()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ mod button;
|
|||
mod charts;
|
||||
mod checkbox;
|
||||
mod cluster_update_popup;
|
||||
mod code_block;
|
||||
mod context_menu;
|
||||
mod control_center;
|
||||
mod date_range_picker;
|
||||
|
|
@ -62,10 +63,11 @@ pub use active_cluster_panel::ActiveClusterPanel;
|
|||
pub use asset_image::AssetImage;
|
||||
pub use avatar::Avatar;
|
||||
pub use blocked_downloads_popup::BlockedDownloadsPopup;
|
||||
pub use button::{Button, ButtonSize, ButtonVariant, link_button, open_folder_button};
|
||||
pub use button::{Button, ButtonSize, ButtonVariant, copy_button, link_button, open_folder_button};
|
||||
pub use charts::{BarChart, PieChart, ValueUnit, slice_color};
|
||||
pub use checkbox::checkbox_labeled;
|
||||
pub use cluster_update_popup::ClusterUpdatePopup;
|
||||
pub use code_block::CodeBlock;
|
||||
pub use context_menu::ContextMenu;
|
||||
pub use control_center::ControlCenter;
|
||||
pub use date_range_picker::{DateRange, DateRangePicker};
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ use skia_safe::sampling_options::CubicResampler;
|
|||
use crate::Route;
|
||||
use crate::components::Button;
|
||||
use crate::components::{
|
||||
ART_PREVIEW_EDGE, AppNavbar, DynamicArt, FileDropOverlay, Icon, IconType, OverlayPopup,
|
||||
ScrollArea, accept_drop,
|
||||
ART_PREVIEW_EDGE, AppNavbar, CodeBlock, DynamicArt, FileDropOverlay, Icon, IconType,
|
||||
OverlayPopup, accept_drop, copy_button,
|
||||
};
|
||||
use crate::layout::AnimatedAppOutlet;
|
||||
use crate::theme;
|
||||
use crate::use_settings_snapshot;
|
||||
use oneclient_core::clusters::Cluster;
|
||||
use oneclient_core::images::BACKGROUND_IMAGE_EDGE;
|
||||
|
|
@ -121,20 +120,6 @@ struct LaunchErrorDialog {
|
|||
impl Component for LaunchErrorDialog {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let dispatch = crate::hooks::use_dispatch();
|
||||
let lines: Vec<Element> = self
|
||||
.message
|
||||
.lines()
|
||||
.map(|line| {
|
||||
label()
|
||||
.text(line.to_string())
|
||||
.font_family(theme::MONO_FONT)
|
||||
.font_size(12.)
|
||||
.width(Size::fill())
|
||||
.color(colors::fg_primary())
|
||||
.into_element()
|
||||
})
|
||||
.collect();
|
||||
|
||||
let close = dispatch.clone();
|
||||
let outside_close = dispatch.clone();
|
||||
let copy_dispatch = dispatch.clone();
|
||||
|
|
@ -181,19 +166,9 @@ impl Component for LaunchErrorDialog {
|
|||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
.height(Size::px(260.))
|
||||
CodeBlock::new(self.message.clone())
|
||||
.background(CODE_BG)
|
||||
.corner_radius(CornerRadius::new_all(8.))
|
||||
.padding(Gaps::new_all(12.))
|
||||
.overflow(Overflow::Clip)
|
||||
.child(
|
||||
ScrollArea::new()
|
||||
.width(Size::fill())
|
||||
.height(Size::fill())
|
||||
.children(lines),
|
||||
),
|
||||
.height(260.),
|
||||
)
|
||||
.child(
|
||||
rect()
|
||||
|
|
@ -201,7 +176,11 @@ impl Component for LaunchErrorDialog {
|
|||
.width(Size::fill())
|
||||
.main_align(Alignment::End)
|
||||
.spacing(10.)
|
||||
.child(copy_error_button(&self.message, copy_dispatch))
|
||||
.child(copy_button(
|
||||
self.message.clone(),
|
||||
"Error message copied to your clipboard.",
|
||||
copy_dispatch,
|
||||
))
|
||||
.child(
|
||||
Button::new()
|
||||
.primary()
|
||||
|
|
@ -214,34 +193,6 @@ impl Component for LaunchErrorDialog {
|
|||
}
|
||||
}
|
||||
|
||||
fn copy_error_button(message: &str, dispatch: crate::Actions) -> impl IntoElement {
|
||||
let message = message.to_string();
|
||||
Button::new()
|
||||
.secondary()
|
||||
.on_press(move |_| {
|
||||
if let Err(err) = freya::text_edit::Clipboard::set(message.clone()) {
|
||||
tracing::warn!("clipboard copy failed: {err:?}");
|
||||
dispatch
|
||||
.notify("Copy failed")
|
||||
.body("Could not copy the error to the clipboard.")
|
||||
.error()
|
||||
.toast_only()
|
||||
.send();
|
||||
} else {
|
||||
dispatch
|
||||
.notify("Copied to clipboard")
|
||||
.body("Error message copied to your clipboard.")
|
||||
.info()
|
||||
.icon(IconType::ClipboardCheck)
|
||||
.toast_only()
|
||||
.send();
|
||||
}
|
||||
})
|
||||
.child(Icon::new(IconType::Copy01).size(14.))
|
||||
.text("Copy")
|
||||
.into_element()
|
||||
}
|
||||
|
||||
pub(crate) fn appshell_overlay(alpha: f32) -> Rect {
|
||||
let alpha = alpha.clamp(0., 1.);
|
||||
let scale = |a: u8| (f32::from(a) * alpha).round() as u8;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue