diff --git a/quasar.config.ts b/quasar.config.ts index 4b6449be1..fe9e559da 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -1,6 +1,7 @@ // Configuration for your app // https://v2.quasar.dev/quasar-cli-vite/quasar-config-file +import path from 'node:path'; import { defineConfig } from '#q-app/wrappers'; export default defineConfig((ctx) => { @@ -47,7 +48,13 @@ export default defineConfig((ctx) => { typescript: { strict: true, vueShim: true, - // extendTsConfig (tsConfig) {} + extendTsConfig(tsConfig) { + tsConfig.compilerOptions.paths = { + '@r2': ['../src'], + '@r2/*': ['../src/*'], + ...tsConfig.compilerOptions.paths, + }; + } }, vueRouterMode: 'history', // available values: 'hash', 'history' @@ -71,6 +78,11 @@ export default defineConfig((ctx) => { extendViteConf (viteConf) { // Force Vite to use esbuild for CSS, overriding any defaults viteConf.build!.cssMinify = 'esbuild'; + + // Allow @r2 alias + viteConf.resolve ??= {}; + viteConf.resolve.alias ??= {}; + (viteConf.resolve.alias as Record)['@r2'] = path.resolve(__dirname, 'src'); }, viteVuePluginOptions: { template: { diff --git a/src/components/ExpandableCard.vue b/src/components/ExpandableCard.vue index 700b75eea..3a699b4e0 100644 --- a/src/components/ExpandableCard.vue +++ b/src/components/ExpandableCard.vue @@ -26,7 +26,7 @@ -
+

{{description}}

diff --git a/src/components/banner/ConcerningPackageBanner.vue b/src/components/banner/ConcerningPackageBanner.vue new file mode 100644 index 000000000..f543c017c --- /dev/null +++ b/src/components/banner/ConcerningPackageBanner.vue @@ -0,0 +1,30 @@ + + + + + diff --git a/src/components/banner/ManagerUpdateBanner.vue b/src/components/banner/ManagerUpdateBanner.vue index 84015dcf0..d593969ff 100644 --- a/src/components/banner/ManagerUpdateBanner.vue +++ b/src/components/banner/ManagerUpdateBanner.vue @@ -43,3 +43,9 @@ onMounted(async () => {
+ + diff --git a/src/components/composables/ConcerningPackageComposable.ts b/src/components/composables/ConcerningPackageComposable.ts new file mode 100644 index 000000000..8dc777a97 --- /dev/null +++ b/src/components/composables/ConcerningPackageComposable.ts @@ -0,0 +1,89 @@ +import { getStore } from '@r2/providers/generic/store/StoreProvider'; +import { computed, onMounted, ref, watch } from 'vue'; +import ManifestV2 from '@r2/model/ManifestV2'; +import ThunderstoreMod from '@r2/model/ThunderstoreMod'; +import VersionNumber from '@r2/model/VersionNumber'; +import * as PackageDb from "@r2/r2mm/manager/PackageDexieStore"; + +type ConcerningPackage = { + fullName: string; + latestVersion: string; +} + +const store = getStore(); + +const activeGame = computed(() => store.state.activeGame); + +const localModList = computed(() => store.state.profile.modList); +const onlineModList = computed>(() => { + const mods: ThunderstoreMod[] = store.state.tsMods.mods; + return new Map(mods.map(value => [value.getFullName(), { + fullName: value.getFullName(), + latestVersion: value.getLatestVersion(), + }])); +}); + +const allConcerningPackages = ref([]); +const activeConcerningPackages = ref([]); + +async function updateConcerningPackages() { + const game = activeGame.value; + const localMods = localModList.value; + const concerningPackages: ManifestV2[] = []; + const modsToCheck: ManifestV2[] = []; + + for (const mod of localMods) { + if (!mod.isOnlineSource()) { + continue; + } + if (!onlineModList.value.has(mod.getName())) { + concerningPackages.push(mod); + } else { + modsToCheck.push(mod); + } + } + + const versionNumbersBatch = await PackageDb.getPackageVersionNumbersBatch( + game.internalFolderName, + modsToCheck.map(mod => mod.getName()) + ); + + for (const mod of modsToCheck) { + const versions = versionNumbersBatch.get(mod.getName()); + const dnf = !versions || !versions.includes(mod.getVersionNumber().toString()); + if (dnf) { + concerningPackages.push(mod); + } + } + + allConcerningPackages.value = concerningPackages; + activeConcerningPackages.value = concerningPackages.filter(value => !value.isTrustedPackage()); +} + +watch([activeGame, localModList], async () => { + await updateConcerningPackages(); +}); + +export function useConcerningPackageComposable() { + + onMounted(async () => { + await updateConcerningPackages(); + }); + + const hasConcerningPackages = computed(() => activeConcerningPackages.value.length > 0); + + function isConcerningPackage(mod: ManifestV2) { + return activeConcerningPackages.value.findIndex(value => value.getName() === mod.getName()) >= 0; + } + + function wasConcerningPackage(mod: ManifestV2) { + return allConcerningPackages.value.findIndex(value => value.getName() === mod.getName()) >= 0; + } + + return { + concerningPackages: activeConcerningPackages, + hasConcerningPackages, + isConcerningPackage, + wasConcerningPackage, + } +} diff --git a/src/components/composables/ModManagementComposable.ts b/src/components/composables/ModManagementComposable.ts new file mode 100644 index 000000000..7fa4520b0 --- /dev/null +++ b/src/components/composables/ModManagementComposable.ts @@ -0,0 +1,35 @@ +import { getStore } from '@r2/providers/generic/store/StoreProvider'; +import Dependants from '@r2/r2mm/mods/Dependants'; +import R2Error from '@r2/model/errors/R2Error'; +import { LogSeverity } from '@r2/providers/ror2/logging/LoggerProvider'; +import ManifestV2 from '@r2/model/ManifestV2'; + +const store = getStore(); + +export function useModManagementComposable() { + + async function uninstallMod(mod: ManifestV2) { + const dependants = Dependants.getDependantList(mod, store.state.profile.modList); + + if (dependants.size > 0) { + store.commit('openUninstallModModal', mod); + return; + } + + try { + await store.dispatch( + 'profile/uninstallModsFromActiveProfile', + { mods: [mod] } + ); + } catch (e) { + store.commit('error/handleError', { + error: R2Error.fromThrownValue(e), + severity: LogSeverity.ACTION_STOPPED + }); + } + } + + return { + uninstallMod + } +} diff --git a/src/components/modals/ConcerningPackageReviewModal.vue b/src/components/modals/ConcerningPackageReviewModal.vue new file mode 100644 index 000000000..7e7092d0e --- /dev/null +++ b/src/components/modals/ConcerningPackageReviewModal.vue @@ -0,0 +1,81 @@ + + + + + diff --git a/src/components/views/LocalModList.vue b/src/components/views/LocalModList.vue index 2da519543..c40c61821 100644 --- a/src/components/views/LocalModList.vue +++ b/src/components/views/LocalModList.vue @@ -3,14 +3,27 @@
+ + + +
@@ -37,12 +50,18 @@ import { State } from '../../store'; import { computed, defineAsyncComponent } from 'vue'; import SkeletonLocalModCard from './LocalModList/SkeletonLocalModCard.vue'; import ManagerUpdateBanner from '../banner/ManagerUpdateBanner.vue'; +import ConcerningPackageBanner from '@r2/components/banner/ConcerningPackageBanner.vue'; const store = getStore(); const LocalModDraggableList = defineAsyncComponent(() => import('./LocalModList/LocalModDraggableList.vue')); const visibleModList = computed(() => store.getters['profile/visibleModList']); +const filters = computed(() => store.state.profile.filters); + +function removeFilter(filter: string) { + store.commit('profile/removeFilter', filter); +} diff --git a/src/components/views/LocalModList/LocalModCard.vue b/src/components/views/LocalModList/LocalModCard.vue index ec48152cf..a46c6051e 100644 --- a/src/components/views/LocalModList/LocalModCard.vue +++ b/src/components/views/LocalModList/LocalModCard.vue @@ -16,6 +16,8 @@ import { State } from '../../../store'; import { UnsatisfiedDependencies } from '../../../store/modules/ProfileModule'; import ThunderstoreMod from "../../../model/ThunderstoreMod"; import ThunderstoreVersion from "../../../model/ThunderstoreVersion"; +import { useConcerningPackageComposable } from '@r2/components/composables/ConcerningPackageComposable'; +import { useModManagementComposable } from '@r2/components/composables/ModManagementComposable'; const store = getStore(); @@ -26,6 +28,9 @@ type LocalModCardProps = { const props = defineProps(); +const { isConcerningPackage, wasConcerningPackage } = useConcerningPackageComposable(); +const { uninstallMod } = useModManagementComposable(); + const disableChangePending = ref(false); const icon = useModIcon(() => props.mod); @@ -98,27 +103,6 @@ async function enableMod(mod: ManifestV2) { disableChangePending.value = false; } -async function uninstallMod() { - const dependants = Dependants.getDependantList(props.mod, localModList.value); - - if (dependants.size > 0) { - store.commit('openUninstallModModal', props.mod); - return; - } - - try { - await store.dispatch( - 'profile/uninstallModsFromActiveProfile', - { mods: [props.mod] } - ); - } catch (e) { - store.commit('error/handleError', { - error: R2Error.fromThrownValue(e), - severity: LogSeverity.ACTION_STOPPED - }); - } -} - function updateMod() { if (tsMod.value !== undefined) { store.commit('openDownloadModVersionSelectModal', tsMod.value); @@ -152,6 +136,14 @@ function viewAssociatedMods() { function getReadableDate(value: number): string { return valueToReadableDate(value); } + +function dependencyStringToModName(x: string) { + return x.substring(0, x.lastIndexOf('-')); +} + +function openReviewModal() { + store.commit('openConcerningModReviewModal', props.mod); +}