From 24fb67f88ceca9bec04b49fae5b58759b7b25ec5 Mon Sep 17 00:00:00 2001 From: Jan Date: Tue, 8 Aug 2023 23:59:05 +0200 Subject: feat: Add dropdown menu for profiles (#494) Adds a dropdown menu to settings that allows selecting a different profile. Currently gated behind dev mode being active. --- src-tauri/src/main.rs | 2 + src-tauri/src/northstar/mod.rs | 1 + src-tauri/src/northstar/profile.rs | 76 ++++++++++++++++++++++++++++++++++++++ src-vue/src/i18n/lang/en.json | 11 ++++++ src-vue/src/plugins/store.ts | 20 ++++++++++ src-vue/src/views/SettingsView.vue | 60 ++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 src-tauri/src/northstar/profile.rs diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index a7827a44..66bb98d2 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -158,6 +158,8 @@ fn main() { close_application, development::install_git_main, get_available_northstar_versions, + northstar::profile::fetch_profiles, + northstar::profile::validate_profile, ]) .run(tauri::generate_context!()) { diff --git a/src-tauri/src/northstar/mod.rs b/src-tauri/src/northstar/mod.rs index 173495c6..4fecfe51 100644 --- a/src-tauri/src/northstar/mod.rs +++ b/src-tauri/src/northstar/mod.rs @@ -1,6 +1,7 @@ //! This module deals with handling things around Northstar such as //! - getting version number pub mod install; +pub mod profile; use crate::util::check_ea_app_or_origin_running; use crate::{ diff --git a/src-tauri/src/northstar/profile.rs b/src-tauri/src/northstar/profile.rs new file mode 100644 index 00000000..78e734d0 --- /dev/null +++ b/src-tauri/src/northstar/profile.rs @@ -0,0 +1,76 @@ +use crate::GameInstall; + +// These folders are part of Titanfall 2 and +// should NEVER be used as a Profile +const SKIP_PATHS: [&str; 8] = [ + "___flightcore-temp", + "__overlay", + "bin", + "Core", + "r2", + "vpk", + "platform", + "Support", +]; + +// A profile may have one of these to be detected +const MAY_CONTAIN: [&str; 10] = [ + "mods/", + "plugins/", + "packages/", + "logs/", + "runtime/", + "save_data/", + "Northstar.dll", + "enabledmods.json", + "placeholder.playerdata.pdata", + "LEGAL.txt", +]; + +/// Returns a list of Profile names +/// All the returned Profiles can be found relative to the game path +#[tauri::command] +pub fn fetch_profiles(game_install: GameInstall) -> Result, String> { + let mut profiles: Vec = Vec::new(); + + for content in MAY_CONTAIN { + let pattern = format!("{}/*/{}", game_install.game_path, content); + for e in glob::glob(&pattern).expect("Failed to read glob pattern") { + let path = e.unwrap(); + let mut ancestors = path.ancestors(); + + ancestors.next(); + + let profile_path = std::path::Path::new(ancestors.next().unwrap()); + let profile_name = profile_path + .file_name() + .unwrap() + .to_os_string() + .into_string() + .unwrap(); + + if !profiles.contains(&profile_name) { + profiles.push(profile_name); + } + } + } + + Ok(profiles) +} + +/// Validates if a given profile is actually a valid profile +#[tauri::command] +pub fn validate_profile(game_install: GameInstall, profile: String) -> bool { + // Game files are never a valid profile + // Prevent users with messed up installs from making it even worse + if SKIP_PATHS.contains(&profile.as_str()) { + return false; + } + + log::info!("Validating Profile {}", profile); + + let profile_path = format!("{}/{}", game_install.game_path, profile); + let profile_dir = std::path::Path::new(profile_path.as_str()); + + profile_dir.is_dir() +} diff --git a/src-vue/src/i18n/lang/en.json b/src-vue/src/i18n/lang/en.json index 238c6c41..1fa4e7ee 100644 --- a/src-vue/src/i18n/lang/en.json +++ b/src-vue/src/i18n/lang/en.json @@ -110,6 +110,10 @@ "show_deprecated_mods_desc1": "This allows you to see deprecated mods in the online mods collection.", "show_deprecated_mods_desc2": "Watch out, such mods are usually deprecated for a good reason.", + "profile": { + "active": "Active Profile" + }, + "repair": { "title": "Repair", "open_window": "Open repair window", @@ -150,6 +154,13 @@ } }, + "profile": { + "invalid": { + "title": "Invalid Profile", + "text": "The profile you tried to switch to is no longer valid." + } + }, + "flightcore_outdated": { "title": "FlightCore outdated!", "text": "Please update FlightCore.\nRunning outdated version {oldVersion}.\nNewest is {newVersion}!" diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index e18498a6..8f0c3fee 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -38,6 +38,7 @@ export interface FlightCoreStore { thunderstoreMods: ThunderstoreMod[], thunderstoreModsCategories: string[], installed_mods: NorthstarMod[], + available_profiles: string[], northstar_is_running: boolean, origin_is_running: boolean, @@ -62,6 +63,8 @@ export const store = createStore({ developer_mode: false, game_install: {} as unknown as GameInstall, + available_profiles: [], + flightcore_version: "", installed_northstar_version: "", @@ -284,6 +287,9 @@ export const store = createStore({ return; } + // Clear installed mod list first so we don't end up with leftovers + state.installed_mods = []; + // Call back-end for installed mods await invoke("get_installed_mods_and_properties", { gameInstall: state.game_install }) .then((message) => { @@ -312,6 +318,16 @@ export const store = createStore({ i18n.global.tc(`channels.names.${state.northstar_release_canal}`), i18n.global.tc('channels.release.switch.text', {canal: state.northstar_release_canal}), ); + }, + async fetchProfiles(state: FlightCoreStore) { + await invoke("fetch_profiles", { gameInstall: state.game_install }) + .then((message) => { + state.available_profiles = message as string[]; + }) + .catch((error) => { + console.error(error); + showErrorNotification(error); + }); } } }); @@ -415,6 +431,8 @@ async function _initializeApp(state: any) { await _get_northstar_version_number(state); } + store.commit('fetchProfiles'); + await invoke<[number, number]>("get_server_player_count") .then((message) => { state.player_count = message[0]; @@ -465,6 +483,8 @@ function _initializeListeners(state: any) { * state, for it to be displayed in UI. */ async function _get_northstar_version_number(state: any) { + state.installed_northstar_version = ""; + await invoke("get_northstar_version_number", { gameInstall: state.game_install }) .then((message) => { let northstar_version_number: string = message as string; diff --git a/src-vue/src/views/SettingsView.vue b/src-vue/src/views/SettingsView.vue index c7ca2ded..c209da31 100644 --- a/src-vue/src/views/SettingsView.vue +++ b/src-vue/src/views/SettingsView.vue @@ -21,6 +21,21 @@ + +
+

{{ $t('settings.profile.active') }}

+ + + {{ $store.state.game_install.profile }} + + + +
+

{{ $t('settings.nb_ts_mods_per_page') }}

@@ -96,6 +111,7 @@ import { showErrorNotification, showNotification } from "../utils/ui"; import LanguageSelector from "../components/LanguageSelector.vue"; const persistentStore = new Store('flight-core-settings.json'); import { open } from '@tauri-apps/api/shell'; +import { i18n } from '../main'; export default defineComponent({ name: "SettingsView", @@ -144,6 +160,17 @@ export default defineComponent({ persistentStore.set('thunderstore-mods-per-page', { value }); await persistentStore.save(); // explicit save to disk } + }, + availableProfiles(): Object { + let profiles = this.$store.state.available_profiles + + // convert string array to object array so we can fill a table + let data = profiles.reduce( + (a: Object[], v: string) => [...a, {"name": v}], + [] + ); + + return data; } }, methods: { @@ -172,6 +199,39 @@ export default defineComponent({ async openGameInstallFolder() { // Opens the folder in default file explorer application await open(`${this.$store.state.game_install.game_path}`); + }, + async switchProfile(value: string) { + let store = this.$store; + let state = store.state; + + await invoke("validate_profile", { gameInstall: state.game_install, profile: value }) + .then(async (message) => { + if (!message) + { + // Profile is no longer valid, inform the user... + showErrorNotification( + i18n.global.tc('notification.profile.invalid.text'), + i18n.global.tc('notification.profile.invalid.title') + ); + + // ...and refresh + store.commit('fetchProfiles'); + return; + } + + state.game_install.profile = value; + + // Check for Northstar updates + store.commit('checkNorthstarUpdates'); + + // Save change in persistent store + await persistentStore.set('game-install', { value: state.game_install }); + await persistentStore.save(); // explicit save to disk + }) + .catch((error) => { + console.error(error); + showErrorNotification(error); + }); } }, mounted() { -- cgit v1.2.3