diff options
author | Rémy Raes <contact@remyraes.com> | 2022-11-09 23:42:54 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-09 23:42:54 +0100 |
commit | 32fd7e37d0a2e72a88bac3462fbfac3a5d4b6015 (patch) | |
tree | 02452ede248e12233499caaff4c728d5e2f2ab81 | |
parent | 90b2153a6d2620f55c22c09e9a624f81e50cca44 (diff) | |
download | FlightCore-32fd7e37d0a2e72a88bac3462fbfac3a5d4b6015.tar.gz FlightCore-32fd7e37d0a2e72a88bac3462fbfac3a5d4b6015.zip |
feat: Patch notes (#18)
* feat: add Rust method to fetch Northstar release notes
* feat: fetch release notes on changelog view mount
* feat: only transmit some info to frontend
GitHub API gives much information about releases, we only need some: name,
publication date and content of such release; so other information is not
transmitted to UI.
* feat: add ReleaseInfo Typescript interface matching Rust struct
* feat: display release notes on a timeline
* refactor: remove old releases external link
* build: add marked dependency
* build: add marked types dev dependency
* feat: format release notes' markdown
* fix: member typo in ReleaseInfo interface
* fix: type releases array
* fix: open github links in external browser
* fix: adjust marked import
* refactor: store release notes in store
Release notes are now stored in the app store, so we don't have to
fetch them multiple times.
* fix: notes fetching method is now async
* feat: display a loading bar while release notes are being fetched
* feat: display dates in white
* feat: release notes' dates are human-readable
* fix: make menu bar appear on top of release notes view when scrolled
* feat: add custom scrollbar
* refactor: format releases creation to please reviewer
* Update src-tauri/src/github/mod.rs
* Update src-tauri/src/github/release_notes.rs
* Update src-vue/src/utils/ReleaseInfo.d.ts
* fix: augment scrollbar opacity
* fix: only display releases' release date (no more time of the day)
* fix: adjust Github request user agent
* style: add missing end line in src-vue/src/style.css
* fix: link formatting only targets GitHub PR links (whose name begins with a #)
* fix: timeline element children cannot be bigger than container card
-rw-r--r-- | src-tauri/src/github/mod.rs | 1 | ||||
-rw-r--r-- | src-tauri/src/github/release_notes.rs | 48 | ||||
-rw-r--r-- | src-tauri/src/main.rs | 4 | ||||
-rw-r--r-- | src-vue/package-lock.json | 30 | ||||
-rw-r--r-- | src-vue/package.json | 2 | ||||
-rw-r--r-- | src-vue/src/plugins/store.ts | 7 | ||||
-rw-r--r-- | src-vue/src/style.css | 5 | ||||
-rw-r--r-- | src-vue/src/utils/ReleaseInfo.d.ts | 6 | ||||
-rw-r--r-- | src-vue/src/views/ChangelogView.vue | 80 |
9 files changed, 172 insertions, 11 deletions
diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs new file mode 100644 index 00000000..80a1831a --- /dev/null +++ b/src-tauri/src/github/mod.rs @@ -0,0 +1 @@ +pub mod release_notes; diff --git a/src-tauri/src/github/release_notes.rs b/src-tauri/src/github/release_notes.rs new file mode 100644 index 00000000..db705dab --- /dev/null +++ b/src-tauri/src/github/release_notes.rs @@ -0,0 +1,48 @@ +use std::vec::Vec; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ReleaseInfo { + pub name: String, + pub published_at: String, + pub body: String +} + +#[tauri::command] +pub async fn get_northstar_release_notes() -> Result<Vec<ReleaseInfo>, String> { + println!("Fetching releases notes from GitHub API"); + + let url = "https://api.github.com/repos/R2Northstar/Northstar/releases"; + let user_agent = "GeckoEidechse/FlightCore"; + let client = reqwest::Client::new(); + let res = client + .get(url) + .header(reqwest::header::USER_AGENT, user_agent) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + let json_response: Vec<serde_json::Value> = + serde_json::from_str(&res).expect("JSON was not well-formatted"); + println!("Done checking GitHub API"); + + return Ok( + json_response.iter().map(|release| ReleaseInfo { + name: release.get("name") + .and_then(|value| value.as_str()) + .unwrap() + .to_string(), + published_at: release.get("published_at") + .and_then(|value| value.as_str()) + .unwrap() + .to_string(), + body: release.get("body") + .and_then(|value| value.as_str()) + .unwrap() + .to_string(), + }).collect() + ); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index c3172ee8..26f138ba 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -16,6 +16,9 @@ use app::{ install_northstar, launch_northstar, linux_checks_librs, GameInstall, NorthstarMod, }; +mod github; +use github::release_notes::get_northstar_release_notes; + mod repair_and_verify; use repair_and_verify::{verify_game_files, disable_all_but_core}; @@ -91,6 +94,7 @@ fn main() { set_mod_enabled_status_caller, disable_all_but_core_caller, is_debug_mode, + get_northstar_release_notes, linux_checks, get_installed_mods_caller, ]) diff --git a/src-vue/package-lock.json b/src-vue/package-lock.json index 2a3a21a3..13c2795f 100644 --- a/src-vue/package-lock.json +++ b/src-vue/package-lock.json @@ -10,12 +10,14 @@ "dependencies": { "@element-plus/icons-vue": "^2.0.9", "element-plus": "^2.2.17", + "marked": "^4.1.1", "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#dev", "vue": "^3.2.37", "vue-router": "^4.1.5", "vuex": "^4.0.2" }, "devDependencies": { + "@types/marked": "^4.0.7", "@vitejs/plugin-vue": "^3.1.0", "typescript": "^4.6.4", "vite": "^3.1.0", @@ -131,6 +133,12 @@ "@types/lodash": "*" } }, + "node_modules/@types/marked": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz", + "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==", + "dev": true + }, "node_modules/@types/web-bluetooth": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", @@ -903,6 +911,17 @@ "sourcemap-codec": "^1.4.8" } }, + "node_modules/marked": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", + "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", @@ -1240,6 +1259,12 @@ "@types/lodash": "*" } }, + "@types/marked": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz", + "integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==", + "dev": true + }, "@types/web-bluetooth": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", @@ -1750,6 +1775,11 @@ "sourcemap-codec": "^1.4.8" } }, + "marked": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", + "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==" + }, "memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", diff --git a/src-vue/package.json b/src-vue/package.json index 8da73960..fbe3fc1a 100644 --- a/src-vue/package.json +++ b/src-vue/package.json @@ -11,12 +11,14 @@ "dependencies": { "@element-plus/icons-vue": "^2.0.9", "element-plus": "^2.2.17", + "marked": "^4.1.1", "tauri-plugin-store-api": "github:tauri-apps/tauri-plugin-store#dev", "vue": "^3.2.37", "vue-router": "^4.1.5", "vuex": "^4.0.2" }, "devDependencies": { + "@types/marked": "^4.0.7", "@vitejs/plugin-vue": "^3.1.0", "typescript": "^4.6.4", "vite": "^3.1.0", diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index c731b98d..31b1efec 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -11,6 +11,7 @@ import { appDir } from '@tauri-apps/api/path'; import { open } from '@tauri-apps/api/dialog'; import { Store } from 'tauri-plugin-store-api'; import {router} from "../main"; +import ReleaseInfo from "../utils/ReleaseInfo"; const persistentStore = new Store('flight-core-settings.json'); @@ -25,6 +26,7 @@ export interface FlightCoreStore { installed_northstar_version: string, northstar_state: NorthstarState, northstar_release_canal: ReleaseCanal, + releaseNotes: ReleaseInfo[], northstar_is_running: boolean, origin_is_running: boolean @@ -44,6 +46,7 @@ export const store = createStore<FlightCoreStore>({ installed_northstar_version: "", northstar_state: NorthstarState.GAME_NOT_FOUND, northstar_release_canal: ReleaseCanal.RELEASE, + releaseNotes: [], northstar_is_running: false, origin_is_running: false @@ -181,6 +184,10 @@ export const store = createStore<FlightCoreStore>({ store.commit('updateGamePath'); break; } + }, + async fetchReleaseNotes(state: FlightCoreStore) { + if (state.releaseNotes.length !== 0) return; + state.releaseNotes = await invoke("get_northstar_release_notes"); } } }); diff --git a/src-vue/src/style.css b/src-vue/src/style.css index 6e00975d..66e512f5 100644 --- a/src-vue/src/style.css +++ b/src-vue/src/style.css @@ -28,3 +28,8 @@ body { position: fixed; filter: brightness(0.8); } + +.el-scrollbar { + --el-scrollbar-opacity: 0.5; + --el-scrollbar-hover-opacity: 0.7; +} diff --git a/src-vue/src/utils/ReleaseInfo.d.ts b/src-vue/src/utils/ReleaseInfo.d.ts new file mode 100644 index 00000000..162f7917 --- /dev/null +++ b/src-vue/src/utils/ReleaseInfo.d.ts @@ -0,0 +1,6 @@ +// Matches Rust struct (in release_notes mod). +export default interface ReleaseInfo { + name: string; + published_at: string; + body: string; +} diff --git a/src-vue/src/views/ChangelogView.vue b/src-vue/src/views/ChangelogView.vue index ff4f2659..62d7f820 100644 --- a/src-vue/src/views/ChangelogView.vue +++ b/src-vue/src/views/ChangelogView.vue @@ -1,24 +1,82 @@ <template> - <div class="fc__changelog__container"> - <el-link :underline="false" icon="DocumentCopy" target="_blank" - href="https://github.com/R2Northstar/Northstar/releases"> - Open release notes - </el-link> + <div style="height: calc(100% - var(--fc-menu_height))"> + <div v-if="releases.length === 0" class="fc__changelog__container"> + <el-progress :show-text="false" :percentage="50" :indeterminate="true" /> + </div> + <el-scrollbar v-else> + <el-timeline> + <el-timeline-item + v-for="release in releases" + v-bind:key="release.name" + :timestamp="formatDate(release.published_at)" + placement="top" + > + <el-card> + <h4>{{ release.name }}</h4> + <p v-html="formatRelease(release.body)"></p> + </el-card> + </el-timeline-item> + </el-timeline> + </el-scrollbar> </div> </template> <script lang="ts"> -export default { - name: "ChangelogView" -} +import { defineComponent } from 'vue'; +import ReleaseInfo from '../utils/ReleaseInfo'; +import { marked } from "marked"; + + +export default defineComponent({ + name: "ChangelogView", + async mounted() { + this.$store.commit('fetchReleaseNotes'); + }, + computed: { + releases(): ReleaseInfo[] { + return this.$store.state.releaseNotes; + } + }, + methods: { + // Transforms a Markdown document into an HTML document. + // Taken from Viper launcher: + // https://github.com/0neGal/viper/blob/5106d9ed409a3cc91a7755f961fab1bf91d8b7fb/src/app/launcher.js#L26 + formatRelease(releaseBody: string) { + // GitHub authors' links formatting + let content: string = releaseBody.replaceAll(/\@(\S+)/g, `<a target="_blank" href="https://github.com/$1">@$1</a>`); + + // PR's links formatting + content = content.replaceAll(/\[\#(\S+)\]\(([^)]+)\)/g, `<a target="_blank" href="$2">#$1</a>`); + + return marked.parse(content, {breaks: true}); + }, + // Formats an ISO-formatted date into a human-readable string. + formatDate(timestamp: string): string { + return new Date(timestamp).toLocaleDateString(); + } + } +}); </script> -<style scoped> -.fc__changelog__container { +<style> +.el-scrollbar__view { padding: 20px 30px; } -.el-link { +.fc__changelog__container { + padding: 20px 30px; + position: relative; + overflow-y: auto; + height: calc(100% - var(--fc-menu_height)); color: white; } + +.el-timeline-item__timestamp { + color: white !important; + user-select: none !important; +} + +.el-card__body * { + max-width: 100%; +} </style> |