Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions quasar.config.ts
Original file line number Diff line number Diff line change
@@ -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';
import { nodePolyfills } from 'vite-plugin-node-polyfills';

Expand Down Expand Up @@ -50,8 +51,14 @@ export default defineConfig((ctx) => {

typescript: {
strict: true,
vueShim: true
// extendTsConfig (tsConfig) {}
vueShim: true,
extendTsConfig(tsConfig) {
tsConfig.compilerOptions.paths = {
'@r2': ['../src'],
'@r2/*': ['../src/*'],
...tsConfig.compilerOptions.paths,
};
}
},

vueRouterMode: 'history', // available values: 'hash', 'history'
Expand All @@ -70,7 +77,11 @@ export default defineConfig((ctx) => {
polyfillModulePreload: true,
// distDir

// extendViteConf (viteConf) {},
extendViteConf(viteConf) {
viteConf.resolve ??= {};
viteConf.resolve.alias ??= {};
(viteConf.resolve.alias as Record<string, string>)['@r2'] = path.resolve(__dirname, 'src');
},
viteVuePluginOptions: {
template: {
compilerOptions: {
Expand Down
30 changes: 30 additions & 0 deletions src/components/banner/ConcerningPackageBanner.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">

import { getStore } from '@r2/providers/generic/store/StoreProvider';
import { State } from '@r2/store';
import { useConcerningPackageComposable } from '@r2/components/composables/ConcerningPackageComposable';

const store = getStore<State>();
const { hasConcerningPackages } = useConcerningPackageComposable();

function addUnlinkedFilter() {
store.commit('profile/scopeLocalModListToUnlinkedPackages');
}
</script>

<template>
<div class="notification is-concern margin-right" v-show="hasConcerningPackages">
<span>You have packages that can no longer be found on Thunderstore.</span> <a href="#" @click.stop.prevent="addUnlinkedFilter">Click here to review packages.</a>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might as well call it a "mod" everywhere since that's what we do elsewhere in the mod manager.

</div>
</template>

<style scoped lang="scss">
.is-concern {
background-color: var(--notification-concern-background-color);
color: var(--notification-concern-text-color);
}

.notification {
margin-bottom: 0.5rem;
}
</style>
20 changes: 12 additions & 8 deletions src/components/banner/ManagerUpdateBanner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ onMounted(async () => {

<template>
<div class='notification margin-top margin-right' v-show="portableUpdateAvailable">
<div class='container'>
<p>
An {{ appName }} update is available.
<ExternalLink :url="`https://github.com/ebkr/r2modmanPlus/releases/tag/${updateTagName}`">
Click here to go to the release page.
</ExternalLink>
</p>
</div>
<p>
An {{ appName }} update is available.
<ExternalLink :url="`https://github.com/ebkr/r2modmanPlus/releases/tag/${updateTagName}`">
Click here to go to the release page.
</ExternalLink>
</p>
</div>
</template>

<style scoped lang="scss">
.notification {
margin-bottom: 0.5rem;
}
</style>
35 changes: 35 additions & 0 deletions src/components/composables/ConcerningPackageComposable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* 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, ref, watch } from 'vue';
import ManifestV2 from '@r2/model/ManifestV2';
import ThunderstoreMod from '@r2/model/ThunderstoreMod';

const store = getStore<any>();

const localModList = computed<ManifestV2[]>(() => store.state.profile.modList);
const onlineModList = computed<Map<string, ThunderstoreMod>>(() => {
const mods: ThunderstoreMod[] = store.state.tsMods.mods;
return new Map<string, ThunderstoreMod>(mods.map(value => [value.getFullName(), value]));
});

const concerningPackages = computed<ManifestV2[]>(() => {
return localModList.value.filter(value => !value.isTrustedPackage() && (value.isOnlineSource() && !onlineModList.value.has(value.getName())));
});

export function useConcerningPackageComposable() {

const hasConcerningPackages = computed<boolean>(() => concerningPackages.value.length > 0);

function isConcerningPackage(mod: ManifestV2) {
return concerningPackages.value.findIndex(value => value.getName() === mod.getName()) >= 0;
}

return {
concerningPackages,
hasConcerningPackages,
isConcerningPackage
}
}
35 changes: 35 additions & 0 deletions src/components/composables/ModManagementComposable.ts
Original file line number Diff line number Diff line change
@@ -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<any>();

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
}
}
81 changes: 81 additions & 0 deletions src/components/modals/ConcerningPackageReviewModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script setup lang="ts">
import { computed } from 'vue';
import ModalCard from '@r2/components/ModalCard.vue';
import { State } from '@r2/store';
import { getStore } from '@r2/providers/generic/store/StoreProvider';
import ManifestV2 from '@r2/model/ManifestV2';
import { useModManagementComposable } from '@r2/components/composables/ModManagementComposable';
import R2Error from '@r2/model/errors/R2Error';
import ProfileModList from '@r2/r2mm/mods/ProfileModList';

const store = getStore<State>();

const { uninstallMod } = useModManagementComposable();

const isOpen = computed(() => store.state.modals.isConcerningModReviewModalOpen);
const modToReview = computed<ManifestV2 | null>(() => store.state.modals.concerningModToReview);
const profile = computed(() => store.getters['profile/activeProfile']);

function close() {
store.commit('closeConcerningModReviewModal');
}

async function removeMod() {
await uninstallMod(modToReview.value!);
close();
}

async function trustPackage() {
const mods = await ProfileModList.getModList(profile.value.asImmutableProfile());
if (mods instanceof R2Error) {
console.error(mods);
store.commit('error/handleError', mods);
return;
}
const mod = mods.find(value => value.getName() === modToReview.value?.getName());
if (mod) {
mod.setTrustedPackage(true);
}
try {
const err = await ProfileModList.saveModList(profile.value.asImmutableProfile(), mods);
if (err instanceof R2Error) {
store.commit('error/handleError', err);
return;
}
await store.dispatch('profile/updateModList', mods);
} catch (e) {
store.commit('error/handleError', R2Error.fromThrownValue(e));
} finally {
close();
}
}
</script>

<template>
<ModalCard id="review-package-modal" v-if="isOpen && modToReview" :is-active="isOpen" :can-close="true" @close-modal="close">
<template v-slot:header>
<h2 class="modal-title">Review {{ modToReview.getName() }}</h2>
</template>
<template v-slot:body>
<p class="notification is-warning">It is generally recommended to remove mods that have been removed from Thunderstore.</p>
<div>
<hr/>
<p class="margin-bottom">This mod was originally downloaded using the Online section however no longer appears in the package cache.</p>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mod was originally downloaded using the Online section however no longer appears in the package cache.
The mod wasn't necessarily downloaded from the Online section, it could've been downloaded from the website. The phrase "Package Cache" also sounds like it describes the "cache" folder next to the mod manager's "profiles" folder, so we should probably call it something else.

I would reword this whole section to this:

This mod was originally downloaded from Thunderstore, but can no longer be found on the site.

Mods may be removed at the author's request, for rule violations, or while undergoing verification by moderators.

Other people will be unable to import this mod from exported profiles.

<p class="margin-bottom">When a mod is no longer in the package cache, it means that it has been removed from Thunderstore.</p>
<p>Other people will be unable to import this mod if the profile is exported.</p>
</div>
</template>
<template v-slot:footer>
<button class="button" @click.stop.prevent="trustPackage">
Mark version as safe
</button>
<button class="button is-danger" @click.stop.prevent="removeMod">
Remove mod
</button>
</template>
</ModalCard>
</template>

<style scoped lang="scss">

</style>
20 changes: 19 additions & 1 deletion src/components/views/LocalModList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,27 @@
<div class="search-and-sort">
<SearchAndSort />
</div>

<DisableModModal />
<UninstallModModal />
<AssociatedModsModal />

<ManagerUpdateBanner/>
<ConcerningPackageBanner/>

<slot name="above-list"></slot>

<div class="tags has-addons" v-if="filters.size > 0">
<span class="margin-right" v-for="filter in filters">
<a href="#" @click="removeFilter(filter)">
<div class="tag has-addons">
<span>{{ filter }}</span>
</div>
<span class="tag is-delete">&nbsp;</span>
</a>
</span>
</div>

<div class="mod-list-content">
<div class="draggable-content">
<Suspense>
Expand All @@ -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<State>();

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);
}
</script>

<style lang="scss" scoped>
Expand All @@ -54,7 +73,6 @@ const visibleModList = computed(() => store.getters['profile/visibleModList']);
.mod-list-content {
flex: 1;
overflow-y: auto;
padding-right: 1rem;
}
}
</style>
Loading