diff --git a/quasar.config.ts b/quasar.config.ts index e9d7b21c7..e28148216 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 93775ab23..d6323e56f 100644 --- a/src/components/banner/ManagerUpdateBanner.vue +++ b/src/components/banner/ManagerUpdateBanner.vue @@ -34,13 +34,17 @@ onMounted(async () => { + + diff --git a/src/components/composables/ConcerningPackageComposable.ts b/src/components/composables/ConcerningPackageComposable.ts new file mode 100644 index 000000000..b241dedaf --- /dev/null +++ b/src/components/composables/ConcerningPackageComposable.ts @@ -0,0 +1,93 @@ +/** + * Definitions are outside the composable function so that calculations are shared. + * We do not need to recalculate on a per-usage basis. + */ +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 33a6af0c1..6c2f925a7 100644 --- a/src/components/views/LocalModList/LocalModCard.vue +++ b/src/components/views/LocalModList/LocalModCard.vue @@ -12,6 +12,8 @@ import { splitToNameAndVersion } from '../../../utils/DependencyUtils'; import { computed, onMounted, ref, watch } from 'vue'; import { getStore } from '../../../providers/generic/store/StoreProvider'; import { State } from '../../../store'; +import { useConcerningPackageComposable } from '@r2/components/composables/ConcerningPackageComposable'; +import { useModManagementComposable } from '@r2/components/composables/ModManagementComposable'; const store = getStore(); @@ -21,6 +23,9 @@ type LocalModCardProps = { const props = defineProps(); +const { isConcerningPackage, wasConcerningPackage } = useConcerningPackageComposable(); +const { uninstallMod } = useModManagementComposable(); + const disabledDependencies = ref([]); const missingDependencies = ref([]); const disableChangePending = ref(false); @@ -117,27 +122,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); @@ -179,6 +163,10 @@ function getReadableDate(value: number): string { function dependencyStringToModName(x: string) { return x.substring(0, x.lastIndexOf('-')); } + +function openReviewModal() { + store.commit('openConcerningModReviewModal', props.mod); +}