From c9fb9f0a9b9843fd0b55e16b3a3a2c7d737c9a97 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 6 Jan 2021 12:46:13 -0800 Subject: [PATCH 1/3] reimplement inline changelog --- crates/core/src/addon.rs | 20 +- crates/core/src/repository/backend/curse.rs | 32 +- crates/core/src/repository/backend/git.rs | 64 ++++ crates/core/src/repository/backend/mod.rs | 6 + crates/core/src/repository/backend/tukui.rs | 48 +++ crates/core/src/repository/backend/wowi.rs | 8 + crates/core/src/repository/mod.rs | 44 +++ crates/core/src/utility.rs | 15 + src/gui/element/my_addons.rs | 339 ++++++++++++-------- src/gui/mod.rs | 10 +- src/gui/update.rs | 57 +++- 11 files changed, 503 insertions(+), 140 deletions(-) diff --git a/crates/core/src/addon.rs b/crates/core/src/addon.rs index 4801b68c..faa1d2b6 100644 --- a/crates/core/src/addon.rs +++ b/crates/core/src/addon.rs @@ -1,8 +1,8 @@ use crate::{ - error::ParseError, + error::{ParseError, RepositoryError}, repository::{ - GitKind, GlobalReleaseChannel, ReleaseChannel, RemotePackage, RepositoryIdentifiers, - RepositoryKind, RepositoryMetadata, RepositoryPackage, + Changelog, GitKind, GlobalReleaseChannel, ReleaseChannel, RemotePackage, + RepositoryIdentifiers, RepositoryKind, RepositoryMetadata, RepositoryPackage, }, utility::strip_non_digits, }; @@ -360,6 +360,20 @@ impl Addon { } } + pub async fn changelog( + &self, + default_release_channel: GlobalReleaseChannel, + ) -> Result { + let text = if let Some(repo) = self.repository.as_ref() { + repo.get_changelog(self.release_channel, default_release_channel) + .await? + } else { + None + }; + + Ok(Changelog { text }) + } + /// Returns the curse id of the addon, if applicable. pub fn curse_id(&self) -> Option { if self.repository_kind() == Some(RepositoryKind::Curse) { diff --git a/crates/core/src/repository/backend/curse.rs b/crates/core/src/repository/backend/curse.rs index e11be29f..35375702 100644 --- a/crates/core/src/repository/backend/curse.rs +++ b/crates/core/src/repository/backend/curse.rs @@ -1,8 +1,9 @@ use super::*; use crate::config::Flavor; use crate::error::DownloadError; -use crate::network::post_json_async; +use crate::network::{post_json_async, request_async}; use crate::repository::{ReleaseChannel, RemotePackage}; +use crate::utility::{regex_html_tags_to_newline, regex_html_tags_to_space, truncate}; use async_trait::async_trait; use chrono::{DateTime, Utc}; @@ -43,6 +44,35 @@ impl Backend for Curse { Ok(metadata) } + + async fn get_changelog( + &self, + file_id: Option, + _tag_name: Option, + ) -> Result, RepositoryError> { + let file_id = file_id.ok_or(RepositoryError::CurseChangelogFileId)?; + + let url = format!( + "{}/addon/{}/file/{}/changelog", + API_ENDPOINT, self.id, file_id + ); + + let mut resp = request_async(&url, vec![], None).await?; + + if resp.status().is_success() { + let changelog: String = resp.text_async().await?; + + let c = regex_html_tags_to_newline() + .replace_all(&changelog, "\n") + .to_string(); + let c = regex_html_tags_to_space().replace_all(&c, "").to_string(); + let c = truncate(&c, 2500).to_string(); + + return Ok(Some(c)); + } + + Ok(None) + } } pub(crate) fn metadata_from_curse_package(flavor: Flavor, package: Package) -> RepositoryMetadata { diff --git a/crates/core/src/repository/backend/git.rs b/crates/core/src/repository/backend/git.rs index 7995491b..55c07950 100644 --- a/crates/core/src/repository/backend/git.rs +++ b/crates/core/src/repository/backend/git.rs @@ -105,6 +105,38 @@ mod github { Ok(metadata) } + + async fn get_changelog( + &self, + _file_id: Option, + tag_name: Option, + ) -> Result, RepositoryError> { + let tag_name = tag_name.ok_or(RepositoryError::GitChangelogTagName)?; + + let mut path = self.url.path().split('/'); + // Get rid of leading slash + path.next(); + + let author = path.next().ok_or(RepositoryError::GitMissingAuthor { + url: self.url.to_string(), + })?; + let repo = path.next().ok_or(RepositoryError::GitMissingRepo { + url: self.url.to_string(), + })?; + + let url = format!( + "https://api.github.com/repos/{}/{}/releases/tags/{}", + author, repo, tag_name + ); + + let mut resp = request_async(&url, vec![], None).await?; + + let release: Release = resp + .json() + .map_err(|_| RepositoryError::GitMissingRelease { url })?; + + Ok(Some(release.body)) + } } fn set_remote_package( @@ -312,6 +344,38 @@ mod gitlab { Ok(metadata) } + + async fn get_changelog( + &self, + _file_id: Option, + tag_name: Option, + ) -> Result, RepositoryError> { + let tag_name = tag_name.ok_or(RepositoryError::GitChangelogTagName)?; + + let mut path = self.url.path().split('/'); + // Get rid of leading slash + path.next(); + + let author = path.next().ok_or(RepositoryError::GitMissingAuthor { + url: self.url.to_string(), + })?; + let repo = path.next().ok_or(RepositoryError::GitMissingRepo { + url: self.url.to_string(), + })?; + + let url = format!( + "https://gitlab.com/api/v4/projects/{}%2F{}/releases/{}", + author, repo, tag_name + ); + + let mut resp = request_async(&url, vec![], None).await?; + + let release: Release = resp + .json() + .map_err(|_| RepositoryError::GitMissingRelease { url })?; + + Ok(Some(release.description)) + } } #[derive(Debug, Deserialize, Clone)] diff --git a/crates/core/src/repository/backend/mod.rs b/crates/core/src/repository/backend/mod.rs index 51d935f9..f39953ea 100644 --- a/crates/core/src/repository/backend/mod.rs +++ b/crates/core/src/repository/backend/mod.rs @@ -17,6 +17,12 @@ pub use wowi::WowI; #[async_trait] pub(crate) trait Backend: DynClone + Send + Sync { async fn get_metadata(&self) -> Result; + + async fn get_changelog( + &self, + file_id: Option, + tag_name: Option, + ) -> Result, RepositoryError>; } clone_trait_object!(Backend); diff --git a/crates/core/src/repository/backend/tukui.rs b/crates/core/src/repository/backend/tukui.rs index 263d6437..4cde9cee 100644 --- a/crates/core/src/repository/backend/tukui.rs +++ b/crates/core/src/repository/backend/tukui.rs @@ -3,6 +3,7 @@ use crate::config::Flavor; use crate::error::{DownloadError, RepositoryError}; use crate::network::request_async; use crate::repository::{ReleaseChannel, RemotePackage}; +use crate::utility::{regex_html_tags_to_newline, regex_html_tags_to_space, truncate}; use async_trait::async_trait; use chrono::{NaiveDateTime, TimeZone, Utc}; @@ -26,6 +27,39 @@ impl Backend for Tukui { Ok(metadata) } + + async fn get_changelog( + &self, + _file_id: Option, + _tag_name: Option, + ) -> Result, RepositoryError> { + let url = changelog_endpoint(&self.id, &self.flavor); + + match self.flavor { + Flavor::Retail | Flavor::RetailBeta | Flavor::RetailPTR => { + // Only TukUI and ElvUI main addons has changelog which can be fetched. + // The others is embeded into a page. + if &self.id == "-1" || &self.id == "-2" { + let mut resp = request_async(&url, vec![], None).await?; + + if resp.status().is_success() { + let changelog: String = resp.text()?; + + let c = regex_html_tags_to_newline() + .replace_all(&changelog, "\n") + .to_string(); + let c = regex_html_tags_to_space().replace_all(&c, "").to_string(); + let c = truncate(&c, 2500).to_string(); + + return Ok(Some(c)); + } + } + } + Flavor::Classic | Flavor::ClassicPTR => {} + } + + Ok(None) + } } pub(crate) fn metadata_from_tukui_package(package: TukuiPackage) -> RepositoryMetadata { @@ -92,6 +126,20 @@ fn api_endpoint(id: &str, flavor: &Flavor) -> String { ) } +fn changelog_endpoint(id: &str, flavor: &Flavor) -> String { + match flavor { + Flavor::Retail | Flavor::RetailPTR | Flavor::RetailBeta => match id { + "-1" => "https://www.tukui.org/ui/tukui/changelog".to_owned(), + "-2" => "https://www.tukui.org/ui/elvui/changelog".to_owned(), + _ => format!("https://www.tukui.org/addons.php?id={}&changelog", id), + }, + Flavor::Classic | Flavor::ClassicPTR => format!( + "https://www.tukui.org/classic-addons.php?id={}&changelog", + id + ), + } +} + /// Function to fetch a remote addon package which contains /// information about the addon on the repository. pub(crate) async fn fetch_remote_package( diff --git a/crates/core/src/repository/backend/wowi.rs b/crates/core/src/repository/backend/wowi.rs index 734117fd..6a30b944 100644 --- a/crates/core/src/repository/backend/wowi.rs +++ b/crates/core/src/repository/backend/wowi.rs @@ -36,6 +36,14 @@ impl Backend for WowI { Ok(metadata) } + + async fn get_changelog( + &self, + _file_id: Option, + _tag_name: Option, + ) -> Result, RepositoryError> { + Ok(None) + } } pub(crate) fn metadata_from_wowi_package(package: WowIPackage) -> RepositoryMetadata { diff --git a/crates/core/src/repository/mod.rs b/crates/core/src/repository/mod.rs index 3606a189..148dd8d8 100644 --- a/crates/core/src/repository/mod.rs +++ b/crates/core/src/repository/mod.rs @@ -148,6 +148,45 @@ impl RepositoryPackage { Ok(()) } + + /// Get changelog from the repository + /// + /// `channel` is only used for the Curse & GitHub repository since + /// we can get unique changelogs for each version + pub(crate) async fn get_changelog( + &self, + release_channel: ReleaseChannel, + default_release_channel: GlobalReleaseChannel, + ) -> Result, RepositoryError> { + let release_channel = if release_channel == ReleaseChannel::Default { + default_release_channel.convert_to_release_channel() + } else { + release_channel + }; + + let file_id = if self.kind == RepositoryKind::Curse { + self.metadata + .remote_packages + .get(&release_channel) + .and_then(|p| p.file_id) + } else { + None + }; + + let tag_name = if self.kind.is_git() { + let remote_package = self.metadata.remote_packages.get(&release_channel).ok_or( + RepositoryError::MissingPackageChannel { + channel: release_channel, + }, + )?; + + Some(remote_package.version.clone()) + } else { + None + }; + + self.backend.get_changelog(file_id, tag_name).await + } } /// Metadata from one of the repository APIs @@ -306,3 +345,8 @@ pub struct RepositoryIdentifiers { pub curse: Option, pub git: Option, } + +#[derive(Debug, Clone)] +pub struct Changelog { + pub text: Option, +} diff --git a/crates/core/src/utility.rs b/crates/core/src/utility.rs index 1ced85f7..02e17b1b 100644 --- a/crates/core/src/utility.rs +++ b/crates/core/src/utility.rs @@ -269,6 +269,21 @@ where }) } +pub(crate) fn truncate(s: &str, max_chars: usize) -> &str { + match s.char_indices().nth(max_chars) { + None => s, + Some((idx, _)) => &s[..idx], + } +} + +pub(crate) fn regex_html_tags_to_newline() -> Regex { + regex::Regex::new(r"
|#.\s").unwrap() +} + +pub(crate) fn regex_html_tags_to_space() -> Regex { + regex::Regex::new(r"<[^>]*>|&#?\w+;|[gl]t;").unwrap() +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/gui/element/my_addons.rs b/src/gui/element/my_addons.rs index 0c38c0c9..588270cd 100644 --- a/src/gui/element/my_addons.rs +++ b/src/gui/element/my_addons.rs @@ -216,9 +216,12 @@ pub fn data_row_container<'a, 'b>( Button::new(&mut addon.remote_version_btn_state, remote_version) .style(style::NormalTextButton(color_palette)); - if let Some(link) = &changelog_url { + if changelog_url.is_some() { remote_version_button = - remote_version_button.on_press(Interaction::OpenLink(link.clone())); + remote_version_button.on_press(Interaction::Expand(ExpandType::Changelog { + addon: addon_cloned.clone(), + changelog: None, + })); } let remote_version_button: Element = remote_version_button.into(); @@ -483,151 +486,219 @@ pub fn data_row_container<'a, 'b>( let mut addon_column = Column::new().push(row); if is_addon_expanded { - if let ExpandType::Details(_) = expand_type { - let notes = notes.unwrap_or_else(|| "No description for addon.".to_string()); - let author = author.unwrap_or_else(|| "-".to_string()); - let left_spacer = Space::new(Length::Units(DEFAULT_PADDING), Length::Units(0)); - let space = Space::new(Length::Units(0), Length::Units(DEFAULT_PADDING * 2)); - let bottom_space = Space::new(Length::Units(0), Length::Units(4)); - let notes_title_text = Text::new("Summary").size(DEFAULT_FONT_SIZE); - let notes_text = Text::new(notes).size(DEFAULT_FONT_SIZE); - let author_text = Text::new(author).size(DEFAULT_FONT_SIZE); - let author_title_text = Text::new("Author(s)").size(DEFAULT_FONT_SIZE); - let author_title_container = Container::new(author_title_text) - .style(style::HoverableBrightForegroundContainer(color_palette)); - let notes_title_container = Container::new(notes_title_text) - .style(style::HoverableBrightForegroundContainer(color_palette)); - - let release_date_text: String = if let Some(package) = &release_package { - let f = timeago::Formatter::new(); - let now = Local::now(); - - if let Some(time) = package.date_time.as_ref() { - format!("is {}", f.convert_chrono(*time, now)) + match expand_type { + ExpandType::Details(_) => { + let notes = notes.unwrap_or_else(|| "No description for addon.".to_string()); + let author = author.unwrap_or_else(|| "-".to_string()); + let left_spacer = Space::new(Length::Units(DEFAULT_PADDING), Length::Units(0)); + let space = Space::new(Length::Units(0), Length::Units(DEFAULT_PADDING * 2)); + let bottom_space = Space::new(Length::Units(0), Length::Units(4)); + let notes_title_text = Text::new("Summary").size(DEFAULT_FONT_SIZE); + let notes_text = Text::new(notes).size(DEFAULT_FONT_SIZE); + let author_text = Text::new(author).size(DEFAULT_FONT_SIZE); + let author_title_text = Text::new("Author(s)").size(DEFAULT_FONT_SIZE); + let author_title_container = Container::new(author_title_text) + .style(style::HoverableBrightForegroundContainer(color_palette)); + let notes_title_container = Container::new(notes_title_text) + .style(style::HoverableBrightForegroundContainer(color_palette)); + + let release_date_text: String = if let Some(package) = &release_package { + let f = timeago::Formatter::new(); + let now = Local::now(); + + if let Some(time) = package.date_time.as_ref() { + format!("is {}", f.convert_chrono(*time, now)) + } else { + "".to_string() + } } else { - "".to_string() + "has no avaiable release".to_string() + }; + let release_date_text = Text::new(release_date_text).size(DEFAULT_FONT_SIZE); + let release_date_text_container = Container::new(release_date_text) + .center_y() + .padding(5) + .style(style::FadedBrightForegroundContainer(color_palette)); + + let release_channel_title = + Text::new("Remote release channel").size(DEFAULT_FONT_SIZE); + let release_channel_title_container = Container::new(release_channel_title) + .style(style::FadedBrightForegroundContainer(color_palette)); + let release_channel_list = PickList::new( + &mut addon.pick_release_channel_state, + &ReleaseChannel::ALL[..], + Some(addon.release_channel), + Message::ReleaseChannelSelected, + ) + .text_size(14) + .width(Length::Units(100)) + .style(style::PickList(color_palette)); + + let mut website_button = Button::new( + &mut addon.website_btn_state, + Text::new("Website").size(DEFAULT_FONT_SIZE), + ) + .style(style::DefaultButton(color_palette)); + + if let Some(link) = website_url { + website_button = website_button.on_press(Interaction::OpenLink(link)); } - } else { - "has no avaiable release".to_string() - }; - let release_date_text = Text::new(release_date_text).size(DEFAULT_FONT_SIZE); - let release_date_text_container = Container::new(release_date_text) - .center_y() - .padding(5) - .style(style::FadedBrightForegroundContainer(color_palette)); - - let release_channel_title = Text::new("Remote release channel").size(DEFAULT_FONT_SIZE); - let release_channel_title_container = Container::new(release_channel_title) - .style(style::FadedBrightForegroundContainer(color_palette)); - let release_channel_list = PickList::new( - &mut addon.pick_release_channel_state, - &ReleaseChannel::ALL[..], - Some(addon.release_channel), - Message::ReleaseChannelSelected, - ) - .text_size(14) - .width(Length::Units(100)) - .style(style::PickList(color_palette)); - - let mut website_button = Button::new( - &mut addon.website_btn_state, - Text::new("Website").size(DEFAULT_FONT_SIZE), - ) - .style(style::DefaultButton(color_palette)); - - if let Some(link) = website_url { - website_button = website_button.on_press(Interaction::OpenLink(link)); - } - let website_button: Element = website_button.into(); + let website_button: Element = website_button.into(); - let is_ignored = addon.state == AddonState::Ignored; - let ignore_button_text = if is_ignored { - Text::new("Unignore").size(DEFAULT_FONT_SIZE) - } else { - Text::new("Ignore").size(DEFAULT_FONT_SIZE) - }; + let is_ignored = addon.state == AddonState::Ignored; + let ignore_button_text = if is_ignored { + Text::new("Unignore").size(DEFAULT_FONT_SIZE) + } else { + Text::new("Ignore").size(DEFAULT_FONT_SIZE) + }; + + let mut ignore_button = + Button::new(&mut addon.ignore_btn_state, ignore_button_text) + .on_press(Interaction::Ignore(addon.primary_folder_id.clone())) + .style(style::DefaultButton(color_palette)); + + if is_ignored { + ignore_button = ignore_button + .on_press(Interaction::Unignore(addon.primary_folder_id.clone())); + } else { + ignore_button = ignore_button + .on_press(Interaction::Ignore(addon.primary_folder_id.clone())); + } + + let ignore_button: Element = ignore_button.into(); - let mut ignore_button = Button::new(&mut addon.ignore_btn_state, ignore_button_text) - .on_press(Interaction::Ignore(addon.primary_folder_id.clone())) + let delete_button: Element = Button::new( + &mut addon.delete_btn_state, + Text::new("Delete").size(DEFAULT_FONT_SIZE), + ) + .on_press(Interaction::Delete(addon.primary_folder_id.clone())) + .style(style::DefaultDeleteButton(color_palette)) + .into(); + + let mut changelog_button = Button::new( + &mut addon.changelog_btn_state, + Text::new("Changelog").size(DEFAULT_FONT_SIZE), + ) .style(style::DefaultButton(color_palette)); - if is_ignored { - ignore_button = - ignore_button.on_press(Interaction::Unignore(addon.primary_folder_id.clone())); - } else { - ignore_button = - ignore_button.on_press(Interaction::Ignore(addon.primary_folder_id.clone())); + if changelog_url.is_some() { + changelog_button = + changelog_button.on_press(Interaction::Expand(ExpandType::Changelog { + addon: addon_cloned, + changelog: None, + })); + } + + let changelog_button: Element = changelog_button.into(); + + let test_row = Row::new() + .push(release_channel_list) + .push(release_date_text_container); + + let button_row = Row::new() + .push(Space::new(Length::Fill, Length::Units(0))) + .push(website_button.map(Message::Interaction)) + .push(Space::new(Length::Units(5), Length::Units(0))) + .push(changelog_button.map(Message::Interaction)) + .push(Space::new(Length::Units(5), Length::Units(0))) + .push(ignore_button.map(Message::Interaction)) + .push(Space::new(Length::Units(5), Length::Units(0))) + .push(delete_button.map(Message::Interaction)) + .width(Length::Fill); + let column = Column::new() + .push(author_title_container) + .push(Space::new(Length::Units(0), Length::Units(3))) + .push(author_text) + .push(Space::new(Length::Units(0), Length::Units(15))) + .push(notes_title_container) + .push(Space::new(Length::Units(0), Length::Units(3))) + .push(notes_text) + .push(Space::new(Length::Units(0), Length::Units(15))) + .push(release_channel_title_container) + .push(Space::new(Length::Units(0), Length::Units(3))) + .push(test_row) + .push(space) + .push(button_row) + .push(bottom_space); + let details_container = Container::new(column) + .width(Length::Fill) + .padding(20) + .style(style::FadedNormalForegroundContainer(color_palette)); + + let row = Row::new() + .push(left_spacer) + .push(details_container) + .push(Space::new( + Length::Units(DEFAULT_PADDING + 5), + Length::Units(0), + )) + .spacing(1); + + addon_column = addon_column + .push(Space::new(Length::FillPortion(1), Length::Units(1))) + .push(row); } + ExpandType::Changelog { changelog, .. } => { + let left_spacer = Space::new(Length::Units(DEFAULT_PADDING), Length::Units(0)); + let bottom_space = Space::new(Length::Units(0), Length::Units(4)); - let ignore_button: Element = ignore_button.into(); + let changelog_title_text = Text::new("Changelog").size(DEFAULT_FONT_SIZE); + let changelog_title_container = Container::new(changelog_title_text) + .style(style::BrightForegroundContainer(color_palette)); - let delete_button: Element = Button::new( - &mut addon.delete_btn_state, - Text::new("Delete").size(DEFAULT_FONT_SIZE), - ) - .on_press(Interaction::Delete(addon.primary_folder_id.clone())) - .style(style::DefaultDeleteButton(color_palette)) - .into(); + let changelog_text = match changelog { + Some(changelog) => changelog.text.as_deref().unwrap_or("Please view this changelog in the browser by pressing 'Full Changelog' to the right"), + _ => "Loading...", + }; - let mut changelog_button = Button::new( - &mut addon.changelog_btn_state, - Text::new("Changelog").size(DEFAULT_FONT_SIZE), - ) - .style(style::DefaultButton(color_palette)); + let mut full_changelog_button = Button::new( + &mut addon.changelog_btn_state, + Text::new("Full Changelog").size(DEFAULT_FONT_SIZE), + ) + .style(style::DefaultButton(color_palette)); - if let Some(link) = changelog_url { - changelog_button = changelog_button.on_press(Interaction::OpenLink(link)); - } + if let Some(url) = &changelog_url { + full_changelog_button = + full_changelog_button.on_press(Interaction::OpenLink(url.clone())); + } + + let full_changelog_button: Element = full_changelog_button.into(); + + let mut button_row = + Row::new().push(Space::new(Length::FillPortion(1), Length::Units(0))); - let changelog_button: Element = changelog_button.into(); - - let test_row = Row::new() - .push(release_channel_list) - .push(release_date_text_container); - - let button_row = Row::new() - .push(Space::new(Length::Fill, Length::Units(0))) - .push(website_button.map(Message::Interaction)) - .push(Space::new(Length::Units(5), Length::Units(0))) - .push(changelog_button.map(Message::Interaction)) - .push(Space::new(Length::Units(5), Length::Units(0))) - .push(ignore_button.map(Message::Interaction)) - .push(Space::new(Length::Units(5), Length::Units(0))) - .push(delete_button.map(Message::Interaction)) - .width(Length::Fill); - let column = Column::new() - .push(author_title_container) - .push(Space::new(Length::Units(0), Length::Units(3))) - .push(author_text) - .push(Space::new(Length::Units(0), Length::Units(15))) - .push(notes_title_container) - .push(Space::new(Length::Units(0), Length::Units(3))) - .push(notes_text) - .push(Space::new(Length::Units(0), Length::Units(15))) - .push(release_channel_title_container) - .push(Space::new(Length::Units(0), Length::Units(3))) - .push(test_row) - .push(space) - .push(button_row) - .push(bottom_space); - let details_container = Container::new(column) - .width(Length::Fill) - .padding(20) - .style(style::FadedNormalForegroundContainer(color_palette)); - - let row = Row::new() - .push(left_spacer) - .push(details_container) - .push(Space::new( - Length::Units(DEFAULT_PADDING + 5), - Length::Units(0), - )) - .spacing(1); - - addon_column = addon_column - .push(Space::new(Length::FillPortion(1), Length::Units(1))) - .push(row); + if changelog_url.is_some() { + button_row = button_row.push(full_changelog_button.map(Message::Interaction)); + } + + let column = Column::new() + .push(changelog_title_container) + .push(Space::new(Length::Units(0), Length::Units(12))) + .push(Text::new(changelog_text).size(DEFAULT_FONT_SIZE)) + .push(Space::new(Length::Units(0), Length::Units(8))) + .push(button_row) + .push(bottom_space); + + let details_container = Container::new(column) + .width(Length::Fill) + .padding(20) + .style(style::FadedNormalForegroundContainer(color_palette)); + + let row = Row::new() + .push(left_spacer) + .push(details_container) + .push(Space::new( + Length::Units(DEFAULT_PADDING + 5), + Length::Units(0), + )) + .spacing(1); + + addon_column = addon_column + .push(Space::new(Length::FillPortion(1), Length::Units(1))) + .push(row); + } + ExpandType::None => {} } } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 63c997a1..e9e02ede 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -14,7 +14,7 @@ use ajour_core::{ config::{ColumnConfig, ColumnConfigV2, Config, Flavor, SelfUpdateChannel}, error::*, fs::PersistentData, - repository::{GlobalReleaseChannel, ReleaseChannel}, + repository::{Changelog, GlobalReleaseChannel, ReleaseChannel}, theme::{load_user_themes, Theme}, utility::{self, get_latest_release}, }; @@ -162,6 +162,7 @@ pub enum Message { WeakAurasAccountSelected(String), ParsedAuras((Flavor, Result, ajour_weak_auras::Error>)), AurasUpdated((Flavor, Result, ajour_weak_auras::Error>)), + FetchedChangelog((Addon, Result)), } pub struct Ajour { @@ -448,6 +449,9 @@ impl Application for Ajour { // Checks if the current addon is expanded. let is_addon_expanded = match &self.expanded_type { ExpandType::Details(a) => a.primary_folder_id == addon.primary_folder_id, + ExpandType::Changelog { addon: a, .. } => { + addon.primary_folder_id == a.primary_folder_id + } ExpandType::None => false, }; @@ -1072,6 +1076,10 @@ impl Default for InstallFromSCMState { #[derive(Debug, Clone)] pub enum ExpandType { Details(Addon), + Changelog { + addon: Addon, + changelog: Option, + }, None, } diff --git a/src/gui/update.rs b/src/gui/update.rs index 9bfe76ab..e84ab44d 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -19,7 +19,7 @@ use { fs::{delete_addons, delete_saved_variables, install_addon, PersistentData}, network::download_addon, parse::{read_addon_directory, update_addon_fingerprint}, - repository::{RepositoryKind, RepositoryPackage}, + repository::{Changelog, RepositoryKind, RepositoryPackage}, utility::{download_update_to_temp_file, get_latest_release, wow_path_resolution}, }, ajour_weak_auras::{Aura, AuraStatus}, @@ -327,11 +327,57 @@ pub fn handle_message(ajour: &mut Ajour, message: Message) -> Result { + log::debug!( + "Interaction::Expand(Changelog({:?}))", + &addon.primary_folder_id + ); + let should_close = match &ajour.expanded_type { + ExpandType::Changelog { addon: a, .. } => { + addon.primary_folder_id == a.primary_folder_id + } + _ => false, + }; + + if should_close { + ajour.expanded_type = ExpandType::None; + } else { + ajour.expanded_type = expand_type.clone(); + + return Ok(Command::perform( + perform_fetch_changelog( + addon.clone(), + ajour.config.addons.global_release_channel, + ), + Message::FetchedChangelog, + )); + } + } ExpandType::None => { log::debug!("Interaction::Expand(ExpandType::None)"); } } } + Message::FetchedChangelog((addon, result)) => match result { + Ok(changelog) => { + log::debug!("Message::FetchedChangelog({})", &addon.primary_folder_id); + + if let ExpandType::Changelog { + addon: a, + changelog: c, + } = &mut ajour.expanded_type + { + if a.primary_folder_id == addon.primary_folder_id { + *c = Some(changelog); + } + } + } + error @ Err(_) => { + let error = error.context("Failed to fetch changelog").unwrap_err(); + log_error(&error); + ajour.error = Some(error); + } + }, Message::Interaction(Interaction::Delete(id)) => { log::debug!("Interaction::Delete({})", &id); @@ -2045,6 +2091,15 @@ async fn is_weak_auras_installed(flavor: Flavor, addon_dir: PathBuf) -> (Flavor, ) } +async fn perform_fetch_changelog( + addon: Addon, + default_release_channel: GlobalReleaseChannel, +) -> (Addon, Result) { + let changelog = addon.changelog(default_release_channel).await; + + (addon, changelog) +} + fn sort_addons( addons: &mut [Addon], global_release_channel: GlobalReleaseChannel, From 34e39ccc6252c9fd63884267d985a88870b33185 Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 6 Jan 2021 12:48:07 -0800 Subject: [PATCH 2/3] update CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69cca67f..6595131b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ and `Removed`. ## [Unreleased] +### Added + +- Added back inline changelogs for remote version. Clicking on the remote version + will show the changelog inline instead of opening a browser window. + ### Fixed - Parsing error causing WeakAuras to fail parsing due to missing "version" field From 3fb222ca1b4376b37263a0d311d0be725021965d Mon Sep 17 00:00:00 2001 From: Cory Forsstrom Date: Wed, 6 Jan 2021 15:34:14 -0800 Subject: [PATCH 3/3] collapse changelog on entire row click --- src/gui/update.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/gui/update.rs b/src/gui/update.rs index e84ab44d..319a4669 100644 --- a/src/gui/update.rs +++ b/src/gui/update.rs @@ -314,10 +314,16 @@ pub fn handle_message(ajour: &mut Ajour, message: Message) -> Result { // An addon can be exanded in two ways. match &expand_type { - ExpandType::Details(a) => { - log::debug!("Interaction::Expand(Details({:?}))", &a.primary_folder_id); + ExpandType::Details(addon) => { + log::debug!( + "Interaction::Expand(Details({:?}))", + &addon.primary_folder_id + ); let should_close = match &ajour.expanded_type { - ExpandType::Details(ea) => a.primary_folder_id == ea.primary_folder_id, + ExpandType::Details(a) => addon.primary_folder_id == a.primary_folder_id, + ExpandType::Changelog { addon: a, .. } => { + addon.primary_folder_id == a.primary_folder_id + } _ => false, };