aboutsummaryrefslogtreecommitdiff
path: root/src-vue/src
diff options
context:
space:
mode:
authorGeckoEidechse <40122905+GeckoEidechse@users.noreply.github.com>2022-10-04 22:05:26 +0200
committerGitHub <noreply@github.com>2022-10-04 22:05:26 +0200
commitdd2f37620b8a4d925bdb55badfb598c30d9c5ba8 (patch)
treee1f79d454123f51d2a5c0adbc9d4eeee07186df5 /src-vue/src
parent7f0ee9be80988f13f1d234136725a03db0335ec5 (diff)
parent5912e98a02e799b7ad6c910567fe18988ab84798 (diff)
downloadFlightCore-dd2f37620b8a4d925bdb55badfb598c30d9c5ba8.tar.gz
FlightCore-dd2f37620b8a4d925bdb55badfb598c30d9c5ba8.zip
Merge pull request #2 from Alystrasz/feat/new-ui
Full UI rework
Diffstat (limited to 'src-vue/src')
-rw-r--r--src-vue/src/App.vue111
-rw-r--r--src-vue/src/assets/vue.svg1
-rw-r--r--src-vue/src/assets/wallpaperflare.com_wallpaper.jpgbin0 -> 586994 bytes
-rw-r--r--src-vue/src/components/PlayButton.vue69
-rw-r--r--src-vue/src/main.ts23
-rw-r--r--src-vue/src/plugins/store.ts207
-rw-r--r--src-vue/src/style.css15
-rw-r--r--src-vue/src/utils/GameInstall.ts4
-rw-r--r--src-vue/src/utils/InstallType.ts8
-rw-r--r--src-vue/src/utils/NorthstarState.ts8
-rw-r--r--src-vue/src/utils/ReleaseCanal.ts4
-rw-r--r--src-vue/src/utils/Tabs.ts6
-rw-r--r--src-vue/src/views/ChangelogView.vue23
-rw-r--r--src-vue/src/views/DeveloperView.vue41
-rw-r--r--src-vue/src/views/PlayView.vue126
-rw-r--r--src-vue/src/views/SettingsView.vue88
-rw-r--r--src-vue/src/vite-env.d.ts7
-rw-r--r--src-vue/src/vuex-shim.d.ts8
18 files changed, 749 insertions, 0 deletions
diff --git a/src-vue/src/App.vue b/src-vue/src/App.vue
new file mode 100644
index 00000000..219862bd
--- /dev/null
+++ b/src-vue/src/App.vue
@@ -0,0 +1,111 @@
+<script lang="ts">
+import ChangelogView from './views/ChangelogView.vue';
+import DeveloperView from './views/DeveloperView.vue';
+import PlayView from './views/PlayView.vue';
+import SettingsView from './views/SettingsView.vue';
+import { appWindow } from '@tauri-apps/api/window';
+import { store } from './plugins/store';
+
+export default {
+ components: {
+ ChangelogView,
+ DeveloperView,
+ PlayView,
+ SettingsView
+ },
+ data() {
+ return {}
+ },
+ mounted: () => {
+ store.commit('initialize');
+ },
+ methods: {
+ minimize() {
+ appWindow.minimize()
+ },
+ close() {
+ appWindow.close()
+ }
+ }
+}
+</script>
+
+<template>
+ <div id="fc_bg__container" data-tauri-drag-region />
+ <el-tabs v-model="$store.state.current_tab" class="fc_menu__tabs" type="card">
+ <el-tab-pane name="Play" label="Play"><PlayView /></el-tab-pane>
+ <el-tab-pane name="Changelog" label="Changelog"><ChangelogView /></el-tab-pane>
+ <!-- <el-tab-pane label="Mods">Mods</el-tab-pane> -->
+ <el-tab-pane name="Settings" label="Settings"><SettingsView/></el-tab-pane>
+ <el-tab-pane v-if="$store.state.developer_mode" name="Dev" label="Dev">
+ <DeveloperView/>
+ </el-tab-pane>
+ </el-tabs>
+ <div id="fc_window__controls">
+ <el-button color="white" icon="SemiSelect" @click="minimize" circle />
+ <el-button color="white" icon="CloseBold" @click="close" circle />
+ </div>
+</template>
+
+<style>
+/* Borders reset */
+.fc_menu__tabs .el-tabs__nav, .fc_menu__tabs .el-tabs__header {
+ border: none !important;
+}
+
+/* Header item */
+.fc_menu__tabs .el-tabs__item {
+ color: #b4b6b9;
+ text-transform: uppercase;
+ border: none !important;
+ font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
+ font-weight: bold;
+ font-size: large;
+ margin: 10px 0;
+}
+
+.fc_menu__tabs .el-tabs__item:hover {
+ color: #c6c9ce;
+}
+
+.fc_menu__tabs .is-active {
+ color: white !important;
+}
+
+/* Header menu */
+.fc_menu__tabs .el-tabs__header {
+ background-image: radial-gradient(transparent 1px);
+ backdrop-filter: saturate(50%) blur(4px);
+ height: auto !important;
+}
+
+/* Window controls */
+#fc_window__controls {
+ display: flex;
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: var(--el-tabs-header-height);
+}
+
+#fc_window__controls > button {
+ color: white;
+ font-size: 20px;
+ margin: auto 5px;
+ background: none;
+ border: none;
+}
+
+#fc_window__controls > button:hover {
+ color: #c6c9ce;
+}
+
+#fc_window__controls > button:active {
+ color: #56585a;
+}
+
+#fc_window__controls > button:last-of-type {
+ margin-right: 15px;
+}
+
+</style>
diff --git a/src-vue/src/assets/vue.svg b/src-vue/src/assets/vue.svg
new file mode 100644
index 00000000..770e9d33
--- /dev/null
+++ b/src-vue/src/assets/vue.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg> \ No newline at end of file
diff --git a/src-vue/src/assets/wallpaperflare.com_wallpaper.jpg b/src-vue/src/assets/wallpaperflare.com_wallpaper.jpg
new file mode 100644
index 00000000..74a840cd
--- /dev/null
+++ b/src-vue/src/assets/wallpaperflare.com_wallpaper.jpg
Binary files differ
diff --git a/src-vue/src/components/PlayButton.vue b/src-vue/src/components/PlayButton.vue
new file mode 100644
index 00000000..8e0b4149
--- /dev/null
+++ b/src-vue/src/components/PlayButton.vue
@@ -0,0 +1,69 @@
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { NorthstarState } from '../utils/NorthstarState';
+import { ReleaseCanal } from '../utils/ReleaseCanal';
+
+export default defineComponent({
+ name: 'PlayButton',
+ computed: {
+ playButtonLabel(): string {
+ if (this.$store.state.northstar_is_running) {
+ return "Game is running";
+ }
+
+ switch(this.$store.state.northstar_state) {
+ case NorthstarState.GAME_NOT_FOUND:
+ return "Titanfall2 not found";
+ case NorthstarState.INSTALL:
+ return "Install";
+ case NorthstarState.INSTALLING:
+ return "Installing..."
+ case NorthstarState.MUST_UPDATE:
+ return "Update";
+ case NorthstarState.UPDATING:
+ return "Updating...";
+ case NorthstarState.READY_TO_PLAY:
+ return "Launch game";
+
+ default:
+ return "";
+ }
+ },
+ northstarIsRunning(): boolean {
+ return this.$store.state.northstar_is_running;
+ },
+ options(): {key: string, value: string}[] {
+ return Object.keys(ReleaseCanal).map(function (v) {
+ return {
+ key: v,
+ value: Object.keys(ReleaseCanal)[Object.values(ReleaseCanal).indexOf(v)]
+ }
+ });
+ }
+ },
+ methods: {
+ launchGame() {
+ this.$store.commit('launchGame');
+ }
+ }
+});
+</script>
+
+<template>
+ <el-button :disabled="northstarIsRunning" type="primary" size="large" @click="launchGame" class="fc_launch__button">
+ {{ playButtonLabel }}
+ </el-button>
+</template>
+
+<style scoped>
+button {
+ text-transform: uppercase;
+ border-radius: 2px;
+ padding: 30px;
+ font-size: 15px;
+}
+.fc_launch__button:focus {
+ background-color: var(--el-color-primary);
+ border-color: var(--el-color-primary);
+}
+</style>
diff --git a/src-vue/src/main.ts b/src-vue/src/main.ts
new file mode 100644
index 00000000..0ac31a2d
--- /dev/null
+++ b/src-vue/src/main.ts
@@ -0,0 +1,23 @@
+import { createApp } from 'vue'
+import App from './App.vue'
+import ElementPlus from "element-plus";
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+
+
+// styles
+import 'element-plus/theme-chalk/index.css';
+import './style.css'
+import { store } from './plugins/store';
+
+const app = createApp(App);
+app.use(ElementPlus);
+
+// icons
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+ app.component(key, component);
+}
+
+// style
+app.use( store, '$store' );
+
+app.mount('#app')
diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts
new file mode 100644
index 00000000..ccc90b1b
--- /dev/null
+++ b/src-vue/src/plugins/store.ts
@@ -0,0 +1,207 @@
+import { createStore } from 'vuex';
+import { listen, Event as TauriEvent } from "@tauri-apps/api/event";
+import { Tabs } from "../utils/Tabs";
+import { InstallType } from "../utils/InstallType";
+import { invoke } from "@tauri-apps/api";
+import { GameInstall } from "../utils/GameInstall";
+import { ReleaseCanal } from "../utils/ReleaseCanal";
+import { ElNotification } from 'element-plus';
+import { NorthstarState } from '../utils/NorthstarState';
+
+
+export interface FlightCoreStore {
+ current_tab: Tabs,
+ developer_mode: boolean,
+ game_path: string,
+ install_type: InstallType,
+
+ installed_northstar_version: string,
+ northstar_state: NorthstarState,
+ release_canal: ReleaseCanal,
+
+ northstar_is_running: boolean,
+ origin_is_running: boolean
+}
+
+export const store = createStore<FlightCoreStore>({
+ state (): FlightCoreStore {
+ return {
+ current_tab: Tabs.PLAY,
+ developer_mode: false,
+ game_path: undefined as unknown as string,
+ install_type: undefined as unknown as InstallType,
+
+ installed_northstar_version: "",
+ northstar_state: NorthstarState.GAME_NOT_FOUND,
+ release_canal: ReleaseCanal.RELEASE,
+
+ northstar_is_running: false,
+ origin_is_running: false
+ }
+ },
+ mutations: {
+ checkNorthstarUpdates(state) {
+ _get_northstar_version_number(state);
+ },
+ toggleDeveloperMode(state) {
+ state.developer_mode = !state.developer_mode;
+
+ // Reset tab when closing dev mode.
+ if (!state.developer_mode) {
+ store.commit('updateCurrentTab', Tabs.PLAY);
+ }
+ },
+ initialize(state) {
+ _initializeApp(state);
+ _checkForFlightCoreUpdates(state);
+ _initializeListeners(state);
+ },
+ updateCurrentTab(state: any, newTab: Tabs) {
+ state.current_tab = newTab;
+ },
+ async launchGame(state: any) {
+ // TODO update installation if release track was switched
+ switch (state.northstar_state) {
+ // Install northstar if it wasn't detected.
+ case NorthstarState.INSTALL:
+ let install_northstar_result = invoke("install_northstar_caller", { gamePath: state.game_path, northstarPackageName: ReleaseCanal.RELEASE });
+ state.northstar_state = NorthstarState.INSTALLING;
+
+ await install_northstar_result.then((message) => {
+ console.log(message);
+ })
+ .catch((error) => {
+ console.error(error);
+ alert(error);
+ });
+
+ _get_northstar_version_number(state);
+ break;
+
+ // Update northstar if it is outdated.
+ case NorthstarState.MUST_UPDATE:
+ // Updating is the same as installing, simply overwrites the existing files
+ let reinstall_northstar_result = invoke("install_northstar_caller", { gamePath: state.game_path, northstarPackageName: ReleaseCanal.RELEASE });
+ state.northstar_state = NorthstarState.UPDATING;
+
+ await reinstall_northstar_result.then((message) => {
+ console.log(message);
+ })
+ .catch((error) => {
+ console.error(error);
+ alert(error);
+ });
+
+ _get_northstar_version_number(state);
+ break;
+
+ // Game is ready to play.
+ case NorthstarState.READY_TO_PLAY:
+ // Show an error message if Origin is not running.
+ if (!state.origin_is_running) {
+ ElNotification({
+ title: 'Origin is not running',
+ message: "Northstar cannot launch while you're not authenticated with Origin.",
+ type: 'warning',
+ position: 'bottom-right'
+ });
+
+ // If Origin isn't running, end here
+ return;
+ }
+
+ let game_install = {
+ game_path: state.game_path,
+ install_type: state.install_type
+ } as GameInstall;
+ await invoke("launch_northstar_caller", { gameInstall: game_install })
+ .then((message) => {
+ console.log(message);
+ // NorthstarState.RUNNING
+ })
+ .catch((error) => {
+ console.error(error);
+ alert(error);
+ });
+ break;
+ }
+ }
+ }
+});
+
+/**
+ * This is called when application root component has been mounted.
+ * It invokes all Rust methods that are needed to initialize UI.
+ */
+async function _initializeApp(state: any) {
+ const result = await invoke("find_game_install_location_caller")
+ .catch((err) => {
+ // Gamepath not found or other error
+ console.error(err);
+ alert(err);
+ });
+ const typedResult: GameInstall = result as GameInstall;
+ state.game_path = typedResult.game_path;
+ state.install_type = typedResult.install_type;
+
+ // Check installed Northstar version if found
+ await _get_northstar_version_number(state);
+}
+
+async function _checkForFlightCoreUpdates(state: FlightCoreStore) {
+ // Check if FlightCore up-to-date
+ let flightcore_is_outdated = await invoke("check_is_flightcore_outdated_caller") as boolean;
+
+ // Get FlightCore version number
+ let flightcore_version_number = await invoke("get_version_number") as string;
+
+ if (flightcore_is_outdated) {
+ ElNotification({
+ title: 'FlightCore outdated!',
+ message: `Please update FlightCore. Running outdated version ${flightcore_version_number}`,
+ type: 'warning',
+ position: 'bottom-right',
+ duration: 0 // Duration `0` means the notification will not auto-vanish
+ });
+ }
+}
+
+/**
+ * This registers callbacks listening to events from Rust-backend.
+ * Those events include Origin and Northstar running state.
+ */
+function _initializeListeners(state: any) {
+ listen("origin-running-ping", function (evt: TauriEvent<any>) {
+ state.origin_is_running = evt.payload as boolean;
+ });
+
+ listen("northstar-running-ping", function (evt: TauriEvent<any>) {
+ state.northstar_is_running = evt.payload as boolean;
+ });
+}
+
+/**
+ * This retrieves Northstar version tag, and stores it in application
+ * state, for it to be displayed in UI.
+ */
+async function _get_northstar_version_number(state: any) {
+ let northstar_version_number: string = await invoke("get_northstar_version_number_caller", { gamePath: state.game_path });
+ if (northstar_version_number && northstar_version_number.length > 0) {
+ state.installed_northstar_version = northstar_version_number;
+ state.northstar_state = NorthstarState.READY_TO_PLAY;
+
+ await invoke("check_is_northstar_outdated", { gamePath: state.game_path, northstarPackageName: ReleaseCanal.RELEASE })
+ .then((message) => {
+ if (message) {
+ state.northstar_state = NorthstarState.MUST_UPDATE;
+ }
+ })
+ .catch((error) => {
+ console.error(error);
+ alert(error);
+ });
+ }
+ else {
+ state.northstar_state = NorthstarState.INSTALL;
+ }
+}
diff --git a/src-vue/src/style.css b/src-vue/src/style.css
new file mode 100644
index 00000000..eee68edd
--- /dev/null
+++ b/src-vue/src/style.css
@@ -0,0 +1,15 @@
+body {
+ margin: 0;
+ font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
+ --el-tabs-header-height: 60px;
+ user-select: none;
+}
+
+#fc_bg__container {
+ background: url(/src/assets/wallpaperflare.com_wallpaper.jpg) center no-repeat;
+ background-size: cover;
+ height: 100%;
+ width: 100%;
+ position: fixed;
+ filter: brightness(0.8);
+}
diff --git a/src-vue/src/utils/GameInstall.ts b/src-vue/src/utils/GameInstall.ts
new file mode 100644
index 00000000..07358f6c
--- /dev/null
+++ b/src-vue/src/utils/GameInstall.ts
@@ -0,0 +1,4 @@
+export interface GameInstall {
+ game_path: string;
+ install_type: string;
+}
diff --git a/src-vue/src/utils/InstallType.ts b/src-vue/src/utils/InstallType.ts
new file mode 100644
index 00000000..797f4077
--- /dev/null
+++ b/src-vue/src/utils/InstallType.ts
@@ -0,0 +1,8 @@
+// Enumerates the way Titanfall2 could be installed (Steam/Origin/EA-Desktop)
+// Needs to be synced with `pub enum InstallType` in /src-tauri/src/lib.rs
+export enum InstallType {
+ STEAM = 'STEAM',
+ ORIGIN = 'ORIGIN',
+ EAPLAY = 'EAPLAY',
+ UNKNOWN = 'UNKNOWN', // used when the install location was manually selected
+}
diff --git a/src-vue/src/utils/NorthstarState.ts b/src-vue/src/utils/NorthstarState.ts
new file mode 100644
index 00000000..4150a380
--- /dev/null
+++ b/src-vue/src/utils/NorthstarState.ts
@@ -0,0 +1,8 @@
+export enum NorthstarState {
+ GAME_NOT_FOUND,
+ INSTALL,
+ INSTALLING,
+ MUST_UPDATE,
+ UPDATING,
+ READY_TO_PLAY
+} \ No newline at end of file
diff --git a/src-vue/src/utils/ReleaseCanal.ts b/src-vue/src/utils/ReleaseCanal.ts
new file mode 100644
index 00000000..9363aa25
--- /dev/null
+++ b/src-vue/src/utils/ReleaseCanal.ts
@@ -0,0 +1,4 @@
+export enum ReleaseCanal {
+ RELEASE = <any>'Northstar',
+ RELEASE_CANDIDATE = <any>'NorthstarReleaseCandidate'
+}
diff --git a/src-vue/src/utils/Tabs.ts b/src-vue/src/utils/Tabs.ts
new file mode 100644
index 00000000..5266da81
--- /dev/null
+++ b/src-vue/src/utils/Tabs.ts
@@ -0,0 +1,6 @@
+export enum Tabs {
+ PLAY = 'Play',
+ CHANGELOG = 'Changelog',
+ SETTINGS = 'Settings',
+ DEV = 'Dev'
+}
diff --git a/src-vue/src/views/ChangelogView.vue b/src-vue/src/views/ChangelogView.vue
new file mode 100644
index 00000000..9fca5e5b
--- /dev/null
+++ b/src-vue/src/views/ChangelogView.vue
@@ -0,0 +1,23 @@
+<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>
+</template>
+
+<script lang="ts">
+export default {
+ name: "ChangelogView"
+}
+</script>
+
+<style scoped>
+.fc__changelog__container {
+ padding: 20px 30px;
+}
+
+.el-link {
+ color: white;
+}
+</style>
diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue
new file mode 100644
index 00000000..52dd29fd
--- /dev/null
+++ b/src-vue/src/views/DeveloperView.vue
@@ -0,0 +1,41 @@
+<template>
+ <div class="fc__developer__container">
+ <el-button type="primary" @click="disableDevMode">
+ Disable developer mode
+ </el-button>
+
+ <el-button type="primary" @click="crashApplication">
+ Panic button
+ </el-button>
+ </div>
+</template>
+
+<script lang="ts">
+import {defineComponent} from "vue";
+import { invoke } from "@tauri-apps/api";
+import { ElNotification } from "element-plus";
+
+export default defineComponent({
+ name: "DeveloperView",
+ methods: {
+ disableDevMode() {
+ this.$store.commit('toggleDeveloperMode');
+ },
+ async crashApplication() {
+ await invoke("force_panic");
+ ElNotification({
+ title: 'Error',
+ message: "Never should have been able to get here!",
+ type: 'error',
+ position: 'bottom-right'
+ });
+ }
+ }
+});
+</script>
+
+<style scoped>
+.fc__developer__container {
+ padding: 20px 30px;
+}
+</style>
diff --git a/src-vue/src/views/PlayView.vue b/src-vue/src/views/PlayView.vue
new file mode 100644
index 00000000..b02acc59
--- /dev/null
+++ b/src-vue/src/views/PlayView.vue
@@ -0,0 +1,126 @@
+<script lang="ts">
+import { ElNotification } from 'element-plus';
+import {Tabs} from "../utils/Tabs";
+import PlayButton from '../components/PlayButton.vue';
+import { defineComponent } from "vue";
+
+export default defineComponent({
+ data() {
+ return {
+ developerModeClicks: 0
+ }
+ },
+ components: {
+ PlayButton
+ },
+ computed: {
+ northstarIsRunning(): boolean {
+ return this.$store.state.northstar_is_running;
+ },
+ northstarVersion(): string {
+ return this.$store.state.installed_northstar_version;
+ },
+ },
+ methods: {
+ activateDeveloperMode() {
+ this.developerModeClicks += 1;
+ if (this.developerModeClicks >= 6) {
+ this.$store.state.developer_mode = true;
+ ElNotification({
+ title: 'Watch out!',
+ message: 'Developer mode enabled.',
+ type: 'info',
+ position: 'bottom-right'
+ });
+ this.developerModeClicks = 0;
+ }
+ },
+
+ showChangelogPage() {
+ this.$store.commit('updateCurrentTab', Tabs.CHANGELOG);
+ }
+ }
+});
+</script>
+
+<template>
+ <div class="fc_launch__container">
+ <div class="fc_title">Northstar</div>
+ <div class="fc_northstar__version__container">
+ <div class="fc_northstar__version" @click="activateDeveloperMode">
+ {{ northstarVersion === '' ? 'Unknown version' : `v${northstarVersion}` }}
+ </div>
+ <div v-if="northstarVersion !== ''" class="fc_changelog__link" @click="showChangelogPage">
+ (see patch notes)
+ </div>
+ </div>
+ <div>
+ <PlayButton/>
+ <div v-if="$store.state.developer_mode" id="fc_services__status">
+ <div>
+ <div class="fc_version__line">Northstar is running: </div>
+ <div class="fc_version__line fc_version__line__boolean"> {{ northstarIsRunning }}</div>
+ </div>
+ <div>
+ <div class="fc_version__line">Origin is running: </div>
+ <div class="fc_version__line fc_version__line__boolean">{{ $store.state.origin_is_running }}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<style scoped>
+.fc_launch__container {
+ margin: 50px;
+ position: fixed;
+ bottom: 0;
+}
+
+/* Titles */
+.fc_title {
+ color: white;
+ font-size: 50px;
+ font-weight: bold;
+}
+
+/* Northstar version + changelog link */
+.fc_northstar__version__container {
+ margin-bottom: 20px;
+ color: rgb(168, 168, 168);
+}
+
+.fc_northstar__version, .fc_changelog__link {
+ display: inline-block;
+}
+
+.fc_changelog__link {
+ margin-left: 3px;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+
+.fc_launch__button:focus {
+ background-color: var(--el-color-primary);
+ border-color: var(--el-color-primary);
+}
+
+
+#fc_services__status {
+ display: inline-block;
+ position: fixed;
+ padding: 10px 20px;
+ color: #e8edef;
+}
+
+.fc_version__line {
+ display: inline-block;
+}
+
+.fc_version__line__boolean {
+ margin-left: 5px;
+ margin-bottom: 5px;
+ color: #b4b6b9;
+}
+</style>
diff --git a/src-vue/src/views/SettingsView.vue b/src-vue/src/views/SettingsView.vue
new file mode 100644
index 00000000..0e5498e9
--- /dev/null
+++ b/src-vue/src/views/SettingsView.vue
@@ -0,0 +1,88 @@
+<template>
+ <div class="fc_settings__container">
+ <!-- Game folder location -->
+ <h3>Manage installation</h3>
+ <el-input
+ v-model="$store.state.game_path"
+ class="w-50 m-2"
+ placeholder="Choose installation folder"
+ @click="updateGamePath"
+ >
+ <template #prepend>
+ <el-button icon="Folder" @click="updateGamePath"/>
+ </template>
+ </el-input>
+ <h3>About:</h3>
+ UI design inspired by <el-link :underline="false" target="_blank" href="https://github.com/TFORevive/tforevive_launcher/" type="primary">TFORevive Launcher</el-link> (not yet public)
+ </div>
+</template>
+
+<script lang="ts">
+import { open } from '@tauri-apps/api/dialog';
+import { appDir } from '@tauri-apps/api/path';
+import { invoke } from "@tauri-apps/api";
+import { defineComponent } from "vue";
+import { ElNotification } from 'element-plus';
+
+export default defineComponent({
+ name: "SettingsView",
+ methods: {
+ async updateGamePath() {
+ // Open a selection dialog for directories
+ const selected = await open({
+ directory: true,
+ multiple: false,
+ defaultPath: await appDir(),
+ });
+ if (Array.isArray(selected)) {
+ // user selected multiple directories
+ alert("Please only select a single directory");
+ } else if (selected === null) {
+ // user cancelled the selection
+ } else {
+ // user selected a single directory
+
+ // Verify if valid Titanfall2 install location
+ let is_valid_titanfall2_install = await invoke("verify_install_location", { gamePath: selected }) as boolean;
+ if (is_valid_titanfall2_install) {
+ this.$store.state.game_path = selected;
+ // Check for Northstar install
+ this.$store.commit('checkNorthstarUpdates');
+ }
+ else {
+ // Not valid Titanfall2 install
+ ElNotification({
+ title: 'Wrong folder',
+ message: "Selected folder is not a valid Titanfall2 install.",
+ type: 'error',
+ position: 'bottom-right'
+ });
+ }
+ }
+ }
+ },
+ mounted() {
+ document.querySelector('input')!.disabled = true;
+ }
+});
+</script>
+
+<style scoped>
+.fc_settings__container {
+ max-width: 1200px;
+ padding: 20px 30px;
+ margin: 0 auto;
+ color: white;
+}
+
+h3:first-of-type {
+ margin-top: 0;
+ margin-bottom: 1em;
+ text-transform: uppercase;
+ font-weight: unset;
+}
+
+.el-input {
+ width: 50%;
+}
+</style>
diff --git a/src-vue/src/vite-env.d.ts b/src-vue/src/vite-env.d.ts
new file mode 100644
index 00000000..323c78a6
--- /dev/null
+++ b/src-vue/src/vite-env.d.ts
@@ -0,0 +1,7 @@
+/// <reference types="vite/client" />
+
+declare module '*.vue' {
+ import type { DefineComponent } from 'vue'
+ const component: DefineComponent<{}, {}, any>
+ export default component
+}
diff --git a/src-vue/src/vuex-shim.d.ts b/src-vue/src/vuex-shim.d.ts
new file mode 100644
index 00000000..40438a97
--- /dev/null
+++ b/src-vue/src/vuex-shim.d.ts
@@ -0,0 +1,8 @@
+import { ComponentCustomProperties } from 'vue'
+import { Store } from 'vuex'
+
+declare module '@vue/runtime-core' {
+ interface ComponentCustomProperties {
+ $store: Store<FlightCoreStore>
+ }
+}