diff options
Diffstat (limited to 'src-vue/src')
-rw-r--r-- | src-vue/src/App.vue | 111 | ||||
-rw-r--r-- | src-vue/src/assets/vue.svg | 1 | ||||
-rw-r--r-- | src-vue/src/assets/wallpaperflare.com_wallpaper.jpg | bin | 0 -> 586994 bytes | |||
-rw-r--r-- | src-vue/src/components/PlayButton.vue | 68 | ||||
-rw-r--r-- | src-vue/src/main.ts | 23 | ||||
-rw-r--r-- | src-vue/src/plugins/store.ts | 193 | ||||
-rw-r--r-- | src-vue/src/style.css | 15 | ||||
-rw-r--r-- | src-vue/src/utils/GameInstall.ts | 4 | ||||
-rw-r--r-- | src-vue/src/utils/InstallType.ts | 8 | ||||
-rw-r--r-- | src-vue/src/utils/NorthstarState.ts | 7 | ||||
-rw-r--r-- | src-vue/src/utils/ReleaseCanal.ts | 4 | ||||
-rw-r--r-- | src-vue/src/utils/Tabs.ts | 6 | ||||
-rw-r--r-- | src-vue/src/views/ChangelogView.vue | 23 | ||||
-rw-r--r-- | src-vue/src/views/DeveloperView.vue | 26 | ||||
-rw-r--r-- | src-vue/src/views/PlayView.vue | 126 | ||||
-rw-r--r-- | src-vue/src/views/SettingsView.vue | 52 | ||||
-rw-r--r-- | src-vue/src/vite-env.d.ts | 7 | ||||
-rw-r--r-- | src-vue/src/vuex-shim.d.ts | 8 |
18 files changed, 682 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 Binary files differnew file mode 100644 index 00000000..74a840cd --- /dev/null +++ b/src-vue/src/assets/wallpaperflare.com_wallpaper.jpg diff --git a/src-vue/src/components/PlayButton.vue b/src-vue/src/components/PlayButton.vue new file mode 100644 index 00000000..b5541abe --- /dev/null +++ b/src-vue/src/components/PlayButton.vue @@ -0,0 +1,68 @@ +<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.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, + // @ts-ignore + value: ReleaseCanal[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..544d314a --- /dev/null +++ b/src-vue/src/plugins/store.ts @@ -0,0 +1,193 @@ +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 type Store = { + 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({ + state () { + return { + current_tab: Tabs.PLAY, + developer_mode: false, + game_path: "this/is/the/game/path", + install_type: undefined, + + installed_northstar_version: "", + northstar_state: NorthstarState.INSTALL, + release_canal: ReleaseCanal.RELEASE, + + northstar_is_running: false, + origin_is_running: false + } + }, + mutations: { + 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 + + // Install northstar if it wasn't detected. + if (state.northstar_state === 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); + return; + } + + // Update northstar if it is outdated. + if (state.northstar_state === NorthstarState.MUST_UPDATE) { + // Updating is the same as installing, simply overwrites the existing files + let install_northstar_result = invoke("install_northstar_caller", { gamePath: state.game_path, northstarPackageName: ReleaseCanal.RELEASE }); + state.northstar_state = NorthstarState.UPDATING; + + await install_northstar_result.then((message) => { + console.log(message); + }) + .catch((error) => { + console.error(error); + alert(error); + }); + + _get_northstar_version_number(state); + return; + } + + // Game is ready to play + if (state.northstar_state === 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); + }); + return; + } + } + } +}); + +/** + * 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) { + // @ts-ignore + const result: GameInstall = await invoke("find_game_install_location_caller") + .catch((err) => { + // Gamepath not found or other error + console.error(err); + alert(err); + }); + state.game_path = result.game_path; + state.install_type = result.install_type as InstallType; + + // Check installed Northstar version if found + await _get_northstar_version_number(state); +} + +// TODO +async function _checkForFlightCoreUpdates(state: any) { + // Get version number + let version_number_string = await invoke("get_version_number") as string; + // Check if up-to-date + let flightcore_is_outdated = await invoke("check_is_flightcore_outdated_caller") as boolean; +} + +/** + * 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); + }); + } +} 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..d6e31923 --- /dev/null +++ b/src-vue/src/utils/NorthstarState.ts @@ -0,0 +1,7 @@ +export enum NorthstarState { + 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..ca1ba8a5 --- /dev/null +++ b/src-vue/src/utils/ReleaseCanal.ts @@ -0,0 +1,4 @@ +export enum ReleaseCanal { + RELEASE = 'Northstar', + RELEASE_CANDIDATE = '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..bfb206a2 --- /dev/null +++ b/src-vue/src/views/DeveloperView.vue @@ -0,0 +1,26 @@ +<template> + <div class="fc__developer__container"> + <el-button type="primary" @click="disableDevMode"> + Disable developer mode + </el-button> + </div> +</template> + +<script lang="ts"> +import {defineComponent} from "vue"; + +export default defineComponent({ + name: "DeveloperView", + methods: { + disableDevMode() { + this.$store.commit('toggleDeveloperMode'); + } + } +}); +</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..64779f6a --- /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..e0ffc026 --- /dev/null +++ b/src-vue/src/views/SettingsView.vue @@ -0,0 +1,52 @@ +<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"> +export default { + name: "SettingsView", + methods: { + updateGamePath() { + console.log('TODO: update path'); + } + }, + 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..1429115b --- /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<State>
+ }
+}
|