aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src-tauri/bindings/CommitHead.ts4
-rw-r--r--src-tauri/bindings/PullRequestType.ts3
-rw-r--r--src-tauri/bindings/PullsApiResponseElement.ts4
-rw-r--r--src-tauri/bindings/Repo.ts3
-rw-r--r--src-tauri/src/constants.rs6
-rw-r--r--src-tauri/src/github/mod.rs1
-rw-r--r--src-tauri/src/github/pull_requests.rs446
-rw-r--r--src-tauri/src/github/release_notes.rs2
-rw-r--r--src-tauri/src/main.rs4
-rw-r--r--src-vue/src/components/PullRequestsSelector.vue103
-rw-r--r--src-vue/src/plugins/modules/pull_requests.ts112
-rw-r--r--src-vue/src/plugins/store.ts5
-rw-r--r--src-vue/src/views/DeveloperView.vue7
13 files changed, 698 insertions, 2 deletions
diff --git a/src-tauri/bindings/CommitHead.ts b/src-tauri/bindings/CommitHead.ts
new file mode 100644
index 00000000..58f31657
--- /dev/null
+++ b/src-tauri/bindings/CommitHead.ts
@@ -0,0 +1,4 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { Repo } from "./Repo";
+
+export interface CommitHead { sha: string, ref: string, repo: Repo, } \ No newline at end of file
diff --git a/src-tauri/bindings/PullRequestType.ts b/src-tauri/bindings/PullRequestType.ts
new file mode 100644
index 00000000..2d1fd0a8
--- /dev/null
+++ b/src-tauri/bindings/PullRequestType.ts
@@ -0,0 +1,3 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export type PullRequestType = "MODS" | "LAUNCHER"; \ No newline at end of file
diff --git a/src-tauri/bindings/PullsApiResponseElement.ts b/src-tauri/bindings/PullsApiResponseElement.ts
new file mode 100644
index 00000000..b2b5c476
--- /dev/null
+++ b/src-tauri/bindings/PullsApiResponseElement.ts
@@ -0,0 +1,4 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+import type { CommitHead } from "./CommitHead";
+
+export interface PullsApiResponseElement { number: bigint, title: string, url: string, head: CommitHead, html_url: string, } \ No newline at end of file
diff --git a/src-tauri/bindings/Repo.ts b/src-tauri/bindings/Repo.ts
new file mode 100644
index 00000000..836f39d7
--- /dev/null
+++ b/src-tauri/bindings/Repo.ts
@@ -0,0 +1,3 @@
+// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+
+export interface Repo { full_name: string, } \ No newline at end of file
diff --git a/src-tauri/src/constants.rs b/src-tauri/src/constants.rs
index 73cb6fb3..57a10e02 100644
--- a/src-tauri/src/constants.rs
+++ b/src-tauri/src/constants.rs
@@ -28,3 +28,9 @@ pub const BLACKLISTED_MODS: [&str; 3] = [
// Titanfall2 game IDs on Origin/EA-App
pub const TITANFALL2_ORIGIN_IDS: [&str; 2] = ["Origin.OFR.50.0001452", "Origin.OFR.50.0001456"];
+
+// GitHub API endpoints for launcher/mods PRs
+pub const PULLS_API_ENDPOINT_LAUNCHER: &str =
+ "https://api.github.com/repos/R2Northstar/NorthstarLauncher/pulls";
+pub const PULLS_API_ENDPOINT_MODS: &str =
+ "https://api.github.com/repos/R2Northstar/NorthstarMods/pulls";
diff --git a/src-tauri/src/github/mod.rs b/src-tauri/src/github/mod.rs
index 80a1831a..942f0db2 100644
--- a/src-tauri/src/github/mod.rs
+++ b/src-tauri/src/github/mod.rs
@@ -1 +1,2 @@
+pub mod pull_requests;
pub mod release_notes;
diff --git a/src-tauri/src/github/pull_requests.rs b/src-tauri/src/github/pull_requests.rs
new file mode 100644
index 00000000..2b7be30b
--- /dev/null
+++ b/src-tauri/src/github/pull_requests.rs
@@ -0,0 +1,446 @@
+use crate::github::release_notes::fetch_github_releases_api;
+
+use anyhow::anyhow;
+use app::check_is_valid_game_path;
+use app::constants::{APP_USER_AGENT, PULLS_API_ENDPOINT_LAUNCHER, PULLS_API_ENDPOINT_MODS};
+use serde::{Deserialize, Serialize};
+use std::fs;
+use std::fs::File;
+use std::io;
+use std::io::prelude::*;
+use std::path::Path;
+use ts_rs::TS;
+
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+struct Repo {
+ full_name: String,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+struct CommitHead {
+ sha: String,
+ #[serde(rename = "ref")]
+ gh_ref: String,
+ repo: Repo,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+pub struct PullsApiResponseElement {
+ number: i64,
+ title: String,
+ url: String,
+ head: CommitHead,
+ html_url: String,
+}
+
+// GitHub API response JSON elements as structs
+#[derive(Debug, Deserialize, Clone)]
+struct WorkflowRun {
+ id: u64,
+ head_sha: String,
+}
+#[derive(Debug, Deserialize, Clone)]
+struct ActionsRunsResponse {
+ workflow_runs: Vec<WorkflowRun>,
+}
+
+#[derive(Debug, Deserialize, Clone)]
+struct Artifact {
+ id: u64,
+ workflow_run: WorkflowRun,
+}
+
+#[derive(Debug, Deserialize, Clone)]
+struct ArtifactsResponse {
+ artifacts: Vec<Artifact>,
+}
+
+#[derive(Serialize, Deserialize, Debug, Clone, TS)]
+#[ts(export)]
+pub enum PullRequestType {
+ MODS,
+ LAUNCHER,
+}
+
+/// Parse pull requests from specified URL
+pub async fn get_pull_requests(url: String) -> Result<Vec<PullsApiResponseElement>, String> {
+ let json_response = match fetch_github_releases_api(&url).await {
+ Ok(result) => result,
+ Err(err) => return Err(err.to_string()),
+ };
+
+ let pulls_response: Vec<PullsApiResponseElement> = match serde_json::from_str(&json_response) {
+ Ok(res) => res,
+ Err(err) => return Err(err.to_string()),
+ };
+
+ Ok(pulls_response)
+}
+
+/// Gets either launcher or mods PRs
+#[tauri::command]
+pub async fn get_pull_requests_wrapper(
+ install_type: PullRequestType,
+) -> Result<Vec<PullsApiResponseElement>, String> {
+ let api_pr_url = match install_type {
+ PullRequestType::MODS => PULLS_API_ENDPOINT_MODS,
+ PullRequestType::LAUNCHER => PULLS_API_ENDPOINT_LAUNCHER,
+ };
+
+ get_pull_requests(api_pr_url.to_string()).await
+}
+
+fn unzip(zip_file_name: &str) -> String {
+ let fname = std::path::Path::new(zip_file_name);
+ let file = fs::File::open(fname).unwrap();
+
+ let mut archive = zip::ZipArchive::new(file).unwrap();
+
+ let mut folder_name = "".to_string();
+
+ for i in 0..archive.len() {
+ let mut file = archive.by_index(i).unwrap();
+ let outpath = match file.enclosed_name() {
+ Some(path) => path.to_owned(),
+ None => continue,
+ };
+
+ {
+ let comment = file.comment();
+ if !comment.is_empty() {
+ println!("File {} comment: {}", i, comment);
+ }
+ }
+
+ if i == 0 {
+ // Sanity check that it's a folder
+ assert!((*file.name()).ends_with('/'));
+
+ folder_name = format!("{}", outpath.display());
+ println!("{}", folder_name);
+ }
+
+ if (*file.name()).ends_with('/') {
+ fs::create_dir_all(&outpath).unwrap();
+ } else {
+ if let Some(p) = outpath.parent() {
+ if !p.exists() {
+ fs::create_dir_all(p).unwrap();
+ }
+ }
+ let mut outfile = fs::File::create(&outpath).unwrap();
+ io::copy(&mut file, &mut outfile).unwrap();
+ }
+
+ // Get and Set permissions
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+
+ if let Some(mode) = file.unix_mode() {
+ fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap();
+ }
+ }
+ }
+ folder_name
+}
+
+pub async fn check_github_api(url: &str) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
+ let client = reqwest::Client::new();
+ let res = client
+ .get(url)
+ .header(reqwest::header::USER_AGENT, APP_USER_AGENT)
+ .send()
+ .await
+ .unwrap()
+ .text()
+ .await
+ .unwrap();
+
+ let json: serde_json::Value = serde_json::from_str(&res).expect("JSON was not well-formatted");
+
+ Ok(json)
+}
+
+/// Downloads a file from given URL
+async fn download_zip(download_url: String, location: String) -> Result<(), anyhow::Error> {
+ let client = reqwest::Client::new();
+ let resp = client
+ .get(download_url)
+ .header(reqwest::header::USER_AGENT, APP_USER_AGENT)
+ .send()
+ .await?;
+
+ // Error out earlier if non-successful response
+ if !resp.status().is_success() {
+ // Return error cause wrong game path
+ return Err(anyhow!(
+ "Couldn't download zip. Received error code \"{}\"",
+ resp.status()
+ ));
+ }
+
+ let mut out = fs::File::create(format!("{}/ns-dev-test-helper-temp-pr-files.zip", location))
+ .expect("failed to create file");
+ let bytes = resp.bytes().await?;
+ let mut cursor = std::io::Cursor::new(bytes);
+ std::io::copy(&mut cursor, &mut out)?;
+ Ok(())
+}
+
+fn unzip_launcher_zip(zip_file_name: &str) -> String {
+ let outfolder_name = "ns-dev-test-helper-temp-pr-files";
+ let fname = std::path::Path::new(zip_file_name);
+ let file = fs::File::open(fname).unwrap();
+
+ let mut archive = zip::ZipArchive::new(file).unwrap();
+
+ fs::create_dir_all(outfolder_name).unwrap();
+
+ for i in 0..archive.len() {
+ let mut file = archive.by_index(i).unwrap();
+ let outpath = match file.enclosed_name() {
+ Some(path) => path.to_owned(),
+ None => continue,
+ };
+
+ {
+ let comment = file.comment();
+ if !comment.is_empty() {
+ println!("File {} comment: {}", i, comment);
+ }
+ }
+
+ // Only extract two hardcoded files
+ if *file.name() == *"NorthstarLauncher.exe" || *file.name() == *"Northstar.dll" {
+ println!(
+ "File {} extracted to \"{}\" ({} bytes)",
+ i,
+ outpath.display(),
+ file.size()
+ );
+ if let Some(p) = outpath.parent() {
+ if !p.exists() {
+ fs::create_dir_all(p).unwrap();
+ }
+ }
+ let mut outfile =
+ fs::File::create(format!("{}/{}", outfolder_name, outpath.display())).unwrap();
+ std::io::copy(&mut file, &mut outfile).unwrap();
+ }
+
+ // Get and Set permissions
+ #[cfg(unix)]
+ {
+ use std::os::unix::fs::PermissionsExt;
+
+ if let Some(mode) = file.unix_mode() {
+ fs::set_permissions(&outpath, fs::Permissions::from_mode(mode)).unwrap();
+ }
+ }
+ }
+ outfolder_name.to_string()
+}
+
+/// Recursively copies files from one directory to another
+fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
+ fs::create_dir_all(&dst)?;
+ for entry in fs::read_dir(src)? {
+ let entry = entry?;
+ let ty = entry.file_type()?;
+ if ty.is_dir() {
+ copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
+ } else {
+ fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
+ }
+ }
+ Ok(())
+}
+
+/// Gets GitHub download link of a mods PR
+fn get_mods_download_link(pull_request: PullsApiResponseElement) -> Result<String, anyhow::Error> {
+ // {pr object} -> number == pr_number
+ // -> head -> ref
+ // -> repo -> full_name
+
+ // Use repo and branch name to get download link
+ let download_url = format!(
+ "https://github.com/{}/archive/refs/heads/{}.zip",
+ pull_request.head.repo.full_name, // repo name
+ pull_request.head.gh_ref, // branch name
+ );
+
+ Ok(download_url)
+}
+
+/// Gets `nightly.link` artifact download link of a launcher PR
+async fn get_launcher_download_link(
+ pull_request: PullsApiResponseElement,
+) -> Result<String, String> {
+ // Iterate over the first 10 pages of
+ for i in 1..=10 {
+ // Crossreference with runs API
+ let runs_response: ActionsRunsResponse = match check_github_api(&format!(
+ "https://api.github.com/repos/R2Northstar/NorthstarLauncher/actions/runs?page={}",
+ i
+ ))
+ .await
+ {
+ Ok(result) => serde_json::from_value(result).unwrap(),
+ Err(err) => return Err(format!("{}", err)),
+ };
+
+ // Cross-reference PR head commit sha against workflow runs
+ for workflow_run in &runs_response.workflow_runs {
+ // If head commit sha of run and PR match, grab CI output
+ if workflow_run.head_sha == pull_request.head.sha {
+ // Check artifacts
+ let api_url = format!("https://api.github.com/repos/R2Northstar/NorthstarLauncher/actions/runs/{}/artifacts", workflow_run.id);
+ let artifacts_response: ArtifactsResponse = serde_json::from_value(
+ check_github_api(&api_url).await.expect("Failed request"),
+ )
+ .unwrap();
+
+ // Iterate over artifacts
+ for artifact in artifacts_response.artifacts {
+ // Make sure run is from PR head commit
+ if artifact.workflow_run.head_sha == workflow_run.head_sha {
+ dbg!(artifact.id);
+
+ // Download artifact
+ return Ok(format!("https://nightly.link/R2Northstar/NorthstarLauncher/actions/artifacts/{}.zip", artifact.id));
+ }
+ }
+ }
+ }
+ }
+
+ Err(format!(
+ "Couldn't grab download link for PR \"{}\". PR might be too old and therefore no CI build has been deleted. Maybe ask author to update?",
+ pull_request.number
+ ))
+}
+
+/// Adds a batch file that allows for launching Northstar with mods PR profile
+fn add_batch_file(game_install_path: &str) {
+ let batch_path = format!("{}/r2ns-launch-mod-pr-version.bat", game_install_path);
+ let path = Path::new(&batch_path);
+ let display = path.display();
+
+ // Open a file in write-only mode, returns `io::Result<File>`
+ let mut file = match File::create(path) {
+ Err(why) => panic!("couldn't create {}: {}", display, why),
+ Ok(file) => file,
+ };
+
+ // Write the string to `file`, returns `io::Result<()>`
+ let batch_file_content =
+ "NorthstarLauncher.exe -profile=R2Northstar-PR-test-managed-folder\r\n";
+
+ match file.write_all(batch_file_content.as_bytes()) {
+ Err(why) => panic!("couldn't write to {}: {}", display, why),
+ Ok(_) => println!("successfully wrote to {}", display),
+ }
+}
+
+/// Downloads selected launcher PR and extracts it into game install path
+#[tauri::command]
+pub async fn apply_launcher_pr(
+ pull_request: PullsApiResponseElement,
+ game_install_path: &str,
+) -> Result<(), String> {
+ // Exit early if wrong game path
+ check_is_valid_game_path(game_install_path)?;
+
+ // get download link
+ let download_url = get_launcher_download_link(pull_request).await?;
+
+ // download
+ match download_zip(download_url, ".".to_string()).await {
+ Ok(_) => (),
+ Err(err) => return Err(err.to_string()),
+ };
+
+ // extract
+ let zip_extract_folder_name = unzip_launcher_zip("ns-dev-test-helper-temp-pr-files.zip");
+ fs::remove_file("ns-dev-test-helper-temp-pr-files.zip").unwrap();
+
+ // Copy downloaded folder to game install folder
+ match copy_dir_all(zip_extract_folder_name.clone(), game_install_path) {
+ Ok(_) => (),
+ Err(err) => {
+ return Err(format!("Failed copying files: {}", err));
+ }
+ }
+
+ // Delete old unzipped
+ fs::remove_dir_all(zip_extract_folder_name).unwrap();
+
+ println!("All done with installing launcher PR");
+ Ok(())
+}
+
+/// Downloads selected mods PR and extracts it into profile in game install path
+#[tauri::command]
+pub async fn apply_mods_pr(
+ pull_request: PullsApiResponseElement,
+ game_install_path: &str,
+) -> Result<(), String> {
+ // Exit early if wrong game path
+ check_is_valid_game_path(game_install_path)?;
+
+ let download_url = match get_mods_download_link(pull_request) {
+ Ok(url) => url,
+ Err(err) => return Err(err.to_string()),
+ };
+
+ match download_zip(download_url, ".".to_string()).await {
+ Ok(()) => (),
+ Err(err) => return Err(err.to_string()),
+ };
+
+ // Extract folder and delete zip
+ let zip_extract_folder_name = unzip("ns-dev-test-helper-temp-pr-files.zip");
+ fs::remove_file("ns-dev-test-helper-temp-pr-files.zip").unwrap();
+
+ // Delete previously managed folder
+ if std::fs::remove_dir_all(format!(
+ "{}/R2Northstar-PR-test-managed-folder",
+ game_install_path
+ ))
+ .is_err()
+ {
+ if std::path::Path::new(&format!(
+ "{}/R2Northstar-PR-test-managed-folder",
+ game_install_path
+ ))
+ .exists()
+ {
+ println!("Failed removing previous dir");
+ } else {
+ println!("Failed removing folder that doesn't exist. Probably cause first run");
+ }
+ };
+
+ // Copy downloaded folder to game install folder
+ copy_dir_all(
+ zip_extract_folder_name.clone(),
+ format!(
+ "{}/R2Northstar-PR-test-managed-folder/mods",
+ game_install_path
+ ),
+ )
+ .unwrap();
+
+ // Delete old copy
+ std::fs::remove_dir_all(zip_extract_folder_name).unwrap();
+
+ // Add batch file to launch right profile
+ add_batch_file(game_install_path);
+
+ println!("All done with installing mods PR");
+ Ok(())
+}
diff --git a/src-tauri/src/github/release_notes.rs b/src-tauri/src/github/release_notes.rs
index 6dee4576..b59358ec 100644
--- a/src-tauri/src/github/release_notes.rs
+++ b/src-tauri/src/github/release_notes.rs
@@ -19,7 +19,7 @@ pub struct FlightCoreVersion {
}
// Fetches repo release API and returns response as string
-async fn fetch_github_releases_api(url: &str) -> Result<String, String> {
+pub async fn fetch_github_releases_api(url: &str) -> Result<String, String> {
println!("Fetching releases notes from GitHub API");
let client = reqwest::Client::new();
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index c0c8a186..c7451763 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -15,6 +15,7 @@ use app::{
};
mod github;
+use github::pull_requests::{apply_launcher_pr, apply_mods_pr, get_pull_requests_wrapper};
use github::release_notes::{
check_is_flightcore_outdated, get_newest_flightcore_version, get_northstar_release_notes,
};
@@ -115,6 +116,9 @@ fn main() {
get_server_player_count,
delete_thunderstore_mod,
query_thunderstore_packages_api,
+ get_pull_requests_wrapper,
+ apply_launcher_pr,
+ apply_mods_pr,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
diff --git a/src-vue/src/components/PullRequestsSelector.vue b/src-vue/src/components/PullRequestsSelector.vue
new file mode 100644
index 00000000..58a355f4
--- /dev/null
+++ b/src-vue/src/components/PullRequestsSelector.vue
@@ -0,0 +1,103 @@
+<template>
+ <div>
+ <el-collapse @change="onChange">
+ <el-collapse-item title="Launcher PRs" name="1">
+ <p v-if="pull_requests_launcher.length === 0">
+ <el-progress
+ :show-text="false"
+ :percentage="100"
+ status="warning"
+ :indeterminate="true"
+ :duration="1"
+ style="margin: 15px"
+ />
+ </p>
+ <el-card v-else shadow="hover" v-for="pull_request in pull_requests_launcher"
+ v-bind:key="pull_request.url">
+ <el-button type="primary" @click="installLauncherPR(pull_request)">Install</el-button>
+ <a target="_blank" :href="pull_request.html_url">
+ {{ pull_request.number }}: {{ pull_request.title }}
+ </a>
+ </el-card>
+ </el-collapse-item>
+
+ <el-collapse-item title="Mods PRs" name="2">
+ <div style="margin: 15px">
+ <el-alert title="Warning" type="warning" :closable="false" show-icon>
+ Mod PRs are installed into a separate profile. Make sure to launch via
+ 'r2ns-launch-mod-pr-version.bat' or via '-profile=R2Northstar-PR-test-managed-folder' to actually
+ run the PR version!
+ </el-alert>
+ </div>
+ <p v-if="pull_requests_mods.length === 0">
+ <el-progress
+ :show-text="false"
+ :percentage="100"
+ status="warning"
+ :indeterminate="true"
+ :duration="1"
+ style="margin: 15px"
+ />
+ </p>
+ <el-card v-else shadow="hover" v-for="pull_request in pull_requests_mods" v-bind:key="pull_request.url">
+ <el-button type="primary" @click="installModsPR(pull_request)">Install</el-button>
+ <a target="_blank" :href="pull_request.html_url">
+ {{ pull_request.number }}: {{ pull_request.title }}
+ </a>
+ </el-card>
+ </el-collapse-item>
+ </el-collapse>
+ </div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue'
+import { PullRequestType } from '../../../src-tauri/bindings/PullRequestType';
+import { PullsApiResponseElement } from '../../../src-tauri/bindings/PullsApiResponseElement';
+import { invoke } from "@tauri-apps/api";
+import { ElNotification } from "element-plus";
+
+export default defineComponent({
+ name: 'PullRequestsSelector',
+ computed: {
+ pull_requests_launcher(): PullsApiResponseElement[] {
+ return this.$store.state.pullrequests.pull_requests_launcher;
+ },
+ pull_requests_mods(): PullsApiResponseElement[] {
+ return this.$store.state.pullrequests.pull_requests_mods;
+ },
+ },
+ methods: {
+ onChange(e: Object) {
+ const openedCollapseNames = Object.values(e);
+ if (openedCollapseNames.includes('1') && this.pull_requests_launcher.length === 0) {
+ this.getPullRequests('LAUNCHER');
+ }
+ if (openedCollapseNames.includes('2') && this.pull_requests_mods.length === 0) {
+ this.getPullRequests('MODS');
+ }
+ },
+ async getPullRequests(pull_request_type: PullRequestType) {
+ this.$store.commit('getPullRequests', pull_request_type);
+ },
+ async installLauncherPR(pull_request: PullsApiResponseElement) {
+ this.$store.commit('installLauncherPR', pull_request);
+ },
+ async installModsPR(pull_request: PullsApiResponseElement) {
+ this.$store.commit('installModsPR', pull_request);
+ },
+ }
+})
+</script>
+
+<style scoped>
+.el-collapse {
+ border-radius: var(--el-border-radius-base);
+ overflow: hidden;
+}
+
+:deep(.el-collapse-item__header) {
+ padding-left: 10px;
+ font-size: 14px;
+}
+</style>
diff --git a/src-vue/src/plugins/modules/pull_requests.ts b/src-vue/src/plugins/modules/pull_requests.ts
new file mode 100644
index 00000000..e64703d3
--- /dev/null
+++ b/src-vue/src/plugins/modules/pull_requests.ts
@@ -0,0 +1,112 @@
+import { ElNotification } from "element-plus";
+import { invoke } from "@tauri-apps/api";
+import { PullsApiResponseElement } from "../../../../src-tauri/bindings/PullsApiResponseElement";
+import { PullRequestType } from '../../../../src-tauri/bindings/PullRequestType';
+import { store } from "../store";
+
+interface PullRequestStoreState {
+ searchValue: string,
+ pull_requests_launcher: PullsApiResponseElement[],
+ pull_requests_mods: PullsApiResponseElement[],
+}
+
+export const pullRequestModule = {
+ state: () => ({
+ pull_requests_launcher: [],
+ pull_requests_mods: [],
+ }),
+ mutations: {
+ async getPullRequests(state: PullRequestStoreState, pull_request_type: PullRequestType) {
+ await invoke<PullsApiResponseElement[]>("get_pull_requests_wrapper", { installType: pull_request_type })
+ .then((message) => {
+ switch (pull_request_type) {
+ case "MODS":
+ state.pull_requests_mods = message;
+ break;
+
+ case "LAUNCHER":
+ state.pull_requests_launcher = message;
+ break;
+
+ default:
+ console.error("We should never end up here");
+ }
+ })
+ .catch((error) => {
+ ElNotification({
+ title: 'Error',
+ message: error,
+ type: 'error',
+ position: 'bottom-right'
+ });
+ });
+ },
+ async installLauncherPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) {
+ // Send notification telling the user to wait for the process to finish
+ const notification = ElNotification({
+ title: `Installing launcher PR ${pull_request.number}`,
+ message: 'Please wait',
+ duration: 0,
+ type: 'info',
+ position: 'bottom-right'
+ });
+
+ await invoke("apply_launcher_pr", { pullRequest: pull_request, gameInstallPath: store.state.game_path })
+ .then((message) => {
+ console.log(message);
+ // Show user notification if mod install completed.
+ ElNotification({
+ title: `Done`,
+ message: `Installed ${pull_request.number}: "${pull_request.title}"`,
+ type: 'success',
+ position: 'bottom-right'
+ });
+ })
+ .catch((error) => {
+ ElNotification({
+ title: 'Error',
+ message: error,
+ type: 'error',
+ position: 'bottom-right'
+ });
+ })
+ .finally(() => {
+ // Clear old notification
+ notification.close();
+ });
+ },
+ async installModsPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) {
+ // Send notification telling the user to wait for the process to finish
+ const notification = ElNotification({
+ title: `Installing mods PR ${pull_request.number}`,
+ message: 'Please wait',
+ duration: 0,
+ type: 'info',
+ position: 'bottom-right'
+ });
+
+ await invoke("apply_mods_pr", { pullRequest: pull_request, gameInstallPath: store.state.game_path })
+ .then((message) => {
+ // Show user notification if mod install completed.
+ ElNotification({
+ title: `Done`,
+ message: `Installed ${pull_request.number}: "${pull_request.title}"\nMake sure to launch via batch file or by specifying correct profile!`,
+ type: 'success',
+ position: 'bottom-right'
+ });
+ })
+ .catch((error) => {
+ ElNotification({
+ title: 'Error',
+ message: error,
+ type: 'error',
+ position: 'bottom-right'
+ });
+ })
+ .finally(() => {
+ // Clear old notification
+ notification.close();
+ });
+ },
+ }
+}
diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts
index e44b1c3f..8671d12b 100644
--- a/src-vue/src/plugins/store.ts
+++ b/src-vue/src/plugins/store.ts
@@ -16,6 +16,8 @@ import { ReleaseInfo } from "../../../src-tauri/bindings/ReleaseInfo";
import { ThunderstoreMod } from "../../../src-tauri/bindings/ThunderstoreMod";
import { NorthstarMod } from "../../../src-tauri/bindings/NorthstarMod";
import { searchModule } from './modules/search';
+import { pullRequestModule } from './modules/pull_requests';
+import { PullsApiResponseElement } from "../../../src-tauri/bindings/PullsApiResponseElement";
const persistentStore = new Store('flight-core-settings.json');
@@ -51,7 +53,8 @@ let notification_handle: NotificationHandle;
export const store = createStore<FlightCoreStore>({
modules: {
- search: searchModule
+ search: searchModule,
+ pullrequests: pullRequestModule,
},
state(): FlightCoreStore {
return {
diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue
index b51e1bf1..11cb4f5d 100644
--- a/src-vue/src/views/DeveloperView.vue
+++ b/src-vue/src/views/DeveloperView.vue
@@ -55,6 +55,9 @@
<el-button type="primary" @click="clearFlightCorePersistentStore">
Delete FlightCore persistent store
</el-button>
+
+ <h3>Testing</h3>
+ <pull-requests-selector />
</el-scrollbar>
</div>
</template>
@@ -66,10 +69,14 @@ import { ElNotification } from "element-plus";
import { GameInstall } from "../utils/GameInstall";
import { Store } from 'tauri-plugin-store-api';
import { ReleaseCanal } from "../utils/ReleaseCanal";
+import PullRequestsSelector from "../components/PullRequestsSelector.vue";
const persistentStore = new Store('flight-core-settings.json');
export default defineComponent({
name: "DeveloperView",
+ components: {
+ PullRequestsSelector
+ },
data() {
return {
mod_to_install_field_string : "",