Modrinth/apps/frontend/src/components/ui/servers/BackupRenameModal.vue
2025-08-05 11:49:34 +01:00

143 lines
3.7 KiB
Vue

<template>
<NewModal ref="modal" header="Renaming backup" @show="focusInput">
<div class="flex flex-col gap-2 md:w-[600px]">
<label for="backup-name-input">
<span class="text-lg font-semibold text-contrast"> Name </span>
</label>
<input
id="backup-name-input"
ref="input"
v-model="backupName"
type="text"
class="bg-bg-input w-full rounded-lg p-4"
:placeholder="`Backup #${backupNumber}`"
maxlength="48"
/>
<div v-if="nameExists" class="flex items-center gap-1">
<IssuesIcon class="hidden text-orange sm:block" />
<span class="text-sm text-orange">
You already have a backup named '<span class="font-semibold">{{ trimmedName }}</span
>'
</span>
</div>
</div>
<div class="mt-2 flex justify-start gap-2">
<ButtonStyled color="brand">
<button :disabled="isRenaming || nameExists" @click="renameBackup">
<template v-if="isRenaming">
<SpinnerIcon class="animate-spin" />
Renaming...
</template>
<template v-else>
<SaveIcon />
Save changes
</template>
</button>
</ButtonStyled>
<ButtonStyled>
<button @click="hide">
<XIcon />
Cancel
</button>
</ButtonStyled>
</div>
</NewModal>
</template>
<script setup lang="ts">
import { IssuesIcon,SaveIcon, SpinnerIcon, XIcon } from "@modrinth/assets";
import { ButtonStyled, NewModal } from "@modrinth/ui";
import type { Backup } from "@modrinth/utils";
import { computed,nextTick, ref } from "vue";
import type { ModrinthServer } from "~/composables/servers/modrinth-servers.ts";
const props = defineProps<{
server: ModrinthServer;
}>();
const modal = ref<InstanceType<typeof NewModal>>();
const input = ref<HTMLInputElement>();
const backupName = ref("");
const originalName = ref("");
const isRenaming = ref(false);
const currentBackup = ref<Backup | null>(null);
const trimmedName = computed(() => backupName.value.trim());
const nameExists = computed(() => {
if (!props.server.backups?.data || trimmedName.value === originalName.value || isRenaming.value) {
return false;
}
return props.server.backups.data.some(
(backup: Backup) => backup.name.trim().toLowerCase() === trimmedName.value.toLowerCase(),
);
});
const backupNumber = computed(
() => (props.server.backups?.data?.findIndex((b) => b.id === currentBackup.value?.id) ?? 0) + 1,
);
const focusInput = () => {
nextTick(() => {
setTimeout(() => {
input.value?.focus();
}, 100);
});
};
function show(backup: Backup) {
currentBackup.value = backup;
backupName.value = backup.name;
originalName.value = backup.name;
isRenaming.value = false;
modal.value?.show();
}
function hide() {
modal.value?.hide();
}
const renameBackup = async () => {
if (!currentBackup.value) {
addNotification({
type: "error",
title: "Error renaming backup",
text: "Current backup is null",
});
return;
}
if (trimmedName.value === originalName.value) {
hide();
return;
}
isRenaming.value = true;
try {
let newName = trimmedName.value;
if (newName.length === 0) {
newName = `Backup #${backupNumber.value}`;
}
await props.server.backups?.rename(currentBackup.value.id, newName);
hide();
await props.server.refresh();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
addNotification({ type: "error", title: "Error renaming backup", text: message });
} finally {
hide();
isRenaming.value = false;
}
};
defineExpose({
show,
hide,
});
</script>