diff options
author | GeckoEidechse <gecko.eidechse+git@pm.me> | 2024-12-22 23:55:52 +0100 |
---|---|---|
committer | GeckoEidechse <gecko.eidechse+git@pm.me> | 2024-12-22 23:55:52 +0100 |
commit | f1dee718da95836ffa5c0985c9e8f5643e0f3f6f (patch) | |
tree | 24967a28bcae1fc1e5b08da9f58bcc678ed52937 /src-vue/src/components | |
parent | cc5ae684221d3165479d7a68556a2bb6fa81cf3a (diff) | |
download | FlightCore-f1dee718da95836ffa5c0985c9e8f5643e0f3f6f.tar.gz FlightCore-f1dee718da95836ffa5c0985c9e8f5643e0f3f6f.zip |
dev: Replace with sample Tauri 2.0 project
as a first step to convert FlightCore to Tauri 2.0
Diffstat (limited to 'src-vue/src/components')
-rw-r--r-- | src-vue/src/components/InstallProgressBar.vue | 102 | ||||
-rw-r--r-- | src-vue/src/components/LanguageSelector.vue | 75 | ||||
-rw-r--r-- | src-vue/src/components/LocalModCard.vue | 120 | ||||
-rw-r--r-- | src-vue/src/components/ModsMenu.vue | 141 | ||||
-rw-r--r-- | src-vue/src/components/NotificationButton.vue | 81 | ||||
-rw-r--r-- | src-vue/src/components/PlayButton.vue | 157 | ||||
-rw-r--r-- | src-vue/src/components/PullRequestsSelector.vue | 181 | ||||
-rw-r--r-- | src-vue/src/components/ThunderstoreModCard.vue | 319 |
8 files changed, 0 insertions, 1176 deletions
diff --git a/src-vue/src/components/InstallProgressBar.vue b/src-vue/src/components/InstallProgressBar.vue deleted file mode 100644 index 21901ac0..00000000 --- a/src-vue/src/components/InstallProgressBar.vue +++ /dev/null @@ -1,102 +0,0 @@ -<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 deleted file mode 100644 index 0c47e674..00000000 --- a/src-vue/src/components/LanguageSelector.vue +++ /dev/null @@ -1,75 +0,0 @@ -<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' - }, - { - value: 'es', - label: 'Español' - }, - { - value: 'pl', - label: 'polski' - }, - { - value: 'ru', - label: 'русский' - }, - { - value: 'it', - label: 'Italiano' - }, - { - value: 'da', - label: 'Dansk' - }, - { - value: 'zh_Hans', - label: '简体中文' - }, - ] - }), - 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/LocalModCard.vue b/src-vue/src/components/LocalModCard.vue deleted file mode 100644 index 697cced1..00000000 --- a/src-vue/src/components/LocalModCard.vue +++ /dev/null @@ -1,120 +0,0 @@ -<template> - <el-card shadow="hover"> - <div class="name"> - {{ 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') + '\n' + mod.thunderstore_mod_string" - src="/src/assets/thunderstore-icon.png" - class="image" - height="16" - /> - </div> - <div> - <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" - class="switch" - /> - <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> - </div> - </el-card> -</template> - -<script lang="ts"> -import { defineComponent } from "vue"; -import { invoke } from "@tauri-apps/api"; -import { NorthstarMod } from "../../../src-tauri/bindings/NorthstarMod"; -import { showErrorNotification, showNotification } from "../utils/ui"; - -export default defineComponent({ - name: "LocalModCard", - props: { - mod: { - required: true, - type: Object as () => NorthstarMod - } - }, - data() { - return { - global_load_indicator: false, - }; - }, - methods: { - async updateWhichModsEnabled(mod: NorthstarMod) { - this.global_load_indicator = true; - - // enable/disable specific mod - try { - await invoke("set_mod_enabled_status", { - gameInstall: this.$store.state.game_install, - modName: mod.name, - // Need to set it to the opposite of current state, - // as current state is only updated after command is run - isEnabled: !mod.enabled, - }) - } - catch (error) { - showErrorNotification(`${error}`); - this.global_load_indicator = false; - return false; - } - - this.global_load_indicator = false; - return true; - }, - async deleteMod(mod: NorthstarMod) { - await invoke("delete_northstar_mod", { gameInstall: this.$store.state.game_install, nsmodName: mod.name }) - .then((message) => { - // Just a visual indicator that it worked - showNotification(this.$t('mods.local.success_deleting', { modName: mod.name })); - }) - .catch((error) => { - showErrorNotification(error); - }) - .finally(() => { - this.$store.commit('loadInstalledMods'); - }); - }, - } -}); -</script> - -<style scoped> - /* - This is a hack to style the card body - since it doesn't work with scoped styles - */ - :deep(.el-card__body) { - display: flex !important; - align-items: center; - width: 100%; - justify-content: space-between; - } - - .name { - display: flex; - } - - .image { - margin: 0 5px; - } - - .switch { - padding-left: 5px; - padding-right: 5px; - } -</style> diff --git a/src-vue/src/components/ModsMenu.vue b/src-vue/src/components/ModsMenu.vue deleted file mode 100644 index 66ecc71a..00000000 --- a/src-vue/src/components/ModsMenu.vue +++ /dev/null @@ -1,141 +0,0 @@ -<template> - <nav class="fc_mods__menu"> - <el-menu - default-active="1" - text-color="#fff" - > - <h5>{{ $t('menu.mods') }}</h5> - <el-menu-item index="1" @click="$emit('showLocalMods', true)"> - <el-icon><Folder /></el-icon> - <span>{{ $t('mods.menu.local') }}</span> - </el-menu-item> - - <!-- Display a badge if there are some outdated Thunderstore mods --> - <el-menu-item v-if="outdatedThunderstoreModsCount !== 0" index="2" @click="$emit('showLocalMods', false)"> - <el-badge :value="outdatedThunderstoreModsCount" class="item" type="warning"> - <el-icon><Connection /></el-icon> - <span>{{ $t('mods.menu.online') }}</span> - </el-badge> - </el-menu-item> - <el-menu-item v-else index="2" @click="$emit('showLocalMods', false)"> - <el-icon><Connection /></el-icon> - <span>{{ $t('mods.menu.online') }}</span> - </el-menu-item> - - <!-- Search inputs --> - <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="$t('mods.menu.sort_mods')" - > - <el-option - v-for="item of sortValues" - :key="item.value" - :label="item.label" - :value="item.value" - /> - </el-select> - <el-select - v-if="!showingLocalMods" - v-model="$store.state.search.selectedCategories" - multiple - :placeholder="$t('mods.menu.select_categories')" - > - <el-option - v-for="item in $store.state.thunderstoreModsCategories" - :key="item" - :label="item" - :value="item" - /> - </el-select> - - </el-menu> - </nav> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue' -import { SortOptions } from '../utils/SortOptions.d'; -import { isThunderstoreModOutdated } from "../utils/thunderstore/version"; -import { ThunderstoreMod } from "../../../src-tauri/bindings/ThunderstoreMod"; - -export default defineComponent({ - name: 'ModsMenu', - props: { - showingLocalMods: { - required: true, - type: Boolean - } - }, - mounted() { - this.$store.state.search.sortValue = this.sortValues[3].value; - }, - computed: { - outdatedThunderstoreModsCount(): number { - return this.$store.state.thunderstoreMods - .filter((mod: ThunderstoreMod) => isThunderstoreModOutdated(mod)) - .length; - }, - sortValues(): { label: string, value: string }[] { - return Object.keys(SortOptions).map((key: string) => ({ - value: key, - label: this.$t('mods.menu.sort.' + Object.values(SortOptions)[Object.keys(SortOptions).indexOf(key)]) - })); - } - } -}) -</script> - -<style scoped> -.fc_mods__menu { - display: flex; - max-width: 222px; - min-width: 222px; - padding: 10px; -} - -.fc_mods__menu h5 { - margin: 8px 0 16px 5px; -} - -.fc_mods__menu h5:not(:first-child) { - margin-top: 32px; -} - -.fc_mods__menu > .el-menu { - background-color: transparent; - border: none; - width: 100%; -} - -.fc_mods__menu > .el-menu > .el-menu-item { - height: 32px; - margin-bottom: 5px; - border-radius: 5px; - color: #e2e6e7; -} - -.fc_mods__menu > .el-menu > .el-menu-item:hover { - background-color: #4e4e4e3b; -} - -.fc_mods__menu > .el-menu > .el-menu-item.is-active { - color: white; - background-color: #4e4e4e7a; -} - -.el-select { - width: 100%; - margin-top: 5px; -} - -/* Outdated thunderstore mods count */ -.el-badge { - width: 100%; -} -.el-badge:deep(.el-badge__content) { - top: 28px !important; -} -</style> diff --git a/src-vue/src/components/NotificationButton.vue b/src-vue/src/components/NotificationButton.vue deleted file mode 100644 index 3835032d..00000000 --- a/src-vue/src/components/NotificationButton.vue +++ /dev/null @@ -1,81 +0,0 @@ -<template> - <el-dropdown trigger="click" placement="bottom-end" max-height="280" popper-class="fc_popper"> - <el-badge v-if="notifications.length != 0" :value="notifications.length" :max="9" class="item" type="primary"> - <el-button color="white" icon="BellFilled" circle /> - </el-badge> - <el-button v-else color="white" icon="BellFilled" circle /> - <template #dropdown> - <el-dropdown-menu :key="counter"> - <el-alert - v-if="notifications.length != 0" - v-for="(notification, i) in notifications" - :key="i" - :title="notification.title" - :description="notification.text" - :type="notification.type" - show-icon - style="width: 300px" - @close.stop="removeNotification(i)" - /> - <el-result - v-else - icon="success" - :title="i18n.global.tc('notification.no_new.title')" - :sub-title="i18n.global.tc('notification.no_new.text')" - > - <template #icon> - </template> - </el-result> - </el-dropdown-menu> - </template> - </el-dropdown> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue'; -import {Notification} from '../plugins/modules/notifications'; -import {i18n} from "../main"; - -export default defineComponent({ - name: 'NotificationButton', - data: () => ({ - // This variable is used as a key for the dropdown menu, so we can force it to refresh when a item is deleted. - counter: 0 - }), - computed: { - i18n() { - return i18n - }, - notifications(): Notification[] { - return this.$store.state.notifications.notifications; - } - }, - methods: { - removeNotification(index: number) { - this.$store.commit('removeNotification', index); - // By refreshing the notifications list, we ensure the first notification get the index 0, ensuring this list - // is synchronized with the store list. - this.counter += 1; - } - } -}) -</script> - -<style scoped> -.el-dropdown { - height: 100%; - max-height: 300px; -} - -.el-button { - margin: auto 25px auto 0 !important; -} - -.el-alert { - margin: 5px 10px 5px 5px; -} - -.el-badge:deep(sup) { - transform: translate(-10px, 5px) !important; -} -</style> diff --git a/src-vue/src/components/PlayButton.vue b/src-vue/src/components/PlayButton.vue deleted file mode 100644 index 57b408c2..00000000 --- a/src-vue/src/components/PlayButton.vue +++ /dev/null @@ -1,157 +0,0 @@ -<script lang="ts"> -import { defineComponent } from 'vue'; -import { NorthstarState } from '../utils/NorthstarState'; -import { ReleaseCanal } from '../utils/ReleaseCanal'; - -export default defineComponent({ - name: 'PlayButton', - computed: { - currentCanal: { - get(): ReleaseCanal { - return this.$store.state.northstar_release_canal; - }, - set(value: ReleaseCanal) { - if (value !== this.currentCanal) { - this.$store.commit('toggleReleaseCandidate'); - } - } - }, - playButtonLabel(): string { - if (this.$store.state.northstar_is_running) { - return this.$t("play.button.northstar_is_running"); - } - - switch (this.$store.state.northstar_state) { - case NorthstarState.GAME_NOT_FOUND: - return this.$t("play.button.select_game_dir"); - case NorthstarState.INSTALL: - return this.$t("play.button.install"); - case NorthstarState.INSTALLING: - return this.$t("play.button.installing"); - case NorthstarState.MUST_UPDATE: - return this.$t("play.button.update"); - case NorthstarState.UPDATING: - return this.$t("play.button.updating"); - case NorthstarState.READY_TO_PLAY: - return this.$t("play.button.ready_to_play"); - - default: - return ""; - } - }, - northstarIsRunning(): boolean { - return this.$store.state.northstar_is_running; - }, - options(): { key: string, value: string }[] { - return Object.keys(ReleaseCanal).map(function (v) { - return { - key: v, - value: Object.keys(ReleaseCanal)[Object.values(ReleaseCanal).indexOf(v)] - } - }); - }, - selectOptions(): { label: string, options: { value: ReleaseCanal, label: string }[] }[] { - return [ - { - label: 'Beta', - options: [ - { - value: ReleaseCanal.RELEASE_CANDIDATE, - label: this.$t('channels.names.NorthstarReleaseCandidate'), - }, - ] - }, - { - label: 'Stable', - options: [ - { - value: ReleaseCanal.RELEASE, - label: 'Northstar', - }, - ] - } - ]; - }, - showReleaseSwitch(): boolean { - return this.$store.state.enableReleasesSwitch; - }, - - /** - * Button has rounded edges on its right only if releases switching is enabled. - */ - buttonRadiusStyle(): string { - return this.showReleaseSwitch - ? 'border-radius: 2px 0 0 2px;' - : 'border-radius: 2px'; - }, - }, - methods: { - async launchGame() { - this.$store.commit('launchGame'); - } - } -}); -</script> - -<template> - <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> -nav { - margin-right: 20px; -} - -button { - text-transform: uppercase; - padding: 30px; - font-size: 15px; - margin-right: 0; -} - -.fc_launch__button:focus { - background-color: var(--el-color-primary); - border-color: var(--el-color-primary); -} - -/* Release canal selector */ -.el-select { - width: 31px; - border-left: 1px solid rgb(176, 205, 255); -} - -.el-select:deep(.el-select__wrapper) { - padding: 0 9px 0 0; - background-color: var(--el-color-primary); - border: none; - border-radius: 0 2px 2px 0; - height: 62px; - box-shadow: none !important; - --el-disabled-bg-color: #a0cfff; -} - -.el-select:deep(.el-icon) { - color: white !important; -} -</style> diff --git a/src-vue/src/components/PullRequestsSelector.vue b/src-vue/src/components/PullRequestsSelector.vue deleted file mode 100644 index bd17ed14..00000000 --- a/src-vue/src/components/PullRequestsSelector.vue +++ /dev/null @@ -1,181 +0,0 @@ -<template> - <div> - <el-collapse @change="onChange"> - <el-collapse-item name="1" @keydown.space="launcherSearchSpace"> - <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" - :percentage="100" - status="warning" - :indeterminate="true" - :duration="1" - style="margin: 15px" - /> - </p> - <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-tag v-for="label in pull_request.labels">{{ label }}</el-tag> - </el-card> - <div v-else class="no_matching_pr"> - No matching PR found. - </div> - </el-collapse-item> - - <el-collapse-item name="2" @keydown.space="modsSearchSpace"> - <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 - 'r2ns-launch-mod-pr-version.bat' or via '-profile=R2Northstar-PR-test-managed-folder' to actually - run the PR version! - </el-alert> - </div> - <p v-if="pull_requests_mods.length === 0"> - <el-progress - :show-text="false" - :percentage="100" - status="warning" - :indeterminate="true" - :duration="1" - style="margin: 15px" - /> - </p> - <el-card - v-else-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-tag v-for="label in pull_request.labels">{{ label }}</el-tag> - </el-card> - <div v-else class="no_matching_pr"> - No matching PR found. - </div> - </el-collapse-item> - </el-collapse> - </div> -</template> - -<script lang="ts"> -import { defineComponent } from 'vue' -import { PullRequestType } from '../../../src-tauri/bindings/PullRequestType'; -import { PullsApiResponseElement } from '../../../src-tauri/bindings/PullsApiResponseElement'; -import { fuzzy_filter } from "../utils/filter"; - -export default defineComponent({ - name: 'PullRequestsSelector', - data: () => ({ - launcherSearch: '', - modsSearch: '' - }), - computed: { - pull_requests_launcher(): PullsApiResponseElement[] { - return this.$store.state.pullrequests.pull_requests_launcher; - }, - pull_requests_mods(): PullsApiResponseElement[] { - return this.$store.state.pullrequests.pull_requests_mods; - }, - - 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 and title - fuzzy_filter(pr.number.toString(), this.launcherSearch) || fuzzy_filter(pr.title, this.launcherSearch)); - }, - 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 and title - fuzzy_filter(pr.number.toString(), this.modsSearch) || fuzzy_filter(pr.title, this.modsSearch)); - }, - }, - methods: { - onChange(e: Object) { - const openedCollapseNames = Object.values(e); - if (openedCollapseNames.includes('1') && this.pull_requests_launcher.length === 0) { - this.getPullRequests('Launcher'); - } - if (openedCollapseNames.includes('2') && this.pull_requests_mods.length === 0) { - this.getPullRequests('Mods'); - } - }, - launcherSearchSpace(e: KeyboardEvent) { - e.preventDefault(); - this.launcherSearch += ' '; - }, - modsSearchSpace(e: KeyboardEvent) { - e.preventDefault(); - this.modsSearch += ' '; - }, - 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); - }, - async installModsPR(pull_request: PullsApiResponseElement) { - this.$store.commit('installModsPR', pull_request); - }, - } -}) -</script> - -<style scoped> -.el-collapse { - border-radius: var(--el-border-radius-base); - overflow: hidden; -} - -:deep(.el-collapse-item__header) { - padding-left: 10px; - font-size: 14px; -} - -.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 deleted file mode 100644 index 11be7545..00000000 --- a/src-vue/src/components/ThunderstoreModCard.vue +++ /dev/null @@ -1,319 +0,0 @@ -<template> - <el-card :body-style="getBodyStyle" :style="getCardStyle"> - <img - :src="latestVersion.icon" - class="image" - /> - <div style="padding: 0 10px 10px;"> - <span class="statContainer"> - <el-icon class="no-inherit"> - <Download /> - </el-icon> - {{ modDownloadsCount }} - </span> - - <span class="statContainer"> - {{ mod.rating_score }} - <el-icon class="no-inherit"> - <Star /> - </el-icon> - </span> - <br /> - - <div class="name hide-text-overflow">{{ mod.name }}</div> - <div class="author hide-text-overflow">{{ $t('mods.card.by') }} {{ mod.owner }}</div> - <div class="desc"> - {{ latestVersion.description }} - </div> - - <span style="display: flex"> - <el-button - :type="modButtonType" - style="flex: 6" - :loading="isBeingInstalled || isBeingUpdated" - @click.stop="installMod(mod)" - > - {{ $t(modButtonText) }} - </el-button> - - <!-- Information dropdown menu --> - <el-button v-if="!modIsRemovable" - link type="info" class="infoBtn" @click="openURL(mod.package_url)"> - <el-icon> - <InfoFilled /> - </el-icon> - </el-button> - - <el-dropdown v-else> - <el-icon class="infoBtn moreBtn"> - <MoreFilled /> - </el-icon> - <template #dropdown> - <el-dropdown-menu> - <el-dropdown-item @click="openURL(mod.package_url)"> - {{ $t('mods.card.more_info') }} - </el-dropdown-item> - <el-dropdown-item @click="deleteMod(mod)"> - {{ $t('mods.card.remove') }} - </el-dropdown-item> - </el-dropdown-menu> - </template> - </el-dropdown> - </span> - </div> - </el-card> -</template> - -<script lang="ts"> -import { defineComponent } from "vue"; -import { ThunderstoreMod } from "../../../src-tauri/bindings/ThunderstoreMod"; -import { ThunderstoreModVersion } from "../../../src-tauri/bindings/ThunderstoreModVersion"; -import { invoke, shell } from "@tauri-apps/api"; -import { ThunderstoreModStatus } from "../utils/thunderstore/ThunderstoreModStatus"; -import { NorthstarMod } from "../../../src-tauri/bindings/NorthstarMod"; -import { NorthstarState } from "../utils/NorthstarState"; -import { ElMessageBox } from "element-plus"; -import { showErrorNotification, showNotification } from "../utils/ui"; - -export default defineComponent({ - name: "ThunderstoreModCard", - props: { - mod: { - required: true, - type: Object as () => ThunderstoreMod - } - }, - data: () => ({ - isBeingInstalled: false, - isBeingUpdated: false - }), - computed: { - getBodyStyle(): Object { - return this.mod.is_deprecated ? { 'background-color': 'rgba(255, 0, 0, 0.42)' } : {}; - }, - - getCardStyle(): Object { - return this.mod.is_deprecated ? { 'border': '1px solid red' } : {}; - }, - - latestVersion(): ThunderstoreModVersion { - return this.mod.versions[0]; - }, - - /** - * Returns the status of a given mod. - */ - modStatus(): ThunderstoreModStatus { - if (this.isBeingInstalled) { - return ThunderstoreModStatus.BEING_INSTALLED; - } - if (this.isBeingUpdated) { - return ThunderstoreModStatus.BEING_UPDATED; - } - - // Ensure mod is up-to-date. - const tsModPrefix = this.getThunderstoreDependencyStringPrefix(this.latestVersion.full_name); - const matchingMods: NorthstarMod[] = this.$store.state.installed_mods.filter((mod: NorthstarMod) => { - if (!mod.thunderstore_mod_string) return false; - return this.getThunderstoreDependencyStringPrefix(mod.thunderstore_mod_string!) === tsModPrefix; - }); - if (matchingMods.length !== 0) { - // There shouldn't be several mods with same dependency string, but we never know... - const matchingMod = matchingMods[0]; - // A mod is outdated if its dependency strings differs from Thunderstore dependency string - // (no need for semver check here) - return matchingMod.thunderstore_mod_string === this.latestVersion.full_name - ? ThunderstoreModStatus.INSTALLED - : ThunderstoreModStatus.OUTDATED; - } - - return ThunderstoreModStatus.NOT_INSTALLED; - }, - - /** - * Returns button text associated to a mod. - */ - modButtonText(): string { - switch (this.modStatus) { - case ThunderstoreModStatus.BEING_INSTALLED: - return "mods.card.button.being_installed"; - case ThunderstoreModStatus.BEING_UPDATED: - return "mods.card.button.being_updated"; - case ThunderstoreModStatus.INSTALLED: - return "mods.card.button.installed"; - case ThunderstoreModStatus.NOT_INSTALLED: - return "mods.card.button.install"; - case ThunderstoreModStatus.OUTDATED: - return "mods.card.button.outdated"; - } - }, - - /** - * Returns button type associated to a mod. - */ - modButtonType(): string { - switch (this.modStatus) { - case ThunderstoreModStatus.BEING_INSTALLED: - return "primary"; - case ThunderstoreModStatus.INSTALLED: - return "success"; - case ThunderstoreModStatus.NOT_INSTALLED: - return "primary"; - case ThunderstoreModStatus.OUTDATED: - case ThunderstoreModStatus.BEING_UPDATED: - return "warning"; - } - }, - - /** - * Tells if a Thunderstore mod can be removed. - * This is used to tell if we should display the "Remove mod" option. - **/ - modIsRemovable(): boolean { - return [ThunderstoreModStatus.INSTALLED, ThunderstoreModStatus.OUTDATED] - .includes(this.modStatus); - }, - - /** - * This computes the total count of downloads of a given mod, by adding - * download count of each of its releases. - */ - modDownloadsCount(): number { - let totalDownloads = 0; - this.mod.versions.map((version: ThunderstoreModVersion) => totalDownloads += version.downloads); - return totalDownloads; - }, - }, - methods: { - /** - * This opens an URL in user's favorite web browser. - * This is used to open Thunderstore mod pages. - */ - openURL(url: string): void { - shell.open(url); - }, - - /** - * Strips off a Thunderstore dependency string from its version - * (e.g. "taskinoz-WallrunningTitans-1.0.0" to - * "taskinoz-WallrunningTitans"). - */ - getThunderstoreDependencyStringPrefix(dependency: string): string { - const dependencyStringMembers = dependency.split('-'); - return `${dependencyStringMembers[0]}-${dependencyStringMembers[1]}`; - }, - - async deleteMod(mod: ThunderstoreMod) { - - // Show pop-up to confirm delete - ElMessageBox.confirm( - this.$t('mods.card.remove_dialog_text'), - this.$t('mods.card.remove_dialog_title'), - { - confirmButtonText: this.$t('generic.yes'), - cancelButtonText: this.$t('generic.cancel'), - type: 'warning', - } - ) - .then(async () => { // Deletion confirmed - await invoke<string>("delete_thunderstore_mod", { gameInstall: this.$store.state.game_install, thunderstoreModString: this.latestVersion.full_name }) - .then((message) => { - showNotification(this.$t('mods.card.remove_success', { modName: mod.name }), message); - }) - .catch((error) => { - showErrorNotification(error); - }) - .finally(() => { - this.$store.commit('loadInstalledMods'); - }); - }) - .catch(() => { // Deletion cancelled - console.log("Deleting Thunderstore mod cancelled.") - }) - }, - - async installMod(mod: ThunderstoreMod) { - // set internal state according to current installation state - if (this.modStatus === ThunderstoreModStatus.OUTDATED) { - this.isBeingUpdated = true; - } else { - this.isBeingInstalled = true; - } - - // Capture translation method in a context, so it can be used outside Vue component context. - // (see https://github.com/R2NorthstarTools/FlightCore/issues/384) - (async (translate: Function) => { - await invoke<string>("install_mod_wrapper", { gameInstall: this.$store.state.game_install, thunderstoreModString: this.latestVersion.full_name }).then((message) => { - showNotification(translate('mods.card.install_success', { modName: mod.name }), message); - }) - .catch((error) => { - showErrorNotification(error); - }) - .finally(() => { - this.isBeingInstalled = false; - this.isBeingUpdated = false; - this.$store.commit('loadInstalledMods'); - }); - // @ts-ignore - })(this.$i18n.t); - - }, - } -}); -</script> - -<style scoped> -.el-card { - display: inline-block; - max-width: 178px; - margin: 5px; - --el-card-padding: 0; -} - -.deprecated { - background-color: red !important; -} - -.author { - font-size: 14px; - font-style: italic; -} - -.hide-text-overflow { - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; -} - -.desc { - font-size: 12px; - margin: 8px 0 16px; - height: 57px; - text-overflow: ellipsis; - overflow: hidden; -} - -.statContainer { - font-size: 14px; -} - -.statContainer:nth-child(2) { - float: right; -} - -.infoBtn { - width: 20px; - padding: 0 !important; - font-size: 20px; - border: none; -} - -.moreBtn { - margin-left: 10px; - height: auto; -} - -.image { - background-color: lightgray; -} -</style> |