aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRémy Raes <contact@remyraes.com>2022-11-09 23:42:54 +0100
committerGitHub <noreply@github.com>2022-11-09 23:42:54 +0100
commit32fd7e37d0a2e72a88bac3462fbfac3a5d4b6015 (patch)
tree02452ede248e12233499caaff4c728d5e2f2ab81
parent90b2153a6d2620f55c22c09e9a624f81e50cca44 (diff)
downloadFlightCore-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.rs1
-rw-r--r--src-tauri/src/github/release_notes.rs48
-rw-r--r--src-tauri/src/main.rs4
-rw-r--r--src-vue/package-lock.json30
-rw-r--r--src-vue/package.json2
-rw-r--r--src-vue/src/plugins/store.ts7
-rw-r--r--src-vue/src/style.css5
-rw-r--r--src-vue/src/utils/ReleaseInfo.d.ts6
-rw-r--r--src-vue/src/views/ChangelogView.vue80
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>