diff --git a/package.json b/package.json index 13050ae8ef793f7ec7e617f05a6bb2fbcba8a7e0..159c10204bd19b74231ee34990ca888540d6a131 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,9 @@ "axios": "^0.26.1", "bootstrap": "^4.6.1", "bootstrap-icons": "^1.8.1", - "bootstrap-vue": "^2.21.2", + "bootstrap-vue": "^2.22.0", "core-js": "^3.21.1", + "deep-object-diff": "^1.1.7", "file-saver": "^2.0.5", "http-status-codes": "^2.2.0", "jose": "^4.6.0", diff --git a/src/App.vue b/src/App.vue index 18d35dfb813d3744da0d8e49a454494dabfb6d92..887be908a5acc56d7dcec4535fda0cbea84fcb98 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,7 +1,7 @@ <template> <div id="app"> <Navbar /> - <LoadingIndicator :show="loading" /> + <LoadingIndicator /> <main> <div class="mr-0 d-flex"> <SidebarMenu class="mr-2" /> @@ -21,9 +21,9 @@ import { defineComponent } from "vue-demi"; // import the main store -import { useMainStore } from "@/store/index"; -import { useProjectStore } from "@/modules/project/store"; -import { useUserStore } from "@/modules/user/store"; +import useMainStore from "@/store/index"; +import useProjectStore from "@/modules/project/store"; +import useUserStore from "@/modules/user/store"; export default defineComponent({ setup() { @@ -39,10 +39,6 @@ export default defineComponent({ }, computed: { - loading(): boolean { - return this.mainStore.coscine.loading.counter > 0; - }, - loggedIn(): boolean { return this.mainStore.loggedIn; }, diff --git a/src/components/BreadCrumbs.vue b/src/components/BreadCrumbs.vue index b36ff3271efa2b2383b8e44340866501c99b5b7f..370f4d86a183b8960819fb94b54894e80732dea6 100644 --- a/src/components/BreadCrumbs.vue +++ b/src/components/BreadCrumbs.vue @@ -23,11 +23,11 @@ import { defineComponent } from "vue-demi"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; // import the project store -import { useProjectStore } from "@/modules/project/store"; +import useProjectStore from "@/modules/project/store"; // import the resource store -import { useResourceStore } from "@/modules/resource/store"; +import useResourceStore from "@/modules/resource/store"; import type { ProjectObject, diff --git a/src/components/ExpiryToast.vue b/src/components/ExpiryToast.vue index 48c28960385a1937eeadce97471fa021ad00c92c..bbd03bda38831b9951f2d126bc3d719f80c3bfa5 100644 --- a/src/components/ExpiryToast.vue +++ b/src/components/ExpiryToast.vue @@ -28,8 +28,8 @@ import { defineComponent } from "@vue/composition-api"; import * as jose from "jose"; import moment from "moment"; -import { useMainStore } from "@/store/index"; -import { useLoginStore } from "@/modules/login/store"; +import useMainStore from "@/store/index"; +import useLoginStore from "@/modules/login/store"; export default defineComponent({ setup() { diff --git a/src/components/LoadingIndicator.vue b/src/components/LoadingIndicator.vue index 5f89cbbcd924fffb147f73d267b18f9ee3e09a93..ab33c3ba8b77a759b1d59862f74cce9e49d23acd 100644 --- a/src/components/LoadingIndicator.vue +++ b/src/components/LoadingIndicator.vue @@ -6,13 +6,38 @@ </template> <script lang="ts"> +import useAdminStore from "@/modules/admin/store"; +import useErrorStore from "@/modules/error/store"; +import useLoginStore from "@/modules/login/store"; +import useMainStore from "@/store"; +import usePidStore from "@/modules/pid/store"; +import useProjectStore from "@/modules/project/store"; +import useResourceStore from "@/modules/resource/store"; +import useSearchStore from "@/modules/search/store"; +import useUserStore from "@/modules/user/store"; + +import { loadingCounterEventHandler } from "@/plugins/loadingCounter"; import { defineComponent } from "vue-demi"; export default defineComponent({ - props: { - show: { - default: false, - type: Boolean, + setup() { + const mainStore = useMainStore(); + + loadingCounterEventHandler(useAdminStore); + loadingCounterEventHandler(useErrorStore); + loadingCounterEventHandler(useLoginStore); + loadingCounterEventHandler(usePidStore); + loadingCounterEventHandler(useProjectStore); + loadingCounterEventHandler(useResourceStore); + loadingCounterEventHandler(useSearchStore); + loadingCounterEventHandler(useUserStore); + + return { mainStore }; + }, + + computed: { + show(): boolean { + return this.mainStore.isLoading; }, }, }); diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index a352d3cb3cc73d45410d316e10fd9fde65473bf3..ffcd08c91ec4ed544633247d1958970475a013f0 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -118,11 +118,11 @@ <script lang="ts"> import { defineComponent } from "vue-demi"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; // import the login store -import { useLoginStore } from "@/modules/login/store"; +import useLoginStore from "@/modules/login/store"; // import the user store -import { useUserStore } from "@/modules/user/store"; +import useUserStore from "@/modules/user/store"; import type { UserObject } from "@coscine/api-client/dist/types/Coscine.Api.User"; export default defineComponent({ diff --git a/src/components/SidebarMenu.vue b/src/components/SidebarMenu.vue index 8c59d3a9ca1ef39b0d2d3742048809a2a034338f..38b90f02db84ef45c2ef8e681c716730e7eed6b9 100644 --- a/src/components/SidebarMenu.vue +++ b/src/components/SidebarMenu.vue @@ -27,11 +27,11 @@ import "bootstrap-icons/font/fonts/bootstrap-icons.woff"; import "bootstrap-icons/font/fonts/bootstrap-icons.woff2"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; // import the project store -import { useProjectStore } from "@/modules/project/store"; +import useProjectStore from "@/modules/project/store"; // import the resource store -import { useResourceStore } from "@/modules/resource/store"; +import useResourceStore from "@/modules/resource/store"; import type { ProjectObject, diff --git a/src/components/banner/Maintenance.vue b/src/components/banner/Maintenance.vue index 9dfebc8af87a984f38eee80d58a725e5290a0fb6..da121db853479694ab7653bcb3b613b9e0aa41a7 100644 --- a/src/components/banner/Maintenance.vue +++ b/src/components/banner/Maintenance.vue @@ -1,9 +1,7 @@ <template> <b-alert v-if="visibility" - :show=" - visibility && maintenance.type !== undefined && maintenance.type !== null - " + :show="show" @dismissed="saveVisibility" dismissible variant="warning" @@ -21,7 +19,7 @@ <script lang="ts"> import { defineComponent } from "vue-demi"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { MaintenanceReturnObject } from "@coscine/api-client/dist/types/Coscine.Api.Notices"; export default defineComponent({ @@ -44,9 +42,12 @@ export default defineComponent({ return ( this.mainStore.coscine.banner.maintenanceVisibility !== this.mainStore.coscine.banner.dateString && - this.mainStore.coscine.banner.dateString !== "" + this.mainStore.coscine.banner.dateString.trim() !== "" ); }, + show(): boolean { + return this.visibility && this.maintenance.type ? true : false; + }, }, watch: { diff --git a/src/components/banner/Pilot.vue b/src/components/banner/Pilot.vue index e552728e7904bb307f9889c7e37fd2d8be7a53fb..e84a8d0d1fd9285a5a6c5171b42c19ce378168ae 100644 --- a/src/components/banner/Pilot.vue +++ b/src/components/banner/Pilot.vue @@ -18,7 +18,7 @@ <script lang="ts"> import { defineComponent } from "vue-demi"; -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { const mainStore = useMainStore(); diff --git a/src/i18n/de.ts b/src/i18n/de.ts index e60091c6250be300fcafd2875a1f47c47681f823..58a8510463457f2c95469d5a30961ec72a36a17a 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -44,6 +44,7 @@ export default { buttons: { addUser: "Benutzer hinzufügen", + archive: "@:(default.archive)", back: "Zurück", cancel: "Abbrechen", close: "Schließen", @@ -64,6 +65,7 @@ export default { save: "Speichern", submit: "Abschicken", tokenCreate: "Zugriffstoken erstellen", + unarchive: "Archivieren rückgängig", } as VueI18n.LocaleMessageObject, default: { diff --git a/src/i18n/en.ts b/src/i18n/en.ts index d3617b7272c55a29e78506cf710f6dbe747808f9..1cf6727f4efcb3dc6343be3cef430461b1442322 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -42,6 +42,7 @@ export default { buttons: { addUser: "Add User", + archive: "@:(default.archive)", back: "Back", cancel: "Cancel", close: "Close", @@ -62,6 +63,7 @@ export default { save: "Save", submit: "Submit", tokenCreate: "Create Access Token", + unarchive: "Unarchive", } as VueI18n.LocaleMessageObject, default: { diff --git a/src/modules/admin/AdminModule.vue b/src/modules/admin/AdminModule.vue index 3c25b41c39608931f89ef6aed06845aa8513515d..bb98cfb53a04fb8a2cfcca9690344c63a0fd9cd1 100644 --- a/src/modules/admin/AdminModule.vue +++ b/src/modules/admin/AdminModule.vue @@ -8,9 +8,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useAdminStore } from "./store"; +import useAdminStore from "./store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/admin/pages/Admin.vue b/src/modules/admin/pages/Admin.vue index 8ab5b95e9e20ccba9955fbbae97661e8930970a5..5f19ae17089fab68d6251813414797d2d9a0e4a3 100644 --- a/src/modules/admin/pages/Admin.vue +++ b/src/modules/admin/pages/Admin.vue @@ -4,9 +4,9 @@ class="container flex flex-col items-center px-5 py-12 mx-auto text-gray-600 body-font md:flex-row" > <div> - <CoscineHeadline :headline="$parent.$t('page.admin.title')" /> + <CoscineHeadline :headline="$t('page.admin.title')" /> <p class="mb-8 leading-relaxed dark:text-white"> - {{ $parent.$t("page.admin.description") }} + {{ $t("page.admin.description") }} </p> <img alt="From Coscine Old" src="@/assets/images/Admin.png" /> </div> @@ -19,9 +19,9 @@ import { defineComponent } from "vue-demi"; import CoscineHeadline from "@/components/CoscineHeadline.vue"; // import the store for current module -import { useAdminStore } from "../store"; +import useAdminStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/error/ErrorModule.vue b/src/modules/error/ErrorModule.vue index 7013d82570e91ea685ddec87a8bab91731f2008a..b0c836e15071ec59543c3d4c2cf60472db4c21a8 100644 --- a/src/modules/error/ErrorModule.vue +++ b/src/modules/error/ErrorModule.vue @@ -8,9 +8,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useErrorStore } from "./store"; +import useErrorStore from "./store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/error/pages/NotFound.vue b/src/modules/error/pages/NotFound.vue index 55316f0bcb5d565233c5a0b765618fe58b8028c9..37431cd9ed7ad6a6b9162e4a1c0e246766041d23 100644 --- a/src/modules/error/pages/NotFound.vue +++ b/src/modules/error/pages/NotFound.vue @@ -4,9 +4,9 @@ class="container flex flex-col items-center px-5 py-12 mx-auto text-gray-600 body-font md:flex-row" > <div> - <CoscineHeadline :headline="$parent.$t('page.notFound.title')" /> + <CoscineHeadline :headline="$t('page.notFound.title')" /> <p class="mb-8 leading-relaxed dark:text-white"> - {{ $parent.$t("page.notFound.description") }} + {{ $t("page.notFound.description") }} </p> <img alt="Oopsie!" src="@/assets/images/404.jpg" /> </div> @@ -19,9 +19,9 @@ import { defineComponent } from "vue-demi"; import CoscineHeadline from "@/components/CoscineHeadline.vue"; // import the store for current module -import { useErrorStore } from "../store"; +import useErrorStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/login/LoginModule.vue b/src/modules/login/LoginModule.vue index 46d5b674558a03894a4f452ace24e014ce003746..9e15fabc356cedc112ad3022da2e4a24c627c12e 100644 --- a/src/modules/login/LoginModule.vue +++ b/src/modules/login/LoginModule.vue @@ -8,9 +8,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useLoginStore } from "./store"; +import useLoginStore from "./store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/login/pages/Login.vue b/src/modules/login/pages/Login.vue index a3559fe5a945fc1c8c269eb283cde1bbba46839c..db12620ac6ab4c97163cab9cf60f40c12d2588d9 100644 --- a/src/modules/login/pages/Login.vue +++ b/src/modules/login/pages/Login.vue @@ -6,9 +6,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useLoginStore } from "../store"; +import useLoginStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/login/store.ts b/src/modules/login/store.ts index 42659cfee8daf791ba112740fd7e49bbf5773f5e..33ca895890673b816945f75f94878a5c644ef67f 100644 --- a/src/modules/login/store.ts +++ b/src/modules/login/store.ts @@ -3,7 +3,7 @@ import { LoginState } from "./types"; import { RawLocation, Route } from "vue-router"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; /* Store variable name is "this.<id>Store" diff --git a/src/modules/pid/PidModule.vue b/src/modules/pid/PidModule.vue index ab3a36d9e49ea248dbda1f43b1d49024cc9614de..8010d3fde3cbcfc7dc0a82f381886d1de1bd2ef5 100644 --- a/src/modules/pid/PidModule.vue +++ b/src/modules/pid/PidModule.vue @@ -8,9 +8,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { usePidStore } from "./store"; +import usePidStore from "./store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/pid/pages/Pid.vue b/src/modules/pid/pages/Pid.vue index 2e4b3c7ed36c65a55840c78004f62745319f8f75..3e863c39311a7a757c59fda22c88e3fbe5a1d716 100644 --- a/src/modules/pid/pages/Pid.vue +++ b/src/modules/pid/pages/Pid.vue @@ -4,9 +4,9 @@ class="container flex flex-col items-center px-5 py-12 mx-auto text-gray-600 body-font md:flex-row" > <div> - <CoscineHeadline :headline="$parent.$t('page.pid.title')" /> + <CoscineHeadline :headline="$t('page.pid.title')" /> <p class="mb-8 leading-relaxed dark:text-white"> - {{ $parent.$t("page.pid.description") }} + {{ $t("page.pid.description") }} </p> </div> </section> @@ -18,9 +18,9 @@ import { defineComponent } from "vue-demi"; import CoscineHeadline from "@/components/CoscineHeadline.vue"; // import the store for current module -import { usePidStore } from "../store"; +import usePidStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/project/ProjectModule.vue b/src/modules/project/ProjectModule.vue index 350259a2114ddced7d98ffb4a7ac0402e3e43026..3666440bc4ab0fe54f51c7c0957d01cff67a0fcc 100644 --- a/src/modules/project/ProjectModule.vue +++ b/src/modules/project/ProjectModule.vue @@ -8,10 +8,10 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useProjectStore } from "./store"; -import { useResourceStore } from "@/modules/resource/store"; +import useProjectStore from "./store"; +import useResourceStore from "@/modules/resource/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; import type { Route } from "vue-router"; diff --git a/src/modules/project/RootProjectModule.vue b/src/modules/project/RootProjectModule.vue index 8e038ec9fbdbab7d2fa4dac476d5068761b033fc..47570e87ca1bfc0f0433ae24abf27c00d7c6a426 100644 --- a/src/modules/project/RootProjectModule.vue +++ b/src/modules/project/RootProjectModule.vue @@ -8,9 +8,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useProjectStore } from "./store"; +import useProjectStore from "./store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/project/i18n/de.ts b/src/modules/project/i18n/de.ts index 860a8fc5cabc5ee8ee951cc4945a60277d461046..c455e76fbd750d337405d455fde78b45a0f233e7 100644 --- a/src/modules/project/i18n/de.ts +++ b/src/modules/project/i18n/de.ts @@ -232,9 +232,9 @@ export default { "@:(form.project.projectKeywords)@:(form.project.labelSymbol)", tagPlaceholder: "Sie können diesen Tag hinzufügen", - projectVisibility: "Sichtbarkeit", - projectVisibilityLabel: - "@:(form.project.projectVisibility)@:(form.project.labelSymbol)", + projectMetadataVisibility: "Sichtbarkeit der Metadaten", + projectMetadataVisibilityLabel: + "@:(form.project.projectMetadataVisibility)@:(form.project.labelSymbol)", projectGrantId: "Grant ID", projectGrantIdHelp: diff --git a/src/modules/project/i18n/en.ts b/src/modules/project/i18n/en.ts index f6ee36874ac81382e8c5d288dcf4781dac3197a3..308050af07536f9f8e620342263f9c058fa60bb4 100644 --- a/src/modules/project/i18n/en.ts +++ b/src/modules/project/i18n/en.ts @@ -227,9 +227,9 @@ export default { "@:(form.project.projectKeywords)@:(form.project.labelSymbol)", tagPlaceholder: "You can add this tag", - projectVisibility: "Visibility", - projectVisibilityLabel: - "@:(form.project.projectVisibility)@:(form.project.labelSymbol)", + projectMetadataVisibility: "Metadata Visibility", + projectMetadataVisibilityLabel: + "@:(form.project.projectMetadataVisibility)@:(form.project.labelSymbol)", projectGrantId: "Grant ID", projectGrantIdHelp: diff --git a/src/modules/project/pages/CreateProject.vue b/src/modules/project/pages/CreateProject.vue index fb20976297295c3d02cf245ca4074585002e3a13..a138e27cec9b37090c100af1eb695c8bbaf7b14f 100644 --- a/src/modules/project/pages/CreateProject.vue +++ b/src/modules/project/pages/CreateProject.vue @@ -1,6 +1,6 @@ <template> <div id="createProject"> - <CoscineHeadline :headline="$parent.$t('page.createProject.title')" /> + <CoscineHeadline :headline="$t('page.createProject.title')" /> <b-row> <div class="col-sm-2" /> <div class="col-sm-8"> @@ -13,7 +13,7 @@ class="m-0" > <b-form-text id="AffiliationMessage"> - {{ $parent.$t("page.createProject.affiliationMessage") }} + {{ $t("page.createProject.affiliationMessage") }} </b-form-text> </b-form-group> @@ -47,7 +47,7 @@ isWaitingForResponse " > - {{ $parent.$t("buttons.submit") }}</b-button + {{ $t("buttons.submit") }}</b-button > </b-form-group> </b-form> @@ -55,9 +55,7 @@ <!-- Loading Spinner on Submit --> <LoadingSpinner :isWaitingForResponse="isWaitingForResponse" - :textAfter=" - $parent.$t('page.createProject.loadingSpinnerProjectCreation') - " + :textAfter="$t('page.createProject.loadingSpinnerProjectCreation')" /> </div> <div class="col-sm-2" /> @@ -82,10 +80,10 @@ import type { } from "@coscine/api-client/dist/types/Coscine.Api.Project"; // import the store for current module -import { useProjectStore } from "../store"; +import useProjectStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; -import { useResourceStore } from "@/modules/resource/store"; +import useMainStore from "@/store/index"; +import useResourceStore from "@/modules/resource/store"; import { navigateToProject } from "@/router"; export default defineComponent({ @@ -200,8 +198,8 @@ export default defineComponent({ this.isWaitingForResponse = false; // On Failure this.makeToast( - this.$parent.$t("toast.onSave.failure.message").toString(), - this.$parent.$t("toast.onSave.failure.title").toString(), + this.$t("toast.onSave.failure.message").toString(), + this.$t("toast.onSave.failure.title").toString(), "danger" ); } diff --git a/src/modules/project/pages/ListProjects.vue b/src/modules/project/pages/ListProjects.vue index aeb91ad6b8201bf62c8858c1e2fa0800792386af..536f2e4e90d26918cb6139c54e012b64e4ae1aca 100644 --- a/src/modules/project/pages/ListProjects.vue +++ b/src/modules/project/pages/ListProjects.vue @@ -1,10 +1,10 @@ <template> <div id="project"> - <CoscineHeadline :headline="$parent.$tc('page.listProjects.title', 0)" /> + <CoscineHeadline :headline="$tc('page.listProjects.title', 0)" /> <div class="list"> <b-card-group deck> <CoscineCard - :title="$parent.$t('page.listProjects.addProject')" + :title="$t('page.listProjects.addProject')" type="create" :to="toCreateProject()" @open-card="openCreateProject($event)" @@ -28,9 +28,9 @@ import { RawLocation } from "vue-router"; import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; // import the store for current module -import { useProjectStore } from "../store"; +import useProjectStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/project/pages/Members.vue b/src/modules/project/pages/Members.vue index 8aaab28042bda8eb4f14d9fa0fe59d9d32bb4f66..0791f1e36c677b10be76a3c3d627ee331cd9ecfb 100644 --- a/src/modules/project/pages/Members.vue +++ b/src/modules/project/pages/Members.vue @@ -1,6 +1,6 @@ <template> <div id="users"> - <CoscineHeadline :headline="$parent.$t('page.members.title')" /> + <CoscineHeadline :headline="$t('page.members.title')" /> <div class="UserManagement"> <!-- Search Row --> @@ -17,7 +17,7 @@ <b-col> <b-tabs content-class="mt-2"> <!-- Project Members --> - <b-tab :title="$parent.$t('page.members.membersTabTitle')"> + <b-tab :title="$t('page.members.membersTabTitle')"> <MembersTable id="userTable" :headers="memberHeaders" @@ -25,10 +25,10 @@ :filter="filter" :busy="isBusy" :roles="roles" - :emptyText="$parent.$t('page.members.emptyTableText')" - :emptyFilteredText="$parent.$t('page.members.emptyFilterText')" - :deleteText="$parent.$t('buttons.delete')" - :removeText="$parent.$t('buttons.remove')" + :emptyText="$t('page.members.emptyTableText')" + :emptyFilteredText="$t('page.members.emptyFilterText')" + :deleteText="$t('buttons.delete')" + :removeText="$t('buttons.remove')" :ownerCount="ownerCount" :ownerRole="ownerRole" @tableFilteredRows="onFilteredRows" @@ -38,7 +38,7 @@ </b-tab> <!-- Invited Users --> - <b-tab :title="$parent.$t('page.members.externalUsersTabTitle')"> + <b-tab :title="$t('page.members.externalUsersTabTitle')"> <MembersTable id="invitationTable" :headers="invitationHeaders" @@ -46,10 +46,10 @@ :filter="filter" :busy="isBusy" :roles="roles" - :emptyText="$parent.$t('page.members.emptyTableText')" - :emptyFilteredText="$parent.$t('page.members.emptyFilterText')" - :deleteText="$parent.$t('buttons.delete')" - :removeText="$parent.$t('buttons.remove')" + :emptyText="$t('page.members.emptyTableText')" + :emptyFilteredText="$t('page.members.emptyFilterText')" + :deleteText="$t('buttons.delete')" + :removeText="$t('buttons.remove')" :ownerCount="ownerCount" :ownerRole="ownerRole" sort-by="email" @@ -120,10 +120,10 @@ import InvitationPendingModal from "./components/modals/InvitationPendingModal.v import InviteUserModal from "./components/modals/InviteUserModal.vue"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; // import the store for current module -import { useProjectStore } from "../store"; -import { useUserStore } from "@/modules/user/store"; +import useProjectStore from "../store"; +import useUserStore from "@/modules/user/store"; import type { InvitationReturnObject, @@ -197,27 +197,27 @@ export default defineComponent({ // header text react on language changes. return [ { - label: this.$parent.$t("page.members.firstName"), + label: this.$t("page.members.firstName"), key: "user.givenname", sortable: true, }, { - label: this.$parent.$t("page.members.lastName"), + label: this.$t("page.members.lastName"), key: "user.surname", sortable: true, }, { - label: this.$parent.$t("page.members.email"), + label: this.$t("page.members.email"), key: "user.emailAddress", sortable: true, }, { - label: this.$parent.$t("page.members.role"), + label: this.$t("page.members.role"), key: "role", sortable: true, }, { - label: this.$parent.$t("page.members.actions"), + label: this.$t("page.members.actions"), key: "memberActions", sortable: true, }, @@ -228,22 +228,22 @@ export default defineComponent({ // header text react on language changes. return [ { - label: this.$parent.$t("page.members.email"), + label: this.$t("page.members.email"), key: "userMail", sortable: true, }, { - label: this.$parent.$t("page.members.role"), + label: this.$t("page.members.role"), key: "roleId", sortable: true, }, { - label: this.$parent.$t("page.members.status"), + label: this.$t("page.members.status"), key: "expiration", sortable: true, }, { - label: this.$parent.$t("page.members.actions"), + label: this.$t("page.members.actions"), key: "invitationActions", sortable: true, }, @@ -284,12 +284,12 @@ export default defineComponent({ await this.projectStore.retrieveInvitations(this.project); this.makeToast( toastText, - this.$parent.$t("page.members.userManagement").toString() + this.$t("page.members.userManagement").toString() ); } catch { this.makeToast( errorText, - this.$parent.$t("page.members.userManagement").toString(), + this.$t("page.members.userManagement").toString(), "danger" ); } @@ -339,7 +339,7 @@ export default defineComponent({ await this.projectStore.retrieveInvitations(this.project); this.makeToast( text, - this.$parent.$t("page.members.userManagement").toString() + this.$t("page.members.userManagement").toString() ); } else { const errorMsg = this.$parent @@ -350,7 +350,7 @@ export default defineComponent({ this.makeToast( errorMsg, - this.$parent.$t("page.members.userManagement").toString(), + this.$t("page.members.userManagement").toString(), "danger" ); } @@ -396,10 +396,7 @@ export default defineComponent({ await this.projectStore.storeProjectRole(projectRole); await this.getProjectRoles(); - this.makeToast( - text, - this.$parent.$t("page.members.userManagement").toString() - ); + this.makeToast(text, this.$t("page.members.userManagement").toString()); }, getRoleNameFromId(roleId: string): string | null | undefined { @@ -435,10 +432,7 @@ export default defineComponent({ await this.projectStore.storeProjectRole(projectRole); await this.getProjectRoles(); - this.makeToast( - text, - this.$parent.$t("page.members.userManagement").toString() - ); + this.makeToast(text, this.$t("page.members.userManagement").toString()); if (callback) { callback(); } @@ -493,10 +487,7 @@ export default defineComponent({ .toString(); await this.projectStore.deleteProjectRole(this.candidateForDeletion); await this.getProjectRoles(); - this.makeToast( - text, - this.$parent.$t("page.members.userManagement").toString() - ); + this.makeToast(text, this.$t("page.members.userManagement").toString()); this.isDeleteUserModalVisible = false; }, diff --git a/src/modules/project/pages/ProjectPage.vue b/src/modules/project/pages/ProjectPage.vue index d5754252fa110c382bb4154740156fc9cd6c9cdb..a122dd17a8f01f3ee2f2bad8fa08bf331f34c51a 100644 --- a/src/modules/project/pages/ProjectPage.vue +++ b/src/modules/project/pages/ProjectPage.vue @@ -71,10 +71,10 @@ import type { } from "@coscine/api-client/dist/types/Coscine.Api.Project"; // import the store for current module -import { useProjectStore } from "../store"; +import useProjectStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; -import { useResourceStore } from "@/modules/resource/store"; +import useMainStore from "@/store/index"; +import useResourceStore from "@/modules/resource/store"; export default defineComponent({ setup() { diff --git a/src/modules/project/pages/Quota.vue b/src/modules/project/pages/Quota.vue index c76a738d53d1cc0b6131afcbb3d9606147f118c9..2b1374e840835510660165ae109faa8695cbe2cf 100644 --- a/src/modules/project/pages/Quota.vue +++ b/src/modules/project/pages/Quota.vue @@ -3,14 +3,14 @@ <b-row> <b-col> <!-- Quota Management --> - <CoscineHeadline :headline="$parent.$t('page.quota.headline')" /> + <CoscineHeadline :headline="$t('page.quota.headline')" /> <!-- Dropdowns Row --> <coscine-form-group> <b-row> <!-- Project Dropdown --> <b-col> - {{ $parent.$t("page.quota.projectLabel") }} + {{ $t("page.quota.projectLabel") }} <b-form-select :options="projects" value-field="id" @@ -20,7 +20,7 @@ > <template #first> <b-form-select-option :value="null" disabled>{{ - $parent.$t("page.quota.emptyProjectSelect") + $t("page.quota.emptyProjectSelect") }}</b-form-select-option> </template> </b-form-select> @@ -28,7 +28,7 @@ <!-- Resource Type Dropdown --> <b-col> - {{ $parent.$t("page.quota.resourceTypeLabel") }} + {{ $t("page.quota.resourceTypeLabel") }} <b-form-select v-model="selectedResourceTypeId" :options="resourceTypesSorted" @@ -37,7 +37,7 @@ > <template #first> <b-form-select-option :value="null" disabled>{{ - $parent.$t("page.quota.emptyResourceTypeSelect") + $t("page.quota.emptyResourceTypeSelect") }}</b-form-select-option> </template> </b-form-select> @@ -50,9 +50,9 @@ class="projectQuotaSlider" :label=" selectedQuota.allocated + - $parent.$t('page.quota.rangeText1').toString() + + $t('page.quota.rangeText1').toString() + selectedQuota.maximum + - $parent.$t('page.quota.rangeText2').toString() + $t('page.quota.rangeText2').toString() " :disabled=" selectedResourceTypeInformation && @@ -60,7 +60,7 @@ " > <div class="rangeVisual"> - {{ totalAllocatedSpace }}{{ $parent.$t("page.quota.gb") }} + {{ totalAllocatedSpace }}{{ $t("page.quota.gb") }} <b-form-input v-model="selectedQuota.allocated" type="range" @@ -69,7 +69,7 @@ :disabled="storingQuota" @change="changeProjectQuota" ></b-form-input> - {{ selectedQuota.maximum }}{{ $parent.$t("page.quota.gb") }} + {{ selectedQuota.maximum }}{{ $t("page.quota.gb") }} </div> </coscine-form-group> @@ -78,17 +78,17 @@ <b-col> <b-button class="float-right" - :href="$parent.$t('page.quota.moreHelpLink')" + :href="$t('page.quota.moreHelpLink')" target="_blank" > - {{ $parent.$t("page.quota.moreHelp") }} + {{ $t("page.quota.moreHelp") }} </b-button> </b-col> </b-row> <div class="h-divider" /> <!-- Resources --> - <CoscineHeadline :headline="$parent.$t('page.quota.resources')" /> + <CoscineHeadline :headline="$t('page.quota.resources')" /> <!-- Resources Table --> <coscine-form-group> @@ -99,33 +99,33 @@ show-empty :empty-text=" selectedResourceTypeId !== '' - ? $parent.$t('page.quota.emptyTableText') - : $parent.$t('page.quota.noResourceTypeChoosen') + ? $t('page.quota.emptyTableText') + : $t('page.quota.noResourceTypeChoosen') " > <!-- Resource (Name) Column Header --> <template #head(name)> - <span>{{ $parent.$t("page.quota.resource") }}</span> + <span>{{ $t("page.quota.resource") }}</span> </template> <!-- Reserved Column Header --> <template #head(allocated)> - <span>{{ $parent.$t("page.quota.allocated") }}</span> + <span>{{ $t("page.quota.allocated") }}</span> </template> <!-- Used Column Header --> <template #head(used)> - <span>{{ $parent.$t("page.quota.used") }}</span> + <span>{{ $t("page.quota.used") }}</span> </template> <!-- Adjust Quota Column Header --> <template #head(adjust)> - <span>{{ $parent.$t("page.quota.adjustQuota") }}</span> + <span>{{ $t("page.quota.adjustQuota") }}</span> </template> <!-- Reserved Cell Contents --> <template #cell(allocated)="data"> - {{ data.item.allocated }}{{ $parent.$t("page.quota.gb") }} + {{ data.item.allocated }}{{ $t("page.quota.gb") }} </template> <!-- Used Cell Contents --> @@ -136,7 +136,7 @@ <!-- Adjust Quota Slider Contents --> <template #cell(adjust)="data"> <div class="rangeVisual"> - {{ data.item.used }}{{ $parent.$t("page.quota.gb") }} + {{ data.item.used }}{{ $t("page.quota.gb") }} <b-form-input v-model="data.item.allocated" type="range" @@ -145,7 +145,7 @@ :disabled="storingQuota" @change="changeResourceQuota(data.item)" /> - {{ data.item.maximum }}{{ $parent.$t("page.quota.gb") }} + {{ data.item.maximum }}{{ $t("page.quota.gb") }} </div> </template> @@ -153,7 +153,7 @@ <template #table-busy> <div class="text-center text-danger my-2"> <b-spinner class="align-middle"></b-spinner> - <strong>{{ $parent.$t("page.quota.loading") }}</strong> + <strong>{{ $t("page.quota.loading") }}</strong> </div> </template> </b-table> @@ -167,10 +167,10 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useProjectStore } from "../store"; -import { useResourceStore } from "@/modules/resource/store"; +import useProjectStore from "../store"; +import useResourceStore from "@/modules/resource/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { ProjectObject, diff --git a/src/modules/project/pages/Settings.vue b/src/modules/project/pages/Settings.vue index ac3b4494d3361a534af467bbccbea98856eb3a73..a9fd4a1f15911bc5ab89b6237aa9ae37d33a3d5d 100644 --- a/src/modules/project/pages/Settings.vue +++ b/src/modules/project/pages/Settings.vue @@ -1,6 +1,6 @@ <template> <div id="project"> - <CoscineHeadline :headline="$parent.$t('sidebar.projectSettings')" /> + <CoscineHeadline :headline="$t('sidebar.projectSettings')" /> <b-row> <div class="col-sm-2" /> <div class="col-sm-8"> @@ -38,7 +38,7 @@ isWaitingForResponse " > - {{ $parent.$t("buttons.submit") }}</b-button + {{ $t("buttons.submit") }}</b-button > <!-- Delete Button --> @@ -48,7 +48,7 @@ class="float-left" @click.prevent="isDeleteModalVisible = true" > - {{ $parent.$t("buttons.delete") }}</b-button + {{ $t("buttons.delete") }}</b-button > </b-form-group> </b-form> @@ -79,9 +79,9 @@ import FormMetadata from "./components/FormMetadata.vue"; import DeleteProjectModal from "./components/modals/DeleteProjectModal.vue"; // import the store for current module -import { useProjectStore } from "../store"; +import useProjectStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { ProjectObject, @@ -256,14 +256,14 @@ export default defineComponent({ // Refresh the project information in the store await this.projectStore.refreshProjectInformation(this.project); this.makeToast( - this.$parent.$t("toast.onSave.success.message").toString(), - this.$parent.$t("toast.onSave.success.title").toString() + this.$t("toast.onSave.success.message").toString(), + this.$t("toast.onSave.success.title").toString() ); } else { // On Failure this.makeToast( - this.$parent.$t("toast.onSave.failure.message").toString(), - this.$parent.$t("toast.onSave.failure.title").toString(), + this.$t("toast.onSave.failure.message").toString(), + this.$t("toast.onSave.failure.title").toString(), "danger" ); } diff --git a/src/modules/project/pages/components/FormMetadata.vue b/src/modules/project/pages/components/FormMetadata.vue index 7d3c8fcb89265edbb75a28298e0d6672c4773431..af92c2fc22390a1038fced1c16a33440d5a9e1e3 100644 --- a/src/modules/project/pages/components/FormMetadata.vue +++ b/src/modules/project/pages/components/FormMetadata.vue @@ -1,9 +1,7 @@ <template> <div> <!-- Project Metadata Section --> - <CoscineHeadline - :headline="$parent.$parent.$t('form.project.activatedImportFromParent')" - /> + <CoscineHeadline :headline="$t('form.project.activatedImportFromParent')" /> <!-- Copy Metadata --> <coscine-form-group @@ -11,7 +9,7 @@ :mandatory="false" labelFor="CopyData" :label=" - $parent.$parent.$t('form.project.copyMetadataLabel', { + $t('form.project.copyMetadataLabel', { project: parentProject.projectName, }) " @@ -26,7 +24,7 @@ @click.prevent="copyMetadataFromParent" :disabled="disabled" > - {{ $parent.$parent.$t("buttons.copyMetadata") }}</b-button + {{ $t("buttons.copyMetadata") }}</b-button > </coscine-form-group> @@ -34,9 +32,7 @@ <coscine-form-group :mandatory="true" labelFor="PrincipleInvestigators" - :label=" - $parent.$parent.$t('form.project.projectPrincipleInvestigatorsLabel') - " + :label="$t('form.project.projectPrincipleInvestigatorsLabel')" :isLoading="isLoading" type="input" > @@ -48,9 +44,7 @@ ? !$v.projectForm.principleInvestigators.$error : null " - :placeholder=" - $parent.$parent.$t('form.project.projectPrincipleInvestigators') - " + :placeholder="$t('form.project.projectPrincipleInvestigators')" :maxlength=" maxLengthFromValidation($v.projectForm.principleInvestigators) " @@ -59,7 +53,7 @@ /> <div class="invalid-tooltip"> {{ - $parent.$parent.$t("form.project.projectPrincipleInvestigatorsHelp", { + $t("form.project.projectPrincipleInvestigatorsHelp", { maxLength: maxLengthFromValidation( $v.projectForm.principleInvestigators ), @@ -72,7 +66,7 @@ <coscine-form-group :mandatory="true" labelFor="StartDate" - :label="$parent.$parent.$t('form.project.projectStartLabel')" + :label="$t('form.project.projectStartLabel')" :isLoading="isLoading" type="input" > @@ -89,7 +83,7 @@ required calendar-width="100%" start-weekday="1" - :placeholder="$parent.$parent.$t('form.project.projectStart')" + :placeholder="$t('form.project.projectStart')" /> </coscine-form-group> @@ -97,7 +91,7 @@ <coscine-form-group :mandatory="true" labelFor="EndDate" - :label="$parent.$parent.$t('form.project.projectEndLabel')" + :label="$t('form.project.projectEndLabel')" :isLoading="isLoading" type="input" > @@ -113,7 +107,7 @@ required calendar-width="100%" start-weekday="1" - :placeholder="$parent.$parent.$t('form.project.projectEnd')" + :placeholder="$t('form.project.projectEnd')" /> </coscine-form-group> @@ -121,7 +115,7 @@ <coscine-form-group :mandatory="true" labelFor="Discipline" - :label="$parent.$parent.$t('form.project.projectDisciplineLabel')" + :label="$t('form.project.projectDisciplineLabel')" :isLoading="isLoading" type="input" > @@ -134,7 +128,7 @@ :hide-selected="true" :label="disciplineLabel" :track-by="disciplineLabel" - :placeholder="$parent.$parent.$t('form.project.projectDiscipline')" + :placeholder="$t('form.project.projectDiscipline')" > <template #singleLabel(props)> <div :disabled="disabled"> @@ -153,7 +147,7 @@ <coscine-form-group :mandatory="true" labelFor="Organization" - :label="$parent.$parent.$t('form.project.projectOrganizationLabel')" + :label="$t('form.project.projectOrganizationLabel')" :isLoading="isLoading" type="input" > @@ -167,7 +161,7 @@ :hide-selected="true" label="displayName" track-by="url" - :placeholder="$parent.$parent.$t('form.project.projectOrganization')" + :placeholder="$t('form.project.projectOrganization')" @search-change="retrieveOrganizations" > <template #singleLabel(props)> @@ -181,10 +175,10 @@ </div> </template> <template #noOptions> - {{ $parent.$parent.$t("form.project.projectOrganizationNoOptions") }} + {{ $t("form.project.projectOrganizationNoOptions") }} </template> <template #noResult> - {{ $parent.$parent.$t("form.project.projectOrganizationNoResult") }} + {{ $t("form.project.projectOrganizationNoResult") }} </template> </multiselect> </coscine-form-group> @@ -192,7 +186,7 @@ <!-- Project Keywords --> <coscine-form-group labelFor="Keywords" - :label="$parent.$parent.$t('form.project.projectKeywordsLabel')" + :label="$t('form.project.projectKeywordsLabel')" :isLoading="isLoading" type="input" > @@ -201,26 +195,24 @@ v-model="selectedKeyword" :disabled="disabled" :options="selectedKeyword" - :placeholder=" - $parent.$parent.$t('form.project.projectKeywordsPlaceholder') - " + :placeholder="$t('form.project.projectKeywordsPlaceholder')" :multiple="true" :taggable="true" :max="limitKeywords(selectedKeyword)" - :tag-placeholder="$parent.$parent.$t('form.project.tagPlaceholder')" + :tag-placeholder="$t('form.project.tagPlaceholder')" @tag="addTag" @remove="$v.projectForm.keywords.$touch()" > <template #maxElements> {{ - $parent.$parent.$t("form.project.projectKeywordsHelp", { + $t("form.project.projectKeywordsHelp", { maxLength: maxLengthFromValidation($v.projectForm.keywords), }) }} </template> <template #noOptions> {{ - $parent.$parent.$t("form.project.projectKeywordsEmpty", { + $t("form.project.projectKeywordsEmpty", { maxLength: maxLengthFromValidation($v.projectForm.keywords), }) }} @@ -232,7 +224,7 @@ <coscine-form-group :mandatory="true" labelFor="Visibility" - :label="$parent.$parent.$t('form.project.projectVisibilityLabel')" + :label="$t('form.project.projectMetadataVisibilityLabel')" :isLoading="isLoading" type="input" > @@ -251,7 +243,7 @@ <!-- Grant ID --> <coscine-form-group labelFor="GrantId" - :label="$parent.$parent.$t('form.project.projectGrantIdLabel')" + :label="$t('form.project.projectGrantIdLabel')" :isLoading="isLoading" type="input" > @@ -261,14 +253,14 @@ :state=" $v.projectForm.grantId.$dirty ? !$v.projectForm.grantId.$error : null " - :placeholder="$parent.$parent.$t('form.project.projectGrantId')" + :placeholder="$t('form.project.projectGrantId')" :maxlength="maxLengthFromValidation($v.projectForm.grantId)" required :disabled="disabled" /> <div class="invalid-tooltip"> {{ - $parent.$parent.$t("form.project.projectGrantIdHelp", { + $t("form.project.projectGrantIdHelp", { maxLength: maxLengthFromValidation($v.projectForm.grantId), }) }} @@ -292,9 +284,9 @@ import type { } from "@coscine/api-client/dist/types/Coscine.Api.Project"; // import the store for current module -import { useProjectStore } from "../../store"; +import useProjectStore from "../../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { @@ -404,7 +396,7 @@ export default defineComponent({ this.onProjectLoaded(); }, visibilities() { - // Used in Create Project + // Used in Project Create if (!this.currentProject) { this.setDefaultVisibility(); } diff --git a/src/modules/project/pages/components/FormNaming.vue b/src/modules/project/pages/components/FormNaming.vue index e37d477417ceb5ce522465e06ccac9cc3c8ec045..e2be4519bdb0f1fe21f6473ffdb35f21b264de7a 100644 --- a/src/modules/project/pages/components/FormNaming.vue +++ b/src/modules/project/pages/components/FormNaming.vue @@ -4,7 +4,7 @@ <CoscineFormGroup :mandatory="true" labelFor="ProjectName" - :label="$parent.$parent.$t('form.project.projectNameLabel')" + :label="$t('form.project.projectNameLabel')" :isLoading="isLoading" type="input" > @@ -17,14 +17,14 @@ ? !$v.projectForm.projectName.$error : null " - :placeholder="$parent.$parent.$t('form.project.projectName')" + :placeholder="$t('form.project.projectName')" :maxlength="maxLengthFromValidation($v.projectForm.projectName)" required :disabled="disabled" /> <div class="invalid-tooltip"> {{ - $parent.$parent.$t("form.project.projectNameHelp", { + $t("form.project.projectNameHelp", { maxLength: maxLengthFromValidation($v.projectForm.projectName), }) }} @@ -35,7 +35,7 @@ <CoscineFormGroup :mandatory="true" labelFor="DisplayName" - :label="$parent.$parent.$t('form.project.displayNameLabel')" + :label="$t('form.project.displayNameLabel')" :isLoading="isLoading" type="input" > @@ -48,14 +48,14 @@ ? !$v.projectForm.displayName.$error : null " - :placeholder="$parent.$parent.$t('form.project.displayName')" + :placeholder="$t('form.project.displayName')" :maxlength="maxLengthFromValidation($v.projectForm.displayName)" required :disabled="disabled" /> <div class="invalid-tooltip"> {{ - $parent.$parent.$t("form.project.displayNameHelp", { + $t("form.project.displayNameHelp", { maxLength: maxLengthFromValidation($v.projectForm.displayName), }) }} @@ -66,7 +66,7 @@ <CoscineFormGroup :mandatory="true" labelFor="Description" - :label="$parent.$parent.$t('form.project.projectDescriptionLabel')" + :label="$t('form.project.projectDescriptionLabel')" :isLoading="isLoading" type="input" > @@ -78,14 +78,14 @@ ? !$v.projectForm.description.$error : null " - :placeholder="$parent.$parent.$t('form.project.projectDescription')" + :placeholder="$t('form.project.projectDescription')" :maxlength="maxLengthFromValidation($v.projectForm.description)" required :disabled="disabled" /> <div class="invalid-tooltip"> {{ - $parent.$parent.$t("form.project.projectDescriptionHelp", { + $t("form.project.projectDescriptionHelp", { maxLength: maxLengthFromValidation($v.projectForm.description), }) }} diff --git a/src/modules/project/pages/components/MembersList.vue b/src/modules/project/pages/components/MembersList.vue index e8c2fe8b1fced3a008a4314a54c09cd6fda9c113..540f88afbc6ec61b94c7fa7dd4530dbb8aeeeb65 100644 --- a/src/modules/project/pages/components/MembersList.vue +++ b/src/modules/project/pages/components/MembersList.vue @@ -1,8 +1,6 @@ <template> <div> - <CoscineHeadline - :headline="$parent.$parent.$tc('page.project.member', 2)" - /> + <CoscineHeadline :headline="$tc('page.project.member', 2)" /> <!-- Members --> <div class="list"> <div class="user-complete-name-list container-fluid"> @@ -37,8 +35,8 @@ v-b-tooltip.hover.bottom :title=" ownerCount < 2 && isOwner - ? $parent.$parent.$t('page.project.members.tooltipDisabled') - : $parent.$parent.$t('page.project.members.tooltip') + ? $t('page.project.members.tooltipDisabled') + : $t('page.project.members.tooltip') " >{{ $t("buttons.leave") }}</b-button > @@ -49,7 +47,7 @@ <!-- Manage Members Link --> <div v-if="isOwner" class="mt-2"> <router-link :to="{ name: 'project-members' }"> - {{ $parent.$parent.$t("page.project.members.toProjectMembers") }} + {{ $t("page.project.members.toProjectMembers") }} </router-link> </div> @@ -59,7 +57,7 @@ v-model="leaveModal" hide-footer :title=" - $parent.$parent.$t('page.project.members.modal.title', { + $t('page.project.members.modal.title', { name: project.displayName, }) " @@ -67,7 +65,7 @@ <!-- Leave Modal Body --> <div class="mb-3"> {{ - $parent.$parent.$t("page.project.members.modal.body", { + $t("page.project.members.modal.body", { name: project.displayName, }) }} @@ -80,10 +78,10 @@ @click="leaveModal = false" ref="modalLeaveProjectCancel" autofocus - >{{ $parent.$parent.$t("buttons.cancel") }}</b-button + >{{ $t("buttons.cancel") }}</b-button > <b-button class="float-Left" variant="danger" @click="leaveProject">{{ - $parent.$parent.$t("buttons.leave") + $t("buttons.leave") }}</b-button> </b-modal> </div> @@ -100,10 +98,10 @@ import type { } from "@coscine/api-client/dist/types/Coscine.Api.Project"; // import the store for current module -import { useProjectStore } from "../../store"; +import useProjectStore from "../../store"; // import the main store -import { useMainStore } from "@/store/index"; -import { useUserStore } from "@/modules/user/store"; +import useMainStore from "@/store/index"; +import useUserStore from "@/modules/user/store"; export default defineComponent({ setup() { diff --git a/src/modules/project/pages/components/MembersTable.vue b/src/modules/project/pages/components/MembersTable.vue index c8106b8ece61fd5380534085d68c72ec634af260..29d748c6a610bd95f52b483f754afed10efaa3e3 100644 --- a/src/modules/project/pages/components/MembersTable.vue +++ b/src/modules/project/pages/components/MembersTable.vue @@ -99,7 +99,7 @@ size="sm" @click="resendInvitation(selectedInvitation.item)" :disabled="!checkExpiration(selectedInvitation.item.expiration)" - >{{ $parent.$parent.$parent.$parent.$t("buttons.resend") }} + >{{ $t("buttons.resend") }} </b-button> </div> </template> @@ -205,13 +205,9 @@ export default defineComponent({ }, getStatus(expiration: string) { if (this.checkExpiration(expiration)) { - return this.$parent.$parent.$parent.$parent.$t( - "page.members.expiredStatus" - ); + return this.$t("page.members.expiredStatus"); } else { - return this.$parent.$parent.$parent.$parent.$t( - "page.members.pendingStatus" - ); + return this.$t("page.members.pendingStatus"); } }, revokeInvitation(selectedInvitation: InvitationReturnObject) { diff --git a/src/modules/project/pages/components/UserSearchRow.vue b/src/modules/project/pages/components/UserSearchRow.vue index 2dfe5c9388d76d4fd8fb68b03aafeaae864254ea..180d78e32b52416b2ee15acb97a4e3e5d783604b 100644 --- a/src/modules/project/pages/components/UserSearchRow.vue +++ b/src/modules/project/pages/components/UserSearchRow.vue @@ -7,9 +7,7 @@ <b-col sm="5" @keydown.enter.prevent.self="" id="firstCol"> <v-select v-model="selectedAddingUser" - :placeholder=" - $parent.$parent.$t('page.members.searchUserPlaceholder') - " + :placeholder="$t('page.members.searchUserPlaceholder')" :class="{ adaptSelect: true, 'no-results': queriedUsers.length === 0, @@ -22,24 +20,22 @@ label="displayName" > <template v-if="searchString === ''" slot="no-options"> - {{ $parent.$parent.$t("page.members.pleaseTypeSomething") }} + {{ $t("page.members.pleaseTypeSomething") }} </template> <template v-else-if="validEmail" slot="no-options"> - {{ $parent.$parent.$t("page.members.searchEmailInvite") }} + {{ $t("page.members.searchEmailInvite") }} </template> <template v-else-if="searchString.length < 3" slot="no-options"> - {{ - $parent.$parent.$t("page.members.searchNotEnoughCharacters") - }} + {{ $t("page.members.searchNotEnoughCharacters") }} </template> <template v-else slot="no-options"> - {{ $parent.$parent.$t("page.members.noUserOptions") }} + {{ $t("page.members.noUserOptions") }} </template> <template slot="option" slot-scope="option"> <div class="d-center"> <span v-if="validEmail"> {{ - $parent.$parent.$t("page.members.inviteUserCaption", { + $t("page.members.inviteUserCaption", { displayName: option.displayName, }) }} @@ -48,7 +44,7 @@ {{ option.displayName }} </span> <span v-if="option.hasProjectRole">{{ - $parent.$parent.$t("page.members.alreadyGotRole") + $t("page.members.alreadyGotRole") }}</span> </div> </template> @@ -86,7 +82,7 @@ name="inviteUserToProject" @click="prepareInvitation(newUserRole)" :disabled="!validInvitation" - >{{ $parent.$parent.$t("buttons.invite") }}</b-button + >{{ $t("buttons.invite") }}</b-button > <!-- Import --> <b-button @@ -94,12 +90,12 @@ name="addUserToProject" @click="addUser(newUserRole)" :disabled="!validSelection" - >{{ $parent.$parent.$t("buttons.addUser") }}</b-button + >{{ $t("buttons.addUser") }}</b-button > <b-button name="importUser" @click="$bvModal.show('importUserModal')" - >{{ $parent.$parent.$t("buttons.import") }}</b-button + >{{ $t("buttons.import") }}</b-button > </b-col> </b-row> @@ -118,7 +114,7 @@ type="search" id="filterInput" @input="setFilter" - :placeholder="$parent.$parent.$t('page.members.typeToSearch')" + :placeholder="$t('page.members.typeToSearch')" ></b-form-input> </b-input-group> </b-col> @@ -134,7 +130,7 @@ import "vue-select/dist/vue-select.css"; Vue.component("v-select", vSelect); -import { useUserStore } from "@/modules/user/store"; +import useUserStore from "@/modules/user/store"; import type { ProjectObject, diff --git a/src/modules/project/pages/components/modals/DeleteModal.vue b/src/modules/project/pages/components/modals/DeleteModal.vue index 6456c1d7881ace53cfa8a94badfc98aec3d190a1..75518c90d7c26595c40809850bc864d74080dbf5 100644 --- a/src/modules/project/pages/components/modals/DeleteModal.vue +++ b/src/modules/project/pages/components/modals/DeleteModal.vue @@ -2,17 +2,17 @@ <div> <b-modal :visible="visible" - :title="$parent.$parent.$t(titleKey)" + :title="$t(titleKey)" ok-variant="danger" - :ok-title="$parent.$parent.$t('buttons.delete')" - :cancel-title="$parent.$parent.$t('buttons.cancel')" + :ok-title="$t('buttons.delete')" + :cancel-title="$t('buttons.cancel')" @hidden="$emit('close', $event.target.value)" @ok="$emit('ok', $event.target.value)" @cancel="$emit('close', $event.target.value)" > <div v-html=" - $parent.$parent.$t(descriptionKey, { + $t(descriptionKey, { user: selectedUser, projectName: selectedProject, }) diff --git a/src/modules/project/pages/components/modals/DeleteProjectModal.vue b/src/modules/project/pages/components/modals/DeleteProjectModal.vue index 6cc32487313c4f1cdada682cca12a824de002d0d..e5963bb0fd315806629a84ae678812f2d4de2f92 100644 --- a/src/modules/project/pages/components/modals/DeleteProjectModal.vue +++ b/src/modules/project/pages/components/modals/DeleteProjectModal.vue @@ -6,10 +6,15 @@ @close="close" @hidden="hidden" :hide-footer="true" - :title="$parent.$parent.$t(titleKey)" > + <!-- Title --> + <template #modal-title> + <span class="h6"> {{ $t(titleKey) }} </span> + </template> + + <!-- Body --> <div> - {{ $parent.$parent.$t(descriptionKey) }} + {{ $t(descriptionKey) }} </div> <b-form-group> @@ -27,7 +32,7 @@ @keyup.enter.prevent="clickDelete" ></b-form-input> <div class="invalid-tooltip"> - {{ $parent.$parent.$t("page.settings.modal.deleteModalHelp") }} + {{ $t("page.settings.modal.deleteModalHelp") }} </div> </b-form-group> diff --git a/src/modules/project/pages/components/modals/ImportUserModal.vue b/src/modules/project/pages/components/modals/ImportUserModal.vue index 08c55e0898c6037275ccab95ee8893b460873e7e..148ab409092bf2b7d8d64dacbf3890ba39c74326 100644 --- a/src/modules/project/pages/components/modals/ImportUserModal.vue +++ b/src/modules/project/pages/components/modals/ImportUserModal.vue @@ -1,7 +1,7 @@ <template> <b-modal id="importUserModal" - :title="$parent.$parent.$t('page.members.importUserTitle')" + :title="$t('page.members.importUserTitle')" @hidden="resetModal" size="lg" hide-footer @@ -12,7 +12,7 @@ <b-form-select v-model="selectedProject" @change="getProjectRoles"> <template #first> <b-form-select-option :value="null" disabled>{{ - $parent.$parent.$t("page.members.searchProjectPlaceholder") + $t("page.members.searchProjectPlaceholder") }}</b-form-select-option> </template> <b-form-select-option @@ -37,11 +37,9 @@ :headers="memberHeaders" :items="additionalMembers" :roles="roles" - :emptyText="$parent.$parent.$t('page.members.emptyImportTableText')" - :emptyFilteredText=" - $parent.$parent.$t('page.members.emptyImportTableFilterText') - " - :removeText="$parent.$parent.$t('page.members.removeUser')" + :emptyText="$t('page.members.emptyImportTableText')" + :emptyFilteredText="$t('page.members.emptyImportTableFilterText')" + :removeText="$t('page.members.removeUser')" @selectedItem="removeSelectedRow" /> </div> @@ -58,7 +56,7 @@ name="importUsers" @click="importUser" :disabled="additionalMembers.length === 0" - >{{ $parent.$parent.$t("buttons.import") }}</b-button + >{{ $t("buttons.import") }}</b-button > </div> </b-col> @@ -71,7 +69,7 @@ <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; -import { useProjectStore } from "../../../store"; +import useProjectStore from "../../../store"; import MembersTable from "../MembersTable.vue"; diff --git a/src/modules/project/pages/components/modals/InvitationPendingModal.vue b/src/modules/project/pages/components/modals/InvitationPendingModal.vue index 82c7142cb6a5d412f1cc96b6a71b9a29110f5bf2..74493cec37b047e37e4905a75885b31cc6c068c7 100644 --- a/src/modules/project/pages/components/modals/InvitationPendingModal.vue +++ b/src/modules/project/pages/components/modals/InvitationPendingModal.vue @@ -1,24 +1,24 @@ <template> <b-modal id="invitationPendingModal" - :title="$parent.$parent.$t('page.members.inviteUserTitle')" + :title="$t('page.members.inviteUserTitle')" :hide-footer="true" > <ul> <li v-html=" - $parent.$parent.$t('page.members.invitationPendingTextTop', { + $t('page.members.invitationPendingTextTop', { email: candidateForInvitation.email, }) " ></li> <li> - {{ $parent.$parent.$t("page.members.invitationPendingTextBottom") }} + {{ $t("page.members.invitationPendingTextBottom") }} </li> </ul> <br /> <b-button @click="$bvModal.hide('invitationPendingModal')">{{ - $parent.$parent.$t("buttons.cancel") + $t("buttons.cancel") }}</b-button> </b-modal> </template> diff --git a/src/modules/project/pages/components/modals/InviteUserModal.vue b/src/modules/project/pages/components/modals/InviteUserModal.vue index d14bef5f832041b838feb92e0aa7a2b530b43c8a..0854a6a00d04e0cecbd3bc7742dd19eb8e2516fc 100644 --- a/src/modules/project/pages/components/modals/InviteUserModal.vue +++ b/src/modules/project/pages/components/modals/InviteUserModal.vue @@ -1,14 +1,14 @@ <template> <b-modal id="inviteUserModal" - :title="$parent.$parent.$t('page.members.inviteUserTitle')" + :title="$t('page.members.inviteUserTitle')" :hide-footer="true" > <!-- Body Text - New Invitation --> <div v-if="candidateForInvitation && !candidateForInvitation.invited" v-html=" - $parent.$parent.$t('page.members.inviteUserText', { + $t('page.members.inviteUserText', { email: candidateForInvitation.email, role: getRoleNameFromId(candidateForInvitation.role), projectName: projectName, @@ -20,7 +20,7 @@ <div v-else-if="candidateForInvitation && candidateForInvitation.invited" v-html=" - $parent.$parent.$t('page.members.existingEmailInvitation', { + $t('page.members.existingEmailInvitation', { email: candidateForInvitation.email, }) " @@ -37,8 +37,8 @@ > {{ !candidateForInvitation.invited - ? $parent.$parent.$t("page.members.inviteUser") - : $parent.$parent.$t("page.members.reInviteUser") + ? $t("page.members.inviteUser") + : $t("page.members.reInviteUser") }} </b-button> @@ -48,7 +48,7 @@ variant="secondary" @click="$bvModal.hide('inviteUserModal')" > - {{ $parent.$parent.$t("buttons.cancel") }} + {{ $t("buttons.cancel") }} </b-button> </div> </b-modal> @@ -86,7 +86,7 @@ export default defineComponent({ } }, inviteUser() { - const text = this.$parent.$parent + const text = this.$parent .$t("page.members.invitedUserText", { email: this.candidateForInvitation.email, role: this.candidateForInvitation.role @@ -95,7 +95,7 @@ export default defineComponent({ projectName: this.projectName, }) .toString(); - const errorText = this.$parent.$parent + const errorText = this.$parent .$t("page.members.invitedUserError", { email: this.candidateForInvitation.email, }) diff --git a/src/modules/project/store.ts b/src/modules/project/store.ts index 872f6dbdc2975fb8f539ddd8a26b8d9f262f68cd..78d963ef0556f2c60ecd4f6a0a054ea75712b353 100644 --- a/src/modules/project/store.ts +++ b/src/modules/project/store.ts @@ -33,6 +33,7 @@ import type { UpdateResourceObject, } from "@coscine/api-client/dist/types/Coscine.Api.Quota"; import type { Route } from "vue-router"; +import { updatedDiff } from "deep-object-diff"; import useUserStore from "../user/store"; @@ -72,11 +73,8 @@ export const useProjectStore = defineStore({ */ getters: { currentProject(): ProjectObject | null { - if (this.currentSlug && this.allProjects) { - const project = this.allProjects?.find( - (p) => p.slug === this.currentSlug - ); - return project ? project : null; + if (this.currentSlug && this.visitedProjects[this.currentSlug]) { + return this.visitedProjects[this.currentSlug]; } else { return null; } @@ -505,6 +503,10 @@ export const useProjectStore = defineStore({ addProjectAsVisited(project: ProjectObject | null) { if (project && project.slug) { + const updatedKeys = updatedDiff( + this.visitedProjects[project.slug], + project + ); if (!this.visitedProjects[project.slug]) { // Important! Keep object assignment reactive() const visitedProject: VisitedProjectObject = reactive({ @@ -516,6 +518,8 @@ export const useProjectStore = defineStore({ quotas: null, }); this.visitedProjects[project.slug] = visitedProject; + } else if (updatedKeys && Object.keys(updatedKeys).length > 0) { + Object.assign(this.visitedProjects[project.slug], updatedKeys); } } }, diff --git a/src/modules/resource/ResourceModule.vue b/src/modules/resource/ResourceModule.vue index d811d483415f6c3ddd5a0da175ec800f598de6a5..d1d2f808b5d5719e2dd4ba9bf8f695ce51352529 100644 --- a/src/modules/resource/ResourceModule.vue +++ b/src/modules/resource/ResourceModule.vue @@ -9,10 +9,10 @@ import { defineComponent } from "vue-demi"; import { ResourceI18nMessages } from "./i18n"; // import the store for current module -import { useResourceStore } from "./store"; -import { useProjectStore } from "@/modules/project/store"; +import useResourceStore from "./store"; +import useProjectStore from "@/modules/project/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { VisitedResourceObject } from "./types"; import type { ResourceTypeInformation } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; @@ -51,6 +51,7 @@ export default defineComponent({ watch: { resourceTypeInformation() { + this.retrieveUsedQuota(); this.setI18n(); }, }, @@ -66,15 +67,23 @@ export default defineComponent({ if (!this.resourceStore.currentFullApplicationProfile) { this.resourceStore.retrieveApplicationProfile(this.resource); } + } + // Extract Quota Retrieval, resourceTypeInformation is not loaded yet on direct links and is undefined + this.retrieveUsedQuota(); + this.setI18n(); + }, + + retrieveUsedQuota() { + if (this.resource) { if ( !this.resourceStore.currentUsedQuota && - this.resourceTypeInformation?.isQuotaAdjustable + this.resourceTypeInformation?.isQuotaAvailable ) { this.resourceStore.retrieveUsedQuota(this.resource); } } - this.setI18n(); }, + setI18n() { this.$i18n.mergeLocaleMessage("de", cloneDeep(ResourceI18nMessages.de)); this.$i18n.mergeLocaleMessage("en", cloneDeep(ResourceI18nMessages.en)); diff --git a/src/modules/resource/components/create-resource/Configuration.vue b/src/modules/resource/components/create-resource/Configuration.vue index 411e99073d2513e3e293891015c1338690365c54..621822ffbc3ba4f888a35d5f884a4bb127c6a6c3 100644 --- a/src/modules/resource/components/create-resource/Configuration.vue +++ b/src/modules/resource/components/create-resource/Configuration.vue @@ -5,7 +5,7 @@ <coscine-form-group :mandatory="true" labelFor="ResourceTypes" - :label="$t('page.createResource.setup.labels.resourceType')" + :label="$t('page.createResource.configuration.labels.resourceType')" :isLoading="isLoading" > <multiselect @@ -72,16 +72,16 @@ resourceTypeHasSize " target="divButtonNext" - :title="$t('page.createResource.setup.popover.title')" + :title="$t('page.createResource.configuration.popover.title')" placement="top" triggers="hover" :delay="{ show: 0, hide: 500 }" > - {{ $t("page.createResource.setup.popover.body") }} + {{ $t("page.createResource.configuration.popover.body") }} <!-- Router Link --> <router-link v-if="isOwner" :to="{ name: 'project-quota' }"> - {{ $t("page.createResource.setup.needMore") }} + {{ $t("page.createResource.configuration.needMore") }} </router-link> </b-popover> </div> @@ -90,10 +90,10 @@ <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; // import the store for current module -import { useResourceStore } from "../../store"; -import { useProjectStore } from "@/modules/project/store"; +import useResourceStore from "../../store"; +import useProjectStore from "@/modules/project/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import ConfigurationSizeSlider from "./ConfigurationSizeSlider.vue"; import "@/plugins/deprecated/vue-multiselect"; import type { diff --git a/src/modules/resource/components/create-resource/ConfigurationSizeSlider.vue b/src/modules/resource/components/create-resource/ConfigurationSizeSlider.vue index 8640399425ca06fa56835bcabe062fc4d023763b..8ef0793a2db2e0d530d28002ff63171b0d577f65 100644 --- a/src/modules/resource/components/create-resource/ConfigurationSizeSlider.vue +++ b/src/modules/resource/components/create-resource/ConfigurationSizeSlider.vue @@ -2,7 +2,7 @@ <div class="SetupPageRds"> <coscine-form-group :mandatory="true" - :label="$t('page.createResource.setup.labels.size')" + :label="$t('page.createResource.configuration.labels.size')" > <!-- Slider --> <b-form-input @@ -19,7 +19,7 @@ <!-- Router Link --> <router-link v-if="isOwner" :to="{ name: 'project-quota' }"> - {{ $t("page.createResource.setup.needMore") }} + {{ $t("page.createResource.configuration.needMore") }} </router-link> </div> </coscine-form-group> @@ -28,7 +28,7 @@ <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; -import { useProjectStore } from "@/modules/project/store"; +import useProjectStore from "@/modules/project/store"; import { isNumber } from "lodash"; import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; @@ -72,7 +72,7 @@ export default defineComponent({ const locale = this.$root.$i18n.locale; return num.toLocaleString(locale); }; - const text = this.$t("page.createResource.setup.bucketSize", { + const text = this.$t("page.createResource.configuration.bucketSize", { size: formatNumber(this.sliderValue), files: formatNumber(this.sliderValue * 2048), }).toString(); diff --git a/src/modules/resource/components/create-resource/General.vue b/src/modules/resource/components/create-resource/General.vue index e8c65b29740ad27f015f9d6df0e4fed9740a0fac..6f7c98c1227a4192ed1e1b28d5f905b2483c311a 100644 --- a/src/modules/resource/components/create-resource/General.vue +++ b/src/modules/resource/components/create-resource/General.vue @@ -6,9 +6,7 @@ <coscine-form-group :mandatory="true" labelFor="ResourceName" - :label=" - $t('page.createResource.general.form.resource.resourceNameLabel') - " + :label="$t('form.resource.resourceNameLabel')" :isLoading="isLoading" > <b-form-input @@ -20,16 +18,14 @@ ? !$v.resourceForm.resourceName.$error : null " - :placeholder=" - $t('page.createResource.general.form.resource.resourceName') - " + :placeholder="$t('form.resource.resourceName')" :maxlength="maxLengthFromValidation($v.resourceForm.resourceName)" required :readonly="readonly" /> <div class="invalid-tooltip"> {{ - $t("page.createResource.general.form.resource.resourceNameHelp", { + $t("form.resource.resourceNameHelp", { maxLength: maxLengthFromValidation($v.resourceForm.resourceName), }) }} @@ -40,9 +36,7 @@ <coscine-form-group :mandatory="true" labelFor="DisplayName" - :label=" - $t('page.createResource.general.form.resource.displayNameLabel') - " + :label="$t('form.resource.displayNameLabel')" :isLoading="isLoading" > <b-form-input @@ -54,16 +48,14 @@ ? !$v.resourceForm.displayName.$error : null " - :placeholder=" - $t('page.createResource.general.form.resource.displayName') - " + :placeholder="$t('form.resource.displayName')" :maxlength="maxLengthFromValidation($v.resourceForm.displayName)" required :readonly="readonly" /> <div class="invalid-tooltip"> {{ - $t("page.createResource.general.form.resource.displayNameHelp", { + $t("form.resource.displayNameHelp", { maxLength: maxLengthFromValidation($v.resourceForm.displayName), }) }} @@ -74,11 +66,7 @@ <coscine-form-group :mandatory="true" labelFor="Description" - :label=" - $t( - 'page.createResource.general.form.resource.resourceDescriptionLabel' - ) - " + :label="$t('form.resource.resourceDescriptionLabel')" :isLoading="isLoading" > <b-form-textarea @@ -89,21 +77,16 @@ ? !$v.resourceForm.description.$error : null " - :placeholder=" - $t('page.createResource.general.form.resource.resourceDescription') - " + :placeholder="$t('form.resource.resourceDescription')" :maxlength="maxLengthFromValidation($v.resourceForm.description)" required :readonly="readonly" /> <div class="invalid-tooltip"> {{ - $t( - "page.createResource.general.form.resource.resourceDescriptionHelp", - { - maxLength: maxLengthFromValidation($v.resourceForm.description), - } - ) + $t("form.resource.resourceDescriptionHelp", { + maxLength: maxLengthFromValidation($v.resourceForm.description), + }) }} </div> </coscine-form-group> @@ -112,11 +95,7 @@ <coscine-form-group :mandatory="true" labelFor="Discipline" - :label=" - $t( - 'page.createResource.general.form.resource.resourceDisciplineLabel' - ) - " + :label="$t('form.resource.resourceDisciplineLabel')" :isLoading="isLoading" > <multiselect @@ -127,9 +106,7 @@ :hide-selected="true" :label="disciplineLabel" :track-by="disciplineLabel" - :placeholder=" - $t('page.createResource.general.form.resource.resourceDiscipline') - " + :placeholder="$t('form.resource.resourceDiscipline')" :disabled="readonly" > <template #singleLabel(props)> @@ -148,48 +125,34 @@ <!-- Keywords --> <coscine-form-group labelFor="Keywords" - :label=" - $t('page.createResource.general.form.resource.resourceKeywordsLabel') - " + :label="$t('form.resource.resourceKeywordsLabel')" :isLoading="isLoading" > <multiselect id="Keywords" v-model="selectedKeyword" :options="selectedKeyword" - :placeholder=" - $t( - 'page.createResource.general.form.resource.resourceKeywordsPlaceholder' - ) - " + :placeholder="$t('form.resource.resourceKeywordsPlaceholder')" :multiple="true" :taggable="true" :max="limitKeywords(selectedKeyword)" - :tag-placeholder=" - $t('page.createResource.general.form.resource.tagPlaceholder') - " + :tag-placeholder="$t('form.resource.tagPlaceholder')" @tag="addTag" @remove="$v.resourceForm.keywords.$touch()" :disabled="readonly" > <template #maxElements> {{ - $t( - "page.createResource.general.form.resource.resourceKeywordsHelp", - { - maxLength: maxLengthFromValidation($v.resourceForm.keywords), - } - ) + $t("form.resource.resourceKeywordsHelp", { + maxLength: maxLengthFromValidation($v.resourceForm.keywords), + }) }} </template> <template #noOptions> {{ - $t( - "page.createResource.general.form.resource.resourceKeywordsEmpty", - { - maxLength: maxLengthFromValidation($v.resourceForm.keywords), - } - ) + $t("form.resource.resourceKeywordsEmpty", { + maxLength: maxLengthFromValidation($v.resourceForm.keywords), + }) }} </template> </multiselect> @@ -199,11 +162,7 @@ <coscine-form-group :mandatory="true" labelFor="Visibility" - :label=" - $t( - 'page.createResource.general.form.resource.resourceVisibilityLabel' - ) - " + :label="$t('form.resource.resourceMetadataVisibilityLabel')" :isLoading="isLoading" > <b-form-radio-group @@ -221,24 +180,14 @@ <!-- License --> <coscine-form-group labelFor="ResourceLicense" - :label=" - $t('page.createResource.general.form.resource.resourceLicenseLabel') - " + :label="$t('form.resource.resourceLicenseLabel')" :isLoading="isLoading" :info="true" > <template #popover> - {{ - $t( - "page.createResource.general.form.resource.resourceLicensePopover" - ) - }} + {{ $t("form.resource.resourceLicensePopover") }} <b-link - :href=" - $t( - 'page.createResource.general.form.resource.resourceLicensePopoverUrl' - ).toString() - " + :href="$t('form.resource.resourceLicensePopoverUrl').toString()" target="_blank" >{{ $t("default.help") }} </b-link> @@ -255,18 +204,12 @@ ? !$v.resourceForm.license.$error : null " - :placeholder=" - $t('page.createResource.general.form.resource.resourceLicense') - " + :placeholder="$t('form.resource.resourceLicense')" :disabled="readonly" > <template #first> <option :value="null" disabled> - {{ - $t( - "page.createResource.general.form.resource.resourceLicenseSelect" - ) - }} + {{ $t("form.resource.resourceLicenseSelect") }} </option> </template> </b-form-select> @@ -275,22 +218,14 @@ <!-- Internal Rules for Reuse --> <coscine-form-group labelFor="InternalRulesForReuse" - :label=" - $t('page.createResource.general.form.resource.resourceReuseLabel') - " + :label="$t('form.resource.resourceReuseLabel')" :isLoading="isLoading" :info="true" > <template #popover> - {{ - $t("page.createResource.general.form.resource.resourceReusePopover") - }} + {{ $t("form.resource.resourceReusePopover") }} <b-link - :href=" - $t( - 'page.createResource.general.form.resource.resourceReusePopoverUrl' - ).toString() - " + :href="$t('form.resource.resourceReusePopoverUrl').toString()" target="_blank" >{{ $t("default.help") }} </b-link> @@ -303,48 +238,30 @@ ? !$v.resourceForm.usageRights.$error : null " - :placeholder=" - $t('page.createResource.general.form.resource.resourceReuse') - " + :placeholder="$t('form.resource.resourceReuse')" :maxlength="maxLengthFromValidation($v.resourceForm.usageRights)" required :readonly="readonly" /> <div class="invalid-tooltip"> {{ - $t("page.createResource.general.form.resource.resourceReuseHelp", { + $t("form.resource.resourceReuseHelp", { maxLength: maxLengthFromValidation($v.resourceForm.usageRights), }) }} </div> </coscine-form-group> </b-form> - - <div v-if="!readonly"> - <!-- Button Back --> - <b-button @click.prevent="back" variant="outline-primary" - >{{ $t("buttons.back") }} - </b-button> - - <!-- Button Next --> - <b-button - @click.prevent="next" - class="float-right" - variant="outline-primary" - :disabled="!valid" - >{{ $t("buttons.next") }} - </b-button> - </div> </div> </template> <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; // import the store for current module -import { useResourceStore } from "../../store"; -import { useProjectStore } from "@/modules/project/store"; +import useResourceStore from "../../store"; +import useProjectStore from "@/modules/project/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import { Validation, validationMixin } from "vuelidate"; import { required, maxLength } from "vuelidate/lib/validators"; import "@/plugins/deprecated/vue-multiselect"; @@ -426,6 +343,9 @@ export default defineComponent({ project(): ProjectObject | null { return this.projectStore.currentProject; }, + resource(): ResourceObject | null { + return this.resourceStore.currentResource; + }, visibilities(): VisibilityObject[] | null { return this.projectStore.visibilities; }, @@ -441,7 +361,7 @@ export default defineComponent({ return `displayName${locale}`; }, valid(): boolean { - return !this.$v.$invalid; + return !this.$v.resourceForm.$invalid; }, }, @@ -449,7 +369,11 @@ export default defineComponent({ project() { this.initTabContent(); }, + resource() { + this.initTabContent(); + }, visibilities() { + // Used in Create Resource if (!this.resourceForm.visibility) { this.setDefaultVisibility(); } @@ -471,6 +395,12 @@ export default defineComponent({ }, deep: true, }, + "$v.resourceForm": { + handler() { + this.$emit("validation", this.$v.resourceForm); + }, + deep: true, + }, valid() { this.$emit("valid", this.valid); }, @@ -481,17 +411,6 @@ export default defineComponent({ }, methods: { - back() { - this.$emit("back"); - }, - - next() { - this.$v.resourceForm.$touch(); - if (!this.$v.resourceForm.$invalid) { - this.$emit("next"); - } - }, - initTabContent() { if (this.project) { this.loadDisciplines(this.project); @@ -500,7 +419,11 @@ export default defineComponent({ if (this.resourceForm.license && this.resourceForm.license.id) { this.selectedLicense = this.resourceForm.license.id; } - this.setDefaultVisibility(); + if (this.resourceForm.visibility && this.resourceForm.visibility.id) { + this.selectedVisibility = this.resourceForm.visibility.id; + } else { + this.setDefaultVisibility(); + } }, maxLengthFromValidation(validation: Validation): string { @@ -593,9 +516,8 @@ export default defineComponent({ }, emits: { - back: null, - next: null, valid: (_: boolean) => null, + validation: (_: Validation) => null, input: (_: ResourceObject) => null, }, }); diff --git a/src/modules/resource/components/create-resource/Metadata.vue b/src/modules/resource/components/create-resource/Metadata.vue index 5a02359a15de3d317014bd89f6959ca4518b0245..02887dd7a69638e8527df7038599e563c50e1a53 100644 --- a/src/modules/resource/components/create-resource/Metadata.vue +++ b/src/modules/resource/components/create-resource/Metadata.vue @@ -79,28 +79,14 @@ :userReceiver="async () => user" mimeType="application/ld+json" /> - - <!-- Button Back --> - <b-button @click.prevent="back" variant="outline-primary" - >{{ $t("buttons.back") }} - </b-button> - - <!-- Button Next --> - <b-button - @click.prevent="next" - class="float-right" - variant="outline-primary" - :disabled="!valid" - >{{ $t("buttons.next") }} - </b-button> </div> </template> <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; // import the store for current module -import { useResourceStore } from "../../store"; -import { useUserStore } from "@/modules/user/store"; +import useResourceStore from "../../store"; +import useUserStore from "@/modules/user/store"; import "@/plugins/form-generator"; import CreateAPModal from "./modals/CreateAPModal.vue"; import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; @@ -194,12 +180,6 @@ export default defineComponent({ async receiveClass(className: string): Promise<BilingualLabels> { return await this.resourceStore.getClass(className); }, - back() { - this.$emit("back"); - }, - next() { - this.$emit("next"); - }, notifyFormGenerator() { if (!this.resource.fixedValues) { this.$set(this.resource, "fixedValues", {}); @@ -213,8 +193,6 @@ export default defineComponent({ }, emits: { - back: null, - next: null, valid: (_: boolean) => null, input: (_: ResourceObject) => null, }, diff --git a/src/modules/resource/components/create-resource/Overview.vue b/src/modules/resource/components/create-resource/Overview.vue index 37d9c840bb62a7fa92881e0dcbcba7b59726e917..4911072f22f59bce641024b95af6e5ad4b1f6acf 100644 --- a/src/modules/resource/components/create-resource/Overview.vue +++ b/src/modules/resource/components/create-resource/Overview.vue @@ -2,15 +2,12 @@ <div class="overview"> <!-- Resource Configuration --> <b-card @click.prevent="toTab(tabs[0])" class="my-2"> - <CoscineHeadline - :headline="$t('page.createResource.steps.first')" - h="h5" - /> + <CoscineHeadline :headline="$t('form.steps.first')" h="h5" /> <coscine-form-group v-if="resource && resource.resourceTypeOption" :mandatory="true" - :label="$t('page.createResource.setup.labels.resourceType')" + :label="$t('page.createResource.configuration.labels.resourceType')" > <b-form-input readonly @@ -23,7 +20,7 @@ <coscine-form-group v-if="resource && resource.resourceTypeOption" :mandatory="true" - :label="$t('page.createResource.setup.labels.size')" + :label="$t('page.createResource.configuration.labels.size')" > <b-form-input readonly :value="resourceSizeText" /> </coscine-form-group> @@ -31,10 +28,7 @@ <!-- General Information --> <b-card @click.prevent="toTab(tabs[1])" class="my-2"> - <CoscineHeadline - :headline="$t('page.createResource.steps.second')" - h="h5" - /> + <CoscineHeadline :headline="$t('form.steps.second')" h="h5" /> <!-- General --> <General v-model="resource" :readonly="true" /> @@ -42,10 +36,7 @@ <!-- Metadata --> <b-card @click.prevent="toTab(tabs[2])" class="my-2 p-0"> - <CoscineHeadline - :headline="$t('page.createResource.steps.third')" - h="h5" - /> + <CoscineHeadline :headline="$t('form.steps.third')" h="h5" /> <!-- Form Generator --> <b-row v-if="isLoadingFormGenerator" align-h="center" class="my-2"> @@ -88,9 +79,9 @@ <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; // import the store for current module -import { useResourceStore } from "@/modules/resource/store"; -import { useProjectStore } from "@/modules/project/store"; -import { useUserStore } from "@/modules/user/store"; +import useResourceStore from "@/modules/resource/store"; +import useProjectStore from "@/modules/project/store"; +import useUserStore from "@/modules/user/store"; import "@/plugins/form-generator"; import General from "./General.vue"; import { navigateToProject } from "@/router"; @@ -98,7 +89,7 @@ import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api. import type { ResourceCreationTab, ResourceTypeOption } from "../../types"; import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api.Metadata"; -import { UserObject } from "@coscine/api-client/dist/types/Coscine.Api.User"; +import type { UserObject } from "@coscine/api-client/dist/types/Coscine.Api.User"; export default defineComponent({ components: { General }, @@ -196,8 +187,8 @@ export default defineComponent({ this.$emit("waitingForResponse", false); // On Failure this.makeToast( - this.$parent.$t("toast.onSave.failure.message").toString(), - this.$parent.$t("toast.onSave.failure.title").toString(), + this.$t("toast.onSave.failure.message").toString(), + this.$t("toast.onSave.failure.title").toString(), "danger" ); } diff --git a/src/modules/resource/components/create-resource/modals/CreateAPModal.vue b/src/modules/resource/components/create-resource/modals/CreateAPModal.vue index 966b45b9c2f2f5e37d444c98474f748e4eeca5b8..827f9335c4ef6d376e32cee6043e78a7659316d8 100644 --- a/src/modules/resource/components/create-resource/modals/CreateAPModal.vue +++ b/src/modules/resource/components/create-resource/modals/CreateAPModal.vue @@ -34,9 +34,9 @@ <script lang="ts"> import { defineComponent } from "vue-demi"; -import { useProjectStore } from "@/modules/project/store"; +import useProjectStore from "@/modules/project/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; export default defineComponent({ diff --git a/src/modules/resource/components/FilesView.vue b/src/modules/resource/components/resource-page/FilesView.vue similarity index 95% rename from src/modules/resource/components/FilesView.vue rename to src/modules/resource/components/resource-page/FilesView.vue index 32f1f895fdf981d897e0713a9a7ee0b10a73697b..3f96e72dbcff8b47a5a81b2a08177235f7f49780 100644 --- a/src/modules/resource/components/FilesView.vue +++ b/src/modules/resource/components/resource-page/FilesView.vue @@ -26,15 +26,13 @@ ref="adaptTable" @row-selected="onRowSelected" show-empty - :empty-text="$parent.$parent.$t('page.resource.emptyTableText')" - :empty-filtered-text=" - $parent.$parent.$t('page.resource.emptyFilterText') - " + :empty-text="$t('page.resource.emptyTableText')" + :empty-filtered-text="$t('page.resource.emptyFilterText')" > <div slot="table-busy" class="text-center text-danger my-2"> <b-spinner class="align-middle"></b-spinner> <strong style="margin-left: 1%">{{ - $parent.$parent.$t("page.resource.loading") + $t("page.resource.loading") }}</strong> </div> <template v-slot:head(name)="row"> @@ -42,7 +40,7 @@ v-model="selectAll" @change="allSelect(row)" ></b-form-checkbox> - <span>{{ $parent.$parent.$t("page.resource.fileName") }}</span> + <span>{{ $t("page.resource.fileName") }}</span> </template> <template v-slot:head(lastModified)="row"> <span>{{ row.label }}</span> @@ -111,7 +109,7 @@ row.item.isFolder ? openFolder(row.item) : openFile(row.item) " >{{ - $parent.$parent.$t("page.resource.metadataManagerBtnDownload") + $t("page.resource.metadataManagerBtnDownload") }}</b-dropdown-item > <b-dropdown-item @@ -136,10 +134,10 @@ import { defineComponent, PropType, reactive } from "vue-demi"; // import the store for current module -import { useResourceStore } from "../store"; -import { useProjectStore } from "@/modules/project/store"; +import useResourceStore from "../../store"; +import useProjectStore from "@/modules/project/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import FilesViewHeader from "./FilesViewHeader.vue"; @@ -148,9 +146,9 @@ import type { FolderContent, FolderInformation, ReadOnlyFolderInformation, -} from "../utils/EntryDefinition"; +} from "../../utils/EntryDefinition"; -import MetadataManagerUtil from "../utils/MetadataManagerUtil"; +import MetadataManagerUtil from "../../utils/MetadataManagerUtil"; import fileSaver from "file-saver"; @@ -159,9 +157,9 @@ import type { ApplicationProfile, Metadata, VisitedResourceObject, -} from "../types"; +} from "../../types"; import type { BFormRow, BTable, BvTableField } from "bootstrap-vue"; -import { FileUtil } from "../utils/FileUtil"; +import { FileUtil } from "../../utils/FileUtil"; import { v4 as uuidv4 } from "uuid"; @@ -251,21 +249,19 @@ export default defineComponent({ return { defaultHeaders: [ { - label: this.$parent.$parent.$t("page.resource.fileName").toString(), + label: this.$t("page.resource.fileName").toString(), key: "name", sortable: true, active: true, }, { - label: this.$parent.$parent - .$t("page.resource.lastModified") - .toString(), + label: this.$parent.$t("page.resource.lastModified").toString(), key: "lastModified", sortable: true, active: true, }, { - label: this.$parent.$parent.$t("page.resource.size").toString(), + label: this.$t("page.resource.size").toString(), key: "size", sortable: true, active: true, diff --git a/src/modules/resource/components/FilesViewHeader.vue b/src/modules/resource/components/resource-page/FilesViewHeader.vue similarity index 79% rename from src/modules/resource/components/FilesViewHeader.vue rename to src/modules/resource/components/resource-page/FilesViewHeader.vue index 39267928ddba129063c03343b3a8a56e5ff58c47..61a580dc994932133d67a0cec166a20b82d42e4f 100644 --- a/src/modules/resource/components/FilesViewHeader.vue +++ b/src/modules/resource/components/resource-page/FilesViewHeader.vue @@ -12,16 +12,14 @@ " > {{ - $parent.$parent.$parent.$t( - "resourceTypes." + resource.type.displayName + ".displayName" - ) + $t("resourceTypes." + resource.type.displayName + ".displayName") }}: {{ resource.displayName }} </p> </span> <b-icon id="resourceDetails" icon="info-circle" - :title="$parent.$parent.$parent.$t('page.resource.info')" + :title="$t('page.resource.info')" /> <b-popover v-if="resource" @@ -36,31 +34,23 @@ > <div v-if="resource.displayName"> <span - ><b - >{{ $parent.$parent.$parent.$t("page.resource.displayName") }}: - </b> </span + ><b>{{ $t("page.resource.displayName") }}: </b> </span ><span>{{ resource.displayName }}</span> </div> <div v-if="resource.pid"> <span - ><b - >{{ $parent.$parent.$parent.$t("page.resource.PID") }}: - </b> </span + ><b>{{ $t("page.resource.PID") }}: </b> </span ><span>{{ resource.pid }}</span> </div> <div v-if="resource.description"> <span - ><b - >{{ $parent.$parent.$parent.$t("page.resource.description") }}: - </b> </span + ><b>{{ $t("page.resource.description") }}: </b> </span ><span>{{ resource.description }}</span ><br /> </div> <div v-if="resource.disciplines && resource.disciplines.length > 0"> <span - ><b - >{{ $parent.$parent.$parent.$t("page.resource.disciplines") }}: - </b> + ><b>{{ $t("page.resource.disciplines") }}: </b> </span> <ul> <li @@ -78,9 +68,7 @@ </div> <div v-if="resource.keywords && resource.keywords.length > 0"> <span - ><b - >{{ $parent.$parent.$parent.$t("page.resource.keywords") }}: - </b> </span + ><b>{{ $t("page.resource.keywords") }}: </b> </span ><br /> <ul> <li v-for="keyword in resource.keywords" v-bind:key="keyword"> @@ -90,52 +78,38 @@ </div> <div v-if="resource.visibility"> <span - ><b - >{{ $parent.$parent.$parent.$t("page.resource.visibility") }}: - </b> </span + ><b>{{ $t("page.resource.visibility") }}: </b> </span ><span>{{ resource.visibility.displayName }}</span ><br /> </div> <div v-if="resource.license"> <span - ><b - >{{ - $parent.$parent.$parent.$t("page.resource.resourceLicense") - }}: - </b> </span + ><b>{{ $t("page.resource.resourceLicense") }}: </b> </span ><span>{{ resource.license.displayName }}</span ><br /> </div> <div v-if="resource.usageRights"> <span - ><b - >{{ $parent.$parent.$parent.$t("page.resource.usageRights") }}: - </b> </span + ><b>{{ $t("page.resource.usageRights") }}: </b> </span ><span>{{ resource.usageRights }}</span ><br /> </div> </b-popover> <b-button @click="edit" - :title="$parent.$parent.$parent.$t('page.resource.edit')" + :title="$t('page.resource.edit')" class="btn btn-sm" v-if="canEditResource" > - <b-icon - icon="pencil-fill" - :title="$parent.$parent.$parent.$t('page.resource.edit')" - /> + <b-icon icon="pencil-fill" :title="$t('page.resource.edit')" /> </b-button> <b-button @click="upload" - :title="$parent.$parent.$parent.$t('page.resource.upload')" + :title="$t('page.resource.upload')" class="btn btn-sm" :disabled="isUploading || readOnly || (resource && resource.archived)" > - <b-icon - icon="plus" - :title="$parent.$parent.$parent.$t('page.resource.upload')" - /> + <b-icon icon="plus" :title="$t('page.resource.upload')" /> </b-button> <span v-if="resource && resource.archived" class="badgeWrap"> <b-badge pill variant="warning">{{ $t("default.archived") }}</b-badge> @@ -170,9 +144,7 @@ <b-form-input type="search" id="filterInput" - :placeholder=" - $parent.$parent.$parent.$t('page.resource.typeToSearch') - " + :placeholder="$t('page.resource.typeToSearch')" :value="value" @input="$emit('input', $event)" ></b-form-input> @@ -185,23 +157,22 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useResourceStore } from "../store"; -import { useProjectStore } from "@/modules/project/store"; +import useResourceStore from "../../store"; +import useProjectStore from "@/modules/project/store"; // import the user store -import { useUserStore } from "@/modules/user/store"; +import useUserStore from "@/modules/user/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; -import { FileUtil } from "../utils/FileUtil"; -import { +import { FileUtil } from "../../utils/FileUtil"; +import router from "@/router"; +import type { ProjectObject, ProjectQuotaReturnObject, UserObject, } from "@coscine/api-client/dist/types/Coscine.Api.Project"; -import router from "@/router"; - import type { ResourceTypeInformation } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; -import type { VisitedResourceObject } from "../types"; +import type { VisitedResourceObject } from "../../types"; export default defineComponent({ setup() { diff --git a/src/modules/resource/components/MetadataManager.vue b/src/modules/resource/components/resource-page/MetadataManager.vue similarity index 95% rename from src/modules/resource/components/MetadataManager.vue rename to src/modules/resource/components/resource-page/MetadataManager.vue index 62bc4683610080f17ea5ab0470678635974043be..c9a221543194bf9769f91f6db06043638aec460a 100644 --- a/src/modules/resource/components/MetadataManager.vue +++ b/src/modules/resource/components/resource-page/MetadataManager.vue @@ -101,15 +101,15 @@ import { defineComponent, PropType, VNode } from "vue-demi"; // import the store for current module -import { useResourceStore } from "../store"; -import { useProjectStore } from "@/modules/project/store"; -import { useUserStore } from "@/modules/user/store"; +import useResourceStore from "../../store"; +import useProjectStore from "@/modules/project/store"; +import useUserStore from "@/modules/user/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import fileSaver from "file-saver"; -import MetadataManagerUtil from "../utils/MetadataManagerUtil"; +import MetadataManagerUtil from "../../utils/MetadataManagerUtil"; import MetadataManagerHeader from "./metadata/MetadataManagerHeader.vue"; import MetadataManagerTable from "./metadata/MetadataManagerTable.vue"; @@ -121,7 +121,10 @@ import DeleteFolderContentsModal from "./modals/DeleteFolderContentsModal.vue"; import SaveDuplicateFilesModal from "./modals/SaveDuplicateFilesModal.vue"; import ValidationPopover from "./popovers/ValidationPopover.vue"; -import type { FileInformation, FolderContent } from "../utils/EntryDefinition"; +import type { + FileInformation, + FolderContent, +} from "../../utils/EntryDefinition"; import "@/plugins/form-generator"; import { cloneDeep } from "lodash"; @@ -137,7 +140,7 @@ import type ValidationReport from "rdf-validate-shacl/src/validation-report"; import { v4 as uuidv4 } from "uuid"; import type { ValidationResult } from "rdf-validate-shacl/src/validation-report"; -import type { ApplicationProfile, Metadata } from "../types"; +import type { ApplicationProfile, Metadata } from "../../types"; export default defineComponent({ setup() { @@ -393,10 +396,7 @@ export default defineComponent({ const h = this.$createElement; const content = []; content.push( - "" + - this.$parent.$parent.$t( - "page.resource.toastSavingFailedBodyTop" - ) + "" + this.$t("page.resource.toastSavingFailedBodyTop") ); content.push(h("br")); @@ -406,30 +406,21 @@ export default defineComponent({ } content.push( - "" + - this.$parent.$parent.$t( - "page.resource.toastSavingFailedBodyBottom" - ) + "" + this.$t("page.resource.toastSavingFailedBodyBottom") ); const vNodesMsg = h("span", {}, content); this.makeToast( - "" + - this.$parent.$parent.$t("page.resource.toastSavingFailedTitle"), + "" + this.$t("page.resource.toastSavingFailedTitle"), vNodesMsg, true ); } if (numberOfSuccessfulUploadedFiles > 0) { this.makeToast( + "" + this.$t("page.resource.toastSavingSuccessfulTitle"), "" + - this.$parent.$parent.$t( - "page.resource.toastSavingSuccessfulTitle" - ), - "" + - this.$parent.$parent.$t( - "page.resource.toastSavingSuccessfulBody" - ) + + this.$t("page.resource.toastSavingSuccessfulBody") + numberOfSuccessfulUploadedFiles ); } diff --git a/src/modules/resource/components/metadata/MetadataManagerFileInformation.vue b/src/modules/resource/components/resource-page/metadata/MetadataManagerFileInformation.vue similarity index 64% rename from src/modules/resource/components/metadata/MetadataManagerFileInformation.vue rename to src/modules/resource/components/resource-page/metadata/MetadataManagerFileInformation.vue index ab0a60141612e744207d21080287c8a62f7c7dac..b899014670fbfa74ce68d7abe1c1e5e20bee005d 100644 --- a/src/modules/resource/components/metadata/MetadataManagerFileInformation.vue +++ b/src/modules/resource/components/resource-page/metadata/MetadataManagerFileInformation.vue @@ -1,50 +1,42 @@ <template> <span> - <coscine-form-group - :label="$parent.$parent.$parent.$t('page.resource.infoFileName')" - > + <coscine-form-group :label="$t('page.resource.infoFileName')"> <div class="fileInfoField"> {{ !fileListEdit[currentFileId].name - ? $parent.$parent.$parent.$t("page.resource.infoFileNoInformation") + ? $t("page.resource.infoFileNoInformation") : fileListEdit[currentFileId].name }} </div> </coscine-form-group> - <coscine-form-group - :label="$parent.$parent.$parent.$t('page.resource.infoFileLastModified')" - > + <coscine-form-group :label="$t('page.resource.infoFileLastModified')"> <div class="fileInfoField"> {{ !fileListEdit[currentFileId].lastModified - ? $parent.$parent.$parent.$t("page.resource.infoFileNoInformation") + ? $t("page.resource.infoFileNoInformation") : new Date( fileListEdit[currentFileId].lastModified ).toLocaleDateString($i18n.locale) }} </div> </coscine-form-group> - <coscine-form-group - :label="$parent.$parent.$parent.$t('page.resource.infoFileCreated')" - > + <coscine-form-group :label="$t('page.resource.infoFileCreated')"> <div class="fileInfoField"> {{ !fileListEdit[currentFileId].created - ? $parent.$parent.$parent.$t("page.resource.infoFileNoInformation") + ? $t("page.resource.infoFileNoInformation") : new Date(fileListEdit[currentFileId].created).toLocaleDateString( $i18n.locale ) }} </div> </coscine-form-group> - <coscine-form-group - :label="$parent.$parent.$parent.$t('page.resource.infoFileSize')" - > + <coscine-form-group :label="$t('page.resource.infoFileSize')"> <div class="fileInfoField"> {{ fileListEdit[currentFileId].size === undefined || fileListEdit[currentFileId].size === null - ? $parent.$parent.$parent.$t("page.resource.infoFileNoInformation") + ? $t("page.resource.infoFileNoInformation") : fileListEdit[currentFileId].size + " Bytes" }} </div> @@ -55,7 +47,7 @@ <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; -import type { FolderContent } from "../../utils/EntryDefinition"; +import type { FolderContent } from "../../../utils/EntryDefinition"; export default defineComponent({ props: { diff --git a/src/modules/resource/components/metadata/MetadataManagerFooter.vue b/src/modules/resource/components/resource-page/metadata/MetadataManagerFooter.vue similarity index 88% rename from src/modules/resource/components/metadata/MetadataManagerFooter.vue rename to src/modules/resource/components/resource-page/metadata/MetadataManagerFooter.vue index d689a450d44c51dad3c1a018fbed578807ca0ec1..e3c3e7f5e2a11841e13c23e3212ae3d1ff36d1f9 100644 --- a/src/modules/resource/components/metadata/MetadataManagerFooter.vue +++ b/src/modules/resource/components/resource-page/metadata/MetadataManagerFooter.vue @@ -35,16 +35,10 @@ ><b-spinner label="Spinning" v-show="isUploading"></b-spinner >{{ isUploading - ? $parent.$parent.$parent.$t( - "page.resource.metadataManagerBtnSaving" - ) + ? $t("page.resource.metadataManagerBtnSaving") : !showDetail - ? $parent.$parent.$parent.$t( - "page.resource.metadataManagerBtnUpload" - ) - : $parent.$parent.$parent.$t( - "page.resource.metadataManagerBtnUpdate" - ) + ? $t("page.resource.metadataManagerBtnUpload") + : $t("page.resource.metadataManagerBtnUpdate") }}</b-button > </b-col> diff --git a/src/modules/resource/components/metadata/MetadataManagerHeader.vue b/src/modules/resource/components/resource-page/metadata/MetadataManagerHeader.vue similarity index 80% rename from src/modules/resource/components/metadata/MetadataManagerHeader.vue rename to src/modules/resource/components/resource-page/metadata/MetadataManagerHeader.vue index 746a6866a72f143f38a831be80c0bd6b3a15b42d..7d13b717768a380e040ce9a8638ebe13092c3a46 100644 --- a/src/modules/resource/components/metadata/MetadataManagerHeader.vue +++ b/src/modules/resource/components/resource-page/metadata/MetadataManagerHeader.vue @@ -5,18 +5,10 @@ id="buttonSelectFiles" variant="secondary" @click="$emit('selectFiles')" - :placeholder=" - $parent.$parent.$parent.$t( - 'page.resource.metadataManagerBtnSelectFiles' - ) - " + :placeholder="$t('page.resource.metadataManagerBtnSelectFiles')" :disabled="isUploading || readOnly || (resource && resource.archived)" autofocus - >{{ - $parent.$parent.$parent.$t( - "page.resource.metadataManagerBtnSelectFiles" - ) - }} + >{{ $t("page.resource.metadataManagerBtnSelectFiles") }} </b-button> </b-col> <b-col> @@ -42,11 +34,7 @@ v-if="!editableDataUrl" @click="$emit('download')" :disabled="!showDetail || shownFiles.length === 0" - >{{ - $parent.$parent.$parent.$t( - "page.resource.metadataManagerBtnDownload" - ) - }}</b-button + >{{ $t("page.resource.metadataManagerBtnDownload") }}</b-button > </b-input-group> </b-col> @@ -57,7 +45,7 @@ import { defineComponent, PropType } from "vue-demi"; import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; -import type { FolderContent } from "../../utils/EntryDefinition"; +import type { FolderContent } from "../../../utils/EntryDefinition"; export default defineComponent({ props: { diff --git a/src/modules/resource/components/metadata/MetadataManagerSpecialProperties.vue b/src/modules/resource/components/resource-page/metadata/MetadataManagerSpecialProperties.vue similarity index 90% rename from src/modules/resource/components/metadata/MetadataManagerSpecialProperties.vue rename to src/modules/resource/components/resource-page/metadata/MetadataManagerSpecialProperties.vue index 73837dde786e970b98198ead2cce6334d1b4e842..680b65b57ee87720f93b8be8591099558527b14a 100644 --- a/src/modules/resource/components/metadata/MetadataManagerSpecialProperties.vue +++ b/src/modules/resource/components/resource-page/metadata/MetadataManagerSpecialProperties.vue @@ -4,14 +4,14 @@ <coscine-form-group :mandatory="true" labelFor="dataUrl" - :label="$parent.$parent.$parent.$t('page.resource.dataUrl')" + :label="$t('page.resource.dataUrl')" > <b-input-group> <b-form-input id="dataUrl" @input="updateDataUrl(currentFolderContent, $event)" :value="currentFolderContent.dataUrl" - :placeholder="$parent.$parent.$parent.$t('page.resource.dataUrl')" + :placeholder="$t('page.resource.dataUrl')" :disabled="resource.archived || readOnly" /> <b-button id="URLBtn" @click="openURL" :disabled="!isValidUrl" @@ -24,12 +24,12 @@ <coscine-form-group :mandatory="true" labelFor="metadataKey" - :label="$parent.$parent.$parent.$t('page.resource.metadataKey')" + :label="$t('page.resource.metadataKey')" > <b-form-input @change="updateAbsolutePath(currentFolderContent, $event)" :value="currentFolderContent.name" - :placeholder="$parent.$parent.$parent.$t('page.resource.metadataKey')" + :placeholder="$t('page.resource.metadataKey')" :disabled="resource.archived || readOnly" /> </coscine-form-group> @@ -43,7 +43,7 @@ import { defineComponent, PropType } from "vue-demi"; import type { FileInformation, FolderContent, -} from "../../utils/EntryDefinition"; +} from "../../../utils/EntryDefinition"; import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; export default defineComponent({ diff --git a/src/modules/resource/components/metadata/MetadataManagerTable.vue b/src/modules/resource/components/resource-page/metadata/MetadataManagerTable.vue similarity index 95% rename from src/modules/resource/components/metadata/MetadataManagerTable.vue rename to src/modules/resource/components/resource-page/metadata/MetadataManagerTable.vue index c3a8b940ac30cb5a2b55f2d2c2205749c21ed372..abc8293c7b6fc0e1e1c2018b43b0d23d310116b9 100644 --- a/src/modules/resource/components/metadata/MetadataManagerTable.vue +++ b/src/modules/resource/components/resource-page/metadata/MetadataManagerTable.vue @@ -36,7 +36,7 @@ @click="$emit('loadAllFilesTab')" :pressed="currentFileId === -1" variant="outline-secondary" - >{{ $parent.$parent.$t("page.resource.allFiles") }}</b-button + >{{ $t("page.resource.allFiles") }}</b-button > <span v-if="shownFiles.length > 1 && shownFiles.length <= 10"> <b-button @@ -64,7 +64,7 @@ {{ currentFolderContent.name }} </span> <span v-else-if="shownFiles.length > 0">{{ - $parent.$parent.$t("page.resource.allFiles") + $t("page.resource.allFiles") }}</span> </b-col> </b-row> @@ -74,7 +74,7 @@ <script lang="ts"> import { defineComponent, PropType } from "vue-demi"; -import type { FolderContent } from "../../utils/EntryDefinition"; +import type { FolderContent } from "../../../utils/EntryDefinition"; export default defineComponent({ props: { diff --git a/src/modules/resource/components/modals/DeleteFolderContentsModal.vue b/src/modules/resource/components/resource-page/modals/DeleteFolderContentsModal.vue similarity index 82% rename from src/modules/resource/components/modals/DeleteFolderContentsModal.vue rename to src/modules/resource/components/resource-page/modals/DeleteFolderContentsModal.vue index 45fca191cbc596b1b7c71adcf584e7ca65e6f2e8..066826c37f0d4bafa49e33086eb05e195c97273d 100644 --- a/src/modules/resource/components/modals/DeleteFolderContentsModal.vue +++ b/src/modules/resource/components/resource-page/modals/DeleteFolderContentsModal.vue @@ -4,22 +4,14 @@ :visible="visible" size="lg" hide-footer - :title=" - $parent.$parent.$parent.$t( - 'page.resource.modalDeleteFolderContentsHeader' - ) - " + :title="$t('page.resource.modalDeleteFolderContentsHeader')" @hidden="$emit('close', $event.target.value)" @ok="$emit('ok', $event.target.value)" @cancel="$emit('close', $event.target.value)" @shown="$emit('shown', $event.target.value)" > <div> - {{ - $parent.$parent.$parent.$t( - "page.resource.modalDeleteFolderContentsBody" - ) - }} + {{ $t("page.resource.modalDeleteFolderContentsBody") }} </div> <br /> <div diff --git a/src/modules/resource/components/modals/SaveDuplicateFilesModal.vue b/src/modules/resource/components/resource-page/modals/SaveDuplicateFilesModal.vue similarity index 67% rename from src/modules/resource/components/modals/SaveDuplicateFilesModal.vue rename to src/modules/resource/components/resource-page/modals/SaveDuplicateFilesModal.vue index ed5efa652338537f3bb19b5399bedb57b27e0c78..c8e42d4612089fe16aa4b10ca97e7ead38b95185 100644 --- a/src/modules/resource/components/modals/SaveDuplicateFilesModal.vue +++ b/src/modules/resource/components/resource-page/modals/SaveDuplicateFilesModal.vue @@ -4,18 +4,14 @@ :visible="visible" size="lg" hide-footer - :title=" - $parent.$parent.$parent.$t('page.resource.modalSaveDuplicateFilesHeader') - " + :title="$t('page.resource.modalSaveDuplicateFilesHeader')" @hidden="$emit('close', $event.target.value)" @ok="$emit('ok', $event.target.value)" @cancel="$emit('close', $event.target.value)" @shown="$emit('shown', $event.target.value)" > <div> - {{ - $parent.$parent.$parent.$t("page.resource.modalSaveDuplicateFilesBody") - }} + {{ $t("page.resource.modalSaveDuplicateFilesBody") }} </div> <br /> <div @@ -31,30 +27,18 @@ class="float-left" @click="$emit('close', $event.target.value)" autofocus - >{{ - $parent.$parent.$parent.$t( - "page.resource.modalSaveDuplicateFilesBtnCancel" - ) - }}</b-button + >{{ $t("page.resource.modalSaveDuplicateFilesBtnCancel") }}</b-button > <b-button class="modalButtonFloatCenter" @click="$emit('skip', $event.target.value)" - >{{ - $parent.$parent.$parent.$t( - "page.resource.modalSaveDuplicateFilesBtnSkip" - ) - }}</b-button + >{{ $t("page.resource.modalSaveDuplicateFilesBtnSkip") }}</b-button > <b-button class="float-right" style="justify-content: flex-end" @click="$emit('ok', $event.target.value)" - >{{ - $parent.$parent.$parent.$t( - "page.resource.modalSaveDuplicateFilesBtnOverwrite" - ) - }}</b-button + >{{ $t("page.resource.modalSaveDuplicateFilesBtnOverwrite") }}</b-button > </div> </b-modal> diff --git a/src/modules/resource/components/popovers/ValidationPopover.vue b/src/modules/resource/components/resource-page/popovers/ValidationPopover.vue similarity index 91% rename from src/modules/resource/components/popovers/ValidationPopover.vue rename to src/modules/resource/components/resource-page/popovers/ValidationPopover.vue index b077902d39ec1f93b41f5b323e99ffb3a58c1d83..5a3c5e4308a20bd144f1edff823be47e29808eac 100644 --- a/src/modules/resource/components/popovers/ValidationPopover.vue +++ b/src/modules/resource/components/resource-page/popovers/ValidationPopover.vue @@ -8,9 +8,7 @@ placement="top" > <template v-slot:title - ><b>{{ - $parent.$parent.$parent.$t("page.resource.validationErrors") - }}</b></template + ><b>{{ $t("page.resource.validationErrors") }}</b></template > <div> <ul> diff --git a/src/modules/resource/components/settings/Actions.vue b/src/modules/resource/components/settings/Actions.vue new file mode 100644 index 0000000000000000000000000000000000000000..ff0aeed9fd8044668ab4e5415f8441d2a8880772 --- /dev/null +++ b/src/modules/resource/components/settings/Actions.vue @@ -0,0 +1,143 @@ +<template> + <div> + <!-- Archive --> + <coscine-form-group + labelFor="Archive" + :label="$t('page.settings.actions.resourceArchiveLabel')" + :isLoading="isLoading" + class="d-flex align-items-center" + > + <b-form-checkbox + id="Archive" + v-model="resourceForm.archived" + :disabled="!(isOwner || isResourceCreator) || !resourceForm" + @click.native.prevent="isArchiveModalVisible = true" + switch + size="lg" + /> + </coscine-form-group> + + <!-- Delete Resource --> + <coscine-form-group + labelFor="DeleteResource" + :label="$t('page.settings.actions.resourceDeleteLabel')" + :isLoading="isLoading" + class="d-flex align-items-center" + > + <!-- Delete Button --> + <b-button + id="DeleteResource" + type="delete" + variant="danger" + :disabled="!resourceForm" + @click.prevent="isDeleteModalVisible = true" + > + {{ $t("buttons.delete") }}</b-button + > + </coscine-form-group> + + <!-- Archive Resource Modal --> + <ArchiveResourceModal + :open="isArchiveModalVisible" + :archived="resourceForm.archived" + @close="isArchiveModalVisible = false" + @toggleArchive="toggleArchive" + /> + + <!-- Delete Resource Modal --> + <DeleteResourceModal + :open="isDeleteModalVisible" + :displayName="resourceForm.displayName" + @close="isDeleteModalVisible = false" + @clickDelete="clickDelete" + /> + </div> +</template> + +<script lang="ts"> +import { defineComponent, PropType } from "vue-demi"; +import useProjectStore from "@/modules/project/store"; +import useUserStore from "@/modules/user/store"; +import ArchiveResourceModal from "./modals/ArchiveResourceModal.vue"; +import DeleteResourceModal from "./modals/DeleteResourceModal.vue"; +import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; +import type { UserObject } from "@coscine/api-client/dist/types/Coscine.Api.User"; + +export default defineComponent({ + setup() { + const projectStore = useProjectStore(); + const userStore = useUserStore(); + + return { projectStore, userStore }; + }, + + components: { + ArchiveResourceModal, + DeleteResourceModal, + }, + + props: { + value: { + type: Object as PropType<ResourceObject>, + required: true, + }, + isLoading: { + default: false, + type: Boolean, + }, + }, + + data() { + return { + resourceForm: this.value, + isArchiveModalVisible: false, + isDeleteModalVisible: false, + }; + }, + + computed: { + isOwner(): boolean | undefined { + return this.projectStore.currentUserRoleIsOwner; + }, + user(): UserObject | null { + return this.userStore.user; + }, + isResourceCreator(): boolean | undefined { + if (this.resourceForm.creator && this.user && this.user.id) { + return this.resourceForm.creator === this.user.id; + } else { + return undefined; + } + }, + }, + + watch: { + resource: { + handler() { + this.$emit("input", this.resourceForm); + }, + deep: true, + }, + }, + + methods: { + toggleArchive() { + this.$emit("toggleArchive"); + this.isArchiveModalVisible = false; + }, + + clickDelete() { + this.$emit("clickDelete"); + this.isDeleteModalVisible = false; + }, + }, + + emits: { + toggleArchive: null, + clickDelete: null, + input: (_: ResourceObject) => null, + }, +}); +</script> + +<style></style> diff --git a/src/modules/resource/components/settings/Configuration.vue b/src/modules/resource/components/settings/Configuration.vue new file mode 100644 index 0000000000000000000000000000000000000000..35a7866020c55ae83dbc2980139178210ffe3077 --- /dev/null +++ b/src/modules/resource/components/settings/Configuration.vue @@ -0,0 +1,120 @@ +<template> + <div> + <!-- Content --> + <div v-if="resource"> + <!-- Individual Fields --> + <div + v-for="(resourceOption, index) of Object.keys( + resource.resourceTypeOption + )" + :key="index" + > + <!-- Field Definition --> + <coscine-form-group + v-if="resourceOption !== 'Id'" + :labelFor="resourceOption" + :label=" + $t( + 'page.settings.configuration.resource' + resourceOption + 'Label' + ) + " + > + <!-- Resource Size Field --> + <b-form-input + v-if="resourceOption === 'Size'" + :value="resourceSizeText" + :readonly="readonly" + /> + + <!-- Other Fields --> + <b-button-group v-else class="w-100"> + <!-- Text Field --> + <b-form-input + v-model="resource.resourceTypeOption[resourceOption]" + :readonly="readonly" + /> + + <!-- Copy Button --> + <b-button + :id="`copyButton${index}`" + @click=" + copyText(resource.resourceTypeOption[resourceOption], index) + " + > + <b-icon icon="clipboard" /> + </b-button> + + <!-- Copied Tooltip --> + <b-tooltip + :id="`copyTooltip${index}`" + :target="`copyButton${index}`" + placement="top" + triggers="blur" + > + {{ + $t("page.settings.configuration.toClipboard", { + resourceOption: $t( + "page.settings.configuration.resource" + resourceOption + ), + }) + }} + </b-tooltip> + </b-button-group> + </coscine-form-group> + </div> + </div> + </div> +</template> + +<script lang="ts"> +import { defineComponent } from "vue-demi"; +// import the store for current module +import useResourceStore from "../../store"; +import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; +import type { ResourceTypeOption } from "../../types"; + +export default defineComponent({ + setup() { + const resourceStore = useResourceStore(); + + return { resourceStore }; + }, + + components: {}, + + data() { + return { + readonly: true, + }; + }, + + computed: { + resource(): ResourceObject | null { + return this.resourceStore.currentResource; + }, + resourceSizeText(): string { + if ( + this.resource && + (this.resource.resourceTypeOption as ResourceTypeOption).Size + ) { + return ( + (this.resource.resourceTypeOption as ResourceTypeOption).Size + " GB" + ); + } else { + return this.$t("default.none").toString(); + } + }, + }, + + methods: { + copyText(value: string, index: number) { + if (this.resource) { + navigator.clipboard.writeText(value); + this.$root.$emit("bv::show::tooltip", `copyTooltip${index}`); + } + }, + }, +}); +</script> + +<style></style> diff --git a/src/modules/resource/components/settings/Metadata.vue b/src/modules/resource/components/settings/Metadata.vue new file mode 100644 index 0000000000000000000000000000000000000000..87a7d3e0ab2b403f963c1b9eff9d50cd40d9d6ab --- /dev/null +++ b/src/modules/resource/components/settings/Metadata.vue @@ -0,0 +1,96 @@ +<template> + <div> + <!-- Form Generator --> + <b-row v-if="isLoadingFormGenerator" align-h="center" class="my-2"> + <b-spinner variant="secondary" /> + </b-row> + <FormGenerator + v-else + :key="resourceForm.applicationProfile" + :disabledMode="!isOwner || resource.archived" + :fixedValueMode="true" + :fixedValues="resourceForm.fixedValues" + :applicationProfileId="resourceForm.applicationProfile" + :SHACLDefinition="applicationProfileString" + :classReceiver="receiveClass" + :userReceiver="async () => user" + mimeType="application/ld+json" + /> + </div> +</template> + +<script lang="ts"> +import { defineComponent, PropType } from "vue-demi"; +// import the store for current module +import useResourceStore from "../../store"; +import useProjectStore from "@/modules/project/store"; +import useUserStore from "@/modules/user/store"; +import "@/plugins/form-generator"; +import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; +import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api.Metadata"; +import type { UserObject } from "@coscine/api-client/dist/types/Coscine.Api.User"; + +export default defineComponent({ + setup() { + const resourceStore = useResourceStore(); + const projectStore = useProjectStore(); + const userStore = useUserStore(); + + return { resourceStore, projectStore, userStore }; + }, + + props: { + value: { + type: Object as PropType<ResourceObject>, + required: true, + }, + applicationProfileString: { + type: [String, null] as PropType<string | null>, + default: null, + }, + isLoadingFormGenerator: { + type: Boolean, + required: true, + }, + }, + + data() { + return { + resourceForm: this.value, + }; + }, + + computed: { + resource(): ResourceObject | null { + return this.resourceStore.currentResource; + }, + isOwner(): boolean | undefined { + return this.projectStore.currentUserRoleIsOwner; + }, + user(): UserObject | null { + return this.userStore.user; + }, + }, + + watch: { + resourceForm: { + handler() { + this.$emit("input", this.resourceForm); + }, + deep: true, + }, + }, + + methods: { + async receiveClass(className: string): Promise<BilingualLabels> { + return await this.resourceStore.getClass(className); + }, + }, + + emits: { + input: (_: ResourceObject) => null, + }, +}); +</script> + +<style></style> diff --git a/src/modules/resource/components/settings/Overview.vue b/src/modules/resource/components/settings/Overview.vue new file mode 100644 index 0000000000000000000000000000000000000000..d01f1079a4cf884edae201a373ef46466969e2af --- /dev/null +++ b/src/modules/resource/components/settings/Overview.vue @@ -0,0 +1,277 @@ +<template> + <div class="overview"> + <!-- First Row --> + <b-row id="selectBoxes"> + <!-- Project --> + <b-col> + <coscine-form-group + labelFor="ProjectName" + :label="$t('form.project.projectNameLabel')" + :isLoading="!project" + type="input" + > + <b-form-input + v-if="project" + id="ProjectName" + v-model="project.displayName" + :readonly="readonly" + required + /> + </coscine-form-group> + </b-col> + + <!-- Resource Dropdown --> + <b-col> + <coscine-form-group + labelFor="Resource" + :label="$t('page.settings.overview.resourceLabel')" + :isLoading="!resource" + > + <b-form-select + v-if="resource" + id="Resource" + v-model="selectedResource" + :options="allResources" + value-field="id" + text-field="displayName" + @change="switchResource" + > + <template #first> + <b-form-select-option :value="undefined" disabled> + {{ $t("page.settings.overview.resourceSelect") }} + </b-form-select-option> + </template> + </b-form-select> + </coscine-form-group> + </b-col> + </b-row> + + <!-- Second Row --> + <b-row> + <!-- Display Name --> + <b-col> + <coscine-form-group + labelFor="DisplayName" + :label="$t('form.resource.resourceNameLabel')" + :isLoading="!resource" + > + <b-form-input + v-if="resource" + id="DisplayName" + v-model="resource.displayName" + required + :readonly="readonly" + /> + </coscine-form-group> + </b-col> + + <!-- Persistent ID --> + <b-col> + <coscine-form-group + labelFor="PersistentId" + :label="$t('page.settings.overview.persistentIdLabel')" + :isLoading="!resource" + > + <b-button-group class="w-100"> + <!-- Text Field --> + <b-form-input + v-if="resource" + id="PersistentId" + v-model="resource.pid" + required + :readonly="readonly" + /> + + <!-- Copy Button --> + <b-button id="copyPidButton" @click="copyPID()"> + <b-icon icon="clipboard" /> + </b-button> + + <!-- Copied Tooltip --> + <b-tooltip + id="copyPidTooltip" + target="copyPidButton" + placement="top" + triggers="blur" + > + {{ + $t("page.settings.configuration.toClipboard", { + resourceOption: $t("page.settings.overview.persistentId"), + }) + }} + </b-tooltip> + </b-button-group> + </coscine-form-group> + </b-col> + </b-row> + + <!-- Third Row --> + <b-row> + <!-- Resource Type --> + <b-col> + <coscine-form-group + labelFor="ResourceType" + :label="$t('page.settings.overview.resourceTypeLabel')" + :isLoading="!resource" + type="input" + > + <b-form-input + v-if="resource" + id="ResourceType" + :value=" + $t('resourceTypes.' + resource.type.displayName + '.displayName') + " + required + :readonly="readonly" + /> + </coscine-form-group> + </b-col> + + <!-- Resource Quota --> + <b-col> + <coscine-form-group + v-if="resource && resource.resourceTypeOption.Size" + labelFor="Quota" + :label="$t('page.settings.overview.quotaLabel')" + :isLoading="!(resource && quotaCurrent >= 0 && quotaCurrent !== null)" + > + <b-button-group class="progressContainer h-100 w-100"> + <!-- Progress Bar --> + <b-progress + :max="quotaMaximum" + height="inherit" + class="w-100 border-1 light" + > + <b-progress-bar + :value="quotaCurrent" + :variant=" + quotaCurrent / quotaMaximum >= 0.8 ? 'danger' : 'primary' + " + > + <span> + <strong + :class=" + quotaCurrent / quotaMaximum < 0.5 + ? 'justify-content-center d-flex position-absolute w-100 text-body' + : '' + " + > + {{ formatBytes(quotaCurrent) }} / + {{ formatBytes(quotaMaximum) }} + </strong> + </span> + </b-progress-bar> + </b-progress> + + <!-- Quota Page --> + <b-button v-if="isOwner" :to="{ name: 'project-quota' }"> + <b-icon icon="gear" /> + </b-button> + </b-button-group> + </coscine-form-group> + </b-col> + </b-row> + </div> +</template> + +<script lang="ts"> +import { defineComponent, PropType } from "vue-demi"; +// import the store for current module +import useResourceStore from "../../store"; +import useProjectStore from "@/modules/project/store"; +import { FileUtil } from "../../utils/FileUtil"; +import type { ResourceObject } from "@coscine/api-client/dist/types/Coscine.Api.Resources"; +import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; +import type { RawLocation } from "vue-router"; + +export default defineComponent({ + setup() { + const resourceStore = useResourceStore(); + const projectStore = useProjectStore(); + + return { resourceStore, projectStore }; + }, + + components: {}, + + props: { + value: { + type: Object as PropType<ResourceObject>, + required: false, + }, + }, + + data() { + return { + readonly: true, + selectedResource: undefined as string | undefined, + conversionFactor: Math.pow(1024, 3), // Byte <-> GB + }; + }, + + created() { + // Fill the resource form + this.onResourceLoaded(); + }, + + computed: { + project(): ProjectObject | null { + return this.projectStore.currentProject; + }, + resource(): ResourceObject | null { + return this.resourceStore.currentResource; + }, + isOwner(): boolean | undefined { + return this.projectStore.currentUserRoleIsOwner; + }, + allResources(): ResourceObject[] | null { + return this.projectStore.currentResources; + }, + quotaCurrent(): number | null { + return this.resourceStore.currentUsedQuota; // value is in Bytes + }, + quotaMaximum(): number | null { + if (this.resource) { + return this.resource.resourceTypeOption.Size * this.conversionFactor; // GB to Byte + } else { + return null; + } + }, + }, + + watch: { + resource() { + this.onResourceLoaded(); + }, + }, + + methods: { + onResourceLoaded() { + if (this.resource) { + this.selectedResource = this.resource.id; + } + }, + copyPID() { + if (this.resource) { + navigator.clipboard.writeText( + "http://hdl.handle.net/" + this.resource.pid + ); + this.$root.$emit("bv::show::tooltip", "copyPidTooltip"); + } + }, + switchResource(id: string) { + const route = { + name: "resource-settings", + params: { guid: id }, + } as RawLocation; + const url = this.$router.resolve(route).href; + window.location.href = url; + }, + formatBytes(bytes: number) { + return FileUtil.formatBytes(bytes); + }, + }, +}); +</script> + +<style scoped></style> diff --git a/src/modules/resource/components/settings/modals/ArchiveResourceModal.vue b/src/modules/resource/components/settings/modals/ArchiveResourceModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..fcbe449b46fe7f03f1fd7f5fb4f12e2a73a3955f --- /dev/null +++ b/src/modules/resource/components/settings/modals/ArchiveResourceModal.vue @@ -0,0 +1,87 @@ +<template> + <!-- Archive Resource Modal --> + <b-modal v-model="isOpen" @close="close" @hidden="hidden" :hide-footer="true"> + <!-- Title --> + <template #modal-title> + <span class="h6"> + {{ $t(`page.settings.actions.${action}.modal.title`) }} + </span> + </template> + + <!-- Body --> + <i18n + :path="`page.settings.actions.${action}.modal.body`" + tag="p" + class="mb-3" + > + <template #br><br /></template> + </i18n> + + <template> + <!-- Modal Cancel Button --> + <b-button @click="close">{{ $t("buttons.cancel") }}</b-button> + <!-- Modal Delete Button --> + <b-button @click="toggleArchive" variant="primary" class="float-right"> + {{ $t(`buttons.${action}`) }} + </b-button> + </template> + </b-modal> +</template> + +<script lang="ts"> +import { defineComponent, PropType } from "vue-demi"; + +export default defineComponent({ + props: { + archived: { + type: [Boolean, null] as PropType<boolean | null>, + default: null, + }, + open: { + required: true, + default: () => false, + type: Boolean, + }, + }, + + data() { + return { + isOpen: false, + deleteName: "", + inputState: false, + showHelp: false, + }; + }, + + computed: { + action(): string { + return this.archived ? "unarchive" : "archive"; + }, + }, + + watch: { + open() { + this.isOpen = this.open; + }, + }, + + methods: { + close() { + this.isOpen = false; + }, + hidden() { + this.$emit("close"); + }, + toggleArchive() { + this.$emit("toggleArchive"); + }, + }, + + emits: { + toggleArchive: null, + close: null, + }, +}); +</script> + +<style></style> diff --git a/src/modules/resource/components/settings/modals/DeleteResourceModal.vue b/src/modules/resource/components/settings/modals/DeleteResourceModal.vue new file mode 100644 index 0000000000000000000000000000000000000000..4da11940a68ad0d677419f97e661f2c292bcf902 --- /dev/null +++ b/src/modules/resource/components/settings/modals/DeleteResourceModal.vue @@ -0,0 +1,115 @@ +<template> + <!-- Delete Resource Modal --> + <b-modal v-model="isOpen" @close="close" @hidden="hidden" :hide-footer="true"> + <!-- Title --> + <template #modal-title> + <span class="h6"> {{ $t(titleKey) }} </span> + </template> + + <div> + {{ $t(descriptionKey) }} + </div> + + <b-form-group> + <!-- Modal Resource Display Name --> + <p class="h6 my-2"> + {{ displayName }} + </p> + + <!-- Modal Input Text Field --> + <b-form-input + v-model="deleteName" + :state="nameValid" + :placeholder="displayName" + required + @keyup.enter.prevent="clickDelete" + ></b-form-input> + <div class="invalid-tooltip"> + {{ $t("page.settings.actions.delete.modal.help") }} + </div> + </b-form-group> + + <template> + <!-- Modal Cancel Button --> + <b-button @click="close">{{ $t("buttons.cancel") }}</b-button> + <!-- Modal Delete Button --> + <b-button + @click="clickDelete" + variant="danger" + class="float-right" + :disabled="!nameValid" + >{{ $t("buttons.delete") }}</b-button + > + </template> + </b-modal> +</template> + +<script lang="ts"> +import { defineComponent } from "vue-demi"; + +export default defineComponent({ + props: { + descriptionKey: { + default: "page.settings.actions.delete.modal.body", + type: String, + }, + titleKey: { + default: "page.settings.actions.delete.modal.title", + type: String, + }, + open: { + required: true, + default: () => false, + type: Boolean, + }, + displayName: { + default: "", + type: String, + }, + }, + + data() { + return { + isOpen: false, + deleteName: "", + inputState: false, + showHelp: false, + }; + }, + + computed: { + nameValid(): boolean | null { + return this.deleteName && this.displayName + ? this.deleteName.trim() === this.displayName.trim() + : null; + }, + }, + + watch: { + open() { + this.isOpen = this.open; + }, + }, + + methods: { + close() { + this.isOpen = false; + }, + hidden() { + this.$emit("close"); + }, + clickDelete() { + if (this.nameValid) { + this.$emit("clickDelete"); + } + }, + }, + + emits: { + clickDelete: null, + close: null, + }, +}); +</script> + +<style></style> diff --git a/src/modules/resource/i18n/de.ts b/src/modules/resource/i18n/de.ts index 9db4f95fbe17edfac23bef5ca5c01fe7125ae858..83cbe6ddd81ba0b82c2960994df8407d982edab4 100644 --- a/src/modules/resource/i18n/de.ts +++ b/src/modules/resource/i18n/de.ts @@ -9,14 +9,8 @@ export default { page: { createResource: { title: "Ressource hinzufügen", - steps: { - first: "Ressourcen-Konfiguration", - second: "Generelle Informationen", - third: "Metadaten der Ressource", - fourth: "Übersicht & Bestätigung", - }, - setup: { - title: "Schritt 1: @:(page.createResource.steps.first)", + configuration: { + title: "Schritt 1: @:(form.steps.first)", needMore: "Benötigen Sie mehr Speicher?", bucketSize: "{size} GB, dies entspricht circa {files} Dateien.", labels: { @@ -33,72 +27,10 @@ export default { }, }, general: { - title: "Schritt 2: @:(page.createResource.steps.second)", - form: { - resource: { - labelSymbol: ":", - - resourceName: "Ressourcenname", - resourceNameHelp: - "Dieses Feld ist erforderlich und besitzt eine Maximallänge von {maxLength} Zeichen.", - resourceNameLabel: - "@:(page.createResource.general.form.resource.resourceName)@:(page.createResource.general.form.resource.labelSymbol)", - - displayName: "Anzeigename", - displayNameHelp: - "Dieses Feld ist erforderlich und besitzt eine Maximallänge von {maxLength} Zeichen.", - displayNameLabel: - "@:(page.createResource.general.form.resource.displayName)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceDescription: "Ressourcenbeschreibung", - resourceDescriptionHelp: - "Dieses Feld ist erforderlich und besitzt eine Maximallänge von {maxLength} Zeichen.", - resourceDescriptionLabel: - "@:(page.createResource.general.form.resource.resourceDescription)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceDiscipline: "Disziplin", - resourceDisciplineLabel: - "@:(page.createResource.general.form.resource.resourceDiscipline)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceKeywords: "Ressourcenschlagwörter", - resourceKeywordsPlaceholder: - 'Tippen und drücken Sie "Enter", um ein Schlagwort einzufügen.', - resourceKeywordsHelp: - "Dieses Feld besitzt eine Maximallänge von {maxLength} Zeichen.", - resourceKeywordsEmpty: "Die Liste ist leer.", - resourceKeywordsLabel: - "@:(page.createResource.general.form.resource.resourceKeywords)@:(page.createResource.general.form.resource.labelSymbol)", - tagPlaceholder: "Sie können diesen Tag hinzufügen", - - resourceVisibility: "Sichtbarkeit", - resourceVisibilityLabel: - "@:(page.createResource.general.form.resource.resourceVisibility)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceLicense: "Lizenz", - resourceLicenseLabel: - "@:(page.createResource.general.form.resource.resourceLicense)@:(page.createResource.general.form.resource.labelSymbol)", - resourceLicensePopover: - "Für weitere Informationen zu Lizenzen siehe ", - resourceLicensePopoverUrl: - "https://help.itc.rwth-aachen.de/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", - resourceLicenseSelect: "Bitte wählen Sie eine Lizenz aus", - resourceLicenseHelp: - "Dieses Feld besitzt eine Maximallänge von {maxLength} Zeichen.", - - resourceReuse: "Interne Regeln zur Nachnutzung", - resourceReuseLabel: - "@:(page.createResource.general.form.resource.resourceReuse)@:(page.createResource.general.form.resource.labelSymbol)", - resourceReusePopover: - "Für weitere Informationen zu internen Regeln zur Nachnutzung siehe ", - resourceReusePopoverUrl: - "https://help.itc.rwth-aachen.de/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", - resourceReuseHelp: - "Dieses Feld besitzt eine Maximallänge von {maxLength} Zeichen.", - }, - }, + title: "Schritt 2: @:(form.steps.second)", }, metadata: { - title: "Schritt 3: @:(page.createResource.steps.third)", + title: "Schritt 3: @:(form.steps.third)", createAp: { tooltip: "Anfrage zur Erstellung eines Applikationsprofils", tooltipDisabled: @@ -111,7 +43,7 @@ export default { selectApplicationProfile: "Bitte wählen sie ein Applikationsprofil aus", }, overview: { - title: "Schritt 4: @:(page.createResource.steps.fourth)", + title: "Schritt 4: @:(form.steps.fourth)", resourceConfiguration: "Ressourcen Konfiguration", }, multiselect: { @@ -150,7 +82,7 @@ export default { description: "Ressourcenbeschreibung", disciplines: "Disziplin", keywords: "Ressourcenschlagwörter", - visibility: "Sichtbarkeit", + visibility: "Sichtbarkeit der Metadaten", license: "Lizenz", usageRights: "Verwendungsrechte", @@ -232,8 +164,108 @@ export default { size: "Dateigröße", }, settings: { - title: "Settings-Seite", - description: "Das ist die @:page.settings.title des Coscine UIv2 Apps", + title: "@:(breadcrumbs.resource.settings)", + overview: { + resource: "Ressource", + resourceLabel: + "@:(page.settings.overview.resource)@:(form.labelSymbol)", + resourceSelect: "Bitte wählen Sie eine Ressource aus", + + resourceTypeLabel: + "@:(page.createResource.configuration.labels.resourceType)", + + persistentId: "Persistent Identifier (PID)", + persistentIdLabel: + "@:(page.settings.overview.persistentId)@:(form.labelSymbol)", + + quota: "Quota", + quotaLabel: "@:(page.settings.overview.quota)@:(form.labelSymbol)", + }, + configuration: { + resourceBucketName: "Bucket-Name", + resourceBucketNameLabel: + "@:(page.settings.configuration.resourceBucketName)@:(form.labelSymbol)", + + resourceSize: "Ressourcengröße", + resourceSizeLabel: + "@:(page.settings.configuration.resourceSize)@:(form.labelSymbol)", + + resourceAccessKey: "Access Key", + resourceReadAccessKey: + "@:(page.settings.configuration.resourceAccessKey) (Lesen)", + resourceReadAccessKeyLabel: + "@:(page.settings.configuration.resourceReadAccessKey)@:(form.labelSymbol)", + + resourceWriteAccessKey: + "@:(page.settings.configuration.resourceAccessKey) (Schreiben)", + resourceWriteAccessKeyLabel: + "@:(page.settings.configuration.resourceWriteAccessKey)@:(form.labelSymbol)", + + resourceSecretKey: "Secret Key", + resourceReadSecretKey: + "@:(page.settings.configuration.resourceSecretKey) (Lesen)", + resourceReadSecretKeyLabel: + "@:(page.settings.configuration.resourceReadSecretKey)@:(form.labelSymbol)", + + resourceWriteSecretKey: + "@:(page.settings.configuration.resourceSecretKey) (Schreiben)", + resourceWriteSecretKeyLabel: + "@:(page.settings.configuration.resourceWriteSecretKey)@:(form.labelSymbol)", + + resourceEndpoint: "End Point", + resourceEndpointLabel: + "@:(page.settings.configuration.resourceEndpoint)@:(form.labelSymbol)", + + resourceResourceUrl: "Ressourcen-URL", + resourceResourceUrlLabel: + "@:(page.settings.configuration.resourceResourceUrl)@:(form.labelSymbol)", + + resourceRepositoryNumber: "Repository ID", + resourceRepositoryNumberLabel: + "@:(page.settings.configuration.resourceRepositoryNumber)@:(form.labelSymbol)", + + resourceRepositoryUrl: "Repository URL", + resourceRepositoryUrlLabel: + "@:(page.settings.configuration.resourceRepositoryUrl)@:(form.labelSymbol)", + + toClipboard: "{resourceOption} wurde in der Zwischenablage kopiert", + }, + actions: { + resourceArchive: "Ressource archivieren", + resourceArchiveLabel: + "@:(page.settings.actions.resourceArchive)@:(form.labelSymbol)", + + resourceDelete: "Ressource löschen", + resourceDeleteLabel: + "@:(page.settings.actions.resourceDelete)@:(form.labelSymbol)", + archive: { + modal: { + title: "Ressource archivieren", + body: "Wenn der Status einer Ressource auf archiviert gesetzt wird, können Daten und Metadaten von Benutzern nicht mehr geändert werden. Das Lesen der Daten und das Herunterladen von Dateien ist jedoch weiterhin möglich. {br}{br}Der Status kann von Projektbesitzern zurückgesetzt werden, wenn die Daten ergänzt oder aktualisiert werden sollen.", + }, + toast: { + title: "Ressource archiviert", + body: "Die Ressource wurde erfolgreich archiviert.", + }, + }, + unarchive: { + modal: { + title: "Aufheben des archivierten Status", + body: "Wenn der archivierte Status nicht mehr gesetzt ist, kehren die Ressourcen zur normalen Funktionalität zurück (z.B. Hinzufügen neuer Dateien und Aktualisieren von Metadaten). {br}{br}Der Status kann von Projektbesitzern wieder gesetzt werden, um zu verhindern, dass Benutzer Daten bearbeiten.", + }, + toast: { + title: "Ressourcenstatus zurückgesetzt", + body: "Der Archivierungsstatus der Ressource wurde erfolgreich zurückgesetzt.", + }, + }, + delete: { + modal: { + title: "Ressource wirklich entfernen?", + body: "Wenn Sie sicher sind, dass Sie diese Ressource entfernen möchten, wiederholen Sie bitte den Ressourcennamen:", + help: "Der angegebene Name stimmt nicht mit dem Ressourcenname überein.", + }, + }, + }, }, }, @@ -295,5 +327,74 @@ export default { }, }, }, - } as VueI18n.LocaleMessageObject, + }, + + form: { + labelSymbol: ":", + project: { + projectName: "Projektname", + projectNameLabel: "@:(form.project.projectName)@:(form.labelSymbol)", + }, + resource: { + resourceName: "Ressourcenname", + resourceNameHelp: + "Dieses Feld ist erforderlich und besitzt eine Maximallänge von {maxLength} Zeichen.", + resourceNameLabel: "@:(form.resource.resourceName)@:(form.labelSymbol)", + + displayName: "Anzeigename", + displayNameHelp: + "Dieses Feld ist erforderlich und besitzt eine Maximallänge von {maxLength} Zeichen.", + displayNameLabel: "@:(form.resource.displayName)@:(form.labelSymbol)", + + resourceDescription: "Ressourcenbeschreibung", + resourceDescriptionHelp: + "Dieses Feld ist erforderlich und besitzt eine Maximallänge von {maxLength} Zeichen.", + resourceDescriptionLabel: + "@:(form.resource.resourceDescription)@:(form.labelSymbol)", + + resourceDiscipline: "Disziplin", + resourceDisciplineLabel: + "@:(form.resource.resourceDiscipline)@:(form.labelSymbol)", + + resourceKeywords: "Ressourcenschlagwörter", + resourceKeywordsPlaceholder: + 'Tippen und drücken Sie "Enter", um ein Schlagwort einzufügen.', + resourceKeywordsHelp: + "Dieses Feld besitzt eine Maximallänge von {maxLength} Zeichen.", + resourceKeywordsEmpty: "Die Liste ist leer.", + resourceKeywordsLabel: + "@:(form.resource.resourceKeywords)@:(form.labelSymbol)", + tagPlaceholder: "Sie können diesen Tag hinzufügen", + + resourceMetadataVisibility: "Sichtbarkeit der Metadaten", + resourceMetadataVisibilityLabel: + "@:(form.resource.resourceMetadataVisibility)@:(form.labelSymbol)", + + resourceLicense: "Lizenz", + resourceLicenseLabel: + "@:(form.resource.resourceLicense)@:(form.labelSymbol)", + resourceLicensePopover: "Für weitere Informationen zu Lizenzen siehe ", + resourceLicensePopoverUrl: + "https://help.itc.rwth-aachen.de/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", + resourceLicenseSelect: "Bitte wählen Sie eine Lizenz aus", + resourceLicenseHelp: + "Dieses Feld besitzt eine Maximallänge von {maxLength} Zeichen.", + + resourceReuse: "Interne Regeln zur Nachnutzung", + resourceReuseLabel: "@:(form.resource.resourceReuse)@:(form.labelSymbol)", + resourceReusePopover: + "Für weitere Informationen zu internen Regeln zur Nachnutzung siehe ", + resourceReusePopoverUrl: + "https://help.itc.rwth-aachen.de/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", + resourceReuseHelp: + "Dieses Feld besitzt eine Maximallänge von {maxLength} Zeichen.", + }, + steps: { + first: "Ressourcen-Konfiguration", + second: "Generelle Informationen", + third: "Metadaten der Ressource", + fourth: "Übersicht & Bestätigung", + fifth: "Aktionen", + }, + }, } as VueI18n.LocaleMessageObject; diff --git a/src/modules/resource/i18n/en.ts b/src/modules/resource/i18n/en.ts index 752d3a7e8a000f1f417cb8559fb4231cfa17d914..39c2f3ae188dff91c0087bd79d417076d708705c 100644 --- a/src/modules/resource/i18n/en.ts +++ b/src/modules/resource/i18n/en.ts @@ -9,14 +9,8 @@ export default { page: { createResource: { title: "Add Resource", - steps: { - first: "Resource Configuration", - second: "General Information", - third: "Resource Metadata", - fourth: "Overview & Confirm", - }, - setup: { - title: "Step 1: @:(page.createResource.steps.first)", + configuration: { + title: "Step 1: @:(form.steps.first)", needMore: "Need more?", bucketSize: "{size} GB, this equals approximately {files} files.", labels: { @@ -33,71 +27,10 @@ export default { }, }, general: { - title: "Step 2: @:(page.createResource.steps.second)", - form: { - resource: { - labelSymbol: ":", - - resourceName: "Resource Name", - resourceNameHelp: - "This is a required field and can only contain up to {maxLength} characters.", - resourceNameLabel: - "@:(page.createResource.general.form.resource.resourceName)@:(page.createResource.general.form.resource.labelSymbol)", - - displayName: "Display Name", - displayNameHelp: - "This is a required field and can only contain up to {maxLength} characters.", - displayNameLabel: - "@:(page.createResource.general.form.resource.displayName)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceDescription: "Resource Description", - resourceDescriptionHelp: - "This is a required field and can only contain up to {maxLength} characters.", - resourceDescriptionLabel: - "@:(page.createResource.general.form.resource.resourceDescription)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceDiscipline: "Discipline", - resourceDisciplineLabel: - "@:(page.createResource.general.form.resource.resourceDiscipline)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceKeywords: "Resource Keywords", - resourceKeywordsPlaceholder: - 'Type, then press "Enter" to insert a Keyword.', - resourceKeywordsHelp: - "This field can only contain up to {maxLength} characters.", - resourceKeywordsEmpty: "The list of keywords is empty.", - resourceKeywordsLabel: - "@:(page.createResource.general.form.resource.resourceKeywords)@:(page.createResource.general.form.resource.labelSymbol)", - tagPlaceholder: "You can add this tag", - - resourceVisibility: "Visibility", - resourceVisibilityLabel: - "@:(page.createResource.general.form.resource.resourceVisibility)@:(page.createResource.general.form.resource.labelSymbol)", - - resourceLicense: "License", - resourceLicenseLabel: - "@:(page.createResource.general.form.resource.resourceLicense)@:(page.createResource.general.form.resource.labelSymbol)", - resourceLicensePopover: "For more information on licenses see ", - resourceLicensePopoverUrl: - "https://help.itc.rwth-aachen.de/en/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", - resourceLicenseSelect: "Please select a License", - resourceLicenseHelp: - "This field can only contain up to {maxLength} characters.", - - resourceReuse: "Internal Rules for Reuse", - resourceReuseLabel: - "@:(page.createResource.general.form.resource.resourceReuse)@:(page.createResource.general.form.resource.labelSymbol)", - resourceReusePopover: - "For more information on internal rules for reuse see ", - resourceReusePopoverUrl: - "https://help.itc.rwth-aachen.de/en/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", - resourceReuseHelp: - "This field can only contain up to {maxLength} characters.", - }, - }, + title: "Step 2: @:(form.steps.second)", }, metadata: { - title: "Step 3: @:(page.createResource.steps.third)", + title: "Step 3: @:(form.steps.third)", createAp: { tooltip: "Request for creation of application profiles", tooltipDisabled: @@ -110,7 +43,7 @@ export default { selectApplicationProfile: "Please select an application profile", }, overview: { - title: "Step 4: @:(page.createResource.steps.fourth)", + title: "Step 4: @:(form.steps.fourth)", resourceConfiguration: "Resource Configuration", }, multiselect: { @@ -148,7 +81,7 @@ export default { description: "Resource Description", disciplines: "Discipline", keywords: "Resource Keywords", - visibility: "Visibility", + visibility: "Metadata Visibility", license: "License", usageRights: "Usage Rights", @@ -228,8 +161,108 @@ export default { size: "File Size", }, settings: { - title: "Settings Page", - description: "This is the @:page.settings.title for the Coscine UIv2 App", + title: "@:(breadcrumbs.resource.settings)", + overview: { + resource: "Resource", + resourceLabel: + "@:(page.settings.overview.resource)@:(form.labelSymbol)", + resourceSelect: "Please select a resource", + + resourceTypeLabel: + "@:(page.createResource.configuration.labels.resourceType)", + + persistentId: "Persistent Identifier (PID)", + persistentIdLabel: + "@:(page.settings.overview.persistentId)@:(form.labelSymbol)", + + quota: "Quota", + quotaLabel: "@:(page.settings.overview.quota)@:(form.labelSymbol)", + }, + configuration: { + resourceBucketName: "Bucket Name", + resourceBucketNameLabel: + "@:(page.settings.configuration.resourceBucketName)@:(form.labelSymbol)", + + resourceSize: "Resource Size", + resourceSizeLabel: + "@:(page.settings.configuration.resourceSize)@:(form.labelSymbol)", + + resourceAccessKey: "Access Key", + resourceReadAccessKey: + "@:(page.settings.configuration.resourceAccessKey) (Reading)", + resourceReadAccessKeyLabel: + "@:(page.settings.configuration.resourceReadAccessKey)@:(form.labelSymbol)", + + resourceWriteAccessKey: + "@:(page.settings.configuration.resourceAccessKey) (Writing)", + resourceWriteAccessKeyLabel: + "@:(page.settings.configuration.resourceWriteAccessKey)@:(form.labelSymbol)", + + resourceSecretKey: "Secret Key", + resourceReadSecretKey: + "@:(page.settings.configuration.resourceSecretKey) (Reading)", + resourceReadSecretKeyLabel: + "@:(page.settings.configuration.resourceReadSecretKey)@:(form.labelSymbol)", + + resourceWriteSecretKey: + "@:(page.settings.configuration.resourceSecretKey) (Writing)", + resourceWriteSecretKeyLabel: + "@:(page.settings.configuration.resourceWriteSecretKey)@:(form.labelSymbol)", + + resourceEndpoint: "End Point", + resourceEndpointLabel: + "@:(page.settings.configuration.resourceEndpoint)@:(form.labelSymbol)", + + resourceResourceUrl: "Resource Url", + resourceResourceUrlLabel: + "@:(page.settings.configuration.resourceResourceUrl)@:(form.labelSymbol)", + + resourceRepositoryNumber: "Repository ID", + resourceRepositoryNumberLabel: + "@:(page.settings.configuration.resourceRepositoryNumber)@:(form.labelSymbol)", + + resourceRepositoryUrl: "Repository URL", + resourceRepositoryUrlLabel: + "@:(page.settings.configuration.resourceRepositoryUrl)@:(form.labelSymbol)", + + toClipboard: "{resourceOption} has been copied to clipboard", + }, + actions: { + resourceArchive: "Archive Resource", + resourceArchiveLabel: + "@:(page.settings.actions.resourceArchive)@:(form.labelSymbol)", + + resourceDelete: "Delete Resource", + resourceDeleteLabel: + "@:(page.settings.actions.resourceDelete)@:(form.labelSymbol)", + archive: { + modal: { + title: "Archive resource", + body: "If the status of a resource is set to archived, data and metadata can not be changed by users any longer. However, reading the data and downloading files is still possible. {br}{br}The status can be unset by project owners if the data has to be extended or updated.", + }, + toast: { + title: "Resource archived", + body: "The resource was successfully set to archived.", + }, + }, + unarchive: { + modal: { + title: "Unset archived status", + body: "If the archived-status is unset, resources return to normal functionality (e.g. adding new files and updating metadata). {br}{br}The archived status can be set again by project owners to prevent users from editing data.", + }, + toast: { + title: "Resource status reset", + body: "The resource archived status was successfully set to normal.", + }, + }, + delete: { + modal: { + title: "Do you really want to delete this resource?", + body: "If you are sure you really want to delete this resource, please type its name:", + help: "The entered name does not match the resource name.", + }, + }, + }, }, }, @@ -286,5 +319,74 @@ export default { }, }, }, - } as VueI18n.LocaleMessageObject, + }, + + form: { + labelSymbol: ":", + project: { + projectName: "Project Name", + projectNameLabel: "@:(form.project.projectName)@:(form.labelSymbol)", + }, + resource: { + resourceName: "Resource Name", + resourceNameHelp: + "This is a required field and can only contain up to {maxLength} characters.", + resourceNameLabel: "@:(form.resource.resourceName)@:(form.labelSymbol)", + + displayName: "Display Name", + displayNameHelp: + "This is a required field and can only contain up to {maxLength} characters.", + displayNameLabel: "@:(form.resource.displayName)@:(form.labelSymbol)", + + resourceDescription: "Resource Description", + resourceDescriptionHelp: + "This is a required field and can only contain up to {maxLength} characters.", + resourceDescriptionLabel: + "@:(form.resource.resourceDescription)@:(form.labelSymbol)", + + resourceDiscipline: "Discipline", + resourceDisciplineLabel: + "@:(form.resource.resourceDiscipline)@:(form.labelSymbol)", + + resourceKeywords: "Resource Keywords", + resourceKeywordsPlaceholder: + 'Type, then press "Enter" to insert a Keyword.', + resourceKeywordsHelp: + "This field can only contain up to {maxLength} characters.", + resourceKeywordsEmpty: "The list of keywords is empty.", + resourceKeywordsLabel: + "@:(form.resource.resourceKeywords)@:(form.labelSymbol)", + tagPlaceholder: "You can add this tag", + + resourceMetadataVisibility: "Metadata Visibility", + resourceMetadataVisibilityLabel: + "@:(form.resource.resourceMetadataVisibility)@:(form.labelSymbol)", + + resourceLicense: "License", + resourceLicenseLabel: + "@:(form.resource.resourceLicense)@:(form.labelSymbol)", + resourceLicensePopover: "For more information on licenses see ", + resourceLicensePopoverUrl: + "https://help.itc.rwth-aachen.de/en/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", + resourceLicenseSelect: "Please select a License", + resourceLicenseHelp: + "This field can only contain up to {maxLength} characters.", + + resourceReuse: "Internal Rules for Reuse", + resourceReuseLabel: "@:(form.resource.resourceReuse)@:(form.labelSymbol)", + resourceReusePopover: + "For more information on internal rules for reuse see ", + resourceReusePopoverUrl: + "https://help.itc.rwth-aachen.de/en/service/b2b7729fd93f4c7080b475776f6b5d87/article/7812e3fcd3a241808f5b91e11b6ca3a9/", + resourceReuseHelp: + "This field can only contain up to {maxLength} characters.", + }, + steps: { + first: "Resource Configuration", + second: "General Information", + third: "Resource Metadata", + fourth: "Overview & Confirm", + fifth: "Actions", + }, + }, } as VueI18n.LocaleMessageObject; diff --git a/src/modules/resource/pages/CreateResource.vue b/src/modules/resource/pages/CreateResource.vue index eb37bc36ef45f9a7cdc4563e850e99e9255f8872..b2e8f0aa14b480ff8661fb8240f28b8832a0ea1d 100644 --- a/src/modules/resource/pages/CreateResource.vue +++ b/src/modules/resource/pages/CreateResource.vue @@ -17,7 +17,7 @@ <b-row> <div class="col-sm-2" /> <div class="col-sm-8"> - <Setup + <Configuration v-if="currentTab === 0" v-model="resource" :isLoading="isLoading" @@ -30,8 +30,6 @@ :isLoading="isLoading" :readonly="false" @valid="setNextTab" - @back="back" - @next="next" /> <Metadata v-else-if="currentTab === 2" @@ -40,8 +38,6 @@ :applicationProfileString="applicationProfileString" :isLoadingFormGenerator="isLoadingFormGenerator" @valid="setNextTab" - @back="back" - @next="next" /> <Overview v-else-if="currentTab === 3" @@ -53,6 +49,22 @@ @toTab="toTab" @waitingForResponse="isWaitingForResponse = $event" /> + + <b-form-group v-if="currentTab > 0 && currentTab < tabs.length - 1"> + <!-- Button Back --> + <b-button @click.prevent="back" variant="outline-primary" + >{{ $t("buttons.back") }} + </b-button> + + <!-- Button Next --> + <b-button + @click.prevent="next" + class="float-right" + variant="outline-primary" + :disabled="!tabs[currentTab + 1].active" + >{{ $t("buttons.next") }} + </b-button> + </b-form-group> </div> <div class="col-sm-2" /> </b-row> @@ -65,9 +77,9 @@ <script lang="ts"> import { defineComponent } from "vue-demi"; // import the store for current module -import { useResourceStore } from "../store"; -import { useProjectStore } from "@/modules/project/store"; -import Setup from "../components/create-resource/Configuration.vue"; +import useResourceStore from "../store"; +import useProjectStore from "@/modules/project/store"; +import Configuration from "../components/create-resource/Configuration.vue"; import General from "../components/create-resource/General.vue"; import Metadata from "../components/create-resource/Metadata.vue"; import Overview from "../components/create-resource/Overview.vue"; @@ -84,7 +96,7 @@ import type { } from "../types"; export default defineComponent({ - components: { Setup, General, Metadata, Overview }, + components: { Configuration, General, Metadata, Overview }, setup() { const projectStore = useProjectStore(); const resourceStore = useResourceStore(); @@ -123,9 +135,9 @@ export default defineComponent({ // Order does matter return [ { - title: this.$t("page.createResource.setup.title").toString(), + title: this.$t("page.createResource.configuration.title").toString(), active: true, - step: "setup", + step: "configuration", }, { title: this.$t("page.createResource.general.title").toString(), diff --git a/src/modules/resource/pages/ResourcePage.vue b/src/modules/resource/pages/ResourcePage.vue index ee3ac29c33dc8095638a5325c490d96f5e0770db..2817eaf19ed6e31ce1d9f75650ba9c8bb150b0eb 100644 --- a/src/modules/resource/pages/ResourcePage.vue +++ b/src/modules/resource/pages/ResourcePage.vue @@ -8,11 +8,11 @@ @drop.prevent="uploadDrop" > <div class="droppable" v-if="showDroppable && fileAddable"> - <p class="droppableText">{{ $parent.$t("page.resource.canDropFile") }}</p> + <p class="droppableText">{{ $t("page.resource.canDropFile") }}</p> </div> <coscine-headline v-show="!isFullscreen" - :headline="$parent.$t('page.resource.resources')" + :headline="$t('page.resource.resources')" /> <b-form-file ref="fileTrigger" @@ -49,9 +49,7 @@ squared id="metadataManagerToggleFullscreen" @click="toggleMenu()" - ><span>{{ - $parent.$t("page.resource.metadataManager") - }}</span></b-button + ><span>{{ $t("page.resource.metadataManager") }}</span></b-button > <div class="card"> <div class="card-body"> @@ -104,13 +102,13 @@ import { defineComponent } from "vue-demi"; import type Vue from "vue"; // import the store for current module -import { useResourceStore } from "../store"; -import { useProjectStore } from "@/modules/project/store"; +import useResourceStore from "../store"; +import useProjectStore from "@/modules/project/store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; -import FilesView from "../components/FilesView.vue"; -import MetadataManager from "../components/MetadataManager.vue"; +import FilesView from "../components/resource-page/FilesView.vue"; +import MetadataManager from "../components/resource-page/MetadataManager.vue"; import type { FileInformation, diff --git a/src/modules/resource/pages/Settings.vue b/src/modules/resource/pages/Settings.vue index 67e7e983021bf5689f404b9e8917d05e2c0c9fe3..de0b6837c685ea748f9cc23bd3644a9bb349cb32 100644 --- a/src/modules/resource/pages/Settings.vue +++ b/src/modules/resource/pages/Settings.vue @@ -1,43 +1,341 @@ <template> - <div> - <section - class="container flex flex-col items-center px-5 py-12 mx-auto text-gray-600 body-font md:flex-row" - > - <div> - <CoscineHeadline :headline="$parent.$t('page.settings.title')" /> - <p class="mb-8 leading-relaxed dark:text-white"> - {{ $parent.$t("page.settings.description") }} - </p> - <img - alt="From Coscine Old" - src="@/assets/images/Project-Id-R-Guid-Settings.png" - /> - </div> - </section> + <div id="settings"> + <CoscineHeadline :headline="$t('page.settings.title')" /> + + <!-- Overview --> + <Overview /> + + <!-- Navigation Tabs --> + <b-tabs justified v-model="currentTab" class="my-4" v-if="resource"> + <b-tab + v-for="(tab, index) in tabs" + :key="index" + :disabled="!tab.active" + @click.prevent="toTab(tab)" + :title="tab.title" + /> + </b-tabs> + <b-row v-else align-h="center" class="my-4"> + <b-spinner variant="secondary" /> + </b-row> + + <!-- Configuration --> + <Configuration v-show="tabs[currentTab].step === 'configuration'" /> + + <!-- General Overview from Create Resource --> + <General + v-show="tabs[currentTab].step === 'general'" + v-model="resourceForm" + :isLoading="isLoading" + :readonly="false" + @validation="validation = $event" + /> + + <!-- Metadata --> + <Metadata + v-show="tabs[currentTab].step === 'metadata'" + v-model="resourceForm" + :applicationProfileString="applicationProfileString" + :isLoadingFormGenerator="isLoading" + @clickSave="clickSave" + /> + + <!-- Actions --> + <Actions + v-if="resource" + v-show="tabs[currentTab].step === 'action'" + v-model="resource" + @toggleArchive="toggleArchive" + @clickDelete="clickDelete" + /> + + <!-- Button Confirm --> + <b-form-group> + <b-button + v-if="confirmButtonVisibility" + @click.prevent="clickSave" + :disabled="!validResourceForm" + class="float-right" + variant="primary" + > + {{ $t("buttons.confirm") }} + </b-button> + </b-form-group> + + <!-- Loading Spinner on Submit --> + <LoadingSpinner :isWaitingForResponse="isWaitingForResponse" /> </div> </template> <script lang="ts"> import { defineComponent } from "vue-demi"; -import CoscineHeadline from "@/components/CoscineHeadline.vue"; - // import the store for current module -import { useResourceStore } from "../store"; -import { useProjectStore } from "@/modules/project/store"; -// import the main store -import { useMainStore } from "@/store/index"; +import useResourceStore from "../store"; +import useProjectStore from "@/modules/project/store"; +import { navigateToProject } from "@/router"; +import General from "../components/create-resource/General.vue"; +import Metadata from "../components/settings/Metadata.vue"; +import Actions from "../components/settings/Actions.vue"; +import Overview from "../components/settings/Overview.vue"; +import Configuration from "../components/settings/Configuration.vue"; +import type { + DisciplineObject, + ResourceObject, + VisibilityObject, +} from "@coscine/api-client/dist/types/Coscine.Api.Resources"; +import type { ResourceCreationTab, ResourceTypeOption } from "../types"; +import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; +import type { Validation } from "vuelidate"; export default defineComponent({ setup() { - const mainStore = useMainStore(); const resourceStore = useResourceStore(); const projectStore = useProjectStore(); - return { mainStore, resourceStore, projectStore }; + return { resourceStore, projectStore }; }, components: { - CoscineHeadline, + Overview, + Configuration, + General, + Metadata, + Actions, + }, + + data() { + return { + currentTab: 0, + resourceForm: { + description: "", + displayName: "", + resourceName: "", + keywords: "", + license: "", + usageRights: "", + disciplines: [] as DisciplineObject[], + visibility: {} as VisibilityObject, + resourceTypeOption: {} as ResourceTypeOption, + } as ResourceObject, + applicationProfileString: null as string | null, + validation: {} as Validation, + isWaitingForResponse: false, + isLoading: false, + }; + }, + + computed: { + project(): ProjectObject | null { + return this.projectStore.currentProject; + }, + resource(): ResourceObject | null { + return this.resourceStore.currentResource; + }, + validResourceForm(): boolean { + if (this.resource && this.resource.archived) { + // Limit button to only changes in the "general" tab, when the resource is archived. + return !this.validation.$invalid && this.validation.$anyDirty; + } else { + // Button should always be enabled; Alternatively make the validation check here. + return true; + } + }, + confirmButtonVisibility(): boolean { + if (this.resource && this.resource.archived) { + // Limit button to only be visible in the "general" tab, when the resource is archived. + return this.tabs[this.currentTab].step === "general"; + } else { + return ( + this.tabs[this.currentTab].step === "general" || + this.tabs[this.currentTab].step === "metadata" + ); + } + }, + tabs(): ResourceCreationTab[] { + // Order does matter + const tabs = [ + { + title: this.$t("form.steps.first").toString(), + active: true, + step: "configuration", + }, + { + title: this.$t("form.steps.second").toString(), + active: true, + step: "general", + }, + { + title: this.$t("form.steps.third").toString(), + active: true, + step: "metadata", + }, + { + title: this.$t("form.steps.fifth").toString(), + active: true, + step: "action", + }, + ]; + if (this.resource && this.resource.resourceTypeOption) { + const keys = Object.keys(this.resource.resourceTypeOption); + if (keys.length === 0 || keys.filter((e) => e !== "Id").length === 0) { + return tabs.splice(1); + } + } + return tabs; + }, + }, + + watch: { + resource() { + // Filling the form requires a watcher for the cases + // when resource may be unset (e.g. when entering from a direct link) + this.onResourceLoaded(); + }, + }, + + async created() { + this.isLoading = true; + + // Load Project Visibilities if not present + if (this.projectStore.visibilities === null) { + await this.projectStore.retrieveVisibilities(); + } + // Load Project Disciplines if not present + if (this.projectStore.disciplines === null) { + await this.projectStore.retrieveDisciplines(); + } + // Load Project Licenses if not present + if (this.projectStore.licenses === null) { + await this.projectStore.retrieveLicenses(); + } + this.onResourceLoaded(); + this.isLoading = false; + }, + + methods: { + onResourceLoaded() { + if (this.resource) { + // Fill the form. Note that regular assignment makes this.resource react on this.resourceForm changes! + Object.assign(this.resourceForm, this.resource); // Use this to only copy the properties + this.getApplicationProfile(); + } + }, + + toTab(tab: ResourceCreationTab) { + this.currentTab = this.tabs.indexOf(tab); + }, + + async getApplicationProfile() { + if (this.resource && this.resource.applicationProfile) { + this.isLoading = true; + const applicationProfile = + await this.resourceStore.getApplicationProfile( + this.resource.applicationProfile + ); + this.applicationProfileString = JSON.stringify(applicationProfile); + this.isLoading = false; + } else { + this.applicationProfileString = null; + } + }, + + makeToast( + text = "Message", + givenTitle = "Title", + variant: string | undefined = undefined + ) { + this.$root.$bvToast.toast(text, { + title: givenTitle, + autoHideDelay: 5000, + toaster: "b-toaster-bottom-right", + variant: variant, + noCloseButton: true, + }); + }, + + async clickSave() { + this.isWaitingForResponse = true; + const success = await this.resourceStore.updateResource( + this.resourceForm + ); + if (success) { + // On Success + // Refresh the project information in the store + await this.projectStore.refreshProjectInformation(this.project); + if (this.resource && this.resource.id) { + await this.resourceStore.retrieveResource(this.resource.id); + } + this.makeToast( + this.$t("toast.onSave.success.message").toString(), + this.$t("toast.onSave.success.title").toString() + ); + } else { + // On Failure + this.makeToast( + this.$t("toast.onSave.failure.message").toString(), + this.$t("toast.onSave.failure.title").toString(), + "danger" + ); + } + this.isWaitingForResponse = false; + }, + + async toggleArchive() { + if (this.resource) { + this.isWaitingForResponse = true; + const success = await this.resourceStore.setResourceArchiveStatus( + this.resource, + !this.resource.archived + ); + if (success) { + // On Success + // Refresh the project information in the store + await this.projectStore.refreshProjectInformation(this.project); + if (this.resource && this.resource.id) { + await this.resourceStore.retrieveResource(this.resource.id); + } + const action = !this.resource.archived ? "unarchive" : "archive"; + this.makeToast( + this.$t(`page.settings.actions.${action}.toast.body`).toString(), + this.$t(`page.settings.actions.${action}.toast.title`).toString() + ); + } else { + // On Failure + this.makeToast( + this.$t("toast.onSave.failure.message").toString(), + this.$t("toast.onSave.failure.title").toString(), + "danger" + ); + } + this.isWaitingForResponse = false; + } + }, + + async clickDelete() { + this.isWaitingForResponse = true; + const success = await this.resourceStore.deleteResource( + this.resourceForm + ); + if (success) { + // On Success + const parentProject = this.project; + // Refresh the project information in the store + await this.projectStore.refreshProjectInformation(parentProject); + this.makeToast( + this.$t("toast.onDelete.success.message").toString(), + this.$t("toast.onDelete.success.title").toString() + ); + navigateToProject(parentProject); + } else { + // On Failure + this.makeToast( + this.$t("toast.onDelete.failure.message").toString(), + this.$t("toast.onDelete.failure.title").toString(), + "danger" + ); + } + this.isWaitingForResponse = false; + }, }, }); </script> diff --git a/src/modules/resource/store.ts b/src/modules/resource/store.ts index 6ba4a006872739bfb4822e88c79b333d23bf01e2..4725aaeaac3d348e10e4c8b366e98ff95c3a8c6a 100644 --- a/src/modules/resource/store.ts +++ b/src/modules/resource/store.ts @@ -19,7 +19,7 @@ import type { Route } from "vue-router/types/router"; import { useLocalStorage } from "@vueuse/core"; import type { BilingualLabels } from "@coscine/api-client/dist/types/Coscine.Api.Metadata"; import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; - +import { updatedDiff } from "deep-object-diff"; /* Store variable name is "this.<id>Store" id: "resource" --> this.resourceStore @@ -173,6 +173,60 @@ export const useResourceStore = defineStore({ } }, + async updateResource(resource: ResourceObject): Promise<boolean> { + if (resource.id) { + const apiResponse = await ResourceApi.resourceUpdate( + resource.id, + resource + ); + if (apiResponse.status === StatusCodes.OK) { + return true; + } else { + // Handle other Status Codes + return false; + } + } else { + console.error("Selected resource's ID is undefined."); + return false; + } + }, + + async setResourceArchiveStatus( + resource: ResourceObject, + status: boolean + ): Promise<boolean> { + if (resource.id) { + const apiResponse = await ResourceApi.resourceSetResourceReadonly( + resource.id, + status + ); + if (apiResponse.status === StatusCodes.OK) { + return true; + } else { + // Handle other Status Codes + return false; + } + } else { + console.error("Selected resource's ID is undefined."); + return false; + } + }, + + async deleteResource(resource: ResourceObject): Promise<boolean> { + if (resource.id) { + const apiResponse = await ResourceApi.resourceDelete(resource.id); + if (apiResponse.status === StatusCodes.OK) { + return true; + } else { + // Handle other Status Codes + return false; + } + } else { + console.error("Selected resource's ID is undefined."); + return false; + } + }, + async handleUnsetResource(resource: ResourceObject | null, route: Route) { const routeParams = route.params; if ( @@ -195,6 +249,10 @@ export const useResourceStore = defineStore({ addResourceAsVisited(resource: ResourceObject | null) { if (resource && resource.id) { + const updatedKeys = updatedDiff( + this.visitedResources[resource.id], + resource + ); if (!this.visitedResources[resource.id]) { // Important! Keep object assignment reactive() const visitedResource: VisitedResourceObject = reactive({ @@ -207,6 +265,8 @@ export const useResourceStore = defineStore({ ), }); this.visitedResources[resource.id] = visitedResource; + } else if (updatedKeys && Object.keys(updatedKeys).length > 0) { + Object.assign(this.visitedResources[resource.id], updatedKeys); } } }, diff --git a/src/modules/search/SearchModule.vue b/src/modules/search/SearchModule.vue index 2939eecb0b1f4328c01a3db84592c6dad37090f3..b25069d5188a30360cc3abbdcfe349928ea7c06c 100644 --- a/src/modules/search/SearchModule.vue +++ b/src/modules/search/SearchModule.vue @@ -8,9 +8,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useSearchStore } from "./store"; +import useSearchStore from "./store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/search/pages/Search.vue b/src/modules/search/pages/Search.vue index c12673094ab4bc7d7997c3993776aab47bebb418..9337e54aee73bf4eb0860c050faa859c41472283 100644 --- a/src/modules/search/pages/Search.vue +++ b/src/modules/search/pages/Search.vue @@ -1,6 +1,6 @@ <template> <div class="search"> - <CoscineHeadline :headline="$parent.$t('page.search.title')" /> + <CoscineHeadline :headline="$t('page.search.title')" /> <b-row id="mainRow"> <!-- Sidebar --> @@ -12,14 +12,14 @@ <b-col id="searchField" align-self="start" class="pl-0"> <b-form-input v-model="searchText" - :placeholder="$parent.$t('page.search.search')" + :placeholder="$t('page.search.search')" ></b-form-input> </b-col> <b-col sm="2" id="selectProjCol" align-self="center" class="pl-0"> <b-form-select v-model="selectProjValue"> <template #first> <b-form-select-option :value="null" disabled - >{{ $parent.$t("page.search.allProjects") }} + >{{ $t("page.search.allProjects") }} </b-form-select-option> </template> </b-form-select> @@ -28,7 +28,7 @@ <b-form-select v-model="selectResValue"> <template #first> <b-form-select-option :value="null" disabled - >{{ $parent.$t("page.search.allResources") }} + >{{ $t("page.search.allResources") }} </b-form-select-option> </template> </b-form-select> @@ -36,14 +36,14 @@ <b-col sm="0" align-self="center" class="text-right p-0"> <b-button-group> <b-button id="searchButton" variant="primary"> - {{ $parent.$t("page.search.search") }} + {{ $t("page.search.search") }} </b-button> <b-dropdown id="searchDropdown" right size="sm" variant="primary"> <b-dropdown-item>{{ - $parent.$t("page.search.buttonSearch.Item1") + $t("page.search.buttonSearch.Item1") }}</b-dropdown-item> <b-dropdown-item>{{ - $parent.$t("page.search.buttonSearch.Item2") + $t("page.search.buttonSearch.Item2") }}</b-dropdown-item> </b-dropdown> </b-button-group> @@ -121,7 +121,7 @@ <template #empty> <h6 class="text-center"> - {{ $parent.$t("page.search.emptySearch") }} + {{ $t("page.search.emptySearch") }} </h6> </template> @@ -135,7 +135,7 @@ " class="p-2 text-center text-muted border-top" > - {{ $parent.$t("page.search.endSearchResults") }} + {{ $t("page.search.endSearchResults") }} </div> </template> </b-table> @@ -175,9 +175,9 @@ import Result from "./components/Result.vue"; import Sidebar from "./components/Sidebar.vue"; // import the store for current module -import { useSearchStore } from "../store"; +import useSearchStore from "../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { DummyDataType } from "../types"; diff --git a/src/modules/search/pages/components/Result.vue b/src/modules/search/pages/components/Result.vue index e39d57afc956bd2a59b2a16ea77d79b4d016ed3a..5b72bc7ab3e415277e03d1eef508b6bf259b25c3 100644 --- a/src/modules/search/pages/components/Result.vue +++ b/src/modules/search/pages/components/Result.vue @@ -36,9 +36,9 @@ import { defineComponent, PropType } from "vue-demi"; import { DummyDataType } from "../../types"; // import the store for current module -import { useSearchStore } from "../../store"; +import useSearchStore from "../../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/search/pages/components/Sidebar.vue b/src/modules/search/pages/components/Sidebar.vue index eecf99799713fd4490d01f6609c16ac66e4b821f..8f8bb0d36c709d8bd672c3059f316663f10bc191 100644 --- a/src/modules/search/pages/components/Sidebar.vue +++ b/src/modules/search/pages/components/Sidebar.vue @@ -14,9 +14,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useSearchStore } from "../../store"; +import useSearchStore from "../../store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/user/UserModule.vue b/src/modules/user/UserModule.vue index 50333402ffea621d8527d9953c9d00db24e2f524..d8ae5577583f500ea49272bc22496c17934f8764 100644 --- a/src/modules/user/UserModule.vue +++ b/src/modules/user/UserModule.vue @@ -8,9 +8,9 @@ import { defineComponent } from "vue-demi"; // import the store for current module -import { useUserStore } from "./store"; +import useUserStore from "./store"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; export default defineComponent({ setup() { diff --git a/src/modules/user/pages/UserProfile.vue b/src/modules/user/pages/UserProfile.vue index 846dcbeda8b3267cc356cd09ba6071edebac741c..399a70cd1c8bd368c6b4f487f3a8f728771fe089 100644 --- a/src/modules/user/pages/UserProfile.vue +++ b/src/modules/user/pages/UserProfile.vue @@ -5,17 +5,13 @@ <b-form @submit.stop.prevent="onSubmit"> <!-- Personal Information --> <CoscineHeadline - :headline=" - $parent.$t('page.userprofile.form.personalInformation.header') - " + :headline="$t('page.userprofile.form.personalInformation.header')" /> <!-- Title --> <coscine-form-group labelFor="title" :label=" - $parent.$t( - 'page.userprofile.form.personalInformation.labels.titleLabel' - ) + $t('page.userprofile.form.personalInformation.labels.titleLabel') " :isLoading="isLoading" type="input" @@ -28,23 +24,23 @@ label="displayName" track-by="displayName" :placeholder=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.multiselect.placeholderTitle' ) " :selectLabel=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.multiselect.selectEnter' ) " > <span slot="noResult">{{ - $parent.$t( + $t( "page.userprofile.form.personalInformation.multiselect.noResults" ) }}</span> <span slot="noOptions">{{ - $parent.$t( + $t( "page.userprofile.form.personalInformation.multiselect.noOptions" ) }}</span> @@ -55,7 +51,7 @@ :mandatory="true" labelFor="givenname" :label=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.labels.givenNameLabel' ) " @@ -67,7 +63,7 @@ v-model="$v.form.givenname.$model" :state="$v.form.givenname.$dirty ? !$v.form.givenname.$error : null" :placeholder=" - $parent.$t('page.userprofile.form.personalInformation.givenName') + $t('page.userprofile.form.personalInformation.givenName') " /> </coscine-form-group> @@ -76,9 +72,7 @@ :mandatory="true" labelFor="surname" :label=" - $parent.$t( - 'page.userprofile.form.personalInformation.labels.surnameLabel' - ) + $t('page.userprofile.form.personalInformation.labels.surnameLabel') " :isLoading="isLoading" type="input" @@ -88,7 +82,7 @@ v-model="$v.form.surname.$model" :state="$v.form.surname.$dirty ? !$v.form.surname.$error : null" :placeholder=" - $parent.$t('page.userprofile.form.personalInformation.surname') + $t('page.userprofile.form.personalInformation.surname') " /> </coscine-form-group> @@ -97,9 +91,7 @@ :mandatory="true" labelFor="Email" :label=" - $parent.$t( - 'page.userprofile.form.personalInformation.labels.emailLabel' - ) + $t('page.userprofile.form.personalInformation.labels.emailLabel') " :isLoading="isLoading" type="input" @@ -110,9 +102,7 @@ :state=" $v.form.emailAddress.$dirty ? !$v.form.emailAddress.$error : null " - :placeholder=" - $parent.$t('page.userprofile.form.personalInformation.email') - " + :placeholder="$t('page.userprofile.form.personalInformation.email')" /> <div id="emailHint">{{ emailHint }}</div> </coscine-form-group> @@ -121,7 +111,7 @@ :mandatory="true" labelFor="organization" :label=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.labels.organizationLabel' ) " @@ -138,7 +128,7 @@ label="displayName" track-by="displayName" :placeholder=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.multiselect.placeholderText' ) " @@ -152,7 +142,7 @@ </template> <template slot="noOptions"> {{ - $parent.$t( + $t( "page.userprofile.form.personalInformation.multiselect.noOptionsOrganization" ) }} @@ -167,9 +157,7 @@ label="displayName" track-by="displayName" :placeholder=" - $parent.$t( - 'page.userprofile.form.personalInformation.organization' - ) + $t('page.userprofile.form.personalInformation.organization') " :disabled="true" > @@ -180,7 +168,7 @@ :mandatory="true" labelFor="institute" :label=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.labels.instituteLabel' ) " @@ -193,7 +181,7 @@ v-model="$v.form.institute.$model" type="text" :placeholder=" - $parent.$t('page.userprofile.form.personalInformation.institute') + $t('page.userprofile.form.personalInformation.institute') " > </b-form-input> @@ -206,7 +194,7 @@ label="displayName" track-by="displayName" :placeholder=" - $parent.$t('page.userprofile.form.personalInformation.institute') + $t('page.userprofile.form.personalInformation.institute') " :disabled="true" > @@ -217,7 +205,7 @@ :mandatory="true" labelFor="Discipline" :label=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.labels.disciplineLabel' ) " @@ -232,7 +220,7 @@ :label="disciplineLabel" :track-by="disciplineLabel" :placeholder=" - $parent.$t( + $t( 'page.userprofile.form.personalInformation.multiselect.placeholderDiscipline' ) " @@ -249,22 +237,20 @@ <!-- Access Token --> <div class="h-divider" /> <CoscineHeadline - :headline="$parent.$t('page.userprofile.form.accessToken.header')" + :headline="$t('page.userprofile.form.accessToken.header')" /> <AccessToken /> <!-- User Preferences --> <div class="h-divider"></div> <CoscineHeadline - :headline="$parent.$t('page.userprofile.form.userPreferences.header')" + :headline="$t('page.userprofile.form.userPreferences.header')" /> <coscine-form-group :mandatory="true" labelFor="language" :label=" - $parent.$t( - 'page.userprofile.form.userPreferences.labels.languageLabel' - ) + $t('page.userprofile.form.userPreferences.labels.languageLabel') " :isLoading="isLoading" type="button" @@ -289,16 +275,12 @@ <!-- Connected Accounts --> <div class="h-divider" /> <CoscineHeadline - :headline=" - $parent.$t('page.userprofile.form.connectedAccounts.header') - " + :headline="$t('page.userprofile.form.connectedAccounts.header')" /> <coscine-form-group :mandatory="true" :label=" - $parent.$t( - 'page.userprofile.form.connectedAccounts.labels.orcidLabel' - ) + $t('page.userprofile.form.connectedAccounts.labels.orcidLabel') " :isLoading="isLoading" type="button" @@ -310,18 +292,14 @@ @click.prevent="clickConnect('orcid')" :disabled="orcidConnected" >{{ - orcidConnected - ? $parent.$t("buttons.connected") - : $parent.$t("buttons.connect") + orcidConnected ? $t("buttons.connected") : $t("buttons.connect") }}</b-button > </coscine-form-group> <coscine-form-group :mandatory="true" :label=" - $parent.$t( - 'page.userprofile.form.connectedAccounts.labels.shibbolethLabel' - ) + $t('page.userprofile.form.connectedAccounts.labels.shibbolethLabel') " :isLoading="isLoading" type="button" @@ -334,8 +312,8 @@ :disabled="shibbolethConnected" >{{ shibbolethConnected - ? $parent.$t("buttons.connected") - : $parent.$t("buttons.connect") + ? $t("buttons.connected") + : $t("buttons.connect") }}</b-button > </coscine-form-group> @@ -381,7 +359,7 @@ shift-h="-4" ></b-icon> </b-iconstack> - {{ $parent.$t("buttons.save") }}</b-button + {{ $t("buttons.save") }}</b-button > </coscine-form-group> </b-form> @@ -403,9 +381,9 @@ import CoscineHeadline from "@/components/CoscineHeadline.vue"; import "@/plugins/deprecated/vue-multiselect"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; // import the store for current module -import { useUserStore } from "../store"; +import useUserStore from "../store"; import type { ContactChangeObject, DisciplineObject, @@ -599,13 +577,13 @@ export default defineComponent({ await this.userStore.updateUser(this.form); this.$v.form.$reset(); this.makeToast( - this.$parent.$t("toast.onSave.success.message").toString(), - this.$parent.$t("toast.onSave.success.title").toString() + this.$t("toast.onSave.success.message").toString(), + this.$t("toast.onSave.success.title").toString() ); } catch { this.makeToast( - this.$parent.$t("toast.onSave.failure.message").toString(), - this.$parent.$t("toast.onSave.failure.title").toString() + this.$t("toast.onSave.failure.message").toString(), + this.$t("toast.onSave.failure.title").toString() ); } finally { this.savingProfile = false; diff --git a/src/modules/user/pages/components/AccessToken.vue b/src/modules/user/pages/components/AccessToken.vue index e478024cdf04ce42e047ae20778a680e1ee78f02..d05e76b06d70e0297dc90cda3d456bace04ce301 100644 --- a/src/modules/user/pages/components/AccessToken.vue +++ b/src/modules/user/pages/components/AccessToken.vue @@ -3,16 +3,8 @@ <!-- Modal Create Access Token --> <CoscineModal v-model="isCreateModalVisible" - :title=" - $parent.$parent.$t( - 'page.userprofile.form.accessToken.modal.createToken.title' - ) - " - :body=" - $parent.$parent.$t( - 'page.userprofile.form.accessToken.modal.createToken.body' - ) - " + :title="$t('page.userprofile.form.accessToken.modal.createToken.title')" + :body="$t('page.userprofile.form.accessToken.modal.createToken.body')" > <div class="create-modal-content"> <b-button-group id="tokenButtonGroup" style="width: 100%"> @@ -25,7 +17,7 @@ ><b-icon-clipboard /></b-button> <b-tooltip ref="tooltip" target="copyButton" triggers="focus">{{ - $parent.$parent.$t( + $t( "page.userprofile.form.accessToken.modal.createToken.copyToClipboard" ) }}</b-tooltip> @@ -34,7 +26,7 @@ <template #buttons> <div align="right"> <b-button name="close" @click="isCreateModalVisible = false">{{ - $parent.$parent.$t("buttons.close") + $t("buttons.close") }}</b-button> </div> </template> @@ -43,30 +35,22 @@ <!-- Modal Revoke Access Token --> <CoscineModal v-model="isRevokeModalVisible" - :title=" - $parent.$parent.$t( - 'page.userprofile.form.accessToken.modal.revokeToken.title' - ) - " - :body=" - $parent.$parent.$t( - 'page.userprofile.form.accessToken.modal.revokeToken.body' - ) - " + :title="$t('page.userprofile.form.accessToken.modal.revokeToken.title')" + :body="$t('page.userprofile.form.accessToken.modal.revokeToken.body')" > <br /> <div class="revoke-modal-content">{{ selectedTokenName }}</div> <br /> <template #buttons> <b-button name="close" @click="isRevokeModalVisible = false">{{ - $parent.$parent.$t("buttons.cancel") + $t("buttons.cancel") }}</b-button> <b-button name="deleteToken" @click="confirmRevoke" variant="danger" style="float: right" - >{{ $parent.$parent.$t("buttons.revoke") }}</b-button + >{{ $t("buttons.revoke") }}</b-button > </template> </CoscineModal> @@ -79,7 +63,7 @@ > <!-- Token Body Text --> <b-form-text id="TokenBodytext" style="text-align: left"> - {{ $parent.$parent.$t("page.userprofile.form.accessToken.bodyText") }} + {{ $t("page.userprofile.form.accessToken.bodyText") }} </b-form-text> </b-form-group> <!-- Token Name --> @@ -88,18 +72,12 @@ label-for="TokenName" label-cols-sm="3" label-align-sm="right" - :label=" - $parent.$parent.$t( - 'page.userprofile.form.accessToken.labels.tokenNameLabel' - ) - " + :label="$t('page.userprofile.form.accessToken.labels.tokenNameLabel')" > <b-form-input id="TokenName" v-model="$v.token.TokenName.$model" - :placeholder=" - $parent.$parent.$t('page.userprofile.form.accessToken.tokenName') - " + :placeholder="$t('page.userprofile.form.accessToken.tokenName')" /> </b-form-group> <!-- Expires on --> @@ -107,20 +85,14 @@ label-for="ExpiredDate" label-cols-sm="3" label-align-sm="right" - :label=" - $parent.$parent.$t( - 'page.userprofile.form.accessToken.labels.tokenExpireLabel' - ) - " + :label="$t('page.userprofile.form.accessToken.labels.tokenExpireLabel')" > <b-form-datepicker v-model="$v.token.TokenExpirationDate.$model" :min="tokenValidityBounds.minDate" :max="tokenValidityBounds.maxDate" :locale="$i18n.locale" - :placeholder=" - $parent.$parent.$t('page.userprofile.form.accessToken.tokenExpire') - " + :placeholder="$t('page.userprofile.form.accessToken.tokenExpire')" :date-format-options="dateFormatOptions" ></b-form-datepicker> </b-form-group> @@ -134,7 +106,7 @@ style="margin-right: 0px" @click.prevent="createToken" :disabled="$v.token.$invalid" - >{{ $parent.$parent.$t("buttons.tokenCreate") }}</b-button + >{{ $t("buttons.tokenCreate") }}</b-button > </b-form-group> @@ -162,11 +134,7 @@ :items="tokens" :locale="$i18n.locale" :show-empty="true" - :empty-text=" - $parent.$parent.$t( - 'page.userprofile.form.accessToken.table.emptyText' - ) - " + :empty-text="$t('page.userprofile.form.accessToken.table.emptyText')" class="mb-0 text-left" fixed small @@ -188,7 +156,7 @@ v-on:click="revokeToken(selectedToken.item)" size="sm" variant="danger" - >{{ $parent.$parent.$t("buttons.revoke") }}</b-button + >{{ $t("buttons.revoke") }}</b-button > </template> </b-table> @@ -207,13 +175,13 @@ import { BIconClipboard } from "bootstrap-vue"; import CoscineModal from "@/components/CoscineModal.vue"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; // import the store for current module -import { useUserStore } from "../../store"; +import useUserStore from "../../store"; import moment from "moment"; -import { +import type { AddApiTokenParameter, ApiTokenObject, } from "@coscine/api-client/dist/types/Coscine.Api.Token"; @@ -253,27 +221,23 @@ export default defineComponent({ isRevokeModalVisible: false, headers: [ { - label: this.$parent.$parent.$t( - "page.userprofile.form.accessToken.tokenName" - ), + label: this.$t("page.userprofile.form.accessToken.tokenName"), key: "name", }, { - label: this.$parent.$parent.$t( + label: this.$t( "page.userprofile.form.accessToken.table.tokenCreated" ), key: "created", }, { - label: this.$parent.$parent.$t( + label: this.$t( "page.userprofile.form.accessToken.table.tokenExpires" ), key: "expires", }, { - label: this.$parent.$parent.$t( - "page.userprofile.form.accessToken.table.tokenAction" - ), + label: this.$t("page.userprofile.form.accessToken.table.tokenAction"), key: "actions", }, ], diff --git a/src/modules/user/store.ts b/src/modules/user/store.ts index 1342c5235ad76273f2311208e942b59ff08ed8ec..8762e4e3071a4673e226bdeedfb77d7f58ee6acd 100644 --- a/src/modules/user/store.ts +++ b/src/modules/user/store.ts @@ -13,7 +13,7 @@ import { defineStore } from "pinia"; import type { UserState } from "./types"; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; /* Store variable name is "this.<id>Store" diff --git a/src/plugins/loadingCounter.ts b/src/plugins/loadingCounter.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0927c4f911ff3f6e898728a460c985e5034ed8f --- /dev/null +++ b/src/plugins/loadingCounter.ts @@ -0,0 +1,27 @@ +import useMainStore from "@/store"; + +import type { StoreDefinition } from "pinia"; + +export function loadingCounterEventHandler(store: StoreDefinition) { + const mainStore = useMainStore(); + + store().$onAction( + ({ + after, // Hook after the action returns or resolves + onError, // Hook if the action throws or rejects + }) => { + // Increment the loading counter + mainStore.coscine.loading.counter++; + + // Decrease the loading counter + after(() => { + mainStore.coscine.loading.counter--; + }); + + // Decrease the loading counter + onError(() => { + mainStore.coscine.loading.counter--; + }); + } + ); +} diff --git a/src/router/index.ts b/src/router/index.ts index 431e9ddcbc0fcd543285acf20e8846f197208f42..c94909883cddb38dbc65ce3bcfaecd73d6b358ba 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -52,7 +52,7 @@ VueRouter.prototype.push = async function (location: RawLocation) { }; // import the main store -import { useMainStore } from "@/store/index"; +import useMainStore from "@/store/index"; import type { ProjectObject } from "@coscine/api-client/dist/types/Coscine.Api.Project"; import i18n, { def } from "@/plugins/vue-i18n"; import type VueI18n from "vue-i18n"; @@ -74,10 +74,6 @@ router.beforeEach((to, _, next) => { // Handle access token from URL mainStore.setAccessTokenFromRoute(router, to); mainStore.getMaintenance(); - // Set the page not loading - mainStore.$patch((state) => { - state.coscine.loading.counter = 0; - }); if (to.meta?.requiresAuth && !mainStore.loggedIn) { // Route requires auth, check if logged in // if not, redirect to login page. diff --git a/src/store/index.ts b/src/store/index.ts index 4c92f2adf1d5b46f992372534bd86be2ae60819f..f9ff652165f12941e6b70d08b64b6c784d735971 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -8,7 +8,7 @@ import { removeQueryParameterFromUrl } from "@/router"; import { NoticeApi } from "@coscine/api-client"; import { StatusCodes } from "http-status-codes"; -import { useLoginStore } from "@/modules/login/store"; +import useLoginStore from "@/modules/login/store"; /* Store variable name is "this.<id>Store" @@ -31,7 +31,7 @@ export const useMainStore = defineStore({ id: useLocalStorage("coscine.clientcorrelation.id", uuidv4()), }, loading: { - counter: useLocalStorage("coscine.loading.counter", 0), + counter: 0, }, locale: useLocalStorage("coscine.locale", "en"), banner: { @@ -60,6 +60,9 @@ export const useMainStore = defineStore({ :label = "this.mainStore.<getter_name>; */ getters: { + isLoading(): boolean { + return this.coscine.loading.counter > 0; + }, loggedIn() { const loginStore = useLoginStore(); if (this.coscine.authorization.bearer && !loginStore.expiredSession) { diff --git a/src/store/types.d.ts b/src/store/types.d.ts index f6489cbf3ee4d3bb60c0599259ef1ff562cfe4cc..67edb2d70657d329d27911e38e4c080920eeddfb 100644 --- a/src/store/types.d.ts +++ b/src/store/types.d.ts @@ -15,7 +15,7 @@ export interface MainState { id: RemovableRef<string>; }; loading: { - counter: RemovableRef<number>; + counter: number; }; locale: RemovableRef<string>; banner: { diff --git a/yarn.lock-workspace b/yarn.lock-workspace index d37b3039175d67401855aabaa9572a723f4da438..22ada528f23ceb18bed9d75901e1b00aa918845c 100644 --- a/yarn.lock-workspace +++ b/yarn.lock-workspace @@ -1196,7 +1196,7 @@ __metadata: "@vue/cli-plugin-eslint": ^4.5.15 "@vue/eslint-config-prettier": ^6.0.0 "@vue/eslint-config-typescript": ^8.0.0 - bootstrap-vue: ^2.20.1 + bootstrap-vue: ^2.22.0 conventional-changelog-eslint: 3.0.9 core-js: ^3.8.2 eslint: ^8.11.0 @@ -4595,20 +4595,20 @@ __metadata: languageName: node linkType: hard -"bootstrap-vue@npm:^2.20.1, bootstrap-vue@npm:^2.21.2": - version: 2.21.2 - resolution: "bootstrap-vue@npm:2.21.2" +"bootstrap-vue@npm:^2.22.0": + version: 2.22.0 + resolution: "bootstrap-vue@npm:2.22.0" dependencies: "@nuxt/opencollective": ^0.3.2 - bootstrap: ">=4.5.3 <5.0.0" + bootstrap: ^4.6.1 popper.js: ^1.16.1 portal-vue: ^2.1.7 vue-functional-data-merge: ^3.1.0 - checksum: cf49df1a38917d9fcfca7f015f2660880c11cae8b36da612cb485c995af3823ec9da9620b65f9f5a8dfaa1c88d8bf03dfeb4a7501a6c2c52abc92c6c3af97319 + checksum: 801f148de895b4e390d2d79629f0da5c2fd8fd6d002230f4572ff0354e64f70236363bb69496451009bcd582d9b4eea2c31055649c7c99b8971bce5b418a4f01 languageName: node linkType: hard -"bootstrap@npm:>=4.5.3 <5.0.0, bootstrap@npm:^4.6.1": +"bootstrap@npm:^4.6.1": version: 4.6.1 resolution: "bootstrap@npm:4.6.1" peerDependencies: @@ -6496,6 +6496,13 @@ __metadata: languageName: node linkType: hard +"deep-object-diff@npm:^1.1.7": + version: 1.1.7 + resolution: "deep-object-diff@npm:1.1.7" + checksum: 543fb1ae87b138ad260691e6949e72bf7dc144825084b7ad1886bb725d2ace1c19ed1ef1280f1116243e86bf2c6b942f45c670958b1468f644613f28c5dc97ea + languageName: node + linkType: hard + "deepmerge-ts@npm:^2.0.1": version: 2.0.1 resolution: "deepmerge-ts@npm:2.0.1" @@ -17214,9 +17221,10 @@ __metadata: axios: ^0.26.1 bootstrap: ^4.6.1 bootstrap-icons: ^1.8.1 - bootstrap-vue: ^2.21.2 + bootstrap-vue: ^2.22.0 conventional-changelog-eslint: ^3.0.9 core-js: ^3.21.1 + deep-object-diff: ^1.1.7 eslint: ^8.11.0 eslint-import-resolver-node: ^0.3.6 eslint-plugin-eslint-comments: ^3.2.0