use super::*;
#[cfg(all(feature = "ticket", feature = "gui"))]
use crate::ticket::TicketAction;
#[cfg(feature = "html")]
use mkutil::html_scraping::PlaceImageHint;
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "mongo", derive(Serialize, Deserialize,))]
pub struct Reply {
#[cfg_attr(feature = "mongo", serde(skip))]
ext: ReplyExpand,
#[cfg(feature = "html")]
#[cfg_attr(feature = "mongo", serde(rename = "content",))]
raw_html: String,
sender: String,
#[cfg_attr(
feature = "mongo",
serde(
rename = "datetime",
with = "bson::serde_helpers::chrono_datetime_as_bson_datetime"
)
)]
pub created_at: DateTime<Utc>,
}
impl Reply {
pub fn empty() -> Self {
Self {
ext: Default::default(),
#[cfg(feature = "html")]
raw_html: String::new(),
sender: String::new(),
created_at: Utc::now(),
}
}
#[cfg(feature = "html")]
fn notes_with_image_array(mut self, img_hint: &PlaceImageHint) -> Self {
self.ext.note = Note::default().with_html(&self.raw_html, img_hint);
self
}
#[cfg(all(feature = "html", feature = "gui"))]
fn notes_with_inplace_embed(mut self, img_hint: &PlaceImageHint) -> Self {
self.ext.note = Note::inplace_embed().with_html(&self.raw_html, img_hint);
self
}
fn sent_by_self(mut self, current_username: &str) -> Self {
self.ext.sent_by_self = current_username == self.sender;
self
}
fn created_at_local(mut self, chrono_fmt: &str) -> Self {
self.ext = self.ext.created_at_local(self.created_at, chrono_fmt);
self
}
fn created_now(mut self) -> Self {
self.created_at = Utc::now();
self
}
fn sender(mut self, sender: String) -> Self {
self.sender = sender;
self
}
#[cfg(feature = "html")]
fn note_from_composer(mut self) -> AnyResult<Self> {
self.raw_html = self.ext.composer.inner()?.into_html_legacy()?;
Ok(self)
}
fn expand(mut self, ext: ReplyExpand) -> Self {
self.ext = ext;
self
}
pub fn start_editing(&mut self) {
self.ext.start_editing()
}
pub fn note_as_mut(&mut self) -> &mut Note {
&mut self.ext.note
}
pub fn clone_with_composer(&mut self) -> Self {
let ext = std::mem::take(&mut self.ext);
self.clone().expand(ext)
}
pub fn composer_as_ref_unwrap(&self) -> &Composer {
self.ext.composer.inner_as_ref_unwrap()
}
pub fn composer_as_mut_unwrap(&mut self) -> &mut Composer {
self.ext.composer.inner_as_mut_unwrap()
}
pub fn upload_images(
&mut self,
project: &Project,
asset: &ProductionAsset,
upload: impl Fn(&Path, &Project, &ProductionAsset) -> AnyResult<PathBuf>,
) -> ImageUploaded {
self.ext
.composer
.inner_as_mut_unwrap()
.upload_images(project, asset, upload)
}
pub fn texture_ids_mut(&mut self) {
self.ext.note.texture_ids_mut();
}
pub fn send_by_self(&self) -> bool {
self.ext.sent_by_self
}
}
#[cfg(feature = "gui")]
impl Reply {
pub fn sender_ui(&self, ui: &mut egui::Ui) {
if self.ext.sent_by_self {
ui.label(format!("☆ {} (me)", self.sender));
} else {
ui.label(&self.sender);
};
}
pub fn created_at_local_ui(&self, ui: &mut egui::Ui) {
ui.small(&self.ext.created_at_local);
}
#[cfg(feature = "ticket")]
pub fn show_dt_submit_edit_unwrap_buttons(
&mut self,
ui: &mut egui::Ui,
subticket: Option<&ObjectId>,
tx: &Sender<TicketAction>,
) {
use crate::ticket::MODIFIER_LAZY_HINT;
ui.horizontal(|ui| {
ui.with_layout(Layout::right_to_left(Align::Max), |ui| {
if ui.button("❌ Cancel").clicked() {
self.mode_mut(MediaMode::Read);
};
if ui
.add_enabled(
!self.ext.composer.0.as_ref().unwrap().is_empty(),
egui::Button::new("✔ Submit Edits"),
)
.on_hover_text(MODIFIER_LAZY_HINT)
.clicked()
{
let _ = tx.send(TicketAction::EditReply {
subticket: subticket.cloned(),
existing: None,
updated: self.clone_with_composer(),
lazy: ui.ctx().input(|i| i.modifiers.alt),
});
}
});
});
}
}
impl ReadWriteSuggest for Reply {
fn write_suggest() -> Self {
Self::empty().with_mode(MediaMode::WriteSuggest)
}
fn with_mode(mut self, mode: MediaMode) -> Self {
self.mode_mut(mode);
self
}
fn mode(&self) -> &MediaMode {
&self.ext.mode
}
fn mode_mut(&mut self, mode: MediaMode) {
self.ext.mode = mode;
}
#[cfg(feature = "gui")]
fn read_mode_ui(&mut self, ui: &mut egui::Ui) {
ui.colored_label(Color32::TEMPORARY_COLOR, "Raw HTML")
.on_hover_text(format!("{:?}", self.raw_html));
}
#[cfg(feature = "gui")]
fn write_suggest_ui(&mut self, ui: &mut egui::Ui) {
if ui
.button("➕ New Reply")
.on_hover_text("Add a new reply")
.clicked()
{
if self.ext.composer.is_none() {
self.ext.start_new_composing();
} else {
self.mode_mut(MediaMode::WriteCompose);
};
};
}
}
#[derive(Debug, Clone, Default)]
struct ReplyExpand {
mode: MediaMode,
sent_by_self: bool,
created_at_local: String,
note: Note,
composer: DiscardableComposer,
}
impl ReplyExpand {
fn start_new_composing(&mut self) {
self.composer = DiscardableComposer::new("Write your reply:");
self.mode = MediaMode::WriteCompose;
}
fn start_editing(&mut self) {
self.composer = DiscardableComposer::from_note(&self.note, "Edit reply:");
self.mode = MediaMode::WriteEdit;
}
fn created_at_local(mut self, created_at: DateTime<Utc>, chrono_fmt: &str) -> Self {
let local_time: DateTime<Local> = DateTime::from(created_at);
self.created_at_local = local_time.format(&chrono_fmt).to_string();
self
}
}
#[cfg(all(feature = "html", feature = "gui"))]
#[derive(Debug)]
pub struct ReplyReadBuilder(Reply);
#[cfg(all(feature = "html", feature = "gui"))]
impl ReplyReadBuilder {
pub fn new(reply: Reply) -> Self {
Self(reply)
}
pub fn finish(
self,
current_username: &str,
chrono_fmt: &str,
layout: &EmbedLayoutPref,
img_hint: &PlaceImageHint,
) -> Reply {
match layout {
EmbedLayoutPref::RowOrColumn => self.0.notes_with_image_array(img_hint),
EmbedLayoutPref::Carousel => self.0.notes_with_inplace_embed(img_hint),
}
.sent_by_self(current_username)
.created_at_local(chrono_fmt)
}
}
#[cfg(feature = "html")]
pub struct ReplyWriteBuilder(Reply);
#[cfg(feature = "html")]
impl ReplyWriteBuilder {
pub fn new(reply: Reply) -> Self {
Self(reply)
}
pub fn finish(self, sender: String, is_editing: bool) -> AnyResult<Reply> {
let reply = self.0.sender(sender).note_from_composer()?;
if is_editing {
Ok(reply)
} else {
Ok(reply.created_now())
}
}
}