Skip to content

Commit

Permalink
Merge pull request #1328 from ebkr/rename-profile-modal-refactor
Browse files Browse the repository at this point in the history
Refactor Rename Profile Modal by separating it from Profiles.vue
  • Loading branch information
anttimaki authored Jun 11, 2024
2 parents d40bc4b + 64c0a36 commit 6bb478c
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 33 deletions.
110 changes: 110 additions & 0 deletions src/components/profiles-modals/RenameProfileModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<script lang="ts">
import { Vue, Component, Ref, Watch } from 'vue-property-decorator';
import { ModalCard } from "../all";
import sanitize from 'sanitize-filename';
import R2Error from "../../model/errors/R2Error";
import Profile from "../../model/Profile";
@Component({
components: {ModalCard}
})
export default class RenameProfileModal extends Vue {
@Ref() readonly nameInput: HTMLInputElement | undefined;
private newProfileName: string = '';
@Watch('$store.state.profile.activeProfile')
activeProfileChanged(newProfile: Profile, oldProfile: Profile|null) {
if (
// Modal was just created and has no value yet, use any available.
this.newProfileName === "" ||
// Profile was not previously selected, use the new active profile.
oldProfile === null ||
// Avoid losing user's changes when the profile unexpectedly changes.
oldProfile.getProfileName() === this.newProfileName
) {
this.newProfileName = newProfile.getProfileName();
}
}
get isOpen(): boolean {
const isOpen_ = this.$store.state.modals.isRenameProfileModalOpen;
if (isOpen_) {
this.$nextTick(() => {
if (this.nameInput) {
this.nameInput.focus();
}
});
}
return isOpen_;
}
get profileList(): string[] {
return this.$store.state.profiles.profileList;
}
closeModal() {
this.newProfileName = this.$store.state.profile.activeProfile.getProfileName();
this.$store.commit('closeRenameProfileModal');
}
makeProfileNameSafe(nameToSanitize: string): string {
return sanitize(nameToSanitize);
}
doesProfileExist(nameToCheck: string): boolean {
if ((nameToCheck.match(new RegExp('^([a-zA-Z0-9])(\\s|[a-zA-Z0-9]|_|-|[.])*$'))) === null) {
return true;
}
const safe: string = this.makeProfileNameSafe(nameToCheck);
return (this.profileList.some(function (profile: string) {
return profile.toLowerCase() === safe.toLowerCase()
}));
}
async performRename() {
try {
await this.$store.dispatch('profiles/renameProfile', {newName: this.newProfileName});
} catch (e) {
const err = R2Error.fromThrownValue(e, 'Error whilst renaming profile');
this.$store.commit('error/handleError', err);
}
this.closeModal();
}
}
</script>
<template>
<ModalCard v-if="isOpen" :is-active="isOpen" @close-modal="closeModal">

<template v-slot:header>
<p class="modal-card-title">Rename a profile</p>
</template>
<template v-slot:body>
<p>This profile will store its own mods independently from other profiles.</p>

<input
class="input"
v-model="newProfileName"
@keyup.enter="!doesProfileExist(newProfileName) && performRename(newProfileName)"
ref="nameInput"
/>

<span class="tag is-dark" v-if="newProfileName === '' || makeProfileNameSafe(newProfileName) === ''">
Profile name required
</span>
<span class="tag is-success" v-else-if="!doesProfileExist(newProfileName)">
"{{makeProfileNameSafe(newProfileName)}}" is available
</span>
<span class="tag is-danger" v-else-if="doesProfileExist(newProfileName)">
"{{makeProfileNameSafe(newProfileName)}}" is either already in use, or contains invalid characters
</span>
</template>
<template v-slot:footer>
<button class="button is-danger" v-if="doesProfileExist(newProfileName)" disabled>Rename</button>
<button class="button is-info" @click="performRename(newProfileName)" v-else>Rename</button>
</template>

</ModalCard>
</template>
42 changes: 10 additions & 32 deletions src/pages/Profiles.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<template>
<div>
<DeleteProfileModal />
<RenameProfileModal />
<!-- Create modal -->
<div :class="['modal', {'is-active':(addingProfile !== false || renamingProfile !== false)}]">
<div :class="['modal', {'is-active':(addingProfile !== false)}]">
<div class="modal-background" @click="closeNewProfileModal()"></div>
<div class="modal-content">
<div class="card">
<header class="card-header">
<p class="card-header-title">{{addingProfileType}} a profile</p>
</header>
<template v-if="(addingProfile && importUpdateSelection === 'IMPORT') || (addingProfile && importUpdateSelection === null) || renamingProfile">
<template v-if="(addingProfile && importUpdateSelection === 'IMPORT') || (addingProfile && importUpdateSelection === null)">
<div class="card-content">
<p>This profile will store its own mods independently from other profiles.</p>
<br/>
Expand Down Expand Up @@ -47,10 +48,6 @@
<button id="modal-update-profile-invalid" class="button is-danger" v-if="!doesProfileExist(selectedProfile)">Update profile: {{ selectedProfile }}</button>
<button id="modal-update-profile" class="button is-info" v-else @click="updateProfile()">Update profile: {{ selectedProfile }}</button>
</template>
<template v-if="renamingProfile">
<button id="modal-rename-profile-invalid" class="button is-danger" v-if="doesProfileExist(newProfileName)">Rename</button>
<button id="modal-rename-profile" class="button is-info" @click="performRename(newProfileName)" v-else>Rename</button>
</template>
</div>
</div>
</div>
Expand Down Expand Up @@ -185,7 +182,7 @@
</div>
<div class="level-item">
<a id="rename-profile-disabled" class="button" v-if="selectedProfile === 'Default'" :disabled="true">Rename</a>
<a id="rename-profile" class="button" @click="renameProfile()" v-else>Rename</a>
<a id="rename-profile" class="button" @click="openRenameProfileModal()" v-else>Rename</a>
</div>
<div class="level-item">
<a id="create-profile" class="button" @click="importUpdateSelection = null; newProfile('Create', undefined)">Create new</a>
Expand Down Expand Up @@ -240,6 +237,7 @@ import ManagerInformation from '../_managerinf/ManagerInformation';
import GameDirectoryResolverProvider from '../providers/ror2/game/GameDirectoryResolverProvider';
import { ProfileImportExport } from '../r2mm/mods/ProfileImportExport';
import DeleteProfileModal from "../components/profiles-modals/DeleteProfileModal.vue";
import RenameProfileModal from "../components/profiles-modals/RenameProfileModal.vue";
let fs: FsProvider;
Expand All @@ -248,6 +246,7 @@ let fs: FsProvider;
hero: Hero,
'progress-bar': Progress,
DeleteProfileModal,
RenameProfileModal,
},
})
export default class Profiles extends Vue {
Expand All @@ -270,8 +269,6 @@ export default class Profiles extends Vue {
private listenerId: number = 0;
private renamingProfile: boolean = false;
get activeProfile(): Profile {
return this.$store.getters['profile/activeProfile'];
}
Expand Down Expand Up @@ -321,27 +318,6 @@ export default class Profiles extends Vue {
profile.toLowerCase() === safe.toLowerCase())) !== undefined;
}
renameProfile() {
this.newProfileName = this.selectedProfile;
this.addingProfileType = "Rename";
this.renamingProfile = true;
this.$nextTick(() => {
if (this.profileNameInput) {
this.profileNameInput.focus();
}
});
}
async performRename(newName: string) {
await fs.rename(
path.join(Profile.getDirectory(), this.selectedProfile),
path.join(Profile.getDirectory(), newName)
);
this.closeNewProfileModal();
await this.updateProfileList();
await this.setSelectedProfile(newName, false);
}
// Open modal for entering a name for a new profile. Triggered
// either through user action or profile importing via file or code.
newProfile(type: string, nameOverride: string | undefined) {
Expand Down Expand Up @@ -376,13 +352,16 @@ export default class Profiles extends Vue {
closeNewProfileModal() {
this.addingProfile = false;
this.renamingProfile = false;
}
openDeleteProfileModal() {
this.$store.commit('openDeleteProfileModal');
}
openRenameProfileModal() {
this.$store.commit('openRenameProfileModal');
}
makeProfileNameSafe(nameToSanitize: string): string {
return sanitize(nameToSanitize);
}
Expand Down Expand Up @@ -617,7 +596,6 @@ export default class Profiles extends Vue {
async updateProfileList() {
const profilesDirectory: string = this.activeProfile.getDirectory();
try {
const profilesDirectoryContents = await fs.readdir(profilesDirectory);
let promises = profilesDirectoryContents.map(async function(file) {
Expand Down
10 changes: 10 additions & 0 deletions src/store/modules/ModalsModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface State {
isDisableModModalOpen: boolean;
isDownloadModModalOpen: boolean;
isGameRunningModalOpen: boolean;
isRenameProfileModalOpen: boolean;
isUninstallModModalOpen: boolean;
uninstallModModalMod: ManifestV2 | null;
}
Expand All @@ -26,6 +27,7 @@ export default {
isDisableModModalOpen: false,
isDownloadModModalOpen: false,
isGameRunningModalOpen: false,
isRenameProfileModalOpen: false,
isUninstallModModalOpen: false,
uninstallModModalMod: null,
}),
Expand Down Expand Up @@ -58,6 +60,10 @@ export default {
state.isGameRunningModalOpen = false;
},

closeRenameProfileModal: function(state: State): void {
state.isRenameProfileModalOpen = false;
},

closeUninstallModModal: function(state: State): void {
state.isUninstallModModalOpen = false;
state.uninstallModModalMod = null;
Expand Down Expand Up @@ -90,6 +96,10 @@ export default {
state.isGameRunningModalOpen = true;
},

openRenameProfileModal: function(state: State): void {
state.isRenameProfileModalOpen = true;
},

openUninstallModModal: function(state: State, mod: ManifestV2): void {
state.uninstallModModalMod = mod;
state.isUninstallModModalOpen = true;
Expand Down
32 changes: 31 additions & 1 deletion src/store/modules/ProfilesModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ActionTree } from "vuex";
import { State as RootState } from "../../store";
import Profile from "../../model/Profile";
import FsProvider from "../../providers/generic/file/FsProvider";
import path from "path";

interface State {
profileList: string[];
Expand Down Expand Up @@ -36,7 +37,7 @@ export const ProfilesModule = {
throw R2Error.fromThrownValue(e, 'Error whilst deleting profile from disk');
}

state.profileList = state.profileList.filter((p: string) => p !== profileName ||p === 'Default')
state.profileList = state.profileList.filter((p: string) => p !== profileName || p === 'Default')
await dispatch('setSelectedProfile', { profileName: 'Default', prewarmCache: true });
},

Expand All @@ -48,5 +49,34 @@ export const ProfilesModule = {
await dispatch('tsMods/prewarmCache', null, { root: true });
}
},

async renameProfile({commit, rootGetters, state, dispatch}, params: { newName: string }) {
const activeProfile: Profile = rootGetters['profile/activeProfile'];
const oldName = activeProfile.getProfileName();

try {
await FsProvider.instance.rename(
path.join(Profile.getDirectory(), oldName),
path.join(Profile.getDirectory(), params.newName)
);
} catch (e) {
throw R2Error.fromThrownValue(e, 'Error whilst renaming a profile on disk');
}
await dispatch('setSelectedProfile', { profileName: params.newName, prewarmCache: false });
await dispatch('updateProfileList');
},

async updateProfileList({commit, rootGetters}) {
const profilesDirectory: string = rootGetters['profile/activeProfile'].getDirectory();

let profilesDirectoryContents = await FsProvider.instance.readdir(profilesDirectory);
let promises = profilesDirectoryContents.map(async function(file) {
return ((await FsProvider.instance.stat(path.join(profilesDirectory, file))).isDirectory() && file.toLowerCase() !== 'default' && file.toLowerCase() !== "_profile_update")
? file : undefined;
});
Promise.all(promises).then((profileList) => {
commit('setProfileList', ["Default", ...profileList.filter(file => file)].sort());
})
}
},
}

0 comments on commit 6bb478c

Please sign in to comment.