From b2a4f37425bbbcf8df1ae91d00b4bdedca7ab836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20G=C3=B6bel?= <dgoebel@techfak.uni-bielefeld.de> Date: Tue, 16 Jan 2024 13:55:24 +0100 Subject: [PATCH] Add update resource modal #88 --- src/components/NavbarTop.vue | 1 + src/components/resources/ResourceCard.vue | 18 ++- .../{ => modals}/CreateResourceModal.vue | 0 .../resources/modals/UpdateResourceModal.vue | 129 ++++++++++++++++++ .../{ => modals}/UploadResourceInfoModal.vue | 0 src/stores/resources.ts | 20 +++ src/views/resources/MyResourcesView.vue | 26 +++- 7 files changed, 189 insertions(+), 5 deletions(-) rename src/components/resources/{ => modals}/CreateResourceModal.vue (100%) create mode 100644 src/components/resources/modals/UpdateResourceModal.vue rename src/components/resources/{ => modals}/UploadResourceInfoModal.vue (100%) diff --git a/src/components/NavbarTop.vue b/src/components/NavbarTop.vue index 60447d3..57d8e84 100644 --- a/src/components/NavbarTop.vue +++ b/src/components/NavbarTop.vue @@ -241,6 +241,7 @@ watch( modal-i-d="advancedUsageModal" modal-label="Advanced Usage Modal" v-if="store.authenticated" + size-modifier="lg" > <template v-slot:header> <h3>Advanced Usage</h3> diff --git a/src/components/resources/ResourceCard.vue b/src/components/resources/ResourceCard.vue index ffcedbd..c4bca17 100644 --- a/src/components/resources/ResourceCard.vue +++ b/src/components/resources/ResourceCard.vue @@ -27,12 +27,15 @@ let refreshTimeout: NodeJS.Timeout | undefined = undefined; const emit = defineEmits<{ (e: "click-info", resourceVersion: ResourceVersionOut): void; + (e: "click-update", resource: ResourceOut): void; }>(); const resourceVersionS3Ready = ref<Record<string, boolean>>({}); -const resourceVersions = computed<ResourceVersionOut[]>( - () => props.resource.versions, +const resourceVersions = computed<ResourceVersionOut[]>(() => + [...props.resource.versions].sort((a, b) => + a.created_at < b.created_at ? 1 : -1, + ), ); function checkS3Resource(resourceVersion: ResourceVersionOut) { @@ -92,6 +95,17 @@ onMounted(() => { <div v-else> <span>{{ props.resource.name }}</span> </div> + <button + v-if="props.extended" + :disabled="props.loading" + class="btn btn-primary" + type="button" + data-bs-toggle="modal" + data-bs-target="#updateResourceModal" + @click="emit('click-update', props.resource)" + > + Update + </button> </div> <p class="card-text"> <span v-if="props.loading" class="placeholder-glow" diff --git a/src/components/resources/CreateResourceModal.vue b/src/components/resources/modals/CreateResourceModal.vue similarity index 100% rename from src/components/resources/CreateResourceModal.vue rename to src/components/resources/modals/CreateResourceModal.vue diff --git a/src/components/resources/modals/UpdateResourceModal.vue b/src/components/resources/modals/UpdateResourceModal.vue new file mode 100644 index 0000000..f40a8d1 --- /dev/null +++ b/src/components/resources/modals/UpdateResourceModal.vue @@ -0,0 +1,129 @@ +<script setup lang="ts"> +import { reactive, onMounted, ref } from "vue"; +import BootstrapModal from "@/components/modals/BootstrapModal.vue"; +import { Modal } from "bootstrap"; +import { useResourceStore } from "@/stores/resources"; +import type { ResourceVersionIn, ResourceOut } from "@/client/resource"; +import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; +import { Tooltip } from "bootstrap"; + +const resourceRepository = useResourceStore(); + +const resourceUpdateForm = ref<HTMLFormElement | undefined>(undefined); + +const resourceUpdate = reactive<ResourceVersionIn>({ + release: "", +}); + +const formState = reactive<{ + validated: boolean; + loading: boolean; +}>({ + validated: false, + loading: false, +}); + +const props = defineProps<{ + modalId: string; + resource: ResourceOut; +}>(); + +let updateResourceModal: Modal | null = null; + +function updateResource() { + formState.validated = true; + resourceUpdate.release = resourceUpdate.release.trim(); + if (resourceUpdateForm.value?.checkValidity()) { + formState.loading = true; + resourceRepository + .updateResource(props.resource.resource_id, resourceUpdate) + .then(() => { + updateResourceModal?.hide(); + resourceUpdate.release = ""; + formState.validated = false; + }) + .finally(() => { + formState.loading = false; + }); + } +} + +function modalClosed() { + formState.validated = false; +} + +onMounted(() => { + updateResourceModal = new Modal("#" + props.modalId); + new Tooltip("#tooltip-update-resource-release"); +}); +</script> + +<template> + <bootstrap-modal + :modalID="modalId" + static-backdrop + modal-label="Update Resource Modal" + v-on="{ 'hidden.bs.modal': modalClosed }" + > + <template #header> + Update Resource <b>{{ props.resource.name }}</b></template + > + <template #body> + <form + id="resourceUpdateForm" + :class="{ 'was-validated': formState.validated }" + ref="resourceUpdateForm" + > + <div class="mb-3"> + <label for="resourceUpdateReleaseInput" class="form-label"> + Release + </label> + <div class="input-group"> + <div class="input-group-text"> + <font-awesome-icon icon="fa-solid fa-tag" /> + </div> + <input + class="form-control" + id="resourceUpdateReleaseInput" + required + minlength="3" + maxlength="32" + v-model="resourceUpdate.release" + placeholder="Next release name" + /> + <div + class="input-group-text hover-info" + id="tooltip-update-resource-release" + data-bs-toggle="tooltip" + data-bs-title="The name of the next resource version" + > + <font-awesome-icon icon="fa-solid fa-circle-question" /> + </div> + </div> + </div> + </form> + </template> + <template v-slot:footer> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal"> + Close + </button> + <button + type="submit" + form="resourceUpdateForm" + class="btn btn-primary" + :disabled="formState.loading || !props.resource.resource_id" + @click.prevent="updateResource" + > + <span + v-if="formState.loading" + class="spinner-border spinner-border-sm" + role="status" + aria-hidden="true" + ></span> + Save + </button> + </template> + </bootstrap-modal> +</template> + +<style scoped></style> diff --git a/src/components/resources/UploadResourceInfoModal.vue b/src/components/resources/modals/UploadResourceInfoModal.vue similarity index 100% rename from src/components/resources/UploadResourceInfoModal.vue rename to src/components/resources/modals/UploadResourceInfoModal.vue diff --git a/src/stores/resources.ts b/src/stores/resources.ts index 49516a1..40aa0cf 100644 --- a/src/stores/resources.ts +++ b/src/stores/resources.ts @@ -2,6 +2,7 @@ import { defineStore } from "pinia"; import type { ResourceIn, ResourceOut, + ResourceVersionIn, ResourceVersionOut, } from "@/client/resource"; import { ResourceService, ResourceVersionService } from "@/client/resource"; @@ -96,6 +97,7 @@ export const useResourceStore = defineStore({ undefined ) { this.fetchOwnResource(resourceVersion.resource_id); + return changedResourceVersion; } const versionIndex = this.ownResourceMapping[ changedResourceVersion.resource_id @@ -116,5 +118,23 @@ export const useResourceStore = defineStore({ return changedResourceVersion; }); }, + updateResource( + resource_id: string, + version: ResourceVersionIn, + ): Promise<ResourceVersionOut> { + return ResourceVersionService.resourceVersionRequestResourceVersion( + resource_id, + version, + ).then((versionOut) => { + if (this.ownResourceMapping[versionOut.resource_id] == undefined) { + this.fetchOwnResource(versionOut.resource_id); + return versionOut; + } + this.ownResourceMapping[versionOut.resource_id].versions.push( + versionOut, + ); + return versionOut; + }); + }, }, }); diff --git a/src/views/resources/MyResourcesView.vue b/src/views/resources/MyResourcesView.vue index 11dcacb..e9a3d23 100644 --- a/src/views/resources/MyResourcesView.vue +++ b/src/views/resources/MyResourcesView.vue @@ -3,11 +3,12 @@ import { onMounted, reactive } from "vue"; import { useResourceStore } from "@/stores/resources"; import CardTransitionGroup from "@/components/transitions/CardTransitionGroup.vue"; import ResourceCard from "@/components/resources/ResourceCard.vue"; -import CreateResourceModal from "@/components/resources/CreateResourceModal.vue"; -import UploadResourceInfoModal from "@/components/resources/UploadResourceInfoModal.vue"; +import CreateResourceModal from "@/components/resources/modals/CreateResourceModal.vue"; +import UploadResourceInfoModal from "@/components/resources/modals/UploadResourceInfoModal.vue"; import { useS3KeyStore } from "@/stores/s3keys"; -import type { ResourceVersionOut } from "@/client/resource"; +import type { ResourceVersionOut, ResourceOut } from "@/client/resource"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; +import UpdateResourceModal from "@/components/resources/modals/UpdateResourceModal.vue"; const resourceRepository = useResourceStore(); const s3KeyRepository = useS3KeyStore(); @@ -15,15 +16,28 @@ const s3KeyRepository = useS3KeyStore(); const resourceState = reactive<{ loading: boolean; resourceVersionInfo?: ResourceVersionOut; + updateResource: ResourceOut; }>({ loading: true, resourceVersionInfo: undefined, + updateResource: { + name: "", + description: "", + source: "", + resource_id: "", + versions: [], + maintainer_id: "", + }, }); function setResourceVersionInfo(resourceVersionInfo?: ResourceVersionOut) { resourceState.resourceVersionInfo = resourceVersionInfo; } +function setResourceUpdate(resource: ResourceOut) { + resourceState.updateResource = resource; +} + onMounted(() => { let fetchedResources = false; s3KeyRepository.fetchS3Keys(() => { @@ -43,6 +57,10 @@ onMounted(() => { modal-id="uploadResourceInfoModal" :resource-version="resourceState.resourceVersionInfo" /> + <update-resource-modal + :resource="resourceState.updateResource" + modal-id="updateResourceModal" + /> <div class="row m-2 border-bottom mb-4 justify-content-between align-items-center pb-2" > @@ -90,6 +108,7 @@ onMounted(() => { style="width: 48%" extended @click-info="setResourceVersionInfo" + @click-update="setResourceUpdate" /> </CardTransitionGroup> </div> @@ -110,6 +129,7 @@ onMounted(() => { }" style="min-width: 48%" loading + extended /> </div> </template> -- GitLab