diff options
author | GeckoEidechse <gecko.eidechse+git@pm.me> | 2023-04-26 21:58:55 +0200 |
---|---|---|
committer | GeckoEidechse <gecko.eidechse+git@pm.me> | 2023-04-26 21:58:55 +0200 |
commit | 10616b295eb23c8250a0d874fe05211f73a8ba81 (patch) | |
tree | aa9496bd16d973f69f6c4d9df7695cae469fad83 /src-vue/src | |
parent | 85bb5253657c16d9674a9be2f6c8090b413ca7fb (diff) | |
parent | e38ab60e1e4f565f0dafdb7b539e386a390594d7 (diff) | |
download | FlightCore-10616b295eb23c8250a0d874fe05211f73a8ba81.tar.gz FlightCore-10616b295eb23c8250a0d874fe05211f73a8ba81.zip |
Merge branch 'main' into fix/handle-failed-download
Diffstat (limited to 'src-vue/src')
23 files changed, 1247 insertions, 437 deletions
diff --git a/src-vue/src/App.vue b/src-vue/src/App.vue index f740bd2f..f80e000d 100644 --- a/src-vue/src/App.vue +++ b/src-vue/src/App.vue @@ -6,7 +6,8 @@ import ModsView from './views/ModsView.vue'; import SettingsView from './views/SettingsView.vue'; import { appWindow } from '@tauri-apps/api/window'; import { store } from './plugins/store'; -import { invoke, window as tauriWindow } from "@tauri-apps/api"; +import { Store } from 'tauri-plugin-store-api'; +import { invoke } from "@tauri-apps/api"; export default { components: { @@ -19,8 +20,18 @@ export default { data() { return {} }, - mounted: () => { + mounted: async function() { store.commit('initialize'); + + // Initialize interface language + const persistentStore = new Store('flight-core-settings.json'); + let lang: string | null = await persistentStore.get('lang'); + if (lang === null) { + lang = navigator.language.substring(0, 2); + persistentStore.set('lang', lang); + await persistentStore.save(); + } + this.$root!.$i18n.locale = lang; }, methods: { async toggleMaximize() { @@ -56,11 +67,11 @@ export default { id="fc__menu_items" data-tauri-drag-region > - <el-menu-item index="/">Play</el-menu-item> - <el-menu-item index="/changelog">Changelog</el-menu-item> - <el-menu-item index="/mods">Mods</el-menu-item> - <el-menu-item index="/settings">Settings</el-menu-item> - <el-menu-item index="/dev" v-if="$store.state.developer_mode">Dev</el-menu-item> + <el-menu-item index="/">{{ $t('menu.play') }}</el-menu-item> + <el-menu-item index="/changelog">{{ $t('menu.changelog') }}</el-menu-item> + <el-menu-item index="/mods">{{ $t('menu.mods') }}</el-menu-item> + <el-menu-item index="/settings">{{ $t('menu.settings') }}</el-menu-item> + <el-menu-item index="/dev" v-if="$store.state.developer_mode">{{ $t('menu.dev') }}</el-menu-item> </el-menu> <!-- Window controls --> @@ -82,8 +93,20 @@ export default { top: 0; width: 100%; height: var(--fc-menu_height); - background-image: radial-gradient(transparent 1px); - backdrop-filter: saturate(50%) blur(4px); +} + +#fc__menu_bar::before { + position: absolute; + content: ""; + inset: 0; /* same as { top: 0; right: 0; bottom: 0; left: 0; } */ + background-image: linear-gradient(to bottom, red, orange); + z-index: 1; + opacity: 0; + transition: opacity 1s linear; +} + +#fc__menu_bar:hover::before { + opacity: 1; } /* Borders reset */ diff --git a/src-vue/src/components/InstallProgressBar.vue b/src-vue/src/components/InstallProgressBar.vue new file mode 100644 index 00000000..d0c2047c --- /dev/null +++ b/src-vue/src/components/InstallProgressBar.vue @@ -0,0 +1,102 @@ +<script lang="ts"> +import { defineComponent } from 'vue'; +import { appWindow } from '@tauri-apps/api/window'; +import { InstallProgress } from '../../../src-tauri/bindings/InstallProgress'; + +export default defineComponent({ + name: 'InstallProgressBar', + computed: { + progressBarStyle(): string { + return !this.install_or_update ? 'hide-progress' : ''; + } + }, + data() { + return { + percentage: 0, + color: '#409EFF', + install_or_update: false, + status: "unknown", + current_downloaded: -1, + total_size: -1, + }; + }, + methods: { + formatBytes(bytes: number, decimals = 2) { + if (bytes === 0) return '0 Bytes'; + const k = 1000; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; + }, + formatText() { + if (this.status == "DOWNLOADING") { + const current_downloaded_string = this.formatBytes(this.current_downloaded); + const total_size_string = this.formatBytes(this.total_size); + const status = this.$t("generic.downloading"); + return `${status}: ${current_downloaded_string}/${total_size_string}`; + } + if (this.status == "EXTRACTING") { + return this.$t("generic.extracting"); + } + return "Inactive"; // Needed to keep same size format when progress bar is hidden + } + }, + mounted() { + appWindow.listen<InstallProgress>( + 'northstar-install-download-progress', + ({ event, payload }) => { + this.install_or_update = true; + let progress = payload; + this.status = progress.state; + if (progress.state == "DOWNLOADING") { + this.percentage = ((Number(progress.current_downloaded) / Number(progress.total_size)) * 100); + this.color = '#409EFF'; + this.current_downloaded = Number(progress.current_downloaded); + this.total_size = Number(progress.total_size); + } + if (progress.state == "EXTRACTING") { + this.percentage = 100; + this.color = '#67C23A'; + } + if (progress.state == "DONE") { + // Clear state again + this.install_or_update = false + } + } + ); + } +}); +</script> + +<template> + <el-progress + :class="progressBarStyle" + :format="formatText" + :percentage="percentage" + :color="color" + :indeterminate="status === 'EXTRACTING'" + :duration="1" + > + </el-progress> +</template> + +<style scoped> +.el-progress { + margin-top: 10px; +} + +/* Set progress bar width */ +.el-progress:deep(.el-progress-bar) { + width: 200px; + flex-grow: initial; +} + +.el-progress:deep(.el-progress__text) { + line-height: 1.2; +} + +.hide-progress { + opacity: 0; +} +</style> diff --git a/src-vue/src/components/LanguageSelector.vue b/src-vue/src/components/LanguageSelector.vue new file mode 100644 index 00000000..5b1e7ebd --- /dev/null +++ b/src-vue/src/components/LanguageSelector.vue @@ -0,0 +1,51 @@ +<template> + <el-select v-model="value" class="m-2" + :placeholder="$t('settings.language_select')" size="large" + @change="onChange" + > + <el-option + v-for="item in options" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-select> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import { Store } from 'tauri-plugin-store-api'; +const persistentStore = new Store('flight-core-settings.json'); + +export default defineComponent({ + name: 'LanguageSelector', + data: () => ({ + value: '', + options: [ + { + value: 'en', + label: 'English' + }, + { + value: 'fr', + label: 'Français' + }, + { + value: 'de', + label: 'Deutsch' + }, + ] + }), + mounted: async function() { + const lang: string = await persistentStore.get('lang') as string; + this.value = lang; + }, + methods: { + async onChange(value: string) { + this.$root!.$i18n.locale = value; + persistentStore.set('lang', value); + await persistentStore.save(); + } + } +}) +</script> diff --git a/src-vue/src/components/ModsMenu.vue b/src-vue/src/components/ModsMenu.vue index 9b62fcfa..656c05a6 100644 --- a/src-vue/src/components/ModsMenu.vue +++ b/src-vue/src/components/ModsMenu.vue @@ -7,20 +7,20 @@ <h5>Mods</h5> <el-menu-item index="1" @click="$emit('showLocalMods', true)"> <el-icon><Folder /></el-icon> - <span>Local</span> + <span>{{ $t('mods.menu.local') }}</span> </el-menu-item> <el-menu-item index="2" @click="$emit('showLocalMods', false)"> <el-icon><Connection /></el-icon> - <span>Online</span> + <span>{{ $t('mods.menu.online') }}</span> </el-menu-item> <!-- Search inputs --> - <h5>Filter</h5> - <el-input v-model="$store.state.search.searchValue" placeholder="Search" clearable /> + <h5>{{ $t('mods.menu.filter') }}</h5> + <el-input v-model="$store.state.search.searchValue" :placeholder="$t('mods.menu.search')" clearable /> <el-select v-if="!showingLocalMods" v-model="$store.state.search.sortValue" - placeholder="Sort mods" + :placeholder="$t('mods.menu.sort_mods')" > <el-option v-for="item of sortValues" @@ -33,7 +33,7 @@ v-if="!showingLocalMods" v-model="$store.state.search.selectedCategories" multiple - placeholder="Select categories" + :placeholder="$t('mods.menu.select_categories')" > <el-option v-for="item in $store.state.thunderstoreModsCategories" @@ -66,7 +66,7 @@ export default defineComponent({ sortValues(): {label: string, value: string}[] { return Object.keys(SortOptions).map((key: string) => ({ value: key, - label: Object.values(SortOptions)[Object.keys(SortOptions).indexOf(key)] + label: this.$t('mods.menu.sort.' + Object.values(SortOptions)[Object.keys(SortOptions).indexOf(key)]) })); } } diff --git a/src-vue/src/components/PlayButton.vue b/src-vue/src/components/PlayButton.vue index 687f12a4..208b4703 100644 --- a/src-vue/src/components/PlayButton.vue +++ b/src-vue/src/components/PlayButton.vue @@ -18,22 +18,22 @@ export default defineComponent({ }, playButtonLabel(): string { if (this.$store.state.northstar_is_running) { - return "Game is running"; + return this.$t("play.button.northstar_is_running"); } switch(this.$store.state.northstar_state) { case NorthstarState.GAME_NOT_FOUND: - return "Select Titanfall2 game folder"; + return this.$t("play.button.select_game_dir"); case NorthstarState.INSTALL: - return "Install"; + return this.$t("play.button.install"); case NorthstarState.INSTALLING: - return "Installing..." + return this.$t("play.button.installing"); case NorthstarState.MUST_UPDATE: - return "Update"; + return this.$t("play.button.update"); case NorthstarState.UPDATING: - return "Updating..."; + return this.$t("play.button.updating"); case NorthstarState.READY_TO_PLAY: - return "Launch game"; + return this.$t("play.button.ready_to_play"); default: return ""; @@ -57,7 +57,7 @@ export default defineComponent({ options: [ { value: ReleaseCanal.RELEASE_CANDIDATE, - label: 'Northstar release candidate', + label: this.$t('channels.names.NorthstarReleaseCandidate'), }, ] }, @@ -83,10 +83,10 @@ export default defineComponent({ return this.showReleaseSwitch ? 'border-radius: 2px 0 0 2px;' : 'border-radius: 2px'; - } + }, }, methods: { - launchGame() { + async launchGame() { this.$store.commit('launchGame'); } } @@ -94,30 +94,31 @@ export default defineComponent({ </script> <template> - <el-button :disabled="northstarIsRunning" - type="primary" size="large" @click="launchGame" - class="fc_launch__button" :style="buttonRadiusStyle"> - {{ playButtonLabel }} - </el-button> - <el-select v-if="showReleaseSwitch" :disabled="northstarIsRunning" - v-model="currentCanal" placeholder="Select"> - <el-option-group - v-for="group in selectOptions" - :key="group.label" - :label="group.label" - > - <el-option - v-for="item in group.options" - :key="item.value" - :label="item.label" - :value="item.value" - /> - </el-option-group> - </el-select> + <nav> + <el-button :disabled="northstarIsRunning" + type="primary" size="large" @click="launchGame" + class="fc_launch__button" :style="buttonRadiusStyle"> + {{ playButtonLabel }} + </el-button> + <el-select v-if="showReleaseSwitch" :disabled="northstarIsRunning" + v-model="currentCanal" placeholder="Select"> + <el-option-group + v-for="group in selectOptions" + :key="group.label" + :label="group.label" + > + <el-option + v-for="item in group.options" + :key="item.value" + :label="item.label" + :value="item.value" + /> + </el-option-group> + </el-select> + </nav> </template> <style scoped> - button { text-transform: uppercase; padding: 30px; @@ -130,7 +131,6 @@ button { } /* Release canal selector */ - .el-select { width: 0; margin-right: 50px; diff --git a/src-vue/src/components/PullRequestsSelector.vue b/src-vue/src/components/PullRequestsSelector.vue index 58a355f4..ba95624a 100644 --- a/src-vue/src/components/PullRequestsSelector.vue +++ b/src-vue/src/components/PullRequestsSelector.vue @@ -1,7 +1,12 @@ <template> <div> <el-collapse @change="onChange"> - <el-collapse-item title="Launcher PRs" name="1"> + <el-collapse-item name="1"> + <template #title> + Launcher PRs + <el-input class="pr_search_input" v-model="launcherSearch" placeholder="Filter pull requests" @click.stop="() => false"></el-input> + </template> + <p v-if="pull_requests_launcher.length === 0"> <el-progress :show-text="false" @@ -12,16 +17,28 @@ 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-card + v-else-if="filtered_launcher_pull_requests.length !== 0" + shadow="hover" + v-for="pull_request in filtered_launcher_pull_requests" + v-bind:key="pull_request.url" + > <el-button type="primary" @click="installLauncherPR(pull_request)">Install</el-button> + <el-button type="primary" @click="downloadLauncherPR(pull_request)">Download</el-button> <a target="_blank" :href="pull_request.html_url"> {{ pull_request.number }}: {{ pull_request.title }} </a> </el-card> + <div v-else class="no_matching_pr"> + No matching PR found. + </div> </el-collapse-item> - <el-collapse-item title="Mods PRs" name="2"> + <el-collapse-item name="2"> + <template #title> + Mods PRs + <el-input class="pr_search_input" v-model="modsSearch" placeholder="Filter pull requests" @click.stop="() => false"></el-input> + </template> <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 @@ -39,12 +56,21 @@ 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-card + v-else-if="filtered_mods_pull_requests.length !== 0" + shadow="hover" + v-for="pull_request in filtered_mods_pull_requests" + v-bind:key="pull_request.url" + > <el-button type="primary" @click="installModsPR(pull_request)">Install</el-button> + <el-button type="primary" @click="downloadModsPR(pull_request)">Download</el-button> <a target="_blank" :href="pull_request.html_url"> {{ pull_request.number }}: {{ pull_request.title }} </a> </el-card> + <div v-else class="no_matching_pr"> + No matching PR found. + </div> </el-collapse-item> </el-collapse> </div> @@ -54,11 +80,13 @@ 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', + data: () => ({ + launcherSearch: '', + modsSearch: '' + }), computed: { pull_requests_launcher(): PullsApiResponseElement[] { return this.$store.state.pullrequests.pull_requests_launcher; @@ -66,20 +94,49 @@ export default defineComponent({ pull_requests_mods(): PullsApiResponseElement[] { return this.$store.state.pullrequests.pull_requests_mods; }, + + filtered_launcher_pull_requests(): PullsApiResponseElement[] { + if (this.launcherSearch.length === 0) { + return this.pull_requests_launcher; + } + + return this.pull_requests_launcher.filter(pr => + // Check PR id + pr.number.toString().indexOf(this.launcherSearch) !== -1 + // Check PR title + || pr.title.toLowerCase().indexOf(this.launcherSearch.toLowerCase()) !== -1); + }, + filtered_mods_pull_requests(): PullsApiResponseElement[] { + if (this.modsSearch.length === 0) { + return this.pull_requests_mods; + } + + return this.pull_requests_mods.filter(pr => + // Check PR id + pr.number.toString().indexOf(this.modsSearch) !== -1 + // Check PR title + || pr.title.toLowerCase().indexOf(this.modsSearch.toLowerCase()) !== -1); + }, }, methods: { onChange(e: Object) { const openedCollapseNames = Object.values(e); if (openedCollapseNames.includes('1') && this.pull_requests_launcher.length === 0) { - this.getPullRequests('LAUNCHER'); + this.getPullRequests('Launcher'); } if (openedCollapseNames.includes('2') && this.pull_requests_mods.length === 0) { - this.getPullRequests('MODS'); + this.getPullRequests('Mods'); } }, async getPullRequests(pull_request_type: PullRequestType) { this.$store.commit('getPullRequests', pull_request_type); }, + async downloadLauncherPR(pull_request: PullsApiResponseElement) { + this.$store.commit('downloadLauncherPR', pull_request); + }, + async downloadModsPR(pull_request: PullsApiResponseElement) { + this.$store.commit('downloadModsPR', pull_request); + }, async installLauncherPR(pull_request: PullsApiResponseElement) { this.$store.commit('installLauncherPR', pull_request); }, @@ -100,4 +157,18 @@ export default defineComponent({ padding-left: 10px; font-size: 14px; } + +.el-collapse:deep(.el-collapse-item__arrow) { + margin: 0 8px; +} + +.pr_search_input { + width: 200px; + margin: 0 0 0 auto; +} + +.no_matching_pr { + margin: 0 auto; + width: max-content; +} </style> diff --git a/src-vue/src/components/ThunderstoreModCard.vue b/src-vue/src/components/ThunderstoreModCard.vue index c9f6768c..fec95f14 100644 --- a/src-vue/src/components/ThunderstoreModCard.vue +++ b/src-vue/src/components/ThunderstoreModCard.vue @@ -21,7 +21,7 @@ <br/> <div class="name hide-text-overflow">{{ mod.name }}</div> - <div class="author hide-text-overflow">by {{ mod.owner }}</div> + <div class="author hide-text-overflow">{{ $t('mods.card.by') }} {{ mod.owner }}</div> <div class="desc"> {{ latestVersion.description }} </div> @@ -33,7 +33,7 @@ :loading="isBeingInstalled || isBeingUpdated" @click.stop="installMod(mod)" > - {{ modButtonText }} + {{ $t(modButtonText) }} </el-button> <!-- Information dropdown menu --> @@ -51,10 +51,10 @@ <template #dropdown> <el-dropdown-menu> <el-dropdown-item @click="openURL(mod.package_url)"> - More info + {{ $t('mods.card.more_info') }} </el-dropdown-item> <el-dropdown-item @click="deleteMod(mod)"> - Remove mod + {{ $t('mods.card.remove') }} </el-dropdown-item> </el-dropdown-menu> </template> @@ -72,9 +72,9 @@ import {invoke, shell} from "@tauri-apps/api"; import {ThunderstoreModStatus} from "../utils/thunderstore/ThunderstoreModStatus"; import {NorthstarMod} from "../../../src-tauri/bindings/NorthstarMod"; import {GameInstall} from "../utils/GameInstall"; -import {ElNotification} from "element-plus"; import { NorthstarState } from "../utils/NorthstarState"; import { ElMessageBox } from "element-plus"; +import { showErrorNotification, showNotification } from "../utils/ui"; export default defineComponent({ name: "ThunderstoreModCard", @@ -129,15 +129,15 @@ export default defineComponent({ modButtonText(): string { switch (this.modStatus) { case ThunderstoreModStatus.BEING_INSTALLED: - return "Installing..."; + return "mods.card.button.being_installed"; case ThunderstoreModStatus.BEING_UPDATED: - return "Updating..."; + return "mods.card.button.being_updated"; case ThunderstoreModStatus.INSTALLED: - return "Installed"; + return "mods.card.button.installed"; case ThunderstoreModStatus.NOT_INSTALLED: - return "Install"; + return "mods.card.button.install"; case ThunderstoreModStatus.OUTDATED: - return "Update"; + return "mods.card.button.outdated"; } }, @@ -200,11 +200,11 @@ export default defineComponent({ // Show pop-up to confirm delete ElMessageBox.confirm( - 'Delete Thunderstore mod?', - 'Warning', + this.$t('mods.card.remove_dialog_text'), + this.$t('mods.card.remove_dialog_title'), { - confirmButtonText: 'OK', - cancelButtonText: 'Cancel', + confirmButtonText: this.$t('generic.yes'), + cancelButtonText: this.$t('generic.cancel'), type: 'warning', } ) @@ -214,22 +214,12 @@ export default defineComponent({ install_type: this.$store.state.install_type } as GameInstall; - await invoke("delete_thunderstore_mod", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }) + await invoke<string>("delete_thunderstore_mod", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }) .then((message) => { - ElNotification({ - title: `Removed ${mod.name}`, - message: message as string, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('mods.card.remove_success', {modName: mod.name}), message); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { this.$store.commit('loadInstalledMods'); @@ -253,21 +243,11 @@ export default defineComponent({ this.isBeingInstalled = true; } - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => { - ElNotification({ - title: `Installed ${mod.name}`, - message: message as string, - type: 'success', - position: 'bottom-right' - }); + await invoke<string>("install_mod_caller", { gameInstall: game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => { + showNotification(this.$t('mods.card.install_success', {modName: mod.name}), message); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { this.isBeingInstalled = false; diff --git a/src-vue/src/i18n/lang/de.json b/src-vue/src/i18n/lang/de.json new file mode 100644 index 00000000..1955b717 --- /dev/null +++ b/src-vue/src/i18n/lang/de.json @@ -0,0 +1,164 @@ +{ + "menu": { + "play": "Spielen", + "changelog": "Versionhistorie", + "mods": "Mods", + "settings": "Einstellungen", + "dev": "Dev" + }, + + "generic": { + "yes": "Ja", + "no": "Nein", + "error": "Fehler", + "cancel": "Abbrechen", + "informationShort": "Info", + "downloading": "Herunterladen", + "extracting": "Extrahieren", + "done": "Fertig", + "success": "Erfolg" + }, + + "play": { + "button": { + "northstar_is_running": "Spiel läuft", + "select_game_dir": "Titanfall2 ordner wählen", + "install": "Installieren", + "installing": "Installiert...", + "update": "Aktualisieren", + "updating": "Aktualisiert...", + "ready_to_play": "Spiel starten" + }, + + "unknown_version": "Unbekannte Version", + "see_patch_notes": "Siehe Patch-Notizen", + "players": "Spieler", + "servers": "Server", + "unable_to_load_playercount": "Spielerzahl konnte nicht geladen werden", + "northstar_running": "Northstar läuft:", + "origin_running": "Origin läuft:" + }, + + "mods": { + "local": { + "no_mods": "Keine Mods gefunden.", + "delete_confirm": "Bist du dir sicher, dass du diesen Mod löschen möchtest?", + "delete": "Löschen", + "part_of_ts_mod": "Dieser Northstar Mod ist teil eines Thunderstore Mods", + "success_deleting": "{modName} erfolgreich gelöscht" + }, + + "online": { + "no_match": "Keine passenden Mods gefunden.", + "try_another_search": "Versuche eine andere Suchanfrage!" + }, + + "menu": { + "local": "Lokal", + "online": "Online", + "filter": "Filter", + "search": "Suche", + "sort_mods": "Mods sortieren", + "select_categories": "Kategorien wählen", + + "sort": { + "name_asc": "Nach Name (A to Z)", + "name_desc": "Nach Name (Z to A)", + "date_asc": "Nach Datum (älteste zuerst)", + "date_desc": "Nach Datum (neuste zuerst)", + "most_downloaded": "Am meisten heruntergeladen", + "top_rated": "Am besten bewerted" + } + }, + + "card": { + "button": { + "being_installed": "Installiert...", + "being_updated": "Aktualisiert...", + "installed": "Installiert", + "install": "Installieren", + "outdated": "Aktualisieren" + }, + + "by": "von", + "more_info": "Mehr Info", + "remove": "Mod entfernen", + "remove_dialog_title": "Warnung", + "remove_dialog_text": "Thunderstore Mod entfernen?", + "remove_success": "{modName} entfernt", + "install_success": "{modName} installiert" + } + }, + + "settings": { + "manage_install": "Installation verwalten", + "choose_folder": "Installationsordner wählen", + "nb_ts_mods_per_page": "Anzahl an Thunderstore Mods pro Seite", + "nb_ts_mods_per_page_desc1": "Ändern dieser Einstellung kann die Leistung beim Suchen von Thunderstore Mods beeinflussen.", + "nb_ts_mods_per_page_desc2": "Setze diesen Wert auf 0 um alle Mods auf einer einzelnen Seite anzuzeigen", + "nb_ts_mods_reset": "Standard wiederherstellen", + "language": "Sprache", + "language_select": "Bevorzugte Sprache wählen", + "about": "Über:", + "flightcore_version": "FlightCore Version:", + "testing": "Testen:", + "enable_test_channels": "Testversionen aktivieren", + "dev_mode_enabled_title": "Vorsicht!", + "dev_mod_enabled_text": "Entwicklermodus aktiviert.", + + "repair": { + "title": "Reparieren", + "open_window": "Reparierfenster öffnen", + + "window": { + "title": "FlightCore Reparierfenster", + "warning": "Dieses Fenster enthält verschiedene Funktionen um gängige Probleme mit Northstar und FlightCore zu beheben.", + "disable_all_but_core": "Alle außer notwendige Mods deaktivieren", + "disable_all_but_core_success": "Alle außer notwendige Mods wurden deaktiviert", + "force_reinstall_ns": "Northstar reinstallieren", + "force_delete_temp_dl": "Temporären FlightCore Downloadordner löschen", + "delete_persistent_store": "FlightCore Einstellungen zurücksetzen", + "reinstall_title": "Northstar wird neu installiert", + "reinstall_text": "Bitte warten", + "reinstall_success": "Northstar erfolgreich neu installiert" + } + } + }, + + "notification": { + "game_folder": { + "new": { + "title": "Neuer Spielordner", + "text": "Spielordner erfolgreich aktualisiert." + }, + + "wrong": { + "title": "Falscher Ordner", + "text": "Der gewählte Ordner enthält keine valide Titanfall2 Installation." + }, + + "not_found": { + "title": "Titanfall2 nicht gefunden!", + "text": "Bitte wähle den Installationsordner manuell aus" + } + }, + + "flightcore_outdated": { + "title": "FlightCore veraltet!", + "text": "Bitte aktualisiere FlightCore.\nDu hast die veraltetet Version {oldVersion}.\nNeuste Version ist {newVersion}!" + } + }, + + "channels": { + "release": { + "switch": { + "text": "Releasekanal zu \"{canal}\" gewechselt." + } + }, + + "names": { + "Northstar": "Northstar", + "NorthstarReleaseCandidate": "Northstar Release Candidate" + } + } +} diff --git a/src-vue/src/i18n/lang/en.json b/src-vue/src/i18n/lang/en.json new file mode 100644 index 00000000..407b69d1 --- /dev/null +++ b/src-vue/src/i18n/lang/en.json @@ -0,0 +1,164 @@ +{ + "menu": { + "play": "Play", + "changelog": "Changelog", + "mods": "Mods", + "settings": "Settings", + "dev": "Dev" + }, + + "generic": { + "yes": "Yes", + "no": "No", + "error": "Error", + "cancel": "Cancel", + "informationShort": "Info", + "downloading": "Downloading", + "extracting": "Extracting", + "done": "Done", + "success": "Success" + }, + + "play": { + "button": { + "northstar_is_running": "Game is running", + "select_game_dir": "Select Titanfall2 game folder", + "install": "Install", + "installing": "Installing...", + "update": "Update", + "updating": "Updating...", + "ready_to_play": "Launch game" + }, + + "unknown_version": "Unknown version", + "see_patch_notes": "see patch notes", + "players": "players", + "servers": "servers", + "unable_to_load_playercount": "Unable to load playercount", + "northstar_running": "Northstar is running:", + "origin_running": "Origin is running:" + }, + + "mods": { + "local": { + "no_mods": "No mods were found.", + "delete_confirm": "Are you sure to delete this mod?", + "delete": "Delete", + "part_of_ts_mod": "This Northstar mod is part of a Thunderstore mod", + "success_deleting": "Success deleting {modName}" + }, + + "online": { + "no_match": "No matching mod has been found.", + "try_another_search": "Try another search!" + }, + + "menu": { + "local": "Local", + "online": "Online", + "filter": "Filter", + "search": "Search", + "sort_mods": "Sort mods", + "select_categories": "Select categories", + + "sort": { + "name_asc": "By name (A to Z)", + "name_desc": "By name (Z to A)", + "date_asc": "By date (from oldest)", + "date_desc": "By date (from newest)", + "most_downloaded": "Most downloaded", + "top_rated": "Top rated" + } + }, + + "card": { + "button": { + "being_installed": "Installing...", + "being_updated": "Updating...", + "installed": "Installed", + "install": "Install", + "outdated": "Update" + }, + + "by": "by", + "more_info": "More info", + "remove": "Remove mod", + "remove_dialog_title": "Warning", + "remove_dialog_text": "Delete Thunderstore mod?", + "remove_success": "Removed {modName}", + "install_success": "Installed {modName}" + } + }, + + "settings": { + "manage_install": "Manage installation", + "choose_folder": "Choose installation folder", + "nb_ts_mods_per_page": "Number of Thunderstore mods per page", + "nb_ts_mods_per_page_desc1": "This has an impact on display performances when browsing Thunderstore mods.", + "nb_ts_mods_per_page_desc2": "Set this value to 0 to disable pagination.", + "nb_ts_mods_reset": "Reset to default", + "language": "Language", + "language_select": "Select your favorite language", + "about": "About:", + "flightcore_version": "FlightCore version:", + "testing": "Testing:", + "enable_test_channels": "Enable testing release channels", + "dev_mode_enabled_title": "Watch out!", + "dev_mode_enabled_text": "Developer mode enabled.", + + "repair": { + "title": "Repair", + "open_window": "Open repair window", + + "window": { + "title": "FlightCore repair window", + "warning": "This window contains various functionality to repair common issues with Northstar and FlightCore.", + "disable_all_but_core": "Disable all but core mods", + "disable_all_but_core_success": "Disabled all mods but core", + "force_reinstall_ns": "Force reinstall Northstar", + "force_delete_temp_dl": "Force delete temp download folder", + "delete_persistent_store": "Delete FlightCore persistent store", + "reinstall_title": "Force reinstalling Northstar", + "reinstall_text": "Please wait", + "reinstall_success": "Successfully reinstalled Northstar" + } + } + }, + + "notification": { + "game_folder": { + "new": { + "title": "New game folder", + "text": "Game folder was successfully updated." + }, + + "wrong": { + "title": "Wrong folder", + "text": "Selected folder is not a valid Titanfall2 install." + }, + + "not_found": { + "title": "Titanfall2 not found!", + "text": "Please manually select install location" + } + }, + + "flightcore_outdated": { + "title": "FlightCore outdated!", + "text": "Please update FlightCore.\nRunning outdated version {oldVersion}.\nNewest is {newVersion}!" + } + }, + + "channels": { + "release": { + "switch": { + "text": "Switched release channel to \"{canal}\"." + } + }, + + "names": { + "Northstar": "Northstar", + "NorthstarReleaseCandidate": "Northstar release candidate" + } + } +} diff --git a/src-vue/src/i18n/lang/fr.json b/src-vue/src/i18n/lang/fr.json new file mode 100644 index 00000000..aae11bb7 --- /dev/null +++ b/src-vue/src/i18n/lang/fr.json @@ -0,0 +1,164 @@ +{ + "menu": { + "play": "Jouer", + "changelog": "Notes", + "mods": "Mods", + "settings": "Paramètres", + "dev": "Dev" + }, + + "generic": { + "yes": "Oui", + "no": "Non", + "error": "Erreur", + "cancel": "Annuler", + "informationShort": "Info", + "downloading": "Téléchargement", + "extracting": "Extraction", + "done": "Fait", + "success": "Succès" + }, + + "play": { + "button": { + "northstar_is_running": "En cours d'utilisation", + "select_game_dir": "Sélectionner le dossier du jeu", + "install": "Installer", + "installing": "Installation...", + "update": "Mettre à jour", + "updating": "Mise à jour...", + "ready_to_play": "Jouer" + }, + + "unknown_version": "Version inconnue", + "see_patch_notes": "voir les notes de version", + "players": "joueurs", + "servers": "serveurs", + "unable_to_load_playercount": "Impossible de charger les statistiques", + "northstar_running": "Northstar est en cours d'exécution :", + "origin_running": "Origin est en cours d'exécution :" + }, + + "mods": { + "local": { + "no_mods": "Aucun mod trouvé.", + "delete_confirm": "Êtes-vous certain de vouloir supprimer ce mod ?", + "delete": "Supprimer", + "part_of_ts_mod": "Ce mod Northstar fait partie d'un mod Thunderstore", + "success_deleting": "Succès de la suppression de {modName}" + }, + + "online": { + "no_match": "Aucun mod correspondant n'a été trouvé.", + "try_another_search": "Essayez une autre recherche !" + }, + + "menu": { + "local": "Local", + "online": "En ligne", + "filter": "Filtrer", + "search": "Chercher", + "sort_mods": "Trier les mods", + "select_categories": "Choisir les catégories", + + "sort": { + "name_asc": "Par nom (de A à Z)", + "name_desc": "Par nom (de Z à A)", + "date_asc": "Par date (du plus vieux)", + "date_desc": "Par date (du plus récent)", + "most_downloaded": "Plus téléchargés", + "top_rated": "Mieux notés" + } + }, + + "card": { + "button": { + "being_installed": "Installation...", + "being_updated": "Mise à jour...", + "installed": "Installé", + "install": "Installer", + "outdated": "Mettre à jour" + }, + + "by": "par", + "more_info": "Plus d'informations", + "remove": "Supprimer le mod", + "remove_dialog_title": "Attention !", + "remove_dialog_text": "Voulez-vous vraiment supprimer ce mod Thunderstore ?", + "remove_success": "{modName} supprimé", + "install_success": "{modName} installé" + } + }, + + "settings": { + "manage_install": "Gérer l'installation", + "choose_folder": "Choisir le dossier d'installation du jeu", + "nb_ts_mods_per_page": "Nombre de mods Thunderstore par page", + "nb_ts_mods_per_page_desc1": "Ce paramètre a un impact sur les performances d'affichage des mods Thunderstore.", + "nb_ts_mods_per_page_desc2": "Réglez-le sur 0 pour désactiver la pagination.", + "nb_ts_mods_reset": "Valeur par défaut", + "language": "Langue", + "language_select": "Sélectionnez votre langue", + "about": "À propos", + "flightcore_version": "Version de FlightCore :", + "testing": "Tests :", + "enable_test_channels": "Activer le test de versions de pré-production", + "dev_mode_enabled_title": "Attention !", + "dev_mode_enabled_text": "Mode développeur activé.", + + "repair": { + "title": "Dépannage", + "open_window": "Ouvrir la fenêtre de dépannage", + + "window": { + "title": "Fenêtre de dépannage FlightCore", + "warning": "Cette fenêtre contient plusieurs fonctionnalité de résolution de problèmes courants avec Northstar et FlightCore.", + "disable_all_but_core": "Désactiver tous les mods (sauf ceux de Northstar)", + "disable_all_but_core_success": "Tous les mods sauf ceux de Northstar ont été désactivés", + "force_reinstall_ns": "Forcer la réinstallation de Northstar", + "force_delete_temp_dl": "Supprimer le dossier de téléchargement temporaire", + "delete_persistent_store": "Supprimer l'espace de stockage local de FlightCore", + "reinstall_title": "Forcer la réinstallation de Northstar", + "reinstall_text": "Veuillez patienter", + "reinstall_success": "Northstar réinstallé avec succès" + } + } + }, + + "notification": { + "game_folder": { + "new": { + "title": "Nouveau dossier", + "text": "Le dossier du jeu a bien été mis à jour." + }, + + "wrong": { + "title": "Mauvais dossier", + "text": "Le dossier sélectionné ne contient pas d'installation de Titanfall2." + }, + + "not_found": { + "title": "Titanfall2 non trouvé", + "text": "Veuillez sélectionner manuellement le dossier du jeu." + } + }, + + "flightcore_outdated": { + "title": "Mise à jour disponible !", + "text": "Veuillez mettre à jour FlightCore.\nVersion actuelle : {oldVersion}.\nNouvelle version : {newVersion}." + } + }, + + "channels": { + "release": { + "switch": { + "text": "Le canal de téléchargement a été réglé sur \"{canal}\"." + } + }, + + "names": { + "Northstar": "Northstar", + "NorthstarReleaseCandidate": "Version de pré-release" + } + } +} diff --git a/src-vue/src/main.ts b/src-vue/src/main.ts index 57d41865..94a0196b 100644 --- a/src-vue/src/main.ts +++ b/src-vue/src/main.ts @@ -1,4 +1,5 @@ import { createApp } from 'vue' +import { createI18n } from "vue-i18n"; import App from './App.vue' import ElementPlus from "element-plus"; import * as ElementPlusIconsVue from '@element-plus/icons-vue' @@ -10,10 +11,22 @@ import SettingsView from "./views/SettingsView.vue"; import DeveloperView from "./views/DeveloperView.vue"; import RepairView from "./views/RepairView.vue"; import {createRouter, createWebHashHistory} from "vue-router"; +import en from "./i18n/lang/en.json"; +import fr from "./i18n/lang/fr.json"; +import de from "./i18n/lang/de.json"; const app = createApp(App); +// internationalization +export const i18n = createI18n({ + locale: 'en', + fallbackLocale: 'en', + messages: { + en, fr, de + } +}); +app.use(i18n); // styles import 'element-plus/theme-chalk/index.css'; diff --git a/src-vue/src/plugins/modules/pull_requests.ts b/src-vue/src/plugins/modules/pull_requests.ts index 573856ad..ff9ec322 100644 --- a/src-vue/src/plugins/modules/pull_requests.ts +++ b/src-vue/src/plugins/modules/pull_requests.ts @@ -1,8 +1,8 @@ -import { ElNotification } from "element-plus"; -import { invoke } from "@tauri-apps/api"; +import { invoke, shell } from "@tauri-apps/api"; import { PullsApiResponseElement } from "../../../../src-tauri/bindings/PullsApiResponseElement"; import { PullRequestType } from '../../../../src-tauri/bindings/PullRequestType'; import { store } from "../store"; +import { showErrorNotification, showNotification } from "../../utils/ui"; interface PullRequestStoreState { searchValue: string, @@ -20,11 +20,11 @@ export const pullRequestModule = { await invoke<PullsApiResponseElement[]>("get_pull_requests_wrapper", { installType: pull_request_type }) .then((message) => { switch (pull_request_type) { - case "MODS": + case "Mods": state.pull_requests_mods = message; break; - case "LAUNCHER": + case "Launcher": state.pull_requests_launcher = message; break; @@ -33,42 +33,35 @@ export const pullRequestModule = { } }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, + async downloadLauncherPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) { + await invoke<string>("get_launcher_download_link", { pullRequest: pull_request }) + .then((url) => { + // Open URL in default HTTPS handler (i.e. default browser) + shell.open(url); + }) + .catch((error) => { + showErrorNotification(error); + }); + }, + async downloadModsPR(state: PullRequestStoreState, pull_request: PullsApiResponseElement) { + let url = `https://github.com/${pull_request.head.repo.full_name}/archive/refs/heads/${pull_request.head.ref}.zip` + shell.open(url); + }, 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' - }); + const notification = showNotification(`Installing launcher PR ${pull_request.number}`, 'Please wait', 'info', 0); 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' - }); + showNotification(`Done`, `Installed ${pull_request.number}: "${pull_request.title}"`); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { // Clear old notification @@ -77,32 +70,20 @@ export const pullRequestModule = { }, 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' - }); + const notification = showNotification(`Installing mods PR ${pull_request.number}`, 'Please wait', 'info', 0); 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', - duration: 7_000, // in ms - position: 'bottom-right' - }); + showNotification( + `Done`, + `Installed ${pull_request.number}: "${pull_request.title}"\nMake sure to launch via batch file or by specifying correct profile!`, + 'success', + 7000 + ); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { // Clear old notification diff --git a/src-vue/src/plugins/store.ts b/src-vue/src/plugins/store.ts index 8671d12b..00b8f35a 100644 --- a/src-vue/src/plugins/store.ts +++ b/src-vue/src/plugins/store.ts @@ -6,7 +6,7 @@ import { invoke } from "@tauri-apps/api"; import { GameInstall } from "../utils/GameInstall"; import { ReleaseCanal } from "../utils/ReleaseCanal"; import { FlightCoreVersion } from "../../../src-tauri/bindings/FlightCoreVersion"; -import { ElNotification, NotificationHandle } from 'element-plus'; +import { NotificationHandle } from 'element-plus'; import { NorthstarState } from '../utils/NorthstarState'; import { appDir } from '@tauri-apps/api/path'; import { open } from '@tauri-apps/api/dialog'; @@ -16,8 +16,9 @@ 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 { i18n } from '../main'; import { pullRequestModule } from './modules/pull_requests'; -import { PullsApiResponseElement } from "../../../src-tauri/bindings/PullsApiResponseElement"; +import { showErrorNotification, showNotification } from '../utils/ui'; const persistentStore = new Store('flight-core-settings.json'); @@ -51,6 +52,7 @@ export interface FlightCoreStore { let notification_handle: NotificationHandle; + export const store = createStore<FlightCoreStore>({ modules: { search: searchModule, @@ -87,13 +89,23 @@ export const store = createStore<FlightCoreStore>({ checkNorthstarUpdates(state) { _get_northstar_version_number(state); }, - toggleDeveloperMode(state) { + async toggleDebugMode(_state) { + let menu_bar_handle = document.querySelector('#fc_menu-bar'); + if (menu_bar_handle !== null) { + menu_bar_handle.classList.toggle('developer_build'); + } + }, + async toggleDeveloperMode(state) { state.developer_mode = !state.developer_mode; // Reset tab when closing dev mode. if (!state.developer_mode) { store.commit('updateCurrentTab', Tabs.PLAY); } + + // Save dev mode state in persistent store + await persistentStore.set('dev_mode', state.developer_mode); + await persistentStore.save(); }, initialize(state) { _initializeApp(state); @@ -122,12 +134,10 @@ export const store = createStore<FlightCoreStore>({ let is_valid_titanfall2_install = await invoke("verify_install_location", { gamePath: selected }) as boolean; if (is_valid_titanfall2_install) { state.game_path = selected; - ElNotification({ - title: 'New game folder', - message: "Game folder was successfully updated.", - type: 'success', - position: 'bottom-right' - }); + showNotification( + i18n.global.tc('notification.game_folder.new.title'), + i18n.global.tc('notification.game_folder.new.text') + ); try { notification_handle.close(); } @@ -143,18 +153,17 @@ export const store = createStore<FlightCoreStore>({ // Save change in persistent store await persistentStore.set('game-install', { value: game_install }); + await persistentStore.save(); // explicit save to disk // Check for Northstar install 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' - }); + showErrorNotification( + i18n.global.tc('notification.game_folder.wrong.text'), + i18n.global.tc('notification.game_folder.wrong.title') + ); } } }, @@ -222,12 +231,7 @@ export const store = createStore<FlightCoreStore>({ }) .catch((error) => { console.error(error); - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); break; @@ -236,6 +240,23 @@ export const store = createStore<FlightCoreStore>({ break; } }, + async launchGameSteam(state: any, no_checks = false) { + let game_install = { + game_path: state.game_path, + install_type: state.install_type + } as GameInstall; + + await invoke("launch_northstar_steam_caller", { gameInstall: game_install, bypassChecks: no_checks }) + .then((message) => { + showNotification('Success'); + }) + .catch((error) => { + console.error(error); + showErrorNotification(error); + }); + + return; + }, async fetchReleaseNotes(state: FlightCoreStore) { if (state.releaseNotes.length !== 0) return; state.releaseNotes = await invoke("get_northstar_release_notes"); @@ -291,12 +312,7 @@ export const store = createStore<FlightCoreStore>({ }) .catch((error) => { console.error(error); - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async toggleReleaseCandidate(state: FlightCoreStore) { @@ -307,17 +323,16 @@ export const store = createStore<FlightCoreStore>({ // Save change in persistent store await persistentStore.set('northstar-release-canal', { value: state.northstar_release_canal }); + await persistentStore.save(); // explicit save to disk // Update current state so that update check etc can be performed store.commit("checkNorthstarUpdates"); // Display notification to highlight change - ElNotification({ - title: `${state.northstar_release_canal}`, - message: `Switched release channel to: "${state.northstar_release_canal}"`, - type: 'success', - position: 'bottom-right' - }); + showNotification( + i18n.global.tc(`channels.names.${state.northstar_release_canal}`), + i18n.global.tc('channels.release.switch.text', {canal: state.northstar_release_canal}), + ); } } }); @@ -327,18 +342,18 @@ export const store = createStore<FlightCoreStore>({ * It invokes all Rust methods that are needed to initialize UI. */ async function _initializeApp(state: any) { - // Enable dev mode directly if application is in debug mode - if (await invoke("is_debug_mode")) { - state.developer_mode = true; - - // Make menubar striped if debug build - let menu_bar_handle = document.querySelector('#fc_menu-bar'); - console.log(menu_bar_handle); - if (menu_bar_handle !== null) { - menu_bar_handle.classList.toggle('developer_build'); - } - } else { - // Disable context menu in release build. + // Display dev view if dev mode was previously enabled. + const devModeEnabled: boolean = await persistentStore.get('dev_mode') ?? false; + const debugModeEnabled: boolean = await invoke("is_debug_mode"); + if (devModeEnabled) { + store.commit('toggleDeveloperMode'); + } + if (debugModeEnabled) { + store.commit('toggleDebugMode'); + } + + // Disable context menu in release build. + if (!debugModeEnabled) { document.addEventListener('contextmenu', event => event.preventDefault()); } @@ -391,13 +406,12 @@ async function _initializeApp(state: any) { .catch((err) => { // Gamepath not found or other error console.error(err); - notification_handle = ElNotification({ - title: 'Titanfall2 not found!', - message: "Please manually select install location", - type: 'error', - position: 'bottom-right', - duration: 0 // Duration `0` means the notification will not auto-vanish - }); + notification_handle = showNotification( + i18n.global.tc('notification.game_folder.not_found.title'), + i18n.global.tc('notification.game_folder.not_found.text'), + 'error', + 0 // Duration `0` means the notification will not auto-vanish + ); }); } @@ -407,6 +421,7 @@ async function _initializeApp(state: any) { // Save change in persistent store await persistentStore.set('game-install', { value: typedResult }); + await persistentStore.save(); // explicit save to disk // Update UI store state.game_path = typedResult.game_path; @@ -433,13 +448,12 @@ async function _checkForFlightCoreUpdates(state: FlightCoreStore) { if (flightcore_is_outdated) { let newest_flightcore_version = await invoke("get_newest_flightcore_version") as FlightCoreVersion; - ElNotification({ - title: 'FlightCore outdated!', - message: `Please update FlightCore.\nRunning outdated version ${state.flightcore_version}.\nNewest is ${newest_flightcore_version.tag_name}!`, - type: 'warning', - position: 'bottom-right', - duration: 0 // Duration `0` means the notification will not auto-vanish - }); + showNotification( + i18n.global.tc('notification.flightcore_outdated.title'), + i18n.global.tc('notification.flightcore_outdated.text', {oldVersion: state.flightcore_version, newVersion: newest_flightcore_version.tag_name}), + 'warning', + 0 // Duration `0` means the notification will not auto-vanish + ); } } @@ -455,6 +469,11 @@ function _initializeListeners(state: any) { listen("northstar-running-ping", function (evt: TauriEvent<any>) { state.northstar_is_running = evt.payload as boolean; }); + + listen("northstar-statistics", function (evt: TauriEvent<{Ok: [number, number]}>) { + state.player_count = evt.payload.Ok[0]; + state.server_count = evt.payload.Ok[1]; + }); } /** diff --git a/src-vue/src/style.css b/src-vue/src/style.css index 4401ac96..0366b0ed 100644 --- a/src-vue/src/style.css +++ b/src-vue/src/style.css @@ -41,3 +41,8 @@ body { height: 100%; color: white; } + +.noModMessage { + color: white; + margin: 30px 15px; +} diff --git a/src-vue/src/utils/SortOptions.d.ts b/src-vue/src/utils/SortOptions.d.ts index 6bdd0a4a..b6f180d2 100644 --- a/src-vue/src/utils/SortOptions.d.ts +++ b/src-vue/src/utils/SortOptions.d.ts @@ -1,8 +1,8 @@ export enum SortOptions { - NAME_ASC = 'By name (A to Z)', - NAME_DESC = 'By name (Z to A)', - DATE_ASC = 'By date (from oldest)', - DATE_DESC = 'By date (from newest)', - MOST_DOWNLOADED = "Most downloaded", - TOP_RATED = "Top rated" + NAME_ASC = 'name_asc', + NAME_DESC = 'name_desc', + DATE_ASC = 'date_asc', + DATE_DESC = 'date_desc', + MOST_DOWNLOADED = 'most_downloaded', + TOP_RATED = 'top_rated' } diff --git a/src-vue/src/utils/ui.ts b/src-vue/src/utils/ui.ts new file mode 100644 index 00000000..b84d7666 --- /dev/null +++ b/src-vue/src/utils/ui.ts @@ -0,0 +1,29 @@ +import { ElNotification, NotificationHandle } from "element-plus"; +import { i18n } from "../main"; + +/** + * Displays content to the user in the form of a notification appearing on screen bottom right. + **/ +function showNotification( + title: string, + message: string = '', + type: 'success' | 'warning' | 'error' | 'info' = 'success', + duration: number = 4500 +): NotificationHandle { + return ElNotification({ + title, message, type, duration, + position: 'bottom-right', + }); +} + +/** + * Helper method displaying an error message to the user. + **/ +function showErrorNotification( + error: string, + title: string = i18n.global.tc('generic.error') +): NotificationHandle { + return showNotification(title, error, 'error'); +} + +export {showNotification, showErrorNotification}; diff --git a/src-vue/src/views/ChangelogView.vue b/src-vue/src/views/ChangelogView.vue index 9335220e..e68beded 100644 --- a/src-vue/src/views/ChangelogView.vue +++ b/src-vue/src/views/ChangelogView.vue @@ -46,7 +46,7 @@ export default defineComponent({ 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>`); + content = content.replaceAll(/\[(\S*)\#(\S+)\]\(([^)]+)\)/g, `<a target="_blank" href="$3">$1#$2</a>`); return marked.parse(content, {breaks: true}); }, diff --git a/src-vue/src/views/DeveloperView.vue b/src-vue/src/views/DeveloperView.vue index 52117b8d..7e11bd11 100644 --- a/src-vue/src/views/DeveloperView.vue +++ b/src-vue/src/views/DeveloperView.vue @@ -27,12 +27,8 @@ Launch Northstar (bypass all checks) </el-button> - <h3>Mod install:</h3> - - <el-input v-model="mod_to_install_field_string" placeholder="Please input Thunderstore dependency string (example: AuthorName-ModName-1.2.3)" clearable /> - - <el-button type="primary" @click="installMod"> - Install mod + <el-button type="primary" @click="launchGameViaSteam"> + Launch Northstar via Steam </el-button> <h3>Repair:</h3> @@ -44,6 +40,48 @@ <h3>Testing</h3> <pull-requests-selector /> + + <h3>Mod install:</h3> + + <el-input v-model="mod_to_install_field_string" placeholder="Please input Thunderstore dependency string (example: AuthorName-ModName-1.2.3)" clearable /> + + <el-button type="primary" @click="installMod"> + Install mod + </el-button> + + <h3>Release management</h3> + + <el-button type="primary" @click="getTags"> + Get tags + </el-button> + + <el-select v-model="firstTag" class="m-2" placeholder="First tag"> + <el-option + v-for="item in ns_release_tags" + :key="item.value" + :label="item.label" + :value="item" + /> + </el-select> + <el-select v-model="secondTag" class="m-2" placeholder="Second tag"> + <el-option + v-for="item in ns_release_tags" + :key="item.value" + :label="item.label" + :value="item" + /> + </el-select> + + <el-button type="primary" @click="compareTags"> + Compare Tags + </el-button> + + <el-input + v-model="release_notes_text" + type="textarea" + :rows="5" + placeholder="Output" + /> </el-scrollbar> </div> </template> @@ -51,9 +89,10 @@ <script lang="ts"> import { defineComponent } from "vue"; import { invoke } from "@tauri-apps/api"; -import { ElNotification } from "element-plus"; import { GameInstall } from "../utils/GameInstall"; +import { TagWrapper } from "../../../src-tauri/bindings/TagWrapper"; import PullRequestsSelector from "../components/PullRequestsSelector.vue"; +import { showErrorNotification, showNotification } from "../utils/ui"; export default defineComponent({ name: "DeveloperView", @@ -63,44 +102,54 @@ export default defineComponent({ data() { return { mod_to_install_field_string : "", + release_notes_text : "", + first_tag: { label: '', value: {name: ''} }, + second_tag: { label: '', value: {name: ''} }, + ns_release_tags: [] as TagWrapper[], } }, + computed: { + firstTag: { + get(): TagWrapper { + return this.first_tag; + }, + set(value: TagWrapper) { + this.first_tag = value; + } + }, + secondTag: { + get(): TagWrapper { + return this.second_tag; + }, + set(value: TagWrapper) { + this.second_tag = value; + } + }, + }, 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' - }); + showErrorNotification("Never should have been able to get here!"); }, async checkLinuxCompatibility() { await invoke("linux_checks") .then(() => { - ElNotification({ - title: 'Linux compatible', - message: 'All checks passed', - type: 'success', - position: 'bottom-right' - }); + showNotification('Linux compatible', 'All checks passed'); }) .catch((error) => { - ElNotification({ - title: 'Not linux compatible', - message: error, - type: 'error', - position: 'bottom-right' - }); + showNotification('Not Linux compatible', error, 'error'); console.error(error); }); }, async launchGameWithoutChecks() { this.$store.commit('launchGame', true); }, + async launchGameViaSteam() { + this.$store.commit('launchGameSteam', true); + }, async getInstalledMods() { let game_install = { game_path: this.$store.state.game_path, @@ -112,20 +161,10 @@ export default defineComponent({ console.log(message); // Just a visual indicator that it worked - ElNotification({ - title: 'Success', - message: "Success", - type: 'success', - position: 'bottom-right' - }); + showNotification('Success'); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async installMod() { @@ -134,22 +173,32 @@ export default defineComponent({ install_type: this.$store.state.install_type } as GameInstall; let mod_to_install = this.mod_to_install_field_string; - await invoke("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install }).then((message) => { + await invoke<string>("install_mod_caller", { gameInstall: game_install, thunderstoreModString: mod_to_install }).then((message) => { // Show user notification if mod install completed. - ElNotification({ - title: `Installed ${mod_to_install}`, - message: message as string, - type: 'success', - position: 'bottom-right' - }); + showNotification(`Installed ${mod_to_install}`, message); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); + }); + }, + async getTags() { + await invoke<TagWrapper[]>("get_list_of_tags") + .then((message) => { + this.ns_release_tags = message; + showNotification("Done", "Fetched tags"); + }) + .catch((error) => { + showErrorNotification(error); + }); + }, + async compareTags() { + await invoke<string>("compare_tags", {firstTag: this.firstTag.value, secondTag: this.secondTag.value}) + .then((message) => { + this.release_notes_text = message; + showNotification("Done", "Generated release notes"); + }) + .catch((error) => { + showErrorNotification(error); }); }, } diff --git a/src-vue/src/views/PlayView.vue b/src-vue/src/views/PlayView.vue index beca6724..76f4f328 100644 --- a/src-vue/src/views/PlayView.vue +++ b/src-vue/src/views/PlayView.vue @@ -1,12 +1,13 @@ <script lang="ts"> -import { ElNotification } from 'element-plus'; import { Tabs } from "../utils/Tabs"; import PlayButton from '../components/PlayButton.vue'; import { defineComponent } from "vue"; +import InstallProgressBar from "../components/InstallProgressBar.vue"; export default defineComponent({ components: { - PlayButton + PlayButton, + InstallProgressBar }, computed: { northstarIsRunning(): boolean { @@ -34,37 +35,40 @@ export default defineComponent({ <div class="fc_launch__container"> <div class="fc_title">Northstar</div> <div class="fc_northstar__version__container"> - {{ northstarVersion === '' ? 'Unknown version' : `v${northstarVersion}` }} + {{ northstarVersion === '' ? $t('play.unknown_version') : `v${northstarVersion}` }} <div v-if="northstarVersion !== ''" class="fc_changelog__link" @click="showChangelogPage"> - (see patch notes) + ({{ $t('play.see_patch_notes') }}) </div> <div v-if="playerCount >= 0" class="fc-stats__container"> - {{ playerCount }} players, - {{ serverCount }} servers + {{ playerCount }} {{ $t('play.players') }}, + {{ serverCount }} {{ $t('play.servers') }} </div> <div v-else="playerCount >= 0" class="fc-stats__container"> - Unable to load playercount + {{ $t('play.unable_to_load_playercount') }} </div> </div> - <div> + + <!-- Align play button and services state container --> + <div style="display: flex"> <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">{{ $t('play.northstar_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">{{ $t('play.origin_running') }}</div> <div class="fc_version__line fc_version__line__boolean">{{ $store.state.origin_is_running }}</div> </div> </div> </div> + <InstallProgressBar /> </div> </template> <style scoped> .fc_launch__container { - margin: 50px; + margin: 50px 50px 30px 50px; position: fixed; bottom: 0; } @@ -102,13 +106,9 @@ export default defineComponent({ border-color: var(--el-color-primary); } - #fc_services__status { - display: inline-block; - position: fixed; - padding: 10px 20px; color: #e8edef; - bottom: 43px; + align-self: end; } .fc_version__line { diff --git a/src-vue/src/views/RepairView.vue b/src-vue/src/views/RepairView.vue index e7cd479a..c11518d0 100644 --- a/src-vue/src/views/RepairView.vue +++ b/src-vue/src/views/RepairView.vue @@ -1,30 +1,30 @@ <template> <div class="fc-container"> <el-scrollbar> - <el-alert title="Info" type="info" :closable="false" show-icon> - This window contains various functionality to repair common issues with Northstar and FlightCore. + <el-alert :title="$t('generic.informationShort')" type="info" :closable="false" show-icon> + {{ $t('settings.repair.window.warning') }} </el-alert> - <h1>Repair</h1> + <h1>{{ $t('settings.repair.title') }}</h1> <h2>Northstar</h2> <el-button type="primary" @click="disableAllModsButCore"> - Disable all but core mods + {{ $t('settings.repair.window.disable_all_but_core') }} </el-button> <el-button type="primary" @click="forceInstallNorthstar"> - Force reinstall Northstar + {{ $t('settings.repair.window.force_reinstall_ns') }} </el-button> <h2>FlightCore</h2> <el-button type="primary" @click="cleanUpDownloadFolder"> - Force delete temp download folder + {{ $t('settings.repair.window.force_delete_temp_dl') }} </el-button> <el-button type="primary" @click="clearFlightCorePersistentStore"> - Delete FlightCore persistent store + {{ $t('settings.repair.window.delete_persistent_store') }} </el-button> </el-scrollbar> </div> @@ -32,15 +32,22 @@ <script lang="ts"> import { defineComponent } from "vue"; -import { ElNotification } from "element-plus"; import { GameInstall } from "../utils/GameInstall"; +import { InstallProgress } from "../../../src-tauri/bindings/InstallProgress"; import { invoke } from "@tauri-apps/api"; import { ReleaseCanal } from "../utils/ReleaseCanal"; import { Store } from 'tauri-plugin-store-api'; +import { showErrorNotification, showNotification } from "../utils/ui"; +import { appWindow } from "@tauri-apps/api/window"; const persistentStore = new Store('flight-core-settings.json'); export default defineComponent({ name: "RepairView", + computed: { + lang(): string { + return this.$root!.$i18n.locale; + } + }, methods: { async disableAllModsButCore() { let game_install = { @@ -49,20 +56,10 @@ export default defineComponent({ } as GameInstall; await invoke("disable_all_but_core", { gameInstall: game_install }) .then((message) => { - ElNotification({ - title: 'Success', - message: "Disabled all mods but core", - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('generic.success'), this.$t('settings.repair.window.disable_all_but_core_success')); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async forceInstallNorthstar() { @@ -72,33 +69,32 @@ export default defineComponent({ } as GameInstall; // Send notification telling the user to wait for the process to finish - const notification = ElNotification({ - title: 'Force reinstalling Northstar', - message: 'Please wait', - duration: 0, - type: 'info', - position: 'bottom-right' - }); + const notification = showNotification( + this.$t('settings.repair.window.reinstall_title'), + this.$t('settings.repair.window.reinstall_text'), + 'info', + 0 + ); let install_northstar_result = invoke("install_northstar_caller", { gamePath: game_install.game_path, northstarPackageName: ReleaseCanal.RELEASE }); + + appWindow.listen<InstallProgress>( + 'northstar-install-download-progress', + ({ event, payload }) => { + let typed_payload = payload; + console.log("current_downloaded:", typed_payload.current_downloaded); + console.log("total_size: ", typed_payload.total_size); + console.log("state: ", typed_payload.state); + } + ); await install_northstar_result .then((message) => { // Send notification - ElNotification({ - title: `Done`, - message: `Successfully reinstalled Northstar`, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('generic.done'), this.$t('settings.repair.window.reinstall_success')); this.$store.commit('checkNorthstarUpdates'); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); console.error(error); }) .finally(() => { @@ -113,20 +109,10 @@ export default defineComponent({ } as GameInstall; await invoke("clean_up_download_folder_caller", { gameInstall: game_install, force: true }).then((message) => { // Show user notification if task completed. - ElNotification({ - title: `Done`, - message: `Done`, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('generic.done'), this.$t('generic.done')); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, async clearFlightCorePersistentStore() { @@ -135,6 +121,13 @@ export default defineComponent({ // ...and save await persistentStore.save(); }, + }, + watch: { + // Lang value is propagated to repair view after it's mounted, so we need to watch + // its value, and update window title accordingly. + lang(newv: string) { + appWindow.setTitle( this.$t('settings.repair.window.title') ); + } } }); </script> diff --git a/src-vue/src/views/SettingsView.vue b/src-vue/src/views/SettingsView.vue index c93d69ff..772a4c5c 100644 --- a/src-vue/src/views/SettingsView.vue +++ b/src-vue/src/views/SettingsView.vue @@ -3,53 +3,68 @@ <el-scrollbar> <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> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.manage_install') }}</h3> + <el-input + v-model="$store.state.game_path" + :placeholder="$t('settings.choose_folder')" + @click="updateGamePath" + > + <template #prepend> + <el-button icon="Folder" @click="updateGamePath"/> + </template> + </el-input> + </div> <!-- Thunderstore mods per page configuration --> <div class="fc_parameter__panel"> - <h3>Number of Thunderstore mods per page</h3> + <h3>{{ $t('settings.nb_ts_mods_per_page') }}</h3> <h6> - This has an impact on display performances when browsing Thunderstore mods.<br> - Set this value to 0 to disable pagination. + {{ $t('settings.nb_ts_mods_per_page_desc1') }}<br> + {{ $t('settings.nb_ts_mods_per_page_desc2') }} </h6> - <el-input - v-model="modsPerPage" + <el-input + v-model="modsPerPage" type="number" > <template #append> - <el-button @click="modsPerPage = 20">Reset to default</el-button> + <el-button @click="modsPerPage = 20"> + {{ $t('settings.nb_ts_mods_reset') }} + </el-button> </template> </el-input> </div> - <h3>Repair</h3> - <el-button type="primary" @click="openRepairWindow"> - Open Repair window - </el-button> + <!-- Interface localization --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.language') }}</h3> + <language-selector/> + </div> + + <!-- Repair window --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.repair.title') }}</h3> + <el-button type="primary" @click="openRepairWindow"> + {{ $t('settings.repair.open_window') }} + </el-button> + </div> + + <!-- About section --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.about') }}</h3> + <div class="fc_northstar__version" @click="activateDeveloperMode"> + {{ $t('settings.flightcore_version') }} {{ flightcoreVersion === '' ? 'Unknown version' : `${flightcoreVersion}` }} + </div> + </div> - <h3>About:</h3> - <div class="fc_northstar__version" @click="activateDeveloperMode"> - FlightCore Version: {{ flightcoreVersion === '' ? 'Unknown version' : `${flightcoreVersion}` }} + <!-- Testing section --> + <div class="fc_parameter__panel"> + <h3>{{ $t('settings.testing') }}</h3> + <span> + {{ $t('settings.enable_test_channels') }} + <el-switch v-model="enableReleasesSwitch"></el-switch> + </span> </div> - <br /> - <br /> - 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) - - <h3>Testing:</h3> - <span> - Enable testing release channels - <el-switch v-model="enableReleasesSwitch"></el-switch> - </span> </div> </el-scrollbar> </div> @@ -57,14 +72,18 @@ <script lang="ts"> import { defineComponent } from "vue"; -import { ElNotification } from 'element-plus'; import { invoke } from "@tauri-apps/api"; import { ReleaseCanal } from "../utils/ReleaseCanal"; import { Store } from 'tauri-plugin-store-api'; +import { showErrorNotification, showNotification } from "../utils/ui"; +import LanguageSelector from "../components/LanguageSelector.vue"; const persistentStore = new Store('flight-core-settings.json'); export default defineComponent({ name: "SettingsView", + components: { + LanguageSelector + }, data() { return { developerModeClicks: 0 @@ -78,9 +97,10 @@ export default defineComponent({ get(): boolean { return this.$store.state.enableReleasesSwitch; }, - set(value: boolean): void { + async set(value: boolean): Promise<void> { this.$store.state.enableReleasesSwitch = value; persistentStore.set('northstar-releases-switching', { value }); + await persistentStore.save(); // explicit save to disk // When disabling switch, we switch release canal to stable release, to avoid users being // stuck with release candidate after disabling release switching. @@ -93,23 +113,23 @@ export default defineComponent({ get(): number { return this.$store.state.mods_per_page; }, - set(value: number) { + async set(value: number) { this.$store.state.mods_per_page = value; persistentStore.set('thunderstore-mods-per-page', { value }); + await persistentStore.save(); // explicit save to disk } } }, 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' - }); + if (this.developerModeClicks >= 6 && !this.$store.state.developer_mode) { + this.$store.commit('toggleDeveloperMode'); + showNotification( + this.$t('settings.dev_mode_enabled_title'), + this.$t('settings.dev_mode_enabled_text'), + 'info' + ); this.developerModeClicks = 0; } }, @@ -120,12 +140,7 @@ export default defineComponent({ await invoke("open_repair_window") .then((message) => { }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }); }, }, @@ -154,7 +169,7 @@ h3:first-of-type { font-weight: unset; } -.el-input { +.el-input, .el-select { width: 50%; } @@ -165,7 +180,7 @@ h3:first-of-type { /* Parameter panel styles */ .fc_parameter__panel { - margin: 30px 0; + margin-bottom: 30px; } .fc_parameter__panel h3 { diff --git a/src-vue/src/views/mods/LocalModsView.vue b/src-vue/src/views/mods/LocalModsView.vue index ed801b7a..3fb90bdc 100644 --- a/src-vue/src/views/mods/LocalModsView.vue +++ b/src-vue/src/views/mods/LocalModsView.vue @@ -1,38 +1,44 @@ <template> - <el-scrollbar> - <div> - <p v-if="mods.length === 0">No mods were found.</p> - <el-card v-else shadow="hover" v-for="mod in mods" v-bind:key="mod.name"> - <el-switch style="--el-switch-on-color: #13ce66; --el-switch-off-color: #8957e5" v-model="mod.enabled" - :before-change="() => updateWhichModsEnabled(mod)" :loading="global_load_indicator" /> - <el-popconfirm - title="Are you sure to delete this mod?" - @confirm="deleteMod(mod)" - > - <template #reference> - <el-button type="danger">Delete</el-button> - </template> - </el-popconfirm> - {{ mod.name }} - <span v-if="mod.version != null">(v{{ mod.version }})</span> - <img - v-if="mod.thunderstore_mod_string != null" - title="This Northstar mod is part of a Thunderstore mod" - src="/src/assets/thunderstore-icon.png" - class="image" - height="16" - /> - </el-card> - </div> + <!-- Message displayed if no mod matched searched words --> + <div v-if="mods.length === 0" class="noModMessage"> + {{ $t('mods.local.no_mods') }} + </div> + + <el-scrollbar v-else> + <el-card shadow="hover" v-for="mod in mods" v-bind:key="mod.name"> + <el-switch style="--el-switch-on-color: #13ce66; --el-switch-off-color: #8957e5" v-model="mod.enabled" + :before-change="() => updateWhichModsEnabled(mod)" :loading="global_load_indicator" /> + <el-popconfirm + :title="$t('mods.local.delete_confirm')" + :confirm-button-text="$t('generic.yes')" + :cancel-button-text="$t('generic.no')" + @confirm="deleteMod(mod)" + > + <template #reference> + <el-button type="danger"> + {{ $t('mods.local.delete') }} + </el-button> + </template> + </el-popconfirm> + {{ mod.name }} + <span v-if="mod.version != null">(v{{ mod.version }})</span> + <img + v-if="mod.thunderstore_mod_string != null" + :title="$t('mods.local.part_of_ts_mod')" + src="/src/assets/thunderstore-icon.png" + class="image" + height="16" + /> + </el-card> </el-scrollbar> </template> <script lang="ts"> import { invoke } from '@tauri-apps/api'; -import { ElNotification } from 'element-plus'; import { defineComponent } from 'vue'; import { GameInstall } from '../../utils/GameInstall'; import { NorthstarMod } from "../../../../src-tauri/bindings/NorthstarMod"; +import { showErrorNotification, showNotification } from '../../utils/ui'; export default defineComponent({ name: 'LocalModsView', @@ -79,12 +85,7 @@ export default defineComponent({ }) } catch (error) { - ElNotification({ - title: 'Error', - message: `${error}`, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(`${error}`); this.global_load_indicator = false; return false; } @@ -100,19 +101,10 @@ export default defineComponent({ await invoke("delete_northstar_mod", { gameInstall: game_install, nsmodName: mod.name }) .then((message) => { // Just a visual indicator that it worked - ElNotification({ - title: `Success deleting ${mod.name}`, - type: 'success', - position: 'bottom-right' - }); + showNotification(this.$t('mods.local.success_deleting', {modName: mod.name})); }) .catch((error) => { - ElNotification({ - title: 'Error', - message: error, - type: 'error', - position: 'bottom-right' - }); + showErrorNotification(error); }) .finally(() => { this.$store.commit('loadInstalledMods'); diff --git a/src-vue/src/views/mods/ThunderstoreModsView.vue b/src-vue/src/views/mods/ThunderstoreModsView.vue index 19809f3e..5a7270df 100644 --- a/src-vue/src/views/mods/ThunderstoreModsView.vue +++ b/src-vue/src/views/mods/ThunderstoreModsView.vue @@ -3,11 +3,17 @@ <div v-if="mods.length === 0" class="fc__changelog__container">
<el-progress :show-text="false" :percentage="50" :indeterminate="true" />
</div>
+
+ <!-- Message displayed if no mod matched searched words -->
+ <div v-else-if="filteredMods.length === 0" class="noModMessage">
+ {{ $t('mods.online.no_match') }}<br/>
+ {{ $t('mods.online.try_another_search') }}
+ </div>
+
<el-scrollbar v-else class="container" ref="scrollbar">
<div class="card-container">
- <div class="pagination_container">
+ <div class="pagination_container" v-if="shouldDisplayPagination">
<el-pagination
- v-if="shouldDisplayPagination"
:currentPage="currentPageIndex + 1"
layout="prev, pager, next"
:page-size="modsPerPage"
@@ -16,12 +22,6 @@ />
</div>
- <!-- Message displayed if no mod matched searched words -->
- <div v-if="filteredMods.length === 0" class="modMessage">
- No matching mod has been found.<br/>
- Try another search!
- </div>
-
<!-- Mod cards -->
<thunderstore-mod-card v-for="mod of currentPageMods" v-bind:key="mod.name" :mod="mod" />
</div>
@@ -199,11 +199,6 @@ export default defineComponent({ margin: 0 0 0 10px !important;
}
-.modMessage {
- color: white;
- margin: 20px 5px;
-}
-
.card-container {
margin: 0 auto;
}
|